<commit_before># Copyright (c) 2012 <NAME>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#

"""Package implementing the ability for client and server SMTP sessions to
authenticate.

For servers, use |Auth| as a base class and override the
:meth:`~Auth.verify_secret()` and :meth:`~Auth.get_secret()` methods as needed.
To modify which auth mechanisms are advertised in clear- and encrypted
channels, you can override the :meth:`~Auth.get_available_mechanisms()` method
as well. Your |Auth| sub-class may be passed in to your
:class:`~slimta.smtp.server.Server` or :class:`~slimta.edge.smtp.SmtpEdge`.

"""

from __future__ import absolute_import

import re

from .. import SmtpError
from ..reply import Reply

__all__ = ['CredentialsInvalidError', 'Auth',
           'ServerMechanism', 'ClientMechanism']

noarg_pattern = re.compile(r'^([a-zA-Z0-9_-]+)$')
witharg_pattern = re.compile(r'^([a-zA-Z0-9_-]+)\s+(.*)$')


class AuthError(SmtpError):
    pass


class ServerAuthError(AuthError):

    def __init__(self, msg, reply):
        super(ServerAuthError, self).__init__(msg)
        self.reply = reply


class InvalidMechanismError(ServerAuthError):

    def __init__(self, arg):
        msg = 'Invalid authentication mechanism'
        reply = Reply('504', '5.5.4 '+msg)
        super(InvalidMechanismError, self).__init__(msg, reply)


class AuthenticationCanceled(ServerAuthError):

    def __init__(self):
        msg = 'Authentication canceled by client'
        reply = Reply('501', '5.7.0 '+msg)
        super(AuthenticationCanceled, self).__init__(msg, reply)


class CredentialsInvalidError(ServerAuthError):
    """Thrown when a clients credentials were not correct."""

    def __init__(self):
        msg = 'Authentication credentials invalid'
        reply = Reply('535', '5.7.8 '+msg)
        super(CredentialsInvalidError, self).__init__(msg, reply)


class AuthSession(object):

    def __init__(self, auth_obj, session):
        super(AuthSession, self).__init__()
        self.session = session
        self.auth_obj = auth_obj

    def _get_available(self):
        encrypted = self.session.encrypted
        available = self.auth_obj.get_available_mechanisms(encrypted)
        if not hasattr(self.auth_obj, 'get_secret'):
            available = [mech for mech in available
                         if not mech.requires_get_secret]
        return available

    def __str__(self):
        available = self._get_available()
        if available:
            return ' '.join([mech.name for mech in available])
        else:
            raise ValueError('No mechanisms available')

    def _parse_arg(self, arg):
        match = noarg_pattern.match(arg)
        if match:
            return match.group(1).upper(), None
        match = witharg_pattern.match(arg)
        if match:
            return match.group(1).upper(), match.group(2)
        raise InvalidMechanismError(arg)

    def server_attempt(self, io, arg):
        verify_secret = self.auth_obj.verify_secret
        get_secret = getattr(self.auth_obj, 'get_secret', None)
        mechanism_name, mechanism_arg = self._parse_arg(arg)
        for mechanism in self._get_available():
            if mechanism.name == mechanism_name:
                mech_obj = mechanism(verify_secret, get_secret)
                if mechanism_arg == '*':
                    raise AuthenticationCanceled()
                identity = mech_obj.server_attempt(io, mechanism_arg)
                if not identity:
                    msg = 'Invalid identity returned from authentication'
                    raise ValueError(msg)
                return identity
        raise InvalidMechanismError(arg)


class Auth(object):
    """Base class that handles the authentication for an SMTP server session.
    This class must be inherited to define either or both of the
    ``verify_secret`` or ``get_secret`` methods.

    :param session: This argument is maintained for backwards-compatibility.
    :type session: unused

    .. meth:: verify_secret(self, authcid, secret, authzid=None)

       For SMTP server authentication, this must should be overriden to
       verify the given secret for mechanisms that have access to it.  If
       ``secret`` is invalid for the given identities,
       :class:`CredentialsInvalidError` should be thrown.

        If this method is not implemented by an inheriting class,
        ``get_secret`` will be used to verify given auth secrets.

       :param authcid: The authentication identity, usually the username.
       :param secret: The secret (i.e. password) string to verify for the
                      given authentication and authorization identities.
       :param authzid: The authorization identity, if applicable.
       :returns: A representation of the identity that was successfully
                 authenticated. This may be ``authcid``, ``authzid``, a tuple
                 of both, or any alternative.
       :raises: :class:`CredentialsInvalidError`

    .. meth:: get_secret(self, authcid, authzid=None)

       For SMTP server authentication mechanisms such as ``CRAM-MD5``, the
       client provides a hash of their secret credentials, and thus the server
       must also have access to the secret credentials in plain-text. This<commit_msg>fix a sphinx error<commit_after># Copyright (c) 2012 <NAME>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#

"""Package implementing the ability for client and server SMTP sessions to
authenticate.

For servers, use |Auth| as a base class and override the
:meth:`~Auth.verify_secret()` and :meth:`~Auth.get_secret()` methods as needed.
To modify which auth mechanisms are advertised in clear- and encrypted
channels, you can override the :meth:`~Auth.get_available_mechanisms()` method
as well. Your |Auth| sub-class may be passed in to your
:class:`~slimta.smtp.server.Server` or :class:`~slimta.edge.smtp.SmtpEdge`.

"""

from __future__ import absolute_import

import re

from .. import SmtpError
from ..reply import Reply

__all__ = ['CredentialsInvalidError', 'Auth',
           'ServerMechanism', 'ClientMechanism']

noarg_pattern = re.compile(r'^([a-zA-Z0-9_-]+)$')
witharg_pattern = re.compile(r'^([a-zA-Z0-9_-]+)\s+(.*)$')


class AuthError(SmtpError):
    pass


class ServerAuthError(AuthError):

    def __init__(self, msg, reply):
        super(ServerAuthError, self).__init__(msg)
        self.reply = reply


class InvalidMechanismError(ServerAuthError):

    def __init__(self, arg):
        msg = 'Invalid authentication mechanism'
        reply = Reply('504', '5.5.4 '+msg)
        super(InvalidMechanismError, self).__init__(msg, reply)


class AuthenticationCanceled(ServerAuthError):

    def __init__(self):
        msg = 'Authentication canceled by client'
        reply = Reply('501', '5.7.0 '+msg)
        super(AuthenticationCanceled, self).__init__(msg, reply)


class CredentialsInvalidError(ServerAuthError):
    """Thrown when a clients credentials were not correct."""

    def __init__(self):
        msg = 'Authentication credentials invalid'
        reply = Reply('535', '5.7.8 '+msg)
        super(CredentialsInvalidError, self).__init__(msg, reply)


class AuthSession(object):

    def __init__(self, auth_obj, session):
        super(AuthSession, self).__init__()
        self.session = session
        self.auth_obj = auth_obj

    def _get_available(self):
        encrypted = self.session.encrypted
        available = self.auth_obj.get_available_mechanisms(encrypted)
        if not hasattr(self.auth_obj, 'get_secret'):
            available = [mech for mech in available
                         if not mech.requires_get_secret]
        return available

    def __str__(self):
        available = self._get_available()
        if available:
            return ' '.join([mech.name for mech in available])
        else:
            raise ValueError('No mechanisms available')

    def _parse_arg(self, arg):
        match = noarg_pattern.match(arg)
        if match:
            return match.group(1).upper(), None
        match = witharg_pattern.match(arg)
        if match:
            return match.group(1).upper(), match.group(2)
        raise InvalidMechanismError(arg)

    def server_attempt(self, io, arg):
        verify_secret = self.auth_obj.verify_secret
        get_secret = getattr(self.auth_obj, 'get_secret', None)
        mechanism_name, mechanism_arg = self._parse_arg(arg)
        for mechanism in self._get_available():
            if mechanism.name == mechanism_name:
                mech_obj = mechanism(verify_secret, get_secret)
                if mechanism_arg == '*':
                    raise AuthenticationCanceled()
                identity = mech_obj.server_attempt(io, mechanism_arg)
                if not identity:
                    msg = 'Invalid identity returned from authentication'
                    raise ValueError(msg)
                return identity
        raise InvalidMechanismError(arg)


class Auth(object):
    """Base class that handles the authentication for an SMTP server session.
    This class must be inherited to define either or both of the
    ``verify_secret`` or ``get_secret`` methods.

    :param session: This argument is maintained for backwards-compatibility.
    :type session: unused

    .. method:: verify_secret(self, authcid, secret, authzid=None)

       For SMTP server authentication, this must should be overriden to
       verify the given secret for mechanisms that have access to it.  If
       ``secret`` is invalid for the given identities,
       :class:`CredentialsInvalidError` should be thrown.

        If this method is not implemented by an inheriting class,
        ``get_secret`` will be used to verify given auth secrets.

       :param authcid: The authentication identity, usually the username.
       :param secret: The secret (i.e. password) string to verify for the
                      given authentication and authorization identities.
       :param authzid: The authorization identity, if applicable.
       :returns: A representation of the identity that was successfully
                 authenticated. This may be ``authcid``, ``authzid``, a tuple
                 of both, or any alternative.
       :raises: :class:`CredentialsInvalidError`

    .. method:: get_secret(self, authcid, authzid=None)

       For SMTP server authentication mechanisms such as ``CRAM-MD5``, the
       client provides a hash of their secret credentials, and thus the server
       must also have access to the secret credentials in plain-text. This
{'fraction_non_alphanumeric': 0.08528920359614926, 'fraction_numerical': 0.0047736494550083535, 'mean_word_length': 3.1105297580117726, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, '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': '898342', 'n_tokens_mistral': 3426, 'n_tokens_neox': 3268, 'n_words': 1242}