[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)
|
||||
* server mode needs better documentation
|
||||
* 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())
|
||||
|
||||
|
||||
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,18 +23,18 @@ 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'
|
||||
|
@ -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():
|
||||
|
|
|
@ -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' ]
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
# 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:
|
||||
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):
|
||||
# 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
|
||||
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
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue