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