#! /usr/bin/env python
import sys
import dbus
import shlex
import subprocess
from signal import SIGTERM
import os
from time import sleep

# dbus constants.
WPAS_DBUS_SERVICE = "fi.epitest.hostap.WPASupplicant"
WPAS_DBUS_INTERFACE = "fi.epitest.hostap.WPASupplicant"
WPAS_DBUS_OPATH = "/fi/epitest/hostap/WPASupplicant"

WPAS_DBUS_INTERFACES_INTERFACE = "fi.epitest.hostap.WPASupplicant.Interface"
WPAS_DBUS_INTERFACES_OPATH = "/fi/epitest/hostap/WPASupplicant/Interfaces"
WPAS_DBUS_BSSID_INTERFACE = "fi.epitest.hostap.WPASupplicant.BSSID"
WPAS_DBUS_NETWORKS_INTERFACE = "fi.epitest.hostap.WPASupplicant.Network"

def read_config(config):
    cfg = shlex.split(open(config, "r").read())
    options = {}
    for line in cfg:
        (var, delim, value) = line.partition('=')
        if delim:
            var = var.lstrip()                                          # JP: allow assignments to be indented, as they can be when sourced
            if var[0] != '#':
                value = value.partition("#")[0].rstrip()                # JP: strip off comments and trailing whitespace
                if value[0] == value[-1] and value[0] in ('"',"'"):     # JP: strip any surrounding quotes
                    value=value[1:-1]
                options[var] = value
    return options


def wep_hex2dec(key):
    if len(key) not in [10, 26]:
        fail("Bad key", report_type="err")
    
    x=0
    new_key=[]
    while x<len(key):
        new_key.append(int(key[x:x+2],16))
        x+=2

    return new_key

# JP: connect to our hookable reporting functions in /usr/lib/network/globals

def report(report_type, *args):
    report_handler.stdin.write('report_%s %s\n' % (report_type, ' '.join(map(repr,args))))

def fail(msg=None, report_type="fail"):
    if msg:
        report(report_type, msg)
    try:
        pid = open("/run/wpa_supplicant.pid").read()
    except IOError:
        pass
    else:
        os.kill(int(pid),SIGTERM)
    sys.exit(1)


def start(profile, essid):
    # TODO: Add check if it's even a wireless interface
    # Interface up - probably redundant, should be 'ip' instead.
    #try:
    #    subprocess.check_call(["ifconfig", profile['INTERFACE'], "up"])
    #except subprocess.CalledProcessError:
    #    print " - Could not bring interface up"
    #    return False

    # Manually set any iwconfig options - redundant, use custom wpa_supplicant config instead.
    #if profile.has_key('IWCONFIG'):
    #    subprocess.call(["iwconfig", profile['INTERFACE'], profile['IWCONFIG']])

    # Base arguments
    args=["wpa_supplicant", "-Bu", "-P/run/wpa_supplicant.pid"]
    
    try:
        args.append(profile['WPA_OPTS'])
    except KeyError:
        args.append("-Dwext")

    if profile['SECURITY'] == "wpa-config":
        try:
            args.append("-c" + profile["WPA_CONF"])
        except KeyError:
            args.append("-c/etc/wpa_supplicant.conf")
    elif not profile['SECURITY'] in ['wpa', 'wep', 'none']:
        fail("Invalid security chosen", report_type="err")

    # Start wpa_supplicant
    report('debug', 'wireless_dbus', 'starting wpa_supplicant')
    supplicant = subprocess.Popen(args,stderr=subprocess.STDOUT,stdout=subprocess.PIPE)
    output = supplicant.communicate()[0]
    if supplicant.returncode not in [255,0]:
        print >>sys.stderr, output              # JP: print to stderr
        fail("Could not start wpa_supplicant")

    # Connect to wpa_supplicant
    report('debug', 'wireless_dbus', 'connecting to wpa_supplicant')
    bus = dbus.SystemBus()
    wpas_obj = bus.get_object(WPAS_DBUS_SERVICE, WPAS_DBUS_OPATH)
    wpas = dbus.Interface(wpas_obj, WPAS_DBUS_INTERFACE)
   
    # Add/Get interface path
    try:
        driver=profile["WPA_DRIVER"]
    except KeyError:
        driver="wext"

    try:
        path = wpas.getInterface(profile["INTERFACE"])
    except dbus.exceptions.DBusException:
        path = wpas.addInterface(profile["INTERFACE"], {"driver":dbus.String(driver,variant_level=1)})

    # Get interface object
    if_obj = bus.get_object(WPAS_DBUS_SERVICE, path)
    iface = dbus.Interface(if_obj, WPAS_DBUS_INTERFACES_INTERFACE);  
    
    # Add and select the network. Networks already specified for wpa-config
    if profile['SECURITY'] in ['wpa','wep','none']:
        report('debug', 'wireless_dbus', 'add and select network')
        
        path = iface.addNetwork()
        net_obj = bus.get_object(WPAS_DBUS_SERVICE, path)
        rnet = dbus.Interface(net_obj, WPAS_DBUS_NETWORKS_INTERFACE)
        iface.selectNetwork(rnet)

    if not essid:
        essid = profile["ESSID"]

    if profile['SECURITY'] == "wpa":
        opts = dbus.Dictionary({"ssid": dbus.ByteArray(essid), 
                                "psk": dbus.String(profile['KEY'])}, 
                               signature="sv")
        report('debug', 'wireless_dbus', 'connect to network with security=wpa')
        rnet.set(opts)
    elif profile['SECURITY'] == "wep":
        key=profile['KEY']
        if key[:2] == "s:": # String key prefixed by "s:"
            keydbus=key[2:]
        else: # Hex key 
            key=wep_hex2dec(key)
            keydbus = dbus.ByteArray()
            for l in key:
                keydbus+=chr(l)

        opts = dbus.Dictionary({"ssid": dbus.ByteArray(essid),
                                "key_mgmt": dbus.String("NONE"),
                                "wep_tx_keyidx": dbus.Int32(1),
                                "wep_key0": dbus.ByteArray(keydbus)},
                               signature="sv")
        report('debug', 'wireless_dbus', 'connect to network with security=wep')
        rnet.set(opts)
    elif profile['SECURITY'] == "none":
        opts = dbus.Dictionary({"ssid": dbus.ByteArray(essid)}, 
                               signature="sv")
        report('debug', 'wireless_dbus', 'connect to network with security=none')
        rnet.set(opts)
       
    # Determine timeout
    try:
        timeout = int(profile["TIMEOUT"])
    except KeyError:
        timeout = 15

    # Check for association
    n=0
    while n <= timeout:
        n+=1
        sleep(1)
        state = iface.state()
        if state == "COMPLETED":
            break
        
        if n == timeout:
            fail("Association/Authentication failed:" + state)

    # Run ethernet and get an ip.
    try:
        subprocess.check_call([ETHERNET_IPROUTE, "up", sys.argv[2]])
    except subprocess.CalledProcessError:
        fail()
    sys.exit(0)
  
def stop(profile):
    ret = subprocess.call([ETHERNET_IPROUTE, "down", sys.argv[2]])
    os.kill(int(open("/run/wpa_supplicant.pid").read()),SIGTERM)
    sys.exit(ret)

if __name__ == "__main__":

    CONN_DIR = os.path.abspath(os.path.dirname(sys.argv[0]))
    ETHERNET_IPROUTE = os.path.join(CONN_DIR,"ethernet-iproute")
    WIRELESS = os.path.join(CONN_DIR,"wireless")

    # setup bash report_* handler
    report_handler_script = 'source %s/../globals; while read cmd args; do eval $cmd "$args"; done' % (CONN_DIR,)
    report_handler = subprocess.Popen(report_handler_script, executable='/bin/bash', stdin=subprocess.PIPE, shell=True)

    try:
        profile_name = sys.argv[2]
        profile = read_config("/etc/network.d/"+profile_name)

        essid = sys.argv[3] if len(sys.argv)>3 else ""   # JP: pass literal ESSID as an argument, so that we can have entry in profile be a regexp
        if sys.argv[1] == "up":
            start(profile, essid)
        elif sys.argv[1] == "down":
            stop(profile)

    finally:
        report_handler.stdin.close()
        report_handler.wait()

# vim: et ts=4
