summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--README2
-rwxr-xr-xdemo.py2
-rwxr-xr-xdemo_server.py5
-rw-r--r--paramiko/__init__.py14
-rw-r--r--paramiko/auth_transport.py63
-rw-r--r--paramiko/transport.py103
6 files changed, 176 insertions, 13 deletions
diff --git a/README b/README
index a67b889e..9b48e6a7 100644
--- a/README
+++ b/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)
* can't handle password-protected private key files
* 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
diff --git a/demo.py b/demo.py
index 963f7d83..9bea9876 100755
--- a/demo.py
+++ b/demo.py
@@ -108,7 +108,7 @@ try:
if len(path) == 0:
path = default_path
key.read_private_key_file(path)
- t.auth_key(username, key, event)
+ t.auth_publickey(username, key, event)
elif auth == 'd':
key = paramiko.DSSKey()
default_path = os.environ['HOME'] + '/.ssh/id_dsa'
diff --git a/demo_server.py b/demo_server.py
index 65b45cf7..a80b7f9e 100755
--- a/demo_server.py
+++ b/demo_server.py
@@ -41,6 +41,10 @@ class ServerTransport(paramiko.Transport):
return self.AUTH_SUCCESSFUL
return self.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"
@@ -86,7 +90,6 @@ try:
print '(Failed to load moduli -- gex will be unsupported.)'
raise
t.add_server_key(host_key)
- t.ultra_debug = 0
t.start_server(event)
while 1:
event.wait(0.1)
diff --git a/paramiko/__init__.py b/paramiko/__init__.py
index 76531611..bed1f8e8 100644
--- a/paramiko/__init__.py
+++ b/paramiko/__init__.py
@@ -9,11 +9,15 @@ __author__ = "Robey Pointer <robey@lag.net>"
__date__ = "10 Nov 2003"
__version__ = "0.1-charmander"
__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):
+ """
+ Exception thrown by failures in SSH2 protocol negotiation or logic errors.
+ """
pass
class Transport (auth_transport.Transport):
@@ -34,9 +38,17 @@ class Channel (channel.Channel):
pass
class RSAKey (rsakey.RSAKey):
+ """
+ Representation of an RSA key which can be used to sign and verify SSH2
+ data.
+ """
pass
class DSSKey (dsskey.DSSKey):
+ """
+ Representation of a DSS key which can be used to sign an verify SSH2
+ data.
+ """
pass
diff --git a/paramiko/auth_transport.py b/paramiko/auth_transport.py
index 5c4516f5..d2376c55 100644
--- a/paramiko/auth_transport.py
+++ b/paramiko/auth_transport.py
@@ -56,7 +56,7 @@ class Transport (BaseTransport):
"""
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
sign data from the server, so it must include the private part. The
@@ -113,15 +113,70 @@ class Transport (BaseTransport):
self.lock.release()
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'
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
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
def check_auth_publickey(self, username, key):
diff --git a/paramiko/transport.py b/paramiko/transport.py
index cf914f75..ce82d75c 100644
--- a/paramiko/transport.py
+++ b/paramiko/transport.py
@@ -176,10 +176,59 @@ class BaseTransport (threading.Thread):
return out
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.start()
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.completion_event = event
self.start()
@@ -301,9 +350,29 @@ class BaseTransport (threading.Thread):
return self.active
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')
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
try:
self.lock.acquire()
@@ -361,7 +430,33 @@ class BaseTransport (threading.Thread):
return True
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
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
and authenticate using a password or private key. This is a shortcut
for L{start_client}, L{get_remote_server_key}, and
- L{Transport.auth_password} or L{Transport.auth_key}. Use those methods
- if you want more control.
+ L{Transport.auth_password} or L{Transport.auth_publickey}. Use those
+ methods if you want more control.
You can use this method immediately after creating a Transport to
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)
else:
self._log(DEBUG, 'Attempting password auth...')
- self.auth_key(username, pkey, event)
+ self.auth_publickey(username, pkey, event)
while 1:
event.wait(0.1)
if not self.active: