[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:
parent
1fdec8bd06
commit
0f3bf86617
|
@ -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.)
|
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},
|
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
|
and use L{start_server <Transport.start_server>} or
|
||||||
L{start_client <paramiko.transport.BaseTransport.start_client>} to negoatite
|
L{start_client <Transport.start_client>} to negoatite
|
||||||
with the remote host as either a server or client. As a client, you are
|
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
|
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,
|
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)"
|
__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 ssh_exception, file, packet, agent, server, util
|
||||||
import sftp_client, sftp_attr, sftp_handle, sftp_server, sftp_si
|
import sftp_client, sftp_attr, sftp_handle, sftp_server, sftp_si
|
||||||
|
|
||||||
from transport import randpool, SecurityOptions, BaseTransport
|
from transport import randpool, SecurityOptions, Transport
|
||||||
from auth_transport import Transport
|
from auth_handler import AuthHandler
|
||||||
from channel import Channel, ChannelFile
|
from channel import Channel, ChannelFile
|
||||||
from ssh_exception import SSHException, PasswordRequiredException, BadAuthenticationType
|
from ssh_exception import SSHException, PasswordRequiredException, BadAuthenticationType
|
||||||
from server import ServerInterface, SubsystemHandler
|
from server import ServerInterface, SubsystemHandler
|
||||||
|
@ -91,7 +91,7 @@ from pkey import PKey
|
||||||
# fix module names for epydoc
|
# fix module names for epydoc
|
||||||
for x in [Transport, SecurityOptions, Channel, SFTPServer, SSHException, \
|
for x in [Transport, SecurityOptions, Channel, SFTPServer, SSHException, \
|
||||||
PasswordRequiredException, BadAuthenticationType, ChannelFile, \
|
PasswordRequiredException, BadAuthenticationType, ChannelFile, \
|
||||||
SubsystemHandler, BaseTransport, RSAKey, DSSKey, SFTPError, \
|
SubsystemHandler, AuthHandler, RSAKey, DSSKey, SFTPError, \
|
||||||
SFTP, SFTPClient, SFTPServer, Message, Packetizer, SFTPAttributes, \
|
SFTP, SFTPClient, SFTPServer, Message, Packetizer, SFTPAttributes, \
|
||||||
SFTPHandle, SFTPServerInterface, BufferedFile, Agent, AgentKey, \
|
SFTPHandle, SFTPServerInterface, BufferedFile, Agent, AgentKey, \
|
||||||
PKey, BaseSFTP, SFTPFile, ServerInterface]:
|
PKey, BaseSFTP, SFTPFile, ServerInterface]:
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
|
||||||
})
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
#!/usr/bin/python
|
|
||||||
|
|
||||||
# Copyright (C) 2003-2005 Robey Pointer <robey@lag.net>
|
# Copyright (C) 2003-2005 Robey Pointer <robey@lag.net>
|
||||||
#
|
#
|
||||||
# This file is part of paramiko.
|
# This file is part of paramiko.
|
||||||
|
|
|
@ -25,8 +25,6 @@ L{ServerInterface} is an interface to override for server support.
|
||||||
import threading
|
import threading
|
||||||
from common import *
|
from common import *
|
||||||
import util
|
import util
|
||||||
from transport import BaseTransport
|
|
||||||
from auth_transport import Transport
|
|
||||||
|
|
||||||
class ServerInterface (object):
|
class ServerInterface (object):
|
||||||
"""
|
"""
|
||||||
|
@ -286,7 +284,7 @@ class ServerInterface (object):
|
||||||
subsystem. An example of a subsystem is C{sftp}.
|
subsystem. An example of a subsystem is C{sftp}.
|
||||||
|
|
||||||
The default implementation checks for a subsystem handler assigned via
|
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
|
If one has been set, the handler is invoked and this method returns
|
||||||
C{True}. Otherwise it returns C{False}.
|
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
|
Handler for a subsytem in server mode. If you create a subclass of this
|
||||||
class and pass it to
|
class and pass it to
|
||||||
L{Transport.set_subsystem_handler <BaseTransport.set_subsystem_handler>},
|
L{Transport.set_subsystem_handler},
|
||||||
an object of this
|
an object of this
|
||||||
class will be created for each request for this subsystem. Each new object
|
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}.
|
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
|
@note: It is the responsibility of this method to exit if the
|
||||||
underlying L{Transport} is closed. This can be done by checking
|
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
|
on the L{Channel}. If this method loops forever without checking
|
||||||
for this case, your python interpreter may refuse to exit because
|
for this case, your python interpreter may refuse to exit because
|
||||||
this thread will still be running.
|
this thread will still be running.
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
# 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
|
import sys, os, string, threading, socket, struct, time
|
||||||
|
@ -35,6 +35,7 @@ from dsskey import DSSKey
|
||||||
from kex_group1 import KexGroup1
|
from kex_group1 import KexGroup1
|
||||||
from kex_gex import KexGex
|
from kex_gex import KexGex
|
||||||
from primes import ModulusPack
|
from primes import ModulusPack
|
||||||
|
from auth_handler import AuthHandler
|
||||||
|
|
||||||
# these come from PyCrypt
|
# these come from PyCrypt
|
||||||
# http://www.amk.ca/python/writing/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")
|
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
|
An SSH Transport attaches to a stream (usually a socket), negotiates an
|
||||||
of channels across an SSH session. Basically everything but authentication
|
encrypted session, authenticates, and then creates stream tunnels, called
|
||||||
is done here.
|
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'
|
_PROTO_ID = '2.0'
|
||||||
_CLIENT_ID = 'paramiko_1.4'
|
_CLIENT_ID = 'paramiko_1.4'
|
||||||
|
|
||||||
|
@ -240,6 +243,7 @@ class BaseTransport (threading.Thread):
|
||||||
self.log_name = 'paramiko.transport'
|
self.log_name = 'paramiko.transport'
|
||||||
self.logger = util.get_logger(self.log_name)
|
self.logger = util.get_logger(self.log_name)
|
||||||
self.packetizer.set_log(self.logger)
|
self.packetizer.set_log(self.logger)
|
||||||
|
self.auth_handler = None
|
||||||
# user-defined event callbacks:
|
# user-defined event callbacks:
|
||||||
self.completion_event = None
|
self.completion_event = None
|
||||||
# server mode:
|
# server mode:
|
||||||
|
@ -259,17 +263,22 @@ class BaseTransport (threading.Thread):
|
||||||
|
|
||||||
@rtype: str
|
@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:
|
if not self.active:
|
||||||
out += ' (unconnected)'
|
out += ' (unconnected)'
|
||||||
else:
|
else:
|
||||||
if self.local_cipher != '':
|
if self.local_cipher != '':
|
||||||
out += ' (cipher %s, %d bits)' % (self.local_cipher,
|
out += ' (cipher %s, %d bits)' % (self.local_cipher,
|
||||||
self._cipher_info[self.local_cipher]['key-size'] * 8)
|
self._cipher_info[self.local_cipher]['key-size'] * 8)
|
||||||
if len(self.channels) == 1:
|
if self.is_authenticated():
|
||||||
out += ' (active; 1 open channel)'
|
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:
|
else:
|
||||||
out += ' (active; %d open channels)' % len(self.channels)
|
out += ' (connecting)'
|
||||||
out += '>'
|
out += '>'
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
@ -464,19 +473,19 @@ class BaseTransport (threading.Thread):
|
||||||
|
|
||||||
@note: This has no effect when used in client mode.
|
@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
|
# places to look for the openssh "moduli" file
|
||||||
file_list = [ '/etc/ssh/moduli', '/usr/local/etc/moduli' ]
|
file_list = [ '/etc/ssh/moduli', '/usr/local/etc/moduli' ]
|
||||||
if filename is not None:
|
if filename is not None:
|
||||||
file_list.insert(0, filename)
|
file_list.insert(0, filename)
|
||||||
for fn in file_list:
|
for fn in file_list:
|
||||||
try:
|
try:
|
||||||
BaseTransport._modulus_pack.read_file(fn)
|
Transport._modulus_pack.read_file(fn)
|
||||||
return True
|
return True
|
||||||
except IOError:
|
except IOError:
|
||||||
pass
|
pass
|
||||||
# none succeeded
|
# none succeeded
|
||||||
BaseTransport._modulus_pack = None
|
Transport._modulus_pack = None
|
||||||
return False
|
return False
|
||||||
load_server_moduli = staticmethod(load_server_moduli)
|
load_server_moduli = staticmethod(load_server_moduli)
|
||||||
|
|
||||||
|
@ -834,6 +843,128 @@ class BaseTransport (threading.Thread):
|
||||||
finally:
|
finally:
|
||||||
self.lock.release()
|
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):
|
def set_log_channel(self, name):
|
||||||
"""
|
"""
|
||||||
Set the channel for this transport's logging. The default is
|
Set the channel for this transport's logging. The default is
|
||||||
|
@ -1023,6 +1154,8 @@ class BaseTransport (threading.Thread):
|
||||||
self._log(ERROR, 'Channel request for unknown channel %d' % chanid)
|
self._log(ERROR, 'Channel request for unknown channel %d' % chanid)
|
||||||
self.active = False
|
self.active = False
|
||||||
self.packetizer.close()
|
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:
|
else:
|
||||||
self._log(WARNING, 'Oops, unhandled type %d' % ptype)
|
self._log(WARNING, 'Oops, unhandled type %d' % ptype)
|
||||||
msg = Message()
|
msg = Message()
|
||||||
|
@ -1056,8 +1189,8 @@ class BaseTransport (threading.Thread):
|
||||||
self.packetizer.close()
|
self.packetizer.close()
|
||||||
if self.completion_event != None:
|
if self.completion_event != None:
|
||||||
self.completion_event.set()
|
self.completion_event.set()
|
||||||
if self.auth_event != None:
|
if self.auth_handler is not None:
|
||||||
self.auth_event.set()
|
self.auth_handler.abort()
|
||||||
for event in self.channel_events.values():
|
for event in self.channel_events.values():
|
||||||
event.set()
|
event.set()
|
||||||
self.sock.close()
|
self.sock.close()
|
||||||
|
@ -1291,6 +1424,9 @@ class BaseTransport (threading.Thread):
|
||||||
self.local_kex_init = self.remote_kex_init = None
|
self.local_kex_init = self.remote_kex_init = None
|
||||||
self.K = None
|
self.K = None
|
||||||
self.kex_engine = 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:
|
if not self.initial_kex_done:
|
||||||
# this was the first key exchange
|
# this was the first key exchange
|
||||||
self.initial_kex_done = True
|
self.initial_kex_done = True
|
||||||
|
@ -1472,5 +1608,3 @@ class BaseTransport (threading.Thread):
|
||||||
MSG_CHANNEL_EOF: Channel._handle_eof,
|
MSG_CHANNEL_EOF: Channel._handle_eof,
|
||||||
MSG_CHANNEL_CLOSE: Channel._handle_close,
|
MSG_CHANNEL_CLOSE: Channel._handle_close,
|
||||||
}
|
}
|
||||||
|
|
||||||
from server import ServerInterface
|
|
||||||
|
|
Loading…
Reference in New Issue