#!/usr/bin/python3

#
# Copyright (C) 2022 Nethesis S.r.l.
# SPDX-License-Identifier: GPL-2.0-only
#

import os
import pwd
import grp
import sys
import os.path
import hashlib
import nsmigration
import subprocess
from nethsec import firewall, utils

def is_supported(cipher):
    try:
        p = subprocess.run(["/usr/sbin/openvpn", "--show-digests", "--show-ciphers"], capture_output=True, text=True, check=True)
        return cipher.lower() in p.stdout.lower()
    except:
        return False

def save_cert(path, data):
    # setup rw permissions only for nobody user
    os.umask(0o077)
    with open(path, 'w') as f:
        f.write(data)
    
    uid = pwd.getpwnam("nobody").pw_uid
    gid = grp.getgrnam("nogroup").gr_gid
    os.chown(path, uid, gid)

def import_tunnel(u, tunnel, ttype):
    name = tunnel.pop('ns_name')
    iname = utils.get_id(name, 10)
    if u.get("openvpn", iname, default=None) is not None:
        # A tunnel with the same name already exists:
        # create an hash for it
        nhash = hashlib.md5(name.encode('ascii')).hexdigest()
        name = f'mgr-{nhash[:5]}'
        iname = utils.get_id(name, 10)
    cert_dir=f"/etc/openvpn/{iname}/pki/"
    os.makedirs(cert_dir, exist_ok=True)

    nsmigration.vprint(f"Creating OpenVPN tunnel {ttype} {name}")

    u.set("openvpn", iname, "openvpn")
    u.set("openvpn", iname, "ns_name", name[0:10])
    # Setup auth files
    for option in ['dh', 'ca' , 'cert', 'key', 'secret']:
        if option in tunnel:
            value = tunnel.pop(option)
            save_cert(os.path.join(cert_dir, f'{option}.pem'), value)
            u.set("openvpn", iname, option, os.path.join(cert_dir, f'{option}.pem'))

    # Convert remote to a list
    if tunnel.get('remote') and type(tunnel.get('remote')) != list:
        tunnel['remote'] = [tunnel['remote']]
    # Setup general options
    for option in tunnel:
        u.set("openvpn", iname, option, tunnel[option])

    if ttype == 'server' and tunnel['topology'] != 'p2p':
        # Setup dynamic config for iroute:
        # OpenVPN on NethSec requires an iroute directive for the remote network
        u.set("openvpn", iname, 'client_connect', f'"/usr/libexec/ns-openvpn/openvpn-connect {iname}"')
        u.set("openvpn", iname, 'client_disconnect', f'"/usr/libexec/ns-openvpn/openvpn-disconnect {iname}"')

        # Open OpenVPN port
        proto = tunnel['proto'].removesuffix('-server')
        firewall.add_service(u, f'ovpn{name}', tunnel['lport'], proto, link=f'openvpn/{iname}')

    if 'auth' in tunnel and not is_supported(tunnel['auth']):
        nsmigration.vprint(f"Disabling OpenVPN tunnel {name}: auth {tunnel['auth']} not supported")
        u.set("openvpn", iname, 'enabled', '0')
    if 'cipher' in tunnel and not is_supported(tunnel['cipher']):
        nsmigration.vprint(f"Disabling OpenVPN tunnel {name}: cipher {tunnel['cipher']} not supported")
        u.set("openvpn", iname, 'enabled', '0')

    # Add management socket
    u.set("openvpn", iname, 'management', f'/var/run/openvpn_{iname}.socket unix')
    # Setup the device with max 16 characters
    device = f'tun{name}'[:15]
    u.set("openvpn", iname, 'dev', device)
    # Add interface to LAN
    ovpn_interface = firewall.add_vpn_interface(u, name, device, link=f'openvpn/{iname}')
    if not firewall.zone_exists(u, 'openvpn'):
        firewall.add_trusted_zone(u, "openvpn", [ovpn_interface])
    else:
        firewall.add_device_to_zone(u, device, 'openvpn')


(u, data, nmap) = nsmigration.init("openvpn_tunnels.json")

if not data:
    sys.exit(0)

for server in data['servers']:
    import_tunnel(u, server, 'server')

for client in data['clients']:
    import_tunnel(u, client, 'client')

# Save configuration
u.commit("openvpn")
firewall.apply(u)
