[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
This commit is contained in:
Robey Pointer 2005-08-09 07:40:07 +00:00
parent 1fdec8bd06
commit 0f3bf86617
6 changed files with 474 additions and 470 deletions

View File

@ -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]:

315
paramiko/auth_handler.py Normal file
View File

@ -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,
}

View File

@ -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,
})

View File

@ -1,5 +1,3 @@
#!/usr/bin/python
# Copyright (C) 2003-2005 Robey Pointer <robey@lag.net>
#
# This file is part of paramiko.

View File

@ -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.

View File

@ -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