[project @ Arch-1:robey@lag.net--2003-public%secsh--dev--1.0--patch-66]
new ServerInterface class, outbound rekey works, etc. a bunch of changes that i'm too lazy to split out into individual patches: * all the server overrides from transport.py have been moved into a separate class ServerInterface, so server code doesn't have to subclass the whole paramiko library * updated demo_server to subclass ServerInterface * when re-keying during a session, block other messages until the new keys are activated (openssh doensn't like any other traffic during a rekey) * re-key when outbound limits are tripped too (was only counting inbound traffic) * don't log scary things on EOF
This commit is contained in:
parent
34d975b972
commit
c86c4f3949
1
README
1
README
|
@ -155,4 +155,3 @@ v0.9 FEAROW
|
||||||
* multi-part auth not supported (ie, need username AND pk)
|
* multi-part auth not supported (ie, need username AND pk)
|
||||||
* server mode needs better documentation
|
* server mode needs better documentation
|
||||||
* sftp server mode
|
* sftp server mode
|
||||||
* make invoke_subsystem, etc wait for a reply
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ host_key.read_private_key_file('demo_dss_key')
|
||||||
print 'Read key: ' + paramiko.util.hexify(host_key.get_fingerprint())
|
print 'Read key: ' + paramiko.util.hexify(host_key.get_fingerprint())
|
||||||
|
|
||||||
|
|
||||||
class ServerTransport(paramiko.Transport):
|
class Server (paramiko.ServerInterface):
|
||||||
# 'data' is the output of base64.encodestring(str(key))
|
# 'data' is the output of base64.encodestring(str(key))
|
||||||
data = 'AAAAB3NzaC1yc2EAAAABIwAAAIEAyO4it3fHlmGZWJaGrfeHOVY7RWO3P9M7hpfAu7jJ2d7eothvfeuoRFtJwhUmZDluRdFyhFY/hFAh76PJKGAusIqIQKlkJxMCKDqIexkgHAfID/6mqvmnSJf0b5W8v5h2pI/stOSwTQ+pxVhwJ9ctYDhRSlF0iTUWT10hcuO4Ks8='
|
data = 'AAAAB3NzaC1yc2EAAAABIwAAAIEAyO4it3fHlmGZWJaGrfeHOVY7RWO3P9M7hpfAu7jJ2d7eothvfeuoRFtJwhUmZDluRdFyhFY/hFAh76PJKGAusIqIQKlkJxMCKDqIexkgHAfID/6mqvmnSJf0b5W8v5h2pI/stOSwTQ+pxVhwJ9ctYDhRSlF0iTUWT10hcuO4Ks8='
|
||||||
good_pub_key = paramiko.RSAKey(data=base64.decodestring(data))
|
good_pub_key = paramiko.RSAKey(data=base64.decodestring(data))
|
||||||
|
@ -23,24 +23,24 @@ class ServerTransport(paramiko.Transport):
|
||||||
def check_channel_request(self, kind, chanid):
|
def check_channel_request(self, kind, chanid):
|
||||||
if kind == 'session':
|
if kind == 'session':
|
||||||
return ServerChannel(chanid)
|
return ServerChannel(chanid)
|
||||||
return self.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
|
return paramiko.Transport.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
|
||||||
|
|
||||||
def check_auth_password(self, username, password):
|
def check_auth_password(self, username, password):
|
||||||
if (username == 'robey') and (password == 'foo'):
|
if (username == 'robey') and (password == 'foo'):
|
||||||
return self.AUTH_SUCCESSFUL
|
return paramiko.Transport.AUTH_SUCCESSFUL
|
||||||
return self.AUTH_FAILED
|
return paramiko.Transport.AUTH_FAILED
|
||||||
|
|
||||||
def check_auth_publickey(self, username, key):
|
def check_auth_publickey(self, username, key):
|
||||||
print 'Auth attempt with key: ' + paramiko.util.hexify(key.get_fingerprint())
|
print 'Auth attempt with key: ' + paramiko.util.hexify(key.get_fingerprint())
|
||||||
if (username == 'robey') and (key == self.good_pub_key):
|
if (username == 'robey') and (key == self.good_pub_key):
|
||||||
return self.AUTH_SUCCESSFUL
|
return paramiko.Transport.AUTH_SUCCESSFUL
|
||||||
return self.AUTH_FAILED
|
return paramiko.Transport.AUTH_FAILED
|
||||||
|
|
||||||
def get_allowed_auths(self, username):
|
def get_allowed_auths(self, username):
|
||||||
return 'password,publickey'
|
return 'password,publickey'
|
||||||
|
|
||||||
|
|
||||||
class ServerChannel(paramiko.Channel):
|
class ServerChannel (paramiko.Channel):
|
||||||
"Channel descendant that pretends to understand pty and shell requests"
|
"Channel descendant that pretends to understand pty and shell requests"
|
||||||
|
|
||||||
def __init__(self, chanid):
|
def __init__(self, chanid):
|
||||||
|
@ -61,7 +61,6 @@ try:
|
||||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
sock.bind(('', 2200))
|
sock.bind(('', 2200))
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
|
|
||||||
print '*** Bind failed: ' + str(e)
|
print '*** Bind failed: ' + str(e)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
@ -79,14 +78,14 @@ print 'Got a connection!'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
event = threading.Event()
|
event = threading.Event()
|
||||||
t = ServerTransport(client)
|
t = paramiko.Transport(client)
|
||||||
try:
|
try:
|
||||||
t.load_server_moduli()
|
t.load_server_moduli()
|
||||||
except:
|
except:
|
||||||
print '(Failed to load moduli -- gex will be unsupported.)'
|
print '(Failed to load moduli -- gex will be unsupported.)'
|
||||||
raise
|
raise
|
||||||
t.add_server_key(host_key)
|
t.add_server_key(host_key)
|
||||||
t.start_server(event)
|
t.start_server(event, Server())
|
||||||
while 1:
|
while 1:
|
||||||
event.wait(0.1)
|
event.wait(0.1)
|
||||||
if not t.is_active():
|
if not t.is_active():
|
||||||
|
|
|
@ -76,6 +76,7 @@ SSHException = ssh_exception.SSHException
|
||||||
Message = message.Message
|
Message = message.Message
|
||||||
PasswordRequiredException = ssh_exception.PasswordRequiredException
|
PasswordRequiredException = ssh_exception.PasswordRequiredException
|
||||||
SFTP = sftp.SFTP
|
SFTP = sftp.SFTP
|
||||||
|
ServerInterface = server.ServerInterface
|
||||||
|
|
||||||
|
|
||||||
__all__ = [ 'Transport',
|
__all__ = [ 'Transport',
|
||||||
|
@ -86,6 +87,7 @@ __all__ = [ 'Transport',
|
||||||
'SSHException',
|
'SSHException',
|
||||||
'PasswordRequiredException',
|
'PasswordRequiredException',
|
||||||
'SFTP',
|
'SFTP',
|
||||||
|
'ServerInterface',
|
||||||
'transport',
|
'transport',
|
||||||
'auth_transport',
|
'auth_transport',
|
||||||
'channel',
|
'channel',
|
||||||
|
@ -95,4 +97,5 @@ __all__ = [ 'Transport',
|
||||||
'message',
|
'message',
|
||||||
'ssh_exception',
|
'ssh_exception',
|
||||||
'sftp',
|
'sftp',
|
||||||
|
'server',
|
||||||
'util' ]
|
'util' ]
|
||||||
|
|
|
@ -156,102 +156,6 @@ class Transport (BaseTransport):
|
||||||
finally:
|
finally:
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
|
|
||||||
def get_allowed_auths(self, username):
|
|
||||||
"""
|
|
||||||
I{(subclass override)}
|
|
||||||
Return a list of authentication methods supported by the server.
|
|
||||||
This list is sent to clients attempting to authenticate, to inform them
|
|
||||||
of authentication methods that might be successful.
|
|
||||||
|
|
||||||
The "list" is actually a string of comma-separated names of types of
|
|
||||||
authentication. Possible values are C{"password"}, C{"publickey"},
|
|
||||||
and C{"none"}.
|
|
||||||
|
|
||||||
The default implementation always returns C{"password"}.
|
|
||||||
|
|
||||||
@param username: the username requesting authentication.
|
|
||||||
@type username: string
|
|
||||||
@return: a comma-separated list of authentication types
|
|
||||||
@rtype: string
|
|
||||||
"""
|
|
||||||
return 'password'
|
|
||||||
|
|
||||||
def check_auth_none(self, username):
|
|
||||||
"""
|
|
||||||
I{(subclass override)}
|
|
||||||
Determine if a client may open channels with no (further)
|
|
||||||
authentication. You should override this method in server mode.
|
|
||||||
|
|
||||||
Return C{AUTH_FAILED} if the client must authenticate, or
|
|
||||||
C{AUTH_SUCCESSFUL} if it's okay for the client to not authenticate.
|
|
||||||
|
|
||||||
The default implementation always returns C{AUTH_FAILED}.
|
|
||||||
|
|
||||||
@param username: the username of the client.
|
|
||||||
@type username: string
|
|
||||||
@return: C{AUTH_FAILED} if the authentication fails; C{AUTH_SUCCESSFUL}
|
|
||||||
if it succeeds.
|
|
||||||
@rtype: int
|
|
||||||
"""
|
|
||||||
return self.AUTH_FAILED
|
|
||||||
|
|
||||||
def check_auth_password(self, username, password):
|
|
||||||
"""
|
|
||||||
I{(subclass override)}
|
|
||||||
Determine if a given username and password supplied by the client is
|
|
||||||
acceptable for use in authentication. You should override this method
|
|
||||||
in server mode.
|
|
||||||
|
|
||||||
Return C{AUTH_FAILED} if the password is not accepted,
|
|
||||||
C{AUTH_SUCCESSFUL} if the password is accepted and completes the
|
|
||||||
authentication, or C{AUTH_PARTIALLY_SUCCESSFUL} if your authentication
|
|
||||||
is stateful, and this key is accepted for authentication, but more
|
|
||||||
authentication is required. (In this latter case, L{get_allowed_auths}
|
|
||||||
will be called to report to the client what options it has for
|
|
||||||
continuing the authentication.)
|
|
||||||
|
|
||||||
The default implementation always returns C{AUTH_FAILED}.
|
|
||||||
|
|
||||||
@param username: the username of the authenticating client.
|
|
||||||
@type username: string
|
|
||||||
@param password: the password given by the client.
|
|
||||||
@type password: string
|
|
||||||
@return: C{AUTH_FAILED} if the authentication fails; C{AUTH_SUCCESSFUL}
|
|
||||||
if it succeeds; C{AUTH_PARTIALLY_SUCCESSFUL} if the password auth is
|
|
||||||
successful, but authentication must continue.
|
|
||||||
@rtype: int
|
|
||||||
"""
|
|
||||||
return self.AUTH_FAILED
|
|
||||||
|
|
||||||
def check_auth_publickey(self, username, key):
|
|
||||||
"""
|
|
||||||
I{(subclass override)}
|
|
||||||
Determine if a given key supplied by the client is acceptable for use
|
|
||||||
in authentication. You should override this method in server mode to
|
|
||||||
check the username and key and decide if you would accept a signature
|
|
||||||
made using this key.
|
|
||||||
|
|
||||||
Return C{AUTH_FAILED} if the key is not accepted, C{AUTH_SUCCESSFUL}
|
|
||||||
if the key is accepted and completes the authentication, or
|
|
||||||
C{AUTH_PARTIALLY_SUCCESSFUL} if your authentication is stateful, and
|
|
||||||
this key is accepted for authentication, but more authentication is
|
|
||||||
required. (In this latter case, L{get_allowed_auths} will be called
|
|
||||||
to report to the client what options it has for continuing the
|
|
||||||
authentication.)
|
|
||||||
|
|
||||||
The default implementation always returns C{AUTH_FAILED}.
|
|
||||||
|
|
||||||
@param username: the username of the authenticating client.
|
|
||||||
@type username: string
|
|
||||||
@param key: the key object provided by the client.
|
|
||||||
@type key: L{PKey <pkey.PKey>}
|
|
||||||
@return: C{AUTH_FAILED} if the client can't authenticate with this key;
|
|
||||||
C{AUTH_SUCCESSFUL} if it can; C{AUTH_PARTIALLY_SUCCESSFUL} if it can
|
|
||||||
authenticate with this key but must continue with authentication.
|
|
||||||
@rtype: int
|
|
||||||
"""
|
|
||||||
return self.AUTH_FAILED
|
|
||||||
|
|
||||||
|
|
||||||
### internals...
|
### internals...
|
||||||
|
|
||||||
|
@ -355,7 +259,7 @@ class Transport (BaseTransport):
|
||||||
self.auth_username = username
|
self.auth_username = username
|
||||||
|
|
||||||
if method == 'none':
|
if method == 'none':
|
||||||
result = self.check_auth_none(username)
|
result = self.server_object.check_auth_none(username)
|
||||||
elif method == 'password':
|
elif method == 'password':
|
||||||
changereq = m.get_boolean()
|
changereq = m.get_boolean()
|
||||||
password = m.get_string().decode('UTF-8')
|
password = m.get_string().decode('UTF-8')
|
||||||
|
@ -366,7 +270,7 @@ class Transport (BaseTransport):
|
||||||
newpassword = m.get_string().decode('UTF-8')
|
newpassword = m.get_string().decode('UTF-8')
|
||||||
result = self.AUTH_FAILED
|
result = self.AUTH_FAILED
|
||||||
else:
|
else:
|
||||||
result = self.check_auth_password(username, password)
|
result = self.server_object.check_auth_password(username, password)
|
||||||
elif method == 'publickey':
|
elif method == 'publickey':
|
||||||
sig_attached = m.get_boolean()
|
sig_attached = m.get_boolean()
|
||||||
keytype = m.get_string()
|
keytype = m.get_string()
|
||||||
|
@ -377,7 +281,7 @@ class Transport (BaseTransport):
|
||||||
self._disconnect_no_more_auth()
|
self._disconnect_no_more_auth()
|
||||||
return
|
return
|
||||||
# first check if this key is okay... if not, we can skip the verify
|
# first check if this key is okay... if not, we can skip the verify
|
||||||
result = self.check_auth_publickey(username, key)
|
result = self.server_object.check_auth_publickey(username, key)
|
||||||
if result != self.AUTH_FAILED:
|
if result != self.AUTH_FAILED:
|
||||||
# key is okay, verify it
|
# key is okay, verify it
|
||||||
if not sig_attached:
|
if not sig_attached:
|
||||||
|
@ -395,7 +299,7 @@ class Transport (BaseTransport):
|
||||||
self._log(DEBUG, 'Auth rejected: invalid signature')
|
self._log(DEBUG, 'Auth rejected: invalid signature')
|
||||||
result = self.AUTH_FAILED
|
result = self.AUTH_FAILED
|
||||||
else:
|
else:
|
||||||
result = self.check_auth_none(username)
|
result = self.server_object.check_auth_none(username)
|
||||||
# okay, send result
|
# okay, send result
|
||||||
m = Message()
|
m = Message()
|
||||||
if result == self.AUTH_SUCCESSFUL:
|
if result == self.AUTH_SUCCESSFUL:
|
||||||
|
@ -405,7 +309,7 @@ class Transport (BaseTransport):
|
||||||
else:
|
else:
|
||||||
self._log(DEBUG, 'Auth rejected.')
|
self._log(DEBUG, 'Auth rejected.')
|
||||||
m.add_byte(chr(MSG_USERAUTH_FAILURE))
|
m.add_byte(chr(MSG_USERAUTH_FAILURE))
|
||||||
m.add_string(self.get_allowed_auths(username))
|
m.add_string(self.server_object.get_allowed_auths(username))
|
||||||
if result == self.AUTH_PARTIALLY_SUCCESSFUL:
|
if result == self.AUTH_PARTIALLY_SUCCESSFUL:
|
||||||
m.add_boolean(1)
|
m.add_boolean(1)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -0,0 +1,154 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
# Copyright (C) 2003-2004 Robey Pointer <robey@lag.net>
|
||||||
|
#
|
||||||
|
# This file is part of paramiko.
|
||||||
|
#
|
||||||
|
# Paramiko is free software; you can redistribute it and/or modify it under the
|
||||||
|
# terms of the GNU Lesser General Public License as published by the Free
|
||||||
|
# Software Foundation; either version 2.1 of the License, or (at your option)
|
||||||
|
# any later version.
|
||||||
|
#
|
||||||
|
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
|
# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||||
|
|
||||||
|
"""
|
||||||
|
L{ServerInterface} is an interface to override for server support.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from auth_transport import Transport
|
||||||
|
|
||||||
|
class ServerInterface (object):
|
||||||
|
"""
|
||||||
|
This class defines an interface for controlling the behavior of paramiko
|
||||||
|
in server mode.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def check_channel_request(self, kind, chanid):
|
||||||
|
"""
|
||||||
|
Determine if a channel request of a given type will be granted, and
|
||||||
|
return a suitable L{Channel} object. This method is called in server
|
||||||
|
mode when the client requests a channel, after authentication is
|
||||||
|
complete.
|
||||||
|
|
||||||
|
You will generally want to subclass L{Channel} to override some of the
|
||||||
|
methods for handling client requests (such as connecting to a subsystem
|
||||||
|
opening a shell) to determine what you want to allow or disallow. For
|
||||||
|
this reason, L{check_channel_request} must return a new object of that
|
||||||
|
type. The C{chanid} parameter is passed so that you can use it in
|
||||||
|
L{Channel}'s constructor.
|
||||||
|
|
||||||
|
The default implementation always returns C{None}, rejecting any
|
||||||
|
channel requests. A useful server must override this method.
|
||||||
|
|
||||||
|
@param kind: the kind of channel the client would like to open
|
||||||
|
(usually C{"session"}).
|
||||||
|
@type kind: string
|
||||||
|
@param chanid: ID of the channel, required to create a new L{Channel}
|
||||||
|
object.
|
||||||
|
@type chanid: int
|
||||||
|
@return: a new L{Channel} object (or subclass thereof), or C{None} to
|
||||||
|
refuse the request.
|
||||||
|
@rtype: L{Channel}
|
||||||
|
"""
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_allowed_auths(self, username):
|
||||||
|
"""
|
||||||
|
Return a list of authentication methods supported by the server.
|
||||||
|
This list is sent to clients attempting to authenticate, to inform them
|
||||||
|
of authentication methods that might be successful.
|
||||||
|
|
||||||
|
The "list" is actually a string of comma-separated names of types of
|
||||||
|
authentication. Possible values are C{"password"}, C{"publickey"},
|
||||||
|
and C{"none"}.
|
||||||
|
|
||||||
|
The default implementation always returns C{"password"}.
|
||||||
|
|
||||||
|
@param username: the username requesting authentication.
|
||||||
|
@type username: string
|
||||||
|
@return: a comma-separated list of authentication types
|
||||||
|
@rtype: string
|
||||||
|
"""
|
||||||
|
return 'password'
|
||||||
|
|
||||||
|
def check_auth_none(self, username):
|
||||||
|
"""
|
||||||
|
Determine if a client may open channels with no (further)
|
||||||
|
authentication.
|
||||||
|
|
||||||
|
Return L{Transport.AUTH_FAILED} if the client must authenticate, or
|
||||||
|
L{Transport.AUTH_SUCCESSFUL} if it's okay for the client to not
|
||||||
|
authenticate.
|
||||||
|
|
||||||
|
The default implementation always returns L{Transport.AUTH_FAILED}.
|
||||||
|
|
||||||
|
@param username: the username of the client.
|
||||||
|
@type username: string
|
||||||
|
@return: L{Transport.AUTH_FAILED} if the authentication fails;
|
||||||
|
L{Transport.AUTH_SUCCESSFUL} if it succeeds.
|
||||||
|
@rtype: int
|
||||||
|
"""
|
||||||
|
return Transport.AUTH_FAILED
|
||||||
|
|
||||||
|
def check_auth_password(self, username, password):
|
||||||
|
"""
|
||||||
|
Determine if a given username and password supplied by the client is
|
||||||
|
acceptable for use in authentication.
|
||||||
|
|
||||||
|
Return L{Transport.AUTH_FAILED} if the password is not accepted,
|
||||||
|
L{Transport.AUTH_SUCCESSFUL} if the password is accepted and completes
|
||||||
|
the authentication, or L{Transport.AUTH_PARTIALLY_SUCCESSFUL} if your
|
||||||
|
authentication is stateful, and this key is accepted for
|
||||||
|
authentication, but more authentication is required. (In this latter
|
||||||
|
case, L{get_allowed_auths} will be called to report to the client what
|
||||||
|
options it has for continuing the authentication.)
|
||||||
|
|
||||||
|
The default implementation always returns L{Transport.AUTH_FAILED}.
|
||||||
|
|
||||||
|
@param username: the username of the authenticating client.
|
||||||
|
@type username: string
|
||||||
|
@param password: the password given by the client.
|
||||||
|
@type password: string
|
||||||
|
@return: L{Transport.AUTH_FAILED} if the authentication fails;
|
||||||
|
L{Transport.AUTH_SUCCESSFUL} if it succeeds;
|
||||||
|
L{Transport.AUTH_PARTIALLY_SUCCESSFUL} if the password auth is
|
||||||
|
successful, but authentication must continue.
|
||||||
|
@rtype: int
|
||||||
|
"""
|
||||||
|
return Transport.AUTH_FAILED
|
||||||
|
|
||||||
|
def check_auth_publickey(self, username, key):
|
||||||
|
"""
|
||||||
|
Determine if a given key supplied by the client is acceptable for use
|
||||||
|
in authentication. You should override this method in server mode to
|
||||||
|
check the username and key and decide if you would accept a signature
|
||||||
|
made using this key.
|
||||||
|
|
||||||
|
Return L{Transport.AUTH_FAILED} if the key is not accepted,
|
||||||
|
L{Transport.AUTH_SUCCESSFUL} if the key is accepted and completes the
|
||||||
|
authentication, or L{Transport.AUTH_PARTIALLY_SUCCESSFUL} if your
|
||||||
|
authentication is stateful, and this key is accepted for
|
||||||
|
authentication, but more authentication is required. (In this latter
|
||||||
|
case, L{get_allowed_auths} will be called to report to the client what
|
||||||
|
options it has for continuing the authentication.)
|
||||||
|
|
||||||
|
The default implementation always returns L{Transport.AUTH_FAILED}.
|
||||||
|
|
||||||
|
@param username: the username of the authenticating client.
|
||||||
|
@type username: string
|
||||||
|
@param key: the key object provided by the client.
|
||||||
|
@type key: L{PKey <pkey.PKey>}
|
||||||
|
@return: L{Transport.AUTH_FAILED} if the client can't authenticate
|
||||||
|
with this key; L{Transport.AUTH_SUCCESSFUL} if it can;
|
||||||
|
L{Transport.AUTH_PARTIALLY_SUCCESSFUL} if it can authenticate with
|
||||||
|
this key but must continue with authentication.
|
||||||
|
@rtype: int
|
||||||
|
"""
|
||||||
|
return Transport.AUTH_FAILED
|
|
@ -138,6 +138,9 @@ class BaseTransport (threading.Thread):
|
||||||
self.sock = sock
|
self.sock = sock
|
||||||
# Python < 2.3 doesn't have the settimeout method - RogerB
|
# Python < 2.3 doesn't have the settimeout method - RogerB
|
||||||
try:
|
try:
|
||||||
|
# we set the timeout so we can check self.active periodically to
|
||||||
|
# see if we should bail. socket.timeout exception is never
|
||||||
|
# propagated.
|
||||||
self.sock.settimeout(0.1)
|
self.sock.settimeout(0.1)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
@ -155,7 +158,7 @@ class BaseTransport (threading.Thread):
|
||||||
self.expected_packet = 0
|
self.expected_packet = 0
|
||||||
self.active = False
|
self.active = False
|
||||||
self.initial_kex_done = False
|
self.initial_kex_done = False
|
||||||
self.write_lock = threading.Lock() # lock around outbound writes (packet computation)
|
self.write_lock = threading.RLock() # lock around outbound writes (packet computation)
|
||||||
self.lock = threading.Lock() # synchronization (always higher level than write_lock)
|
self.lock = threading.Lock() # synchronization (always higher level than write_lock)
|
||||||
self.channels = { } # (id -> Channel)
|
self.channels = { } # (id -> Channel)
|
||||||
self.channel_events = { } # (id -> Event)
|
self.channel_events = { } # (id -> Event)
|
||||||
|
@ -165,10 +168,13 @@ class BaseTransport (threading.Thread):
|
||||||
self.max_packet_size = 32768
|
self.max_packet_size = 32768
|
||||||
self.ultra_debug = False
|
self.ultra_debug = False
|
||||||
self.saved_exception = None
|
self.saved_exception = None
|
||||||
|
self.clear_to_send = threading.Event()
|
||||||
# used for noticing when to re-key:
|
# used for noticing when to re-key:
|
||||||
self.received_bytes = 0
|
self.received_bytes = 0
|
||||||
self.received_packets = 0
|
self.received_packets = 0
|
||||||
self.received_packets_overflow = 0
|
self.received_packets_overflow = 0
|
||||||
|
self.sent_bytes = 0
|
||||||
|
self.sent_packets = 0
|
||||||
# user-defined event callbacks:
|
# user-defined event callbacks:
|
||||||
self.completion_event = None
|
self.completion_event = None
|
||||||
# keepalives:
|
# keepalives:
|
||||||
|
@ -176,6 +182,7 @@ class BaseTransport (threading.Thread):
|
||||||
self.keepalive_last = time.time()
|
self.keepalive_last = time.time()
|
||||||
# server mode:
|
# server mode:
|
||||||
self.server_mode = 0
|
self.server_mode = 0
|
||||||
|
self.server_object = None
|
||||||
self.server_key_dict = { }
|
self.server_key_dict = { }
|
||||||
self.server_accepts = [ ]
|
self.server_accepts = [ ]
|
||||||
self.server_accept_cv = threading.Condition(self.lock)
|
self.server_accept_cv = threading.Condition(self.lock)
|
||||||
|
@ -223,7 +230,7 @@ class BaseTransport (threading.Thread):
|
||||||
self.completion_event = event
|
self.completion_event = event
|
||||||
self.start()
|
self.start()
|
||||||
|
|
||||||
def start_server(self, event=None):
|
def start_server(self, event=None, server=None):
|
||||||
"""
|
"""
|
||||||
Negotiate a new SSH2 session as a server. This is the first step after
|
Negotiate a new SSH2 session as a server. This is the first step after
|
||||||
creating a new L{Transport} and setting up your server host key(s). A
|
creating a new L{Transport} and setting up your server host key(s). A
|
||||||
|
@ -235,15 +242,16 @@ class BaseTransport (threading.Thread):
|
||||||
|
|
||||||
After a successful negotiation, the client will need to authenticate.
|
After a successful negotiation, the client will need to authenticate.
|
||||||
Override the methods
|
Override the methods
|
||||||
L{get_allowed_auths <Transport.get_allowed_auths>},
|
L{get_allowed_auths <ServerInterface.get_allowed_auths>},
|
||||||
L{check_auth_none <Transport.check_auth_none>},
|
L{check_auth_none <ServerInterface.check_auth_none>},
|
||||||
L{check_auth_password <Transport.check_auth_password>}, and
|
L{check_auth_password <ServerInterface.check_auth_password>}, and
|
||||||
L{check_auth_publickey <Transport.check_auth_publickey>} to control the
|
L{check_auth_publickey <ServerInterface.check_auth_publickey>} in the
|
||||||
authentication process.
|
given C{server} object to control the authentication process.
|
||||||
|
|
||||||
After a successful authentication, the client should request to open
|
After a successful authentication, the client should request to open
|
||||||
a channel. Override L{check_channel_request} to allow channels to
|
a channel. Override
|
||||||
be opened.
|
L{check_channel_request <ServerInterface.check_channel_request>} in the
|
||||||
|
given C{server} object to allow channels to be opened.
|
||||||
|
|
||||||
@note: After calling this method (or L{start_client} or L{connect}),
|
@note: After calling this method (or L{start_client} or L{connect}),
|
||||||
you should no longer directly read from or write to the original socket
|
you should no longer directly read from or write to the original socket
|
||||||
|
@ -251,8 +259,14 @@ class BaseTransport (threading.Thread):
|
||||||
|
|
||||||
@param event: an event to trigger when negotiation is complete.
|
@param event: an event to trigger when negotiation is complete.
|
||||||
@type event: threading.Event
|
@type event: threading.Event
|
||||||
|
@param server: an object used to perform authentication and create
|
||||||
|
L{Channel}s.
|
||||||
|
@type server: L{server.ServerInterface}
|
||||||
"""
|
"""
|
||||||
|
if server is None:
|
||||||
|
server = ServerInterface()
|
||||||
self.server_mode = 1
|
self.server_mode = 1
|
||||||
|
self.server_object = server
|
||||||
self.completion_event = event
|
self.completion_event = event
|
||||||
self.start()
|
self.start()
|
||||||
|
|
||||||
|
@ -422,7 +436,7 @@ class BaseTransport (threading.Thread):
|
||||||
self.channel_events[chanid] = event = threading.Event()
|
self.channel_events[chanid] = event = threading.Event()
|
||||||
chan._set_transport(self)
|
chan._set_transport(self)
|
||||||
chan._set_window(self.window_size, self.max_packet_size)
|
chan._set_window(self.window_size, self.max_packet_size)
|
||||||
self._send_message(m)
|
self._send_user_message(m)
|
||||||
finally:
|
finally:
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
while 1:
|
while 1:
|
||||||
|
@ -457,7 +471,7 @@ class BaseTransport (threading.Thread):
|
||||||
if bytes is None:
|
if bytes is None:
|
||||||
bytes = (ord(randpool.get_bytes(1)) % 32) + 10
|
bytes = (ord(randpool.get_bytes(1)) % 32) + 10
|
||||||
m.add_bytes(randpool.get_bytes(bytes))
|
m.add_bytes(randpool.get_bytes(bytes))
|
||||||
self._send_message(m)
|
self._send_user_message(m)
|
||||||
|
|
||||||
def renegotiate_keys(self):
|
def renegotiate_keys(self):
|
||||||
"""
|
"""
|
||||||
|
@ -528,7 +542,7 @@ class BaseTransport (threading.Thread):
|
||||||
for item in data:
|
for item in data:
|
||||||
m.add(item)
|
m.add(item)
|
||||||
self._log(DEBUG, 'Sending global request "%s"' % kind)
|
self._log(DEBUG, 'Sending global request "%s"' % kind)
|
||||||
self._send_message(m)
|
self._send_user_message(m)
|
||||||
if not wait:
|
if not wait:
|
||||||
return True
|
return True
|
||||||
while True:
|
while True:
|
||||||
|
@ -539,36 +553,6 @@ class BaseTransport (threading.Thread):
|
||||||
break
|
break
|
||||||
return self.global_response
|
return self.global_response
|
||||||
|
|
||||||
def check_channel_request(self, kind, chanid):
|
|
||||||
"""
|
|
||||||
I{(subclass override)}
|
|
||||||
Determine if a channel request of a given type will be granted, and
|
|
||||||
return a suitable L{Channel} object. This method is called in server
|
|
||||||
mode when the client requests a channel, after authentication is
|
|
||||||
complete.
|
|
||||||
|
|
||||||
In server mode, you will generally want to subclass L{Channel} to
|
|
||||||
override some of the methods for handling client requests (such as
|
|
||||||
connecting to a subsystem or opening a shell) to determine what you
|
|
||||||
want to allow or disallow. For this reason, L{check_channel_request}
|
|
||||||
must return a new object of that type. The C{chanid} parameter is
|
|
||||||
passed so that you can use it in L{Channel}'s constructor.
|
|
||||||
|
|
||||||
The default implementation always returns C{None}, rejecting any
|
|
||||||
channel requests. A useful server must override this method.
|
|
||||||
|
|
||||||
@param kind: the kind of channel the client would like to open
|
|
||||||
(usually C{"session"}).
|
|
||||||
@type kind: string
|
|
||||||
@param chanid: ID of the channel, required to create a new L{Channel}
|
|
||||||
object.
|
|
||||||
@type chanid: int
|
|
||||||
@return: a new L{Channel} object (or subclass thereof), or C{None} to
|
|
||||||
refuse the request.
|
|
||||||
@rtype: L{Channel}
|
|
||||||
"""
|
|
||||||
return None
|
|
||||||
|
|
||||||
def check_global_request(self, kind, msg):
|
def check_global_request(self, kind, msg):
|
||||||
"""
|
"""
|
||||||
I{(subclass override)}
|
I{(subclass override)}
|
||||||
|
@ -771,7 +755,11 @@ class BaseTransport (threading.Thread):
|
||||||
def _write_all(self, out):
|
def _write_all(self, out):
|
||||||
self.keepalive_last = time.time()
|
self.keepalive_last = time.time()
|
||||||
while len(out) > 0:
|
while len(out) > 0:
|
||||||
n = self.sock.send(out)
|
try:
|
||||||
|
n = self.sock.send(out)
|
||||||
|
except:
|
||||||
|
# could be: (32, 'Broken pipe')
|
||||||
|
n = -1
|
||||||
if n < 0:
|
if n < 0:
|
||||||
raise EOFError()
|
raise EOFError()
|
||||||
if n == len(out):
|
if n == len(out):
|
||||||
|
@ -810,9 +798,33 @@ class BaseTransport (threading.Thread):
|
||||||
self.sequence_number_out += 1L
|
self.sequence_number_out += 1L
|
||||||
self.sequence_number_out %= 0x100000000L
|
self.sequence_number_out %= 0x100000000L
|
||||||
self._write_all(out)
|
self._write_all(out)
|
||||||
|
|
||||||
|
self.sent_bytes += len(out)
|
||||||
|
self.sent_packets += 1
|
||||||
|
if ((self.sent_packets >= self.REKEY_PACKETS) or (self.sent_bytes >= self.REKEY_BYTES)) \
|
||||||
|
and (self.local_kex_init is None):
|
||||||
|
# only ask once for rekeying
|
||||||
|
self._log(DEBUG, 'Rekeying (hit %d packets, %d bytes sent)' %
|
||||||
|
(self.sent_packets, self.sent_bytes))
|
||||||
|
self.received_packets_overflow = 0
|
||||||
|
self._send_kex_init()
|
||||||
finally:
|
finally:
|
||||||
self.write_lock.release()
|
self.write_lock.release()
|
||||||
|
|
||||||
|
def _send_user_message(self, data):
|
||||||
|
"""
|
||||||
|
send a message, but block if we're in key negotiation. this is used
|
||||||
|
for user-initiated requests.
|
||||||
|
"""
|
||||||
|
while 1:
|
||||||
|
self.clear_to_send.wait(0.1)
|
||||||
|
if not self.active:
|
||||||
|
self._log(DEBUG, 'Dropping user packet because connection is dead.')
|
||||||
|
return
|
||||||
|
if self.clear_to_send.isSet():
|
||||||
|
break
|
||||||
|
self._send_message(data)
|
||||||
|
|
||||||
def _read_message(self):
|
def _read_message(self):
|
||||||
"only one thread will ever be in this function"
|
"only one thread will ever be in this function"
|
||||||
header = self._read_all(self.block_size_in)
|
header = self._read_all(self.block_size_in)
|
||||||
|
@ -850,19 +862,19 @@ class BaseTransport (threading.Thread):
|
||||||
# check for rekey
|
# check for rekey
|
||||||
self.received_bytes += packet_size + self.remote_mac_len + 4
|
self.received_bytes += packet_size + self.remote_mac_len + 4
|
||||||
self.received_packets += 1
|
self.received_packets += 1
|
||||||
if (self.received_packets >= self.REKEY_PACKETS) or (self.received_bytes >= self.REKEY_BYTES):
|
if self.local_kex_init is not None:
|
||||||
|
# we've asked to rekey -- give them 20 packets to comply before
|
||||||
|
# dropping the connection
|
||||||
|
self.received_packets_overflow += 1
|
||||||
|
if self.received_packets_overflow >= 20:
|
||||||
|
raise SSHException('Remote transport is ignoring rekey requests')
|
||||||
|
elif (self.received_packets >= self.REKEY_PACKETS) or \
|
||||||
|
(self.received_bytes >= self.REKEY_BYTES):
|
||||||
# only ask once for rekeying
|
# only ask once for rekeying
|
||||||
if self.local_kex_init is None:
|
self._log(DEBUG, 'Rekeying (hit %d packets, %d bytes received)' %
|
||||||
self._log(DEBUG, 'Rekeying (hit %d packets, %d bytes)' % (self.received_packets,
|
(self.received_packets, self.received_bytes))
|
||||||
self.received_bytes))
|
self.received_packets_overflow = 0
|
||||||
self.received_packets_overflow = 0
|
self._send_kex_init()
|
||||||
self._send_kex_init()
|
|
||||||
else:
|
|
||||||
# we've asked to rekey already -- give them 20 packets to
|
|
||||||
# comply, then just drop the connection
|
|
||||||
self.received_packets_overflow += 1
|
|
||||||
if self.received_packets_overflow >= 20:
|
|
||||||
raise SSHException('Remote transport is ignoring rekey requests')
|
|
||||||
|
|
||||||
cmd = ord(payload[0])
|
cmd = ord(payload[0])
|
||||||
self._log(DEBUG, 'Read packet $%x, length %d' % (cmd, len(payload)))
|
self._log(DEBUG, 'Read packet $%x, length %d' % (cmd, len(payload)))
|
||||||
|
@ -964,8 +976,8 @@ class BaseTransport (threading.Thread):
|
||||||
self._log(ERROR, util.tb_strings())
|
self._log(ERROR, util.tb_strings())
|
||||||
self.saved_exception = e
|
self.saved_exception = e
|
||||||
except EOFError, e:
|
except EOFError, e:
|
||||||
self._log(DEBUG, 'EOF')
|
self._log(DEBUG, 'EOF in transport thread')
|
||||||
self._log(DEBUG, util.tb_strings())
|
#self._log(DEBUG, util.tb_strings())
|
||||||
self.saved_exception = e
|
self.saved_exception = e
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
self._log(ERROR, 'Unknown exception: ' + str(e))
|
self._log(ERROR, 'Unknown exception: ' + str(e))
|
||||||
|
@ -990,6 +1002,7 @@ class BaseTransport (threading.Thread):
|
||||||
|
|
||||||
def _negotiate_keys(self, m):
|
def _negotiate_keys(self, m):
|
||||||
# throws SSHException on anything unusual
|
# throws SSHException on anything unusual
|
||||||
|
self.clear_to_send.clear()
|
||||||
if self.local_kex_init == None:
|
if self.local_kex_init == None:
|
||||||
# remote side wants to renegotiate
|
# remote side wants to renegotiate
|
||||||
self._send_kex_init()
|
self._send_kex_init()
|
||||||
|
@ -1033,6 +1046,7 @@ class BaseTransport (threading.Thread):
|
||||||
announce to the other side that we'd like to negotiate keys, and what
|
announce to the other side that we'd like to negotiate keys, and what
|
||||||
kind of key negotiation we support.
|
kind of key negotiation we support.
|
||||||
"""
|
"""
|
||||||
|
self.clear_to_send.clear()
|
||||||
if self.server_mode:
|
if self.server_mode:
|
||||||
if (self._modulus_pack is None) and ('diffie-hellman-group-exchange-sha1' in self.preferred_kex):
|
if (self._modulus_pack is None) and ('diffie-hellman-group-exchange-sha1' in self.preferred_kex):
|
||||||
# can't do group-exchange if we don't have a pack of potential primes
|
# can't do group-exchange if we don't have a pack of potential primes
|
||||||
|
@ -1066,6 +1080,8 @@ class BaseTransport (threading.Thread):
|
||||||
self.received_bytes = 0
|
self.received_bytes = 0
|
||||||
self.received_packets = 0
|
self.received_packets = 0
|
||||||
self.received_packets_overflow = 0
|
self.received_packets_overflow = 0
|
||||||
|
self.sent_bytes = 0
|
||||||
|
self.sent_packets = 0
|
||||||
|
|
||||||
cookie = m.get_bytes(16)
|
cookie = m.get_bytes(16)
|
||||||
kex_algo_list = m.get_list()
|
kex_algo_list = m.get_list()
|
||||||
|
@ -1211,6 +1227,8 @@ class BaseTransport (threading.Thread):
|
||||||
# send an event?
|
# send an event?
|
||||||
if self.completion_event != None:
|
if self.completion_event != None:
|
||||||
self.completion_event.set()
|
self.completion_event.set()
|
||||||
|
# it's now okay to send data again (if this was a re-key)
|
||||||
|
self.clear_to_send.set()
|
||||||
return
|
return
|
||||||
|
|
||||||
def _parse_disconnect(self, m):
|
def _parse_disconnect(self, m):
|
||||||
|
@ -1307,7 +1325,7 @@ class BaseTransport (threading.Thread):
|
||||||
self.channel_counter += 1
|
self.channel_counter += 1
|
||||||
finally:
|
finally:
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
chan = self.check_channel_request(kind, my_chanid)
|
chan = self.server_object.check_channel_request(kind, my_chanid)
|
||||||
if (chan is None) or (type(chan) is int):
|
if (chan is None) or (type(chan) is int):
|
||||||
self._log(DEBUG, 'Rejecting "%s" channel request from client.' % kind)
|
self._log(DEBUG, 'Rejecting "%s" channel request from client.' % kind)
|
||||||
reject = True
|
reject = True
|
||||||
|
@ -1373,3 +1391,5 @@ class BaseTransport (threading.Thread):
|
||||||
MSG_CHANNEL_EOF: Channel._handle_eof,
|
MSG_CHANNEL_EOF: Channel._handle_eof,
|
||||||
MSG_CHANNEL_CLOSE: Channel._handle_close,
|
MSG_CHANNEL_CLOSE: Channel._handle_close,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
from server import ServerInterface
|
||||||
|
|
Loading…
Reference in New Issue