1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 """
20 L{AuthHandler}
21 """
22
23 import threading
24 import weakref
25
26
27 import encodings.utf_8
28
29 from ssh.common import *
30 from ssh import util
31 from ssh.message import Message
32 from ssh.ssh_exception import SSHException, AuthenticationException, \
33 BadAuthenticationType, PartialAuthentication
34 from ssh.server import InteractiveQuery
35
36
38 """
39 Internal class to handle the mechanics of authentication.
40 """
41
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
53 self.auth_username = None
54 self.auth_fail_count = 0
55
57 return self.authenticated
58
60 if self.transport.server_mode:
61 return self.auth_username
62 else:
63 return self.username
64
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
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
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
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
113 if self.auth_event is not None:
114 self.auth_event.set()
115
116
117
118
119
125
134
143
155
176
178 service = m.get_string()
179 if self.transport.server_mode and (service == 'ssh-userauth'):
180
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
187 self._disconnect_service_not_available()
188
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
223
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
256
258 if not self.transport.server_mode:
259
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
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
291
292 pass
293 if changereq:
294
295
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
321 result = self.transport.server_object.check_auth_publickey(username, key)
322 if result != AUTH_FAILED:
323
324 if not sig_attached:
325
326
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
344 self._interactive_query(result)
345 return
346 else:
347 result = self.transport.server_object.check_auth_none(username)
348
349 self._send_auth_result(username, method, result)
350
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
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
380
381
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()
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
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