Ausgabe der neuen DB Einträge
This commit is contained in:
parent
bad48e1627
commit
cfbbb9ee3d
2399 changed files with 843193 additions and 43 deletions
|
|
@ -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
|
||||
"""
|
||||
294
venv/lib/python3.9/site-packages/twisted/conch/ssh/_kex.py
Normal file
294
venv/lib/python3.9/site-packages/twisted/conch/ssh/_kex.py
Normal file
|
|
@ -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)
|
||||
|
|
@ -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))
|
||||
296
venv/lib/python3.9/site-packages/twisted/conch/ssh/agent.py
Normal file
296
venv/lib/python3.9/site-packages/twisted/conch/ssh/agent.py
Normal file
|
|
@ -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
|
||||
320
venv/lib/python3.9/site-packages/twisted/conch/ssh/channel.py
Normal file
320
venv/lib/python3.9/site-packages/twisted/conch/ssh/channel.py
Normal file
|
|
@ -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.
|
||||
"""
|
||||
93
venv/lib/python3.9/site-packages/twisted/conch/ssh/common.py
Normal file
93
venv/lib/python3.9/site-packages/twisted/conch/ssh/common.py
Normal file
|
|
@ -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
|
||||
653
venv/lib/python3.9/site-packages/twisted/conch/ssh/connection.py
Normal file
653
venv/lib/python3.9/site-packages/twisted/conch/ssh/connection.py
Normal file
|
|
@ -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
|
||||
123
venv/lib/python3.9/site-packages/twisted/conch/ssh/factory.py
Normal file
123
venv/lib/python3.9/site-packages/twisted/conch/ssh/factory.py
Normal file
|
|
@ -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]
|
||||
1055
venv/lib/python3.9/site-packages/twisted/conch/ssh/filetransfer.py
Normal file
1055
venv/lib/python3.9/site-packages/twisted/conch/ssh/filetransfer.py
Normal file
File diff suppressed because it is too large
Load diff
269
venv/lib/python3.9/site-packages/twisted/conch/ssh/forwarding.py
Normal file
269
venv/lib/python3.9/site-packages/twisted/conch/ssh/forwarding.py
Normal file
|
|
@ -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
|
||||
"""
|
||||
1678
venv/lib/python3.9/site-packages/twisted/conch/ssh/keys.py
Normal file
1678
venv/lib/python3.9/site-packages/twisted/conch/ssh/keys.py
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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()
|
||||
362
venv/lib/python3.9/site-packages/twisted/conch/ssh/session.py
Normal file
362
venv/lib/python3.9/site-packages/twisted/conch/ssh/session.py
Normal file
|
|
@ -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)
|
||||
45
venv/lib/python3.9/site-packages/twisted/conch/ssh/sexpy.py
Normal file
45
venv/lib/python3.9/site-packages/twisted/conch/ssh/sexpy.py
Normal file
|
|
@ -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
|
||||
2127
venv/lib/python3.9/site-packages/twisted/conch/ssh/transport.py
Normal file
2127
venv/lib/python3.9/site-packages/twisted/conch/ssh/transport.py
Normal file
File diff suppressed because it is too large
Load diff
770
venv/lib/python3.9/site-packages/twisted/conch/ssh/userauth.py
Normal file
770
venv/lib/python3.9/site-packages/twisted/conch/ssh/userauth.py
Normal file
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue