#!/usr/bin/env python3
#
# Copyright (C) 2019 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
# published by the Free Software Foundation.
#
# 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, see <http://www.gnu.org/licenses/>.
#
#

import sys
import os
import subprocess
import tempfile
import pathlib
import ssl

import vyos.defaults
from vyos.config import Config
from vyos import ConfigError

vyos_conf_scripts_dir = vyos.defaults.directories['conf_mode']

# XXX: this model will need to be extended for tag nodes
dependencies = [
    'https.py',
]

def status_self_signed(cert_data):
# check existence and expiration date
    path = pathlib.Path(cert_data['conf'])
    if not path.is_file():
        return False
    path = pathlib.Path(cert_data['crt'])
    if not path.is_file():
        return False
    path = pathlib.Path(cert_data['key'])
    if not path.is_file():
        return False

    # check if certificate is 1/2 past lifetime, with openssl -checkend
    end_days = int(cert_data['lifetime'])
    end_seconds = int(0.5*60*60*24*end_days)
    checkend_cmd = ('openssl x509 -checkend {end} -noout -in {crt}'
                    ''.format(end=end_seconds, **cert_data))
    try:
        subprocess.check_call(checkend_cmd, shell=True)
        return True
    except subprocess.CalledProcessError as err:
        if err.returncode == 1:
            return False
        else:
            print("Called process error: {}.".format(err))

def generate_self_signed(cert_data):
    san_config = None

    if ssl.OPENSSL_VERSION_INFO < (1, 1, 1, 0, 0):
        san_config = tempfile.NamedTemporaryFile()
        with open(san_config.name, 'w') as fd:
            fd.write('[req]\n')
            fd.write('distinguished_name=req\n')
            fd.write('[san]\n')
            fd.write('subjectAltName=DNS:vyos\n')

        openssl_req_cmd = ('openssl req -x509 -nodes -days {lifetime} '
                           '-newkey rsa:4096 -keyout {key} -out {crt} '
                           '-subj "/O=Sentrium/OU=VyOS/CN=vyos" '
                           '-extensions san -config {san_conf}'
                           ''.format(san_conf=san_config.name,
                                     **cert_data))

    else:
        openssl_req_cmd = ('openssl req -x509 -nodes -days {lifetime} '
                           '-newkey rsa:4096 -keyout {key} -out {crt} '
                           '-subj "/O=Sentrium/OU=VyOS/CN=vyos" '
                           '-addext "subjectAltName=DNS:vyos"'
                           ''.format(**cert_data))

    try:
        subprocess.check_call(openssl_req_cmd, shell=True)
    except subprocess.CalledProcessError as err:
        print("Called process error: {}.".format(err))

    os.chmod('{key}'.format(**cert_data), 0o400)

    with open('{conf}'.format(**cert_data), 'w') as f:
        f.write('ssl_certificate {crt};\n'.format(**cert_data))
        f.write('ssl_certificate_key {key};\n'.format(**cert_data))

    if san_config:
        san_config.close()

def get_config():
    vyos_cert = vyos.defaults.vyos_cert_data

    conf = Config()
    if not conf.exists('service https certificates system-generated-certificate'):
        return None
    else:
        conf.set_level('service https certificates system-generated-certificate')

    if conf.exists('lifetime'):
        lifetime = conf.return_value('lifetime')
        vyos_cert['lifetime'] = lifetime

    return vyos_cert

def verify(vyos_cert):
    return None

def generate(vyos_cert):
    if vyos_cert is None:
        return None

    if not status_self_signed(vyos_cert):
        generate_self_signed(vyos_cert)

def apply(vyos_cert):
    for dep in dependencies:
        cmd = '{0}/{1}'.format(vyos_conf_scripts_dir, dep)
        try:
            subprocess.check_call(cmd, shell=True)
        except subprocess.CalledProcessError as err:
            raise ConfigError("{}.".format(err))

if __name__ == '__main__':
    try:
        c = get_config()
        verify(c)
        generate(c)
        apply(c)
    except ConfigError as e:
        print(e)
        sys.exit(1)