auth_handler.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667
  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. `.AuthHandler`
  20. """
  21. import weakref
  22. import time
  23. from paramiko.common import (
  24. cMSG_SERVICE_REQUEST, cMSG_DISCONNECT, DISCONNECT_SERVICE_NOT_AVAILABLE,
  25. DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, cMSG_USERAUTH_REQUEST,
  26. cMSG_SERVICE_ACCEPT, DEBUG, AUTH_SUCCESSFUL, INFO, cMSG_USERAUTH_SUCCESS,
  27. 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. cMSG_USERAUTH_GSSAPI_RESPONSE, cMSG_USERAUTH_GSSAPI_TOKEN,
  33. cMSG_USERAUTH_GSSAPI_MIC, MSG_USERAUTH_GSSAPI_RESPONSE,
  34. MSG_USERAUTH_GSSAPI_TOKEN, MSG_USERAUTH_GSSAPI_ERROR,
  35. MSG_USERAUTH_GSSAPI_ERRTOK, MSG_USERAUTH_GSSAPI_MIC, MSG_NAMES,
  36. )
  37. from paramiko.message import Message
  38. from paramiko.py3compat import bytestring
  39. from paramiko.ssh_exception import (
  40. SSHException, AuthenticationException, BadAuthenticationType,
  41. PartialAuthentication,
  42. )
  43. from paramiko.server import InteractiveQuery
  44. from paramiko.ssh_gss import GSSAuth
  45. class AuthHandler (object):
  46. """
  47. Internal class to handle the mechanics of authentication.
  48. """
  49. def __init__(self, transport):
  50. self.transport = weakref.proxy(transport)
  51. self.username = None
  52. self.authenticated = False
  53. self.auth_event = None
  54. self.auth_method = ''
  55. self.banner = None
  56. self.password = None
  57. self.private_key = None
  58. self.interactive_handler = None
  59. self.submethods = None
  60. # for server mode:
  61. self.auth_username = None
  62. self.auth_fail_count = 0
  63. # for GSSAPI
  64. self.gss_host = None
  65. self.gss_deleg_creds = True
  66. def is_authenticated(self):
  67. return self.authenticated
  68. def get_username(self):
  69. if self.transport.server_mode:
  70. return self.auth_username
  71. else:
  72. return self.username
  73. def auth_none(self, username, event):
  74. self.transport.lock.acquire()
  75. try:
  76. self.auth_event = event
  77. self.auth_method = 'none'
  78. self.username = username
  79. self._request_auth()
  80. finally:
  81. self.transport.lock.release()
  82. def auth_publickey(self, username, key, event):
  83. self.transport.lock.acquire()
  84. try:
  85. self.auth_event = event
  86. self.auth_method = 'publickey'
  87. self.username = username
  88. self.private_key = key
  89. self._request_auth()
  90. finally:
  91. self.transport.lock.release()
  92. def auth_password(self, username, password, event):
  93. self.transport.lock.acquire()
  94. try:
  95. self.auth_event = event
  96. self.auth_method = 'password'
  97. self.username = username
  98. self.password = password
  99. self._request_auth()
  100. finally:
  101. self.transport.lock.release()
  102. def auth_interactive(self, username, handler, event, submethods=''):
  103. """
  104. response_list = handler(title, instructions, prompt_list)
  105. """
  106. self.transport.lock.acquire()
  107. try:
  108. self.auth_event = event
  109. self.auth_method = 'keyboard-interactive'
  110. self.username = username
  111. self.interactive_handler = handler
  112. self.submethods = submethods
  113. self._request_auth()
  114. finally:
  115. self.transport.lock.release()
  116. def auth_gssapi_with_mic(self, username, gss_host, gss_deleg_creds, event):
  117. self.transport.lock.acquire()
  118. try:
  119. self.auth_event = event
  120. self.auth_method = 'gssapi-with-mic'
  121. self.username = username
  122. self.gss_host = gss_host
  123. self.gss_deleg_creds = gss_deleg_creds
  124. self._request_auth()
  125. finally:
  126. self.transport.lock.release()
  127. def auth_gssapi_keyex(self, username, event):
  128. self.transport.lock.acquire()
  129. try:
  130. self.auth_event = event
  131. self.auth_method = 'gssapi-keyex'
  132. self.username = username
  133. self._request_auth()
  134. finally:
  135. self.transport.lock.release()
  136. def abort(self):
  137. if self.auth_event is not None:
  138. self.auth_event.set()
  139. # ...internals...
  140. def _request_auth(self):
  141. m = Message()
  142. m.add_byte(cMSG_SERVICE_REQUEST)
  143. m.add_string('ssh-userauth')
  144. self.transport._send_message(m)
  145. def _disconnect_service_not_available(self):
  146. m = Message()
  147. m.add_byte(cMSG_DISCONNECT)
  148. m.add_int(DISCONNECT_SERVICE_NOT_AVAILABLE)
  149. m.add_string('Service not available')
  150. m.add_string('en')
  151. self.transport._send_message(m)
  152. self.transport.close()
  153. def _disconnect_no_more_auth(self):
  154. m = Message()
  155. m.add_byte(cMSG_DISCONNECT)
  156. m.add_int(DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE)
  157. m.add_string('No more auth methods available')
  158. m.add_string('en')
  159. self.transport._send_message(m)
  160. self.transport.close()
  161. def _get_session_blob(self, key, service, username):
  162. m = Message()
  163. m.add_string(self.transport.session_id)
  164. m.add_byte(cMSG_USERAUTH_REQUEST)
  165. m.add_string(username)
  166. m.add_string(service)
  167. m.add_string('publickey')
  168. m.add_boolean(True)
  169. m.add_string(key.get_name())
  170. m.add_string(key)
  171. return m.asbytes()
  172. def wait_for_response(self, event):
  173. max_ts = None
  174. if self.transport.auth_timeout is not None:
  175. max_ts = time.time() + self.transport.auth_timeout
  176. while True:
  177. event.wait(0.1)
  178. if not self.transport.is_active():
  179. e = self.transport.get_exception()
  180. if (e is None) or issubclass(e.__class__, EOFError):
  181. e = AuthenticationException('Authentication failed.')
  182. raise e
  183. if event.is_set():
  184. break
  185. if max_ts is not None and max_ts <= time.time():
  186. raise AuthenticationException('Authentication timeout.')
  187. if not self.is_authenticated():
  188. e = self.transport.get_exception()
  189. if e is None:
  190. e = AuthenticationException('Authentication failed.')
  191. # this is horrible. Python Exception isn't yet descended from
  192. # object, so type(e) won't work. :(
  193. if issubclass(e.__class__, PartialAuthentication):
  194. return e.allowed_types
  195. raise e
  196. return []
  197. def _parse_service_request(self, m):
  198. service = m.get_text()
  199. if self.transport.server_mode and (service == 'ssh-userauth'):
  200. # accepted
  201. m = Message()
  202. m.add_byte(cMSG_SERVICE_ACCEPT)
  203. m.add_string(service)
  204. self.transport._send_message(m)
  205. return
  206. # dunno this one
  207. self._disconnect_service_not_available()
  208. def _parse_service_accept(self, m):
  209. service = m.get_text()
  210. if service == 'ssh-userauth':
  211. self.transport._log(DEBUG, 'userauth is OK')
  212. m = Message()
  213. m.add_byte(cMSG_USERAUTH_REQUEST)
  214. m.add_string(self.username)
  215. m.add_string('ssh-connection')
  216. m.add_string(self.auth_method)
  217. if self.auth_method == 'password':
  218. m.add_boolean(False)
  219. password = bytestring(self.password)
  220. m.add_string(password)
  221. elif self.auth_method == 'publickey':
  222. m.add_boolean(True)
  223. m.add_string(self.private_key.get_name())
  224. m.add_string(self.private_key)
  225. blob = self._get_session_blob(
  226. self.private_key, 'ssh-connection', self.username)
  227. sig = self.private_key.sign_ssh_data(blob)
  228. m.add_string(sig)
  229. elif self.auth_method == 'keyboard-interactive':
  230. m.add_string('')
  231. m.add_string(self.submethods)
  232. elif self.auth_method == "gssapi-with-mic":
  233. sshgss = GSSAuth(self.auth_method, self.gss_deleg_creds)
  234. m.add_bytes(sshgss.ssh_gss_oids())
  235. # send the supported GSSAPI OIDs to the server
  236. self.transport._send_message(m)
  237. ptype, m = self.transport.packetizer.read_message()
  238. if ptype == MSG_USERAUTH_BANNER:
  239. self._parse_userauth_banner(m)
  240. ptype, m = self.transport.packetizer.read_message()
  241. if ptype == MSG_USERAUTH_GSSAPI_RESPONSE:
  242. # Read the mechanism selected by the server. We send just
  243. # the Kerberos V5 OID, so the server can only respond with
  244. # this OID.
  245. mech = m.get_string()
  246. m = Message()
  247. m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN)
  248. m.add_string(sshgss.ssh_init_sec_context(self.gss_host,
  249. mech,
  250. self.username,))
  251. self.transport._send_message(m)
  252. while True:
  253. ptype, m = self.transport.packetizer.read_message()
  254. if ptype == MSG_USERAUTH_GSSAPI_TOKEN:
  255. srv_token = m.get_string()
  256. next_token = sshgss.ssh_init_sec_context(
  257. self.gss_host,
  258. mech,
  259. self.username,
  260. srv_token)
  261. # After this step the GSSAPI should not return any
  262. # token. If it does, we keep sending the token to
  263. # the server until no more token is returned.
  264. if next_token is None:
  265. break
  266. else:
  267. m = Message()
  268. m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN)
  269. m.add_string(next_token)
  270. self.transport.send_message(m)
  271. else:
  272. raise SSHException(
  273. "Received Package: %s" % MSG_NAMES[ptype])
  274. m = Message()
  275. m.add_byte(cMSG_USERAUTH_GSSAPI_MIC)
  276. # send the MIC to the server
  277. m.add_string(sshgss.ssh_get_mic(self.transport.session_id))
  278. elif ptype == MSG_USERAUTH_GSSAPI_ERRTOK:
  279. # RFC 4462 says we are not required to implement GSS-API
  280. # error messages.
  281. # See RFC 4462 Section 3.8 in
  282. # http://www.ietf.org/rfc/rfc4462.txt
  283. raise SSHException("Server returned an error token")
  284. elif ptype == MSG_USERAUTH_GSSAPI_ERROR:
  285. maj_status = m.get_int()
  286. min_status = m.get_int()
  287. err_msg = m.get_string()
  288. m.get_string() # Lang tag - discarded
  289. raise SSHException("GSS-API Error:\nMajor Status: %s\n\
  290. Minor Status: %s\ \nError Message:\
  291. %s\n") % (str(maj_status),
  292. str(min_status),
  293. err_msg)
  294. elif ptype == MSG_USERAUTH_FAILURE:
  295. self._parse_userauth_failure(m)
  296. return
  297. else:
  298. raise SSHException(
  299. "Received Package: %s" % MSG_NAMES[ptype])
  300. elif (
  301. self.auth_method == 'gssapi-keyex' and
  302. self.transport.gss_kex_used
  303. ):
  304. kexgss = self.transport.kexgss_ctxt
  305. kexgss.set_username(self.username)
  306. mic_token = kexgss.ssh_get_mic(self.transport.session_id)
  307. m.add_string(mic_token)
  308. elif self.auth_method == 'none':
  309. pass
  310. else:
  311. raise SSHException(
  312. 'Unknown auth method "%s"' % self.auth_method)
  313. self.transport._send_message(m)
  314. else:
  315. self.transport._log(
  316. DEBUG,
  317. 'Service request "%s" accepted (?)' % service)
  318. def _send_auth_result(self, username, method, result):
  319. # okay, send result
  320. m = Message()
  321. if result == AUTH_SUCCESSFUL:
  322. self.transport._log(INFO, 'Auth granted (%s).' % method)
  323. m.add_byte(cMSG_USERAUTH_SUCCESS)
  324. self.authenticated = True
  325. else:
  326. self.transport._log(INFO, 'Auth rejected (%s).' % method)
  327. m.add_byte(cMSG_USERAUTH_FAILURE)
  328. m.add_string(
  329. self.transport.server_object.get_allowed_auths(username))
  330. if result == AUTH_PARTIALLY_SUCCESSFUL:
  331. m.add_boolean(True)
  332. else:
  333. m.add_boolean(False)
  334. self.auth_fail_count += 1
  335. self.transport._send_message(m)
  336. if self.auth_fail_count >= 10:
  337. self._disconnect_no_more_auth()
  338. if result == AUTH_SUCCESSFUL:
  339. self.transport._auth_trigger()
  340. def _interactive_query(self, q):
  341. # make interactive query instead of response
  342. m = Message()
  343. m.add_byte(cMSG_USERAUTH_INFO_REQUEST)
  344. m.add_string(q.name)
  345. m.add_string(q.instructions)
  346. m.add_string(bytes())
  347. m.add_int(len(q.prompts))
  348. for p in q.prompts:
  349. m.add_string(p[0])
  350. m.add_boolean(p[1])
  351. self.transport._send_message(m)
  352. def _parse_userauth_request(self, m):
  353. if not self.transport.server_mode:
  354. # er, uh... what?
  355. m = Message()
  356. m.add_byte(cMSG_USERAUTH_FAILURE)
  357. m.add_string('none')
  358. m.add_boolean(False)
  359. self.transport._send_message(m)
  360. return
  361. if self.authenticated:
  362. # ignore
  363. return
  364. username = m.get_text()
  365. service = m.get_text()
  366. method = m.get_text()
  367. self.transport._log(
  368. DEBUG,
  369. 'Auth request (type=%s) service=%s, username=%s' % (
  370. method, service, username))
  371. if service != 'ssh-connection':
  372. self._disconnect_service_not_available()
  373. return
  374. if ((self.auth_username is not None) and
  375. (self.auth_username != username)):
  376. self.transport._log(
  377. WARNING,
  378. 'Auth rejected because the client attempted to change username in mid-flight' # noqa
  379. )
  380. self._disconnect_no_more_auth()
  381. return
  382. self.auth_username = username
  383. # check if GSS-API authentication is enabled
  384. gss_auth = self.transport.server_object.enable_auth_gssapi()
  385. if method == 'none':
  386. result = self.transport.server_object.check_auth_none(username)
  387. elif method == 'password':
  388. changereq = m.get_boolean()
  389. password = m.get_binary()
  390. try:
  391. password = password.decode('UTF-8')
  392. except UnicodeError:
  393. # some clients/servers expect non-utf-8 passwords!
  394. # in this case, just return the raw byte string.
  395. pass
  396. if changereq:
  397. # always treated as failure, since we don't support changing
  398. # passwords, but collect the list of valid auth types from
  399. # the callback anyway
  400. self.transport._log(
  401. DEBUG,
  402. 'Auth request to change passwords (rejected)')
  403. newpassword = m.get_binary()
  404. try:
  405. newpassword = newpassword.decode('UTF-8', 'replace')
  406. except UnicodeError:
  407. pass
  408. result = AUTH_FAILED
  409. else:
  410. result = self.transport.server_object.check_auth_password(
  411. username, password)
  412. elif method == 'publickey':
  413. sig_attached = m.get_boolean()
  414. keytype = m.get_text()
  415. keyblob = m.get_binary()
  416. try:
  417. key = self.transport._key_info[keytype](Message(keyblob))
  418. except SSHException as e:
  419. self.transport._log(
  420. INFO,
  421. 'Auth rejected: public key: %s' % str(e))
  422. key = None
  423. except:
  424. self.transport._log(
  425. INFO,
  426. 'Auth rejected: unsupported or mangled public key')
  427. key = None
  428. if key is None:
  429. self._disconnect_no_more_auth()
  430. return
  431. # first check if this key is okay... if not, we can skip the verify
  432. result = self.transport.server_object.check_auth_publickey(
  433. username, key)
  434. if result != AUTH_FAILED:
  435. # key is okay, verify it
  436. if not sig_attached:
  437. # client wants to know if this key is acceptable, before it
  438. # signs anything... send special "ok" message
  439. m = Message()
  440. m.add_byte(cMSG_USERAUTH_PK_OK)
  441. m.add_string(keytype)
  442. m.add_string(keyblob)
  443. self.transport._send_message(m)
  444. return
  445. sig = Message(m.get_binary())
  446. blob = self._get_session_blob(key, service, username)
  447. if not key.verify_ssh_sig(blob, sig):
  448. self.transport._log(
  449. INFO,
  450. 'Auth rejected: invalid signature')
  451. result = AUTH_FAILED
  452. elif method == 'keyboard-interactive':
  453. submethods = m.get_string()
  454. result = self.transport.server_object.check_auth_interactive(
  455. username, submethods)
  456. if isinstance(result, InteractiveQuery):
  457. # make interactive query instead of response
  458. self._interactive_query(result)
  459. return
  460. elif method == "gssapi-with-mic" and gss_auth:
  461. sshgss = GSSAuth(method)
  462. # Read the number of OID mechanisms supported by the client.
  463. # OpenSSH sends just one OID. It's the Kerveros V5 OID and that's
  464. # the only OID we support.
  465. mechs = m.get_int()
  466. # We can't accept more than one OID, so if the SSH client sends
  467. # more than one, disconnect.
  468. if mechs > 1:
  469. self.transport._log(
  470. INFO,
  471. 'Disconnect: Received more than one GSS-API OID mechanism')
  472. self._disconnect_no_more_auth()
  473. desired_mech = m.get_string()
  474. mech_ok = sshgss.ssh_check_mech(desired_mech)
  475. # if we don't support the mechanism, disconnect.
  476. if not mech_ok:
  477. self.transport._log(
  478. INFO,
  479. 'Disconnect: Received an invalid GSS-API OID mechanism')
  480. self._disconnect_no_more_auth()
  481. # send the Kerberos V5 GSSAPI OID to the client
  482. supported_mech = sshgss.ssh_gss_oids("server")
  483. # RFC 4462 says we are not required to implement GSS-API error
  484. # messages. See section 3.8 in http://www.ietf.org/rfc/rfc4462.txt
  485. while True:
  486. m = Message()
  487. m.add_byte(cMSG_USERAUTH_GSSAPI_RESPONSE)
  488. m.add_bytes(supported_mech)
  489. self.transport._send_message(m)
  490. ptype, m = self.transport.packetizer.read_message()
  491. if ptype == MSG_USERAUTH_GSSAPI_TOKEN:
  492. client_token = m.get_string()
  493. # use the client token as input to establish a secure
  494. # context.
  495. try:
  496. token = sshgss.ssh_accept_sec_context(self.gss_host,
  497. client_token,
  498. username)
  499. except Exception:
  500. result = AUTH_FAILED
  501. self._send_auth_result(username, method, result)
  502. raise
  503. if token is not None:
  504. m = Message()
  505. m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN)
  506. m.add_string(token)
  507. self.transport._send_message(m)
  508. else:
  509. result = AUTH_FAILED
  510. self._send_auth_result(username, method, result)
  511. return
  512. # check MIC
  513. ptype, m = self.transport.packetizer.read_message()
  514. if ptype == MSG_USERAUTH_GSSAPI_MIC:
  515. break
  516. mic_token = m.get_string()
  517. try:
  518. sshgss.ssh_check_mic(mic_token,
  519. self.transport.session_id,
  520. username)
  521. except Exception:
  522. result = AUTH_FAILED
  523. self._send_auth_result(username, method, result)
  524. raise
  525. # TODO: Implement client credential saving.
  526. # The OpenSSH server is able to create a TGT with the delegated
  527. # client credentials, but this is not supported by GSS-API.
  528. result = AUTH_SUCCESSFUL
  529. self.transport.server_object.check_auth_gssapi_with_mic(
  530. username, result)
  531. elif method == "gssapi-keyex" and gss_auth:
  532. mic_token = m.get_string()
  533. sshgss = self.transport.kexgss_ctxt
  534. if sshgss is None:
  535. # If there is no valid context, we reject the authentication
  536. result = AUTH_FAILED
  537. self._send_auth_result(username, method, result)
  538. try:
  539. sshgss.ssh_check_mic(mic_token,
  540. self.transport.session_id,
  541. self.auth_username)
  542. except Exception:
  543. result = AUTH_FAILED
  544. self._send_auth_result(username, method, result)
  545. raise
  546. result = AUTH_SUCCESSFUL
  547. self.transport.server_object.check_auth_gssapi_keyex(
  548. username, result)
  549. else:
  550. result = self.transport.server_object.check_auth_none(username)
  551. # okay, send result
  552. self._send_auth_result(username, method, result)
  553. def _parse_userauth_success(self, m):
  554. self.transport._log(
  555. INFO,
  556. 'Authentication (%s) successful!' % self.auth_method)
  557. self.authenticated = True
  558. self.transport._auth_trigger()
  559. if self.auth_event is not None:
  560. self.auth_event.set()
  561. def _parse_userauth_failure(self, m):
  562. authlist = m.get_list()
  563. partial = m.get_boolean()
  564. if partial:
  565. self.transport._log(INFO, 'Authentication continues...')
  566. self.transport._log(DEBUG, 'Methods: ' + str(authlist))
  567. self.transport.saved_exception = PartialAuthentication(authlist)
  568. elif self.auth_method not in authlist:
  569. self.transport._log(
  570. DEBUG,
  571. 'Authentication type (%s) not permitted.' % self.auth_method)
  572. self.transport._log(
  573. DEBUG,
  574. 'Allowed methods: ' + str(authlist))
  575. self.transport.saved_exception = BadAuthenticationType(
  576. 'Bad authentication type', authlist)
  577. else:
  578. self.transport._log(
  579. INFO,
  580. 'Authentication (%s) failed.' % self.auth_method)
  581. self.authenticated = False
  582. self.username = None
  583. if self.auth_event is not None:
  584. self.auth_event.set()
  585. def _parse_userauth_banner(self, m):
  586. banner = m.get_string()
  587. self.banner = banner
  588. self.transport._log(INFO, 'Auth banner: %s' % banner)
  589. # who cares.
  590. def _parse_userauth_info_request(self, m):
  591. if self.auth_method != 'keyboard-interactive':
  592. raise SSHException('Illegal info request from server')
  593. title = m.get_text()
  594. instructions = m.get_text()
  595. m.get_binary() # lang
  596. prompts = m.get_int()
  597. prompt_list = []
  598. for i in range(prompts):
  599. prompt_list.append((m.get_text(), m.get_boolean()))
  600. response_list = self.interactive_handler(
  601. title, instructions, prompt_list)
  602. m = Message()
  603. m.add_byte(cMSG_USERAUTH_INFO_RESPONSE)
  604. m.add_int(len(response_list))
  605. for r in response_list:
  606. m.add_string(r)
  607. self.transport._send_message(m)
  608. def _parse_userauth_info_response(self, m):
  609. if not self.transport.server_mode:
  610. raise SSHException('Illegal info response from server')
  611. n = m.get_int()
  612. responses = []
  613. for i in range(n):
  614. responses.append(m.get_text())
  615. result = self.transport.server_object.check_auth_interactive_response(
  616. responses)
  617. if isinstance(result, InteractiveQuery):
  618. # make interactive query instead of response
  619. self._interactive_query(result)
  620. return
  621. self._send_auth_result(
  622. self.auth_username, 'keyboard-interactive', result)
  623. _handler_table = {
  624. MSG_SERVICE_REQUEST: _parse_service_request,
  625. MSG_SERVICE_ACCEPT: _parse_service_accept,
  626. MSG_USERAUTH_REQUEST: _parse_userauth_request,
  627. MSG_USERAUTH_SUCCESS: _parse_userauth_success,
  628. MSG_USERAUTH_FAILURE: _parse_userauth_failure,
  629. MSG_USERAUTH_BANNER: _parse_userauth_banner,
  630. MSG_USERAUTH_INFO_REQUEST: _parse_userauth_info_request,
  631. MSG_USERAUTH_INFO_RESPONSE: _parse_userauth_info_response,
  632. }