diff options
author | Robey Pointer <robey@lag.net> | 2004-09-03 22:39:20 +0000 |
---|---|---|
committer | Robey Pointer <robey@lag.net> | 2004-09-03 22:39:20 +0000 |
commit | aba7e37a383fc3d7ffedc6d9e433f65223ac5fe2 (patch) | |
tree | 0747489e0578ef2c62c51312b339cc79d96e1312 | |
parent | 440b3de06abfd358e93e698fde178ef0c5c85939 (diff) |
[project @ Arch-1:robey@lag.net--2003-public%secsh--dev--1.0--patch-70]
clean up server interface; no longer need to subclass Channel
- export AUTH_*, OPEN_FAILED_*, and the new OPEN_SUCCEEDED into the paramiko
namespace instead of making people dig into paramiko.Transport.AUTH_* etc.
- move all of the check_* methods from Channel to ServerInterface so apps
don't need to subclass Channel anymore just to run an ssh server
- ServerInterface.check_channel_request() returns an error code now, not a
new Channel object
- fix demo_server.py to follow all these changes
- fix a bunch of places where i used "string" in docstrings but meant "str"
- added Channel.get_id()
-rw-r--r-- | README | 2 | ||||
-rwxr-xr-x | demo_server.py | 37 | ||||
-rw-r--r-- | paramiko/__init__.py | 3 | ||||
-rw-r--r-- | paramiko/auth_transport.py | 43 | ||||
-rw-r--r-- | paramiko/channel.py | 138 | ||||
-rw-r--r-- | paramiko/common.py | 13 | ||||
-rw-r--r-- | paramiko/server.py | 195 | ||||
-rw-r--r-- | paramiko/sftp.py | 6 | ||||
-rw-r--r-- | paramiko/transport.py | 64 |
9 files changed, 293 insertions, 208 deletions
@@ -155,3 +155,5 @@ v0.9 FEAROW * multi-part auth not supported (ie, need username AND pk) * server mode needs better documentation * sftp server mode + +ivysaur? diff --git a/demo_server.py b/demo_server.py index 8d889963..2f08f590 100755 --- a/demo_server.py +++ b/demo_server.py @@ -20,38 +20,34 @@ class Server (paramiko.ServerInterface): data = 'AAAAB3NzaC1yc2EAAAABIwAAAIEAyO4it3fHlmGZWJaGrfeHOVY7RWO3P9M7hpfAu7jJ2d7eothvfeuoRFtJwhUmZDluRdFyhFY/hFAh76PJKGAusIqIQKlkJxMCKDqIexkgHAfID/6mqvmnSJf0b5W8v5h2pI/stOSwTQ+pxVhwJ9ctYDhRSlF0iTUWT10hcuO4Ks8=' good_pub_key = paramiko.RSAKey(data=base64.decodestring(data)) + def __init__(self): + self.event = threading.Event() + def check_channel_request(self, kind, chanid): if kind == 'session': - return ServerChannel(chanid) - return paramiko.Transport.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED + return paramiko.OPEN_SUCCEEDED + return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED def check_auth_password(self, username, password): if (username == 'robey') and (password == 'foo'): - return paramiko.Transport.AUTH_SUCCESSFUL - return paramiko.Transport.AUTH_FAILED + return paramiko.AUTH_SUCCESSFUL + return paramiko.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 paramiko.Transport.AUTH_SUCCESSFUL - return paramiko.Transport.AUTH_FAILED + return paramiko.AUTH_SUCCESSFUL + return paramiko.AUTH_FAILED def get_allowed_auths(self, username): return 'password,publickey' - -class ServerChannel (paramiko.Channel): - "Channel descendant that pretends to understand pty and shell requests" - - def __init__(self, chanid): - paramiko.Channel.__init__(self, chanid) - self.event = threading.Event() - - def check_pty_request(self, term, width, height, pixelwidth, pixelheight, modes): + def check_channel_shell_request(self, channel): + self.event.set() return True - def check_shell_request(self): - self.event.set() + def check_channel_pty_request(self, channel, term, width, height, pixelwidth, + pixelheight, modes): return True @@ -85,7 +81,8 @@ try: print '(Failed to load moduli -- gex will be unsupported.)' raise t.add_server_key(host_key) - t.start_server(event, Server()) + server = Server() + t.start_server(event, server) while 1: event.wait(0.1) if not t.is_active(): @@ -101,8 +98,8 @@ try: print '*** No channel.' sys.exit(1) print 'Authenticated!' - chan.event.wait(10) - if not chan.event.isSet(): + server.event.wait(10) + if not server.event.isSet(): print '*** Client never asked for a shell.' sys.exit(1) diff --git a/paramiko/__init__.py b/paramiko/__init__.py index 3af2125d..3bfc90cd 100644 --- a/paramiko/__init__.py +++ b/paramiko/__init__.py @@ -79,6 +79,9 @@ SFTP = sftp.SFTP ServerInterface = server.ServerInterface SecurityOptions = transport.SecurityOptions +from common import AUTH_SUCCESSFUL, AUTH_PARTIALLY_SUCCESSFUL, AUTH_FAILED, \ + OPEN_SUCCEEDED, OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED, OPEN_FAILED_CONNECT_FAILED, \ + OPEN_FAILED_UNKNOWN_CHANNEL_TYPE, OPEN_FAILED_RESOURCE_SHORTAGE __all__ = [ 'Transport', 'SecurityOptions', diff --git a/paramiko/auth_transport.py b/paramiko/auth_transport.py index 55f63633..c1919cd3 100644 --- a/paramiko/auth_transport.py +++ b/paramiko/auth_transport.py @@ -47,8 +47,6 @@ class Transport (BaseTransport): another shell window). """ - AUTH_SUCCESSFUL, AUTH_PARTIALLY_SUCCESSFUL, AUTH_FAILED = range(3) - def __init__(self, sock): BaseTransport.__init__(self, sock) self.username = None @@ -60,20 +58,22 @@ class Transport (BaseTransport): self.auth_complete = 0 def __repr__(self): + out = '<paramiko.Transport at %s' % hex(id(self)) if not self.active: - return '<paramiko.Transport (unconnected)>' - out = '<paramiko.Transport' - if self.local_cipher != '': - out += ' (cipher %s, %d bits)' % (self.local_cipher, self._cipher_info[self.local_cipher]['key-size'] * 8) - if self.authenticated: - if len(self.channels) == 1: - out += ' (active; 1 open channel)' - else: - out += ' (active; %d open channels)' % len(self.channels) - elif self.initial_kex_done: - out += ' (connected; awaiting auth)' + out += ' (unconnected)' else: - out += ' (connecting)' + if self.local_cipher != '': + out += ' (cipher %s, %d bits)' % (self.local_cipher, + self._cipher_info[self.local_cipher]['key-size'] * 8) + if self.authenticated: + if len(self.channels) == 1: + out += ' (active; 1 open channel)' + else: + out += ' (active; %d open channels)' % len(self.channels) + elif self.initial_kex_done: + out += ' (connected; awaiting auth)' + else: + out += ' (connecting)' out += '>' return out @@ -268,21 +268,24 @@ class Transport (BaseTransport): # the list of valid auth types from the callback anyway self._log(DEBUG, 'Auth request to change passwords (rejected)') newpassword = m.get_string().decode('UTF-8') - result = self.AUTH_FAILED + result = AUTH_FAILED else: result = self.server_object.check_auth_password(username, password) elif method == 'publickey': sig_attached = m.get_boolean() keytype = m.get_string() keyblob = m.get_string() - key = self._key_from_blob(keytype, keyblob) + try: + key = self._key_info[keytype](Message(keyblob)) + except: + key = None if (key is None) or (not key.valid): self._log(DEBUG, 'Auth rejected: unsupported or mangled public key') self._disconnect_no_more_auth() return # first check if this key is okay... if not, we can skip the verify result = self.server_object.check_auth_publickey(username, key) - if result != self.AUTH_FAILED: + if result != AUTH_FAILED: # key is okay, verify it if not sig_attached: # client wants to know if this key is acceptable, before it @@ -297,12 +300,12 @@ class Transport (BaseTransport): blob = self._get_session_blob(key, service, username) if not key.verify_ssh_sig(blob, sig): self._log(DEBUG, 'Auth rejected: invalid signature') - result = self.AUTH_FAILED + result = AUTH_FAILED else: result = self.server_object.check_auth_none(username) # okay, send result m = Message() - if result == self.AUTH_SUCCESSFUL: + if result == AUTH_SUCCESSFUL: self._log(DEBUG, 'Auth granted.') m.add_byte(chr(MSG_USERAUTH_SUCCESS)) self.auth_complete = 1 @@ -310,7 +313,7 @@ class Transport (BaseTransport): self._log(DEBUG, 'Auth rejected.') m.add_byte(chr(MSG_USERAUTH_FAILURE)) m.add_string(self.server_object.get_allowed_auths(username)) - if result == self.AUTH_PARTIALLY_SUCCESSFUL: + if result == AUTH_PARTIALLY_SUCCESSFUL: m.add_boolean(1) else: m.add_boolean(0) diff --git a/paramiko/channel.py b/paramiko/channel.py index 67e9ea4a..cb22a6b2 100644 --- a/paramiko/channel.py +++ b/paramiko/channel.py @@ -87,7 +87,7 @@ class Channel (object): """ Returns a string representation of this object, for debugging. - @rtype: string + @rtype: str """ out = '<paramiko.Channel %d' % self.chanid if self.closed: @@ -111,7 +111,7 @@ class Channel (object): basic terminal semantics for the next command you execute. @param term: the terminal type to emulate (for example, C{'vt100'}). - @type term: string + @type term: str @param width: width (in characters) of the terminal screen @type width: int @param height: height (in characters) of the terminal screen @@ -173,7 +173,7 @@ class Channel (object): being executed. @param command: a shell command to execute. - @type command: string + @type command: str @return: C{True} if the operation succeeded; C{False} if not. @rtype: bool """ @@ -201,7 +201,7 @@ class Channel (object): requested subsystem. @param subsystem: name of the subsystem being requested. - @type subsystem: string + @type subsystem: str @return: C{True} if the operation succeeded; C{False} if not. @rtype: bool """ @@ -268,8 +268,8 @@ class Channel (object): of the log level used for debugging. The name can be fetched with the L{get_name} method. - @param name: new channel name - @type name: string + @param name: new channel name. + @type name: str """ self.name = name self.logger = logging.getLogger('paramiko.chan.' + name) @@ -278,11 +278,25 @@ class Channel (object): """ Get the name of this channel that was previously set by L{set_name}. - @return: the name of this channel - @rtype: string + @return: the name of this channel. + @rtype: str """ return self.name + def get_id(self): + """ + Return the ID # for this channel. The channel ID is unique across + a L{Transport} and usually a small number. It's also the number + passed to L{ServerInterface.check_channel_request} when determining + whether to accept a channel request in server mode. + + @return: the ID of this channel. + @rtype: int + + @since: ivysaur + """ + return self.chanid + ### socket API @@ -389,7 +403,7 @@ class Channel (object): @param nbytes: maximum number of bytes to read. @type nbytes: int @return: data. - @rtype: string + @rtype: str @raise socket.timeout: if no data is ready before the timeout set by L{settimeout}. @@ -436,7 +450,7 @@ class Channel (object): data. @param s: data to send. - @type s: string + @type s: str @return: number of bytes actually sent. @rtype: int @@ -490,7 +504,7 @@ class Channel (object): either all data has been sent or an error occurs. Nothing is returned. @param s: data to send. - @type s: string + @type s: str @raise socket.timeout: if sending stalled for longer than the timeout set by L{settimeout}. @@ -579,83 +593,6 @@ class Channel (object): ### overrides - def check_pty_request(self, term, width, height, pixelwidth, pixelheight, modes): - """ - I{(subclass override)} - Determine if a pseudo-terminal of the given dimensions (usually - requested for shell access) can be provided. - - The default implementation always returns C{False}. - - @param term: type of terminal requested (for example, C{"vt100"}). - @type term: string - @param width: width of screen in characters. - @type width: int - @param height: height of screen in characters. - @type height: int - @param pixelwidth: width of screen in pixels, if known (may be C{0} if - unknown). - @type pixelwidth: int - @param pixelheight: height of screen in pixels, if known (may be C{0} - if unknown). - @type pixelheight: int - @return: C{True} if the psuedo-terminal has been allocated; C{False} - otherwise. - @rtype: boolean - """ - return False - - def check_shell_request(self): - """ - I{(subclass override)} - Determine if a shell will be provided to the client. If this method - returns C{True}, this channel should be connected to the stdin/stdout - of a shell. - - The default implementation always returns C{False}. - - @return: C{True} if this channel is now hooked up to a shell; C{False} - if a shell can't or won't be provided. - @rtype: boolean - """ - return False - - def check_subsystem_request(self, name): - """ - I{(subclass override)} - Determine if a requested subsystem will be provided to the client. If - this method returns C{True}, all future I/O through this channel will - be assumed to be connected to the requested subsystem. An example of - a subsystem is C{sftp}. - - The default implementation always returns C{False}. - - @return: C{True} if this channel is now hooked up to the requested - subsystem; C{False} if that subsystem can't or won't be provided. - @rtype: boolean - """ - return False - - def check_window_change_request(self, width, height, pixelwidth, pixelheight): - """ - I{(subclass override)} - Determine if the pseudo-terminal can be resized. - - The default implementation always returns C{False}. - - @param width: width of screen in characters. - @type width: int - @param height: height of screen in characters. - @type height: int - @param pixelwidth: width of screen in pixels, if known (may be C{0} if - unknown). - @type pixelwidth: int - @param pixelheight: height of screen in pixels, if known (may be C{0} - if unknown). - @type pixelheight: int - @return: C{True} if the terminal was resized; C{False} if not. - """ - return False ### calls from Transport @@ -717,6 +654,7 @@ class Channel (object): def _handle_request(self, m): key = m.get_string() want_reply = m.get_boolean() + server = self.transport.server_object ok = False if key == 'exit-status': self.exit_status = m.get_int() @@ -731,18 +669,32 @@ class Channel (object): pixelwidth = m.get_int() pixelheight = m.get_int() modes = m.get_string() - ok = self.check_pty_request(term, width, height, pixelwidth, pixelheight, modes) + if server is None: + ok = False + else: + ok = server.check_channel_pty_request(self, term, width, height, pixelwidth, + pixelheight, modes) elif key == 'shell': - ok = self.check_shell_request() + if server is None: + ok = False + else: + ok = server.check_channel_shell_request(self) elif key == 'subsystem': name = m.get_string() - ok = self.check_subsystem_request(name) + if server is None: + ok = False + else: + ok = server.check_channel_subsystem_request(self, name) elif key == 'window-change': width = m.get_int() height = m.get_int() pixelwidth = m.get_int() pixelheight = m.get_int() - ok = self.check_window_change_request(width, height, pixelwidth, pixelheight) + if server is None: + ok = False + else: + ok = server.check_channel_window_change_request(self, width, height, pixelwidth, + pixelheight) else: self._log(DEBUG, 'Unhandled channel request "%s"' % key) ok = False @@ -931,7 +883,7 @@ class ChannelFile (BufferedFile): """ Returns a string representation of this object, for debugging. - @rtype: string + @rtype: str """ return '<paramiko.ChannelFile from ' + repr(self.channel) + '>' diff --git a/paramiko/common.py b/paramiko/common.py index 8f42fe29..2347464a 100644 --- a/paramiko/common.py +++ b/paramiko/common.py @@ -35,7 +35,19 @@ MSG_CHANNEL_OPEN, MSG_CHANNEL_OPEN_SUCCESS, MSG_CHANNEL_OPEN_FAILURE, \ MSG_CHANNEL_SUCCESS, MSG_CHANNEL_FAILURE = range(90, 101) +# authentication request return codes: + +AUTH_SUCCESSFUL, AUTH_PARTIALLY_SUCCESSFUL, AUTH_FAILED = range(3) + + # channel request failed reasons: + +(OPEN_SUCCEEDED, + OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED, + OPEN_FAILED_CONNECT_FAILED, + OPEN_FAILED_UNKNOWN_CHANNEL_TYPE, + OPEN_FAILED_RESOURCE_SHORTAGE) = range(0, 5) + CONNECTION_FAILED_CODE = { 1: 'Administratively prohibited', 2: 'Connect failed', @@ -43,6 +55,7 @@ CONNECTION_FAILED_CODE = { 4: 'Resource shortage' } + DISCONNECT_SERVICE_NOT_AVAILABLE, DISCONNECT_AUTH_CANCELLED_BY_USER, \ DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 7, 13, 14 diff --git a/paramiko/server.py b/paramiko/server.py index 2a9b0153..25924b61 100644 --- a/paramiko/server.py +++ b/paramiko/server.py @@ -22,6 +22,7 @@ L{ServerInterface} is an interface to override for server support. """ +from common import * from auth_transport import Transport class ServerInterface (object): @@ -37,31 +38,46 @@ class ServerInterface (object): 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. + return C{OPEN_SUCCEEDED} or an error code. 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. + If you allow channel requests (and an ssh server that didn't would be + useless), you should also override some of the channel request methods + below, which are used to determine which services will be allowed on + a given channel: + - L{check_channel_pty_request} + - L{check_channel_shell_request} + - L{check_channel_subsystem_request} + - L{check_channel_window_change_request} - The default implementation always returns C{None}, rejecting any - channel requests. A useful server must override this method. + The C{chanid} parameter is a small number that uniquely identifies the + channel within a L{Transport}. A L{Channel} object is not created + unless this method returns C{OPEN_SUCCEEDED} -- once a + L{Channel} object is created, you can call L{Channel.get_id} to + retrieve the channel ID. + + The return value should either be C{OPEN_SUCCEEDED} (or + C{0}) to allow the channel request, or one of the following error + codes to reject it: + - C{OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED} + - C{OPEN_FAILED_CONNECT_FAILED} + - C{OPEN_FAILED_UNKNOWN_CHANNEL_TYPE} + - C{OPEN_FAILED_RESOURCE_SHORTAGE} + + The default implementation always returns + C{OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED}. @param kind: the kind of channel the client would like to open (usually C{"session"}). - @type kind: string + @type kind: str @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: a success or failure code (listed above). + @rtype: int """ - return None + return OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED def get_allowed_auths(self, username): """ @@ -76,9 +92,9 @@ class ServerInterface (object): The default implementation always returns C{"password"}. @param username: the username requesting authentication. - @type username: string + @type username: str @return: a comma-separated list of authentication types - @rtype: string + @rtype: str """ return 'password' @@ -87,46 +103,46 @@ class ServerInterface (object): 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 + Return L{AUTH_FAILED} if the client must authenticate, or + L{AUTH_SUCCESSFUL} if it's okay for the client to not authenticate. - The default implementation always returns L{Transport.AUTH_FAILED}. + The default implementation always returns L{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. + @type username: str + @return: L{AUTH_FAILED} if the authentication fails; + L{AUTH_SUCCESSFUL} if it succeeds. @rtype: int """ - return Transport.AUTH_FAILED + return 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 + Return L{AUTH_FAILED} if the password is not accepted, + L{AUTH_SUCCESSFUL} if the password is accepted and completes + the authentication, or L{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}. + The default implementation always returns L{AUTH_FAILED}. @param username: the username of the authenticating client. - @type username: string + @type username: str @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 + @type password: str + @return: L{AUTH_FAILED} if the authentication fails; + L{AUTH_SUCCESSFUL} if it succeeds; + L{AUTH_PARTIALLY_SUCCESSFUL} if the password auth is successful, but authentication must continue. @rtype: int """ - return Transport.AUTH_FAILED + return AUTH_FAILED def check_auth_publickey(self, username, key): """ @@ -135,24 +151,115 @@ class ServerInterface (object): 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 + Return L{AUTH_FAILED} if the key is not accepted, + L{AUTH_SUCCESSFUL} if the key is accepted and completes the + authentication, or L{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}. + The default implementation always returns L{AUTH_FAILED}. @param username: the username of the authenticating client. - @type username: string + @type username: str @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 + @return: L{AUTH_FAILED} if the client can't authenticate + with this key; L{AUTH_SUCCESSFUL} if it can; + L{AUTH_PARTIALLY_SUCCESSFUL} if it can authenticate with this key but must continue with authentication. @rtype: int """ - return Transport.AUTH_FAILED + return AUTH_FAILED + + + ### Channel requests + + + def check_channel_pty_request(self, channel, term, width, height, pixelwidth, pixelheight, + modes): + """ + Determine if a pseudo-terminal of the given dimensions (usually + requested for shell access) can be provided on the given channel. + + The default implementation always returns C{False}. + + @param channel: the L{Channel} the pty request arrived on. + @type channel: L{Channel} + @param term: type of terminal requested (for example, C{"vt100"}). + @type term: str + @param width: width of screen in characters. + @type width: int + @param height: height of screen in characters. + @type height: int + @param pixelwidth: width of screen in pixels, if known (may be C{0} if + unknown). + @type pixelwidth: int + @param pixelheight: height of screen in pixels, if known (may be C{0} + if unknown). + @type pixelheight: int + @return: C{True} if the psuedo-terminal has been allocated; C{False} + otherwise. + @rtype: boolean + """ + return False + + def check_channel_shell_request(self, channel): + """ + Determine if a shell will be provided to the client on the given + channel. If this method returns C{True}, the channel should be + connected to the stdin/stdout of a shell (or something that acts like + a shell). + + The default implementation always returns C{False}. + + @param channel: the L{Channel} the pty request arrived on. + @type channel: L{Channel} + @return: C{True} if this channel is now hooked up to a shell; C{False} + if a shell can't or won't be provided. + @rtype: boolean + """ + return False + + def check_channel_subsystem_request(self, channel, name): + """ + Determine if a requested subsystem will be provided to the client on + the given channel. If this method returns C{True}, all future I/O + through this channel will be assumed to be connected to the requested + subsystem. An example of a subsystem is C{sftp}. + + The default implementation always returns C{False}. + + @param channel: the L{Channel} the pty request arrived on. + @type channel: L{Channel} + @param name: name of the requested subsystem. + @type name: str + @return: C{True} if this channel is now hooked up to the requested + subsystem; C{False} if that subsystem can't or won't be provided. + @rtype: boolean + """ + return False + + def check_channel_window_change_request(self, channel, width, height, pixelwidth, pixelheight): + """ + Determine if the pseudo-terminal on the given channel can be resized. + This only makes sense if a pty was previously allocated on it. + + The default implementation always returns C{False}. + + @param channel: the L{Channel} the pty request arrived on. + @type channel: L{Channel} + @param width: width of screen in characters. + @type width: int + @param height: height of screen in characters. + @type height: int + @param pixelwidth: width of screen in pixels, if known (may be C{0} if + unknown). + @type pixelwidth: int + @param pixelheight: height of screen in pixels, if known (may be C{0} + if unknown). + @type pixelheight: int + @return: C{True} if the terminal was resized; C{False} if not. + """ + return False diff --git a/paramiko/sftp.py b/paramiko/sftp.py index fb977d74..95214f1a 100644 --- a/paramiko/sftp.py +++ b/paramiko/sftp.py @@ -36,7 +36,7 @@ _FX_OK = 0 _FX_EOF, _FX_NO_SUCH_FILE, _FX_PERMISSION_DENIED, _FX_FAILURE, _FX_BAD_MESSAGE, \ _FX_NO_CONNECTION, _FX_CONNECTION_LOST, _FX_OP_UNSUPPORTED = range(1, 9) -VERSION = 3 +_VERSION = 3 class SFTPAttributes (object): @@ -238,12 +238,12 @@ class SFTP (object): else: self.logger = logging.getLogger('paramiko.sftp') # protocol: (maybe should move to a different method) - self._send_packet(_CMD_INIT, struct.pack('>I', VERSION)) + self._send_packet(_CMD_INIT, struct.pack('>I', _VERSION)) t, data = self._read_packet() if t != _CMD_VERSION: raise SFTPError('Incompatible sftp protocol') version = struct.unpack('>I', data[:4])[0] -# if version != VERSION: +# if version != _VERSION: # raise SFTPError('Incompatible sftp protocol') def from_transport(selfclass, t): diff --git a/paramiko/transport.py b/paramiko/transport.py index 90de98cc..15486712 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -64,6 +64,8 @@ class SecurityOptions (object): If you try to add an algorithm that paramiko doesn't recognize, C{ValueError} will be raised. If you try to assign something besides a tuple to one of the fields, L{TypeError} will be raised. + + @since: ivysaur """ __slots__ = [ 'ciphers', 'digests', 'key_types', 'kex', '_transport' ] @@ -74,7 +76,7 @@ class SecurityOptions (object): """ Returns a string representation of this object, for debugging. - @rtype: string + @rtype: str """ return '<paramiko.SecurityOptions for %s>' % repr(self._transport) @@ -162,9 +164,6 @@ class BaseTransport (threading.Thread): REKEY_PACKETS = pow(2, 30) REKEY_BYTES = pow(2, 30) - OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED, OPEN_FAILED_CONNECT_FAILED, OPEN_FAILED_UNKNOWN_CHANNEL_TYPE, \ - OPEN_FAILED_RESOURCE_SHORTAGE = range(1, 5) - _modulus_pack = None def __init__(self, sock): @@ -176,7 +175,7 @@ class BaseTransport (threading.Thread): If the object is not actually a socket, it must have the following methods: - - C{send(string)}: Writes from 1 to C{len(string)} bytes, and + - C{send(str)}: Writes from 1 to C{len(str)} bytes, and returns an int representing the number of bytes written. Returns 0 or raises C{EOFError} if the stream has been closed. - C{recv(int)}: Reads from 1 to C{int} bytes and returns them as a @@ -264,17 +263,19 @@ class BaseTransport (threading.Thread): """ Returns a string representation of this object, for debugging. - @rtype: string + @rtype: str """ + out = '<paramiko.BaseTransport at %s' % hex(id(self)) if not self.active: - return '<paramiko.BaseTransport (unconnected)>' - out = '<paramiko.BaseTransport' - if self.local_cipher != '': - out += ' (cipher %s, %d bits)' % (self.local_cipher, self._cipher_info[self.local_cipher]['key-size'] * 8) - if len(self.channels) == 1: - out += ' (active; 1 open channel)' + out += ' (unconnected)' else: - out += ' (active; %d open channels)' % len(self.channels) + if self.local_cipher != '': + out += ' (cipher %s, %d bits)' % (self.local_cipher, + self._cipher_info[self.local_cipher]['key-size'] * 8) + if len(self.channels) == 1: + out += ' (active; 1 open channel)' + else: + out += ' (active; %d open channels)' % len(self.channels) out += '>' return out @@ -287,6 +288,8 @@ class BaseTransport (threading.Thread): @return: an object that can be used to change the preferred algorithms for encryption, digest (hash), public key, and key exchange. @rtype: L{SecurityOptions} + + @since: ivysaur """ return SecurityOptions(self) @@ -407,7 +410,7 @@ class BaseTransport (threading.Thread): @param filename: optional path to the moduli file, if you happen to know that it's not in a standard location. - @type filename: string + @type filename: str @return: True if a moduli file was successfully loaded; False otherwise. @rtype: bool @@ -502,6 +505,9 @@ class BaseTransport (threading.Thread): @rtype: L{Channel} """ chan = None + if not self.active: + # don't bother trying to allocate a channel + return None try: self.lock.acquire() chanid = self.channel_counter @@ -603,7 +609,7 @@ class BaseTransport (threading.Thread): extensions to the SSH2 protocol. @param kind: name of the request. - @type kind: string + @type kind: str @param data: an optional tuple containing additional data to attach to the request. @type data: tuple @@ -659,7 +665,7 @@ class BaseTransport (threading.Thread): does not support any global requests. @param kind: the kind of global request being made. - @type kind: string + @type kind: str @param msg: any extra arguments to the request. @type msg: L{Message} @return: C{True} or a tuple of data if the request was granted; @@ -706,15 +712,15 @@ class BaseTransport (threading.Thread): @param hostkeytype: the type of host key expected from the server (usually C{"ssh-rsa"} or C{"ssh-dss"}), or C{None} if you don't want to do host key verification. - @type hostkeytype: string + @type hostkeytype: str @param hostkey: the host key expected from the server, or C{None} if you don't want to do host key verification. - @type hostkey: string + @type hostkey: str @param username: the username to authenticate as. - @type username: string + @type username: str @param password: a password to use for authentication, if you want to use password authentication; otherwise C{None}. - @type password: string + @type password: str @param pkey: a private key to use for authentication, if you want to use private key authentication; otherwise C{None}. @type pkey: L{PKey<pkey.PKey>} @@ -1042,6 +1048,9 @@ class BaseTransport (threading.Thread): chanid = m.get_int() if self.channels.has_key(chanid): self._channel_handler_table[ptype](self.channels[chanid], m) + else: + self._log(ERROR, 'Channel request for unknown channel %d' % chanid) + self.active = False else: self._log(WARNING, 'Oops, unhandled type %d' % ptype) msg = Message() @@ -1127,7 +1136,9 @@ class BaseTransport (threading.Thread): 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 - self._preferred_kex.remove('diffie-hellman-group-exchange-sha1') + pkex = list(self.get_security_options().kex) + pkex.remove('diffie-hellman-group-exchange-sha1') + self.get_security_options().kex = pkex available_server_keys = filter(self.server_key_dict.keys().__contains__, self._preferred_keys) else: @@ -1394,7 +1405,7 @@ class BaseTransport (threading.Thread): if not self.server_mode: self._log(DEBUG, 'Rejecting "%s" channel request from server.' % kind) reject = True - reason = self.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED + reason = OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED else: try: self.lock.acquire() @@ -1402,14 +1413,10 @@ class BaseTransport (threading.Thread): self.channel_counter += 1 finally: self.lock.release() - chan = self.server_object.check_channel_request(kind, my_chanid) - if (chan is None) or (type(chan) is int): + reason = self.server_object.check_channel_request(kind, my_chanid) + if reason != OPEN_SUCCEEDED: self._log(DEBUG, 'Rejecting "%s" channel request from client.' % kind) reject = True - if type(chan) is int: - reason = chan - else: - reason = self.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED if reject: msg = Message() msg.add_byte(chr(MSG_CHANNEL_OPEN_FAILURE)) @@ -1419,6 +1426,7 @@ class BaseTransport (threading.Thread): msg.add_string('en') self._send_message(msg) return + chan = Channel(my_chanid) try: self.lock.acquire() self.channels[my_chanid] = chan |