# hostapd.py -
#   configure system as an AP directing all client queries to the web ui
#

import re
import os
import uuid
import textwrap
import subprocess
from spiro.logger import log, debug


def init():
    """makes sure everything runs smoothly later on."""
    p = subprocess.run(["sudo", "systemctl", "daemon-reload"], capture_output=False)


def install_reqs():
    """installs hostapd and dnsmasq. returns a tuple of (installation status, output of failed command)."""
    reqs = ['dnsmasq', 'hostapd']
    for r in reqs:
        p = subprocess.run(['dpkg', '-l', r], capture_output=False)
        if p.returncode != 0:
            # install requirement
            p = subprocess.run(["apt", "install", "-y", r], capture_output=False)
            if p.returncode != 0:
                # installation failed
                return (False, p.stderr)
            # stop the service as it is not properly configured yet
            p = subprocess.run(["systemctl", "stop", r], capture_output=False)

    return (True, None)


def config_hostapd():
    """sets up hostapd config, see also get_ssid()"""
    # use last 6 digits of MAC address as unique id
    u = str(uuid.uuid1())
    id = "spiro-" + u[30:36]
    pwd = u[0:8]

    with open("/etc/hostapd/hostapd.conf", "w") as f:
        f.write(textwrap.dedent("""\
                                # auto-generated by spiro software
                                interface=wlan0
                                driver=nl80211
                                ssid={0}
                                hw_mode=g
                                channel=7
                                wmm_enabled=0
                                macaddr_acl=0
                                auth_algs=1
                                ignore_broadcast_ssid=0
                                wpa=2
                                wpa_passphrase={1}
                                wpa_key_mgmt=WPA-PSK
                                wpa_pairwise=TKIP
                                rsn_pairwise=CCMP
                                """.format(id, pwd)))


def config_dnsmasq():
    """sets up dnsmasq config. all dns queries are forwarded to the local ip (192.168.138.1)"""
    with open("/etc/dnsmasq.conf", "w") as f:
        f.write(textwrap.dedent("""\
                                # auto-generated by spiro software
                                interface=wlan0
                                dhcp-range=192.168.138.10,192.168.138.254,12h
                                address=/#/192.168.138.1
                                no-resolv
                                no-poll
                                no-hosts
                                domain=spiro.local
                                resolv-file=/dev/null
                                """))
    with open("/etc/default/dnsmasq", "w") as f:
        f.write(textwrap.dedent("""\
                                ENABLED=1
                                DNSMASQ_EXCEPT=lo
                                """))


def config_dhcpcd(enable):
    """enables/disables static ip for the wlan0 interface. replaces the system dhcpcd.conf with our own."""
    with open("/etc/dhcpcd.conf", "w") as f:
        f.write(textwrap.dedent("""\
                                # auto-generated by spiro software
                                hostname
                                clientid
                                persistent
                                option rapid_commit
                                option domain_name_servers, domain_name, domain_search, host_name
                                option classless_static_routes
                                option interface_mtu
                                option ntp_servers
                                require dhcp_server_identifier
                                slaac private
                                """))
        if enable == True:
            f.write(textwrap.dedent("""\
                                    interface wlan0
                                    static ip_address=192.168.138.1/24
                                    nohook wpa_supplicant
                                    """))


def restart_services():
    """restarts the required services after config updates. returns False if any restart command returns
    a non-zero exit code."""
    services = ['dhcpcd', 'dnsmasq', 'hostapd']
    codes = []

    for s in services:
        p = subprocess.run(["sudo", "systemctl", "restart", s], capture_output=False)
        debug("Restarted service {0} with status code {1}.".format(s, p.returncode))
        codes.append(p.returncode)

    return(all(c == 0 for c in codes))


def enable_services():
    # make sure wlan interface is not blocked
    p = subprocess.run(['rfkill', 'unblock', 'wlan'], capture_output=False)

    services = ['dnsmasq', 'hostapd']
    for s in services:
        p = subprocess.run(['sudo', 'systemctl', 'unmask', s], capture_output=False)
        p = subprocess.run(['sudo', 'systemctl', 'enable', s], capture_output=False)


def disable_services():
    services = ['dnsmasq', 'hostapd']
    for s in services:
        p = subprocess.run(['sudo', 'systemctl', 'stop', s], capture_output=False)
        p = subprocess.run(['sudo', 'systemctl', 'disable', s], capture_output=False)


def start_ap():
    if not is_ready():
        # initial steps, run 'sudo spiro --enable-hotspot'
        log("Setting up dependencies...")
        init()
        install_reqs()
        log("Configuring system...")
        config_dnsmasq()
        config_hostapd()

    config_dhcpcd(enable=True)
    log("Starting services...")
    enable_services()
    r = restart_services()
    if not r:
        log("Failed to restart services.")
        log("Setting up access point failed.")
        return(1)
    else:
        ssid, pwd = get_ssid()
        log("Access point configured and enabled. Below are the details for connecting to it:")
        log("\nSSID:     " + ssid)
        log("Password: " + pwd)
        log("\nConnect to the web interface using the address http://spiro.local")
        return(0)


def stop_ap():
    log("Disabling services...")
    init()
    config_dhcpcd(enable=False)
    p = subprocess.run(['sudo', 'systemctl', 'restart', 'dhcpcd'], capture_output=False)
    disable_services()
    log("Access point disabled.")


def get_ssid():
    """reads ssid/password from hostapd.conf. doesn't handle comments."""
    ssid = None
    pwd = <PASSWORD>
    params = {}

    try:
        with open("/etc/hostapd/hostapd.conf", "r") as f:
            for l in f:
                lgrp = re.match(r'\s*(\w+)\s*=\s*([\w-]+)\s*', l)
                if lgrp:
                    params[lgrp.group(1)] = lgrp.group(2)
    except OSError as e:
        debug('get_ssid(): ' + str(e))
        return None, None

    return params.get('ssid'), params.get('wpa_passphrase')


def is_ready():
    """returns True if wifi hotspot is ready to be used (i.e., spiro --enable-hotspot was
       executed at some point)"""
    inst_reqs = ['dnsmasq', 'hostapd']
    for r in inst_reqs:
        p = subprocess.run(['dpkg', '-l', r], capture_output=True)
        if p.returncode != 0:
            debug("is_ready: dpkg failed for " + r)
            return False

    cfg_reqs = ["/etc/hostapd/hostapd.conf", "/etc/dnsmasq.conf", "/etc/dhcpcd.conf"]
    for cfg_file in cfg_reqs:
        with open(cfg_file, "r") as f:
            if not re.search(r'# auto-generated by spiro software', f.read()):
                debug("is_ready: no spiro signature in " + cfg_file)
                return False

    return True


def is_enabled():
    """checks if hostapd and dnsmasq services are running. possibly too minimal."""
    services = ['hostapd', 'dnsmasq']

    for svc in services:
        p = subprocess.run(['systemctl', 'is-enabled', svc], capture_output=True)
        if p.returncode != 0:
            return False

    return True

# hostapd.py -
#   configure system as an AP directing all client queries to the web ui
#

import re
import os
import uuid
import textwrap
import subprocess
from spiro.logger import log, debug


def init():
    """makes sure everything runs smoothly later on."""
    p = subprocess.run(["sudo", "systemctl", "daemon-reload"], capture_output=False)


def install_reqs():
    """installs hostapd and dnsmasq. returns a tuple of (installation status, output of failed command)."""
    reqs = ['dnsmasq', 'hostapd']
    for r in reqs:
        p = subprocess.run(['dpkg', '-l', r], capture_output=False)
        if p.returncode != 0:
            # install requirement
            p = subprocess.run(["apt", "install", "-y", r], capture_output=False)
            if p.returncode != 0:
                # installation failed
                return (False, p.stderr)
            # stop the service as it is not properly configured yet
            p = subprocess.run(["systemctl", "stop", r], capture_output=False)

    return (True, None)


def config_hostapd():
    """sets up hostapd config, see also get_ssid()"""
    # use last 6 digits of MAC address as unique id
    u = str(uuid.uuid1())
    id = "spiro-" + u[30:36]
    pwd = u[0:8]

    with open("/etc/hostapd/hostapd.conf", "w") as f:
        f.write(textwrap.dedent("""\
                                # auto-generated by spiro software
                                interface=wlan0
                                driver=nl80211
                                ssid={0}
                                hw_mode=g
                                channel=7
                                wmm_enabled=0
                                macaddr_acl=0
                                auth_algs=1
                                ignore_broadcast_ssid=0
                                wpa=2
                                wpa_passphrase={1}
                                wpa_key_mgmt=WPA-PSK
                                wpa_pairwise=TKIP
                                rsn_pairwise=CCMP
                                """.format(id, pwd)))


def config_dnsmasq():
    """sets up dnsmasq config. all dns queries are forwarded to the local ip (192.168.138.1)"""
    with open("/etc/dnsmasq.conf", "w") as f:
        f.write(textwrap.dedent("""\
                                # auto-generated by spiro software
                                interface=wlan0
                                dhcp-range=192.168.138.10,192.168.138.254,12h
                                address=/#/192.168.138.1
                                no-resolv
                                no-poll
                                no-hosts
                                domain=spiro.local
                                resolv-file=/dev/null
                                """))
    with open("/etc/default/dnsmasq", "w") as f:
        f.write(textwrap.dedent("""\
                                ENABLED=1
                                DNSMASQ_EXCEPT=lo
                                """))


def config_dhcpcd(enable):
    """enables/disables static ip for the wlan0 interface. replaces the system dhcpcd.conf with our own."""
    with open("/etc/dhcpcd.conf", "w") as f:
        f.write(textwrap.dedent("""\
                                # auto-generated by spiro software
                                hostname
                                clientid
                                persistent
                                option rapid_commit
                                option domain_name_servers, domain_name, domain_search, host_name
                                option classless_static_routes
                                option interface_mtu
                                option ntp_servers
                                require dhcp_server_identifier
                                slaac private
                                """))
        if enable == True:
            f.write(textwrap.dedent("""\
                                    interface wlan0
                                    static ip_address=192.168.138.1/24
                                    nohook wpa_supplicant
                                    """))


def restart_services():
    """restarts the required services after config updates. returns False if any restart command returns
    a non-zero exit code."""
    services = ['dhcpcd', 'dnsmasq', 'hostapd']
    codes = []

    for s in services:
        p = subprocess.run(["sudo", "systemctl", "restart", s], capture_output=False)
        debug("Restarted service {0} with status code {1}.".format(s, p.returncode))
        codes.append(p.returncode)

    return(all(c == 0 for c in codes))


def enable_services():
    # make sure wlan interface is not blocked
    p = subprocess.run(['rfkill', 'unblock', 'wlan'], capture_output=False)

    services = ['dnsmasq', 'hostapd']
    for s in services:
        p = subprocess.run(['sudo', 'systemctl', 'unmask', s], capture_output=False)
        p = subprocess.run(['sudo', 'systemctl', 'enable', s], capture_output=False)


def disable_services():
    services = ['dnsmasq', 'hostapd']
    for s in services:
        p = subprocess.run(['sudo', 'systemctl', 'stop', s], capture_output=False)
        p = subprocess.run(['sudo', 'systemctl', 'disable', s], capture_output=False)


def start_ap():
    if not is_ready():
        # initial steps, run 'sudo spiro --enable-hotspot'
        log("Setting up dependencies...")
        init()
        install_reqs()
        log("Configuring system...")
        config_dnsmasq()
        config_hostapd()

    config_dhcpcd(enable=True)
    log("Starting services...")
    enable_services()
    r = restart_services()
    if not r:
        log("Failed to restart services.")
        log("Setting up access point failed.")
        return(1)
    else:
        ssid, pwd = get_ssid()
        log("Access point configured and enabled. Below are the details for connecting to it:")
        log("\nSSID:     " + ssid)
        log("Password: " + pwd)
        log("\nConnect to the web interface using the address http://spiro.local")
        return(0)


def stop_ap():
    log("Disabling services...")
    init()
    config_dhcpcd(enable=False)
    p = subprocess.run(['sudo', 'systemctl', 'restart', 'dhcpcd'], capture_output=False)
    disable_services()
    log("Access point disabled.")


def get_ssid():
    """reads ssid/password from hostapd.conf. doesn't handle comments."""
    ssid = None
    pwd = <PASSWORD>
    params = {}

    try:
        with open("/etc/hostapd/hostapd.conf", "r") as f:
            for l in f:
                lgrp = re.match(r'\s*(\w+)\s*=\s*([\w-]+)\s*', l)
                if lgrp:
                    params[lgrp.group(1)] = lgrp.group(2)
    except OSError as e:
        debug('get_ssid(): ' + str(e))
        return None, None

    return params.get('ssid'), params.get('wpa_passphrase')


def is_ready():
    """returns True if wifi hotspot is ready to be used (i.e., spiro --enable-hotspot was
       executed at some point)"""
    inst_reqs = ['dnsmasq', 'hostapd']
    for r in inst_reqs:
        p = subprocess.run(['dpkg', '-l', r], capture_output=True)
        if p.returncode != 0:
            debug("is_ready: dpkg failed for " + r)
            return False

    cfg_reqs = ["/etc/hostapd/hostapd.conf", "/etc/dnsmasq.conf", "/etc/dhcpcd.conf"]
    for cfg_file in cfg_reqs:
        with open(cfg_file, "r") as f:
            if not re.search(r'# auto-generated by spiro software', f.read()):
                debug("is_ready: no spiro signature in " + cfg_file)
                return False

    return True


def is_enabled():
    """checks if hostapd and dnsmasq services are running. possibly too minimal."""
    services = ['hostapd', 'dnsmasq']

    for svc in services:
        p = subprocess.run(['systemctl', 'is-enabled', svc], capture_output=True)
        if p.returncode != 0:
            return False

    return True

# hostapd.py - #   configure system as an AP directing all client queries to the web ui # makes sure everything runs smoothly later on. installs hostapd and dnsmasq. returns a tuple of (installation status, output of failed command). # install requirement # installation failed # stop the service as it is not properly configured yet sets up hostapd config, see also get_ssid() # use last 6 digits of MAC address as unique id \                                 # auto-generated by spiro software                                 interface=wlan0                                 driver=nl80211                                 ssid={0}                                 hw_mode=g                                 channel=7                                 wmm_enabled=0                                 macaddr_acl=0                                 auth_algs=1                                 ignore_broadcast_ssid=0                                 wpa=2                                 wpa_passphrase={1}                                 wpa_key_mgmt=WPA-PSK                                 wpa_pairwise=TKIP                                 rsn_pairwise=CCMP sets up dnsmasq config. all dns queries are forwarded to the local ip (192.168.138.1) \                                 # auto-generated by spiro software                                 interface=wlan0                                 dhcp-range=192.168.138.10,192.168.138.254,12h                                 address=/#/192.168.138.1                                 no-resolv                                 no-poll                                 no-hosts                                 domain=spiro.local                                 resolv-file=/dev/null \                                 ENABLED=1                                 DNSMASQ_EXCEPT=lo enables/disables static ip for the wlan0 interface. replaces the system dhcpcd.conf with our own. \                                 # auto-generated by spiro software                                 hostname                                 clientid                                 persistent                                 option rapid_commit                                 option domain_name_servers, domain_name, domain_search, host_name                                 option classless_static_routes                                 option interface_mtu                                 option ntp_servers                                 require dhcp_server_identifier                                 slaac private \                                     interface wlan0                                     static ip_address=192.168.138.1/24                                     nohook wpa_supplicant restarts the required services after config updates. returns False if any restart command returns     a non-zero exit code. # make sure wlan interface is not blocked # initial steps, run 'sudo spiro --enable-hotspot' reads ssid/password from hostapd.conf. doesn't handle comments. returns True if wifi hotspot is ready to be used (i.e., spiro --enable-hotspot was        executed at some point) checks if hostapd and dnsmasq services are running. possibly too minimal.