[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:
Robey Pointer 2004-08-27 00:57:40 +00:00
parent 34d975b972
commit c86c4f3949
6 changed files with 249 additions and 170 deletions

1
README
View File

@ -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

View File

@ -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():

View File

@ -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' ]

View File

@ -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:

154
paramiko/server.py Normal file
View File

@ -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

View File

@ -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