[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:
parent
440b3de06a
commit
aba7e37a38
2
README
2
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?
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) + '>'
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue