pyxmpp.sasl.digest_md5

Package pyxmpp :: Package sasl :: Module digest_md5 |
[hide private]

# Source Code for Module pyxmpp.sasl.digest_md5

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