#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
# © 2012, Multapplied Networks, Inc.

import grp
import os
import pwd
import subprocess
import sys
import configparser

BONDINGADMIN_CONFIG = '/etc/bondingadmin/bondingadmin.conf'
OPENSSL_CMD = '/usr/bin/openssl'


def load_config():
    parser = configparser.ConfigParser()
    parser.read(BONDINGADMIN_CONFIG)
    return parser


def source_bash_variables(location):
    """
    Load bash-style variables from a script, and return a dictionary mapping of them
    """
    # set -o posix makes set not print any functions, only variables
    args = ['bash', '-c', 'source {file} && set -o posix && set'.format(file=location)]

    proc = subprocess.Popen(args, stdout=subprocess.PIPE)
    stdout, stderr = proc.communicate()

    bash_vars = {}
    for line in stdout.decode('UTF-8').split('\n'):
        (key, _, value) = line.partition("=")
        bash_vars[key] = value

    return bash_vars


def create_bonding_directories(directories):
    """
    Create the initial directories for the bondingadmin CA
    """
    for directory in directories:
        if os.path.exists(directory):
            continue
        try:
            os.makedirs(directory)
        except os.error as error:
            print('Error creating {directory}: {error}'.format(directory=directory, error=error), file=sys.stderr)
            sys.exit(1)


def get_openssl_subject():
    config = load_config()
    return '/C={country}/ST={province}/L={city}/O={organization} Bonded Internet root CA/CN={hostname}'.format(
        country=config.get('partner', 'country'),
        province=config.get('partner', 'province'),
        city=config.get('partner', 'city'),
        organization=config.get('partner', 'short_name'),
        hostname=config.get('partner', 'mgmt_server_url'),
    )


def recursive_chown_directory(directory, user, group):
    """
    Change the ownership of `directory` and all files and directories under it to be
    owned by `user`:`group`
    """
    uid = pwd.getpwnam(user).pw_uid
    gid = grp.getgrnam(group).gr_gid
    matches = []
    for root, dirnames, filenames in os.walk(directory):
        for filename in filenames:
            matches.append(os.path.join(root, filename))

    for match in matches:
        os.chown(match, uid, gid)


def create_certificate_authority(openssl_config, serial_file, index_file, crlnumber_file, key_file, cert_file, crl_file, valid_days, randfile):
    """
    If they don't exist, create any files necessary for the certificate authority.
    """
    if not os.path.exists(serial_file):
        with open(serial_file, 'w') as f:
            f.write('01')

    if not os.path.exists(index_file):
        with open(index_file, 'w') as f:
            # Create the file by opening and closing it
            pass

    if not os.path.exists(crlnumber_file):
        with open(crlnumber_file, 'w') as f:
            f.write('01')

    if not os.path.exists(key_file):
        print('Writing CA key and certificate')
        old_umask = os.umask(0o077)
        proc = subprocess.Popen(
            [
                OPENSSL_CMD,
                'req',
                '-config', openssl_config,
                '-new',
                '-x509',
                '-nodes',
                '-extensions', 'v3_ca',
                '-days', valid_days,
                '-out', cert_file,
                '-keyout', key_file,
                '-subj', get_openssl_subject(),
            ],
            env=dict(os.environ, RANDFILE=randfile),
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE
        )
        stdout, stderr = proc.communicate()
        if proc.returncode != 0:
            print('Error creating CA: {stderr}'.format(stderr=stderr), file=sys.stderr)
            sys.exit(proc.returncode)
        os.chmod(cert_file, 0o644)
        os.umask(old_umask)
    else:
        print('CA key already exists at {key_file}, not generating key or certificate.'.format(key_file=key_file))

    if not os.path.exists(crl_file):
        print('Writing CA certificate revokation list')
        proc = subprocess.Popen(['/usr/sbin/update-bondingadmin-crl'])
        proc.communicate()
    else:
        print('CA certificate revokation list already exists, not generating a new one.')


def main():
    bash_vars = source_bash_variables('/usr/share/bondingadmin/default/ca-vars')
    create_bonding_directories([
        bash_vars['BONDINGADMIN_DIR'],
        bash_vars['PRIVATE_DIR'],
        bash_vars['CA_DIR'],
        bash_vars['CERTS_DIR'],
        bash_vars['NEWCERTS_DIR'],
        bash_vars['CSRS_DIR'],
    ])
    create_certificate_authority(
        bash_vars['CONFIG'],
        bash_vars['SERIAL_FILE'],
        bash_vars['INDEX_FILE'],
        bash_vars['CRLNUMBER_FILE'],
        bash_vars['CA_KEY'],
        bash_vars['CA_CERT'],
        bash_vars['CRL_FILE'],
        bash_vars['CA_CERT_VALID_DAYS'],
        bash_vars['RANDFILE']
    )
    recursive_chown_directory(
        bash_vars['CA_DIR'],
        bash_vars['HTTPD_USER'],
        bash_vars['HTTPD_GROUP']
    )


if __name__ == '__main__':
    main()
