#!/usr/bin/python
#
# Command line interface for NetworkManager
#
# Copyright (C) 2007 robert.kenneth.frank@gmail.com
# Copyright (C) 2008 Ricardo Salveti <ricardo.salveti@openbossa.org>
# Copyright (C) 2008 Insituto Nokia de Tecnologia
#
# 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, version 2
# of the License.
#
# 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.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#
# Based on the network-manager-cli from Robert Kenneth

__author__ = "Ricardo Salveti"
__version__ = "0.3"

#TODO:
#  - Support for vpn, daemon, security (leap), test devices, dialup
#  - Check the need of other vars as properties
#  - Better error handling
#  - Validate config values
#  - Show more info about the connection to the user

import dbus
import dbus.service
import gobject
import os
import signal
from socket import inet_ntoa
from struct import pack
from dbus.mainloop.glib import DBusGMainLoop
from dbus.proxies import ProxyObject
from ConfigParser import ConfigParser
from optparse import OptionParser, OptionGroup

DBUS_PROPERTIES = "org.freedesktop.DBus.Properties"

NM_DBUS_SERVICE = "org.freedesktop.NetworkManager"
NM_DBUS_IFACE = "org.freedesktop.NetworkManager"
NM_DBUS_IFACE_DEVICE = "org.freedesktop.NetworkManager.Device"
NM_DBUS_IFACE_DEVICE_WIRED = "org.freedesktop.NetworkManager.Device.Wired"
NM_DBUS_IFACE_DEVICE_WIRELESS = "org.freedesktop.NetworkManager.Device.Wireless"
NM_DBUS_IFACE_DEVICE_GSM = "org.freedesktop.NetworkManager.Device.Gsm"
NM_DBUS_IFACE_IP4CONFIG = "org.freedesktop.NetworkManager.IP4Config"
NM_DBUS_IFACE_AP = "org.freedesktop.NetworkManager.AccessPoint"
NM_DBUS_PATH = "/org/freedesktop/NetworkManager"

NM_DBUS_IFACE_USER_SETTINGS = "org.freedesktop.NetworkManagerUserSettings"
NM_DBUS_IFACE_SETTINGS = "org.freedesktop.NetworkManagerSettings"
NM_DBUS_PATH_SETTINGS = "/org/freedesktop/NetworkManagerSettings"

NM_DBUS_PATH_VPN = "/org/freedesktop/NetworkManager/VPNConnections"
NM_DBUS_IFACE_VPN = "org.freedesktop.NetworkManager.VPNConnections"

# Types of NetworkManager devices
NM_DEVICE_TYPE_UNKNOWN = 0
NM_DEVICE_TYPE_ETHERNET = 1
NM_DEVICE_TYPE_WIFI = 2
NM_DEVICE_TYPE_GSM = 3
NM_DEVICE_TYPE_CDMA = 4

channels = {    #based on `wlist channels`
    2412:   1,
    2417:   2,
    2422:   3,
    2427:   4,
    2432:   5,
    2437:   6,
    2442:   7,
    2447:   8,
    2452:   9,
    2457:  10,
    2462:  11,

    5180:  36,
    5200:  40,
    5220:  44,
    5240:  48,
    5260:  52,
    5280:  56,
    5300:  60,
    5320:  64,

    5745: 149,
    5765: 153,
    5785: 157,
    5805: 161,
    5825: 165
}

# Types of NetworkManager states
nm_state = {
    0: 'Unknown',
    1: 'Sleeping',
    2: 'Connecting',
    3: 'Connected',
    4: 'Disconnected'
}

# NM Device States
nm_device_state = {
    0: 'Unknown',
    1: 'Unmanaged',
    2: 'Unavailable',
    3: 'Disconnected',
    4: 'Prepare',
    5: 'Config',
    6: 'Need Auth',
    7: 'IP Config',
    8: 'Activated',
    9: 'Failed'
}

# Active connetions states
nm_active_con_state = {
    0: 'Unknown',
    1: 'Activating',
    2: 'Activated'
}

# NM-802.11 Modes
nm_80211_mode = {
    0: 'Unknown',
    1: 'Adhoc',
    2: 'Infra'
}

# General device capability bits
NM_DEVICE_CAP_NONE              = 0x00000000
NM_DEVICE_CAP_NM_SUPPORTED      = 0x00000001
NM_DEVICE_CAP_CARRIER_DETECT    = 0x00000002

# 802.11 wireless-specific device capability bits
NM_WIFI_DEVICE_CAP_NONE          = 0x00000000
NM_WIFI_DEVICE_CAP_CIPHER_WEP40  = 0x00000001
NM_WIFI_DEVICE_CAP_CIPHER_WEP104 = 0x00000002
NM_WIFI_DEVICE_CAP_CIPHER_TKIP   = 0x00000004
NM_WIFI_DEVICE_CAP_CIPHER_CCMP   = 0x00000008
NM_WIFI_DEVICE_CAP_WPA           = 0x00000010
NM_WIFI_DEVICE_CAP_RSN           = 0x00000020

# 802.11 Access Point flags
NM_802_11_AP_FLAGS_NONE     = 0x00000000
NM_802_11_AP_FLAGS_PRIVACY  = 0x00000001

# 802.11 Access Point security flags
NM_802_11_AP_SEC_NONE               = 0x00000000
NM_802_11_AP_SEC_PAIR_WEP40         = 0x00000001
NM_802_11_AP_SEC_PAIR_WEP104        = 0x00000002
NM_802_11_AP_SEC_PAIR_TKIP          = 0x00000004
NM_802_11_AP_SEC_PAIR_CCMP          = 0x00000008
NM_802_11_AP_SEC_GROUP_WEP40        = 0x00000010
NM_802_11_AP_SEC_GROUP_WEP104       = 0x00000020
NM_802_11_AP_SEC_GROUP_TKIP         = 0x00000040
NM_802_11_AP_SEC_GROUP_CCMP         = 0x00000080
NM_802_11_AP_SEC_KEY_MGMT_PSK       = 0x00000100
NM_802_11_AP_SEC_KEY_MGMT_802_1X    = 0x00000200


DBusGMainLoop(set_as_default=True)
bus = dbus.SystemBus()
loop = gobject.MainLoop()

class NMCLI:
    """Network Manager Cli Class.

    Class that communicates with Network Manager by dbus, listing devices,
    creating new connection settings and connecting.

    """

    def __init__(self, verbose=False):
        """Initialize structures and update the devices.

        Keyword arguments:
        verbose -- if should be more verbose when printing any information

        """
        self._verbose = verbose

        # List of all available devices
        self.devices = {}
        # Specific lists of devices
        self.wired_devices = {}
        self.wireless_devices = {}
        self.gsm_devices = {}

        # List of connections
        self.connections = 0 # id helper
        self.active_connections = []
        self.wired_connection = None
        self.wireless_connection = None
        self.gsm_connection = None

        self.nm = NetworkManager()
        self.nmusermset = None

        self.update_devices()

    def list_devices(self, list_networks=False):
        """Lists out each device (and network if verbose is set).

        Keyword arguments:
        list_networks -- if should also print the networks (for wireless)

        """
        if self.wired_devices:
            print "Wired Devices:"
            for iface_name in self.wired_devices:
                self.devices[iface_name].print_device_info()
                print

        if self.wireless_devices:
            print "Wireless Devices:"
            for iface_name in self.wireless_devices:
                self.devices[iface_name].print_device_info()
                if list_networks:
                    self.devices[iface_name].print_aps_info()
                print

        if self.gsm_devices:
            print "GSM Devices:"
            for iface_name in self.gsm_devices:
                self.devices[iface_name].print_device_info()
                print

    def _list_networks_helper(self, wdevice):
        """Helper to list a device's networks."""
        print "%6s:" % wdevice.Interface
        wdevice.print_aps_info()
        print

    def list_networks(self, device=None):
        """Print a list of networks detected from a device, or all.

        Keyword arguments:
        device -- print the networks of just the specified device

        """
        print "Wireless Devices:"
        if device:
            try:
                wdevice = self.wireless_devices[device]
            except:
                print "ERROR: Invalid wireless device.\n"
                raise
            self._list_networks_helper(wdevice)
        else:
            for devname in self.wireless_devices:
                self._list_networks_helper(self.devices[devname])

    def print_nm_status(self):
        """Print current NM status."""
        print "Current Network Manager Status: %s\n" % \
                self.nm.nm_state_str()

    def connect(self, wired=None, wireless=None, gsm=None):
        """Make a connection with a device.

        Keyword arguments:
        wired -- wired options
        wireless -- wireless options
        gsm -- gsm options
        """

        def send_connection(connsett_path):
            """Send the connection to NM"""
            connsett = self.nmusermset.get_connection_by_objpath(connsett_path)
            extra = "/"
            device = ""
            if connsett == self.wired_connection:
                device = wired['device']
                devpath = self.wired_devices[device].Udi
            elif connsett == self.wireless_connection:
                device = wireless['device']
                devpath = self.wireless_devices[device].Udi
            elif connsett == self.gsm_connection:
                device = gsm['device']
                devpath = self.gsm_devices[device].Udi

            print "Device %s: activating the connection" % device

            conn = self.nm.ActivateConnection(NM_DBUS_IFACE_USER_SETTINGS,
                    connsett_path, devpath, extra,
                    dbus_interface=NM_DBUS_IFACE)
            self.active_connections.append(conn)

            print "Device %s: created connection '%s'" % (device, conn)

            # Connect the properties changed to show the user the connection status
            obj = bus.get_object(NM_DBUS_IFACE, conn)
            iface = dbus.Interface(obj, NM_DBUS_IFACE + ".Connection.Active")
            iface.connect_to_signal("PropertiesChanged",
                    self.signal_active_connection_change, path_keyword='path')

        # Setting up the user main settings service
        self.nmusermset = NMUserMainSettings()

        # Creating the connection settings objects
        if wired:
            self.wired_connection = self.create_wired_connection(wired)
            self.nmusermset.add_connection_setting(self.wired_connection)
        if wireless:
            self.wireless_connection = self.create_wireless_connection(wireless)
            self.nmusermset.add_connection_setting(self.wireless_connection)
        if gsm:
            self.gsm_connection = self.create_gsm_connection(gsm)
            self.nmusermset.add_connection_setting(self.gsm_connection)

        # Register the newconnection signal handler
        # XXX: should we create an object for nmsettings?
        obj = bus.get_object(NM_DBUS_IFACE_USER_SETTINGS, NM_DBUS_PATH_SETTINGS)
        iface = dbus.Interface(obj, NM_DBUS_IFACE_SETTINGS)
        iface.connect_to_signal("NewConnection", send_connection)

        # XXX: this should be in somewhere else
        gobject.timeout_add(1000, self._emit_signals)

    def _emit_signals(self):
        """Emit new connection signal."""
        for conn in self.nmusermset.ListConnections():
            self.nmusermset.NewConnection(conn)

    def disconnect(self):
        """Disconnect all valid connections."""
        for conn in self.nm.ActiveConnections:
            if conn in self.active_connections:
                print "Deactivating connection '%s'" % conn
                self.nm.DeactivateConnection(conn)

    def sleep(self):
        """Tell NetworkManager to go into offline mode."""
        self.nm.Sleep(True)
        self.print_nm_status()

    def wake(self):
        """Tell NetworkManager to come back from offline mode."""
        self.nm.Sleep(False)
        self.print_nm_status()

    def update_devices(self):
        """Get and sets up a list of devices.

        Sets up lists of devices:
         * devices - all devices
         * wired_devices - all wired devices
         * wireless_devices - all wireless devices
         * gsm_devices - all gsm devices
        """
        # Empty the old structures
        self.devices = {}
        self.wired_devices = {}
        self.wireless_devices = {}
        self.gsm_devices = {}

        devices = self.nm.GetDevices()

        for device in devices:
            devobj = bus.get_object(NM_DBUS_SERVICE, device)
            devi = dbus.Interface(devobj, DBUS_PROPERTIES)
            device_type = devi.Get(NM_DBUS_SERVICE, "DeviceType")

            if device_type == NM_DEVICE_TYPE_ETHERNET:
                nmdevice = NMDeviceWired(device)
                self.wired_devices[nmdevice.Interface] = nmdevice
            elif device_type == NM_DEVICE_TYPE_WIFI:
                nmdevice = NMDeviceWireless(device)
                self.wireless_devices[nmdevice.Interface] = nmdevice
            elif device_type == NM_DEVICE_TYPE_GSM:
                nmdevice = NMDeviceGsm(device)
                self.gsm_devices[nmdevice.Interface] = nmdevice

            self.devices[nmdevice.Interface] = nmdevice

    def create_wired_connection(self, opt):
        id = self.connections
        self.connections+=1
        properties = {}
        properties['802-3-ethernet'] = {
                'mac-address': self.wired_devices[opt['device']].HwAddress}
        properties['connection'] = {
                'autoconnect': True,
                'type': '802-3-ethernet',
                'id': 'netm-cli-wired',
                'uuid': '53f7d6d2-a327-4316-819a-79135cba7ee2'}
        properties['ipv4'] = {
                'method': 'auto'}
        return NMUserConnectionSettings(id, properties)

    def create_wireless_connection(self, opt):
        id = self.connections
        self.connections+=1
        properties = {}
        # All we need for basic connection
        properties['802-11-wireless'] = {
                'ssid': [dbus.Byte(c) for c in opt['ssid']],
                'mode': 'infrastructure'}
        properties['connection'] = {
                'autoconnect': True,
                'type': '802-11-wireless',
                'id': 'netm-cli-wireless',
                'uuid': '50813901-c573-4238-84b5-060f93c7ab8a'}
        properties['ipv4'] = {
                'method': 'auto'}

        # Need just in case of WEP or WAP
        if opt['contype'] and opt['contype'] != 'OPEN':
            properties['802-11-wireless']['security'] = '802-11-wireless-security'
            properties['802-11-wireless-security'] = {}

        # WEP
        if opt['contype'] == 'WEP':
            properties['802-11-wireless-security']['key-mgmt'] = 'none'
            properties['802-11-wireless-security']['wep-key0'] = opt['hexkey']
            if opt['wep_alg'] == 'shared':
                properties['802-11-wireless-security']['auth-alg'] = 'shared'
        # WPA and WPA2
        elif opt['contype'] == 'WPA' or opt['contype'] == 'WPA2':
            properties['802-11-wireless-security']['key-mgmt'] = 'wpa-psk'
            properties['802-11-wireless-security']['psk'] = opt['hexkey']
        return NMUserConnectionSettings(id, properties)

    def create_gsm_connection(self, opt):
        id = self.connections
        self.connections+=1
        properties = {}
        properties['connection'] = {
                'type': 'gsm',
                'id': 'netm-cli-gsm',
                'uuid': '292d24f3-77c5-4a4a-8f02-4a685b157446'}
        properties['serial'] = {'baud': int(opt['baud'])}
        properties['ppp'] = {}
        properties['gsm'] = {
                'username': opt['user'],
                'password': opt['passwd'],
                'apn': opt['apn'],
                'number': opt['number']}
        properties['ipv4'] = {
                'method': 'auto',
                'ignore-auto-dns': 0}
        return NMUserConnectionSettings(id, properties)

    # Signals
    def signal_active_connection_change(self, properties, path):
        if properties.has_key('State'):
            print "%s - State: %s" % (path,
                        nm_active_con_state[properties['State']])
            if nm_active_con_state[properties['State']] == 'Unknown':
                if path in self.active_connections:
                    self.active_connections.remove(path)
                if not self.active_connections:
                    print "Failed to connect, aborting."
                    loop.quit()

# NM Object
class NMObject(ProxyObject):
    """Main NM Object.

    Object to map the NM dbus objects. Makes the access to the
    dbus object's methods easier, you can just call it directly.
    For properties you need to set a custom property with the correct
    name.

    """

    def __init__(self, objpath, interface):
        ProxyObject.__init__(self, bus, NM_DBUS_SERVICE, objpath)
        self.dbus_properties = dbus.Interface(self, DBUS_PROPERTIES)
        self.obji = dbus.Interface(self, interface)

    def getp(self, property):
        return self.dbus_properties.Get(NM_DBUS_SERVICE, property)

    def setp(self, property, value):
        self.dbus_properties.Set(NM_DBUS_SERVICE, property, value)

# Network Manager Object
class NetworkManager(NMObject):
    """Network Manager object.

    The main dbus object.

    """

    def __init__(self):
        NMObject.__init__(self, NM_DBUS_PATH, NM_DBUS_IFACE)
        self.WirelessHardwareEnabled = self.getp("WirelessHardwareEnabled")

        # Signals
        self.obji.connect_to_signal('StateChanged',
                        self.signal_state_changed, path_keyword='path')

    def get_state(self):
        return self.getp("State")

    def get_active_connections(self):
        return self.getp("ActiveConnections")

    def get_wireless_enabled(self):
        return self.getp("WirelessEnabled")

    def set_wireless_enabled(self, value):
        if not isinstance(value, bool):
            return
        self.setp("WirelessEnabled", value)

    def nm_state_str(self):
        """NetworkManager state as a string."""
        return nm_state[self.State]

    def signal_state_changed(self, state, path):
        print "%s - State: %s" % (path, nm_state[state])

    ActiveConnections = property(get_active_connections)
    State = property(get_state)
    WirelessEnabled = property(get_wireless_enabled, set_wireless_enabled)

# Devices
class NMDevice(NMObject):
    """Network Manager device object.

    Common code between the devices.

    """

    def __init__(self, device):
        """Get the data from dbus object's 'device'.

        Keyword arguments:
        device -- device obj path

        """
        NMObject.__init__(self, device, NM_DBUS_IFACE_DEVICE)
        # Properties
        self.Udi = self.getp("Udi")
        self.Interface = self.getp("Interface")
        self.Driver = self.getp("Driver")
        self.Capabilities = self.getp("Capabilities")
        self.Ip4Address = self.getp("Ip4Address")
        self.Dhcp4Config = self.getp("Dhcp4Config")
        self.Managed = self.getp("Managed")
        self.DeviceType = self.getp("DeviceType")
        self.Ip4Config = self.getp("Ip4Config")
        if self.Ip4Config != "/":
            self.ip4configobj = NMIP4Config(self.Ip4Config)
        else:
            self.ip4configobj = None

        # Signals
        self.obji.connect_to_signal('StateChanged', self.signal_state_changed)

    def get_state(self):
        return self.getp("State")

    def print_specific_info(self):
        pass

    def print_ip4config_info(self):
        print "\tIP config:"
        if self.ip4configobj:
            self.ip4configobj.print_ip4config_info()
        else:
            print "\t\tNo IP settings found"

    def print_device_info(self):
        """Print general device information."""
        if nm_device_state[self.State] == 'Activated':
            print "%6s*:" % self.Interface
        else:
            print "%6s:" % self.Interface
        print "\tUdi: %s" % self.Udi
        print "\tDriver: %s" % self.Driver
        print "\tCapabilities:"
        if self.Capabilities & NM_DEVICE_CAP_NONE:
            print "\t\t- No Capabilities"
        if self.Capabilities & NM_DEVICE_CAP_NM_SUPPORTED:
            print "\t\t- Supported"
        if self.Capabilities & NM_DEVICE_CAP_CARRIER_DETECT:
            print "\t\t- Carrier Detect"
        print "\tState: %s" % nm_device_state[self.State]
        print "\tManaged: %s" % bool(self.Managed)

        self.print_ip4config_info()
        self.print_specific_info()

    def signal_state_changed(self, new_state, old_state, reason):
        print "Device %s: state - %s" % (self.Interface,
                                        nm_device_state[new_state])

    State = property(get_state)

class NMDeviceWired(NMDevice):
    """Network Managed wired device object."""

    def __init__(self, device):
        NMDevice.__init__(self, device)
        self.HwAddress = self.getp("HwAddress")
        self.Speed = self.getp("Speed")
        self.Carrier = self.getp("Carrier")

    def print_specific_info(self):
        print "\tHwAddress: %s" % self.HwAddress
        print "\tSpeed: %s" % self.Speed
        print "\tCarrier: %s" % self.Carrier

class NMDeviceWireless(NMDevice):
    """Network Managed wireless device object."""

    def __init__(self, device):
        NMDevice.__init__(self, device)
        self.HwAddress = self.getp("HwAddress")
        self.Mode = self.getp("Mode")
        self.Bitrate = self.getp("Bitrate")
        self.WirelessCapabilities = self.getp("WirelessCapabilities")
        self.aps = self._get_aps()

    def _get_aps(self):
        aps = {}
        for ap in self.GetAccessPoints():
            nmap = NMAP(ap)
            aps[nmap.HwAddress] = nmap
        return aps

    def print_specific_info(self):
        print "\tHwAddress: %s" % self.HwAddress
        print "\tMode: %s" % nm_80211_mode[self.Mode]
        print "\tBitrate: %s" % self.Bitrate
        print "\tWireless Capabilities:"
        if self.WirelessCapabilities & NM_WIFI_DEVICE_CAP_NONE:
            print "\t\t- No Capabilities"
        if self.WirelessCapabilities & NM_WIFI_DEVICE_CAP_CIPHER_WEP40:
            print "\t\t- WEP 40"
        if self.WirelessCapabilities & NM_WIFI_DEVICE_CAP_CIPHER_WEP104:
            print "\t\t- WEP 104"
        if self.WirelessCapabilities & NM_WIFI_DEVICE_CAP_CIPHER_TKIP:
            print "\t\t- TKIP"
        if self.WirelessCapabilities & NM_WIFI_DEVICE_CAP_CIPHER_CCMP:
            print "\t\t- CCMP"
        if self.WirelessCapabilities & NM_WIFI_DEVICE_CAP_WPA:
            print "\t\t- WPA"
        if self.WirelessCapabilities & NM_WIFI_DEVICE_CAP_RSN:
            print "\t\t- RSN"

    def print_aps_info(self):
        print "\tNetworks:"
        for ap_name in self.aps:
            self.aps[ap_name].print_ap_info()

class NMDeviceGsm(NMDevice):
    """Network Managed gsm device object."""

    def __init__(self, device):
        NMDevice.__init__(self, device)

# AP Class
class NMAP(NMObject):
    """Network Managed AP device object."""

    def __init__(self, ap):
        """Get the data from dbus object's 'ap'.

        Keyword arguments:
        ap -- ap obj path

        """
        NMObject.__init__(self, ap, NM_DBUS_IFACE_AP)
        # Properties
        self.Ssid = self.getp("Ssid")
        self.HwAddress = self.getp("HwAddress")
        self.Flags = self.getp("Flags")
        self.Frequency = self.getp("Frequency")
        self.MaxBitrate = self.getp("MaxBitrate")
        self.Mode = self.getp("Mode")
        self.RsnFlags = self.getp("RsnFlags")
        self.WpaFlags = self.getp("WpaFlags")
        self.Channel = 0
        if self.Frequency in channels.keys():
            self.Channel = channels[self.Frequency]

    def get_strength(self):
        return self.getp("Strength")

    def ap_ssid_str(self, array):
        ap_str = ""
        for char in array:
            ap_str += "%c" % char
        return ap_str

    def print_ap_info(self):
        """Print general ap information."""
        print "%25s hwaddr%18s chan%3s freq%5s Ghz strength%3d%% "\
              "rate %s Mb/s type %s" % (self.ap_ssid_str(self.Ssid),
                                        self.HwAddress,
                                        self.Channel,
                                        self.Frequency,
                                        self.Strength,
                                        self.MaxBitrate/1000,
                                        self.WpaFlags)

    Strength = property(get_strength)

# IP4 Config Class
class NMIP4Config(NMObject):
    """Network Manager IP4Config object."""

    def __init__(self, ip4config):
        """Get the data from dbus object's 'ip4config'.

        Keyword arguments:
        ip4config -- ip4config obj path

        """
        NMObject.__init__(self, ip4config, NM_DBUS_IFACE_IP4CONFIG)
        self.Addresses = self.getp("Addresses")
        self.Routes = self.getp("Routes")
        self.Domains = self.getp("Domains")
        self.Nameservers = self.getp("Nameservers")

    def ip_str(self, ip):
        """Return the ip address as string.

        Keyword arguments:
        ip -- the ip address in decimal

        """
        return inet_ntoa(pack('L', ip))

    def netmask_str(self, mask):
        """Return the string format of netmask.

        Keyword arguments:
        mask -- the netmask in decimal (e.g. 22,24)
        """
        netmask = ((1<<mask)-1)<<32-mask
        return inet_ntoa(pack('!L', netmask))

    def print_addresses(self):
        addresses = self.Addresses[0]
        print "\t\tIP\t\t%s" % self.ip_str(addresses[0])
        print "\t\tNetmask\t\t%s" % self.netmask_str(addresses[1])
        print "\t\tGatway\t\t%s" % self.ip_str(addresses[2])
        print "\t\tDomains:"
        for domain in self.Domains:
            print "\t\t\t\t%s" % domain
        print "\t\tNameservers:"
        for nameserver in self.Nameservers:
            print "\t\t\t\t%s" % self.ip_str(nameserver)

    def print_ip4config_info(self):
        self.print_addresses()

# User Main Settings (the real service)
class NMUserMainSettings(dbus.service.Object):
    def __init__(self):
        bus_name = dbus.service.BusName(NM_DBUS_IFACE_USER_SETTINGS,
                                        dbus.SystemBus())
        dbus.service.Object.__init__(self, bus_name,
                                        NM_DBUS_PATH_SETTINGS)
        self.connsetts = []

    def add_connection_setting(self, connsett):
        self.connsetts.append(connsett)

    def get_connection_by_objpath(self, objpath):
        for connsett in self.connsetts:
            if (connsett.get_objpath() == objpath):
                return connsett
        return None

    @dbus.service.method(NM_DBUS_IFACE_SETTINGS, in_signature='',
                                                  out_signature='ao')
    def ListConnections(self):
        objpaths = []
        for connsett in self.connsetts:
            objpaths.append(connsett.get_objpath())
        return objpaths

    @dbus.service.signal(NM_DBUS_IFACE_SETTINGS, signature='o')
    def NewConnection(self, connsett):
        pass

# Connection Settings
class NMUserConnectionSettings(dbus.service.Object):
    def __init__(self, id, properties={}):
        self.id = id
        self.objpath = NM_DBUS_PATH_SETTINGS + "/" + str(id)
        bus_name = dbus.service.BusName(NM_DBUS_IFACE_USER_SETTINGS,
                                        dbus.SystemBus())
        dbus.service.Object.__init__(self, bus_name, self.objpath)
        self.settings = properties

    def get_objpath(self):
        return self.objpath

    @dbus.service.method(NM_DBUS_IFACE_SETTINGS + ".Connection",
                                    in_signature='', out_signature='')
    def Delete(self):
        pass

    @dbus.service.method(NM_DBUS_IFACE_SETTINGS + ".Connection",
                           in_signature='', out_signature='a{sa{sv}}')
    def GetSettings(self):
        return self.settings

    @dbus.service.method(NM_DBUS_IFACE_SETTINGS + ".Connection",
                           in_signature='a{sa{sv}}', out_signature='')
    def Update(self, properties):
        self.settings = properties

    @dbus.service.signal(NM_DBUS_IFACE_SETTINGS + ".Connection",
                                                         signature='')
    def Removed(self):
        pass

    @dbus.service.signal(NM_DBUS_IFACE_SETTINGS + ".Connection",
                                                signature='a{sa{sv}}')
    def Updated(self, blah):
        pass

    @dbus.service.method(NM_DBUS_IFACE_SETTINGS + ".Connection.Secrets",
                        in_signature='sasb', out_signature='a{sa{sv}}')
    def GetSecrets(self, setting_name, hints, request_new):
        return self.settings

# Config Class

class NMConfig:
    """Network Manager cli config object."""

    def __init__(self):
        self.parser = ConfigParser()
        self.filename = os.path.expanduser('~/.netm-cli')
        self.parser.read([self.filename])

    def get(self, section, name, default=None):
        """Get the value of a option.

        Section of the config file and the option name.
        You can pass a default value if the option doesn't exist.

        """
        if not self.parser.has_option(section, name):
            return default
        return self.parser.get(section, name)

    def set(self, section, option, value):
        """Set an option.

        This change is not persistent unless saved with 'save()'.

        """
        if not self.parser.has_section(section):
            self.parser.add_section(section)
        return self.parser.set(section, option, value)

    def remove(self, section, name):
        """Remove an option."""
        if self.parser.has_section(section):
            self.parser.remove_option(section, name)

    def save(self):
        """Save the configuration file with all modifications."""
        if not self.filename:
            return
        fileobj = file(self.filename, 'w')
        try:
            self.parser.write(fileobj)
        finally:
            fileobj.close()

def validate_wired_options(nmcli, opt):
    """Validate the wired options."""
    errors = []
    if not nmcli.wired_devices.has_key(opt['device']):
        errors.append('invalid wired device')
    return errors

def validate_wireless_options(nmcli, opt):
    """Validate the wireless options."""
    errors = []
    if not nmcli.wireless_devices.has_key(opt['device']):
        errors.append('invalid wireless device')
    if not opt['ssid']:
        errors.append('missing ssid option for wireless connection')
    if opt['contype'] in ['WEP', 'WPA', 'WPA2'] and not opt['hexkey']:
        errors.append('missing hex-key option to connect with %s' % opt['contype'])
    if opt['contype'] == 'WEP' and len(opt['hexkey']) != 10 and len(opt['hexkey']) != 26:
        errors.append('WEP hex-key len should be 10 (64 bits) or 26 (128 bits)')
    if opt['contype'] == 'WEP' and not opt['wep_alg']:
        errors.append('missing WEP authentication method')
    return errors

def validate_gsm_options(nmcli, opt):
    """Validate the gms options."""
    errors = []
    if not nmcli.gsm_devices.has_key(opt['device']):
        errors.append('invalid gsm device')
    if not opt['apn']:
        errors.append('missing apn option for gsm connection')
    if not opt['number']:
        errors.append('missing number option for gsm connection')
    return errors

def main():
    usage = 'usage: %prog [options]'
    epilog = 'Report bugs to <rsalveti@gmail.com>.'
    description = "Command Line interface for Network Manager"
    parser = OptionParser(usage=usage, version='%prog ' + str(__version__),
                          epilog=epilog, description=description)

    common_group = OptionGroup(parser, 'Common Options')
    wired_group = OptionGroup(parser, 'Wired Options')
    wireless_group = OptionGroup(parser, 'Wireless Options')
    gsm_group = OptionGroup(parser, 'GSM Options')

    wep_group = OptionGroup(parser, 'WEP Options')
    config_group = OptionGroup(parser, 'Config Options')

    # Common options
    common_group.add_option('-c', '--connect', action='store_true',
            dest='connect', default=False,
            help='connect to all defined networks')
    common_group.add_option('-l', '--list-devices', action='store_true',
            dest='list', default=False, help='list device(s) info')
    common_group.add_option('-n', '--list-networks', action='store_true',
            dest='networks', default=False, help='list networks')
    common_group.add_option('-S', '--nm-status', action='store_true',
            dest='nmstatus', default=False, help='shows current NM status')
    common_group.add_option('--sleep', action='store_true', default=False,
            dest='sleep', help='put NetworkManager to sleep')
    common_group.add_option('--wake-up', action='store_true', default=False,
            dest='wake_up', help='wake NetworkManager up')
    common_group.add_option('-v', '--verbose', action='store_true', default=False,
            dest='verbose', help='verbose info')

    # Wired options
    wired_group.add_option('-e', '--ethernet', dest='ethernet',
            help='ethernet device used to make the connection', default=None) 

    # Wireless options
    wireless_group.add_option('-w', '--wireless', dest='wireless',
            help='wireless device used to make the connection')
    wireless_group.add_option('-s', '--ssid', dest='ssid',
            help='connect to the network named ssid')
    wireless_group.add_option('-k', '--hex-key', dest='hexkey', default=None,
            help='hex key used to connect to a network (WEP and WPA)')
    wireless_group.add_option('-t', '--connection_type', dest='contype',
            help='connection type to connect (OPEN, WEP, WPA, WPA2)',
            choices=['OPEN','WEP','WPA','WPA2'])
    wireless_group.add_option('-P', '--wireless-power', dest='wpower',
            help='Show status and turn on/off the wireless card (on, off, show)',
            choices=['on','off','show'])
    wep_group.add_option('--wep-auth', dest='wep_alg',
            help='WEP authentication method (open or shared)',
            choices=['open', 'shared'])

    # GSM options
    gsm_group.add_option('-g', '--gsm', dest='gsm',
            help='GSM device used to make the connection')
    gsm_group.add_option('-u', '--user', dest='user',
            help='connection username', default='')
    gsm_group.add_option('-p', '--passwd', dest='passwd',
            help='connection password', default='')
    gsm_group.add_option('-a', '--apn', dest='apn',
            help='connection apn', default='')
    gsm_group.add_option('-N', '--number', dest='number',
            help='connection number', default='')
    gsm_group.add_option('-b', '--baud', dest='baud',
            help='gsm serial baud rate (default 115200)', default=115200)

    # Config options
    config_group.add_option('--save-config', action='store_true', default=False,
            dest='savecfg', help='save the options to the config file')

    # Groups
    parser.add_option_group(common_group)
    parser.add_option_group(wired_group)
    parser.add_option_group(wireless_group)
    parser.add_option_group(wep_group)
    parser.add_option_group(gsm_group)
    parser.add_option_group(config_group)

    (options, args) = parser.parse_args()

    # We need at least one action
    if not options.connect and not options.list \
                    and not options.networks \
                    and not options.nmstatus \
                    and not options.sleep \
                    and not options.wake_up \
                    and not options.wpower:
        parser.error("You need to specify at least one action.")

    # NMCli object
    nmcli = NMCLI(options.verbose)

    # Getting data from the config file
    config = NMConfig()

    # Connection options
    wddevice = None
    wdoptions = {}
    wdevice = None
    woptions = {}
    gsmdevice = None
    gsmoptions = {}

    if options.connect and options.ethernet:
        # Wired device options
        wddevice = options.ethernet
        wdoptions['device'] = wddevice
        wderrors = validate_wired_options(nmcli, wdoptions)
        if wderrors:
            parser.error(wderrors)
    if options.connect and options.wireless:
        # Wireless device options
        wdevice = options.wireless
        woptions['device'] = wdevice
        woptions['ssid'] = options.ssid or config.get(wdevice, 'ssid')
        woptions['hexkey'] = options.hexkey or config.get(wdevice, 'hexkey')
        woptions['contype'] = options.contype or config.get(wdevice, 'contype')
        woptions['wep_alg'] = options.wep_alg or config.get(wdevice, 'wep_alg')
        werrors = validate_wireless_options(nmcli, woptions)
        if werrors:
            parser.error(werrors)
    if options.connect and options.gsm:
        # GSM options
        gsmdevice = options.gsm
        gsmoptions['device'] = gsmdevice
        gsmoptions['user'] = options.user or config.get(gsmdevice, 'user')
        gsmoptions['passwd'] = options.passwd or config.get(gsmdevice, 'passwd')
        gsmoptions['apn'] = options.apn or config.get(gsmdevice, 'apn')
        gsmoptions['number'] = options.number or config.get(gsmdevice, 'number')
        gsmoptions['baud'] = options.baud or config.get(gsmdevice, 'baud')
        gsmerrors = validate_gsm_options(nmcli, gsmoptions)
        if gsmerrors:
            parser.error(gsmerrors)

    # Arguments checking
    if options.connect and not (options.ethernet or \
                        options.wireless or options.gsm):
        parser.error("missing a connection device option "
                                "(wired, wireless or gsm)")
    if (options.networks or options.wireless) and options.wpower == 'off':
        parser.error("can't use wireless interface when requesting to "
                                "turn it off")

    # Wireless power switch
    if options.wpower:
        if options.wpower == 'on' and not nmcli.nm.WirelessEnabled:
            nmcli.nm.WirelessEnabled = True
        elif options.wpower == 'off' and nmcli.nm.WirelessEnabled:
            nmcli.nm.WirelessEnabled = False
        print "The current wireless power status is %s" % \
                   ('ON' if nmcli.nm.WirelessEnabled else 'OFF')

    # In case of wireless request, lets check if it's on
    if (options.networks or options.wireless) and not nmcli.nm.WirelessEnabled:
        print "INFO: The current wireless power status is OFF"
        print "      Use '-P on' option to turn it on before using the device"
        return

    # Main actions
    if options.nmstatus:
        nmcli.print_nm_status()
    if options.sleep:
        nmcli.sleep()
    elif options.wake_up:
        nmcli.wake()
    if options.list:
        nmcli.list_devices(options.networks)
    elif options.networks:
        nmcli.list_networks(wdevice)
    if options.connect:
        nmcli.connect(wdoptions, woptions, gsmoptions)
    if options.savecfg:
        if options.wireless:
            keys = woptions.keys()
            keys.remove('device')
            for key in keys:
                config.set(wdevice, key, woptions[key])
        if options.gsm:
            keys = gsmoptions.keys()
            keys.remove('device')
            for key in keys:
                config.set(gsmdevice, key, gsmoptions[key])
        config.save()

    if options.connect:
        try:
            def signal_handler(sig, action):
                raise Exception
            signal.signal(signal.SIGTERM, signal_handler)
            loop.run()
        except:
            nmcli.disconnect()

# Run the program
if __name__ == '__main__':
    main()
