1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 16   
 17  """DIGEST-MD5 authentication mechanism for PyXMPP SASL implementation. 
 18   
 19  Normative reference: 
 20    - `RFC 2831 <http://www.ietf.org/rfc/rfc2831.txt>`__ 
 21  """ 
 22   
 23  __revision__="$Id: digest_md5.py 683 2008-12-05 18:25:45Z jajcus $" 
 24  __docformat__="restructuredtext en" 
 25   
 26  from binascii import b2a_hex 
 27  import re 
 28  import logging 
 29   
 30  try: 
 31      import hashlib 
 32      md5_factory = hashlib.md5 
 33  except: 
 34      import md5 
 35      md5_factory = md5.new 
 36   
 37  from pyxmpp.sasl.core import ClientAuthenticator,ServerAuthenticator 
 38  from pyxmpp.sasl.core import Failure,Response,Challenge,Success,Failure 
 39   
 40  from pyxmpp.utils import to_utf8,from_utf8 
 41   
 42  quote_re=re.compile(r"(?<!\\)\\(.)") 
 43   
 45      """Unquote quoted value from DIGEST-MD5 challenge or response. 
 46   
 47      If `s` doesn't start or doesn't end with '"' then return it unchanged, 
 48      remove the quotes and escape backslashes otherwise. 
 49   
 50      :Parameters: 
 51          - `s`: a quoted string. 
 52      :Types: 
 53          - `s`: `str` 
 54   
 55      :return: the unquoted string. 
 56      :returntype: `str`""" 
 57      if not s.startswith('"') or not s.endswith('"'): 
 58          return s 
 59      return quote_re.sub(r"\1",s[1:-1]) 
  60   
 62      """Prepare a string for quoting for DIGEST-MD5 challenge or response. 
 63   
 64      Don't add the quotes, only escape '"' and "\\" with backslashes. 
 65   
 66      :Parameters: 
 67          - `s`: a raw string. 
 68      :Types: 
 69          - `s`: `str` 
 70   
 71      :return: `s` with '"' and "\\" escaped using "\\". 
 72      :returntype: `str`""" 
 73      s=s.replace('\\','\\\\') 
 74      s=s.replace('"','\\"') 
 75      return '%s' % (s,) 
  76   
 78      """H function of the DIGEST-MD5 algorithm (MD5 sum). 
 79   
 80      :Parameters: 
 81          - `s`: a string. 
 82      :Types: 
 83          - `s`: `str` 
 84   
 85      :return: MD5 sum of the string. 
 86      :returntype: `str`""" 
 87      return md5_factory(s).digest() 
  88   
 90      """KD function of the DIGEST-MD5 algorithm. 
 91   
 92      :Parameters: 
 93          - `k`: a string. 
 94          - `s`: a string. 
 95      :Types: 
 96          - `k`: `str` 
 97          - `s`: `str` 
 98   
 99      :return: MD5 sum of the strings joined with ':'. 
100      :returntype: `str`""" 
101      return _h_value("%s:%s" % (k,s)) 
 102   
104      """Compute MD5 sum of username:realm:password. 
105   
106      :Parameters: 
107          - `username`: a username. 
108          - `realm`: a realm. 
109          - `passwd`: a password. 
110      :Types: 
111          - `username`: `str` 
112          - `realm`: `str` 
113          - `passwd`: `str` 
114   
115      :return: the MD5 sum of the parameters joined with ':'. 
116      :returntype: `str`""" 
117      if realm is None: 
118          realm="" 
119      if type(passwd) is unicode: 
120          passwd=passwd.encode("utf-8") 
121      return _h_value("%s:%s:%s" % (username,realm,passwd)) 
 122   
124      """Compute DIGEST-MD5 response value. 
125   
126      :Parameters: 
127          - `urp_hash`: MD5 sum of username:realm:password. 
128          - `nonce`: nonce value from a server challenge. 
129          - `cnonce`: cnonce value from the client response. 
130          - `nonce_count`: nonce count value. 
131          - `authzid`: authorization id. 
132          - `digest_uri`: digest-uri value. 
133      :Types: 
134          - `urp_hash`: `str` 
135          - `nonce`: `str` 
136          - `nonce_count`: `int` 
137          - `authzid`: `str` 
138          - `digest_uri`: `str` 
139   
140      :return: the computed response value. 
141      :returntype: `str`""" 
142      if authzid: 
143          a1="%s:%s:%s:%s" % (urp_hash,nonce,cnonce,authzid) 
144      else: 
145          a1="%s:%s:%s" % (urp_hash,nonce,cnonce) 
146      a2="AUTHENTICATE:"+digest_uri 
147      return b2a_hex(_kd_value( b2a_hex(_h_value(a1)),"%s:%s:%s:%s:%s" % ( 
148              nonce,nonce_count, 
149              cnonce,"auth",b2a_hex(_h_value(a2)) ) )) 
 150   
152      """Compute DIGEST-MD5 rspauth value. 
153   
154      :Parameters: 
155          - `urp_hash`: MD5 sum of username:realm:password. 
156          - `nonce`: nonce value from a server challenge. 
157          - `cnonce`: cnonce value from the client response. 
158          - `nonce_count`: nonce count value. 
159          - `authzid`: authorization id. 
160          - `digest_uri`: digest-uri value. 
161      :Types: 
162          - `urp_hash`: `str` 
163          - `nonce`: `str` 
164          - `nonce_count`: `int` 
165          - `authzid`: `str` 
166          - `digest_uri`: `str` 
167   
168      :return: the computed rspauth value. 
169      :returntype: `str`""" 
170      if authzid: 
171          a1="%s:%s:%s:%s" % (urp_hash,nonce,cnonce,authzid) 
172      else: 
173          a1="%s:%s:%s" % (urp_hash,nonce,cnonce) 
174      a2=":"+digest_uri 
175      return b2a_hex(_kd_value( b2a_hex(_h_value(a1)),"%s:%s:%s:%s:%s" % ( 
176              nonce,nonce_count, 
177              cnonce,"auth",b2a_hex(_h_value(a2)) ) )) 
 178   
179  _param_re=re.compile(r'^(?P<var>[^=]+)\=(?P<val>(\"(([^"\\]+)|(\\\")' 
180          r'|(\\\\))+\")|([^",]+))(\s*\,\s*(?P<rest>.*))?$') 
181   
183      """Provides PLAIN SASL authentication for a client. 
184   
185      :Ivariables: 
186          - `password`: current authentication password 
187          - `pformat`: current authentication password format 
188          - `realm`: current authentication realm 
189      """ 
190   
192          """Initialize a `DigestMD5ClientAuthenticator` object. 
193   
194          :Parameters: 
195              - `password_manager`: name of the password manager object providing 
196                authentication credentials. 
197          :Types: 
198              - `password_manager`: `PasswordManager`""" 
199          ClientAuthenticator.__init__(self,password_manager) 
200          self.username=None 
201          self.rspauth_checked=None 
202          self.response_auth=None 
203          self.authzid=None 
204          self.pformat=None 
205          self.realm=None 
206          self.password=None 
207          self.nonce_count=None 
208          self.__logger=logging.getLogger("pyxmpp.sasl.DigestMD5ClientAuthenticator") 
 209   
210 -    def start(self,username,authzid): 
 211          """Start the authentication process initializing client state. 
212   
213          :Parameters: 
214              - `username`: username (authentication id). 
215              - `authzid`: authorization id. 
216          :Types: 
217              - `username`: `unicode` 
218              - `authzid`: `unicode` 
219   
220          :return: the (empty) initial response 
221          :returntype: `sasl.Response` or `sasl.Failure`""" 
222          self.username=from_utf8(username) 
223          if authzid: 
224              self.authzid=from_utf8(authzid) 
225          else: 
226              self.authzid="" 
227          self.password=None 
228          self.pformat=None 
229          self.nonce_count=0 
230          self.response_auth=None 
231          self.rspauth_checked=0 
232          self.realm=None 
233          return Response() 
 234   
236          """Process a challenge and return the response. 
237   
238          :Parameters: 
239              - `challenge`: the challenge from server. 
240          :Types: 
241              - `challenge`: `str` 
242   
243          :return: the response or a failure indicator. 
244          :returntype: `sasl.Response` or `sasl.Failure`""" 
245          if not challenge: 
246              self.__logger.debug("Empty challenge") 
247              return Failure("bad-challenge") 
248          challenge=challenge.split('\x00')[0]  
249          if self.response_auth: 
250              return self._final_challenge(challenge) 
251          realms=[] 
252          nonce=None 
253          charset="iso-8859-1" 
254          while challenge: 
255              m=_param_re.match(challenge) 
256              if not m: 
257                  self.__logger.debug("Challenge syntax error: %r" % (challenge,)) 
258                  return Failure("bad-challenge") 
259              challenge=m.group("rest") 
260              var=m.group("var") 
261              val=m.group("val") 
262              self.__logger.debug("%r: %r" % (var,val)) 
263              if var=="realm": 
264                  realms.append(_unquote(val)) 
265              elif var=="nonce": 
266                  if nonce: 
267                      self.__logger.debug("Duplicate nonce") 
268                      return Failure("bad-challenge") 
269                  nonce=_unquote(val) 
270              elif var=="qop": 
271                  qopl=_unquote(val).split(",") 
272                  if "auth" not in qopl: 
273                      self.__logger.debug("auth not supported") 
274                      return Failure("not-implemented") 
275              elif var=="charset": 
276                  if val!="utf-8": 
277                      self.__logger.debug("charset given and not utf-8") 
278                      return Failure("bad-challenge") 
279                  charset="utf-8" 
280              elif var=="algorithm": 
281                  if val!="md5-sess": 
282                      self.__logger.debug("algorithm given and not md5-sess") 
283                      return Failure("bad-challenge") 
284          if not nonce: 
285              self.__logger.debug("nonce not given") 
286              return Failure("bad-challenge") 
287          self._get_password() 
288          return self._make_response(charset,realms,nonce) 
 289   
291          """Retrieve user's password from the password manager. 
292   
293          Set `self.password` to the password and `self.pformat` 
294          to its format name ('plain' or 'md5:user:realm:pass').""" 
295          if self.password is None: 
296              self.password,self.pformat=self.password_manager.get_password( 
297                          self.username,["plain","md5:user:realm:pass"]) 
298          if not self.password or self.pformat not in ("plain","md5:user:realm:pass"): 
299              self.__logger.debug("Couldn't get plain password. Password: %r Format: %r" 
300                              % (self.password,self.pformat)) 
301              return Failure("password-unavailable") 
 302   
304          """Make a response for the first challenge from the server. 
305   
306          :Parameters: 
307              - `charset`: charset name from the challenge. 
308              - `realms`: realms list from the challenge. 
309              - `nonce`: nonce value from the challenge. 
310          :Types: 
311              - `charset`: `str` 
312              - `realms`: `str` 
313              - `nonce`: `str` 
314   
315          :return: the response or a failure indicator. 
316          :returntype: `sasl.Response` or `sasl.Failure`""" 
317          params=[] 
318          realm=self._get_realm(realms,charset) 
319          if isinstance(realm,Failure): 
320              return realm 
321          elif realm: 
322              realm=_quote(realm) 
323              params.append('realm="%s"' % (realm,)) 
324   
325          try: 
326              username=self.username.encode(charset) 
327          except UnicodeError: 
328              self.__logger.debug("Couldn't encode username to %r" % (charset,)) 
329              return Failure("incompatible-charset") 
330   
331          username=_quote(username) 
332          params.append('username="%s"' % (username,)) 
333   
334          cnonce=self.password_manager.generate_nonce() 
335          cnonce=_quote(cnonce) 
336          params.append('cnonce="%s"' % (cnonce,)) 
337   
338          params.append('nonce="%s"' % (_quote(nonce),)) 
339   
340          self.nonce_count+=1 
341          nonce_count="%08x" % (self.nonce_count,) 
342          params.append('nc=%s' % (nonce_count,)) 
343   
344          params.append('qop=auth') 
345   
346          serv_type=self.password_manager.get_serv_type().encode("us-ascii") 
347          host=self.password_manager.get_serv_host().encode("us-ascii") 
348          serv_name=self.password_manager.get_serv_name().encode("us-ascii") 
349   
350          if serv_name and serv_name != host: 
351              digest_uri="%s/%s/%s" % (serv_type,host,serv_name) 
352          else: 
353              digest_uri="%s/%s" % (serv_type,host) 
354   
355          digest_uri=_quote(digest_uri) 
356          params.append('digest-uri="%s"' % (digest_uri,)) 
357   
358          if self.authzid: 
359              try: 
360                  authzid=self.authzid.encode(charset) 
361              except UnicodeError: 
362                  self.__logger.debug("Couldn't encode authzid to %r" % (charset,)) 
363                  return Failure("incompatible-charset") 
364              authzid=_quote(authzid) 
365          else: 
366              authzid="" 
367   
368          if self.pformat=="md5:user:realm:pass": 
369              urp_hash=self.password 
370          else: 
371              urp_hash=_make_urp_hash(username,realm,self.password) 
372   
373          response=_compute_response(urp_hash,nonce,cnonce,nonce_count, 
374                              authzid,digest_uri) 
375          self.response_auth=_compute_response_auth(urp_hash,nonce,cnonce, 
376                              nonce_count,authzid,digest_uri) 
377          params.append('response=%s' % (response,)) 
378          if authzid: 
379              params.append('authzid="%s"' % (authzid,)) 
380          return Response(",".join(params)) 
 381   
383          """Choose a realm from the list specified by the server. 
384   
385          :Parameters: 
386              - `realms`: the realm list. 
387              - `charset`: encoding of realms on the list. 
388          :Types: 
389              - `realms`: `list` of `str` 
390              - `charset`: `str` 
391   
392          :return: the realm chosen or a failure indicator. 
393          :returntype: `str` or `Failure`""" 
394          if realms: 
395              realms=[unicode(r,charset) for r in realms] 
396              realm=self.password_manager.choose_realm(realms) 
397          else: 
398              realm=self.password_manager.choose_realm([]) 
399          if realm: 
400              if type(realm) is unicode: 
401                  try: 
402                      realm=realm.encode(charset) 
403                  except UnicodeError: 
404                      self.__logger.debug("Couldn't encode realm to %r" % (charset,)) 
405                      return Failure("incompatible-charset") 
406              elif charset!="utf-8": 
407                  try: 
408                      realm=unicode(realm,"utf-8").encode(charset) 
409                  except UnicodeError: 
410                      self.__logger.debug("Couldn't encode realm from utf-8 to %r" 
411                                          % (charset,)) 
412                      return Failure("incompatible-charset") 
413              self.realm=realm 
414          return realm 
 415   
417          """Process the second challenge from the server and return the response. 
418   
419          :Parameters: 
420              - `challenge`: the challenge from server. 
421          :Types: 
422              - `challenge`: `str` 
423   
424          :return: the response or a failure indicator. 
425          :returntype: `sasl.Response` or `sasl.Failure`""" 
426          if self.rspauth_checked: 
427              return Failure("extra-challenge") 
428          challenge=challenge.split('\x00')[0] 
429          rspauth=None 
430          while challenge: 
431              m=_param_re.match(challenge) 
432              if not m: 
433                  self.__logger.debug("Challenge syntax error: %r" % (challenge,)) 
434                  return Failure("bad-challenge") 
435              challenge=m.group("rest") 
436              var=m.group("var") 
437              val=m.group("val") 
438              self.__logger.debug("%r: %r" % (var,val)) 
439              if var=="rspauth": 
440                  rspauth=val 
441          if not rspauth: 
442              self.__logger.debug("Final challenge without rspauth") 
443              return Failure("bad-success") 
444          if rspauth==self.response_auth: 
445              self.rspauth_checked=1 
446              return Response("") 
447          else: 
448              self.__logger.debug("Wrong rspauth value - peer is cheating?") 
449              self.__logger.debug("my rspauth: %r" % (self.response_auth,)) 
450              return Failure("bad-success") 
 451   
453          """Process success indicator from the server. 
454   
455          Process any addiitional data passed with the success. 
456          Fail if the server was not authenticated. 
457   
458          :Parameters: 
459              - `data`: an optional additional data with success. 
460          :Types: 
461              - `data`: `str` 
462   
463          :return: success or failure indicator. 
464          :returntype: `sasl.Success` or `sasl.Failure`""" 
465          if not self.response_auth: 
466              self.__logger.debug("Got success too early") 
467              return Failure("bad-success") 
468          if self.rspauth_checked: 
469              return Success(self.username,self.realm,self.authzid) 
470          else: 
471              r = self._final_challenge(data) 
472              if isinstance(r, Failure): 
473                  return r 
474              if self.rspauth_checked: 
475                  return Success(self.username,self.realm,self.authzid) 
476              else: 
477                  self.__logger.debug("Something went wrong when processing additional data with success?") 
478                  return Failure("bad-success") 
  479   
481      """Provides DIGEST-MD5 SASL authentication for a server.""" 
482   
484          """Initialize a `DigestMD5ServerAuthenticator` object. 
485   
486          :Parameters: 
487              - `password_manager`: name of the password manager object providing 
488                authentication credential verification. 
489          :Types: 
490              - `password_manager`: `PasswordManager`""" 
491          ServerAuthenticator.__init__(self,password_manager) 
492          self.nonce=None 
493          self.username=None 
494          self.realm=None 
495          self.authzid=None 
496          self.done=None 
497          self.last_nonce_count=None 
498          self.__logger=logging.getLogger("pyxmpp.sasl.DigestMD5ServerAuthenticator") 
 499   
500 -    def start(self,response): 
 501          """Start the authentication process. 
502   
503          :Parameters: 
504              - `response`: the initial response from the client (empty for 
505                DIGEST-MD5). 
506          :Types: 
507              - `response`: `str` 
508   
509          :return: a challenge, a success indicator or a failure indicator. 
510          :returntype: `sasl.Challenge`, `sasl.Success` or `sasl.Failure`""" 
511          _unused = response 
512          self.last_nonce_count=0 
513          params=[] 
514          realms=self.password_manager.get_realms() 
515          if realms: 
516              self.realm=_quote(realms[0]) 
517              for r in realms: 
518                  r=_quote(r) 
519                  params.append('realm="%s"' % (r,)) 
520          else: 
521              self.realm=None 
522          nonce=_quote(self.password_manager.generate_nonce()) 
523          self.nonce=nonce 
524          params.append('nonce="%s"' % (nonce,)) 
525          params.append('qop="auth"') 
526          params.append('charset=utf-8') 
527          params.append('algorithm=md5-sess') 
528          self.authzid=None 
529          self.done=0 
530          return Challenge(",".join(params)) 
 531   
533          """Process a client reponse. 
534   
535          :Parameters: 
536              - `response`: the response from the client. 
537          :Types: 
538              - `response`: `str` 
539   
540          :return: a challenge, a success indicator or a failure indicator. 
541          :returntype: `sasl.Challenge`, `sasl.Success` or `sasl.Failure`""" 
542          if self.done: 
543              return Success(self.username,self.realm,self.authzid) 
544          if not response: 
545              return Failure("not-authorized") 
546          return self._parse_response(response) 
 547   
549          """Parse a client reponse and pass to further processing. 
550   
551          :Parameters: 
552              - `response`: the response from the client. 
553          :Types: 
554              - `response`: `str` 
555   
556          :return: a challenge, a success indicator or a failure indicator. 
557          :returntype: `sasl.Challenge`, `sasl.Success` or `sasl.Failure`""" 
558          response=response.split('\x00')[0]  
559          if self.realm: 
560              realm=to_utf8(self.realm) 
561              realm=_quote(realm) 
562          else: 
563              realm=None 
564          username=None 
565          cnonce=None 
566          digest_uri=None 
567          response_val=None 
568          authzid=None 
569          nonce_count=None 
570          while response: 
571              m=_param_re.match(response) 
572              if not m: 
573                  self.__logger.debug("Response syntax error: %r" % (response,)) 
574                  return Failure("not-authorized") 
575              response=m.group("rest") 
576              var=m.group("var") 
577              val=m.group("val") 
578              self.__logger.debug("%r: %r" % (var,val)) 
579              if var=="realm": 
580                  realm=val[1:-1] 
581              elif var=="cnonce": 
582                  if cnonce: 
583                      self.__logger.debug("Duplicate cnonce") 
584                      return Failure("not-authorized") 
585                  cnonce=val[1:-1] 
586              elif var=="qop": 
587                  if val!='auth': 
588                      self.__logger.debug("qop other then 'auth'") 
589                      return Failure("not-authorized") 
590              elif var=="digest-uri": 
591                  digest_uri=val[1:-1] 
592              elif var=="authzid": 
593                  authzid=val[1:-1] 
594              elif var=="username": 
595                  username=val[1:-1] 
596              elif var=="response": 
597                  response_val=val 
598              elif var=="nc": 
599                  nonce_count=val 
600                  self.last_nonce_count+=1 
601                  if int(nonce_count)!=self.last_nonce_count: 
602                      self.__logger.debug("bad nonce: %r != %r" 
603                              % (nonce_count,self.last_nonce_count)) 
604                      return Failure("not-authorized") 
605          return self._check_params(username,realm,cnonce,digest_uri, 
606                  response_val,authzid,nonce_count) 
 607   
608 -    def _check_params(self,username,realm,cnonce,digest_uri, 
609              response_val,authzid,nonce_count): 
 610          """Check parameters of a client reponse and pass them to further 
611          processing. 
612   
613          :Parameters: 
614              - `username`: user name. 
615              - `realm`: realm. 
616              - `cnonce`: cnonce value. 
617              - `digest_uri`: digest-uri value. 
618              - `response_val`: response value computed by the client. 
619              - `authzid`: authorization id. 
620              - `nonce_count`: nonce count value. 
621          :Types: 
622              - `username`: `str` 
623              - `realm`: `str` 
624              - `cnonce`: `str` 
625              - `digest_uri`: `str` 
626              - `response_val`: `str` 
627              - `authzid`: `str` 
628              - `nonce_count`: `int` 
629   
630          :return: a challenge, a success indicator or a failure indicator. 
631          :returntype: `sasl.Challenge`, `sasl.Success` or `sasl.Failure`""" 
632          if not cnonce: 
633              self.__logger.debug("Required 'cnonce' parameter not given") 
634              return Failure("not-authorized") 
635          if not response_val: 
636              self.__logger.debug("Required 'response' parameter not given") 
637              return Failure("not-authorized") 
638          if not username: 
639              self.__logger.debug("Required 'username' parameter not given") 
640              return Failure("not-authorized") 
641          if not digest_uri: 
642              self.__logger.debug("Required 'digest_uri' parameter not given") 
643              return Failure("not-authorized") 
644          if not nonce_count: 
645              self.__logger.debug("Required 'nc' parameter not given") 
646              return Failure("not-authorized") 
647          return self._make_final_challenge(username,realm,cnonce,digest_uri, 
648                  response_val,authzid,nonce_count) 
 649   
652          """Send the second challenge in reply to the client response. 
653   
654          :Parameters: 
655              - `username`: user name. 
656              - `realm`: realm. 
657              - `cnonce`: cnonce value. 
658              - `digest_uri`: digest-uri value. 
659              - `response_val`: response value computed by the client. 
660              - `authzid`: authorization id. 
661              - `nonce_count`: nonce count value. 
662          :Types: 
663              - `username`: `str` 
664              - `realm`: `str` 
665              - `cnonce`: `str` 
666              - `digest_uri`: `str` 
667              - `response_val`: `str` 
668              - `authzid`: `str` 
669              - `nonce_count`: `int` 
670   
671          :return: a challenge, a success indicator or a failure indicator. 
672          :returntype: `sasl.Challenge`, `sasl.Success` or `sasl.Failure`""" 
673          username_uq=from_utf8(username.replace('\\','')) 
674          if authzid: 
675              authzid_uq=from_utf8(authzid.replace('\\','')) 
676          else: 
677              authzid_uq=None 
678          if realm: 
679              realm_uq=from_utf8(realm.replace('\\','')) 
680          else: 
681              realm_uq=None 
682          digest_uri_uq=digest_uri.replace('\\','') 
683          self.username=username_uq 
684          self.realm=realm_uq 
685          password,pformat=self.password_manager.get_password( 
686                      username_uq,realm_uq,("plain","md5:user:realm:pass")) 
687          if pformat=="md5:user:realm:pass": 
688              urp_hash=password 
689          elif pformat=="plain": 
690              urp_hash=_make_urp_hash(username,realm,password) 
691          else: 
692              self.__logger.debug("Couldn't get password.") 
693              return Failure("not-authorized") 
694          valid_response=_compute_response(urp_hash,self.nonce,cnonce, 
695                              nonce_count,authzid,digest_uri) 
696          if response_val!=valid_response: 
697              self.__logger.debug("Response mismatch: %r != %r" % (response_val,valid_response)) 
698              return Failure("not-authorized") 
699          s=digest_uri_uq.split("/") 
700          if len(s)==3: 
701              serv_type,host,serv_name=s 
702          elif len(s)==2: 
703              serv_type,host=s 
704              serv_name=None 
705          else: 
706              self.__logger.debug("Bad digest_uri: %r" % (digest_uri_uq,)) 
707              return Failure("not-authorized") 
708          info={} 
709          info["mechanism"]="DIGEST-MD5" 
710          info["username"]=username_uq 
711          info["serv-type"]=serv_type 
712          info["host"]=host 
713          info["serv-name"]=serv_name 
714          if self.password_manager.check_authzid(authzid_uq,info): 
715              rspauth=_compute_response_auth(urp_hash,self.nonce, 
716                              cnonce,nonce_count,authzid,digest_uri) 
717              self.authzid=authzid 
718              self.done=1 
719              return Challenge("rspauth="+rspauth) 
720          else: 
721              self.__logger.debug("Authzid check failed") 
722              return Failure("invalid_authzid") 
  723   
724   
725