summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRobey Pointer <robey@lag.net>2005-08-09 07:40:07 +0000
committerRobey Pointer <robey@lag.net>2005-08-09 07:40:07 +0000
commit0f3bf86617999de2aff056b8455aa33c64188e3a (patch)
tree2bffa163a8ca48420924db7ef7b62d6162e690c4
parent1fdec8bd06b22c098de6e731364c707c7292cc1f (diff)
[project @ Arch-1:robey@lag.net--2005-master-shake%paramiko--dev--1--patch-54]
smooth BaseTransport and Transport together, and move the auth stuff into AuthHandler -- an improvement i made in jaramiko and decided deserved to be backported
-rw-r--r--paramiko/__init__.py12
-rw-r--r--paramiko/auth_handler.py315
-rw-r--r--paramiko/auth_transport.py441
-rw-r--r--paramiko/common.py2
-rw-r--r--paramiko/server.py8
-rw-r--r--paramiko/transport.py166
6 files changed, 474 insertions, 470 deletions
diff --git a/paramiko/__init__.py b/paramiko/__init__.py
index dd7ed0ae..e9ed3754 100644
--- a/paramiko/__init__.py
+++ b/paramiko/__init__.py
@@ -27,8 +27,8 @@ protocol also includes the ability to open arbitrary channels to remote
services across an encrypted tunnel. (This is how C{sftp} works, for example.)
To use this package, pass a socket (or socket-like object) to a L{Transport},
-and use L{start_server <paramiko.transport.BaseTransport.start_server>} or
-L{start_client <paramiko.transport.BaseTransport.start_client>} to negoatite
+and use L{start_server <Transport.start_server>} or
+L{start_client <Transport.start_client>} to negoatite
with the remote host as either a server or client. As a client, you are
responsible for authenticating using a password or private key, and checking
the server's host key. I{(Key signature and verification is done by paramiko,
@@ -64,12 +64,12 @@ __version__ = "1.4 (oddish)"
__license__ = "GNU Lesser General Public License (LGPL)"
-import transport, auth_transport, channel, rsakey, dsskey, message
+import transport, auth_handler, channel, rsakey, dsskey, message
import ssh_exception, file, packet, agent, server, util
import sftp_client, sftp_attr, sftp_handle, sftp_server, sftp_si
-from transport import randpool, SecurityOptions, BaseTransport
-from auth_transport import Transport
+from transport import randpool, SecurityOptions, Transport
+from auth_handler import AuthHandler
from channel import Channel, ChannelFile
from ssh_exception import SSHException, PasswordRequiredException, BadAuthenticationType
from server import ServerInterface, SubsystemHandler
@@ -91,7 +91,7 @@ from pkey import PKey
# fix module names for epydoc
for x in [Transport, SecurityOptions, Channel, SFTPServer, SSHException, \
PasswordRequiredException, BadAuthenticationType, ChannelFile, \
- SubsystemHandler, BaseTransport, RSAKey, DSSKey, SFTPError, \
+ SubsystemHandler, AuthHandler, RSAKey, DSSKey, SFTPError, \
SFTP, SFTPClient, SFTPServer, Message, Packetizer, SFTPAttributes, \
SFTPHandle, SFTPServerInterface, BufferedFile, Agent, AgentKey, \
PKey, BaseSFTP, SFTPFile, ServerInterface]:
diff --git a/paramiko/auth_handler.py b/paramiko/auth_handler.py
new file mode 100644
index 00000000..95965fa8
--- /dev/null
+++ b/paramiko/auth_handler.py
@@ -0,0 +1,315 @@
+# Copyright (C) 2003-2005 Robey Pointer <robey@lag.net>
+#
+# This file is part of paramiko.
+#
+# Paramiko is free software; you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+"""
+L{AuthHandler}
+"""
+
+import threading
+
+# this helps freezing utils
+import encodings.utf_8
+
+from common import *
+import util
+from message import Message
+from ssh_exception import SSHException, BadAuthenticationType, PartialAuthentication
+
+
+class AuthHandler (object):
+ """
+ Internal class to handle the mechanics of authentication.
+ """
+
+ def __init__(self, transport):
+ self.transport = transport
+ self.username = None
+ self.authenticated = False
+ self.auth_event = None
+ self.auth_method = ''
+ self.password = None
+ self.private_key = None
+ # for server mode:
+ self.auth_username = None
+ self.auth_fail_count = 0
+
+ def is_authenticated(self):
+ return self.authenticated
+
+ def get_username(self):
+ if self.transport.server_mode:
+ return self.auth_username
+ else:
+ return self.username
+
+ def auth_publickey(self, username, key, event):
+ self.transport.lock.acquire()
+ try:
+ self.auth_event = event
+ self.auth_method = 'publickey'
+ self.username = username
+ self.private_key = key
+ self._request_auth()
+ finally:
+ self.transport.lock.release()
+
+ def auth_password(self, username, password, event):
+ self.transport.lock.acquire()
+ try:
+ self.auth_event = event
+ self.auth_method = 'password'
+ self.username = username
+ self.password = password
+ self._request_auth()
+ finally:
+ self.transport.lock.release()
+
+ def abort(self):
+ if self.auth_event is not None:
+ self.auth_event.set()
+
+
+ ### internals...
+
+
+ def _request_auth(self):
+ m = Message()
+ m.add_byte(chr(MSG_SERVICE_REQUEST))
+ m.add_string('ssh-userauth')
+ self.transport._send_message(m)
+
+ def _disconnect_service_not_available(self):
+ m = Message()
+ m.add_byte(chr(MSG_DISCONNECT))
+ m.add_int(DISCONNECT_SERVICE_NOT_AVAILABLE)
+ m.add_string('Service not available')
+ m.add_string('en')
+ self.transport._send_message(m)
+ self.transport.close()
+
+ def _disconnect_no_more_auth(self):
+ m = Message()
+ m.add_byte(chr(MSG_DISCONNECT))
+ m.add_int(DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE)
+ m.add_string('No more auth methods available')
+ m.add_string('en')
+ self.transport._send_message(m)
+ self.transport.close()
+
+ def _get_session_blob(self, key, service, username):
+ m = Message()
+ m.add_string(self.transport.session_id)
+ m.add_byte(chr(MSG_USERAUTH_REQUEST))
+ m.add_string(username)
+ m.add_string(service)
+ m.add_string('publickey')
+ m.add_boolean(1)
+ m.add_string(key.get_name())
+ m.add_string(str(key))
+ return str(m)
+
+ def wait_for_response(self, event):
+ while True:
+ event.wait(0.1)
+ if not self.transport.is_active():
+ e = self.transport.get_exception()
+ if e is None:
+ e = SSHException('Authentication failed.')
+ raise e
+ if event.isSet():
+ break
+ if not self.is_authenticated():
+ e = self.transport.get_exception()
+ if e is None:
+ e = SSHException('Authentication failed.')
+ # this is horrible. python Exception isn't yet descended from
+ # object, so type(e) won't work. :(
+ if issubclass(e.__class__, PartialAuthentication):
+ return e.allowed_types
+ raise e
+ return []
+
+ def _parse_service_request(self, m):
+ service = m.get_string()
+ if self.transport.server_mode and (service == 'ssh-userauth'):
+ # accepted
+ m = Message()
+ m.add_byte(chr(MSG_SERVICE_ACCEPT))
+ m.add_string(service)
+ self.transport._send_message(m)
+ return
+ # dunno this one
+ self._disconnect_service_not_available()
+
+ def _parse_service_accept(self, m):
+ service = m.get_string()
+ if service == 'ssh-userauth':
+ self.transport._log(DEBUG, 'userauth is OK')
+ m = Message()
+ m.add_byte(chr(MSG_USERAUTH_REQUEST))
+ m.add_string(self.username)
+ m.add_string('ssh-connection')
+ m.add_string(self.auth_method)
+ if self.auth_method == 'password':
+ m.add_boolean(False)
+ m.add_string(self.password.encode('UTF-8'))
+ elif self.auth_method == 'publickey':
+ m.add_boolean(True)
+ m.add_string(self.private_key.get_name())
+ m.add_string(str(self.private_key))
+ blob = self._get_session_blob(self.private_key, 'ssh-connection', self.username)
+ sig = self.private_key.sign_ssh_data(self.transport.randpool, blob)
+ m.add_string(str(sig))
+ else:
+ raise SSHException('Unknown auth method "%s"' % self.auth_method)
+ self.transport._send_message(m)
+ else:
+ self.transport._log(DEBUG, 'Service request "%s" accepted (?)' % service)
+
+ def _parse_userauth_request(self, m):
+ if not self.transport.server_mode:
+ # er, uh... what?
+ m = Message()
+ m.add_byte(chr(MSG_USERAUTH_FAILURE))
+ m.add_string('none')
+ m.add_boolean(0)
+ self.transport._send_message(m)
+ return
+ if self.authenticated:
+ # ignore
+ return
+ username = m.get_string()
+ service = m.get_string()
+ method = m.get_string()
+ self.transport._log(DEBUG, 'Auth request (type=%s) service=%s, username=%s' % (method, service, username))
+ if service != 'ssh-connection':
+ self._disconnect_service_not_available()
+ return
+ if (self.auth_username is not None) and (self.auth_username != username):
+ self.transport._log(WARNING, 'Auth rejected because the client attempted to change username in mid-flight')
+ self._disconnect_no_more_auth()
+ return
+ self.auth_username = username
+
+ if method == 'none':
+ result = self.transport.server_object.check_auth_none(username)
+ elif method == 'password':
+ changereq = m.get_boolean()
+ password = m.get_string().decode('UTF-8', 'replace')
+ if changereq:
+ # always treated as failure, since we don't support changing passwords, but collect
+ # the list of valid auth types from the callback anyway
+ self.transport._log(DEBUG, 'Auth request to change passwords (rejected)')
+ newpassword = m.get_string().decode('UTF-8', 'replace')
+ result = AUTH_FAILED
+ else:
+ result = self.transport.server_object.check_auth_password(username, password)
+ elif method == 'publickey':
+ sig_attached = m.get_boolean()
+ keytype = m.get_string()
+ keyblob = m.get_string()
+ try:
+ key = self.transport._key_info[keytype](Message(keyblob))
+ except SSHException, e:
+ self.transport._log(INFO, 'Auth rejected: public key: %s' % str(e))
+ key = None
+ except:
+ self.transport._log(INFO, 'Auth rejected: unsupported or mangled public key')
+ key = None
+ if key is None:
+ self._disconnect_no_more_auth()
+ return
+ # first check if this key is okay... if not, we can skip the verify
+ result = self.transport.server_object.check_auth_publickey(username, key)
+ if result != AUTH_FAILED:
+ # key is okay, verify it
+ if not sig_attached:
+ # client wants to know if this key is acceptable, before it
+ # signs anything... send special "ok" message
+ m = Message()
+ m.add_byte(chr(MSG_USERAUTH_PK_OK))
+ m.add_string(keytype)
+ m.add_string(keyblob)
+ self.transport._send_message(m)
+ return
+ sig = Message(m.get_string())
+ blob = self._get_session_blob(key, service, username)
+ if not key.verify_ssh_sig(blob, sig):
+ self.transport._log(INFO, 'Auth rejected: invalid signature')
+ result = AUTH_FAILED
+ else:
+ result = self.transport.server_object.check_auth_none(username)
+ # okay, send result
+ m = Message()
+ if result == AUTH_SUCCESSFUL:
+ self.transport._log(INFO, 'Auth granted (%s).' % method)
+ m.add_byte(chr(MSG_USERAUTH_SUCCESS))
+ self.authenticated = True
+ else:
+ self.transport._log(INFO, 'Auth rejected (%s).' % method)
+ m.add_byte(chr(MSG_USERAUTH_FAILURE))
+ m.add_string(self.transport.server_object.get_allowed_auths(username))
+ if result == AUTH_PARTIALLY_SUCCESSFUL:
+ m.add_boolean(1)
+ else:
+ m.add_boolean(0)
+ self.auth_fail_count += 1
+ self.transport._send_message(m)
+ if self.auth_fail_count >= 10:
+ self._disconnect_no_more_auth()
+
+ def _parse_userauth_success(self, m):
+ self.transport._log(INFO, 'Authentication successful!')
+ self.authenticated = True
+ if self.auth_event != None:
+ self.auth_event.set()
+
+ def _parse_userauth_failure(self, m):
+ authlist = m.get_list()
+ partial = m.get_boolean()
+ if partial:
+ self.transport._log(INFO, 'Authentication continues...')
+ self.transport._log(DEBUG, 'Methods: ' + str(authlist))
+ self.transport.saved_exception = PartialAuthentication(authlist)
+ elif self.auth_method not in authlist:
+ self.transport._log(INFO, 'Authentication type not permitted.')
+ self.transport._log(DEBUG, 'Allowed methods: ' + str(authlist))
+ self.transport.saved_exception = BadAuthenticationType('Bad authentication type', authlist)
+ else:
+ self.transport._log(INFO, 'Authentication failed.')
+ self.authenticated = False
+ self.username = None
+ if self.auth_event != None:
+ self.auth_event.set()
+
+ def _parse_userauth_banner(self, m):
+ banner = m.get_string()
+ lang = m.get_string()
+ self.transport._log(INFO, 'Auth banner: ' + banner)
+ # who cares.
+
+ _handler_table = {
+ MSG_SERVICE_REQUEST: _parse_service_request,
+ MSG_SERVICE_ACCEPT: _parse_service_accept,
+ MSG_USERAUTH_REQUEST: _parse_userauth_request,
+ MSG_USERAUTH_SUCCESS: _parse_userauth_success,
+ MSG_USERAUTH_FAILURE: _parse_userauth_failure,
+ MSG_USERAUTH_BANNER: _parse_userauth_banner,
+ }
+
+
diff --git a/paramiko/auth_transport.py b/paramiko/auth_transport.py
deleted file mode 100644
index f9ecf70b..00000000
--- a/paramiko/auth_transport.py
+++ /dev/null
@@ -1,441 +0,0 @@
-# Copyright (C) 2003-2005 Robey Pointer <robey@lag.net>
-#
-# This file is part of paramiko.
-#
-# Paramiko is free software; you can redistribute it and/or modify it under the
-# terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation; either version 2.1 of the License, or (at your option)
-# any later version.
-#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
-
-"""
-L{Transport} is a subclass of L{BaseTransport} that handles authentication.
-This separation keeps either class file from being too unwieldy.
-"""
-
-import threading
-
-# this helps freezing utils
-import encodings.utf_8
-
-from common import *
-import util
-from transport import BaseTransport
-from message import Message
-from ssh_exception import SSHException, BadAuthenticationType, PartialAuthentication
-
-
-class Transport (BaseTransport):
- """
- An SSH Transport attaches to a stream (usually a socket), negotiates an
- encrypted session, authenticates, and then creates stream tunnels, called
- L{Channel}s, across the session. Multiple channels can be multiplexed
- across a single session (and often are, in the case of port forwardings).
- """
-
- def __init__(self, sock):
- BaseTransport.__init__(self, sock)
- self.username = None
- self.authenticated = False
- self.auth_event = None
- self.auth_method = ''
- self.password = None
- self.private_key = None
- # for server mode:
- self.auth_username = None
- self.auth_fail_count = 0
-
- def __repr__(self):
- out = '<paramiko.Transport at %s' % hex(long(id(self)) & 0xffffffffL)
- if not self.active:
- out += ' (unconnected)'
- else:
- 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
-
- def is_authenticated(self):
- """
- Return true if this session is active and authenticated.
-
- @return: True if the session is still open and has been authenticated successfully;
- False if authentication failed and/or the session is closed.
- @rtype: bool
- """
- return self.authenticated and self.active
-
- def get_username(self):
- """
- Return the username this connection is authenticated for. If the
- session is not authenticated (or authentication failed), this method
- returns C{None}.
-
- @return: username that was authenticated, or C{None}.
- @rtype: string
-
- @since: fearow
- """
- if self.server_mode:
- return self.auth_username
- else:
- return self.username
-
- def auth_publickey(self, username, key, event=None):
- """
- 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.
-
- If an C{event} is passed in, this method will return immediately, and
- the event will be triggered once authentication succeeds or fails. On
- success, L{is_authenticated} will return C{True}. On failure, you may
- use L{get_exception} to get more detailed error information.
-
- Since 1.1, if no event is passed, this method will block until the
- authentication succeeds or fails. On failure, an exception is raised.
- Otherwise, the method simply returns.
-
- If the server requires multi-step authentication (which is very rare),
- this method will return a list of auth types permissible for the next
- step. Otherwise, in the normal case, an empty list is returned.
-
- @param username: the username to authenticate as.
- @type username: string
- @param key: the private key to authenticate with.
- @type key: L{PKey <pkey.PKey>}
- @param event: an event to trigger when the authentication attempt is
- complete (whether it was successful or not)
- @type event: threading.Event
- @return: list of auth types permissible for the next stage of
- authentication (normally empty).
- @rtype: list
-
- @raise BadAuthenticationType: if public-key authentication isn't
- allowed by the server for this user (and no event was passed in).
- @raise SSHException: if the authentication failed (and no event was
- passed in).
- """
- if (not self.active) or (not self.initial_kex_done):
- # we should never try to authenticate unless we're on a secure link
- raise SSHException('No existing session')
- if event is None:
- my_event = threading.Event()
- else:
- my_event = event
- self.lock.acquire()
- try:
- self.auth_event = my_event
- self.auth_method = 'publickey'
- self.username = username
- self.private_key = key
- self._request_auth()
- finally:
- self.lock.release()
- if event is not None:
- # caller wants to wait for event themselves
- return []
- return self._wait_for_response(my_event)
-
- def auth_password(self, username, password, event=None):
- """
- Authenticate to the server using a password. The username and password
- are sent over an encrypted link.
-
- If an C{event} is passed in, this method will return immediately, and
- the event will be triggered once authentication succeeds or fails. On
- success, L{is_authenticated} will return C{True}. On failure, you may
- use L{get_exception} to get more detailed error information.
-
- Since 1.1, if no event is passed, this method will block until the
- authentication succeeds or fails. On failure, an exception is raised.
- Otherwise, the method simply returns.
-
- If the server requires multi-step authentication (which is very rare),
- this method will return a list of auth types permissible for the next
- step. Otherwise, in the normal case, an empty list is returned.
-
- @param username: the username to authenticate as
- @type username: string
- @param password: the password to authenticate with
- @type password: string
- @param event: an event to trigger when the authentication attempt is
- complete (whether it was successful or not)
- @type event: threading.Event
- @return: list of auth types permissible for the next stage of
- authentication (normally empty)
- @rtype: list
-
- @raise BadAuthenticationType: if password authentication isn't
- allowed by the server for this user (and no event was passed in)
- @raise SSHException: if the authentication failed (and no event was
- passed in)
- """
- if (not self.active) or (not self.initial_kex_done):
- # we should never try to send the password unless we're on a secure link
- raise SSHException('No existing session')
- if event is None:
- my_event = threading.Event()
- else:
- my_event = event
- self.lock.acquire()
- try:
- self.auth_event = my_event
- self.auth_method = 'password'
- self.username = username
- self.password = password
- self._request_auth()
- finally:
- self.lock.release()
- if event is not None:
- # caller wants to wait for event themselves
- return []
- return self._wait_for_response(my_event)
-
-
- ### internals...
-
-
- def _request_auth(self):
- m = Message()
- m.add_byte(chr(MSG_SERVICE_REQUEST))
- m.add_string('ssh-userauth')
- self._send_message(m)
-
- def _disconnect_service_not_available(self):
- m = Message()
- m.add_byte(chr(MSG_DISCONNECT))
- m.add_int(DISCONNECT_SERVICE_NOT_AVAILABLE)
- m.add_string('Service not available')
- m.add_string('en')
- self._send_message(m)
- self.close()
-
- def _disconnect_no_more_auth(self):
- m = Message()
- m.add_byte(chr(MSG_DISCONNECT))
- m.add_int(DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE)
- m.add_string('No more auth methods available')
- m.add_string('en')
- self._send_message(m)
- self.close()
-
- def _get_session_blob(self, key, service, username):
- m = Message()
- m.add_string(self.session_id)
- m.add_byte(chr(MSG_USERAUTH_REQUEST))
- m.add_string(username)
- m.add_string(service)
- m.add_string('publickey')
- m.add_boolean(1)
- m.add_string(key.get_name())
- m.add_string(str(key))
- return str(m)
-
- def _wait_for_response(self, event):
- while True:
- event.wait(0.1)
- if not self.active:
- e = self.get_exception()
- if e is None:
- e = SSHException('Authentication failed.')
- raise e
- if event.isSet():
- break
- if not self.is_authenticated():
- e = self.get_exception()
- if e is None:
- e = SSHException('Authentication failed.')
- # this is horrible. python Exception isn't yet descended from
- # object, so type(e) won't work. :(
- if issubclass(e.__class__, PartialAuthentication):
- return e.allowed_types
- raise e
- return []
-
- def _parse_service_request(self, m):
- service = m.get_string()
- if self.server_mode and (service == 'ssh-userauth'):
- # accepted
- m = Message()
- m.add_byte(chr(MSG_SERVICE_ACCEPT))
- m.add_string(service)
- self._send_message(m)
- return
- # dunno this one
- self._disconnect_service_not_available()
-
- def _parse_service_accept(self, m):
- service = m.get_string()
- if service == 'ssh-userauth':
- self._log(DEBUG, 'userauth is OK')
- m = Message()
- m.add_byte(chr(MSG_USERAUTH_REQUEST))
- m.add_string(self.username)
- m.add_string('ssh-connection')
- m.add_string(self.auth_method)
- if self.auth_method == 'password':
- m.add_boolean(False)
- m.add_string(self.password.encode('UTF-8'))
- elif self.auth_method == 'publickey':
- m.add_boolean(True)
- m.add_string(self.private_key.get_name())
- m.add_string(str(self.private_key))
- blob = self._get_session_blob(self.private_key, 'ssh-connection', self.username)
- sig = self.private_key.sign_ssh_data(self.randpool, blob)
- m.add_string(str(sig))
- else:
- raise SSHException('Unknown auth method "%s"' % self.auth_method)
- self._send_message(m)
- else:
- self._log(DEBUG, 'Service request "%s" accepted (?)' % service)
-
- def _parse_userauth_request(self, m):
- if not self.server_mode:
- # er, uh... what?
- m = Message()
- m.add_byte(chr(MSG_USERAUTH_FAILURE))
- m.add_string('none')
- m.add_boolean(0)
- self._send_message(m)
- return
- if self.authenticated:
- # ignore
- return
- username = m.get_string()
- service = m.get_string()
- method = m.get_string()
- self._log(DEBUG, 'Auth request (type=%s) service=%s, username=%s' % (method, service, username))
- if service != 'ssh-connection':
- self._disconnect_service_not_available()
- return
- if (self.auth_username is not None) and (self.auth_username != username):
- self._log(WARNING, 'Auth rejected because the client attempted to change username in mid-flight')
- self._disconnect_no_more_auth()
- return
- self.auth_username = username
-
- if method == 'none':
- result = self.server_object.check_auth_none(username)
- elif method == 'password':
- changereq = m.get_boolean()
- password = m.get_string().decode('UTF-8', 'replace')
- if changereq:
- # always treated as failure, since we don't support changing passwords, but collect
- # 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', 'replace')
- 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()
- try:
- key = self._key_info[keytype](Message(keyblob))
- except SSHException, e:
- self._log(INFO, 'Auth rejected: public key: %s' % str(e))
- key = None
- except:
- self._log(INFO, 'Auth rejected: unsupported or mangled public key')
- key = None
- if key is None:
- 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 != AUTH_FAILED:
- # key is okay, verify it
- if not sig_attached:
- # client wants to know if this key is acceptable, before it
- # signs anything... send special "ok" message
- m = Message()
- m.add_byte(chr(MSG_USERAUTH_PK_OK))
- m.add_string(keytype)
- m.add_string(keyblob)
- self._send_message(m)
- return
- sig = Message(m.get_string())
- blob = self._get_session_blob(key, service, username)
- if not key.verify_ssh_sig(blob, sig):
- self._log(INFO, 'Auth rejected: invalid signature')
- result = AUTH_FAILED
- else:
- result = self.server_object.check_auth_none(username)
- # okay, send result
- m = Message()
- if result == AUTH_SUCCESSFUL:
- self._log(INFO, 'Auth granted (%s).' % method)
- m.add_byte(chr(MSG_USERAUTH_SUCCESS))
- self.authenticated = True
- else:
- self._log(INFO, 'Auth rejected (%s).' % method)
- m.add_byte(chr(MSG_USERAUTH_FAILURE))
- m.add_string(self.server_object.get_allowed_auths(username))
- if result == AUTH_PARTIALLY_SUCCESSFUL:
- m.add_boolean(1)
- else:
- m.add_boolean(0)
- self.auth_fail_count += 1
- self._send_message(m)
- if self.auth_fail_count >= 10:
- self._disconnect_no_more_auth()
-
- def _parse_userauth_success(self, m):
- self._log(INFO, 'Authentication successful!')
- self.authenticated = True
- if self.auth_event != None:
- self.auth_event.set()
-
- def _parse_userauth_failure(self, m):
- authlist = m.get_list()
- partial = m.get_boolean()
- if partial:
- self._log(INFO, 'Authentication continues...')
- self._log(DEBUG, 'Methods: ' + str(authlist))
- self.saved_exception = PartialAuthentication(authlist)
- elif self.auth_method not in authlist:
- self._log(INFO, 'Authentication type not permitted.')
- self._log(DEBUG, 'Allowed methods: ' + str(authlist))
- self.saved_exception = BadAuthenticationType('Bad authentication type', authlist)
- else:
- self._log(INFO, 'Authentication failed.')
- self.authenticated = False
- self.username = None
- if self.auth_event != None:
- self.auth_event.set()
-
- def _parse_userauth_banner(self, m):
- banner = m.get_string()
- lang = m.get_string()
- self._log(INFO, 'Auth banner: ' + banner)
- # who cares.
-
- _handler_table = BaseTransport._handler_table.copy()
- _handler_table.update({
- MSG_SERVICE_REQUEST: _parse_service_request,
- MSG_SERVICE_ACCEPT: _parse_service_accept,
- MSG_USERAUTH_REQUEST: _parse_userauth_request,
- MSG_USERAUTH_SUCCESS: _parse_userauth_success,
- MSG_USERAUTH_FAILURE: _parse_userauth_failure,
- MSG_USERAUTH_BANNER: _parse_userauth_banner,
- })
-
diff --git a/paramiko/common.py b/paramiko/common.py
index 42793751..d0a0f80b 100644
--- a/paramiko/common.py
+++ b/paramiko/common.py
@@ -1,5 +1,3 @@
-#!/usr/bin/python
-
# Copyright (C) 2003-2005 Robey Pointer <robey@lag.net>
#
# This file is part of paramiko.
diff --git a/paramiko/server.py b/paramiko/server.py
index 747a36d3..151dd124 100644
--- a/paramiko/server.py
+++ b/paramiko/server.py
@@ -25,8 +25,6 @@ L{ServerInterface} is an interface to override for server support.
import threading
from common import *
import util
-from transport import BaseTransport
-from auth_transport import Transport
class ServerInterface (object):
"""
@@ -286,7 +284,7 @@ class ServerInterface (object):
subsystem. An example of a subsystem is C{sftp}.
The default implementation checks for a subsystem handler assigned via
- L{Transport.set_subsystem_handler <BaseTransport.set_subsystem_handler>}.
+ L{Transport.set_subsystem_handler}.
If one has been set, the handler is invoked and this method returns
C{True}. Otherwise it returns C{False}.
@@ -338,7 +336,7 @@ class SubsystemHandler (threading.Thread):
"""
Handler for a subsytem in server mode. If you create a subclass of this
class and pass it to
- L{Transport.set_subsystem_handler <BaseTransport.set_subsystem_handler>},
+ L{Transport.set_subsystem_handler},
an object of this
class will be created for each request for this subsystem. Each new object
will be executed within its own new thread by calling L{start_subsystem}.
@@ -409,7 +407,7 @@ class SubsystemHandler (threading.Thread):
@note: It is the responsibility of this method to exit if the
underlying L{Transport} is closed. This can be done by checking
- L{Transport.is_active <BaseTransport.is_active>} or noticing an EOF
+ L{Transport.is_active} or noticing an EOF
on the L{Channel}. If this method loops forever without checking
for this case, your python interpreter may refuse to exit because
this thread will still be running.
diff --git a/paramiko/transport.py b/paramiko/transport.py
index 43837e4a..3e604d64 100644
--- a/paramiko/transport.py
+++ b/paramiko/transport.py
@@ -17,7 +17,7 @@
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
"""
-L{BaseTransport} handles the core SSH2 protocol.
+L{Transport} handles the core SSH2 protocol.
"""
import sys, os, string, threading, socket, struct, time
@@ -35,6 +35,7 @@ from dsskey import DSSKey
from kex_group1 import KexGroup1
from kex_gex import KexGex
from primes import ModulusPack
+from auth_handler import AuthHandler
# these come from PyCrypt
# http://www.amk.ca/python/writing/pycrypt/
@@ -124,12 +125,14 @@ class SecurityOptions (object):
kex = property(_get_kex, _set_kex, None, "Key exchange algorithms")
-class BaseTransport (threading.Thread):
+class Transport (threading.Thread):
"""
- Handles protocol negotiation, key exchange, encryption, and the creation
- of channels across an SSH session. Basically everything but authentication
- is done here.
+ An SSH Transport attaches to a stream (usually a socket), negotiates an
+ encrypted session, authenticates, and then creates stream tunnels, called
+ L{Channel}s, across the session. Multiple channels can be multiplexed
+ across a single session (and often are, in the case of port forwardings).
"""
+
_PROTO_ID = '2.0'
_CLIENT_ID = 'paramiko_1.4'
@@ -240,6 +243,7 @@ class BaseTransport (threading.Thread):
self.log_name = 'paramiko.transport'
self.logger = util.get_logger(self.log_name)
self.packetizer.set_log(self.logger)
+ self.auth_handler = None
# user-defined event callbacks:
self.completion_event = None
# server mode:
@@ -259,17 +263,22 @@ class BaseTransport (threading.Thread):
@rtype: str
"""
- out = '<paramiko.BaseTransport at %s' % hex(long(id(self)) & 0xffffffffL)
+ out = '<paramiko.Transport at %s' % hex(long(id(self)) & 0xffffffffL)
if not self.active:
out += ' (unconnected)'
else:
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)'
+ if self.is_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 += ' (active; %d open channels)' % len(self.channels)
+ out += ' (connecting)'
out += '>'
return out
@@ -464,19 +473,19 @@ class BaseTransport (threading.Thread):
@note: This has no effect when used in client mode.
"""
- BaseTransport._modulus_pack = ModulusPack(randpool)
+ Transport._modulus_pack = ModulusPack(randpool)
# places to look for the openssh "moduli" file
file_list = [ '/etc/ssh/moduli', '/usr/local/etc/moduli' ]
if filename is not None:
file_list.insert(0, filename)
for fn in file_list:
try:
- BaseTransport._modulus_pack.read_file(fn)
+ Transport._modulus_pack.read_file(fn)
return True
except IOError:
pass
# none succeeded
- BaseTransport._modulus_pack = None
+ Transport._modulus_pack = None
return False
load_server_moduli = staticmethod(load_server_moduli)
@@ -833,6 +842,128 @@ class BaseTransport (threading.Thread):
self.subsystem_table[name] = (handler, larg, kwarg)
finally:
self.lock.release()
+
+ def is_authenticated(self):
+ """
+ Return true if this session is active and authenticated.
+
+ @return: True if the session is still open and has been authenticated
+ successfully; False if authentication failed and/or the session is
+ closed.
+ @rtype: bool
+ """
+ return self.active and (self.auth_handler is not None) and self.auth_handler.is_authenticated()
+
+ def get_username(self):
+ """
+ Return the username this connection is authenticated for. If the
+ session is not authenticated (or authentication failed), this method
+ returns C{None}.
+
+ @return: username that was authenticated, or C{None}.
+ @rtype: string
+
+ @since: fearow
+ """
+ if not self.active or (self.auth_handler is None):
+ return None
+ return self.auth_handler.get_username()
+
+ def auth_password(self, username, password, event=None):
+ """
+ Authenticate to the server using a password. The username and password
+ are sent over an encrypted link.
+
+ If an C{event} is passed in, this method will return immediately, and
+ the event will be triggered once authentication succeeds or fails. On
+ success, L{is_authenticated} will return C{True}. On failure, you may
+ use L{get_exception} to get more detailed error information.
+
+ Since 1.1, if no event is passed, this method will block until the
+ authentication succeeds or fails. On failure, an exception is raised.
+ Otherwise, the method simply returns.
+
+ If the server requires multi-step authentication (which is very rare),
+ this method will return a list of auth types permissible for the next
+ step. Otherwise, in the normal case, an empty list is returned.
+
+ @param username: the username to authenticate as
+ @type username: string
+ @param password: the password to authenticate with
+ @type password: string
+ @param event: an event to trigger when the authentication attempt is
+ complete (whether it was successful or not)
+ @type event: threading.Event
+ @return: list of auth types permissible for the next stage of
+ authentication (normally empty)
+ @rtype: list
+
+ @raise BadAuthenticationType: if password authentication isn't
+ allowed by the server for this user (and no event was passed in)
+ @raise SSHException: if the authentication failed (and no event was
+ passed in)
+ """
+ if (not self.active) or (not self.initial_kex_done):
+ # we should never try to send the password unless we're on a secure link
+ raise SSHException('No existing session')
+ if event is None:
+ my_event = threading.Event()
+ else:
+ my_event = event
+ self.auth_handler = AuthHandler(self)
+ self.auth_handler.auth_password(username, password, my_event)
+ if event is not None:
+ # caller wants to wait for event themselves
+ return []
+ return self.auth_handler.wait_for_response(my_event)
+
+ def auth_publickey(self, username, key, event=None):
+ """
+ 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.
+
+ If an C{event} is passed in, this method will return immediately, and
+ the event will be triggered once authentication succeeds or fails. On
+ success, L{is_authenticated} will return C{True}. On failure, you may
+ use L{get_exception} to get more detailed error information.
+
+ Since 1.1, if no event is passed, this method will block until the
+ authentication succeeds or fails. On failure, an exception is raised.
+ Otherwise, the method simply returns.
+
+ If the server requires multi-step authentication (which is very rare),
+ this method will return a list of auth types permissible for the next
+ step. Otherwise, in the normal case, an empty list is returned.
+
+ @param username: the username to authenticate as.
+ @type username: string
+ @param key: the private key to authenticate with.
+ @type key: L{PKey <pkey.PKey>}
+ @param event: an event to trigger when the authentication attempt is
+ complete (whether it was successful or not)
+ @type event: threading.Event
+ @return: list of auth types permissible for the next stage of
+ authentication (normally empty).
+ @rtype: list
+
+ @raise BadAuthenticationType: if public-key authentication isn't
+ allowed by the server for this user (and no event was passed in).
+ @raise SSHException: if the authentication failed (and no event was
+ passed in).
+ """
+ if (not self.active) or (not self.initial_kex_done):
+ # we should never try to authenticate unless we're on a secure link
+ raise SSHException('No existing session')
+ if event is None:
+ my_event = threading.Event()
+ else:
+ my_event = event
+ self.auth_handler = AuthHandler(self)
+ self.auth_handler.auth_publickey(username, key, my_event)
+ if event is not None:
+ # caller wants to wait for event themselves
+ return []
+ return self.auth_handler.wait_for_response(my_event)
def set_log_channel(self, name):
"""
@@ -1023,6 +1154,8 @@ class BaseTransport (threading.Thread):
self._log(ERROR, 'Channel request for unknown channel %d' % chanid)
self.active = False
self.packetizer.close()
+ elif (self.auth_handler is not None) and self.auth_handler._handler_table.has_key(ptype):
+ self.auth_handler._handler_table[ptype](self.auth_handler, m)
else:
self._log(WARNING, 'Oops, unhandled type %d' % ptype)
msg = Message()
@@ -1056,8 +1189,8 @@ class BaseTransport (threading.Thread):
self.packetizer.close()
if self.completion_event != None:
self.completion_event.set()
- if self.auth_event != None:
- self.auth_event.set()
+ if self.auth_handler is not None:
+ self.auth_handler.abort()
for event in self.channel_events.values():
event.set()
self.sock.close()
@@ -1291,6 +1424,9 @@ class BaseTransport (threading.Thread):
self.local_kex_init = self.remote_kex_init = None
self.K = None
self.kex_engine = None
+ if self.server_mode and (self.auth_handler is None):
+ # create auth handler for server mode
+ self.auth_handler = AuthHandler(self)
if not self.initial_kex_done:
# this was the first key exchange
self.initial_kex_done = True
@@ -1472,5 +1608,3 @@ class BaseTransport (threading.Thread):
MSG_CHANNEL_EOF: Channel._handle_eof,
MSG_CHANNEL_CLOSE: Channel._handle_close,
}
-
-from server import ServerInterface