[project @ Arch-1:robey@lag.net--2005-master-shake%paramiko--dev--1--patch-59]
add auth_none and auth_interactive to support 'no auth' and 'keyboard-interactive auth'; for password auth, add a mode where it will try to fallback to keyboard-interactive in a pinch
This commit is contained in:
parent
24045332c5
commit
364479610e
|
@ -57,6 +57,16 @@ class AuthHandler (object):
|
||||||
else:
|
else:
|
||||||
return self.username
|
return self.username
|
||||||
|
|
||||||
|
def auth_none(self, username, event):
|
||||||
|
self.transport.lock.acquire()
|
||||||
|
try:
|
||||||
|
self.auth_event = event
|
||||||
|
self.auth_method = 'none'
|
||||||
|
self.username = username
|
||||||
|
self._request_auth()
|
||||||
|
finally:
|
||||||
|
self.transport.lock.release()
|
||||||
|
|
||||||
def auth_publickey(self, username, key, event):
|
def auth_publickey(self, username, key, event):
|
||||||
self.transport.lock.acquire()
|
self.transport.lock.acquire()
|
||||||
try:
|
try:
|
||||||
|
@ -79,6 +89,21 @@ class AuthHandler (object):
|
||||||
finally:
|
finally:
|
||||||
self.transport.lock.release()
|
self.transport.lock.release()
|
||||||
|
|
||||||
|
def auth_interactive(self, username, handler, event, submethods=''):
|
||||||
|
"""
|
||||||
|
response_list = handler(title, instructions, prompt_list)
|
||||||
|
"""
|
||||||
|
self.transport.lock.acquire()
|
||||||
|
try:
|
||||||
|
self.auth_event = event
|
||||||
|
self.auth_method = 'keyboard-interactive'
|
||||||
|
self.username = username
|
||||||
|
self.interactive_handler = handler
|
||||||
|
self.submethods = submethods
|
||||||
|
self._request_auth()
|
||||||
|
finally:
|
||||||
|
self.transport.lock.release()
|
||||||
|
|
||||||
def abort(self):
|
def abort(self):
|
||||||
if self.auth_event is not None:
|
if self.auth_event is not None:
|
||||||
self.auth_event.set()
|
self.auth_event.set()
|
||||||
|
@ -175,6 +200,11 @@ class AuthHandler (object):
|
||||||
blob = self._get_session_blob(self.private_key, 'ssh-connection', self.username)
|
blob = self._get_session_blob(self.private_key, 'ssh-connection', self.username)
|
||||||
sig = self.private_key.sign_ssh_data(self.transport.randpool, blob)
|
sig = self.private_key.sign_ssh_data(self.transport.randpool, blob)
|
||||||
m.add_string(str(sig))
|
m.add_string(str(sig))
|
||||||
|
elif self.auth_method == 'keyboard-interactive':
|
||||||
|
m.add_string('')
|
||||||
|
m.add_string(self.submethods)
|
||||||
|
elif self.auth_method == 'none':
|
||||||
|
pass
|
||||||
else:
|
else:
|
||||||
raise SSHException('Unknown auth method "%s"' % self.auth_method)
|
raise SSHException('Unknown auth method "%s"' % self.auth_method)
|
||||||
self.transport._send_message(m)
|
self.transport._send_message(m)
|
||||||
|
@ -302,6 +332,25 @@ class AuthHandler (object):
|
||||||
lang = m.get_string()
|
lang = m.get_string()
|
||||||
self.transport._log(INFO, 'Auth banner: ' + banner)
|
self.transport._log(INFO, 'Auth banner: ' + banner)
|
||||||
# who cares.
|
# who cares.
|
||||||
|
|
||||||
|
def _parse_userauth_info_request(self, m):
|
||||||
|
if self.auth_method != 'keyboard-interactive':
|
||||||
|
raise SSHException('Illegal info request from server')
|
||||||
|
title = m.get_string()
|
||||||
|
instructions = m.get_string()
|
||||||
|
m.get_string() # lang
|
||||||
|
prompts = m.get_int()
|
||||||
|
prompt_list = []
|
||||||
|
for i in range(prompts):
|
||||||
|
prompt_list.append((m.get_string(), m.get_boolean()))
|
||||||
|
response_list = self.interactive_handler(title, instructions, prompt_list)
|
||||||
|
|
||||||
|
m = Message()
|
||||||
|
m.add_byte(chr(MSG_USERAUTH_INFO_RESPONSE))
|
||||||
|
m.add_int(len(response_list))
|
||||||
|
for r in response_list:
|
||||||
|
m.add_string(r)
|
||||||
|
self.transport._send_message(m)
|
||||||
|
|
||||||
_handler_table = {
|
_handler_table = {
|
||||||
MSG_SERVICE_REQUEST: _parse_service_request,
|
MSG_SERVICE_REQUEST: _parse_service_request,
|
||||||
|
@ -310,6 +359,7 @@ class AuthHandler (object):
|
||||||
MSG_USERAUTH_SUCCESS: _parse_userauth_success,
|
MSG_USERAUTH_SUCCESS: _parse_userauth_success,
|
||||||
MSG_USERAUTH_FAILURE: _parse_userauth_failure,
|
MSG_USERAUTH_FAILURE: _parse_userauth_failure,
|
||||||
MSG_USERAUTH_BANNER: _parse_userauth_banner,
|
MSG_USERAUTH_BANNER: _parse_userauth_banner,
|
||||||
|
MSG_USERAUTH_INFO_REQUEST: _parse_userauth_info_request,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ MSG_KEXINIT, MSG_NEWKEYS = range(20, 22)
|
||||||
MSG_USERAUTH_REQUEST, MSG_USERAUTH_FAILURE, MSG_USERAUTH_SUCCESS, \
|
MSG_USERAUTH_REQUEST, MSG_USERAUTH_FAILURE, MSG_USERAUTH_SUCCESS, \
|
||||||
MSG_USERAUTH_BANNER = range(50, 54)
|
MSG_USERAUTH_BANNER = range(50, 54)
|
||||||
MSG_USERAUTH_PK_OK = 60
|
MSG_USERAUTH_PK_OK = 60
|
||||||
|
MSG_USERAUTH_INFO_REQUEST, MSG_USERAUTH_INFO_RESPONSE = range(60, 62)
|
||||||
MSG_GLOBAL_REQUEST, MSG_REQUEST_SUCCESS, MSG_REQUEST_FAILURE = range(80, 83)
|
MSG_GLOBAL_REQUEST, MSG_REQUEST_SUCCESS, MSG_REQUEST_FAILURE = range(80, 83)
|
||||||
MSG_CHANNEL_OPEN, MSG_CHANNEL_OPEN_SUCCESS, MSG_CHANNEL_OPEN_FAILURE, \
|
MSG_CHANNEL_OPEN, MSG_CHANNEL_OPEN_SUCCESS, MSG_CHANNEL_OPEN_FAILURE, \
|
||||||
MSG_CHANNEL_WINDOW_ADJUST, MSG_CHANNEL_DATA, MSG_CHANNEL_EXTENDED_DATA, \
|
MSG_CHANNEL_WINDOW_ADJUST, MSG_CHANNEL_DATA, MSG_CHANNEL_EXTENDED_DATA, \
|
||||||
|
@ -51,7 +52,8 @@ MSG_NAMES = {
|
||||||
MSG_USERAUTH_FAILURE: 'userauth-failure',
|
MSG_USERAUTH_FAILURE: 'userauth-failure',
|
||||||
MSG_USERAUTH_SUCCESS: 'userauth-success',
|
MSG_USERAUTH_SUCCESS: 'userauth-success',
|
||||||
MSG_USERAUTH_BANNER: 'userauth--banner',
|
MSG_USERAUTH_BANNER: 'userauth--banner',
|
||||||
MSG_USERAUTH_PK_OK: 'userauth-pk-ok',
|
MSG_USERAUTH_PK_OK: 'userauth-60(pk-ok/info-request)',
|
||||||
|
MSG_USERAUTH_INFO_RESPONSE: 'userauth-info-response',
|
||||||
MSG_GLOBAL_REQUEST: 'global-request',
|
MSG_GLOBAL_REQUEST: 'global-request',
|
||||||
MSG_REQUEST_SUCCESS: 'request-success',
|
MSG_REQUEST_SUCCESS: 'request-success',
|
||||||
MSG_REQUEST_FAILURE: 'request-failure',
|
MSG_REQUEST_FAILURE: 'request-failure',
|
||||||
|
|
|
@ -24,7 +24,7 @@ import sys, os, string, threading, socket, struct, time
|
||||||
import weakref
|
import weakref
|
||||||
|
|
||||||
from common import *
|
from common import *
|
||||||
from ssh_exception import SSHException
|
from ssh_exception import SSHException, BadAuthenticationType
|
||||||
from message import Message
|
from message import Message
|
||||||
from channel import Channel
|
from channel import Channel
|
||||||
from sftp_client import SFTPClient
|
from sftp_client import SFTPClient
|
||||||
|
@ -869,7 +869,34 @@ class Transport (threading.Thread):
|
||||||
return None
|
return None
|
||||||
return self.auth_handler.get_username()
|
return self.auth_handler.get_username()
|
||||||
|
|
||||||
def auth_password(self, username, password, event=None):
|
def auth_none(self, username):
|
||||||
|
"""
|
||||||
|
Try to authenticate to the server using no authentication at all.
|
||||||
|
This will almost always fail. It may be useful for determining the
|
||||||
|
list of authentication types supported by the server, by catching the
|
||||||
|
L{BadAuthenticationType} exception raised.
|
||||||
|
|
||||||
|
@param username: the username to authenticate as
|
||||||
|
@type username: string
|
||||||
|
@return: list of auth types permissible for the next stage of
|
||||||
|
authentication (normally empty)
|
||||||
|
@rtype: list
|
||||||
|
|
||||||
|
@raise BadAuthenticationType: if "none" authentication isn't allowed
|
||||||
|
by the server for this user
|
||||||
|
@raise SSHException: if the authentication failed due to a network
|
||||||
|
error
|
||||||
|
|
||||||
|
@since: 1.5
|
||||||
|
"""
|
||||||
|
if (not self.active) or (not self.initial_kex_done):
|
||||||
|
raise SSHException('No existing session')
|
||||||
|
my_event = threading.Event()
|
||||||
|
self.auth_handler = AuthHandler(self)
|
||||||
|
self.auth_handler.auth_none(username, my_event)
|
||||||
|
return self.auth_handler.wait_for_response(my_event)
|
||||||
|
|
||||||
|
def auth_password(self, username, password, event=None, fallback=True):
|
||||||
"""
|
"""
|
||||||
Authenticate to the server using a password. The username and password
|
Authenticate to the server using a password. The username and password
|
||||||
are sent over an encrypted link.
|
are sent over an encrypted link.
|
||||||
|
@ -883,6 +910,15 @@ class Transport (threading.Thread):
|
||||||
authentication succeeds or fails. On failure, an exception is raised.
|
authentication succeeds or fails. On failure, an exception is raised.
|
||||||
Otherwise, the method simply returns.
|
Otherwise, the method simply returns.
|
||||||
|
|
||||||
|
Since 1.5, if no event is passed and C{fallback} is C{True} (the
|
||||||
|
default), if the server doesn't support plain password authentication
|
||||||
|
but does support so-called "keyboard-interactive" mode, an attempt
|
||||||
|
will be made to authenticate using this interactive mode. If it fails,
|
||||||
|
the normal exception will be thrown as if the attempt had never been
|
||||||
|
made. This is useful for some recent Gentoo and Debian distributions,
|
||||||
|
which turn off plain password authentication in a misguided belief
|
||||||
|
that interactive authentication is "more secure". (It's not.)
|
||||||
|
|
||||||
If the server requires multi-step authentication (which is very rare),
|
If the server requires multi-step authentication (which is very rare),
|
||||||
this method will return a list of auth types permissible for the next
|
this method will return a list of auth types permissible for the next
|
||||||
step. Otherwise, in the normal case, an empty list is returned.
|
step. Otherwise, in the normal case, an empty list is returned.
|
||||||
|
@ -894,6 +930,10 @@ class Transport (threading.Thread):
|
||||||
@param event: an event to trigger when the authentication attempt is
|
@param event: an event to trigger when the authentication attempt is
|
||||||
complete (whether it was successful or not)
|
complete (whether it was successful or not)
|
||||||
@type event: threading.Event
|
@type event: threading.Event
|
||||||
|
@param fallback: C{True} if an attempt at an automated "interactive"
|
||||||
|
password auth should be made if the server doesn't support normal
|
||||||
|
password auth
|
||||||
|
@type fallback: bool
|
||||||
@return: list of auth types permissible for the next stage of
|
@return: list of auth types permissible for the next stage of
|
||||||
authentication (normally empty)
|
authentication (normally empty)
|
||||||
@rtype: list
|
@rtype: list
|
||||||
|
@ -915,7 +955,28 @@ class Transport (threading.Thread):
|
||||||
if event is not None:
|
if event is not None:
|
||||||
# caller wants to wait for event themselves
|
# caller wants to wait for event themselves
|
||||||
return []
|
return []
|
||||||
return self.auth_handler.wait_for_response(my_event)
|
try:
|
||||||
|
return self.auth_handler.wait_for_response(my_event)
|
||||||
|
except BadAuthenticationType, x:
|
||||||
|
# if password auth isn't allowed, but keyboard-interactive *is*, try to fudge it
|
||||||
|
if not fallback or not 'keyboard-interactive' in x.allowed_types:
|
||||||
|
raise
|
||||||
|
try:
|
||||||
|
def handler(title, instructions, fields):
|
||||||
|
self._log(DEBUG, 'title=%r, instructions=%r, fields=%r' % (title, instructions, fields))
|
||||||
|
if len(fields) > 1:
|
||||||
|
raise SSHException('Fallback authentication failed.')
|
||||||
|
if len(fields) == 0:
|
||||||
|
# for some reason, at least on os x, a 2nd request will
|
||||||
|
# be made with zero fields requested. maybe it's just
|
||||||
|
# to try to fake out automated scripting of the exact
|
||||||
|
# type we're doing here. *shrug* :)
|
||||||
|
return []
|
||||||
|
return [ password ]
|
||||||
|
return self.auth_interactive(username, handler)
|
||||||
|
except SSHException, ignored:
|
||||||
|
# attempt failed; just raise the original exception
|
||||||
|
raise x
|
||||||
|
|
||||||
def auth_publickey(self, username, key, event=None):
|
def auth_publickey(self, username, key, event=None):
|
||||||
"""
|
"""
|
||||||
|
@ -935,9 +996,9 @@ class Transport (threading.Thread):
|
||||||
this method will return a list of auth types permissible for the next
|
this method will return a list of auth types permissible for the next
|
||||||
step. Otherwise, in the normal case, an empty list is returned.
|
step. Otherwise, in the normal case, an empty list is returned.
|
||||||
|
|
||||||
@param username: the username to authenticate as.
|
@param username: the username to authenticate as
|
||||||
@type username: string
|
@type username: string
|
||||||
@param key: the private key to authenticate with.
|
@param key: the private key to authenticate with
|
||||||
@type key: L{PKey <pkey.PKey>}
|
@type key: L{PKey <pkey.PKey>}
|
||||||
@param event: an event to trigger when the authentication attempt is
|
@param event: an event to trigger when the authentication attempt is
|
||||||
complete (whether it was successful or not)
|
complete (whether it was successful or not)
|
||||||
|
@ -964,6 +1025,59 @@ class Transport (threading.Thread):
|
||||||
# caller wants to wait for event themselves
|
# caller wants to wait for event themselves
|
||||||
return []
|
return []
|
||||||
return self.auth_handler.wait_for_response(my_event)
|
return self.auth_handler.wait_for_response(my_event)
|
||||||
|
|
||||||
|
def auth_interactive(self, username, handler, submethods=''):
|
||||||
|
"""
|
||||||
|
Authenticate to the server interactively. A handler is used to answer
|
||||||
|
arbitrary questions from the server. On many servers, this is just a
|
||||||
|
dumb wrapper around PAM.
|
||||||
|
|
||||||
|
This method will block until the authentication succeeds or fails,
|
||||||
|
peroidically calling the handler asynchronously to get answers to
|
||||||
|
authentication questions. The handler may be called more than once
|
||||||
|
if the server continues to ask questions.
|
||||||
|
|
||||||
|
The handler is expected to be a callable that will handle calls of the
|
||||||
|
form: C{handler(title, instructions, prompt_list)}. The C{title} is
|
||||||
|
meant to be a dialog-window title, and the C{instructions} are user
|
||||||
|
instructions (both are strings). C{prompt_list} will be a list of
|
||||||
|
prompts, each prompt being a tuple of C{(str, bool)}. The string is
|
||||||
|
the prompt and the boolean indicates whether the user text should be
|
||||||
|
echoed.
|
||||||
|
|
||||||
|
A sample call would thus be:
|
||||||
|
C{handler('title', 'instructions', [('Password:', False)])}.
|
||||||
|
|
||||||
|
The handler should return a list or tuple of answers to the server's
|
||||||
|
questions.
|
||||||
|
|
||||||
|
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 handler: a handler for responding to server questions
|
||||||
|
@type handler: callable
|
||||||
|
@param submethods: a string list of desired submethods (optional)
|
||||||
|
@type submethods: str
|
||||||
|
@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
|
||||||
|
@raise SSHException: if the authentication failed
|
||||||
|
|
||||||
|
@since: 1.5
|
||||||
|
"""
|
||||||
|
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')
|
||||||
|
my_event = threading.Event()
|
||||||
|
self.auth_handler = AuthHandler(self)
|
||||||
|
self.auth_handler.auth_interactive(username, handler, my_event, submethods)
|
||||||
|
return self.auth_handler.wait_for_response(my_event)
|
||||||
|
|
||||||
def set_log_channel(self, name):
|
def set_log_channel(self, name):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue