| Home | Trees | Indices | Help |
|---|
|
|
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
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 # for server mode:
53 self.auth_username = None
54 self.auth_fail_count = 0
55
58
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
115
116
117 ### internals...
118
119
121 m = Message()
122 m.add_byte(chr(MSG_SERVICE_REQUEST))
123 m.add_string('ssh-userauth')
124 self.transport._send_message(m)
125
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
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
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
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
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
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 # 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
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
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
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 # who cares.
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() # 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
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
| Home | Trees | Indices | Help |
|---|
| Generated by Epydoc 3.0.1 on Mon May 23 13:50:08 2011 | http://epydoc.sourceforge.net |