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 distributed 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  `.AuthHandler` 
 21  """ 
 22   
 23  import weakref 
 24  from paramiko.common import cMSG_SERVICE_REQUEST, cMSG_DISCONNECT, \ 
 25      DISCONNECT_SERVICE_NOT_AVAILABLE, DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, \ 
 26      cMSG_USERAUTH_REQUEST, cMSG_SERVICE_ACCEPT, DEBUG, AUTH_SUCCESSFUL, INFO, \ 
 27      cMSG_USERAUTH_SUCCESS, cMSG_USERAUTH_FAILURE, AUTH_PARTIALLY_SUCCESSFUL, \ 
 28      cMSG_USERAUTH_INFO_REQUEST, WARNING, AUTH_FAILED, cMSG_USERAUTH_PK_OK, \ 
 29      cMSG_USERAUTH_INFO_RESPONSE, MSG_SERVICE_REQUEST, MSG_SERVICE_ACCEPT, \ 
 30      MSG_USERAUTH_REQUEST, MSG_USERAUTH_SUCCESS, MSG_USERAUTH_FAILURE, \ 
 31      MSG_USERAUTH_BANNER, MSG_USERAUTH_INFO_REQUEST, MSG_USERAUTH_INFO_RESPONSE 
 32   
 33  from paramiko.message import Message 
 34  from paramiko.py3compat import bytestring 
 35  from paramiko.ssh_exception import SSHException, AuthenticationException, \ 
 36      BadAuthenticationType, PartialAuthentication 
 37  from paramiko.server import InteractiveQuery 
 38   
 39   
40 -class AuthHandler (object):
41 """ 42 Internal class to handle the mechanics of authentication. 43 """ 44
45 - def __init__(self, transport):
46 self.transport = weakref.proxy(transport) 47 self.username = None 48 self.authenticated = False 49 self.auth_event = None 50 self.auth_method = '' 51 self.banner = None 52 self.password = None 53 self.private_key = None 54 self.interactive_handler = None 55 self.submethods = None 56 # for server mode: 57 self.auth_username = None 58 self.auth_fail_count = 0
59
60 - def is_authenticated(self):
61 return self.authenticated
62
63 - def get_username(self):
64 if self.transport.server_mode: 65 return self.auth_username 66 else: 67 return self.username
68
69 - def auth_none(self, username, event):
70 self.transport.lock.acquire() 71 try: 72 self.auth_event = event 73 self.auth_method = 'none' 74 self.username = username 75 self._request_auth() 76 finally: 77 self.transport.lock.release()
78
79 - def auth_publickey(self, username, key, event):
80 self.transport.lock.acquire() 81 try: 82 self.auth_event = event 83 self.auth_method = 'publickey' 84 self.username = username 85 self.private_key = key 86 self._request_auth() 87 finally: 88 self.transport.lock.release()
89
90 - def auth_password(self, username, password, event):
91 self.transport.lock.acquire() 92 try: 93 self.auth_event = event 94 self.auth_method = 'password' 95 self.username = username 96 self.password = password 97 self._request_auth() 98 finally: 99 self.transport.lock.release()
100
101 - def auth_interactive(self, username, handler, event, submethods=''):
102 """ 103 response_list = handler(title, instructions, prompt_list) 104 """ 105 self.transport.lock.acquire() 106 try: 107 self.auth_event = event 108 self.auth_method = 'keyboard-interactive' 109 self.username = username 110 self.interactive_handler = handler 111 self.submethods = submethods 112 self._request_auth() 113 finally: 114 self.transport.lock.release()
115
116 - def abort(self):
117 if self.auth_event is not None: 118 self.auth_event.set()
119 120 ### internals... 121
122 - def _request_auth(self):
123 m = Message() 124 m.add_byte(cMSG_SERVICE_REQUEST) 125 m.add_string('ssh-userauth') 126 self.transport._send_message(m)
127
129 m = Message() 130 m.add_byte(cMSG_DISCONNECT) 131 m.add_int(DISCONNECT_SERVICE_NOT_AVAILABLE) 132 m.add_string('Service not available') 133 m.add_string('en') 134 self.transport._send_message(m) 135 self.transport.close()
136
137 - def _disconnect_no_more_auth(self):
138 m = Message() 139 m.add_byte(cMSG_DISCONNECT) 140 m.add_int(DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE) 141 m.add_string('No more auth methods available') 142 m.add_string('en') 143 self.transport._send_message(m) 144 self.transport.close()
145
146 - def _get_session_blob(self, key, service, username):
147 m = Message() 148 m.add_string(self.transport.session_id) 149 m.add_byte(cMSG_USERAUTH_REQUEST) 150 m.add_string(username) 151 m.add_string(service) 152 m.add_string('publickey') 153 m.add_boolean(True) 154 m.add_string(key.get_name()) 155 m.add_string(key) 156 return m.asbytes()
157
158 - def wait_for_response(self, event):
159 while True: 160 event.wait(0.1) 161 if not self.transport.is_active(): 162 e = self.transport.get_exception() 163 if (e is None) or issubclass(e.__class__, EOFError): 164 e = AuthenticationException('Authentication failed.') 165 raise e 166 if event.isSet(): 167 break 168 if not self.is_authenticated(): 169 e = self.transport.get_exception() 170 if e is None: 171 e = AuthenticationException('Authentication failed.') 172 # this is horrible. Python Exception isn't yet descended from 173 # object, so type(e) won't work. :( 174 if issubclass(e.__class__, PartialAuthentication): 175 return e.allowed_types 176 raise e 177 return []
178
179 - def _parse_service_request(self, m):
180 service = m.get_text() 181 if self.transport.server_mode and (service == 'ssh-userauth'): 182 # accepted 183 m = Message() 184 m.add_byte(cMSG_SERVICE_ACCEPT) 185 m.add_string(service) 186 self.transport._send_message(m) 187 return 188 # dunno this one 189 self._disconnect_service_not_available()
190
191 - def _parse_service_accept(self, m):
192 service = m.get_text() 193 if service == 'ssh-userauth': 194 self.transport._log(DEBUG, 'userauth is OK') 195 m = Message() 196 m.add_byte(cMSG_USERAUTH_REQUEST) 197 m.add_string(self.username) 198 m.add_string('ssh-connection') 199 m.add_string(self.auth_method) 200 if self.auth_method == 'password': 201 m.add_boolean(False) 202 password = bytestring(self.password) 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(self.private_key) 208 blob = self._get_session_blob(self.private_key, 'ssh-connection', self.username) 209 sig = self.private_key.sign_ssh_data(blob) 210 m.add_string(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(cMSG_USERAUTH_SUCCESS) 228 self.authenticated = True 229 else: 230 self.transport._log(INFO, 'Auth rejected (%s).' % method) 231 m.add_byte(cMSG_USERAUTH_FAILURE) 232 m.add_string(self.transport.server_object.get_allowed_auths(username)) 233 if result == AUTH_PARTIALLY_SUCCESSFUL: 234 m.add_boolean(True) 235 else: 236 m.add_boolean(False) 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(cMSG_USERAUTH_INFO_REQUEST) 248 m.add_string(q.name) 249 m.add_string(q.instructions) 250 m.add_string(bytes()) 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(cMSG_USERAUTH_FAILURE) 262 m.add_string('none') 263 m.add_boolean(False) 264 self.transport._send_message(m) 265 return 266 if self.authenticated: 267 # ignore 268 return 269 username = m.get_text() 270 service = m.get_text() 271 method = m.get_text() 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_binary() 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_binary() 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_text() 308 keyblob = m.get_binary() 309 try: 310 key = self.transport._key_info[keytype](Message(keyblob)) 311 except SSHException as 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(cMSG_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_binary()) 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 is not 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 is not None: 374 self.auth_event.set()
375
376 - def _parse_userauth_banner(self, m):
377 banner = m.get_string() 378 self.banner = banner 379 lang = m.get_string() 380 self.transport._log(INFO, 'Auth banner: %s' % banner)
381 # who cares. 382
383 - def _parse_userauth_info_request(self, m):
384 if self.auth_method != 'keyboard-interactive': 385 raise SSHException('Illegal info request from server') 386 title = m.get_text() 387 instructions = m.get_text() 388 m.get_binary() # lang 389 prompts = m.get_int() 390 prompt_list = [] 391 for i in range(prompts): 392 prompt_list.append((m.get_text(), m.get_boolean())) 393 response_list = self.interactive_handler(title, instructions, prompt_list) 394 395 m = Message() 396 m.add_byte(cMSG_USERAUTH_INFO_RESPONSE) 397 m.add_int(len(response_list)) 398 for r in response_list: 399 m.add_string(r) 400 self.transport._send_message(m)
401
402 - def _parse_userauth_info_response(self, m):
403 if not self.transport.server_mode: 404 raise SSHException('Illegal info response from server') 405 n = m.get_int() 406 responses = [] 407 for i in range(n): 408 responses.append(m.get_text()) 409 result = self.transport.server_object.check_auth_interactive_response(responses) 410 if isinstance(type(result), InteractiveQuery): 411 # make interactive query instead of response 412 self._interactive_query(result) 413 return 414 self._send_auth_result(self.auth_username, 'keyboard-interactive', result)
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