aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/Twisted/py2/twisted/conch
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
parent523f645a83a0ec97a0332dbc3863bb354c92a328 (diff)
downloadydb-b8cf9e88f4c5c64d9406af533d8948deb050d695.tar.gz
add kikimr_configure
Diffstat (limited to 'contrib/python/Twisted/py2/twisted/conch')
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/__init__.py7
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/avatar.py45
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/checkers.py592
-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
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/endpoints.py872
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/error.py103
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/insults/__init__.py4
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/insults/helper.py517
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/insults/insults.py1289
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/insults/text.py176
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/insults/window.py1027
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/interfaces.py444
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/ls.py83
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/manhole.py401
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/manhole_ssh.py141
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/manhole_tap.py165
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/mixin.py55
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/openssh_compat/__init__.py11
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/openssh_compat/factory.py72
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/openssh_compat/primes.py30
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/recvline.py374
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/ssh/__init__.py10
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/ssh/_kex.py294
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/ssh/address.py47
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/ssh/agent.py296
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/ssh/channel.py320
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/ssh/common.py93
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/ssh/connection.py653
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/ssh/factory.py123
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/ssh/filetransfer.py1055
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/ssh/forwarding.py269
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/ssh/keys.py1678
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/ssh/service.py48
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/ssh/session.py362
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/ssh/sexpy.py45
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/ssh/transport.py2127
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/ssh/userauth.py770
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/stdio.py120
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/tap.py86
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/telnet.py1194
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/ttymodes.py121
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/ui/__init__.py11
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/ui/ansi.py240
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/ui/tkvt100.py202
-rw-r--r--contrib/python/Twisted/py2/twisted/conch/unix.py535
51 files changed, 18401 insertions, 0 deletions
diff --git a/contrib/python/Twisted/py2/twisted/conch/__init__.py b/contrib/python/Twisted/py2/twisted/conch/__init__.py
new file mode 100644
index 0000000000..adc49d01e6
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/__init__.py
@@ -0,0 +1,7 @@
+# -*- test-case-name: twisted.conch.test -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Twisted Conch: The Twisted Shell. Terminal emulation, SSHv2 and telnet.
+"""
diff --git a/contrib/python/Twisted/py2/twisted/conch/avatar.py b/contrib/python/Twisted/py2/twisted/conch/avatar.py
new file mode 100644
index 0000000000..2f6850dbb8
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/avatar.py
@@ -0,0 +1,45 @@
+# -*- test-case-name: twisted.conch.test.test_conch -*-
+
+from __future__ import absolute_import, division
+
+from zope.interface import implementer
+
+from twisted.conch.error import ConchError
+from twisted.conch.interfaces import IConchUser
+from twisted.conch.ssh.connection import OPEN_UNKNOWN_CHANNEL_TYPE
+from twisted.python import log
+from twisted.python.compat import nativeString
+
+
+@implementer(IConchUser)
+class ConchUser:
+ def __init__(self):
+ self.channelLookup = {}
+ self.subsystemLookup = {}
+
+
+ def lookupChannel(self, channelType, windowSize, maxPacket, data):
+ klass = self.channelLookup.get(channelType, None)
+ if not klass:
+ raise ConchError(OPEN_UNKNOWN_CHANNEL_TYPE, "unknown channel")
+ else:
+ return klass(remoteWindow=windowSize,
+ remoteMaxPacket=maxPacket,
+ data=data, avatar=self)
+
+
+ def lookupSubsystem(self, subsystem, data):
+ log.msg(repr(self.subsystemLookup))
+ klass = self.subsystemLookup.get(subsystem, None)
+ if not klass:
+ return False
+ return klass(data, avatar=self)
+
+
+ def gotGlobalRequest(self, requestType, data):
+ # XXX should this use method dispatch?
+ requestType = nativeString(requestType.replace(b'-', b'_'))
+ f = getattr(self, "global_%s" % requestType, None)
+ if not f:
+ return 0
+ return f(data)
diff --git a/contrib/python/Twisted/py2/twisted/conch/checkers.py b/contrib/python/Twisted/py2/twisted/conch/checkers.py
new file mode 100644
index 0000000000..d4fcf5bc78
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/checkers.py
@@ -0,0 +1,592 @@
+# -*- test-case-name: twisted.conch.test.test_checkers -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Provide L{ICredentialsChecker} implementations to be used in Conch protocols.
+"""
+
+from __future__ import absolute_import, division
+
+import sys
+import binascii
+import errno
+
+try:
+ import pwd
+except ImportError:
+ pwd = None
+else:
+ import crypt
+
+try:
+ import spwd
+except ImportError:
+ spwd = None
+
+from zope.interface import providedBy, implementer, Interface
+
+from incremental import Version
+
+from twisted.conch import error
+from twisted.conch.ssh import keys
+from twisted.cred.checkers import ICredentialsChecker
+from twisted.cred.credentials import IUsernamePassword, ISSHPrivateKey
+from twisted.cred.error import UnauthorizedLogin, UnhandledCredentials
+from twisted.internet import defer
+from twisted.python.compat import _keys, _PY3, _b64decodebytes
+from twisted.python import failure, reflect, log
+from twisted.python.deprecate import deprecatedModuleAttribute
+from twisted.python.util import runAsEffectiveUser
+from twisted.python.filepath import FilePath
+
+
+
+
+def verifyCryptedPassword(crypted, pw):
+ """
+ Check that the password, when crypted, matches the stored crypted password.
+
+ @param crypted: The stored crypted password.
+ @type crypted: L{str}
+ @param pw: The password the user has given.
+ @type pw: L{str}
+
+ @rtype: L{bool}
+ """
+ return crypt.crypt(pw, crypted) == crypted
+
+
+
+def _pwdGetByName(username):
+ """
+ Look up a user in the /etc/passwd database using the pwd module. If the
+ pwd module is not available, return None.
+
+ @param username: the username of the user to return the passwd database
+ information for.
+ @type username: L{str}
+ """
+ if pwd is None:
+ return None
+ return pwd.getpwnam(username)
+
+
+
+def _shadowGetByName(username):
+ """
+ Look up a user in the /etc/shadow database using the spwd module. If it is
+ not available, return L{None}.
+
+ @param username: the username of the user to return the shadow database
+ information for.
+ @type username: L{str}
+ """
+ if spwd is not None:
+ f = spwd.getspnam
+ else:
+ return None
+ return runAsEffectiveUser(0, 0, f, username)
+
+
+
+@implementer(ICredentialsChecker)
+class UNIXPasswordDatabase:
+ """
+ A checker which validates users out of the UNIX password databases, or
+ databases of a compatible format.
+
+ @ivar _getByNameFunctions: a C{list} of functions which are called in order
+ to valid a user. The default value is such that the C{/etc/passwd}
+ database will be tried first, followed by the C{/etc/shadow} database.
+ """
+ credentialInterfaces = IUsernamePassword,
+
+ def __init__(self, getByNameFunctions=None):
+ if getByNameFunctions is None:
+ getByNameFunctions = [_pwdGetByName, _shadowGetByName]
+ self._getByNameFunctions = getByNameFunctions
+
+
+ def requestAvatarId(self, credentials):
+ # We get bytes, but the Py3 pwd module uses str. So attempt to decode
+ # it using the same method that CPython does for the file on disk.
+ if _PY3:
+ username = credentials.username.decode(sys.getfilesystemencoding())
+ password = credentials.password.decode(sys.getfilesystemencoding())
+ else:
+ username = credentials.username
+ password = credentials.password
+
+ for func in self._getByNameFunctions:
+ try:
+ pwnam = func(username)
+ except KeyError:
+ return defer.fail(UnauthorizedLogin("invalid username"))
+ else:
+ if pwnam is not None:
+ crypted = pwnam[1]
+ if crypted == '':
+ continue
+
+ if verifyCryptedPassword(crypted, password):
+ return defer.succeed(credentials.username)
+ # fallback
+ return defer.fail(UnauthorizedLogin("unable to verify password"))
+
+
+
+@implementer(ICredentialsChecker)
+class SSHPublicKeyDatabase:
+ """
+ Checker that authenticates SSH public keys, based on public keys listed in
+ authorized_keys and authorized_keys2 files in user .ssh/ directories.
+ """
+ credentialInterfaces = (ISSHPrivateKey,)
+
+ _userdb = pwd
+
+ def requestAvatarId(self, credentials):
+ d = defer.maybeDeferred(self.checkKey, credentials)
+ d.addCallback(self._cbRequestAvatarId, credentials)
+ d.addErrback(self._ebRequestAvatarId)
+ return d
+
+
+ def _cbRequestAvatarId(self, validKey, credentials):
+ """
+ Check whether the credentials themselves are valid, now that we know
+ if the key matches the user.
+
+ @param validKey: A boolean indicating whether or not the public key
+ matches a key in the user's authorized_keys file.
+
+ @param credentials: The credentials offered by the user.
+ @type credentials: L{ISSHPrivateKey} provider
+
+ @raise UnauthorizedLogin: (as a failure) if the key does not match the
+ user in C{credentials}. Also raised if the user provides an invalid
+ signature.
+
+ @raise ValidPublicKey: (as a failure) if the key matches the user but
+ the credentials do not include a signature. See
+ L{error.ValidPublicKey} for more information.
+
+ @return: The user's username, if authentication was successful.
+ """
+ if not validKey:
+ return failure.Failure(UnauthorizedLogin("invalid key"))
+ if not credentials.signature:
+ return failure.Failure(error.ValidPublicKey())
+ else:
+ try:
+ pubKey = keys.Key.fromString(credentials.blob)
+ if pubKey.verify(credentials.signature, credentials.sigData):
+ return credentials.username
+ except: # any error should be treated as a failed login
+ log.err()
+ return failure.Failure(UnauthorizedLogin('error while verifying key'))
+ return failure.Failure(UnauthorizedLogin("unable to verify key"))
+
+
+ def getAuthorizedKeysFiles(self, credentials):
+ """
+ Return a list of L{FilePath} instances for I{authorized_keys} files
+ which might contain information about authorized keys for the given
+ credentials.
+
+ On OpenSSH servers, the default location of the file containing the
+ list of authorized public keys is
+ U{$HOME/.ssh/authorized_keys<http://www.openbsd.org/cgi-bin/man.cgi?query=sshd_config>}.
+
+ I{$HOME/.ssh/authorized_keys2} is also returned, though it has been
+ U{deprecated by OpenSSH since
+ 2001<http://marc.info/?m=100508718416162>}.
+
+ @return: A list of L{FilePath} instances to files with the authorized keys.
+ """
+ pwent = self._userdb.getpwnam(credentials.username)
+ root = FilePath(pwent.pw_dir).child('.ssh')
+ files = ['authorized_keys', 'authorized_keys2']
+ return [root.child(f) for f in files]
+
+
+ def checkKey(self, credentials):
+ """
+ Retrieve files containing authorized keys and check against user
+ credentials.
+ """
+ ouid, ogid = self._userdb.getpwnam(credentials.username)[2:4]
+ for filepath in self.getAuthorizedKeysFiles(credentials):
+ if not filepath.exists():
+ continue
+ try:
+ lines = filepath.open()
+ except IOError as e:
+ if e.errno == errno.EACCES:
+ lines = runAsEffectiveUser(ouid, ogid, filepath.open)
+ else:
+ raise
+ with lines:
+ for l in lines:
+ l2 = l.split()
+ if len(l2) < 2:
+ continue
+ try:
+ if _b64decodebytes(l2[1]) == credentials.blob:
+ return True
+ except binascii.Error:
+ continue
+ return False
+
+
+ def _ebRequestAvatarId(self, f):
+ if not f.check(UnauthorizedLogin):
+ log.msg(f)
+ return failure.Failure(UnauthorizedLogin("unable to get avatar id"))
+ return f
+
+
+
+@implementer(ICredentialsChecker)
+class SSHProtocolChecker:
+ """
+ SSHProtocolChecker is a checker that requires multiple authentications
+ to succeed. To add a checker, call my registerChecker method with
+ the checker and the interface.
+
+ After each successful authenticate, I call my areDone method with the
+ avatar id. To get a list of the successful credentials for an avatar id,
+ use C{SSHProcotolChecker.successfulCredentials[avatarId]}. If L{areDone}
+ returns True, the authentication has succeeded.
+ """
+
+ def __init__(self):
+ self.checkers = {}
+ self.successfulCredentials = {}
+
+
+ def get_credentialInterfaces(self):
+ return _keys(self.checkers)
+
+ credentialInterfaces = property(get_credentialInterfaces)
+
+ def registerChecker(self, checker, *credentialInterfaces):
+ if not credentialInterfaces:
+ credentialInterfaces = checker.credentialInterfaces
+ for credentialInterface in credentialInterfaces:
+ self.checkers[credentialInterface] = checker
+
+
+ def requestAvatarId(self, credentials):
+ """
+ Part of the L{ICredentialsChecker} interface. Called by a portal with
+ some credentials to check if they'll authenticate a user. We check the
+ interfaces that the credentials provide against our list of acceptable
+ checkers. If one of them matches, we ask that checker to verify the
+ credentials. If they're valid, we call our L{_cbGoodAuthentication}
+ method to continue.
+
+ @param credentials: the credentials the L{Portal} wants us to verify
+ """
+ ifac = providedBy(credentials)
+ for i in ifac:
+ c = self.checkers.get(i)
+ if c is not None:
+ d = defer.maybeDeferred(c.requestAvatarId, credentials)
+ return d.addCallback(self._cbGoodAuthentication,
+ credentials)
+ return defer.fail(UnhandledCredentials("No checker for %s" % \
+ ', '.join(map(reflect.qual, ifac))))
+
+
+ def _cbGoodAuthentication(self, avatarId, credentials):
+ """
+ Called if a checker has verified the credentials. We call our
+ L{areDone} method to see if the whole of the successful authentications
+ are enough. If they are, we return the avatar ID returned by the first
+ checker.
+ """
+ if avatarId not in self.successfulCredentials:
+ self.successfulCredentials[avatarId] = []
+ self.successfulCredentials[avatarId].append(credentials)
+ if self.areDone(avatarId):
+ del self.successfulCredentials[avatarId]
+ return avatarId
+ else:
+ raise error.NotEnoughAuthentication()
+
+
+ def areDone(self, avatarId):
+ """
+ Override to determine if the authentication is finished for a given
+ avatarId.
+
+ @param avatarId: the avatar returned by the first checker. For
+ this checker to function correctly, all the checkers must
+ return the same avatar ID.
+ """
+ return True
+
+
+
+deprecatedModuleAttribute(
+ Version("Twisted", 15, 0, 0),
+ ("Please use twisted.conch.checkers.SSHPublicKeyChecker, "
+ "initialized with an instance of "
+ "twisted.conch.checkers.UNIXAuthorizedKeysFiles instead."),
+ __name__, "SSHPublicKeyDatabase")
+
+
+
+class IAuthorizedKeysDB(Interface):
+ """
+ An object that provides valid authorized ssh keys mapped to usernames.
+
+ @since: 15.0
+ """
+ def getAuthorizedKeys(avatarId):
+ """
+ Gets an iterable of authorized keys that are valid for the given
+ C{avatarId}.
+
+ @param avatarId: the ID of the avatar
+ @type avatarId: valid return value of
+ L{twisted.cred.checkers.ICredentialsChecker.requestAvatarId}
+
+ @return: an iterable of L{twisted.conch.ssh.keys.Key}
+ """
+
+
+
+def readAuthorizedKeyFile(fileobj, parseKey=keys.Key.fromString):
+ """
+ Reads keys from an authorized keys file. Any non-comment line that cannot
+ be parsed as a key will be ignored, although that particular line will
+ be logged.
+
+ @param fileobj: something from which to read lines which can be parsed
+ as keys
+ @type fileobj: L{file}-like object
+
+ @param parseKey: a callable that takes a string and returns a
+ L{twisted.conch.ssh.keys.Key}, mainly to be used for testing. The
+ default is L{twisted.conch.ssh.keys.Key.fromString}.
+ @type parseKey: L{callable}
+
+ @return: an iterable of L{twisted.conch.ssh.keys.Key}
+ @rtype: iterable
+
+ @since: 15.0
+ """
+ for line in fileobj:
+ line = line.strip()
+ if line and not line.startswith(b'#'): # for comments
+ try:
+ yield parseKey(line)
+ except keys.BadKeyError as e:
+ log.msg('Unable to parse line "{0}" as a key: {1!s}'
+ .format(line, e))
+
+
+
+def _keysFromFilepaths(filepaths, parseKey):
+ """
+ Helper function that turns an iterable of filepaths into a generator of
+ keys. If any file cannot be read, a message is logged but it is
+ otherwise ignored.
+
+ @param filepaths: iterable of L{twisted.python.filepath.FilePath}.
+ @type filepaths: iterable
+
+ @param parseKey: a callable that takes a string and returns a
+ L{twisted.conch.ssh.keys.Key}
+ @type parseKey: L{callable}
+
+ @return: generator of L{twisted.conch.ssh.keys.Key}
+ @rtype: generator
+
+ @since: 15.0
+ """
+ for fp in filepaths:
+ if fp.exists():
+ try:
+ with fp.open() as f:
+ for key in readAuthorizedKeyFile(f, parseKey):
+ yield key
+ except (IOError, OSError) as e:
+ log.msg("Unable to read {0}: {1!s}".format(fp.path, e))
+
+
+
+@implementer(IAuthorizedKeysDB)
+class InMemorySSHKeyDB(object):
+ """
+ Object that provides SSH public keys based on a dictionary of usernames
+ mapped to L{twisted.conch.ssh.keys.Key}s.
+
+ @since: 15.0
+ """
+ def __init__(self, mapping):
+ """
+ Initializes a new L{InMemorySSHKeyDB}.
+
+ @param mapping: mapping of usernames to iterables of
+ L{twisted.conch.ssh.keys.Key}s
+ @type mapping: L{dict}
+
+ """
+ self._mapping = mapping
+
+
+ def getAuthorizedKeys(self, username):
+ return self._mapping.get(username, [])
+
+
+
+@implementer(IAuthorizedKeysDB)
+class UNIXAuthorizedKeysFiles(object):
+ """
+ Object that provides SSH public keys based on public keys listed in
+ authorized_keys and authorized_keys2 files in UNIX user .ssh/ directories.
+ If any of the files cannot be read, a message is logged but that file is
+ otherwise ignored.
+
+ @since: 15.0
+ """
+ def __init__(self, userdb=None, parseKey=keys.Key.fromString):
+ """
+ Initializes a new L{UNIXAuthorizedKeysFiles}.
+
+ @param userdb: access to the Unix user account and password database
+ (default is the Python module L{pwd})
+ @type userdb: L{pwd}-like object
+
+ @param parseKey: a callable that takes a string and returns a
+ L{twisted.conch.ssh.keys.Key}, mainly to be used for testing. The
+ default is L{twisted.conch.ssh.keys.Key.fromString}.
+ @type parseKey: L{callable}
+ """
+ self._userdb = userdb
+ self._parseKey = parseKey
+ if userdb is None:
+ self._userdb = pwd
+
+
+ def getAuthorizedKeys(self, username):
+ try:
+ passwd = self._userdb.getpwnam(username)
+ except KeyError:
+ return ()
+
+ root = FilePath(passwd.pw_dir).child('.ssh')
+ files = ['authorized_keys', 'authorized_keys2']
+ return _keysFromFilepaths((root.child(f) for f in files),
+ self._parseKey)
+
+
+
+@implementer(ICredentialsChecker)
+class SSHPublicKeyChecker(object):
+ """
+ Checker that authenticates SSH public keys, based on public keys listed in
+ authorized_keys and authorized_keys2 files in user .ssh/ directories.
+
+ Initializing this checker with a L{UNIXAuthorizedKeysFiles} should be
+ used instead of L{twisted.conch.checkers.SSHPublicKeyDatabase}.
+
+ @since: 15.0
+ """
+ credentialInterfaces = (ISSHPrivateKey,)
+
+ def __init__(self, keydb):
+ """
+ Initializes a L{SSHPublicKeyChecker}.
+
+ @param keydb: a provider of L{IAuthorizedKeysDB}
+ @type keydb: L{IAuthorizedKeysDB} provider
+ """
+ self._keydb = keydb
+
+
+ def requestAvatarId(self, credentials):
+ d = defer.maybeDeferred(self._sanityCheckKey, credentials)
+ d.addCallback(self._checkKey, credentials)
+ d.addCallback(self._verifyKey, credentials)
+ return d
+
+
+ def _sanityCheckKey(self, credentials):
+ """
+ Checks whether the provided credentials are a valid SSH key with a
+ signature (does not actually verify the signature).
+
+ @param credentials: the credentials offered by the user
+ @type credentials: L{ISSHPrivateKey} provider
+
+ @raise ValidPublicKey: the credentials do not include a signature. See
+ L{error.ValidPublicKey} for more information.
+
+ @raise BadKeyError: The key included with the credentials is not
+ recognized as a key.
+
+ @return: the key in the credentials
+ @rtype: L{twisted.conch.ssh.keys.Key}
+ """
+ if not credentials.signature:
+ raise error.ValidPublicKey()
+
+ return keys.Key.fromString(credentials.blob)
+
+
+ def _checkKey(self, pubKey, credentials):
+ """
+ Checks the public key against all authorized keys (if any) for the
+ user.
+
+ @param pubKey: the key in the credentials (just to prevent it from
+ having to be calculated again)
+ @type pubKey:
+
+ @param credentials: the credentials offered by the user
+ @type credentials: L{ISSHPrivateKey} provider
+
+ @raise UnauthorizedLogin: If the key is not authorized, or if there
+ was any error obtaining a list of authorized keys for the user.
+
+ @return: C{pubKey} if the key is authorized
+ @rtype: L{twisted.conch.ssh.keys.Key}
+ """
+ if any(key == pubKey for key in
+ self._keydb.getAuthorizedKeys(credentials.username)):
+ return pubKey
+
+ raise UnauthorizedLogin("Key not authorized")
+
+
+ def _verifyKey(self, pubKey, credentials):
+ """
+ Checks whether the credentials themselves are valid, now that we know
+ if the key matches the user.
+
+ @param pubKey: the key in the credentials (just to prevent it from
+ having to be calculated again)
+ @type pubKey: L{twisted.conch.ssh.keys.Key}
+
+ @param credentials: the credentials offered by the user
+ @type credentials: L{ISSHPrivateKey} provider
+
+ @raise UnauthorizedLogin: If the key signature is invalid or there
+ was any error verifying the signature.
+
+ @return: The user's username, if authentication was successful
+ @rtype: L{bytes}
+ """
+ try:
+ if pubKey.verify(credentials.signature, credentials.sigData):
+ return credentials.username
+ except: # Any error should be treated as a failed login
+ log.err()
+ raise UnauthorizedLogin('Error while verifying key')
+
+ raise UnauthorizedLogin("Key signature invalid.")
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']
diff --git a/contrib/python/Twisted/py2/twisted/conch/endpoints.py b/contrib/python/Twisted/py2/twisted/conch/endpoints.py
new file mode 100644
index 0000000000..2e19d0870f
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/endpoints.py
@@ -0,0 +1,872 @@
+# -*- test-case-name: twisted.conch.test.test_endpoints -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Endpoint implementations of various SSH interactions.
+"""
+
+__all__ = [
+ 'AuthenticationFailed', 'SSHCommandAddress', 'SSHCommandClientEndpoint']
+
+from struct import unpack
+from os.path import expanduser
+
+import signal
+
+from zope.interface import Interface, implementer
+
+from twisted.logger import Logger
+from twisted.python.compat import nativeString, networkString
+from twisted.python.filepath import FilePath
+from twisted.python.failure import Failure
+from twisted.internet.error import ConnectionDone, ProcessTerminated
+from twisted.internet.interfaces import IStreamClientEndpoint
+from twisted.internet.protocol import Factory
+from twisted.internet.defer import Deferred, succeed, CancelledError
+from twisted.internet.endpoints import TCP4ClientEndpoint, connectProtocol
+
+from twisted.conch.ssh.keys import Key
+from twisted.conch.ssh.common import getNS, NS
+from twisted.conch.ssh.transport import SSHClientTransport
+from twisted.conch.ssh.connection import SSHConnection
+from twisted.conch.ssh.userauth import SSHUserAuthClient
+from twisted.conch.ssh.channel import SSHChannel
+from twisted.conch.client.knownhosts import ConsoleUI, KnownHostsFile
+from twisted.conch.client.agent import SSHAgentClient
+from twisted.conch.client.default import _KNOWN_HOSTS
+
+
+class AuthenticationFailed(Exception):
+ """
+ An SSH session could not be established because authentication was not
+ successful.
+ """
+
+
+
+# This should be public. See #6541.
+class _ISSHConnectionCreator(Interface):
+ """
+ An L{_ISSHConnectionCreator} knows how to create SSH connections somehow.
+ """
+ def secureConnection():
+ """
+ Return a new, connected, secured, but not yet authenticated instance of
+ L{twisted.conch.ssh.transport.SSHServerTransport} or
+ L{twisted.conch.ssh.transport.SSHClientTransport}.
+ """
+
+
+ def cleanupConnection(connection, immediate):
+ """
+ Perform cleanup necessary for a connection object previously returned
+ from this creator's C{secureConnection} method.
+
+ @param connection: An L{twisted.conch.ssh.transport.SSHServerTransport}
+ or L{twisted.conch.ssh.transport.SSHClientTransport} returned by a
+ previous call to C{secureConnection}. It is no longer needed by
+ the caller of that method and may be closed or otherwise cleaned up
+ as necessary.
+
+ @param immediate: If C{True} don't wait for any network communication,
+ just close the connection immediately and as aggressively as
+ necessary.
+ """
+
+
+
+class SSHCommandAddress(object):
+ """
+ An L{SSHCommandAddress} instance represents the address of an SSH server, a
+ username which was used to authenticate with that server, and a command
+ which was run there.
+
+ @ivar server: See L{__init__}
+ @ivar username: See L{__init__}
+ @ivar command: See L{__init__}
+ """
+ def __init__(self, server, username, command):
+ """
+ @param server: The address of the SSH server on which the command is
+ running.
+ @type server: L{IAddress} provider
+
+ @param username: An authentication username which was used to
+ authenticate against the server at the given address.
+ @type username: L{bytes}
+
+ @param command: A command which was run in a session channel on the
+ server at the given address.
+ @type command: L{bytes}
+ """
+ self.server = server
+ self.username = username
+ self.command = command
+
+
+
+class _CommandChannel(SSHChannel):
+ """
+ A L{_CommandChannel} executes a command in a session channel and connects
+ its input and output to an L{IProtocol} provider.
+
+ @ivar _creator: See L{__init__}
+ @ivar _command: See L{__init__}
+ @ivar _protocolFactory: See L{__init__}
+ @ivar _commandConnected: See L{__init__}
+ @ivar _protocol: An L{IProtocol} provider created using C{_protocolFactory}
+ which is hooked up to the running command's input and output streams.
+ """
+ name = b'session'
+ _log = Logger()
+
+ def __init__(self, creator, command, protocolFactory, commandConnected):
+ """
+ @param creator: The L{_ISSHConnectionCreator} provider which was used
+ to get the connection which this channel exists on.
+ @type creator: L{_ISSHConnectionCreator} provider
+
+ @param command: The command to be executed.
+ @type command: L{bytes}
+
+ @param protocolFactory: A client factory to use to build a L{IProtocol}
+ provider to use to associate with the running command.
+
+ @param commandConnected: A L{Deferred} to use to signal that execution
+ of the command has failed or that it has succeeded and the command
+ is now running.
+ @type commandConnected: L{Deferred}
+ """
+ SSHChannel.__init__(self)
+ self._creator = creator
+ self._command = command
+ self._protocolFactory = protocolFactory
+ self._commandConnected = commandConnected
+ self._reason = None
+
+
+ def openFailed(self, reason):
+ """
+ When the request to open a new channel to run this command in fails,
+ fire the C{commandConnected} deferred with a failure indicating that.
+ """
+ self._commandConnected.errback(reason)
+
+
+ def channelOpen(self, ignored):
+ """
+ When the request to open a new channel to run this command in succeeds,
+ issue an C{"exec"} request to run the command.
+ """
+ command = self.conn.sendRequest(
+ self, b'exec', NS(self._command), wantReply=True)
+ command.addCallbacks(self._execSuccess, self._execFailure)
+
+
+ def _execFailure(self, reason):
+ """
+ When the request to execute the command in this channel fails, fire the
+ C{commandConnected} deferred with a failure indicating this.
+
+ @param reason: The cause of the command execution failure.
+ @type reason: L{Failure}
+ """
+ self._commandConnected.errback(reason)
+
+
+ def _execSuccess(self, ignored):
+ """
+ When the request to execute the command in this channel succeeds, use
+ C{protocolFactory} to build a protocol to handle the command's input
+ and output and connect the protocol to a transport representing those
+ streams.
+
+ Also fire C{commandConnected} with the created protocol after it is
+ connected to its transport.
+
+ @param ignored: The (ignored) result of the execute request
+ """
+ self._protocol = self._protocolFactory.buildProtocol(
+ SSHCommandAddress(
+ self.conn.transport.transport.getPeer(),
+ self.conn.transport.creator.username,
+ self.conn.transport.creator.command))
+ self._protocol.makeConnection(self)
+ self._commandConnected.callback(self._protocol)
+
+
+ def dataReceived(self, data):
+ """
+ When the command's stdout data arrives over the channel, deliver it to
+ the protocol instance.
+
+ @param data: The bytes from the command's stdout.
+ @type data: L{bytes}
+ """
+ self._protocol.dataReceived(data)
+
+
+ def request_exit_status(self, data):
+ """
+ When the server sends the command's exit status, record it for later
+ delivery to the protocol.
+
+ @param data: The network-order four byte representation of the exit
+ status of the command.
+ @type data: L{bytes}
+ """
+ (status,) = unpack('>L', data)
+ if status != 0:
+ self._reason = ProcessTerminated(status, None, None)
+
+
+ def request_exit_signal(self, data):
+ """
+ When the server sends the command's exit status, record it for later
+ delivery to the protocol.
+
+ @param data: The network-order four byte representation of the exit
+ signal of the command.
+ @type data: L{bytes}
+ """
+ shortSignalName, data = getNS(data)
+ coreDumped, data = bool(ord(data[0:1])), data[1:]
+ errorMessage, data = getNS(data)
+ languageTag, data = getNS(data)
+ signalName = "SIG%s" % (nativeString(shortSignalName),)
+ signalID = getattr(signal, signalName, -1)
+ self._log.info(
+ "Process exited with signal {shortSignalName!r};"
+ " core dumped: {coreDumped};"
+ " error message: {errorMessage};"
+ " language: {languageTag!r}",
+ shortSignalName=shortSignalName,
+ coreDumped=coreDumped,
+ errorMessage=errorMessage.decode('utf-8'),
+ languageTag=languageTag,
+ )
+ self._reason = ProcessTerminated(None, signalID, None)
+
+
+ def closed(self):
+ """
+ When the channel closes, deliver disconnection notification to the
+ protocol.
+ """
+ self._creator.cleanupConnection(self.conn, False)
+ if self._reason is None:
+ reason = ConnectionDone("ssh channel closed")
+ else:
+ reason = self._reason
+ self._protocol.connectionLost(Failure(reason))
+
+
+
+class _ConnectionReady(SSHConnection):
+ """
+ L{_ConnectionReady} is an L{SSHConnection} (an SSH service) which only
+ propagates the I{serviceStarted} event to a L{Deferred} to be handled
+ elsewhere.
+ """
+ def __init__(self, ready):
+ """
+ @param ready: A L{Deferred} which should be fired when
+ I{serviceStarted} happens.
+ """
+ SSHConnection.__init__(self)
+ self._ready = ready
+
+
+ def serviceStarted(self):
+ """
+ When the SSH I{connection} I{service} this object represents is ready
+ to be used, fire the C{connectionReady} L{Deferred} to publish that
+ event to some other interested party.
+
+ """
+ self._ready.callback(self)
+ del self._ready
+
+
+
+class _UserAuth(SSHUserAuthClient):
+ """
+ L{_UserAuth} implements the client part of SSH user authentication in the
+ convenient way a user might expect if they are familiar with the
+ interactive I{ssh} command line client.
+
+ L{_UserAuth} supports key-based authentication, password-based
+ authentication, and delegating authentication to an agent.
+ """
+ password = None
+ keys = None
+ agent = None
+
+ def getPublicKey(self):
+ """
+ Retrieve the next public key object to offer to the server, possibly
+ delegating to an authentication agent if there is one.
+
+ @return: The public part of a key pair that could be used to
+ authenticate with the server, or L{None} if there are no more
+ public keys to try.
+ @rtype: L{twisted.conch.ssh.keys.Key} or L{None}
+ """
+ if self.agent is not None:
+ return self.agent.getPublicKey()
+
+ if self.keys:
+ self.key = self.keys.pop(0)
+ else:
+ self.key = None
+ return self.key.public()
+
+
+ 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{str}
+ """
+ if self.agent is not None:
+ return self.agent.signData(publicKey.blob(), signData)
+ else:
+ return SSHUserAuthClient.signData(self, publicKey, signData)
+
+
+ def getPrivateKey(self):
+ """
+ Get the private part of a key pair to use for authentication. The key
+ corresponds to the public part most recently returned from
+ C{getPublicKey}.
+
+ @return: A L{Deferred} which fires with the private key.
+ @rtype: L{Deferred}
+ """
+ return succeed(self.key)
+
+
+ def getPassword(self):
+ """
+ Get the password to use for authentication.
+
+ @return: A L{Deferred} which fires with the password, or L{None} if the
+ password was not specified.
+ """
+ if self.password is None:
+ return
+ return succeed(self.password)
+
+
+ def ssh_USERAUTH_SUCCESS(self, packet):
+ """
+ Handle user authentication success in the normal way, but also make a
+ note of the state change on the L{_CommandTransport}.
+ """
+ self.transport._state = b'CHANNELLING'
+ return SSHUserAuthClient.ssh_USERAUTH_SUCCESS(self, packet)
+
+
+ def connectToAgent(self, endpoint):
+ """
+ Set up a connection to the authentication agent and trigger its
+ initialization.
+
+ @param endpoint: An endpoint which can be used to connect to the
+ authentication agent.
+ @type endpoint: L{IStreamClientEndpoint} provider
+
+ @return: A L{Deferred} which fires when the agent connection is ready
+ for use.
+ """
+ factory = Factory()
+ factory.protocol = SSHAgentClient
+ d = endpoint.connect(factory)
+ def connected(agent):
+ self.agent = agent
+ return agent.getPublicKeys()
+ d.addCallback(connected)
+ return d
+
+
+ def loseAgentConnection(self):
+ """
+ Disconnect the agent.
+ """
+ if self.agent is None:
+ return
+ self.agent.transport.loseConnection()
+
+
+
+class _CommandTransport(SSHClientTransport):
+ """
+ L{_CommandTransport} is an SSH client I{transport} which includes a host
+ key verification step before it will proceed to secure the connection.
+
+ L{_CommandTransport} also knows how to set up a connection to an
+ authentication agent if it is told where it can connect to one.
+
+ @ivar _userauth: The L{_UserAuth} instance which is in charge of the
+ overall authentication process or L{None} if the SSH connection has not
+ reach yet the C{user-auth} service.
+ @type _userauth: L{_UserAuth}
+ """
+ # STARTING -> SECURING -> AUTHENTICATING -> CHANNELLING -> RUNNING
+ _state = b'STARTING'
+
+ _hostKeyFailure = None
+
+ _userauth = None
+
+
+ def __init__(self, creator):
+ """
+ @param creator: The L{_NewConnectionHelper} that created this
+ connection.
+
+ @type creator: L{_NewConnectionHelper}.
+ """
+ self.connectionReady = Deferred(
+ lambda d: self.transport.abortConnection())
+ # Clear the reference to that deferred to help the garbage collector
+ # and to signal to other parts of this implementation (in particular
+ # connectionLost) that it has already been fired and does not need to
+ # be fired again.
+ def readyFired(result):
+ self.connectionReady = None
+ return result
+ self.connectionReady.addBoth(readyFired)
+ self.creator = creator
+
+
+ def verifyHostKey(self, hostKey, fingerprint):
+ """
+ Ask the L{KnownHostsFile} provider available on the factory which
+ created this protocol this protocol to verify the given host key.
+
+ @return: A L{Deferred} which fires with the result of
+ L{KnownHostsFile.verifyHostKey}.
+ """
+ hostname = self.creator.hostname
+ ip = networkString(self.transport.getPeer().host)
+
+ self._state = b'SECURING'
+ d = self.creator.knownHosts.verifyHostKey(
+ self.creator.ui, hostname, ip, Key.fromString(hostKey))
+ d.addErrback(self._saveHostKeyFailure)
+ return d
+
+
+ def _saveHostKeyFailure(self, reason):
+ """
+ When host key verification fails, record the reason for the failure in
+ order to fire a L{Deferred} with it later.
+
+ @param reason: The cause of the host key verification failure.
+ @type reason: L{Failure}
+
+ @return: C{reason}
+ @rtype: L{Failure}
+ """
+ self._hostKeyFailure = reason
+ return reason
+
+
+ def connectionSecure(self):
+ """
+ When the connection is secure, start the authentication process.
+ """
+ self._state = b'AUTHENTICATING'
+
+ command = _ConnectionReady(self.connectionReady)
+
+ self._userauth = _UserAuth(self.creator.username, command)
+ self._userauth.password = self.creator.password
+ if self.creator.keys:
+ self._userauth.keys = list(self.creator.keys)
+
+ if self.creator.agentEndpoint is not None:
+ d = self._userauth.connectToAgent(self.creator.agentEndpoint)
+ else:
+ d = succeed(None)
+
+ def maybeGotAgent(ignored):
+ self.requestService(self._userauth)
+ d.addBoth(maybeGotAgent)
+
+
+ def connectionLost(self, reason):
+ """
+ When the underlying connection to the SSH server is lost, if there were
+ any connection setup errors, propagate them. Also, clean up the
+ connection to the ssh agent if one was created.
+ """
+ if self._userauth:
+ self._userauth.loseAgentConnection()
+
+ if self._state == b'RUNNING' or self.connectionReady is None:
+ return
+ if self._state == b'SECURING' and self._hostKeyFailure is not None:
+ reason = self._hostKeyFailure
+ elif self._state == b'AUTHENTICATING':
+ reason = Failure(
+ AuthenticationFailed("Connection lost while authenticating"))
+ self.connectionReady.errback(reason)
+
+
+
+@implementer(IStreamClientEndpoint)
+class SSHCommandClientEndpoint(object):
+ """
+ L{SSHCommandClientEndpoint} exposes the command-executing functionality of
+ SSH servers.
+
+ L{SSHCommandClientEndpoint} can set up a new SSH connection, authenticate
+ it in any one of a number of different ways (keys, passwords, agents),
+ launch a command over that connection and then associate its input and
+ output with a protocol.
+
+ It can also re-use an existing, already-authenticated SSH connection
+ (perhaps one which already has some SSH channels being used for other
+ purposes). In this case it creates a new SSH channel to use to execute the
+ command. Notably this means it supports multiplexing several different
+ command invocations over a single SSH connection.
+ """
+
+ def __init__(self, creator, command):
+ """
+ @param creator: An L{_ISSHConnectionCreator} provider which will be
+ used to set up the SSH connection which will be used to run a
+ command.
+ @type creator: L{_ISSHConnectionCreator} provider
+
+ @param command: The command line to execute on the SSH server. This
+ byte string is interpreted by a shell on the SSH server, so it may
+ have a value like C{"ls /"}. Take care when trying to run a
+ command like C{"/Volumes/My Stuff/a-program"} - spaces (and other
+ special bytes) may require escaping.
+ @type command: L{bytes}
+
+ """
+ self._creator = creator
+ self._command = command
+
+
+ @classmethod
+ def newConnection(cls, reactor, command, username, hostname, port=None,
+ keys=None, password=None, agentEndpoint=None,
+ knownHosts=None, ui=None):
+ """
+ Create and return a new endpoint which will try to create a new
+ connection to an SSH server and run a command over it. It will also
+ close the connection if there are problems leading up to the command
+ being executed, after the command finishes, or if the connection
+ L{Deferred} is cancelled.
+
+ @param reactor: The reactor to use to establish the connection.
+ @type reactor: L{IReactorTCP} provider
+
+ @param command: See L{__init__}'s C{command} argument.
+
+ @param username: The username with which to authenticate to the SSH
+ server.
+ @type username: L{bytes}
+
+ @param hostname: The hostname of the SSH server.
+ @type hostname: L{bytes}
+
+ @param port: The port number of the SSH server. By default, the
+ standard SSH port number is used.
+ @type port: L{int}
+
+ @param keys: Private keys with which to authenticate to the SSH server,
+ if key authentication is to be attempted (otherwise L{None}).
+ @type keys: L{list} of L{Key}
+
+ @param password: The password with which to authenticate to the SSH
+ server, if password authentication is to be attempted (otherwise
+ L{None}).
+ @type password: L{bytes} or L{None}
+
+ @param agentEndpoint: An L{IStreamClientEndpoint} provider which may be
+ used to connect to an SSH agent, if one is to be used to help with
+ authentication.
+ @type agentEndpoint: L{IStreamClientEndpoint} provider
+
+ @param knownHosts: The currently known host keys, used to check the
+ host key presented by the server we actually connect to.
+ @type knownHosts: L{KnownHostsFile}
+
+ @param ui: An object for interacting with users to make decisions about
+ whether to accept the server host keys. If L{None}, a L{ConsoleUI}
+ connected to /dev/tty will be used; if /dev/tty is unavailable, an
+ object which answers C{b"no"} to all prompts will be used.
+ @type ui: L{None} or L{ConsoleUI}
+
+ @return: A new instance of C{cls} (probably
+ L{SSHCommandClientEndpoint}).
+ """
+ helper = _NewConnectionHelper(
+ reactor, hostname, port, command, username, keys, password,
+ agentEndpoint, knownHosts, ui)
+ return cls(helper, command)
+
+
+ @classmethod
+ def existingConnection(cls, connection, command):
+ """
+ Create and return a new endpoint which will try to open a new channel
+ on an existing SSH connection and run a command over it. It will
+ B{not} close the connection if there is a problem executing the command
+ or after the command finishes.
+
+ @param connection: An existing connection to an SSH server.
+ @type connection: L{SSHConnection}
+
+ @param command: See L{SSHCommandClientEndpoint.newConnection}'s
+ C{command} parameter.
+ @type command: L{bytes}
+
+ @return: A new instance of C{cls} (probably
+ L{SSHCommandClientEndpoint}).
+ """
+ helper = _ExistingConnectionHelper(connection)
+ return cls(helper, command)
+
+
+ def connect(self, protocolFactory):
+ """
+ Set up an SSH connection, use a channel from that connection to launch
+ a command, and hook the stdin and stdout of that command up as a
+ transport for a protocol created by the given factory.
+
+ @param protocolFactory: A L{Factory} to use to create the protocol
+ which will be connected to the stdin and stdout of the command on
+ the SSH server.
+
+ @return: A L{Deferred} which will fire with an error if the connection
+ cannot be set up for any reason or with the protocol instance
+ created by C{protocolFactory} once it has been connected to the
+ command.
+ """
+ d = self._creator.secureConnection()
+ d.addCallback(self._executeCommand, protocolFactory)
+ return d
+
+
+ def _executeCommand(self, connection, protocolFactory):
+ """
+ Given a secured SSH connection, try to execute a command in a new
+ channel created on it and associate the result with a protocol from the
+ given factory.
+
+ @param connection: See L{SSHCommandClientEndpoint.existingConnection}'s
+ C{connection} parameter.
+
+ @param protocolFactory: See L{SSHCommandClientEndpoint.connect}'s
+ C{protocolFactory} parameter.
+
+ @return: See L{SSHCommandClientEndpoint.connect}'s return value.
+ """
+ commandConnected = Deferred()
+
+ def disconnectOnFailure(passthrough):
+ # Close the connection immediately in case of cancellation, since
+ # that implies user wants it gone immediately (e.g. a timeout):
+ immediate = passthrough.check(CancelledError)
+ self._creator.cleanupConnection(connection, immediate)
+ return passthrough
+ commandConnected.addErrback(disconnectOnFailure)
+
+ channel = _CommandChannel(
+ self._creator, self._command, protocolFactory, commandConnected)
+ connection.openChannel(channel)
+ return commandConnected
+
+
+
+class _ReadFile(object):
+ """
+ A weakly file-like object which can be used with L{KnownHostsFile} to
+ respond in the negative to all prompts for decisions.
+ """
+ def __init__(self, contents):
+ """
+ @param contents: L{bytes} which will be returned from every C{readline}
+ call.
+ """
+ self._contents = contents
+
+
+ def write(self, data):
+ """
+ No-op.
+
+ @param data: ignored
+ """
+
+
+ def readline(self, count=-1):
+ """
+ Always give back the byte string that this L{_ReadFile} was initialized
+ with.
+
+ @param count: ignored
+
+ @return: A fixed byte-string.
+ @rtype: L{bytes}
+ """
+ return self._contents
+
+
+ def close(self):
+ """
+ No-op.
+ """
+
+
+
+@implementer(_ISSHConnectionCreator)
+class _NewConnectionHelper(object):
+ """
+ L{_NewConnectionHelper} implements L{_ISSHConnectionCreator} by
+ establishing a brand new SSH connection, securing it, and authenticating.
+ """
+ _KNOWN_HOSTS = _KNOWN_HOSTS
+ port = 22
+
+ def __init__(self, reactor, hostname, port, command, username, keys,
+ password, agentEndpoint, knownHosts, ui,
+ tty=FilePath(b"/dev/tty")):
+ """
+ @param tty: The path of the tty device to use in case C{ui} is L{None}.
+ @type tty: L{FilePath}
+
+ @see: L{SSHCommandClientEndpoint.newConnection}
+ """
+ self.reactor = reactor
+ self.hostname = hostname
+ if port is not None:
+ self.port = port
+ self.command = command
+ self.username = username
+ self.keys = keys
+ self.password = password
+ self.agentEndpoint = agentEndpoint
+ if knownHosts is None:
+ knownHosts = self._knownHosts()
+ self.knownHosts = knownHosts
+
+ if ui is None:
+ ui = ConsoleUI(self._opener)
+ self.ui = ui
+ self.tty = tty
+
+
+ def _opener(self):
+ """
+ Open the tty if possible, otherwise give back a file-like object from
+ which C{b"no"} can be read.
+
+ For use as the opener argument to L{ConsoleUI}.
+ """
+ try:
+ return self.tty.open("rb+")
+ except:
+ # Give back a file-like object from which can be read a byte string
+ # that KnownHostsFile recognizes as rejecting some option (b"no").
+ return _ReadFile(b"no")
+
+
+ @classmethod
+ def _knownHosts(cls):
+ """
+
+ @return: A L{KnownHostsFile} instance pointed at the user's personal
+ I{known hosts} file.
+ @type: L{KnownHostsFile}
+ """
+ return KnownHostsFile.fromPath(FilePath(expanduser(cls._KNOWN_HOSTS)))
+
+
+ def secureConnection(self):
+ """
+ Create and return a new SSH connection which has been secured and on
+ which authentication has already happened.
+
+ @return: A L{Deferred} which fires with the ready-to-use connection or
+ with a failure if something prevents the connection from being
+ setup, secured, or authenticated.
+ """
+ protocol = _CommandTransport(self)
+ ready = protocol.connectionReady
+
+ sshClient = TCP4ClientEndpoint(
+ self.reactor, nativeString(self.hostname), self.port)
+
+ d = connectProtocol(sshClient, protocol)
+ d.addCallback(lambda ignored: ready)
+ return d
+
+
+ def cleanupConnection(self, connection, immediate):
+ """
+ Clean up the connection by closing it. The command running on the
+ endpoint has ended so the connection is no longer needed.
+
+ @param connection: The L{SSHConnection} to close.
+ @type connection: L{SSHConnection}
+
+ @param immediate: Whether to close connection immediately.
+ @type immediate: L{bool}.
+ """
+ if immediate:
+ # We're assuming the underlying connection is an ITCPTransport,
+ # which is what the current implementation is restricted to:
+ connection.transport.transport.abortConnection()
+ else:
+ connection.transport.loseConnection()
+
+
+
+@implementer(_ISSHConnectionCreator)
+class _ExistingConnectionHelper(object):
+ """
+ L{_ExistingConnectionHelper} implements L{_ISSHConnectionCreator} by
+ handing out an existing SSH connection which is supplied to its
+ initializer.
+ """
+
+ def __init__(self, connection):
+ """
+ @param connection: See L{SSHCommandClientEndpoint.existingConnection}'s
+ C{connection} parameter.
+ """
+ self.connection = connection
+
+
+ def secureConnection(self):
+ """
+
+ @return: A L{Deferred} that fires synchronously with the
+ already-established connection object.
+ """
+ return succeed(self.connection)
+
+
+ def cleanupConnection(self, connection, immediate):
+ """
+ Do not do any cleanup on the connection. Leave that responsibility to
+ whatever code created it in the first place.
+
+ @param connection: The L{SSHConnection} which will not be modified in
+ any way.
+ @type connection: L{SSHConnection}
+
+ @param immediate: An argument which will be ignored.
+ @type immediate: L{bool}.
+ """
diff --git a/contrib/python/Twisted/py2/twisted/conch/error.py b/contrib/python/Twisted/py2/twisted/conch/error.py
new file mode 100644
index 0000000000..c8297c3964
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/error.py
@@ -0,0 +1,103 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+An error to represent bad things happening in Conch.
+
+Maintainer: Paul Swartz
+"""
+
+from __future__ import absolute_import, division
+
+from twisted.cred.error import UnauthorizedLogin
+
+
+class ConchError(Exception):
+ def __init__(self, value, data = None):
+ Exception.__init__(self, value, data)
+ self.value = value
+ self.data = data
+
+
+
+class NotEnoughAuthentication(Exception):
+ """
+ This is thrown if the authentication is valid, but is not enough to
+ successfully verify the user. i.e. don't retry this type of
+ authentication, try another one.
+ """
+
+
+
+class ValidPublicKey(UnauthorizedLogin):
+ """
+ Raised by public key checkers when they receive public key credentials
+ that don't contain a signature at all, but are valid in every other way.
+ (e.g. the public key matches one in the user's authorized_keys file).
+
+ Protocol code (eg
+ L{SSHUserAuthServer<twisted.conch.ssh.userauth.SSHUserAuthServer>}) which
+ attempts to log in using
+ L{ISSHPrivateKey<twisted.cred.credentials.ISSHPrivateKey>} credentials
+ should be prepared to handle a failure of this type by telling the user to
+ re-authenticate using the same key and to include a signature with the new
+ attempt.
+
+ See U{http://www.ietf.org/rfc/rfc4252.txt} section 7 for more details.
+ """
+
+
+
+class IgnoreAuthentication(Exception):
+ """
+ This is thrown to let the UserAuthServer know it doesn't need to handle the
+ authentication anymore.
+ """
+
+
+
+class MissingKeyStoreError(Exception):
+ """
+ Raised if an SSHAgentServer starts receiving data without its factory
+ providing a keys dict on which to read/write key data.
+ """
+
+
+
+class UserRejectedKey(Exception):
+ """
+ The user interactively rejected a key.
+ """
+
+
+
+class InvalidEntry(Exception):
+ """
+ An entry in a known_hosts file could not be interpreted as a valid entry.
+ """
+
+
+
+class HostKeyChanged(Exception):
+ """
+ The host key of a remote host has changed.
+
+ @ivar offendingEntry: The entry which contains the persistent host key that
+ disagrees with the given host key.
+
+ @type offendingEntry: L{twisted.conch.interfaces.IKnownHostEntry}
+
+ @ivar path: a reference to the known_hosts file that the offending entry
+ was loaded from
+
+ @type path: L{twisted.python.filepath.FilePath}
+
+ @ivar lineno: The line number of the offending entry in the given path.
+
+ @type lineno: L{int}
+ """
+ def __init__(self, offendingEntry, path, lineno):
+ Exception.__init__(self)
+ self.offendingEntry = offendingEntry
+ self.path = path
+ self.lineno = lineno
diff --git a/contrib/python/Twisted/py2/twisted/conch/insults/__init__.py b/contrib/python/Twisted/py2/twisted/conch/insults/__init__.py
new file mode 100644
index 0000000000..3d83876698
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/insults/__init__.py
@@ -0,0 +1,4 @@
+"""
+Insults: a replacement for Curses/S-Lang.
+
+Very basic at the moment."""
diff --git a/contrib/python/Twisted/py2/twisted/conch/insults/helper.py b/contrib/python/Twisted/py2/twisted/conch/insults/helper.py
new file mode 100644
index 0000000000..0485bfdbe6
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/insults/helper.py
@@ -0,0 +1,517 @@
+# -*- test-case-name: twisted.conch.test.test_helper -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Partial in-memory terminal emulator
+
+@author: Jp Calderone
+"""
+
+from __future__ import print_function
+
+import re, string
+
+from zope.interface import implementer
+
+from incremental import Version
+
+from twisted.internet import defer, protocol, reactor
+from twisted.python import log, _textattributes
+from twisted.python.compat import iterbytes
+from twisted.python.deprecate import deprecated, deprecatedModuleAttribute
+from twisted.conch.insults import insults
+
+FOREGROUND = 30
+BACKGROUND = 40
+BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, N_COLORS = range(9)
+
+
+
+class _FormattingState(_textattributes._FormattingStateMixin):
+ """
+ Represents the formatting state/attributes of a single character.
+
+ Character set, intensity, underlinedness, blinkitude, video
+ reversal, as well as foreground and background colors made up a
+ character's attributes.
+ """
+ compareAttributes = (
+ 'charset', 'bold', 'underline', 'blink', 'reverseVideo', 'foreground',
+ 'background', '_subtracting')
+
+
+ def __init__(self, charset=insults.G0, bold=False, underline=False,
+ blink=False, reverseVideo=False, foreground=WHITE,
+ background=BLACK, _subtracting=False):
+ self.charset = charset
+ self.bold = bold
+ self.underline = underline
+ self.blink = blink
+ self.reverseVideo = reverseVideo
+ self.foreground = foreground
+ self.background = background
+ self._subtracting = _subtracting
+
+
+ @deprecated(Version('Twisted', 13, 1, 0))
+ def wantOne(self, **kw):
+ """
+ Add a character attribute to a copy of this formatting state.
+
+ @param **kw: An optional attribute name and value can be provided with
+ a keyword argument.
+
+ @return: A formatting state instance with the new attribute.
+
+ @see: L{DefaultFormattingState._withAttribute}.
+ """
+ k, v = kw.popitem()
+ return self._withAttribute(k, v)
+
+
+ def toVT102(self):
+ # Spit out a vt102 control sequence that will set up
+ # all the attributes set here. Except charset.
+ attrs = []
+ if self._subtracting:
+ attrs.append(0)
+ if self.bold:
+ attrs.append(insults.BOLD)
+ if self.underline:
+ attrs.append(insults.UNDERLINE)
+ if self.blink:
+ attrs.append(insults.BLINK)
+ if self.reverseVideo:
+ attrs.append(insults.REVERSE_VIDEO)
+ if self.foreground != WHITE:
+ attrs.append(FOREGROUND + self.foreground)
+ if self.background != BLACK:
+ attrs.append(BACKGROUND + self.background)
+ if attrs:
+ return '\x1b[' + ';'.join(map(str, attrs)) + 'm'
+ return ''
+
+CharacterAttribute = _FormattingState
+
+deprecatedModuleAttribute(
+ Version('Twisted', 13, 1, 0),
+ 'Use twisted.conch.insults.text.assembleFormattedText instead.',
+ 'twisted.conch.insults.helper',
+ 'CharacterAttribute')
+
+
+
+# XXX - need to support scroll regions and scroll history
+@implementer(insults.ITerminalTransport)
+class TerminalBuffer(protocol.Protocol):
+ """
+ An in-memory terminal emulator.
+ """
+ for keyID in (b'UP_ARROW', b'DOWN_ARROW', b'RIGHT_ARROW', b'LEFT_ARROW',
+ b'HOME', b'INSERT', b'DELETE', b'END', b'PGUP', b'PGDN',
+ b'F1', b'F2', b'F3', b'F4', b'F5', b'F6', b'F7', b'F8', b'F9',
+ b'F10', b'F11', b'F12'):
+ execBytes = keyID + b" = object()"
+ execStr = execBytes.decode("ascii")
+ exec(execStr)
+
+ TAB = b'\t'
+ BACKSPACE = b'\x7f'
+
+ width = 80
+ height = 24
+
+ fill = b' '
+ void = object()
+
+ def getCharacter(self, x, y):
+ return self.lines[y][x]
+
+
+ def connectionMade(self):
+ self.reset()
+
+
+ def write(self, data):
+ """
+ Add the given printable bytes to the terminal.
+
+ Line feeds in L{bytes} will be replaced with carriage return / line
+ feed pairs.
+ """
+ for b in iterbytes(data.replace(b'\n', b'\r\n')):
+ self.insertAtCursor(b)
+
+
+ def _currentFormattingState(self):
+ return _FormattingState(self.activeCharset, **self.graphicRendition)
+
+
+ def insertAtCursor(self, b):
+ """
+ Add one byte to the terminal at the cursor and make consequent state
+ updates.
+
+ If b is a carriage return, move the cursor to the beginning of the
+ current row.
+
+ If b is a line feed, move the cursor to the next row or scroll down if
+ the cursor is already in the last row.
+
+ Otherwise, if b is printable, put it at the cursor position (inserting
+ or overwriting as dictated by the current mode) and move the cursor.
+ """
+ if b == b'\r':
+ self.x = 0
+ elif b == b'\n':
+ self._scrollDown()
+ elif b in string.printable.encode("ascii"):
+ if self.x >= self.width:
+ self.nextLine()
+ ch = (b, self._currentFormattingState())
+ if self.modes.get(insults.modes.IRM):
+ self.lines[self.y][self.x:self.x] = [ch]
+ self.lines[self.y].pop()
+ else:
+ self.lines[self.y][self.x] = ch
+ self.x += 1
+
+
+ def _emptyLine(self, width):
+ return [(self.void, self._currentFormattingState())
+ for i in range(width)]
+
+
+ def _scrollDown(self):
+ self.y += 1
+ if self.y >= self.height:
+ self.y -= 1
+ del self.lines[0]
+ self.lines.append(self._emptyLine(self.width))
+
+
+ def _scrollUp(self):
+ self.y -= 1
+ if self.y < 0:
+ self.y = 0
+ del self.lines[-1]
+ self.lines.insert(0, self._emptyLine(self.width))
+
+
+ def cursorUp(self, n=1):
+ self.y = max(0, self.y - n)
+
+
+ def cursorDown(self, n=1):
+ self.y = min(self.height - 1, self.y + n)
+
+
+ def cursorBackward(self, n=1):
+ self.x = max(0, self.x - n)
+
+
+ def cursorForward(self, n=1):
+ self.x = min(self.width, self.x + n)
+
+
+ def cursorPosition(self, column, line):
+ self.x = column
+ self.y = line
+
+
+ def cursorHome(self):
+ self.x = self.home.x
+ self.y = self.home.y
+
+
+ def index(self):
+ self._scrollDown()
+
+
+ def reverseIndex(self):
+ self._scrollUp()
+
+
+ def nextLine(self):
+ """
+ Update the cursor position attributes and scroll down if appropriate.
+ """
+ self.x = 0
+ self._scrollDown()
+
+
+ def saveCursor(self):
+ self._savedCursor = (self.x, self.y)
+
+
+ def restoreCursor(self):
+ self.x, self.y = self._savedCursor
+ del self._savedCursor
+
+
+ def setModes(self, modes):
+ for m in modes:
+ self.modes[m] = True
+
+
+ def resetModes(self, modes):
+ for m in modes:
+ try:
+ del self.modes[m]
+ except KeyError:
+ pass
+
+
+ def setPrivateModes(self, modes):
+ """
+ Enable the given modes.
+
+ Track which modes have been enabled so that the implementations of
+ other L{insults.ITerminalTransport} methods can be properly implemented
+ to respect these settings.
+
+ @see: L{resetPrivateModes}
+ @see: L{insults.ITerminalTransport.setPrivateModes}
+ """
+ for m in modes:
+ self.privateModes[m] = True
+
+
+ def resetPrivateModes(self, modes):
+ """
+ Disable the given modes.
+
+ @see: L{setPrivateModes}
+ @see: L{insults.ITerminalTransport.resetPrivateModes}
+ """
+ for m in modes:
+ try:
+ del self.privateModes[m]
+ except KeyError:
+ pass
+
+
+ def applicationKeypadMode(self):
+ self.keypadMode = 'app'
+
+
+ def numericKeypadMode(self):
+ self.keypadMode = 'num'
+
+
+ def selectCharacterSet(self, charSet, which):
+ self.charsets[which] = charSet
+
+
+ def shiftIn(self):
+ self.activeCharset = insults.G0
+
+
+ def shiftOut(self):
+ self.activeCharset = insults.G1
+
+
+ def singleShift2(self):
+ oldActiveCharset = self.activeCharset
+ self.activeCharset = insults.G2
+ f = self.insertAtCursor
+ def insertAtCursor(b):
+ f(b)
+ del self.insertAtCursor
+ self.activeCharset = oldActiveCharset
+ self.insertAtCursor = insertAtCursor
+
+
+ def singleShift3(self):
+ oldActiveCharset = self.activeCharset
+ self.activeCharset = insults.G3
+ f = self.insertAtCursor
+ def insertAtCursor(b):
+ f(b)
+ del self.insertAtCursor
+ self.activeCharset = oldActiveCharset
+ self.insertAtCursor = insertAtCursor
+
+
+ def selectGraphicRendition(self, *attributes):
+ for a in attributes:
+ if a == insults.NORMAL:
+ self.graphicRendition = {
+ 'bold': False,
+ 'underline': False,
+ 'blink': False,
+ 'reverseVideo': False,
+ 'foreground': WHITE,
+ 'background': BLACK}
+ elif a == insults.BOLD:
+ self.graphicRendition['bold'] = True
+ elif a == insults.UNDERLINE:
+ self.graphicRendition['underline'] = True
+ elif a == insults.BLINK:
+ self.graphicRendition['blink'] = True
+ elif a == insults.REVERSE_VIDEO:
+ self.graphicRendition['reverseVideo'] = True
+ else:
+ try:
+ v = int(a)
+ except ValueError:
+ log.msg("Unknown graphic rendition attribute: " + repr(a))
+ else:
+ if FOREGROUND <= v <= FOREGROUND + N_COLORS:
+ self.graphicRendition['foreground'] = v - FOREGROUND
+ elif BACKGROUND <= v <= BACKGROUND + N_COLORS:
+ self.graphicRendition['background'] = v - BACKGROUND
+ else:
+ log.msg("Unknown graphic rendition attribute: " + repr(a))
+
+
+ def eraseLine(self):
+ self.lines[self.y] = self._emptyLine(self.width)
+
+
+ def eraseToLineEnd(self):
+ width = self.width - self.x
+ self.lines[self.y][self.x:] = self._emptyLine(width)
+
+
+ def eraseToLineBeginning(self):
+ self.lines[self.y][:self.x + 1] = self._emptyLine(self.x + 1)
+
+
+ def eraseDisplay(self):
+ self.lines = [self._emptyLine(self.width) for i in range(self.height)]
+
+
+ def eraseToDisplayEnd(self):
+ self.eraseToLineEnd()
+ height = self.height - self.y - 1
+ self.lines[self.y + 1:] = [self._emptyLine(self.width) for i in range(height)]
+
+
+ def eraseToDisplayBeginning(self):
+ self.eraseToLineBeginning()
+ self.lines[:self.y] = [self._emptyLine(self.width) for i in range(self.y)]
+
+
+ def deleteCharacter(self, n=1):
+ del self.lines[self.y][self.x:self.x+n]
+ self.lines[self.y].extend(self._emptyLine(min(self.width - self.x, n)))
+
+
+ def insertLine(self, n=1):
+ self.lines[self.y:self.y] = [self._emptyLine(self.width) for i in range(n)]
+ del self.lines[self.height:]
+
+
+ def deleteLine(self, n=1):
+ del self.lines[self.y:self.y+n]
+ self.lines.extend([self._emptyLine(self.width) for i in range(n)])
+
+
+ def reportCursorPosition(self):
+ return (self.x, self.y)
+
+
+ def reset(self):
+ self.home = insults.Vector(0, 0)
+ self.x = self.y = 0
+ self.modes = {}
+ self.privateModes = {}
+ self.setPrivateModes([insults.privateModes.AUTO_WRAP,
+ insults.privateModes.CURSOR_MODE])
+ self.numericKeypad = 'app'
+ self.activeCharset = insults.G0
+ self.graphicRendition = {
+ 'bold': False,
+ 'underline': False,
+ 'blink': False,
+ 'reverseVideo': False,
+ 'foreground': WHITE,
+ 'background': BLACK}
+ self.charsets = {
+ insults.G0: insults.CS_US,
+ insults.G1: insults.CS_US,
+ insults.G2: insults.CS_ALTERNATE,
+ insults.G3: insults.CS_ALTERNATE_SPECIAL}
+ self.eraseDisplay()
+
+
+ def unhandledControlSequence(self, buf):
+ print('Could not handle', repr(buf))
+
+
+ def __bytes__(self):
+ lines = []
+ for L in self.lines:
+ buf = []
+ length = 0
+ for (ch, attr) in L:
+ if ch is not self.void:
+ buf.append(ch)
+ length = len(buf)
+ else:
+ buf.append(self.fill)
+ lines.append(b''.join(buf[:length]))
+ return b'\n'.join(lines)
+
+
+
+class ExpectationTimeout(Exception):
+ pass
+
+
+
+class ExpectableBuffer(TerminalBuffer):
+ _mark = 0
+
+ def connectionMade(self):
+ TerminalBuffer.connectionMade(self)
+ self._expecting = []
+
+
+ def write(self, data):
+ TerminalBuffer.write(self, data)
+ self._checkExpected()
+
+
+ def cursorHome(self):
+ TerminalBuffer.cursorHome(self)
+ self._mark = 0
+
+
+ def _timeoutExpected(self, d):
+ d.errback(ExpectationTimeout())
+ self._checkExpected()
+
+
+ def _checkExpected(self):
+ s = self.__bytes__()[self._mark:]
+ while self._expecting:
+ expr, timer, deferred = self._expecting[0]
+ if timer and not timer.active():
+ del self._expecting[0]
+ continue
+ for match in expr.finditer(s):
+ if timer:
+ timer.cancel()
+ del self._expecting[0]
+ self._mark += match.end()
+ s = s[match.end():]
+ deferred.callback(match)
+ break
+ else:
+ return
+
+
+ def expect(self, expression, timeout=None, scheduler=reactor):
+ d = defer.Deferred()
+ timer = None
+ if timeout:
+ timer = scheduler.callLater(timeout, self._timeoutExpected, d)
+ self._expecting.append((re.compile(expression), timer, d))
+ self._checkExpected()
+ return d
+
+__all__ = [
+ 'CharacterAttribute', 'TerminalBuffer', 'ExpectableBuffer']
diff --git a/contrib/python/Twisted/py2/twisted/conch/insults/insults.py b/contrib/python/Twisted/py2/twisted/conch/insults/insults.py
new file mode 100644
index 0000000000..a583174415
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/insults/insults.py
@@ -0,0 +1,1289 @@
+# -*- test-case-name: twisted.conch.test.test_insults -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+VT102 and VT220 terminal manipulation.
+
+@author: Jp Calderone
+"""
+
+from zope.interface import implementer, Interface
+
+from twisted.internet import protocol, defer, interfaces as iinternet
+from twisted.python.compat import intToBytes, iterbytes, networkString
+
+
+
+class ITerminalProtocol(Interface):
+ def makeConnection(transport):
+ """
+ Called with an L{ITerminalTransport} when a connection is established.
+ """
+
+ def keystrokeReceived(keyID, modifier):
+ """
+ A keystroke was received.
+
+ Each keystroke corresponds to one invocation of this method.
+ keyID is a string identifier for that key. Printable characters
+ are represented by themselves. Control keys, such as arrows and
+ function keys, are represented with symbolic constants on
+ L{ServerProtocol}.
+ """
+
+ def terminalSize(width, height):
+ """
+ Called to indicate the size of the terminal.
+
+ A terminal of 80x24 should be assumed if this method is not
+ called. This method might not be called for real terminals.
+ """
+
+ def unhandledControlSequence(seq):
+ """
+ Called when an unsupported control sequence is received.
+
+ @type seq: L{str}
+ @param seq: The whole control sequence which could not be interpreted.
+ """
+
+ def connectionLost(reason):
+ """
+ Called when the connection has been lost.
+
+ reason is a Failure describing why.
+ """
+
+
+
+@implementer(ITerminalProtocol)
+class TerminalProtocol(object):
+ def makeConnection(self, terminal):
+ # assert ITerminalTransport.providedBy(transport), "TerminalProtocol.makeConnection must be passed an ITerminalTransport implementor"
+ self.terminal = terminal
+ self.connectionMade()
+
+
+ def connectionMade(self):
+ """
+ Called after a connection has been established.
+ """
+
+
+ def keystrokeReceived(self, keyID, modifier):
+ pass
+
+
+ def terminalSize(self, width, height):
+ pass
+
+
+ def unhandledControlSequence(self, seq):
+ pass
+
+
+ def connectionLost(self, reason):
+ pass
+
+
+
+class ITerminalTransport(iinternet.ITransport):
+ def cursorUp(n=1):
+ """
+ Move the cursor up n lines.
+ """
+
+
+ def cursorDown(n=1):
+ """
+ Move the cursor down n lines.
+ """
+
+
+ def cursorForward(n=1):
+ """
+ Move the cursor right n columns.
+ """
+
+
+ def cursorBackward(n=1):
+ """
+ Move the cursor left n columns.
+ """
+
+
+ def cursorPosition(column, line):
+ """
+ Move the cursor to the given line and column.
+ """
+
+
+ def cursorHome():
+ """
+ Move the cursor home.
+ """
+
+
+ def index():
+ """
+ Move the cursor down one line, performing scrolling if necessary.
+ """
+
+
+ def reverseIndex():
+ """
+ Move the cursor up one line, performing scrolling if necessary.
+ """
+
+
+ def nextLine():
+ """
+ Move the cursor to the first position on the next line, performing scrolling if necessary.
+ """
+
+
+ def saveCursor():
+ """
+ Save the cursor position, character attribute, character set, and origin mode selection.
+ """
+
+
+ def restoreCursor():
+ """
+ Restore the previously saved cursor position, character attribute, character set, and origin mode selection.
+
+ If no cursor state was previously saved, move the cursor to the home position.
+ """
+
+
+ def setModes(modes):
+ """
+ Set the given modes on the terminal.
+ """
+
+ def resetModes(mode):
+ """
+ Reset the given modes on the terminal.
+ """
+
+
+ def setPrivateModes(modes):
+ """
+ Set the given DEC private modes on the terminal.
+ """
+
+
+ def resetPrivateModes(modes):
+ """
+ Reset the given DEC private modes on the terminal.
+ """
+
+
+ def applicationKeypadMode():
+ """
+ Cause keypad to generate control functions.
+
+ Cursor key mode selects the type of characters generated by cursor keys.
+ """
+
+
+ def numericKeypadMode():
+ """
+ Cause keypad to generate normal characters.
+ """
+
+
+ def selectCharacterSet(charSet, which):
+ """
+ Select a character set.
+
+ charSet should be one of CS_US, CS_UK, CS_DRAWING, CS_ALTERNATE, or
+ CS_ALTERNATE_SPECIAL.
+
+ which should be one of G0 or G1.
+ """
+
+
+ def shiftIn():
+ """
+ Activate the G0 character set.
+ """
+
+
+ def shiftOut():
+ """
+ Activate the G1 character set.
+ """
+
+
+ def singleShift2():
+ """
+ Shift to the G2 character set for a single character.
+ """
+
+
+ def singleShift3():
+ """
+ Shift to the G3 character set for a single character.
+ """
+
+
+ def selectGraphicRendition(*attributes):
+ """
+ Enabled one or more character attributes.
+
+ Arguments should be one or more of UNDERLINE, REVERSE_VIDEO, BLINK, or BOLD.
+ NORMAL may also be specified to disable all character attributes.
+ """
+
+
+ def horizontalTabulationSet():
+ """
+ Set a tab stop at the current cursor position.
+ """
+
+
+ def tabulationClear():
+ """
+ Clear the tab stop at the current cursor position.
+ """
+
+
+ def tabulationClearAll():
+ """
+ Clear all tab stops.
+ """
+
+
+ def doubleHeightLine(top=True):
+ """
+ Make the current line the top or bottom half of a double-height, double-width line.
+
+ If top is True, the current line is the top half. Otherwise, it is the bottom half.
+ """
+
+
+ def singleWidthLine():
+ """
+ Make the current line a single-width, single-height line.
+ """
+
+
+ def doubleWidthLine():
+ """
+ Make the current line a double-width line.
+ """
+
+
+ def eraseToLineEnd():
+ """
+ Erase from the cursor to the end of line, including cursor position.
+ """
+
+
+ def eraseToLineBeginning():
+ """
+ Erase from the cursor to the beginning of the line, including the cursor position.
+ """
+
+
+ def eraseLine():
+ """
+ Erase the entire cursor line.
+ """
+
+
+ def eraseToDisplayEnd():
+ """
+ Erase from the cursor to the end of the display, including the cursor position.
+ """
+
+
+ def eraseToDisplayBeginning():
+ """
+ Erase from the cursor to the beginning of the display, including the cursor position.
+ """
+
+
+ def eraseDisplay():
+ """
+ Erase the entire display.
+ """
+
+
+ def deleteCharacter(n=1):
+ """
+ Delete n characters starting at the cursor position.
+
+ Characters to the right of deleted characters are shifted to the left.
+ """
+
+
+ def insertLine(n=1):
+ """
+ Insert n lines at the cursor position.
+
+ Lines below the cursor are shifted down. Lines moved past the bottom margin are lost.
+ This command is ignored when the cursor is outside the scroll region.
+ """
+
+
+ def deleteLine(n=1):
+ """
+ Delete n lines starting at the cursor position.
+
+ Lines below the cursor are shifted up. This command is ignored when the cursor is outside
+ the scroll region.
+ """
+
+
+ def reportCursorPosition():
+ """
+ Return a Deferred that fires with a two-tuple of (x, y) indicating the cursor position.
+ """
+
+
+ def reset():
+ """
+ Reset the terminal to its initial state.
+ """
+
+
+ def unhandledControlSequence(seq):
+ """
+ Called when an unsupported control sequence is received.
+
+ @type seq: L{str}
+ @param seq: The whole control sequence which could not be interpreted.
+ """
+
+
+CSI = b'\x1b'
+CST = {b'~': b'tilde'}
+
+class modes:
+ """
+ ECMA 48 standardized modes
+ """
+
+ # BREAKS YOPUR KEYBOARD MOFO
+ KEYBOARD_ACTION = KAM = 2
+
+ # When set, enables character insertion. New display characters
+ # move old display characters to the right. Characters moved past
+ # the right margin are lost.
+
+ # When reset, enables replacement mode (disables character
+ # insertion). New display characters replace old display
+ # characters at cursor position. The old character is erased.
+ INSERTION_REPLACEMENT = IRM = 4
+
+ # Set causes a received linefeed, form feed, or vertical tab to
+ # move cursor to first column of next line. RETURN transmits both
+ # a carriage return and linefeed. This selection is also called
+ # new line option.
+
+ # Reset causes a received linefeed, form feed, or vertical tab to
+ # move cursor to next line in current column. RETURN transmits a
+ # carriage return.
+ LINEFEED_NEWLINE = LNM = 20
+
+
+
+class privateModes:
+ """
+ ANSI-Compatible Private Modes
+ """
+ ERROR = 0
+ CURSOR_KEY = 1
+ ANSI_VT52 = 2
+ COLUMN = 3
+ SCROLL = 4
+ SCREEN = 5
+ ORIGIN = 6
+ AUTO_WRAP = 7
+ AUTO_REPEAT = 8
+ PRINTER_FORM_FEED = 18
+ PRINTER_EXTENT = 19
+
+ # Toggle cursor visibility (reset hides it)
+ CURSOR_MODE = 25
+
+
+# Character sets
+CS_US = b'CS_US'
+CS_UK = b'CS_UK'
+CS_DRAWING = b'CS_DRAWING'
+CS_ALTERNATE = b'CS_ALTERNATE'
+CS_ALTERNATE_SPECIAL = b'CS_ALTERNATE_SPECIAL'
+
+# Groupings (or something?? These are like variables that can be bound to character sets)
+G0 = b'G0'
+G1 = b'G1'
+
+# G2 and G3 cannot be changed, but they can be shifted to.
+G2 = b'G2'
+G3 = b'G3'
+
+# Character attributes
+
+NORMAL = 0
+BOLD = 1
+UNDERLINE = 4
+BLINK = 5
+REVERSE_VIDEO = 7
+
+class Vector:
+ def __init__(self, x, y):
+ self.x = x
+ self.y = y
+
+
+
+def log(s):
+ with open('log', 'a') as f:
+ f.write(str(s) + '\n')
+
+# XXX TODO - These attributes are really part of the
+# ITerminalTransport interface, I think.
+_KEY_NAMES = ('UP_ARROW', 'DOWN_ARROW', 'RIGHT_ARROW', 'LEFT_ARROW',
+ 'HOME', 'INSERT', 'DELETE', 'END', 'PGUP', 'PGDN', 'NUMPAD_MIDDLE',
+ 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9',
+ 'F10', 'F11', 'F12',
+
+ 'ALT', 'SHIFT', 'CONTROL')
+
+class _const(object):
+ """
+ @ivar name: A string naming this constant
+ """
+ def __init__(self, name):
+ self.name = name
+
+
+ def __repr__(self):
+ return '[' + self.name + ']'
+
+
+ def __bytes__(self):
+ return ('[' + self.name + ']').encode("ascii")
+
+
+FUNCTION_KEYS = [
+ _const(_name).__bytes__() for _name in _KEY_NAMES]
+
+
+
+@implementer(ITerminalTransport)
+class ServerProtocol(protocol.Protocol):
+ protocolFactory = None
+ terminalProtocol = None
+
+ TAB = b'\t'
+ BACKSPACE = b'\x7f'
+ ##
+
+ lastWrite = b''
+
+ state = b'data'
+
+ termSize = Vector(80, 24)
+ cursorPos = Vector(0, 0)
+ scrollRegion = None
+
+ # Factory who instantiated me
+ factory = None
+
+ def __init__(self, protocolFactory=None, *a, **kw):
+ """
+ @param protocolFactory: A callable which will be invoked with
+ *a, **kw and should return an ITerminalProtocol implementor.
+ This will be invoked when a connection to this ServerProtocol
+ is established.
+
+ @param a: Any positional arguments to pass to protocolFactory.
+ @param kw: Any keyword arguments to pass to protocolFactory.
+ """
+ # assert protocolFactory is None or ITerminalProtocol.implementedBy(protocolFactory), "ServerProtocol.__init__ must be passed an ITerminalProtocol implementor"
+ if protocolFactory is not None:
+ self.protocolFactory = protocolFactory
+ self.protocolArgs = a
+ self.protocolKwArgs = kw
+
+ self._cursorReports = []
+
+
+ def connectionMade(self):
+ if self.protocolFactory is not None:
+ self.terminalProtocol = self.protocolFactory(*self.protocolArgs, **self.protocolKwArgs)
+
+ try:
+ factory = self.factory
+ except AttributeError:
+ pass
+ else:
+ self.terminalProtocol.factory = factory
+
+ self.terminalProtocol.makeConnection(self)
+
+
+ def dataReceived(self, data):
+ for ch in iterbytes(data):
+ if self.state == b'data':
+ if ch == b'\x1b':
+ self.state = b'escaped'
+ else:
+ self.terminalProtocol.keystrokeReceived(ch, None)
+ elif self.state == b'escaped':
+ if ch == b'[':
+ self.state = b'bracket-escaped'
+ self.escBuf = []
+ elif ch == b'O':
+ self.state = b'low-function-escaped'
+ else:
+ self.state = b'data'
+ self._handleShortControlSequence(ch)
+ elif self.state == b'bracket-escaped':
+ if ch == b'O':
+ self.state = b'low-function-escaped'
+ elif ch.isalpha() or ch == b'~':
+ self._handleControlSequence(b''.join(self.escBuf) + ch)
+ del self.escBuf
+ self.state = b'data'
+ else:
+ self.escBuf.append(ch)
+ elif self.state == b'low-function-escaped':
+ self._handleLowFunctionControlSequence(ch)
+ self.state = b'data'
+ else:
+ raise ValueError("Illegal state")
+
+
+ def _handleShortControlSequence(self, ch):
+ self.terminalProtocol.keystrokeReceived(ch, self.ALT)
+
+
+ def _handleControlSequence(self, buf):
+ buf = b'\x1b[' + buf
+ f = getattr(self.controlSequenceParser,
+ CST.get(buf[-1:], buf[-1:]).decode("ascii"),
+ None)
+ if f is None:
+ self.unhandledControlSequence(buf)
+ else:
+ f(self, self.terminalProtocol, buf[:-1])
+
+
+ def unhandledControlSequence(self, buf):
+ self.terminalProtocol.unhandledControlSequence(buf)
+
+
+ def _handleLowFunctionControlSequence(self, ch):
+ functionKeys = {b'P': self.F1, b'Q': self.F2,
+ b'R': self.F3, b'S': self.F4}
+ keyID = functionKeys.get(ch)
+ if keyID is not None:
+ self.terminalProtocol.keystrokeReceived(keyID, None)
+ else:
+ self.terminalProtocol.unhandledControlSequence(b'\x1b[O' + ch)
+
+
+ class ControlSequenceParser:
+ def A(self, proto, handler, buf):
+ if buf == b'\x1b[':
+ handler.keystrokeReceived(proto.UP_ARROW, None)
+ else:
+ handler.unhandledControlSequence(buf + b'A')
+
+
+ def B(self, proto, handler, buf):
+ if buf == b'\x1b[':
+ handler.keystrokeReceived(proto.DOWN_ARROW, None)
+ else:
+ handler.unhandledControlSequence(buf + b'B')
+
+
+ def C(self, proto, handler, buf):
+ if buf == b'\x1b[':
+ handler.keystrokeReceived(proto.RIGHT_ARROW, None)
+ else:
+ handler.unhandledControlSequence(buf + b'C')
+
+
+ def D(self, proto, handler, buf):
+ if buf == b'\x1b[':
+ handler.keystrokeReceived(proto.LEFT_ARROW, None)
+ else:
+ handler.unhandledControlSequence(buf + b'D')
+
+
+ def E(self, proto, handler, buf):
+ if buf == b'\x1b[':
+ handler.keystrokeReceived(proto.NUMPAD_MIDDLE, None)
+ else:
+ handler.unhandledControlSequence(buf + b'E')
+
+
+ def F(self, proto, handler, buf):
+ if buf == b'\x1b[':
+ handler.keystrokeReceived(proto.END, None)
+ else:
+ handler.unhandledControlSequence(buf + b'F')
+
+
+ def H(self, proto, handler, buf):
+ if buf == b'\x1b[':
+ handler.keystrokeReceived(proto.HOME, None)
+ else:
+ handler.unhandledControlSequence(buf + b'H')
+
+
+ def R(self, proto, handler, buf):
+ if not proto._cursorReports:
+ handler.unhandledControlSequence(buf + b'R')
+ elif buf.startswith(b'\x1b['):
+ report = buf[2:]
+ parts = report.split(b';')
+ if len(parts) != 2:
+ handler.unhandledControlSequence(buf + b'R')
+ else:
+ Pl, Pc = parts
+ try:
+ Pl, Pc = int(Pl), int(Pc)
+ except ValueError:
+ handler.unhandledControlSequence(buf + b'R')
+ else:
+ d = proto._cursorReports.pop(0)
+ d.callback((Pc - 1, Pl - 1))
+ else:
+ handler.unhandledControlSequence(buf + b'R')
+
+
+ def Z(self, proto, handler, buf):
+ if buf == b'\x1b[':
+ handler.keystrokeReceived(proto.TAB, proto.SHIFT)
+ else:
+ handler.unhandledControlSequence(buf + b'Z')
+
+
+ def tilde(self, proto, handler, buf):
+ map = {1: proto.HOME, 2: proto.INSERT, 3: proto.DELETE,
+ 4: proto.END, 5: proto.PGUP, 6: proto.PGDN,
+
+ 15: proto.F5, 17: proto.F6, 18: proto.F7,
+ 19: proto.F8, 20: proto.F9, 21: proto.F10,
+ 23: proto.F11, 24: proto.F12}
+
+ if buf.startswith(b'\x1b['):
+ ch = buf[2:]
+ try:
+ v = int(ch)
+ except ValueError:
+ handler.unhandledControlSequence(buf + b'~')
+ else:
+ symbolic = map.get(v)
+ if symbolic is not None:
+ handler.keystrokeReceived(map[v], None)
+ else:
+ handler.unhandledControlSequence(buf + b'~')
+ else:
+ handler.unhandledControlSequence(buf + b'~')
+
+ controlSequenceParser = ControlSequenceParser()
+
+
+ # ITerminalTransport
+ def cursorUp(self, n=1):
+ assert n >= 1
+ self.cursorPos.y = max(self.cursorPos.y - n, 0)
+ self.write(b'\x1b[' + intToBytes(n) + b'A')
+
+
+ def cursorDown(self, n=1):
+ assert n >= 1
+ self.cursorPos.y = min(self.cursorPos.y + n, self.termSize.y - 1)
+ self.write(b'\x1b[' + intToBytes(n) + b'B')
+
+
+ def cursorForward(self, n=1):
+ assert n >= 1
+ self.cursorPos.x = min(self.cursorPos.x + n, self.termSize.x - 1)
+ self.write(b'\x1b[' + intToBytes(n) + b'C')
+
+
+ def cursorBackward(self, n=1):
+ assert n >= 1
+ self.cursorPos.x = max(self.cursorPos.x - n, 0)
+ self.write(b'\x1b[' + intToBytes(n) + b'D')
+
+
+ def cursorPosition(self, column, line):
+ self.write(b'\x1b[' +
+ intToBytes(line + 1) +
+ b';' +
+ intToBytes(column + 1) +
+ b'H')
+
+
+ def cursorHome(self):
+ self.cursorPos.x = self.cursorPos.y = 0
+ self.write(b'\x1b[H')
+
+
+ def index(self):
+ # ECMA48 5th Edition removes this
+ self.cursorPos.y = min(self.cursorPos.y + 1, self.termSize.y - 1)
+ self.write(b'\x1bD')
+
+
+ def reverseIndex(self):
+ self.cursorPos.y = max(self.cursorPos.y - 1, 0)
+ self.write(b'\x1bM')
+
+
+ def nextLine(self):
+ self.cursorPos.x = 0
+ self.cursorPos.y = min(self.cursorPos.y + 1, self.termSize.y - 1)
+ self.write(b'\n')
+
+
+ def saveCursor(self):
+ self._savedCursorPos = Vector(self.cursorPos.x, self.cursorPos.y)
+ self.write(b'\x1b7')
+
+
+ def restoreCursor(self):
+ self.cursorPos = self._savedCursorPos
+ del self._savedCursorPos
+ self.write(b'\x1b8')
+
+
+ def setModes(self, modes):
+ # XXX Support ANSI-Compatible private modes
+ modesBytes = b';'.join([intToBytes(mode) for mode in modes])
+ self.write(b'\x1b[' + modesBytes + b'h')
+
+
+ def setPrivateModes(self, modes):
+ modesBytes = b';'.join([intToBytes(mode) for mode in modes])
+ self.write(b'\x1b[?' + modesBytes + b'h')
+
+
+ def resetModes(self, modes):
+ # XXX Support ANSI-Compatible private modes
+ modesBytes = b';'.join([intToBytes(mode) for mode in modes])
+ self.write(b'\x1b[' + modesBytes + b'l')
+
+
+ def resetPrivateModes(self, modes):
+ modesBytes = b';'.join([intToBytes(mode) for mode in modes])
+ self.write(b'\x1b[?' + modesBytes + b'l')
+
+
+ def applicationKeypadMode(self):
+ self.write(b'\x1b=')
+
+
+ def numericKeypadMode(self):
+ self.write(b'\x1b>')
+
+
+ def selectCharacterSet(self, charSet, which):
+ # XXX Rewrite these as dict lookups
+ if which == G0:
+ which = b'('
+ elif which == G1:
+ which = b')'
+ else:
+ raise ValueError("`which' argument to selectCharacterSet must be G0 or G1")
+ if charSet == CS_UK:
+ charSet = b'A'
+ elif charSet == CS_US:
+ charSet = b'B'
+ elif charSet == CS_DRAWING:
+ charSet = b'0'
+ elif charSet == CS_ALTERNATE:
+ charSet = b'1'
+ elif charSet == CS_ALTERNATE_SPECIAL:
+ charSet = b'2'
+ else:
+ raise ValueError("Invalid `charSet' argument to selectCharacterSet")
+ self.write(b'\x1b' + which + charSet)
+
+
+ def shiftIn(self):
+ self.write(b'\x15')
+
+
+ def shiftOut(self):
+ self.write(b'\x14')
+
+
+ def singleShift2(self):
+ self.write(b'\x1bN')
+
+
+ def singleShift3(self):
+ self.write(b'\x1bO')
+
+
+ def selectGraphicRendition(self, *attributes):
+ # each member of attributes must be a native string
+ attrs = []
+ for a in attributes:
+ attrs.append(networkString(a))
+ self.write(b'\x1b[' +
+ b';'.join(attrs) +
+ b'm')
+
+
+ def horizontalTabulationSet(self):
+ self.write(b'\x1bH')
+
+
+ def tabulationClear(self):
+ self.write(b'\x1b[q')
+
+
+ def tabulationClearAll(self):
+ self.write(b'\x1b[3q')
+
+
+ def doubleHeightLine(self, top=True):
+ if top:
+ self.write(b'\x1b#3')
+ else:
+ self.write(b'\x1b#4')
+
+
+ def singleWidthLine(self):
+ self.write(b'\x1b#5')
+
+
+ def doubleWidthLine(self):
+ self.write(b'\x1b#6')
+
+
+ def eraseToLineEnd(self):
+ self.write(b'\x1b[K')
+
+
+ def eraseToLineBeginning(self):
+ self.write(b'\x1b[1K')
+
+
+ def eraseLine(self):
+ self.write(b'\x1b[2K')
+
+
+ def eraseToDisplayEnd(self):
+ self.write(b'\x1b[J')
+
+
+ def eraseToDisplayBeginning(self):
+ self.write(b'\x1b[1J')
+
+
+ def eraseDisplay(self):
+ self.write(b'\x1b[2J')
+
+
+ def deleteCharacter(self, n=1):
+ self.write(b'\x1b[' + intToBytes(n) + b'P')
+
+
+ def insertLine(self, n=1):
+ self.write(b'\x1b[' + intToBytes(n) + b'L')
+
+
+ def deleteLine(self, n=1):
+ self.write(b'\x1b[' + intToBytes(n) + b'M')
+
+
+ def setScrollRegion(self, first=None, last=None):
+ if first is not None:
+ first = intToBytes(first)
+ else:
+ first = b''
+ if last is not None:
+ last = intToBytes(last)
+ else:
+ last = b''
+ self.write(b'\x1b[' + first + b';' + last + b'r')
+
+
+ def resetScrollRegion(self):
+ self.setScrollRegion()
+
+
+ def reportCursorPosition(self):
+ d = defer.Deferred()
+ self._cursorReports.append(d)
+ self.write(b'\x1b[6n')
+ return d
+
+
+ def reset(self):
+ self.cursorPos.x = self.cursorPos.y = 0
+ try:
+ del self._savedCursorPos
+ except AttributeError:
+ pass
+ self.write(b'\x1bc')
+
+
+ # ITransport
+ def write(self, data):
+ if data:
+ if not isinstance(data, bytes):
+ data = data.encode("utf-8")
+ self.lastWrite = data
+ self.transport.write(b'\r\n'.join(data.split(b'\n')))
+
+
+ def writeSequence(self, data):
+ self.write(b''.join(data))
+
+
+ def loseConnection(self):
+ self.reset()
+ self.transport.loseConnection()
+
+
+ def connectionLost(self, reason):
+ if self.terminalProtocol is not None:
+ try:
+ self.terminalProtocol.connectionLost(reason)
+ finally:
+ self.terminalProtocol = None
+# Add symbolic names for function keys
+for name, const in zip(_KEY_NAMES, FUNCTION_KEYS):
+ setattr(ServerProtocol, name, const)
+
+
+
+class ClientProtocol(protocol.Protocol):
+
+ terminalFactory = None
+ terminal = None
+
+ state = b'data'
+
+ _escBuf = None
+
+ _shorts = {
+ b'D': b'index',
+ b'M': b'reverseIndex',
+ b'E': b'nextLine',
+ b'7': b'saveCursor',
+ b'8': b'restoreCursor',
+ b'=': b'applicationKeypadMode',
+ b'>': b'numericKeypadMode',
+ b'N': b'singleShift2',
+ b'O': b'singleShift3',
+ b'H': b'horizontalTabulationSet',
+ b'c': b'reset'}
+
+ _longs = {
+ b'[': b'bracket-escape',
+ b'(': b'select-g0',
+ b')': b'select-g1',
+ b'#': b'select-height-width'}
+
+ _charsets = {
+ b'A': CS_UK,
+ b'B': CS_US,
+ b'0': CS_DRAWING,
+ b'1': CS_ALTERNATE,
+ b'2': CS_ALTERNATE_SPECIAL}
+
+ # Factory who instantiated me
+ factory = None
+
+ def __init__(self, terminalFactory=None, *a, **kw):
+ """
+ @param terminalFactory: A callable which will be invoked with
+ *a, **kw and should return an ITerminalTransport provider.
+ This will be invoked when this ClientProtocol establishes a
+ connection.
+
+ @param a: Any positional arguments to pass to terminalFactory.
+ @param kw: Any keyword arguments to pass to terminalFactory.
+ """
+ # assert terminalFactory is None or ITerminalTransport.implementedBy(terminalFactory), "ClientProtocol.__init__ must be passed an ITerminalTransport implementor"
+ if terminalFactory is not None:
+ self.terminalFactory = terminalFactory
+ self.terminalArgs = a
+ self.terminalKwArgs = kw
+
+
+ def connectionMade(self):
+ if self.terminalFactory is not None:
+ self.terminal = self.terminalFactory(*self.terminalArgs, **self.terminalKwArgs)
+ self.terminal.factory = self.factory
+ self.terminal.makeConnection(self)
+
+
+ def connectionLost(self, reason):
+ if self.terminal is not None:
+ try:
+ self.terminal.connectionLost(reason)
+ finally:
+ del self.terminal
+
+
+ def dataReceived(self, data):
+ """
+ Parse the given data from a terminal server, dispatching to event
+ handlers defined by C{self.terminal}.
+ """
+ toWrite = []
+ for b in iterbytes(data):
+ if self.state == b'data':
+ if b == b'\x1b':
+ if toWrite:
+ self.terminal.write(b''.join(toWrite))
+ del toWrite[:]
+ self.state = b'escaped'
+ elif b == b'\x14':
+ if toWrite:
+ self.terminal.write(b''.join(toWrite))
+ del toWrite[:]
+ self.terminal.shiftOut()
+ elif b == b'\x15':
+ if toWrite:
+ self.terminal.write(b''.join(toWrite))
+ del toWrite[:]
+ self.terminal.shiftIn()
+ elif b == b'\x08':
+ if toWrite:
+ self.terminal.write(b''.join(toWrite))
+ del toWrite[:]
+ self.terminal.cursorBackward()
+ else:
+ toWrite.append(b)
+ elif self.state == b'escaped':
+ fName = self._shorts.get(b)
+ if fName is not None:
+ self.state = b'data'
+ getattr(self.terminal, fName.decode("ascii"))()
+ else:
+ state = self._longs.get(b)
+ if state is not None:
+ self.state = state
+ else:
+ self.terminal.unhandledControlSequence(b'\x1b' + b)
+ self.state = b'data'
+ elif self.state == b'bracket-escape':
+ if self._escBuf is None:
+ self._escBuf = []
+ if b.isalpha() or b == b'~':
+ self._handleControlSequence(b''.join(self._escBuf), b)
+ del self._escBuf
+ self.state = b'data'
+ else:
+ self._escBuf.append(b)
+ elif self.state == b'select-g0':
+ self.terminal.selectCharacterSet(self._charsets.get(b, b), G0)
+ self.state = b'data'
+ elif self.state == b'select-g1':
+ self.terminal.selectCharacterSet(self._charsets.get(b, b), G1)
+ self.state = b'data'
+ elif self.state == b'select-height-width':
+ self._handleHeightWidth(b)
+ self.state = b'data'
+ else:
+ raise ValueError("Illegal state")
+ if toWrite:
+ self.terminal.write(b''.join(toWrite))
+
+
+ def _handleControlSequence(self, buf, terminal):
+ f = getattr(self.controlSequenceParser, CST.get(terminal, terminal).decode("ascii"), None)
+ if f is None:
+ self.terminal.unhandledControlSequence(b'\x1b[' + buf + terminal)
+ else:
+ f(self, self.terminal, buf)
+
+
+ class ControlSequenceParser:
+ def _makeSimple(ch, fName):
+ n = 'cursor' + fName
+ def simple(self, proto, handler, buf):
+ if not buf:
+ getattr(handler, n)(1)
+ else:
+ try:
+ m = int(buf)
+ except ValueError:
+ handler.unhandledControlSequence(b'\x1b[' + buf + ch)
+ else:
+ getattr(handler, n)(m)
+ return simple
+
+ for (ch, fName) in (('A', 'Up'),
+ ('B', 'Down'),
+ ('C', 'Forward'),
+ ('D', 'Backward')):
+ exec(ch + " = _makeSimple(ch, fName)")
+ del _makeSimple
+
+
+ def h(self, proto, handler, buf):
+ # XXX - Handle '?' to introduce ANSI-Compatible private modes.
+ try:
+ modes = [int(mode) for mode in buf.split(b';')]
+ except ValueError:
+ handler.unhandledControlSequence(b'\x1b[' + buf + b'h')
+ else:
+ handler.setModes(modes)
+
+
+ def l(self, proto, handler, buf):
+ # XXX - Handle '?' to introduce ANSI-Compatible private modes.
+ try:
+ modes = [int(mode) for mode in buf.split(b';')]
+ except ValueError:
+ handler.unhandledControlSequence(b'\x1b[' + buf + 'l')
+ else:
+ handler.resetModes(modes)
+
+
+ def r(self, proto, handler, buf):
+ parts = buf.split(b';')
+ if len(parts) == 1:
+ handler.setScrollRegion(None, None)
+ elif len(parts) == 2:
+ try:
+ if parts[0]:
+ pt = int(parts[0])
+ else:
+ pt = None
+ if parts[1]:
+ pb = int(parts[1])
+ else:
+ pb = None
+ except ValueError:
+ handler.unhandledControlSequence(b'\x1b[' + buf + b'r')
+ else:
+ handler.setScrollRegion(pt, pb)
+ else:
+ handler.unhandledControlSequence(b'\x1b[' + buf + b'r')
+
+ def K(self, proto, handler, buf):
+ if not buf:
+ handler.eraseToLineEnd()
+ elif buf == b'1':
+ handler.eraseToLineBeginning()
+ elif buf == b'2':
+ handler.eraseLine()
+ else:
+ handler.unhandledControlSequence(b'\x1b[' + buf + b'K')
+
+
+ def H(self, proto, handler, buf):
+ handler.cursorHome()
+
+
+ def J(self, proto, handler, buf):
+ if not buf:
+ handler.eraseToDisplayEnd()
+ elif buf == b'1':
+ handler.eraseToDisplayBeginning()
+ elif buf == b'2':
+ handler.eraseDisplay()
+ else:
+ handler.unhandledControlSequence(b'\x1b[' + buf + b'J')
+
+
+ def P(self, proto, handler, buf):
+ if not buf:
+ handler.deleteCharacter(1)
+ else:
+ try:
+ n = int(buf)
+ except ValueError:
+ handler.unhandledControlSequence(b'\x1b[' + buf + b'P')
+ else:
+ handler.deleteCharacter(n)
+
+ def L(self, proto, handler, buf):
+ if not buf:
+ handler.insertLine(1)
+ else:
+ try:
+ n = int(buf)
+ except ValueError:
+ handler.unhandledControlSequence(b'\x1b[' + buf + b'L')
+ else:
+ handler.insertLine(n)
+
+
+ def M(self, proto, handler, buf):
+ if not buf:
+ handler.deleteLine(1)
+ else:
+ try:
+ n = int(buf)
+ except ValueError:
+ handler.unhandledControlSequence(b'\x1b[' + buf + b'M')
+ else:
+ handler.deleteLine(n)
+
+
+ def n(self, proto, handler, buf):
+ if buf == b'6':
+ x, y = handler.reportCursorPosition()
+ proto.transport.write(b'\x1b['
+ + intToBytes(x+1)
+ + b';'
+ + intToBytes(y+1)
+ + b'R')
+ else:
+ handler.unhandledControlSequence(b'\x1b[' + buf + b'n')
+
+
+ def m(self, proto, handler, buf):
+ if not buf:
+ handler.selectGraphicRendition(NORMAL)
+ else:
+ attrs = []
+ for a in buf.split(b';'):
+ try:
+ a = int(a)
+ except ValueError:
+ pass
+ attrs.append(a)
+ handler.selectGraphicRendition(*attrs)
+
+ controlSequenceParser = ControlSequenceParser()
+
+
+ def _handleHeightWidth(self, b):
+ if b == b'3':
+ self.terminal.doubleHeightLine(True)
+ elif b == b'4':
+ self.terminal.doubleHeightLine(False)
+ elif b == b'5':
+ self.terminal.singleWidthLine()
+ elif b == b'6':
+ self.terminal.doubleWidthLine()
+ else:
+ self.terminal.unhandledControlSequence(b'\x1b#' + b)
+
+
+__all__ = [
+ # Interfaces
+ 'ITerminalProtocol', 'ITerminalTransport',
+
+ # Symbolic constants
+ 'modes', 'privateModes', 'FUNCTION_KEYS',
+
+ 'CS_US', 'CS_UK', 'CS_DRAWING', 'CS_ALTERNATE', 'CS_ALTERNATE_SPECIAL',
+ 'G0', 'G1', 'G2', 'G3',
+
+ 'UNDERLINE', 'REVERSE_VIDEO', 'BLINK', 'BOLD', 'NORMAL',
+
+ # Protocol classes
+ 'ServerProtocol', 'ClientProtocol']
diff --git a/contrib/python/Twisted/py2/twisted/conch/insults/text.py b/contrib/python/Twisted/py2/twisted/conch/insults/text.py
new file mode 100644
index 0000000000..54476f71a1
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/insults/text.py
@@ -0,0 +1,176 @@
+# -*- test-case-name: twisted.conch.test.test_text -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Character attribute manipulation API.
+
+This module provides a domain-specific language (using Python syntax)
+for the creation of text with additional display attributes associated
+with it. It is intended as an alternative to manually building up
+strings containing ECMA 48 character attribute control codes. It
+currently supports foreground and background colors (black, red,
+green, yellow, blue, magenta, cyan, and white), intensity selection,
+underlining, blinking and reverse video. Character set selection
+support is planned.
+
+Character attributes are specified by using two Python operations:
+attribute lookup and indexing. For example, the string \"Hello
+world\" with red foreground and all other attributes set to their
+defaults, assuming the name twisted.conch.insults.text.attributes has
+been imported and bound to the name \"A\" (with the statement C{from
+twisted.conch.insults.text import attributes as A}, for example) one
+uses this expression::
+
+ A.fg.red[\"Hello world\"]
+
+Other foreground colors are set by substituting their name for
+\"red\". To set both a foreground and a background color, this
+expression is used::
+
+ A.fg.red[A.bg.green[\"Hello world\"]]
+
+Note that either A.bg.green can be nested within A.fg.red or vice
+versa. Also note that multiple items can be nested within a single
+index operation by separating them with commas::
+
+ A.bg.green[A.fg.red[\"Hello\"], " ", A.fg.blue[\"world\"]]
+
+Other character attributes are set in a similar fashion. To specify a
+blinking version of the previous expression::
+
+ A.blink[A.bg.green[A.fg.red[\"Hello\"], " ", A.fg.blue[\"world\"]]]
+
+C{A.reverseVideo}, C{A.underline}, and C{A.bold} are also valid.
+
+A third operation is actually supported: unary negation. This turns
+off an attribute when an enclosing expression would otherwise have
+caused it to be on. For example::
+
+ A.underline[A.fg.red[\"Hello\", -A.underline[\" world\"]]]
+
+A formatting structure can then be serialized into a string containing the
+necessary VT102 control codes with L{assembleFormattedText}.
+
+@see: L{twisted.conch.insults.text._CharacterAttributes}
+@author: Jp Calderone
+"""
+
+from incremental import Version
+
+from twisted.conch.insults import helper, insults
+from twisted.python import _textattributes
+from twisted.python.deprecate import deprecatedModuleAttribute
+
+
+
+flatten = _textattributes.flatten
+
+deprecatedModuleAttribute(
+ Version('Twisted', 13, 1, 0),
+ 'Use twisted.conch.insults.text.assembleFormattedText instead.',
+ 'twisted.conch.insults.text',
+ 'flatten')
+
+_TEXT_COLORS = {
+ 'black': helper.BLACK,
+ 'red': helper.RED,
+ 'green': helper.GREEN,
+ 'yellow': helper.YELLOW,
+ 'blue': helper.BLUE,
+ 'magenta': helper.MAGENTA,
+ 'cyan': helper.CYAN,
+ 'white': helper.WHITE}
+
+
+
+class _CharacterAttributes(_textattributes.CharacterAttributesMixin):
+ """
+ Factory for character attributes, including foreground and background color
+ and non-color attributes such as bold, reverse video and underline.
+
+ Character attributes are applied to actual text by using object
+ indexing-syntax (C{obj['abc']}) after accessing a factory attribute, for
+ example::
+
+ attributes.bold['Some text']
+
+ These can be nested to mix attributes::
+
+ attributes.bold[attributes.underline['Some text']]
+
+ And multiple values can be passed::
+
+ attributes.normal[attributes.bold['Some'], ' text']
+
+ Non-color attributes can be accessed by attribute name, available
+ attributes are:
+
+ - bold
+ - blink
+ - reverseVideo
+ - underline
+
+ Available colors are:
+
+ 0. black
+ 1. red
+ 2. green
+ 3. yellow
+ 4. blue
+ 5. magenta
+ 6. cyan
+ 7. white
+
+ @ivar fg: Foreground colors accessed by attribute name, see above
+ for possible names.
+
+ @ivar bg: Background colors accessed by attribute name, see above
+ for possible names.
+ """
+ fg = _textattributes._ColorAttribute(
+ _textattributes._ForegroundColorAttr, _TEXT_COLORS)
+ bg = _textattributes._ColorAttribute(
+ _textattributes._BackgroundColorAttr, _TEXT_COLORS)
+
+ attrs = {
+ 'bold': insults.BOLD,
+ 'blink': insults.BLINK,
+ 'underline': insults.UNDERLINE,
+ 'reverseVideo': insults.REVERSE_VIDEO}
+
+
+
+def assembleFormattedText(formatted):
+ """
+ Assemble formatted text from structured information.
+
+ Currently handled formatting includes: bold, blink, reverse, underline and
+ color codes.
+
+ For example::
+
+ from twisted.conch.insults.text import attributes as A
+ assembleFormattedText(
+ A.normal[A.bold['Time: '], A.fg.lightRed['Now!']])
+
+ Would produce "Time: " in bold formatting, followed by "Now!" with a
+ foreground color of light red and without any additional formatting.
+
+ @param formatted: Structured text and attributes.
+
+ @rtype: L{str}
+ @return: String containing VT102 control sequences that mimic those
+ specified by C{formatted}.
+
+ @see: L{twisted.conch.insults.text._CharacterAttributes}
+ @since: 13.1
+ """
+ return _textattributes.flatten(
+ formatted, helper._FormattingState(), 'toVT102')
+
+
+
+attributes = _CharacterAttributes()
+
+__all__ = ['attributes', 'flatten']
diff --git a/contrib/python/Twisted/py2/twisted/conch/insults/window.py b/contrib/python/Twisted/py2/twisted/conch/insults/window.py
new file mode 100644
index 0000000000..d3caf7d3f4
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/insults/window.py
@@ -0,0 +1,1027 @@
+# -*- test-case-name: twisted.conch.test.test_window -*-
+
+"""
+Simple insults-based widget library
+
+@author: Jp Calderone
+"""
+
+import array
+
+from twisted.conch.insults import insults, helper
+from twisted.python import text as tptext
+from twisted.python.compat import (_PY3, _bytesChr as chr)
+
+class YieldFocus(Exception):
+ """
+ Input focus manipulation exception
+ """
+
+
+
+class BoundedTerminalWrapper(object):
+ def __init__(self, terminal, width, height, xoff, yoff):
+ self.width = width
+ self.height = height
+ self.xoff = xoff
+ self.yoff = yoff
+ self.terminal = terminal
+ self.cursorForward = terminal.cursorForward
+ self.selectCharacterSet = terminal.selectCharacterSet
+ self.selectGraphicRendition = terminal.selectGraphicRendition
+ self.saveCursor = terminal.saveCursor
+ self.restoreCursor = terminal.restoreCursor
+
+
+ def cursorPosition(self, x, y):
+ return self.terminal.cursorPosition(
+ self.xoff + min(self.width, x),
+ self.yoff + min(self.height, y)
+ )
+
+
+ def cursorHome(self):
+ return self.terminal.cursorPosition(
+ self.xoff, self.yoff)
+
+
+ def write(self, data):
+ return self.terminal.write(data)
+
+
+
+class Widget(object):
+ focused = False
+ parent = None
+ dirty = False
+ width = height = None
+
+ def repaint(self):
+ if not self.dirty:
+ self.dirty = True
+ if self.parent is not None and not self.parent.dirty:
+ self.parent.repaint()
+
+
+ def filthy(self):
+ self.dirty = True
+
+
+ def redraw(self, width, height, terminal):
+ self.filthy()
+ self.draw(width, height, terminal)
+
+
+ def draw(self, width, height, terminal):
+ if width != self.width or height != self.height or self.dirty:
+ self.width = width
+ self.height = height
+ self.dirty = False
+ self.render(width, height, terminal)
+
+
+ def render(self, width, height, terminal):
+ pass
+
+
+ def sizeHint(self):
+ return None
+
+
+ def keystrokeReceived(self, keyID, modifier):
+ if keyID == b'\t':
+ self.tabReceived(modifier)
+ elif keyID == b'\x7f':
+ self.backspaceReceived()
+ elif keyID in insults.FUNCTION_KEYS:
+ self.functionKeyReceived(keyID, modifier)
+ else:
+ self.characterReceived(keyID, modifier)
+
+
+ def tabReceived(self, modifier):
+ # XXX TODO - Handle shift+tab
+ raise YieldFocus()
+
+
+ def focusReceived(self):
+ """
+ Called when focus is being given to this widget.
+
+ May raise YieldFocus is this widget does not want focus.
+ """
+ self.focused = True
+ self.repaint()
+
+
+ def focusLost(self):
+ self.focused = False
+ self.repaint()
+
+
+ def backspaceReceived(self):
+ pass
+
+
+ def functionKeyReceived(self, keyID, modifier):
+ name = keyID
+ if not isinstance(keyID, str):
+ name = name.decode("utf-8")
+ func = getattr(self, 'func_' + name, None)
+ if func is not None:
+ func(modifier)
+
+
+ def characterReceived(self, keyID, modifier):
+ pass
+
+
+
+class ContainerWidget(Widget):
+ """
+ @ivar focusedChild: The contained widget which currently has
+ focus, or None.
+ """
+ focusedChild = None
+ focused = False
+
+ def __init__(self):
+ Widget.__init__(self)
+ self.children = []
+
+
+ def addChild(self, child):
+ assert child.parent is None
+ child.parent = self
+ self.children.append(child)
+ if self.focusedChild is None and self.focused:
+ try:
+ child.focusReceived()
+ except YieldFocus:
+ pass
+ else:
+ self.focusedChild = child
+ self.repaint()
+
+
+ def remChild(self, child):
+ assert child.parent is self
+ child.parent = None
+ self.children.remove(child)
+ self.repaint()
+
+
+ def filthy(self):
+ for ch in self.children:
+ ch.filthy()
+ Widget.filthy(self)
+
+
+ def render(self, width, height, terminal):
+ for ch in self.children:
+ ch.draw(width, height, terminal)
+
+
+ def changeFocus(self):
+ self.repaint()
+
+ if self.focusedChild is not None:
+ self.focusedChild.focusLost()
+ focusedChild = self.focusedChild
+ self.focusedChild = None
+ try:
+ curFocus = self.children.index(focusedChild) + 1
+ except ValueError:
+ raise YieldFocus()
+ else:
+ curFocus = 0
+ while curFocus < len(self.children):
+ try:
+ self.children[curFocus].focusReceived()
+ except YieldFocus:
+ curFocus += 1
+ else:
+ self.focusedChild = self.children[curFocus]
+ return
+ # None of our children wanted focus
+ raise YieldFocus()
+
+
+ def focusReceived(self):
+ self.changeFocus()
+ self.focused = True
+
+
+ def keystrokeReceived(self, keyID, modifier):
+ if self.focusedChild is not None:
+ try:
+ self.focusedChild.keystrokeReceived(keyID, modifier)
+ except YieldFocus:
+ self.changeFocus()
+ self.repaint()
+ else:
+ Widget.keystrokeReceived(self, keyID, modifier)
+
+
+
+class TopWindow(ContainerWidget):
+ """
+ A top-level container object which provides focus wrap-around and paint
+ scheduling.
+
+ @ivar painter: A no-argument callable which will be invoked when this
+ widget needs to be redrawn.
+
+ @ivar scheduler: A one-argument callable which will be invoked with a
+ no-argument callable and should arrange for it to invoked at some point in
+ the near future. The no-argument callable will cause this widget and all
+ its children to be redrawn. It is typically beneficial for the no-argument
+ callable to be invoked at the end of handling for whatever event is
+ currently active; for example, it might make sense to call it at the end of
+ L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
+ Note, however, that since calls to this may also be made in response to no
+ apparent event, arrangements should be made for the function to be called
+ even if an event handler such as C{keystrokeReceived} is not on the call
+ stack (eg, using
+ L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
+ with a short timeout).
+ """
+ focused = True
+
+ def __init__(self, painter, scheduler):
+ ContainerWidget.__init__(self)
+ self.painter = painter
+ self.scheduler = scheduler
+
+ _paintCall = None
+
+
+ def repaint(self):
+ if self._paintCall is None:
+ self._paintCall = object()
+ self.scheduler(self._paint)
+ ContainerWidget.repaint(self)
+
+
+ def _paint(self):
+ self._paintCall = None
+ self.painter()
+
+
+ def changeFocus(self):
+ try:
+ ContainerWidget.changeFocus(self)
+ except YieldFocus:
+ try:
+ ContainerWidget.changeFocus(self)
+ except YieldFocus:
+ pass
+
+
+ def keystrokeReceived(self, keyID, modifier):
+ try:
+ ContainerWidget.keystrokeReceived(self, keyID, modifier)
+ except YieldFocus:
+ self.changeFocus()
+
+
+
+class AbsoluteBox(ContainerWidget):
+ def moveChild(self, child, x, y):
+ for n in range(len(self.children)):
+ if self.children[n][0] is child:
+ self.children[n] = (child, x, y)
+ break
+ else:
+ raise ValueError("No such child", child)
+
+
+ def render(self, width, height, terminal):
+ for (ch, x, y) in self.children:
+ wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
+ ch.draw(width, height, wrap)
+
+
+
+class _Box(ContainerWidget):
+ TOP, CENTER, BOTTOM = range(3)
+
+ def __init__(self, gravity=CENTER):
+ ContainerWidget.__init__(self)
+ self.gravity = gravity
+
+
+ def sizeHint(self):
+ height = 0
+ width = 0
+ for ch in self.children:
+ hint = ch.sizeHint()
+ if hint is None:
+ hint = (None, None)
+
+ if self.variableDimension == 0:
+ if hint[0] is None:
+ width = None
+ elif width is not None:
+ width += hint[0]
+ if hint[1] is None:
+ height = None
+ elif height is not None:
+ height = max(height, hint[1])
+ else:
+ if hint[0] is None:
+ width = None
+ elif width is not None:
+ width = max(width, hint[0])
+ if hint[1] is None:
+ height = None
+ elif height is not None:
+ height += hint[1]
+
+ return width, height
+
+
+ def render(self, width, height, terminal):
+ if not self.children:
+ return
+
+ greedy = 0
+ wants = []
+ for ch in self.children:
+ hint = ch.sizeHint()
+ if hint is None:
+ hint = (None, None)
+ if hint[self.variableDimension] is None:
+ greedy += 1
+ wants.append(hint[self.variableDimension])
+
+ length = (width, height)[self.variableDimension]
+ totalWant = sum([w for w in wants if w is not None])
+ if greedy:
+ leftForGreedy = int((length - totalWant) / greedy)
+
+ widthOffset = heightOffset = 0
+
+ for want, ch in zip(wants, self.children):
+ if want is None:
+ want = leftForGreedy
+
+ subWidth, subHeight = width, height
+ if self.variableDimension == 0:
+ subWidth = want
+ else:
+ subHeight = want
+
+ wrap = BoundedTerminalWrapper(
+ terminal,
+ subWidth,
+ subHeight,
+ widthOffset,
+ heightOffset,
+ )
+ ch.draw(subWidth, subHeight, wrap)
+ if self.variableDimension == 0:
+ widthOffset += want
+ else:
+ heightOffset += want
+
+
+
+class HBox(_Box):
+ variableDimension = 0
+
+
+
+class VBox(_Box):
+ variableDimension = 1
+
+
+
+class Packer(ContainerWidget):
+ def render(self, width, height, terminal):
+ if not self.children:
+ return
+
+ root = int(len(self.children) ** 0.5 + 0.5)
+ boxes = [VBox() for n in range(root)]
+ for n, ch in enumerate(self.children):
+ boxes[n % len(boxes)].addChild(ch)
+ h = HBox()
+ map(h.addChild, boxes)
+ h.render(width, height, terminal)
+
+
+
+class Canvas(Widget):
+ focused = False
+
+ contents = None
+
+ def __init__(self):
+ Widget.__init__(self)
+ self.resize(1, 1)
+
+
+ def resize(self, width, height):
+ contents = array.array('B', b' ' * width * height)
+ if self.contents is not None:
+ for x in range(min(width, self._width)):
+ for y in range(min(height, self._height)):
+ contents[width * y + x] = self[x, y]
+ self.contents = contents
+ self._width = width
+ self._height = height
+ if self.x >= width:
+ self.x = width - 1
+ if self.y >= height:
+ self.y = height - 1
+
+
+ def __getitem__(self, index):
+ (x, y) = index
+ return self.contents[(self._width * y) + x]
+
+
+ def __setitem__(self, index, value):
+ (x, y) = index
+ self.contents[(self._width * y) + x] = value
+
+
+ def clear(self):
+ self.contents = array.array('B', b' ' * len(self.contents))
+
+
+ def render(self, width, height, terminal):
+ if not width or not height:
+ return
+
+ if width != self._width or height != self._height:
+ self.resize(width, height)
+ for i in range(height):
+ terminal.cursorPosition(0, i)
+ if _PY3:
+ text = self.contents[self._width * i:
+ self._width * i + self._width
+ ].tobytes()
+ else:
+ text = self.contents[self._width * i:
+ self._width * i + self._width
+ ].tostring()
+ text = text[:width]
+ terminal.write(text)
+
+
+
+def horizontalLine(terminal, y, left, right):
+ terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
+ terminal.cursorPosition(left, y)
+ terminal.write(chr(0o161) * (right - left))
+ terminal.selectCharacterSet(insults.CS_US, insults.G0)
+
+
+
+def verticalLine(terminal, x, top, bottom):
+ terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
+ for n in range(top, bottom):
+ terminal.cursorPosition(x, n)
+ terminal.write(chr(0o170))
+ terminal.selectCharacterSet(insults.CS_US, insults.G0)
+
+
+def rectangle(terminal, position, dimension):
+ """
+ Draw a rectangle
+
+ @type position: L{tuple}
+ @param position: A tuple of the (top, left) coordinates of the rectangle.
+ @type dimension: L{tuple}
+ @param dimension: A tuple of the (width, height) size of the rectangle.
+ """
+ (top, left) = position
+ (width, height) = dimension
+ terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
+
+ terminal.cursorPosition(top, left)
+ terminal.write(chr(0o154))
+ terminal.write(chr(0o161) * (width - 2))
+ terminal.write(chr(0o153))
+ for n in range(height - 2):
+ terminal.cursorPosition(left, top + n + 1)
+ terminal.write(chr(0o170))
+ terminal.cursorForward(width - 2)
+ terminal.write(chr(0o170))
+ terminal.cursorPosition(0, top + height - 1)
+ terminal.write(chr(0o155))
+ terminal.write(chr(0o161) * (width - 2))
+ terminal.write(chr(0o152))
+
+ terminal.selectCharacterSet(insults.CS_US, insults.G0)
+
+
+
+class Border(Widget):
+ def __init__(self, containee):
+ Widget.__init__(self)
+ self.containee = containee
+ self.containee.parent = self
+
+
+ def focusReceived(self):
+ return self.containee.focusReceived()
+
+
+ def focusLost(self):
+ return self.containee.focusLost()
+
+
+ def keystrokeReceived(self, keyID, modifier):
+ return self.containee.keystrokeReceived(keyID, modifier)
+
+
+ def sizeHint(self):
+ hint = self.containee.sizeHint()
+ if hint is None:
+ hint = (None, None)
+ if hint[0] is None:
+ x = None
+ else:
+ x = hint[0] + 2
+ if hint[1] is None:
+ y = None
+ else:
+ y = hint[1] + 2
+ return x, y
+
+
+ def filthy(self):
+ self.containee.filthy()
+ Widget.filthy(self)
+
+
+ def render(self, width, height, terminal):
+ if self.containee.focused:
+ terminal.write(b'\x1b[31m')
+ rectangle(terminal, (0, 0), (width, height))
+ terminal.write(b'\x1b[0m')
+ wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
+ self.containee.draw(width - 2, height - 2, wrap)
+
+
+
+class Button(Widget):
+ def __init__(self, label, onPress):
+ Widget.__init__(self)
+ self.label = label
+ self.onPress = onPress
+
+
+ def sizeHint(self):
+ return len(self.label), 1
+
+
+ def characterReceived(self, keyID, modifier):
+ if keyID == b'\r':
+ self.onPress()
+
+
+ def render(self, width, height, terminal):
+ terminal.cursorPosition(0, 0)
+ if self.focused:
+ terminal.write(b'\x1b[1m' + self.label + b'\x1b[0m')
+ else:
+ terminal.write(self.label)
+
+
+
+class TextInput(Widget):
+ def __init__(self, maxwidth, onSubmit):
+ Widget.__init__(self)
+ self.onSubmit = onSubmit
+ self.maxwidth = maxwidth
+ self.buffer = b''
+ self.cursor = 0
+
+
+ def setText(self, text):
+ self.buffer = text[:self.maxwidth]
+ self.cursor = len(self.buffer)
+ self.repaint()
+
+
+ def func_LEFT_ARROW(self, modifier):
+ if self.cursor > 0:
+ self.cursor -= 1
+ self.repaint()
+
+
+ def func_RIGHT_ARROW(self, modifier):
+ if self.cursor < len(self.buffer):
+ self.cursor += 1
+ self.repaint()
+
+
+ def backspaceReceived(self):
+ if self.cursor > 0:
+ self.buffer = self.buffer[:self.cursor - 1] + self.buffer[self.cursor:]
+ self.cursor -= 1
+ self.repaint()
+
+
+ def characterReceived(self, keyID, modifier):
+ if keyID == b'\r':
+ self.onSubmit(self.buffer)
+ else:
+ if len(self.buffer) < self.maxwidth:
+ self.buffer = self.buffer[:self.cursor] + keyID + self.buffer[self.cursor:]
+ self.cursor += 1
+ self.repaint()
+
+
+ def sizeHint(self):
+ return self.maxwidth + 1, 1
+
+
+ def render(self, width, height, terminal):
+ currentText = self._renderText()
+ terminal.cursorPosition(0, 0)
+ if self.focused:
+ terminal.write(currentText[:self.cursor])
+ cursor(terminal, currentText[self.cursor:self.cursor+1] or b' ')
+ terminal.write(currentText[self.cursor+1:])
+ terminal.write(b' ' * (self.maxwidth - len(currentText) + 1))
+ else:
+ more = self.maxwidth - len(currentText)
+ terminal.write(currentText + b'_' * more)
+
+
+ def _renderText(self):
+ return self.buffer
+
+
+
+class PasswordInput(TextInput):
+ def _renderText(self):
+ return '*' * len(self.buffer)
+
+
+
+class TextOutput(Widget):
+ text = b''
+
+ def __init__(self, size=None):
+ Widget.__init__(self)
+ self.size = size
+
+
+
+ def sizeHint(self):
+ return self.size
+
+
+
+ def render(self, width, height, terminal):
+ terminal.cursorPosition(0, 0)
+ text = self.text[:width]
+ terminal.write(text + b' ' * (width - len(text)))
+
+
+
+ def setText(self, text):
+ self.text = text
+ self.repaint()
+
+
+ def focusReceived(self):
+ raise YieldFocus()
+
+
+
+class TextOutputArea(TextOutput):
+ WRAP, TRUNCATE = range(2)
+
+ def __init__(self, size=None, longLines=WRAP):
+ TextOutput.__init__(self, size)
+ self.longLines = longLines
+
+
+ def render(self, width, height, terminal):
+ n = 0
+ inputLines = self.text.splitlines()
+ outputLines = []
+ while inputLines:
+ if self.longLines == self.WRAP:
+ line = inputLines.pop(0)
+ if not isinstance(line, str):
+ line = line.decode("utf-8")
+ wrappedLines = []
+ for wrappedLine in tptext.greedyWrap(line, width):
+ if not isinstance(wrappedLine, bytes):
+ wrappedLine = wrappedLine.encode("utf-8")
+ wrappedLines.append(wrappedLine)
+ outputLines.extend(wrappedLines or [b''])
+ else:
+ outputLines.append(inputLines.pop(0)[:width])
+ if len(outputLines) >= height:
+ break
+ for n, L in enumerate(outputLines[:height]):
+ terminal.cursorPosition(0, n)
+ terminal.write(L)
+
+
+
+class Viewport(Widget):
+ _xOffset = 0
+ _yOffset = 0
+
+ def xOffset():
+ def get(self):
+ return self._xOffset
+ def set(self, value):
+ if self._xOffset != value:
+ self._xOffset = value
+ self.repaint()
+ return get, set
+ xOffset = property(*xOffset())
+
+
+ def yOffset():
+ def get(self):
+ return self._yOffset
+ def set(self, value):
+ if self._yOffset != value:
+ self._yOffset = value
+ self.repaint()
+ return get, set
+ yOffset = property(*yOffset())
+
+ _width = 160
+ _height = 24
+
+
+ def __init__(self, containee):
+ Widget.__init__(self)
+ self.containee = containee
+ self.containee.parent = self
+
+ self._buf = helper.TerminalBuffer()
+ self._buf.width = self._width
+ self._buf.height = self._height
+ self._buf.connectionMade()
+
+
+ def filthy(self):
+ self.containee.filthy()
+ Widget.filthy(self)
+
+
+ def render(self, width, height, terminal):
+ self.containee.draw(self._width, self._height, self._buf)
+
+ # XXX /Lame/
+ for y, line in enumerate(self._buf.lines[self._yOffset:self._yOffset + height]):
+ terminal.cursorPosition(0, y)
+ n = 0
+ for n, (ch, attr) in enumerate(line[self._xOffset:self._xOffset + width]):
+ if ch is self._buf.void:
+ ch = b' '
+ terminal.write(ch)
+ if n < width:
+ terminal.write(b' ' * (width - n - 1))
+
+
+
+class _Scrollbar(Widget):
+ def __init__(self, onScroll):
+ Widget.__init__(self)
+ self.onScroll = onScroll
+ self.percent = 0.0
+
+
+ def smaller(self):
+ self.percent = min(1.0, max(0.0, self.onScroll(-1)))
+ self.repaint()
+
+
+ def bigger(self):
+ self.percent = min(1.0, max(0.0, self.onScroll(+1)))
+ self.repaint()
+
+
+
+class HorizontalScrollbar(_Scrollbar):
+ def sizeHint(self):
+ return (None, 1)
+
+
+ def func_LEFT_ARROW(self, modifier):
+ self.smaller()
+
+
+ def func_RIGHT_ARROW(self, modifier):
+ self.bigger()
+
+ _left = u'\N{BLACK LEFT-POINTING TRIANGLE}'
+ _right = u'\N{BLACK RIGHT-POINTING TRIANGLE}'
+ _bar = u'\N{LIGHT SHADE}'
+ _slider = u'\N{DARK SHADE}'
+
+
+ def render(self, width, height, terminal):
+ terminal.cursorPosition(0, 0)
+ n = width - 3
+ before = int(n * self.percent)
+ after = n - before
+ me = self._left + (self._bar * before) + self._slider + (self._bar * after) + self._right
+ terminal.write(me.encode('utf-8'))
+
+
+
+class VerticalScrollbar(_Scrollbar):
+ def sizeHint(self):
+ return (1, None)
+
+
+ def func_UP_ARROW(self, modifier):
+ self.smaller()
+
+
+ def func_DOWN_ARROW(self, modifier):
+ self.bigger()
+
+ _up = u'\N{BLACK UP-POINTING TRIANGLE}'
+ _down = u'\N{BLACK DOWN-POINTING TRIANGLE}'
+ _bar = u'\N{LIGHT SHADE}'
+ _slider = u'\N{DARK SHADE}'
+
+
+ def render(self, width, height, terminal):
+ terminal.cursorPosition(0, 0)
+ knob = int(self.percent * (height - 2))
+ terminal.write(self._up.encode('utf-8'))
+ for i in range(1, height - 1):
+ terminal.cursorPosition(0, i)
+ if i != (knob + 1):
+ terminal.write(self._bar.encode('utf-8'))
+ else:
+ terminal.write(self._slider.encode('utf-8'))
+ terminal.cursorPosition(0, height - 1)
+ terminal.write(self._down.encode('utf-8'))
+
+
+
+class ScrolledArea(Widget):
+ """
+ A L{ScrolledArea} contains another widget wrapped in a viewport and
+ vertical and horizontal scrollbars for moving the viewport around.
+ """
+ def __init__(self, containee):
+ Widget.__init__(self)
+ self._viewport = Viewport(containee)
+ self._horiz = HorizontalScrollbar(self._horizScroll)
+ self._vert = VerticalScrollbar(self._vertScroll)
+
+ for w in self._viewport, self._horiz, self._vert:
+ w.parent = self
+
+
+ def _horizScroll(self, n):
+ self._viewport.xOffset += n
+ self._viewport.xOffset = max(0, self._viewport.xOffset)
+ return self._viewport.xOffset / 25.0
+
+
+ def _vertScroll(self, n):
+ self._viewport.yOffset += n
+ self._viewport.yOffset = max(0, self._viewport.yOffset)
+ return self._viewport.yOffset / 25.0
+
+
+ def func_UP_ARROW(self, modifier):
+ self._vert.smaller()
+
+
+ def func_DOWN_ARROW(self, modifier):
+ self._vert.bigger()
+
+
+ def func_LEFT_ARROW(self, modifier):
+ self._horiz.smaller()
+
+
+ def func_RIGHT_ARROW(self, modifier):
+ self._horiz.bigger()
+
+
+ def filthy(self):
+ self._viewport.filthy()
+ self._horiz.filthy()
+ self._vert.filthy()
+ Widget.filthy(self)
+
+
+ def render(self, width, height, terminal):
+ wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
+ self._viewport.draw(width - 2, height - 2, wrapper)
+ if self.focused:
+ terminal.write(b'\x1b[31m')
+ horizontalLine(terminal, 0, 1, width - 1)
+ verticalLine(terminal, 0, 1, height - 1)
+ self._vert.draw(1, height - 1, BoundedTerminalWrapper(terminal, 1, height - 1, width - 1, 0))
+ self._horiz.draw(width, 1, BoundedTerminalWrapper(terminal, width, 1, 0, height - 1))
+ terminal.write(b'\x1b[0m')
+
+
+
+def cursor(terminal, ch):
+ terminal.saveCursor()
+ terminal.selectGraphicRendition(str(insults.REVERSE_VIDEO))
+ terminal.write(ch)
+ terminal.restoreCursor()
+ terminal.cursorForward()
+
+
+
+class Selection(Widget):
+ # Index into the sequence
+ focusedIndex = 0
+
+ # Offset into the displayed subset of the sequence
+ renderOffset = 0
+
+ def __init__(self, sequence, onSelect, minVisible=None):
+ Widget.__init__(self)
+ self.sequence = sequence
+ self.onSelect = onSelect
+ self.minVisible = minVisible
+ if minVisible is not None:
+ self._width = max(map(len, self.sequence))
+
+
+ def sizeHint(self):
+ if self.minVisible is not None:
+ return self._width, self.minVisible
+
+
+ def func_UP_ARROW(self, modifier):
+ if self.focusedIndex > 0:
+ self.focusedIndex -= 1
+ if self.renderOffset > 0:
+ self.renderOffset -= 1
+ self.repaint()
+
+
+ def func_PGUP(self, modifier):
+ if self.renderOffset != 0:
+ self.focusedIndex -= self.renderOffset
+ self.renderOffset = 0
+ else:
+ self.focusedIndex = max(0, self.focusedIndex - self.height)
+ self.repaint()
+
+
+ def func_DOWN_ARROW(self, modifier):
+ if self.focusedIndex < len(self.sequence) - 1:
+ self.focusedIndex += 1
+ if self.renderOffset < self.height - 1:
+ self.renderOffset += 1
+ self.repaint()
+
+
+ def func_PGDN(self, modifier):
+ if self.renderOffset != self.height - 1:
+ change = self.height - self.renderOffset - 1
+ if change + self.focusedIndex >= len(self.sequence):
+ change = len(self.sequence) - self.focusedIndex - 1
+ self.focusedIndex += change
+ self.renderOffset = self.height - 1
+ else:
+ self.focusedIndex = min(len(self.sequence) - 1, self.focusedIndex + self.height)
+ self.repaint()
+
+
+ def characterReceived(self, keyID, modifier):
+ if keyID == b'\r':
+ self.onSelect(self.sequence[self.focusedIndex])
+
+
+ def render(self, width, height, terminal):
+ self.height = height
+ start = self.focusedIndex - self.renderOffset
+ if start > len(self.sequence) - height:
+ start = max(0, len(self.sequence) - height)
+
+ elements = self.sequence[start:start+height]
+
+ for n, ele in enumerate(elements):
+ terminal.cursorPosition(0, n)
+ if n == self.renderOffset:
+ terminal.saveCursor()
+ if self.focused:
+ modes = str(insults.REVERSE_VIDEO), str(insults.BOLD)
+ else:
+ modes = str(insults.REVERSE_VIDEO),
+ terminal.selectGraphicRendition(*modes)
+ text = ele[:width]
+ terminal.write(text + (b' ' * (width - len(text))))
+ if n == self.renderOffset:
+ terminal.restoreCursor()
diff --git a/contrib/python/Twisted/py2/twisted/conch/interfaces.py b/contrib/python/Twisted/py2/twisted/conch/interfaces.py
new file mode 100644
index 0000000000..cdf5489898
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/interfaces.py
@@ -0,0 +1,444 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This module contains interfaces defined for the L{twisted.conch} package.
+"""
+
+from zope.interface import Interface, Attribute
+
+class IConchUser(Interface):
+ """
+ A user who has been authenticated to Cred through Conch. This is
+ the interface between the SSH connection and the user.
+ """
+
+ conn = Attribute('The SSHConnection object for this user.')
+
+ def lookupChannel(channelType, windowSize, maxPacket, data):
+ """
+ The other side requested a channel of some sort.
+
+ C{channelType} is the type of channel being requested,
+ as an ssh connection protocol channel type.
+ C{data} is any other packet data (often nothing).
+
+ We return a subclass of L{SSHChannel<ssh.channel.SSHChannel>}. If
+ the channel type is unknown, we return C{None}.
+
+ For other failures, we raise an exception. If a
+ L{ConchError<error.ConchError>} is raised, the C{.value} will
+ be the message, and the C{.data} will be the error code.
+
+ @param channelType: The requested channel type
+ @type channelType: L{bytes}
+ @param windowSize: The initial size of the remote window
+ @type windowSize: L{int}
+ @param maxPacket: The largest packet we should send
+ @type maxPacket: L{int}
+ @param data: Additional request data
+ @type data: L{bytes}
+ @rtype: a subclass of L{SSHChannel} or L{None}
+ """
+
+ def lookupSubsystem(subsystem, data):
+ """
+ The other side requested a subsystem.
+
+ We return a L{Protocol} implementing the requested subsystem.
+ If the subsystem is not available, we return C{None}.
+
+ @param subsystem: The name of the subsystem being requested
+ @type subsystem: L{bytes}
+ @param data: Additional request data (often nothing)
+ @type data: L{bytes}
+ @rtype: L{Protocol} or L{None}
+ """
+
+ def gotGlobalRequest(requestType, data):
+ """
+ A global request was sent from the other side.
+
+ We return a true value on success or a false value on failure.
+ If we indicate success by returning a tuple, its second item
+ will be sent to the other side as additional response data.
+
+ @param requestType: The type of the request
+ @type requestType: L{bytes}
+ @param data: Additional request data
+ @type data: L{bytes}
+ @rtype: boolean or L{tuple}
+ """
+
+
+
+class ISession(Interface):
+
+ def getPty(term, windowSize, modes):
+ """
+ Get a pseudo-terminal for use by a shell or command.
+
+ If a pseudo-terminal is not available, or the request otherwise
+ fails, raise an exception.
+ """
+
+ def openShell(proto):
+ """
+ Open a shell and connect it to proto.
+
+ @param proto: a L{ProcessProtocol} instance.
+ """
+
+ def execCommand(proto, command):
+ """
+ Execute a command.
+
+ @param proto: a L{ProcessProtocol} instance.
+ """
+
+ def windowChanged(newWindowSize):
+ """
+ Called when the size of the remote screen has changed.
+ """
+
+ def eofReceived():
+ """
+ Called when the other side has indicated no more data will be sent.
+ """
+
+ def closed():
+ """
+ Called when the session is closed.
+ """
+
+
+
+class ISFTPServer(Interface):
+ """
+ SFTP subsystem for server-side communication.
+
+ Each method should check to verify that the user has permission for
+ their actions.
+ """
+
+ avatar = Attribute(
+ """
+ The avatar returned by the Realm that we are authenticated with,
+ and represents the logged-in user.
+ """)
+
+
+ def gotVersion(otherVersion, extData):
+ """
+ Called when the client sends their version info.
+
+ otherVersion is an integer representing the version of the SFTP
+ protocol they are claiming.
+ extData is a dictionary of extended_name : extended_data items.
+ These items are sent by the client to indicate additional features.
+
+ This method should return a dictionary of extended_name : extended_data
+ items. These items are the additional features (if any) supported
+ by the server.
+ """
+ return {}
+
+
+ def openFile(filename, flags, attrs):
+ """
+ Called when the clients asks to open a file.
+
+ @param filename: a string representing the file to open.
+
+ @param flags: an integer of the flags to open the file with, ORed
+ together. The flags and their values are listed at the bottom of
+ L{twisted.conch.ssh.filetransfer} as FXF_*.
+
+ @param attrs: a list of attributes to open the file with. It is a
+ dictionary, consisting of 0 or more keys. The possible keys are::
+
+ size: the size of the file in bytes
+ uid: the user ID of the file as an integer
+ gid: the group ID of the file as an integer
+ permissions: the permissions of the file with as an integer.
+ the bit representation of this field is defined by POSIX.
+ atime: the access time of the file as seconds since the epoch.
+ mtime: the modification time of the file as seconds since the epoch.
+ ext_*: extended attributes. The server is not required to
+ understand this, but it may.
+
+ NOTE: there is no way to indicate text or binary files. it is up
+ to the SFTP client to deal with this.
+
+ This method returns an object that meets the ISFTPFile interface.
+ Alternatively, it can return a L{Deferred} that will be called back
+ with the object.
+ """
+
+
+ def removeFile(filename):
+ """
+ Remove the given file.
+
+ This method returns when the remove succeeds, or a Deferred that is
+ called back when it succeeds.
+
+ @param filename: the name of the file as a string.
+ """
+
+
+ def renameFile(oldpath, newpath):
+ """
+ Rename the given file.
+
+ This method returns when the rename succeeds, or a L{Deferred} that is
+ called back when it succeeds. If the rename fails, C{renameFile} will
+ raise an implementation-dependent exception.
+
+ @param oldpath: the current location of the file.
+ @param newpath: the new file name.
+ """
+
+
+ def makeDirectory(path, attrs):
+ """
+ Make a directory.
+
+ This method returns when the directory is created, or a Deferred that
+ is called back when it is created.
+
+ @param path: the name of the directory to create as a string.
+ @param attrs: a dictionary of attributes to create the directory with.
+ Its meaning is the same as the attrs in the L{openFile} method.
+ """
+
+
+ def removeDirectory(path):
+ """
+ Remove a directory (non-recursively)
+
+ It is an error to remove a directory that has files or directories in
+ it.
+
+ This method returns when the directory is removed, or a Deferred that
+ is called back when it is removed.
+
+ @param path: the directory to remove.
+ """
+
+
+ def openDirectory(path):
+ """
+ Open a directory for scanning.
+
+ This method returns an iterable object that has a close() method,
+ or a Deferred that is called back with same.
+
+ The close() method is called when the client is finished reading
+ from the directory. At this point, the iterable will no longer
+ be used.
+
+ The iterable should return triples of the form (filename,
+ longname, attrs) or Deferreds that return the same. The
+ sequence must support __getitem__, but otherwise may be any
+ 'sequence-like' object.
+
+ filename is the name of the file relative to the directory.
+ logname is an expanded format of the filename. The recommended format
+ is:
+ -rwxr-xr-x 1 mjos staff 348911 Mar 25 14:29 t-filexfer
+ 1234567890 123 12345678 12345678 12345678 123456789012
+
+ The first line is sample output, the second is the length of the field.
+ The fields are: permissions, link count, user owner, group owner,
+ size in bytes, modification time.
+
+ attrs is a dictionary in the format of the attrs argument to openFile.
+
+ @param path: the directory to open.
+ """
+
+
+ def getAttrs(path, followLinks):
+ """
+ Return the attributes for the given path.
+
+ This method returns a dictionary in the same format as the attrs
+ argument to openFile or a Deferred that is called back with same.
+
+ @param path: the path to return attributes for as a string.
+ @param followLinks: a boolean. If it is True, follow symbolic links
+ and return attributes for the real path at the base. If it is False,
+ return attributes for the specified path.
+ """
+
+
+ def setAttrs(path, attrs):
+ """
+ Set the attributes for the path.
+
+ This method returns when the attributes are set or a Deferred that is
+ called back when they are.
+
+ @param path: the path to set attributes for as a string.
+ @param attrs: a dictionary in the same format as the attrs argument to
+ L{openFile}.
+ """
+
+
+ def readLink(path):
+ """
+ Find the root of a set of symbolic links.
+
+ This method returns the target of the link, or a Deferred that
+ returns the same.
+
+ @param path: the path of the symlink to read.
+ """
+
+
+ def makeLink(linkPath, targetPath):
+ """
+ Create a symbolic link.
+
+ This method returns when the link is made, or a Deferred that
+ returns the same.
+
+ @param linkPath: the pathname of the symlink as a string.
+ @param targetPath: the path of the target of the link as a string.
+ """
+
+
+ def realPath(path):
+ """
+ Convert any path to an absolute path.
+
+ This method returns the absolute path as a string, or a Deferred
+ that returns the same.
+
+ @param path: the path to convert as a string.
+ """
+
+
+ def extendedRequest(extendedName, extendedData):
+ """
+ This is the extension mechanism for SFTP. The other side can send us
+ arbitrary requests.
+
+ If we don't implement the request given by extendedName, raise
+ NotImplementedError.
+
+ The return value is a string, or a Deferred that will be called
+ back with a string.
+
+ @param extendedName: the name of the request as a string.
+ @param extendedData: the data the other side sent with the request,
+ as a string.
+ """
+
+
+
+class IKnownHostEntry(Interface):
+ """
+ A L{IKnownHostEntry} is an entry in an OpenSSH-formatted C{known_hosts}
+ file.
+
+ @since: 8.2
+ """
+
+ def matchesKey(key):
+ """
+ Return True if this entry matches the given Key object, False
+ otherwise.
+
+ @param key: The key object to match against.
+ @type key: L{twisted.conch.ssh.keys.Key}
+ """
+
+
+ def matchesHost(hostname):
+ """
+ Return True if this entry matches the given hostname, False otherwise.
+
+ Note that this does no name resolution; if you want to match an IP
+ address, you have to resolve it yourself, and pass it in as a dotted
+ quad string.
+
+ @param hostname: The hostname to match against.
+ @type hostname: L{str}
+ """
+
+
+ def toString():
+ """
+
+ @return: a serialized string representation of this entry, suitable for
+ inclusion in a known_hosts file. (Newline not included.)
+
+ @rtype: L{str}
+ """
+
+
+
+class ISFTPFile(Interface):
+ """
+ This represents an open file on the server. An object adhering to this
+ interface should be returned from L{openFile}().
+ """
+
+ def close():
+ """
+ Close the file.
+
+ This method returns nothing if the close succeeds immediately, or a
+ Deferred that is called back when the close succeeds.
+ """
+
+
+ def readChunk(offset, length):
+ """
+ Read from the file.
+
+ If EOF is reached before any data is read, raise EOFError.
+
+ This method returns the data as a string, or a Deferred that is
+ called back with same.
+
+ @param offset: an integer that is the index to start from in the file.
+ @param length: the maximum length of data to return. The actual amount
+ returned may less than this. For normal disk files, however,
+ this should read the requested number (up to the end of the file).
+ """
+
+
+ def writeChunk(offset, data):
+ """
+ Write to the file.
+
+ This method returns when the write completes, or a Deferred that is
+ called when it completes.
+
+ @param offset: an integer that is the index to start from in the file.
+ @param data: a string that is the data to write.
+ """
+
+
+ def getAttrs():
+ """
+ Return the attributes for the file.
+
+ This method returns a dictionary in the same format as the attrs
+ argument to L{openFile} or a L{Deferred} that is called back with same.
+ """
+
+
+ def setAttrs(attrs):
+ """
+ Set the attributes for the file.
+
+ This method returns when the attributes are set or a Deferred that is
+ called back when they are.
+
+ @param attrs: a dictionary in the same format as the attrs argument to
+ L{openFile}.
+ """
diff --git a/contrib/python/Twisted/py2/twisted/conch/ls.py b/contrib/python/Twisted/py2/twisted/conch/ls.py
new file mode 100644
index 0000000000..85da665dc9
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/ls.py
@@ -0,0 +1,83 @@
+# -*- test-case-name: twisted.conch.test.test_cftp -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import array
+import stat
+
+from time import time, strftime, localtime
+from twisted.python.compat import _PY3
+
+# Locale-independent month names to use instead of strftime's
+_MONTH_NAMES = dict(list(zip(
+ list(range(1, 13)),
+ "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split())))
+
+
+def lsLine(name, s):
+ """
+ Build an 'ls' line for a file ('file' in its generic sense, it
+ can be of any type).
+ """
+ mode = s.st_mode
+ perms = array.array('B', b'-'*10)
+ ft = stat.S_IFMT(mode)
+ if stat.S_ISDIR(ft): perms[0] = ord('d')
+ elif stat.S_ISCHR(ft): perms[0] = ord('c')
+ elif stat.S_ISBLK(ft): perms[0] = ord('b')
+ elif stat.S_ISREG(ft): perms[0] = ord('-')
+ elif stat.S_ISFIFO(ft): perms[0] = ord('f')
+ elif stat.S_ISLNK(ft): perms[0] = ord('l')
+ elif stat.S_ISSOCK(ft): perms[0] = ord('s')
+ else: perms[0] = ord('!')
+ # User
+ if mode&stat.S_IRUSR:perms[1] = ord('r')
+ if mode&stat.S_IWUSR:perms[2] = ord('w')
+ if mode&stat.S_IXUSR:perms[3] = ord('x')
+ # Group
+ if mode&stat.S_IRGRP:perms[4] = ord('r')
+ if mode&stat.S_IWGRP:perms[5] = ord('w')
+ if mode&stat.S_IXGRP:perms[6] = ord('x')
+ # Other
+ if mode&stat.S_IROTH:perms[7] = ord('r')
+ if mode&stat.S_IWOTH:perms[8] = ord('w')
+ if mode&stat.S_IXOTH:perms[9] = ord('x')
+ # Suid/sgid
+ if mode&stat.S_ISUID:
+ if perms[3] == ord('x'): perms[3] = ord('s')
+ else: perms[3] = ord('S')
+ if mode&stat.S_ISGID:
+ if perms[6] == ord('x'): perms[6] = ord('s')
+ else: perms[6] = ord('S')
+
+ if _PY3:
+ if isinstance(name, bytes):
+ name = name.decode("utf-8")
+ lsPerms = perms.tobytes()
+ lsPerms = lsPerms.decode("utf-8")
+ else:
+ lsPerms = perms.tostring()
+
+ lsresult = [
+ lsPerms,
+ str(s.st_nlink).rjust(5),
+ ' ',
+ str(s.st_uid).ljust(9),
+ str(s.st_gid).ljust(9),
+ str(s.st_size).rjust(8),
+ ' ',
+ ]
+ # Need to specify the month manually, as strftime depends on locale
+ ttup = localtime(s.st_mtime)
+ sixmonths = 60 * 60 * 24 * 7 * 26
+ if s.st_mtime + sixmonths < time(): # Last edited more than 6mo ago
+ strtime = strftime("%%s %d %Y ", ttup)
+ else:
+ strtime = strftime("%%s %d %H:%M ", ttup)
+ lsresult.append(strtime % (_MONTH_NAMES[ttup[1]],))
+
+ lsresult.append(name)
+ return ''.join(lsresult)
+
+
+__all__ = ['lsLine']
diff --git a/contrib/python/Twisted/py2/twisted/conch/manhole.py b/contrib/python/Twisted/py2/twisted/conch/manhole.py
new file mode 100644
index 0000000000..70e16b70cd
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/manhole.py
@@ -0,0 +1,401 @@
+# -*- test-case-name: twisted.conch.test.test_manhole -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Line-input oriented interactive interpreter loop.
+
+Provides classes for handling Python source input and arbitrary output
+interactively from a Twisted application. Also included is syntax coloring
+code with support for VT102 terminals, control code handling (^C, ^D, ^Q),
+and reasonable handling of Deferreds.
+
+@author: Jp Calderone
+"""
+
+import code, sys, tokenize
+from io import BytesIO
+
+from twisted.conch import recvline
+
+from twisted.internet import defer
+from twisted.python.compat import _tokenize, _get_async_param
+from twisted.python.htmlizer import TokenPrinter
+
+
+
+class FileWrapper:
+ """
+ Minimal write-file-like object.
+
+ Writes are translated into addOutput calls on an object passed to
+ __init__. Newlines are also converted from network to local style.
+ """
+
+ softspace = 0
+ state = 'normal'
+
+ def __init__(self, o):
+ self.o = o
+
+
+ def flush(self):
+ pass
+
+
+ def write(self, data):
+ self.o.addOutput(data.replace('\r\n', '\n'))
+
+
+ def writelines(self, lines):
+ self.write(''.join(lines))
+
+
+
+class ManholeInterpreter(code.InteractiveInterpreter):
+ """
+ Interactive Interpreter with special output and Deferred support.
+
+ Aside from the features provided by L{code.InteractiveInterpreter}, this
+ class captures sys.stdout output and redirects it to the appropriate
+ location (the Manhole protocol instance). It also treats Deferreds
+ which reach the top-level specially: each is formatted to the user with
+ a unique identifier and a new callback and errback added to it, each of
+ which will format the unique identifier and the result with which the
+ Deferred fires and then pass it on to the next participant in the
+ callback chain.
+ """
+
+ numDeferreds = 0
+ def __init__(self, handler, locals=None, filename="<console>"):
+ code.InteractiveInterpreter.__init__(self, locals)
+ self._pendingDeferreds = {}
+ self.handler = handler
+ self.filename = filename
+ self.resetBuffer()
+
+
+ def resetBuffer(self):
+ """
+ Reset the input buffer.
+ """
+ self.buffer = []
+
+
+ def push(self, line):
+ """
+ Push a line to the interpreter.
+
+ The line should not have a trailing newline; it may have
+ internal newlines. The line is appended to a buffer and the
+ interpreter's runsource() method is called with the
+ concatenated contents of the buffer as source. If this
+ indicates that the command was executed or invalid, the buffer
+ is reset; otherwise, the command is incomplete, and the buffer
+ is left as it was after the line was appended. The return
+ value is 1 if more input is required, 0 if the line was dealt
+ with in some way (this is the same as runsource()).
+
+ @param line: line of text
+ @type line: L{bytes}
+ @return: L{bool} from L{code.InteractiveInterpreter.runsource}
+ """
+ self.buffer.append(line)
+ source = b"\n".join(self.buffer)
+ source = source.decode("utf-8")
+ more = self.runsource(source, self.filename)
+ if not more:
+ self.resetBuffer()
+ return more
+
+
+ def runcode(self, *a, **kw):
+ orighook, sys.displayhook = sys.displayhook, self.displayhook
+ try:
+ origout, sys.stdout = sys.stdout, FileWrapper(self.handler)
+ try:
+ code.InteractiveInterpreter.runcode(self, *a, **kw)
+ finally:
+ sys.stdout = origout
+ finally:
+ sys.displayhook = orighook
+
+
+ def displayhook(self, obj):
+ self.locals['_'] = obj
+ if isinstance(obj, defer.Deferred):
+ # XXX Ick, where is my "hasFired()" interface?
+ if hasattr(obj, "result"):
+ self.write(repr(obj))
+ elif id(obj) in self._pendingDeferreds:
+ self.write("<Deferred #%d>" % (self._pendingDeferreds[id(obj)][0],))
+ else:
+ d = self._pendingDeferreds
+ k = self.numDeferreds
+ d[id(obj)] = (k, obj)
+ self.numDeferreds += 1
+ obj.addCallbacks(self._cbDisplayDeferred, self._ebDisplayDeferred,
+ callbackArgs=(k, obj), errbackArgs=(k, obj))
+ self.write("<Deferred #%d>" % (k,))
+ elif obj is not None:
+ self.write(repr(obj))
+
+
+ def _cbDisplayDeferred(self, result, k, obj):
+ self.write("Deferred #%d called back: %r" % (k, result), True)
+ del self._pendingDeferreds[id(obj)]
+ return result
+
+
+ def _ebDisplayDeferred(self, failure, k, obj):
+ self.write("Deferred #%d failed: %r" % (k, failure.getErrorMessage()), True)
+ del self._pendingDeferreds[id(obj)]
+ return failure
+
+
+ def write(self, data, isAsync=None, **kwargs):
+ isAsync = _get_async_param(isAsync, **kwargs)
+ self.handler.addOutput(data, isAsync)
+
+
+
+CTRL_C = b'\x03'
+CTRL_D = b'\x04'
+CTRL_BACKSLASH = b'\x1c'
+CTRL_L = b'\x0c'
+CTRL_A = b'\x01'
+CTRL_E = b'\x05'
+
+
+
+class Manhole(recvline.HistoricRecvLine):
+ """
+ Mediator between a fancy line source and an interactive interpreter.
+
+ This accepts lines from its transport and passes them on to a
+ L{ManholeInterpreter}. Control commands (^C, ^D, ^\) are also handled
+ with something approximating their normal terminal-mode behavior. It
+ can optionally be constructed with a dict which will be used as the
+ local namespace for any code executed.
+ """
+
+ namespace = None
+
+ def __init__(self, namespace=None):
+ recvline.HistoricRecvLine.__init__(self)
+ if namespace is not None:
+ self.namespace = namespace.copy()
+
+
+ def connectionMade(self):
+ recvline.HistoricRecvLine.connectionMade(self)
+ self.interpreter = ManholeInterpreter(self, self.namespace)
+ self.keyHandlers[CTRL_C] = self.handle_INT
+ self.keyHandlers[CTRL_D] = self.handle_EOF
+ self.keyHandlers[CTRL_L] = self.handle_FF
+ self.keyHandlers[CTRL_A] = self.handle_HOME
+ self.keyHandlers[CTRL_E] = self.handle_END
+ self.keyHandlers[CTRL_BACKSLASH] = self.handle_QUIT
+
+
+ def handle_INT(self):
+ """
+ Handle ^C as an interrupt keystroke by resetting the current input
+ variables to their initial state.
+ """
+ self.pn = 0
+ self.lineBuffer = []
+ self.lineBufferIndex = 0
+ self.interpreter.resetBuffer()
+
+ self.terminal.nextLine()
+ self.terminal.write(b"KeyboardInterrupt")
+ self.terminal.nextLine()
+ self.terminal.write(self.ps[self.pn])
+
+
+ def handle_EOF(self):
+ if self.lineBuffer:
+ self.terminal.write(b'\a')
+ else:
+ self.handle_QUIT()
+
+
+ def handle_FF(self):
+ """
+ Handle a 'form feed' byte - generally used to request a screen
+ refresh/redraw.
+ """
+ self.terminal.eraseDisplay()
+ self.terminal.cursorHome()
+ self.drawInputLine()
+
+
+ def handle_QUIT(self):
+ self.terminal.loseConnection()
+
+
+ def _needsNewline(self):
+ w = self.terminal.lastWrite
+ return not w.endswith(b'\n') and not w.endswith(b'\x1bE')
+
+
+ def addOutput(self, data, isAsync=None, **kwargs):
+ isAsync = _get_async_param(isAsync, **kwargs)
+ if isAsync:
+ self.terminal.eraseLine()
+ self.terminal.cursorBackward(len(self.lineBuffer) +
+ len(self.ps[self.pn]))
+
+ self.terminal.write(data)
+
+ if isAsync:
+ if self._needsNewline():
+ self.terminal.nextLine()
+
+ self.terminal.write(self.ps[self.pn])
+
+ if self.lineBuffer:
+ oldBuffer = self.lineBuffer
+ self.lineBuffer = []
+ self.lineBufferIndex = 0
+
+ self._deliverBuffer(oldBuffer)
+
+
+ def lineReceived(self, line):
+ more = self.interpreter.push(line)
+ self.pn = bool(more)
+ if self._needsNewline():
+ self.terminal.nextLine()
+ self.terminal.write(self.ps[self.pn])
+
+
+
+class VT102Writer:
+ """
+ Colorizer for Python tokens.
+
+ A series of tokens are written to instances of this object. Each is
+ colored in a particular way. The final line of the result of this is
+ generally added to the output.
+ """
+
+ typeToColor = {
+ 'identifier': b'\x1b[31m',
+ 'keyword': b'\x1b[32m',
+ 'parameter': b'\x1b[33m',
+ 'variable': b'\x1b[1;33m',
+ 'string': b'\x1b[35m',
+ 'number': b'\x1b[36m',
+ 'op': b'\x1b[37m'}
+
+ normalColor = b'\x1b[0m'
+
+ def __init__(self):
+ self.written = []
+
+
+ def color(self, type):
+ r = self.typeToColor.get(type, b'')
+ return r
+
+
+ def write(self, token, type=None):
+ if token and token != b'\r':
+ c = self.color(type)
+ if c:
+ self.written.append(c)
+ self.written.append(token)
+ if c:
+ self.written.append(self.normalColor)
+
+
+ def __bytes__(self):
+ s = b''.join(self.written)
+ return s.strip(b'\n').splitlines()[-1]
+
+ if bytes == str:
+ # Compat with Python 2.7
+ __str__ = __bytes__
+
+
+
+def lastColorizedLine(source):
+ """
+ Tokenize and colorize the given Python source.
+
+ Returns a VT102-format colorized version of the last line of C{source}.
+
+ @param source: Python source code
+ @type source: L{str} or L{bytes}
+ @return: L{bytes} of colorized source
+ """
+ if not isinstance(source, bytes):
+ source = source.encode("utf-8")
+ w = VT102Writer()
+ p = TokenPrinter(w.write).printtoken
+ s = BytesIO(source)
+
+ for token in _tokenize(s.readline):
+ (tokenType, string, start, end, line) = token
+ p(tokenType, string, start, end, line)
+
+ return bytes(w)
+
+
+
+class ColoredManhole(Manhole):
+ """
+ A REPL which syntax colors input as users type it.
+ """
+
+ def getSource(self):
+ """
+ Return a string containing the currently entered source.
+
+ This is only the code which will be considered for execution
+ next.
+ """
+ return (b'\n'.join(self.interpreter.buffer) +
+ b'\n' +
+ b''.join(self.lineBuffer))
+
+
+ def characterReceived(self, ch, moreCharactersComing):
+ if self.mode == 'insert':
+ self.lineBuffer.insert(self.lineBufferIndex, ch)
+ else:
+ self.lineBuffer[self.lineBufferIndex:self.lineBufferIndex+1] = [ch]
+ self.lineBufferIndex += 1
+
+ if moreCharactersComing:
+ # Skip it all, we'll get called with another character in
+ # like 2 femtoseconds.
+ return
+
+ if ch == b' ':
+ # Don't bother to try to color whitespace
+ self.terminal.write(ch)
+ return
+
+ source = self.getSource()
+
+ # Try to write some junk
+ try:
+ coloredLine = lastColorizedLine(source)
+ except tokenize.TokenError:
+ # We couldn't do it. Strange. Oh well, just add the character.
+ self.terminal.write(ch)
+ else:
+ # Success! Clear the source on this line.
+ self.terminal.eraseLine()
+ self.terminal.cursorBackward(len(self.lineBuffer) + len(self.ps[self.pn]) - 1)
+
+ # And write a new, colorized one.
+ self.terminal.write(self.ps[self.pn] + coloredLine)
+
+ # And move the cursor to where it belongs
+ n = len(self.lineBuffer) - self.lineBufferIndex
+ if n:
+ self.terminal.cursorBackward(n)
diff --git a/contrib/python/Twisted/py2/twisted/conch/manhole_ssh.py b/contrib/python/Twisted/py2/twisted/conch/manhole_ssh.py
new file mode 100644
index 0000000000..84b242f24e
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/manhole_ssh.py
@@ -0,0 +1,141 @@
+# -*- test-case-name: twisted.conch.test.test_manhole -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+insults/SSH integration support.
+
+@author: Jp Calderone
+"""
+
+from zope.interface import implementer
+
+from twisted.conch import avatar, interfaces as iconch, error as econch
+from twisted.conch.ssh import factory, session
+from twisted.python import components
+
+from twisted.conch.insults import insults
+
+
+class _Glue:
+ """
+ A feeble class for making one attribute look like another.
+
+ This should be replaced with a real class at some point, probably.
+ Try not to write new code that uses it.
+ """
+ def __init__(self, **kw):
+ self.__dict__.update(kw)
+
+
+ def __getattr__(self, name):
+ raise AttributeError(self.name, "has no attribute", name)
+
+
+
+class TerminalSessionTransport:
+ def __init__(self, proto, chainedProtocol, avatar, width, height):
+ self.proto = proto
+ self.avatar = avatar
+ self.chainedProtocol = chainedProtocol
+
+ protoSession = self.proto.session
+
+ self.proto.makeConnection(
+ _Glue(write=self.chainedProtocol.dataReceived,
+ loseConnection=lambda: avatar.conn.sendClose(protoSession),
+ name="SSH Proto Transport"))
+
+ def loseConnection():
+ self.proto.loseConnection()
+
+ self.chainedProtocol.makeConnection(
+ _Glue(write=self.proto.write,
+ loseConnection=loseConnection,
+ name="Chained Proto Transport"))
+
+ # XXX TODO
+ # chainedProtocol is supposed to be an ITerminalTransport,
+ # maybe. That means perhaps its terminalProtocol attribute is
+ # an ITerminalProtocol, it could be. So calling terminalSize
+ # on that should do the right thing But it'd be nice to clean
+ # this bit up.
+ self.chainedProtocol.terminalProtocol.terminalSize(width, height)
+
+
+
+@implementer(iconch.ISession)
+class TerminalSession(components.Adapter):
+ transportFactory = TerminalSessionTransport
+ chainedProtocolFactory = insults.ServerProtocol
+
+ def getPty(self, term, windowSize, attrs):
+ self.height, self.width = windowSize[:2]
+
+
+ def openShell(self, proto):
+ self.transportFactory(
+ proto, self.chainedProtocolFactory(),
+ iconch.IConchUser(self.original),
+ self.width, self.height)
+
+
+ def execCommand(self, proto, cmd):
+ raise econch.ConchError("Cannot execute commands")
+
+
+ def closed(self):
+ pass
+
+
+
+class TerminalUser(avatar.ConchUser, components.Adapter):
+ def __init__(self, original, avatarId):
+ components.Adapter.__init__(self, original)
+ avatar.ConchUser.__init__(self)
+ self.channelLookup[b'session'] = session.SSHSession
+
+
+
+class TerminalRealm:
+ userFactory = TerminalUser
+ sessionFactory = TerminalSession
+
+ transportFactory = TerminalSessionTransport
+ chainedProtocolFactory = insults.ServerProtocol
+
+ def _getAvatar(self, avatarId):
+ comp = components.Componentized()
+ user = self.userFactory(comp, avatarId)
+ sess = self.sessionFactory(comp)
+
+ sess.transportFactory = self.transportFactory
+ sess.chainedProtocolFactory = self.chainedProtocolFactory
+
+ comp.setComponent(iconch.IConchUser, user)
+ comp.setComponent(iconch.ISession, sess)
+
+ return user
+
+
+ def __init__(self, transportFactory=None):
+ if transportFactory is not None:
+ self.transportFactory = transportFactory
+
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ for i in interfaces:
+ if i is iconch.IConchUser:
+ return (iconch.IConchUser,
+ self._getAvatar(avatarId),
+ lambda: None)
+ raise NotImplementedError()
+
+
+
+class ConchFactory(factory.SSHFactory):
+ publicKeys = {}
+ privateKeys = {}
+
+ def __init__(self, portal):
+ self.portal = portal
diff --git a/contrib/python/Twisted/py2/twisted/conch/manhole_tap.py b/contrib/python/Twisted/py2/twisted/conch/manhole_tap.py
new file mode 100644
index 0000000000..b5fb78b60c
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/manhole_tap.py
@@ -0,0 +1,165 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+TAP plugin for creating telnet- and ssh-accessible manhole servers.
+
+@author: Jp Calderone
+"""
+
+from zope.interface import implementer
+
+from twisted.internet import protocol
+from twisted.application import service, strports
+from twisted.cred import portal, checkers
+from twisted.python import usage, filepath
+
+from twisted.conch import manhole, manhole_ssh, telnet
+from twisted.conch.insults import insults
+from twisted.conch.ssh import keys
+
+
+
+class makeTelnetProtocol:
+ def __init__(self, portal):
+ self.portal = portal
+
+ def __call__(self):
+ auth = telnet.AuthenticatingTelnetProtocol
+ args = (self.portal,)
+ return telnet.TelnetTransport(auth, *args)
+
+
+
+class chainedProtocolFactory:
+ def __init__(self, namespace):
+ self.namespace = namespace
+
+ def __call__(self):
+ return insults.ServerProtocol(manhole.ColoredManhole, self.namespace)
+
+
+
+@implementer(portal.IRealm)
+class _StupidRealm:
+ def __init__(self, proto, *a, **kw):
+ self.protocolFactory = proto
+ self.protocolArgs = a
+ self.protocolKwArgs = kw
+
+ def requestAvatar(self, avatarId, *interfaces):
+ if telnet.ITelnetProtocol in interfaces:
+ return (telnet.ITelnetProtocol,
+ self.protocolFactory(*self.protocolArgs,
+ **self.protocolKwArgs),
+ lambda: None)
+ raise NotImplementedError()
+
+
+
+class Options(usage.Options):
+ optParameters = [
+ ["telnetPort", "t", None,
+ ("strports description of the address on which to listen for telnet "
+ "connections")],
+ ["sshPort", "s", None,
+ ("strports description of the address on which to listen for ssh "
+ "connections")],
+ ["passwd", "p", "/etc/passwd",
+ "name of a passwd(5)-format username/password file"],
+ ["sshKeyDir", None, "<USER DATA DIR>",
+ "Directory where the autogenerated SSH key is kept."],
+ ["sshKeyName", None, "server.key",
+ "Filename of the autogenerated SSH key."],
+ ["sshKeySize", None, 4096,
+ "Size of the automatically generated SSH key."],
+ ]
+
+ def __init__(self):
+ usage.Options.__init__(self)
+ self['namespace'] = None
+
+ def postOptions(self):
+ if self['telnetPort'] is None and self['sshPort'] is None:
+ raise usage.UsageError(
+ "At least one of --telnetPort and --sshPort must be specified")
+
+
+
+def makeService(options):
+ """
+ Create a manhole server service.
+
+ @type options: L{dict}
+ @param options: A mapping describing the configuration of
+ the desired service. Recognized key/value pairs are::
+
+ "telnetPort": strports description of the address on which
+ to listen for telnet connections. If None,
+ no telnet service will be started.
+
+ "sshPort": strports description of the address on which to
+ listen for ssh connections. If None, no ssh
+ service will be started.
+
+ "namespace": dictionary containing desired initial locals
+ for manhole connections. If None, an empty
+ dictionary will be used.
+
+ "passwd": Name of a passwd(5)-format username/password file.
+
+ "sshKeyDir": The folder that the SSH server key will be kept in.
+
+ "sshKeyName": The filename of the key.
+
+ "sshKeySize": The size of the key, in bits. Default is 4096.
+
+ @rtype: L{twisted.application.service.IService}
+ @return: A manhole service.
+ """
+ svc = service.MultiService()
+
+ namespace = options['namespace']
+ if namespace is None:
+ namespace = {}
+
+ checker = checkers.FilePasswordDB(options['passwd'])
+
+ if options['telnetPort']:
+ telnetRealm = _StupidRealm(telnet.TelnetBootstrapProtocol,
+ insults.ServerProtocol,
+ manhole.ColoredManhole,
+ namespace)
+
+ telnetPortal = portal.Portal(telnetRealm, [checker])
+
+ telnetFactory = protocol.ServerFactory()
+ telnetFactory.protocol = makeTelnetProtocol(telnetPortal)
+ telnetService = strports.service(options['telnetPort'],
+ telnetFactory)
+ telnetService.setServiceParent(svc)
+
+ if options['sshPort']:
+ sshRealm = manhole_ssh.TerminalRealm()
+ sshRealm.chainedProtocolFactory = chainedProtocolFactory(namespace)
+
+ sshPortal = portal.Portal(sshRealm, [checker])
+ sshFactory = manhole_ssh.ConchFactory(sshPortal)
+
+ if options['sshKeyDir'] != "<USER DATA DIR>":
+ keyDir = options['sshKeyDir']
+ else:
+ from twisted.python._appdirs import getDataDirectory
+ keyDir = getDataDirectory()
+
+ keyLocation = filepath.FilePath(keyDir).child(options['sshKeyName'])
+
+ sshKey = keys._getPersistentRSAKey(keyLocation,
+ int(options['sshKeySize']))
+ sshFactory.publicKeys[b"ssh-rsa"] = sshKey
+ sshFactory.privateKeys[b"ssh-rsa"] = sshKey
+
+ sshService = strports.service(options['sshPort'], sshFactory)
+ sshService.setServiceParent(svc)
+
+ return svc
diff --git a/contrib/python/Twisted/py2/twisted/conch/mixin.py b/contrib/python/Twisted/py2/twisted/conch/mixin.py
new file mode 100644
index 0000000000..976e9ad18f
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/mixin.py
@@ -0,0 +1,55 @@
+# -*- test-case-name: twisted.conch.test.test_mixin -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Experimental optimization
+
+This module provides a single mixin class which allows protocols to
+collapse numerous small writes into a single larger one.
+
+@author: Jp Calderone
+"""
+
+from twisted.internet import reactor
+
+class BufferingMixin:
+ """
+ Mixin which adds write buffering.
+ """
+ _delayedWriteCall = None
+ data = None
+
+ DELAY = 0.0
+
+ def schedule(self):
+ return reactor.callLater(self.DELAY, self.flush)
+
+
+ def reschedule(self, token):
+ token.reset(self.DELAY)
+
+
+ def write(self, data):
+ """
+ Buffer some bytes to be written soon.
+
+ Every call to this function delays the real write by C{self.DELAY}
+ seconds. When the delay expires, all collected bytes are written
+ to the underlying transport using L{ITransport.writeSequence}.
+ """
+ if self._delayedWriteCall is None:
+ self.data = []
+ self._delayedWriteCall = self.schedule()
+ else:
+ self.reschedule(self._delayedWriteCall)
+ self.data.append(data)
+
+
+ def flush(self):
+ """
+ Flush the buffer immediately.
+ """
+ self._delayedWriteCall = None
+ self.transport.writeSequence(self.data)
+ self.data = None
diff --git a/contrib/python/Twisted/py2/twisted/conch/openssh_compat/__init__.py b/contrib/python/Twisted/py2/twisted/conch/openssh_compat/__init__.py
new file mode 100644
index 0000000000..69d5927d1f
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/openssh_compat/__init__.py
@@ -0,0 +1,11 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+"""
+Support for OpenSSH configuration files.
+
+Maintainer: Paul Swartz
+"""
+
diff --git a/contrib/python/Twisted/py2/twisted/conch/openssh_compat/factory.py b/contrib/python/Twisted/py2/twisted/conch/openssh_compat/factory.py
new file mode 100644
index 0000000000..eeea823226
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/openssh_compat/factory.py
@@ -0,0 +1,72 @@
+# -*- test-case-name: twisted.conch.test.test_openssh_compat -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Factory for reading openssh configuration files: public keys, private keys, and
+moduli file.
+"""
+
+import os, errno
+
+from twisted.python import log
+from twisted.python.util import runAsEffectiveUser
+
+from twisted.conch.ssh import keys, factory, common
+from twisted.conch.openssh_compat import primes
+
+
+
+class OpenSSHFactory(factory.SSHFactory):
+ dataRoot = '/usr/local/etc'
+ # For openbsd which puts moduli in a different directory from keys.
+ moduliRoot = '/usr/local/etc'
+
+
+ def getPublicKeys(self):
+ """
+ Return the server public keys.
+ """
+ ks = {}
+ for filename in os.listdir(self.dataRoot):
+ if filename[:9] == 'ssh_host_' and filename[-8:]=='_key.pub':
+ try:
+ k = keys.Key.fromFile(
+ os.path.join(self.dataRoot, filename))
+ t = common.getNS(k.blob())[0]
+ ks[t] = k
+ except Exception as e:
+ log.msg('bad public key file %s: %s' % (filename, e))
+ return ks
+
+
+ def getPrivateKeys(self):
+ """
+ Return the server private keys.
+ """
+ privateKeys = {}
+ for filename in os.listdir(self.dataRoot):
+ if filename[:9] == 'ssh_host_' and filename[-4:]=='_key':
+ fullPath = os.path.join(self.dataRoot, filename)
+ try:
+ key = keys.Key.fromFile(fullPath)
+ except IOError as e:
+ if e.errno == errno.EACCES:
+ # Not allowed, let's switch to root
+ key = runAsEffectiveUser(
+ 0, 0, keys.Key.fromFile, fullPath)
+ privateKeys[key.sshType()] = key
+ else:
+ raise
+ except Exception as e:
+ log.msg('bad private key file %s: %s' % (filename, e))
+ else:
+ privateKeys[key.sshType()] = key
+ return privateKeys
+
+
+ def getPrimes(self):
+ try:
+ return primes.parseModuliFile(self.moduliRoot+'/moduli')
+ except IOError:
+ return None
diff --git a/contrib/python/Twisted/py2/twisted/conch/openssh_compat/primes.py b/contrib/python/Twisted/py2/twisted/conch/openssh_compat/primes.py
new file mode 100644
index 0000000000..79cc7ff125
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/openssh_compat/primes.py
@@ -0,0 +1,30 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+"""
+Parsing for the moduli file, which contains Diffie-Hellman prime groups.
+
+Maintainer: Paul Swartz
+"""
+
+from twisted.python.compat import long
+
+
+def parseModuliFile(filename):
+ with open(filename) as f:
+ lines = f.readlines()
+ primes = {}
+ for l in lines:
+ l = l.strip()
+ if not l or l[0]=='#':
+ continue
+ tim, typ, tst, tri, size, gen, mod = l.split()
+ size = int(size) + 1
+ gen = long(gen)
+ mod = long(mod, 16)
+ if size not in primes:
+ primes[size] = []
+ primes[size].append((gen, mod))
+ return primes
diff --git a/contrib/python/Twisted/py2/twisted/conch/recvline.py b/contrib/python/Twisted/py2/twisted/conch/recvline.py
new file mode 100644
index 0000000000..f7801b720e
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/recvline.py
@@ -0,0 +1,374 @@
+# -*- test-case-name: twisted.conch.test.test_recvline -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Basic line editing support.
+
+@author: Jp Calderone
+"""
+
+import string
+
+from zope.interface import implementer
+
+from twisted.conch.insults import insults, helper
+
+from twisted.python import log, reflect
+from twisted.python.compat import iterbytes
+
+_counters = {}
+class Logging(object):
+ """
+ Wrapper which logs attribute lookups.
+
+ This was useful in debugging something, I guess. I forget what.
+ It can probably be deleted or moved somewhere more appropriate.
+ Nothing special going on here, really.
+ """
+ def __init__(self, original):
+ self.original = original
+ key = reflect.qual(original.__class__)
+ count = _counters.get(key, 0)
+ _counters[key] = count + 1
+ self._logFile = open(key + '-' + str(count), 'w')
+
+
+ def __str__(self):
+ return str(super(Logging, self).__getattribute__('original'))
+
+
+ def __repr__(self):
+ return repr(super(Logging, self).__getattribute__('original'))
+
+
+ def __getattribute__(self, name):
+ original = super(Logging, self).__getattribute__('original')
+ logFile = super(Logging, self).__getattribute__('_logFile')
+ logFile.write(name + '\n')
+ return getattr(original, name)
+
+
+
+@implementer(insults.ITerminalTransport)
+class TransportSequence(object):
+ """
+ An L{ITerminalTransport} implementation which forwards calls to
+ one or more other L{ITerminalTransport}s.
+
+ This is a cheap way for servers to keep track of the state they
+ expect the client to see, since all terminal manipulations can be
+ send to the real client and to a terminal emulator that lives in
+ the server process.
+ """
+
+ for keyID in (b'UP_ARROW', b'DOWN_ARROW', b'RIGHT_ARROW', b'LEFT_ARROW',
+ b'HOME', b'INSERT', b'DELETE', b'END', b'PGUP', b'PGDN',
+ b'F1', b'F2', b'F3', b'F4', b'F5', b'F6', b'F7', b'F8',
+ b'F9', b'F10', b'F11', b'F12'):
+ execBytes = keyID + b" = object()"
+ execStr = execBytes.decode("ascii")
+ exec(execStr)
+
+ TAB = b'\t'
+ BACKSPACE = b'\x7f'
+
+ def __init__(self, *transports):
+ assert transports, (
+ "Cannot construct a TransportSequence with no transports")
+ self.transports = transports
+
+ for method in insults.ITerminalTransport:
+ exec("""\
+def %s(self, *a, **kw):
+ for tpt in self.transports:
+ result = tpt.%s(*a, **kw)
+ return result
+""" % (method, method))
+
+
+
+class LocalTerminalBufferMixin(object):
+ """
+ A mixin for RecvLine subclasses which records the state of the terminal.
+
+ This is accomplished by performing all L{ITerminalTransport} operations on both
+ the transport passed to makeConnection and an instance of helper.TerminalBuffer.
+
+ @ivar terminalCopy: A L{helper.TerminalBuffer} instance which efforts
+ will be made to keep up to date with the actual terminal
+ associated with this protocol instance.
+ """
+
+ def makeConnection(self, transport):
+ self.terminalCopy = helper.TerminalBuffer()
+ self.terminalCopy.connectionMade()
+ return super(LocalTerminalBufferMixin, self).makeConnection(
+ TransportSequence(transport, self.terminalCopy))
+
+
+ def __str__(self):
+ return str(self.terminalCopy)
+
+
+
+class RecvLine(insults.TerminalProtocol):
+ """
+ L{TerminalProtocol} which adds line editing features.
+
+ Clients will be prompted for lines of input with all the usual
+ features: character echoing, left and right arrow support for
+ moving the cursor to different areas of the line buffer, backspace
+ and delete for removing characters, and insert for toggling
+ between typeover and insert mode. Tabs will be expanded to enough
+ spaces to move the cursor to the next tabstop (every four
+ characters by default). Enter causes the line buffer to be
+ cleared and the line to be passed to the lineReceived() method
+ which, by default, does nothing. Subclasses are responsible for
+ redrawing the input prompt (this will probably change).
+ """
+ width = 80
+ height = 24
+
+ TABSTOP = 4
+
+ ps = (b'>>> ', b'... ')
+ pn = 0
+ _printableChars = string.printable.encode("ascii")
+
+ def connectionMade(self):
+ # A list containing the characters making up the current line
+ self.lineBuffer = []
+
+ # A zero-based (wtf else?) index into self.lineBuffer.
+ # Indicates the current cursor position.
+ self.lineBufferIndex = 0
+
+ t = self.terminal
+ # A map of keyIDs to bound instance methods.
+ self.keyHandlers = {
+ t.LEFT_ARROW: self.handle_LEFT,
+ t.RIGHT_ARROW: self.handle_RIGHT,
+ t.TAB: self.handle_TAB,
+
+ # Both of these should not be necessary, but figuring out
+ # which is necessary is a huge hassle.
+ b'\r': self.handle_RETURN,
+ b'\n': self.handle_RETURN,
+
+ t.BACKSPACE: self.handle_BACKSPACE,
+ t.DELETE: self.handle_DELETE,
+ t.INSERT: self.handle_INSERT,
+ t.HOME: self.handle_HOME,
+ t.END: self.handle_END}
+
+ self.initializeScreen()
+
+
+ def initializeScreen(self):
+ # Hmm, state sucks. Oh well.
+ # For now we will just take over the whole terminal.
+ self.terminal.reset()
+ self.terminal.write(self.ps[self.pn])
+ # XXX Note: I would prefer to default to starting in insert
+ # mode, however this does not seem to actually work! I do not
+ # know why. This is probably of interest to implementors
+ # subclassing RecvLine.
+
+ # XXX XXX Note: But the unit tests all expect the initial mode
+ # to be insert right now. Fuck, there needs to be a way to
+ # query the current mode or something.
+ # self.setTypeoverMode()
+ self.setInsertMode()
+
+
+ def currentLineBuffer(self):
+ s = b''.join(self.lineBuffer)
+ return s[:self.lineBufferIndex], s[self.lineBufferIndex:]
+
+
+ def setInsertMode(self):
+ self.mode = 'insert'
+ self.terminal.setModes([insults.modes.IRM])
+
+
+ def setTypeoverMode(self):
+ self.mode = 'typeover'
+ self.terminal.resetModes([insults.modes.IRM])
+
+
+ def drawInputLine(self):
+ """
+ Write a line containing the current input prompt and the current line
+ buffer at the current cursor position.
+ """
+ self.terminal.write(self.ps[self.pn] + b''.join(self.lineBuffer))
+
+
+ def terminalSize(self, width, height):
+ # XXX - Clear the previous input line, redraw it at the new
+ # cursor position
+ self.terminal.eraseDisplay()
+ self.terminal.cursorHome()
+ self.width = width
+ self.height = height
+ self.drawInputLine()
+
+
+ def unhandledControlSequence(self, seq):
+ pass
+
+
+ def keystrokeReceived(self, keyID, modifier):
+ m = self.keyHandlers.get(keyID)
+ if m is not None:
+ m()
+ elif keyID in self._printableChars:
+ self.characterReceived(keyID, False)
+ else:
+ log.msg("Received unhandled keyID: %r" % (keyID,))
+
+
+ def characterReceived(self, ch, moreCharactersComing):
+ if self.mode == 'insert':
+ self.lineBuffer.insert(self.lineBufferIndex, ch)
+ else:
+ self.lineBuffer[self.lineBufferIndex:self.lineBufferIndex+1] = [ch]
+ self.lineBufferIndex += 1
+ self.terminal.write(ch)
+
+
+ def handle_TAB(self):
+ n = self.TABSTOP - (len(self.lineBuffer) % self.TABSTOP)
+ self.terminal.cursorForward(n)
+ self.lineBufferIndex += n
+ self.lineBuffer.extend(iterbytes(b' ' * n))
+
+
+ def handle_LEFT(self):
+ if self.lineBufferIndex > 0:
+ self.lineBufferIndex -= 1
+ self.terminal.cursorBackward()
+
+
+ def handle_RIGHT(self):
+ if self.lineBufferIndex < len(self.lineBuffer):
+ self.lineBufferIndex += 1
+ self.terminal.cursorForward()
+
+
+ def handle_HOME(self):
+ if self.lineBufferIndex:
+ self.terminal.cursorBackward(self.lineBufferIndex)
+ self.lineBufferIndex = 0
+
+
+ def handle_END(self):
+ offset = len(self.lineBuffer) - self.lineBufferIndex
+ if offset:
+ self.terminal.cursorForward(offset)
+ self.lineBufferIndex = len(self.lineBuffer)
+
+
+ def handle_BACKSPACE(self):
+ if self.lineBufferIndex > 0:
+ self.lineBufferIndex -= 1
+ del self.lineBuffer[self.lineBufferIndex]
+ self.terminal.cursorBackward()
+ self.terminal.deleteCharacter()
+
+
+ def handle_DELETE(self):
+ if self.lineBufferIndex < len(self.lineBuffer):
+ del self.lineBuffer[self.lineBufferIndex]
+ self.terminal.deleteCharacter()
+
+
+ def handle_RETURN(self):
+ line = b''.join(self.lineBuffer)
+ self.lineBuffer = []
+ self.lineBufferIndex = 0
+ self.terminal.nextLine()
+ self.lineReceived(line)
+
+
+ def handle_INSERT(self):
+ assert self.mode in ('typeover', 'insert')
+ if self.mode == 'typeover':
+ self.setInsertMode()
+ else:
+ self.setTypeoverMode()
+
+
+ def lineReceived(self, line):
+ pass
+
+
+
+class HistoricRecvLine(RecvLine):
+ """
+ L{TerminalProtocol} which adds both basic line-editing features and input history.
+
+ Everything supported by L{RecvLine} is also supported by this class. In addition, the
+ up and down arrows traverse the input history. Each received line is automatically
+ added to the end of the input history.
+ """
+ def connectionMade(self):
+ RecvLine.connectionMade(self)
+
+ self.historyLines = []
+ self.historyPosition = 0
+
+ t = self.terminal
+ self.keyHandlers.update({t.UP_ARROW: self.handle_UP,
+ t.DOWN_ARROW: self.handle_DOWN})
+
+
+ def currentHistoryBuffer(self):
+ b = tuple(self.historyLines)
+ return b[:self.historyPosition], b[self.historyPosition:]
+
+
+ def _deliverBuffer(self, buf):
+ if buf:
+ for ch in iterbytes(buf[:-1]):
+ self.characterReceived(ch, True)
+ self.characterReceived(buf[-1:], False)
+
+
+ def handle_UP(self):
+ if self.lineBuffer and self.historyPosition == len(self.historyLines):
+ self.historyLines.append(b''.join(self.lineBuffer))
+ if self.historyPosition > 0:
+ self.handle_HOME()
+ self.terminal.eraseToLineEnd()
+
+ self.historyPosition -= 1
+ self.lineBuffer = []
+
+ self._deliverBuffer(self.historyLines[self.historyPosition])
+
+
+ def handle_DOWN(self):
+ if self.historyPosition < len(self.historyLines) - 1:
+ self.handle_HOME()
+ self.terminal.eraseToLineEnd()
+
+ self.historyPosition += 1
+ self.lineBuffer = []
+
+ self._deliverBuffer(self.historyLines[self.historyPosition])
+ else:
+ self.handle_HOME()
+ self.terminal.eraseToLineEnd()
+
+ self.historyPosition = len(self.historyLines)
+ self.lineBuffer = []
+ self.lineBufferIndex = 0
+
+
+ def handle_RETURN(self):
+ if self.lineBuffer:
+ self.historyLines.append(b''.join(self.lineBuffer))
+ self.historyPosition = len(self.historyLines)
+ return RecvLine.handle_RETURN(self)
diff --git a/contrib/python/Twisted/py2/twisted/conch/ssh/__init__.py b/contrib/python/Twisted/py2/twisted/conch/ssh/__init__.py
new file mode 100644
index 0000000000..4b7f024b99
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/ssh/__init__.py
@@ -0,0 +1,10 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+"""
+An SSHv2 implementation for Twisted. Part of the Twisted.Conch package.
+
+Maintainer: Paul Swartz
+"""
diff --git a/contrib/python/Twisted/py2/twisted/conch/ssh/_kex.py b/contrib/python/Twisted/py2/twisted/conch/ssh/_kex.py
new file mode 100644
index 0000000000..922cf8b6a1
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/ssh/_kex.py
@@ -0,0 +1,294 @@
+# -*- test-case-name: twisted.conch.test.test_transport -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+SSH key exchange handling.
+"""
+
+from __future__ import absolute_import, division
+
+from hashlib import sha1, sha256, sha384, sha512
+
+from zope.interface import Attribute, implementer, Interface
+
+from twisted.conch import error
+from twisted.python.compat import long
+
+
+class _IKexAlgorithm(Interface):
+ """
+ An L{_IKexAlgorithm} describes a key exchange algorithm.
+ """
+
+ preference = Attribute(
+ "An L{int} giving the preference of the algorithm when negotiating "
+ "key exchange. Algorithms with lower precedence values are more "
+ "preferred.")
+
+ hashProcessor = Attribute(
+ "A callable hash algorithm constructor (e.g. C{hashlib.sha256}) "
+ "suitable for use with this key exchange algorithm.")
+
+
+
+class _IFixedGroupKexAlgorithm(_IKexAlgorithm):
+ """
+ An L{_IFixedGroupKexAlgorithm} describes a key exchange algorithm with a
+ fixed prime / generator group.
+ """
+
+ prime = Attribute(
+ "A L{long} giving the prime number used in Diffie-Hellman key "
+ "exchange, or L{None} if not applicable.")
+
+ generator = Attribute(
+ "A L{long} giving the generator number used in Diffie-Hellman key "
+ "exchange, or L{None} if not applicable. (This is not related to "
+ "Python generator functions.)")
+
+
+
+class _IEllipticCurveExchangeKexAlgorithm(_IKexAlgorithm):
+ """
+ An L{_IEllipticCurveExchangeKexAlgorithm} describes a key exchange algorithm
+ that uses an elliptic curve exchange between the client and server.
+ """
+
+
+
+class _IGroupExchangeKexAlgorithm(_IKexAlgorithm):
+ """
+ An L{_IGroupExchangeKexAlgorithm} describes a key exchange algorithm
+ that uses group exchange between the client and server.
+
+ A prime / generator group should be chosen at run time based on the
+ requested size. See RFC 4419.
+ """
+
+
+
+@implementer(_IEllipticCurveExchangeKexAlgorithm)
+class _Curve25519SHA256(object):
+ """
+ Elliptic Curve Key Exchange using Curve25519 and SHA256. Defined in
+ U{https://datatracker.ietf.org/doc/draft-ietf-curdle-ssh-curves/}.
+ """
+ preference = 1
+ hashProcessor = sha256
+
+
+
+@implementer(_IEllipticCurveExchangeKexAlgorithm)
+class _Curve25519SHA256LibSSH(object):
+ """
+ As L{_Curve25519SHA256}, but with a pre-standardized algorithm name.
+ """
+ preference = 2
+ hashProcessor = sha256
+
+
+
+@implementer(_IEllipticCurveExchangeKexAlgorithm)
+class _ECDH256(object):
+ """
+ Elliptic Curve Key Exchange with SHA-256 as HASH. Defined in
+ RFC 5656.
+ """
+ preference = 3
+ hashProcessor = sha256
+
+
+
+@implementer(_IEllipticCurveExchangeKexAlgorithm)
+class _ECDH384(object):
+ """
+ Elliptic Curve Key Exchange with SHA-384 as HASH. Defined in
+ RFC 5656.
+ """
+ preference = 4
+ hashProcessor = sha384
+
+
+
+@implementer(_IEllipticCurveExchangeKexAlgorithm)
+class _ECDH512(object):
+ """
+ Elliptic Curve Key Exchange with SHA-512 as HASH. Defined in
+ RFC 5656.
+ """
+ preference = 5
+ hashProcessor = sha512
+
+
+
+@implementer(_IGroupExchangeKexAlgorithm)
+class _DHGroupExchangeSHA256(object):
+ """
+ Diffie-Hellman Group and Key Exchange with SHA-256 as HASH. Defined in
+ RFC 4419, 4.2.
+ """
+
+ preference = 6
+ hashProcessor = sha256
+
+
+
+@implementer(_IGroupExchangeKexAlgorithm)
+class _DHGroupExchangeSHA1(object):
+ """
+ Diffie-Hellman Group and Key Exchange with SHA-1 as HASH. Defined in
+ RFC 4419, 4.1.
+ """
+
+ preference = 7
+ hashProcessor = sha1
+
+
+
+@implementer(_IFixedGroupKexAlgorithm)
+class _DHGroup14SHA1(object):
+ """
+ Diffie-Hellman key exchange with SHA-1 as HASH and Oakley Group 14
+ (2048-bit MODP Group). Defined in RFC 4253, 8.2.
+ """
+
+ preference = 8
+ hashProcessor = sha1
+ # Diffie-Hellman primes from Oakley Group 14 (RFC 3526, 3).
+ prime = long('32317006071311007300338913926423828248817941241140239112842'
+ '00975140074170663435422261968941736356934711790173790970419175460587'
+ '32091950288537589861856221532121754125149017745202702357960782362488'
+ '84246189477587641105928646099411723245426622522193230540919037680524'
+ '23551912567971587011700105805587765103886184728025797605490356973256'
+ '15261670813393617995413364765591603683178967290731783845896806396719'
+ '00977202194168647225871031411336429319536193471636533209717077448227'
+ '98858856536920864529663607725026895550592836275112117409697299806841'
+ '05543595848665832916421362182310789909994486524682624169720359118525'
+ '07045361090559')
+ generator = 2
+
+
+
+# Which ECDH hash function to use is dependent on the size.
+_kexAlgorithms = {
+ b"curve25519-sha256": _Curve25519SHA256(),
+ b"curve25519-sha256@libssh.org": _Curve25519SHA256LibSSH(),
+ b"diffie-hellman-group-exchange-sha256": _DHGroupExchangeSHA256(),
+ b"diffie-hellman-group-exchange-sha1": _DHGroupExchangeSHA1(),
+ b"diffie-hellman-group14-sha1": _DHGroup14SHA1(),
+ b"ecdh-sha2-nistp256": _ECDH256(),
+ b"ecdh-sha2-nistp384": _ECDH384(),
+ b"ecdh-sha2-nistp521": _ECDH512(),
+ }
+
+
+
+def getKex(kexAlgorithm):
+ """
+ Get a description of a named key exchange algorithm.
+
+ @param kexAlgorithm: The key exchange algorithm name.
+ @type kexAlgorithm: L{bytes}
+
+ @return: A description of the key exchange algorithm named by
+ C{kexAlgorithm}.
+ @rtype: L{_IKexAlgorithm}
+
+ @raises ConchError: if the key exchange algorithm is not found.
+ """
+ if kexAlgorithm not in _kexAlgorithms:
+ raise error.ConchError(
+ "Unsupported key exchange algorithm: %s" % (kexAlgorithm,))
+ return _kexAlgorithms[kexAlgorithm]
+
+
+
+def isEllipticCurve(kexAlgorithm):
+ """
+ Returns C{True} if C{kexAlgorithm} is an elliptic curve.
+
+ @param kexAlgorithm: The key exchange algorithm name.
+ @type kexAlgorithm: C{str}
+
+ @return: C{True} if C{kexAlgorithm} is an elliptic curve,
+ otherwise C{False}.
+ @rtype: C{bool}
+ """
+ return _IEllipticCurveExchangeKexAlgorithm.providedBy(getKex(kexAlgorithm))
+
+
+
+def isFixedGroup(kexAlgorithm):
+ """
+ Returns C{True} if C{kexAlgorithm} has a fixed prime / generator group.
+
+ @param kexAlgorithm: The key exchange algorithm name.
+ @type kexAlgorithm: L{bytes}
+
+ @return: C{True} if C{kexAlgorithm} has a fixed prime / generator group,
+ otherwise C{False}.
+ @rtype: L{bool}
+ """
+ return _IFixedGroupKexAlgorithm.providedBy(getKex(kexAlgorithm))
+
+
+
+def getHashProcessor(kexAlgorithm):
+ """
+ Get the hash algorithm callable to use in key exchange.
+
+ @param kexAlgorithm: The key exchange algorithm name.
+ @type kexAlgorithm: L{bytes}
+
+ @return: A callable hash algorithm constructor (e.g. C{hashlib.sha256}).
+ @rtype: C{callable}
+ """
+ kex = getKex(kexAlgorithm)
+ return kex.hashProcessor
+
+
+
+def getDHGeneratorAndPrime(kexAlgorithm):
+ """
+ Get the generator and the prime to use in key exchange.
+
+ @param kexAlgorithm: The key exchange algorithm name.
+ @type kexAlgorithm: L{bytes}
+
+ @return: A L{tuple} containing L{long} generator and L{long} prime.
+ @rtype: L{tuple}
+ """
+ kex = getKex(kexAlgorithm)
+ return kex.generator, kex.prime
+
+
+
+def getSupportedKeyExchanges():
+ """
+ Get a list of supported key exchange algorithm names in order of
+ preference.
+
+ @return: A C{list} of supported key exchange algorithm names.
+ @rtype: C{list} of L{bytes}
+ """
+ from cryptography.hazmat.backends import default_backend
+ from cryptography.hazmat.primitives.asymmetric import ec
+ from twisted.conch.ssh.keys import _curveTable
+
+ backend = default_backend()
+ kexAlgorithms = _kexAlgorithms.copy()
+ for keyAlgorithm in list(kexAlgorithms):
+ if keyAlgorithm.startswith(b"ecdh"):
+ keyAlgorithmDsa = keyAlgorithm.replace(b"ecdh", b"ecdsa")
+ supported = backend.elliptic_curve_exchange_algorithm_supported(
+ ec.ECDH(), _curveTable[keyAlgorithmDsa])
+ elif keyAlgorithm.startswith(b"curve25519-sha256"):
+ supported = backend.x25519_supported()
+ else:
+ supported = True
+ if not supported:
+ kexAlgorithms.pop(keyAlgorithm)
+ return sorted(
+ kexAlgorithms,
+ key=lambda kexAlgorithm: kexAlgorithms[kexAlgorithm].preference)
diff --git a/contrib/python/Twisted/py2/twisted/conch/ssh/address.py b/contrib/python/Twisted/py2/twisted/conch/ssh/address.py
new file mode 100644
index 0000000000..969740268c
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/ssh/address.py
@@ -0,0 +1,47 @@
+# -*- test-case-name: twisted.conch.test.test_address -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Address object for SSH network connections.
+
+Maintainer: Paul Swartz
+
+@since: 12.1
+"""
+
+from __future__ import division, absolute_import
+
+from zope.interface import implementer
+
+from twisted.internet.interfaces import IAddress
+from twisted.python import util
+
+
+
+@implementer(IAddress)
+class SSHTransportAddress(util.FancyEqMixin, object):
+ """
+ Object representing an SSH Transport endpoint.
+
+ This is used to ensure that any code inspecting this address and
+ attempting to construct a similar connection based upon it is not
+ mislead into creating a transport which is not similar to the one it is
+ indicating.
+
+ @ivar address: An instance of an object which implements I{IAddress} to
+ which this transport address is connected.
+ """
+
+ compareAttributes = ('address',)
+
+ def __init__(self, address):
+ self.address = address
+
+
+ def __repr__(self):
+ return 'SSHTransportAddress(%r)' % (self.address,)
+
+
+ def __hash__(self):
+ return hash(('SSH', self.address))
diff --git a/contrib/python/Twisted/py2/twisted/conch/ssh/agent.py b/contrib/python/Twisted/py2/twisted/conch/ssh/agent.py
new file mode 100644
index 0000000000..03c0de8068
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/ssh/agent.py
@@ -0,0 +1,296 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Implements the SSH v2 key agent protocol. This protocol is documented in the
+SSH source code, in the file
+U{PROTOCOL.agent<http://www.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.agent>}.
+
+Maintainer: Paul Swartz
+"""
+
+from __future__ import absolute_import, division
+
+import struct
+
+from twisted.conch.ssh.common import NS, getNS, getMP
+from twisted.conch.error import ConchError, MissingKeyStoreError
+from twisted.conch.ssh import keys
+from twisted.internet import defer, protocol
+from twisted.python.compat import itervalues
+
+
+
+class SSHAgentClient(protocol.Protocol):
+ """
+ The client side of the SSH agent protocol. This is equivalent to
+ ssh-add(1) and can be used with either ssh-agent(1) or the SSHAgentServer
+ protocol, also in this package.
+ """
+
+ def __init__(self):
+ self.buf = b''
+ self.deferreds = []
+
+
+ def dataReceived(self, data):
+ self.buf += data
+ while 1:
+ if len(self.buf) <= 4:
+ return
+ packLen = struct.unpack('!L', self.buf[:4])[0]
+ if len(self.buf) < 4 + packLen:
+ return
+ packet, self.buf = self.buf[4:4 + packLen], self.buf[4 + packLen:]
+ reqType = ord(packet[0:1])
+ d = self.deferreds.pop(0)
+ if reqType == AGENT_FAILURE:
+ d.errback(ConchError('agent failure'))
+ elif reqType == AGENT_SUCCESS:
+ d.callback(b'')
+ else:
+ d.callback(packet)
+
+
+ def sendRequest(self, reqType, data):
+ pack = struct.pack('!LB',len(data) + 1, reqType) + data
+ self.transport.write(pack)
+ d = defer.Deferred()
+ self.deferreds.append(d)
+ return d
+
+
+ def requestIdentities(self):
+ """
+ @return: A L{Deferred} which will fire with a list of all keys found in
+ the SSH agent. The list of keys is comprised of (public key blob,
+ comment) tuples.
+ """
+ d = self.sendRequest(AGENTC_REQUEST_IDENTITIES, b'')
+ d.addCallback(self._cbRequestIdentities)
+ return d
+
+
+ def _cbRequestIdentities(self, data):
+ """
+ Unpack a collection of identities into a list of tuples comprised of
+ public key blobs and comments.
+ """
+ if ord(data[0:1]) != AGENT_IDENTITIES_ANSWER:
+ raise ConchError('unexpected response: %i' % ord(data[0:1]))
+ numKeys = struct.unpack('!L', data[1:5])[0]
+ result = []
+ data = data[5:]
+ for i in range(numKeys):
+ blob, data = getNS(data)
+ comment, data = getNS(data)
+ result.append((blob, comment))
+ return result
+
+
+ def addIdentity(self, blob, comment = b''):
+ """
+ Add a private key blob to the agent's collection of keys.
+ """
+ req = blob
+ req += NS(comment)
+ return self.sendRequest(AGENTC_ADD_IDENTITY, req)
+
+
+ def signData(self, blob, data):
+ """
+ Request that the agent sign the given C{data} with the private key
+ which corresponds to the public key given by C{blob}. The private
+ key should have been added to the agent already.
+
+ @type blob: L{bytes}
+ @type data: L{bytes}
+ @return: A L{Deferred} which fires with a signature for given data
+ created with the given key.
+ """
+ req = NS(blob)
+ req += NS(data)
+ req += b'\000\000\000\000' # flags
+ return self.sendRequest(AGENTC_SIGN_REQUEST, req).addCallback(self._cbSignData)
+
+
+ def _cbSignData(self, data):
+ if ord(data[0:1]) != AGENT_SIGN_RESPONSE:
+ raise ConchError('unexpected data: %i' % ord(data[0:1]))
+ signature = getNS(data[1:])[0]
+ return signature
+
+
+ def removeIdentity(self, blob):
+ """
+ Remove the private key corresponding to the public key in blob from the
+ running agent.
+ """
+ req = NS(blob)
+ return self.sendRequest(AGENTC_REMOVE_IDENTITY, req)
+
+
+ def removeAllIdentities(self):
+ """
+ Remove all keys from the running agent.
+ """
+ return self.sendRequest(AGENTC_REMOVE_ALL_IDENTITIES, b'')
+
+
+
+class SSHAgentServer(protocol.Protocol):
+ """
+ The server side of the SSH agent protocol. This is equivalent to
+ ssh-agent(1) and can be used with either ssh-add(1) or the SSHAgentClient
+ protocol, also in this package.
+ """
+
+ def __init__(self):
+ self.buf = b''
+
+
+ def dataReceived(self, data):
+ self.buf += data
+ while 1:
+ if len(self.buf) <= 4:
+ return
+ packLen = struct.unpack('!L', self.buf[:4])[0]
+ if len(self.buf) < 4 + packLen:
+ return
+ packet, self.buf = self.buf[4:4 + packLen], self.buf[4 + packLen:]
+ reqType = ord(packet[0:1])
+ reqName = messages.get(reqType, None)
+ if not reqName:
+ self.sendResponse(AGENT_FAILURE, b'')
+ else:
+ f = getattr(self, 'agentc_%s' % reqName)
+ if getattr(self.factory, 'keys', None) is None:
+ self.sendResponse(AGENT_FAILURE, b'')
+ raise MissingKeyStoreError()
+ f(packet[1:])
+
+
+ def sendResponse(self, reqType, data):
+ pack = struct.pack('!LB', len(data) + 1, reqType) + data
+ self.transport.write(pack)
+
+
+ def agentc_REQUEST_IDENTITIES(self, data):
+ """
+ Return all of the identities that have been added to the server
+ """
+ assert data == b''
+ numKeys = len(self.factory.keys)
+ resp = []
+
+ resp.append(struct.pack('!L', numKeys))
+ for key, comment in itervalues(self.factory.keys):
+ resp.append(NS(key.blob())) # yes, wrapped in an NS
+ resp.append(NS(comment))
+ self.sendResponse(AGENT_IDENTITIES_ANSWER, b''.join(resp))
+
+
+ def agentc_SIGN_REQUEST(self, data):
+ """
+ Data is a structure with a reference to an already added key object and
+ some data that the clients wants signed with that key. If the key
+ object wasn't loaded, return AGENT_FAILURE, else return the signature.
+ """
+ blob, data = getNS(data)
+ if blob not in self.factory.keys:
+ return self.sendResponse(AGENT_FAILURE, b'')
+ signData, data = getNS(data)
+ assert data == b'\000\000\000\000'
+ self.sendResponse(AGENT_SIGN_RESPONSE, NS(self.factory.keys[blob][0].sign(signData)))
+
+
+ def agentc_ADD_IDENTITY(self, data):
+ """
+ Adds a private key to the agent's collection of identities. On
+ subsequent interactions, the private key can be accessed using only the
+ corresponding public key.
+ """
+
+ # need to pre-read the key data so we can get past it to the comment string
+ keyType, rest = getNS(data)
+ if keyType == b'ssh-rsa':
+ nmp = 6
+ elif keyType == b'ssh-dss':
+ nmp = 5
+ else:
+ raise keys.BadKeyError('unknown blob type: %s' % keyType)
+
+ rest = getMP(rest, nmp)[-1] # ignore the key data for now, we just want the comment
+ comment, rest = getNS(rest) # the comment, tacked onto the end of the key blob
+
+ k = keys.Key.fromString(data, type='private_blob') # not wrapped in NS here
+ self.factory.keys[k.blob()] = (k, comment)
+ self.sendResponse(AGENT_SUCCESS, b'')
+
+
+ def agentc_REMOVE_IDENTITY(self, data):
+ """
+ Remove a specific key from the agent's collection of identities.
+ """
+ blob, _ = getNS(data)
+ k = keys.Key.fromString(blob, type='blob')
+ del self.factory.keys[k.blob()]
+ self.sendResponse(AGENT_SUCCESS, b'')
+
+
+ def agentc_REMOVE_ALL_IDENTITIES(self, data):
+ """
+ Remove all keys from the agent's collection of identities.
+ """
+ assert data == b''
+ self.factory.keys = {}
+ self.sendResponse(AGENT_SUCCESS, b'')
+
+ # v1 messages that we ignore because we don't keep v1 keys
+ # open-ssh sends both v1 and v2 commands, so we have to
+ # do no-ops for v1 commands or we'll get "bad request" errors
+
+ def agentc_REQUEST_RSA_IDENTITIES(self, data):
+ """
+ v1 message for listing RSA1 keys; superseded by
+ agentc_REQUEST_IDENTITIES, which handles different key types.
+ """
+ self.sendResponse(AGENT_RSA_IDENTITIES_ANSWER, struct.pack('!L', 0))
+
+
+ def agentc_REMOVE_RSA_IDENTITY(self, data):
+ """
+ v1 message for removing RSA1 keys; superseded by
+ agentc_REMOVE_IDENTITY, which handles different key types.
+ """
+ self.sendResponse(AGENT_SUCCESS, b'')
+
+
+ def agentc_REMOVE_ALL_RSA_IDENTITIES(self, data):
+ """
+ v1 message for removing all RSA1 keys; superseded by
+ agentc_REMOVE_ALL_IDENTITIES, which handles different key types.
+ """
+ self.sendResponse(AGENT_SUCCESS, b'')
+
+
+AGENTC_REQUEST_RSA_IDENTITIES = 1
+AGENT_RSA_IDENTITIES_ANSWER = 2
+AGENT_FAILURE = 5
+AGENT_SUCCESS = 6
+
+AGENTC_REMOVE_RSA_IDENTITY = 8
+AGENTC_REMOVE_ALL_RSA_IDENTITIES = 9
+
+AGENTC_REQUEST_IDENTITIES = 11
+AGENT_IDENTITIES_ANSWER = 12
+AGENTC_SIGN_REQUEST = 13
+AGENT_SIGN_RESPONSE = 14
+AGENTC_ADD_IDENTITY = 17
+AGENTC_REMOVE_IDENTITY = 18
+AGENTC_REMOVE_ALL_IDENTITIES = 19
+
+messages = {}
+for name, value in locals().copy().items():
+ if name[:7] == 'AGENTC_':
+ messages[value] = name[7:] # doesn't handle doubles
diff --git a/contrib/python/Twisted/py2/twisted/conch/ssh/channel.py b/contrib/python/Twisted/py2/twisted/conch/ssh/channel.py
new file mode 100644
index 0000000000..51e2a2f914
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/ssh/channel.py
@@ -0,0 +1,320 @@
+# -*- test-case-name: twisted.conch.test.test_channel -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+The parent class for all the SSH Channels. Currently implemented channels
+are session, direct-tcp, and forwarded-tcp.
+
+Maintainer: Paul Swartz
+"""
+
+from __future__ import division, absolute_import
+
+from zope.interface import implementer
+
+from twisted.python import log
+from twisted.python.compat import nativeString, intToBytes
+from twisted.internet import interfaces
+
+
+
+@implementer(interfaces.ITransport)
+class SSHChannel(log.Logger):
+ """
+ A class that represents a multiplexed channel over an SSH connection.
+ The channel has a local window which is the maximum amount of data it will
+ receive, and a remote which is the maximum amount of data the remote side
+ will accept. There is also a maximum packet size for any individual data
+ packet going each way.
+
+ @ivar name: the name of the channel.
+ @type name: L{bytes}
+ @ivar localWindowSize: the maximum size of the local window in bytes.
+ @type localWindowSize: L{int}
+ @ivar localWindowLeft: how many bytes are left in the local window.
+ @type localWindowLeft: L{int}
+ @ivar localMaxPacket: the maximum size of packet we will accept in bytes.
+ @type localMaxPacket: L{int}
+ @ivar remoteWindowLeft: how many bytes are left in the remote window.
+ @type remoteWindowLeft: L{int}
+ @ivar remoteMaxPacket: the maximum size of a packet the remote side will
+ accept in bytes.
+ @type remoteMaxPacket: L{int}
+ @ivar conn: the connection this channel is multiplexed through.
+ @type conn: L{SSHConnection}
+ @ivar data: any data to send to the other side when the channel is
+ requested.
+ @type data: L{bytes}
+ @ivar avatar: an avatar for the logged-in user (if a server channel)
+ @ivar localClosed: True if we aren't accepting more data.
+ @type localClosed: L{bool}
+ @ivar remoteClosed: True if the other side isn't accepting more data.
+ @type remoteClosed: L{bool}
+ """
+
+ name = None # only needed for client channels
+
+ def __init__(self, localWindow = 0, localMaxPacket = 0,
+ remoteWindow = 0, remoteMaxPacket = 0,
+ conn = None, data=None, avatar = None):
+ self.localWindowSize = localWindow or 131072
+ self.localWindowLeft = self.localWindowSize
+ self.localMaxPacket = localMaxPacket or 32768
+ self.remoteWindowLeft = remoteWindow
+ self.remoteMaxPacket = remoteMaxPacket
+ self.areWriting = 1
+ self.conn = conn
+ self.data = data
+ self.avatar = avatar
+ self.specificData = b''
+ self.buf = b''
+ self.extBuf = []
+ self.closing = 0
+ self.localClosed = 0
+ self.remoteClosed = 0
+ self.id = None # gets set later by SSHConnection
+
+
+ def __str__(self):
+ return nativeString(self.__bytes__())
+
+
+ def __bytes__(self):
+ """
+ Return a byte string representation of the channel
+ """
+ name = self.name
+ if not name:
+ name = b'None'
+
+ return (b'<SSHChannel ' + name +
+ b' (lw ' + intToBytes(self.localWindowLeft) +
+ b' rw ' + intToBytes(self.remoteWindowLeft) +
+ b')>')
+
+
+ def logPrefix(self):
+ id = (self.id is not None and str(self.id)) or "unknown"
+ name = self.name
+ if name:
+ name = nativeString(name)
+ return "SSHChannel %s (%s) on %s" % (name, id,
+ self.conn.logPrefix())
+
+
+ def channelOpen(self, specificData):
+ """
+ Called when the channel is opened. specificData is any data that the
+ other side sent us when opening the channel.
+
+ @type specificData: L{bytes}
+ """
+ log.msg('channel open')
+
+
+ def openFailed(self, reason):
+ """
+ Called when the open failed for some reason.
+ reason.desc is a string descrption, reason.code the SSH error code.
+
+ @type reason: L{error.ConchError}
+ """
+ log.msg('other side refused open\nreason: %s'% reason)
+
+
+ def addWindowBytes(self, data):
+ """
+ Called when bytes are added to the remote window. By default it clears
+ the data buffers.
+
+ @type data: L{bytes}
+ """
+ self.remoteWindowLeft = self.remoteWindowLeft+data
+ if not self.areWriting and not self.closing:
+ self.areWriting = True
+ self.startWriting()
+ if self.buf:
+ b = self.buf
+ self.buf = b''
+ self.write(b)
+ if self.extBuf:
+ b = self.extBuf
+ self.extBuf = []
+ for (type, data) in b:
+ self.writeExtended(type, data)
+
+
+ def requestReceived(self, requestType, data):
+ """
+ Called when a request is sent to this channel. By default it delegates
+ to self.request_<requestType>.
+ If this function returns true, the request succeeded, otherwise it
+ failed.
+
+ @type requestType: L{bytes}
+ @type data: L{bytes}
+ @rtype: L{bool}
+ """
+ foo = nativeString(requestType.replace(b'-', b'_'))
+ f = getattr(self, 'request_%s'%foo, None)
+ if f:
+ return f(data)
+ log.msg('unhandled request for %s'%requestType)
+ return 0
+
+
+ def dataReceived(self, data):
+ """
+ Called when we receive data.
+
+ @type data: L{bytes}
+ """
+ log.msg('got data %s'%repr(data))
+
+
+ def extReceived(self, dataType, data):
+ """
+ Called when we receive extended data (usually standard error).
+
+ @type dataType: L{int}
+ @type data: L{str}
+ """
+ log.msg('got extended data %s %s'%(dataType, repr(data)))
+
+
+ def eofReceived(self):
+ """
+ Called when the other side will send no more data.
+ """
+ log.msg('remote eof')
+
+
+ def closeReceived(self):
+ """
+ Called when the other side has closed the channel.
+ """
+ log.msg('remote close')
+ self.loseConnection()
+
+
+ def closed(self):
+ """
+ Called when the channel is closed. This means that both our side and
+ the remote side have closed the channel.
+ """
+ log.msg('closed')
+
+
+ def write(self, data):
+ """
+ Write some data to the channel. If there is not enough remote window
+ available, buffer until it is. Otherwise, split the data into
+ packets of length remoteMaxPacket and send them.
+
+ @type data: L{bytes}
+ """
+ if self.buf:
+ self.buf += data
+ return
+ top = len(data)
+ if top > self.remoteWindowLeft:
+ data, self.buf = (data[:self.remoteWindowLeft],
+ data[self.remoteWindowLeft:])
+ self.areWriting = 0
+ self.stopWriting()
+ top = self.remoteWindowLeft
+ rmp = self.remoteMaxPacket
+ write = self.conn.sendData
+ r = range(0, top, rmp)
+ for offset in r:
+ write(self, data[offset: offset+rmp])
+ self.remoteWindowLeft -= top
+ if self.closing and not self.buf:
+ self.loseConnection() # try again
+
+
+ def writeExtended(self, dataType, data):
+ """
+ Send extended data to this channel. If there is not enough remote
+ window available, buffer until there is. Otherwise, split the data
+ into packets of length remoteMaxPacket and send them.
+
+ @type dataType: L{int}
+ @type data: L{bytes}
+ """
+ if self.extBuf:
+ if self.extBuf[-1][0] == dataType:
+ self.extBuf[-1][1] += data
+ else:
+ self.extBuf.append([dataType, data])
+ return
+ if len(data) > self.remoteWindowLeft:
+ data, self.extBuf = (data[:self.remoteWindowLeft],
+ [[dataType, data[self.remoteWindowLeft:]]])
+ self.areWriting = 0
+ self.stopWriting()
+ while len(data) > self.remoteMaxPacket:
+ self.conn.sendExtendedData(self, dataType,
+ data[:self.remoteMaxPacket])
+ data = data[self.remoteMaxPacket:]
+ self.remoteWindowLeft -= self.remoteMaxPacket
+ if data:
+ self.conn.sendExtendedData(self, dataType, data)
+ self.remoteWindowLeft -= len(data)
+ if self.closing:
+ self.loseConnection() # try again
+
+
+ def writeSequence(self, data):
+ """
+ Part of the Transport interface. Write a list of strings to the
+ channel.
+
+ @type data: C{list} of L{str}
+ """
+ self.write(b''.join(data))
+
+
+ def loseConnection(self):
+ """
+ Close the channel if there is no buferred data. Otherwise, note the
+ request and return.
+ """
+ self.closing = 1
+ if not self.buf and not self.extBuf:
+ self.conn.sendClose(self)
+
+
+ def getPeer(self):
+ """
+ See: L{ITransport.getPeer}
+
+ @return: The remote address of this connection.
+ @rtype: L{SSHTransportAddress}.
+ """
+ return self.conn.transport.getPeer()
+
+
+ def getHost(self):
+ """
+ See: L{ITransport.getHost}
+
+ @return: An address describing this side of the connection.
+ @rtype: L{SSHTransportAddress}.
+ """
+ return self.conn.transport.getHost()
+
+
+ def stopWriting(self):
+ """
+ Called when the remote buffer is full, as a hint to stop writing.
+ This can be ignored, but it can be helpful.
+ """
+
+
+ def startWriting(self):
+ """
+ Called when the remote buffer has more room, as a hint to continue
+ writing.
+ """
diff --git a/contrib/python/Twisted/py2/twisted/conch/ssh/common.py b/contrib/python/Twisted/py2/twisted/conch/ssh/common.py
new file mode 100644
index 0000000000..8a0f136c36
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/ssh/common.py
@@ -0,0 +1,93 @@
+# -*- test-case-name: twisted.conch.test.test_ssh -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Common functions for the SSH classes.
+
+Maintainer: Paul Swartz
+"""
+
+from __future__ import absolute_import, division
+
+import struct
+
+from cryptography.utils import int_from_bytes, int_to_bytes
+
+from twisted.python.compat import unicode
+from twisted.python.deprecate import deprecated
+from twisted.python.versions import Version
+
+__all__ = ["NS", "getNS", "MP", "getMP", "ffs"]
+
+
+
+def NS(t):
+ """
+ net string
+ """
+ if isinstance(t, unicode):
+ t = t.encode("utf-8")
+ return struct.pack('!L', len(t)) + t
+
+
+
+def getNS(s, count=1):
+ """
+ get net string
+ """
+ ns = []
+ c = 0
+ for i in range(count):
+ l, = struct.unpack('!L', s[c:c + 4])
+ ns.append(s[c + 4:4 + l + c])
+ c += 4 + l
+ return tuple(ns) + (s[c:],)
+
+
+
+def MP(number):
+ if number == 0:
+ return b'\000' * 4
+ assert number > 0
+ bn = int_to_bytes(number)
+ if ord(bn[0:1]) & 128:
+ bn = b'\000' + bn
+ return struct.pack('>L', len(bn)) + bn
+
+
+
+def getMP(data, count=1):
+ """
+ Get multiple precision integer out of the string. A multiple precision
+ integer is stored as a 4-byte length followed by length bytes of the
+ integer. If count is specified, get count integers out of the string.
+ The return value is a tuple of count integers followed by the rest of
+ the data.
+ """
+ mp = []
+ c = 0
+ for i in range(count):
+ length, = struct.unpack('>L', data[c:c + 4])
+ mp.append(int_from_bytes(data[c + 4:c + 4 + length], 'big'))
+ c += 4 + length
+ return tuple(mp) + (data[c:],)
+
+
+
+def ffs(c, s):
+ """
+ first from second
+ goes through the first list, looking for items in the second, returns the first one
+ """
+ for i in c:
+ if i in s:
+ return i
+
+
+
+@deprecated(Version("Twisted", 16, 5, 0))
+def install():
+ # This used to install gmpy, but is technically public API, so just do
+ # nothing.
+ pass
diff --git a/contrib/python/Twisted/py2/twisted/conch/ssh/connection.py b/contrib/python/Twisted/py2/twisted/conch/ssh/connection.py
new file mode 100644
index 0000000000..16ef6444a0
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/ssh/connection.py
@@ -0,0 +1,653 @@
+# -*- test-case-name: twisted.conch.test.test_connection -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This module contains the implementation of the ssh-connection service, which
+allows access to the shell and port-forwarding.
+
+Maintainer: Paul Swartz
+"""
+from __future__ import division, absolute_import
+
+import string
+import struct
+
+import twisted.internet.error
+from twisted.conch.ssh import service, common
+from twisted.conch import error
+from twisted.internet import defer
+from twisted.python import log
+from twisted.python.compat import (
+ nativeString, networkString, long, _bytesChr as chr)
+
+
+
+class SSHConnection(service.SSHService):
+ """
+ An implementation of the 'ssh-connection' service. It is used to
+ multiplex multiple channels over the single SSH connection.
+
+ @ivar localChannelID: the next number to use as a local channel ID.
+ @type localChannelID: L{int}
+ @ivar channels: a L{dict} mapping a local channel ID to C{SSHChannel}
+ subclasses.
+ @type channels: L{dict}
+ @ivar localToRemoteChannel: a L{dict} mapping a local channel ID to a
+ remote channel ID.
+ @type localToRemoteChannel: L{dict}
+ @ivar channelsToRemoteChannel: a L{dict} mapping a C{SSHChannel} subclass
+ to remote channel ID.
+ @type channelsToRemoteChannel: L{dict}
+ @ivar deferreds: a L{dict} mapping a local channel ID to a C{list} of
+ C{Deferreds} for outstanding channel requests. Also, the 'global'
+ key stores the C{list} of pending global request C{Deferred}s.
+ """
+ name = b'ssh-connection'
+
+ def __init__(self):
+ self.localChannelID = 0 # this is the current # to use for channel ID
+ self.localToRemoteChannel = {} # local channel ID -> remote channel ID
+ self.channels = {} # local channel ID -> subclass of SSHChannel
+ self.channelsToRemoteChannel = {} # subclass of SSHChannel ->
+ # remote channel ID
+ self.deferreds = {"global": []} # local channel -> list of deferreds
+ # for pending requests or 'global' -> list of
+ # deferreds for global requests
+ self.transport = None # gets set later
+
+
+ def serviceStarted(self):
+ if hasattr(self.transport, 'avatar'):
+ self.transport.avatar.conn = self
+
+
+ def serviceStopped(self):
+ """
+ Called when the connection is stopped.
+ """
+ # Close any fully open channels
+ for channel in list(self.channelsToRemoteChannel.keys()):
+ self.channelClosed(channel)
+ # Indicate failure to any channels that were in the process of
+ # opening but not yet open.
+ while self.channels:
+ (_, channel) = self.channels.popitem()
+ log.callWithLogger(channel, channel.openFailed,
+ twisted.internet.error.ConnectionLost())
+ # Errback any unfinished global requests.
+ self._cleanupGlobalDeferreds()
+
+
+ def _cleanupGlobalDeferreds(self):
+ """
+ All pending requests that have returned a deferred must be errbacked
+ when this service is stopped, otherwise they might be left uncalled and
+ uncallable.
+ """
+ for d in self.deferreds["global"]:
+ d.errback(error.ConchError("Connection stopped."))
+ del self.deferreds["global"][:]
+
+
+ # packet methods
+ def ssh_GLOBAL_REQUEST(self, packet):
+ """
+ The other side has made a global request. Payload::
+ string request type
+ bool want reply
+ <request specific data>
+
+ This dispatches to self.gotGlobalRequest.
+ """
+ requestType, rest = common.getNS(packet)
+ wantReply, rest = ord(rest[0:1]), rest[1:]
+ ret = self.gotGlobalRequest(requestType, rest)
+ if wantReply:
+ reply = MSG_REQUEST_FAILURE
+ data = b''
+ if ret:
+ reply = MSG_REQUEST_SUCCESS
+ if isinstance(ret, (tuple, list)):
+ data = ret[1]
+ self.transport.sendPacket(reply, data)
+
+ def ssh_REQUEST_SUCCESS(self, packet):
+ """
+ Our global request succeeded. Get the appropriate Deferred and call
+ it back with the packet we received.
+ """
+ log.msg('RS')
+ self.deferreds['global'].pop(0).callback(packet)
+
+ def ssh_REQUEST_FAILURE(self, packet):
+ """
+ Our global request failed. Get the appropriate Deferred and errback
+ it with the packet we received.
+ """
+ log.msg('RF')
+ self.deferreds['global'].pop(0).errback(
+ error.ConchError('global request failed', packet))
+
+ def ssh_CHANNEL_OPEN(self, packet):
+ """
+ The other side wants to get a channel. Payload::
+ string channel name
+ uint32 remote channel number
+ uint32 remote window size
+ uint32 remote maximum packet size
+ <channel specific data>
+
+ We get a channel from self.getChannel(), give it a local channel number
+ and notify the other side. Then notify the channel by calling its
+ channelOpen method.
+ """
+ channelType, rest = common.getNS(packet)
+ senderChannel, windowSize, maxPacket = struct.unpack('>3L', rest[:12])
+ packet = rest[12:]
+ try:
+ channel = self.getChannel(channelType, windowSize, maxPacket,
+ packet)
+ localChannel = self.localChannelID
+ self.localChannelID += 1
+ channel.id = localChannel
+ self.channels[localChannel] = channel
+ self.channelsToRemoteChannel[channel] = senderChannel
+ self.localToRemoteChannel[localChannel] = senderChannel
+ self.transport.sendPacket(MSG_CHANNEL_OPEN_CONFIRMATION,
+ struct.pack('>4L', senderChannel, localChannel,
+ channel.localWindowSize,
+ channel.localMaxPacket)+channel.specificData)
+ log.callWithLogger(channel, channel.channelOpen, packet)
+ except Exception as e:
+ log.err(e, 'channel open failed')
+ if isinstance(e, error.ConchError):
+ textualInfo, reason = e.args
+ if isinstance(textualInfo, (int, long)):
+ # See #3657 and #3071
+ textualInfo, reason = reason, textualInfo
+ else:
+ reason = OPEN_CONNECT_FAILED
+ textualInfo = "unknown failure"
+ self.transport.sendPacket(
+ MSG_CHANNEL_OPEN_FAILURE,
+ struct.pack('>2L', senderChannel, reason) +
+ common.NS(networkString(textualInfo)) + common.NS(b''))
+
+ def ssh_CHANNEL_OPEN_CONFIRMATION(self, packet):
+ """
+ The other side accepted our MSG_CHANNEL_OPEN request. Payload::
+ uint32 local channel number
+ uint32 remote channel number
+ uint32 remote window size
+ uint32 remote maximum packet size
+ <channel specific data>
+
+ Find the channel using the local channel number and notify its
+ channelOpen method.
+ """
+ (localChannel, remoteChannel, windowSize,
+ maxPacket) = struct.unpack('>4L', packet[: 16])
+ specificData = packet[16:]
+ channel = self.channels[localChannel]
+ channel.conn = self
+ self.localToRemoteChannel[localChannel] = remoteChannel
+ self.channelsToRemoteChannel[channel] = remoteChannel
+ channel.remoteWindowLeft = windowSize
+ channel.remoteMaxPacket = maxPacket
+ log.callWithLogger(channel, channel.channelOpen, specificData)
+
+ def ssh_CHANNEL_OPEN_FAILURE(self, packet):
+ """
+ The other side did not accept our MSG_CHANNEL_OPEN request. Payload::
+ uint32 local channel number
+ uint32 reason code
+ string reason description
+
+ Find the channel using the local channel number and notify it by
+ calling its openFailed() method.
+ """
+ localChannel, reasonCode = struct.unpack('>2L', packet[:8])
+ reasonDesc = common.getNS(packet[8:])[0]
+ channel = self.channels[localChannel]
+ del self.channels[localChannel]
+ channel.conn = self
+ reason = error.ConchError(reasonDesc, reasonCode)
+ log.callWithLogger(channel, channel.openFailed, reason)
+
+ def ssh_CHANNEL_WINDOW_ADJUST(self, packet):
+ """
+ The other side is adding bytes to its window. Payload::
+ uint32 local channel number
+ uint32 bytes to add
+
+ Call the channel's addWindowBytes() method to add new bytes to the
+ remote window.
+ """
+ localChannel, bytesToAdd = struct.unpack('>2L', packet[:8])
+ channel = self.channels[localChannel]
+ log.callWithLogger(channel, channel.addWindowBytes, bytesToAdd)
+
+ def ssh_CHANNEL_DATA(self, packet):
+ """
+ The other side is sending us data. Payload::
+ uint32 local channel number
+ string data
+
+ Check to make sure the other side hasn't sent too much data (more
+ than what's in the window, or more than the maximum packet size). If
+ they have, close the channel. Otherwise, decrease the available
+ window and pass the data to the channel's dataReceived().
+ """
+ localChannel, dataLength = struct.unpack('>2L', packet[:8])
+ channel = self.channels[localChannel]
+ # XXX should this move to dataReceived to put client in charge?
+ if (dataLength > channel.localWindowLeft or
+ dataLength > channel.localMaxPacket): # more data than we want
+ log.callWithLogger(channel, log.msg, 'too much data')
+ self.sendClose(channel)
+ return
+ #packet = packet[:channel.localWindowLeft+4]
+ data = common.getNS(packet[4:])[0]
+ channel.localWindowLeft -= dataLength
+ if channel.localWindowLeft < channel.localWindowSize // 2:
+ self.adjustWindow(channel, channel.localWindowSize - \
+ channel.localWindowLeft)
+ #log.msg('local window left: %s/%s' % (channel.localWindowLeft,
+ # channel.localWindowSize))
+ log.callWithLogger(channel, channel.dataReceived, data)
+
+ def ssh_CHANNEL_EXTENDED_DATA(self, packet):
+ """
+ The other side is sending us exteneded data. Payload::
+ uint32 local channel number
+ uint32 type code
+ string data
+
+ Check to make sure the other side hasn't sent too much data (more
+ than what's in the window, or than the maximum packet size). If
+ they have, close the channel. Otherwise, decrease the available
+ window and pass the data and type code to the channel's
+ extReceived().
+ """
+ localChannel, typeCode, dataLength = struct.unpack('>3L', packet[:12])
+ channel = self.channels[localChannel]
+ if (dataLength > channel.localWindowLeft or
+ dataLength > channel.localMaxPacket):
+ log.callWithLogger(channel, log.msg, 'too much extdata')
+ self.sendClose(channel)
+ return
+ data = common.getNS(packet[8:])[0]
+ channel.localWindowLeft -= dataLength
+ if channel.localWindowLeft < channel.localWindowSize // 2:
+ self.adjustWindow(channel, channel.localWindowSize -
+ channel.localWindowLeft)
+ log.callWithLogger(channel, channel.extReceived, typeCode, data)
+
+ def ssh_CHANNEL_EOF(self, packet):
+ """
+ The other side is not sending any more data. Payload::
+ uint32 local channel number
+
+ Notify the channel by calling its eofReceived() method.
+ """
+ localChannel = struct.unpack('>L', packet[:4])[0]
+ channel = self.channels[localChannel]
+ log.callWithLogger(channel, channel.eofReceived)
+
+ def ssh_CHANNEL_CLOSE(self, packet):
+ """
+ The other side is closing its end; it does not want to receive any
+ more data. Payload::
+ uint32 local channel number
+
+ Notify the channnel by calling its closeReceived() method. If
+ the channel has also sent a close message, call self.channelClosed().
+ """
+ localChannel = struct.unpack('>L', packet[:4])[0]
+ channel = self.channels[localChannel]
+ log.callWithLogger(channel, channel.closeReceived)
+ channel.remoteClosed = True
+ if channel.localClosed and channel.remoteClosed:
+ self.channelClosed(channel)
+
+ def ssh_CHANNEL_REQUEST(self, packet):
+ """
+ The other side is sending a request to a channel. Payload::
+ uint32 local channel number
+ string request name
+ bool want reply
+ <request specific data>
+
+ Pass the message to the channel's requestReceived method. If the
+ other side wants a reply, add callbacks which will send the
+ reply.
+ """
+ localChannel = struct.unpack('>L', packet[:4])[0]
+ requestType, rest = common.getNS(packet[4:])
+ wantReply = ord(rest[0:1])
+ channel = self.channels[localChannel]
+ d = defer.maybeDeferred(log.callWithLogger, channel,
+ channel.requestReceived, requestType, rest[1:])
+ if wantReply:
+ d.addCallback(self._cbChannelRequest, localChannel)
+ d.addErrback(self._ebChannelRequest, localChannel)
+ return d
+
+ def _cbChannelRequest(self, result, localChannel):
+ """
+ Called back if the other side wanted a reply to a channel request. If
+ the result is true, send a MSG_CHANNEL_SUCCESS. Otherwise, raise
+ a C{error.ConchError}
+
+ @param result: the value returned from the channel's requestReceived()
+ method. If it's False, the request failed.
+ @type result: L{bool}
+ @param localChannel: the local channel ID of the channel to which the
+ request was made.
+ @type localChannel: L{int}
+ @raises ConchError: if the result is False.
+ """
+ if not result:
+ raise error.ConchError('failed request')
+ self.transport.sendPacket(MSG_CHANNEL_SUCCESS, struct.pack('>L',
+ self.localToRemoteChannel[localChannel]))
+
+ def _ebChannelRequest(self, result, localChannel):
+ """
+ Called if the other wisde wanted a reply to the channel requeset and
+ the channel request failed.
+
+ @param result: a Failure, but it's not used.
+ @param localChannel: the local channel ID of the channel to which the
+ request was made.
+ @type localChannel: L{int}
+ """
+ self.transport.sendPacket(MSG_CHANNEL_FAILURE, struct.pack('>L',
+ self.localToRemoteChannel[localChannel]))
+
+ def ssh_CHANNEL_SUCCESS(self, packet):
+ """
+ Our channel request to the other side succeeded. Payload::
+ uint32 local channel number
+
+ Get the C{Deferred} out of self.deferreds and call it back.
+ """
+ localChannel = struct.unpack('>L', packet[:4])[0]
+ if self.deferreds.get(localChannel):
+ d = self.deferreds[localChannel].pop(0)
+ log.callWithLogger(self.channels[localChannel],
+ d.callback, '')
+
+ def ssh_CHANNEL_FAILURE(self, packet):
+ """
+ Our channel request to the other side failed. Payload::
+ uint32 local channel number
+
+ Get the C{Deferred} out of self.deferreds and errback it with a
+ C{error.ConchError}.
+ """
+ localChannel = struct.unpack('>L', packet[:4])[0]
+ if self.deferreds.get(localChannel):
+ d = self.deferreds[localChannel].pop(0)
+ log.callWithLogger(self.channels[localChannel],
+ d.errback,
+ error.ConchError('channel request failed'))
+
+ # methods for users of the connection to call
+
+ def sendGlobalRequest(self, request, data, wantReply=0):
+ """
+ Send a global request for this connection. Current this is only used
+ for remote->local TCP forwarding.
+
+ @type request: L{bytes}
+ @type data: L{bytes}
+ @type wantReply: L{bool}
+ @rtype C{Deferred}/L{None}
+ """
+ self.transport.sendPacket(MSG_GLOBAL_REQUEST,
+ common.NS(request)
+ + (wantReply and b'\xff' or b'\x00')
+ + data)
+ if wantReply:
+ d = defer.Deferred()
+ self.deferreds['global'].append(d)
+ return d
+
+ def openChannel(self, channel, extra=b''):
+ """
+ Open a new channel on this connection.
+
+ @type channel: subclass of C{SSHChannel}
+ @type extra: L{bytes}
+ """
+ log.msg('opening channel %s with %s %s'%(self.localChannelID,
+ channel.localWindowSize, channel.localMaxPacket))
+ self.transport.sendPacket(MSG_CHANNEL_OPEN, common.NS(channel.name)
+ + struct.pack('>3L', self.localChannelID,
+ channel.localWindowSize, channel.localMaxPacket)
+ + extra)
+ channel.id = self.localChannelID
+ self.channels[self.localChannelID] = channel
+ self.localChannelID += 1
+
+ def sendRequest(self, channel, requestType, data, wantReply=0):
+ """
+ Send a request to a channel.
+
+ @type channel: subclass of C{SSHChannel}
+ @type requestType: L{bytes}
+ @type data: L{bytes}
+ @type wantReply: L{bool}
+ @rtype C{Deferred}/L{None}
+ """
+ if channel.localClosed:
+ return
+ log.msg('sending request %r' % (requestType))
+ self.transport.sendPacket(MSG_CHANNEL_REQUEST, struct.pack('>L',
+ self.channelsToRemoteChannel[channel])
+ + common.NS(requestType)+chr(wantReply)
+ + data)
+ if wantReply:
+ d = defer.Deferred()
+ self.deferreds.setdefault(channel.id, []).append(d)
+ return d
+
+ def adjustWindow(self, channel, bytesToAdd):
+ """
+ Tell the other side that we will receive more data. This should not
+ normally need to be called as it is managed automatically.
+
+ @type channel: subclass of L{SSHChannel}
+ @type bytesToAdd: L{int}
+ """
+ if channel.localClosed:
+ return # we're already closed
+ self.transport.sendPacket(MSG_CHANNEL_WINDOW_ADJUST, struct.pack('>2L',
+ self.channelsToRemoteChannel[channel],
+ bytesToAdd))
+ log.msg('adding %i to %i in channel %i' % (bytesToAdd,
+ channel.localWindowLeft, channel.id))
+ channel.localWindowLeft += bytesToAdd
+
+ def sendData(self, channel, data):
+ """
+ Send data to a channel. This should not normally be used: instead use
+ channel.write(data) as it manages the window automatically.
+
+ @type channel: subclass of L{SSHChannel}
+ @type data: L{bytes}
+ """
+ if channel.localClosed:
+ return # we're already closed
+ self.transport.sendPacket(MSG_CHANNEL_DATA, struct.pack('>L',
+ self.channelsToRemoteChannel[channel]) +
+ common.NS(data))
+
+ def sendExtendedData(self, channel, dataType, data):
+ """
+ Send extended data to a channel. This should not normally be used:
+ instead use channel.writeExtendedData(data, dataType) as it manages
+ the window automatically.
+
+ @type channel: subclass of L{SSHChannel}
+ @type dataType: L{int}
+ @type data: L{bytes}
+ """
+ if channel.localClosed:
+ return # we're already closed
+ self.transport.sendPacket(MSG_CHANNEL_EXTENDED_DATA, struct.pack('>2L',
+ self.channelsToRemoteChannel[channel],dataType) \
+ + common.NS(data))
+
+ def sendEOF(self, channel):
+ """
+ Send an EOF (End of File) for a channel.
+
+ @type channel: subclass of L{SSHChannel}
+ """
+ if channel.localClosed:
+ return # we're already closed
+ log.msg('sending eof')
+ self.transport.sendPacket(MSG_CHANNEL_EOF, struct.pack('>L',
+ self.channelsToRemoteChannel[channel]))
+
+ def sendClose(self, channel):
+ """
+ Close a channel.
+
+ @type channel: subclass of L{SSHChannel}
+ """
+ if channel.localClosed:
+ return # we're already closed
+ log.msg('sending close %i' % channel.id)
+ self.transport.sendPacket(MSG_CHANNEL_CLOSE, struct.pack('>L',
+ self.channelsToRemoteChannel[channel]))
+ channel.localClosed = True
+ if channel.localClosed and channel.remoteClosed:
+ self.channelClosed(channel)
+
+ # methods to override
+ def getChannel(self, channelType, windowSize, maxPacket, data):
+ """
+ The other side requested a channel of some sort.
+ channelType is the type of channel being requested,
+ windowSize is the initial size of the remote window,
+ maxPacket is the largest packet we should send,
+ data is any other packet data (often nothing).
+
+ We return a subclass of L{SSHChannel}.
+
+ By default, this dispatches to a method 'channel_channelType' with any
+ non-alphanumerics in the channelType replace with _'s. If it cannot
+ find a suitable method, it returns an OPEN_UNKNOWN_CHANNEL_TYPE error.
+ The method is called with arguments of windowSize, maxPacket, data.
+
+ @type channelType: L{bytes}
+ @type windowSize: L{int}
+ @type maxPacket: L{int}
+ @type data: L{bytes}
+ @rtype: subclass of L{SSHChannel}/L{tuple}
+ """
+ log.msg('got channel %r request' % (channelType))
+ if hasattr(self.transport, "avatar"): # this is a server!
+ chan = self.transport.avatar.lookupChannel(channelType,
+ windowSize,
+ maxPacket,
+ data)
+ else:
+ channelType = channelType.translate(TRANSLATE_TABLE)
+ attr = 'channel_%s' % nativeString(channelType)
+ f = getattr(self, attr, None)
+ if f is not None:
+ chan = f(windowSize, maxPacket, data)
+ else:
+ chan = None
+ if chan is None:
+ raise error.ConchError('unknown channel',
+ OPEN_UNKNOWN_CHANNEL_TYPE)
+ else:
+ chan.conn = self
+ return chan
+
+ def gotGlobalRequest(self, requestType, data):
+ """
+ We got a global request. pretty much, this is just used by the client
+ to request that we forward a port from the server to the client.
+ Returns either:
+ - 1: request accepted
+ - 1, <data>: request accepted with request specific data
+ - 0: request denied
+
+ By default, this dispatches to a method 'global_requestType' with
+ -'s in requestType replaced with _'s. The found method is passed data.
+ If this method cannot be found, this method returns 0. Otherwise, it
+ returns the return value of that method.
+
+ @type requestType: L{bytes}
+ @type data: L{bytes}
+ @rtype: L{int}/L{tuple}
+ """
+ log.msg('got global %s request' % requestType)
+ if hasattr(self.transport, 'avatar'): # this is a server!
+ return self.transport.avatar.gotGlobalRequest(requestType, data)
+
+ requestType = nativeString(requestType.replace(b'-',b'_'))
+ f = getattr(self, 'global_%s' % requestType, None)
+ if not f:
+ return 0
+ return f(data)
+
+ def channelClosed(self, channel):
+ """
+ Called when a channel is closed.
+ It clears the local state related to the channel, and calls
+ channel.closed().
+ MAKE SURE YOU CALL THIS METHOD, even if you subclass L{SSHConnection}.
+ If you don't, things will break mysteriously.
+
+ @type channel: L{SSHChannel}
+ """
+ if channel in self.channelsToRemoteChannel: # actually open
+ channel.localClosed = channel.remoteClosed = True
+ del self.localToRemoteChannel[channel.id]
+ del self.channels[channel.id]
+ del self.channelsToRemoteChannel[channel]
+ for d in self.deferreds.pop(channel.id, []):
+ d.errback(error.ConchError("Channel closed."))
+ log.callWithLogger(channel, channel.closed)
+
+
+
+MSG_GLOBAL_REQUEST = 80
+MSG_REQUEST_SUCCESS = 81
+MSG_REQUEST_FAILURE = 82
+MSG_CHANNEL_OPEN = 90
+MSG_CHANNEL_OPEN_CONFIRMATION = 91
+MSG_CHANNEL_OPEN_FAILURE = 92
+MSG_CHANNEL_WINDOW_ADJUST = 93
+MSG_CHANNEL_DATA = 94
+MSG_CHANNEL_EXTENDED_DATA = 95
+MSG_CHANNEL_EOF = 96
+MSG_CHANNEL_CLOSE = 97
+MSG_CHANNEL_REQUEST = 98
+MSG_CHANNEL_SUCCESS = 99
+MSG_CHANNEL_FAILURE = 100
+
+OPEN_ADMINISTRATIVELY_PROHIBITED = 1
+OPEN_CONNECT_FAILED = 2
+OPEN_UNKNOWN_CHANNEL_TYPE = 3
+OPEN_RESOURCE_SHORTAGE = 4
+
+EXTENDED_DATA_STDERR = 1
+
+messages = {}
+for name, value in locals().copy().items():
+ if name[:4] == 'MSG_':
+ messages[value] = name # Doesn't handle doubles
+
+alphanums = networkString(string.ascii_letters + string.digits)
+TRANSLATE_TABLE = b''.join([chr(i) in alphanums and chr(i) or b'_'
+ for i in range(256)])
+SSHConnection.protocolMessages = messages
diff --git a/contrib/python/Twisted/py2/twisted/conch/ssh/factory.py b/contrib/python/Twisted/py2/twisted/conch/ssh/factory.py
new file mode 100644
index 0000000000..16658e7c59
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/ssh/factory.py
@@ -0,0 +1,123 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+A Factory for SSH servers.
+
+See also L{twisted.conch.openssh_compat.factory} for OpenSSH compatibility.
+
+Maintainer: Paul Swartz
+"""
+
+from __future__ import division, absolute_import
+
+from twisted.internet import protocol
+from twisted.python import log
+
+from twisted.conch import error
+from twisted.conch.ssh import (_kex, transport, userauth, connection)
+
+import random
+
+
+class SSHFactory(protocol.Factory):
+ """
+ A Factory for SSH servers.
+ """
+ protocol = transport.SSHServerTransport
+
+ services = {
+ b'ssh-userauth':userauth.SSHUserAuthServer,
+ b'ssh-connection':connection.SSHConnection
+ }
+ def startFactory(self):
+ """
+ Check for public and private keys.
+ """
+ if not hasattr(self,'publicKeys'):
+ self.publicKeys = self.getPublicKeys()
+ if not hasattr(self,'privateKeys'):
+ self.privateKeys = self.getPrivateKeys()
+ if not self.publicKeys or not self.privateKeys:
+ raise error.ConchError('no host keys, failing')
+ if not hasattr(self,'primes'):
+ self.primes = self.getPrimes()
+
+
+ def buildProtocol(self, addr):
+ """
+ Create an instance of the server side of the SSH protocol.
+
+ @type addr: L{twisted.internet.interfaces.IAddress} provider
+ @param addr: The address at which the server will listen.
+
+ @rtype: L{twisted.conch.ssh.transport.SSHServerTransport}
+ @return: The built transport.
+ """
+ t = protocol.Factory.buildProtocol(self, addr)
+ t.supportedPublicKeys = self.privateKeys.keys()
+ if not self.primes:
+ log.msg('disabling non-fixed-group key exchange algorithms '
+ 'because we cannot find moduli file')
+ t.supportedKeyExchanges = [
+ kexAlgorithm for kexAlgorithm in t.supportedKeyExchanges
+ if _kex.isFixedGroup(kexAlgorithm) or
+ _kex.isEllipticCurve(kexAlgorithm)]
+ return t
+
+
+ def getPublicKeys(self):
+ """
+ Called when the factory is started to get the public portions of the
+ servers host keys. Returns a dictionary mapping SSH key types to
+ public key strings.
+
+ @rtype: L{dict}
+ """
+ raise NotImplementedError('getPublicKeys unimplemented')
+
+
+ def getPrivateKeys(self):
+ """
+ Called when the factory is started to get the private portions of the
+ servers host keys. Returns a dictionary mapping SSH key types to
+ L{twisted.conch.ssh.keys.Key} objects.
+
+ @rtype: L{dict}
+ """
+ raise NotImplementedError('getPrivateKeys unimplemented')
+
+
+ def getPrimes(self):
+ """
+ Called when the factory is started to get Diffie-Hellman generators and
+ primes to use. Returns a dictionary mapping number of bits to lists
+ of tuple of (generator, prime).
+
+ @rtype: L{dict}
+ """
+
+
+ def getDHPrime(self, bits):
+ """
+ Return a tuple of (g, p) for a Diffe-Hellman process, with p being as
+ close to bits bits as possible.
+
+ @type bits: L{int}
+ @rtype: L{tuple}
+ """
+ primesKeys = sorted(self.primes.keys(), key=lambda i: abs(i - bits))
+ realBits = primesKeys[0]
+ return random.choice(self.primes[realBits])
+
+
+ def getService(self, transport, service):
+ """
+ Return a class to use as a service for the given transport.
+
+ @type transport: L{transport.SSHServerTransport}
+ @type service: L{bytes}
+ @rtype: subclass of L{service.SSHService}
+ """
+ if service == b'ssh-userauth' or hasattr(transport, 'avatar'):
+ return self.services[service]
diff --git a/contrib/python/Twisted/py2/twisted/conch/ssh/filetransfer.py b/contrib/python/Twisted/py2/twisted/conch/ssh/filetransfer.py
new file mode 100644
index 0000000000..cd739e5361
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/ssh/filetransfer.py
@@ -0,0 +1,1055 @@
+# -*- test-case-name: twisted.conch.test.test_filetransfer -*-
+#
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from __future__ import division, absolute_import
+
+import errno
+import struct
+import warnings
+
+from zope.interface import implementer
+
+from twisted.conch.interfaces import ISFTPServer, ISFTPFile
+from twisted.conch.ssh.common import NS, getNS
+from twisted.internet import defer, protocol
+from twisted.python import failure, log
+from twisted.python.compat import (
+ _PY3, range, itervalues, nativeString, networkString)
+
+
+
+class FileTransferBase(protocol.Protocol):
+
+ versions = (3, )
+
+ packetTypes = {}
+
+ def __init__(self):
+ self.buf = b''
+ self.otherVersion = None # This gets set
+
+
+ def sendPacket(self, kind, data):
+ self.transport.write(struct.pack('!LB', len(data)+1, kind) + data)
+
+
+ def dataReceived(self, data):
+ self.buf += data
+ while len(self.buf) > 5:
+ length, kind = struct.unpack('!LB', self.buf[:5])
+ if len(self.buf) < 4 + length:
+ return
+ data, self.buf = self.buf[5:4+length], self.buf[4+length:]
+ packetType = self.packetTypes.get(kind, None)
+ if not packetType:
+ log.msg('no packet type for', kind)
+ continue
+ f = getattr(self, 'packet_{}'.format(packetType), None)
+ if not f:
+ log.msg('not implemented: {}'.format(packetType))
+ log.msg(repr(data[4:]))
+ reqId, = struct.unpack('!L', data[:4])
+ self._sendStatus(reqId, FX_OP_UNSUPPORTED,
+ "don't understand {}".format(packetType))
+ # XXX not implemented
+ continue
+ try:
+ f(data)
+ except Exception:
+ log.err()
+ continue
+
+
+ def _parseAttributes(self, data):
+ (flags,) = struct.unpack('!L', data[:4])
+ attrs = {}
+ data = data[4:]
+ if flags & FILEXFER_ATTR_SIZE == FILEXFER_ATTR_SIZE:
+ (size,) = struct.unpack('!Q', data[:8])
+ attrs['size'] = size
+ data = data[8:]
+ if flags & FILEXFER_ATTR_OWNERGROUP == FILEXFER_ATTR_OWNERGROUP:
+ uid, gid = struct.unpack('!2L', data[:8])
+ attrs['uid'] = uid
+ attrs['gid'] = gid
+ data = data[8:]
+ if flags & FILEXFER_ATTR_PERMISSIONS == FILEXFER_ATTR_PERMISSIONS:
+ (perms,) = struct.unpack('!L', data[:4])
+ attrs['permissions'] = perms
+ data = data[4:]
+ if flags & FILEXFER_ATTR_ACMODTIME == FILEXFER_ATTR_ACMODTIME:
+ atime, mtime = struct.unpack('!2L', data[:8])
+ attrs['atime'] = atime
+ attrs['mtime'] = mtime
+ data = data[8:]
+ if flags & FILEXFER_ATTR_EXTENDED == FILEXFER_ATTR_EXTENDED:
+ (extendedCount,) = struct.unpack('!L', data[:4])
+ data = data[4:]
+ for i in range(extendedCount):
+ (extendedType, data) = getNS(data)
+ (extendedData, data) = getNS(data)
+ attrs['ext_{}'.format(nativeString(extendedType))] = \
+ extendedData
+ return attrs, data
+
+
+ def _packAttributes(self, attrs):
+ flags = 0
+ data = b''
+ if 'size' in attrs:
+ data += struct.pack('!Q', attrs['size'])
+ flags |= FILEXFER_ATTR_SIZE
+ if 'uid' in attrs and 'gid' in attrs:
+ data += struct.pack('!2L', attrs['uid'], attrs['gid'])
+ flags |= FILEXFER_ATTR_OWNERGROUP
+ if 'permissions' in attrs:
+ data += struct.pack('!L', attrs['permissions'])
+ flags |= FILEXFER_ATTR_PERMISSIONS
+ if 'atime' in attrs and 'mtime' in attrs:
+ data += struct.pack('!2L', attrs['atime'], attrs['mtime'])
+ flags |= FILEXFER_ATTR_ACMODTIME
+ extended = []
+ for k in attrs:
+ if k.startswith('ext_'):
+ extType = NS(networkString(k[4:]))
+ extData = NS(attrs[k])
+ extended.append(extType + extData)
+ if extended:
+ data += struct.pack('!L', len(extended))
+ data += b''.join(extended)
+ flags |= FILEXFER_ATTR_EXTENDED
+ return struct.pack('!L', flags) + data
+
+
+
+class FileTransferServer(FileTransferBase):
+
+ def __init__(self, data=None, avatar=None):
+ FileTransferBase.__init__(self)
+ self.client = ISFTPServer(avatar) # yay interfaces
+ self.openFiles = {}
+ self.openDirs = {}
+
+
+ def packet_INIT(self, data):
+ (version,) = struct.unpack('!L', data[:4])
+ self.version = min(list(self.versions) + [version])
+ data = data[4:]
+ ext = {}
+ while data:
+ extName, data = getNS(data)
+ extData, data = getNS(data)
+ ext[extName] = extData
+ ourExt = self.client.gotVersion(version, ext)
+ ourExtData = b""
+ for (k, v) in ourExt.items():
+ ourExtData += NS(k) + NS(v)
+ self.sendPacket(FXP_VERSION, struct.pack('!L', self.version) +
+ ourExtData)
+
+
+ def packet_OPEN(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ filename, data = getNS(data)
+ (flags,) = struct.unpack('!L', data[:4])
+ data = data[4:]
+ attrs, data = self._parseAttributes(data)
+ assert data == b'', 'still have data in OPEN: {!r}'.format(data)
+ d = defer.maybeDeferred(self.client.openFile, filename, flags, attrs)
+ d.addCallback(self._cbOpenFile, requestId)
+ d.addErrback(self._ebStatus, requestId, b"open failed")
+
+
+ def _cbOpenFile(self, fileObj, requestId):
+ fileId = networkString(str(hash(fileObj)))
+ if fileId in self.openFiles:
+ raise KeyError('id already open')
+ self.openFiles[fileId] = fileObj
+ self.sendPacket(FXP_HANDLE, requestId + NS(fileId))
+
+
+ def packet_CLOSE(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ handle, data = getNS(data)
+ assert data == b'', 'still have data in CLOSE: {!r}'.format(data)
+ if handle in self.openFiles:
+ fileObj = self.openFiles[handle]
+ d = defer.maybeDeferred(fileObj.close)
+ d.addCallback(self._cbClose, handle, requestId)
+ d.addErrback(self._ebStatus, requestId, b"close failed")
+ elif handle in self.openDirs:
+ dirObj = self.openDirs[handle][0]
+ d = defer.maybeDeferred(dirObj.close)
+ d.addCallback(self._cbClose, handle, requestId, 1)
+ d.addErrback(self._ebStatus, requestId, b"close failed")
+ else:
+ self._ebClose(failure.Failure(KeyError()), requestId)
+
+
+ def _cbClose(self, result, handle, requestId, isDir=0):
+ if isDir:
+ del self.openDirs[handle]
+ else:
+ del self.openFiles[handle]
+ self._sendStatus(requestId, FX_OK, b'file closed')
+
+
+ def packet_READ(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ handle, data = getNS(data)
+ (offset, length), data = struct.unpack('!QL', data[:12]), data[12:]
+ assert data == b'', 'still have data in READ: {!r}'.format(data)
+ if handle not in self.openFiles:
+ self._ebRead(failure.Failure(KeyError()), requestId)
+ else:
+ fileObj = self.openFiles[handle]
+ d = defer.maybeDeferred(fileObj.readChunk, offset, length)
+ d.addCallback(self._cbRead, requestId)
+ d.addErrback(self._ebStatus, requestId, b"read failed")
+
+
+ def _cbRead(self, result, requestId):
+ if result == b'': # Python's read will return this for EOF
+ raise EOFError()
+ self.sendPacket(FXP_DATA, requestId + NS(result))
+
+
+ def packet_WRITE(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ handle, data = getNS(data)
+ offset, = struct.unpack('!Q', data[:8])
+ data = data[8:]
+ writeData, data = getNS(data)
+ assert data == b'', 'still have data in WRITE: {!r}'.format(data)
+ if handle not in self.openFiles:
+ self._ebWrite(failure.Failure(KeyError()), requestId)
+ else:
+ fileObj = self.openFiles[handle]
+ d = defer.maybeDeferred(fileObj.writeChunk, offset, writeData)
+ d.addCallback(self._cbStatus, requestId, b"write succeeded")
+ d.addErrback(self._ebStatus, requestId, b"write failed")
+
+
+ def packet_REMOVE(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ filename, data = getNS(data)
+ assert data == b'', 'still have data in REMOVE: {!r}'.format(data)
+ d = defer.maybeDeferred(self.client.removeFile, filename)
+ d.addCallback(self._cbStatus, requestId, b"remove succeeded")
+ d.addErrback(self._ebStatus, requestId, b"remove failed")
+
+
+ def packet_RENAME(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ oldPath, data = getNS(data)
+ newPath, data = getNS(data)
+ assert data == b'', 'still have data in RENAME: {!r}'.format(data)
+ d = defer.maybeDeferred(self.client.renameFile, oldPath, newPath)
+ d.addCallback(self._cbStatus, requestId, b"rename succeeded")
+ d.addErrback(self._ebStatus, requestId, b"rename failed")
+
+
+ def packet_MKDIR(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ path, data = getNS(data)
+ attrs, data = self._parseAttributes(data)
+ assert data == b'', 'still have data in MKDIR: {!r}'.format(data)
+ d = defer.maybeDeferred(self.client.makeDirectory, path, attrs)
+ d.addCallback(self._cbStatus, requestId, b"mkdir succeeded")
+ d.addErrback(self._ebStatus, requestId, b"mkdir failed")
+
+
+ def packet_RMDIR(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ path, data = getNS(data)
+ assert data == b'', 'still have data in RMDIR: {!r}'.format(data)
+ d = defer.maybeDeferred(self.client.removeDirectory, path)
+ d.addCallback(self._cbStatus, requestId, b"rmdir succeeded")
+ d.addErrback(self._ebStatus, requestId, b"rmdir failed")
+
+
+ def packet_OPENDIR(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ path, data = getNS(data)
+ assert data == b'', 'still have data in OPENDIR: {!r}'.format(data)
+ d = defer.maybeDeferred(self.client.openDirectory, path)
+ d.addCallback(self._cbOpenDirectory, requestId)
+ d.addErrback(self._ebStatus, requestId, b"opendir failed")
+
+
+ def _cbOpenDirectory(self, dirObj, requestId):
+ handle = networkString((str(hash(dirObj))))
+ if handle in self.openDirs:
+ raise KeyError("already opened this directory")
+ self.openDirs[handle] = [dirObj, iter(dirObj)]
+ self.sendPacket(FXP_HANDLE, requestId + NS(handle))
+
+
+ def packet_READDIR(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ handle, data = getNS(data)
+ assert data == b'', 'still have data in READDIR: {!r}'.format(data)
+ if handle not in self.openDirs:
+ self._ebStatus(failure.Failure(KeyError()), requestId)
+ else:
+ dirObj, dirIter = self.openDirs[handle]
+ d = defer.maybeDeferred(self._scanDirectory, dirIter, [])
+ d.addCallback(self._cbSendDirectory, requestId)
+ d.addErrback(self._ebStatus, requestId, b"scan directory failed")
+
+
+ def _scanDirectory(self, dirIter, f):
+ while len(f) < 250:
+ try:
+ info = next(dirIter)
+ except StopIteration:
+ if not f:
+ raise EOFError
+ return f
+ if isinstance(info, defer.Deferred):
+ info.addCallback(self._cbScanDirectory, dirIter, f)
+ return
+ else:
+ f.append(info)
+ return f
+
+
+ def _cbScanDirectory(self, result, dirIter, f):
+ f.append(result)
+ return self._scanDirectory(dirIter, f)
+
+
+ def _cbSendDirectory(self, result, requestId):
+ data = b''
+ for (filename, longname, attrs) in result:
+ data += NS(filename)
+ data += NS(longname)
+ data += self._packAttributes(attrs)
+ self.sendPacket(FXP_NAME, requestId +
+ struct.pack('!L', len(result))+data)
+
+
+ def packet_STAT(self, data, followLinks=1):
+ requestId = data[:4]
+ data = data[4:]
+ path, data = getNS(data)
+ assert data == b'', 'still have data in STAT/LSTAT: {!r}'.format(data)
+ d = defer.maybeDeferred(self.client.getAttrs, path, followLinks)
+ d.addCallback(self._cbStat, requestId)
+ d.addErrback(self._ebStatus, requestId, b'stat/lstat failed')
+
+
+ def packet_LSTAT(self, data):
+ self.packet_STAT(data, 0)
+
+
+ def packet_FSTAT(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ handle, data = getNS(data)
+ assert data == b'', 'still have data in FSTAT: {!r}'.format(data)
+ if handle not in self.openFiles:
+ self._ebStatus(failure.Failure(KeyError(
+ '{} not in self.openFiles'.format(handle))), requestId)
+ else:
+ fileObj = self.openFiles[handle]
+ d = defer.maybeDeferred(fileObj.getAttrs)
+ d.addCallback(self._cbStat, requestId)
+ d.addErrback(self._ebStatus, requestId, b'fstat failed')
+
+
+ def _cbStat(self, result, requestId):
+ data = requestId + self._packAttributes(result)
+ self.sendPacket(FXP_ATTRS, data)
+
+
+ def packet_SETSTAT(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ path, data = getNS(data)
+ attrs, data = self._parseAttributes(data)
+ if data != b'':
+ log.msg('WARN: still have data in SETSTAT: {!r}'.format(data))
+ d = defer.maybeDeferred(self.client.setAttrs, path, attrs)
+ d.addCallback(self._cbStatus, requestId, b'setstat succeeded')
+ d.addErrback(self._ebStatus, requestId, b'setstat failed')
+
+
+ def packet_FSETSTAT(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ handle, data = getNS(data)
+ attrs, data = self._parseAttributes(data)
+ assert data == b'', 'still have data in FSETSTAT: {!r}'.format(data)
+ if handle not in self.openFiles:
+ self._ebStatus(failure.Failure(KeyError()), requestId)
+ else:
+ fileObj = self.openFiles[handle]
+ d = defer.maybeDeferred(fileObj.setAttrs, attrs)
+ d.addCallback(self._cbStatus, requestId, b'fsetstat succeeded')
+ d.addErrback(self._ebStatus, requestId, b'fsetstat failed')
+
+
+ def packet_READLINK(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ path, data = getNS(data)
+ assert data == b'', 'still have data in READLINK: {!r}'.format(data)
+ d = defer.maybeDeferred(self.client.readLink, path)
+ d.addCallback(self._cbReadLink, requestId)
+ d.addErrback(self._ebStatus, requestId, b'readlink failed')
+
+
+ def _cbReadLink(self, result, requestId):
+ self._cbSendDirectory([(result, b'', {})], requestId)
+
+
+ def packet_SYMLINK(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ linkPath, data = getNS(data)
+ targetPath, data = getNS(data)
+ d = defer.maybeDeferred(self.client.makeLink, linkPath, targetPath)
+ d.addCallback(self._cbStatus, requestId, b'symlink succeeded')
+ d.addErrback(self._ebStatus, requestId, b'symlink failed')
+
+
+ def packet_REALPATH(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ path, data = getNS(data)
+ assert data == b'', 'still have data in REALPATH: {!r}'.format(data)
+ d = defer.maybeDeferred(self.client.realPath, path)
+ d.addCallback(self._cbReadLink, requestId) # Same return format
+ d.addErrback(self._ebStatus, requestId, b'realpath failed')
+
+
+ def packet_EXTENDED(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ extName, extData = getNS(data)
+ d = defer.maybeDeferred(self.client.extendedRequest, extName, extData)
+ d.addCallback(self._cbExtended, requestId)
+ d.addErrback(self._ebStatus, requestId,
+ b'extended ' + extName + b' failed')
+
+
+ def _cbExtended(self, data, requestId):
+ self.sendPacket(FXP_EXTENDED_REPLY, requestId + data)
+
+
+ def _cbStatus(self, result, requestId, msg=b"request succeeded"):
+ self._sendStatus(requestId, FX_OK, msg)
+
+
+ def _ebStatus(self, reason, requestId, msg=b"request failed"):
+ code = FX_FAILURE
+ message = msg
+ if isinstance(reason.value, (IOError, OSError)):
+ if reason.value.errno == errno.ENOENT: # No such file
+ code = FX_NO_SUCH_FILE
+ message = networkString(reason.value.strerror)
+ elif reason.value.errno == errno.EACCES: # Permission denied
+ code = FX_PERMISSION_DENIED
+ message = networkString(reason.value.strerror)
+ elif reason.value.errno == errno.EEXIST:
+ code = FX_FILE_ALREADY_EXISTS
+ else:
+ log.err(reason)
+ elif isinstance(reason.value, EOFError): # EOF
+ code = FX_EOF
+ if reason.value.args:
+ message = networkString(reason.value.args[0])
+ elif isinstance(reason.value, NotImplementedError):
+ code = FX_OP_UNSUPPORTED
+ if reason.value.args:
+ message = networkString(reason.value.args[0])
+ elif isinstance(reason.value, SFTPError):
+ code = reason.value.code
+ message = networkString(reason.value.message)
+ else:
+ log.err(reason)
+ self._sendStatus(requestId, code, message)
+
+
+ def _sendStatus(self, requestId, code, message, lang=b''):
+ """
+ Helper method to send a FXP_STATUS message.
+ """
+ data = requestId + struct.pack('!L', code)
+ data += NS(message)
+ data += NS(lang)
+ self.sendPacket(FXP_STATUS, data)
+
+
+ def connectionLost(self, reason):
+ """
+ Clean all opened files and directories.
+ """
+ for fileObj in self.openFiles.values():
+ fileObj.close()
+ self.openFiles = {}
+ for (dirObj, dirIter) in self.openDirs.values():
+ dirObj.close()
+ self.openDirs = {}
+
+
+
+class FileTransferClient(FileTransferBase):
+
+ def __init__(self, extData={}):
+ """
+ @param extData: a dict of extended_name : extended_data items
+ to be sent to the server.
+ """
+ FileTransferBase.__init__(self)
+ self.extData = {}
+ self.counter = 0
+ self.openRequests = {} # id -> Deferred
+
+
+ def connectionMade(self):
+ data = struct.pack('!L', max(self.versions))
+ for (k, v) in itervalues(self.extData):
+ data += NS(k) + NS(v)
+ self.sendPacket(FXP_INIT, data)
+
+
+ def _sendRequest(self, msg, data):
+ data = struct.pack('!L', self.counter) + data
+ d = defer.Deferred()
+ self.openRequests[self.counter] = d
+ self.counter += 1
+ self.sendPacket(msg, data)
+ return d
+
+
+ def _parseRequest(self, data):
+ (id,) = struct.unpack('!L', data[:4])
+ d = self.openRequests[id]
+ del self.openRequests[id]
+ return d, data[4:]
+
+
+ def openFile(self, filename, flags, attrs):
+ """
+ Open a file.
+
+ This method returns a L{Deferred} that is called back with an object
+ that provides the L{ISFTPFile} interface.
+
+ @type filename: L{bytes}
+ @param filename: a string representing the file to open.
+
+ @param flags: an integer of the flags to open the file with, ORed together.
+ The flags and their values are listed at the bottom of this file.
+
+ @param attrs: a list of attributes to open the file with. It is a
+ dictionary, consisting of 0 or more keys. The possible keys are::
+
+ size: the size of the file in bytes
+ uid: the user ID of the file as an integer
+ gid: the group ID of the file as an integer
+ permissions: the permissions of the file with as an integer.
+ the bit representation of this field is defined by POSIX.
+ atime: the access time of the file as seconds since the epoch.
+ mtime: the modification time of the file as seconds since the epoch.
+ ext_*: extended attributes. The server is not required to
+ understand this, but it may.
+
+ NOTE: there is no way to indicate text or binary files. it is up
+ to the SFTP client to deal with this.
+ """
+ data = NS(filename) + struct.pack('!L', flags) + self._packAttributes(attrs)
+ d = self._sendRequest(FXP_OPEN, data)
+ d.addCallback(self._cbOpenHandle, ClientFile, filename)
+ return d
+
+
+ def _cbOpenHandle(self, handle, handleClass, name):
+ """
+ Callback invoked when an OPEN or OPENDIR request succeeds.
+
+ @param handle: The handle returned by the server
+ @type handle: L{bytes}
+ @param handleClass: The class that will represent the
+ newly-opened file or directory to the user (either L{ClientFile} or
+ L{ClientDirectory}).
+ @param name: The name of the file or directory represented
+ by C{handle}.
+ @type name: L{bytes}
+ """
+ cb = handleClass(self, handle)
+ cb.name = name
+ return cb
+
+
+ def removeFile(self, filename):
+ """
+ Remove the given file.
+
+ This method returns a Deferred that is called back when it succeeds.
+
+ @type filename: L{bytes}
+ @param filename: the name of the file as a string.
+ """
+ return self._sendRequest(FXP_REMOVE, NS(filename))
+
+
+ def renameFile(self, oldpath, newpath):
+ """
+ Rename the given file.
+
+ This method returns a Deferred that is called back when it succeeds.
+
+ @type oldpath: L{bytes}
+ @param oldpath: the current location of the file.
+ @type newpath: L{bytes}
+ @param newpath: the new file name.
+ """
+ return self._sendRequest(FXP_RENAME, NS(oldpath)+NS(newpath))
+
+
+ def makeDirectory(self, path, attrs):
+ """
+ Make a directory.
+
+ This method returns a Deferred that is called back when it is
+ created.
+
+ @type path: L{bytes}
+ @param path: the name of the directory to create as a string.
+
+ @param attrs: a dictionary of attributes to create the directory
+ with. Its meaning is the same as the attrs in the openFile method.
+ """
+ return self._sendRequest(FXP_MKDIR, NS(path)+self._packAttributes(attrs))
+
+
+ def removeDirectory(self, path):
+ """
+ Remove a directory (non-recursively)
+
+ It is an error to remove a directory that has files or directories in
+ it.
+
+ This method returns a Deferred that is called back when it is removed.
+
+ @type path: L{bytes}
+ @param path: the directory to remove.
+ """
+ return self._sendRequest(FXP_RMDIR, NS(path))
+
+
+ def openDirectory(self, path):
+ """
+ Open a directory for scanning.
+
+ This method returns a Deferred that is called back with an iterable
+ object that has a close() method.
+
+ The close() method is called when the client is finished reading
+ from the directory. At this point, the iterable will no longer
+ be used.
+
+ The iterable returns triples of the form (filename, longname, attrs)
+ or a Deferred that returns the same. The sequence must support
+ __getitem__, but otherwise may be any 'sequence-like' object.
+
+ filename is the name of the file relative to the directory.
+ logname is an expanded format of the filename. The recommended format
+ is:
+ -rwxr-xr-x 1 mjos staff 348911 Mar 25 14:29 t-filexfer
+ 1234567890 123 12345678 12345678 12345678 123456789012
+
+ The first line is sample output, the second is the length of the field.
+ The fields are: permissions, link count, user owner, group owner,
+ size in bytes, modification time.
+
+ attrs is a dictionary in the format of the attrs argument to openFile.
+
+ @type path: L{bytes}
+ @param path: the directory to open.
+ """
+ d = self._sendRequest(FXP_OPENDIR, NS(path))
+ d.addCallback(self._cbOpenHandle, ClientDirectory, path)
+ return d
+
+
+ def getAttrs(self, path, followLinks=0):
+ """
+ Return the attributes for the given path.
+
+ This method returns a dictionary in the same format as the attrs
+ argument to openFile or a Deferred that is called back with same.
+
+ @type path: L{bytes}
+ @param path: the path to return attributes for as a string.
+ @param followLinks: a boolean. if it is True, follow symbolic links
+ and return attributes for the real path at the base. if it is False,
+ return attributes for the specified path.
+ """
+ if followLinks: m = FXP_STAT
+ else: m = FXP_LSTAT
+ return self._sendRequest(m, NS(path))
+
+
+ def setAttrs(self, path, attrs):
+ """
+ Set the attributes for the path.
+
+ This method returns when the attributes are set or a Deferred that is
+ called back when they are.
+
+ @type path: L{bytes}
+ @param path: the path to set attributes for as a string.
+ @param attrs: a dictionary in the same format as the attrs argument to
+ openFile.
+ """
+ data = NS(path) + self._packAttributes(attrs)
+ return self._sendRequest(FXP_SETSTAT, data)
+
+
+ def readLink(self, path):
+ """
+ Find the root of a set of symbolic links.
+
+ This method returns the target of the link, or a Deferred that
+ returns the same.
+
+ @type path: L{bytes}
+ @param path: the path of the symlink to read.
+ """
+ d = self._sendRequest(FXP_READLINK, NS(path))
+ return d.addCallback(self._cbRealPath)
+
+
+ def makeLink(self, linkPath, targetPath):
+ """
+ Create a symbolic link.
+
+ This method returns when the link is made, or a Deferred that
+ returns the same.
+
+ @type linkPath: L{bytes}
+ @param linkPath: the pathname of the symlink as a string
+ @type targetPath: L{bytes}
+ @param targetPath: the path of the target of the link as a string.
+ """
+ return self._sendRequest(FXP_SYMLINK, NS(linkPath)+NS(targetPath))
+
+
+ def realPath(self, path):
+ """
+ Convert any path to an absolute path.
+
+ This method returns the absolute path as a string, or a Deferred
+ that returns the same.
+
+ @type path: L{bytes}
+ @param path: the path to convert as a string.
+ """
+ d = self._sendRequest(FXP_REALPATH, NS(path))
+ return d.addCallback(self._cbRealPath)
+
+
+ def _cbRealPath(self, result):
+ name, longname, attrs = result[0]
+ if _PY3:
+ name = name.decode("utf-8")
+ return name
+
+
+ def extendedRequest(self, request, data):
+ """
+ Make an extended request of the server.
+
+ The method returns a Deferred that is called back with
+ the result of the extended request.
+
+ @type request: L{bytes}
+ @param request: the name of the extended request to make.
+ @type data: L{bytes}
+ @param data: any other data that goes along with the request.
+ """
+ return self._sendRequest(FXP_EXTENDED, NS(request) + data)
+
+
+ def packet_VERSION(self, data):
+ version, = struct.unpack('!L', data[:4])
+ data = data[4:]
+ d = {}
+ while data:
+ k, data = getNS(data)
+ v, data = getNS(data)
+ d[k]=v
+ self.version = version
+ self.gotServerVersion(version, d)
+
+
+ def packet_STATUS(self, data):
+ d, data = self._parseRequest(data)
+ code, = struct.unpack('!L', data[:4])
+ data = data[4:]
+ if len(data) >= 4:
+ msg, data = getNS(data)
+ if len(data) >= 4:
+ lang, data = getNS(data)
+ else:
+ lang = b''
+ else:
+ msg = b''
+ lang = b''
+ if code == FX_OK:
+ d.callback((msg, lang))
+ elif code == FX_EOF:
+ d.errback(EOFError(msg))
+ elif code == FX_OP_UNSUPPORTED:
+ d.errback(NotImplementedError(msg))
+ else:
+ d.errback(SFTPError(code, nativeString(msg), lang))
+
+
+ def packet_HANDLE(self, data):
+ d, data = self._parseRequest(data)
+ handle, _ = getNS(data)
+ d.callback(handle)
+
+
+ def packet_DATA(self, data):
+ d, data = self._parseRequest(data)
+ d.callback(getNS(data)[0])
+
+
+ def packet_NAME(self, data):
+ d, data = self._parseRequest(data)
+ count, = struct.unpack('!L', data[:4])
+ data = data[4:]
+ files = []
+ for i in range(count):
+ filename, data = getNS(data)
+ longname, data = getNS(data)
+ attrs, data = self._parseAttributes(data)
+ files.append((filename, longname, attrs))
+ d.callback(files)
+
+
+ def packet_ATTRS(self, data):
+ d, data = self._parseRequest(data)
+ d.callback(self._parseAttributes(data)[0])
+
+
+ def packet_EXTENDED_REPLY(self, data):
+ d, data = self._parseRequest(data)
+ d.callback(data)
+
+
+ def gotServerVersion(self, serverVersion, extData):
+ """
+ Called when the client sends their version info.
+
+ @param otherVersion: an integer representing the version of the SFTP
+ protocol they are claiming.
+ @param extData: a dictionary of extended_name : extended_data items.
+ These items are sent by the client to indicate additional features.
+ """
+
+
+
+@implementer(ISFTPFile)
+class ClientFile:
+ def __init__(self, parent, handle):
+ self.parent = parent
+ self.handle = NS(handle)
+
+
+ def close(self):
+ return self.parent._sendRequest(FXP_CLOSE, self.handle)
+
+
+ def readChunk(self, offset, length):
+ data = self.handle + struct.pack("!QL", offset, length)
+ return self.parent._sendRequest(FXP_READ, data)
+
+
+ def writeChunk(self, offset, chunk):
+ data = self.handle + struct.pack("!Q", offset) + NS(chunk)
+ return self.parent._sendRequest(FXP_WRITE, data)
+
+
+ def getAttrs(self):
+ return self.parent._sendRequest(FXP_FSTAT, self.handle)
+
+
+ def setAttrs(self, attrs):
+ data = self.handle + self.parent._packAttributes(attrs)
+ return self.parent._sendRequest(FXP_FSTAT, data)
+
+
+
+class ClientDirectory:
+
+ def __init__(self, parent, handle):
+ self.parent = parent
+ self.handle = NS(handle)
+ self.filesCache = []
+
+
+ def read(self):
+ return self.parent._sendRequest(FXP_READDIR, self.handle)
+
+
+ def close(self):
+ if self.handle is None:
+ return defer.succeed(None)
+ d = self.parent._sendRequest(FXP_CLOSE, self.handle)
+ self.handle = None
+ return d
+
+
+ def __iter__(self):
+ return self
+
+
+ def __next__(self):
+ warnings.warn(
+ ('Using twisted.conch.ssh.filetransfer.ClientDirectory '
+ 'as an iterator was deprecated in Twisted 18.9.0.'),
+ category=DeprecationWarning,
+ stacklevel=2)
+ if self.filesCache:
+ return self.filesCache.pop(0)
+ if self.filesCache is None:
+ raise StopIteration()
+ d = self.read()
+ d.addCallbacks(self._cbReadDir, self._ebReadDir)
+ return d
+
+ next = __next__
+
+
+ def _cbReadDir(self, names):
+ self.filesCache = names[1:]
+ return names[0]
+
+
+ def _ebReadDir(self, reason):
+ reason.trap(EOFError)
+ self.filesCache = None
+ return failure.Failure(StopIteration())
+
+
+
+class SFTPError(Exception):
+
+ def __init__(self, errorCode, errorMessage, lang=''):
+ Exception.__init__(self)
+ self.code = errorCode
+ self._message = errorMessage
+ self.lang = lang
+
+
+ def message(self):
+ """
+ A string received over the network that explains the error to a human.
+ """
+ # Python 2.6 deprecates assigning to the 'message' attribute of an
+ # exception. We define this read-only property here in order to
+ # prevent the warning about deprecation while maintaining backwards
+ # compatibility with object clients that rely on the 'message'
+ # attribute being set correctly. See bug #3897.
+ return self._message
+ message = property(message)
+
+
+ def __str__(self):
+ return 'SFTPError {}: {}'.format(self.code, self.message)
+
+
+
+FXP_INIT = 1
+FXP_VERSION = 2
+FXP_OPEN = 3
+FXP_CLOSE = 4
+FXP_READ = 5
+FXP_WRITE = 6
+FXP_LSTAT = 7
+FXP_FSTAT = 8
+FXP_SETSTAT = 9
+FXP_FSETSTAT = 10
+FXP_OPENDIR = 11
+FXP_READDIR = 12
+FXP_REMOVE = 13
+FXP_MKDIR = 14
+FXP_RMDIR = 15
+FXP_REALPATH = 16
+FXP_STAT = 17
+FXP_RENAME = 18
+FXP_READLINK = 19
+FXP_SYMLINK = 20
+FXP_STATUS = 101
+FXP_HANDLE = 102
+FXP_DATA = 103
+FXP_NAME = 104
+FXP_ATTRS = 105
+FXP_EXTENDED = 200
+FXP_EXTENDED_REPLY = 201
+
+FILEXFER_ATTR_SIZE = 0x00000001
+FILEXFER_ATTR_UIDGID = 0x00000002
+FILEXFER_ATTR_OWNERGROUP = FILEXFER_ATTR_UIDGID
+FILEXFER_ATTR_PERMISSIONS = 0x00000004
+FILEXFER_ATTR_ACMODTIME = 0x00000008
+FILEXFER_ATTR_EXTENDED = 0x80000000
+
+FILEXFER_TYPE_REGULAR = 1
+FILEXFER_TYPE_DIRECTORY = 2
+FILEXFER_TYPE_SYMLINK = 3
+FILEXFER_TYPE_SPECIAL = 4
+FILEXFER_TYPE_UNKNOWN = 5
+
+FXF_READ = 0x00000001
+FXF_WRITE = 0x00000002
+FXF_APPEND = 0x00000004
+FXF_CREAT = 0x00000008
+FXF_TRUNC = 0x00000010
+FXF_EXCL = 0x00000020
+FXF_TEXT = 0x00000040
+
+FX_OK = 0
+FX_EOF = 1
+FX_NO_SUCH_FILE = 2
+FX_PERMISSION_DENIED = 3
+FX_FAILURE = 4
+FX_BAD_MESSAGE = 5
+FX_NO_CONNECTION = 6
+FX_CONNECTION_LOST = 7
+FX_OP_UNSUPPORTED = 8
+FX_FILE_ALREADY_EXISTS = 11
+# http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/ defines more
+# useful error codes, but so far OpenSSH doesn't implement them. We use them
+# internally for clarity, but for now define them all as FX_FAILURE to be
+# compatible with existing software.
+FX_NOT_A_DIRECTORY = FX_FAILURE
+FX_FILE_IS_A_DIRECTORY = FX_FAILURE
+
+
+# initialize FileTransferBase.packetTypes:
+g = globals()
+for name in list(g.keys()):
+ if name.startswith('FXP_'):
+ value = g[name]
+ FileTransferBase.packetTypes[value] = name[4:]
+del g, name, value
diff --git a/contrib/python/Twisted/py2/twisted/conch/ssh/forwarding.py b/contrib/python/Twisted/py2/twisted/conch/ssh/forwarding.py
new file mode 100644
index 0000000000..dd61e75f64
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/ssh/forwarding.py
@@ -0,0 +1,269 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This module contains the implementation of the TCP forwarding, which allows
+clients and servers to forward arbitrary TCP data across the connection.
+
+Maintainer: Paul Swartz
+"""
+
+from __future__ import division, absolute_import
+
+import struct
+
+from twisted.internet import protocol, reactor
+from twisted.internet.endpoints import HostnameEndpoint, connectProtocol
+from twisted.python import log
+from twisted.python.compat import _PY3, unicode
+
+from twisted.conch.ssh import common, channel
+
+class SSHListenForwardingFactory(protocol.Factory):
+ def __init__(self, connection, hostport, klass):
+ self.conn = connection
+ self.hostport = hostport # tuple
+ self.klass = klass
+
+ def buildProtocol(self, addr):
+ channel = self.klass(conn = self.conn)
+ client = SSHForwardingClient(channel)
+ channel.client = client
+ addrTuple = (addr.host, addr.port)
+ channelOpenData = packOpen_direct_tcpip(self.hostport, addrTuple)
+ self.conn.openChannel(channel, channelOpenData)
+ return client
+
+class SSHListenForwardingChannel(channel.SSHChannel):
+
+ def channelOpen(self, specificData):
+ log.msg('opened forwarding channel %s' % self.id)
+ if len(self.client.buf)>1:
+ b = self.client.buf[1:]
+ self.write(b)
+ self.client.buf = b''
+
+ def openFailed(self, reason):
+ self.closed()
+
+ def dataReceived(self, data):
+ self.client.transport.write(data)
+
+ def eofReceived(self):
+ self.client.transport.loseConnection()
+
+ def closed(self):
+ if hasattr(self, 'client'):
+ log.msg('closing local forwarding channel %s' % self.id)
+ self.client.transport.loseConnection()
+ del self.client
+
+class SSHListenClientForwardingChannel(SSHListenForwardingChannel):
+
+ name = b'direct-tcpip'
+
+class SSHListenServerForwardingChannel(SSHListenForwardingChannel):
+
+ name = b'forwarded-tcpip'
+
+
+
+class SSHConnectForwardingChannel(channel.SSHChannel):
+ """
+ Channel used for handling server side forwarding request.
+ It acts as a client for the remote forwarding destination.
+
+ @ivar hostport: C{(host, port)} requested by client as forwarding
+ destination.
+ @type hostport: L{tuple} or a C{sequence}
+
+ @ivar client: Protocol connected to the forwarding destination.
+ @type client: L{protocol.Protocol}
+
+ @ivar clientBuf: Data received while forwarding channel is not yet
+ connected.
+ @type clientBuf: L{bytes}
+
+ @var _reactor: Reactor used for TCP connections.
+ @type _reactor: A reactor.
+
+ @ivar _channelOpenDeferred: Deferred used in testing to check the
+ result of C{channelOpen}.
+ @type _channelOpenDeferred: L{twisted.internet.defer.Deferred}
+ """
+ _reactor = reactor
+
+ def __init__(self, hostport, *args, **kw):
+ channel.SSHChannel.__init__(self, *args, **kw)
+ self.hostport = hostport
+ self.client = None
+ self.clientBuf = b''
+
+
+ def channelOpen(self, specificData):
+ """
+ See: L{channel.SSHChannel}
+ """
+ log.msg("connecting to %s:%i" % self.hostport)
+ ep = HostnameEndpoint(
+ self._reactor, self.hostport[0], self.hostport[1])
+ d = connectProtocol(ep, SSHForwardingClient(self))
+ d.addCallbacks(self._setClient, self._close)
+ self._channelOpenDeferred = d
+
+ def _setClient(self, client):
+ """
+ Called when the connection was established to the forwarding
+ destination.
+
+ @param client: Client protocol connected to the forwarding destination.
+ @type client: L{protocol.Protocol}
+ """
+ self.client = client
+ log.msg("connected to %s:%i" % self.hostport)
+ if self.clientBuf:
+ self.client.transport.write(self.clientBuf)
+ self.clientBuf = None
+ if self.client.buf[1:]:
+ self.write(self.client.buf[1:])
+ self.client.buf = b''
+
+
+ def _close(self, reason):
+ """
+ Called when failed to connect to the forwarding destination.
+
+ @param reason: Reason why connection failed.
+ @type reason: L{twisted.python.failure.Failure}
+ """
+ log.msg("failed to connect: %s" % reason)
+ self.loseConnection()
+
+
+ def dataReceived(self, data):
+ """
+ See: L{channel.SSHChannel}
+ """
+ if self.client:
+ self.client.transport.write(data)
+ else:
+ self.clientBuf += data
+
+
+ def closed(self):
+ """
+ See: L{channel.SSHChannel}
+ """
+ if self.client:
+ log.msg('closed remote forwarding channel %s' % self.id)
+ if self.client.channel:
+ self.loseConnection()
+ self.client.transport.loseConnection()
+ del self.client
+
+
+
+def openConnectForwardingClient(remoteWindow, remoteMaxPacket, data, avatar):
+ remoteHP, origHP = unpackOpen_direct_tcpip(data)
+ return SSHConnectForwardingChannel(remoteHP,
+ remoteWindow=remoteWindow,
+ remoteMaxPacket=remoteMaxPacket,
+ avatar=avatar)
+
+class SSHForwardingClient(protocol.Protocol):
+
+ def __init__(self, channel):
+ self.channel = channel
+ self.buf = b'\000'
+
+ def dataReceived(self, data):
+ if self.buf:
+ self.buf += data
+ else:
+ self.channel.write(data)
+
+ def connectionLost(self, reason):
+ if self.channel:
+ self.channel.loseConnection()
+ self.channel = None
+
+
+def packOpen_direct_tcpip(destination, source):
+ """
+ Pack the data suitable for sending in a CHANNEL_OPEN packet.
+
+ @type destination: L{tuple}
+ @param destination: A tuple of the (host, port) of the destination host.
+
+ @type source: L{tuple}
+ @param source: A tuple of the (host, port) of the source host.
+ """
+ (connHost, connPort) = destination
+ (origHost, origPort) = source
+ if isinstance(connHost, unicode):
+ connHost = connHost.encode("utf-8")
+ if isinstance(origHost, unicode):
+ origHost = origHost.encode("utf-8")
+ conn = common.NS(connHost) + struct.pack('>L', connPort)
+ orig = common.NS(origHost) + struct.pack('>L', origPort)
+ return conn + orig
+
+packOpen_forwarded_tcpip = packOpen_direct_tcpip
+
+def unpackOpen_direct_tcpip(data):
+ """Unpack the data to a usable format.
+ """
+ connHost, rest = common.getNS(data)
+ if _PY3 and isinstance(connHost, bytes):
+ connHost = connHost.decode("utf-8")
+ connPort = int(struct.unpack('>L', rest[:4])[0])
+ origHost, rest = common.getNS(rest[4:])
+ if _PY3 and isinstance(origHost, bytes):
+ origHost = origHost.decode("utf-8")
+ origPort = int(struct.unpack('>L', rest[:4])[0])
+ return (connHost, connPort), (origHost, origPort)
+
+unpackOpen_forwarded_tcpip = unpackOpen_direct_tcpip
+
+
+
+def packGlobal_tcpip_forward(peer):
+ """
+ Pack the data for tcpip forwarding.
+
+ @param peer: A tuple of the (host, port) .
+ @type peer: L{tuple}
+ """
+ (host, port) = peer
+ return common.NS(host) + struct.pack('>L', port)
+
+
+
+def unpackGlobal_tcpip_forward(data):
+ host, rest = common.getNS(data)
+ if _PY3 and isinstance(host, bytes):
+ host = host.decode("utf-8")
+ port = int(struct.unpack('>L', rest[:4])[0])
+ return host, port
+
+"""This is how the data -> eof -> close stuff /should/ work.
+
+debug3: channel 1: waiting for connection
+debug1: channel 1: connected
+debug1: channel 1: read<=0 rfd 7 len 0
+debug1: channel 1: read failed
+debug1: channel 1: close_read
+debug1: channel 1: input open -> drain
+debug1: channel 1: ibuf empty
+debug1: channel 1: send eof
+debug1: channel 1: input drain -> closed
+debug1: channel 1: rcvd eof
+debug1: channel 1: output open -> drain
+debug1: channel 1: obuf empty
+debug1: channel 1: close_write
+debug1: channel 1: output drain -> closed
+debug1: channel 1: rcvd close
+debug3: channel 1: will not send data after close
+debug1: channel 1: send close
+debug1: channel 1: is dead
+"""
diff --git a/contrib/python/Twisted/py2/twisted/conch/ssh/keys.py b/contrib/python/Twisted/py2/twisted/conch/ssh/keys.py
new file mode 100644
index 0000000000..fcbf9d2866
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/ssh/keys.py
@@ -0,0 +1,1678 @@
+# -*- test-case-name: twisted.conch.test.test_keys -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Handling of RSA, DSA, and EC keys.
+"""
+
+from __future__ import absolute_import, division
+
+import binascii
+import itertools
+
+from hashlib import md5, sha256
+import base64
+import struct
+import warnings
+
+import bcrypt
+from cryptography.exceptions import InvalidSignature
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives import hashes, serialization
+from cryptography.hazmat.primitives.asymmetric import dsa, rsa, padding, ec
+from cryptography.hazmat.primitives.serialization import (
+ load_pem_private_key, load_ssh_public_key)
+from cryptography import utils
+
+try:
+
+ from cryptography.hazmat.primitives.asymmetric.utils import (
+ encode_dss_signature, decode_dss_signature)
+except ImportError:
+ from cryptography.hazmat.primitives.asymmetric.utils import (
+ encode_rfc6979_signature as encode_dss_signature,
+ decode_rfc6979_signature as decode_dss_signature)
+from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
+
+from pyasn1.error import PyAsn1Error
+from pyasn1.type import univ
+from pyasn1.codec.ber import decoder as berDecoder
+from pyasn1.codec.ber import encoder as berEncoder
+
+from twisted.conch.ssh import common, sexpy
+from twisted.conch.ssh.common import int_from_bytes, int_to_bytes
+from twisted.python import randbytes
+from twisted.python.compat import (
+ iterbytes, long, izip, nativeString, unicode, _PY3,
+ _b64decodebytes as decodebytes, _b64encodebytes as encodebytes,
+ _bytesChr as chr)
+from twisted.python.constants import NamedConstant, Names
+from twisted.python.deprecate import _mutuallyExclusiveArguments
+
+# Curve lookup table
+_curveTable = {
+ b'ecdsa-sha2-nistp256': ec.SECP256R1(),
+ b'ecdsa-sha2-nistp384': ec.SECP384R1(),
+ b'ecdsa-sha2-nistp521': ec.SECP521R1(),
+}
+
+_secToNist = {
+ b'secp256r1' : b'nistp256',
+ b'secp384r1' : b'nistp384',
+ b'secp521r1' : b'nistp521',
+}
+
+
+
+
+
+class BadKeyError(Exception):
+ """
+ Raised when a key isn't what we expected from it.
+
+ XXX: we really need to check for bad keys
+ """
+
+
+
+class EncryptedKeyError(Exception):
+ """
+ Raised when an encrypted key is presented to fromString/fromFile without
+ a password.
+ """
+
+
+
+class BadFingerPrintFormat(Exception):
+ """
+ Raises when unsupported fingerprint formats are presented to fingerprint.
+ """
+
+
+
+class FingerprintFormats(Names):
+ """
+ Constants representing the supported formats of key fingerprints.
+
+ @cvar MD5_HEX: Named constant representing fingerprint format generated
+ using md5[RFC1321] algorithm in hexadecimal encoding.
+ @type MD5_HEX: L{twisted.python.constants.NamedConstant}
+
+ @cvar SHA256_BASE64: Named constant representing fingerprint format
+ generated using sha256[RFC4634] algorithm in base64 encoding
+ @type SHA256_BASE64: L{twisted.python.constants.NamedConstant}
+ """
+ MD5_HEX = NamedConstant()
+ SHA256_BASE64 = NamedConstant()
+
+
+
+class Key(object):
+ """
+ An object representing a key. A key can be either a public or
+ private key. A public key can verify a signature; a private key can
+ create or verify a signature. To generate a string that can be stored
+ on disk, use the toString method. If you have a private key, but want
+ the string representation of the public key, use Key.public().toString().
+ """
+
+ @classmethod
+ def fromFile(cls, filename, type=None, passphrase=None):
+ """
+ Load a key from a file.
+
+ @param filename: The path to load key data from.
+
+ @type type: L{str} or L{None}
+ @param type: A string describing the format the key data is in, or
+ L{None} to attempt detection of the type.
+
+ @type passphrase: L{bytes} or L{None}
+ @param passphrase: The passphrase the key is encrypted with, or L{None}
+ if there is no encryption.
+
+ @rtype: L{Key}
+ @return: The loaded key.
+ """
+ with open(filename, 'rb') as f:
+ return cls.fromString(f.read(), type, passphrase)
+
+
+ @classmethod
+ def fromString(cls, data, type=None, passphrase=None):
+ """
+ Return a Key object corresponding to the string data.
+ type is optionally the type of string, matching a _fromString_*
+ method. Otherwise, the _guessStringType() classmethod will be used
+ to guess a type. If the key is encrypted, passphrase is used as
+ the decryption key.
+
+ @type data: L{bytes}
+ @param data: The key data.
+
+ @type type: L{str} or L{None}
+ @param type: A string describing the format the key data is in, or
+ L{None} to attempt detection of the type.
+
+ @type passphrase: L{bytes} or L{None}
+ @param passphrase: The passphrase the key is encrypted with, or L{None}
+ if there is no encryption.
+
+ @rtype: L{Key}
+ @return: The loaded key.
+ """
+ if isinstance(data, unicode):
+ data = data.encode("utf-8")
+ if isinstance(passphrase, unicode):
+ passphrase = passphrase.encode("utf-8")
+ if type is None:
+ type = cls._guessStringType(data)
+ if type is None:
+ raise BadKeyError('cannot guess the type of %r' % (data,))
+ method = getattr(cls, '_fromString_%s' % (type.upper(),), None)
+ if method is None:
+ raise BadKeyError('no _fromString method for %s' % (type,))
+ if method.__code__.co_argcount == 2: # No passphrase
+ if passphrase:
+ raise BadKeyError('key not encrypted')
+ return method(data)
+ else:
+ return method(data, passphrase)
+
+
+ @classmethod
+ def _fromString_BLOB(cls, blob):
+ """
+ Return a public key object corresponding to this public key blob.
+ The format of a RSA public key blob is::
+ string 'ssh-rsa'
+ integer e
+ integer n
+
+ The format of a DSA public key blob is::
+ string 'ssh-dss'
+ integer p
+ integer q
+ integer g
+ integer y
+
+ The format of ECDSA-SHA2-* public key blob is::
+ string 'ecdsa-sha2-[identifier]'
+ integer x
+ integer y
+
+ identifier is the standard NIST curve name.
+
+ @type blob: L{bytes}
+ @param blob: The key data.
+
+ @return: A new key.
+ @rtype: L{twisted.conch.ssh.keys.Key}
+ @raises BadKeyError: if the key type (the first string) is unknown.
+ """
+ keyType, rest = common.getNS(blob)
+ if keyType == b'ssh-rsa':
+ e, n, rest = common.getMP(rest, 2)
+ return cls(
+ rsa.RSAPublicNumbers(e, n).public_key(default_backend()))
+ elif keyType == b'ssh-dss':
+ p, q, g, y, rest = common.getMP(rest, 4)
+ return cls(
+ dsa.DSAPublicNumbers(
+ y=y,
+ parameter_numbers=dsa.DSAParameterNumbers(
+ p=p,
+ q=q,
+ g=g
+ )
+ ).public_key(default_backend())
+ )
+ elif keyType in _curveTable:
+ return cls(
+ ec.EllipticCurvePublicKey.from_encoded_point(
+ _curveTable[keyType], common.getNS(rest, 2)[1]
+ )
+ )
+ else:
+ raise BadKeyError('unknown blob type: %s' % (keyType,))
+
+
+ @classmethod
+ def _fromString_PRIVATE_BLOB(cls, blob):
+ """
+ Return a private key object corresponding to this private key blob.
+ The blob formats are as follows:
+
+ RSA keys::
+ string 'ssh-rsa'
+ integer n
+ integer e
+ integer d
+ integer u
+ integer p
+ integer q
+
+ DSA keys::
+ string 'ssh-dss'
+ integer p
+ integer q
+ integer g
+ integer y
+ integer x
+
+ EC keys::
+ string 'ecdsa-sha2-[identifier]'
+ string identifier
+ string q
+ integer privateValue
+
+ identifier is the standard NIST curve name.
+
+
+ @type blob: L{bytes}
+ @param blob: The key data.
+
+ @return: A new key.
+ @rtype: L{twisted.conch.ssh.keys.Key}
+ @raises BadKeyError: if
+ * the key type (the first string) is unknown
+ * the curve name of an ECDSA key does not match the key type
+ """
+ keyType, rest = common.getNS(blob)
+
+ if keyType == b'ssh-rsa':
+ n, e, d, u, p, q, rest = common.getMP(rest, 6)
+ return cls._fromRSAComponents(n=n, e=e, d=d, p=p, q=q)
+ elif keyType == b'ssh-dss':
+ p, q, g, y, x, rest = common.getMP(rest, 5)
+ return cls._fromDSAComponents(y=y, g=g, p=p, q=q, x=x)
+ elif keyType in _curveTable:
+ curve = _curveTable[keyType]
+ curveName, q, rest = common.getNS(rest, 2)
+ if curveName != _secToNist[curve.name.encode('ascii')]:
+ raise BadKeyError('ECDSA curve name %r does not match key '
+ 'type %r' % (curveName, keyType))
+ privateValue, rest = common.getMP(rest)
+ return cls._fromECEncodedPoint(
+ encodedPoint=q, curve=keyType, privateValue=privateValue)
+ else:
+ raise BadKeyError('unknown blob type: %s' % (keyType,))
+
+
+ @classmethod
+ def _fromString_PUBLIC_OPENSSH(cls, data):
+ """
+ Return a public key object corresponding to this OpenSSH public key
+ string. The format of an OpenSSH public key string is::
+ <key type> <base64-encoded public key blob>
+
+ @type data: L{bytes}
+ @param data: The key data.
+
+ @return: A new key.
+ @rtype: L{twisted.conch.ssh.keys.Key}
+ @raises BadKeyError: if the blob type is unknown.
+ """
+ # ECDSA keys don't need base64 decoding which is required
+ # for RSA or DSA key.
+ if data.startswith(b'ecdsa-sha2'):
+ return cls(load_ssh_public_key(data, default_backend()))
+ blob = decodebytes(data.split()[1])
+ return cls._fromString_BLOB(blob)
+
+
+ @classmethod
+ def _fromPrivateOpenSSH_v1(cls, data, passphrase):
+ """
+ Return a private key object corresponding to this OpenSSH private key
+ string, in the "openssh-key-v1" format introduced in OpenSSH 6.5.
+
+ The format of an openssh-key-v1 private key string is::
+ -----BEGIN OPENSSH PRIVATE KEY-----
+ <base64-encoded SSH protocol string>
+ -----END OPENSSH PRIVATE KEY-----
+
+ The SSH protocol string is as described in
+ U{PROTOCOL.key<https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.key>}.
+
+ @type data: L{bytes}
+ @param data: The key data.
+
+ @type passphrase: L{bytes} or L{None}
+ @param passphrase: The passphrase the key is encrypted with, or L{None}
+ if it is not encrypted.
+
+ @return: A new key.
+ @rtype: L{twisted.conch.ssh.keys.Key}
+ @raises BadKeyError: if
+ * a passphrase is provided for an unencrypted key
+ * the SSH protocol encoding is incorrect
+ @raises EncryptedKeyError: if
+ * a passphrase is not provided for an encrypted key
+ """
+ lines = data.strip().splitlines()
+ keyList = decodebytes(b''.join(lines[1:-1]))
+ if not keyList.startswith(b'openssh-key-v1\0'):
+ raise BadKeyError('unknown OpenSSH private key format')
+ keyList = keyList[len(b'openssh-key-v1\0'):]
+ cipher, kdf, kdfOptions, rest = common.getNS(keyList, 3)
+ n = struct.unpack('!L', rest[:4])[0]
+ if n != 1:
+ raise BadKeyError('only OpenSSH private key files containing '
+ 'a single key are supported')
+ # Ignore public key
+ _, encPrivKeyList, _ = common.getNS(rest[4:], 2)
+ if cipher != b'none':
+ if not passphrase:
+ raise EncryptedKeyError('Passphrase must be provided '
+ 'for an encrypted key')
+ # Determine cipher
+ if cipher in (b'aes128-ctr', b'aes192-ctr', b'aes256-ctr'):
+ algorithmClass = algorithms.AES
+ blockSize = 16
+ keySize = int(cipher[3:6]) // 8
+ ivSize = blockSize
+ else:
+ raise BadKeyError('unknown encryption type %r' % (cipher,))
+ if kdf == b'bcrypt':
+ salt, rest = common.getNS(kdfOptions)
+ rounds = struct.unpack('!L', rest[:4])[0]
+ decKey = bcrypt.kdf(
+ passphrase, salt, keySize + ivSize, rounds,
+ # We can only use the number of rounds that OpenSSH used.
+ ignore_few_rounds=True)
+ else:
+ raise BadKeyError('unknown KDF type %r' % (kdf,))
+ if (len(encPrivKeyList) % blockSize) != 0:
+ raise BadKeyError('bad padding')
+ decryptor = Cipher(
+ algorithmClass(decKey[:keySize]),
+ modes.CTR(decKey[keySize:keySize + ivSize]),
+ backend=default_backend()
+ ).decryptor()
+ privKeyList = (
+ decryptor.update(encPrivKeyList) + decryptor.finalize())
+ else:
+ if kdf != b'none':
+ raise BadKeyError('private key specifies KDF %r but no '
+ 'cipher' % (kdf,))
+ privKeyList = encPrivKeyList
+ check1 = struct.unpack('!L', privKeyList[:4])[0]
+ check2 = struct.unpack('!L', privKeyList[4:8])[0]
+ if check1 != check2:
+ raise BadKeyError('check values do not match: %d != %d' %
+ (check1, check2))
+ return cls._fromString_PRIVATE_BLOB(privKeyList[8:])
+
+
+ @classmethod
+ def _fromPrivateOpenSSH_PEM(cls, data, passphrase):
+ """
+ Return a private key object corresponding to this OpenSSH private key
+ string, in the old PEM-based format.
+
+ The format of a PEM-based OpenSSH private key string is::
+ -----BEGIN <key type> PRIVATE KEY-----
+ [Proc-Type: 4,ENCRYPTED
+ DEK-Info: DES-EDE3-CBC,<initialization value>]
+ <base64-encoded ASN.1 structure>
+ ------END <key type> PRIVATE KEY------
+
+ The ASN.1 structure of a RSA key is::
+ (0, n, e, d, p, q)
+
+ The ASN.1 structure of a DSA key is::
+ (0, p, q, g, y, x)
+
+ The ASN.1 structure of a ECDSA key is::
+ (ECParameters, OID, NULL)
+
+ @type data: L{bytes}
+ @param data: The key data.
+
+ @type passphrase: L{bytes} or L{None}
+ @param passphrase: The passphrase the key is encrypted with, or L{None}
+ if it is not encrypted.
+
+ @return: A new key.
+ @rtype: L{twisted.conch.ssh.keys.Key}
+ @raises BadKeyError: if
+ * a passphrase is provided for an unencrypted key
+ * the ASN.1 encoding is incorrect
+ @raises EncryptedKeyError: if
+ * a passphrase is not provided for an encrypted key
+ """
+ lines = data.strip().splitlines()
+ kind = lines[0][11:-17]
+ if lines[1].startswith(b'Proc-Type: 4,ENCRYPTED'):
+ if not passphrase:
+ raise EncryptedKeyError('Passphrase must be provided '
+ 'for an encrypted key')
+
+ # Determine cipher and initialization vector
+ try:
+ _, cipherIVInfo = lines[2].split(b' ', 1)
+ cipher, ivdata = cipherIVInfo.rstrip().split(b',', 1)
+ except ValueError:
+ raise BadKeyError('invalid DEK-info %r' % (lines[2],))
+
+ if cipher in (b'AES-128-CBC', b'AES-256-CBC'):
+ algorithmClass = algorithms.AES
+ keySize = int(cipher.split(b'-')[1]) // 8
+ if len(ivdata) != 32:
+ raise BadKeyError('AES encrypted key with a bad IV')
+ elif cipher == b'DES-EDE3-CBC':
+ algorithmClass = algorithms.TripleDES
+ keySize = 24
+ if len(ivdata) != 16:
+ raise BadKeyError('DES encrypted key with a bad IV')
+ else:
+ raise BadKeyError('unknown encryption type %r' % (cipher,))
+
+ # Extract keyData for decoding
+ iv = bytes(bytearray([int(ivdata[i:i + 2], 16)
+ for i in range(0, len(ivdata), 2)]))
+ ba = md5(passphrase + iv[:8]).digest()
+ bb = md5(ba + passphrase + iv[:8]).digest()
+ decKey = (ba + bb)[:keySize]
+ b64Data = decodebytes(b''.join(lines[3:-1]))
+
+ decryptor = Cipher(
+ algorithmClass(decKey),
+ modes.CBC(iv),
+ backend=default_backend()
+ ).decryptor()
+ keyData = decryptor.update(b64Data) + decryptor.finalize()
+
+ removeLen = ord(keyData[-1:])
+ keyData = keyData[:-removeLen]
+ else:
+ b64Data = b''.join(lines[1:-1])
+ keyData = decodebytes(b64Data)
+
+ try:
+ decodedKey = berDecoder.decode(keyData)[0]
+ except PyAsn1Error as e:
+ raise BadKeyError(
+ 'Failed to decode key (Bad Passphrase?): %s' % (e,))
+
+ if kind == b'EC':
+ return cls(
+ load_pem_private_key(data, passphrase, default_backend()))
+
+ if kind == b'RSA':
+ if len(decodedKey) == 2: # Alternate RSA key
+ decodedKey = decodedKey[0]
+ if len(decodedKey) < 6:
+ raise BadKeyError('RSA key failed to decode properly')
+
+ n, e, d, p, q, dmp1, dmq1, iqmp = [
+ long(value) for value in decodedKey[1:9]
+ ]
+ return cls(
+ rsa.RSAPrivateNumbers(
+ p=p,
+ q=q,
+ d=d,
+ dmp1=dmp1,
+ dmq1=dmq1,
+ iqmp=iqmp,
+ public_numbers=rsa.RSAPublicNumbers(e=e, n=n),
+ ).private_key(default_backend())
+ )
+ elif kind == b'DSA':
+ p, q, g, y, x = [long(value) for value in decodedKey[1: 6]]
+ if len(decodedKey) < 6:
+ raise BadKeyError('DSA key failed to decode properly')
+ return cls(
+ dsa.DSAPrivateNumbers(
+ x=x,
+ public_numbers=dsa.DSAPublicNumbers(
+ y=y,
+ parameter_numbers=dsa.DSAParameterNumbers(
+ p=p,
+ q=q,
+ g=g
+ )
+ )
+ ).private_key(backend=default_backend())
+ )
+ else:
+ raise BadKeyError("unknown key type %s" % (kind,))
+
+
+ @classmethod
+ def _fromString_PRIVATE_OPENSSH(cls, data, passphrase):
+ """
+ Return a private key object corresponding to this OpenSSH private key
+ string. If the key is encrypted, passphrase MUST be provided.
+ Providing a passphrase for an unencrypted key is an error.
+
+ @type data: L{bytes}
+ @param data: The key data.
+
+ @type passphrase: L{bytes} or L{None}
+ @param passphrase: The passphrase the key is encrypted with, or L{None}
+ if it is not encrypted.
+
+ @return: A new key.
+ @rtype: L{twisted.conch.ssh.keys.Key}
+ @raises BadKeyError: if
+ * a passphrase is provided for an unencrypted key
+ * the encoding is incorrect
+ @raises EncryptedKeyError: if
+ * a passphrase is not provided for an encrypted key
+ """
+ if data.strip().splitlines()[0][11:-17] == b'OPENSSH':
+ # New-format (openssh-key-v1) key
+ return cls._fromPrivateOpenSSH_v1(data, passphrase)
+ else:
+ # Old-format (PEM) key
+ return cls._fromPrivateOpenSSH_PEM(data, passphrase)
+
+ @classmethod
+ def _fromString_PUBLIC_LSH(cls, data):
+ """
+ Return a public key corresponding to this LSH public key string.
+ The LSH public key string format is::
+ <s-expression: ('public-key', (<key type>, (<name, <value>)+))>
+
+ The names for a RSA (key type 'rsa-pkcs1-sha1') key are: n, e.
+ The names for a DSA (key type 'dsa') key are: y, g, p, q.
+
+ @type data: L{bytes}
+ @param data: The key data.
+
+ @return: A new key.
+ @rtype: L{twisted.conch.ssh.keys.Key}
+ @raises BadKeyError: if the key type is unknown
+ """
+ sexp = sexpy.parse(decodebytes(data[1:-1]))
+ assert sexp[0] == b'public-key'
+ kd = {}
+ for name, data in sexp[1][1:]:
+ kd[name] = common.getMP(common.NS(data))[0]
+ if sexp[1][0] == b'dsa':
+ return cls._fromDSAComponents(
+ y=kd[b'y'], g=kd[b'g'], p=kd[b'p'], q=kd[b'q'])
+
+ elif sexp[1][0] == b'rsa-pkcs1-sha1':
+ return cls._fromRSAComponents(n=kd[b'n'], e=kd[b'e'])
+ else:
+ raise BadKeyError('unknown lsh key type %s' % (sexp[1][0],))
+
+ @classmethod
+ def _fromString_PRIVATE_LSH(cls, data):
+ """
+ Return a private key corresponding to this LSH private key string.
+ The LSH private key string format is::
+ <s-expression: ('private-key', (<key type>, (<name>, <value>)+))>
+
+ The names for a RSA (key type 'rsa-pkcs1-sha1') key are: n, e, d, p, q.
+ The names for a DSA (key type 'dsa') key are: y, g, p, q, x.
+
+ @type data: L{bytes}
+ @param data: The key data.
+
+ @return: A new key.
+ @rtype: L{twisted.conch.ssh.keys.Key}
+ @raises BadKeyError: if the key type is unknown
+ """
+ sexp = sexpy.parse(data)
+ assert sexp[0] == b'private-key'
+ kd = {}
+ for name, data in sexp[1][1:]:
+ kd[name] = common.getMP(common.NS(data))[0]
+ if sexp[1][0] == b'dsa':
+ assert len(kd) == 5, len(kd)
+ return cls._fromDSAComponents(
+ y=kd[b'y'], g=kd[b'g'], p=kd[b'p'], q=kd[b'q'], x=kd[b'x'])
+ elif sexp[1][0] == b'rsa-pkcs1':
+ assert len(kd) == 8, len(kd)
+ if kd[b'p'] > kd[b'q']: # Make p smaller than q
+ kd[b'p'], kd[b'q'] = kd[b'q'], kd[b'p']
+ return cls._fromRSAComponents(
+ n=kd[b'n'], e=kd[b'e'], d=kd[b'd'], p=kd[b'p'], q=kd[b'q'])
+
+ else:
+ raise BadKeyError('unknown lsh key type %s' % (sexp[1][0],))
+
+ @classmethod
+ def _fromString_AGENTV3(cls, data):
+ """
+ Return a private key object corresponsing to the Secure Shell Key
+ Agent v3 format.
+
+ The SSH Key Agent v3 format for a RSA key is::
+ string 'ssh-rsa'
+ integer e
+ integer d
+ integer n
+ integer u
+ integer p
+ integer q
+
+ The SSH Key Agent v3 format for a DSA key is::
+ string 'ssh-dss'
+ integer p
+ integer q
+ integer g
+ integer y
+ integer x
+
+ @type data: L{bytes}
+ @param data: The key data.
+
+ @return: A new key.
+ @rtype: L{twisted.conch.ssh.keys.Key}
+ @raises BadKeyError: if the key type (the first string) is unknown
+ """
+ keyType, data = common.getNS(data)
+ if keyType == b'ssh-dss':
+ p, data = common.getMP(data)
+ q, data = common.getMP(data)
+ g, data = common.getMP(data)
+ y, data = common.getMP(data)
+ x, data = common.getMP(data)
+ return cls._fromDSAComponents(y=y, g=g, p=p, q=q, x=x)
+ elif keyType == b'ssh-rsa':
+ e, data = common.getMP(data)
+ d, data = common.getMP(data)
+ n, data = common.getMP(data)
+ u, data = common.getMP(data)
+ p, data = common.getMP(data)
+ q, data = common.getMP(data)
+ return cls._fromRSAComponents(n=n, e=e, d=d, p=p, q=q, u=u)
+ else:
+ raise BadKeyError("unknown key type %s" % (keyType,))
+
+ @classmethod
+ def _guessStringType(cls, data):
+ """
+ Guess the type of key in data. The types map to _fromString_*
+ methods.
+
+ @type data: L{bytes}
+ @param data: The key data.
+ """
+ if data.startswith(b'ssh-') or data.startswith(b'ecdsa-sha2-'):
+ return 'public_openssh'
+ elif data.startswith(b'-----BEGIN'):
+ return 'private_openssh'
+ elif data.startswith(b'{'):
+ return 'public_lsh'
+ elif data.startswith(b'('):
+ return 'private_lsh'
+ elif data.startswith(b'\x00\x00\x00\x07ssh-') or data.startswith(b'\x00\x00\x00\x13ecdsa-'):
+ ignored, rest = common.getNS(data)
+ count = 0
+ while rest:
+ count += 1
+ ignored, rest = common.getMP(rest)
+ if count > 4:
+ return 'agentv3'
+ else:
+ return 'blob'
+
+ @classmethod
+ def _fromRSAComponents(cls, n, e, d=None, p=None, q=None, u=None):
+ """
+ Build a key from RSA numerical components.
+
+ @type n: L{int}
+ @param n: The 'n' RSA variable.
+
+ @type e: L{int}
+ @param e: The 'e' RSA variable.
+
+ @type d: L{int} or L{None}
+ @param d: The 'd' RSA variable (optional for a public key).
+
+ @type p: L{int} or L{None}
+ @param p: The 'p' RSA variable (optional for a public key).
+
+ @type q: L{int} or L{None}
+ @param q: The 'q' RSA variable (optional for a public key).
+
+ @type u: L{int} or L{None}
+ @param u: The 'u' RSA variable. Ignored, as its value is determined by
+ p and q.
+
+ @rtype: L{Key}
+ @return: An RSA key constructed from the values as given.
+ """
+ publicNumbers = rsa.RSAPublicNumbers(e=e, n=n)
+ if d is None:
+ # We have public components.
+ keyObject = publicNumbers.public_key(default_backend())
+ else:
+ privateNumbers = rsa.RSAPrivateNumbers(
+ p=p,
+ q=q,
+ d=d,
+ dmp1=rsa.rsa_crt_dmp1(d, p),
+ dmq1=rsa.rsa_crt_dmq1(d, q),
+ iqmp=rsa.rsa_crt_iqmp(p, q),
+ public_numbers=publicNumbers,
+ )
+ keyObject = privateNumbers.private_key(default_backend())
+
+ return cls(keyObject)
+
+ @classmethod
+ def _fromDSAComponents(cls, y, p, q, g, x=None):
+ """
+ Build a key from DSA numerical components.
+
+ @type y: L{int}
+ @param y: The 'y' DSA variable.
+
+ @type p: L{int}
+ @param p: The 'p' DSA variable.
+
+ @type q: L{int}
+ @param q: The 'q' DSA variable.
+
+ @type g: L{int}
+ @param g: The 'g' DSA variable.
+
+ @type x: L{int} or L{None}
+ @param x: The 'x' DSA variable (optional for a public key)
+
+ @rtype: L{Key}
+ @return: A DSA key constructed from the values as given.
+ """
+ publicNumbers = dsa.DSAPublicNumbers(
+ y=y, parameter_numbers=dsa.DSAParameterNumbers(p=p, q=q, g=g))
+ if x is None:
+ # We have public components.
+ keyObject = publicNumbers.public_key(default_backend())
+ else:
+ privateNumbers = dsa.DSAPrivateNumbers(
+ x=x, public_numbers=publicNumbers)
+ keyObject = privateNumbers.private_key(default_backend())
+
+ return cls(keyObject)
+
+ @classmethod
+ def _fromECComponents(cls, x, y, curve, privateValue=None):
+ """
+ Build a key from EC components.
+
+ @param x: The affine x component of the public point used for verifying.
+ @type x: L{int}
+
+ @param y: The affine y component of the public point used for verifying.
+ @type y: L{int}
+
+ @param curve: NIST name of elliptic curve.
+ @type curve: L{bytes}
+
+ @param privateValue: The private value.
+ @type privateValue: L{int}
+ """
+
+ publicNumbers = ec.EllipticCurvePublicNumbers(
+ x=x, y=y, curve=_curveTable[curve])
+ if privateValue is None:
+ # We have public components.
+ keyObject = publicNumbers.public_key(default_backend())
+ else:
+ privateNumbers = ec.EllipticCurvePrivateNumbers(
+ private_value=privateValue, public_numbers=publicNumbers)
+ keyObject = privateNumbers.private_key(default_backend())
+
+ return cls(keyObject)
+
+ @classmethod
+ def _fromECEncodedPoint(cls, encodedPoint, curve, privateValue=None):
+ """
+ Build a key from an EC encoded point.
+
+ @param encodedPoint: The public point encoded as in SEC 1 v2.0
+ section 2.3.3.
+ @type encodedPoint: L{bytes}
+
+ @param curve: NIST name of elliptic curve.
+ @type curve: L{bytes}
+
+ @param privateValue: The private value.
+ @type privateValue: L{int}
+ """
+
+ if privateValue is None:
+ # We have public components.
+ keyObject = ec.EllipticCurvePublicKey.from_encoded_point(
+ _curveTable[curve], encodedPoint
+ )
+ else:
+ keyObject = ec.derive_private_key(
+ privateValue, _curveTable[curve], default_backend()
+ )
+
+ return cls(keyObject)
+
+ def __init__(self, keyObject):
+ """
+ Initialize with a private or public
+ C{cryptography.hazmat.primitives.asymmetric} key.
+
+ @param keyObject: Low level key.
+ @type keyObject: C{cryptography.hazmat.primitives.asymmetric} key.
+ """
+ self._keyObject = keyObject
+
+ def __eq__(self, other):
+ """
+ Return True if other represents an object with the same key.
+ """
+ if type(self) == type(other):
+ return self.type() == other.type() and self.data() == other.data()
+ else:
+ return NotImplemented
+
+ def __ne__(self, other):
+ """
+ Return True if other represents anything other than this key.
+ """
+ result = self.__eq__(other)
+ if result == NotImplemented:
+ return result
+ return not result
+
+ def __repr__(self):
+ """
+ Return a pretty representation of this object.
+ """
+ if self.type() == 'EC':
+ data = self.data()
+ name = data['curve'].decode('utf-8')
+
+ if self.isPublic():
+ out = '<Elliptic Curve Public Key (%s bits)' % (name[-3:],)
+ else:
+ out = '<Elliptic Curve Private Key (%s bits)' % (name[-3:],)
+
+ for k, v in sorted(data.items()):
+ if _PY3 and k == 'curve':
+ out += "\ncurve:\n\t%s" % (name,)
+ else:
+ out += "\n%s:\n\t%s" % (k, v)
+
+ return out + ">\n"
+ else:
+ lines = [
+ '<%s %s (%s bits)' % (
+ nativeString(self.type()),
+ self.isPublic() and 'Public Key' or 'Private Key',
+ self._keyObject.key_size)]
+ for k, v in sorted(self.data().items()):
+ lines.append('attr %s:' % (k,))
+ by = common.MP(v)[4:]
+ while by:
+ m = by[:15]
+ by = by[15:]
+ o = ''
+ for c in iterbytes(m):
+ o = o + '%02x:' % (ord(c),)
+ if len(m) < 15:
+ o = o[:-1]
+ lines.append('\t' + o)
+ lines[-1] = lines[-1] + '>'
+ return '\n'.join(lines)
+
+ def isPublic(self):
+ """
+ Check if this instance is a public key.
+
+ @return: C{True} if this is a public key.
+ """
+ return isinstance(
+ self._keyObject,
+ (rsa.RSAPublicKey, dsa.DSAPublicKey, ec.EllipticCurvePublicKey))
+
+ def public(self):
+ """
+ Returns a version of this key containing only the public key data.
+ If this is a public key, this may or may not be the same object
+ as self.
+
+ @rtype: L{Key}
+ @return: A public key.
+ """
+ if self.isPublic():
+ return self
+ else:
+ return Key(self._keyObject.public_key())
+
+ def fingerprint(self, format=FingerprintFormats.MD5_HEX):
+ """
+ The fingerprint of a public key consists of the output of the
+ message-digest algorithm in the specified format.
+ Supported formats include L{FingerprintFormats.MD5_HEX} and
+ L{FingerprintFormats.SHA256_BASE64}
+
+ The input to the algorithm is the public key data as specified by [RFC4253].
+
+ The output of sha256[RFC4634] algorithm is presented to the
+ user in the form of base64 encoded sha256 hashes.
+ Example: C{US5jTUa0kgX5ZxdqaGF0yGRu8EgKXHNmoT8jHKo1StM=}
+
+ The output of the MD5[RFC1321](default) algorithm is presented to the user as
+ a sequence of 16 octets printed as hexadecimal with lowercase letters
+ and separated by colons.
+ Example: C{c1:b1:30:29:d7:b8:de:6c:97:77:10:d7:46:41:63:87}
+
+ @param format: Format for fingerprint generation. Consists
+ hash function and representation format.
+ Default is L{FingerprintFormats.MD5_HEX}
+
+ @since: 8.2
+
+ @return: the user presentation of this L{Key}'s fingerprint, as a
+ string.
+
+ @rtype: L{str}
+ """
+ if format is FingerprintFormats.SHA256_BASE64:
+ return nativeString(base64.b64encode(
+ sha256(self.blob()).digest()))
+ elif format is FingerprintFormats.MD5_HEX:
+ return nativeString(
+ b':'.join([binascii.hexlify(x)
+ for x in iterbytes(md5(self.blob()).digest())]))
+ else:
+ raise BadFingerPrintFormat(
+ 'Unsupported fingerprint format: %s' % (format,))
+
+ def type(self):
+ """
+ Return the type of the object we wrap. Currently this can only be
+ 'RSA', 'DSA', or 'EC'.
+
+ @rtype: L{str}
+ @raises RuntimeError: If the object type is unknown.
+ """
+ if isinstance(
+ self._keyObject, (rsa.RSAPublicKey, rsa.RSAPrivateKey)):
+ return 'RSA'
+ elif isinstance(
+ self._keyObject, (dsa.DSAPublicKey, dsa.DSAPrivateKey)):
+ return 'DSA'
+ elif isinstance(
+ self._keyObject, (ec.EllipticCurvePublicKey, ec.EllipticCurvePrivateKey)):
+ return 'EC'
+ else:
+ raise RuntimeError(
+ 'unknown type of object: %r' % (self._keyObject,))
+
+ def sshType(self):
+ """
+ Get the type of the object we wrap as defined in the SSH protocol,
+ defined in RFC 4253, Section 6.6. Currently this can only be b'ssh-rsa',
+ b'ssh-dss' or b'ecdsa-sha2-[identifier]'.
+
+ identifier is the standard NIST curve name
+
+ @return: The key type format.
+ @rtype: L{bytes}
+ """
+ if self.type() == 'EC':
+ return b'ecdsa-sha2-' + _secToNist[self._keyObject.curve.name.encode('ascii')]
+ else:
+ return {'RSA': b'ssh-rsa', 'DSA': b'ssh-dss'}[self.type()]
+
+ def size(self):
+ """
+ Return the size of the object we wrap.
+
+ @return: The size of the key.
+ @rtype: L{int}
+ """
+ if self._keyObject is None:
+ return 0
+ elif self.type() == 'EC':
+ return self._keyObject.curve.key_size
+ return self._keyObject.key_size
+
+ def data(self):
+ """
+ Return the values of the public key as a dictionary.
+
+ @rtype: L{dict}
+ """
+ if isinstance(self._keyObject, rsa.RSAPublicKey):
+ numbers = self._keyObject.public_numbers()
+ return {
+ "n": numbers.n,
+ "e": numbers.e,
+ }
+ elif isinstance(self._keyObject, rsa.RSAPrivateKey):
+ numbers = self._keyObject.private_numbers()
+ return {
+ "n": numbers.public_numbers.n,
+ "e": numbers.public_numbers.e,
+ "d": numbers.d,
+ "p": numbers.p,
+ "q": numbers.q,
+ # Use a trick: iqmp is q^-1 % p, u is p^-1 % q
+ "u": rsa.rsa_crt_iqmp(numbers.q, numbers.p),
+ }
+ elif isinstance(self._keyObject, dsa.DSAPublicKey):
+ numbers = self._keyObject.public_numbers()
+ return {
+ "y": numbers.y,
+ "g": numbers.parameter_numbers.g,
+ "p": numbers.parameter_numbers.p,
+ "q": numbers.parameter_numbers.q,
+ }
+ elif isinstance(self._keyObject, dsa.DSAPrivateKey):
+ numbers = self._keyObject.private_numbers()
+ return {
+ "x": numbers.x,
+ "y": numbers.public_numbers.y,
+ "g": numbers.public_numbers.parameter_numbers.g,
+ "p": numbers.public_numbers.parameter_numbers.p,
+ "q": numbers.public_numbers.parameter_numbers.q,
+ }
+ elif isinstance(self._keyObject, ec.EllipticCurvePublicKey):
+ numbers = self._keyObject.public_numbers()
+ return {
+ "x": numbers.x,
+ "y": numbers.y,
+ "curve": self.sshType(),
+ }
+ elif isinstance(self._keyObject, ec.EllipticCurvePrivateKey):
+ numbers = self._keyObject.private_numbers()
+ return {
+ "x": numbers.public_numbers.x,
+ "y": numbers.public_numbers.y,
+ "privateValue": numbers.private_value,
+ "curve": self.sshType(),
+ }
+
+ else:
+ raise RuntimeError("Unexpected key type: %s" % (self._keyObject,))
+
+ def blob(self):
+ """
+ Return the public key blob for this key. The blob is the
+ over-the-wire format for public keys.
+
+ SECSH-TRANS RFC 4253 Section 6.6.
+
+ RSA keys::
+ string 'ssh-rsa'
+ integer e
+ integer n
+
+ DSA keys::
+ string 'ssh-dss'
+ integer p
+ integer q
+ integer g
+ integer y
+
+ EC keys::
+ string 'ecdsa-sha2-[identifier]'
+ integer x
+ integer y
+
+ identifier is the standard NIST curve name
+
+ @rtype: L{bytes}
+ """
+ type = self.type()
+ data = self.data()
+ if type == 'RSA':
+ return (common.NS(b'ssh-rsa') + common.MP(data['e']) +
+ common.MP(data['n']))
+ elif type == 'DSA':
+ return (common.NS(b'ssh-dss') + common.MP(data['p']) +
+ common.MP(data['q']) + common.MP(data['g']) +
+ common.MP(data['y']))
+ else: # EC
+ byteLength = (self._keyObject.curve.key_size + 7) // 8
+ return (common.NS(data['curve']) + common.NS(data["curve"][-8:]) +
+ common.NS(b'\x04' + utils.int_to_bytes(data['x'], byteLength) +
+ utils.int_to_bytes(data['y'], byteLength)))
+
+
+ def privateBlob(self):
+ """
+ Return the private key blob for this key. The blob is the
+ over-the-wire format for private keys:
+
+ Specification in OpenSSH PROTOCOL.agent
+
+ RSA keys::
+ string 'ssh-rsa'
+ integer n
+ integer e
+ integer d
+ integer u
+ integer p
+ integer q
+
+ DSA keys::
+ string 'ssh-dss'
+ integer p
+ integer q
+ integer g
+ integer y
+ integer x
+
+ EC keys::
+ string 'ecdsa-sha2-[identifier]'
+ integer x
+ integer y
+ integer privateValue
+
+ identifier is the NIST standard curve name.
+ """
+ type = self.type()
+ data = self.data()
+ if type == 'RSA':
+ iqmp = rsa.rsa_crt_iqmp(data['p'], data['q'])
+ return (common.NS(b'ssh-rsa') + common.MP(data['n']) +
+ common.MP(data['e']) + common.MP(data['d']) +
+ common.MP(iqmp) + common.MP(data['p']) +
+ common.MP(data['q']))
+ elif type == 'DSA':
+ return (common.NS(b'ssh-dss') + common.MP(data['p']) +
+ common.MP(data['q']) + common.MP(data['g']) +
+ common.MP(data['y']) + common.MP(data['x']))
+ else: # EC
+ encPub = self._keyObject.public_key().public_bytes(
+ serialization.Encoding.X962,
+ serialization.PublicFormat.UncompressedPoint
+ )
+ return (common.NS(data['curve']) + common.NS(data['curve'][-8:]) +
+ common.NS(encPub) + common.MP(data['privateValue']))
+
+ @_mutuallyExclusiveArguments([
+ ['extra', 'comment'],
+ ['extra', 'passphrase'],
+ ])
+ def toString(self, type, extra=None, subtype=None, comment=None,
+ passphrase=None):
+ """
+ Create a string representation of this key. If the key is a private
+ key and you want the representation of its public key, use
+ C{key.public().toString()}. type maps to a _toString_* method.
+
+ @param type: The type of string to emit. Currently supported values
+ are C{'OPENSSH'}, C{'LSH'}, and C{'AGENTV3'}.
+ @type type: L{str}
+
+ @param extra: Any extra data supported by the selected format which
+ is not part of the key itself. For public OpenSSH keys, this is
+ a comment. For private OpenSSH keys, this is a passphrase to
+ encrypt with. (Deprecated since Twisted 20.3.0; use C{comment}
+ or C{passphrase} as appropriate instead.)
+ @type extra: L{bytes} or L{unicode} or L{None}
+
+ @param subtype: A subtype of the requested C{type} to emit. Only
+ supported for private OpenSSH keys, for which the currently
+ supported subtypes are C{'PEM'} and C{'v1'}. If not given, an
+ appropriate default is used.
+ @type subtype: L{str} or L{None}
+
+ @param comment: A comment to include with the key. Only supported
+ for OpenSSH keys.
+
+ Present since Twisted 20.3.0.
+
+ @type comment: L{bytes} or L{unicode} or L{None}
+
+ @param passphrase: A passphrase to encrypt the key with. Only
+ supported for private OpenSSH keys.
+
+ Present since Twisted 20.3.0.
+
+ @type passphrase: L{bytes} or L{unicode} or L{None}
+
+ @rtype: L{bytes}
+ """
+ if extra is not None:
+ # Compatibility with old parameter format.
+ warnings.warn(
+ "The 'extra' argument to "
+ "twisted.conch.ssh.keys.Key.toString was deprecated in "
+ "Twisted 20.3.0; use 'comment' or 'passphrase' instead.",
+ DeprecationWarning, stacklevel=3)
+ if self.isPublic():
+ comment = extra
+ else:
+ passphrase = extra
+ if isinstance(comment, unicode):
+ comment = comment.encode("utf-8")
+ if isinstance(passphrase, unicode):
+ passphrase = passphrase.encode("utf-8")
+ method = getattr(self, '_toString_%s' % (type.upper(),), None)
+ if method is None:
+ raise BadKeyError('unknown key type: %s' % (type,))
+ return method(subtype=subtype, comment=comment, passphrase=passphrase)
+
+ def _toPublicOpenSSH(self, comment=None):
+ """
+ Return a public OpenSSH key string.
+
+ See _fromString_PUBLIC_OPENSSH for the string format.
+
+ @type comment: L{bytes} or L{None}
+ @param comment: A comment to include with the key, or L{None} to
+ omit the comment.
+ """
+ if self.type() == 'EC':
+ if not comment:
+ comment = b''
+ return (self._keyObject.public_bytes(
+ serialization.Encoding.OpenSSH,
+ serialization.PublicFormat.OpenSSH
+ ) + b' ' + comment).strip()
+
+ b64Data = encodebytes(self.blob()).replace(b'\n', b'')
+ if not comment:
+ comment = b''
+ return (self.sshType() + b' ' + b64Data + b' ' + comment).strip()
+
+ def _toPrivateOpenSSH_v1(self, comment=None, passphrase=None):
+ """
+ Return a private OpenSSH key string, in the "openssh-key-v1" format
+ introduced in OpenSSH 6.5.
+
+ See _fromPrivateOpenSSH_v1 for the string format.
+
+ @type passphrase: L{bytes} or L{None}
+ @param passphrase: The passphrase to encrypt the key with, or L{None}
+ if it is not encrypted.
+ """
+ if passphrase:
+ # For now we just hardcode the cipher to the one used by
+ # OpenSSH. We could make this configurable later if it's
+ # needed.
+ cipher = algorithms.AES
+ cipherName = b'aes256-ctr'
+ kdfName = b'bcrypt'
+ blockSize = cipher.block_size // 8
+ keySize = 32
+ ivSize = blockSize
+ salt = randbytes.secureRandom(ivSize)
+ rounds = 100
+ kdfOptions = common.NS(salt) + struct.pack('!L', rounds)
+ else:
+ cipherName = b'none'
+ kdfName = b'none'
+ blockSize = 8
+ kdfOptions = b''
+ check = randbytes.secureRandom(4)
+ privKeyList = (
+ check + check + self.privateBlob() + common.NS(comment or b''))
+ padByte = 0
+ while len(privKeyList) % blockSize:
+ padByte += 1
+ privKeyList += chr(padByte & 0xFF)
+ if passphrase:
+ encKey = bcrypt.kdf(passphrase, salt, keySize + ivSize, 100)
+ encryptor = Cipher(
+ cipher(encKey[:keySize]),
+ modes.CTR(encKey[keySize:keySize + ivSize]),
+ backend=default_backend()
+ ).encryptor()
+ encPrivKeyList = (
+ encryptor.update(privKeyList) + encryptor.finalize())
+ else:
+ encPrivKeyList = privKeyList
+ blob = (
+ b'openssh-key-v1\0' +
+ common.NS(cipherName) +
+ common.NS(kdfName) + common.NS(kdfOptions) +
+ struct.pack('!L', 1) +
+ common.NS(self.blob()) +
+ common.NS(encPrivKeyList))
+ b64Data = encodebytes(blob).replace(b'\n', b'')
+ lines = (
+ [b'-----BEGIN OPENSSH PRIVATE KEY-----'] +
+ [b64Data[i:i + 64] for i in range(0, len(b64Data), 64)] +
+ [b'-----END OPENSSH PRIVATE KEY-----'])
+ return b'\n'.join(lines) + b'\n'
+
+ def _toPrivateOpenSSH_PEM(self, passphrase=None):
+ """
+ Return a private OpenSSH key string, in the old PEM-based format.
+
+ See _fromPrivateOpenSSH_PEM for the string format.
+
+ @type passphrase: L{bytes} or L{None}
+ @param passphrase: The passphrase to encrypt the key with, or L{None}
+ if it is not encrypted.
+ """
+ if self.type() == 'EC':
+ # EC keys has complex ASN.1 structure hence we do this this way.
+ if not passphrase:
+ # unencrypted private key
+ encryptor = serialization.NoEncryption()
+ else:
+ encryptor = serialization.BestAvailableEncryption(passphrase)
+
+ return self._keyObject.private_bytes(
+ serialization.Encoding.PEM,
+ serialization.PrivateFormat.TraditionalOpenSSL,
+ encryptor)
+
+ data = self.data()
+ lines = [b''.join((b'-----BEGIN ', self.type().encode('ascii'),
+ b' PRIVATE KEY-----'))]
+ if self.type() == 'RSA':
+ p, q = data['p'], data['q']
+ iqmp = rsa.rsa_crt_iqmp(p, q)
+ objData = (0, data['n'], data['e'], data['d'], p, q,
+ data['d'] % (p - 1), data['d'] % (q - 1),
+ iqmp)
+ else:
+ objData = (0, data['p'], data['q'], data['g'], data['y'],
+ data['x'])
+ asn1Sequence = univ.Sequence()
+ for index, value in izip(itertools.count(), objData):
+ asn1Sequence.setComponentByPosition(index, univ.Integer(value))
+ asn1Data = berEncoder.encode(asn1Sequence)
+ if passphrase:
+ iv = randbytes.secureRandom(8)
+ hexiv = ''.join(['%02X' % (ord(x),) for x in iterbytes(iv)])
+ hexiv = hexiv.encode('ascii')
+ lines.append(b'Proc-Type: 4,ENCRYPTED')
+ lines.append(b'DEK-Info: DES-EDE3-CBC,' + hexiv + b'\n')
+ ba = md5(passphrase + iv).digest()
+ bb = md5(ba + passphrase + iv).digest()
+ encKey = (ba + bb)[:24]
+ padLen = 8 - (len(asn1Data) % 8)
+ asn1Data += chr(padLen) * padLen
+
+ encryptor = Cipher(
+ algorithms.TripleDES(encKey),
+ modes.CBC(iv),
+ backend=default_backend()
+ ).encryptor()
+
+ asn1Data = encryptor.update(asn1Data) + encryptor.finalize()
+
+ b64Data = encodebytes(asn1Data).replace(b'\n', b'')
+ lines += [b64Data[i:i + 64] for i in range(0, len(b64Data), 64)]
+ lines.append(b''.join((b'-----END ', self.type().encode('ascii'),
+ b' PRIVATE KEY-----')))
+ return b'\n'.join(lines)
+
+ def _toString_OPENSSH(self, subtype=None, comment=None, passphrase=None):
+ """
+ Return a public or private OpenSSH string. See
+ _fromString_PUBLIC_OPENSSH and _fromPrivateOpenSSH_PEM for the
+ string formats. If extra is present, it represents a comment for a
+ public key, or a passphrase for a private key.
+
+ @param extra: Comment for a public key or passphrase for a
+ private key
+ @type extra: L{bytes}
+
+ @rtype: L{bytes}
+ """
+ if self.isPublic():
+ return self._toPublicOpenSSH(comment=comment)
+ elif subtype is None or subtype == 'PEM':
+ return self._toPrivateOpenSSH_PEM(passphrase=passphrase)
+ elif subtype == 'v1':
+ return self._toPrivateOpenSSH_v1(
+ comment=comment, passphrase=passphrase)
+ else:
+ raise ValueError('unknown subtype %s' % (subtype,))
+
+ def _toString_LSH(self, **kwargs):
+ """
+ Return a public or private LSH key. See _fromString_PUBLIC_LSH and
+ _fromString_PRIVATE_LSH for the key formats.
+
+ @rtype: L{bytes}
+ """
+ data = self.data()
+ type = self.type()
+ if self.isPublic():
+ if type == 'RSA':
+ keyData = sexpy.pack([[b'public-key',
+ [b'rsa-pkcs1-sha1',
+ [b'n', common.MP(data['n'])[4:]],
+ [b'e', common.MP(data['e'])[4:]]]]])
+ elif type == 'DSA':
+ keyData = sexpy.pack([[b'public-key',
+ [b'dsa',
+ [b'p', common.MP(data['p'])[4:]],
+ [b'q', common.MP(data['q'])[4:]],
+ [b'g', common.MP(data['g'])[4:]],
+ [b'y', common.MP(data['y'])[4:]]]]])
+ else:
+ raise BadKeyError("unknown key type %s" % (type,))
+ return (b'{' + encodebytes(keyData).replace(b'\n', b'') +
+ b'}')
+ else:
+ if type == 'RSA':
+ p, q = data['p'], data['q']
+ iqmp = rsa.rsa_crt_iqmp(p, q)
+ return sexpy.pack([[b'private-key',
+ [b'rsa-pkcs1',
+ [b'n', common.MP(data['n'])[4:]],
+ [b'e', common.MP(data['e'])[4:]],
+ [b'd', common.MP(data['d'])[4:]],
+ [b'p', common.MP(q)[4:]],
+ [b'q', common.MP(p)[4:]],
+ [b'a', common.MP(
+ data['d'] % (q - 1))[4:]],
+ [b'b', common.MP(
+ data['d'] % (p - 1))[4:]],
+ [b'c', common.MP(iqmp)[4:]]]]])
+ elif type == 'DSA':
+ return sexpy.pack([[b'private-key',
+ [b'dsa',
+ [b'p', common.MP(data['p'])[4:]],
+ [b'q', common.MP(data['q'])[4:]],
+ [b'g', common.MP(data['g'])[4:]],
+ [b'y', common.MP(data['y'])[4:]],
+ [b'x', common.MP(data['x'])[4:]]]]])
+ else:
+ raise BadKeyError("unknown key type %s'" % (type,))
+
+ def _toString_AGENTV3(self, **kwargs):
+ """
+ Return a private Secure Shell Agent v3 key. See
+ _fromString_AGENTV3 for the key format.
+
+ @rtype: L{bytes}
+ """
+ data = self.data()
+ if not self.isPublic():
+ if self.type() == 'RSA':
+ values = (data['e'], data['d'], data['n'], data['u'],
+ data['p'], data['q'])
+ elif self.type() == 'DSA':
+ values = (data['p'], data['q'], data['g'], data['y'],
+ data['x'])
+ return common.NS(self.sshType()) + b''.join(map(common.MP, values))
+
+ def sign(self, data):
+ """
+ Sign some data with this key.
+
+ SECSH-TRANS RFC 4253 Section 6.6.
+
+ @type data: L{bytes}
+ @param data: The data to sign.
+
+ @rtype: L{bytes}
+ @return: A signature for the given data.
+ """
+ keyType = self.type()
+ if keyType == 'RSA':
+ sig = self._keyObject.sign(data, padding.PKCS1v15(), hashes.SHA1())
+ ret = common.NS(sig)
+
+ elif keyType == 'DSA':
+ sig = self._keyObject.sign(data, hashes.SHA1())
+ (r, s) = decode_dss_signature(sig)
+ # SSH insists that the DSS signature blob be two 160-bit integers
+ # concatenated together. The sig[0], [1] numbers from obj.sign
+ # are just numbers, and could be any length from 0 to 160 bits.
+ # Make sure they are padded out to 160 bits (20 bytes each)
+ ret = common.NS(int_to_bytes(r, 20) + int_to_bytes(s, 20))
+
+ elif keyType == 'EC': # Pragma: no branch
+ # Hash size depends on key size
+ keySize = self.size()
+ if keySize <= 256:
+ hashSize = hashes.SHA256()
+ elif keySize <= 384:
+ hashSize = hashes.SHA384()
+ else:
+ hashSize = hashes.SHA512()
+ signature = self._keyObject.sign(data, ec.ECDSA(hashSize))
+ (r, s) = decode_dss_signature(signature)
+
+ rb = int_to_bytes(r)
+ sb = int_to_bytes(s)
+
+ # Int_to_bytes returns rb[0] as a str in python2
+ # and an as int in python3
+ if type(rb[0]) is str:
+ rcomp = ord(rb[0])
+ else:
+ rcomp = rb[0]
+
+ # If the MSB is set, prepend a null byte for correct formatting.
+ if rcomp & 0x80:
+ rb = b"\x00" + rb
+
+ if type(sb[0]) is str:
+ scomp = ord(sb[0])
+ else:
+ scomp = sb[0]
+
+ if scomp & 0x80:
+ sb = b"\x00" + sb
+
+ ret = common.NS(common.NS(rb) + common.NS(sb))
+ return common.NS(self.sshType()) + ret
+
+ def verify(self, signature, data):
+ """
+ Verify a signature using this key.
+
+ @type signature: L{bytes}
+ @param signature: The signature to verify.
+
+ @type data: L{bytes}
+ @param data: The signed data.
+
+ @rtype: L{bool}
+ @return: C{True} if the signature is valid.
+ """
+ if len(signature) == 40:
+ # DSA key with no padding
+ signatureType, signature = b'ssh-dss', common.NS(signature)
+ else:
+ signatureType, signature = common.getNS(signature)
+
+ if signatureType != self.sshType():
+ return False
+
+ keyType = self.type()
+ if keyType == 'RSA':
+ k = self._keyObject
+ if not self.isPublic():
+ k = k.public_key()
+ args = (
+ common.getNS(signature)[0],
+ data,
+ padding.PKCS1v15(),
+ hashes.SHA1(),
+ )
+ elif keyType == 'DSA':
+ concatenatedSignature = common.getNS(signature)[0]
+ r = int_from_bytes(concatenatedSignature[:20], 'big')
+ s = int_from_bytes(concatenatedSignature[20:], 'big')
+ signature = encode_dss_signature(r, s)
+ k = self._keyObject
+ if not self.isPublic():
+ k = k.public_key()
+ args = (signature, data, hashes.SHA1())
+
+ elif keyType == 'EC': # Pragma: no branch
+ concatenatedSignature = common.getNS(signature)[0]
+ rstr, sstr, rest = common.getNS(concatenatedSignature, 2)
+ r = int_from_bytes(rstr, 'big')
+ s = int_from_bytes(sstr, 'big')
+ signature = encode_dss_signature(r, s)
+
+ k = self._keyObject
+ if not self.isPublic():
+ k = k.public_key()
+
+ keySize = self.size()
+ if keySize <= 256: # Hash size depends on key size
+ hashSize = hashes.SHA256()
+ elif keySize <= 384:
+ hashSize = hashes.SHA384()
+ else:
+ hashSize = hashes.SHA512()
+ args = (signature, data, ec.ECDSA(hashSize))
+
+ try:
+ k.verify(*args)
+ except InvalidSignature:
+ return False
+ else:
+ return True
+
+
+def _getPersistentRSAKey(location, keySize=4096):
+ """
+ This function returns a persistent L{Key}.
+
+ The key is loaded from a PEM file in C{location}. If it does not exist, a
+ key with the key size of C{keySize} is generated and saved.
+
+ @param location: Where the key is stored.
+ @type location: L{twisted.python.filepath.FilePath}
+
+ @param keySize: The size of the key, if it needs to be generated.
+ @type keySize: L{int}
+
+ @returns: A persistent key.
+ @rtype: L{Key}
+ """
+ location.parent().makedirs(ignoreExistingDirectory=True)
+
+ # If it doesn't exist, we want to generate a new key and save it
+ if not location.exists():
+ privateKey = rsa.generate_private_key(
+ public_exponent=65537,
+ key_size=keySize,
+ backend=default_backend()
+ )
+
+ pem = privateKey.private_bytes(
+ encoding=serialization.Encoding.PEM,
+ format=serialization.PrivateFormat.TraditionalOpenSSL,
+ encryption_algorithm=serialization.NoEncryption()
+ )
+
+ location.setContent(pem)
+
+ # By this point (save any hilarious race conditions) we should have a
+ # working PEM file. Load it!
+ # (Future archaeological readers: I chose not to short circuit above,
+ # because then there's two exit paths to this code!)
+ with location.open("rb") as keyFile:
+ privateKey = serialization.load_pem_private_key(
+ keyFile.read(),
+ password=None,
+ backend=default_backend()
+ )
+ return Key(privateKey)
diff --git a/contrib/python/Twisted/py2/twisted/conch/ssh/service.py b/contrib/python/Twisted/py2/twisted/conch/ssh/service.py
new file mode 100644
index 0000000000..94a34cce9b
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/ssh/service.py
@@ -0,0 +1,48 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+The parent class for all the SSH services. Currently implemented services
+are ssh-userauth and ssh-connection.
+
+Maintainer: Paul Swartz
+"""
+
+from __future__ import division, absolute_import
+
+from twisted.python import log
+
+class SSHService(log.Logger):
+ name = None # this is the ssh name for the service
+ protocolMessages = {} # these map #'s -> protocol names
+ transport = None # gets set later
+
+ def serviceStarted(self):
+ """
+ called when the service is active on the transport.
+ """
+
+ def serviceStopped(self):
+ """
+ called when the service is stopped, either by the connection ending
+ or by another service being started
+ """
+
+ def logPrefix(self):
+ return "SSHService %r on %s" % (self.name,
+ self.transport.transport.logPrefix())
+
+ def packetReceived(self, messageNum, packet):
+ """
+ called when we receive a packet on the transport
+ """
+ #print self.protocolMessages
+ if messageNum in self.protocolMessages:
+ messageType = self.protocolMessages[messageNum]
+ f = getattr(self,'ssh_%s' % messageType[4:],
+ None)
+ if f is not None:
+ return f(packet)
+ log.msg("couldn't handle %r" % messageNum)
+ log.msg(repr(packet))
+ self.transport.sendUnimplemented()
diff --git a/contrib/python/Twisted/py2/twisted/conch/ssh/session.py b/contrib/python/Twisted/py2/twisted/conch/ssh/session.py
new file mode 100644
index 0000000000..3a2f5d54c7
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/ssh/session.py
@@ -0,0 +1,362 @@
+# -*- test-case-name: twisted.conch.test.test_session -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This module contains the implementation of SSHSession, which (by default)
+allows access to a shell and a python interpreter over SSH.
+
+Maintainer: Paul Swartz
+"""
+
+from __future__ import division, absolute_import
+
+import struct
+import signal
+import sys
+import os
+
+from zope.interface import implementer
+
+from twisted.internet import interfaces, protocol
+from twisted.python import log
+from twisted.python.compat import _bytesChr as chr, networkString
+from twisted.conch.interfaces import ISession
+from twisted.conch.ssh import common, channel, connection
+
+
+class SSHSession(channel.SSHChannel):
+
+ name = b'session'
+ def __init__(self, *args, **kw):
+ channel.SSHChannel.__init__(self, *args, **kw)
+ self.buf = b''
+ self.client = None
+ self.session = None
+
+ def request_subsystem(self, data):
+ subsystem, ignored= common.getNS(data)
+ log.msg('asking for subsystem "%s"' % subsystem)
+ client = self.avatar.lookupSubsystem(subsystem, data)
+ if client:
+ pp = SSHSessionProcessProtocol(self)
+ proto = wrapProcessProtocol(pp)
+ client.makeConnection(proto)
+ pp.makeConnection(wrapProtocol(client))
+ self.client = pp
+ return 1
+ else:
+ log.msg('failed to get subsystem')
+ return 0
+
+ def request_shell(self, data):
+ log.msg('getting shell')
+ if not self.session:
+ self.session = ISession(self.avatar)
+ try:
+ pp = SSHSessionProcessProtocol(self)
+ self.session.openShell(pp)
+ except:
+ log.deferr()
+ return 0
+ else:
+ self.client = pp
+ return 1
+
+ def request_exec(self, data):
+ if not self.session:
+ self.session = ISession(self.avatar)
+ f,data = common.getNS(data)
+ log.msg('executing command "%s"' % f)
+ try:
+ pp = SSHSessionProcessProtocol(self)
+ self.session.execCommand(pp, f)
+ except:
+ log.deferr()
+ return 0
+ else:
+ self.client = pp
+ return 1
+
+ def request_pty_req(self, data):
+ if not self.session:
+ self.session = ISession(self.avatar)
+ term, windowSize, modes = parseRequest_pty_req(data)
+ log.msg('pty request: %r %r' % (term, windowSize))
+ try:
+ self.session.getPty(term, windowSize, modes)
+ except:
+ log.err()
+ return 0
+ else:
+ return 1
+
+ def request_window_change(self, data):
+ if not self.session:
+ self.session = ISession(self.avatar)
+ winSize = parseRequest_window_change(data)
+ try:
+ self.session.windowChanged(winSize)
+ except:
+ log.msg('error changing window size')
+ log.err()
+ return 0
+ else:
+ return 1
+
+ def dataReceived(self, data):
+ if not self.client:
+ #self.conn.sendClose(self)
+ self.buf += data
+ return
+ self.client.transport.write(data)
+
+ def extReceived(self, dataType, data):
+ if dataType == connection.EXTENDED_DATA_STDERR:
+ if self.client and hasattr(self.client.transport, 'writeErr'):
+ self.client.transport.writeErr(data)
+ else:
+ log.msg('weird extended data: %s'%dataType)
+
+ def eofReceived(self):
+ if self.session:
+ self.session.eofReceived()
+ elif self.client:
+ self.conn.sendClose(self)
+
+ def closed(self):
+ if self.session:
+ self.session.closed()
+ elif self.client:
+ self.client.transport.loseConnection()
+
+ #def closeReceived(self):
+ # self.loseConnection() # don't know what to do with this
+
+ def loseConnection(self):
+ if self.client:
+ self.client.transport.loseConnection()
+ channel.SSHChannel.loseConnection(self)
+
+class _ProtocolWrapper(protocol.ProcessProtocol):
+ """
+ This class wraps a L{Protocol} instance in a L{ProcessProtocol} instance.
+ """
+ def __init__(self, proto):
+ self.proto = proto
+
+ def connectionMade(self): self.proto.connectionMade()
+
+ def outReceived(self, data): self.proto.dataReceived(data)
+
+ def processEnded(self, reason): self.proto.connectionLost(reason)
+
+class _DummyTransport:
+
+ def __init__(self, proto):
+ self.proto = proto
+
+ def dataReceived(self, data):
+ self.proto.transport.write(data)
+
+ def write(self, data):
+ self.proto.dataReceived(data)
+
+ def writeSequence(self, seq):
+ self.write(b''.join(seq))
+
+ def loseConnection(self):
+ self.proto.connectionLost(protocol.connectionDone)
+
+def wrapProcessProtocol(inst):
+ if isinstance(inst, protocol.Protocol):
+ return _ProtocolWrapper(inst)
+ else:
+ return inst
+
+def wrapProtocol(proto):
+ return _DummyTransport(proto)
+
+
+
+# SUPPORTED_SIGNALS is a list of signals that every session channel is supposed
+# to accept. See RFC 4254
+SUPPORTED_SIGNALS = ["ABRT", "ALRM", "FPE", "HUP", "ILL", "INT", "KILL",
+ "PIPE", "QUIT", "SEGV", "TERM", "USR1", "USR2"]
+
+
+
+@implementer(interfaces.ITransport)
+class SSHSessionProcessProtocol(protocol.ProcessProtocol):
+ """I am both an L{IProcessProtocol} and an L{ITransport}.
+
+ I am a transport to the remote endpoint and a process protocol to the
+ local subsystem.
+ """
+
+ # once initialized, a dictionary mapping signal values to strings
+ # that follow RFC 4254.
+ _signalValuesToNames = None
+
+ def __init__(self, session):
+ self.session = session
+ self.lostOutOrErrFlag = False
+
+ def connectionMade(self):
+ if self.session.buf:
+ self.transport.write(self.session.buf)
+ self.session.buf = None
+
+ def outReceived(self, data):
+ self.session.write(data)
+
+ def errReceived(self, err):
+ self.session.writeExtended(connection.EXTENDED_DATA_STDERR, err)
+
+ def outConnectionLost(self):
+ """
+ EOF should only be sent when both STDOUT and STDERR have been closed.
+ """
+ if self.lostOutOrErrFlag:
+ self.session.conn.sendEOF(self.session)
+ else:
+ self.lostOutOrErrFlag = True
+
+ def errConnectionLost(self):
+ """
+ See outConnectionLost().
+ """
+ self.outConnectionLost()
+
+ def connectionLost(self, reason = None):
+ self.session.loseConnection()
+
+
+ def _getSignalName(self, signum):
+ """
+ Get a signal name given a signal number.
+ """
+ if self._signalValuesToNames is None:
+ self._signalValuesToNames = {}
+ # make sure that the POSIX ones are the defaults
+ for signame in SUPPORTED_SIGNALS:
+ signame = 'SIG' + signame
+ sigvalue = getattr(signal, signame, None)
+ if sigvalue is not None:
+ self._signalValuesToNames[sigvalue] = signame
+ for k, v in signal.__dict__.items():
+ # Check for platform specific signals, ignoring Python specific
+ # SIG_DFL and SIG_IGN
+ if k.startswith('SIG') and not k.startswith('SIG_'):
+ if v not in self._signalValuesToNames:
+ self._signalValuesToNames[v] = k + '@' + sys.platform
+ return self._signalValuesToNames[signum]
+
+
+ def processEnded(self, reason=None):
+ """
+ When we are told the process ended, try to notify the other side about
+ how the process ended using the exit-signal or exit-status requests.
+ Also, close the channel.
+ """
+ if reason is not None:
+ err = reason.value
+ if err.signal is not None:
+ signame = self._getSignalName(err.signal)
+ if (getattr(os, 'WCOREDUMP', None) is not None and
+ os.WCOREDUMP(err.status)):
+ log.msg('exitSignal: %s (core dumped)' % (signame,))
+ coreDumped = 1
+ else:
+ log.msg('exitSignal: %s' % (signame,))
+ coreDumped = 0
+ self.session.conn.sendRequest(
+ self.session, b'exit-signal',
+ common.NS(networkString(signame[3:])) + chr(coreDumped) +
+ common.NS(b'') + common.NS(b''))
+ elif err.exitCode is not None:
+ log.msg('exitCode: %r' % (err.exitCode,))
+ self.session.conn.sendRequest(self.session, b'exit-status',
+ struct.pack('>L', err.exitCode))
+ self.session.loseConnection()
+
+
+ def getHost(self):
+ """
+ Return the host from my session's transport.
+ """
+ return self.session.conn.transport.getHost()
+
+
+ def getPeer(self):
+ """
+ Return the peer from my session's transport.
+ """
+ return self.session.conn.transport.getPeer()
+
+
+ def write(self, data):
+ self.session.write(data)
+
+
+ def writeSequence(self, seq):
+ self.session.write(b''.join(seq))
+
+
+ def loseConnection(self):
+ self.session.loseConnection()
+
+
+
+class SSHSessionClient(protocol.Protocol):
+
+ def dataReceived(self, data):
+ if self.transport:
+ self.transport.write(data)
+
+# methods factored out to make live easier on server writers
+def parseRequest_pty_req(data):
+ """Parse the data from a pty-req request into usable data.
+
+ @returns: a tuple of (terminal type, (rows, cols, xpixel, ypixel), modes)
+ """
+ term, rest = common.getNS(data)
+ cols, rows, xpixel, ypixel = struct.unpack('>4L', rest[: 16])
+ modes, ignored= common.getNS(rest[16:])
+ winSize = (rows, cols, xpixel, ypixel)
+ modes = [(ord(modes[i:i+1]), struct.unpack('>L', modes[i+1: i+5])[0])
+ for i in range(0, len(modes)-1, 5)]
+ return term, winSize, modes
+
+def packRequest_pty_req(term, geometry, modes):
+ """
+ Pack a pty-req request so that it is suitable for sending.
+
+ NOTE: modes must be packed before being sent here.
+
+ @type geometry: L{tuple}
+ @param geometry: A tuple of (rows, columns, xpixel, ypixel)
+ """
+ (rows, cols, xpixel, ypixel) = geometry
+ termPacked = common.NS(term)
+ winSizePacked = struct.pack('>4L', cols, rows, xpixel, ypixel)
+ modesPacked = common.NS(modes) # depend on the client packing modes
+ return termPacked + winSizePacked + modesPacked
+
+def parseRequest_window_change(data):
+ """Parse the data from a window-change request into usuable data.
+
+ @returns: a tuple of (rows, cols, xpixel, ypixel)
+ """
+ cols, rows, xpixel, ypixel = struct.unpack('>4L', data)
+ return rows, cols, xpixel, ypixel
+
+def packRequest_window_change(geometry):
+ """
+ Pack a window-change request so that it is suitable for sending.
+
+ @type geometry: L{tuple}
+ @param geometry: A tuple of (rows, columns, xpixel, ypixel)
+ """
+ (rows, cols, xpixel, ypixel) = geometry
+ return struct.pack('>4L', cols, rows, xpixel, ypixel)
diff --git a/contrib/python/Twisted/py2/twisted/conch/ssh/sexpy.py b/contrib/python/Twisted/py2/twisted/conch/ssh/sexpy.py
new file mode 100644
index 0000000000..c5f102e4f1
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/ssh/sexpy.py
@@ -0,0 +1,45 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from __future__ import absolute_import, division
+
+from twisted.python.compat import intToBytes
+
+
+def parse(s):
+ s = s.strip()
+ expr = []
+ while s:
+ if s[0:1] == b'(':
+ newSexp = []
+ if expr:
+ expr[-1].append(newSexp)
+ expr.append(newSexp)
+ s = s[1:]
+ continue
+ if s[0:1] == b')':
+ aList = expr.pop()
+ s=s[1:]
+ if not expr:
+ assert not s
+ return aList
+ continue
+ i = 0
+ while s[i:i+1].isdigit(): i+=1
+ assert i
+ length = int(s[:i])
+ data = s[i+1:i+1+length]
+ expr[-1].append(data)
+ s=s[i+1+length:]
+ assert 0, "this should not happen"
+
+def pack(sexp):
+ s = b""
+ for o in sexp:
+ if type(o) in (type(()), type([])):
+ s+=b'('
+ s+=pack(o)
+ s+=b')'
+ else:
+ s+=intToBytes(len(o)) + b":" + o
+ return s
diff --git a/contrib/python/Twisted/py2/twisted/conch/ssh/transport.py b/contrib/python/Twisted/py2/twisted/conch/ssh/transport.py
new file mode 100644
index 0000000000..e5d75eab18
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/ssh/transport.py
@@ -0,0 +1,2127 @@
+# -*- test-case-name: twisted.conch.test.test_transport -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+The lowest level SSH protocol. This handles the key negotiation, the
+encryption and the compression. The transport layer is described in
+RFC 4253.
+
+Maintainer: Paul Swartz
+"""
+
+from __future__ import absolute_import, division
+
+import binascii
+import hmac
+import struct
+import zlib
+
+from hashlib import md5, sha1, sha256, sha384, sha512
+
+from cryptography.exceptions import UnsupportedAlgorithm
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives import serialization
+from cryptography.hazmat.primitives.ciphers import algorithms, modes, Cipher
+from cryptography.hazmat.primitives.asymmetric import dh, ec, x25519
+
+from twisted import __version__ as twisted_version
+from twisted.internet import protocol, defer
+from twisted.python import log, randbytes
+from twisted.python.compat import iterbytes, _bytesChr as chr, networkString
+
+# This import is needed if SHA256 hashing is used.
+# from twisted.python.compat import nativeString
+
+from twisted.conch.ssh import address, keys, _kex
+from twisted.conch.ssh.common import (
+ NS, getNS, MP, getMP, ffs, int_from_bytes
+)
+
+
+
+def _mpFromBytes(data):
+ """Make an SSH multiple-precision integer from big-endian L{bytes}.
+
+ Used in ECDH key exchange.
+
+ @type data: L{bytes}
+ @param data: The input data, interpreted as a big-endian octet string.
+
+ @rtype: L{bytes}
+ @return: The given data encoded as an SSH multiple-precision integer.
+ """
+ return MP(int_from_bytes(data, 'big'))
+
+
+
+class _MACParams(tuple):
+ """
+ L{_MACParams} represents the parameters necessary to compute SSH MAC
+ (Message Authenticate Codes).
+
+ L{_MACParams} is a L{tuple} subclass to maintain compatibility with older
+ versions of the code. The elements of a L{_MACParams} are::
+
+ 0. The digest object used for the MAC
+ 1. The inner pad ("ipad") string
+ 2. The outer pad ("opad") string
+ 3. The size of the digest produced by the digest object
+
+ L{_MACParams} is also an object lesson in why tuples are a bad type for
+ public APIs.
+
+ @ivar key: The HMAC key which will be used.
+ """
+
+
+
+class SSHCiphers:
+ """
+ SSHCiphers represents all the encryption operations that need to occur
+ to encrypt and authenticate the SSH connection.
+
+ @cvar cipherMap: A dictionary mapping SSH encryption names to 3-tuples of
+ (<cryptography.hazmat.primitives.interfaces.CipherAlgorithm>,
+ <block size>, <cryptography.hazmat.primitives.interfaces.Mode>)
+ @cvar macMap: A dictionary mapping SSH MAC names to hash modules.
+
+ @ivar outCipType: the string type of the outgoing cipher.
+ @ivar inCipType: the string type of the incoming cipher.
+ @ivar outMACType: the string type of the incoming MAC.
+ @ivar inMACType: the string type of the incoming MAC.
+ @ivar encBlockSize: the block size of the outgoing cipher.
+ @ivar decBlockSize: the block size of the incoming cipher.
+ @ivar verifyDigestSize: the size of the incoming MAC.
+ @ivar outMAC: a tuple of (<hash module>, <inner key>, <outer key>,
+ <digest size>) representing the outgoing MAC.
+ @ivar inMAc: see outMAC, but for the incoming MAC.
+ """
+
+ cipherMap = {
+ b'3des-cbc': (algorithms.TripleDES, 24, modes.CBC),
+ b'blowfish-cbc': (algorithms.Blowfish, 16, modes.CBC),
+ b'aes256-cbc': (algorithms.AES, 32, modes.CBC),
+ b'aes192-cbc': (algorithms.AES, 24, modes.CBC),
+ b'aes128-cbc': (algorithms.AES, 16, modes.CBC),
+ b'cast128-cbc': (algorithms.CAST5, 16, modes.CBC),
+ b'aes128-ctr': (algorithms.AES, 16, modes.CTR),
+ b'aes192-ctr': (algorithms.AES, 24, modes.CTR),
+ b'aes256-ctr': (algorithms.AES, 32, modes.CTR),
+ b'3des-ctr': (algorithms.TripleDES, 24, modes.CTR),
+ b'blowfish-ctr': (algorithms.Blowfish, 16, modes.CTR),
+ b'cast128-ctr': (algorithms.CAST5, 16, modes.CTR),
+ b'none': (None, 0, modes.CBC),
+ }
+ macMap = {
+ b'hmac-sha2-512': sha512,
+ b'hmac-sha2-384': sha384,
+ b'hmac-sha2-256': sha256,
+ b'hmac-sha1': sha1,
+ b'hmac-md5': md5,
+ b'none': None
+ }
+
+
+ def __init__(self, outCip, inCip, outMac, inMac):
+ self.outCipType = outCip
+ self.inCipType = inCip
+ self.outMACType = outMac
+ self.inMACType = inMac
+ self.encBlockSize = 0
+ self.decBlockSize = 0
+ self.verifyDigestSize = 0
+ self.outMAC = (None, b'', b'', 0)
+ self.inMAC = (None, b'', b'', 0)
+
+
+ def setKeys(self, outIV, outKey, inIV, inKey, outInteg, inInteg):
+ """
+ Set up the ciphers and hashes using the given keys,
+
+ @param outIV: the outgoing initialization vector
+ @param outKey: the outgoing encryption key
+ @param inIV: the incoming initialization vector
+ @param inKey: the incoming encryption key
+ @param outInteg: the outgoing integrity key
+ @param inInteg: the incoming integrity key.
+ """
+ o = self._getCipher(self.outCipType, outIV, outKey)
+ self.encryptor = o.encryptor()
+ self.encBlockSize = o.algorithm.block_size // 8
+ o = self._getCipher(self.inCipType, inIV, inKey)
+ self.decryptor = o.decryptor()
+ self.decBlockSize = o.algorithm.block_size // 8
+ self.outMAC = self._getMAC(self.outMACType, outInteg)
+ self.inMAC = self._getMAC(self.inMACType, inInteg)
+ if self.inMAC:
+ self.verifyDigestSize = self.inMAC[3]
+
+
+ def _getCipher(self, cip, iv, key):
+ """
+ Creates an initialized cipher object.
+
+ @param cip: the name of the cipher, maps into cipherMap
+ @param iv: the initialzation vector
+ @param key: the encryption key
+
+ @return: the cipher object.
+ """
+ algorithmClass, keySize, modeClass = self.cipherMap[cip]
+ if algorithmClass is None:
+ return _DummyCipher()
+
+ return Cipher(
+ algorithmClass(key[:keySize]),
+ modeClass(iv[:algorithmClass.block_size // 8]),
+ backend=default_backend(),
+ )
+
+
+ def _getMAC(self, mac, key):
+ """
+ Gets a 4-tuple representing the message authentication code.
+ (<hash module>, <inner hash value>, <outer hash value>,
+ <digest size>)
+
+ @type mac: L{bytes}
+ @param mac: a key mapping into macMap
+
+ @type key: L{bytes}
+ @param key: the MAC key.
+
+ @rtype: L{bytes}
+ @return: The MAC components.
+ """
+ mod = self.macMap[mac]
+ if not mod:
+ return (None, b'', b'', 0)
+
+ # With stdlib we can only get attributes fron an instantiated object.
+ hashObject = mod()
+ digestSize = hashObject.digest_size
+ blockSize = hashObject.block_size
+
+ # Truncation here appears to contravene RFC 2104, section 2. However,
+ # implementing the hashing behavior prescribed by the RFC breaks
+ # interoperability with OpenSSH (at least version 5.5p1).
+ key = key[:digestSize] + (b'\x00' * (blockSize - digestSize))
+ i = key.translate(hmac.trans_36)
+ o = key.translate(hmac.trans_5C)
+ result = _MACParams((mod, i, o, digestSize))
+ result.key = key
+ return result
+
+
+ def encrypt(self, blocks):
+ """
+ Encrypt some data.
+
+ @type blocks: L{bytes}
+ @param blocks: The data to encrypt.
+
+ @rtype: L{bytes}
+ @return: The encrypted data.
+ """
+ return self.encryptor.update(blocks)
+
+
+ def decrypt(self, blocks):
+ """
+ Decrypt some data.
+
+ @type blocks: L{bytes}
+ @param blocks: The data to decrypt.
+
+ @rtype: L{bytes}
+ @return: The decrypted data.
+ """
+ return self.decryptor.update(blocks)
+
+
+ def makeMAC(self, seqid, data):
+ """
+ Create a message authentication code (MAC) for the given packet using
+ the outgoing MAC values.
+
+ @type seqid: L{int}
+ @param seqid: The sequence ID of the outgoing packet.
+
+ @type data: L{bytes}
+ @param data: The data to create a MAC for.
+
+ @rtype: L{str}
+ @return: The serialized MAC.
+ """
+ if not self.outMAC[0]:
+ return b''
+ data = struct.pack('>L', seqid) + data
+ return hmac.HMAC(self.outMAC.key, data, self.outMAC[0]).digest()
+
+
+ def verify(self, seqid, data, mac):
+ """
+ Verify an incoming MAC using the incoming MAC values.
+
+ @type seqid: L{int}
+ @param seqid: The sequence ID of the incoming packet.
+
+ @type data: L{bytes}
+ @param data: The packet data to verify.
+
+ @type mac: L{bytes}
+ @param mac: The MAC sent with the packet.
+
+ @rtype: L{bool}
+ @return: C{True} if the MAC is valid.
+ """
+ if not self.inMAC[0]:
+ return mac == b''
+ data = struct.pack('>L', seqid) + data
+ outer = hmac.HMAC(self.inMAC.key, data, self.inMAC[0]).digest()
+ return mac == outer
+
+
+
+def _getSupportedCiphers():
+ """
+ Build a list of ciphers that are supported by the backend in use.
+
+ @return: a list of supported ciphers.
+ @rtype: L{list} of L{str}
+ """
+ supportedCiphers = []
+ cs = [b'aes256-ctr', b'aes256-cbc', b'aes192-ctr', b'aes192-cbc',
+ b'aes128-ctr', b'aes128-cbc', b'cast128-ctr', b'cast128-cbc',
+ b'blowfish-ctr', b'blowfish-cbc', b'3des-ctr', b'3des-cbc']
+ for cipher in cs:
+ algorithmClass, keySize, modeClass = SSHCiphers.cipherMap[cipher]
+ try:
+ Cipher(
+ algorithmClass(b' ' * keySize),
+ modeClass(b' ' * (algorithmClass.block_size // 8)),
+ backend=default_backend(),
+ ).encryptor()
+ except UnsupportedAlgorithm:
+ pass
+ else:
+ supportedCiphers.append(cipher)
+ return supportedCiphers
+
+
+
+class SSHTransportBase(protocol.Protocol):
+ """
+ Protocol supporting basic SSH functionality: sending/receiving packets
+ and message dispatch. To connect to or run a server, you must use
+ SSHClientTransport or SSHServerTransport.
+
+ @ivar protocolVersion: A string representing the version of the SSH
+ protocol we support. Currently defaults to '2.0'.
+
+ @ivar version: A string representing the version of the server or client.
+ Currently defaults to 'Twisted'.
+
+ @ivar comment: An optional string giving more information about the
+ server or client.
+
+ @ivar supportedCiphers: A list of strings representing the encryption
+ algorithms supported, in order from most-preferred to least.
+
+ @ivar supportedMACs: A list of strings representing the message
+ authentication codes (hashes) supported, in order from most-preferred
+ to least. Both this and supportedCiphers can include 'none' to use
+ no encryption or authentication, but that must be done manually,
+
+ @ivar supportedKeyExchanges: A list of strings representing the
+ key exchanges supported, in order from most-preferred to least.
+
+ @ivar supportedPublicKeys: A list of strings representing the
+ public key types supported, in order from most-preferred to least.
+
+ @ivar supportedCompressions: A list of strings representing compression
+ types supported, from most-preferred to least.
+
+ @ivar supportedLanguages: A list of strings representing languages
+ supported, from most-preferred to least.
+
+ @ivar supportedVersions: A container of strings representing supported ssh
+ protocol version numbers.
+
+ @ivar isClient: A boolean indicating whether this is a client or server.
+
+ @ivar gotVersion: A boolean indicating whether we have received the
+ version string from the other side.
+
+ @ivar buf: Data we've received but hasn't been parsed into a packet.
+
+ @ivar outgoingPacketSequence: the sequence number of the next packet we
+ will send.
+
+ @ivar incomingPacketSequence: the sequence number of the next packet we
+ are expecting from the other side.
+
+ @ivar outgoingCompression: an object supporting the .compress(str) and
+ .flush() methods, or None if there is no outgoing compression. Used to
+ compress outgoing data.
+
+ @ivar outgoingCompressionType: A string representing the outgoing
+ compression type.
+
+ @ivar incomingCompression: an object supporting the .decompress(str)
+ method, or None if there is no incoming compression. Used to
+ decompress incoming data.
+
+ @ivar incomingCompressionType: A string representing the incoming
+ compression type.
+
+ @ivar ourVersionString: the version string that we sent to the other side.
+ Used in the key exchange.
+
+ @ivar otherVersionString: the version string sent by the other side. Used
+ in the key exchange.
+
+ @ivar ourKexInitPayload: the MSG_KEXINIT payload we sent. Used in the key
+ exchange.
+
+ @ivar otherKexInitPayload: the MSG_KEXINIT payload we received. Used in
+ the key exchange
+
+ @ivar sessionID: a string that is unique to this SSH session. Created as
+ part of the key exchange, sessionID is used to generate the various
+ encryption and authentication keys.
+
+ @ivar service: an SSHService instance, or None. If it's set to an object,
+ it's the currently running service.
+
+ @ivar kexAlg: the agreed-upon key exchange algorithm.
+
+ @ivar keyAlg: the agreed-upon public key type for the key exchange.
+
+ @ivar currentEncryptions: an SSHCiphers instance. It represents the
+ current encryption and authentication options for the transport.
+
+ @ivar nextEncryptions: an SSHCiphers instance. Held here until the
+ MSG_NEWKEYS messages are exchanged, when nextEncryptions is
+ transitioned to currentEncryptions.
+
+ @ivar first: the first bytes of the next packet. In order to avoid
+ decrypting data twice, the first bytes are decrypted and stored until
+ the whole packet is available.
+
+ @ivar _keyExchangeState: The current protocol state with respect to key
+ exchange. This is either C{_KEY_EXCHANGE_NONE} if no key exchange is
+ in progress (and returns to this value after any key exchange
+ completqes), C{_KEY_EXCHANGE_REQUESTED} if this side of the connection
+ initiated a key exchange, and C{_KEY_EXCHANGE_PROGRESSING} if the other
+ side of the connection initiated a key exchange. C{_KEY_EXCHANGE_NONE}
+ is the initial value (however SSH connections begin with key exchange,
+ so it will quickly change to another state).
+
+ @ivar _blockedByKeyExchange: Whenever C{_keyExchangeState} is not
+ C{_KEY_EXCHANGE_NONE}, this is a C{list} of pending messages which were
+ passed to L{sendPacket} but could not be sent because it is not legal
+ to send them while a key exchange is in progress. When the key
+ exchange completes, another attempt is made to send these messages.
+ """
+ protocolVersion = b'2.0'
+ version = b'Twisted_' + twisted_version.encode('ascii')
+ comment = b''
+ ourVersionString = (b'SSH-' + protocolVersion + b'-' + version + b' '
+ + comment).strip()
+
+ # L{None} is supported as cipher and hmac. For security they are disabled
+ # by default. To enable them, subclass this class and add it, or do:
+ # SSHTransportBase.supportedCiphers.append('none')
+ # List ordered by preference.
+ supportedCiphers = _getSupportedCiphers()
+ supportedMACs = [
+ b'hmac-sha2-512',
+ b'hmac-sha2-384',
+ b'hmac-sha2-256',
+ b'hmac-sha1',
+ b'hmac-md5',
+ # `none`,
+ ]
+
+ supportedKeyExchanges = _kex.getSupportedKeyExchanges()
+ supportedPublicKeys = []
+
+ # Add the supported EC keys, and change the name from ecdh* to ecdsa*
+ for eckey in supportedKeyExchanges:
+ if eckey.find(b'ecdh') != -1:
+ supportedPublicKeys += [eckey.replace(b'ecdh', b'ecdsa')]
+
+ supportedPublicKeys += [b'ssh-rsa', b'ssh-dss']
+
+ supportedCompressions = [b'none', b'zlib']
+ supportedLanguages = ()
+ supportedVersions = (b'1.99', b'2.0')
+ isClient = False
+ gotVersion = False
+ buf = b''
+ outgoingPacketSequence = 0
+ incomingPacketSequence = 0
+ outgoingCompression = None
+ incomingCompression = None
+ sessionID = None
+ service = None
+
+ # There is no key exchange activity in progress.
+ _KEY_EXCHANGE_NONE = '_KEY_EXCHANGE_NONE'
+
+ # Key exchange is in progress and we started it.
+ _KEY_EXCHANGE_REQUESTED = '_KEY_EXCHANGE_REQUESTED'
+
+ # Key exchange is in progress and both sides have sent KEXINIT messages.
+ _KEY_EXCHANGE_PROGRESSING = '_KEY_EXCHANGE_PROGRESSING'
+
+ # There is a fourth conceptual state not represented here: KEXINIT received
+ # but not sent. Since we always send a KEXINIT as soon as we get it, we
+ # can't ever be in that state.
+
+ # The current key exchange state.
+ _keyExchangeState = _KEY_EXCHANGE_NONE
+ _blockedByKeyExchange = None
+
+ def connectionLost(self, reason):
+ """
+ When the underlying connection is closed, stop the running service (if
+ any), and log out the avatar (if any).
+
+ @type reason: L{twisted.python.failure.Failure}
+ @param reason: The cause of the connection being closed.
+ """
+ if self.service:
+ self.service.serviceStopped()
+ if hasattr(self, 'avatar'):
+ self.logoutFunction()
+ log.msg('connection lost')
+
+
+ def connectionMade(self):
+ """
+ Called when the connection is made to the other side. We sent our
+ version and the MSG_KEXINIT packet.
+ """
+ self.transport.write(self.ourVersionString + b'\r\n')
+ self.currentEncryptions = SSHCiphers(b'none', b'none', b'none',
+ b'none')
+ self.currentEncryptions.setKeys(b'', b'', b'', b'', b'', b'')
+ self.sendKexInit()
+
+
+ def sendKexInit(self):
+ """
+ Send a I{KEXINIT} message to initiate key exchange or to respond to a
+ key exchange initiated by the peer.
+
+ @raise RuntimeError: If a key exchange has already been started and it
+ is not appropriate to send a I{KEXINIT} message at this time.
+
+ @return: L{None}
+ """
+ if self._keyExchangeState != self._KEY_EXCHANGE_NONE:
+ raise RuntimeError(
+ "Cannot send KEXINIT while key exchange state is %r" % (
+ self._keyExchangeState,))
+
+ self.ourKexInitPayload = b''.join([
+ chr(MSG_KEXINIT),
+ randbytes.secureRandom(16),
+ NS(b','.join(self.supportedKeyExchanges)),
+ NS(b','.join(self.supportedPublicKeys)),
+ NS(b','.join(self.supportedCiphers)),
+ NS(b','.join(self.supportedCiphers)),
+ NS(b','.join(self.supportedMACs)),
+ NS(b','.join(self.supportedMACs)),
+ NS(b','.join(self.supportedCompressions)),
+ NS(b','.join(self.supportedCompressions)),
+ NS(b','.join(self.supportedLanguages)),
+ NS(b','.join(self.supportedLanguages)),
+ b'\000\000\000\000\000'])
+ self.sendPacket(MSG_KEXINIT, self.ourKexInitPayload[1:])
+ self._keyExchangeState = self._KEY_EXCHANGE_REQUESTED
+ self._blockedByKeyExchange = []
+
+
+ def _allowedKeyExchangeMessageType(self, messageType):
+ """
+ Determine if the given message type may be sent while key exchange is
+ in progress.
+
+ @param messageType: The type of message
+ @type messageType: L{int}
+
+ @return: C{True} if the given type of message may be sent while key
+ exchange is in progress, C{False} if it may not.
+ @rtype: L{bool}
+
+ @see: U{http://tools.ietf.org/html/rfc4253#section-7.1}
+ """
+ # Written somewhat peculularly to reflect the way the specification
+ # defines the allowed message types.
+ if 1 <= messageType <= 19:
+ return messageType not in (MSG_SERVICE_REQUEST, MSG_SERVICE_ACCEPT)
+ if 20 <= messageType <= 29:
+ return messageType not in (MSG_KEXINIT,)
+ return 30 <= messageType <= 49
+
+
+ def sendPacket(self, messageType, payload):
+ """
+ Sends a packet. If it's been set up, compress the data, encrypt it,
+ and authenticate it before sending. If key exchange is in progress and
+ the message is not part of key exchange, queue it to be sent later.
+
+ @param messageType: The type of the packet; generally one of the
+ MSG_* values.
+ @type messageType: L{int}
+ @param payload: The payload for the message.
+ @type payload: L{str}
+ """
+ if self._keyExchangeState != self._KEY_EXCHANGE_NONE:
+ if not self._allowedKeyExchangeMessageType(messageType):
+ self._blockedByKeyExchange.append((messageType, payload))
+ return
+
+ payload = chr(messageType) + payload
+ if self.outgoingCompression:
+ payload = (self.outgoingCompression.compress(payload)
+ + self.outgoingCompression.flush(2))
+ bs = self.currentEncryptions.encBlockSize
+ # 4 for the packet length and 1 for the padding length
+ totalSize = 5 + len(payload)
+ lenPad = bs - (totalSize % bs)
+ if lenPad < 4:
+ lenPad = lenPad + bs
+ packet = (struct.pack('!LB',
+ totalSize + lenPad - 4, lenPad) +
+ payload + randbytes.secureRandom(lenPad))
+ encPacket = (
+ self.currentEncryptions.encrypt(packet) +
+ self.currentEncryptions.makeMAC(
+ self.outgoingPacketSequence, packet))
+ self.transport.write(encPacket)
+ self.outgoingPacketSequence += 1
+
+
+ def getPacket(self):
+ """
+ Try to return a decrypted, authenticated, and decompressed packet
+ out of the buffer. If there is not enough data, return None.
+
+ @rtype: L{str} or L{None}
+ @return: The decoded packet, if any.
+ """
+ bs = self.currentEncryptions.decBlockSize
+ ms = self.currentEncryptions.verifyDigestSize
+ if len(self.buf) < bs:
+ # Not enough data for a block
+ return
+ if not hasattr(self, 'first'):
+ first = self.currentEncryptions.decrypt(self.buf[:bs])
+ else:
+ first = self.first
+ del self.first
+ packetLen, paddingLen = struct.unpack('!LB', first[:5])
+ if packetLen > 1048576: # 1024 ** 2
+ self.sendDisconnect(
+ DISCONNECT_PROTOCOL_ERROR,
+ networkString('bad packet length {}'.format(packetLen)))
+ return
+ if len(self.buf) < packetLen + 4 + ms:
+ # Not enough data for a packet
+ self.first = first
+ return
+ if (packetLen + 4) % bs != 0:
+ self.sendDisconnect(
+ DISCONNECT_PROTOCOL_ERROR,
+ networkString(
+ 'bad packet mod (%i%%%i == %i)' % (
+ packetLen + 4, bs, (packetLen + 4) % bs)))
+ return
+ encData, self.buf = self.buf[:4 + packetLen], self.buf[4 + packetLen:]
+ packet = first + self.currentEncryptions.decrypt(encData[bs:])
+ if len(packet) != 4 + packetLen:
+ self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR,
+ b'bad decryption')
+ return
+ if ms:
+ macData, self.buf = self.buf[:ms], self.buf[ms:]
+ if not self.currentEncryptions.verify(self.incomingPacketSequence,
+ packet, macData):
+ self.sendDisconnect(DISCONNECT_MAC_ERROR, b'bad MAC')
+ return
+ payload = packet[5:-paddingLen]
+ if self.incomingCompression:
+ try:
+ payload = self.incomingCompression.decompress(payload)
+ except:
+ # Tolerate any errors in decompression
+ log.err()
+ self.sendDisconnect(DISCONNECT_COMPRESSION_ERROR,
+ b'compression error')
+ return
+ self.incomingPacketSequence += 1
+ return payload
+
+
+ def _unsupportedVersionReceived(self, remoteVersion):
+ """
+ Called when an unsupported version of the ssh protocol is received from
+ the remote endpoint.
+
+ @param remoteVersion: remote ssh protocol version which is unsupported
+ by us.
+ @type remoteVersion: L{str}
+ """
+ self.sendDisconnect(DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED,
+ b'bad version ' + remoteVersion)
+
+
+ def dataReceived(self, data):
+ """
+ First, check for the version string (SSH-2.0-*). After that has been
+ received, this method adds data to the buffer, and pulls out any
+ packets.
+
+ @type data: L{bytes}
+ @param data: The data that was received.
+ """
+ self.buf = self.buf + data
+ if not self.gotVersion:
+ if self.buf.find(b'\n', self.buf.find(b'SSH-')) == -1:
+ return
+
+ # RFC 4253 section 4.2 ask for strict `\r\n` line ending.
+ # Here we are a bit more relaxed and accept implementations ending
+ # only in '\n'.
+ # https://tools.ietf.org/html/rfc4253#section-4.2
+ lines = self.buf.split(b'\n')
+ for p in lines:
+ if p.startswith(b'SSH-'):
+ self.gotVersion = True
+ # Since the line was split on '\n' and most of the time
+ # it uses '\r\n' we may get an extra '\r'.
+ self.otherVersionString = p.rstrip(b'\r')
+ remoteVersion = p.split(b'-')[1]
+ if remoteVersion not in self.supportedVersions:
+ self._unsupportedVersionReceived(remoteVersion)
+ return
+ i = lines.index(p)
+ self.buf = b'\n'.join(lines[i + 1:])
+ packet = self.getPacket()
+ while packet:
+ messageNum = ord(packet[0:1])
+ self.dispatchMessage(messageNum, packet[1:])
+ packet = self.getPacket()
+
+
+ def dispatchMessage(self, messageNum, payload):
+ """
+ Send a received message to the appropriate method.
+
+ @type messageNum: L{int}
+ @param messageNum: The message number.
+
+ @type payload: L{bytes}
+ @param payload: The message payload.
+ """
+ if messageNum < 50 and messageNum in messages:
+ messageType = messages[messageNum][4:]
+ f = getattr(self, 'ssh_%s' % (messageType,), None)
+ if f is not None:
+ f(payload)
+ else:
+ log.msg("couldn't handle %s" % messageType)
+ log.msg(repr(payload))
+ self.sendUnimplemented()
+ elif self.service:
+ log.callWithLogger(self.service, self.service.packetReceived,
+ messageNum, payload)
+ else:
+ log.msg("couldn't handle %s" % messageNum)
+ log.msg(repr(payload))
+ self.sendUnimplemented()
+
+
+ def getPeer(self):
+ """
+ Returns an L{SSHTransportAddress} corresponding to the other (peer)
+ side of this transport.
+
+ @return: L{SSHTransportAddress} for the peer
+ @rtype: L{SSHTransportAddress}
+ @since: 12.1
+ """
+ return address.SSHTransportAddress(self.transport.getPeer())
+
+
+ def getHost(self):
+ """
+ Returns an L{SSHTransportAddress} corresponding to the this side of
+ transport.
+
+ @return: L{SSHTransportAddress} for the peer
+ @rtype: L{SSHTransportAddress}
+ @since: 12.1
+ """
+ return address.SSHTransportAddress(self.transport.getHost())
+
+
+ @property
+ def kexAlg(self):
+ """
+ The key exchange algorithm name agreed between client and server.
+ """
+ return self._kexAlg
+
+
+ @kexAlg.setter
+ def kexAlg(self, value):
+ """
+ Set the key exchange algorithm name.
+ """
+ self._kexAlg = value
+
+ # Client-initiated rekeying looks like this:
+ #
+ # C> MSG_KEXINIT
+ # S> MSG_KEXINIT
+ # C> MSG_KEX_DH_GEX_REQUEST or MSG_KEXDH_INIT
+ # S> MSG_KEX_DH_GEX_GROUP or MSG_KEXDH_REPLY
+ # C> MSG_KEX_DH_GEX_INIT or --
+ # S> MSG_KEX_DH_GEX_REPLY or --
+ # C> MSG_NEWKEYS
+ # S> MSG_NEWKEYS
+ #
+ # Server-initiated rekeying is the same, only the first two messages are
+ # switched.
+
+
+ def ssh_KEXINIT(self, packet):
+ """
+ Called when we receive a MSG_KEXINIT message. Payload::
+ bytes[16] cookie
+ string keyExchangeAlgorithms
+ string keyAlgorithms
+ string incomingEncryptions
+ string outgoingEncryptions
+ string incomingAuthentications
+ string outgoingAuthentications
+ string incomingCompressions
+ string outgoingCompressions
+ string incomingLanguages
+ string outgoingLanguages
+ bool firstPacketFollows
+ unit32 0 (reserved)
+
+ Starts setting up the key exchange, keys, encryptions, and
+ authentications. Extended by ssh_KEXINIT in SSHServerTransport and
+ SSHClientTransport.
+
+ @type packet: L{bytes}
+ @param packet: The message data.
+
+ @return: A L{tuple} of negotiated key exchange algorithms, key
+ algorithms, and unhandled data, or L{None} if something went wrong.
+ """
+ self.otherKexInitPayload = chr(MSG_KEXINIT) + packet
+ # This is useless to us:
+ # cookie = packet[: 16]
+ k = getNS(packet[16:], 10)
+ strings, rest = k[:-1], k[-1]
+ (kexAlgs, keyAlgs, encCS, encSC, macCS, macSC, compCS, compSC, langCS,
+ langSC) = [s.split(b',') for s in strings]
+ # These are the server directions
+ outs = [encSC, macSC, compSC]
+ ins = [encCS, macSC, compCS]
+ if self.isClient:
+ outs, ins = ins, outs # Switch directions
+ server = (self.supportedKeyExchanges, self.supportedPublicKeys,
+ self.supportedCiphers, self.supportedCiphers,
+ self.supportedMACs, self.supportedMACs,
+ self.supportedCompressions, self.supportedCompressions)
+ client = (kexAlgs, keyAlgs, outs[0], ins[0], outs[1], ins[1],
+ outs[2], ins[2])
+ if self.isClient:
+ server, client = client, server
+ self.kexAlg = ffs(client[0], server[0])
+ self.keyAlg = ffs(client[1], server[1])
+ self.nextEncryptions = SSHCiphers(
+ ffs(client[2], server[2]),
+ ffs(client[3], server[3]),
+ ffs(client[4], server[4]),
+ ffs(client[5], server[5]))
+ self.outgoingCompressionType = ffs(client[6], server[6])
+ self.incomingCompressionType = ffs(client[7], server[7])
+ if None in (self.kexAlg, self.keyAlg, self.outgoingCompressionType,
+ self.incomingCompressionType):
+ self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED,
+ b"couldn't match all kex parts")
+ return
+ if None in self.nextEncryptions.__dict__.values():
+ self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED,
+ b"couldn't match all kex parts")
+ return
+ log.msg('kex alg, key alg: %r %r' % (self.kexAlg, self.keyAlg))
+ log.msg('outgoing: %r %r %r' % (self.nextEncryptions.outCipType,
+ self.nextEncryptions.outMACType,
+ self.outgoingCompressionType))
+ log.msg('incoming: %r %r %r' % (self.nextEncryptions.inCipType,
+ self.nextEncryptions.inMACType,
+ self.incomingCompressionType))
+
+ if self._keyExchangeState == self._KEY_EXCHANGE_REQUESTED:
+ self._keyExchangeState = self._KEY_EXCHANGE_PROGRESSING
+ else:
+ self.sendKexInit()
+
+ return kexAlgs, keyAlgs, rest # For SSHServerTransport to use
+
+
+ def ssh_DISCONNECT(self, packet):
+ """
+ Called when we receive a MSG_DISCONNECT message. Payload::
+ long code
+ string description
+
+ This means that the other side has disconnected. Pass the message up
+ and disconnect ourselves.
+
+ @type packet: L{bytes}
+ @param packet: The message data.
+ """
+ reasonCode = struct.unpack('>L', packet[: 4])[0]
+ description, foo = getNS(packet[4:])
+ self.receiveError(reasonCode, description)
+ self.transport.loseConnection()
+
+
+ def ssh_IGNORE(self, packet):
+ """
+ Called when we receive a MSG_IGNORE message. No payload.
+ This means nothing; we simply return.
+
+ @type packet: L{bytes}
+ @param packet: The message data.
+ """
+
+
+ def ssh_UNIMPLEMENTED(self, packet):
+ """
+ Called when we receive a MSG_UNIMPLEMENTED message. Payload::
+ long packet
+
+ This means that the other side did not implement one of our packets.
+
+ @type packet: L{bytes}
+ @param packet: The message data.
+ """
+ seqnum, = struct.unpack('>L', packet)
+ self.receiveUnimplemented(seqnum)
+
+
+ def ssh_DEBUG(self, packet):
+ """
+ Called when we receive a MSG_DEBUG message. Payload::
+ bool alwaysDisplay
+ string message
+ string language
+
+ This means the other side has passed along some debugging info.
+
+ @type packet: L{bytes}
+ @param packet: The message data.
+ """
+ alwaysDisplay = bool(ord(packet[0:1]))
+ message, lang, foo = getNS(packet[1:], 2)
+ self.receiveDebug(alwaysDisplay, message, lang)
+
+
+ def setService(self, service):
+ """
+ Set our service to service and start it running. If we were
+ running a service previously, stop it first.
+
+ @type service: C{SSHService}
+ @param service: The service to attach.
+ """
+ log.msg('starting service %r' % (service.name,))
+ if self.service:
+ self.service.serviceStopped()
+ self.service = service
+ service.transport = self
+ self.service.serviceStarted()
+
+
+ def sendDebug(self, message, alwaysDisplay=False, language=b''):
+ """
+ Send a debug message to the other side.
+
+ @param message: the message to send.
+ @type message: L{str}
+ @param alwaysDisplay: if True, tell the other side to always
+ display this message.
+ @type alwaysDisplay: L{bool}
+ @param language: optionally, the language the message is in.
+ @type language: L{str}
+ """
+ self.sendPacket(MSG_DEBUG, chr(alwaysDisplay) + NS(message) +
+ NS(language))
+
+
+ def sendIgnore(self, message):
+ """
+ Send a message that will be ignored by the other side. This is
+ useful to fool attacks based on guessing packet sizes in the
+ encrypted stream.
+
+ @param message: data to send with the message
+ @type message: L{str}
+ """
+ self.sendPacket(MSG_IGNORE, NS(message))
+
+
+ def sendUnimplemented(self):
+ """
+ Send a message to the other side that the last packet was not
+ understood.
+ """
+ seqnum = self.incomingPacketSequence
+ self.sendPacket(MSG_UNIMPLEMENTED, struct.pack('!L', seqnum))
+
+
+ def sendDisconnect(self, reason, desc):
+ """
+ Send a disconnect message to the other side and then disconnect.
+
+ @param reason: the reason for the disconnect. Should be one of the
+ DISCONNECT_* values.
+ @type reason: L{int}
+ @param desc: a descrption of the reason for the disconnection.
+ @type desc: L{str}
+ """
+ self.sendPacket(
+ MSG_DISCONNECT, struct.pack('>L', reason) + NS(desc) + NS(b''))
+ log.msg('Disconnecting with error, code %s\nreason: %s' % (reason,
+ desc))
+ self.transport.loseConnection()
+
+
+ def _startEphemeralDH(self):
+ """
+ Prepares for a Diffie-Hellman key agreement exchange.
+
+ Creates an ephemeral keypair in the group defined by (self.g,
+ self.p) and stores it.
+ """
+
+ numbers = dh.DHParameterNumbers(self.p, self.g)
+ parameters = numbers.parameters(default_backend())
+ self.dhSecretKey = parameters.generate_private_key()
+ y = self.dhSecretKey.public_key().public_numbers().y
+ self.dhSecretKeyPublicMP = MP(y)
+
+
+ def _finishEphemeralDH(self, remoteDHpublicKey):
+ """
+ Completes the Diffie-Hellman key agreement started by
+ _startEphemeralDH, and forgets the ephemeral secret key.
+
+ @type remoteDHpublicKey: L{int}
+ @rtype: L{bytes}
+ @return: The new shared secret, in SSH C{mpint} format.
+
+ """
+
+ remoteKey = dh.DHPublicNumbers(
+ remoteDHpublicKey,
+ dh.DHParameterNumbers(self.p, self.g)
+ ).public_key(default_backend())
+ secret = self.dhSecretKey.exchange(remoteKey)
+ del self.dhSecretKey
+
+ # The result of a Diffie-Hellman exchange is an integer, but
+ # the Cryptography module returns it as bytes in a form that
+ # is only vaguely documented. We fix it up to match the SSH
+ # MP-integer format as described in RFC4251.
+ secret = secret.lstrip(b'\x00')
+ ch = ord(secret[0:1])
+ if ch & 0x80: # High bit set?
+ # Make room for the sign bit
+ prefix = struct.pack('>L', len(secret) + 1) + b'\x00'
+ else:
+ prefix = struct.pack('>L', len(secret))
+ return prefix + secret
+
+
+ def _getKey(self, c, sharedSecret, exchangeHash):
+ """
+ Get one of the keys for authentication/encryption.
+
+ @type c: L{bytes}
+ @param c: The letter identifying which key this is.
+
+ @type sharedSecret: L{bytes}
+ @param sharedSecret: The shared secret K.
+
+ @type exchangeHash: L{bytes}
+ @param exchangeHash: The hash H from key exchange.
+
+ @rtype: L{bytes}
+ @return: The derived key.
+ """
+ hashProcessor = _kex.getHashProcessor(self.kexAlg)
+ k1 = hashProcessor(sharedSecret + exchangeHash + c + self.sessionID)
+ k1 = k1.digest()
+ k2 = hashProcessor(sharedSecret + exchangeHash + k1).digest()
+ k3 = hashProcessor(sharedSecret + exchangeHash + k1 + k2).digest()
+ k4 = hashProcessor(sharedSecret + exchangeHash + k1 + k2 + k3).digest()
+ return k1 + k2 + k3 + k4
+
+
+ def _keySetup(self, sharedSecret, exchangeHash):
+ """
+ Set up the keys for the connection and sends MSG_NEWKEYS when
+ finished,
+
+ @param sharedSecret: a secret string agreed upon using a Diffie-
+ Hellman exchange, so it is only shared between
+ the server and the client.
+ @type sharedSecret: L{str}
+ @param exchangeHash: A hash of various data known by both sides.
+ @type exchangeHash: L{str}
+ """
+ if not self.sessionID:
+ self.sessionID = exchangeHash
+ initIVCS = self._getKey(b'A', sharedSecret, exchangeHash)
+ initIVSC = self._getKey(b'B', sharedSecret, exchangeHash)
+ encKeyCS = self._getKey(b'C', sharedSecret, exchangeHash)
+ encKeySC = self._getKey(b'D', sharedSecret, exchangeHash)
+ integKeyCS = self._getKey(b'E', sharedSecret, exchangeHash)
+ integKeySC = self._getKey(b'F', sharedSecret, exchangeHash)
+ outs = [initIVSC, encKeySC, integKeySC]
+ ins = [initIVCS, encKeyCS, integKeyCS]
+ if self.isClient: # Reverse for the client
+ log.msg('REVERSE')
+ outs, ins = ins, outs
+ self.nextEncryptions.setKeys(outs[0], outs[1], ins[0], ins[1],
+ outs[2], ins[2])
+ self.sendPacket(MSG_NEWKEYS, b'')
+
+
+ def _newKeys(self):
+ """
+ Called back by a subclass once a I{MSG_NEWKEYS} message has been
+ received. This indicates key exchange has completed and new encryption
+ and compression parameters should be adopted. Any messages which were
+ queued during key exchange will also be flushed.
+ """
+ log.msg('NEW KEYS')
+ self.currentEncryptions = self.nextEncryptions
+ if self.outgoingCompressionType == b'zlib':
+ self.outgoingCompression = zlib.compressobj(6)
+ if self.incomingCompressionType == b'zlib':
+ self.incomingCompression = zlib.decompressobj()
+
+ self._keyExchangeState = self._KEY_EXCHANGE_NONE
+ messages = self._blockedByKeyExchange
+ self._blockedByKeyExchange = None
+ for (messageType, payload) in messages:
+ self.sendPacket(messageType, payload)
+
+
+ def isEncrypted(self, direction="out"):
+ """
+ Check if the connection is encrypted in the given direction.
+
+ @type direction: L{str}
+ @param direction: The direction: one of 'out', 'in', or 'both'.
+
+ @rtype: L{bool}
+ @return: C{True} if it is encrypted.
+ """
+ if direction == "out":
+ return self.currentEncryptions.outCipType != b'none'
+ elif direction == "in":
+ return self.currentEncryptions.inCipType != b'none'
+ elif direction == "both":
+ return self.isEncrypted("in") and self.isEncrypted("out")
+ else:
+ raise TypeError('direction must be "out", "in", or "both"')
+
+
+ def isVerified(self, direction="out"):
+ """
+ Check if the connection is verified/authentication in the given direction.
+
+ @type direction: L{str}
+ @param direction: The direction: one of 'out', 'in', or 'both'.
+
+ @rtype: L{bool}
+ @return: C{True} if it is verified.
+ """
+ if direction == "out":
+ return self.currentEncryptions.outMACType != b'none'
+ elif direction == "in":
+ return self.currentEncryptions.inMACType != b'none'
+ elif direction == "both":
+ return self.isVerified("in") and self.isVerified("out")
+ else:
+ raise TypeError('direction must be "out", "in", or "both"')
+
+
+ def loseConnection(self):
+ """
+ Lose the connection to the other side, sending a
+ DISCONNECT_CONNECTION_LOST message.
+ """
+ self.sendDisconnect(DISCONNECT_CONNECTION_LOST,
+ b"user closed connection")
+
+ # Client methods
+
+
+ def receiveError(self, reasonCode, description):
+ """
+ Called when we receive a disconnect error message from the other
+ side.
+
+ @param reasonCode: the reason for the disconnect, one of the
+ DISCONNECT_ values.
+ @type reasonCode: L{int}
+ @param description: a human-readable description of the
+ disconnection.
+ @type description: L{str}
+ """
+ log.msg('Got remote error, code %s\nreason: %s' % (reasonCode,
+ description))
+
+
+ def receiveUnimplemented(self, seqnum):
+ """
+ Called when we receive an unimplemented packet message from the other
+ side.
+
+ @param seqnum: the sequence number that was not understood.
+ @type seqnum: L{int}
+ """
+ log.msg('other side unimplemented packet #%s' % (seqnum,))
+
+
+ def receiveDebug(self, alwaysDisplay, message, lang):
+ """
+ Called when we receive a debug message from the other side.
+
+ @param alwaysDisplay: if True, this message should always be
+ displayed.
+ @type alwaysDisplay: L{bool}
+ @param message: the debug message
+ @type message: L{str}
+ @param lang: optionally the language the message is in.
+ @type lang: L{str}
+ """
+ if alwaysDisplay:
+ log.msg('Remote Debug Message: %s' % (message,))
+
+
+ def _generateECPrivateKey(self):
+ """
+ Generate an private key for ECDH key exchange.
+
+ @rtype: The appropriate private key type matching C{self.kexAlg}:
+ L{EllipticCurvePrivateKey} for C{ecdh-sha2-nistp*}, or
+ L{X25519PrivateKey} for C{curve25519-sha256}.
+ @return: The generated private key.
+ """
+ if self.kexAlg.startswith(b'ecdh-sha2-nistp'):
+ try:
+ curve = keys._curveTable[b'ecdsa' + self.kexAlg[4:]]
+ except KeyError:
+ raise UnsupportedAlgorithm('unused-key')
+
+ return ec.generate_private_key(curve, default_backend())
+ elif self.kexAlg in (
+ b'curve25519-sha256', b'curve25519-sha256@libssh.org'):
+ return x25519.X25519PrivateKey.generate()
+ else:
+ raise UnsupportedAlgorithm(
+ 'Cannot generate elliptic curve private key for %r' %
+ (self.kexAlg,))
+
+
+ def _encodeECPublicKey(self, ecPub):
+ """
+ Encode an elliptic curve public key to bytes.
+
+ @type ecPub: The appropriate public key type matching
+ C{self.kexAlg}: L{EllipticCurvePublicKey} for
+ C{ecdh-sha2-nistp*}, or L{X25519PublicKey} for
+ C{curve25519-sha256}.
+ @param ecPub: The public key to encode.
+
+ @rtype: L{bytes}
+ @return: The encoded public key.
+ """
+ if self.kexAlg.startswith(b'ecdh-sha2-nistp'):
+ return ecPub.public_bytes(
+ serialization.Encoding.X962,
+ serialization.PublicFormat.UncompressedPoint
+ )
+ elif self.kexAlg in (
+ b'curve25519-sha256', b'curve25519-sha256@libssh.org'):
+ return ecPub.public_bytes(
+ serialization.Encoding.Raw,
+ serialization.PublicFormat.Raw
+ )
+ else:
+ raise UnsupportedAlgorithm(
+ 'Cannot encode elliptic curve public key for %r' %
+ (self.kexAlg,))
+
+
+ def _generateECSharedSecret(self, ecPriv, theirECPubBytes):
+ """
+ Generate a shared secret for ECDH key exchange.
+
+ @type ecPriv: The appropriate private key type matching
+ C{self.kexAlg}: L{EllipticCurvePrivateKey} for
+ C{ecdh-sha2-nistp*}, or L{X25519PrivateKey} for
+ C{curve25519-sha256}.
+ @param ecPriv: Our private key.
+
+ @rtype: L{bytes}
+ @return: The generated shared secret, as an SSH multiple-precision
+ integer.
+ """
+ if self.kexAlg.startswith(b'ecdh-sha2-nistp'):
+ try:
+ curve = keys._curveTable[b'ecdsa' + self.kexAlg[4:]]
+ except KeyError:
+ raise UnsupportedAlgorithm('unused-key')
+
+ theirECPub = ec.EllipticCurvePublicKey.from_encoded_point(
+ curve, theirECPubBytes)
+ sharedSecret = ecPriv.exchange(ec.ECDH(), theirECPub)
+ elif self.kexAlg in (
+ b'curve25519-sha256', b'curve25519-sha256@libssh.org'):
+ theirECPub = x25519.X25519PublicKey.from_public_bytes(
+ theirECPubBytes)
+ sharedSecret = ecPriv.exchange(theirECPub)
+ else:
+ raise UnsupportedAlgorithm(
+ 'Cannot generate elliptic curve shared secret for %r' %
+ (self.kexAlg,))
+
+ return _mpFromBytes(sharedSecret)
+
+
+
+class SSHServerTransport(SSHTransportBase):
+ """
+ SSHServerTransport implements the server side of the SSH protocol.
+
+ @ivar isClient: since we are never the client, this is always False.
+
+ @ivar ignoreNextPacket: if True, ignore the next key exchange packet. This
+ is set when the client sends a guessed key exchange packet but with
+ an incorrect guess.
+
+ @ivar dhGexRequest: the KEX_DH_GEX_REQUEST(_OLD) that the client sent.
+ The key generation needs this to be stored.
+
+ @ivar g: the Diffie-Hellman group generator.
+
+ @ivar p: the Diffie-Hellman group prime.
+ """
+ isClient = False
+ ignoreNextPacket = 0
+
+
+ def ssh_KEXINIT(self, packet):
+ """
+ Called when we receive a MSG_KEXINIT message. For a description
+ of the packet, see SSHTransportBase.ssh_KEXINIT(). Additionally,
+ this method checks if a guessed key exchange packet was sent. If
+ it was sent, and it guessed incorrectly, the next key exchange
+ packet MUST be ignored.
+ """
+ retval = SSHTransportBase.ssh_KEXINIT(self, packet)
+ if not retval: # Disconnected
+ return
+ else:
+ kexAlgs, keyAlgs, rest = retval
+ if ord(rest[0:1]): # Flag first_kex_packet_follows?
+ if (kexAlgs[0] != self.supportedKeyExchanges[0] or
+ keyAlgs[0] != self.supportedPublicKeys[0]):
+ self.ignoreNextPacket = True # Guess was wrong
+
+
+ def _ssh_KEX_ECDH_INIT(self, packet):
+ """
+ Called from L{ssh_KEX_DH_GEX_REQUEST_OLD} to handle
+ elliptic curve key exchanges.
+
+ Payload::
+
+ string client Elliptic Curve Diffie-Hellman public key
+
+ Just like L{_ssh_KEXDH_INIT} this message type is also not dispatched
+ directly. Extra check to determine if this is really KEX_ECDH_INIT
+ is required.
+
+ First we load the host's public/private keys.
+ Then we generate the ECDH public/private keypair for the given curve.
+ With that we generate the shared secret key.
+ Then we compute the hash to sign and send back to the client
+ Along with the server's public key and the ECDH public key.
+
+ @type packet: L{bytes}
+ @param packet: The message data.
+
+ @return: None.
+ """
+ # Get the raw client public key.
+ pktPub, packet = getNS(packet)
+
+ # Get the host's public and private keys
+ pubHostKey = self.factory.publicKeys[self.keyAlg]
+ privHostKey = self.factory.privateKeys[self.keyAlg]
+
+ # Generate the private key
+ ecPriv = self._generateECPrivateKey()
+
+ # Get the public key
+ self.ecPub = ecPriv.public_key()
+ encPub = self._encodeECPublicKey(self.ecPub)
+
+ # Generate the shared secret
+ sharedSecret = self._generateECSharedSecret(ecPriv, pktPub)
+
+ # Finish update and digest
+ h = _kex.getHashProcessor(self.kexAlg)()
+ h.update(NS(self.otherVersionString))
+ h.update(NS(self.ourVersionString))
+ h.update(NS(self.otherKexInitPayload))
+ h.update(NS(self.ourKexInitPayload))
+ h.update(NS(pubHostKey.blob()))
+ h.update(NS(pktPub))
+ h.update(NS(encPub))
+ h.update(sharedSecret)
+ exchangeHash = h.digest()
+
+ self.sendPacket(
+ MSG_KEXDH_REPLY,
+ NS(pubHostKey.blob()) + NS(encPub) +
+ NS(privHostKey.sign(exchangeHash)))
+ self._keySetup(sharedSecret, exchangeHash)
+
+
+ def _ssh_KEXDH_INIT(self, packet):
+ """
+ Called to handle the beginning of a non-group key exchange.
+
+ Unlike other message types, this is not dispatched automatically. It
+ is called from C{ssh_KEX_DH_GEX_REQUEST_OLD} because an extra check is
+ required to determine if this is really a KEXDH_INIT message or if it
+ is a KEX_DH_GEX_REQUEST_OLD message.
+
+ The KEXDH_INIT payload::
+
+ integer e (the client's Diffie-Hellman public key)
+
+ We send the KEXDH_REPLY with our host key and signature.
+
+ @type packet: L{bytes}
+ @param packet: The message data.
+ """
+ clientDHpublicKey, foo = getMP(packet)
+ self.g, self.p = _kex.getDHGeneratorAndPrime(self.kexAlg)
+ self._startEphemeralDH()
+ sharedSecret = self._finishEphemeralDH(clientDHpublicKey)
+ h = sha1()
+ h.update(NS(self.otherVersionString))
+ h.update(NS(self.ourVersionString))
+ h.update(NS(self.otherKexInitPayload))
+ h.update(NS(self.ourKexInitPayload))
+ h.update(NS(self.factory.publicKeys[self.keyAlg].blob()))
+ h.update(MP(clientDHpublicKey))
+ h.update(self.dhSecretKeyPublicMP)
+ h.update(sharedSecret)
+ exchangeHash = h.digest()
+ self.sendPacket(
+ MSG_KEXDH_REPLY,
+ NS(self.factory.publicKeys[self.keyAlg].blob()) +
+ self.dhSecretKeyPublicMP +
+ NS(self.factory.privateKeys[self.keyAlg].sign(exchangeHash)))
+ self._keySetup(sharedSecret, exchangeHash)
+
+
+ def ssh_KEX_DH_GEX_REQUEST_OLD(self, packet):
+ """
+ This represents different key exchange methods that share the same
+ integer value. If the message is determined to be a KEXDH_INIT,
+ L{_ssh_KEXDH_INIT} is called to handle it. If it is a KEX_ECDH_INIT,
+ L{_ssh_KEX_ECDH_INIT} is called.
+ Otherwise, for KEX_DH_GEX_REQUEST_OLD payload::
+
+ integer ideal (ideal size for the Diffie-Hellman prime)
+
+ We send the KEX_DH_GEX_GROUP message with the group that is
+ closest in size to ideal.
+
+ If we were told to ignore the next key exchange packet by ssh_KEXINIT,
+ drop it on the floor and return.
+
+ @type packet: L{bytes}
+ @param packet: The message data.
+ """
+ if self.ignoreNextPacket:
+ self.ignoreNextPacket = 0
+ return
+
+ # KEXDH_INIT, KEX_ECDH_INIT, and KEX_DH_GEX_REQUEST_OLD
+ # have the same value, so use another cue
+ # to decide what kind of message the peer sent us.
+ if _kex.isFixedGroup(self.kexAlg):
+ return self._ssh_KEXDH_INIT(packet)
+ elif _kex.isEllipticCurve(self.kexAlg):
+ return self._ssh_KEX_ECDH_INIT(packet)
+ else:
+ self.dhGexRequest = packet
+ ideal = struct.unpack('>L', packet)[0]
+ self.g, self.p = self.factory.getDHPrime(ideal)
+ self._startEphemeralDH()
+ self.sendPacket(MSG_KEX_DH_GEX_GROUP, MP(self.p) + MP(self.g))
+
+
+ def ssh_KEX_DH_GEX_REQUEST(self, packet):
+ """
+ Called when we receive a MSG_KEX_DH_GEX_REQUEST message. Payload::
+ integer minimum
+ integer ideal
+ integer maximum
+
+ The client is asking for a Diffie-Hellman group between minimum and
+ maximum size, and close to ideal if possible. We reply with a
+ MSG_KEX_DH_GEX_GROUP message.
+
+ If we were told to ignore the next key exchange packet by ssh_KEXINIT,
+ drop it on the floor and return.
+
+ @type packet: L{bytes}
+ @param packet: The message data.
+ """
+ if self.ignoreNextPacket:
+ self.ignoreNextPacket = 0
+ return
+ self.dhGexRequest = packet
+ min, ideal, max = struct.unpack('>3L', packet)
+ self.g, self.p = self.factory.getDHPrime(ideal)
+ self._startEphemeralDH()
+ self.sendPacket(MSG_KEX_DH_GEX_GROUP, MP(self.p) + MP(self.g))
+
+
+ def ssh_KEX_DH_GEX_INIT(self, packet):
+ """
+ Called when we get a MSG_KEX_DH_GEX_INIT message. Payload::
+ integer e (client DH public key)
+
+ We send the MSG_KEX_DH_GEX_REPLY message with our host key and
+ signature.
+
+ @type packet: L{bytes}
+ @param packet: The message data.
+ """
+ clientDHpublicKey, foo = getMP(packet)
+ # TODO: we should also look at the value they send to us and reject
+ # insecure values of f (if g==2 and f has a single '1' bit while the
+ # rest are '0's, then they must have used a small y also).
+
+ # TODO: This could be computed when self.p is set up
+ # or do as openssh does and scan f for a single '1' bit instead
+
+ sharedSecret = self._finishEphemeralDH(clientDHpublicKey)
+ h = _kex.getHashProcessor(self.kexAlg)()
+ h.update(NS(self.otherVersionString))
+ h.update(NS(self.ourVersionString))
+ h.update(NS(self.otherKexInitPayload))
+ h.update(NS(self.ourKexInitPayload))
+ h.update(NS(self.factory.publicKeys[self.keyAlg].blob()))
+ h.update(self.dhGexRequest)
+ h.update(MP(self.p))
+ h.update(MP(self.g))
+ h.update(MP(clientDHpublicKey))
+ h.update(self.dhSecretKeyPublicMP)
+ h.update(sharedSecret)
+ exchangeHash = h.digest()
+ self.sendPacket(
+ MSG_KEX_DH_GEX_REPLY,
+ NS(self.factory.publicKeys[self.keyAlg].blob()) +
+ self.dhSecretKeyPublicMP +
+ NS(self.factory.privateKeys[self.keyAlg].sign(exchangeHash)))
+ self._keySetup(sharedSecret, exchangeHash)
+
+
+ def ssh_NEWKEYS(self, packet):
+ """
+ Called when we get a MSG_NEWKEYS message. No payload.
+ When we get this, the keys have been set on both sides, and we
+ start using them to encrypt and authenticate the connection.
+
+ @type packet: L{bytes}
+ @param packet: The message data.
+ """
+ if packet != b'':
+ self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR,
+ b"NEWKEYS takes no data")
+ return
+ self._newKeys()
+
+
+ def ssh_SERVICE_REQUEST(self, packet):
+ """
+ Called when we get a MSG_SERVICE_REQUEST message. Payload::
+ string serviceName
+
+ The client has requested a service. If we can start the service,
+ start it; otherwise, disconnect with
+ DISCONNECT_SERVICE_NOT_AVAILABLE.
+
+ @type packet: L{bytes}
+ @param packet: The message data.
+ """
+ service, rest = getNS(packet)
+ cls = self.factory.getService(self, service)
+ if not cls:
+ self.sendDisconnect(DISCONNECT_SERVICE_NOT_AVAILABLE,
+ b"don't have service " + service)
+ return
+ else:
+ self.sendPacket(MSG_SERVICE_ACCEPT, NS(service))
+ self.setService(cls())
+
+
+
+class SSHClientTransport(SSHTransportBase):
+ """
+ SSHClientTransport implements the client side of the SSH protocol.
+
+ @ivar isClient: since we are always the client, this is always True.
+
+ @ivar _gotNewKeys: if we receive a MSG_NEWKEYS message before we are
+ ready to transition to the new keys, this is set to True so we
+ can transition when the keys are ready locally.
+
+ @ivar x: our Diffie-Hellman private key.
+
+ @ivar e: our Diffie-Hellman public key.
+
+ @ivar g: the Diffie-Hellman group generator.
+
+ @ivar p: the Diffie-Hellman group prime
+
+ @ivar instance: the SSHService object we are requesting.
+
+ @ivar _dhMinimalGroupSize: Minimal acceptable group size advertised by the
+ client in MSG_KEX_DH_GEX_REQUEST.
+ @type _dhMinimalGroupSize: int
+
+ @ivar _dhMaximalGroupSize: Maximal acceptable group size advertised by the
+ client in MSG_KEX_DH_GEX_REQUEST.
+ @type _dhMaximalGroupSize: int
+
+ @ivar _dhPreferredGroupSize: Preferred group size advertised by the client
+ in MSG_KEX_DH_GEX_REQUEST.
+ @type _dhPreferredGroupSize: int
+ """
+ isClient = True
+
+ # Recommended minimal and maximal values from RFC 4419, 3.
+ _dhMinimalGroupSize = 1024
+ _dhMaximalGroupSize = 8192
+ # FIXME: https://twistedmatrix.com/trac/ticket/8103
+ # This may need to be more dynamic; compare kexgex_client in
+ # OpenSSH.
+ _dhPreferredGroupSize = 2048
+
+ def connectionMade(self):
+ """
+ Called when the connection is started with the server. Just sets
+ up a private instance variable.
+ """
+ SSHTransportBase.connectionMade(self)
+ self._gotNewKeys = 0
+
+
+ def ssh_KEXINIT(self, packet):
+ """
+ Called when we receive a MSG_KEXINIT message. For a description
+ of the packet, see SSHTransportBase.ssh_KEXINIT(). Additionally,
+ this method sends the first key exchange packet.
+
+ If the agreed-upon exchange is ECDH, generate a key pair for the
+ corresponding curve and send the public key.
+
+ If the agreed-upon exchange has a fixed prime/generator group,
+ generate a public key and send it in a MSG_KEXDH_INIT message.
+ Otherwise, ask for a 2048 bit group with a MSG_KEX_DH_GEX_REQUEST
+ message.
+ """
+ if SSHTransportBase.ssh_KEXINIT(self, packet) is None:
+ # Connection was disconnected while doing base processing.
+ # Maybe no common protocols were agreed.
+ return
+ # Are we using ECDH?
+ if _kex.isEllipticCurve(self.kexAlg):
+ # Generate the keys
+ self.ecPriv = self._generateECPrivateKey()
+ self.ecPub = self.ecPriv.public_key()
+
+ # DH_GEX_REQUEST_OLD is the same number we need.
+ self.sendPacket(
+ MSG_KEX_DH_GEX_REQUEST_OLD,
+ NS(self._encodeECPublicKey(self.ecPub))
+ )
+ elif _kex.isFixedGroup(self.kexAlg):
+ # We agreed on a fixed group key exchange algorithm.
+ self.g, self.p = _kex.getDHGeneratorAndPrime(self.kexAlg)
+ self._startEphemeralDH()
+ self.sendPacket(MSG_KEXDH_INIT, self.dhSecretKeyPublicMP)
+ else:
+ # We agreed on a dynamic group. Tell the server what range of
+ # group sizes we accept, and what size we prefer; the server
+ # will then select a group.
+ self.sendPacket(
+ MSG_KEX_DH_GEX_REQUEST,
+ struct.pack(
+ '!LLL',
+ self._dhMinimalGroupSize,
+ self._dhPreferredGroupSize,
+ self._dhMaximalGroupSize,
+ ))
+
+
+ def _ssh_KEX_ECDH_REPLY(self, packet):
+ """
+ Called to handle a reply to a ECDH exchange message(KEX_ECDH_INIT).
+
+ Like the handler for I{KEXDH_INIT}, this message type has an
+ overlapping value. This method is called from C{ssh_KEX_DH_GEX_GROUP}
+ if that method detects a non-group key exchange is in progress.
+
+ Payload::
+
+ string serverHostKey
+ string server Elliptic Curve Diffie-Hellman public key
+ string signature
+
+ We verify the host key and continue if it passes verificiation.
+ Otherwise raise an exception and return.
+
+ @type packet: L{bytes}
+ @param packet: The message data.
+
+ @return: A deferred firing when key exchange is complete.
+ """
+ def _continue_KEX_ECDH_REPLY(ignored, hostKey, pubKey, signature):
+ # Save off the host public key.
+ theirECHost = hostKey
+
+ sharedSecret = self._generateECSharedSecret(self.ecPriv, pubKey)
+
+ h = _kex.getHashProcessor(self.kexAlg)()
+ h.update(NS(self.ourVersionString))
+ h.update(NS(self.otherVersionString))
+ h.update(NS(self.ourKexInitPayload))
+ h.update(NS(self.otherKexInitPayload))
+ h.update(NS(theirECHost))
+ h.update(NS(self._encodeECPublicKey(self.ecPub)))
+ h.update(NS(pubKey))
+ h.update(sharedSecret)
+
+ exchangeHash = h.digest()
+
+ if not keys.Key.fromString(theirECHost).verify(
+ signature, exchangeHash):
+ self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED,
+ b'bad signature')
+ else:
+ self._keySetup(sharedSecret, exchangeHash)
+
+ # Get the host public key,
+ # the raw ECDH public key bytes and the signature
+ hostKey, pubKey, signature, packet = getNS(packet, 3)
+
+ # Easier to comment this out for now than to update all of the tests.
+ #fingerprint = nativeString(base64.b64encode(
+ # sha256(hostKey).digest()))
+
+ fingerprint = b':'.join(
+ [binascii.hexlify(ch) for ch in iterbytes(md5(hostKey).digest())])
+ d = self.verifyHostKey(hostKey, fingerprint)
+ d.addCallback(_continue_KEX_ECDH_REPLY, hostKey, pubKey, signature)
+ d.addErrback(
+ lambda unused: self.sendDisconnect(
+ DISCONNECT_HOST_KEY_NOT_VERIFIABLE, b'bad host key'))
+ return d
+
+
+ def _ssh_KEXDH_REPLY(self, packet):
+ """
+ Called to handle a reply to a non-group key exchange message
+ (KEXDH_INIT).
+
+ Like the handler for I{KEXDH_INIT}, this message type has an
+ overlapping value. This method is called from C{ssh_KEX_DH_GEX_GROUP}
+ if that method detects a non-group key exchange is in progress.
+
+ Payload::
+
+ string serverHostKey
+ integer f (server Diffie-Hellman public key)
+ string signature
+
+ We verify the host key by calling verifyHostKey, then continue in
+ _continueKEXDH_REPLY.
+
+ @type packet: L{bytes}
+ @param packet: The message data.
+
+ @return: A deferred firing when key exchange is complete.
+ """
+ pubKey, packet = getNS(packet)
+ f, packet = getMP(packet)
+ signature, packet = getNS(packet)
+ fingerprint = b':'.join([binascii.hexlify(ch) for ch in
+ iterbytes(md5(pubKey).digest())])
+ d = self.verifyHostKey(pubKey, fingerprint)
+ d.addCallback(self._continueKEXDH_REPLY, pubKey, f, signature)
+ d.addErrback(
+ lambda unused: self.sendDisconnect(
+ DISCONNECT_HOST_KEY_NOT_VERIFIABLE, b'bad host key'))
+ return d
+
+
+ def ssh_KEX_DH_GEX_GROUP(self, packet):
+ """
+ This handles different messages which share an integer value.
+
+ If the key exchange does not have a fixed prime/generator group,
+ we generate a Diffie-Hellman public key and send it in a
+ MSG_KEX_DH_GEX_INIT message.
+
+ Payload::
+ string g (group generator)
+ string p (group prime)
+
+ @type packet: L{bytes}
+ @param packet: The message data.
+ """
+ if _kex.isFixedGroup(self.kexAlg):
+ return self._ssh_KEXDH_REPLY(packet)
+ elif _kex.isEllipticCurve(self.kexAlg):
+ return self._ssh_KEX_ECDH_REPLY(packet)
+ else:
+ self.p, rest = getMP(packet)
+ self.g, rest = getMP(rest)
+ self._startEphemeralDH()
+ self.sendPacket(MSG_KEX_DH_GEX_INIT, self.dhSecretKeyPublicMP)
+
+
+ def _continueKEXDH_REPLY(self, ignored, pubKey, f, signature):
+ """
+ The host key has been verified, so we generate the keys.
+
+ @param ignored: Ignored.
+
+ @param pubKey: the public key blob for the server's public key.
+ @type pubKey: L{str}
+ @param f: the server's Diffie-Hellman public key.
+ @type f: L{long}
+ @param signature: the server's signature, verifying that it has the
+ correct private key.
+ @type signature: L{str}
+ """
+ serverKey = keys.Key.fromString(pubKey)
+ sharedSecret = self._finishEphemeralDH(f)
+ h = sha1()
+ h.update(NS(self.ourVersionString))
+ h.update(NS(self.otherVersionString))
+ h.update(NS(self.ourKexInitPayload))
+ h.update(NS(self.otherKexInitPayload))
+ h.update(NS(pubKey))
+ h.update(self.dhSecretKeyPublicMP)
+ h.update(MP(f))
+ h.update(sharedSecret)
+ exchangeHash = h.digest()
+ if not serverKey.verify(signature, exchangeHash):
+ self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED,
+ b'bad signature')
+ return
+ self._keySetup(sharedSecret, exchangeHash)
+
+
+ def ssh_KEX_DH_GEX_REPLY(self, packet):
+ """
+ Called when we receive a MSG_KEX_DH_GEX_REPLY message. Payload::
+ string server host key
+ integer f (server DH public key)
+
+ We verify the host key by calling verifyHostKey, then continue in
+ _continueGEX_REPLY.
+
+ @type packet: L{bytes}
+ @param packet: The message data.
+
+ @return: A deferred firing once key exchange is complete.
+ """
+ pubKey, packet = getNS(packet)
+ f, packet = getMP(packet)
+ signature, packet = getNS(packet)
+ fingerprint = b':'.join(
+ [binascii.hexlify(c) for c in iterbytes(md5(pubKey).digest())])
+ d = self.verifyHostKey(pubKey, fingerprint)
+ d.addCallback(self._continueGEX_REPLY, pubKey, f, signature)
+ d.addErrback(
+ lambda unused: self.sendDisconnect(
+ DISCONNECT_HOST_KEY_NOT_VERIFIABLE, b'bad host key'))
+ return d
+
+
+ def _continueGEX_REPLY(self, ignored, pubKey, f, signature):
+ """
+ The host key has been verified, so we generate the keys.
+
+ @param ignored: Ignored.
+
+ @param pubKey: the public key blob for the server's public key.
+ @type pubKey: L{str}
+ @param f: the server's Diffie-Hellman public key.
+ @type f: L{long}
+ @param signature: the server's signature, verifying that it has the
+ correct private key.
+ @type signature: L{str}
+ """
+ serverKey = keys.Key.fromString(pubKey)
+ sharedSecret = self._finishEphemeralDH(f)
+ h = _kex.getHashProcessor(self.kexAlg)()
+ h.update(NS(self.ourVersionString))
+ h.update(NS(self.otherVersionString))
+ h.update(NS(self.ourKexInitPayload))
+ h.update(NS(self.otherKexInitPayload))
+ h.update(NS(pubKey))
+ h.update(struct.pack(
+ '!LLL',
+ self._dhMinimalGroupSize,
+ self._dhPreferredGroupSize,
+ self._dhMaximalGroupSize,
+ ))
+ h.update(MP(self.p))
+ h.update(MP(self.g))
+ h.update(self.dhSecretKeyPublicMP)
+ h.update(MP(f))
+ h.update(sharedSecret)
+ exchangeHash = h.digest()
+ if not serverKey.verify(signature, exchangeHash):
+ self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED,
+ b'bad signature')
+ return
+ self._keySetup(sharedSecret, exchangeHash)
+
+
+ def _keySetup(self, sharedSecret, exchangeHash):
+ """
+ See SSHTransportBase._keySetup().
+ """
+ SSHTransportBase._keySetup(self, sharedSecret, exchangeHash)
+ if self._gotNewKeys:
+ self.ssh_NEWKEYS(b'')
+
+
+ def ssh_NEWKEYS(self, packet):
+ """
+ Called when we receive a MSG_NEWKEYS message. No payload.
+ If we've finished setting up our own keys, start using them.
+ Otherwise, remember that we've received this message.
+
+ @type packet: L{bytes}
+ @param packet: The message data.
+ """
+ if packet != b'':
+ self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR,
+ b"NEWKEYS takes no data")
+ return
+ if not self.nextEncryptions.encBlockSize:
+ self._gotNewKeys = 1
+ return
+ self._newKeys()
+ self.connectionSecure()
+
+
+ def ssh_SERVICE_ACCEPT(self, packet):
+ """
+ Called when we receive a MSG_SERVICE_ACCEPT message. Payload::
+ string service name
+
+ Start the service we requested.
+
+ @type packet: L{bytes}
+ @param packet: The message data.
+ """
+ if packet == b'':
+ log.msg('got SERVICE_ACCEPT without payload')
+ else:
+ name = getNS(packet)[0]
+ if name != self.instance.name:
+ self.sendDisconnect(
+ DISCONNECT_PROTOCOL_ERROR,
+ b"received accept for service we did not request")
+ self.setService(self.instance)
+
+
+ def requestService(self, instance):
+ """
+ Request that a service be run over this transport.
+
+ @type instance: subclass of L{twisted.conch.ssh.service.SSHService}
+ @param instance: The service to run.
+ """
+ self.sendPacket(MSG_SERVICE_REQUEST, NS(instance.name))
+ self.instance = instance
+
+ # Client methods
+
+
+ def verifyHostKey(self, hostKey, fingerprint):
+ """
+ Returns a Deferred that gets a callback if it is a valid key, or
+ an errback if not.
+
+ @type hostKey: L{bytes}
+ @param hostKey: The host key to verify.
+
+ @type fingerprint: L{bytes}
+ @param fingerprint: The fingerprint of the key.
+
+ @return: A deferred firing with C{True} if the key is valid.
+ """
+ return defer.fail(NotImplementedError())
+
+
+ def connectionSecure(self):
+ """
+ Called when the encryption has been set up. Generally,
+ requestService() is called to run another service over the transport.
+ """
+ raise NotImplementedError()
+
+
+
+class _NullEncryptionContext(object):
+ """
+ An encryption context that does not actually encrypt anything.
+ """
+ def update(self, data):
+ """
+ 'Encrypt' new data by doing nothing.
+
+ @type data: L{bytes}
+ @param data: The data to 'encrypt'.
+
+ @rtype: L{bytes}
+ @return: The 'encrypted' data.
+ """
+ return data
+
+
+
+class _DummyAlgorithm(object):
+ """
+ An encryption algorithm that does not actually encrypt anything.
+ """
+ block_size = 64
+
+
+
+class _DummyCipher(object):
+ """
+ A cipher for the none encryption method.
+
+ @ivar block_size: the block size of the encryption. In the case of the
+ none cipher, this is 8 bytes.
+ """
+ algorithm = _DummyAlgorithm()
+
+
+ def encryptor(self):
+ """
+ Construct a noop encryptor.
+
+ @return: The encryptor.
+ """
+ return _NullEncryptionContext()
+
+
+ def decryptor(self):
+ """
+ Construct a noop decryptor.
+
+ @return: The decryptor.
+ """
+ return _NullEncryptionContext()
+
+
+
+DH_GENERATOR, DH_PRIME = _kex.getDHGeneratorAndPrime(
+ b'diffie-hellman-group14-sha1')
+
+
+MSG_DISCONNECT = 1
+MSG_IGNORE = 2
+MSG_UNIMPLEMENTED = 3
+MSG_DEBUG = 4
+MSG_SERVICE_REQUEST = 5
+MSG_SERVICE_ACCEPT = 6
+MSG_KEXINIT = 20
+MSG_NEWKEYS = 21
+MSG_KEXDH_INIT = 30
+MSG_KEXDH_REPLY = 31
+MSG_KEX_DH_GEX_REQUEST_OLD = 30
+MSG_KEX_DH_GEX_REQUEST = 34
+MSG_KEX_DH_GEX_GROUP = 31
+MSG_KEX_DH_GEX_INIT = 32
+MSG_KEX_DH_GEX_REPLY = 33
+
+
+
+DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT = 1
+DISCONNECT_PROTOCOL_ERROR = 2
+DISCONNECT_KEY_EXCHANGE_FAILED = 3
+DISCONNECT_RESERVED = 4
+DISCONNECT_MAC_ERROR = 5
+DISCONNECT_COMPRESSION_ERROR = 6
+DISCONNECT_SERVICE_NOT_AVAILABLE = 7
+DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED = 8
+DISCONNECT_HOST_KEY_NOT_VERIFIABLE = 9
+DISCONNECT_CONNECTION_LOST = 10
+DISCONNECT_BY_APPLICATION = 11
+DISCONNECT_TOO_MANY_CONNECTIONS = 12
+DISCONNECT_AUTH_CANCELLED_BY_USER = 13
+DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 14
+DISCONNECT_ILLEGAL_USER_NAME = 15
+
+
+
+messages = {}
+for name, value in list(globals().items()):
+ # Avoid legacy messages which overlap with never ones
+ if name.startswith('MSG_') and not name.startswith('MSG_KEXDH_'):
+ messages[value] = name
+# Check for regressions (#5352)
+if 'MSG_KEXDH_INIT' in messages or 'MSG_KEXDH_REPLY' in messages:
+ raise RuntimeError(
+ "legacy SSH mnemonics should not end up in messages dict")
diff --git a/contrib/python/Twisted/py2/twisted/conch/ssh/userauth.py b/contrib/python/Twisted/py2/twisted/conch/ssh/userauth.py
new file mode 100644
index 0000000000..8fab81603a
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/ssh/userauth.py
@@ -0,0 +1,770 @@
+# -*- test-case-name: twisted.conch.test.test_userauth -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Implementation of the ssh-userauth service.
+Currently implemented authentication types are public-key and password.
+
+Maintainer: Paul Swartz
+"""
+
+from __future__ import absolute_import, division
+
+import struct
+
+from twisted.conch import error, interfaces
+from twisted.conch.ssh import keys, transport, service
+from twisted.conch.ssh.common import NS, getNS
+from twisted.cred import credentials
+from twisted.cred.error import UnauthorizedLogin
+from twisted.internet import defer, reactor
+from twisted.python import failure, log
+from twisted.python.compat import nativeString, _bytesChr as chr
+
+
+
+class SSHUserAuthServer(service.SSHService):
+ """
+ A service implementing the server side of the 'ssh-userauth' service. It
+ is used to authenticate the user on the other side as being able to access
+ this server.
+
+ @ivar name: the name of this service: 'ssh-userauth'
+ @type name: L{bytes}
+ @ivar authenticatedWith: a list of authentication methods that have
+ already been used.
+ @type authenticatedWith: L{list}
+ @ivar loginTimeout: the number of seconds we wait before disconnecting
+ the user for taking too long to authenticate
+ @type loginTimeout: L{int}
+ @ivar attemptsBeforeDisconnect: the number of failed login attempts we
+ allow before disconnecting.
+ @type attemptsBeforeDisconnect: L{int}
+ @ivar loginAttempts: the number of login attempts that have been made
+ @type loginAttempts: L{int}
+ @ivar passwordDelay: the number of seconds to delay when the user gives
+ an incorrect password
+ @type passwordDelay: L{int}
+ @ivar interfaceToMethod: a L{dict} mapping credential interfaces to
+ authentication methods. The server checks to see which of the
+ cred interfaces have checkers and tells the client that those methods
+ are valid for authentication.
+ @type interfaceToMethod: L{dict}
+ @ivar supportedAuthentications: A list of the supported authentication
+ methods.
+ @type supportedAuthentications: L{list} of L{bytes}
+ @ivar user: the last username the client tried to authenticate with
+ @type user: L{bytes}
+ @ivar method: the current authentication method
+ @type method: L{bytes}
+ @ivar nextService: the service the user wants started after authentication
+ has been completed.
+ @type nextService: L{bytes}
+ @ivar portal: the L{twisted.cred.portal.Portal} we are using for
+ authentication
+ @type portal: L{twisted.cred.portal.Portal}
+ @ivar clock: an object with a callLater method. Stubbed out for testing.
+ """
+
+ name = b'ssh-userauth'
+ loginTimeout = 10 * 60 * 60
+ # 10 minutes before we disconnect them
+ attemptsBeforeDisconnect = 20
+ # 20 login attempts before a disconnect
+ passwordDelay = 1 # number of seconds to delay on a failed password
+ clock = reactor
+ interfaceToMethod = {
+ credentials.ISSHPrivateKey : b'publickey',
+ credentials.IUsernamePassword : b'password',
+ }
+
+
+ def serviceStarted(self):
+ """
+ Called when the userauth service is started. Set up instance
+ variables, check if we should allow password authentication (only
+ allow if the outgoing connection is encrypted) and set up a login
+ timeout.
+ """
+ self.authenticatedWith = []
+ self.loginAttempts = 0
+ self.user = None
+ self.nextService = None
+ self.portal = self.transport.factory.portal
+
+ self.supportedAuthentications = []
+ for i in self.portal.listCredentialsInterfaces():
+ if i in self.interfaceToMethod:
+ self.supportedAuthentications.append(self.interfaceToMethod[i])
+
+ if not self.transport.isEncrypted('in'):
+ # don't let us transport password in plaintext
+ if b'password' in self.supportedAuthentications:
+ self.supportedAuthentications.remove(b'password')
+ self._cancelLoginTimeout = self.clock.callLater(
+ self.loginTimeout,
+ self.timeoutAuthentication)
+
+
+ def serviceStopped(self):
+ """
+ Called when the userauth service is stopped. Cancel the login timeout
+ if it's still going.
+ """
+ if self._cancelLoginTimeout:
+ self._cancelLoginTimeout.cancel()
+ self._cancelLoginTimeout = None
+
+
+ def timeoutAuthentication(self):
+ """
+ Called when the user has timed out on authentication. Disconnect
+ with a DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE message.
+ """
+ self._cancelLoginTimeout = None
+ self.transport.sendDisconnect(
+ transport.DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE,
+ b'you took too long')
+
+
+ def tryAuth(self, kind, user, data):
+ """
+ Try to authenticate the user with the given method. Dispatches to a
+ auth_* method.
+
+ @param kind: the authentication method to try.
+ @type kind: L{bytes}
+ @param user: the username the client is authenticating with.
+ @type user: L{bytes}
+ @param data: authentication specific data sent by the client.
+ @type data: L{bytes}
+ @return: A Deferred called back if the method succeeded, or erred back
+ if it failed.
+ @rtype: C{defer.Deferred}
+ """
+ log.msg('%r trying auth %r' % (user, kind))
+ if kind not in self.supportedAuthentications:
+ return defer.fail(
+ error.ConchError('unsupported authentication, failing'))
+ kind = nativeString(kind.replace(b'-', b'_'))
+ f = getattr(self, 'auth_%s' % (kind,), None)
+ if f:
+ ret = f(data)
+ if not ret:
+ return defer.fail(
+ error.ConchError(
+ '%s return None instead of a Deferred'
+ % (kind, )))
+ else:
+ return ret
+ return defer.fail(error.ConchError('bad auth type: %s' % (kind,)))
+
+
+ def ssh_USERAUTH_REQUEST(self, packet):
+ """
+ The client has requested authentication. Payload::
+ string user
+ string next service
+ string method
+ <authentication specific data>
+
+ @type packet: L{bytes}
+ """
+ user, nextService, method, rest = getNS(packet, 3)
+ if user != self.user or nextService != self.nextService:
+ self.authenticatedWith = [] # clear auth state
+ self.user = user
+ self.nextService = nextService
+ self.method = method
+ d = self.tryAuth(method, user, rest)
+ if not d:
+ self._ebBadAuth(
+ failure.Failure(error.ConchError('auth returned none')))
+ return
+ d.addCallback(self._cbFinishedAuth)
+ d.addErrback(self._ebMaybeBadAuth)
+ d.addErrback(self._ebBadAuth)
+ return d
+
+
+ def _cbFinishedAuth(self, result):
+ """
+ The callback when user has successfully been authenticated. For a
+ description of the arguments, see L{twisted.cred.portal.Portal.login}.
+ We start the service requested by the user.
+ """
+ (interface, avatar, logout) = result
+ self.transport.avatar = avatar
+ self.transport.logoutFunction = logout
+ service = self.transport.factory.getService(self.transport,
+ self.nextService)
+ if not service:
+ raise error.ConchError('could not get next service: %s'
+ % self.nextService)
+ log.msg('%r authenticated with %r' % (self.user, self.method))
+ self.transport.sendPacket(MSG_USERAUTH_SUCCESS, b'')
+ self.transport.setService(service())
+
+
+ def _ebMaybeBadAuth(self, reason):
+ """
+ An intermediate errback. If the reason is
+ error.NotEnoughAuthentication, we send a MSG_USERAUTH_FAILURE, but
+ with the partial success indicator set.
+
+ @type reason: L{twisted.python.failure.Failure}
+ """
+ reason.trap(error.NotEnoughAuthentication)
+ self.transport.sendPacket(MSG_USERAUTH_FAILURE,
+ NS(b','.join(self.supportedAuthentications)) + b'\xff')
+
+
+ def _ebBadAuth(self, reason):
+ """
+ The final errback in the authentication chain. If the reason is
+ error.IgnoreAuthentication, we simply return; the authentication
+ method has sent its own response. Otherwise, send a failure message
+ and (if the method is not 'none') increment the number of login
+ attempts.
+
+ @type reason: L{twisted.python.failure.Failure}
+ """
+ if reason.check(error.IgnoreAuthentication):
+ return
+ if self.method != b'none':
+ log.msg('%r failed auth %r' % (self.user, self.method))
+ if reason.check(UnauthorizedLogin):
+ log.msg('unauthorized login: %s' % reason.getErrorMessage())
+ elif reason.check(error.ConchError):
+ log.msg('reason: %s' % reason.getErrorMessage())
+ else:
+ log.msg(reason.getTraceback())
+ self.loginAttempts += 1
+ if self.loginAttempts > self.attemptsBeforeDisconnect:
+ self.transport.sendDisconnect(
+ transport.DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE,
+ b'too many bad auths')
+ return
+ self.transport.sendPacket(
+ MSG_USERAUTH_FAILURE,
+ NS(b','.join(self.supportedAuthentications)) + b'\x00')
+
+
+ def auth_publickey(self, packet):
+ """
+ Public key authentication. Payload::
+ byte has signature
+ string algorithm name
+ string key blob
+ [string signature] (if has signature is True)
+
+ Create a SSHPublicKey credential and verify it using our portal.
+ """
+ hasSig = ord(packet[0:1])
+ algName, blob, rest = getNS(packet[1:], 2)
+
+ try:
+ pubKey = keys.Key.fromString(blob)
+ except keys.BadKeyError:
+ error = "Unsupported key type %s or bad key" % (
+ algName.decode('ascii'),)
+ log.msg(error)
+ return defer.fail(UnauthorizedLogin(error))
+
+ signature = hasSig and getNS(rest)[0] or None
+ if hasSig:
+ b = (NS(self.transport.sessionID) + chr(MSG_USERAUTH_REQUEST) +
+ NS(self.user) + NS(self.nextService) + NS(b'publickey') +
+ chr(hasSig) + NS(pubKey.sshType()) + NS(blob))
+ c = credentials.SSHPrivateKey(self.user, algName, blob, b,
+ signature)
+ return self.portal.login(c, None, interfaces.IConchUser)
+ else:
+ c = credentials.SSHPrivateKey(self.user, algName, blob, None, None)
+ return self.portal.login(c, None,
+ interfaces.IConchUser).addErrback(self._ebCheckKey,
+ packet[1:])
+
+
+ def _ebCheckKey(self, reason, packet):
+ """
+ Called back if the user did not sent a signature. If reason is
+ error.ValidPublicKey then this key is valid for the user to
+ authenticate with. Send MSG_USERAUTH_PK_OK.
+ """
+ reason.trap(error.ValidPublicKey)
+ # if we make it here, it means that the publickey is valid
+ self.transport.sendPacket(MSG_USERAUTH_PK_OK, packet)
+ return failure.Failure(error.IgnoreAuthentication())
+
+
+ def auth_password(self, packet):
+ """
+ Password authentication. Payload::
+ string password
+
+ Make a UsernamePassword credential and verify it with our portal.
+ """
+ password = getNS(packet[1:])[0]
+ c = credentials.UsernamePassword(self.user, password)
+ return self.portal.login(c, None, interfaces.IConchUser).addErrback(
+ self._ebPassword)
+
+
+ def _ebPassword(self, f):
+ """
+ If the password is invalid, wait before sending the failure in order
+ to delay brute-force password guessing.
+ """
+ d = defer.Deferred()
+ self.clock.callLater(self.passwordDelay, d.callback, f)
+ return d
+
+
+
+class SSHUserAuthClient(service.SSHService):
+ """
+ A service implementing the client side of 'ssh-userauth'.
+
+ This service will try all authentication methods provided by the server,
+ making callbacks for more information when necessary.
+
+ @ivar name: the name of this service: 'ssh-userauth'
+ @type name: L{str}
+ @ivar preferredOrder: a list of authentication methods that should be used
+ first, in order of preference, if supported by the server
+ @type preferredOrder: L{list}
+ @ivar user: the name of the user to authenticate as
+ @type user: L{bytes}
+ @ivar instance: the service to start after authentication has finished
+ @type instance: L{service.SSHService}
+ @ivar authenticatedWith: a list of strings of authentication methods we've tried
+ @type authenticatedWith: L{list} of L{bytes}
+ @ivar triedPublicKeys: a list of public key objects that we've tried to
+ authenticate with
+ @type triedPublicKeys: L{list} of L{Key}
+ @ivar lastPublicKey: the last public key object we've tried to authenticate
+ with
+ @type lastPublicKey: L{Key}
+ """
+
+ name = b'ssh-userauth'
+ preferredOrder = [b'publickey', b'password', b'keyboard-interactive']
+
+
+ def __init__(self, user, instance):
+ self.user = user
+ self.instance = instance
+
+
+ def serviceStarted(self):
+ self.authenticatedWith = []
+ self.triedPublicKeys = []
+ self.lastPublicKey = None
+ self.askForAuth(b'none', b'')
+
+
+ def askForAuth(self, kind, extraData):
+ """
+ Send a MSG_USERAUTH_REQUEST.
+
+ @param kind: the authentication method to try.
+ @type kind: L{bytes}
+ @param extraData: method-specific data to go in the packet
+ @type extraData: L{bytes}
+ """
+ self.lastAuth = kind
+ self.transport.sendPacket(MSG_USERAUTH_REQUEST, NS(self.user) +
+ NS(self.instance.name) + NS(kind) + extraData)
+
+
+ def tryAuth(self, kind):
+ """
+ Dispatch to an authentication method.
+
+ @param kind: the authentication method
+ @type kind: L{bytes}
+ """
+ kind = nativeString(kind.replace(b'-', b'_'))
+ log.msg('trying to auth with %s' % (kind,))
+ f = getattr(self,'auth_%s' % (kind,), None)
+ if f:
+ return f()
+
+
+ def _ebAuth(self, ignored, *args):
+ """
+ Generic callback for a failed authentication attempt. Respond by
+ asking for the list of accepted methods (the 'none' method)
+ """
+ self.askForAuth(b'none', b'')
+
+
+ def ssh_USERAUTH_SUCCESS(self, packet):
+ """
+ We received a MSG_USERAUTH_SUCCESS. The server has accepted our
+ authentication, so start the next service.
+ """
+ self.transport.setService(self.instance)
+
+
+ def ssh_USERAUTH_FAILURE(self, packet):
+ """
+ We received a MSG_USERAUTH_FAILURE. Payload::
+ string methods
+ byte partial success
+
+ If partial success is C{True}, then the previous method succeeded but is
+ not sufficient for authentication. C{methods} is a comma-separated list
+ of accepted authentication methods.
+
+ We sort the list of methods by their position in C{self.preferredOrder},
+ removing methods that have already succeeded. We then call
+ C{self.tryAuth} with the most preferred method.
+
+ @param packet: the C{MSG_USERAUTH_FAILURE} payload.
+ @type packet: L{bytes}
+
+ @return: a L{defer.Deferred} that will be callbacked with L{None} as
+ soon as all authentication methods have been tried, or L{None} if no
+ more authentication methods are available.
+ @rtype: C{defer.Deferred} or L{None}
+ """
+ canContinue, partial = getNS(packet)
+ partial = ord(partial)
+ if partial:
+ self.authenticatedWith.append(self.lastAuth)
+
+ def orderByPreference(meth):
+ """
+ Invoked once per authentication method in order to extract a
+ comparison key which is then used for sorting.
+
+ @param meth: the authentication method.
+ @type meth: L{bytes}
+
+ @return: the comparison key for C{meth}.
+ @rtype: L{int}
+ """
+ if meth in self.preferredOrder:
+ return self.preferredOrder.index(meth)
+ else:
+ # put the element at the end of the list.
+ return len(self.preferredOrder)
+
+ canContinue = sorted([meth for meth in canContinue.split(b',')
+ if meth not in self.authenticatedWith],
+ key=orderByPreference)
+
+ log.msg('can continue with: %s' % canContinue)
+ return self._cbUserauthFailure(None, iter(canContinue))
+
+
+ def _cbUserauthFailure(self, result, iterator):
+ if result:
+ return
+ try:
+ method = next(iterator)
+ except StopIteration:
+ self.transport.sendDisconnect(
+ transport.DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE,
+ b'no more authentication methods available')
+ else:
+ d = defer.maybeDeferred(self.tryAuth, method)
+ d.addCallback(self._cbUserauthFailure, iterator)
+ return d
+
+
+ def ssh_USERAUTH_PK_OK(self, packet):
+ """
+ This message (number 60) can mean several different messages depending
+ on the current authentication type. We dispatch to individual methods
+ in order to handle this request.
+ """
+ func = getattr(self, 'ssh_USERAUTH_PK_OK_%s' %
+ nativeString(self.lastAuth.replace(b'-', b'_')), None)
+ if func is not None:
+ return func(packet)
+ else:
+ self.askForAuth(b'none', b'')
+
+
+ def ssh_USERAUTH_PK_OK_publickey(self, packet):
+ """
+ This is MSG_USERAUTH_PK. Our public key is valid, so we create a
+ signature and try to authenticate with it.
+ """
+ publicKey = self.lastPublicKey
+ b = (NS(self.transport.sessionID) + chr(MSG_USERAUTH_REQUEST) +
+ NS(self.user) + NS(self.instance.name) + NS(b'publickey') +
+ b'\x01' + NS(publicKey.sshType()) + NS(publicKey.blob()))
+ d = self.signData(publicKey, b)
+ if not d:
+ self.askForAuth(b'none', b'')
+ # this will fail, we'll move on
+ return
+ d.addCallback(self._cbSignedData)
+ d.addErrback(self._ebAuth)
+
+
+ def ssh_USERAUTH_PK_OK_password(self, packet):
+ """
+ This is MSG_USERAUTH_PASSWD_CHANGEREQ. The password given has expired.
+ We ask for an old password and a new password, then send both back to
+ the server.
+ """
+ prompt, language, rest = getNS(packet, 2)
+ self._oldPass = self._newPass = None
+ d = self.getPassword(b'Old Password: ')
+ d = d.addCallbacks(self._setOldPass, self._ebAuth)
+ d.addCallback(lambda ignored: self.getPassword(prompt))
+ d.addCallbacks(self._setNewPass, self._ebAuth)
+
+
+ def ssh_USERAUTH_PK_OK_keyboard_interactive(self, packet):
+ """
+ This is MSG_USERAUTH_INFO_RESPONSE. The server has sent us the
+ questions it wants us to answer, so we ask the user and sent the
+ responses.
+ """
+ name, instruction, lang, data = getNS(packet, 3)
+ numPrompts = struct.unpack('!L', data[:4])[0]
+ data = data[4:]
+ prompts = []
+ for i in range(numPrompts):
+ prompt, data = getNS(data)
+ echo = bool(ord(data[0:1]))
+ data = data[1:]
+ prompts.append((prompt, echo))
+ d = self.getGenericAnswers(name, instruction, prompts)
+ d.addCallback(self._cbGenericAnswers)
+ d.addErrback(self._ebAuth)
+
+
+ def _cbSignedData(self, signedData):
+ """
+ Called back out of self.signData with the signed data. Send the
+ authentication request with the signature.
+
+ @param signedData: the data signed by the user's private key.
+ @type signedData: L{bytes}
+ """
+ publicKey = self.lastPublicKey
+ self.askForAuth(b'publickey', b'\x01' + NS(publicKey.sshType()) +
+ NS(publicKey.blob()) + NS(signedData))
+
+
+ def _setOldPass(self, op):
+ """
+ Called back when we are choosing a new password. Simply store the old
+ password for now.
+
+ @param op: the old password as entered by the user
+ @type op: L{bytes}
+ """
+ self._oldPass = op
+
+
+ def _setNewPass(self, np):
+ """
+ Called back when we are choosing a new password. Get the old password
+ and send the authentication message with both.
+
+ @param np: the new password as entered by the user
+ @type np: L{bytes}
+ """
+ op = self._oldPass
+ self._oldPass = None
+ self.askForAuth(b'password', b'\xff' + NS(op) + NS(np))
+
+
+ def _cbGenericAnswers(self, responses):
+ """
+ Called back when we are finished answering keyboard-interactive
+ questions. Send the info back to the server in a
+ MSG_USERAUTH_INFO_RESPONSE.
+
+ @param responses: a list of L{bytes} responses
+ @type responses: L{list}
+ """
+ data = struct.pack('!L', len(responses))
+ for r in responses:
+ data += NS(r.encode('UTF8'))
+ self.transport.sendPacket(MSG_USERAUTH_INFO_RESPONSE, data)
+
+
+ def auth_publickey(self):
+ """
+ Try to authenticate with a public key. Ask the user for a public key;
+ if the user has one, send the request to the server and return True.
+ Otherwise, return False.
+
+ @rtype: L{bool}
+ """
+ d = defer.maybeDeferred(self.getPublicKey)
+ d.addBoth(self._cbGetPublicKey)
+ return d
+
+
+ def _cbGetPublicKey(self, publicKey):
+ if not isinstance(publicKey, keys.Key): # failure or None
+ publicKey = None
+ if publicKey is not None:
+ self.lastPublicKey = publicKey
+ self.triedPublicKeys.append(publicKey)
+ log.msg('using key of type %s' % publicKey.type())
+ self.askForAuth(b'publickey', b'\x00' + NS(publicKey.sshType()) +
+ NS(publicKey.blob()))
+ return True
+ else:
+ return False
+
+
+ def auth_password(self):
+ """
+ Try to authenticate with a password. Ask the user for a password.
+ If the user will return a password, return True. Otherwise, return
+ False.
+
+ @rtype: L{bool}
+ """
+ d = self.getPassword()
+ if d:
+ d.addCallbacks(self._cbPassword, self._ebAuth)
+ return True
+ else: # returned None, don't do password auth
+ return False
+
+
+ def auth_keyboard_interactive(self):
+ """
+ Try to authenticate with keyboard-interactive authentication. Send
+ the request to the server and return True.
+
+ @rtype: L{bool}
+ """
+ log.msg('authing with keyboard-interactive')
+ self.askForAuth(b'keyboard-interactive', NS(b'') + NS(b''))
+ return True
+
+
+ def _cbPassword(self, password):
+ """
+ Called back when the user gives a password. Send the request to the
+ server.
+
+ @param password: the password the user entered
+ @type password: L{bytes}
+ """
+ self.askForAuth(b'password', b'\x00' + NS(password))
+
+
+ def signData(self, publicKey, signData):
+ """
+ Sign the given data with the given public key.
+
+ By default, this will call getPrivateKey to get the private key,
+ then sign the data using Key.sign().
+
+ This method is factored out so that it can be overridden to use
+ alternate methods, such as a key agent.
+
+ @param publicKey: The public key object returned from L{getPublicKey}
+ @type publicKey: L{keys.Key}
+
+ @param signData: the data to be signed by the private key.
+ @type signData: L{bytes}
+ @return: a Deferred that's called back with the signature
+ @rtype: L{defer.Deferred}
+ """
+ key = self.getPrivateKey()
+ if not key:
+ return
+ return key.addCallback(self._cbSignData, signData)
+
+
+ def _cbSignData(self, privateKey, signData):
+ """
+ Called back when the private key is returned. Sign the data and
+ return the signature.
+
+ @param privateKey: the private key object
+ @type publicKey: L{keys.Key}
+ @param signData: the data to be signed by the private key.
+ @type signData: L{bytes}
+ @return: the signature
+ @rtype: L{bytes}
+ """
+ return privateKey.sign(signData)
+
+
+ def getPublicKey(self):
+ """
+ Return a public key for the user. If no more public keys are
+ available, return L{None}.
+
+ This implementation always returns L{None}. Override it in a
+ subclass to actually find and return a public key object.
+
+ @rtype: L{Key} or L{None}
+ """
+ return None
+
+
+ def getPrivateKey(self):
+ """
+ Return a L{Deferred} that will be called back with the private key
+ object corresponding to the last public key from getPublicKey().
+ If the private key is not available, errback on the Deferred.
+
+ @rtype: L{Deferred} called back with L{Key}
+ """
+ return defer.fail(NotImplementedError())
+
+
+ def getPassword(self, prompt = None):
+ """
+ Return a L{Deferred} that will be called back with a password.
+ prompt is a string to display for the password, or None for a generic
+ 'user@hostname's password: '.
+
+ @type prompt: L{bytes}/L{None}
+ @rtype: L{defer.Deferred}
+ """
+ return defer.fail(NotImplementedError())
+
+
+ def getGenericAnswers(self, name, instruction, prompts):
+ """
+ Returns a L{Deferred} with the responses to the promopts.
+
+ @param name: The name of the authentication currently in progress.
+ @param instruction: Describes what the authentication wants.
+ @param prompts: A list of (prompt, echo) pairs, where prompt is a
+ string to display and echo is a boolean indicating whether the
+ user's response should be echoed as they type it.
+ """
+ return defer.fail(NotImplementedError())
+
+
+MSG_USERAUTH_REQUEST = 50
+MSG_USERAUTH_FAILURE = 51
+MSG_USERAUTH_SUCCESS = 52
+MSG_USERAUTH_BANNER = 53
+MSG_USERAUTH_INFO_RESPONSE = 61
+MSG_USERAUTH_PK_OK = 60
+
+messages = {}
+for k, v in list(locals().items()):
+ if k[:4] == 'MSG_':
+ messages[v] = k
+
+SSHUserAuthServer.protocolMessages = messages
+SSHUserAuthClient.protocolMessages = messages
+del messages
+del v
+
+# Doubles, not included in the protocols' mappings
+MSG_USERAUTH_PASSWD_CHANGEREQ = 60
+MSG_USERAUTH_INFO_REQUEST = 60
diff --git a/contrib/python/Twisted/py2/twisted/conch/stdio.py b/contrib/python/Twisted/py2/twisted/conch/stdio.py
new file mode 100644
index 0000000000..78a88d8886
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/stdio.py
@@ -0,0 +1,120 @@
+# -*- test-case-name: twisted.conch.test.test_manhole -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Asynchronous local terminal input handling
+
+@author: Jp Calderone
+"""
+
+import os, tty, sys, termios
+
+from twisted.internet import reactor, stdio, protocol, defer
+from twisted.python import failure, reflect, log
+
+from twisted.conch.insults.insults import ServerProtocol
+from twisted.conch.manhole import ColoredManhole
+
+class UnexpectedOutputError(Exception):
+ pass
+
+
+
+class TerminalProcessProtocol(protocol.ProcessProtocol):
+ def __init__(self, proto):
+ self.proto = proto
+ self.onConnection = defer.Deferred()
+
+
+ def connectionMade(self):
+ self.proto.makeConnection(self)
+ self.onConnection.callback(None)
+ self.onConnection = None
+
+
+ def write(self, data):
+ """
+ Write to the terminal.
+
+ @param data: Data to write.
+ @type data: L{bytes}
+ """
+ self.transport.write(data)
+
+
+ def outReceived(self, data):
+ """
+ Receive data from the terminal.
+
+ @param data: Data received.
+ @type data: L{bytes}
+ """
+ self.proto.dataReceived(data)
+
+
+ def errReceived(self, data):
+ """
+ Report an error.
+
+ @param data: Data to include in L{Failure}.
+ @type data: L{bytes}
+ """
+ self.transport.loseConnection()
+ if self.proto is not None:
+ self.proto.connectionLost(failure.Failure(UnexpectedOutputError(data)))
+ self.proto = None
+
+
+ def childConnectionLost(self, childFD):
+ if self.proto is not None:
+ self.proto.childConnectionLost(childFD)
+
+
+ def processEnded(self, reason):
+ if self.proto is not None:
+ self.proto.connectionLost(reason)
+ self.proto = None
+
+
+
+class ConsoleManhole(ColoredManhole):
+ """
+ A manhole protocol specifically for use with L{stdio.StandardIO}.
+ """
+ def connectionLost(self, reason):
+ """
+ When the connection is lost, there is nothing more to do. Stop the
+ reactor so that the process can exit.
+ """
+ reactor.stop()
+
+
+
+def runWithProtocol(klass):
+ fd = sys.__stdin__.fileno()
+ oldSettings = termios.tcgetattr(fd)
+ tty.setraw(fd)
+ try:
+ stdio.StandardIO(ServerProtocol(klass))
+ reactor.run()
+ finally:
+ termios.tcsetattr(fd, termios.TCSANOW, oldSettings)
+ os.write(fd, b"\r\x1bc\r")
+
+
+
+def main(argv=None):
+ log.startLogging(open('child.log', 'w'))
+
+ if argv is None:
+ argv = sys.argv[1:]
+ if argv:
+ klass = reflect.namedClass(argv[0])
+ else:
+ klass = ConsoleManhole
+ runWithProtocol(klass)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/contrib/python/Twisted/py2/twisted/conch/tap.py b/contrib/python/Twisted/py2/twisted/conch/tap.py
new file mode 100644
index 0000000000..f622854a06
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/tap.py
@@ -0,0 +1,86 @@
+# -*- test-case-name: twisted.conch.test.test_tap -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Support module for making SSH servers with twistd.
+"""
+
+from twisted.conch import unix
+from twisted.conch import checkers as conch_checkers
+from twisted.conch.openssh_compat import factory
+from twisted.cred import portal, strcred
+from twisted.python import usage
+from twisted.application import strports
+
+
+class Options(usage.Options, strcred.AuthOptionMixin):
+ synopsis = "[-i <interface>] [-p <port>] [-d <dir>] "
+ longdesc = ("Makes a Conch SSH server. If no authentication methods are "
+ "specified, the default authentication methods are UNIX passwords "
+ "and SSH public keys. If --auth options are "
+ "passed, only the measures specified will be used.")
+ optParameters = [
+ ["interface", "i", "", "local interface to which we listen"],
+ ["port", "p", "tcp:22", "Port on which to listen"],
+ ["data", "d", "/etc", "directory to look for host keys in"],
+ ["moduli", "", None, "directory to look for moduli in "
+ "(if different from --data)"]
+ ]
+ compData = usage.Completions(
+ optActions={"data": usage.CompleteDirs(descr="data directory"),
+ "moduli": usage.CompleteDirs(descr="moduli directory"),
+ "interface": usage.CompleteNetInterfaces()}
+ )
+
+
+ def __init__(self, *a, **kw):
+ usage.Options.__init__(self, *a, **kw)
+
+ # Call the default addCheckers (for backwards compatibility) that will
+ # be used if no --auth option is provided - note that conch's
+ # UNIXPasswordDatabase is used, instead of twisted.plugins.cred_unix's
+ # checker
+ super(Options, self).addChecker(conch_checkers.UNIXPasswordDatabase())
+ super(Options, self).addChecker(conch_checkers.SSHPublicKeyChecker(
+ conch_checkers.UNIXAuthorizedKeysFiles()))
+ self._usingDefaultAuth = True
+
+
+ def addChecker(self, checker):
+ """
+ Add the checker specified. If any checkers are added, the default
+ checkers are automatically cleared and the only checkers will be the
+ specified one(s).
+ """
+ if self._usingDefaultAuth:
+ self['credCheckers'] = []
+ self['credInterfaces'] = {}
+ self._usingDefaultAuth = False
+ super(Options, self).addChecker(checker)
+
+
+
+def makeService(config):
+ """
+ Construct a service for operating a SSH server.
+
+ @param config: An L{Options} instance specifying server options, including
+ where server keys are stored and what authentication methods to use.
+
+ @return: A L{twisted.application.service.IService} provider which contains
+ the requested SSH server.
+ """
+
+ t = factory.OpenSSHFactory()
+
+ r = unix.UnixSSHRealm()
+ t.portal = portal.Portal(r, config.get('credCheckers', []))
+ t.dataRoot = config['data']
+ t.moduliRoot = config['moduli'] or config['data']
+
+ port = config['port']
+ if config['interface']:
+ # Add warning here
+ port += ':interface=' + config['interface']
+ return strports.service(port, t)
diff --git a/contrib/python/Twisted/py2/twisted/conch/telnet.py b/contrib/python/Twisted/py2/twisted/conch/telnet.py
new file mode 100644
index 0000000000..daa27b8aba
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/telnet.py
@@ -0,0 +1,1194 @@
+# -*- test-case-name: twisted.conch.test.test_telnet -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Telnet protocol implementation.
+
+@author: Jean-Paul Calderone
+"""
+
+from __future__ import absolute_import, division
+
+import struct
+
+from zope.interface import implementer
+
+from twisted.internet import protocol, interfaces as iinternet, defer
+from twisted.python import log
+from twisted.python.compat import _bytesChr as chr, iterbytes
+
+MODE = chr(1)
+EDIT = 1
+TRAPSIG = 2
+MODE_ACK = 4
+SOFT_TAB = 8
+LIT_ECHO = 16
+
+# Characters gleaned from the various (and conflicting) RFCs. Not all of these are correct.
+
+NULL = chr(0) # No operation.
+BEL = chr(7) # Produces an audible or
+ # visible signal (which does
+ # NOT move the print head).
+BS = chr(8) # Moves the print head one
+ # character position towards
+ # the left margin.
+HT = chr(9) # Moves the printer to the
+ # next horizontal tab stop.
+ # It remains unspecified how
+ # either party determines or
+ # establishes where such tab
+ # stops are located.
+LF = chr(10) # Moves the printer to the
+ # next print line, keeping the
+ # same horizontal position.
+VT = chr(11) # Moves the printer to the
+ # next vertical tab stop. It
+ # remains unspecified how
+ # either party determines or
+ # establishes where such tab
+ # stops are located.
+FF = chr(12) # Moves the printer to the top
+ # of the next page, keeping
+ # the same horizontal position.
+CR = chr(13) # Moves the printer to the left
+ # margin of the current line.
+
+ECHO = chr(1) # User-to-Server: Asks the server to send
+ # Echos of the transmitted data.
+SGA = chr(3) # Suppress Go Ahead. Go Ahead is silly
+ # and most modern servers should suppress
+ # it.
+NAWS = chr(31) # Negotiate About Window Size. Indicate that
+ # information about the size of the terminal
+ # can be communicated.
+LINEMODE = chr(34) # Allow line buffering to be
+ # negotiated about.
+
+SE = chr(240) # End of subnegotiation parameters.
+NOP = chr(241) # No operation.
+DM = chr(242) # "Data Mark": The data stream portion
+ # of a Synch. This should always be
+ # accompanied by a TCP Urgent
+ # notification.
+BRK = chr(243) # NVT character Break.
+IP = chr(244) # The function Interrupt Process.
+AO = chr(245) # The function Abort Output
+AYT = chr(246) # The function Are You There.
+EC = chr(247) # The function Erase Character.
+EL = chr(248) # The function Erase Line
+GA = chr(249) # The Go Ahead signal.
+SB = chr(250) # Indicates that what follows is
+ # subnegotiation of the indicated
+ # option.
+WILL = chr(251) # Indicates the desire to begin
+ # performing, or confirmation that
+ # you are now performing, the
+ # indicated option.
+WONT = chr(252) # Indicates the refusal to perform,
+ # or continue performing, the
+ # indicated option.
+DO = chr(253) # Indicates the request that the
+ # other party perform, or
+ # confirmation that you are expecting
+ # the other party to perform, the
+ # indicated option.
+DONT = chr(254) # Indicates the demand that the
+ # other party stop performing,
+ # or confirmation that you are no
+ # longer expecting the other party
+ # to perform, the indicated option.
+IAC = chr(255) # Data Byte 255. Introduces a
+ # telnet command.
+
+LINEMODE_MODE = chr(1)
+LINEMODE_EDIT = chr(1)
+LINEMODE_TRAPSIG = chr(2)
+LINEMODE_MODE_ACK = chr(4)
+LINEMODE_SOFT_TAB = chr(8)
+LINEMODE_LIT_ECHO = chr(16)
+LINEMODE_FORWARDMASK = chr(2)
+LINEMODE_SLC = chr(3)
+LINEMODE_SLC_SYNCH = chr(1)
+LINEMODE_SLC_BRK = chr(2)
+LINEMODE_SLC_IP = chr(3)
+LINEMODE_SLC_AO = chr(4)
+LINEMODE_SLC_AYT = chr(5)
+LINEMODE_SLC_EOR = chr(6)
+LINEMODE_SLC_ABORT = chr(7)
+LINEMODE_SLC_EOF = chr(8)
+LINEMODE_SLC_SUSP = chr(9)
+LINEMODE_SLC_EC = chr(10)
+LINEMODE_SLC_EL = chr(11)
+
+LINEMODE_SLC_EW = chr(12)
+LINEMODE_SLC_RP = chr(13)
+LINEMODE_SLC_LNEXT = chr(14)
+LINEMODE_SLC_XON = chr(15)
+LINEMODE_SLC_XOFF = chr(16)
+LINEMODE_SLC_FORW1 = chr(17)
+LINEMODE_SLC_FORW2 = chr(18)
+LINEMODE_SLC_MCL = chr(19)
+LINEMODE_SLC_MCR = chr(20)
+LINEMODE_SLC_MCWL = chr(21)
+LINEMODE_SLC_MCWR = chr(22)
+LINEMODE_SLC_MCBOL = chr(23)
+LINEMODE_SLC_MCEOL = chr(24)
+LINEMODE_SLC_INSRT = chr(25)
+LINEMODE_SLC_OVER = chr(26)
+LINEMODE_SLC_ECR = chr(27)
+LINEMODE_SLC_EWR = chr(28)
+LINEMODE_SLC_EBOL = chr(29)
+LINEMODE_SLC_EEOL = chr(30)
+
+LINEMODE_SLC_DEFAULT = chr(3)
+LINEMODE_SLC_VALUE = chr(2)
+LINEMODE_SLC_CANTCHANGE = chr(1)
+LINEMODE_SLC_NOSUPPORT = chr(0)
+LINEMODE_SLC_LEVELBITS = chr(3)
+
+LINEMODE_SLC_ACK = chr(128)
+LINEMODE_SLC_FLUSHIN = chr(64)
+LINEMODE_SLC_FLUSHOUT = chr(32)
+LINEMODE_EOF = chr(236)
+LINEMODE_SUSP = chr(237)
+LINEMODE_ABORT = chr(238)
+
+class ITelnetProtocol(iinternet.IProtocol):
+ def unhandledCommand(command, argument):
+ """
+ A command was received but not understood.
+
+ @param command: the command received.
+ @type command: L{str}, a single character.
+ @param argument: the argument to the received command.
+ @type argument: L{str}, a single character, or None if the command that
+ was unhandled does not provide an argument.
+ """
+
+
+ def unhandledSubnegotiation(command, data):
+ """
+ A subnegotiation command was received but not understood.
+
+ @param command: the command being subnegotiated. That is, the first
+ byte after the SB command.
+ @type command: L{str}, a single character.
+ @param data: all other bytes of the subneogation. That is, all but the
+ first bytes between SB and SE, with IAC un-escaping applied.
+ @type data: L{bytes}, each a single character
+ """
+
+
+ def enableLocal(option):
+ """
+ Enable the given option locally.
+
+ This should enable the given option on this side of the
+ telnet connection and return True. If False is returned,
+ the option will be treated as still disabled and the peer
+ will be notified.
+
+ @param option: the option to be enabled.
+ @type option: L{bytes}, a single character.
+ """
+
+
+ def enableRemote(option):
+ """
+ Indicate whether the peer should be allowed to enable this option.
+
+ Returns True if the peer should be allowed to enable this option,
+ False otherwise.
+
+ @param option: the option to be enabled.
+ @type option: L{bytes}, a single character.
+ """
+
+
+ def disableLocal(option):
+ """
+ Disable the given option locally.
+
+ Unlike enableLocal, this method cannot fail. The option must be
+ disabled.
+
+ @param option: the option to be disabled.
+ @type option: L{bytes}, a single character.
+ """
+
+
+ def disableRemote(option):
+ """
+ Indicate that the peer has disabled this option.
+
+ @param option: the option to be disabled.
+ @type option: L{bytes}, a single character.
+ """
+
+
+
+class ITelnetTransport(iinternet.ITransport):
+ def do(option):
+ """
+ Indicate a desire for the peer to begin performing the given option.
+
+ Returns a Deferred that fires with True when the peer begins performing
+ the option, or fails with L{OptionRefused} when the peer refuses to
+ perform it. If the peer is already performing the given option, the
+ Deferred will fail with L{AlreadyEnabled}. If a negotiation regarding
+ this option is already in progress, the Deferred will fail with
+ L{AlreadyNegotiating}.
+
+ Note: It is currently possible that this Deferred will never fire,
+ if the peer never responds, or if the peer believes the option to
+ already be enabled.
+ """
+
+
+ def dont(option):
+ """
+ Indicate a desire for the peer to cease performing the given option.
+
+ Returns a Deferred that fires with True when the peer ceases performing
+ the option. If the peer is not performing the given option, the
+ Deferred will fail with L{AlreadyDisabled}. If negotiation regarding
+ this option is already in progress, the Deferred will fail with
+ L{AlreadyNegotiating}.
+
+ Note: It is currently possible that this Deferred will never fire,
+ if the peer never responds, or if the peer believes the option to
+ already be disabled.
+ """
+
+
+ def will(option):
+ """
+ Indicate our willingness to begin performing this option locally.
+
+ Returns a Deferred that fires with True when the peer agrees to allow us
+ to begin performing this option, or fails with L{OptionRefused} if the
+ peer refuses to allow us to begin performing it. If the option is
+ already enabled locally, the Deferred will fail with L{AlreadyEnabled}.
+ If negotiation regarding this option is already in progress, the
+ Deferred will fail with L{AlreadyNegotiating}.
+
+ Note: It is currently possible that this Deferred will never fire,
+ if the peer never responds, or if the peer believes the option to
+ already be enabled.
+ """
+
+
+ def wont(option):
+ """
+ Indicate that we will stop performing the given option.
+
+ Returns a Deferred that fires with True when the peer acknowledges
+ we have stopped performing this option. If the option is already
+ disabled locally, the Deferred will fail with L{AlreadyDisabled}.
+ If negotiation regarding this option is already in progress,
+ the Deferred will fail with L{AlreadyNegotiating}.
+
+ Note: It is currently possible that this Deferred will never fire,
+ if the peer never responds, or if the peer believes the option to
+ already be disabled.
+ """
+
+
+ def requestNegotiation(about, data):
+ """
+ Send a subnegotiation request.
+
+ @param about: A byte indicating the feature being negotiated.
+ @param data: Any number of L{bytes} containing specific information
+ about the negotiation being requested. No values in this string
+ need to be escaped, as this function will escape any value which
+ requires it.
+ """
+
+
+
+class TelnetError(Exception):
+ pass
+
+
+
+class NegotiationError(TelnetError):
+ def __str__(self):
+ return self.__class__.__module__ + '.' + self.__class__.__name__ + ':' + repr(self.args[0])
+
+
+
+class OptionRefused(NegotiationError):
+ pass
+
+
+
+class AlreadyEnabled(NegotiationError):
+ pass
+
+
+
+class AlreadyDisabled(NegotiationError):
+ pass
+
+
+
+class AlreadyNegotiating(NegotiationError):
+ pass
+
+
+
+@implementer(ITelnetProtocol)
+class TelnetProtocol(protocol.Protocol):
+ def unhandledCommand(self, command, argument):
+ pass
+
+
+ def unhandledSubnegotiation(self, command, data):
+ pass
+
+
+ def enableLocal(self, option):
+ pass
+
+
+ def enableRemote(self, option):
+ pass
+
+
+ def disableLocal(self, option):
+ pass
+
+
+ def disableRemote(self, option):
+ pass
+
+
+
+class Telnet(protocol.Protocol):
+ """
+ @ivar commandMap: A mapping of bytes to callables. When a
+ telnet command is received, the command byte (the first byte
+ after IAC) is looked up in this dictionary. If a callable is
+ found, it is invoked with the argument of the command, or None
+ if the command takes no argument. Values should be added to
+ this dictionary if commands wish to be handled. By default,
+ only WILL, WONT, DO, and DONT are handled. These should not
+ be overridden, as this class handles them correctly and
+ provides an API for interacting with them.
+
+ @ivar negotiationMap: A mapping of bytes to callables. When
+ a subnegotiation command is received, the command byte (the
+ first byte after SB) is looked up in this dictionary. If
+ a callable is found, it is invoked with the argument of the
+ subnegotiation. Values should be added to this dictionary if
+ subnegotiations are to be handled. By default, no values are
+ handled.
+
+ @ivar options: A mapping of option bytes to their current
+ state. This state is likely of little use to user code.
+ Changes should not be made to it.
+
+ @ivar state: A string indicating the current parse state. It
+ can take on the values "data", "escaped", "command", "newline",
+ "subnegotiation", and "subnegotiation-escaped". Changes
+ should not be made to it.
+
+ @ivar transport: This protocol's transport object.
+ """
+
+ # One of a lot of things
+ state = 'data'
+
+ def __init__(self):
+ self.options = {}
+ self.negotiationMap = {}
+ self.commandMap = {
+ WILL: self.telnet_WILL,
+ WONT: self.telnet_WONT,
+ DO: self.telnet_DO,
+ DONT: self.telnet_DONT}
+
+
+ def _write(self, data):
+ self.transport.write(data)
+
+
+ class _OptionState:
+ """
+ Represents the state of an option on both sides of a telnet
+ connection.
+
+ @ivar us: The state of the option on this side of the connection.
+
+ @ivar him: The state of the option on the other side of the
+ connection.
+ """
+ class _Perspective:
+ """
+ Represents the state of an option on side of the telnet
+ connection. Some options can be enabled on a particular side of
+ the connection (RFC 1073 for example: only the client can have
+ NAWS enabled). Other options can be enabled on either or both
+ sides (such as RFC 1372: each side can have its own flow control
+ state).
+
+ @ivar state: C{'yes'} or C{'no'} indicating whether or not this
+ option is enabled on one side of the connection.
+
+ @ivar negotiating: A boolean tracking whether negotiation about
+ this option is in progress.
+
+ @ivar onResult: When negotiation about this option has been
+ initiated by this side of the connection, a L{Deferred}
+ which will fire with the result of the negotiation. L{None}
+ at other times.
+ """
+ state = 'no'
+ negotiating = False
+ onResult = None
+
+ def __str__(self):
+ return self.state + ('*' * self.negotiating)
+
+
+ def __init__(self):
+ self.us = self._Perspective()
+ self.him = self._Perspective()
+
+
+ def __repr__(self):
+ return '<_OptionState us=%s him=%s>' % (self.us, self.him)
+
+
+ def getOptionState(self, opt):
+ return self.options.setdefault(opt, self._OptionState())
+
+
+ def _do(self, option):
+ self._write(IAC + DO + option)
+
+
+ def _dont(self, option):
+ self._write(IAC + DONT + option)
+
+
+ def _will(self, option):
+ self._write(IAC + WILL + option)
+
+
+ def _wont(self, option):
+ self._write(IAC + WONT + option)
+
+
+ def will(self, option):
+ """
+ Indicate our willingness to enable an option.
+ """
+ s = self.getOptionState(option)
+ if s.us.negotiating or s.him.negotiating:
+ return defer.fail(AlreadyNegotiating(option))
+ elif s.us.state == 'yes':
+ return defer.fail(AlreadyEnabled(option))
+ else:
+ s.us.negotiating = True
+ s.us.onResult = d = defer.Deferred()
+ self._will(option)
+ return d
+
+
+ def wont(self, option):
+ """
+ Indicate we are not willing to enable an option.
+ """
+ s = self.getOptionState(option)
+ if s.us.negotiating or s.him.negotiating:
+ return defer.fail(AlreadyNegotiating(option))
+ elif s.us.state == 'no':
+ return defer.fail(AlreadyDisabled(option))
+ else:
+ s.us.negotiating = True
+ s.us.onResult = d = defer.Deferred()
+ self._wont(option)
+ return d
+
+
+ def do(self, option):
+ s = self.getOptionState(option)
+ if s.us.negotiating or s.him.negotiating:
+ return defer.fail(AlreadyNegotiating(option))
+ elif s.him.state == 'yes':
+ return defer.fail(AlreadyEnabled(option))
+ else:
+ s.him.negotiating = True
+ s.him.onResult = d = defer.Deferred()
+ self._do(option)
+ return d
+
+
+ def dont(self, option):
+ s = self.getOptionState(option)
+ if s.us.negotiating or s.him.negotiating:
+ return defer.fail(AlreadyNegotiating(option))
+ elif s.him.state == 'no':
+ return defer.fail(AlreadyDisabled(option))
+ else:
+ s.him.negotiating = True
+ s.him.onResult = d = defer.Deferred()
+ self._dont(option)
+ return d
+
+
+ def requestNegotiation(self, about, data):
+ """
+ Send a negotiation message for the option C{about} with C{data} as the
+ payload.
+
+ @param data: the payload
+ @type data: L{bytes}
+ @see: L{ITelnetTransport.requestNegotiation}
+ """
+ data = data.replace(IAC, IAC * 2)
+ self._write(IAC + SB + about + data + IAC + SE)
+
+
+ def dataReceived(self, data):
+ appDataBuffer = []
+
+ for b in iterbytes(data):
+ if self.state == 'data':
+ if b == IAC:
+ self.state = 'escaped'
+ elif b == b'\r':
+ self.state = 'newline'
+ else:
+ appDataBuffer.append(b)
+ elif self.state == 'escaped':
+ if b == IAC:
+ appDataBuffer.append(b)
+ self.state = 'data'
+ elif b == SB:
+ self.state = 'subnegotiation'
+ self.commands = []
+ elif b in (NOP, DM, BRK, IP, AO, AYT, EC, EL, GA):
+ self.state = 'data'
+ if appDataBuffer:
+ self.applicationDataReceived(b''.join(appDataBuffer))
+ del appDataBuffer[:]
+ self.commandReceived(b, None)
+ elif b in (WILL, WONT, DO, DONT):
+ self.state = 'command'
+ self.command = b
+ else:
+ raise ValueError("Stumped", b)
+ elif self.state == 'command':
+ self.state = 'data'
+ command = self.command
+ del self.command
+ if appDataBuffer:
+ self.applicationDataReceived(b''.join(appDataBuffer))
+ del appDataBuffer[:]
+ self.commandReceived(command, b)
+ elif self.state == 'newline':
+ self.state = 'data'
+ if b == b'\n':
+ appDataBuffer.append(b'\n')
+ elif b == b'\0':
+ appDataBuffer.append(b'\r')
+ elif b == IAC:
+ # IAC isn't really allowed after \r, according to the
+ # RFC, but handling it this way is less surprising than
+ # delivering the IAC to the app as application data.
+ # The purpose of the restriction is to allow terminals
+ # to unambiguously interpret the behavior of the CR
+ # after reading only one more byte. CR LF is supposed
+ # to mean one thing (cursor to next line, first column),
+ # CR NUL another (cursor to first column). Absent the
+ # NUL, it still makes sense to interpret this as CR and
+ # then apply all the usual interpretation to the IAC.
+ appDataBuffer.append(b'\r')
+ self.state = 'escaped'
+ else:
+ appDataBuffer.append(b'\r' + b)
+ elif self.state == 'subnegotiation':
+ if b == IAC:
+ self.state = 'subnegotiation-escaped'
+ else:
+ self.commands.append(b)
+ elif self.state == 'subnegotiation-escaped':
+ if b == SE:
+ self.state = 'data'
+ commands = self.commands
+ del self.commands
+ if appDataBuffer:
+ self.applicationDataReceived(b''.join(appDataBuffer))
+ del appDataBuffer[:]
+ self.negotiate(commands)
+ else:
+ self.state = 'subnegotiation'
+ self.commands.append(b)
+ else:
+ raise ValueError("How'd you do this?")
+
+ if appDataBuffer:
+ self.applicationDataReceived(b''.join(appDataBuffer))
+
+
+ def connectionLost(self, reason):
+ for state in self.options.values():
+ if state.us.onResult is not None:
+ d = state.us.onResult
+ state.us.onResult = None
+ d.errback(reason)
+ if state.him.onResult is not None:
+ d = state.him.onResult
+ state.him.onResult = None
+ d.errback(reason)
+
+
+ def applicationDataReceived(self, data):
+ """
+ Called with application-level data.
+ """
+
+ def unhandledCommand(self, command, argument):
+ """
+ Called for commands for which no handler is installed.
+ """
+
+
+ def commandReceived(self, command, argument):
+ cmdFunc = self.commandMap.get(command)
+ if cmdFunc is None:
+ self.unhandledCommand(command, argument)
+ else:
+ cmdFunc(argument)
+
+
+ def unhandledSubnegotiation(self, command, data):
+ """
+ Called for subnegotiations for which no handler is installed.
+ """
+
+
+ def negotiate(self, data):
+ command, data = data[0], data[1:]
+ cmdFunc = self.negotiationMap.get(command)
+ if cmdFunc is None:
+ self.unhandledSubnegotiation(command, data)
+ else:
+ cmdFunc(data)
+
+
+ def telnet_WILL(self, option):
+ s = self.getOptionState(option)
+ self.willMap[s.him.state, s.him.negotiating](self, s, option)
+
+
+ def will_no_false(self, state, option):
+ # He is unilaterally offering to enable an option.
+ if self.enableRemote(option):
+ state.him.state = 'yes'
+ self._do(option)
+ else:
+ self._dont(option)
+
+
+ def will_no_true(self, state, option):
+ # Peer agreed to enable an option in response to our request.
+ state.him.state = 'yes'
+ state.him.negotiating = False
+ d = state.him.onResult
+ state.him.onResult = None
+ d.callback(True)
+ assert self.enableRemote(option), "enableRemote must return True in this context (for option %r)" % (option,)
+
+
+ def will_yes_false(self, state, option):
+ # He is unilaterally offering to enable an already-enabled option.
+ # Ignore this.
+ pass
+
+
+ def will_yes_true(self, state, option):
+ # This is a bogus state. It is here for completeness. It will
+ # never be entered.
+ assert False, "will_yes_true can never be entered, but was called with %r, %r" % (state, option)
+
+ willMap = {('no', False): will_no_false, ('no', True): will_no_true,
+ ('yes', False): will_yes_false, ('yes', True): will_yes_true}
+
+
+ def telnet_WONT(self, option):
+ s = self.getOptionState(option)
+ self.wontMap[s.him.state, s.him.negotiating](self, s, option)
+
+
+ def wont_no_false(self, state, option):
+ # He is unilaterally demanding that an already-disabled option be/remain disabled.
+ # Ignore this (although we could record it and refuse subsequent enable attempts
+ # from our side - he can always refuse them again though, so we won't)
+ pass
+
+
+ def wont_no_true(self, state, option):
+ # Peer refused to enable an option in response to our request.
+ state.him.negotiating = False
+ d = state.him.onResult
+ state.him.onResult = None
+ d.errback(OptionRefused(option))
+
+
+ def wont_yes_false(self, state, option):
+ # Peer is unilaterally demanding that an option be disabled.
+ state.him.state = 'no'
+ self.disableRemote(option)
+ self._dont(option)
+
+
+ def wont_yes_true(self, state, option):
+ # Peer agreed to disable an option at our request.
+ state.him.state = 'no'
+ state.him.negotiating = False
+ d = state.him.onResult
+ state.him.onResult = None
+ d.callback(True)
+ self.disableRemote(option)
+
+ wontMap = {('no', False): wont_no_false, ('no', True): wont_no_true,
+ ('yes', False): wont_yes_false, ('yes', True): wont_yes_true}
+
+
+ def telnet_DO(self, option):
+ s = self.getOptionState(option)
+ self.doMap[s.us.state, s.us.negotiating](self, s, option)
+
+
+ def do_no_false(self, state, option):
+ # Peer is unilaterally requesting that we enable an option.
+ if self.enableLocal(option):
+ state.us.state = 'yes'
+ self._will(option)
+ else:
+ self._wont(option)
+
+
+ def do_no_true(self, state, option):
+ # Peer agreed to allow us to enable an option at our request.
+ state.us.state = 'yes'
+ state.us.negotiating = False
+ d = state.us.onResult
+ state.us.onResult = None
+ d.callback(True)
+ self.enableLocal(option)
+
+
+ def do_yes_false(self, state, option):
+ # Peer is unilaterally requesting us to enable an already-enabled option.
+ # Ignore this.
+ pass
+
+
+ def do_yes_true(self, state, option):
+ # This is a bogus state. It is here for completeness. It will never be
+ # entered.
+ assert False, "do_yes_true can never be entered, but was called with %r, %r" % (state, option)
+
+ doMap = {('no', False): do_no_false, ('no', True): do_no_true,
+ ('yes', False): do_yes_false, ('yes', True): do_yes_true}
+
+
+ def telnet_DONT(self, option):
+ s = self.getOptionState(option)
+ self.dontMap[s.us.state, s.us.negotiating](self, s, option)
+
+
+ def dont_no_false(self, state, option):
+ # Peer is unilaterally demanding us to disable an already-disabled option.
+ # Ignore this.
+ pass
+
+
+ def dont_no_true(self, state, option):
+ # Offered option was refused. Fail the Deferred returned by the
+ # previous will() call.
+ state.us.negotiating = False
+ d = state.us.onResult
+ state.us.onResult = None
+ d.errback(OptionRefused(option))
+
+
+ def dont_yes_false(self, state, option):
+ # Peer is unilaterally demanding we disable an option.
+ state.us.state = 'no'
+ self.disableLocal(option)
+ self._wont(option)
+
+
+ def dont_yes_true(self, state, option):
+ # Peer acknowledged our notice that we will disable an option.
+ state.us.state = 'no'
+ state.us.negotiating = False
+ d = state.us.onResult
+ state.us.onResult = None
+ d.callback(True)
+ self.disableLocal(option)
+
+ dontMap = {('no', False): dont_no_false, ('no', True): dont_no_true,
+ ('yes', False): dont_yes_false, ('yes', True): dont_yes_true}
+
+
+ def enableLocal(self, option):
+ """
+ Reject all attempts to enable options.
+ """
+ return False
+
+
+ def enableRemote(self, option):
+ """
+ Reject all attempts to enable options.
+ """
+ return False
+
+
+ def disableLocal(self, option):
+ """
+ Signal a programming error by raising an exception.
+
+ L{enableLocal} must return true for the given value of C{option} in
+ order for this method to be called. If a subclass of L{Telnet}
+ overrides enableLocal to allow certain options to be enabled, it must
+ also override disableLocal to disable those options.
+
+ @raise NotImplementedError: Always raised.
+ """
+ raise NotImplementedError(
+ "Don't know how to disable local telnet option %r" % (option,))
+
+
+ def disableRemote(self, option):
+ """
+ Signal a programming error by raising an exception.
+
+ L{enableRemote} must return true for the given value of C{option} in
+ order for this method to be called. If a subclass of L{Telnet}
+ overrides enableRemote to allow certain options to be enabled, it must
+ also override disableRemote tto disable those options.
+
+ @raise NotImplementedError: Always raised.
+ """
+ raise NotImplementedError(
+ "Don't know how to disable remote telnet option %r" % (option,))
+
+
+
+class ProtocolTransportMixin:
+ def write(self, data):
+ self.transport.write(data.replace(b'\n', b'\r\n'))
+
+
+ def writeSequence(self, seq):
+ self.transport.writeSequence(seq)
+
+
+ def loseConnection(self):
+ self.transport.loseConnection()
+
+
+ def getHost(self):
+ return self.transport.getHost()
+
+
+ def getPeer(self):
+ return self.transport.getPeer()
+
+
+
+class TelnetTransport(Telnet, ProtocolTransportMixin):
+ """
+ @ivar protocol: An instance of the protocol to which this
+ transport is connected, or None before the connection is
+ established and after it is lost.
+
+ @ivar protocolFactory: A callable which returns protocol instances
+ which provide L{ITelnetProtocol}. This will be invoked when a
+ connection is established. It is passed *protocolArgs and
+ **protocolKwArgs.
+
+ @ivar protocolArgs: A tuple of additional arguments to
+ pass to protocolFactory.
+
+ @ivar protocolKwArgs: A dictionary of additional arguments
+ to pass to protocolFactory.
+ """
+
+ disconnecting = False
+
+ protocolFactory = None
+ protocol = None
+
+ def __init__(self, protocolFactory=None, *a, **kw):
+ Telnet.__init__(self)
+ if protocolFactory is not None:
+ self.protocolFactory = protocolFactory
+ self.protocolArgs = a
+ self.protocolKwArgs = kw
+
+
+ def connectionMade(self):
+ if self.protocolFactory is not None:
+ self.protocol = self.protocolFactory(*self.protocolArgs, **self.protocolKwArgs)
+ assert ITelnetProtocol.providedBy(self.protocol)
+ try:
+ factory = self.factory
+ except AttributeError:
+ pass
+ else:
+ self.protocol.factory = factory
+ self.protocol.makeConnection(self)
+
+
+ def connectionLost(self, reason):
+ Telnet.connectionLost(self, reason)
+ if self.protocol is not None:
+ try:
+ self.protocol.connectionLost(reason)
+ finally:
+ del self.protocol
+
+
+ def enableLocal(self, option):
+ return self.protocol.enableLocal(option)
+
+
+ def enableRemote(self, option):
+ return self.protocol.enableRemote(option)
+
+
+ def disableLocal(self, option):
+ return self.protocol.disableLocal(option)
+
+
+ def disableRemote(self, option):
+ return self.protocol.disableRemote(option)
+
+
+ def unhandledSubnegotiation(self, command, data):
+ self.protocol.unhandledSubnegotiation(command, data)
+
+
+ def unhandledCommand(self, command, argument):
+ self.protocol.unhandledCommand(command, argument)
+
+
+ def applicationDataReceived(self, data):
+ self.protocol.dataReceived(data)
+
+
+ def write(self, data):
+ ProtocolTransportMixin.write(self, data.replace(b'\xff', b'\xff\xff'))
+
+
+
+class TelnetBootstrapProtocol(TelnetProtocol, ProtocolTransportMixin):
+ protocol = None
+
+ def __init__(self, protocolFactory, *args, **kw):
+ self.protocolFactory = protocolFactory
+ self.protocolArgs = args
+ self.protocolKwArgs = kw
+
+
+ def connectionMade(self):
+ self.transport.negotiationMap[NAWS] = self.telnet_NAWS
+ self.transport.negotiationMap[LINEMODE] = self.telnet_LINEMODE
+
+ for opt in (LINEMODE, NAWS, SGA):
+ self.transport.do(opt).addErrback(log.err)
+ for opt in (ECHO,):
+ self.transport.will(opt).addErrback(log.err)
+
+ self.protocol = self.protocolFactory(*self.protocolArgs, **self.protocolKwArgs)
+
+ try:
+ factory = self.factory
+ except AttributeError:
+ pass
+ else:
+ self.protocol.factory = factory
+
+ self.protocol.makeConnection(self)
+
+
+ def connectionLost(self, reason):
+ if self.protocol is not None:
+ try:
+ self.protocol.connectionLost(reason)
+ finally:
+ del self.protocol
+
+
+ def dataReceived(self, data):
+ self.protocol.dataReceived(data)
+
+
+ def enableLocal(self, opt):
+ if opt == ECHO:
+ return True
+ elif opt == SGA:
+ return True
+ else:
+ return False
+
+
+ def enableRemote(self, opt):
+ if opt == LINEMODE:
+ self.transport.requestNegotiation(LINEMODE, MODE + chr(TRAPSIG))
+ return True
+ elif opt == NAWS:
+ return True
+ elif opt == SGA:
+ return True
+ else:
+ return False
+
+
+ def telnet_NAWS(self, data):
+ # NAWS is client -> server *only*. self.protocol will
+ # therefore be an ITerminalTransport, the `.protocol'
+ # attribute of which will be an ITerminalProtocol. Maybe.
+ # You know what, XXX TODO clean this up.
+ if len(data) == 4:
+ width, height = struct.unpack('!HH', b''.join(data))
+ self.protocol.terminalProtocol.terminalSize(width, height)
+ else:
+ log.msg("Wrong number of NAWS bytes")
+
+ linemodeSubcommands = {
+ LINEMODE_SLC: 'SLC'}
+ def telnet_LINEMODE(self, data):
+ linemodeSubcommand = data[0]
+ if 0:
+ # XXX TODO: This should be enabled to parse linemode subnegotiation.
+ getattr(self, 'linemode_' + self.linemodeSubcommands[linemodeSubcommand])(data[1:])
+
+
+ def linemode_SLC(self, data):
+ chunks = zip(*[iter(data)]*3)
+ for slcFunction, slcValue, slcWhat in chunks:
+ # Later, we should parse stuff.
+ 'SLC', ord(slcFunction), ord(slcValue), ord(slcWhat)
+
+
+from twisted.protocols import basic
+
+class StatefulTelnetProtocol(basic.LineReceiver, TelnetProtocol):
+ delimiter = b'\n'
+
+ state = 'Discard'
+
+ def connectionLost(self, reason):
+ basic.LineReceiver.connectionLost(self, reason)
+ TelnetProtocol.connectionLost(self, reason)
+
+
+ def lineReceived(self, line):
+ oldState = self.state
+ newState = getattr(self, "telnet_" + oldState)(line)
+ if newState is not None:
+ if self.state == oldState:
+ self.state = newState
+ else:
+ log.msg("Warning: state changed and new state returned")
+
+
+ def telnet_Discard(self, line):
+ pass
+
+
+from twisted.cred import credentials
+
+class AuthenticatingTelnetProtocol(StatefulTelnetProtocol):
+ """
+ A protocol which prompts for credentials and attempts to authenticate them.
+
+ Username and password prompts are given (the password is obscured). When the
+ information is collected, it is passed to a portal and an avatar implementing
+ L{ITelnetProtocol} is requested. If an avatar is returned, it connected to this
+ protocol's transport, and this protocol's transport is connected to it.
+ Otherwise, the user is re-prompted for credentials.
+ """
+
+ state = "User"
+ protocol = None
+
+ def __init__(self, portal):
+ self.portal = portal
+
+
+ def connectionMade(self):
+ self.transport.write(b"Username: ")
+
+
+ def connectionLost(self, reason):
+ StatefulTelnetProtocol.connectionLost(self, reason)
+ if self.protocol is not None:
+ try:
+ self.protocol.connectionLost(reason)
+ self.logout()
+ finally:
+ del self.protocol, self.logout
+
+
+ def telnet_User(self, line):
+ self.username = line
+ self.transport.will(ECHO)
+ self.transport.write(b"Password: ")
+ return 'Password'
+
+
+ def telnet_Password(self, line):
+ username, password = self.username, line
+ del self.username
+ def login(ignored):
+ creds = credentials.UsernamePassword(username, password)
+ d = self.portal.login(creds, None, ITelnetProtocol)
+ d.addCallback(self._cbLogin)
+ d.addErrback(self._ebLogin)
+ self.transport.wont(ECHO).addCallback(login)
+ return 'Discard'
+
+
+ def _cbLogin(self, ial):
+ interface, protocol, logout = ial
+ assert interface is ITelnetProtocol
+ self.protocol = protocol
+ self.logout = logout
+ self.state = 'Command'
+
+ protocol.makeConnection(self.transport)
+ self.transport.protocol = protocol
+
+
+ def _ebLogin(self, failure):
+ self.transport.write(b"\nAuthentication failed\n")
+ self.transport.write(b"Username: ")
+ self.state = "User"
+
+
+__all__ = [
+ # Exceptions
+ 'TelnetError', 'NegotiationError', 'OptionRefused',
+ 'AlreadyNegotiating', 'AlreadyEnabled', 'AlreadyDisabled',
+
+ # Interfaces
+ 'ITelnetProtocol', 'ITelnetTransport',
+
+ # Other stuff, protocols, etc.
+ 'Telnet', 'TelnetProtocol', 'TelnetTransport',
+ 'TelnetBootstrapProtocol',
+
+ ]
diff --git a/contrib/python/Twisted/py2/twisted/conch/ttymodes.py b/contrib/python/Twisted/py2/twisted/conch/ttymodes.py
new file mode 100644
index 0000000000..00b4495f3a
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/ttymodes.py
@@ -0,0 +1,121 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+import tty
+# this module was autogenerated.
+
+VINTR = 1
+VQUIT = 2
+VERASE = 3
+VKILL = 4
+VEOF = 5
+VEOL = 6
+VEOL2 = 7
+VSTART = 8
+VSTOP = 9
+VSUSP = 10
+VDSUSP = 11
+VREPRINT = 12
+VWERASE = 13
+VLNEXT = 14
+VFLUSH = 15
+VSWTCH = 16
+VSTATUS = 17
+VDISCARD = 18
+IGNPAR = 30
+PARMRK = 31
+INPCK = 32
+ISTRIP = 33
+INLCR = 34
+IGNCR = 35
+ICRNL = 36
+IUCLC = 37
+IXON = 38
+IXANY = 39
+IXOFF = 40
+IMAXBEL = 41
+ISIG = 50
+ICANON = 51
+XCASE = 52
+ECHO = 53
+ECHOE = 54
+ECHOK = 55
+ECHONL = 56
+NOFLSH = 57
+TOSTOP = 58
+IEXTEN = 59
+ECHOCTL = 60
+ECHOKE = 61
+PENDIN = 62
+OPOST = 70
+OLCUC = 71
+ONLCR = 72
+OCRNL = 73
+ONOCR = 74
+ONLRET = 75
+CS7 = 90
+CS8 = 91
+PARENB = 92
+PARODD = 93
+TTY_OP_ISPEED = 128
+TTY_OP_OSPEED = 129
+
+TTYMODES = {
+ 1 : 'VINTR',
+ 2 : 'VQUIT',
+ 3 : 'VERASE',
+ 4 : 'VKILL',
+ 5 : 'VEOF',
+ 6 : 'VEOL',
+ 7 : 'VEOL2',
+ 8 : 'VSTART',
+ 9 : 'VSTOP',
+ 10 : 'VSUSP',
+ 11 : 'VDSUSP',
+ 12 : 'VREPRINT',
+ 13 : 'VWERASE',
+ 14 : 'VLNEXT',
+ 15 : 'VFLUSH',
+ 16 : 'VSWTCH',
+ 17 : 'VSTATUS',
+ 18 : 'VDISCARD',
+ 30 : (tty.IFLAG, 'IGNPAR'),
+ 31 : (tty.IFLAG, 'PARMRK'),
+ 32 : (tty.IFLAG, 'INPCK'),
+ 33 : (tty.IFLAG, 'ISTRIP'),
+ 34 : (tty.IFLAG, 'INLCR'),
+ 35 : (tty.IFLAG, 'IGNCR'),
+ 36 : (tty.IFLAG, 'ICRNL'),
+ 37 : (tty.IFLAG, 'IUCLC'),
+ 38 : (tty.IFLAG, 'IXON'),
+ 39 : (tty.IFLAG, 'IXANY'),
+ 40 : (tty.IFLAG, 'IXOFF'),
+ 41 : (tty.IFLAG, 'IMAXBEL'),
+ 50 : (tty.LFLAG, 'ISIG'),
+ 51 : (tty.LFLAG, 'ICANON'),
+ 52 : (tty.LFLAG, 'XCASE'),
+ 53 : (tty.LFLAG, 'ECHO'),
+ 54 : (tty.LFLAG, 'ECHOE'),
+ 55 : (tty.LFLAG, 'ECHOK'),
+ 56 : (tty.LFLAG, 'ECHONL'),
+ 57 : (tty.LFLAG, 'NOFLSH'),
+ 58 : (tty.LFLAG, 'TOSTOP'),
+ 59 : (tty.LFLAG, 'IEXTEN'),
+ 60 : (tty.LFLAG, 'ECHOCTL'),
+ 61 : (tty.LFLAG, 'ECHOKE'),
+ 62 : (tty.LFLAG, 'PENDIN'),
+ 70 : (tty.OFLAG, 'OPOST'),
+ 71 : (tty.OFLAG, 'OLCUC'),
+ 72 : (tty.OFLAG, 'ONLCR'),
+ 73 : (tty.OFLAG, 'OCRNL'),
+ 74 : (tty.OFLAG, 'ONOCR'),
+ 75 : (tty.OFLAG, 'ONLRET'),
+# 90 : (tty.CFLAG, 'CS7'),
+# 91 : (tty.CFLAG, 'CS8'),
+ 92 : (tty.CFLAG, 'PARENB'),
+ 93 : (tty.CFLAG, 'PARODD'),
+ 128 : 'ISPEED',
+ 129 : 'OSPEED'
+}
diff --git a/contrib/python/Twisted/py2/twisted/conch/ui/__init__.py b/contrib/python/Twisted/py2/twisted/conch/ui/__init__.py
new file mode 100644
index 0000000000..ea0eea8318
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/ui/__init__.py
@@ -0,0 +1,11 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+
+"""
+twisted.conch.ui is home to the UI elements for tkconch.
+
+Maintainer: Paul Swartz
+"""
diff --git a/contrib/python/Twisted/py2/twisted/conch/ui/ansi.py b/contrib/python/Twisted/py2/twisted/conch/ui/ansi.py
new file mode 100644
index 0000000000..b073532642
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/ui/ansi.py
@@ -0,0 +1,240 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+"""Module to parse ANSI escape sequences
+
+Maintainer: Jean-Paul Calderone
+"""
+
+import string
+
+# Twisted imports
+from twisted.python import log
+
+class ColorText:
+ """
+ Represents an element of text along with the texts colors and
+ additional attributes.
+ """
+
+ # The colors to use
+ COLORS = ('b', 'r', 'g', 'y', 'l', 'm', 'c', 'w')
+ BOLD_COLORS = tuple([x.upper() for x in COLORS])
+ BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(len(COLORS))
+
+ # Color names
+ COLOR_NAMES = (
+ 'Black', 'Red', 'Green', 'Yellow', 'Blue', 'Magenta', 'Cyan', 'White'
+ )
+
+ def __init__(self, text, fg, bg, display, bold, underline, flash, reverse):
+ self.text, self.fg, self.bg = text, fg, bg
+ self.display = display
+ self.bold = bold
+ self.underline = underline
+ self.flash = flash
+ self.reverse = reverse
+ if self.reverse:
+ self.fg, self.bg = self.bg, self.fg
+
+
+class AnsiParser:
+ """
+ Parser class for ANSI codes.
+ """
+
+ # Terminators for cursor movement ansi controls - unsupported
+ CURSOR_SET = ('H', 'f', 'A', 'B', 'C', 'D', 'R', 's', 'u', 'd','G')
+
+ # Terminators for erasure ansi controls - unsupported
+ ERASE_SET = ('J', 'K', 'P')
+
+ # Terminators for mode change ansi controls - unsupported
+ MODE_SET = ('h', 'l')
+
+ # Terminators for keyboard assignment ansi controls - unsupported
+ ASSIGN_SET = ('p',)
+
+ # Terminators for color change ansi controls - supported
+ COLOR_SET = ('m',)
+
+ SETS = (CURSOR_SET, ERASE_SET, MODE_SET, ASSIGN_SET, COLOR_SET)
+
+ def __init__(self, defaultFG, defaultBG):
+ self.defaultFG, self.defaultBG = defaultFG, defaultBG
+ self.currentFG, self.currentBG = self.defaultFG, self.defaultBG
+ self.bold, self.flash, self.underline, self.reverse = 0, 0, 0, 0
+ self.display = 1
+ self.prepend = ''
+
+
+ def stripEscapes(self, string):
+ """
+ Remove all ANSI color escapes from the given string.
+ """
+ result = ''
+ show = 1
+ i = 0
+ L = len(string)
+ while i < L:
+ if show == 0 and string[i] in _sets:
+ show = 1
+ elif show:
+ n = string.find('\x1B', i)
+ if n == -1:
+ return result + string[i:]
+ else:
+ result = result + string[i:n]
+ i = n
+ show = 0
+ i = i + 1
+ return result
+
+ def writeString(self, colorstr):
+ pass
+
+ def parseString(self, str):
+ """
+ Turn a string input into a list of L{ColorText} elements.
+ """
+
+ if self.prepend:
+ str = self.prepend + str
+ self.prepend = ''
+ parts = str.split('\x1B')
+
+ if len(parts) == 1:
+ self.writeString(self.formatText(parts[0]))
+ else:
+ self.writeString(self.formatText(parts[0]))
+ for s in parts[1:]:
+ L = len(s)
+ i = 0
+ type = None
+ while i < L:
+ if s[i] not in string.digits+'[;?':
+ break
+ i+=1
+ if not s:
+ self.prepend = '\x1b'
+ return
+ if s[0]!='[':
+ self.writeString(self.formatText(s[i+1:]))
+ continue
+ else:
+ s=s[1:]
+ i-=1
+ if i==L-1:
+ self.prepend = '\x1b['
+ return
+ type = _setmap.get(s[i], None)
+ if type is None:
+ continue
+
+ if type == AnsiParser.COLOR_SET:
+ self.parseColor(s[:i + 1])
+ s = s[i + 1:]
+ self.writeString(self.formatText(s))
+ elif type == AnsiParser.CURSOR_SET:
+ cursor, s = s[:i+1], s[i+1:]
+ self.parseCursor(cursor)
+ self.writeString(self.formatText(s))
+ elif type == AnsiParser.ERASE_SET:
+ erase, s = s[:i+1], s[i+1:]
+ self.parseErase(erase)
+ self.writeString(self.formatText(s))
+ elif type == AnsiParser.MODE_SET:
+ s = s[i+1:]
+ #self.parseErase('2J')
+ self.writeString(self.formatText(s))
+ elif i == L:
+ self.prepend = '\x1B[' + s
+ else:
+ log.msg('Unhandled ANSI control type: %c' % (s[i],))
+ s = s[i + 1:]
+ self.writeString(self.formatText(s))
+
+ def parseColor(self, str):
+ """
+ Handle a single ANSI color sequence
+ """
+ # Drop the trailing 'm'
+ str = str[:-1]
+
+ if not str:
+ str = '0'
+
+ try:
+ parts = map(int, str.split(';'))
+ except ValueError:
+ log.msg('Invalid ANSI color sequence (%d): %s' % (len(str), str))
+ self.currentFG, self.currentBG = self.defaultFG, self.defaultBG
+ return
+
+ for x in parts:
+ if x == 0:
+ self.currentFG, self.currentBG = self.defaultFG, self.defaultBG
+ self.bold, self.flash, self.underline, self.reverse = 0, 0, 0, 0
+ self.display = 1
+ elif x == 1:
+ self.bold = 1
+ elif 30 <= x <= 37:
+ self.currentFG = x - 30
+ elif 40 <= x <= 47:
+ self.currentBG = x - 40
+ elif x == 39:
+ self.currentFG = self.defaultFG
+ elif x == 49:
+ self.currentBG = self.defaultBG
+ elif x == 4:
+ self.underline = 1
+ elif x == 5:
+ self.flash = 1
+ elif x == 7:
+ self.reverse = 1
+ elif x == 8:
+ self.display = 0
+ elif x == 22:
+ self.bold = 0
+ elif x == 24:
+ self.underline = 0
+ elif x == 25:
+ self.blink = 0
+ elif x == 27:
+ self.reverse = 0
+ elif x == 28:
+ self.display = 1
+ else:
+ log.msg('Unrecognised ANSI color command: %d' % (x,))
+
+ def parseCursor(self, cursor):
+ pass
+
+ def parseErase(self, erase):
+ pass
+
+
+ def pickColor(self, value, mode, BOLD = ColorText.BOLD_COLORS):
+ if mode:
+ return ColorText.COLORS[value]
+ else:
+ return self.bold and BOLD[value] or ColorText.COLORS[value]
+
+
+ def formatText(self, text):
+ return ColorText(
+ text,
+ self.pickColor(self.currentFG, 0),
+ self.pickColor(self.currentBG, 1),
+ self.display, self.bold, self.underline, self.flash, self.reverse
+ )
+
+
+_sets = ''.join(map(''.join, AnsiParser.SETS))
+
+_setmap = {}
+for s in AnsiParser.SETS:
+ for r in s:
+ _setmap[r] = s
+del s
diff --git a/contrib/python/Twisted/py2/twisted/conch/ui/tkvt100.py b/contrib/python/Twisted/py2/twisted/conch/ui/tkvt100.py
new file mode 100644
index 0000000000..0ce4db2915
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/ui/tkvt100.py
@@ -0,0 +1,202 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+"""Module to emulate a VT100 terminal in Tkinter.
+
+Maintainer: Paul Swartz
+"""
+
+try:
+ import tkinter as Tkinter
+ import tkinter.font as tkFont
+except ImportError:
+ import Tkinter, tkFont
+import string
+from . import ansi
+
+ttyFont = None#tkFont.Font(family = 'Courier', size = 10)
+fontWidth, fontHeight = None,None#max(map(ttyFont.measure, string.letters+string.digits)), int(ttyFont.metrics()['linespace'])
+
+colorKeys = (
+ 'b', 'r', 'g', 'y', 'l', 'm', 'c', 'w',
+ 'B', 'R', 'G', 'Y', 'L', 'M', 'C', 'W'
+)
+
+colorMap = {
+ 'b': '#000000', 'r': '#c40000', 'g': '#00c400', 'y': '#c4c400',
+ 'l': '#000080', 'm': '#c400c4', 'c': '#00c4c4', 'w': '#c4c4c4',
+ 'B': '#626262', 'R': '#ff0000', 'G': '#00ff00', 'Y': '#ffff00',
+ 'L': '#0000ff', 'M': '#ff00ff', 'C': '#00ffff', 'W': '#ffffff',
+}
+
+class VT100Frame(Tkinter.Frame):
+ def __init__(self, *args, **kw):
+ global ttyFont, fontHeight, fontWidth
+ ttyFont = tkFont.Font(family = 'Courier', size = 10)
+ fontWidth = max(map(ttyFont.measure, string.ascii_letters+string.digits))
+ fontHeight = int(ttyFont.metrics()['linespace'])
+ self.width = kw.get('width', 80)
+ self.height = kw.get('height', 25)
+ self.callback = kw['callback']
+ del kw['callback']
+ kw['width'] = w = fontWidth * self.width
+ kw['height'] = h = fontHeight * self.height
+ Tkinter.Frame.__init__(self, *args, **kw)
+ self.canvas = Tkinter.Canvas(bg='#000000', width=w, height=h)
+ self.canvas.pack(side=Tkinter.TOP, fill=Tkinter.BOTH, expand=1)
+ self.canvas.bind('<Key>', self.keyPressed)
+ self.canvas.bind('<1>', lambda x: 'break')
+ self.canvas.bind('<Up>', self.upPressed)
+ self.canvas.bind('<Down>', self.downPressed)
+ self.canvas.bind('<Left>', self.leftPressed)
+ self.canvas.bind('<Right>', self.rightPressed)
+ self.canvas.focus()
+
+ self.ansiParser = ansi.AnsiParser(ansi.ColorText.WHITE, ansi.ColorText.BLACK)
+ self.ansiParser.writeString = self.writeString
+ self.ansiParser.parseCursor = self.parseCursor
+ self.ansiParser.parseErase = self.parseErase
+ #for (a, b) in colorMap.items():
+ # self.canvas.tag_config(a, foreground=b)
+ # self.canvas.tag_config('b'+a, background=b)
+ #self.canvas.tag_config('underline', underline=1)
+
+ self.x = 0
+ self.y = 0
+ self.cursor = self.canvas.create_rectangle(0,0,fontWidth-1,fontHeight-1,fill='green',outline='green')
+
+ def _delete(self, sx, sy, ex, ey):
+ csx = sx*fontWidth + 1
+ csy = sy*fontHeight + 1
+ cex = ex*fontWidth + 3
+ cey = ey*fontHeight + 3
+ items = self.canvas.find_overlapping(csx,csy, cex,cey)
+ for item in items:
+ self.canvas.delete(item)
+
+ def _write(self, ch, fg, bg):
+ if self.x == self.width:
+ self.x = 0
+ self.y+=1
+ if self.y == self.height:
+ [self.canvas.move(x,0,-fontHeight) for x in self.canvas.find_all()]
+ self.y-=1
+ canvasX = self.x*fontWidth + 1
+ canvasY = self.y*fontHeight + 1
+ items = self.canvas.find_overlapping(canvasX, canvasY, canvasX+2, canvasY+2)
+ if items:
+ [self.canvas.delete(item) for item in items]
+ if bg:
+ self.canvas.create_rectangle(canvasX, canvasY, canvasX+fontWidth-1, canvasY+fontHeight-1, fill=bg, outline=bg)
+ self.canvas.create_text(canvasX, canvasY, anchor=Tkinter.NW, font=ttyFont, text=ch, fill=fg)
+ self.x+=1
+
+ def write(self, data):
+ #print self.x,self.y,repr(data)
+ #if len(data)>5: raw_input()
+ self.ansiParser.parseString(data)
+ self.canvas.delete(self.cursor)
+ canvasX = self.x*fontWidth + 1
+ canvasY = self.y*fontHeight + 1
+ self.cursor = self.canvas.create_rectangle(canvasX,canvasY,canvasX+fontWidth-1,canvasY+fontHeight-1, fill='green', outline='green')
+ self.canvas.lower(self.cursor)
+
+ def writeString(self, i):
+ if not i.display:
+ return
+ fg = colorMap[i.fg]
+ bg = i.bg != 'b' and colorMap[i.bg]
+ for ch in i.text:
+ b = ord(ch)
+ if b == 7: # bell
+ self.bell()
+ elif b == 8: # BS
+ if self.x:
+ self.x-=1
+ elif b == 9: # TAB
+ [self._write(' ',fg,bg) for index in range(8)]
+ elif b == 10:
+ if self.y == self.height-1:
+ self._delete(0,0,self.width,0)
+ [self.canvas.move(x,0,-fontHeight) for x in self.canvas.find_all()]
+ else:
+ self.y+=1
+ elif b == 13:
+ self.x = 0
+ elif 32 <= b < 127:
+ self._write(ch, fg, bg)
+
+ def parseErase(self, erase):
+ if ';' in erase:
+ end = erase[-1]
+ parts = erase[:-1].split(';')
+ [self.parseErase(x+end) for x in parts]
+ return
+ start = 0
+ x,y = self.x, self.y
+ if len(erase) > 1:
+ start = int(erase[:-1])
+ if erase[-1] == 'J':
+ if start == 0:
+ self._delete(x,y,self.width,self.height)
+ else:
+ self._delete(0,0,self.width,self.height)
+ self.x = 0
+ self.y = 0
+ elif erase[-1] == 'K':
+ if start == 0:
+ self._delete(x,y,self.width,y)
+ elif start == 1:
+ self._delete(0,y,x,y)
+ self.x = 0
+ else:
+ self._delete(0,y,self.width,y)
+ self.x = 0
+ elif erase[-1] == 'P':
+ self._delete(x,y,x+start,y)
+
+ def parseCursor(self, cursor):
+ #if ';' in cursor and cursor[-1]!='H':
+ # end = cursor[-1]
+ # parts = cursor[:-1].split(';')
+ # [self.parseCursor(x+end) for x in parts]
+ # return
+ start = 1
+ if len(cursor) > 1 and cursor[-1]!='H':
+ start = int(cursor[:-1])
+ if cursor[-1] == 'C':
+ self.x+=start
+ elif cursor[-1] == 'D':
+ self.x-=start
+ elif cursor[-1]=='d':
+ self.y=start-1
+ elif cursor[-1]=='G':
+ self.x=start-1
+ elif cursor[-1]=='H':
+ if len(cursor)>1:
+ y,x = map(int, cursor[:-1].split(';'))
+ y-=1
+ x-=1
+ else:
+ x,y=0,0
+ self.x = x
+ self.y = y
+
+ def keyPressed(self, event):
+ if self.callback and event.char:
+ self.callback(event.char)
+ return 'break'
+
+ def upPressed(self, event):
+ self.callback('\x1bOA')
+
+ def downPressed(self, event):
+ self.callback('\x1bOB')
+
+ def rightPressed(self, event):
+ self.callback('\x1bOC')
+
+ def leftPressed(self, event):
+ self.callback('\x1bOD')
diff --git a/contrib/python/Twisted/py2/twisted/conch/unix.py b/contrib/python/Twisted/py2/twisted/conch/unix.py
new file mode 100644
index 0000000000..d9c3f05c89
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/conch/unix.py
@@ -0,0 +1,535 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+A UNIX SSH server.
+"""
+
+import fcntl
+import grp
+import os
+import pty
+import pwd
+import socket
+import struct
+import time
+import tty
+
+from zope.interface import implementer
+
+from twisted.conch import ttymodes
+from twisted.conch.avatar import ConchUser
+from twisted.conch.error import ConchError
+from twisted.conch.ls import lsLine
+from twisted.conch.ssh import session, forwarding, filetransfer
+from twisted.conch.ssh.filetransfer import (
+ FXF_READ, FXF_WRITE, FXF_APPEND, FXF_CREAT, FXF_TRUNC, FXF_EXCL
+)
+from twisted.conch.interfaces import ISession, ISFTPServer, ISFTPFile
+from twisted.cred import portal
+from twisted.internet.error import ProcessExitedAlready
+from twisted.python import components, log
+from twisted.python.compat import _bytesChr as chr, nativeString
+
+try:
+ import utmp
+except ImportError:
+ utmp = None
+
+
+
+@implementer(portal.IRealm)
+class UnixSSHRealm:
+ def requestAvatar(self, username, mind, *interfaces):
+ user = UnixConchUser(username)
+ return interfaces[0], user, user.logout
+
+
+
+class UnixConchUser(ConchUser):
+
+ def __init__(self, username):
+ ConchUser.__init__(self)
+ self.username = username
+ self.pwdData = pwd.getpwnam(self.username)
+ l = [self.pwdData[3]]
+ for groupname, password, gid, userlist in grp.getgrall():
+ if username in userlist:
+ l.append(gid)
+ self.otherGroups = l
+ self.listeners = {} # Dict mapping (interface, port) -> listener
+ self.channelLookup.update(
+ {b"session": session.SSHSession,
+ b"direct-tcpip": forwarding.openConnectForwardingClient})
+
+ self.subsystemLookup.update(
+ {b"sftp": filetransfer.FileTransferServer})
+
+
+ def getUserGroupId(self):
+ return self.pwdData[2:4]
+
+
+ def getOtherGroups(self):
+ return self.otherGroups
+
+
+ def getHomeDir(self):
+ return self.pwdData[5]
+
+
+ def getShell(self):
+ return self.pwdData[6]
+
+
+ def global_tcpip_forward(self, data):
+ hostToBind, portToBind = forwarding.unpackGlobal_tcpip_forward(data)
+ from twisted.internet import reactor
+ try:
+ listener = self._runAsUser(
+ reactor.listenTCP, portToBind,
+ forwarding.SSHListenForwardingFactory(
+ self.conn,
+ (hostToBind, portToBind),
+ forwarding.SSHListenServerForwardingChannel),
+ interface=hostToBind)
+ except:
+ return 0
+ else:
+ self.listeners[(hostToBind, portToBind)] = listener
+ if portToBind == 0:
+ portToBind = listener.getHost()[2] # The port
+ return 1, struct.pack('>L', portToBind)
+ else:
+ return 1
+
+
+ def global_cancel_tcpip_forward(self, data):
+ hostToBind, portToBind = forwarding.unpackGlobal_tcpip_forward(data)
+ listener = self.listeners.get((hostToBind, portToBind), None)
+ if not listener:
+ return 0
+ del self.listeners[(hostToBind, portToBind)]
+ self._runAsUser(listener.stopListening)
+ return 1
+
+
+ def logout(self):
+ # Remove all listeners.
+ for listener in self.listeners.values():
+ self._runAsUser(listener.stopListening)
+ log.msg(
+ 'avatar %s logging out (%i)'
+ % (self.username, len(self.listeners)))
+
+
+ def _runAsUser(self, f, *args, **kw):
+ euid = os.geteuid()
+ egid = os.getegid()
+ groups = os.getgroups()
+ uid, gid = self.getUserGroupId()
+ os.setegid(0)
+ os.seteuid(0)
+ os.setgroups(self.getOtherGroups())
+ os.setegid(gid)
+ os.seteuid(uid)
+ try:
+ f = iter(f)
+ except TypeError:
+ f = [(f, args, kw)]
+ try:
+ for i in f:
+ func = i[0]
+ args = len(i) > 1 and i[1] or ()
+ kw = len(i) > 2 and i[2] or {}
+ r = func(*args, **kw)
+ finally:
+ os.setegid(0)
+ os.seteuid(0)
+ os.setgroups(groups)
+ os.setegid(egid)
+ os.seteuid(euid)
+ return r
+
+
+
+@implementer(ISession)
+class SSHSessionForUnixConchUser:
+ def __init__(self, avatar, reactor=None):
+ """
+ Construct an C{SSHSessionForUnixConchUser}.
+
+ @param avatar: The L{UnixConchUser} for whom this is an SSH session.
+ @param reactor: An L{IReactorProcess} used to handle shell and exec
+ requests. Uses the default reactor if None.
+ """
+ if reactor is None:
+ from twisted.internet import reactor
+ self._reactor = reactor
+ self.avatar = avatar
+ self.environ = {'PATH': '/bin:/usr/bin:/usr/local/bin'}
+ self.pty = None
+ self.ptyTuple = 0
+
+
+ def addUTMPEntry(self, loggedIn=1):
+ if not utmp:
+ return
+ ipAddress = self.avatar.conn.transport.transport.getPeer().host
+ packedIp, = struct.unpack('L', socket.inet_aton(ipAddress))
+ ttyName = self.ptyTuple[2][5:]
+ t = time.time()
+ t1 = int(t)
+ t2 = int((t-t1) * 1e6)
+ entry = utmp.UtmpEntry()
+ entry.ut_type = loggedIn and utmp.USER_PROCESS or utmp.DEAD_PROCESS
+ entry.ut_pid = self.pty.pid
+ entry.ut_line = ttyName
+ entry.ut_id = ttyName[-4:]
+ entry.ut_tv = (t1, t2)
+ if loggedIn:
+ entry.ut_user = self.avatar.username
+ entry.ut_host = socket.gethostbyaddr(ipAddress)[0]
+ entry.ut_addr_v6 = (packedIp, 0, 0, 0)
+ a = utmp.UtmpRecord(utmp.UTMP_FILE)
+ a.pututline(entry)
+ a.endutent()
+ b = utmp.UtmpRecord(utmp.WTMP_FILE)
+ b.pututline(entry)
+ b.endutent()
+
+
+ def getPty(self, term, windowSize, modes):
+ self.environ['TERM'] = term
+ self.winSize = windowSize
+ self.modes = modes
+ master, slave = pty.openpty()
+ ttyname = os.ttyname(slave)
+ self.environ['SSH_TTY'] = ttyname
+ self.ptyTuple = (master, slave, ttyname)
+
+
+ def openShell(self, proto):
+ if not self.ptyTuple: # We didn't get a pty-req.
+ log.msg('tried to get shell without pty, failing')
+ raise ConchError("no pty")
+ uid, gid = self.avatar.getUserGroupId()
+ homeDir = self.avatar.getHomeDir()
+ shell = self.avatar.getShell()
+ self.environ['USER'] = self.avatar.username
+ self.environ['HOME'] = homeDir
+ self.environ['SHELL'] = shell
+ shellExec = os.path.basename(shell)
+ peer = self.avatar.conn.transport.transport.getPeer()
+ host = self.avatar.conn.transport.transport.getHost()
+ self.environ['SSH_CLIENT'] = '%s %s %s' % (
+ peer.host, peer.port, host.port)
+ self.getPtyOwnership()
+ self.pty = self._reactor.spawnProcess(
+ proto, shell, ['-%s' % (shellExec,)], self.environ, homeDir, uid,
+ gid, usePTY=self.ptyTuple)
+ self.addUTMPEntry()
+ fcntl.ioctl(self.pty.fileno(), tty.TIOCSWINSZ,
+ struct.pack('4H', *self.winSize))
+ if self.modes:
+ self.setModes()
+ self.oldWrite = proto.transport.write
+ proto.transport.write = self._writeHack
+ self.avatar.conn.transport.transport.setTcpNoDelay(1)
+
+
+ def execCommand(self, proto, cmd):
+ uid, gid = self.avatar.getUserGroupId()
+ homeDir = self.avatar.getHomeDir()
+ shell = self.avatar.getShell() or '/bin/sh'
+ self.environ['HOME'] = homeDir
+ command = (shell, '-c', cmd)
+ peer = self.avatar.conn.transport.transport.getPeer()
+ host = self.avatar.conn.transport.transport.getHost()
+ self.environ['SSH_CLIENT'] = '%s %s %s' % (
+ peer.host, peer.port, host.port)
+ if self.ptyTuple:
+ self.getPtyOwnership()
+ self.pty = self._reactor.spawnProcess(
+ proto, shell, command, self.environ, homeDir, uid, gid,
+ usePTY=self.ptyTuple or 0)
+ if self.ptyTuple:
+ self.addUTMPEntry()
+ if self.modes:
+ self.setModes()
+ self.avatar.conn.transport.transport.setTcpNoDelay(1)
+
+
+ def getPtyOwnership(self):
+ ttyGid = os.stat(self.ptyTuple[2])[5]
+ uid, gid = self.avatar.getUserGroupId()
+ euid, egid = os.geteuid(), os.getegid()
+ os.setegid(0)
+ os.seteuid(0)
+ try:
+ os.chown(self.ptyTuple[2], uid, ttyGid)
+ finally:
+ os.setegid(egid)
+ os.seteuid(euid)
+
+
+ def setModes(self):
+ pty = self.pty
+ attr = tty.tcgetattr(pty.fileno())
+ for mode, modeValue in self.modes:
+ if mode not in ttymodes.TTYMODES:
+ continue
+ ttyMode = ttymodes.TTYMODES[mode]
+ if len(ttyMode) == 2: # Flag.
+ flag, ttyAttr = ttyMode
+ if not hasattr(tty, ttyAttr):
+ continue
+ ttyval = getattr(tty, ttyAttr)
+ if modeValue:
+ attr[flag] = attr[flag] | ttyval
+ else:
+ attr[flag] = attr[flag] & ~ttyval
+ elif ttyMode == 'OSPEED':
+ attr[tty.OSPEED] = getattr(tty, 'B%s' % (modeValue,))
+ elif ttyMode == 'ISPEED':
+ attr[tty.ISPEED] = getattr(tty, 'B%s' % (modeValue,))
+ else:
+ if not hasattr(tty, ttyMode):
+ continue
+ ttyval = getattr(tty, ttyMode)
+ attr[tty.CC][ttyval] = chr(modeValue)
+ tty.tcsetattr(pty.fileno(), tty.TCSANOW, attr)
+
+
+ def eofReceived(self):
+ if self.pty:
+ self.pty.closeStdin()
+
+
+ def closed(self):
+ if self.ptyTuple and os.path.exists(self.ptyTuple[2]):
+ ttyGID = os.stat(self.ptyTuple[2])[5]
+ os.chown(self.ptyTuple[2], 0, ttyGID)
+ if self.pty:
+ try:
+ self.pty.signalProcess('HUP')
+ except (OSError, ProcessExitedAlready):
+ pass
+ self.pty.loseConnection()
+ self.addUTMPEntry(0)
+ log.msg('shell closed')
+
+
+ def windowChanged(self, winSize):
+ self.winSize = winSize
+ fcntl.ioctl(
+ self.pty.fileno(), tty.TIOCSWINSZ,
+ struct.pack('4H', *self.winSize))
+
+
+ def _writeHack(self, data):
+ """
+ Hack to send ignore messages when we aren't echoing.
+ """
+ if self.pty is not None:
+ attr = tty.tcgetattr(self.pty.fileno())[3]
+ if not attr & tty.ECHO and attr & tty.ICANON: # No echo.
+ self.avatar.conn.transport.sendIgnore('\x00'*(8+len(data)))
+ self.oldWrite(data)
+
+
+
+@implementer(ISFTPServer)
+class SFTPServerForUnixConchUser:
+ def __init__(self, avatar):
+ self.avatar = avatar
+
+
+ def _setAttrs(self, path, attrs):
+ """
+ NOTE: this function assumes it runs as the logged-in user:
+ i.e. under _runAsUser()
+ """
+ if "uid" in attrs and "gid" in attrs:
+ os.chown(path, attrs["uid"], attrs["gid"])
+ if "permissions" in attrs:
+ os.chmod(path, attrs["permissions"])
+ if "atime" in attrs and "mtime" in attrs:
+ os.utime(path, (attrs["atime"], attrs["mtime"]))
+
+
+ def _getAttrs(self, s):
+ return {
+ "size": s.st_size,
+ "uid": s.st_uid,
+ "gid": s.st_gid,
+ "permissions": s.st_mode,
+ "atime": int(s.st_atime),
+ "mtime": int(s.st_mtime)
+ }
+
+
+ def _absPath(self, path):
+ home = self.avatar.getHomeDir()
+ return os.path.join(nativeString(home.path), nativeString(path))
+
+
+ def gotVersion(self, otherVersion, extData):
+ return {}
+
+
+ def openFile(self, filename, flags, attrs):
+ return UnixSFTPFile(self, self._absPath(filename), flags, attrs)
+
+
+ def removeFile(self, filename):
+ filename = self._absPath(filename)
+ return self.avatar._runAsUser(os.remove, filename)
+
+
+ def renameFile(self, oldpath, newpath):
+ oldpath = self._absPath(oldpath)
+ newpath = self._absPath(newpath)
+ return self.avatar._runAsUser(os.rename, oldpath, newpath)
+
+
+ def makeDirectory(self, path, attrs):
+ path = self._absPath(path)
+ return self.avatar._runAsUser(
+ [(os.mkdir, (path,)), (self._setAttrs, (path, attrs))])
+
+
+ def removeDirectory(self, path):
+ path = self._absPath(path)
+ self.avatar._runAsUser(os.rmdir, path)
+
+
+ def openDirectory(self, path):
+ return UnixSFTPDirectory(self, self._absPath(path))
+
+
+ def getAttrs(self, path, followLinks):
+ path = self._absPath(path)
+ if followLinks:
+ s = self.avatar._runAsUser(os.stat, path)
+ else:
+ s = self.avatar._runAsUser(os.lstat, path)
+ return self._getAttrs(s)
+
+
+ def setAttrs(self, path, attrs):
+ path = self._absPath(path)
+ self.avatar._runAsUser(self._setAttrs, path, attrs)
+
+
+ def readLink(self, path):
+ path = self._absPath(path)
+ return self.avatar._runAsUser(os.readlink, path)
+
+
+ def makeLink(self, linkPath, targetPath):
+ linkPath = self._absPath(linkPath)
+ targetPath = self._absPath(targetPath)
+ return self.avatar._runAsUser(os.symlink, targetPath, linkPath)
+
+
+ def realPath(self, path):
+ return os.path.realpath(self._absPath(path))
+
+
+ def extendedRequest(self, extName, extData):
+ raise NotImplementedError
+
+
+
+@implementer(ISFTPFile)
+class UnixSFTPFile:
+ def __init__(self, server, filename, flags, attrs):
+ self.server = server
+ openFlags = 0
+ if flags & FXF_READ == FXF_READ and flags & FXF_WRITE == 0:
+ openFlags = os.O_RDONLY
+ if flags & FXF_WRITE == FXF_WRITE and flags & FXF_READ == 0:
+ openFlags = os.O_WRONLY
+ if flags & FXF_WRITE == FXF_WRITE and flags & FXF_READ == FXF_READ:
+ openFlags = os.O_RDWR
+ if flags & FXF_APPEND == FXF_APPEND:
+ openFlags |= os.O_APPEND
+ if flags & FXF_CREAT == FXF_CREAT:
+ openFlags |= os.O_CREAT
+ if flags & FXF_TRUNC == FXF_TRUNC:
+ openFlags |= os.O_TRUNC
+ if flags & FXF_EXCL == FXF_EXCL:
+ openFlags |= os.O_EXCL
+ if "permissions" in attrs:
+ mode = attrs["permissions"]
+ del attrs["permissions"]
+ else:
+ mode = 0o777
+ fd = server.avatar._runAsUser(os.open, filename, openFlags, mode)
+ if attrs:
+ server.avatar._runAsUser(server._setAttrs, filename, attrs)
+ self.fd = fd
+
+
+ def close(self):
+ return self.server.avatar._runAsUser(os.close, self.fd)
+
+
+ def readChunk(self, offset, length):
+ return self.server.avatar._runAsUser(
+ [(os.lseek, (self.fd, offset, 0)),
+ (os.read, (self.fd, length))])
+
+
+ def writeChunk(self, offset, data):
+ return self.server.avatar._runAsUser(
+ [(os.lseek, (self.fd, offset, 0)),
+ (os.write, (self.fd, data))])
+
+
+ def getAttrs(self):
+ s = self.server.avatar._runAsUser(os.fstat, self.fd)
+ return self.server._getAttrs(s)
+
+
+ def setAttrs(self, attrs):
+ raise NotImplementedError
+
+
+
+class UnixSFTPDirectory:
+
+ def __init__(self, server, directory):
+ self.server = server
+ self.files = server.avatar._runAsUser(os.listdir, directory)
+ self.dir = directory
+
+
+ def __iter__(self):
+ return self
+
+
+ def __next__(self):
+ try:
+ f = self.files.pop(0)
+ except IndexError:
+ raise StopIteration
+ else:
+ s = self.server.avatar._runAsUser(
+ os.lstat, os.path.join(self.dir, f))
+ longname = lsLine(f, s)
+ attrs = self.server._getAttrs(s)
+ return (f, longname, attrs)
+
+ next = __next__
+
+ def close(self):
+ self.files = []
+
+
+
+components.registerAdapter(
+ SFTPServerForUnixConchUser, UnixConchUser, filetransfer.ISFTPServer)
+components.registerAdapter(
+ SSHSessionForUnixConchUser, UnixConchUser, session.ISession)