Connecting to a protected WiFi from Python on Linux
Question: I'm creating a software for Ubuntu Linux that needs to connect to a WiFi AP.
The WiFi network is not predefined and can change several times during a
single run of the software (the user is the one who orders the change). The
idea is this: given a set of SSIDs and their WPA or WEP passphrases, the
software should be able to switch between the networks on the whim, without
the need to change any configuration files anywhere in the system.

The huge problem, as it seems, is to pass the passphrase to the connection.
Here's what I've been operating with so far:

  * Ubuntu 12.10 machine equipped with a WiFi dongle.
  * Python, which runs the software, and which will be used to request the connections
  * connman 0.79
  * wpa_supplicant v1.0
  * d-bus

At first I thought it would be possible to pass the passphrase to connman
through d-bus, but neither this version of connman, nor 1.11, seem to expose
any method for that. Then I found out that it's possible to dump a
`service_<SSID>.conf` file to the `/var/lib/connman/` directory. Contents of
the file are very simple and look like this:

    
    
    [service_SSID]
    Type=wifi
    Name=Network-SSID
    Passphrase=here-goes-the-passphrase
    

Once this file is created, connecting to the network requires a simple call to
net.connman.Service.Connect() method in appropriate service. The problem is
that connman won't parse the config file unless it's restarted. This requires
sudo privileges, additional time, and raises risk for all the "what can go
wrong now" things to occur. Then I figured that passphrase could be somehow
passed to the wpa_supplicant d-bus API, but I failed to find anything.

Google searches have failed me too. It is as if no one ever tried to do this
before.

Command `sudo iwconfig wlan0 essid <SSID> key s:<PASSPHRASE>` results in a
`SET failed on device wlan0 ; Invalid argument.` error. Also, it requires sudo
which I would like to avoid.

I tried to figure out how the wpa_gui program does its magic. First of all I
discovered that it also requires sudo, and that it sends a bunch of commands
directly to `/var/run/wpa_supplicant/wlan0`. Replicating this behavior would
be a last resort for me if I don't figure out anything simplier.

So, the big question is this: How to use Python in order to connect to a
WEP/WPA protected WiFi network?  
I'm also wondering if using connman is a good approach here and if I shouldn't
revert to the Network Manager, which is Ubuntu's default.


Answer: This shows how to do this for WPA.

First of all, ditch connman and use NetworkManager. The example script below
shows how to enable wireless support, check if network with given SSID is
available, connect to that SSID using a WPA passphrase, and then disconnect
from the network and disable wireless. I'm fairly sure that this script can be
improved, but the current version serves enough as an example:

    
    
    #!/usr/bin/python
    
    # This script shows how to connect to a WPA protected WiFi network
    # by communicating through D-Bus to NetworkManager 0.9.
    #
    # Reference URLs:
    # http://projects.gnome.org/NetworkManager/developers/
    # http://projects.gnome.org/NetworkManager/developers/settings-spec-08.html
    
    import dbus
    import time
    
    SEEKED_SSID = "skynet"
    SEEKED_PASSPHRASE = "qwertyuiop"
    
    if __name__ == "__main__":
        bus = dbus.SystemBus()
        # Obtain handles to manager objects.
        manager_bus_object = bus.get_object("org.freedesktop.NetworkManager",
                                            "/org/freedesktop/NetworkManager")
        manager = dbus.Interface(manager_bus_object,
                                 "org.freedesktop.NetworkManager")
        manager_props = dbus.Interface(manager_bus_object,
                                       "org.freedesktop.DBus.Properties")
    
        # Enable Wireless. If Wireless is already enabled, this does nothing.
        was_wifi_enabled = manager_props.Get("org.freedesktop.NetworkManager",
                                             "WirelessEnabled")
        if not was_wifi_enabled:
            print "Enabling WiFi and sleeping for 10 seconds ..."
            manager_props.Set("org.freedesktop.NetworkManager", "WirelessEnabled",
                              True)
            # Give the WiFi adapter some time to scan for APs. This is absolutely
            # the wrong way to do it, and the program should listen for
            # AccessPointAdded() signals, but it will do.
            time.sleep(10)
    
        # Get path to the 'wlan0' device. If you're uncertain whether your WiFi
        # device is wlan0 or something else, you may utilize manager.GetDevices()
        # method to obtain a list of all devices, and then iterate over these
        # devices to check if DeviceType property equals NM_DEVICE_TYPE_WIFI (2).
        device_path = manager.GetDeviceByIpIface("wlan0")
        print "wlan0 path: ", device_path
    
        # Connect to the device's Wireless interface and obtain list of access
        # points.
        device = dbus.Interface(bus.get_object("org.freedesktop.NetworkManager",
                                               device_path),
                                "org.freedesktop.NetworkManager.Device.Wireless")
        accesspoints_paths_list = device.GetAccessPoints()
    
        # Identify our access point. We do this by comparing our desired SSID
        # to the SSID reported by the AP.
        our_ap_path = None
        for ap_path in accesspoints_paths_list:
            ap_props = dbus.Interface(
                bus.get_object("org.freedesktop.NetworkManager", ap_path),
                "org.freedesktop.DBus.Properties")
            ap_ssid = ap_props.Get("org.freedesktop.NetworkManager.AccessPoint",
                                   "Ssid")
            # Returned SSID is a list of ASCII values. Let's convert it to a proper
            # string.
            str_ap_ssid = "".join(chr(i) for i in ap_ssid)
            print ap_path, ": SSID =", str_ap_ssid
            if str_ap_ssid == SEEKED_SSID:
                our_ap_path = ap_path
                break
    
        if not our_ap_path:
            print "AP not found :("
            exit(2)
        print "Our AP: ", our_ap_path
    
        # At this point we have all the data we need. Let's prepare our connection
        # parameters so that we can tell the NetworkManager what is the passphrase.
        connection_params = {
            "802-11-wireless": {
                "security": "802-11-wireless-security",
            },
            "802-11-wireless-security": {
                "key-mgmt": "wpa-psk",
                "psk": SEEKED_PASSPHRASE
            },
        }
    
        # Establish the connection.
        settings_path, connection_path = manager.AddAndActivateConnection(
            connection_params, device_path, our_ap_path)
        print "settings_path =", settings_path
        print "connection_path =", connection_path
    
        # Wait until connection is established. This may take a few seconds.
        NM_ACTIVE_CONNECTION_STATE_ACTIVATED = 2
        print """Waiting for connection to reach """ \
              """NM_ACTIVE_CONNECTION_STATE_ACTIVATED state ..."""
        connection_props = dbus.Interface(
            bus.get_object("org.freedesktop.NetworkManager", connection_path),
            "org.freedesktop.DBus.Properties")
        state = 0
        while True:
            # Loop forever until desired state is detected.
            #
            # A timeout should be implemented here, otherwise the program will
            # get stuck if connection fails.
            #
            # IF PASSWORD IS BAD, NETWORK MANAGER WILL DISPLAY A QUERY DIALOG!
            # This is something that should be avoided, but I don't know how, yet.
            #
            # Also, if connection is disconnected at this point, the Get()
            # method will raise an org.freedesktop.DBus.Error.UnknownMethod
            # exception. This should also be anticipated.
            state = connection_props.Get(
                "org.freedesktop.NetworkManager.Connection.Active", "State")
            if state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED:
                break
            time.sleep(0.001)
        print "Connection established!"
    
        #
        # Connection is established. Do whatever is necessary.
        # ...
        #
        print "Sleeping for 5 seconds ..."
        time.sleep(5)
        print "Disconnecting ..."
    
        # Clean up: disconnect and delete connection settings. If program crashes
        # before this point is reached then connection settings will be stored
        # forever.
        # Some pre-init cleanup feature should be devised to deal with this problem,
        # but this is an issue for another topic.
        manager.DeactivateConnection(connection_path)
        settings = dbus.Interface(
            bus.get_object("org.freedesktop.NetworkManager", settings_path),
            "org.freedesktop.NetworkManager.Settings.Connection")
        settings.Delete()
    
        # Disable Wireless (optional step)
        if not was_wifi_enabled:
            manager_props.Set("org.freedesktop.NetworkManager", "WirelessEnabled",
                              False)
        print "DONE!"
        exit(0)
    

