diff --git a/README b/README index a67b889..9b48e6a 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 963f7d8..9bea987 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 65b45cf..a80b7f9 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 7653161..bed1f8e 100644 --- a/paramiko/__init__.py +++ b/paramiko/__init__.py @@ -9,11 +9,15 @@ __author__ = "Robey Pointer " __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 5c4516f..d2376c5 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 cf914f7..ce82d75 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 } or + L{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 }, + L{check_auth_none }, + L{check_auth_password }, and + L{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: