#!/opt/catchpoint/bin/python
import struct, os, json, re, traceback, shutil, glob, sys, subprocess, tempfile
from string import Template

INET_RE = re.compile(r'\s*inet6?\s.*$')
RH_PREFIX = "/etc/sysconfig/network-scripts/"  # prefix for redhat network scripts
WIFI_PREFIX = "/sys/class/net/"
WIFI_SUFFIX = "/wireless"
# matches order in C# enum, as well as serialized values of enum
SECTYPE_ARRAY = ['WEP', 'WPA', 'WPA2']  # keep synched to C# enum

# caution: this is flimsy code in the sense that there are no guarantees that
# the child process would ever return a result. It could get permanently stuck.
def if_cmd(cmd, dev):
    # redhat only
    try: subprocess.check_call(['/usr/sbin/if' + cmd, dev])
    except: pass

# read the stdout of child process array <cmd> and return it
# cmd should have a short, quickly terminated output
# do not use this to read long or continuous output
def read_stdout(cmd):
    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
    return proc.communicate()[0].decode('utf8')

# attempt to activate the specified network on a redhat box
# return True on success, False otherwise
def activate_wifi(ssid, dev):
    try:
        if_cmd("down", ssid)
        if_cmd("up", ssid)
        # now check if there is an IP v4 or v6 addr associated with this interface
        # if so then we assume that activation worked
        output = read_stdout(["ip", "a", "show", "dev", dev]).split('\n')
        #print "attempting to activate", ssid, "on dev", dev, ", output=", output
        for line in output:
            if INET_RE.match(line):
                return True
    except:
        print(traceback.format_exc())
    return False

# an array of 1 or more dictionaries
# {'secret_key': 'key123', 'sec_type': 0, 'ssid': 'ssid123'}
# are tried one at a time until a successful one is found, if all fail
# then an error is returned.
# Redhat only is supported in this code
def configure_wifi(args):
    # returns only 1 wifi interface or None if interfaces are not wireless
    def find_a_wifi_card():
        devices = glob.glob(WIFI_PREFIX + '*' + WIFI_SUFFIX)
        if len(devices) > 0:
            dev = devices[0]  # just pick the 1st wireless device if there are many
            return dev[len(WIFI_PREFIX):len(dev)-len(WIFI_SUFFIX)]
        else:
            return None

    def delete_file(filename):
        try:
            if len(filename) > 0: os.unlink(filename)
        except: pass

    # applies settings to disk but does not activate the wifi device
    # thus success is not assured even if successful ret code is indicated to caller
    # returns a (status, message, undo function) 3 tuple
    def apply_settings(dev, sec_type, ssid, secret):
        msg = "success"
        undo = lambda: None
        filename1 = ""
        filename2 = ""
        try:
            if sec_type == "WEP":
                key_text = 'KEY=s:"' + secret + '"\n'
            elif sec_type == "WPA":
                key_text = "WPA_ALLOW_WPA=yes\nWPA_ALLOW_WPA2=no\n"
            elif sec_type == "WPA2":
                key_text = "WPA_ALLOW_WPA=no\nWPA_ALLOW_WPA2=yes\n"
            else: # corporate net protocols like LEAP, enterprise WPA not supported at this time
                return (False, "sec_type " + sec_type + " not supported", lambda: None)

            # we don't care if these files already exist, overwrite them
            ssid_file = RH_PREFIX + 'ifcfg-' + ssid
            keys_file = RH_PREFIX + 'keys-' + ssid
            undo = lambda: (delete_file(keys_file), delete_file(ssid_file))
            with tempfile.NamedTemporaryFile(delete=False) as fp:
                filename1 = fp.name
                # there is no default gw in this config on purpose
                # apparently WPA2-PSK is not valid even for WPA2 preshared key scenarios, use WPA instead.
                sec_type_redhat = "WPA-PSK" if sec_type[0:3] == "WPA" else sec_type
                ifcfg = Template("ESSID=${SSID}\n"
                                 "MODE=Managed\n"
                                 "KEY_MGMT=${SECTYPE}\n"
                                 "TYPE=Wireless\n"
                                 "BOOTPROTO=dhcp\n"
                                 "DEFROUTE=no\n"
                                 "IPV4_FAILURE_FATAL=yes\n"
                                 "IPV6INIT=no\n"
                                 "NAME=${SSID}\n"
                                 "ONBOOT=yes\n"
                                 "ONHOTPLUG=yes\n"
                                 "PROXY_METHOD=none\n"
                                 "BROWSER_ONLY=no\n"
                                 "ZONE=public\n"
                                 "${WPA_SECTION}"
                                 "MAC_ADDRESS_RANDOMIZATION=default\n"
                                 "PEERDNS=yes\n"
                                 "PEERROUTES=yes\n"
                                 "DEVICE=${DEVICE}\n")
                text = ifcfg.substitute(SSID=ssid, SECTYPE=sec_type_redhat, WPA_SECTION=key_text, DEVICE=dev)
                fp.write(text)
                fp.close()
                shutil.copy(filename1, ssid_file)
                os.chmod(ssid_file, 0o755)
                if sec_type[0:3] == "WPA":
                    with tempfile.NamedTemporaryFile(delete=False) as fd:
                        filename2 = fd.name
                        fd.write("WPA_PSK=" + secret + '\n')
                        fd.close()
                        shutil.copy(fd.name, keys_file)
                        os.chmod(keys_file, 0o600)
                        os.chown(keys_file, 0, 0) # root must be the owner

        except Exception as e:
            msg = str(e)
        finally: # best effort deletion if file(s) exist
            delete_file(filename1)
            delete_file(filename2)

        return (True, msg, undo)

    try:
       if not os.path.exists(RH_PREFIX):
           return (False, "error: unable to find Redhat and/or NetworkManager.wifi package")

       wifi_dev = find_a_wifi_card()
       if wifi_dev is None:
           return (False, "error: unable to find any wireless hardware")

       if args is None: args = []
       for settings in args:
           intval = -1
           try:
               intval = int(settings['secType'])
           except KeyError: pass
           try:
               sectype_arg = SECTYPE_ARRAY[intval]
           except IndexError:
               return (False, "unrecognized sec_type index: " + settings['sec_type'])

           ssid_arg = settings['ssid']
           secret_arg = settings['secretKey']

           (rc, msg, undo_fn) = apply_settings(wifi_dev, sectype_arg, ssid_arg, secret_arg)
           if not rc:  # i.e. if unable to activate then
               undo_fn()
               continue

           if activate_wifi(settings['ssid'], wifi_dev): # SSID, dev
               return (True, "successfully activated wifi device " + wifi_dev)

           undo_fn()
    except Exception as e:
       print("exception caught", str(e))
       print(traceback.format_exc())

    return (False, "failed to activate wifi on device " + wifi_dev)

data = sys.stdin.read()
# we expect some input here, revert is [""]
data = json.loads(data)

print(json.dumps(configure_wifi(data)))