
#   rhn-client-tools
#
# Copyright (c) 2006--2016 Red Hat, Inc.
#
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; version 2 of the License.
#
#   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 General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
#   02110-1301  USA
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the
# OpenSSL library under certain conditions as described in each
# individual source file, and distribute linked combinations
# including the two.
# You must obey the GNU General Public License in all respects
# for all of the code used other than OpenSSL.  If you modify
# file(s) with this exception, you may extend this exception to your
# version of the file(s), but you are not obligated to do so.  If you
# do not wish to do so, delete this exception statement from your
# version.  If you delete this exception statement from all source
# files in the program, then also delete it here.


from rhn.tb import raise_with_tb
from up2date_client import rpcServer
from up2date_client import up2dateErrors
from up2date_client import capabilities
import sys
import OpenSSL

try: # python2
    import xmlrpclib
except ImportError: # python3
    import xmlrpc.client as xmlrpclib

class _DoCallWrapper(object):

    """
    A callable object that will handle multiple levels of attributes,
    and catch exceptions.
    """

    def __init__(self, server, method_name):
        self._server = server
        self._method_name = method_name

    def __getattr__(self, method_name):
        """ Recursively build up the method name to pass to the server. """
        return _DoCallWrapper(self._server,
            "%s.%s" % (self._method_name, method_name))

    def __call__(self, *args, **kwargs):
        """ Call the method. Catch faults and translate them. """
        method = getattr(self._server, self._method_name)

        try:
            return rpcServer.doCall(method, *args, **kwargs)
        except xmlrpclib.Fault:
            raise_with_tb(self.__exception_from_fault(sys.exc_info()[1]))
        except OpenSSL.SSL.Error:
            # TODO This should probably be moved to rhnlib and raise an
            # exception that subclasses OpenSSL.SSL.Error
            # TODO Is there a better way to detect cert failures?
            error = str(sys.exc_info()[1])
            error = error.strip("[()]")
            pieces = error.split(',')
            message = ""
            if len(pieces) > 2:
                message = pieces[2]
            elif len(pieces) == 2:
                message = pieces[1]
            message = message.strip(" '")
            if message == 'certificate verify failed':
                raise_with_tb(up2dateErrors.SSLCertificateVerifyFailedError())
            else:
                raise_with_tb(up2dateErrors.NetworkError(message))

    def __exception_from_fault(self, fault):
            if fault.faultCode == -3:
                # This username is already taken, or the password is incorrect.
                exception = up2dateErrors.AuthenticationOrAccountCreationError(fault.faultString)
            elif fault.faultCode == -2:
                # Invalid username and password combination.
                exception = up2dateErrors.AuthenticationOrAccountCreationError(fault.faultString)
            elif fault.faultCode == -110:
                # Account is disabled
                exception = up2dateErrors.AuthenticationOrAccountCreationError(fault.faultString)
            elif fault.faultCode == -1:
                exception = up2dateErrors.UnknownMethodException(fault.faultString)
            elif fault.faultCode == -13:
                # Username is too short.
                exception = up2dateErrors.LoginMinLengthError(fault.faultString)
            elif fault.faultCode == -14:
                # too short password
                exception = up2dateErrors.PasswordMinLengthError(
                                          fault.faultString)
            elif fault.faultCode == -15:
                # bad chars in username
                exception = up2dateErrors.ValidationError(fault.faultString)
            elif fault.faultCode == -16:
                # Invalid product registration code.
                # TODO Should this really be a validation error?
                exception = up2dateErrors.ValidationError(fault.faultString)
            elif fault.faultCode == -19:
                # invalid
                exception = up2dateErrors.NoBaseChannelError(fault.faultString)
            elif fault.faultCode == -31:
                # No entitlement
                exception = up2dateErrors.InsuffMgmntEntsError(fault.faultString)
            elif fault.faultCode == -36:
                # rhnException.py says this means "Invalid action."
                # TODO find out which is right
                exception = up2dateErrors.PasswordError(fault.faultString)
            elif abs(fault.faultCode) == 49:
                exception = up2dateErrors.AbuseError(fault.faultString)
            elif abs(fault.faultCode) == 60:
                exception = up2dateErrors.AuthenticationTicketError(fault.faultString)
            elif abs(fault.faultCode) == 74:
                exception = up2dateErrors.RegistrationDeniedError()
            elif abs(fault.faultCode) == 105:
                exception = up2dateErrors.RhnUuidUniquenessError(fault.faultString)
            elif fault.faultCode == 99:
                exception = up2dateErrors.DelayError(fault.faultString)
            elif abs(fault.faultCode) == 91:
                exception = up2dateErrors.InsuffMgmntEntsError(fault.faultString)
            elif fault.faultCode == -106:
                # Invalid username.
                exception = up2dateErrors.ValidationError(fault.faultString)
            elif fault.faultCode == -600:
                # Invalid username.
                exception = up2dateErrors.InvalidRegistrationNumberError(fault.faultString)
            elif fault.faultCode == -601:
                # No entitlements associated with given hardware info
                exception = up2dateErrors.NotEntitlingError(fault.faultString)
            elif fault.faultCode == -602:
                # No entitlements associated with reg num
                exception = up2dateErrors.NotEntitlingError(fault.faultString)
            elif fault.faultCode == -2001 or fault.faultCode == -700:
                exception = up2dateErrors.AuthenticationOrAccountCreationError(
                                          fault.faultString)
            elif fault.faultCode == -701:
                exception = up2dateErrors.PasswordMaxLengthError(
                                          fault.faultString)
            elif fault.faultCode == -61:
                exception = up2dateErrors.ActivationKeyUsageLimitError(
                                          fault.faultString)
            elif fault.faultCode == -5:
                exception = up2dateErrors.UnableToCreateUser(
                            fault.faultString)
            else:
                exception = up2dateErrors.CommunicationError(fault.faultString)

            return exception


class RhnServer(object):
    """
    An rpc server object that calls doCall for you, and catches lower
    level exceptions
    """

    def __init__(
        self,
        serverOverride=None,
        timeout=None,
        rpcServerOverride=None,
        registration=False,
    ):
        if rpcServerOverride is None:
            self._server = rpcServer.getServer(
                serverOverride=serverOverride,
                timeout=timeout,
                registration=registration,
            )
        else:
            self._server = rpcServerOverride
        self._capabilities = None

    def __get_capabilities(self):
        if self._capabilities is None:
            headers = self._server.get_response_headers()
            if headers is None:
                self.registration.welcome_message()
                headers = self._server.get_response_headers()
            self._capabilities = capabilities.Capabilities()
            self._capabilities.populate(headers)
        return self._capabilities

    capabilities = property(__get_capabilities)

    def add_header(self, key, value):
        self._server.add_header(key, value)

    def __getattr__(self, method_name):
        """Return a callable object that will do the work for us."""
        return _DoCallWrapper(self._server, method_name)


class RegistrationRhnServer(RhnServer):
    """
    A specialized RhnServer subclass for handling registration-related calls.

    Intended to be used as a shorthand for registration tasks instead of
    the plain RhnServer.
    """

    def __init__(self, serverOverride=None, timeout=None, rpcServerOverride=None):
        super(RegistrationRhnServer, self).__init__(
            serverOverride=serverOverride,
            timeout=timeout,
            rpcServerOverride=rpcServerOverride,
            registration=True,
        )
