Package paramiko :: Module auth_handler |
[frames] | no frames]

# Source Code for Module paramiko.auth_handler

      1  # Copyright (C) 2003-2007  Robey Pointer <robeypointer@gmail.com>
      2  #
      3  # This file is part of paramiko.
      4  #
      5  # Paramiko is free software; you can redistribute it and/or modify it under the
      6  # terms of the GNU Lesser General Public License as published by the Free
      7  # Software Foundation; either version 2.1 of the License, or (at your option)
      8  # any later version.
      9  #
     10  # Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
     11  # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     12  # A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
     13  # details.
     14  #
     15  # You should have received a copy of the GNU Lesser General Public License
     16  # along with Paramiko; if not, write to the Free Software Foundation, Inc.,
     17  # 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
     18
     19  """
     20  L{AuthHandler}
     21  """
     22
     23  import threading
     24  import weakref
     25
     26  # this helps freezing utils
     27  import encodings.utf_8
     28
     29  from paramiko.common import *
     30  from paramiko import util
     31  from paramiko.message import Message
     32  from paramiko.ssh_exception import SSHException, AuthenticationException, \
     33      BadAuthenticationType, PartialAuthentication
     34  from paramiko.server import InteractiveQuery
     35
     36
     37 -class AuthHandler (object):
     38      """
     39      Internal class to handle the mechanics of authentication.
     40      """
     41
     42 -    def __init__(self, transport):
     43          self.transport = weakref.proxy(transport)
     44          self.username = None
     45          self.authenticated = False
     46          self.auth_event = None
     47          self.auth_method = ''
     48          self.password = None
     49          self.private_key = None
     50          self.interactive_handler = None
     51          self.submethods = None
     52          # for server mode:
     53          self.auth_username = None
     54          self.auth_fail_count = 0
     55
     56 -    def is_authenticated(self):
     57          return self.authenticated
     58
     59 -    def get_username(self):
     60          if self.transport.server_mode:
     61              return self.auth_username
     62          else:
     63              return self.username
     64
     65 -    def auth_none(self, username, event):
     66          self.transport.lock.acquire()
     67          try:
     68              self.auth_event = event
     69              self.auth_method = 'none'
     70              self.username = username
     71              self._request_auth()
     72          finally:
     73              self.transport.lock.release()
     74
     75 -    def auth_publickey(self, username, key, event):
     76          self.transport.lock.acquire()
     77          try:
     78              self.auth_event = event
     79              self.auth_method = 'publickey'
     80              self.username = username
     81              self.private_key = key
     82              self._request_auth()
     83          finally:
     84              self.transport.lock.release()
     85
     86 -    def auth_password(self, username, password, event):
     87          self.transport.lock.acquire()
     88          try:
     89              self.auth_event = event
     90              self.auth_method = 'password'
     91              self.username = username
     92              self.password = password
     93              self._request_auth()
     94          finally:
     95              self.transport.lock.release()
     96
     97 -    def auth_interactive(self, username, handler, event, submethods=''):
     98          """
     99          response_list = handler(title, instructions, prompt_list)
    100          """
    101          self.transport.lock.acquire()
    102          try:
    103              self.auth_event = event
    104              self.auth_method = 'keyboard-interactive'
    105              self.username = username
    106              self.interactive_handler = handler
    107              self.submethods = submethods
    108              self._request_auth()
    109          finally:
    110              self.transport.lock.release()
    111
    112 -    def abort(self):
    113          if self.auth_event is not None:
    114              self.auth_event.set()
    115
    116
    117      ###  internals...
    118
    119
    120 -    def _request_auth(self):
    121          m = Message()
    122          m.add_byte(chr(MSG_SERVICE_REQUEST))
    123          m.add_string('ssh-userauth')
    124          self.transport._send_message(m)
    125
    126 -    def _disconnect_service_not_available(self):
    127          m = Message()
    128          m.add_byte(chr(MSG_DISCONNECT))
    129          m.add_int(DISCONNECT_SERVICE_NOT_AVAILABLE)
    130          m.add_string('Service not available')
    131          m.add_string('en')
    132          self.transport._send_message(m)
    133          self.transport.close()
    134
    135 -    def _disconnect_no_more_auth(self):
    136          m = Message()
    137          m.add_byte(chr(MSG_DISCONNECT))
    138          m.add_int(DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE)
    139          m.add_string('No more auth methods available')
    140          m.add_string('en')
    141          self.transport._send_message(m)
    142          self.transport.close()
    143
    144 -    def _get_session_blob(self, key, service, username):
    145          m = Message()
    146          m.add_string(self.transport.session_id)
    147          m.add_byte(chr(MSG_USERAUTH_REQUEST))
    148          m.add_string(username)
    149          m.add_string(service)
    150          m.add_string('publickey')
    151          m.add_boolean(1)
    152          m.add_string(key.get_name())
    153          m.add_string(str(key))
    154          return str(m)
    155
    156 -    def wait_for_response(self, event):
    157          while True:
    158              event.wait(0.1)
    159              if not self.transport.is_active():
    160                  e = self.transport.get_exception()
    161                  if (e is None) or issubclass(e.__class__, EOFError):
    162                      e = AuthenticationException('Authentication failed.')
    163                  raise e
    164              if event.isSet():
    165                  break
    166          if not self.is_authenticated():
    167              e = self.transport.get_exception()
    168              if e is None:
    169                  e = AuthenticationException('Authentication failed.')
    170              # this is horrible.  python Exception isn't yet descended from
    171              # object, so type(e) won't work. :(
    172              if issubclass(e.__class__, PartialAuthentication):
    173                  return e.allowed_types
    174              raise e
    175          return []
    176
    177 -    def _parse_service_request(self, m):
    178          service = m.get_string()
    179          if self.transport.server_mode and (service == 'ssh-userauth'):
    180              # accepted
    181              m = Message()
    182              m.add_byte(chr(MSG_SERVICE_ACCEPT))
    183              m.add_string(service)
    184              self.transport._send_message(m)
    185              return
    186          # dunno this one
    187          self._disconnect_service_not_available()
    188
    189 -    def _parse_service_accept(self, m):
    190          service = m.get_string()
    191          if service == 'ssh-userauth':
    192              self.transport._log(DEBUG, 'userauth is OK')
    193              m = Message()
    194              m.add_byte(chr(MSG_USERAUTH_REQUEST))
    195              m.add_string(self.username)
    196              m.add_string('ssh-connection')
    197              m.add_string(self.auth_method)
    198              if self.auth_method == 'password':
    199                  m.add_boolean(False)
    200                  password = self.password
    201                  if isinstance(password, unicode):
    202                      password = password.encode('UTF-8')
    203                  m.add_string(password)
    204              elif self.auth_method == 'publickey':
    205                  m.add_boolean(True)
    206                  m.add_string(self.private_key.get_name())
    207                  m.add_string(str(self.private_key))
    208                  blob = self._get_session_blob(self.private_key, 'ssh-connection', self.username)
    209                  sig = self.private_key.sign_ssh_data(self.transport.rng, blob)
    210                  m.add_string(str(sig))
    211              elif self.auth_method == 'keyboard-interactive':
    212                  m.add_string('')
    213                  m.add_string(self.submethods)
    214              elif self.auth_method == 'none':
    215                  pass
    216              else:
    217                  raise SSHException('Unknown auth method "%s"' % self.auth_method)
    218              self.transport._send_message(m)
    219          else:
    220              self.transport._log(DEBUG, 'Service request "%s" accepted (?)' % service)
    221
    222 -    def _send_auth_result(self, username, method, result):
    223          # okay, send result
    224          m = Message()
    225          if result == AUTH_SUCCESSFUL:
    226              self.transport._log(INFO, 'Auth granted (%s).' % method)
    227              m.add_byte(chr(MSG_USERAUTH_SUCCESS))
    228              self.authenticated = True
    229          else:
    230              self.transport._log(INFO, 'Auth rejected (%s).' % method)
    231              m.add_byte(chr(MSG_USERAUTH_FAILURE))
    232              m.add_string(self.transport.server_object.get_allowed_auths(username))
    233              if result == AUTH_PARTIALLY_SUCCESSFUL:
    234                  m.add_boolean(1)
    235              else:
    236                  m.add_boolean(0)
    237                  self.auth_fail_count += 1
    238          self.transport._send_message(m)
    239          if self.auth_fail_count >= 10:
    240              self._disconnect_no_more_auth()
    241          if result == AUTH_SUCCESSFUL:
    242              self.transport._auth_trigger()
    243
    244 -    def _interactive_query(self, q):
    245          # make interactive query instead of response
    246          m = Message()
    247          m.add_byte(chr(MSG_USERAUTH_INFO_REQUEST))
    248          m.add_string(q.name)
    249          m.add_string(q.instructions)
    250          m.add_string('')
    251          m.add_int(len(q.prompts))
    252          for p in q.prompts:
    253              m.add_string(p[0])
    254              m.add_boolean(p[1])
    255          self.transport._send_message(m)
    256
    257 -    def _parse_userauth_request(self, m):
    258          if not self.transport.server_mode:
    259              # er, uh... what?
    260              m = Message()
    261              m.add_byte(chr(MSG_USERAUTH_FAILURE))
    262              m.add_string('none')
    263              m.add_boolean(0)
    264              self.transport._send_message(m)
    265              return
    266          if self.authenticated:
    267              # ignore
    268              return
    269          username = m.get_string()
    270          service = m.get_string()
    271          method = m.get_string()
    272          self.transport._log(DEBUG, 'Auth request (type=%s) service=%s, username=%s' % (method, service, username))
    273          if service != 'ssh-connection':
    274              self._disconnect_service_not_available()
    275              return
    276          if (self.auth_username is not None) and (self.auth_username != username):
    277              self.transport._log(WARNING, 'Auth rejected because the client attempted to change username in mid-flight')
    278              self._disconnect_no_more_auth()
    279              return
    280          self.auth_username = username
    281
    282          if method == 'none':
    283              result = self.transport.server_object.check_auth_none(username)
    284          elif method == 'password':
    285              changereq = m.get_boolean()
    286              password = m.get_string()
    287              try:
    288                  password = password.decode('UTF-8')
    289              except UnicodeError:
    290                  # some clients/servers expect non-utf-8 passwords!
    291                  # in this case, just return the raw byte string.
    292                  pass
    293              if changereq:
    294                  # always treated as failure, since we don't support changing passwords, but collect
    295                  # the list of valid auth types from the callback anyway
    296                  self.transport._log(DEBUG, 'Auth request to change passwords (rejected)')
    297                  newpassword = m.get_string()
    298                  try:
    299                      newpassword = newpassword.decode('UTF-8', 'replace')
    300                  except UnicodeError:
    301                      pass
    302                  result = AUTH_FAILED
    303              else:
    304                  result = self.transport.server_object.check_auth_password(username, password)
    305          elif method == 'publickey':
    306              sig_attached = m.get_boolean()
    307              keytype = m.get_string()
    308              keyblob = m.get_string()
    309              try:
    310                  key = self.transport._key_info[keytype](Message(keyblob))
    311              except SSHException, e:
    312                  self.transport._log(INFO, 'Auth rejected: public key: %s' % str(e))
    313                  key = None
    314              except:
    315                  self.transport._log(INFO, 'Auth rejected: unsupported or mangled public key')
    316                  key = None
    317              if key is None:
    318                  self._disconnect_no_more_auth()
    319                  return
    320              # first check if this key is okay... if not, we can skip the verify
    321              result = self.transport.server_object.check_auth_publickey(username, key)
    322              if result != AUTH_FAILED:
    323                  # key is okay, verify it
    324                  if not sig_attached:
    325                      # client wants to know if this key is acceptable, before it
    326                      # signs anything...  send special "ok" message
    327                      m = Message()
    328                      m.add_byte(chr(MSG_USERAUTH_PK_OK))
    329                      m.add_string(keytype)
    330                      m.add_string(keyblob)
    331                      self.transport._send_message(m)
    332                      return
    333                  sig = Message(m.get_string())
    334                  blob = self._get_session_blob(key, service, username)
    335                  if not key.verify_ssh_sig(blob, sig):
    336                      self.transport._log(INFO, 'Auth rejected: invalid signature')
    337                      result = AUTH_FAILED
    338          elif method == 'keyboard-interactive':
    339              lang = m.get_string()
    340              submethods = m.get_string()
    341              result = self.transport.server_object.check_auth_interactive(username, submethods)
    342              if isinstance(result, InteractiveQuery):
    343                  # make interactive query instead of response
    344                  self._interactive_query(result)
    345                  return
    346          else:
    347              result = self.transport.server_object.check_auth_none(username)
    348          # okay, send result
    349          self._send_auth_result(username, method, result)
    350
    351 -    def _parse_userauth_success(self, m):
    352          self.transport._log(INFO, 'Authentication (%s) successful!' % self.auth_method)
    353          self.authenticated = True
    354          self.transport._auth_trigger()
    355          if self.auth_event != None:
    356              self.auth_event.set()
    357
    358 -    def _parse_userauth_failure(self, m):
    359          authlist = m.get_list()
    360          partial = m.get_boolean()
    361          if partial:
    362              self.transport._log(INFO, 'Authentication continues...')
    363              self.transport._log(DEBUG, 'Methods: ' + str(authlist))
    364              self.transport.saved_exception = PartialAuthentication(authlist)
    365          elif self.auth_method not in authlist:
    366              self.transport._log(DEBUG, 'Authentication type (%s) not permitted.' % self.auth_method)
    367              self.transport._log(DEBUG, 'Allowed methods: ' + str(authlist))
    368              self.transport.saved_exception = BadAuthenticationType('Bad authentication type', authlist)
    369          else:
    370              self.transport._log(INFO, 'Authentication (%s) failed.' % self.auth_method)
    371          self.authenticated = False
    372          self.username = None
    373          if self.auth_event != None:
    374              self.auth_event.set()
    375
    376 -    def _parse_userauth_banner(self, m):
    377          banner = m.get_string()
    378          lang = m.get_string()
    379          self.transport._log(INFO, 'Auth banner: ' + banner)
    380          # who cares.
    381
    382 -    def _parse_userauth_info_request(self, m):
    383          if self.auth_method != 'keyboard-interactive':
    384              raise SSHException('Illegal info request from server')
    385          title = m.get_string()
    386          instructions = m.get_string()
    387          m.get_string()  # lang
    388          prompts = m.get_int()
    389          prompt_list = []
    390          for i in range(prompts):
    391              prompt_list.append((m.get_string(), m.get_boolean()))
    392          response_list = self.interactive_handler(title, instructions, prompt_list)
    393
    394          m = Message()
    395          m.add_byte(chr(MSG_USERAUTH_INFO_RESPONSE))
    396          m.add_int(len(response_list))
    397          for r in response_list:
    398              m.add_string(r)
    399          self.transport._send_message(m)
    400
    401 -    def _parse_userauth_info_response(self, m):
    402          if not self.transport.server_mode:
    403              raise SSHException('Illegal info response from server')
    404          n = m.get_int()
    405          responses = []
    406          for i in range(n):
    407              responses.append(m.get_string())
    408          result = self.transport.server_object.check_auth_interactive_response(responses)
    409          if isinstance(type(result), InteractiveQuery):
    410              # make interactive query instead of response
    411              self._interactive_query(result)
    412              return
    413          self._send_auth_result(self.auth_username, 'keyboard-interactive', result)
    414
    415
    416      _handler_table = {
    417          MSG_SERVICE_REQUEST: _parse_service_request,
    418          MSG_SERVICE_ACCEPT: _parse_service_accept,
    419          MSG_USERAUTH_REQUEST: _parse_userauth_request,
    420          MSG_USERAUTH_SUCCESS: _parse_userauth_success,
    421          MSG_USERAUTH_FAILURE: _parse_userauth_failure,
    422          MSG_USERAUTH_BANNER: _parse_userauth_banner,
    423          MSG_USERAUTH_INFO_REQUEST: _parse_userauth_info_request,
    424          MSG_USERAUTH_INFO_RESPONSE: _parse_userauth_info_response,
    425      }
    426