<commit_before>#
# (C) Copyright 2003-2006 <NAME> <<EMAIL>>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License Version
# 2.1 as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
"""DIGEST-MD5 authentication mechanism for PyXMPP SASL implementation.

Normative reference:
  - `RFC 2831 <http://www.ietf.org/rfc/rfc2831.txt>`__
"""

__revision__="$Id$"
__docformat__="restructuredtext en"

from binascii import b2a_hex
import re
import md5
import logging

from pyxmpp.sasl.core import ClientAuthenticator,ServerAuthenticator
from pyxmpp.sasl.core import Failure,Response,Challenge,Success,Failure

from pyxmpp.utils import to_utf8,from_utf8

quote_re=re.compile(r"(?<!\\)\\(.)")

def _unquote(s):
    """Unquote quoted value from DIGEST-MD5 challenge or response.

    If `s` doesn't start or doesn't end with '"' then return it unchanged,
    remove the quotes and escape backslashes otherwise.

    :Parameters:
        - `s`: a quoted string.
    :Types:
        - `s`: `str`

    :return: the unquoted string.
    :returntype: `str`"""
    if not s.startswith('"') or not s.endswith('"'):
        return s
    return quote_re.sub(r"\1",s[1:-1])

def _quote(s):
    """Prepare a string for quoting for DIGEST-MD5 challenge or response.

    Don't add the quotes, only escape '"' and "\\" with backslashes.

    :Parameters:
        - `s`: a raw string.
    :Types:
        - `s`: `str`

    :return: `s` with '"' and "\\" escaped using "\\".
    :returntype: `str`"""
    s=s.replace('\\','\\\\')
    s=s.replace('"','\\"')
    return '%s' % (s,)

def _h_value(s):
    """H function of the DIGEST-MD5 algorithm (MD5 sum).

    :Parameters:
        - `s`: a string.
    :Types:
        - `s`: `str`

    :return: MD5 sum of the string.
    :returntype: `str`"""
    return md5.new(s).digest()

def _kd_value(k,s):
    """KD function of the DIGEST-MD5 algorithm.

    :Parameters:
        - `k`: a string.
        - `s`: a string.
    :Types:
        - `k`: `str`
        - `s`: `str`

    :return: MD5 sum of the strings joined with ':'.
    :returntype: `str`"""
    return _h_value("%s:%s" % (k,s))

def _make_urp_hash(username,realm,passwd):
    """Compute MD5 sum of username:realm:password.

    :Parameters:
        - `username`: a username.
        - `realm`: a realm.
        - `passwd`: a password.
    :Types:
        - `username`: `str`
        - `realm`: `str`
        - `passwd`: `str`

    :return: the MD5 sum of the parameters joined with ':'.
    :returntype: `str`"""
    if realm is None:
        realm=""
    if type(passwd) is unicode:
        passwd=passwd.encode("utf-8")
    return _h_value("%s:%s:%s" % (username,realm,passwd))

def _compute_response(urp_hash,nonce,cnonce,nonce_count,authzid,digest_uri):
    """Compute DIGEST-MD5 response value.

    :Parameters:
        - `urp_hash`: MD5 sum of username:realm:password.
        - `nonce`: nonce value from a server challenge.
        - `cnonce`: cnonce value from the client response.
        - `nonce_count`: nonce count value.
        - `authzid`: authorization id.
        - `digest_uri`: digest-uri value.
    :Types:
        - `urp_hash`: `str`
        - `nonce`: `str`
        - `nonce_count`: `int`
        - `authzid`: `str`
        - `digest_uri`: `str`

    :return: the computed response value.
    :returntype: `str`"""
    if authzid:
        a1="%s:%s:%s:%s" % (urp_hash,nonce,cnonce,authzid)
    else:
        a1="%s:%s:%s" % (urp_hash,nonce,cnonce)
    a2="AUTHENTICATE:"+digest_uri
    return b2a_hex(_kd_value( b2a_hex(_h_value(a1)),"%s:%s:%s:%s:%s" % (
            nonce,nonce_count,
            cnonce,"auth",b2a_hex(_h_value(a2)) ) ))

def _compute_response_auth(urp_hash,nonce,cnonce,nonce_count,authzid,digest_uri):
    """Compute DIGEST-MD5 rspauth value.

    :Parameters:
        - `urp_hash`: MD5 sum of username:realm:password.
        - `nonce`: nonce value from a server challenge.
        - `cnonce`: cnonce value from the client response.
        - `nonce_count`: nonce count value.
        - `authzid`: authorization id.
        - `digest_uri`: digest-uri value.
    :Types:
        - `urp_hash`: `str`
        - `nonce`: `str`
        - `nonce_count`: `int`
        - `authzid`: `str`
        - `digest_uri`: `str`

    :return: the computed rspauth value.
    :returntype: `str`"""
    if authzid:
        a1="%s:%s:%s:%s" % (urp_hash,nonce,cnonce,authzid)
    else:
        a1="%s:%s:%s" % (urp_hash,nonce,cnonce)
    a2=":"+digest_uri
    return b2a_hex(_kd_value( b2a_hex(_h_value(a1)),"%s:%s:%s:%s:%s" % (
            nonce,nonce_count,
            cnonce,"auth",b2a_hex(_h_value(a2)) ) ))

_param_re=re.compile(r'^(?P<var>[^=]+)\=(?P<val>(\"(([^"\\]+)|(\\\")'
        r'|(\\\\))+\")|([^",]+))(\,(?P<rest>.*))?$')

class DigestMD5ClientAuthenticator(ClientAuthenticator):
    """Provides PLAIN SASL authentication for a client.
<commit_msg>- allow spaces between challenge and response parameters (RFC 2831, section 7.1, '#' rule; needed for jabberd2 compatibility)<commit_after>#
# (C) Copyright 2003-2006 <NAME> <<EMAIL>>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License Version
# 2.1 as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
"""DIGEST-MD5 authentication mechanism for PyXMPP SASL implementation.

Normative reference:
  - `RFC 2831 <http://www.ietf.org/rfc/rfc2831.txt>`__
"""

__revision__="$Id$"
__docformat__="restructuredtext en"

from binascii import b2a_hex
import re
import md5
import logging

from pyxmpp.sasl.core import ClientAuthenticator,ServerAuthenticator
from pyxmpp.sasl.core import Failure,Response,Challenge,Success,Failure

from pyxmpp.utils import to_utf8,from_utf8

quote_re=re.compile(r"(?<!\\)\\(.)")

def _unquote(s):
    """Unquote quoted value from DIGEST-MD5 challenge or response.

    If `s` doesn't start or doesn't end with '"' then return it unchanged,
    remove the quotes and escape backslashes otherwise.

    :Parameters:
        - `s`: a quoted string.
    :Types:
        - `s`: `str`

    :return: the unquoted string.
    :returntype: `str`"""
    if not s.startswith('"') or not s.endswith('"'):
        return s
    return quote_re.sub(r"\1",s[1:-1])

def _quote(s):
    """Prepare a string for quoting for DIGEST-MD5 challenge or response.

    Don't add the quotes, only escape '"' and "\\" with backslashes.

    :Parameters:
        - `s`: a raw string.
    :Types:
        - `s`: `str`

    :return: `s` with '"' and "\\" escaped using "\\".
    :returntype: `str`"""
    s=s.replace('\\','\\\\')
    s=s.replace('"','\\"')
    return '%s' % (s,)

def _h_value(s):
    """H function of the DIGEST-MD5 algorithm (MD5 sum).

    :Parameters:
        - `s`: a string.
    :Types:
        - `s`: `str`

    :return: MD5 sum of the string.
    :returntype: `str`"""
    return md5.new(s).digest()

def _kd_value(k,s):
    """KD function of the DIGEST-MD5 algorithm.

    :Parameters:
        - `k`: a string.
        - `s`: a string.
    :Types:
        - `k`: `str`
        - `s`: `str`

    :return: MD5 sum of the strings joined with ':'.
    :returntype: `str`"""
    return _h_value("%s:%s" % (k,s))

def _make_urp_hash(username,realm,passwd):
    """Compute MD5 sum of username:realm:password.

    :Parameters:
        - `username`: a username.
        - `realm`: a realm.
        - `passwd`: a password.
    :Types:
        - `username`: `str`
        - `realm`: `str`
        - `passwd`: `str`

    :return: the MD5 sum of the parameters joined with ':'.
    :returntype: `str`"""
    if realm is None:
        realm=""
    if type(passwd) is unicode:
        passwd=passwd.encode("utf-8")
    return _h_value("%s:%s:%s" % (username,realm,passwd))

def _compute_response(urp_hash,nonce,cnonce,nonce_count,authzid,digest_uri):
    """Compute DIGEST-MD5 response value.

    :Parameters:
        - `urp_hash`: MD5 sum of username:realm:password.
        - `nonce`: nonce value from a server challenge.
        - `cnonce`: cnonce value from the client response.
        - `nonce_count`: nonce count value.
        - `authzid`: authorization id.
        - `digest_uri`: digest-uri value.
    :Types:
        - `urp_hash`: `str`
        - `nonce`: `str`
        - `nonce_count`: `int`
        - `authzid`: `str`
        - `digest_uri`: `str`

    :return: the computed response value.
    :returntype: `str`"""
    if authzid:
        a1="%s:%s:%s:%s" % (urp_hash,nonce,cnonce,authzid)
    else:
        a1="%s:%s:%s" % (urp_hash,nonce,cnonce)
    a2="AUTHENTICATE:"+digest_uri
    return b2a_hex(_kd_value( b2a_hex(_h_value(a1)),"%s:%s:%s:%s:%s" % (
            nonce,nonce_count,
            cnonce,"auth",b2a_hex(_h_value(a2)) ) ))

def _compute_response_auth(urp_hash,nonce,cnonce,nonce_count,authzid,digest_uri):
    """Compute DIGEST-MD5 rspauth value.

    :Parameters:
        - `urp_hash`: MD5 sum of username:realm:password.
        - `nonce`: nonce value from a server challenge.
        - `cnonce`: cnonce value from the client response.
        - `nonce_count`: nonce count value.
        - `authzid`: authorization id.
        - `digest_uri`: digest-uri value.
    :Types:
        - `urp_hash`: `str`
        - `nonce`: `str`
        - `nonce_count`: `int`
        - `authzid`: `str`
        - `digest_uri`: `str`

    :return: the computed rspauth value.
    :returntype: `str`"""
    if authzid:
        a1="%s:%s:%s:%s" % (urp_hash,nonce,cnonce,authzid)
    else:
        a1="%s:%s:%s" % (urp_hash,nonce,cnonce)
    a2=":"+digest_uri
    return b2a_hex(_kd_value( b2a_hex(_h_value(a1)),"%s:%s:%s:%s:%s" % (
            nonce,nonce_count,
            cnonce,"auth",b2a_hex(_h_value(a2)) ) ))

_param_re=re.compile(r'^(?P<var>[^=]+)\=(?P<val>(\"(([^"\\]+)|(\\\")'
        r'|(\\\\))+\")|([^",]+))(\s*\,\s*(?P<rest>.*))?$')

class DigestMD5ClientAuthenticator(ClientAuthenticator):
    """Provides PLAIN SASL authentication for a client.

{'fraction_non_alphanumeric': 0.15828130738156446, 'fraction_numerical': 0.012761659933896437, 'mean_word_length': 2.851838755304102, 'pattern_counts': {'":': 2, '<': 19, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '87890', 'n_tokens_mistral': 4110, 'n_tokens_neox': 3857, 'n_words': 1006}