aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/Twisted/py2/twisted/conch/client
diff options
context:
space:
mode:
authorshmel1k <shmel1k@ydb.tech>2023-11-26 18:16:14 +0300
committershmel1k <shmel1k@ydb.tech>2023-11-26 18:43:30 +0300
commitb8cf9e88f4c5c64d9406af533d8948deb050d695 (patch)
tree218eb61fb3c3b96ec08b4d8cdfef383104a87d63 /contrib/python/Twisted/py2/twisted/conch/client
parent523f645a83a0ec97a0332dbc3863bb354c92a328 (diff)
downloadydb-b8cf9e88f4c5c64d9406af533d8948deb050d695.tar.gz
add kikimr_configure
Diffstat (limited to 'contrib/python/Twisted/py2/twisted/conch/client')
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/client/__init__.py9
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/client/agent.py73
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/client/connect.py21
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/client/default.py349
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/client/direct.py109
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/client/knownhosts.py630
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/client/options.py103
7 files changed, 1294 insertions, 0 deletions
diff --git a/contrib/python/Twisted/py2/twisted/conch/client/__init__.py b/contrib/python/Twisted/py2/twisted/conch/client/__init__.py
new file mode 100644
index 0000000000..f55d474db4
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/client/__init__.py
@@ -0,0 +1,9 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+"""
+Client support code for Conch.
+
+Maintainer: Paul Swartz
+"""
diff --git a/contrib/python/Twisted/py2/twisted/conch/client/agent.py b/contrib/python/Twisted/py2/twisted/conch/client/agent.py
new file mode 100644
index 0000000000..fdf08356f1
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/client/agent.py
@@ -0,0 +1,73 @@
+# -*- test-case-name: twisted.conch.test.test_default -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Accesses the key agent for user authentication.
+
+Maintainer: Paul Swartz
+"""
+
+import os
+
+from twisted.conch.ssh import agent, channel, keys
+from twisted.internet import protocol, reactor
+from twisted.python import log
+
+
+
+class SSHAgentClient(agent.SSHAgentClient):
+
+ def __init__(self):
+ agent.SSHAgentClient.__init__(self)
+ self.blobs = []
+
+
+ def getPublicKeys(self):
+ return self.requestIdentities().addCallback(self._cbPublicKeys)
+
+
+ def _cbPublicKeys(self, blobcomm):
+ log.msg('got %i public keys' % len(blobcomm))
+ self.blobs = [x[0] for x in blobcomm]
+
+
+ def getPublicKey(self):
+ """
+ Return a L{Key} from the first blob in C{self.blobs}, if any, or
+ return L{None}.
+ """
+ if self.blobs:
+ return keys.Key.fromString(self.blobs.pop(0))
+ return None
+
+
+
+class SSHAgentForwardingChannel(channel.SSHChannel):
+
+ def channelOpen(self, specificData):
+ cc = protocol.ClientCreator(reactor, SSHAgentForwardingLocal)
+ d = cc.connectUNIX(os.environ['SSH_AUTH_SOCK'])
+ d.addCallback(self._cbGotLocal)
+ d.addErrback(lambda x:self.loseConnection())
+ self.buf = ''
+
+
+ def _cbGotLocal(self, local):
+ self.local = local
+ self.dataReceived = self.local.transport.write
+ self.local.dataReceived = self.write
+
+
+ def dataReceived(self, data):
+ self.buf += data
+
+
+ def closed(self):
+ if self.local:
+ self.local.loseConnection()
+ self.local = None
+
+
+class SSHAgentForwardingLocal(protocol.Protocol):
+ pass
diff --git a/contrib/python/Twisted/py2/twisted/conch/client/connect.py b/contrib/python/Twisted/py2/twisted/conch/client/connect.py
new file mode 100644
index 0000000000..ac47187e49
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/client/connect.py
@@ -0,0 +1,21 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+from twisted.conch.client import direct
+
+connectTypes = {"direct" : direct.connect}
+
+def connect(host, port, options, verifyHostKey, userAuthObject):
+ useConnects = ['direct']
+ return _ebConnect(None, useConnects, host, port, options, verifyHostKey,
+ userAuthObject)
+
+def _ebConnect(f, useConnects, host, port, options, vhk, uao):
+ if not useConnects:
+ return f
+ connectType = useConnects.pop(0)
+ f = connectTypes[connectType]
+ d = f(host, port, options, vhk, uao)
+ d.addErrback(_ebConnect, useConnects, host, port, options, vhk, uao)
+ return d
diff --git a/contrib/python/Twisted/py2/twisted/conch/client/default.py b/contrib/python/Twisted/py2/twisted/conch/client/default.py
new file mode 100644
index 0000000000..ff2d635314
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/client/default.py
@@ -0,0 +1,349 @@
+# -*- test-case-name: twisted.conch.test.test_knownhosts,twisted.conch.test.test_default -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Various classes and functions for implementing user-interaction in the
+command-line conch client.
+
+You probably shouldn't use anything in this module directly, since it assumes
+you are sitting at an interactive terminal. For example, to programmatically
+interact with a known_hosts database, use L{twisted.conch.client.knownhosts}.
+"""
+
+from __future__ import print_function
+
+from twisted.python import log
+from twisted.python.compat import (
+ nativeString, raw_input, _PY3, _b64decodebytes as decodebytes)
+from twisted.python.filepath import FilePath
+
+from twisted.conch.error import ConchError
+from twisted.conch.ssh import common, keys, userauth
+from twisted.internet import defer, protocol, reactor
+
+from twisted.conch.client.knownhosts import KnownHostsFile, ConsoleUI
+
+from twisted.conch.client import agent
+
+import os, sys, getpass, contextlib
+
+if _PY3:
+ import io
+
+# The default location of the known hosts file (probably should be parsed out
+# of an ssh config file someday).
+_KNOWN_HOSTS = "~/.ssh/known_hosts"
+
+
+# This name is bound so that the unit tests can use 'patch' to override it.
+_open = open
+
+def verifyHostKey(transport, host, pubKey, fingerprint):
+ """
+ Verify a host's key.
+
+ This function is a gross vestige of some bad factoring in the client
+ internals. The actual implementation, and a better signature of this logic
+ is in L{KnownHostsFile.verifyHostKey}. This function is not deprecated yet
+ because the callers have not yet been rehabilitated, but they should
+ eventually be changed to call that method instead.
+
+ However, this function does perform two functions not implemented by
+ L{KnownHostsFile.verifyHostKey}. It determines the path to the user's
+ known_hosts file based on the options (which should really be the options
+ object's job), and it provides an opener to L{ConsoleUI} which opens
+ '/dev/tty' so that the user will be prompted on the tty of the process even
+ if the input and output of the process has been redirected. This latter
+ part is, somewhat obviously, not portable, but I don't know of a portable
+ equivalent that could be used.
+
+ @param host: Due to a bug in L{SSHClientTransport.verifyHostKey}, this is
+ always the dotted-quad IP address of the host being connected to.
+ @type host: L{str}
+
+ @param transport: the client transport which is attempting to connect to
+ the given host.
+ @type transport: L{SSHClientTransport}
+
+ @param fingerprint: the fingerprint of the given public key, in
+ xx:xx:xx:... format. This is ignored in favor of getting the fingerprint
+ from the key itself.
+ @type fingerprint: L{str}
+
+ @param pubKey: The public key of the server being connected to.
+ @type pubKey: L{str}
+
+ @return: a L{Deferred} which fires with C{1} if the key was successfully
+ verified, or fails if the key could not be successfully verified. Failure
+ types may include L{HostKeyChanged}, L{UserRejectedKey}, L{IOError} or
+ L{KeyboardInterrupt}.
+ """
+ actualHost = transport.factory.options['host']
+ actualKey = keys.Key.fromString(pubKey)
+ kh = KnownHostsFile.fromPath(FilePath(
+ transport.factory.options['known-hosts']
+ or os.path.expanduser(_KNOWN_HOSTS)
+ ))
+ ui = ConsoleUI(lambda : _open("/dev/tty", "r+b", buffering=0))
+ return kh.verifyHostKey(ui, actualHost, host, actualKey)
+
+
+
+def isInKnownHosts(host, pubKey, options):
+ """
+ Checks to see if host is in the known_hosts file for the user.
+
+ @return: 0 if it isn't, 1 if it is and is the same, 2 if it's changed.
+ @rtype: L{int}
+ """
+ keyType = common.getNS(pubKey)[0]
+ retVal = 0
+
+ if not options['known-hosts'] and not os.path.exists(os.path.expanduser('~/.ssh/')):
+ print('Creating ~/.ssh directory...')
+ os.mkdir(os.path.expanduser('~/.ssh'))
+ kh_file = options['known-hosts'] or _KNOWN_HOSTS
+ try:
+ known_hosts = open(os.path.expanduser(kh_file), 'rb')
+ except IOError:
+ return 0
+ with known_hosts:
+ for line in known_hosts.readlines():
+ split = line.split()
+ if len(split) < 3:
+ continue
+ hosts, hostKeyType, encodedKey = split[:3]
+ if host not in hosts.split(b','): # incorrect host
+ continue
+ if hostKeyType != keyType: # incorrect type of key
+ continue
+ try:
+ decodedKey = decodebytes(encodedKey)
+ except:
+ continue
+ if decodedKey == pubKey:
+ return 1
+ else:
+ retVal = 2
+ return retVal
+
+
+
+def getHostKeyAlgorithms(host, options):
+ """
+ Look in known_hosts for a key corresponding to C{host}.
+ This can be used to change the order of supported key types
+ in the KEXINIT packet.
+
+ @type host: L{str}
+ @param host: the host to check in known_hosts
+ @type options: L{twisted.conch.client.options.ConchOptions}
+ @param options: options passed to client
+ @return: L{list} of L{str} representing key types or L{None}.
+ """
+ knownHosts = KnownHostsFile.fromPath(FilePath(
+ options['known-hosts']
+ or os.path.expanduser(_KNOWN_HOSTS)
+ ))
+ keyTypes = []
+ for entry in knownHosts.iterentries():
+ if entry.matchesHost(host):
+ if entry.keyType not in keyTypes:
+ keyTypes.append(entry.keyType)
+ return keyTypes or None
+
+
+
+class SSHUserAuthClient(userauth.SSHUserAuthClient):
+
+ def __init__(self, user, options, *args):
+ userauth.SSHUserAuthClient.__init__(self, user, *args)
+ self.keyAgent = None
+ self.options = options
+ self.usedFiles = []
+ if not options.identitys:
+ options.identitys = ['~/.ssh/id_rsa', '~/.ssh/id_dsa']
+
+
+ def serviceStarted(self):
+ if 'SSH_AUTH_SOCK' in os.environ and not self.options['noagent']:
+ log.msg('using agent')
+ cc = protocol.ClientCreator(reactor, agent.SSHAgentClient)
+ d = cc.connectUNIX(os.environ['SSH_AUTH_SOCK'])
+ d.addCallback(self._setAgent)
+ d.addErrback(self._ebSetAgent)
+ else:
+ userauth.SSHUserAuthClient.serviceStarted(self)
+
+
+ def serviceStopped(self):
+ if self.keyAgent:
+ self.keyAgent.transport.loseConnection()
+ self.keyAgent = None
+
+
+ def _setAgent(self, a):
+ self.keyAgent = a
+ d = self.keyAgent.getPublicKeys()
+ d.addBoth(self._ebSetAgent)
+ return d
+
+
+ def _ebSetAgent(self, f):
+ userauth.SSHUserAuthClient.serviceStarted(self)
+
+
+ def _getPassword(self, prompt):
+ """
+ Prompt for a password using L{getpass.getpass}.
+
+ @param prompt: Written on tty to ask for the input.
+ @type prompt: L{str}
+ @return: The input.
+ @rtype: L{str}
+ """
+ with self._replaceStdoutStdin():
+ try:
+ p = getpass.getpass(prompt)
+ return p
+ except (KeyboardInterrupt, IOError):
+ print()
+ raise ConchError('PEBKAC')
+
+
+ def getPassword(self, prompt = None):
+ if prompt:
+ prompt = nativeString(prompt)
+ else:
+ prompt = ("%s@%s's password: " %
+ (nativeString(self.user), self.transport.transport.getPeer().host))
+ try:
+ # We don't know the encoding the other side is using,
+ # signaling that is not part of the SSH protocol. But
+ # using our defaultencoding is better than just going for
+ # ASCII.
+ p = self._getPassword(prompt).encode(sys.getdefaultencoding())
+ return defer.succeed(p)
+ except ConchError:
+ return defer.fail()
+
+
+ def getPublicKey(self):
+ """
+ Get a public key from the key agent if possible, otherwise look in
+ the next configured identity file for one.
+ """
+ if self.keyAgent:
+ key = self.keyAgent.getPublicKey()
+ if key is not None:
+ return key
+ files = [x for x in self.options.identitys if x not in self.usedFiles]
+ log.msg(str(self.options.identitys))
+ log.msg(str(files))
+ if not files:
+ return None
+ file = files[0]
+ log.msg(file)
+ self.usedFiles.append(file)
+ file = os.path.expanduser(file)
+ file += '.pub'
+ if not os.path.exists(file):
+ return self.getPublicKey() # try again
+ try:
+ return keys.Key.fromFile(file)
+ except keys.BadKeyError:
+ return self.getPublicKey() # try again
+
+
+ def signData(self, publicKey, signData):
+ """
+ Extend the base signing behavior by using an SSH agent to sign the
+ data, if one is available.
+
+ @type publicKey: L{Key}
+ @type signData: L{bytes}
+ """
+ if not self.usedFiles: # agent key
+ return self.keyAgent.signData(publicKey.blob(), signData)
+ else:
+ return userauth.SSHUserAuthClient.signData(self, publicKey, signData)
+
+
+ def getPrivateKey(self):
+ """
+ Try to load the private key from the last used file identified by
+ C{getPublicKey}, potentially asking for the passphrase if the key is
+ encrypted.
+ """
+ file = os.path.expanduser(self.usedFiles[-1])
+ if not os.path.exists(file):
+ return None
+ try:
+ return defer.succeed(keys.Key.fromFile(file))
+ except keys.EncryptedKeyError:
+ for i in range(3):
+ prompt = "Enter passphrase for key '%s': " % self.usedFiles[-1]
+ try:
+ p = self._getPassword(prompt).encode(
+ sys.getfilesystemencoding())
+ return defer.succeed(keys.Key.fromFile(file, passphrase=p))
+ except (keys.BadKeyError, ConchError):
+ pass
+ return defer.fail(ConchError('bad password'))
+ raise
+ except KeyboardInterrupt:
+ print()
+ reactor.stop()
+
+
+ def getGenericAnswers(self, name, instruction, prompts):
+ responses = []
+ with self._replaceStdoutStdin():
+ if name:
+ print(name.decode("utf-8"))
+ if instruction:
+ print(instruction.decode("utf-8"))
+ for prompt, echo in prompts:
+ prompt = prompt.decode("utf-8")
+ if echo:
+ responses.append(raw_input(prompt))
+ else:
+ responses.append(getpass.getpass(prompt))
+ return defer.succeed(responses)
+
+
+ @classmethod
+ def _openTty(cls):
+ """
+ Open /dev/tty as two streams one in read, one in write mode,
+ and return them.
+
+ @return: File objects for reading and writing to /dev/tty,
+ corresponding to standard input and standard output.
+ @rtype: A L{tuple} of L{io.TextIOWrapper} on Python 3.
+ A L{tuple} of binary files on Python 2.
+ """
+ stdin = open("/dev/tty", "rb")
+ stdout = open("/dev/tty", "wb")
+ if _PY3:
+ stdin = io.TextIOWrapper(stdin)
+ stdout = io.TextIOWrapper(stdout)
+ return stdin, stdout
+
+
+ @classmethod
+ @contextlib.contextmanager
+ def _replaceStdoutStdin(cls):
+ """
+ Contextmanager that replaces stdout and stdin with /dev/tty
+ and resets them when it is done.
+ """
+ oldout, oldin = sys.stdout, sys.stdin
+ sys.stdin, sys.stdout = cls._openTty()
+ try:
+ yield
+ finally:
+ sys.stdout.close()
+ sys.stdin.close()
+ sys.stdout, sys.stdin = oldout, oldin
diff --git a/contrib/python/Twisted/py2/twisted/conch/client/direct.py b/contrib/python/Twisted/py2/twisted/conch/client/direct.py
new file mode 100644
index 0000000000..601a9d2dc3
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/client/direct.py
@@ -0,0 +1,109 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from __future__ import print_function
+
+from twisted.internet import defer, protocol, reactor
+from twisted.conch import error
+from twisted.conch.ssh import transport
+from twisted.python import log
+
+
+
+class SSHClientFactory(protocol.ClientFactory):
+
+ def __init__(self, d, options, verifyHostKey, userAuthObject):
+ self.d = d
+ self.options = options
+ self.verifyHostKey = verifyHostKey
+ self.userAuthObject = userAuthObject
+
+
+ def clientConnectionLost(self, connector, reason):
+ if self.options['reconnect']:
+ connector.connect()
+
+
+ def clientConnectionFailed(self, connector, reason):
+ if self.d is None:
+ return
+ d, self.d = self.d, None
+ d.errback(reason)
+
+
+ def buildProtocol(self, addr):
+ trans = SSHClientTransport(self)
+ if self.options['ciphers']:
+ trans.supportedCiphers = self.options['ciphers']
+ if self.options['macs']:
+ trans.supportedMACs = self.options['macs']
+ if self.options['compress']:
+ trans.supportedCompressions[0:1] = ['zlib']
+ if self.options['host-key-algorithms']:
+ trans.supportedPublicKeys = self.options['host-key-algorithms']
+ return trans
+
+
+
+class SSHClientTransport(transport.SSHClientTransport):
+
+ def __init__(self, factory):
+ self.factory = factory
+ self.unixServer = None
+
+
+ def connectionLost(self, reason):
+ if self.unixServer:
+ d = self.unixServer.stopListening()
+ self.unixServer = None
+ else:
+ d = defer.succeed(None)
+ d.addCallback(lambda x:
+ transport.SSHClientTransport.connectionLost(self, reason))
+
+
+ def receiveError(self, code, desc):
+ if self.factory.d is None:
+ return
+ d, self.factory.d = self.factory.d, None
+ d.errback(error.ConchError(desc, code))
+
+
+ def sendDisconnect(self, code, reason):
+ if self.factory.d is None:
+ return
+ d, self.factory.d = self.factory.d, None
+ transport.SSHClientTransport.sendDisconnect(self, code, reason)
+ d.errback(error.ConchError(reason, code))
+
+
+ def receiveDebug(self, alwaysDisplay, message, lang):
+ log.msg('Received Debug Message: %s' % message)
+ if alwaysDisplay: # XXX what should happen here?
+ print(message)
+
+
+ def verifyHostKey(self, pubKey, fingerprint):
+ return self.factory.verifyHostKey(self, self.transport.getPeer().host, pubKey,
+ fingerprint)
+
+
+ def setService(self, service):
+ log.msg('setting client server to %s' % service)
+ transport.SSHClientTransport.setService(self, service)
+ if service.name != 'ssh-userauth' and self.factory.d is not None:
+ d, self.factory.d = self.factory.d, None
+ d.callback(None)
+
+
+ def connectionSecure(self):
+ self.requestService(self.factory.userAuthObject)
+
+
+
+def connect(host, port, options, verifyHostKey, userAuthObject):
+ d = defer.Deferred()
+ factory = SSHClientFactory(d, options, verifyHostKey, userAuthObject)
+ reactor.connectTCP(host, port, factory)
+ return d
diff --git a/contrib/python/Twisted/py2/twisted/conch/client/knownhosts.py b/contrib/python/Twisted/py2/twisted/conch/client/knownhosts.py
new file mode 100644
index 0000000000..aa0a622d41
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/client/knownhosts.py
@@ -0,0 +1,630 @@
+# -*- test-case-name: twisted.conch.test.test_knownhosts -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+An implementation of the OpenSSH known_hosts database.
+
+@since: 8.2
+"""
+
+from __future__ import absolute_import, division
+
+import hmac
+from binascii import Error as DecodeError, b2a_base64, a2b_base64
+from contextlib import closing
+from hashlib import sha1
+import sys
+
+from zope.interface import implementer
+
+from twisted.conch.interfaces import IKnownHostEntry
+from twisted.conch.error import HostKeyChanged, UserRejectedKey, InvalidEntry
+from twisted.conch.ssh.keys import Key, BadKeyError, FingerprintFormats
+from twisted.internet import defer
+from twisted.python import log
+from twisted.python.compat import nativeString, unicode
+from twisted.python.randbytes import secureRandom
+from twisted.python.util import FancyEqMixin
+
+
+def _b64encode(s):
+ """
+ Encode a binary string as base64 with no trailing newline.
+
+ @param s: The string to encode.
+ @type s: L{bytes}
+
+ @return: The base64-encoded string.
+ @rtype: L{bytes}
+ """
+ return b2a_base64(s).strip()
+
+
+
+def _extractCommon(string):
+ """
+ Extract common elements of base64 keys from an entry in a hosts file.
+
+ @param string: A known hosts file entry (a single line).
+ @type string: L{bytes}
+
+ @return: a 4-tuple of hostname data (L{bytes}), ssh key type (L{bytes}), key
+ (L{Key}), and comment (L{bytes} or L{None}). The hostname data is
+ simply the beginning of the line up to the first occurrence of
+ whitespace.
+ @rtype: L{tuple}
+ """
+ elements = string.split(None, 2)
+ if len(elements) != 3:
+ raise InvalidEntry()
+ hostnames, keyType, keyAndComment = elements
+ splitkey = keyAndComment.split(None, 1)
+ if len(splitkey) == 2:
+ keyString, comment = splitkey
+ comment = comment.rstrip(b"\n")
+ else:
+ keyString = splitkey[0]
+ comment = None
+ key = Key.fromString(a2b_base64(keyString))
+ return hostnames, keyType, key, comment
+
+
+
+class _BaseEntry(object):
+ """
+ Abstract base of both hashed and non-hashed entry objects, since they
+ represent keys and key types the same way.
+
+ @ivar keyType: The type of the key; either ssh-dss or ssh-rsa.
+ @type keyType: L{bytes}
+
+ @ivar publicKey: The server public key indicated by this line.
+ @type publicKey: L{twisted.conch.ssh.keys.Key}
+
+ @ivar comment: Trailing garbage after the key line.
+ @type comment: L{bytes}
+ """
+
+ def __init__(self, keyType, publicKey, comment):
+ self.keyType = keyType
+ self.publicKey = publicKey
+ self.comment = comment
+
+
+ def matchesKey(self, keyObject):
+ """
+ Check to see if this entry matches a given key object.
+
+ @param keyObject: A public key object to check.
+ @type keyObject: L{Key}
+
+ @return: C{True} if this entry's key matches C{keyObject}, C{False}
+ otherwise.
+ @rtype: L{bool}
+ """
+ return self.publicKey == keyObject
+
+
+
+@implementer(IKnownHostEntry)
+class PlainEntry(_BaseEntry):
+ """
+ A L{PlainEntry} is a representation of a plain-text entry in a known_hosts
+ file.
+
+ @ivar _hostnames: the list of all host-names associated with this entry.
+ @type _hostnames: L{list} of L{bytes}
+ """
+
+ def __init__(self, hostnames, keyType, publicKey, comment):
+ self._hostnames = hostnames
+ super(PlainEntry, self).__init__(keyType, publicKey, comment)
+
+
+ @classmethod
+ def fromString(cls, string):
+ """
+ Parse a plain-text entry in a known_hosts file, and return a
+ corresponding L{PlainEntry}.
+
+ @param string: a space-separated string formatted like "hostname
+ key-type base64-key-data comment".
+
+ @type string: L{bytes}
+
+ @raise DecodeError: if the key is not valid encoded as valid base64.
+
+ @raise InvalidEntry: if the entry does not have the right number of
+ elements and is therefore invalid.
+
+ @raise BadKeyError: if the key, once decoded from base64, is not
+ actually an SSH key.
+
+ @return: an IKnownHostEntry representing the hostname and key in the
+ input line.
+
+ @rtype: L{PlainEntry}
+ """
+ hostnames, keyType, key, comment = _extractCommon(string)
+ self = cls(hostnames.split(b","), keyType, key, comment)
+ return self
+
+
+ def matchesHost(self, hostname):
+ """
+ Check to see if this entry matches a given hostname.
+
+ @param hostname: A hostname or IP address literal to check against this
+ entry.
+ @type hostname: L{bytes}
+
+ @return: C{True} if this entry is for the given hostname or IP address,
+ C{False} otherwise.
+ @rtype: L{bool}
+ """
+ if isinstance(hostname, unicode):
+ hostname = hostname.encode("utf-8")
+ return hostname in self._hostnames
+
+
+ def toString(self):
+ """
+ Implement L{IKnownHostEntry.toString} by recording the comma-separated
+ hostnames, key type, and base-64 encoded key.
+
+ @return: The string representation of this entry, with unhashed hostname
+ information.
+ @rtype: L{bytes}
+ """
+ fields = [b','.join(self._hostnames),
+ self.keyType,
+ _b64encode(self.publicKey.blob())]
+ if self.comment is not None:
+ fields.append(self.comment)
+ return b' '.join(fields)
+
+
+
+@implementer(IKnownHostEntry)
+class UnparsedEntry(object):
+ """
+ L{UnparsedEntry} is an entry in a L{KnownHostsFile} which can't actually be
+ parsed; therefore it matches no keys and no hosts.
+ """
+
+ def __init__(self, string):
+ """
+ Create an unparsed entry from a line in a known_hosts file which cannot
+ otherwise be parsed.
+ """
+ self._string = string
+
+
+ def matchesHost(self, hostname):
+ """
+ Always returns False.
+ """
+ return False
+
+
+ def matchesKey(self, key):
+ """
+ Always returns False.
+ """
+ return False
+
+
+ def toString(self):
+ """
+ Returns the input line, without its newline if one was given.
+
+ @return: The string representation of this entry, almost exactly as was
+ used to initialize this entry but without a trailing newline.
+ @rtype: L{bytes}
+ """
+ return self._string.rstrip(b"\n")
+
+
+
+def _hmacedString(key, string):
+ """
+ Return the SHA-1 HMAC hash of the given key and string.
+
+ @param key: The HMAC key.
+ @type key: L{bytes}
+
+ @param string: The string to be hashed.
+ @type string: L{bytes}
+
+ @return: The keyed hash value.
+ @rtype: L{bytes}
+ """
+ hash = hmac.HMAC(key, digestmod=sha1)
+ if isinstance(string, unicode):
+ string = string.encode("utf-8")
+ hash.update(string)
+ return hash.digest()
+
+
+
+@implementer(IKnownHostEntry)
+class HashedEntry(_BaseEntry, FancyEqMixin):
+ """
+ A L{HashedEntry} is a representation of an entry in a known_hosts file
+ where the hostname has been hashed and salted.
+
+ @ivar _hostSalt: the salt to combine with a hostname for hashing.
+
+ @ivar _hostHash: the hashed representation of the hostname.
+
+ @cvar MAGIC: the 'hash magic' string used to identify a hashed line in a
+ known_hosts file as opposed to a plaintext one.
+ """
+
+ MAGIC = b'|1|'
+
+ compareAttributes = (
+ "_hostSalt", "_hostHash", "keyType", "publicKey", "comment")
+
+ def __init__(self, hostSalt, hostHash, keyType, publicKey, comment):
+ self._hostSalt = hostSalt
+ self._hostHash = hostHash
+ super(HashedEntry, self).__init__(keyType, publicKey, comment)
+
+
+ @classmethod
+ def fromString(cls, string):
+ """
+ Load a hashed entry from a string representing a line in a known_hosts
+ file.
+
+ @param string: A complete single line from a I{known_hosts} file,
+ formatted as defined by OpenSSH.
+ @type string: L{bytes}
+
+ @raise DecodeError: if the key, the hostname, or the is not valid
+ encoded as valid base64
+
+ @raise InvalidEntry: if the entry does not have the right number of
+ elements and is therefore invalid, or the host/hash portion contains
+ more items than just the host and hash.
+
+ @raise BadKeyError: if the key, once decoded from base64, is not
+ actually an SSH key.
+
+ @return: The newly created L{HashedEntry} instance, initialized with the
+ information from C{string}.
+ """
+ stuff, keyType, key, comment = _extractCommon(string)
+ saltAndHash = stuff[len(cls.MAGIC):].split(b"|")
+ if len(saltAndHash) != 2:
+ raise InvalidEntry()
+ hostSalt, hostHash = saltAndHash
+ self = cls(a2b_base64(hostSalt), a2b_base64(hostHash),
+ keyType, key, comment)
+ return self
+
+
+ def matchesHost(self, hostname):
+ """
+ Implement L{IKnownHostEntry.matchesHost} to compare the hash of the
+ input to the stored hash.
+
+ @param hostname: A hostname or IP address literal to check against this
+ entry.
+ @type hostname: L{bytes}
+
+ @return: C{True} if this entry is for the given hostname or IP address,
+ C{False} otherwise.
+ @rtype: L{bool}
+ """
+ return (_hmacedString(self._hostSalt, hostname) == self._hostHash)
+
+
+ def toString(self):
+ """
+ Implement L{IKnownHostEntry.toString} by base64-encoding the salt, host
+ hash, and key.
+
+ @return: The string representation of this entry, with the hostname part
+ hashed.
+ @rtype: L{bytes}
+ """
+ fields = [self.MAGIC + b'|'.join([_b64encode(self._hostSalt),
+ _b64encode(self._hostHash)]),
+ self.keyType,
+ _b64encode(self.publicKey.blob())]
+ if self.comment is not None:
+ fields.append(self.comment)
+ return b' '.join(fields)
+
+
+
+class KnownHostsFile(object):
+ """
+ A structured representation of an OpenSSH-format ~/.ssh/known_hosts file.
+
+ @ivar _added: A list of L{IKnownHostEntry} providers which have been added
+ to this instance in memory but not yet saved.
+
+ @ivar _clobber: A flag indicating whether the current contents of the save
+ path will be disregarded and potentially overwritten or not. If
+ C{True}, this will be done. If C{False}, entries in the save path will
+ be read and new entries will be saved by appending rather than
+ overwriting.
+ @type _clobber: L{bool}
+
+ @ivar _savePath: See C{savePath} parameter of L{__init__}.
+ """
+
+ def __init__(self, savePath):
+ """
+ Create a new, empty KnownHostsFile.
+
+ Unless you want to erase the current contents of C{savePath}, you want
+ to use L{KnownHostsFile.fromPath} instead.
+
+ @param savePath: The L{FilePath} to which to save new entries.
+ @type savePath: L{FilePath}
+ """
+ self._added = []
+ self._savePath = savePath
+ self._clobber = True
+
+
+ @property
+ def savePath(self):
+ """
+ @see: C{savePath} parameter of L{__init__}
+ """
+ return self._savePath
+
+
+ def iterentries(self):
+ """
+ Iterate over the host entries in this file.
+
+ @return: An iterable the elements of which provide L{IKnownHostEntry}.
+ There is an element for each entry in the file as well as an element
+ for each added but not yet saved entry.
+ @rtype: iterable of L{IKnownHostEntry} providers
+ """
+ for entry in self._added:
+ yield entry
+
+ if self._clobber:
+ return
+
+ try:
+ fp = self._savePath.open()
+ except IOError:
+ return
+
+ with fp:
+ for line in fp:
+ try:
+ if line.startswith(HashedEntry.MAGIC):
+ entry = HashedEntry.fromString(line)
+ else:
+ entry = PlainEntry.fromString(line)
+ except (DecodeError, InvalidEntry, BadKeyError):
+ entry = UnparsedEntry(line)
+ yield entry
+
+
+ def hasHostKey(self, hostname, key):
+ """
+ Check for an entry with matching hostname and key.
+
+ @param hostname: A hostname or IP address literal to check for.
+ @type hostname: L{bytes}
+
+ @param key: The public key to check for.
+ @type key: L{Key}
+
+ @return: C{True} if the given hostname and key are present in this file,
+ C{False} if they are not.
+ @rtype: L{bool}
+
+ @raise HostKeyChanged: if the host key found for the given hostname
+ does not match the given key.
+ """
+ for lineidx, entry in enumerate(self.iterentries(), -len(self._added)):
+ if entry.matchesHost(hostname) and entry.keyType == key.sshType():
+ if entry.matchesKey(key):
+ return True
+ else:
+ # Notice that lineidx is 0-based but HostKeyChanged.lineno
+ # is 1-based.
+ if lineidx < 0:
+ line = None
+ path = None
+ else:
+ line = lineidx + 1
+ path = self._savePath
+ raise HostKeyChanged(entry, path, line)
+ return False
+
+
+ def verifyHostKey(self, ui, hostname, ip, key):
+ """
+ Verify the given host key for the given IP and host, asking for
+ confirmation from, and notifying, the given UI about changes to this
+ file.
+
+ @param ui: The user interface to request an IP address from.
+
+ @param hostname: The hostname that the user requested to connect to.
+
+ @param ip: The string representation of the IP address that is actually
+ being connected to.
+
+ @param key: The public key of the server.
+
+ @return: a L{Deferred} that fires with True when the key has been
+ verified, or fires with an errback when the key either cannot be
+ verified or has changed.
+ @rtype: L{Deferred}
+ """
+ hhk = defer.maybeDeferred(self.hasHostKey, hostname, key)
+ def gotHasKey(result):
+ if result:
+ if not self.hasHostKey(ip, key):
+ ui.warn("Warning: Permanently added the %s host key for "
+ "IP address '%s' to the list of known hosts." %
+ (key.type(), nativeString(ip)))
+ self.addHostKey(ip, key)
+ self.save()
+ return result
+ else:
+ def promptResponse(response):
+ if response:
+ self.addHostKey(hostname, key)
+ self.addHostKey(ip, key)
+ self.save()
+ return response
+ else:
+ raise UserRejectedKey()
+
+ keytype = key.type()
+
+ if keytype == "EC":
+ keytype = "ECDSA"
+
+ prompt = (
+ "The authenticity of host '%s (%s)' "
+ "can't be established.\n"
+ "%s key fingerprint is SHA256:%s.\n"
+ "Are you sure you want to continue connecting (yes/no)? " %
+ (nativeString(hostname), nativeString(ip), keytype,
+ key.fingerprint(format=FingerprintFormats.SHA256_BASE64)))
+ proceed = ui.prompt(prompt.encode(sys.getdefaultencoding()))
+ return proceed.addCallback(promptResponse)
+ return hhk.addCallback(gotHasKey)
+
+
+ def addHostKey(self, hostname, key):
+ """
+ Add a new L{HashedEntry} to the key database.
+
+ Note that you still need to call L{KnownHostsFile.save} if you wish
+ these changes to be persisted.
+
+ @param hostname: A hostname or IP address literal to associate with the
+ new entry.
+ @type hostname: L{bytes}
+
+ @param key: The public key to associate with the new entry.
+ @type key: L{Key}
+
+ @return: The L{HashedEntry} that was added.
+ @rtype: L{HashedEntry}
+ """
+ salt = secureRandom(20)
+ keyType = key.sshType()
+ entry = HashedEntry(salt, _hmacedString(salt, hostname),
+ keyType, key, None)
+ self._added.append(entry)
+ return entry
+
+
+ def save(self):
+ """
+ Save this L{KnownHostsFile} to the path it was loaded from.
+ """
+ p = self._savePath.parent()
+ if not p.isdir():
+ p.makedirs()
+
+ if self._clobber:
+ mode = "wb"
+ else:
+ mode = "ab"
+
+ with self._savePath.open(mode) as hostsFileObj:
+ if self._added:
+ hostsFileObj.write(
+ b"\n".join([entry.toString() for entry in self._added]) +
+ b"\n")
+ self._added = []
+ self._clobber = False
+
+
+ @classmethod
+ def fromPath(cls, path):
+ """
+ Create a new L{KnownHostsFile}, potentially reading existing known
+ hosts information from the given file.
+
+ @param path: A path object to use for both reading contents from and
+ later saving to. If no file exists at this path, it is not an
+ error; a L{KnownHostsFile} with no entries is returned.
+ @type path: L{FilePath}
+
+ @return: A L{KnownHostsFile} initialized with entries from C{path}.
+ @rtype: L{KnownHostsFile}
+ """
+ knownHosts = cls(path)
+ knownHosts._clobber = False
+ return knownHosts
+
+
+
+class ConsoleUI(object):
+ """
+ A UI object that can ask true/false questions and post notifications on the
+ console, to be used during key verification.
+ """
+ def __init__(self, opener):
+ """
+ @param opener: A no-argument callable which should open a console
+ binary-mode file-like object to be used for reading and writing.
+ This initializes the C{opener} attribute.
+ @type opener: callable taking no arguments and returning a read/write
+ file-like object
+ """
+ self.opener = opener
+
+
+ def prompt(self, text):
+ """
+ Write the given text as a prompt to the console output, then read a
+ result from the console input.
+
+ @param text: Something to present to a user to solicit a yes or no
+ response.
+ @type text: L{bytes}
+
+ @return: a L{Deferred} which fires with L{True} when the user answers
+ 'yes' and L{False} when the user answers 'no'. It may errback if
+ there were any I/O errors.
+ """
+ d = defer.succeed(None)
+ def body(ignored):
+ with closing(self.opener()) as f:
+ f.write(text)
+ while True:
+ answer = f.readline().strip().lower()
+ if answer == b'yes':
+ return True
+ elif answer == b'no':
+ return False
+ else:
+ f.write(b"Please type 'yes' or 'no': ")
+ return d.addCallback(body)
+
+
+ def warn(self, text):
+ """
+ Notify the user (non-interactively) of the provided text, by writing it
+ to the console.
+
+ @param text: Some information the user is to be made aware of.
+ @type text: L{bytes}
+ """
+ try:
+ with closing(self.opener()) as f:
+ f.write(text)
+ except:
+ log.err()
diff --git a/contrib/python/Twisted/py2/twisted/conch/client/options.py b/contrib/python/Twisted/py2/twisted/conch/client/options.py
new file mode 100644
index 0000000000..5630fce250
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/client/options.py
@@ -0,0 +1,103 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+from twisted.conch.ssh.transport import SSHClientTransport, SSHCiphers
+from twisted.python import usage
+from twisted.python.compat import unicode
+
+import sys
+
+class ConchOptions(usage.Options):
+
+ optParameters = [['user', 'l', None, 'Log in using this user name.'],
+ ['identity', 'i', None],
+ ['ciphers', 'c', None],
+ ['macs', 'm', None],
+ ['port', 'p', None, 'Connect to this port. Server must be on the same port.'],
+ ['option', 'o', None, 'Ignored OpenSSH options'],
+ ['host-key-algorithms', '', None],
+ ['known-hosts', '', None, 'File to check for host keys'],
+ ['user-authentications', '', None, 'Types of user authentications to use.'],
+ ['logfile', '', None, 'File to log to, or - for stdout'],
+ ]
+
+ optFlags = [['version', 'V', 'Display version number only.'],
+ ['compress', 'C', 'Enable compression.'],
+ ['log', 'v', 'Enable logging (defaults to stderr)'],
+ ['nox11', 'x', 'Disable X11 connection forwarding (default)'],
+ ['agent', 'A', 'Enable authentication agent forwarding'],
+ ['noagent', 'a', 'Disable authentication agent forwarding (default)'],
+ ['reconnect', 'r', 'Reconnect to the server if the connection is lost.'],
+ ]
+
+ compData = usage.Completions(
+ mutuallyExclusive=[("agent", "noagent")],
+ optActions={
+ "user": usage.CompleteUsernames(),
+ "ciphers": usage.CompleteMultiList(
+ SSHCiphers.cipherMap.keys(),
+ descr='ciphers to choose from'),
+ "macs": usage.CompleteMultiList(
+ SSHCiphers.macMap.keys(),
+ descr='macs to choose from'),
+ "host-key-algorithms": usage.CompleteMultiList(
+ SSHClientTransport.supportedPublicKeys,
+ descr='host key algorithms to choose from'),
+ #"user-authentications": usage.CompleteMultiList(?
+ # descr='user authentication types' ),
+ },
+ extraActions=[usage.CompleteUserAtHost(),
+ usage.Completer(descr="command"),
+ usage.Completer(descr='argument',
+ repeat=True)]
+ )
+
+ def __init__(self, *args, **kw):
+ usage.Options.__init__(self, *args, **kw)
+ self.identitys = []
+ self.conns = None
+
+ def opt_identity(self, i):
+ """Identity for public-key authentication"""
+ self.identitys.append(i)
+
+ def opt_ciphers(self, ciphers):
+ "Select encryption algorithms"
+ ciphers = ciphers.split(',')
+ for cipher in ciphers:
+ if cipher not in SSHCiphers.cipherMap:
+ sys.exit("Unknown cipher type '%s'" % cipher)
+ self['ciphers'] = ciphers
+
+
+ def opt_macs(self, macs):
+ "Specify MAC algorithms"
+ if isinstance(macs, unicode):
+ macs = macs.encode("utf-8")
+ macs = macs.split(b',')
+ for mac in macs:
+ if mac not in SSHCiphers.macMap:
+ sys.exit("Unknown mac type '%r'" % mac)
+ self['macs'] = macs
+
+ def opt_host_key_algorithms(self, hkas):
+ "Select host key algorithms"
+ if isinstance(hkas, unicode):
+ hkas = hkas.encode("utf-8")
+ hkas = hkas.split(b',')
+ for hka in hkas:
+ if hka not in SSHClientTransport.supportedPublicKeys:
+ sys.exit("Unknown host key type '%r'" % hka)
+ self['host-key-algorithms'] = hkas
+
+ def opt_user_authentications(self, uas):
+ "Choose how to authenticate to the remote server"
+ if isinstance(uas, unicode):
+ uas = uas.encode("utf-8")
+ self['user-authentications'] = uas.split(b',')
+
+# def opt_compress(self):
+# "Enable compression"
+# self.enableCompression = 1
+# SSHClientTransport.supportedCompressions[0:1] = ['zlib']