#!/usr/bin/python3

import os
import socket
import glob
import ssl
import sys
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
    # if installed into system path, do not muck with things
    sys.path.append(path)
import confluent.sshutil as sshutil
import confluent.certutil as certutil
import confluent.config.configmanager as configmanager
import subprocess
import tempfile
import shutil

def fprint(txt):
    sys.stdout.write(txt)
    sys.stdout.flush()


def tftp_works():
    try:
        subprocess.check_call(['curl', '--connect-timeout', '2', '-sf', 'tftp://localhost/confluent/x86_64/ipxe.efi', '-o', '/dev/null'])
        return True
    except Exception:
        return False

def emprint(txt):
    if sys.stdout.isatty():
        print('\x1b[1m\x1b[4m' + txt + '\x1b[0m')
    else:
        print(txt)

def deployment_configured():
    return os.path.exists('/var/lib/confluent/public/site/confluent_uuid')

def webserver_listening():
    try:
        conn = socket.create_connection(('localhost', 443))
        return conn
    except Exception:
        return False


def certificates_missing_ips(conn):
    # check if the tls can verify by the right CAs, then further
    # check if all ip addresses are in the certificate offered
    ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
    ctx.check_hostname = False
    for cacert in glob.glob('/var/lib/confluent/public/site/tls/*.pem'):
        ctx.load_verify_locations(cacert)
    sock = ctx.wrap_socket(conn)
    crt = sock.getpeercert()
    sans = crt.get('subjectAltName', [])
    ips = certutil.get_ip_addresses()
    missing_ips = []
    for ip in ips:
        for san in sans:
            field, val = san
            if val[-1] == '\n':
                val = val[:-1]
            if ':' in val:
                # must normalize ipv6 to a sane value
                val = socket.getaddrinfo(val, 443, type=socket.SOCK_STREAM)[0][-1][0]
            if ip == val:
                break
        else:
            missing_ips.append(ip)
    return missing_ips


def web_download_works():
    try:
        subprocess.check_call(['curl', '-skf', 'https://localhost/confluent-public/site/confluent_uuid', '-o', '/dev/null'])
    except Exception:
        return False
    return True
            

def nics_missing_ipv6():
    # check for ability to create AF_INET6, for kernel disabled ipv6
    a = socket.socket(socket.AF_INET6)
    ipaddrs = subprocess.check_output(['ip', '-br', 'a']).split(b'\n')
    missingnics = []
    for line in ipaddrs:
        comps = line.split()
        if not comps:
            continue
        iname, state = comps[:2]
        if iname == b'lo':
            continue
        addrs = comps[2:]
        hasv6 = False
        hasv4 = False
        for addr in addrs:
            if b'.' in addr:
                hasv4 = True
            if addr.startswith(b'fe80::'):
                hasv6 = True
        if hasv4 and not hasv6:
            missingnics.append(iname.decode('utf8'))
    return missingnics

def insecure_boot_attempts():
    insecurenodes = set([])
    with open('/var/log/confluent/events') as eventin:
        line = True
        while line:
            line = eventin.readline()
            if 'insecure mode is disabled' in line:
                line = line.split()
                insecurenodes.add(line[7])
    for node in insecurenodes:
        currattr = subprocess.check_output(['nodeattrib', node, 'deployment.useinsecureprotocols'])
        currattr = currattr.split()
        if len(currattr) > 2 and currattr[2] == b'firmware':
            continue
        else:
            return True
    return False


def uuid_matches():
    with open('/var/lib/confluent/public/site/confluent_uuid', 'r') as uuidf:
        fsuuid = uuidf.read().strip()
    dbuuid = configmanager.get_global('confluent_uuid')
    return dbuuid == fsuuid

if __name__ == '__main__':
    sys.stdout.write('OS Deployment: ')
    sys.stdout.flush()
    if deployment_configured():
        print("Initialized")
        sys.stdout.write('Confluent UUID: ')
        sys.stdout.flush()
        if uuid_matches():
            print('Consistent')
        else:
            emprint('Inconsistent between confluent database and /var/lib/confluent (Example resolution: confetty set /uuid resync=1)')
        fprint('Web Server: ')
        conn = webserver_listening()
        if conn:
            print('Running')
            fprint('Web Certificate: ')
            cert = certificates_missing_ips(conn)
            if cert:
                cert = ', '.join(cert)
                emprint('Addresses missing from certificate: {0} (Example resolution: osdeploy initialize -t)'.format(cert))
            else:
                print('OK')
                fprint('Checking web download: ')
                if web_download_works():
                    print('OK')
                else:
                    emprint('Failed to download /confluent-public/site/confluent_uuid')
        else:
            emprint('Not Running (Example resolution: systemctl enable httpd --now)')
        fprint('TFTP Status: ')
        if tftp_works():
            print('OK')
        else:
            emprint('TFTP failure, PXE will not work, though media and HTTP boot can still work. (Example resolution: osdeploy initialize -p)')
        fprint('SSH root user public key: ')
        if glob.glob('/var/lib/confluent/public/site/ssh/*.rootpubkey'):
            print('OK')
        else:
            emprint('No trusted ssh keys for root user, passwordless SSH from managers to nodes may not work (Example resolution: osdeploy initialize -u)')
        if sshutil.sshver() > 7.6:
            fprint('Checking SSH Certificate authority: ')
            try:
                sshutil.prep_ssh_key('/etc/confluent/ssh/ca')
                print('OK')
            except Exception:
                emprint('Failed to load SSH authority key, deployed servers will not have host certificates for known_hosts and users may be unable to ssh between nodes without a password (Example resolution: osdeploy initialize -s)')            
            fprint('Checking confluent SSH automation key: ')
            try:
                sshutil.prep_ssh_key('/etc/confluent/ssh/automation')
                print('OK')
            except subprocess.CalledProcessError:
                emprint('Failed to load confluent automation key, syncfiles and profile ansible plays will not work (Example resolution: osdeploy initialize -a)')
        fprint('Checking for blocked insecure boot: ')
        if insecure_boot_attempts():
            emprint('Some nodes are attempting network boot using PXE or HTTP boot, but the node is not configured to allow this (Example resolution: nodegroupattrib everything deployment.useinsecureprotocols=firmware)')
        else:
            print('OK')
        fprint('Checking IPv6 enablement: ')
        nics = nics_missing_ipv6()
        if nics:
            snics = ','.join(nics)
            emprint('Some interfaces ({0}) have ipv6 disabled, and may be unable to fully perform discovery or deployment (Example resolution: nmcli c m {1} ipv6.method link-local )'.format(snics, nics[0]))
        else:
            print('OK')
    else:
        print("Uninitialized, further OS deployment checks skipped, see `osdeploy initialize` to set up OS deployment feature")
