# -*- 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 cert_cmd
import post_request
import datetime
import subprocess
import shutil

from calculate.core.client.cert_func import new_key_req
from calculate.core.client.function import get_ip_mac_type
from calculate.core.datavars import DataVarsCore
from calculate.lib.utils.files import (makeDirectory, pathJoin, readFile,
                                       writeFile, readFileEx)
from calculate.lib.utils.mount import isMount
from calculate.core.server.admin import Admins
import os
import hashlib
import pwd
import socket
import sys
import re

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

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


def parse_cert_date(date):
    year = int(date[:4])
    month = int(date[4:6])
    day = int(date[6:8])
    hour = int(date[8:10])
    minute = int(date[10:12])
    sec = int(date[12:14])
    return datetime.datetime(year, month, day, hour, minute, sec)


def check(cert, key):
    error_flag = 0
    if not os.path.isfile(cert):
        error_flag = 1
        print _('Certificate %s not found') % cert
    print key, cert
    if not os.path.isfile(key):
        error_flag = 1
        print _('Private key %s not found') % key
    if os.path.isfile(cert) and os.path.isfile(key):
        import OpenSSL
        # check correspondence certificate and private key
        cmd_cert = 'openssl x509 -noout -modulus -in ' + cert
        cmd_key = 'openssl rsa -noout -modulus -in ' + key
        p_cert = subprocess.Popen(cmd_cert.split(), stdout=subprocess.PIPE,
                                  stderr=subprocess.PIPE)
        p_key = subprocess.Popen(cmd_key.split(), stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE)
        if not p_cert.stdout.read().strip() == p_key.stdout.read().strip():
            print _('The certificate does not match the private key')
            error_flag = 1

        # check certificate date
        cert_data = readFile(cert)
        certobj = OpenSSL.crypto.load_certificate(
            OpenSSL.SSL.FILETYPE_PEM, cert_data)
        certobj.get_notBefore()
        try:
            not_after = parse_cert_date(certobj.get_notAfter())
            not_before = parse_cert_date(certobj.get_notBefore())

            date = datetime.datetime.now()

            if not_before > date:
                print _('Certificate creation date later than current date')
                error_flag = 1
            elif not_after < date:
                print _('Certificate expired')
                error_flag = 1
        except ValueError:
            print _('Failed to get certificate work date')
            error_flag = 1

    sys.exit(error_flag)


def init(cert, key, cert_path, data_path, certbase, args, port):
    if args.remove_certificates:
        key_force(cert_path, data_path)

    new_serv_cert = False
    if not check_serv_cert(cert_path):
        print _('Generating the server certificate')
        for step in range(2):
            args = change_args(args, step)
            create_server_cert(cert, key, cert_path, args, port)
        new_serv_cert = True
    else:
        print _('Server certificate now exists.')

    os.chmod(data_path, 0700)

def force_user_cert(server_cert, cert_path, data_path, cert_base, user_name,
                    dv=None):
    def is_crypthome_notmount(dv, username):
        dv.Set('ur_login', user_name, force=True)
        homedir = dv.Get('ur_home_path')
        if (dv.GetBool('ur_home_crypt_set') and
            '.Private' not in isMount(homedir)):
            return True
        return False
        
    if not check_client_cert(user_name, server_cert=server_cert):
        print _('Generating the client certificate')
    else:
        print _('Regenerating the client certificate')
    group = "all"
    if dv:
        admins = Admins(dv)
        if user_name not in admins:
            admins[user_name] = group
            admins.save()
        group = admins[user_name]
        if is_crypthome_notmount(dv, user_name):
            print _("User profile is encrypted. Please perform user login for "
                    "complete of certificate generation")
            return
    create_client_cert(server_cert, cert_path, data_path, cert_base,
                       user_name, group)


def check_serv_cert(cert_path):
    if os.path.isfile(os.path.join(cert_path, 'server.crt')) and \
            os.path.isfile(os.path.join(cert_path, 'server.key')):
        return True
    return False


def check_client_cert(user_name, server_cert=None):
    client_cert_path = check_user_path(user_name)
    if server_cert:
        server_host_name = get_certificate_dn(server_cert)
    else:
        server_host_name = socket.getfqdn()
    crt_fn = os.path.join(client_cert_path, server_host_name + '.crt')
    key_fn = os.path.join(client_cert_path, server_host_name + '.key')
    if os.path.isfile(crt_fn) and os.path.isfile(key_fn):
        return True
    return False


def change_args(args, step=None):
    if step == 0:
        args.host = False
        args.gen_root_cert = True
        args.root_host = False
        args.use_root_cert = False
    elif step == 1:
        args.gen_root_cert = False
        args.use_root_cert = True

    return args


def create_server_cert(cert, key, cert_path, args, port):
    cert_cmd.check_server_certificate(cert, key, cert_path, args, port,
                                      auto=True)


def create_client_cert(server_cert, cert_path, data_path, certbase, user_name,
                       group="all"):
    client_cert_path = check_user_path(user_name)
    if not client_cert_path:
        print _('no path to the client certificate')
        return 1
    req_id = create_request(server_cert, cert_path, data_path, certbase,
                            client_cert_path, user_name)
    sign_certificate(req_id, cert_path, data_path, group)
    get_certificate(cert_path, data_path, certbase, client_cert_path, user_name,
        server_cert=server_cert)


def check_user_path(user_name):
    try:
        pwdObj = pwd.getpwnam(user_name)
    except KeyError as e:
        print e
        return None

    home_dir = pwdObj.pw_dir
    if not os.path.isdir(home_dir):
        if not makeDirectory(home_dir):
            return None
        os.chown(home_dir, pwdObj.pw_uid, pwdObj.pw_gid)
        os.chmod(home_dir, 0700)
    calc_dir = os.path.join(home_dir, '.calculate')
    cert_dir = os.path.join(calc_dir, 'client_cert')
    for directory in [calc_dir, cert_dir]:
        if not os.path.isdir(directory):
            if not makeDirectory(directory):
                return None
        os.chown(directory, pwdObj.pw_uid, pwdObj.pw_gid)
        os.chmod(directory, 0755)

    for path in os.walk(cert_dir):
        os.chown(path[0], pwdObj.pw_uid, pwdObj.pw_gid)
        for _file in path[2]:
            fn = pathJoin(path[0], _file)
            if os.path.isfile(fn):
                os.chown(fn, pwdObj.pw_uid, pwdObj.pw_gid)
                os.chmod(fn, 0644)
    return cert_dir


def create_request(server_cert, cert_path, data_path, certbase,
                   client_cert_path, user_name):
    server_host_name = get_certificate_dn(server_cert)

    key = os.path.join(client_cert_path, server_host_name + '.key')

    client_req_file = new_key_req(key, client_cert_path, server_host_name,
                                  auto=True)

    try:
        pwdObj = pwd.getpwnam(user_name)
    except KeyError as e:
        print e
        return None
    for files in [client_req_file, key + '_pub']:
        if os.path.exists(files):
            os.chown(files, pwdObj.pw_uid, pwdObj.pw_gid)
            os.chmod(files, 0644)
    if os.path.exists(key):
        os.chown(key, pwdObj.pw_uid, pwdObj.pw_gid)
        os.chmod(key, 0600)

    ip, mac, client_type = get_ip_mac_type()
    data = readFile(client_req_file)

    req_id = post_request.serv_post_client_request(
        data, data_path, ip, mac, client_type, certbase, cert_path)

    fc = open(os.path.join(client_cert_path, 'req_id'), 'w')
    fc.write(req_id)
    fc.close()
    return req_id


def sign_certificate(req_id, cert_path, data_path, group="all"):
    cert_cmd.sing_req_by_server(req_id, cert_path, data_path, auto=True,
                                group_name=group)


def get_certificate_dn(cert_file):
    cert_data = readFile(cert_file)
    if cert_data:
        import OpenSSL
        certobj = OpenSSL.crypto.load_certificate(
            OpenSSL.SSL.FILETYPE_PEM, cert_data)
        cert_info = dict(certobj.get_subject().get_components())
        return cert_info["CN"]
    return "localhost"


def clear_localuser_certificates(certbase):
    """
    Удалить все пользовательские сертификаты, создаваемые для локальных
    пользователей
    """
    certdata = readFileEx(certbase, grab=True)
    certdn = os.path.dirname(certbase)
    # оставляем только сертификаты, которые не содержат отметки
    # для какого локального пользователя они созданы
    writedata = "\n".join(x[0] for x in re.findall("^((\S+\s+){6}\S+)\s*$",
                          certdata, flags=re.M))
    with writeFile(certbase) as f:
        f.write("%s\n"%writedata)

    # удаляем физически сертификаты, созданные для локальных пользователей
    for localcert in re.finditer("^(\S+)\s+(\S+\s+){6}\S+\s*$",
                                 certdata, flags=re.M):
        cert_fn = "%s/%s.crt"%(certdn, localcert.group(1))
        try:
            os.unlink(cert_fn)
        except OSError:
            print _("Failed to remove local client certificate") % cert_fn


def get_certificate(cert_path, data_path, certbase, client_cert_path,
                    user_name, server_cert=None):
    req_id_file = os.path.join(client_cert_path, 'req_id')
    if not os.path.exists(req_id_file):
        print _("request not sent or file %s deleted") % req_id_file
        return 1
    fc = open(req_id_file, 'r')
    req_id = fc.read()
    fc.close()

    if server_cert:
        server_host_name = get_certificate_dn(server_cert)
    else:
        server_host_name = socket.getfqdn()

    req_file = os.path.join(client_cert_path, server_host_name + '.csr')
    if not os.path.exists(req_file):
        print _('Request %s not found') % req_file
        return 1
    request = readFile(req_file)
    md5 = hashlib.md5()
    md5.update(request)
    md5sum = md5.hexdigest()

    result = post_request.serv_get_client_cert(
        req_id, md5sum, data_path, certbase, cert_path,
        localuser=user_name)

    cert = result[0]
    if len(result) > 1:
        ca_root = result[1]
    else:
        return None

    if cert == '1':
        print _('The signature request was rejected!')
        return 1
    elif cert == '2':
        print _("The signature request has not been examined yet.")
        print _("Your request ID = %s") % req_id
        return 1
    elif cert == '3':
        print _("The signature request does not match earlier data.")
        return 1
    elif cert == '4':
        print _("The request was sent from another IP.")
        return 1
    cert_file = os.path.join(client_cert_path, server_host_name + '.crt')
    fc = open(cert_file, 'w')
    fc.write(cert)
    fc.close()

    try:
        pwdObj = pwd.getpwnam(user_name)
    except KeyError, e:
        print e
        return None
    os.chown(cert_file, pwdObj.pw_uid, pwdObj.pw_gid)
    os.chmod(cert_file, 0600)

    os.unlink(req_id_file)
    print _('Certificate saved. Your certificate ID: %s') % req_id

    if ca_root:
        clVars = DataVarsCore()
        clVars.importCore()
        clVars.flIniFile()
        system_ca_db = clVars.Get('cl_glob_root_cert')
        if os.path.exists(system_ca_db):
            if ca_root in readFile(system_ca_db):
                return 0

        ca_dir = os.path.join(client_cert_path, 'ca')
        if not os.path.isdir(ca_dir):
            os.makedirs(ca_dir)
        os.chown(ca_dir, pwdObj.pw_uid, pwdObj.pw_gid)
        os.chmod(ca_dir, 0755)
        root_cert_md5 = os.path.join(ca_dir, "cert_list")

        md5 = hashlib.md5()
        md5.update(ca_root)
        md5sum = md5.hexdigest()

        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:
            import OpenSSL

            certobj = OpenSSL.crypto.load_certificate(
                OpenSSL.SSL.FILETYPE_PEM, ca_root)
            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()
            os.chown(root_cert_md5, pwdObj.pw_uid, pwdObj.pw_gid)
            os.chmod(root_cert_md5, 0644)

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

            ca_cert = os.path.join(ca_dir, filename)
            fd = open(ca_cert, 'w')
            fd.write(ca_root)
            fd.close()
            os.chown(ca_cert, pwdObj.pw_uid, pwdObj.pw_gid)
            os.chmod(ca_cert, 0644)

            user_root_cert = os.path.join(ca_dir, 'ca_root.crt')
            fa = open(user_root_cert, 'a')
            fa.write(ca_root)
            fa.close()
            os.chown(user_root_cert, pwdObj.pw_uid, pwdObj.pw_gid)
            os.chmod(user_root_cert, 0644)
        # print _("Certificate added")
        #        else:
        #            print _("file with the CA certificate now exists")

        trust_dir = os.path.join(client_cert_path, 'trusted')
        if not os.path.isdir(trust_dir):
            os.makedirs(trust_dir)
        os.chown(trust_dir, pwdObj.pw_uid, pwdObj.pw_gid)
        os.chmod(trust_dir, 0755)

        ca_certs = os.path.join(trust_dir, "cert.list")

        if not os.path.exists(ca_certs):
            fc = open(ca_certs, "w")
            fc.close()
            os.chown(ca_certs, pwdObj.pw_uid, pwdObj.pw_gid)
            os.chmod(ca_certs, 0644)

        host = 'localhost'
        filename = host
        cert_file_trust = os.path.join(trust_dir, filename)
        fc = open(cert_file_trust, "w")
        fc.write(ca_root)
        fc.close()
        os.chown(cert_file_trust, pwdObj.pw_uid, pwdObj.pw_gid)
        os.chmod(cert_file_trust, 0644)
        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()
    return 0


def key_force(cert_path, data_path):
    while True:
        try:
            resp = raw_input(_('Do you really want to remove all '
                               'certificates, requests and config files from '
                               'the server?') + ' (yes/no): ')
        except KeyboardInterrupt:
            resp = 'no'
        if resp.lower() in ['n', 'no']:
            return 0
        elif resp.lower() in ['y', 'yes']:
            break

    if os.path.isdir(cert_path):
        shutil.rmtree(cert_path)
    remove_dirs = ['conf', 'server_certs', 'client_certs', 'pids', 'sids']
    for rm_dir in remove_dirs:
        remove_dir = os.path.join(data_path, rm_dir)
        if os.path.isdir(remove_dir):
            shutil.rmtree(remove_dir)

    remove_files = ['sid.db', 'sid_pid']
    for rm_file in remove_files:
        remove_file = os.path.join(data_path, rm_file)
        if os.path.isfile(remove_file):
            os.unlink(remove_file)
