| 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.randpool, 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 Sun Nov 1 22:14:20 2009 | http://epydoc.sourceforge.net |