#!/usr/bin/python3
# -*- Mode: python; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details:
#
# Copyright (C) 2008 Novell, Inc.
# Copyright (C) 2009 Red Hat, Inc.
#

import sys, dbus, time, os, string, subprocess, socket

DBUS_INTERFACE_PROPERTIES='org.freedesktop.DBus.Properties'
MM_DBUS_SERVICE='org.freedesktop.ModemManager'
MM_DBUS_PATH='/org/freedesktop/ModemManager'
MM_DBUS_INTERFACE='org.freedesktop.ModemManager'
MM_DBUS_INTERFACE_MODEM='org.freedesktop.ModemManager.Modem'
MM_DBUS_INTERFACE_MODEM_CDMA='org.freedesktop.ModemManager.Modem.Cdma'
MM_DBUS_INTERFACE_MODEM_GSM_CARD='org.freedesktop.ModemManager.Modem.Gsm.Card'
MM_DBUS_INTERFACE_MODEM_GSM_NETWORK='org.freedesktop.ModemManager.Modem.Gsm.Network'
MM_DBUS_INTERFACE_MODEM_SIMPLE='org.freedesktop.ModemManager.Modem.Simple'

def get_cdma_band_class(band_class):
    if band_class == 1:
        return "800MHz"
    elif band_class == 2:
        return "1900MHz"
    else:
        return "Unknown"

def get_reg_state(state):
    if state == 1:
        return "registered (roaming unknown)"
    elif state == 2:
        return "registered on home network"
    elif state == 3:
        return "registered on roaming network"
    else:
        return "unknown"

def cdma_inspect(proxy, dump_private):
    cdma = dbus.Interface(proxy, dbus_interface=MM_DBUS_INTERFACE_MODEM_CDMA)

    esn = "<private>"
    if dump_private:
        try:
            esn = cdma.GetEsn()
        except dbus.exceptions.DBusException:
            esn = "<unavailable>"

    print("")
    print("ESN: %s" % esn)

    try:
        (cdma_1x_state, evdo_state) = cdma.GetRegistrationState()
        print("1x State:   %s" % get_reg_state (cdma_1x_state))
        print("EVDO State: %s" % get_reg_state (evdo_state))
    except dbus.exceptions.DBusException as e:
        print("Error reading registration state: %s" % e)

    try:
        quality = cdma.GetSignalQuality()
        print("Signal quality: %d" % quality)
    except dbus.exceptions.DBusException as e:
        print("Error reading signal quality: %s" % e)

    try:
        info = cdma.GetServingSystem()
        print("Class: %s" % get_cdma_band_class(info[0]))
        print("Band:  %s" % info[1])
        print("SID:   %d" % info[2])
    except dbus.exceptions.DBusException as e:
        print("Error reading serving system: %s" % e)

def cdma_connect(proxy, user, password):
    # Modem.Simple interface
    simple = dbus.Interface(proxy, dbus_interface=MM_DBUS_INTERFACE_MODEM_SIMPLE)
    try:
        simple.Connect({'number':"#777"}, timeout=92)
        print("\nConnected!")
        return True
    except Exception as e:
        print("Error connecting: %s" % e)
    return False


def get_gsm_network_mode(modem):
    mode = modem.GetNetworkMode()
    if mode == 0x0:
        mode = "Unknown"
    elif mode == 0x1:
        mode = "Any"
    elif mode == 0x2:
        mode = "GPRS"
    elif mode == 0x4:
        mode = "EDGE"
    elif mode == 0x8:
        mode = "UMTS"
    elif mode == 0x10:
        mode = "HSDPA"
    elif mode == 0x20:
        mode = "2G Preferred"
    elif mode == 0x40:
        mode = "3G Preferred"
    elif mode == 0x80:
        mode = "2G Only"
    elif mode == 0x100:
        mode = "3G Only"
    elif mode == 0x200:
        mode = "HSUPA"
    elif mode == 0x400:
        mode = "HSPA"
    else:
        mode = "(Unknown)"

    print("Mode: %s" % mode)

def get_gsm_band(modem):
    band = modem.GetBand()
    if band == 0x0:
        band = "Unknown"
    elif band == 0x1:
        band = "Any"
    elif band == 0x2:
        band = "EGSM (900 MHz)"
    elif band == 0x4:
        band = "DCS (1800 MHz)"
    elif band == 0x8:
        band = "PCS (1900 MHz)"
    elif band == 0x10:
        band = "G850 (850 MHz)"
    elif band == 0x20:
        band = "U2100 (WCSMA 2100 MHZ, Class I)"
    elif band == 0x40:
        band = "U1700 (WCDMA 3GPP UMTS1800 MHz, Class III)"
    elif band == 0x80:
        band = "17IV (WCDMA 3GPP AWS 1700/2100 MHz, Class IV)"
    elif band == 0x100:
        band = "U800 (WCDMA 3GPP UMTS800 MHz, Class VI)"
    elif band == 0x200:
        band = "U850 (WCDMA 3GPP UMT850 MHz, Class V)"
    elif band == 0x400:
        band = "U900 (WCDMA 3GPP UMTS900 MHz, Class VIII)"
    elif band == 0x800:
        band = "U17IX (WCDMA 3GPP UMTS MHz, Class IX)"
    else:
        band = "(invalid)"

    print("Band: %s" % band)


def gsm_inspect(proxy, dump_private, do_scan):
    # Gsm.Card interface
    card = dbus.Interface(proxy, dbus_interface=MM_DBUS_INTERFACE_MODEM_GSM_CARD)

    imei = "<private>"
    imsi = "<private>"
    if dump_private:
        try:
            imei = card.GetImei()
        except dbus.exceptions.DBusException:
            imei = "<unavailable>"
        try:
            imsi = card.GetImsi()
        except dbus.exceptions.DBusException:
            imsi = "<unavailable>"

    print("IMEI: %s" % imei)
    print("IMSI: %s" % imsi)

    # Gsm.Network interface
    net = dbus.Interface(proxy, dbus_interface=MM_DBUS_INTERFACE_MODEM_GSM_NETWORK)
    try:
        quality = net.GetSignalQuality()
        print("Signal quality: %d" % quality)
    except dbus.exceptions.DBusException as e:
        print("Error reading signal quality: %s" % e)

    if not do_scan:
        return

    print("Scanning...")
    try:
        results = net.Scan(timeout=120)
    except dbus.exceptions.DBusException as e:
        print("Error scanning: %s" % e)
        results = {}

    for r in results:
        status = r['status']
        if status == "1":
            status = "available"
        elif status == "2":
            status = "current"
        elif status == "3":
            status = "forbidden"
        else:
            status = "(Unknown)"

        access_tech = ""
        try:
            access_tech_num = r['access-tech']
            if access_tech_num == "0":
                access_tech = "(GSM)"
            elif access_tech_num == "1":
                access_tech = "(Compact GSM)"
            elif access_tech_num == "2":
                access_tech = "(UMTS)"
            elif access_tech_num == "3":
                access_tech = "(EDGE)"
            elif access_tech_num == "4":
                access_tech = "(HSDPA)"
            elif access_tech_num == "5":
                access_tech = "(HSUPA)"
            elif access_tech_num == "6":
                access_tech = "(HSPA)"
        except KeyError:
            pass

        if 'operator-long' in r and len(r['operator-long']):
            print("%s: %s %s" % (r['operator-long'], status, access_tech))
        elif 'operator-short' in r and len(r['operator-short']):
            print("%s: %s %s" % (r['operator-short'], status, access_tech))
        else:
            print("%s: %s %s" % (r['operator-num'], status, access_tech))

def gsm_connect(proxy, apn, user, password):
    # Modem.Simple interface
    simple = dbus.Interface(proxy, dbus_interface=MM_DBUS_INTERFACE_MODEM_SIMPLE)
    try:
        opts = {'number':"*99#"}
        if apn is not None:
            opts['apn'] = apn
        if user is not None:
            opts['username'] = user
        if password is not None:
            opts['password'] = password
        simple.Connect(opts, timeout=120)
        print("\nConnected!")
        return True
    except Exception as e:
        print("Error connecting: %s" % e)
    return False

def pppd_find():
    paths = ["/usr/local/sbin/pppd", "/usr/sbin/pppd", "/sbin/pppd"]
    for p in paths:
        if os.path.exists(p):
            return p
    return None

def ppp_start(device, user, password, tmpfile):
    path = pppd_find()
    if not path:
        return None

    args = [path]
    args += ["nodetach"]
    args += ["lock"]
    args += ["nodefaultroute"]
    args += ["debug"]
    if user:
        args += ["user"]
        args += [user]
    args += ["noipdefault"]
    args += ["115200"]
    args += ["noauth"]
    args += ["crtscts"]
    args += ["modem"]
    args += ["usepeerdns"]
    args += ["ipparam"]

    ipparam = ""
    if user:
        ipparam += user
    ipparam += "+"
    if password:
        ipparam += password
    ipparam += "+"
    ipparam += tmpfile
    args += [ipparam]

    args += ["plugin"]
    args += ["mm-test-pppd-plugin.so"]

    args += [device]

    return subprocess.Popen(args, close_fds=True, cwd="/", env={})

def ppp_wait(p, tmpfile):
    i = 0
    while p.poll() == None and i < 30:
        time.sleep(1)
        if os.path.exists(tmpfile):
            f = open(tmpfile, 'r')
            stuff = f.read(500)
            idx = string.find(stuff, "DONE")
            f.close()
            if idx >= 0:
                return True
        i += 1
    return False

def ppp_stop(p):
    import signal
    p.send_signal(signal.SIGTERM)
    p.wait()

def ntop_helper(ip):
    ip = socket.ntohl(ip)
    n1 = ip >> 24 & 0xFF
    n2 = ip >> 16 & 0xFF
    n3 = ip >> 8 & 0xFF
    n4 = ip & 0xFF
    a = "%c%c%c%c" % (n1, n2, n3, n4)
    return socket.inet_ntop(socket.AF_INET, a)

def static_start(iface, modem):
    (addr_num, dns1_num, dns2_num, dns3_num) = modem.GetIP4Config()
    addr = ntop_helper(addr_num)
    dns1 = ntop_helper(dns1_num)
    dns2 = ntop_helper(dns2_num)
    configure_iface(iface, addr, 0, dns1, dns2)

def down_iface(iface):
    ip = ["ip", "addr", "flush", "dev", iface]
    print(" ".join(ip))
    subprocess.call(ip)
    ip = ["ip", "link", "set", iface, "down"]
    print(" ".join(ip))
    subprocess.call(ip)

def configure_iface(iface, addr, gw, dns1, dns2):
    print("\n\n******************************")
    print("iface: %s" % iface)
    print("addr:  %s" % addr)
    print("gw:    %s" % gw)
    print("dns1:  %s" % dns1)
    print("dns2:  %s" % dns2)

    ifconfig = ["ifconfig", iface, "%s/32" % addr]
    if gw != 0:
        ifconfig += ["pointopoint", gw]
    print(" ".join(ifconfig))
    print("\n******************************\n")

    subprocess.call(ifconfig)

def file_configure_iface(tmpfile):
    addr = None
    gw = None
    iface = None
    dns1 = None
    dns2 = None

    f = open(tmpfile, 'r')
    lines = f.readlines()
    for l in lines:
        if l.startswith("addr"):
            addr = l[len("addr"):].strip()
        if l.startswith("gateway"):
            gw = l[len("gateway"):].strip()
        if l.startswith("iface"):
            iface = l[len("iface"):].strip()
        if l.startswith("dns1"):
            dns1 = l[len("dns1"):].strip()
        if l.startswith("dns2"):
            dns2 = l[len("dns2"):].strip()
    f.close()

    configure_iface(iface, addr, gw, dns1, dns2)
    return iface

def try_ping(iface):
    cmd = ["ping", "-I", iface, "-c", "4", "-i", "3", "-w", "20", "4.2.2.1"]
    print(" ".join(cmd))
    retcode = subprocess.call(cmd)
    if retcode != 0:
        print("PING: failed")
    else:
        print("PING: success")


dump_private = False
connect = False
apn = None
user = None
password = None
do_ip = False
do_scan = True
x = 1
while x < len(sys.argv):
    if sys.argv[x] == "--private":
        dump_private = True
    elif sys.argv[x] == "--connect":
        connect = True
    elif (sys.argv[x] == "--user" or sys.argv[x] == "--username"):
        x += 1
        user = sys.argv[x]
    elif sys.argv[x] == "--apn":
        x += 1
        apn = sys.argv[x]
    elif sys.argv[x] == "--password":
        x += 1
        password = sys.argv[x]
    elif sys.argv[x] == "--ip":
        do_ip = True
        if os.geteuid() != 0:
            print("You probably want to be root to use --ip")
            sys.exit(1)
    elif sys.argv[x] == "--no-scan":
        do_scan = False
    x += 1

bus = dbus.SystemBus()

# Get available modems:
try:
    manager_proxy = bus.get_object('org.freedesktop.ModemManager', '/org/freedesktop/ModemManager')
    manager_iface = dbus.Interface(manager_proxy, dbus_interface='org.freedesktop.ModemManager')
    modems = manager_iface.EnumerateDevices()
except dbus.exceptions.DBusException as excp:
    if (excp.get_dbus_name() == "org.freedesktop.DBus.Error.ServiceUnknown"):
        print("ERROR: mm-test doesn't work on ModemManager 1.0 and newer: {}".format(
            excp.get_dbus_message()))
    else:
        print("ERROR: {}".format(excp.get_dbus_message()))
    sys.exit(1)
if not modems:
    print("No modems found")
    sys.exit(1)

for m in modems:
    connect_success = False
    data_device = None

    proxy = bus.get_object(MM_DBUS_SERVICE, m)

    # Properties
    props_iface = dbus.Interface(proxy, dbus_interface='org.freedesktop.DBus.Properties')

    type = props_iface.Get(MM_DBUS_INTERFACE_MODEM, 'Type')
    if type == 1:
        print("GSM modem")
    elif type == 2:
        print("CDMA modem")
    else:
        print("Invalid modem type: %d" % type)

    print("Driver: '%s'" % (props_iface.Get(MM_DBUS_INTERFACE_MODEM, 'Driver')))
    print("Modem device: '%s'" % (props_iface.Get(MM_DBUS_INTERFACE_MODEM, 'MasterDevice')))
    data_device = props_iface.Get(MM_DBUS_INTERFACE_MODEM, 'Device')
    print("Data device: '%s'" % data_device)

    # Modem interface
    modem = dbus.Interface(proxy, dbus_interface=MM_DBUS_INTERFACE_MODEM)

    try:
        modem.Enable(True)
    except dbus.exceptions.DBusException as e:
        print("Error enabling modem: %s" % e)
        sys.exit(1)

    info = modem.GetInfo()
    print("Vendor:  %s" % info[0])
    print("Model:   %s" % info[1])
    print("Version: %s" % info[2])

    if type == 1:
        gsm_inspect(proxy, dump_private, do_scan)
        if connect == True:
            connect_success = gsm_connect(proxy, apn, user, password)
    elif type == 2:
        cdma_inspect(proxy, dump_private)
        if connect == True:
            connect_success = cdma_connect(proxy, user, password)
    print()

    if connect_success and do_ip:
        tmpfile = "/tmp/mm-test-%d.tmp" % os.getpid()
        success = False
        try:
            ip_method = props_iface.Get(MM_DBUS_INTERFACE_MODEM, 'IpMethod')
            if ip_method == 0:
                # ppp
                p = ppp_start(data_device, user, password, tmpfile)
                if ppp_wait(p, tmpfile):
                    data_device = file_configure_iface(tmpfile)
                    success = True
            elif ip_method == 1:
                # static
                static_start(data_device, modem)
                success = True
            elif ip_method == 2:
                # dhcp
                pass
        except Exception as e:
            print("Error setting up IP: %s" % e)

        if success:
            try_ping(data_device)
            print("Waiting for 30s...")
            time.sleep(30)

        print("Disconnecting...")
        try:
            if ip_method == 0:
                ppp_stop(p)
                try:
                    os.remove(tmpfile)
                except:
                    pass
            elif ip_method == 1:
                # static
                down_iface(data_device)
            elif ip_method == 2:
                # dhcp
                down_iface(data_device)

            modem.Disconnect()
        except Exception as e:
            print("Error tearing down IP: %s" % e)

    time.sleep(5)

    modem.Enable(False)

