summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRobey Pointer <robey@lag.net>2004-08-27 00:57:40 +0000
committerRobey Pointer <robey@lag.net>2004-08-27 00:57:40 +0000
commitc86c4f3949e2cc6db3c09828b9518e27c6c3a304 (patch)
tree16d0158a7b14c91de53d1dace8864385db6fb3b8
parent34d975b9722236ae946c02a5c23d7231e67fc4e1 (diff)
[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
-rw-r--r--README1
-rwxr-xr-xdemo_server.py19
-rw-r--r--paramiko/__init__.py3
-rw-r--r--paramiko/auth_transport.py106
-rw-r--r--paramiko/server.py154
-rw-r--r--paramiko/transport.py136
6 files changed, 249 insertions, 170 deletions
diff --git a/README b/README
index 173533c9..8958519f 100644
--- a/README
+++ b/README
@@ -155,4 +155,3 @@ v0.9 FEAROW
* multi-part auth not supported (ie, need username AND pk)
* server mode needs better documentation
* sftp server mode
-* make invoke_subsystem, etc wait for a reply
diff --git a/demo_server.py b/demo_server.py
index 5d6cbb5c..8d889963 100755
--- a/demo_server.py
+++ b/demo_server.py
@@ -15,7 +15,7 @@ host_key.read_private_key_file('demo_dss_key')
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 = 'AAAAB3NzaC1yc2EAAAABIwAAAIEAyO4it3fHlmGZWJaGrfeHOVY7RWO3P9M7hpfAu7jJ2d7eothvfeuoRFtJwhUmZDluRdFyhFY/hFAh76PJKGAusIqIQKlkJxMCKDqIexkgHAfID/6mqvmnSJf0b5W8v5h2pI/stOSwTQ+pxVhwJ9ctYDhRSlF0iTUWT10hcuO4Ks8='
good_pub_key = paramiko.RSAKey(data=base64.decodestring(data))
@@ -23,24 +23,24 @@ class ServerTransport(paramiko.Transport):
def check_channel_request(self, kind, chanid):
if kind == 'session':
return ServerChannel(chanid)
- return self.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
+ return paramiko.Transport.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
def check_auth_password(self, username, password):
if (username == 'robey') and (password == 'foo'):
- return self.AUTH_SUCCESSFUL
- return self.AUTH_FAILED
+ return paramiko.Transport.AUTH_SUCCESSFUL
+ return paramiko.Transport.AUTH_FAILED
def check_auth_publickey(self, username, key):
print 'Auth attempt with key: ' + paramiko.util.hexify(key.get_fingerprint())
if (username == 'robey') and (key == self.good_pub_key):
- return self.AUTH_SUCCESSFUL
- return self.AUTH_FAILED
+ return paramiko.Transport.AUTH_SUCCESSFUL
+ return paramiko.Transport.AUTH_FAILED
def get_allowed_auths(self, username):
return 'password,publickey'
-class ServerChannel(paramiko.Channel):
+class ServerChannel (paramiko.Channel):
"Channel descendant that pretends to understand pty and shell requests"
def __init__(self, chanid):
@@ -61,7 +61,6 @@ try:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('', 2200))
except Exception, e:
-
print '*** Bind failed: ' + str(e)
traceback.print_exc()
sys.exit(1)
@@ -79,14 +78,14 @@ print 'Got a connection!'
try:
event = threading.Event()
- t = ServerTransport(client)
+ t = paramiko.Transport(client)
try:
t.load_server_moduli()
except:
print '(Failed to load moduli -- gex will be unsupported.)'
raise
t.add_server_key(host_key)
- t.start_server(event)
+ t.start_server(event, Server())
while 1:
event.wait(0.1)
if not t.is_active():
diff --git a/paramiko/__init__.py b/paramiko/__init__.py
index 5d2925ef..c987af24 100644
--- a/paramiko/__init__.py
+++ b/paramiko/__init__.py
@@ -76,6 +76,7 @@ SSHException = ssh_exception.SSHException
Message = message.Message
PasswordRequiredException = ssh_exception.PasswordRequiredException
SFTP = sftp.SFTP
+ServerInterface = server.ServerInterface
__all__ = [ 'Transport',
@@ -86,6 +87,7 @@ __all__ = [ 'Transport',
'SSHException',
'PasswordRequiredException',
'SFTP',
+ 'ServerInterface',
'transport',
'auth_transport',
'channel',
@@ -95,4 +97,5 @@ __all__ = [ 'Transport',
'message',
'ssh_exception',
'sftp',
+ 'server',
'util' ]
diff --git a/paramiko/auth_transport.py b/paramiko/auth_transport.py
index 048e3f02..55f63633 100644
--- a/paramiko/auth_transport.py
+++ b/paramiko/auth_transport.py
@@ -156,102 +156,6 @@ class Transport (BaseTransport):
finally:
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...
@@ -355,7 +259,7 @@ class Transport (BaseTransport):
self.auth_username = username
if method == 'none':
- result = self.check_auth_none(username)
+ result = self.server_object.check_auth_none(username)
elif method == 'password':
changereq = m.get_boolean()
password = m.get_string().decode('UTF-8')
@@ -366,7 +270,7 @@ class Transport (BaseTransport):
newpassword = m.get_string().decode('UTF-8')
result = self.AUTH_FAILED
else:
- result = self.check_auth_password(username, password)
+ result = self.server_object.check_auth_password(username, password)
elif method == 'publickey':
sig_attached = m.get_boolean()
keytype = m.get_string()
@@ -377,7 +281,7 @@ class Transport (BaseTransport):
self._disconnect_no_more_auth()
return
# 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:
# key is okay, verify it
if not sig_attached:
@@ -395,7 +299,7 @@ class Transport (BaseTransport):
self._log(DEBUG, 'Auth rejected: invalid signature')
result = self.AUTH_FAILED
else:
- result = self.check_auth_none(username)
+ result = self.server_object.check_auth_none(username)
# okay, send result
m = Message()
if result == self.AUTH_SUCCESSFUL:
@@ -405,7 +309,7 @@ class Transport (BaseTransport):
else:
self._log(DEBUG, 'Auth rejected.')
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:
m.add_boolean(1)
else:
diff --git a/paramiko/server.py b/paramiko/server.py
new file mode 100644
index 00000000..6abcffbf
--- /dev/null
+++ b/paramiko/server.py
@@ -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
diff --git a/paramiko/transport.py b/paramiko/transport.py
index 06f75801..7931a11f 100644
--- a/paramiko/transport.py
+++ b/paramiko/transport.py
@@ -138,6 +138,9 @@ class BaseTransport (threading.Thread):
self.sock = sock
# Python < 2.3 doesn't have the settimeout method - RogerB
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)
except AttributeError:
pass
@@ -155,7 +158,7 @@ class BaseTransport (threading.Thread):
self.expected_packet = 0
self.active = 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.channels = { } # (id -> Channel)
self.channel_events = { } # (id -> Event)
@@ -165,10 +168,13 @@ class BaseTransport (threading.Thread):
self.max_packet_size = 32768
self.ultra_debug = False
self.saved_exception = None
+ self.clear_to_send = threading.Event()
# used for noticing when to re-key:
self.received_bytes = 0
self.received_packets = 0
self.received_packets_overflow = 0
+ self.sent_bytes = 0
+ self.sent_packets = 0
# user-defined event callbacks:
self.completion_event = None
# keepalives:
@@ -176,6 +182,7 @@ class BaseTransport (threading.Thread):
self.keepalive_last = time.time()
# server mode:
self.server_mode = 0
+ self.server_object = None
self.server_key_dict = { }
self.server_accepts = [ ]
self.server_accept_cv = threading.Condition(self.lock)
@@ -223,7 +230,7 @@ class BaseTransport (threading.Thread):
self.completion_event = event
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
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.
Override the methods
- L{get_allowed_auths <Transport.get_allowed_auths>},
- L{check_auth_none <Transport.check_auth_none>},
- L{check_auth_password <Transport.check_auth_password>}, and
- L{check_auth_publickey <Transport.check_auth_publickey>} to control the
- authentication process.
+ L{get_allowed_auths <ServerInterface.get_allowed_auths>},
+ L{check_auth_none <ServerInterface.check_auth_none>},
+ L{check_auth_password <ServerInterface.check_auth_password>}, and
+ L{check_auth_publickey <ServerInterface.check_auth_publickey>} in the
+ given C{server} object to control the authentication process.
After a successful authentication, the client should request to open
- a channel. Override L{check_channel_request} to allow channels to
- be opened.
+ a channel. Override
+ 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}),
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.
@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_object = server
self.completion_event = event
self.start()
@@ -422,7 +436,7 @@ class BaseTransport (threading.Thread):
self.channel_events[chanid] = event = threading.Event()
chan._set_transport(self)
chan._set_window(self.window_size, self.max_packet_size)
- self._send_message(m)
+ self._send_user_message(m)
finally:
self.lock.release()
while 1:
@@ -457,7 +471,7 @@ class BaseTransport (threading.Thread):
if bytes is None:
bytes = (ord(randpool.get_bytes(1)) % 32) + 10
m.add_bytes(randpool.get_bytes(bytes))
- self._send_message(m)
+ self._send_user_message(m)
def renegotiate_keys(self):
"""
@@ -528,7 +542,7 @@ class BaseTransport (threading.Thread):
for item in data:
m.add(item)
self._log(DEBUG, 'Sending global request "%s"' % kind)
- self._send_message(m)
+ self._send_user_message(m)
if not wait:
return True
while True:
@@ -539,36 +553,6 @@ class BaseTransport (threading.Thread):
break
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):
"""
I{(subclass override)}
@@ -771,7 +755,11 @@ class BaseTransport (threading.Thread):
def _write_all(self, out):
self.keepalive_last = time.time()
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:
raise EOFError()
if n == len(out):
@@ -810,9 +798,33 @@ class BaseTransport (threading.Thread):
self.sequence_number_out += 1L
self.sequence_number_out %= 0x100000000L
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:
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):
"only one thread will ever be in this function"
header = self._read_all(self.block_size_in)
@@ -850,19 +862,19 @@ class BaseTransport (threading.Thread):
# check for rekey
self.received_bytes += packet_size + self.remote_mac_len + 4
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
- if self.local_kex_init is None:
- self._log(DEBUG, 'Rekeying (hit %d packets, %d bytes)' % (self.received_packets,
- self.received_bytes))
- self.received_packets_overflow = 0
- 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')
+ self._log(DEBUG, 'Rekeying (hit %d packets, %d bytes received)' %
+ (self.received_packets, self.received_bytes))
+ self.received_packets_overflow = 0
+ self._send_kex_init()
cmd = ord(payload[0])
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.saved_exception = e
except EOFError, e:
- self._log(DEBUG, 'EOF')
- self._log(DEBUG, util.tb_strings())
+ self._log(DEBUG, 'EOF in transport thread')
+ #self._log(DEBUG, util.tb_strings())
self.saved_exception = e
except Exception, e:
self._log(ERROR, 'Unknown exception: ' + str(e))
@@ -990,6 +1002,7 @@ class BaseTransport (threading.Thread):
def _negotiate_keys(self, m):
# throws SSHException on anything unusual
+ self.clear_to_send.clear()
if self.local_kex_init == None:
# remote side wants to renegotiate
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
kind of key negotiation we support.
"""
+ self.clear_to_send.clear()
if self.server_mode:
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
@@ -1066,6 +1080,8 @@ class BaseTransport (threading.Thread):
self.received_bytes = 0
self.received_packets = 0
self.received_packets_overflow = 0
+ self.sent_bytes = 0
+ self.sent_packets = 0
cookie = m.get_bytes(16)
kex_algo_list = m.get_list()
@@ -1211,6 +1227,8 @@ class BaseTransport (threading.Thread):
# send an event?
if self.completion_event != None:
self.completion_event.set()
+ # it's now okay to send data again (if this was a re-key)
+ self.clear_to_send.set()
return
def _parse_disconnect(self, m):
@@ -1307,7 +1325,7 @@ class BaseTransport (threading.Thread):
self.channel_counter += 1
finally:
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):
self._log(DEBUG, 'Rejecting "%s" channel request from client.' % kind)
reject = True
@@ -1373,3 +1391,5 @@ class BaseTransport (threading.Thread):
MSG_CHANNEL_EOF: Channel._handle_eof,
MSG_CHANNEL_CLOSE: Channel._handle_close,
}
+
+from server import ServerInterface