[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()
This commit is contained in:
Robey Pointer 2004-09-03 22:39:20 +00:00
parent 440b3de06a
commit aba7e37a38
9 changed files with 293 additions and 208 deletions

2
README
View File

@ -155,3 +155,5 @@ v0.9 FEAROW
* multi-part auth not supported (ie, need username AND pk) * multi-part auth not supported (ie, need username AND pk)
* server mode needs better documentation * server mode needs better documentation
* sftp server mode * sftp server mode
ivysaur?

View File

@ -20,38 +20,34 @@ class Server (paramiko.ServerInterface):
data = 'AAAAB3NzaC1yc2EAAAABIwAAAIEAyO4it3fHlmGZWJaGrfeHOVY7RWO3P9M7hpfAu7jJ2d7eothvfeuoRFtJwhUmZDluRdFyhFY/hFAh76PJKGAusIqIQKlkJxMCKDqIexkgHAfID/6mqvmnSJf0b5W8v5h2pI/stOSwTQ+pxVhwJ9ctYDhRSlF0iTUWT10hcuO4Ks8=' data = 'AAAAB3NzaC1yc2EAAAABIwAAAIEAyO4it3fHlmGZWJaGrfeHOVY7RWO3P9M7hpfAu7jJ2d7eothvfeuoRFtJwhUmZDluRdFyhFY/hFAh76PJKGAusIqIQKlkJxMCKDqIexkgHAfID/6mqvmnSJf0b5W8v5h2pI/stOSwTQ+pxVhwJ9ctYDhRSlF0iTUWT10hcuO4Ks8='
good_pub_key = paramiko.RSAKey(data=base64.decodestring(data)) good_pub_key = paramiko.RSAKey(data=base64.decodestring(data))
def __init__(self):
self.event = threading.Event()
def check_channel_request(self, kind, chanid): def check_channel_request(self, kind, chanid):
if kind == 'session': if kind == 'session':
return ServerChannel(chanid) return paramiko.OPEN_SUCCEEDED
return paramiko.Transport.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
def check_auth_password(self, username, password): def check_auth_password(self, username, password):
if (username == 'robey') and (password == 'foo'): if (username == 'robey') and (password == 'foo'):
return paramiko.Transport.AUTH_SUCCESSFUL return paramiko.AUTH_SUCCESSFUL
return paramiko.Transport.AUTH_FAILED return paramiko.AUTH_FAILED
def check_auth_publickey(self, username, key): def check_auth_publickey(self, username, key):
print 'Auth attempt with key: ' + paramiko.util.hexify(key.get_fingerprint()) print 'Auth attempt with key: ' + paramiko.util.hexify(key.get_fingerprint())
if (username == 'robey') and (key == self.good_pub_key): if (username == 'robey') and (key == self.good_pub_key):
return paramiko.Transport.AUTH_SUCCESSFUL return paramiko.AUTH_SUCCESSFUL
return paramiko.Transport.AUTH_FAILED return paramiko.AUTH_FAILED
def get_allowed_auths(self, username): def get_allowed_auths(self, username):
return 'password,publickey' return 'password,publickey'
def check_channel_shell_request(self, channel):
class ServerChannel (paramiko.Channel): self.event.set()
"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):
return True return True
def check_shell_request(self): def check_channel_pty_request(self, channel, term, width, height, pixelwidth,
self.event.set() pixelheight, modes):
return True return True
@ -85,7 +81,8 @@ try:
print '(Failed to load moduli -- gex will be unsupported.)' print '(Failed to load moduli -- gex will be unsupported.)'
raise raise
t.add_server_key(host_key) t.add_server_key(host_key)
t.start_server(event, Server()) server = Server()
t.start_server(event, server)
while 1: while 1:
event.wait(0.1) event.wait(0.1)
if not t.is_active(): if not t.is_active():
@ -101,8 +98,8 @@ try:
print '*** No channel.' print '*** No channel.'
sys.exit(1) sys.exit(1)
print 'Authenticated!' print 'Authenticated!'
chan.event.wait(10) server.event.wait(10)
if not chan.event.isSet(): if not server.event.isSet():
print '*** Client never asked for a shell.' print '*** Client never asked for a shell.'
sys.exit(1) sys.exit(1)

View File

@ -79,6 +79,9 @@ SFTP = sftp.SFTP
ServerInterface = server.ServerInterface ServerInterface = server.ServerInterface
SecurityOptions = transport.SecurityOptions 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', __all__ = [ 'Transport',
'SecurityOptions', 'SecurityOptions',

View File

@ -47,8 +47,6 @@ class Transport (BaseTransport):
another shell window). another shell window).
""" """
AUTH_SUCCESSFUL, AUTH_PARTIALLY_SUCCESSFUL, AUTH_FAILED = range(3)
def __init__(self, sock): def __init__(self, sock):
BaseTransport.__init__(self, sock) BaseTransport.__init__(self, sock)
self.username = None self.username = None
@ -60,20 +58,22 @@ class Transport (BaseTransport):
self.auth_complete = 0 self.auth_complete = 0
def __repr__(self): def __repr__(self):
out = '<paramiko.Transport at %s' % hex(id(self))
if not self.active: if not self.active:
return '<paramiko.Transport (unconnected)>' out += ' (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)'
else: 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 += '>' out += '>'
return out return out
@ -268,21 +268,24 @@ class Transport (BaseTransport):
# the list of valid auth types from the callback anyway # the list of valid auth types from the callback anyway
self._log(DEBUG, 'Auth request to change passwords (rejected)') self._log(DEBUG, 'Auth request to change passwords (rejected)')
newpassword = m.get_string().decode('UTF-8') newpassword = m.get_string().decode('UTF-8')
result = self.AUTH_FAILED result = AUTH_FAILED
else: else:
result = self.server_object.check_auth_password(username, password) result = self.server_object.check_auth_password(username, password)
elif method == 'publickey': elif method == 'publickey':
sig_attached = m.get_boolean() sig_attached = m.get_boolean()
keytype = m.get_string() keytype = m.get_string()
keyblob = 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): if (key is None) or (not key.valid):
self._log(DEBUG, 'Auth rejected: unsupported or mangled public key') self._log(DEBUG, 'Auth rejected: unsupported or mangled public key')
self._disconnect_no_more_auth() self._disconnect_no_more_auth()
return return
# first check if this key is okay... if not, we can skip the verify # first check if this key is okay... if not, we can skip the verify
result = self.server_object.check_auth_publickey(username, key) result = self.server_object.check_auth_publickey(username, key)
if result != self.AUTH_FAILED: if result != AUTH_FAILED:
# key is okay, verify it # key is okay, verify it
if not sig_attached: if not sig_attached:
# client wants to know if this key is acceptable, before it # 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) blob = self._get_session_blob(key, service, username)
if not key.verify_ssh_sig(blob, sig): if not key.verify_ssh_sig(blob, sig):
self._log(DEBUG, 'Auth rejected: invalid signature') self._log(DEBUG, 'Auth rejected: invalid signature')
result = self.AUTH_FAILED result = AUTH_FAILED
else: else:
result = self.server_object.check_auth_none(username) result = self.server_object.check_auth_none(username)
# okay, send result # okay, send result
m = Message() m = Message()
if result == self.AUTH_SUCCESSFUL: if result == AUTH_SUCCESSFUL:
self._log(DEBUG, 'Auth granted.') self._log(DEBUG, 'Auth granted.')
m.add_byte(chr(MSG_USERAUTH_SUCCESS)) m.add_byte(chr(MSG_USERAUTH_SUCCESS))
self.auth_complete = 1 self.auth_complete = 1
@ -310,7 +313,7 @@ class Transport (BaseTransport):
self._log(DEBUG, 'Auth rejected.') self._log(DEBUG, 'Auth rejected.')
m.add_byte(chr(MSG_USERAUTH_FAILURE)) m.add_byte(chr(MSG_USERAUTH_FAILURE))
m.add_string(self.server_object.get_allowed_auths(username)) 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) m.add_boolean(1)
else: else:
m.add_boolean(0) m.add_boolean(0)

View File

@ -87,7 +87,7 @@ class Channel (object):
""" """
Returns a string representation of this object, for debugging. Returns a string representation of this object, for debugging.
@rtype: string @rtype: str
""" """
out = '<paramiko.Channel %d' % self.chanid out = '<paramiko.Channel %d' % self.chanid
if self.closed: if self.closed:
@ -111,7 +111,7 @@ class Channel (object):
basic terminal semantics for the next command you execute. basic terminal semantics for the next command you execute.
@param term: the terminal type to emulate (for example, C{'vt100'}). @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 @param width: width (in characters) of the terminal screen
@type width: int @type width: int
@param height: height (in characters) of the terminal screen @param height: height (in characters) of the terminal screen
@ -173,7 +173,7 @@ class Channel (object):
being executed. being executed.
@param command: a shell command to execute. @param command: a shell command to execute.
@type command: string @type command: str
@return: C{True} if the operation succeeded; C{False} if not. @return: C{True} if the operation succeeded; C{False} if not.
@rtype: bool @rtype: bool
""" """
@ -201,7 +201,7 @@ class Channel (object):
requested subsystem. requested subsystem.
@param subsystem: name of the subsystem being requested. @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. @return: C{True} if the operation succeeded; C{False} if not.
@rtype: bool @rtype: bool
""" """
@ -268,8 +268,8 @@ class Channel (object):
of the log level used for debugging. The name can be fetched with the of the log level used for debugging. The name can be fetched with the
L{get_name} method. L{get_name} method.
@param name: new channel name @param name: new channel name.
@type name: string @type name: str
""" """
self.name = name self.name = name
self.logger = logging.getLogger('paramiko.chan.' + 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}. Get the name of this channel that was previously set by L{set_name}.
@return: the name of this channel @return: the name of this channel.
@rtype: string @rtype: str
""" """
return self.name 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 ### socket API
@ -389,7 +403,7 @@ class Channel (object):
@param nbytes: maximum number of bytes to read. @param nbytes: maximum number of bytes to read.
@type nbytes: int @type nbytes: int
@return: data. @return: data.
@rtype: string @rtype: str
@raise socket.timeout: if no data is ready before the timeout set by @raise socket.timeout: if no data is ready before the timeout set by
L{settimeout}. L{settimeout}.
@ -436,7 +450,7 @@ class Channel (object):
data. data.
@param s: data to send. @param s: data to send.
@type s: string @type s: str
@return: number of bytes actually sent. @return: number of bytes actually sent.
@rtype: int @rtype: int
@ -490,7 +504,7 @@ class Channel (object):
either all data has been sent or an error occurs. Nothing is returned. either all data has been sent or an error occurs. Nothing is returned.
@param s: data to send. @param s: data to send.
@type s: string @type s: str
@raise socket.timeout: if sending stalled for longer than the timeout @raise socket.timeout: if sending stalled for longer than the timeout
set by L{settimeout}. set by L{settimeout}.
@ -579,83 +593,6 @@ class Channel (object):
### overrides ### 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 ### calls from Transport
@ -717,6 +654,7 @@ class Channel (object):
def _handle_request(self, m): def _handle_request(self, m):
key = m.get_string() key = m.get_string()
want_reply = m.get_boolean() want_reply = m.get_boolean()
server = self.transport.server_object
ok = False ok = False
if key == 'exit-status': if key == 'exit-status':
self.exit_status = m.get_int() self.exit_status = m.get_int()
@ -731,18 +669,32 @@ class Channel (object):
pixelwidth = m.get_int() pixelwidth = m.get_int()
pixelheight = m.get_int() pixelheight = m.get_int()
modes = m.get_string() 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': 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': elif key == 'subsystem':
name = m.get_string() 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': elif key == 'window-change':
width = m.get_int() width = m.get_int()
height = m.get_int() height = m.get_int()
pixelwidth = m.get_int() pixelwidth = m.get_int()
pixelheight = 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: else:
self._log(DEBUG, 'Unhandled channel request "%s"' % key) self._log(DEBUG, 'Unhandled channel request "%s"' % key)
ok = False ok = False
@ -931,7 +883,7 @@ class ChannelFile (BufferedFile):
""" """
Returns a string representation of this object, for debugging. Returns a string representation of this object, for debugging.
@rtype: string @rtype: str
""" """
return '<paramiko.ChannelFile from ' + repr(self.channel) + '>' return '<paramiko.ChannelFile from ' + repr(self.channel) + '>'

View File

@ -35,7 +35,19 @@ MSG_CHANNEL_OPEN, MSG_CHANNEL_OPEN_SUCCESS, MSG_CHANNEL_OPEN_FAILURE, \
MSG_CHANNEL_SUCCESS, MSG_CHANNEL_FAILURE = range(90, 101) 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: # 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 = { CONNECTION_FAILED_CODE = {
1: 'Administratively prohibited', 1: 'Administratively prohibited',
2: 'Connect failed', 2: 'Connect failed',
@ -43,6 +55,7 @@ CONNECTION_FAILED_CODE = {
4: 'Resource shortage' 4: 'Resource shortage'
} }
DISCONNECT_SERVICE_NOT_AVAILABLE, DISCONNECT_AUTH_CANCELLED_BY_USER, \ DISCONNECT_SERVICE_NOT_AVAILABLE, DISCONNECT_AUTH_CANCELLED_BY_USER, \
DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 7, 13, 14 DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 7, 13, 14

View File

@ -22,6 +22,7 @@
L{ServerInterface} is an interface to override for server support. L{ServerInterface} is an interface to override for server support.
""" """
from common import *
from auth_transport import Transport from auth_transport import Transport
class ServerInterface (object): class ServerInterface (object):
@ -37,31 +38,46 @@ class ServerInterface (object):
def check_channel_request(self, kind, chanid): def check_channel_request(self, kind, chanid):
""" """
Determine if a channel request of a given type will be granted, and 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 return C{OPEN_SUCCEEDED} or an error code. This method is
mode when the client requests a channel, after authentication is called in server mode when the client requests a channel, after
complete. authentication is complete.
You will generally want to subclass L{Channel} to override some of the If you allow channel requests (and an ssh server that didn't would be
methods for handling client requests (such as connecting to a subsystem useless), you should also override some of the channel request methods
opening a shell) to determine what you want to allow or disallow. For below, which are used to determine which services will be allowed on
this reason, L{check_channel_request} must return a new object of that a given channel:
type. The C{chanid} parameter is passed so that you can use it in - L{check_channel_pty_request}
L{Channel}'s constructor. - 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 The C{chanid} parameter is a small number that uniquely identifies the
channel requests. A useful server must override this method. 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 @param kind: the kind of channel the client would like to open
(usually C{"session"}). (usually C{"session"}).
@type kind: string @type kind: str
@param chanid: ID of the channel, required to create a new L{Channel} @param chanid: ID of the channel, required to create a new L{Channel}
object. object.
@type chanid: int @type chanid: int
@return: a new L{Channel} object (or subclass thereof), or C{None} to @return: a success or failure code (listed above).
refuse the request. @rtype: int
@rtype: L{Channel}
""" """
return None return OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
def get_allowed_auths(self, username): def get_allowed_auths(self, username):
""" """
@ -76,9 +92,9 @@ class ServerInterface (object):
The default implementation always returns C{"password"}. The default implementation always returns C{"password"}.
@param username: the username requesting authentication. @param username: the username requesting authentication.
@type username: string @type username: str
@return: a comma-separated list of authentication types @return: a comma-separated list of authentication types
@rtype: string @rtype: str
""" """
return 'password' return 'password'
@ -87,46 +103,46 @@ class ServerInterface (object):
Determine if a client may open channels with no (further) Determine if a client may open channels with no (further)
authentication. authentication.
Return L{Transport.AUTH_FAILED} if the client must authenticate, or Return L{AUTH_FAILED} if the client must authenticate, or
L{Transport.AUTH_SUCCESSFUL} if it's okay for the client to not L{AUTH_SUCCESSFUL} if it's okay for the client to not
authenticate. 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. @param username: the username of the client.
@type username: string @type username: str
@return: L{Transport.AUTH_FAILED} if the authentication fails; @return: L{AUTH_FAILED} if the authentication fails;
L{Transport.AUTH_SUCCESSFUL} if it succeeds. L{AUTH_SUCCESSFUL} if it succeeds.
@rtype: int @rtype: int
""" """
return Transport.AUTH_FAILED return AUTH_FAILED
def check_auth_password(self, username, password): def check_auth_password(self, username, password):
""" """
Determine if a given username and password supplied by the client is Determine if a given username and password supplied by the client is
acceptable for use in authentication. acceptable for use in authentication.
Return L{Transport.AUTH_FAILED} if the password is not accepted, Return L{AUTH_FAILED} if the password is not accepted,
L{Transport.AUTH_SUCCESSFUL} if the password is accepted and completes L{AUTH_SUCCESSFUL} if the password is accepted and completes
the authentication, or L{Transport.AUTH_PARTIALLY_SUCCESSFUL} if your the authentication, or L{AUTH_PARTIALLY_SUCCESSFUL} if your
authentication is stateful, and this key is accepted for authentication is stateful, and this key is accepted for
authentication, but more authentication is required. (In this latter authentication, but more authentication is required. (In this latter
case, L{get_allowed_auths} will be called to report to the client what case, L{get_allowed_auths} will be called to report to the client what
options it has for continuing the authentication.) 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. @param username: the username of the authenticating client.
@type username: string @type username: str
@param password: the password given by the client. @param password: the password given by the client.
@type password: string @type password: str
@return: L{Transport.AUTH_FAILED} if the authentication fails; @return: L{AUTH_FAILED} if the authentication fails;
L{Transport.AUTH_SUCCESSFUL} if it succeeds; L{AUTH_SUCCESSFUL} if it succeeds;
L{Transport.AUTH_PARTIALLY_SUCCESSFUL} if the password auth is L{AUTH_PARTIALLY_SUCCESSFUL} if the password auth is
successful, but authentication must continue. successful, but authentication must continue.
@rtype: int @rtype: int
""" """
return Transport.AUTH_FAILED return AUTH_FAILED
def check_auth_publickey(self, username, key): 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 check the username and key and decide if you would accept a signature
made using this key. made using this key.
Return L{Transport.AUTH_FAILED} if the key is not accepted, Return L{AUTH_FAILED} if the key is not accepted,
L{Transport.AUTH_SUCCESSFUL} if the key is accepted and completes the L{AUTH_SUCCESSFUL} if the key is accepted and completes the
authentication, or L{Transport.AUTH_PARTIALLY_SUCCESSFUL} if your authentication, or L{AUTH_PARTIALLY_SUCCESSFUL} if your
authentication is stateful, and this key is accepted for authentication is stateful, and this key is accepted for
authentication, but more authentication is required. (In this latter authentication, but more authentication is required. (In this latter
case, L{get_allowed_auths} will be called to report to the client what case, L{get_allowed_auths} will be called to report to the client what
options it has for continuing the authentication.) 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. @param username: the username of the authenticating client.
@type username: string @type username: str
@param key: the key object provided by the client. @param key: the key object provided by the client.
@type key: L{PKey <pkey.PKey>} @type key: L{PKey <pkey.PKey>}
@return: L{Transport.AUTH_FAILED} if the client can't authenticate @return: L{AUTH_FAILED} if the client can't authenticate
with this key; L{Transport.AUTH_SUCCESSFUL} if it can; with this key; L{AUTH_SUCCESSFUL} if it can;
L{Transport.AUTH_PARTIALLY_SUCCESSFUL} if it can authenticate with L{AUTH_PARTIALLY_SUCCESSFUL} if it can authenticate with
this key but must continue with authentication. this key but must continue with authentication.
@rtype: int @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

View File

@ -36,7 +36,7 @@ _FX_OK = 0
_FX_EOF, _FX_NO_SUCH_FILE, _FX_PERMISSION_DENIED, _FX_FAILURE, _FX_BAD_MESSAGE, \ _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) _FX_NO_CONNECTION, _FX_CONNECTION_LOST, _FX_OP_UNSUPPORTED = range(1, 9)
VERSION = 3 _VERSION = 3
class SFTPAttributes (object): class SFTPAttributes (object):
@ -238,12 +238,12 @@ class SFTP (object):
else: else:
self.logger = logging.getLogger('paramiko.sftp') self.logger = logging.getLogger('paramiko.sftp')
# protocol: (maybe should move to a different method) # 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() t, data = self._read_packet()
if t != _CMD_VERSION: if t != _CMD_VERSION:
raise SFTPError('Incompatible sftp protocol') raise SFTPError('Incompatible sftp protocol')
version = struct.unpack('>I', data[:4])[0] version = struct.unpack('>I', data[:4])[0]
# if version != VERSION: # if version != _VERSION:
# raise SFTPError('Incompatible sftp protocol') # raise SFTPError('Incompatible sftp protocol')
def from_transport(selfclass, t): def from_transport(selfclass, t):

View File

@ -64,6 +64,8 @@ class SecurityOptions (object):
If you try to add an algorithm that paramiko doesn't recognize, 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 C{ValueError} will be raised. If you try to assign something besides a
tuple to one of the fields, L{TypeError} will be raised. tuple to one of the fields, L{TypeError} will be raised.
@since: ivysaur
""" """
__slots__ = [ 'ciphers', 'digests', 'key_types', 'kex', '_transport' ] __slots__ = [ 'ciphers', 'digests', 'key_types', 'kex', '_transport' ]
@ -74,7 +76,7 @@ class SecurityOptions (object):
""" """
Returns a string representation of this object, for debugging. Returns a string representation of this object, for debugging.
@rtype: string @rtype: str
""" """
return '<paramiko.SecurityOptions for %s>' % repr(self._transport) return '<paramiko.SecurityOptions for %s>' % repr(self._transport)
@ -162,9 +164,6 @@ class BaseTransport (threading.Thread):
REKEY_PACKETS = pow(2, 30) REKEY_PACKETS = pow(2, 30)
REKEY_BYTES = 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 _modulus_pack = None
def __init__(self, sock): 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 If the object is not actually a socket, it must have the following
methods: 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 returns an int representing the number of bytes written. Returns
0 or raises C{EOFError} if the stream has been closed. 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 - 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. 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: if not self.active:
return '<paramiko.BaseTransport (unconnected)>' out += ' (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)'
else: 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 += '>' out += '>'
return out return out
@ -287,6 +288,8 @@ class BaseTransport (threading.Thread):
@return: an object that can be used to change the preferred algorithms @return: an object that can be used to change the preferred algorithms
for encryption, digest (hash), public key, and key exchange. for encryption, digest (hash), public key, and key exchange.
@rtype: L{SecurityOptions} @rtype: L{SecurityOptions}
@since: ivysaur
""" """
return SecurityOptions(self) return SecurityOptions(self)
@ -407,7 +410,7 @@ class BaseTransport (threading.Thread):
@param filename: optional path to the moduli file, if you happen to @param filename: optional path to the moduli file, if you happen to
know that it's not in a standard location. 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 @return: True if a moduli file was successfully loaded; False
otherwise. otherwise.
@rtype: bool @rtype: bool
@ -502,6 +505,9 @@ class BaseTransport (threading.Thread):
@rtype: L{Channel} @rtype: L{Channel}
""" """
chan = None chan = None
if not self.active:
# don't bother trying to allocate a channel
return None
try: try:
self.lock.acquire() self.lock.acquire()
chanid = self.channel_counter chanid = self.channel_counter
@ -603,7 +609,7 @@ class BaseTransport (threading.Thread):
extensions to the SSH2 protocol. extensions to the SSH2 protocol.
@param kind: name of the request. @param kind: name of the request.
@type kind: string @type kind: str
@param data: an optional tuple containing additional data to attach @param data: an optional tuple containing additional data to attach
to the request. to the request.
@type data: tuple @type data: tuple
@ -659,7 +665,7 @@ class BaseTransport (threading.Thread):
does not support any global requests. does not support any global requests.
@param kind: the kind of global request being made. @param kind: the kind of global request being made.
@type kind: string @type kind: str
@param msg: any extra arguments to the request. @param msg: any extra arguments to the request.
@type msg: L{Message} @type msg: L{Message}
@return: C{True} or a tuple of data if the request was granted; @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 @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 (usually C{"ssh-rsa"} or C{"ssh-dss"}), or C{None} if you don't want
to do host key verification. to do host key verification.
@type hostkeytype: string @type hostkeytype: str
@param hostkey: the host key expected from the server, or C{None} if @param hostkey: the host key expected from the server, or C{None} if
you don't want to do host key verification. you don't want to do host key verification.
@type hostkey: string @type hostkey: str
@param username: the username to authenticate as. @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 @param password: a password to use for authentication, if you want to
use password authentication; otherwise C{None}. 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 @param pkey: a private key to use for authentication, if you want to
use private key authentication; otherwise C{None}. use private key authentication; otherwise C{None}.
@type pkey: L{PKey<pkey.PKey>} @type pkey: L{PKey<pkey.PKey>}
@ -1042,6 +1048,9 @@ class BaseTransport (threading.Thread):
chanid = m.get_int() chanid = m.get_int()
if self.channels.has_key(chanid): if self.channels.has_key(chanid):
self._channel_handler_table[ptype](self.channels[chanid], m) self._channel_handler_table[ptype](self.channels[chanid], m)
else:
self._log(ERROR, 'Channel request for unknown channel %d' % chanid)
self.active = False
else: else:
self._log(WARNING, 'Oops, unhandled type %d' % ptype) self._log(WARNING, 'Oops, unhandled type %d' % ptype)
msg = Message() msg = Message()
@ -1127,7 +1136,9 @@ class BaseTransport (threading.Thread):
if self.server_mode: if self.server_mode:
if (self._modulus_pack is None) and ('diffie-hellman-group-exchange-sha1' in self._preferred_kex): if (self._modulus_pack is None) and ('diffie-hellman-group-exchange-sha1' in self._preferred_kex):
# can't do group-exchange if we don't have a pack of potential primes # can't do group-exchange if we don't have a pack of potential primes
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__, available_server_keys = filter(self.server_key_dict.keys().__contains__,
self._preferred_keys) self._preferred_keys)
else: else:
@ -1394,7 +1405,7 @@ class BaseTransport (threading.Thread):
if not self.server_mode: if not self.server_mode:
self._log(DEBUG, 'Rejecting "%s" channel request from server.' % kind) self._log(DEBUG, 'Rejecting "%s" channel request from server.' % kind)
reject = True reject = True
reason = self.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED reason = OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
else: else:
try: try:
self.lock.acquire() self.lock.acquire()
@ -1402,14 +1413,10 @@ class BaseTransport (threading.Thread):
self.channel_counter += 1 self.channel_counter += 1
finally: finally:
self.lock.release() self.lock.release()
chan = self.server_object.check_channel_request(kind, my_chanid) reason = self.server_object.check_channel_request(kind, my_chanid)
if (chan is None) or (type(chan) is int): if reason != OPEN_SUCCEEDED:
self._log(DEBUG, 'Rejecting "%s" channel request from client.' % kind) self._log(DEBUG, 'Rejecting "%s" channel request from client.' % kind)
reject = True reject = True
if type(chan) is int:
reason = chan
else:
reason = self.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
if reject: if reject:
msg = Message() msg = Message()
msg.add_byte(chr(MSG_CHANNEL_OPEN_FAILURE)) msg.add_byte(chr(MSG_CHANNEL_OPEN_FAILURE))
@ -1419,6 +1426,7 @@ class BaseTransport (threading.Thread):
msg.add_string('en') msg.add_string('en')
self._send_message(msg) self._send_message(msg)
return return
chan = Channel(my_chanid)
try: try:
self.lock.acquire() self.lock.acquire()
self.channels[my_chanid] = chan self.channels[my_chanid] = chan