#!/usr/bin/python3

# Copyright (c) 2018 SUSE LLC, Robert Schweikert <rjschwei@suse.com>
#
# This file is part of regionService.
#
# regionService is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# ec2utilsbase 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 regionService.  If not, see <http://www.gnu.org/licenses/>.

"""
Script to generate a server certificate for the region server. Must be run
as root.

Core functionality based on:
    http://skippylovesmalorie.wordpress.com/2010/02/12/how-to-generate-a-self-signed-certificate-using-pyopenssl/

Convenience implementation
"""

from OpenSSL import crypto, SSL
import errno
import getpass
import ipaddress
import optparse
import os
import shutil
import sys
import time


def create_self_signed_cert(cert_dir, cert_file, cert_key, options):
    """
    Create a self signed certificate for this region server.
    """
    # create a key pair
    k = crypto.PKey()
    k.generate_key(crypto.TYPE_RSA, 4096)

    # Figure setting for subjectAltName
    template = 'IP:%s'
    try:
        i = ipaddress.ip_address(options.hostname)
    except ValueError:
        template = 'DNS:%s'
    san = [template % options.hostname]
    try:
        for ip in options.san_ips:
            san.append('IP:%s' % ip)
    except TypeError:
        pass
    try:
        for dns in options.san_dns:
            san.append('DNS:%s' % dns)
    except TypeError:
        pass
    san_string = ', '.join(san)

    # create a self-signed cert
    cert = crypto.X509()
    cert.get_subject().C = options.country
    cert.get_subject().ST = options.state
    cert.get_subject().L = options.location
    cert.get_subject().O = options.org
    cert.get_subject().OU = options.department
    cert.get_subject().CN = options.hostname
    cert.add_extensions([
        crypto.X509Extension(
            b'subjectAltName',
            False,
            san_string.encode())
    ])
    cert.set_serial_number(1000)
    cert.gmtime_adj_notBefore(0)
    cert.gmtime_adj_notAfter(10*365*24*60*60)
    cert.set_issuer(cert.get_subject())
    cert.set_pubkey(k)
    cert.sign(k, 'sha256')

    open('%s/%s' % (cert_dir, cert_file), "wt").write(
            crypto.dump_certificate(crypto.FILETYPE_PEM, cert).decode())
    open('%s/%s' % (cert_dir, cert_key), "wt").write(
            crypto.dump_privatekey(crypto.FILETYPE_PEM, k).decode())

# Must be root, the script modifies the Apache vhost config file
uName = getpass.getuser()
if uName != 'root':
    print('Must execute this script as root')
    sys.exit(1)

# Set up command line argument parsing
argparse = optparse.OptionParser()
help_msg = '2 Letter country identifierfor for the organization creating '
help_msg += 'the certificate'
argparse.add_option(
    '-c', '--country',
    help=help_msg,
    dest='country'
)
argparse.add_option(
    '-d', '--department',
    help='The organizational unit (department) creating the certificate',
    dest='department'
)
argparse.add_option(
    '', '--host',
    help='The IP Address of this host or a DNS resolvable host name',
    dest='hostname'
)
argparse.add_option(
    '-l', '--location',
    help='Location of the organization creating the certificate',
    dest='location'
)
argparse.add_option(
    '-o', '--organization',
    help='The organization creating the certificate',
    dest='org'
)
sHelp = 'The state in which the organization creating the certificate '
sHelp += 'is located'
argparse.add_option(
    '-s', '--state',
    help=sHelp,
    dest='state'
)
argparse.add_option(
    '--san-ip',
    action='append',
    help='Add an additional IP for which the certificate is valid',
    dest='san_ips'
)
argparse.add_option(
    '--san-dns',
    action='append',
    help='Add an additional hostname for which the certificate is valid',
    dest='san_dns'
)

options, args = argparse.parse_args()

cert_file = options.hostname.strip() + '.crt'
key_file = options.hostname.strip() + '.key'

if os.path.exists('/etc/apache2/ssl.crt/%s' % cert_file):
    print('Certificate for this server already exists, taking no action')
    print('/etc/apache2/ssl.crt/%s' % cert_file)
    sys.exit(1)

if os.path.exists('/etc/apache2/ssl.key/%s' % key_file):
    print('Key for this server already exists, taking no action')
    print('/etc/apache2/ssl.crt/%s' % key_file)
    sys.exit(1)

if not os.path.exists('/root/regionServCert'):
    os.mkdir('/root/regionServCert')

if os.path.exists('/root/regionServCert/%s' % cert_file):
    os.unlink('/root/regionServCert/%s' % cert_file)

if os.path.exists('/root/regionServCert/%s' % key_file):
    os.unlink('/root/regionServCert/%s' % key_file)

if os.path.exists('/root/regionServCert/%s.pem' % options.hostname):
    os.unlink('/root/regionServCert/%s.pem' % options.hostname)

create_self_signed_cert(
    '/root/regionServCert',
    cert_file,
    key_file,
    options
)

shutil.copy(
        '/root/regionServCert/%s' % cert_file,
        '/root/regionServCert/%s.pem' % options.hostname
    )

for filename in [cert_file, key_file]:
    part = filename.split('.')[-1]
    source_file = '/root/regionServCert/%s' % filename
    destination = '/etc/apache2/ssl.%s/%s' % (part, filename)
    try:
        os.rename(source_file, destination)
    except OSError as err:
        # OSError: [Errno 18] Invalid cross-device link
        if err.errno == errno.EXDEV:
            # best effort across file systems
            shutil.copyfile(source_file, destination)
            os.unlink(source_file)

# Modify the vhost file and restart apache
ctx = open('/etc/apache2/vhosts.d/regionsrv_vhost.conf', 'r').read()
ctx = ctx.replace('REPLACE_CERT_NAME', cert_file)
ctx = ctx.replace('REPLACE_KEY_NAME', key_file)
fout = open('/etc/apache2/vhosts.d/regionsrv_vhost.conf', 'w')
fout.write(ctx)
fout.close()

# Modify the configuration file for the Apache Access Configuratin service
if os.path.isfile('/etc/cloudServiceAccess/srvAccess.cfg'):
    ctx = open('/etc/cloudServiceAccess/srvAccess.cfg', 'r').read()
    ctx.replace('IP_ADDRESS_THIS_HOST', options.hostname)
    fout = open('/etc/cloudServiceAccess/srvAccess.cfg', 'w')
    fout.write(ctx)
    fout.close()

# Restart Apache
if os.path.exists('/usr/bin/systemctl'):
    os.system('systemctl restart apache2.service')
else:
    os.system('/etc/init.d/apache2 restart')

print('Include /root/regionServCert/%s.pem in the client' % options.hostname)
