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

# Copyright 2012-2013 Calculate Ltd. 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, subprocess, 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
import os, hashlib, OpenSSL, pwd, socket, sys
from calculate.lib.cl_lang import setLocalTranslate
setLocalTranslate('cl_core3',sys.modules[__name__])

def createDate(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, cert_path, data_path, certbase, args):
    error_flag = 0
    if not os.path.isfile(cert):
        error_flag = 1
        print _('Certificate %s not found') %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):
        # 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 = open(cert, 'r').read()
        certobj = OpenSSL.crypto.load_certificate \
                                    (OpenSSL.SSL.FILETYPE_PEM, cert_data)
        certobj.get_notBefore()
        notAfter = createDate(certobj.get_notAfter())
        notBefore = createDate(certobj.get_notBefore())
        date = datetime.datetime.now()

        if notBefore > date:
            print _('Certificate creation date later than current date')
            error_flag = 1
        elif notAfter < date:
            print _('Certificate expired')
            error_flag = 1
    sys.exit(error_flag)

def init(cert, key, cert_path, data_path, certbase, args, port, user_name):
    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.')
    if new_serv_cert or not check_client_cert(user_name):
        print _('Generating the client certificate')
        create_client_cert(cert, cert_path, data_path, certbase, user_name)
    else:
        print _('Client certificate now exists.')

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):
    client_cert_path = check_user_path(user_name)
    server_host_name = socket.getfqdn()
    if os.path.isfile(os.path.join(client_cert_path ,server_host_name+'.crt'))\
                    and os.path.isfile(os.path.join \
                    (client_cert_path, server_host_name+'.key')):
        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):
    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)
    get_certificate(cert_path,data_path,certbase,client_cert_path,user_name)

def check_user_path(user_name):
    try:
        pwdObj = pwd.getpwnam(user_name)
    except KeyError, 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]:
            if os.path.isfile(_file):
                os.chown('/'.join([path[0],_file]),pwdObj.pw_uid,pwdObj.pw_gid)
                os.chmod(directory, 0644)
    return cert_dir

def create_request(server_cert, cert_path, data_path, certbase, \
                   client_cert_path, user_name):
    server_host_name = socket.getfqdn()

    key = os.path.join(client_cert_path, server_host_name + '.key')
    #csr_file = cert_path + server_host_name +'.csr'
    #pritn 'request file = ', csr_file

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

    try:
        pwdObj = pwd.getpwnam(user_name)
    except KeyError, 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 = open(client_req_file).read()

    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):
    cert_cmd.sing_req_by_server(req_id, cert_path, data_path, auto = True)

def get_certificate(cert_path,data_path,certbase,client_cert_path,user_name):
    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()

    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 = open(req_file).read()
    md5 = hashlib.md5()
    md5.update(request)
    md5sum = md5.hexdigest()

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

    cert = result[0]
    if len (result) > 1:
        ca_root = result[1]
    else: 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 open(system_ca_db, 'r').read():
                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:
            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)
