[project @ Arch-1:robey@lag.net--2003-public%secsh--dev--1.0--patch-19]
renamed auth_key -> auth_publickey; more docs. renamed Transport.auth_key to auth_publickey for consistency. and lots more documentation.
This commit is contained in:
parent
daa8a2ec0d
commit
3a8887a420
2
README
2
README
|
@ -135,7 +135,5 @@ are still running (and you'll have to kill -9 from another shell window).
|
||||||
* ctr forms of ciphers are missing (blowfish-ctr, aes128-ctr, aes256-ctr)
|
* ctr forms of ciphers are missing (blowfish-ctr, aes128-ctr, aes256-ctr)
|
||||||
* can't handle password-protected private key files
|
* can't handle password-protected private key files
|
||||||
* multi-part auth not supported (ie, need username AND pk)
|
* multi-part auth not supported (ie, need username AND pk)
|
||||||
* should have a simple synchronous method that handles all auth & events,
|
|
||||||
by pre-seeding the password or key info, and the expected key
|
|
||||||
* server mode needs better doc
|
* server mode needs better doc
|
||||||
|
|
||||||
|
|
2
demo.py
2
demo.py
|
@ -108,7 +108,7 @@ try:
|
||||||
if len(path) == 0:
|
if len(path) == 0:
|
||||||
path = default_path
|
path = default_path
|
||||||
key.read_private_key_file(path)
|
key.read_private_key_file(path)
|
||||||
t.auth_key(username, key, event)
|
t.auth_publickey(username, key, event)
|
||||||
elif auth == 'd':
|
elif auth == 'd':
|
||||||
key = paramiko.DSSKey()
|
key = paramiko.DSSKey()
|
||||||
default_path = os.environ['HOME'] + '/.ssh/id_dsa'
|
default_path = os.environ['HOME'] + '/.ssh/id_dsa'
|
||||||
|
|
|
@ -41,6 +41,10 @@ class ServerTransport(paramiko.Transport):
|
||||||
return self.AUTH_SUCCESSFUL
|
return self.AUTH_SUCCESSFUL
|
||||||
return self.AUTH_FAILED
|
return self.AUTH_FAILED
|
||||||
|
|
||||||
|
def get_allowed_auths(self, username):
|
||||||
|
return 'password,publickey'
|
||||||
|
|
||||||
|
|
||||||
class ServerChannel(paramiko.Channel):
|
class ServerChannel(paramiko.Channel):
|
||||||
"Channel descendant that pretends to understand pty and shell requests"
|
"Channel descendant that pretends to understand pty and shell requests"
|
||||||
|
|
||||||
|
@ -86,7 +90,6 @@ 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.ultra_debug = 0
|
|
||||||
t.start_server(event)
|
t.start_server(event)
|
||||||
while 1:
|
while 1:
|
||||||
event.wait(0.1)
|
event.wait(0.1)
|
||||||
|
|
|
@ -9,11 +9,15 @@ __author__ = "Robey Pointer <robey@lag.net>"
|
||||||
__date__ = "10 Nov 2003"
|
__date__ = "10 Nov 2003"
|
||||||
__version__ = "0.1-charmander"
|
__version__ = "0.1-charmander"
|
||||||
__credits__ = "Huzzah!"
|
__credits__ = "Huzzah!"
|
||||||
|
__license__ = "Lesser GNU Public License (LGPL)"
|
||||||
|
|
||||||
|
|
||||||
import ssh_exception, transport, auth_transport, channel, rsakey, dsskey, util
|
import ssh_exception, transport, auth_transport, channel, rsakey, dsskey
|
||||||
|
|
||||||
class SSHException (ssh_exception.SSHException):
|
class SSHException (ssh_exception.SSHException):
|
||||||
|
"""
|
||||||
|
Exception thrown by failures in SSH2 protocol negotiation or logic errors.
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class Transport (auth_transport.Transport):
|
class Transport (auth_transport.Transport):
|
||||||
|
@ -34,9 +38,17 @@ class Channel (channel.Channel):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class RSAKey (rsakey.RSAKey):
|
class RSAKey (rsakey.RSAKey):
|
||||||
|
"""
|
||||||
|
Representation of an RSA key which can be used to sign and verify SSH2
|
||||||
|
data.
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class DSSKey (dsskey.DSSKey):
|
class DSSKey (dsskey.DSSKey):
|
||||||
|
"""
|
||||||
|
Representation of a DSS key which can be used to sign an verify SSH2
|
||||||
|
data.
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ class Transport (BaseTransport):
|
||||||
"""
|
"""
|
||||||
return self.authenticated and self.active
|
return self.authenticated and self.active
|
||||||
|
|
||||||
def auth_key(self, username, key, event):
|
def auth_publickey(self, username, key, event):
|
||||||
"""
|
"""
|
||||||
Authenticate to the server using a private key. The key is used to
|
Authenticate to the server using a private key. The key is used to
|
||||||
sign data from the server, so it must include the private part. The
|
sign data from the server, so it must include the private part. The
|
||||||
|
@ -113,15 +113,70 @@ class Transport (BaseTransport):
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
|
|
||||||
def get_allowed_auths(self, username):
|
def get_allowed_auths(self, username):
|
||||||
"override me!"
|
"""
|
||||||
|
I{(subclass override)}
|
||||||
|
Return a list of authentication methods supported by the server.
|
||||||
|
This list is sent to clients attempting to authenticate, to inform them
|
||||||
|
of authentication methods that might be successful.
|
||||||
|
|
||||||
|
The "list" is actually a string of comma-separated names of types of
|
||||||
|
authentication. Possible values are C{"password"}, C{"publickey"},
|
||||||
|
and C{"none"}.
|
||||||
|
|
||||||
|
The default implementation always returns C{"password"}.
|
||||||
|
|
||||||
|
@param username: the username requesting authentication.
|
||||||
|
@type username: string
|
||||||
|
@return: a comma-separated list of authentication types
|
||||||
|
@rtype: string
|
||||||
|
"""
|
||||||
return 'password'
|
return 'password'
|
||||||
|
|
||||||
def check_auth_none(self, username):
|
def check_auth_none(self, username):
|
||||||
"override me! return int ==> auth status"
|
"""
|
||||||
|
I{(subclass override)}
|
||||||
|
Determine if a client may open channels with no (further)
|
||||||
|
authentication. You should override this method in server mode.
|
||||||
|
|
||||||
|
Return C{AUTH_FAILED} if the client must authenticate, or
|
||||||
|
C{AUTH_SUCCESSFUL} if it's okay for the client to not authenticate.
|
||||||
|
|
||||||
|
The default implementation always returns C{AUTH_FAILED}.
|
||||||
|
|
||||||
|
@param username: the username of the client.
|
||||||
|
@type username: string
|
||||||
|
@return: C{AUTH_FAILED} if the authentication fails; C{AUTH_SUCCESSFUL}
|
||||||
|
if it succeeds.
|
||||||
|
@rtype: int
|
||||||
|
"""
|
||||||
return self.AUTH_FAILED
|
return self.AUTH_FAILED
|
||||||
|
|
||||||
def check_auth_password(self, username, password):
|
def check_auth_password(self, username, password):
|
||||||
"override me! return int ==> auth status"
|
"""
|
||||||
|
I{(subclass override)}
|
||||||
|
Determine if a given username and password supplied by the client is
|
||||||
|
acceptable for use in authentication. You should override this method
|
||||||
|
in server mode.
|
||||||
|
|
||||||
|
Return C{AUTH_FAILED} if the password is not accepted,
|
||||||
|
C{AUTH_SUCCESSFUL} if the password is accepted and completes the
|
||||||
|
authentication, or C{AUTH_PARTIALLY_SUCCESSFUL} if your authentication
|
||||||
|
is stateful, and this key is accepted for authentication, but more
|
||||||
|
authentication is required. (In this latter case, L{get_allowed_auths}
|
||||||
|
will be called to report to the client what options it has for
|
||||||
|
continuing the authentication.)
|
||||||
|
|
||||||
|
The default implementation always returns C{AUTH_FAILED}.
|
||||||
|
|
||||||
|
@param username: the username of the authenticating client.
|
||||||
|
@type username: string
|
||||||
|
@param password: the password given by the client.
|
||||||
|
@type password: string
|
||||||
|
@return: C{AUTH_FAILED} if the authentication fails; C{AUTH_SUCCESSFUL}
|
||||||
|
if it succeeds; C{AUTH_PARTIALLY_SUCCESSFUL} if the password auth is
|
||||||
|
successful, but authentication must continue.
|
||||||
|
@rtype: int
|
||||||
|
"""
|
||||||
return self.AUTH_FAILED
|
return self.AUTH_FAILED
|
||||||
|
|
||||||
def check_auth_publickey(self, username, key):
|
def check_auth_publickey(self, username, key):
|
||||||
|
|
|
@ -176,10 +176,59 @@ class BaseTransport (threading.Thread):
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def start_client(self, event=None):
|
def start_client(self, event=None):
|
||||||
|
"""
|
||||||
|
Negotiate a new SSH2 session as a client. This is the first step after
|
||||||
|
creating a new L{Transport}. A separate thread is created for protocol
|
||||||
|
negotiation, so this method returns immediately.
|
||||||
|
|
||||||
|
When negotiation is done (successful or not), the given C{Event} will
|
||||||
|
be triggered. On failure, L{is_active} will return C{False}.
|
||||||
|
|
||||||
|
After a successful negotiation, you will usually want to authenticate,
|
||||||
|
calling L{auth_password <Transport.auth_password>} or
|
||||||
|
L{auth_publickey <Transport.auth_publickey>}.
|
||||||
|
|
||||||
|
@note: L{connect} is a simpler method for connecting as a client.
|
||||||
|
|
||||||
|
@note: After calling this method (or L{start_server} or L{connect}),
|
||||||
|
you should no longer directly read from or write to the original socket
|
||||||
|
object.
|
||||||
|
|
||||||
|
@param event: an event to trigger when negotiation is complete.
|
||||||
|
@type event: threading.Event
|
||||||
|
"""
|
||||||
self.completion_event = event
|
self.completion_event = event
|
||||||
self.start()
|
self.start()
|
||||||
|
|
||||||
def start_server(self, event=None):
|
def start_server(self, event=None):
|
||||||
|
"""
|
||||||
|
Negotiate a new SSH2 session as a server. This is the first step after
|
||||||
|
creating a new L{Transport} and setting up your server host key(s). A
|
||||||
|
separate thread is created for protocol negotiation, so this method
|
||||||
|
returns immediately.
|
||||||
|
|
||||||
|
When negotiation is done (successful or not), the given C{Event} will
|
||||||
|
be triggered. On failure, L{is_active} will return C{False}.
|
||||||
|
|
||||||
|
After a successful negotiation, the client will need to authenticate.
|
||||||
|
Override the methods
|
||||||
|
L{get_allowed_auths <Transport.get_allowed_auths>},
|
||||||
|
L{check_auth_none <Transport.check_auth_none>},
|
||||||
|
L{check_auth_password <Transport.check_auth_password>}, and
|
||||||
|
L{check_auth_publickey <Transport.check_auth_publickey>} to control the
|
||||||
|
authentication process.
|
||||||
|
|
||||||
|
After a successful authentication, the client should request to open
|
||||||
|
a channel. Override L{check_channel_request} to allow channels to
|
||||||
|
be opened.
|
||||||
|
|
||||||
|
@note: After calling this method (or L{start_client} or L{connect}),
|
||||||
|
you should no longer directly read from or write to the original socket
|
||||||
|
object.
|
||||||
|
|
||||||
|
@param event: an event to trigger when negotiation is complete.
|
||||||
|
@type event: threading.Event
|
||||||
|
"""
|
||||||
self.server_mode = 1
|
self.server_mode = 1
|
||||||
self.completion_event = event
|
self.completion_event = event
|
||||||
self.start()
|
self.start()
|
||||||
|
@ -301,9 +350,29 @@ class BaseTransport (threading.Thread):
|
||||||
return self.active
|
return self.active
|
||||||
|
|
||||||
def open_session(self):
|
def open_session(self):
|
||||||
|
"""
|
||||||
|
Request a new channel to the server, of type C{"session"}. This
|
||||||
|
is just an alias for C{open_channel('session')}.
|
||||||
|
|
||||||
|
@return: a new L{Channel} on success, or C{None} if the request is
|
||||||
|
rejected or the session ends prematurely.
|
||||||
|
@rtype: L{Channel}
|
||||||
|
"""
|
||||||
return self.open_channel('session')
|
return self.open_channel('session')
|
||||||
|
|
||||||
def open_channel(self, kind):
|
def open_channel(self, kind):
|
||||||
|
"""
|
||||||
|
Request a new channel to the server. L{Channel}s are socket-like
|
||||||
|
objects used for the actual transfer of data across the session.
|
||||||
|
You may only request a channel after negotiating encryption (using
|
||||||
|
L{connect} or L{start_client} and authenticating.
|
||||||
|
|
||||||
|
@param kind: the kind of channel requested (usually C{"session"}).
|
||||||
|
@type kind: string
|
||||||
|
@return: a new L{Channel} on success, or C{None} if the request is
|
||||||
|
rejected or the session ends prematurely.
|
||||||
|
@rtype: L{Channel}
|
||||||
|
"""
|
||||||
chan = None
|
chan = None
|
||||||
try:
|
try:
|
||||||
self.lock.acquire()
|
self.lock.acquire()
|
||||||
|
@ -361,7 +430,33 @@ class BaseTransport (threading.Thread):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def check_channel_request(self, kind, chanid):
|
def check_channel_request(self, kind, chanid):
|
||||||
"override me! return object descended from Channel to allow, or None to reject"
|
"""
|
||||||
|
I{(subclass override)}
|
||||||
|
Determine if a channel request of a given type will be granted, and
|
||||||
|
return a suitable L{Channel} object. This method is called in server
|
||||||
|
mode when the client requests a channel, after authentication is
|
||||||
|
complete.
|
||||||
|
|
||||||
|
In server mode, you will generally want to subclass L{Channel} to
|
||||||
|
override some of the methods for handling client requests (such as
|
||||||
|
connecting to a subsystem or opening a shell) to determine what you
|
||||||
|
want to allow or disallow. For this reason, L{check_channel_request}
|
||||||
|
must return a new object of that type. The C{chanid} parameter is
|
||||||
|
passed so that you can use it in L{Channel}'s constructor.
|
||||||
|
|
||||||
|
The default implementation always returns C{None}, rejecting any
|
||||||
|
channel requests. A useful server must override this method.
|
||||||
|
|
||||||
|
@param kind: the kind of channel the client would like to open
|
||||||
|
(usually C{"session"}).
|
||||||
|
@type kind: string
|
||||||
|
@param chanid: ID of the channel, required to create a new L{Channel}
|
||||||
|
object.
|
||||||
|
@type chanid: int
|
||||||
|
@return: a new L{Channel} object (or subclass thereof), or C{None} to
|
||||||
|
refuse the request.
|
||||||
|
@rtype: L{Channel}
|
||||||
|
"""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def accept(self, timeout=None):
|
def accept(self, timeout=None):
|
||||||
|
@ -385,8 +480,8 @@ class BaseTransport (threading.Thread):
|
||||||
Negotiate an SSH2 session, and optionally verify the server's host key
|
Negotiate an SSH2 session, and optionally verify the server's host key
|
||||||
and authenticate using a password or private key. This is a shortcut
|
and authenticate using a password or private key. This is a shortcut
|
||||||
for L{start_client}, L{get_remote_server_key}, and
|
for L{start_client}, L{get_remote_server_key}, and
|
||||||
L{Transport.auth_password} or L{Transport.auth_key}. Use those methods
|
L{Transport.auth_password} or L{Transport.auth_publickey}. Use those
|
||||||
if you want more control.
|
methods if you want more control.
|
||||||
|
|
||||||
You can use this method immediately after creating a Transport to
|
You can use this method immediately after creating a Transport to
|
||||||
negotiate encryption with a server. If it fails, an exception will be
|
negotiate encryption with a server. If it fails, an exception will be
|
||||||
|
@ -450,7 +545,7 @@ class BaseTransport (threading.Thread):
|
||||||
self.auth_password(username, password, event)
|
self.auth_password(username, password, event)
|
||||||
else:
|
else:
|
||||||
self._log(DEBUG, 'Attempting password auth...')
|
self._log(DEBUG, 'Attempting password auth...')
|
||||||
self.auth_key(username, pkey, event)
|
self.auth_publickey(username, pkey, event)
|
||||||
while 1:
|
while 1:
|
||||||
event.wait(0.1)
|
event.wait(0.1)
|
||||||
if not self.active:
|
if not self.active:
|
||||||
|
|
Loading…
Reference in New Issue