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-2006 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 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
     44 -def _unquote(s):
     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
     61 -def _quote(s):
     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
     77 -def _h_value(s):
     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
     89 -def _kd_value(k,s):
     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
    103 -def _make_urp_hash(username,realm,passwd):
    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
    123 -def _compute_response(urp_hash,nonce,cnonce,nonce_count,authzid,digest_uri):
    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
    151 -def _compute_response_auth(urp_hash,nonce,cnonce,nonce_count,authzid,digest_uri):
    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
    182 -class DigestMD5ClientAuthenticator(ClientAuthenticator):
    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
    191 -    def __init__(self,password_manager):
    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
    235 -    def challenge(self,challenge):
    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] # workaround for some buggy implementations
    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
    290 -    def _get_password(self):
    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
    303 -    def _make_response(self,charset,realms,nonce):
    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
    382 -    def _get_realm(self,realms,charset):
    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
    416 -    def _final_challenge(self,challenge):
    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
    452 -    def finish(self,data):
    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
    480 -class DigestMD5ServerAuthenticator(ServerAuthenticator):
    481      """Provides DIGEST-MD5 SASL authentication for a server."""
    482
    483 -    def __init__(self,password_manager):
    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
    532 -    def response(self,response):
    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
    548 -    def _parse_response(self,response):
    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] # workaround for some SASL implementations
    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
    650 -    def _make_final_challenge(self,username,realm,cnonce,digest_uri,
    651              response_val,authzid,nonce_count):
    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  # vi: sts=4 et sw=4
    725