#! /usr/bin//usr/bin/python3.11
# Imports...
# silence annoying warning messages about deprecated features
# TODO FIX LATER import pyovpn.python.nodepwarn
import pkg_resources

from pyovpn.auth.authlocal import AuthLocal
from pyovpn.util.poversion import pyovpn_version
pkg_resources.require("pyovpn")
from pyovpn.lic.lickey import LicenseKeyString
from pyovpn.subscription.subhelper import SubscriptionHelper
from pyovpn.subscription.state import SubscriptionTracking
from pyovpn.net.net import NetInfoLinux
import json
import os
import subprocess
import re
import sys
import pwd
import getpass
import warnings
import time
import socket
import secrets
import string
from twisted.internet import reactor, defer
from pyovpn.util.auth import AuthTypes
from pyovpn.util.sock import test_port
from pyovpn.linux.linfo import get_n_cores
from pyovpn.eula.eula import get_eula
from pyovpn.util.ec2 import get_user_dict, get_cidr_list, ec2_get_pub_ip
from pyovpn.util.gcp import get_user_dict_gcp, get_cidr_list_gcp, gcp_get_pub_ip
from pyovpn.util.azure import azure_get_pub_ip
from pyovpn.pki.pki import PKI
from pyovpn.ctest.ctcli import ConnectivityTestClient
from pyovpn.util.valid import validate_password_strength
from pyovpn.sagent.upgrade import UpgradeAs
#
# Default configuration parameters for this script...
#

# Show debug/verbose output...
DEBUG = True

# Forces re-initialization...
FORCE = False

# Where to find pyovpn...
TOP = "/usr/local/openvpn_as"

# Where to find primary template config...
AS_TEMPL_CONF = os.path.join(TOP, "etc/as_templ.conf")

# Where to find primary config...
AS_CONF = os.path.join(TOP, "etc/as.conf")

ACTIVE_PROFILE_DEFAULT = {
    "_INTERNAL": {
        "run_api.active_profile": "Default", 
        "webui.edit_profile": "Default"
    }
}

# Where to find primary config...
JSON_CONF = os.path.join(TOP, "etc/config.json")
JSON_CONF_LOCAL = os.path.join(TOP, "etc/config-local.json")

# Default port for web...
DEFAULT_OVPN_PORT = "943"

#
# Script starts here
#

eula = get_eula().decode("utf-8")


def get_raw_input(prompt, batch_default=''):
    if BATCH:
        print("%s%s" % (prompt, batch_default))
        return batch_default
    else:
        return input(prompt)


def print_eula():
    for l in eula.splitlines():
        if l == '* * *':
            break
        print(str(l))


def cloud_bool(key, default):
    s = user_data.get(key, default)
    if s == '0' or s.lower() == 'false':
        return 'false'
    elif s == '1' or s.lower() == 'true':
        return 'true'
    else:
        return default


# TODO: Put code into try/except for more graceful early termination
'''Process command line arguments'''


def process_args():
    from optparse import OptionParser
    usage = "usage: ovpn-init [options]"
    global parser
    parser = OptionParser(usage)
    # yapf: disable
    parser.add_option("", "--verbose", action="store_true", dest="verbose",
                      help="If this argument is used, verbose output will be generated.")
    parser.add_option("", "--force", action="store_true", dest="force",
                      help="If this argument is used, openvpn-as will be re-ilnitialized and all DBs will be wiped!")
    parser.add_option("", "--batch", action="store_true", dest="batch",
                      help="If this argument is used, openvpn-as will run in batch mode, and will not solicit input from the tty.  You should not use this option unless you have viewed the EULA and agree to it (use the --view-eula option to view the EULA).  Using this option indicates your agreement with the EULA.")
    parser.add_option("", "--host", dest="host",
                      help="Set the FQDN of this server for access from the internet.")
    parser.add_option("", "--ec2", action="store_true", dest="ec2",
                      help="Configure using Amazon EC2 user-defined metadata")
    parser.add_option("", "--gcp", action="store_true", dest="gcp",
                      help="Configure using Google GCP user-defined metadata")
    parser.add_option("", "--azure", action="store_true", dest="azure",
                      help="Configure using MS Azure user-defined metadata")
    parser.add_option("", "--oracle", action="store_true", dest="oracle",
                      help="Configure using Oracle Cloud user-defined metadata")
    parser.add_option("", "--secondary", action="store_true", dest="secondary",
                      help="If this argument is used, the node will be configured as a secondary node, to be used for backup or standby purposes.")
    parser.add_option("", "--no_reroute_gw", action="store_true", dest="no_reroute_gw",
                      help="If this argument is used, client traffic will NOT be routed by default through the VPN.")
    parser.add_option("", "--no_reroute_dns", action="store_true", dest="no_reroute_dns",
                      help="If this argument is used, client DNS traffic will NOT be routed by default through the VPN.")
    parser.add_option("", "--no_private", action="store_true", dest="no_private",
                      help="If this argument is used, private subnets will NOT be accessible to clients by default.")
    parser.add_option("", "--local_auth", action="store_true", dest="local_auth",
                      help="Use local authentication via internal DB")
    parser.add_option("", "--license", dest="license",
                      help="Optionally, specify an OpenVPN-AS license key")
    parser.add_option("", "--no_start", action="store_true", dest="no_start",
                      help="Don't automatically start the the Access Server daemon at the conclusion of the script.")
    parser.add_option("", "--view-eula", action="store_true", dest="view_eula",
                      help="View the EULA (End User License Agreement).")

    parser.add_option("", "--key_algorithm",dest='key_algorithm', help="Type of private/public key used for OpenVPN profiles/certificates. Valid options are rsa or an EC curve name (%s)"  % (", ".join(PKI.get_supported_algorithms()))),
    parser.add_option("", "--web_key_algorithm",dest='web_key_algorithm', help="Type of private/public key for self-signed web certificates. Valid options are rsa or an EC curve name (%s)"  % (", ".join(PKI.get_supported_algorithms()))),
    parser.add_option("", "--key_size",dest='key_size', help="OpenVPN profiles/certificate RSA key Size (2048+)")
    parser.add_option("", "--web_key_size",dest='web_key_size', help="Self-signed web certifcate RSA key Size (2048+)")

    # yapf: enable

    return parser.parse_args()


# Get command line arguments...
(opts, args) = process_args()

# View eula only?
if opts.view_eula:
    print_eula()
    sys.exit(0)

# Get verbose/debug option value from cmd line args...
DEBUG = opts.verbose

# Get force option...
FORCE = opts.force

# get batch option
BATCH = opts.batch

# get FQDN of server
HOST = opts.host

# get optional license key
LICENSE = opts.license

# are we running on Amazon EC2?
EC2 = opts.ec2

# are we running on Google GCP?
GCP = opts.gcp

# are we running on MS Azure?
AZURE = opts.azure

def check_and_set_initial_version():
    def is_initial_version_set():
        return_status = subprocess.run(['/usr/local/openvpn_as/scripts/confdba', '--queryact'], stdout=subprocess.PIPE, check=True)
        return UpgradeAs.INITIAL_VERSION in return_status.stdout.decode()

    if not is_initial_version_set():
        as_version = pyovpn_version()
        print(f'Initial version is not set. Setting it to {as_version}...')

        set_status = subprocess.run(['/usr/local/openvpn_as/scripts/confdba', '-mk', UpgradeAs.INITIAL_VERSION, '-v', as_version], stdout=subprocess.PIPE)
        if set_status.returncode != 0:
            print(f'Error: Failed to set {UpgradeAs.INITIAL_VERSION} key. Error - {set_status.returncode}')
            sys.exit(1)


# Check if the default/key size/algorithm are overridden
def check_key_size(opt, name, default=2048):
    if opt:
        try:
            key_size = int(opt)
            if key_size < 2048 or key_size > 4096:
                print("Argument to --%s must be between 2048 and 4096" % name)
                sys.exit(1)
            return key_size
        except ValueError:
            print("Argument to --key_size is invalid")
            sys.exit(1)
    else:
        return default

DEFAULT_KEY_SIZE = check_key_size(opts.key_size, "key_size")
DEFAULT_WEB_KEY_SIZE = check_key_size(opts.web_key_size, "web_key_size", default=DEFAULT_KEY_SIZE)


if opts.key_algorithm:
    alg = opts.key_algorithm.strip().lower()
    if alg not in PKI.get_supported_algorithms():
        print("Public/private key type '%s' specified by --key_algorithm is not supported")
        sys.exit(1)
    DEFAULT_KEY_ALGORITHM = alg
else:
    DEFAULT_KEY_ALGORITHM = "secp384r1"

if opts.web_key_algorithm:
    alg = opts.web_key_algorithm.strip().lower()
    if alg not in PKI.get_supported_algorithms():
        print("Public/private key type '%s' specified by --key_algorithm is not supported")
        sys.exit(1)
    DEFAULT_WEB_KEY_ALGORITHM = alg
else:
    DEFAULT_WEB_KEY_ALGORITHM = DEFAULT_KEY_ALGORITHM
# are we running on Oracle
ORACLE = opts.oracle


def get_cert_algorithms(name, default, default_key_size):
    alg = default
    key_size = default_key_size
    while (True):
        print()
        print("What public/private type/algorithms do you want to use for the %s?" % name )
        print("")
        print("Recommended choices:")
        print("")
        print("rsa       - maximum compatibility")
        print("secp384r1 - elliptic curve, higher security than rsa, allows faster connection setup and smaller user profile files")
        print("showall   - shows all options including non-recommended algorithms.")

        user_input = get_raw_input("> Press ENTER for default [" + default + "]:")
        user_input = user_input.strip().lower()
        if user_input == "":
            alg = default
            break

        if user_input == "showall":
            print(PKI.get_supported_curves_text())
        elif user_input in PKI.get_supported_algorithms():
            alg = user_input
            break
        else:
            print("Error: This public/private key algorithm is not supported.")

    while (True):
        # The EC curves have fixed bit sizes
        if alg != "rsa":
            key_size = default_key_size
            break

        print()
        print("What key size do you want to use for the certificates?")
        print("Key size should be between 2048 and 4096")
        print("")

        user_input = get_raw_input("> Press ENTER for default [ %d ]:" % default_key_size)
        user_input = user_input.strip()

        if user_input == "":
            key_size = default_key_size
            break

        if not user_input.isdigit():
            print("Error: This is an invalid option.")
            continue

        key_size = int(user_input)

        if key_size < 2048 or key_size > 4096:
            print("Error: Key size must be between 2048 and 4096.")
            continue

        key_size = key_size
        break
    return alg, str(key_size)

def get_ip_wrapper():
    def cb(result):
        global HOST
        HOST = result
        reactor.stop()

    def eb(bad):
        reactor.stop()

    d = [ConnectivityTestClient.get_public_ip()]
    d[0].addCallbacks(cb, eb)
    defer.DeferredList(d)
    reactor.run()

    return d


if EC2 or GCP or AZURE or ORACLE:
    user_data = {}
    if EC2:
        user_data = get_user_dict()
        cidr = get_cidr_list()
    elif GCP:
        user_data = get_user_dict_gcp()
        cidr = get_cidr_list_gcp()

    if not HOST and 'public_hostname' in user_data:
        HOST = user_data['public_hostname']
    elif EC2:
        HOST = ec2_get_pub_ip()
    elif GCP:
        HOST = gcp_get_pub_ip()
    elif AZURE:
        HOST = azure_get_pub_ip()
    elif ORACLE:
        deferred = get_ip_wrapper()

    if not LICENSE and user_data and 'license' in user_data:
        LICENSE = user_data['license']

# get AS version number
VERSION = pyovpn_version()

if DEBUG:
    print("VERSION", VERSION)

# Do we need to see if ovpn-init was already run, possibly successfully ?
# TODO: Is there a better check for this ?
if not FORCE:
    if DEBUG:
        print("Info:  Checking that as.conf has been created.")
    if os.path.exists("/usr/local/openvpn_as/etc/as.conf"):
        print("Detected an existing OpenVPN-AS configuration.")
        print("Continuing will delete this configuration and restart from scratch.")
        user_input = get_raw_input("Please enter 'DELETE' to delete existing configuration: ")
        if user_input == 'DELETE':
            FORCE = True
        else:
            print("OpenVPN-AS configuration has not been modified.")
            sys.exit(3)

if FORCE:
    # Stop openvpnas if it is running
    retv = subprocess.run(['systemctl', 'stop', 'openvpnas'])

# Show welcome message...
print()
print("          OpenVPN Access Server")
print("          Initial Configuration Tool")
print("------------------------------------------------------")

# Check if machine is capable of providing a license validation machine lock
# TODO: remove this block after an old licensing is deprecated since we don't need
# liman to validate the subscription
if not LICENSE:
    SA_CMD = ["/usr/local/openvpn_as/scripts/liman", "valid-no-inode"]
    retv = subprocess.run(SA_CMD)
    if DEBUG:
        print("sa cmd=", SA_CMD, retv)
    if retv.returncode != 0:
        print("Error: Could not establish license validation machine lock.")
        print("Access Server will not be able to activate a license key on this host.")
        print("Please see http://www.openvpn.net/access-server/rd/liman-id-failed.html")
        sys.exit(1)


# do EULA
print_eula()
if not BATCH:
    user_input = get_raw_input("Please enter 'yes' to indicate your agreement [no]: ")
    if not user_input.lower().startswith('yes'):
        print("OpenVPN-AS cannot be installed unless the EULA is agreed to.")
        sys.exit(1)

# Continue welcome message
print()
print("Once you provide a few initial configuration settings,")
print("OpenVPN Access Server can be configured by accessing")
print("its Admin Web UI using your Web browser.")

# Func to get server network options...


def get_network_options():
    ifaces_to_show = []
    ips_to_show = []
    default_option = None

    # There's always 'all interfaces', which is also the default
    ifaces_to_show.append("all interfaces")
    ips_to_show.append("0.0.0.0")
    default_option = 0

    # Get a NetInfoLinux object...
    ni = NetInfoLinux()
    interfaces = ni.enum_interfaces()
    if DEBUG:
        print("interfaces=", interfaces)

    # Got a list of interfaces?...
    if interfaces:
        # Got a list of interfaces, get default route...
        try:
            def_route = ni.get_default_route()['Iface']
        except Exception:
            def_route = None
        if DEBUG:
            print("def route=", def_route)

        # Make copy of interfaces and iterate...
        iterlist = interfaces[:]
        for interface in iterlist:
            if DEBUG:
                print("interface=", interface)
            # Found default, get its info...
            if interface['name'] == def_route:
                ifaces_to_show.append(def_route)
                if AZURE or ORACLE:
                    interface['address'] = HOST or interface['address']

                ips_to_show.append(interface['address'])
                # Remove from list...
                interfaces.remove(interface)
            if interface['name'] == 'lo':
                # We wont allow loopback as an option...
                interfaces.remove(interface)

        # Append the rest...
        for interface in interfaces:
            ifaces_to_show.append(interface['name'])
            ips_to_show.append(interface['address'])

    return [ifaces_to_show, ips_to_show, default_option]


# Will this be a primary or secondary node
if opts.secondary:
    NODE_TYPE = "secondary"
else:
    while (True):
        print()
        print("Will this be the primary Access Server node?")
        print("(enter 'no' to configure as a backup or standby node)")
        YN = get_raw_input("> Press ENTER for default [yes]: ")
        if YN.lower().startswith("y") or YN == "":
            NODE_TYPE = "primary"
            break
        elif YN.lower().startswith("n"):
            NODE_TYPE = "secondary"
            break
        else:
            print("Error: This response was not a valid response.")

if NODE_TYPE == "primary":
    # Get network options for server address needed in next query section...
    [SUGGEST_IFACES, SUGGEST_IPS, SUGGEST_OPTION] = get_network_options()

    # Get the ip address/interface the user wants for the server...
    OVPN_IP = None
    OVPN_IFACE = None
    LOGIN_IP = None
    while (True):
        print()
        print("Please specify the network interface and IP address to be")
        print("used by the Admin Web UI:")
        for i in range(len(SUGGEST_IFACES)):
            print("(" + str(i+1) + ") " + \
                SUGGEST_IFACES[i] + ": " + SUGGEST_IPS[i])
        print(f"Please enter the option number from the list above (1- {len(SUGGEST_IFACES)}).")
        user_input = get_raw_input(f"> Press Enter for default [{SUGGEST_OPTION+1}]: ")
        # strip white space...
        user_input = user_input.strip()
        # Check if they just hit enter...
        if user_input == "":
            # Set to default option...
            user_input = str(SUGGEST_OPTION+1)
        if not user_input.isdigit():
            print("Error: This is an invalid option.")
            continue
        # Check within bounds...
        VAL = int(user_input)
        VAL = VAL - 1
        if (VAL < 0) or (VAL >= len(SUGGEST_IPS)):
            print("Error: This is an invalid option.")
            continue
        # Got here so its ok...
        OVPN_IP = SUGGEST_IPS[VAL]
        OVPN_IFACE = SUGGEST_IFACES[VAL]
        LOGIN_IP = OVPN_IP
        # Do some adjustments for 'all interfaces' option...
        if OVPN_IFACE.lower().startswith('all'):
            OVPN_IFACE = 'all'
            LOGIN_IP = SUGGEST_IPS[1]
        break

    if HOST:
        LOGIN_IP = HOST

    if DEBUG:
        print("OVPN_IP=", OVPN_IP)
    if DEBUG:
        print("OVPN_IFACE=", OVPN_IFACE)
    if DEBUG:
        print("LOGIN_IP=", LOGIN_IP)

    # Get the default openvpn port...
    SUGGEST_PORT = DEFAULT_OVPN_PORT

    # Get the certificate otpions

    OVPN_KEY_ALGORITHM, OVPN_KEY_SIZE = get_cert_algorithms("OpenVPN CA", DEFAULT_KEY_ALGORITHM, DEFAULT_KEY_SIZE)
    OVPN_WEB_KEY_ALGORITHM, OVPN_WEB_KEY_SIZE = get_cert_algorithms("self-signed web certificate", DEFAULT_WEB_KEY_ALGORITHM, DEFAULT_WEB_KEY_SIZE)


    # Get the port the user wants, with error checking...
    OVPN_PORT = None
    while (True):
        print()
        print("Please specify the port number for the Admin Web UI.")
        user_input = get_raw_input("> Press ENTER for default [" + SUGGEST_PORT + "]: ")
        user_input = user_input.strip()
        if user_input == "":
            OVPN_PORT = SUGGEST_PORT
            break

        if user_input.isdigit():
            OVPN_PORT = user_input
            break
        else:
            print("Error: This is an invalid port.")

    # get OpenVPN daemon port
    def default_ovpnd_port():
        try443 = test_port(socket.SOCK_STREAM, 443)
        if try443:
            return "443"
        else:
            return "1194"

    OVPND_PORT = None
    SUGGEST_PORT = default_ovpnd_port()
    while (True):
        print()
        print("Please specify the TCP port number for the OpenVPN Daemon")
        user_input = get_raw_input("> Press ENTER for default [" + SUGGEST_PORT + "]: ")
        user_input = user_input.strip()
        if user_input == "":
            OVPND_PORT = SUGGEST_PORT
            break
        if user_input.isdigit():
            OVPND_PORT = user_input
            break
        else:
            print("Error: This is an invalid port.")

    def query_bool_setting(prompt, default_value, nonquery, nonquery_value, cloud_key, cloud_default):
        ret = nonquery_value
        if not nonquery:
            while (True):
                print()
                print(prompt)
                if EC2 or GCP:
                    default = cloud_bool(cloud_key, cloud_default)
                else:
                    default = default_value
                default_show = ''
                if default == 'false':
                    default_show = 'no'
                elif default == 'true':
                    default_show = 'yes'
                YN = get_raw_input("> Press ENTER for default [%s]: " % (default_show, ))
                if YN == "":
                    ret = default
                    break
                elif YN.lower().startswith("y"):
                    ret = "true"
                    break
                elif YN.lower().startswith("n"):
                    ret = "false"
                    break
                else:
                    print("Error: This response was not a valid response.")
        return ret

    REROUTE_GW = query_bool_setting("Should client traffic be routed by default through the VPN?",
                                    'true', opts.no_reroute_gw, 'false', 'reroute_gw', 'false')
    REROUTE_DNS = query_bool_setting("Should client DNS traffic be routed by default through the VPN?",
                                     'true', opts.no_reroute_dns, 'false', 'reroute_dns', 'false')
    AUTH_MODULE = AuthTypes.LOCAL.value
    
    print("Admin user authentication will be ", AUTH_MODULE)

    PRIVATE_ACCESS = "no"
    PRIVATE_NETS = ()
    if not opts.no_private:
        priv_nets = ()
        if EC2 or GCP:
            if cidr:
                priv_nets = cidr
        else:
            priv_nets = NetInfoLinux.get_priv_subnets()
        if priv_nets:
            print()
            print("Private subnets detected: %r" % (priv_nets, ))
        while (True):
            print()
            print("Should private subnets be accessible to clients by default?")
            if EC2:
                YN = get_raw_input("> Press ENTER for EC2 default [yes]: ")
            elif GCP:
                YN = get_raw_input("> Press ENTER for GCP default [yes]: ")
            else:
                YN = get_raw_input("> Press ENTER for default [yes]: ")
            if YN.lower().startswith("y") or YN == "":
                PRIVATE_ACCESS = "nat"
                PRIVATE_NETS = priv_nets
                break
            elif YN.lower().startswith("n"):
                break
            else:
                print("Error: This response was not a valid response.")
        # Get the port the user wants, with error checking...

if NODE_TYPE == "secondary":
    [SUGGEST_IFACES, SUGGEST_IPS, DEFAULT_OPTION] = get_network_options()
    # Note: by default interface is 'all' and LOGIN_IP is network address of machine
    LOGIN_IP = HOST or SUGGEST_IPS[DEFAULT_OPTION+1]
    PRIVATE_ACCESS = "no"
    PRIVATE_NETS = ()
    AUTH_MODULE = AuthTypes.LOCAL.value
    OVPN_IFACE = SUGGEST_IFACES[DEFAULT_OPTION]
    if OVPN_IFACE.lower().startswith('all'):
        OVPN_IFACE = 'all'

    OVPN_PORT = DEFAULT_OVPN_PORT


# Eventually, this var gets filled with the admins we want to enable...
ADMINS = []
ADMIN_USER = "openvpn"
if (EC2 or GCP) and 'admin_user' in user_data:
    ADMIN_USER = user_data['admin_user']

print()
print("To initially login to the Admin Web UI, you must use a")
print("username and password that successfully authenticates you")
print("with the host UNIX system (you can later modify the settings")
print("so that RADIUS or LDAP is used for authentication instead).")
print()
print('You can login to the Admin Web UI as "%s" or specify' % (ADMIN_USER, ))
print("a different user account to use for this purpose.")


# Func to escape slash and double quote in raw strings...
def esc_str(str):
    # Escape the slash...
    outstr = str.replace("\\", "\\\\")
    # Escape the quote...
    outstr = outstr.replace("\"", "\\\"")
    return outstr

PW_SET = False
PASS_LEN = 12


def generate_random_password(pass_len):
    alphabet = string.ascii_letters + string.digits
    password = ''.join(secrets.choice(alphabet) for i in range(pass_len))
    return password


def validate_entered_passwords(user_name, passw):
    ret = validate_password_strength(passw, 8)
    if ret:
        print(f'Error: {ret}')
        return False
    passw_confirm = esc_str(getpass.getpass(
        f"Confirm the password for the '{user_name}' account:"))
    if passw and passw_confirm:
        if passw == passw_confirm:
            return True
        else:
            print("Error: passwords do not match")
    return False


def request_password(user_name, pwd, input_message: str):
    """
        Returns a tuple of string and bool. String corresponds to password and bool to
        whether the password was auto-generated or created by user.
    """
    if BATCH:
        return pwd, True

    PW_SET = False
    for i in range(3):
        passw = esc_str(getpass.getpass(input_message))
        if not passw:
            print(f'Please, remember this password {pwd}')
            return pwd, True
        
        if validate_entered_passwords(user_name, passw):
            PW_SET = True
            break

    if not PW_SET:
        print("Error! You've entered password incorrectly 3 times. Exiting...")
        sys.exit(1)

    return passw, False    
            

def fetch_pass_from_metadata(user_data):
    if 'pwdgen' in user_data:
        return user_data['pwdgen']
    elif 'admin_pw' in user_data:
        return user_data['admin_pw']
    else:
        return None

# Do they want to enable specific admin account ?
while(True):
    if NODE_TYPE == "primary":
        print()
        print('Do you wish to login to the Admin UI as "%s"%s?'
            % (ADMIN_USER, '(as suggested in GCP metadata)' if GCP else ''))
        YN = get_raw_input("> Press ENTER for default [yes]: ")
    elif NODE_TYPE == "secondary":
        passw, is_generated = generate_random_password(PASS_LEN), True
        ADMINS.append([ADMIN_USER, passw, is_generated])
        break

    if YN.lower().startswith("y") or YN == "":
        if GCP:

            metadata_pass = fetch_pass_from_metadata(user_data)
            if metadata_pass:
                passw, is_generated = request_password(ADMIN_USER, metadata_pass,
                f"Type a password for the '{ADMIN_USER}' account (if left blank, GCP metadata suggested password will be used):")
            else:
                passw, is_generated = request_password(ADMIN_USER, generate_random_password(PASS_LEN),
                f"Type a password for the '{ADMIN_USER}' account (if left blank, a random password will be generated):")
            ADMINS.append([ADMIN_USER, passw, is_generated])
            PW_SET = True

        elif EC2  and 'admin_pw' in user_data:
            ADMINS.append([ADMIN_USER, user_data['admin_pw']])
            PW_SET = True
        else:
            passw, is_generated = request_password(ADMIN_USER, generate_random_password(PASS_LEN),
                f"Type a password for the '{ADMIN_USER}' account (if left blank, a random password will be generated):")
            ADMINS.append([ADMIN_USER, passw, is_generated])
            PW_SET = True
        break
    elif YN.lower().startswith("n"):
        break
    else:
        print("Error: This response was not a valid response.")

# Function to determine if user exists...
def user_exists(user):
    # Check this user exists...
    try:
        pwd.getpwnam(user)
        return True
    except Exception:
        return False


def group_exists(group):
    import grp
    try:
        grp.getgrnam(group)
        return True
    except KeyError:
        return False

# If not root enabled, user must choose an existing/or must create a new account...
passw = ''
while (len(ADMINS) == 0):
    print()
    OTHER_LOGIN = get_raw_input("> Specify the username for an existing user or for the new user account: ")
    OTHER_LOGIN = OTHER_LOGIN.strip()
    OTHER_LOGIN = esc_str(OTHER_LOGIN)

    # Check invalid strings for this user...
    regex = re.compile('[a-zA-Z0-9_\s]+')
    matches = regex.match(OTHER_LOGIN)
    if not matches:
        print("Error: This is an invalid username")
        continue

    if GCP:
        metadata_pass = fetch_pass_from_metadata(user_data)
        passw, is_generated = request_password(OTHER_LOGIN, metadata_pass,
            f"Type a password for the '{OTHER_LOGIN}' account (if left blank, GCP metadata suggested password will be used):")
    else:
        passw, is_generated = request_password(OTHER_LOGIN, generate_random_password(PASS_LEN),
                f"Type a password for the '{OTHER_LOGIN}' account (if left blank, a random password will be generated):")
    PW_SET = True

    # Everything is ok...
    ADMINS.append([OTHER_LOGIN, passw, is_generated])


N_CORES = get_n_cores()

if NODE_TYPE == "primary":
    # Keys to be replaced in config-local.json...
    config_db_dict = {
        "host.name": LOGIN_IP,
        "admin_ui.https.ip_address": OVPN_IFACE,
        "admin_ui.https.port": OVPN_PORT,
        "cs.https.ip_address": OVPN_IFACE,
        "cs.https.port": OVPN_PORT,
        "vpn.daemon.0.listen.ip_address": OVPN_IFACE,
        "vpn.daemon.0.server.ip_address": OVPN_IFACE,
        "vpn.daemon.0.listen.port": OVPND_PORT,
        "vpn.client.routing.reroute_gw": REROUTE_GW,
        "vpn.client.routing.reroute_dns": REROUTE_DNS,
        "vpn.server.routing.private_access": PRIVATE_ACCESS,
        "vpn.server.daemon.tcp.port": OVPND_PORT,
        "vpn.server.daemon.tcp.n_daemons": N_CORES,
        "vpn.server.daemon.udp.n_daemons": N_CORES,
    }
elif NODE_TYPE == "secondary":
    config_db_dict = {
        "host.name": LOGIN_IP,
        "cs.https.ip_address": OVPN_IFACE,
        "cs.https.port": OVPN_PORT,
        "vpn.server.routing.private_access": PRIVATE_ACCESS,
    }


def write_json_file(data, filename):
    with open(filename, 'w') as fp:
        json.dump(data, fp)


def read_json_file(filename):
    with open(filename, 'r') as f:
        data = json.load(f)
    return data


def generate_configdb_local_json():
    # Generate the config_local DB JSON initialization
    data = ACTIVE_PROFILE_DEFAULT
    default = data['Default'] = {}
    default.update(config_db_dict)

    if PRIVATE_ACCESS == 'nat':
        for i, sn in enumerate(PRIVATE_NETS):
            default["vpn.server.routing.private_network.%d" % (i, )] = sn

    write_json_file(data, JSON_CONF_LOCAL)


def generate_configdb_json():
    # Generate the config DB JSON initialization
    data = ACTIVE_PROFILE_DEFAULT
    default = data['Default'] = {}

    default.update({"auth.module.type": AUTH_MODULE})

    write_json_file(data, JSON_CONF)


def wait_for_result(d):
    """Iterate twisted reactor to get deferred result"""
    while not d.called:
        time.sleep(0.05)
        reactor.iterate()


def check_subscription(key):
    subhelper = SubscriptionHelper()
    d = subhelper.check_subscription(key)

    def check_cb(reply):
        if isinstance(reply, dict):
            if reply.get('error'):
                print(reply['error'])
                return False
            else:
                valid, msg = SubscriptionTracking.get_subscription_message(reply['state'])
                if not valid:
                    print(msg)
                return valid
        return True

    if check_cb(d):  # check_subscription can return deferred or dict with error
        d.addCallback(check_cb)
        return d


def activate_subscription(key):
    """Check subscription key.
    @return: True if it os valid subscription key;
    """
    d = check_subscription(key)
    if d:
        wait_for_result(d)
        res = d.result
        if res:  # Subscription is valid
            config_db_dict['subscription.bundle'] = key
            print("Activation succeeded")
            return True
    print('Activation failed')


def activate_license(lic):
    cmd = ["/usr/local/openvpn_as/scripts/liman", "-d", "/usr/local/openvpn_as/etc/licenses", "Activate", lic]
    retv = subprocess.run(cmd)
    if retv.returncode == 0:
        print("Activation succeeded")
        return True
    else:
        print("Activation failed\n{}\n{}".format(cmd, retv.returncode))


def activate_as(lic):
    """Activate the AS with Subscription or License key.
    Skip activation when no key provided.
    Skip activation after the first input in batch mode.
    """

    while True:
        print()
        key = get_raw_input("> Please specify your Activation key (or leave blank to specify later): ", lic)
        if not key:
            break

        try:
            LicenseKeyString.validate(key)
            if activate_license(key):
                break
        except Exception:
            if activate_subscription(key):
                break

        if BATCH:
            break


activate_as(LICENSE)

print()
print()
print()
print("Initializing OpenVPN...")

print("Removing Cluster Admin user login...")
cmd = ["userdel", "admin_c"]
if DEBUG:
    print(cmd)
retv = subprocess.run(cmd)

def get_digested_pass(passwd):
    password = AuthLocal.generate_password(passwd.encode()).decode()
    return password

def set_user_key_value(user, key, value):
    CMD = ['/usr/local/openvpn_as/scripts/confdba', '-u', '-p', user, '-mk', key, '-v', value]
    retv = subprocess.run(CMD, stdout=subprocess.PIPE)
    if DEBUG:
        print("userdba cmd=", CMD, retv.returncode)
    return retv.returncode
    
def set_local_password(user, passwd):
    retv = set_user_key_value(user, 'pvt_password_digest', get_digested_pass(passwd))
    if retv != 0:
        print("Error: Could not set password")
        sys.exit(1)

def set_user_auth_type(user, auth_type):
    retv = set_user_key_value(user, 'user_auth_type', auth_type)
    if retv != 0:
        print('Could not set auth method for a user')

# Here's where we add a new account (ie, not 'root' or enabling an existing account)...
if (ADMINS[0][0] != 'root'):
    if DEBUG:
        print(ADMINS)

# Keys to be replaced in as.conf...
dict = {
    
    "auth.pam.0.service": "sshd",
    "db_startup_wait": None,
    "node_type": None,
}

# DB startup wait is only needed on secondary nodes, since
# the daemon may start up before the DB files are in place
if NODE_TYPE == "secondary":
    dict['db_startup_wait'] = '1000000'
    dict['node_type'] = 'SECONDARY'

# Open the template config...
f = open(AS_TEMPL_CONF)
lines = f.readlines()
f.close()

# Replace config items with dictionary items...
for a in range(len(lines)):
    line = lines[a]
    parts = line.split("=")
    if len(parts) != 2:
        continue
    key = parts[0].replace("#", "").strip()
    if key in dict:
        new_line = line
        val = dict[key]
        if val is None:
            new_line = "# " + line
        else:
            new_line = key + "=" + val
        if new_line[-1] != "\n":
            new_line = new_line + "\n"
        lines[a] = new_line

# Write the new as config file...
print("Writing as configuration file...")
f = open(AS_CONF, 'w')
f.writelines(lines)
f.close()


if NODE_TYPE == "primary":
    generate_configdb_local_json()
    generate_configdb_json()
    # Execute sa...
    print("Perform sa init...")
    SA_CMD = ["/usr/local/openvpn_as/scripts/sa", "-s", OVPN_KEY_SIZE, "-A", OVPN_KEY_ALGORITHM, "Init"]
    retv = subprocess.run(SA_CMD, stdout=subprocess.PIPE)
    if DEBUG:
        print("sa cmd=", SA_CMD, retv.returncode)
    if retv.returncode != 0:
        print("Error: Could not initialize sa.")
        sys.exit(1)

    # Execute userdb wipe...
    print("Wiping any previous userdb...")
    USERDBA_CMD = ["/usr/local/openvpn_as/scripts/userdba", "--wipe"]
    retv = subprocess.run(USERDBA_CMD, stdout=subprocess.PIPE)
    if DEBUG:
        print("userdba cmd=", USERDBA_CMD, retv.returncode)
    if retv.returncode != 0:
        print("Error: Could not wipe userdb.")
        sys.exit(1)

    # Execute userdb default profile...
    print("Creating default profile...")
    USERDBA_CMD2 = ["/usr/local/openvpn_as/scripts/userdba", "--mkuser", "--def"]
    retv = subprocess.run(USERDBA_CMD2, stdout=subprocess.PIPE)
    if DEBUG:
        print("userdba cmd=", USERDBA_CMD2, retv.returncode)
    if retv.returncode != 0:
        print("Error: Could not create default profile.")
        sys.exit(1)

    # Execute userdb default profile...
    print("Modifying default profile...")
    USERDBA_CMD3 = ["/usr/local/openvpn_as/scripts/userdba", "--def", "--mod", "--prop", "autogenerate", "--enable"]
    retv = subprocess.run(USERDBA_CMD3, stdout=subprocess.PIPE)
    if DEBUG:
        print("userdba cmd=", USERDBA_CMD3, retv)
    if retv.returncode != 0:
        print("Error: Could not modify default profile.")
        sys.exit(1)

    # Execute mkuser...
    print("Adding new user to userdb...")
    USERDBA_CMD4 = ["/usr/local/openvpn_as/scripts/userdba", "--mkuser", "--user", ADMINS[0][0]]
    retv = subprocess.run(USERDBA_CMD4, stdout=subprocess.PIPE)
    if DEBUG:
        print("userdba cmd=", USERDBA_CMD4, retv.returncode)
    if retv.returncode != 0:
        print("Error: Could not add user to userdb.")
        sys.exit(1)

    # Modify superuser...
    print("Modifying new user as superuser in userdb...")
    USERDBA_CMD5 = ['/usr/local/openvpn_as/scripts/userdba', "--user", ADMINS[0][0], "--mod", "--prop", "superuser", "--enable"]
    retv = subprocess.run(USERDBA_CMD5, stdout=subprocess.PIPE)
    if DEBUG:
        print("userdba cmd=", USERDBA_CMD5, retv.returncode)
    if retv.returncode != 0:
        print("Error: Could not modify user as superuser in userdb.")
        sys.exit(1)

    if PW_SET and ((len(ADMINS[0]) > 1) and (ADMINS[0][1] != None)):
        with warnings.catch_warnings():
            warnings.simplefilter("ignore")
            if len(ADMINS[0]) >= 3 and ADMINS[0][2]:
                print(f'Auto-generated pass = \"{ADMINS[0][1]}\". Setting in db...')
            else:
                print('Setting password in db...')
            set_local_password(ADMINS[0][0], ADMINS[0][1])
    
    set_user_auth_type(ADMINS[0][0], AUTH_MODULE)

    # Execute sa for web certs...
    print("Getting hostname...")
    try:
        HOSTNAME = HOST if HOST else socket.gethostname()
    except Exception as e:
        print(f"Calling gethostname failed, check your network configuration {e}")
        sys.exit(1)
    print("Hostname: %s" % HOSTNAME)
    HOSTNAME = esc_str(HOSTNAME)
    print("Preparing web certificates...")
    web_ssl = "/usr/local/openvpn_as/etc/web-ssl"
    certool = "/usr/local/openvpn_as/scripts/certool"
    SA_CMD1 = [certool, "-d", web_ssl, "-k", OVPN_WEB_KEY_SIZE, "-A", OVPN_WEB_KEY_ALGORITHM, "--type", "ca", "--unique", "--cn", "OpenVPN Web CA"]
    SA_CMD2 = [certool, "-d", web_ssl, "-k", OVPN_WEB_KEY_SIZE, "-A", OVPN_WEB_KEY_ALGORITHM, "--type", "webserver", "--exp=365", "--remove_csr",
               "--sn_off", "--serial", "1", "--name", "server", "--cn", HOSTNAME]
    retv1 = subprocess.run(SA_CMD1, stdout=subprocess.PIPE)
    retv2 = subprocess.run(SA_CMD2, stdout=subprocess.PIPE)
    if DEBUG:
        print("sa web cert cmd =", SA_CMD1, retv1.returncode)
        print("sa web cert cmd =", SA_CMD2, retv2.returncode)
    if retv1.returncode != 0 or retv2.returncode != 0:
        print("Error: Could not initialize web certs.")
        sys.exit(1)


elif NODE_TYPE == "secondary":
    generate_configdb_local_json()
    generate_configdb_json()

    # Execute userdb wipe...
    print("Wiping any previous userdb...")
    USERDBA_CMD = ["/usr/local/openvpn_as/scripts/userdba", "--wipe"]
    retv = subprocess.run(USERDBA_CMD, stdout=subprocess.PIPE)
    if DEBUG:
        print("userdba cmd=", USERDBA_CMD, retv.returncode)
    if retv.returncode != 0:
        print("Error: Could not wipe userdb.")
        sys.exit(1)

    print("Perform sa init...")
    SA_CMD = ["/usr/local/openvpn_as/scripts/sa", "-s", str(DEFAULT_KEY_SIZE), "-A", DEFAULT_KEY_ALGORITHM, "Init"]
    retv = subprocess.run(SA_CMD, stdout=subprocess.PIPE)
    if DEBUG:
        print("sa cmd=", SA_CMD, retv.returncode)
    if retv.returncode != 0:
        print("Error: Could not initialize sa.")
        sys.exit(1)


# Execute web user...
print("Getting web user account...")
CMD1 = ["/usr/local/openvpn_as/scripts/confdba", "--static", "--key", "cs.user"]
try:
    retv = subprocess.check_output(CMD1, stderr=None)
except subprocess.CalledProcessError:
    print("Error: Could not get web user account.")
    sys.exit(1)
if DEBUG:
    print("getting web user acct cmd=", CMD1, retv)

# Extract the web user acct from response...
def correct_response(retv):
    web_user = retv.decode() if isinstance(retv, bytes) else retv
    web_user = web_user[:-1] if web_user[-1:] == '\n' else web_user
    web_user = esc_str(web_user)
    return web_user

web_user = correct_response(retv)

# Execute web group...
print("Adding web group account...")
CMD2 = ["/usr/local/openvpn_as/scripts/confdba", "--static", "--key", "cs.group"]
try:
    retv = subprocess.check_output(CMD2, stderr=None)
except subprocess.CalledProcessError:
    print("Error: Could not add web group account.")
    sys.exit(1)

if DEBUG:
    print("adding web group account cmd=", CMD2, retv)

# Extract the web group acct from response...
web_group = correct_response(retv)

# Execute web account user add...
if not user_exists(web_user):
    print("Adding web user account...")
    CMD3 = ['useradd', '-s', '/sbin/nologin', web_user]
    if DEBUG:
        print("adding web user acct cmd=", CMD3, retv)

    try:
        subprocess.check_output(CMD3)
    except subprocess.CalledProcessError:
        print("Error: Could not execute web user account useradd.")

# Add web_group ...
print("Adding web group...")
ADDGRP = ['groupadd', web_group]
retv = subprocess.run(ADDGRP)
if DEBUG:
    print("adding web group cmd=", ADDGRP, retv)

# Execute chown...
print("Adjusting license directory ownership...")
CMD4 = ["chown", "-R", f"{web_user}.{web_group}", "/usr/local/openvpn_as/etc/licenses"]
retv = subprocess.run(CMD4, stdout=subprocess.PIPE)
if DEBUG:
    print("chown cmd=", CMD4, retv.returncode)
if retv.returncode != 0:
    print("Error: Could not execute chown on license directory.")
    sys.exit(1)

# Execute userdb init...
if NODE_TYPE in ("primary", "secondary"):
    print("Initializing confdb...")

    CONF_INIT = ["/usr/local/openvpn_as/scripts/confdba", "--local", "--load",  "--file", JSON_CONF_LOCAL]
    retv = subprocess.run(CONF_INIT, stdout=subprocess.PIPE)

    if DEBUG:
        print("confdba init cmd=", CONF_INIT, retv)
    if retv.returncode != 0:
        print("Error: Could not initialize confdb_local.")
        sys.exit(1)

    CONF_INIT = ["/usr/local/openvpn_as/scripts/confdba", "--load", "--file", JSON_CONF]
    retv = subprocess.run(CONF_INIT, stdout=subprocess.PIPE)
    if DEBUG:
        print("confdba init cmd=", CONF_INIT, retv.returncode)
    if retv.returncode != 0:
        print("Error: Could not initialize confdb.")
        sys.exit(1)



check_and_set_initial_version()

# Generate PAM config
print("Generating PAM config for openvpnas ...")
GENPAM = "/usr/local/openvpn_as/scripts/openvpnas_gen_pam"
retv = subprocess.run(GENPAM, stdout=subprocess.PIPE)
if DEBUG:
    print("gen pam cmd=", GENPAM, retv)
if retv.returncode != 0:
    print("Error: Could not generate PAM config.")
    sys.exit(1)

print("Enabling service")
enableOVPN = ["systemctl", "enable", "openvpnas"]
# Workaround for bug in RHEL7
retv = subprocess.run(["systemctl", "daemon-reexec"])
#
retv = subprocess.run(enableOVPN).returncode
if retv != 0:
    print("Error: Could not execute '%s' to enable startup/shutdown scripts" % enableOVPN)
    sys.exit(1)
if DEBUG:
    print("enable openvpnas cmd=", enableOVPN, retv)

# Perform iptables command to force initialization...
IPTABLES_NULL = "iptables", "--list"
retv = subprocess.run(IPTABLES_NULL, stdout=subprocess.PIPE)
if retv.returncode != 0:
    print("Warning: Iptables list command failed.  Iptables may not be properly initialized.")
if DEBUG:
    print("iptables null cmd=", IPTABLES_NULL, retv.returncode)

# Start the server daemon...
if not opts.no_start:
    if NODE_TYPE in ("primary", "secondary"):
        print("Starting openvpnas...")
        INIT = ["systemctl", "start", "openvpnas"]
        retv = subprocess.run(INIT, stdout=subprocess.PIPE)
        if DEBUG:
            print("server init=", retv.returncode)
        if retv.returncode != 0:
            print("Error: Could not execute server start.")
            sys.exit(1)

    # Print exit messages...
    print()
    print("NOTE: Your system clock must be correct for OpenVPN Access Server")
    print("to perform correctly.  Please ensure that your time and date")
    print("are correct on this system.")
    print()
    print("Initial Configuration Complete!")

    if NODE_TYPE == "primary":
        print()
        print("You can now continue configuring OpenVPN Access Server by")
        print("directing your Web browser to this URL:")

    if NODE_TYPE == "primary":
        if EC2 and ec2_get_pub_ip():
            LOGIN_IP = ec2_get_pub_ip()
        elif GCP and gcp_get_pub_ip():
            LOGIN_IP = gcp_get_pub_ip()
        print()
        if OVPN_PORT != "443":
            client_ui = "https://" + LOGIN_IP + ":" + OVPN_PORT + "/"
        else:
            client_ui = "https://" + LOGIN_IP + "/"
        admin_ui = client_ui + "admin"
        print(admin_ui)
        print()
        print("During normal operation, OpenVPN AS can be accessed via these URLs:")
        print("Admin  UI:", admin_ui)
        print("Client UI:", client_ui)

        password_msg = ' the password you specified during the setup.'
        # NOTE: ADMINS is a list of lists, where each list represent a user with password.
        # If a user did not specify a password, the generated will be printed
        if len(ADMINS[0]) >= 3 and ADMINS[0][2]:
            password_msg = f'"{ADMINS[0][1]}" password.'

        print(f'To login please use the "{ADMINS[0][0]}" account with {password_msg}')

    if NODE_TYPE == "secondary":
        print()
        print("This node has been configured as a Secondary Access Server")
        print("node for backup/standby.  Please continue with the redundancy")
        print("configuration on the Primary Access Server node.")

    if VERSION:
        print()
        print("See the Release Notes for this release at:")
        print("   https://openvpn.net/vpn-server-resources/release-notes/")
        print()
