# -*- coding: utf-8 -*-

# Copyright 2012-2016 Mir Calculate. http://www.calculate-linux.org
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

import urllib2 as u2
import os
import sys
from sudsds.transport.http import HttpTransport
import httplib
from httplib import HTTPConnection
import socket
import ssl
import hashlib
from calculate.core.datavars import DataVarsCore
from calculate.lib.datavars import DataVars
from calculate.lib.utils.files import readFile
from sudsds.client import Client
from cert_verify import verify, get_CRL

if hasattr(ssl, "PROTOCOL_TLSv1_2"):
    ssl_version = ssl.PROTOCOL_TLSv1_2
else:
    ssl_version = None

_ = lambda x: x
from calculate.lib.cl_lang import setLocalTranslate

setLocalTranslate('cl_core3', sys.modules[__name__])

flag = 0


class Client_suds(Client):
    def set_parameters(self, path_to_cert, CERT_FILE, PKEY_FILE):
        self.path_to_cert = path_to_cert
        if not CERT_FILE:
            CERT_FILE = ''
        self.CERT_FILE = CERT_FILE
        self.REQ_FILE = path_to_cert + 'client.csr'
        self.PKEY_FILE = PKEY_FILE
        self.SID_FILE = path_to_cert + 'sid.int'
        self.CRL_PATH = path_to_cert + 'ca/crl/'
        if not os.path.exists(self.CRL_PATH):
            os.makedirs(self.CRL_PATH)


class clientHTTPSConnection(httplib.HTTPSConnection):
    def __init__(self, host, port=None, key_file=None, cert_file=None,
                 strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
                 source_address=None, cert_path=None):
        HTTPConnection.__init__(self, host, port, strict, timeout,
                                source_address)
        self.key_file = key_file
        self.cert_file = cert_file
        self.cert_path = cert_path
        self.CRL_PATH = cert_path + 'ca/crl/'

    # get filename store cert server
    def cert_list(self, host, ca_certs, server_cert):
        if host == '127.0.0.1':
            host = 'localhost'
        if not os.path.exists(self.trusted_path):
            try:
                os.makedirs(self.trusted_path)
            except OSError:
                pass
        if not os.path.exists(ca_certs):
            fc = open(ca_certs, "w")
            fc.close()
        filename = None
        try:
            with open(ca_certs) as fd:
                t = fd.read()
                # for each line
                for line in t.splitlines():
                    # Split string into a words list
                    words = line.split()
                    if len(words) > 1:
                        # if first word...
                        if words[0] == host:
                            filename = words[1]
            if not filename:
                return None
        except (IndexError, IOError):
            print _("Certificate not found on the client's side")
            return None
        try:
            with open(self.trusted_path + filename, 'r') as fd:
                store_cert = fd.read()
                if store_cert == server_cert:
                    return filename
        except IOError:
            print _("Failed to open the file"), self.trusted_path, filename
        return None

    def add_all_ca_cert(self, list_ca_certs):
        # so root cert be first, ca after
        clVarsCore = DataVarsCore()
        clVarsCore.importCore()
        clVarsCore.flIniFile()

        list_ca_certs.reverse()
        import OpenSSL

        for cert in list_ca_certs:
            system_ca_db = clVarsCore.Get('cl_glob_root_cert')
            if os.path.exists(system_ca_db):
                if cert in readFile(system_ca_db):
                    continue

            clVars = DataVars()
            clVars.flIniFile()
            homePath = clVars.Get('ur_home_path')

            user_root_cert = clVarsCore.Get('cl_user_root_cert')
            user_root_cert = user_root_cert.replace("~", homePath)
            if os.path.exists(user_root_cert):
                if cert in readFile(user_root_cert):
                    continue

            cl_client_cert_dir = clVarsCore.Get('cl_client_cert_dir')

            cl_client_cert_dir = cl_client_cert_dir.replace("~", homePath)
            root_cert_md5 = cl_client_cert_dir + "/ca/cert_list"

            md5 = hashlib.md5()
            md5.update(cert)
            md5sum = md5.hexdigest()
            print "\n================================================="
            print "md5sum = ", md5sum

            if not os.path.exists(root_cert_md5):
                fc = open(root_cert_md5, "w")
                fc.close()

            filename = None
            with open(root_cert_md5) as fd:
                t = fd.read()
                # for each line
                for line in t.splitlines():
                    # Split string into a words list
                    words = line.split(' ', 1)
                    if words[0] == md5sum:
                        filename = words[1]
            if not filename:
                certobj = OpenSSL.crypto.load_certificate(
                    OpenSSL.SSL.FILETYPE_PEM, cert)
                Issuer = certobj.get_issuer().get_components()
                for item in Issuer:
                    if item[0] == 'CN':
                        filename = item[1]

                fc = open(root_cert_md5, "a")
                fc.write('%s %s\n' % (md5sum, filename))
                fc.close()

                if not filename:
                    print _('Field "CN" not found in the certificate!')
                    return 1

                fd = open(cl_client_cert_dir + '/ca/' + filename, 'w')
                fd.write(cert)
                fd.close()

                fa = open(user_root_cert, 'a')
                fa.write(cert)
                fa.close()
                print _("filename = "), filename
                print _("Certificate added")
            else:
                print _("file containing the CA certificate now exists")
            get_CRL(cl_client_cert_dir + '/')

    def add_ca_cert(self, cert, list_ca_certs):
        url = 'https://%s:%s/?wsdl' % (self.host, self.port)
        client = Client_suds(url, transport=HTTPSClientCertTransport(
            None, None, self.cert_path))
        cert = client.service.get_ca()
        if cert == '1':
            print _("Invalid server certificate!")
            sys.exit()

        if cert == '2':
            print _("CA certificate not found on the server")
            sys.exit()

        import OpenSSL

        try:
            certobj = OpenSSL.crypto.load_certificate(
                OpenSSL.SSL.FILETYPE_PEM, cert)
        except (OpenSSL.SSL.Error, IOError):
            print _("Error. Certificate not added to the trusted list.")
            sys.exit()
        print '\n' + _("Fingerprint = %s") % certobj.digest('SHA1')
        print _("Serial number = "), certobj.get_serial_number()
        Issuer = certobj.get_issuer().get_components()
        print '\n' + _("Issuer")
        for i in Issuer:
            print "%s : %s" % (i[0], i[1])
        Subject = certobj.get_subject().get_components()
        print '\n' + _("Subject")
        for subj in Subject:
            print "%s : %s" % (subj[0], subj[1])
        ans = raw_input(_("Add the CA certificate to trusted? y/[n]:"))
        if ans.lower() in ['y', 'yes']:
            list_ca_certs.append(cert)
            self.add_all_ca_cert(list_ca_certs)
        else:
            print _("Certificate not added to trusted")
        sys.exit()

    # add certificate server in trusted
    def add_server_cert(self, cert):
        import OpenSSL

        print _("Untrusted server certificate!")
        certobj = OpenSSL.crypto.load_certificate(OpenSSL.SSL.FILETYPE_PEM,
                                                  cert)
        print '\n' + _("Fingerprint = %s") % certobj.digest('SHA1')
        print _("Serial number = "), certobj.get_serial_number()
        Issuer = certobj.get_issuer().get_components()
        print '\n' + _("Issuer")
        for i in Issuer:
            print "%s : %s" % (i[0], i[1])
        Subject = certobj.get_subject().get_components()
        print '\n' + _("Subject")
        for item in Subject:
            print "%s : %s" % (item[0], item[1])

        print '\n' + _('Add this server certificate to trusted (s) or')
        print _('Try to add the CA and root certificates to trusted (c) or')
        choice = raw_input(_("Quit (q)? s/c/[q]: "))
        if choice.lower() in ['s', 'c']:
            # self.sock = ssl.wrap_socket(sock)
            ca_certs = self.trusted_path + "cert.list"

            if not os.path.exists(ca_certs):
                fc = open(ca_certs, "w")
                fc.close()

            if self.host == '127.0.0.1':
                host = 'localhost'
            else:
                host = self.host
            filename = host
            fc = open(self.trusted_path + filename, "w")
            fc.write(cert)
            fc.close()
            with open(ca_certs) as fd:
                t = fd.read()
                # for each line
                for line in t.splitlines():
                    # Split string into a words list
                    words = line.split()
                    if len(words) > 1:
                        # if first word...
                        if words[0] == host:
                            return 0
            # Open file with compliance server certificates and server hostname
            fcl = open(ca_certs, "a")
            fcl.write(host + ' ' + filename + '\n')
            fcl.close()
        if choice.lower() == 'c':
            clVars = DataVarsCore()
            clVars.importCore()
            clVars.flIniFile()
            cl_client_cert_dir = clVars.Get('cl_client_cert_dir')
            homePath = clVars.Get('ur_home_path')
            cl_client_cert_dir = cl_client_cert_dir.replace("~", homePath)
            root_cert_dir = cl_client_cert_dir + "/ca"

            if not os.path.exists(root_cert_dir):
                try:
                    os.makedirs(root_cert_dir)
                except OSError:
                    print _("Error creating directory %s") % root_cert_dir
                    sys.exit()

            print '\n' + _("Add the CA and root certificates")
            self.list_ca_certs = []
            self.add_ca_cert(cert, self.list_ca_certs)
            sys.exit()
        else:
            sys.exit()

    def connect_trusted_root(self, sock, root_cert, crl_certs):
        self.ca_path = self.cert_path + "ca/"
        server_cert = ssl.get_server_certificate(addr=(self.host, self.port))
        global flag

        if self.cert_file:
            f = verify(server_cert, crl_certs, flag)
            if not f:
                flag = 1
            elif f == 1:
                sys.exit()
        else:
            import time

            time.sleep(1)

        try:
            if ssl_version is None:
                print _("SSL library is not support TLSv1_2")
                self.sock.close()
                return 2

            self.sock = ssl.wrap_socket(sock,
                                        certfile=self.cert_file,
                                        keyfile=self.key_file,
                                        ca_certs=root_cert,
                                        ssl_version=ssl_version,
                                        cert_reqs=ssl.CERT_REQUIRED)

            dercert_after_connect = self.sock.getpeercert(True)
            cert_after_connect = \
                ssl.DER_cert_to_PEM_cert(dercert_after_connect)
            if not server_cert == cert_after_connect:
                print ('\n' +
                       _("WARNING! %s trying to replace the certificate!")
                       % self.host + '\n')
                self.sock.close()
                return 2
            return 0
        except Exception:
            return 1

    def connect_trusted_server(self, sock, crl_certs):
        self.trusted_path = self.cert_path + "trusted/"
        ca_cert_list = self.trusted_path + "cert.list"
        server_cert = ssl.get_server_certificate(addr=(self.host, self.port))
        global flag
        if self.cert_file:
            f = verify(server_cert, crl_certs, flag)
            if not f:
                flag = 1
            elif f == 1:
                sys.exit()
        HTTPSClientCertTransport.filename = self.cert_list(
            self.host, ca_cert_list, server_cert)
        if HTTPSClientCertTransport.filename:
            try:
                if ssl_version is None:
                    print _("SSL library is not support TLSv1_2")
                    HTTPSClientCertTransport.filename = None
                    self.sock.close()
                    return 1

                self.sock = ssl.wrap_socket(sock,
                                            certfile=self.cert_file,
                                            keyfile=self.key_file,
                                            ssl_version=ssl_version,
                                            cert_reqs=ssl.CERT_NONE)
                dercert_after_connect = self.sock.getpeercert(True)
                ssl.DER_cert_to_PEM_cert(dercert_after_connect)
                return 0
            except Exception as e:
                print e
                HTTPSClientCertTransport.filename = None
                return 1
        else:
            self.add_server_cert(server_cert)

    def connect(self):
        """Connect to a host on a given (SSL) port."""
        if self.host == '127.0.0.1':
            self.host = 'localhost'
        sock = socket.create_connection((self.host, self.port),
                                        self.timeout, self.source_address)
        if self._tunnel_host:
            self.sock = sock
            self._tunnel()

        clVars = DataVarsCore()
        clVars.importCore()
        clVars.flIniFile()

        user_root_cert = clVars.Get('cl_user_root_cert')
        homePath = clVars.Get('ur_home_path')
        user_root_cert = user_root_cert.replace("~", homePath)
        result_user_root = 1

        if os.path.exists(user_root_cert):
            result_user_root = self.connect_trusted_root(sock, user_root_cert,
                                                         self.CRL_PATH)
            # print 'rur = ',result_user_root
        if result_user_root == 1:
            glob_root_cert = clVars.Get('cl_glob_root_cert')
            result_root_con = 1
            if os.path.exists(glob_root_cert):
                sock = socket.create_connection((self.host, self.port),
                                                self.timeout,
                                                self.source_address)
                if self._tunnel_host:
                    self.sock = sock
                    self._tunnel()
                result_root_con = self.connect_trusted_root(sock,
                                                            glob_root_cert,
                                                            self.CRL_PATH)
                # print 'rrc = ',result_root_con
            if result_root_con == 1:
                sock = socket.create_connection((self.host, self.port),
                                                self.timeout,
                                                self.source_address)
                if self._tunnel_host:
                    self.sock = sock
                    self._tunnel()
                result_server_con = self.connect_trusted_server(
                    sock, self.CRL_PATH)
                # print 'rsc = ',result_server_con
                if result_server_con in [1, 2]:
                    raise Exception
                    # sys.exit(1)
            elif result_root_con == 2:
                # sys.exit(1)
                raise Exception
        elif result_user_root == 2:
            # sys.exit(1)
            raise Exception


class HTTPSClientAuthHandler(u2.HTTPSHandler):
    def __init__(self, key, cert, cert_path):
        u2.HTTPSHandler.__init__(self)
        self.key = key
        self.cert = cert
        self.cert_path = cert_path

    def https_open(self, req):
        # Rather than pass in a reference to a connection class, we pass in
        # a reference to a function which, for all intents and purposes,  
        # will behave as a constructor 
        return self.do_open(self.getConnection, req)

    def getConnection(self, host, timeout=300):
        return clientHTTPSConnection(host, key_file=self.key,
                                     cert_file=self.cert,
                                     cert_path=self.cert_path)


class HTTPSClientCertTransport(HttpTransport):
    def __init__(self, key, cert, path_to_cert, *args, **kwargs):
        HttpTransport.__init__(self, *args, **kwargs)
        self.key = key
        self.cert = cert
        self.cert_path = path_to_cert

    def u2open(self, u2request):
        """
        Open a connection.
        @param u2request: A urllib2 request.
        @type u2request: urllib2.Requet.
        @return: The opened file-like urllib2 object.
        @rtype: fp
        """
        tm = self.options.timeout
        url = u2.build_opener(HTTPSClientAuthHandler(self.key, self.cert,
                                                     self.cert_path))

        # from urllib2 import URLError
        # try:
        if hasattr(self, "u2ver"):
            if self.u2ver() < 2.6:
                socket.setdefaulttimeout(tm)
                return url.open(u2request)
            else:
                return url.open(u2request, timeout=tm)
        else:
            return url.open(u2request, timeout=tm)
