summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRobey Pointer <robey@lag.net>2004-09-03 22:39:20 +0000
committerRobey Pointer <robey@lag.net>2004-09-03 22:39:20 +0000
commitaba7e37a383fc3d7ffedc6d9e433f65223ac5fe2 (patch)
tree0747489e0578ef2c62c51312b339cc79d96e1312
parent440b3de06abfd358e93e698fde178ef0c5c85939 (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--README2
-rwxr-xr-xdemo_server.py37
-rw-r--r--paramiko/__init__.py3
-rw-r--r--paramiko/auth_transport.py43
-rw-r--r--paramiko/channel.py138
-rw-r--r--paramiko/common.py13
-rw-r--r--paramiko/server.py195
-rw-r--r--paramiko/sftp.py6
-rw-r--r--paramiko/transport.py64
9 files changed, 293 insertions, 208 deletions
diff --git a/README b/README
index 8958519f..b5f06d58 100644
--- a/README
+++ b/README
@@ -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