#!/usr/bin/env python

import sys
import os
import re
import subprocess
import logging, logging.handlers
import ConfigParser

# defaults
config_file = '/etc/synce-hal.conf'
log_level = logging.WARNING

DEFAULT_DEVICE_IP = "169.254.2.1"
DEFAULT_LOCAL_IP = "169.254.2.2"
DEFAULT_NETMASK = "255.255.255.0"
DEFAULT_BROADCAST = "169.254.2.255"

device_ip = False
local_ip = False


#
# fall back to static config
#
def RunIfconfig(iface):

    logger.debug("attempting static ip configuration")
    cmd_list = ["ifconfig",iface,DEFAULT_LOCAL_IP,"netmask",DEFAULT_NETMASK,"broadcast",DEFAULT_BROADCAST]

    global device_ip
    global local_ip

    # returns a Popen object
    try:
        proc = subprocess.Popen(cmd_list, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        output_text = proc.communicate()[0]
    except Exception,e:
        logger.error("failure running ifconfig: %s" % e)
        return False

    rc = proc.returncode

    if rc != 0:
        logger.error("ifconfig failed with return code %d",rc)
        return False

    logger.debug("ifconfig succeeded")

    device_ip = DEFAULT_DEVICE_IP
    local_ip = DEFAULT_LOCAL_IP

    return


#
# try to contact device's dhcp server
#
def RunDhclient(iface):

    logger.debug("attempting dynamic ip configuration")

    pidfile = "/var/run/dhclient-synce-"+iface+".pid"
    leasefile = "/var/run/dhclient-synce-"+iface+".lease"
    configfile = "/usr/share/synce-hal/dhclient.conf"

    cmd_list = ["dhclient","-pf",pidfile,"-lf",leasefile,"-cf",configfile,iface]

    global device_ip
    global local_ip

    # returns a Popen object
    try:
        proc = subprocess.Popen(cmd_list, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        output_text = proc.communicate()[0]
    except Exception,e:
        logger.error("failure running dhclient: %s" % e)
        return False

    rc = proc.returncode

    if rc != 0:
        logger.error("dhclient failed with return code %d",rc)
        return False

    output_list = output_text.split('\n')

    dhcp_ack_re = re.compile('DHCPACK .*from (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})')
    dhcp_bound_re = re.compile('bound to (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})')

    for line in output_list:
        logger.debug("dhclient output: "+line)

        match_obj = dhcp_ack_re.match(line)
        if match_obj != None:
            device_ip = match_obj.group(1)
            logger.debug("dhclient returned device address "+device_ip)

        match_obj = dhcp_bound_re.match(line)
        if match_obj != None:
            local_ip = match_obj.group(1)
            logger.debug("dhclient returned local address "+local_ip)

    return


#
# if we're using dhcp, release it
#
def EndDhclient(iface):

    logger.debug("attempting to release dhcp lease")

    pidfile = "/var/run/dhclient-synce-"+iface+".pid"
    leasefile = "/var/run/dhclient-synce-"+iface+".lease"
    configfile = "/usr/share/synce-hal/dhclient.conf"

    if os.path.isfile(pidfile) != True:
        logger.debug("no pid file %s, not releasing", pidfile)
        return False

    cmd_list = ["dhclient","-r","-pf",pidfile,"-lf",leasefile,"-cf",configfile,iface]

    # returns a Popen object
    try:
        proc = subprocess.Popen(cmd_list, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        output_text = proc.communicate()[0]
    except Exception,e:
        logger.error("failure running dhclient: %s" % e)
        return True

    rc = proc.returncode

    if rc != 0:
        logger.info("dhclient failed with return code %d",rc)
        return True

    logger.debug("released dhcp lease")
    return True


#
# run dccm after the interface is configured
#
def RunDccm():

    logger.debug("starting hal-dccm ...")

    dccm_log_level = "1"

    if log_level <= logging.DEBUG:
        dccm_log_level = "6"
    elif log_level <= logging.INFO:
        dccm_log_level = "4"
    elif log_level <= logging.WARNING:
        dccm_log_level = "3"

    cmd_list = ["/usr/libexec/hal-dccm","--rndis","--device-ip="+device_ip, "--local-ip="+local_ip, "--log-level="+dccm_log_level]

    logger.debug("calling hal-dccm as:")
    logger.debug(cmd_list)

    try:
        os.execv("/usr/libexec/hal-dccm", cmd_list)
    except Exception,e:
        logger.error("failed to exec hal-dccm !!: %s" % e)

    return


def ProcessConfig():

    global log_level
    global DEFAULT_DEVICE_IP
    global DEFAULT_LOCAL_IP
    global DEFAULT_NETMASK
    global DEFAULT_BROADCAST

    logger = logging.getLogger("hal-synce-rndis")

    config = ConfigParser.ConfigParser()
    try:
        config.read(config_file)
    except Exception,e:
        logger.warning("failed to parse config file %s: %s" % (config_file, e))
        return False

    if config.has_option('general', 'loglevel'):
        loglevel_txt = config.get('general', 'loglevel').lower()
        if loglevel_txt == 'critical':
            log_level = logging.CRITICAL
        elif loglevel_txt == 'error':
            log_level = logging.ERROR
        elif loglevel_txt == 'warning':
            log_level = logging.WARNING
        elif loglevel_txt == 'info':
            log_level = logging.INFO
        elif loglevel_txt == 'debug':
            log_level = logging.DEBUG
        else:
            logger.warning("found invalid loglevel in config file %s: %s" % (config_file, loglevel_txt))

        logger.setLevel(log_level)

    if config.has_option('rndis', 'static_device_ip'):
        new_ip = config.get('rndis', 'static_device_ip')
        
        ip_re = re.compile('^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$)')
        ip_match = ip_re.match(new_ip)
        if ip_match != None:
            DEFAULT_DEVICE_IP = new_ip
        else:
            logger.warning("found invalid device_ip in config file %s: %s" % (config_file, new_ip))

    if config.has_option('rndis', 'static_local_ip'):
        new_ip = config.get('rndis', 'static_local_ip')
        
        ip_re = re.compile('^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$)')
        ip_match = ip_re.match(new_ip)
        if ip_match != None:
            DEFAULT_LOCAL_IP = new_ip
        else:
            logger.warning("found invalid local_ip in config file %s: %s" % (config_file, new_ip))

    if config.has_option('rndis', 'static_netmask'):
        new_ip = config.get('rndis', 'static_netmask')
        
        ip_re = re.compile('^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$)')
        ip_match = ip_re.match(new_ip)
        if ip_match != None:
            DEFAULT_NETMASK = new_ip
        else:
            logger.warning("found invalid netmask in config file %s: %s" % (config_file, new_ip))

    if config.has_option('rndis', 'static_broadcast'):
        new_ip = config.get('rndis', 'static_broadcast')
        
        ip_re = re.compile('^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$)')
        ip_match = ip_re.match(new_ip)
        if ip_match != None:
            DEFAULT_BROADCAST = new_ip
        else:
            logger.warning("found invalid broadcast in config file %s: %s" % (config_file, new_ip))

    return True


#
# main()
#
if __name__ == '__main__':

    log_facility = logging.handlers.SysLogHandler.LOG_DAEMON

    logging.basicConfig(level=logging.WARNING,
                        format='%(asctime)s %(name)s %(levelname)s : %(message)s')
    logger = logging.getLogger("hal-synce-rndis")
    sys_log = logging.handlers.SysLogHandler("/dev/log", log_facility)
    syslog_form = logging.Formatter('%(name)s[%(process)d] %(levelname)s : %(message)s')
    sys_log.setFormatter(syslog_form)
    logger.addHandler(sys_log)

    if os.environ.has_key("HALD_ACTION"):
        hald_action = os.environ["HALD_ACTION"]
    else:
        logger.warning("Hal environment not set")
        sys.exit(1)

    ProcessConfig()

    try:
        iface = os.environ["HAL_PROP_NET_INTERFACE"]
    except Exception,e:
        logger.critical("Hal environment not set: %s" % e)
        sys.exit(1)


    if hald_action == "addon":
        logger.debug("running as addon for interface "+iface)

        RunDhclient(iface)

        if device_ip == False or local_ip == False:
            logger.info("error running dhclient, trying static config")

            RunIfconfig(iface)
            if device_ip == False or local_ip == False:
                logger.warning("failed to configure interface")
                sys.exit(1)

        logger.debug("successfully configured interface")

        RunDccm()

        logger.error("aborting ...")
        sys.exit(1)

    if hald_action == "remove":
        logger.debug("running for removal of interface "+iface)
        EndDhclient(iface)

    sys.exit(0)

