gsupplicant: Add a function to enable or disable the selected network
[platform/upstream/connman.git] / gsupplicant / supplicant.c
index 3dc5091..1c360fb 100644 (file)
@@ -29,6 +29,7 @@
 #include <string.h>
 #include <stdint.h>
 #include <syslog.h>
+#include <ctype.h>
 
 #include <glib.h>
 #include <gdbus.h>
@@ -152,6 +153,7 @@ struct _GSupplicantInterface {
        unsigned int pairwise_capa;
        unsigned int scan_capa;
        unsigned int mode_capa;
+       unsigned int max_scan_ssids;
        dbus_bool_t ready;
        GSupplicantState state;
        dbus_bool_t scanning;
@@ -169,21 +171,6 @@ struct _GSupplicantInterface {
        void *data;
 };
 
-struct _GSupplicantNetwork {
-       GSupplicantInterface *interface;
-       char *path;
-       char *group;
-       char *name;
-       unsigned char ssid[32];
-       unsigned int ssid_len;
-       dbus_int16_t signal;
-       GSupplicantMode mode;
-       GSupplicantSecurity security;
-       dbus_bool_t wps;
-       GHashTable *bss_table;
-       GHashTable *config_table;
-};
-
 struct g_supplicant_bss {
        GSupplicantInterface *interface;
        char *path;
@@ -203,6 +190,23 @@ struct g_supplicant_bss {
        dbus_bool_t ieee8021x;
 };
 
+struct _GSupplicantNetwork {
+       GSupplicantInterface *interface;
+       char *path;
+       char *group;
+       char *name;
+       unsigned char ssid[32];
+       unsigned int ssid_len;
+       dbus_int16_t signal;
+       dbus_uint16_t frequency;
+       struct g_supplicant_bss *best_bss;
+       GSupplicantMode mode;
+       GSupplicantSecurity security;
+       dbus_bool_t wps;
+       GHashTable *bss_table;
+       GHashTable *config_table;
+};
+
 static inline void debug(const char *format, ...)
 {
        char str[256];
@@ -406,6 +410,18 @@ static void callback_network_removed(GSupplicantNetwork *network)
        callbacks_pointer->network_removed(network);
 }
 
+static void callback_network_changed(GSupplicantNetwork *network,
+                                       const char *property)
+{
+       if (callbacks_pointer == NULL)
+               return;
+
+       if (callbacks_pointer->network_changed == NULL)
+               return;
+
+       callbacks_pointer->network_changed(network, property);
+}
+
 static void remove_interface(gpointer data)
 {
        GSupplicantInterface *interface = data;
@@ -609,7 +625,13 @@ static void interface_capability(const char *key, DBusMessageIter *iter,
        else if (g_strcmp0(key, "Modes") == 0)
                supplicant_dbus_array_foreach(iter,
                                interface_capability_mode, interface);
-       else
+       else if (g_strcmp0(key, "MaxScanSSID") == 0) {
+               dbus_int32_t max_scan_ssid;
+
+               dbus_message_iter_get_basic(iter, &max_scan_ssid);
+               interface->max_scan_ssids = max_scan_ssid;
+
+       } else
                SUPPLICANT_DBG("key %s type %c",
                                key, dbus_message_iter_get_arg_type(iter));
 }
@@ -712,6 +734,38 @@ unsigned int g_supplicant_interface_get_mode(GSupplicantInterface *interface)
        return interface->mode_capa;
 }
 
+unsigned int g_supplicant_interface_get_max_scan_ssids(
+                               GSupplicantInterface *interface)
+{
+       if (interface == NULL)
+               return 0;
+
+       return interface->max_scan_ssids;
+}
+
+static void set_network_enabled(DBusMessageIter *iter, void *user_data)
+{
+       dbus_bool_t enable = *(dbus_bool_t *)user_data;
+
+       dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &enable);
+}
+
+int g_supplicant_interface_enable_selected_network(GSupplicantInterface *interface,
+                                                       dbus_bool_t enable)
+{
+       if (interface == NULL)
+               return -1;
+
+       if (interface->network_path == NULL)
+               return -1;
+
+       SUPPLICANT_DBG(" ");
+       return supplicant_dbus_property_set(interface->network_path,
+                               SUPPLICANT_INTERFACE ".Network",
+                               "Enabled", DBUS_TYPE_BOOLEAN_AS_STRING,
+                               set_network_enabled, NULL, &enable);
+}
+
 GSupplicantInterface *g_supplicant_network_get_interface(
                                        GSupplicantNetwork *network)
 {
@@ -781,6 +835,14 @@ dbus_int16_t g_supplicant_network_get_signal(GSupplicantNetwork *network)
        return network->signal;
 }
 
+dbus_uint16_t g_supplicant_network_get_frequency(GSupplicantNetwork *network)
+{
+       if (network == NULL)
+               return 0;
+
+       return network->frequency;
+}
+
 dbus_bool_t g_supplicant_network_get_wps(GSupplicantNetwork *network)
 {
        if (network == NULL)
@@ -1005,6 +1067,8 @@ static void add_bss_to_network(struct g_supplicant_bss *bss)
        network->ssid_len = bss->ssid_len;
        memcpy(network->ssid, bss->ssid, bss->ssid_len);
        network->signal = bss->signal;
+       network->frequency = bss->frequency;
+       network->best_bss = bss;
 
        network->wps = FALSE;
        if ((bss->keymgmt & G_SUPPLICANT_KEYMGMT_WPS) != 0)
@@ -1022,6 +1086,12 @@ static void add_bss_to_network(struct g_supplicant_bss *bss)
        callback_network_added(network);
 
 done:
+       if (bss->signal > network->signal) {
+               network->signal = bss->signal;
+               network->best_bss = bss;
+               callback_network_changed(network, "Signal");
+       }
+
        g_hash_table_replace(interface->bss_mapping, bss->path, network);
        g_hash_table_replace(network->bss_table, bss->path, bss);
 
@@ -1166,7 +1236,9 @@ static void bss_process_ies(DBusMessageIter *iter, void *user_data)
        if (ie == NULL || ie_len < 2)
                return;
 
-       for (ie_end = ie+ie_len; ie+ie[1]+1 <= ie_end; ie += ie[1]+2) {
+       for (ie_end = ie + ie_len; ie < ie_end && ie + ie[1] + 1 <= ie_end;
+                                                       ie += ie[1] + 2) {
+
                if (ie[0] != WMM_WPA1_WPS_INFO || ie[1] < WPS_INFO_MIN_LEN ||
                        memcmp(ie+2, WPS_OUI, sizeof(WPS_OUI)) != 0)
                        continue;
@@ -1369,6 +1441,29 @@ static void interface_bss_added_without_keys(DBusMessageIter *iter,
                                                        bss_property, bss);
 }
 
+static void update_signal(gpointer key, gpointer value,
+                                               gpointer user_data)
+{
+       struct g_supplicant_bss *bss = value;
+       GSupplicantNetwork *network = user_data;
+
+       if (bss->signal > network->signal) {
+               network->signal = bss->signal;
+               network->best_bss = bss;
+       }
+}
+
+static void update_network_signal(GSupplicantNetwork *network)
+{
+       if (g_hash_table_size(network->bss_table) <= 1)
+               return;
+
+       g_hash_table_foreach(network->bss_table,
+                               update_signal, network);
+
+       SUPPLICANT_DBG("New network signal %d", network->signal);
+}
+
 static void interface_bss_removed(DBusMessageIter *iter, void *user_data)
 {
        GSupplicantInterface *interface = user_data;
@@ -1388,6 +1483,8 @@ static void interface_bss_removed(DBusMessageIter *iter, void *user_data)
        g_hash_table_remove(interface->bss_mapping, path);
        g_hash_table_remove(network->bss_table, path);
 
+       update_network_signal(network);
+
        if (g_hash_table_size(network->bss_table) == 0)
                g_hash_table_remove(interface->network_table, network->group);
 }
@@ -1842,6 +1939,27 @@ static void signal_bss_changed(const char *path, DBusMessageIter *iter)
                return;
 
        supplicant_dbus_property_foreach(iter, bss_property, bss);
+
+       if (bss->signal == network->signal)
+               return;
+
+       /*
+        * If the new signal is lower than the SSID signal, we need
+        * to check for the new maximum.
+        */
+       if (bss->signal < network->signal) {
+               if (bss != network->best_bss)
+                       return;
+               network->signal = bss->signal;
+               update_network_signal(network);
+       } else {
+               network->signal = bss->signal;
+               network->best_bss = bss;
+       }
+
+       SUPPLICANT_DBG("New network signal for %s %d dBm", network->ssid, network->signal);
+
+       callback_network_changed(network, "Signal");
 }
 
 static void wps_credentials(const char *key, DBusMessageIter *iter,
@@ -2086,6 +2204,13 @@ struct interface_connect_data {
        void *user_data;
 };
 
+struct interface_scan_data {
+       GSupplicantInterface *interface;
+       GSupplicantInterfaceCallback callback;
+       GSupplicantScanParams *scan_params;
+       void *user_data;
+};
+
 static void interface_create_property(const char *key, DBusMessageIter *iter,
                                                        void *user_data)
 {
@@ -2341,9 +2466,11 @@ int g_supplicant_interface_remove(GSupplicantInterface *interface,
 static void interface_scan_result(const char *error,
                                DBusMessageIter *iter, void *user_data)
 {
-       struct interface_data *data = user_data;
+       struct interface_scan_data *data = user_data;
 
        if (error != NULL) {
+               SUPPLICANT_DBG("error %s", error);
+
                if (data->callback != NULL)
                        data->callback(-EIO, data->interface, data->user_data);
        } else {
@@ -2351,27 +2478,137 @@ static void interface_scan_result(const char *error,
                data->interface->scan_data = data->user_data;
        }
 
+       if (data != NULL && data->scan_params != NULL)
+               g_free(data->scan_params);
+
        dbus_free(data);
 }
 
+static void add_scan_frequency(DBusMessageIter *iter, unsigned int freq)
+{
+       DBusMessageIter data;
+       unsigned int width = 0; /* Not used by wpa_supplicant atm */
+
+       dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, NULL, &data);
+
+       dbus_message_iter_append_basic(&data, DBUS_TYPE_UINT32, &freq);
+       dbus_message_iter_append_basic(&data, DBUS_TYPE_UINT32, &width);
+
+       dbus_message_iter_close_container(iter, &data);
+}
+
+static void add_scan_frequencies(DBusMessageIter *iter,
+                                               void *user_data)
+{
+       GSupplicantScanParams *scan_data = user_data;
+       unsigned int freq;
+       int i;
+
+       for (i = 0; i < G_SUPPLICANT_MAX_FAST_SCAN; i++) {
+               freq = scan_data->freqs[i];
+               if (!freq)
+                       break;
+
+               add_scan_frequency(iter, freq);
+       }
+}
+
+static void append_ssid(DBusMessageIter *iter,
+                       const void *ssid, unsigned int len)
+{
+       DBusMessageIter array;
+
+       dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
+       DBUS_TYPE_BYTE_AS_STRING, &array);
+
+       dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE,
+                                                               &ssid, len);
+       dbus_message_iter_close_container(iter, &array);
+}
+
+static void append_ssids(DBusMessageIter *iter, void *user_data)
+{
+       GSupplicantScanParams *scan_data = user_data;
+       int i;
+
+       for (i = 0; i < scan_data->num_ssids; i++)
+               append_ssid(iter, scan_data->ssids[i].ssid,
+                                       scan_data->ssids[i].ssid_len);
+}
+
+static void supplicant_add_scan_frequency(DBusMessageIter *dict,
+               supplicant_dbus_array_function function,
+                                       void *user_data)
+{
+       GSupplicantScanParams *scan_params = user_data;
+       DBusMessageIter entry, value, array;
+       const char *key = "Channels";
+
+       if (scan_params->freqs[0] != 0) {
+               dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY,
+                                               NULL, &entry);
+
+               dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key);
+
+               dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT,
+                                       DBUS_TYPE_ARRAY_AS_STRING
+                                       DBUS_STRUCT_BEGIN_CHAR_AS_STRING
+                                       DBUS_TYPE_UINT32_AS_STRING
+                                       DBUS_TYPE_UINT32_AS_STRING
+                                       DBUS_STRUCT_END_CHAR_AS_STRING,
+                                       &value);
+
+               dbus_message_iter_open_container(&value, DBUS_TYPE_ARRAY,
+                                       DBUS_STRUCT_BEGIN_CHAR_AS_STRING
+                                       DBUS_TYPE_UINT32_AS_STRING
+                                       DBUS_TYPE_UINT32_AS_STRING
+                                       DBUS_STRUCT_END_CHAR_AS_STRING,
+                                       &array);
+
+               if (function)
+                       function(&array, user_data);
+
+               dbus_message_iter_close_container(&value, &array);
+               dbus_message_iter_close_container(&entry, &value);
+               dbus_message_iter_close_container(dict, &entry);
+       }
+}
+
 static void interface_scan_params(DBusMessageIter *iter, void *user_data)
 {
        DBusMessageIter dict;
        const char *type = "passive";
+       struct interface_scan_data *data = user_data;
 
        supplicant_dbus_dict_open(iter, &dict);
 
-       supplicant_dbus_dict_append_basic(&dict, "Type",
-                                               DBUS_TYPE_STRING, &type);
+       if (data && data->scan_params) {
+               type = "active";
+
+               supplicant_dbus_dict_append_basic(&dict, "Type",
+                                       DBUS_TYPE_STRING, &type);
+
+               supplicant_dbus_dict_append_array(&dict, "SSIDs",
+                                               DBUS_TYPE_STRING,
+                                               append_ssids,
+                                               data->scan_params);
+
+               supplicant_add_scan_frequency(&dict, add_scan_frequencies,
+                                               data->scan_params);
+       } else
+               supplicant_dbus_dict_append_basic(&dict, "Type",
+                                       DBUS_TYPE_STRING, &type);
 
        supplicant_dbus_dict_close(iter, &dict);
 }
 
 int g_supplicant_interface_scan(GSupplicantInterface *interface,
+                               GSupplicantScanParams *scan_data,
                                GSupplicantInterfaceCallback callback,
                                                        void *user_data)
 {
-       struct interface_data *data;
+       struct interface_scan_data *data;
+       int ret;
 
        if (interface == NULL)
                return -EINVAL;
@@ -2404,10 +2641,34 @@ int g_supplicant_interface_scan(GSupplicantInterface *interface,
        data->interface = interface;
        data->callback = callback;
        data->user_data = user_data;
+       data->scan_params = scan_data;
 
-       return supplicant_dbus_method_call(interface->path,
+       ret = supplicant_dbus_method_call(interface->path,
                        SUPPLICANT_INTERFACE ".Interface", "Scan",
                        interface_scan_params, interface_scan_result, data);
+
+       if (ret < 0)
+               dbus_free(data);
+
+       return ret;
+}
+
+static int parse_supplicant_error(DBusMessageIter *iter)
+{
+       int err = -ECANCELED;
+       char *key;
+
+       while (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_STRING) {
+               dbus_message_iter_get_basic(iter, &key);
+               if (strncmp(key, "psk", 4) == 0 ||
+                       strncmp(key, "wep_key", 7) == 0) {
+                       err = -ENOKEY;
+                       break;
+               }
+               dbus_message_iter_next(iter);
+       }
+
+       return err;
 }
 
 static void interface_select_network_result(const char *error,
@@ -2419,8 +2680,10 @@ static void interface_select_network_result(const char *error,
        SUPPLICANT_DBG("");
 
        err = 0;
-       if (error != NULL)
-               err = -EIO;
+       if (error != NULL) {
+               SUPPLICANT_DBG("SelectNetwork error %s", error);
+               err = parse_supplicant_error(iter);
+       }
 
        if (data->callback != NULL)
                data->callback(err, data->interface, data->user_data);
@@ -2445,6 +2708,7 @@ static void interface_add_network_result(const char *error,
        struct interface_connect_data *data = user_data;
        GSupplicantInterface *interface = data->interface;
        const char *path;
+       int err;
 
        if (error != NULL)
                goto error;
@@ -2466,6 +2730,11 @@ static void interface_add_network_result(const char *error,
        return;
 
 error:
+       SUPPLICANT_DBG("AddNetwork error %s", error);
+       err = parse_supplicant_error(iter);
+       if (data->callback != NULL)
+               data->callback(err, data->interface, data->user_data);
+
        g_free(interface->network_path);
        interface->network_path = NULL;
        g_free(data->ssid);
@@ -2528,13 +2797,36 @@ static void add_network_security_wep(DBusMessageIter *dict,
        }
 }
 
+static dbus_bool_t is_psk_raw_key(const char *psk)
+{
+       int i;
+
+       /* A raw key is always 64 bytes length... */
+       if (strlen(psk) != 64)
+               return FALSE;
+
+       /* ... and its content is in hex representation */
+       for (i = 0; i < 64; i++)
+               if (!isxdigit((unsigned char) psk[i]))
+                       return FALSE;
+
+       return TRUE;
+}
+
 static void add_network_security_psk(DBusMessageIter *dict,
                                        GSupplicantSSID *ssid)
 {
-       if (ssid->passphrase && strlen(ssid->passphrase) > 0)
+       if (ssid->passphrase && strlen(ssid->passphrase) > 0) {
+
+               if (is_psk_raw_key(ssid->passphrase) == TRUE)
+                       supplicant_dbus_property_append_fixed_array(dict,
+                                                       "psk", DBUS_TYPE_BYTE,
+                                                       &ssid->passphrase, 64);
+               else
                        supplicant_dbus_dict_append_basic(dict, "psk",
-                                               DBUS_TYPE_STRING,
+                                                       DBUS_TYPE_STRING,
                                                        &ssid->passphrase);
+       }
 }
 
 static void add_network_security_tls(DBusMessageIter *dict,
@@ -2583,7 +2875,7 @@ static void add_network_security_peap(DBusMessageIter *dict,
         *              The 2nd phase authentication method
         *              The 2nd phase passphrase
         *
-        * The Client certificate is optional although strongly required
+        * The Client certificate is optional although strongly recommended
         * When setting it, we need in addition
         *              The Client private key file
         *              The Client private key file password
@@ -2591,9 +2883,6 @@ static void add_network_security_peap(DBusMessageIter *dict,
        if (ssid->passphrase == NULL)
                return;
 
-       if (ssid->ca_cert_path == NULL)
-               return;
-
        if (ssid->phase2_auth == NULL)
                return;
 
@@ -2628,7 +2917,8 @@ static void add_network_security_peap(DBusMessageIter *dict,
                                                DBUS_TYPE_STRING,
                                                &ssid->passphrase);
 
-       supplicant_dbus_dict_append_basic(dict, "ca_cert",
+       if (ssid->ca_cert_path)
+               supplicant_dbus_dict_append_basic(dict, "ca_cert",
                                                DBUS_TYPE_STRING,
                                                &ssid->ca_cert_path);
 
@@ -2726,6 +3016,39 @@ static void add_network_security_ciphers(DBusMessageIter *dict,
        g_free(group);
 }
 
+static void add_network_security_proto(DBusMessageIter *dict,
+                                               GSupplicantSSID *ssid)
+{
+       unsigned int protocol, i;
+       char *proto;
+       char *protos[3];
+
+       protocol = ssid->protocol;
+
+       if (protocol == 0)
+               return;
+
+       i = 0;
+
+       if (protocol & G_SUPPLICANT_PROTO_RSN)
+               protos[i++] = "RSN";
+
+       if (protocol & G_SUPPLICANT_PROTO_WPA)
+               protos[i++] = "WPA";
+
+       protos[i] = NULL;
+
+       proto = g_strjoinv(" ", protos);
+
+       SUPPLICANT_DBG("proto %s", proto);
+
+       supplicant_dbus_dict_append_basic(dict, "proto",
+                                               DBUS_TYPE_STRING,
+                                               &proto);
+
+       g_free(proto);
+}
+
 static void add_network_security(DBusMessageIter *dict, GSupplicantSSID *ssid)
 {
        char *key_mgmt;
@@ -2736,19 +3059,22 @@ static void add_network_security(DBusMessageIter *dict, GSupplicantSSID *ssid)
        case G_SUPPLICANT_SECURITY_WEP:
                key_mgmt = "NONE";
                add_network_security_wep(dict, ssid);
+               add_network_security_ciphers(dict, ssid);
                break;
        case G_SUPPLICANT_SECURITY_PSK:
                key_mgmt = "WPA-PSK";
                add_network_security_psk(dict, ssid);
+               add_network_security_ciphers(dict, ssid);
+               add_network_security_proto(dict, ssid);
                break;
        case G_SUPPLICANT_SECURITY_IEEE8021X:
                key_mgmt = "WPA-EAP";
                add_network_security_eap(dict, ssid);
+               add_network_security_ciphers(dict, ssid);
+               add_network_security_proto(dict, ssid);
                break;
        }
 
-       add_network_security_ciphers(dict, ssid);
-
        supplicant_dbus_dict_append_basic(dict, "key_mgmt",
                                DBUS_TYPE_STRING, &key_mgmt);
 }