| /* |
| * New WiFi - communicate with wpa_supplicant using the "new D-Bus api" |
| * |
| * This file initially created by Google, Inc. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * 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 St, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <errno.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include <net/ethernet.h> |
| |
| #include <gdbus.h> |
| |
| #define CONNMAN_API_SUBJECT_TO_CHANGE |
| #include <connman/assert.h> |
| #include <connman/plugin.h> |
| #include <connman/dbus.h> |
| #include <connman/device.h> |
| #include <connman/inet.h> |
| #include <connman/log.h> |
| #include <connman/notifier.h> |
| #include <connman/option.h> |
| #include <connman/profile.h> |
| #include <connman/service.h> |
| #include <connman/blob.h> |
| #include <connman/wifi.h> |
| |
| #ifdef ENABLE_NSS |
| #include "nss.h" |
| #endif |
| |
| #define _DBG_WIFI(fmt, arg...) DBG(DBG_WIFI, fmt, ## arg) |
| |
| #define DBUS_TIMEOUT 5000 /* timeout waiting for a reply */ |
| |
| /* |
| * The time(secs) for supplicant to complete a scan; this |
| * is used to calculate the BSS expiration time in terms |
| * of the long scan interval (kludge, need a better way). |
| */ |
| #define SCAN_COMPLETION_BOUND 10 |
| |
| /* |
| * Define scan interval while connected separately from |
| * "idle" scan interval, in seconds. |
| */ |
| #define WIFI_BACKGROUND_SCAN_INTERVAL 3601 |
| |
| #define IEEE80211_ELEMID_ERP 42 |
| #define IEEE80211_ELEMID_HTCAP 45 |
| #define IEEE80211_ELEMID_HTINFO 61 |
| |
| #define SUPPLICANT_NAME "fi.w1.wpa_supplicant1" |
| #define SUPPLICANT_INTF "fi.w1.wpa_supplicant1" |
| #define SUPPLICANT_PATH "/fi/w1/wpa_supplicant1" |
| |
| /* convenient shorthands */ |
| #define SUPPLICANT_INTERFACE_INTF SUPPLICANT_INTF ".Interface" |
| #define SUPPLICANT_NETWORK_INTF SUPPLICANT_INTF ".Network" |
| #define SUPPLICANT_BSS_INTF SUPPLICANT_INTF ".BSS" |
| |
| #define DBUS_INTF "org.freedesktop.DBus" |
| |
| /* Taken from "WPA Supplicant - Common definitions" */ |
| enum supplicant_state { |
| /** |
| * WPA_DISCONNECTED - Disconnected state |
| * |
| * This state indicates that client is not associated, but is likely to |
| * start looking for an access point. This state is entered when a |
| * connection is lost. |
| */ |
| WPA_DISCONNECTED, |
| |
| /** |
| * WPA_INACTIVE - Inactive state (wpa_supplicant disabled) |
| * |
| * This state is entered if there are no enabled networks in the |
| * configuration. wpa_supplicant is not trying to associate with a new |
| * network and external interaction (e.g., ctrl_iface call to add or |
| * enable a network) is needed to start association. |
| */ |
| WPA_INACTIVE, |
| |
| /** |
| * WPA_SCANNING - Scanning for a network |
| * |
| * This state is entered when wpa_supplicant starts scanning for a |
| * network. |
| */ |
| WPA_SCANNING, |
| |
| /** |
| * WPA_AUTHENTICATING - Trying to authenticate with a BSS/SSID |
| * |
| * This state is entered when wpa_supplicant has found a suitable BSS |
| * to authenticate with and the driver is configured to try to |
| * authenticate with this BSS. This state is used only with drivers |
| * that use wpa_supplicant as the SME. |
| */ |
| WPA_AUTHENTICATING, |
| |
| /** |
| * WPA_ASSOCIATING - Trying to associate with a BSS/SSID |
| * |
| * This state is entered when wpa_supplicant has found a suitable BSS |
| * to associate with and the driver is configured to try to associate |
| * with this BSS in ap_scan=1 mode. When using ap_scan=2 mode, this |
| * state is entered when the driver is configured to try to associate |
| * with a network using the configured SSID and security policy. |
| */ |
| WPA_ASSOCIATING, |
| |
| /** |
| * WPA_ASSOCIATED - Association completed |
| * |
| * This state is entered when the driver reports that association has |
| * been successfully completed with an AP. If IEEE 802.1X is used |
| * (with or without WPA/WPA2), wpa_supplicant remains in this state |
| * until the IEEE 802.1X/EAPOL authentication has been completed. |
| */ |
| WPA_ASSOCIATED, |
| |
| /** |
| * WPA_4WAY_HANDSHAKE - WPA 4-Way Key Handshake in progress |
| * |
| * This state is entered when WPA/WPA2 4-Way Handshake is started. In |
| * case of WPA-PSK, this happens when receiving the first EAPOL-Key |
| * frame after association. In case of WPA-EAP, this state is entered |
| * when the IEEE 802.1X/EAPOL authentication has been completed. |
| */ |
| WPA_4WAY_HANDSHAKE, |
| |
| /** |
| * WPA_GROUP_HANDSHAKE - WPA Group Key Handshake in progress |
| * |
| * This state is entered when 4-Way Key Handshake has been completed |
| * (i.e., when the supplicant sends out message 4/4) and when Group |
| * Key rekeying is started by the AP (i.e., when supplicant receives |
| * message 1/2). |
| */ |
| WPA_GROUP_HANDSHAKE, |
| |
| /** |
| * WPA_COMPLETED - All authentication completed |
| * |
| * This state is entered when the full authentication process is |
| * completed. In case of WPA2, this happens when the 4-Way Handshake is |
| * successfully completed. With WPA, this state is entered after the |
| * Group Key Handshake; with IEEE 802.1X (non-WPA) connection is |
| * completed after dynamic keys are received (or if not used, after |
| * the EAP authentication has been completed). With static WEP keys and |
| * plaintext connections, this state is entered when an association |
| * has been completed. |
| * |
| * This state indicates that the supplicant has completed its |
| * processing for the association phase and that data connection is |
| * fully configured. |
| */ |
| WPA_COMPLETED, |
| |
| /** |
| * WPA_INVALID - Invalid state (parsing error) |
| * |
| * This state is returned if the string input is invalid. It is not |
| * an official wpa_supplicant state. |
| */ |
| WPA_INVALID, |
| }; |
| |
| static const char *supplicant_state_names[] = { |
| [WPA_DISCONNECTED] = "DISCONNECTED", |
| [WPA_INACTIVE] = "INACTIVE", |
| [WPA_SCANNING] = "SCANNING", |
| [WPA_AUTHENTICATING] = "AUTHENTICATING", |
| [WPA_ASSOCIATING] = "ASSOCIATING", |
| [WPA_ASSOCIATED] = "ASSOCIATED", |
| [WPA_4WAY_HANDSHAKE] = "4WAY_HANDSHAKE", |
| [WPA_GROUP_HANDSHAKE] = "GROUP_HANDSHAKE", |
| [WPA_COMPLETED] = "COMPLETED", |
| [WPA_INVALID] = "INVALID", |
| }; |
| |
| #define SUPPLICANT_MAX_SSID_PER_SCAN 4 |
| |
| /* |
| * Supplicant scan result request parameters. |
| */ |
| struct supplicant_scan_request { |
| const char *scan_type; |
| GSList *ssids; |
| }; |
| |
| #define MAX_SSID_LEN 32 /* 802.11 SSID length (octets) */ |
| |
| /* |
| * Supplicant network modes |
| */ |
| #define SUPPLICANT_NETWORK_MODE_MANAGED 0 |
| #define SUPPLICANT_NETWORK_MODE_ADHOC 1 |
| #define SUPPLICANT_NETWORK_MODE_HOSTAP 2 |
| #define SUPPLICANT_NETWORK_MODE_DEFAULT SUPPLICANT_NETWORK_MODE_MANAGED |
| |
| /* |
| * Per-bss state collected from supplicant scan results. |
| */ |
| struct supplicant_result { |
| char path[2*ETH_ALEN+2*MAX_SSID_LEN+1]; |
| char name[MAX_SSID_LEN+1]; |
| unsigned char addr[ETH_ALEN]; |
| unsigned char ssid[MAX_SSID_LEN]; |
| unsigned int ssid_len; |
| gboolean privacy; |
| gboolean has_wpa_psk; |
| gboolean has_rsn_psk; |
| gboolean has_802_1x; |
| gboolean has_wps; |
| dbus_int32_t frequency; |
| dbus_int32_t signal; |
| dbus_int32_t maxrate; |
| |
| unsigned char channel; |
| unsigned short strength; |
| enum connman_network_phymode phymode; |
| const char *mode; |
| const char *security; |
| }; |
| |
| /* |
| * Per-interface "task state". |
| */ |
| struct supplicant_task { |
| int ifindex; /* ifnet index */ |
| char *ifname; /* ifnet name */ |
| char *ifpath; /* wpa_supplicant path for interface */ |
| struct connman_device *device; /* device handle */ |
| struct connman_network *current_bss;/* current associated BSS (ref) */ |
| struct connman_network *network;/* current connection request (ref) */ |
| /* pending connection request (ref) */ |
| struct connman_network *pending_network; |
| enum supplicant_state state; /* wpa_supplicant state from status */ |
| gboolean scanning; /* TRUE if scanning in progress */ |
| gboolean flush_pending; /* TRUE if flush BSS needed */ |
| time_t resume_time; /* time of last system resume */ |
| int scangen; /* scan generation number */ |
| DBusPendingCall *scan_call; /* async dbus call to request scan */ |
| GHashTable *netblocks; /* supplicant network block handles */ |
| GHashTable *bss; /* supplicant BSS handles */ |
| int (*next_method)(struct supplicant_task *); |
| char *country; /* current installed country code */ |
| }; |
| |
| static GSList *task_list = NULL; |
| |
| static DBusConnection *connection; |
| |
| static int interface_set_ap_scan(struct supplicant_task *); |
| static int interface_set_fast_reauth(struct supplicant_task *); |
| static int interface_set_country(struct supplicant_task *, const char *); |
| static int interface_set_bss_expire_age(struct supplicant_task *, uint32_t); |
| static char *network_add(struct supplicant_task *, |
| struct connman_network *network); |
| static int network_remove(struct supplicant_task *, const void *handle); |
| static int interface_remove_all(struct supplicant_task *); |
| static int interface_flush_bss(struct supplicant_task *, int age); |
| static int task_connect(struct supplicant_task *task, |
| struct connman_network *network); |
| |
| static void free_task(struct supplicant_task *task) |
| { |
| _DBG_WIFI("task %p", task); |
| |
| connman_device_unref(task->device); |
| |
| if (task->current_bss != NULL) |
| connman_network_unref(task->current_bss); |
| if (task->network != NULL) |
| connman_network_unref(task->network); |
| if (task->pending_network != NULL) |
| connman_network_unref(task->pending_network); |
| g_hash_table_destroy(task->netblocks); |
| g_hash_table_destroy(task->bss); |
| g_free(task->ifname); |
| g_free(task->ifpath); |
| g_free(task->country); |
| g_free(task); |
| } |
| |
| static struct supplicant_task *find_task_by_index(int index) |
| { |
| GSList *list; |
| |
| for (list = task_list; list; list = list->next) { |
| struct supplicant_task *task = list->data; |
| |
| if (task->ifindex == index) |
| return task; |
| } |
| |
| return NULL; |
| } |
| |
| static struct supplicant_task *find_task_by_path(const char *path) |
| { |
| GSList *list; |
| |
| for (list = task_list; list; list = list->next) { |
| struct supplicant_task *task = list->data; |
| |
| /* NB: prefix handles BSS paths */ |
| if (g_str_has_prefix(path, task->ifpath) == TRUE) |
| return task; |
| } |
| |
| return NULL; |
| } |
| |
| static void task_error(struct supplicant_task *task, const char *fmt, ...) |
| { |
| char buf[1024]; |
| va_list ap; |
| |
| va_start(ap, fmt); |
| snprintf(buf, sizeof(buf), "%s: %s", task->ifname, fmt); |
| connman_verror(buf, ap); |
| va_end(ap); |
| } |
| |
| static void task_info(struct supplicant_task *task, const char *fmt, ...) |
| { |
| char buf[1024]; |
| va_list ap; |
| |
| va_start(ap, fmt); |
| snprintf(buf, sizeof(buf), "%s: %s", task->ifname, fmt); |
| connman_vinfo(buf, ap); |
| va_end(ap); |
| } |
| |
| /* |
| * D-Bus helper functions. |
| */ |
| |
| static const char *__dbus_obj_type_name(int type) |
| { |
| static char hex[16]; |
| |
| switch (type) { |
| case DBUS_TYPE_BOOLEAN: return "boolean"; |
| case DBUS_TYPE_INT16: return "int16"; |
| case DBUS_TYPE_UINT16: return "uint16"; |
| case DBUS_TYPE_UINT32: return "uint32"; |
| case DBUS_TYPE_INT32: return "int32"; |
| case DBUS_TYPE_STRING: return "string"; |
| case DBUS_TYPE_DICT_ENTRY: return "dict"; |
| case DBUS_TYPE_ARRAY: return "array"; |
| case DBUS_TYPE_OBJECT_PATH: return "obj path"; |
| } |
| snprintf(hex, sizeof(hex), "type 0x%x", type); |
| return hex; |
| } |
| static gboolean __check_iter_arg_type(struct supplicant_task *task, |
| DBusMessageIter *iter, int type, const char *func) |
| { |
| if (dbus_message_iter_get_arg_type(iter) != type) { |
| task_error(task, "%s: expected %s got %s", func, |
| __dbus_obj_type_name(type), |
| __dbus_obj_type_name(dbus_message_iter_get_arg_type(iter))); |
| return FALSE; |
| } |
| return TRUE; |
| } |
| #define DBUS_EXPECT_ITER_ARG(iter, type, bad) do { \ |
| if (__check_iter_arg_type(task, iter, type, __func__) == FALSE) { \ |
| bad; \ |
| } \ |
| } while (0) |
| |
| /* |
| * Send a method call and arrange for a reply callback. |
| */ |
| static int send_call2(struct supplicant_task *task, DBusMessage *message, |
| void (*reply_notify)(DBusPendingCall *call, void *user_data), |
| DBusPendingCall **pcall, const char *func, const char *calldesc) |
| { |
| |
| if (dbus_connection_send_with_reply(connection, message, pcall, |
| DBUS_TIMEOUT) == FALSE) { |
| task_error(task, "%s: failed to %s", func, calldesc); |
| dbus_message_unref(message); |
| return -EIO; |
| } |
| if (*pcall == NULL) { |
| task_error(task, "%s: D-Bus connection not available", func); |
| dbus_message_unref(message); |
| return -EIO; |
| } |
| dbus_pending_call_set_notify(*pcall, reply_notify, task, NULL); |
| dbus_message_unref(message); |
| |
| return -EINPROGRESS; |
| } |
| |
| /* |
| * Send a method call and arrange for a reply callback. |
| */ |
| static int send_call(struct supplicant_task *task, DBusMessage *message, |
| void (*reply_notify)(DBusPendingCall *call, void *user_data), |
| const char *func, const char *calldesc) |
| { |
| DBusPendingCall *call; |
| return send_call2(task, message, reply_notify, &call, func, calldesc); |
| } |
| |
| /* |
| * Send a method call and block waiting for reply. |
| */ |
| static DBusMessage *send_call_and_block(struct supplicant_task *task, |
| DBusMessage *message, const char *func, const char *request) |
| { |
| DBusMessage *reply; |
| DBusError error; |
| |
| dbus_error_init(&error); |
| reply = dbus_connection_send_with_reply_and_block(connection, |
| message, -1, &error); |
| if (reply == NULL) { |
| if (dbus_error_is_set(&error) == TRUE) { |
| task_error(task, "%s: %s", func, error.message); |
| dbus_error_free(&error); |
| } else |
| task_error(task, "%s: %s failed", func, request); |
| } |
| dbus_message_unref(message); |
| |
| return reply; |
| } |
| |
| static const char *get_objpath(struct supplicant_task *task, DBusMessage *msg, |
| const char *func, const char *msgdesc) |
| { |
| DBusError error; |
| const char *path; |
| |
| dbus_error_init(&error); |
| if (dbus_message_get_args(msg, &error, DBUS_TYPE_OBJECT_PATH, &path, |
| DBUS_TYPE_INVALID) == FALSE) { |
| if (dbus_error_is_set(&error) == TRUE) { |
| task_error(task, "%s: %s", func, error.message); |
| dbus_error_free(&error); |
| } else |
| task_error(task, "%s: wrong arguments for %s", func, |
| msgdesc); |
| path = NULL; /* NULL indicates failure */ |
| } |
| dbus_message_unref(msg); |
| |
| return path; |
| } |
| |
| /* |
| * Handle GetInterface and CreateInterface method call replies. |
| */ |
| static void interface_reply(DBusPendingCall *call, void *user_data) |
| { |
| struct supplicant_task *task = user_data; |
| DBusMessage *reply; |
| const char *path; |
| |
| _DBG_WIFI("task %p", task); |
| |
| reply = dbus_pending_call_steal_reply(call); |
| dbus_pending_call_unref(call); |
| if (reply == NULL) |
| goto failed; |
| |
| if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) { |
| if (task->next_method != NULL) { |
| int err = task->next_method(task); |
| if (err < 0 && err != -EINPROGRESS) { |
| dbus_message_unref(reply); |
| goto failed; |
| } |
| } |
| dbus_message_unref(reply); |
| return; |
| } |
| |
| path = get_objpath(task, reply, __func__, "Get/CreateInterface"); |
| if (path == NULL) |
| goto failed; |
| |
| _DBG_WIFI("path %s", path); |
| task->ifpath = g_strdup(path); |
| task_list = g_slist_append(task_list, task); |
| |
| interface_remove_all(task); |
| interface_set_ap_scan(task); |
| interface_set_fast_reauth(task); |
| interface_set_country(task, connman_profile_get_country()); |
| interface_set_bss_expire_age(task, |
| connman_device_bgscan_get_long(task->device)+SCAN_COMPLETION_BOUND); |
| interface_flush_bss(task, 0); |
| connman_device_set_powered(task->device, TRUE); |
| return; |
| failed: |
| /* NB: critical error, be sure something is logged */ |
| task_error(task, "%s: Get/CreateInterface failed"); |
| free_task(task); |
| } |
| |
| /* |
| * Get a handle on the interface. |
| */ |
| static int interface_get(struct supplicant_task *task) |
| { |
| DBusMessage *message; |
| |
| _DBG_WIFI("task %p", task); |
| |
| message = dbus_message_new_method_call(SUPPLICANT_NAME, SUPPLICANT_PATH, |
| SUPPLICANT_INTF, "GetInterface"); |
| if (message == NULL) |
| return -ENOMEM; |
| |
| dbus_message_set_auto_start(message, FALSE); |
| dbus_message_append_args(message, DBUS_TYPE_STRING, &task->ifname, |
| DBUS_TYPE_INVALID); |
| |
| task->next_method = NULL; |
| |
| return send_call(task, message, interface_reply, __func__, |
| "get interface"); |
| } |
| |
| /* |
| * Create the interface using the specified driver and config |
| * file. The latter is used to convey parameters that we cannot |
| * otherwise specify through the dbus api. If the interface |
| * cannot be created, fallback to doing a get. |
| */ |
| static int interface_create(struct supplicant_task *task) |
| { |
| const char *driver = connman_option_get_string("wifi"); |
| const char *conffile = SCRIPTDIR "/wpa_supplicant.conf"; |
| DBusMessage *message; |
| DBusMessageIter array, dict; |
| |
| _DBG_WIFI("task %p", task); |
| |
| message = dbus_message_new_method_call(SUPPLICANT_NAME, SUPPLICANT_PATH, |
| SUPPLICANT_INTF, "CreateInterface"); |
| if (message == NULL) |
| return -ENOMEM; |
| |
| dbus_message_set_auto_start(message, FALSE); |
| dbus_message_iter_init_append(message, &array); |
| dbus_message_iter_open_container(&array, DBUS_TYPE_ARRAY, |
| DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING |
| DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING |
| DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); |
| |
| connman_dbus_dict_append_variant(&dict, "Ifname", |
| DBUS_TYPE_STRING, &task->ifname); |
| connman_dbus_dict_append_variant(&dict, "Driver", |
| DBUS_TYPE_STRING, &driver); |
| /* |
| * TODO(sleffler) set global properties directly once wpa_supplicant |
| * supports this. |
| */ |
| connman_dbus_dict_append_variant(&dict, "ConfigFile", |
| DBUS_TYPE_STRING, &conffile); |
| dbus_message_iter_close_container(&array, &dict); |
| |
| /* NB: if we get an error response, try to get the interface */ |
| task->next_method = interface_get; |
| |
| return send_call(task, message, interface_reply, __func__, |
| "create interface"); |
| } |
| |
| static void interface_remove_reply(DBusPendingCall *call, void *user_data) |
| { |
| struct supplicant_task *task = user_data; |
| DBusMessage *reply; |
| |
| _DBG_WIFI("task %p", task); |
| |
| reply = dbus_pending_call_steal_reply(call); |
| dbus_pending_call_unref(call); |
| connman_device_set_powered(task->device, FALSE); |
| connman_inet_ifdown(task->ifindex); |
| free_task(task); |
| dbus_message_unref(reply); |
| } |
| |
| /* |
| * Remove an interface in the supplicant. |
| */ |
| static int interface_remove(struct supplicant_task *task) |
| { |
| DBusMessage *message; |
| |
| _DBG_WIFI("task %p", task); |
| |
| message = dbus_message_new_method_call(SUPPLICANT_NAME, SUPPLICANT_PATH, |
| SUPPLICANT_INTF, "RemoveInterface"); |
| if (message == NULL) |
| return -ENOMEM; |
| |
| dbus_message_set_auto_start(message, FALSE); |
| dbus_message_append_args(message, DBUS_TYPE_OBJECT_PATH, &task->ifpath, |
| DBUS_TYPE_INVALID); |
| |
| return send_call(task, message, interface_remove_reply, __func__, |
| "remove interface"); |
| } |
| |
| /* |
| * Send the supplicant a .Interface request over the d-bus. |
| * The handle is supplied as the object path for the request |
| * if it is not NULL. |
| */ |
| static DBusMessage *interface_send_request(struct supplicant_task *task, |
| const char *request, const void *handle) |
| { |
| DBusMessage *message; |
| |
| message = dbus_message_new_method_call(SUPPLICANT_NAME, task->ifpath, |
| SUPPLICANT_INTERFACE_INTF, request); |
| if (message == NULL) { |
| task_error(task, "%s: cannot allocate dbus msg", request); |
| return NULL; |
| } |
| dbus_message_set_auto_start(message, FALSE); |
| if (handle != NULL) { |
| _DBG_WIFI("Interface.%s handle %s", request, |
| (const char *) handle); |
| dbus_message_append_args(message, |
| DBUS_TYPE_OBJECT_PATH, &handle, DBUS_TYPE_INVALID); |
| } else { |
| /* Disconnect supplies no arg/handle */ |
| _DBG_WIFI("Interface.%s", request); |
| } |
| return send_call_and_block(task, message, __func__, request); |
| } |
| |
| static int interface_set_property(struct supplicant_task *task, |
| const char *prop, int type, void *val) |
| { |
| DBusMessage *message, *reply; |
| const char *interface = SUPPLICANT_INTERFACE_INTF; |
| DBusMessageIter iter; |
| |
| _DBG_WIFI("task %p path %s prop %s", task, task->ifpath, prop); |
| |
| message = dbus_message_new_method_call(SUPPLICANT_NAME, task->ifpath, |
| DBUS_INTF ".Properties", "Set"); |
| if (message == NULL) |
| return -ENOMEM; |
| |
| dbus_message_set_auto_start(message, FALSE); |
| dbus_message_iter_init_append(message, &iter); |
| dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &interface); |
| connman_dbus_property_append_variant(&iter, prop, type, val); |
| |
| reply = send_call_and_block(task, message, __func__, prop); |
| if (reply == NULL) |
| return -EIO; |
| dbus_message_unref(reply); |
| return 0; |
| } |
| |
| static int interface_set_ap_scan(struct supplicant_task *task) |
| { |
| int ap_scan = 1; |
| return interface_set_property(task, "ApScan", |
| DBUS_TYPE_UINT32, &ap_scan); |
| } |
| |
| static int interface_set_fast_reauth(struct supplicant_task *task) |
| { |
| dbus_bool_t fast_reauth = FALSE; |
| return interface_set_property(task, "FastReauth", |
| DBUS_TYPE_BOOLEAN, &fast_reauth); |
| } |
| |
| static int interface_set_country(struct supplicant_task *task, |
| const char *country) |
| { |
| if (g_strcmp0(country, task->country) == 0) |
| return 0; |
| g_free(task->country); |
| task->country = g_strdup(country); |
| return interface_set_property(task, "Country", |
| DBUS_TYPE_STRING, &task->country); |
| } |
| |
| static int interface_set_bss_expire_age(struct supplicant_task *task, |
| uint32_t expire_age) |
| { |
| return interface_set_property(task, "BSSExpireAge", |
| DBUS_TYPE_UINT32, &expire_age); |
| } |
| |
| static void add_blob(DBusMessageIter *dict, void *arg) |
| { |
| const struct blob *blob = arg; |
| |
| dbus_message_iter_append_fixed_array(dict, DBUS_TYPE_BYTE, |
| blob->data, blob->len); |
| } |
| |
| static void append_byte_array(DBusMessageIter *dict, const char *key, |
| const void *data, int len) |
| { |
| /* NB: &data is required by connman_dbus_dict_append_variant_array */ |
| struct blob blob = { .data = (void *)&data, .len = len }; |
| connman_dbus_dict_append_variant_array(dict, key, DBUS_TYPE_BYTE, |
| add_blob, &blob); |
| } |
| |
| static void append_bgscan(DBusMessageIter *dict, |
| struct connman_network *network) |
| { |
| struct connman_device *device = connman_network_get_device(network); |
| char bgscan_buf[80]; |
| const char *bgscan = bgscan_buf; |
| const char *method; |
| int long_interval = WIFI_BACKGROUND_SCAN_INTERVAL; |
| |
| method = connman_device_bgscan_get_method(device); |
| |
| if (method == NULL) { |
| /* |
| * If multiple APs are detected for this SSID, configure |
| * the "simple" method by default. Otherwise, disable |
| * background scanning completely. |
| */ |
| if (connman_network_get_peer_count(network) > 0) |
| method = "simple"; |
| else { |
| connman_info("%s: Disabling background scan since " |
| "no peers to this network were found.", |
| __func__); |
| return; |
| } |
| } else { |
| /* |
| * If the background scan method was explicitly specified, |
| * honor the configured background scan interval. |
| */ |
| long_interval = connman_device_bgscan_get_long(device); |
| } |
| |
| connman_info("%s: Using %s method for background scan with " |
| "long interval %d", __func__, method, |
| long_interval); |
| if (g_strcmp0(method, "simple") == 0 || |
| g_strcmp0(method, "learn") == 0) { |
| |
| snprintf(bgscan_buf, sizeof(bgscan_buf), "%s:%d:%d:%d", |
| method, |
| connman_device_bgscan_get_short(device), |
| connman_device_bgscan_get_signal_threshold(device), |
| long_interval); |
| |
| connman_dbus_dict_append_variant(dict, "bgscan", |
| DBUS_TYPE_STRING, &bgscan); |
| } |
| } |
| |
| static int check_8021x(struct connman_network *network) |
| { |
| const char *identity = |
| connman_network_get_string(network, CONNMAN_WIFI_EAP_IDENTITY); |
| |
| if (identity == NULL || strlen(identity) == 0) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| static const char *append_from_network(DBusMessageIter *dict, |
| struct connman_network *network, |
| const char *network_key, |
| const char *supplicant_key) |
| { |
| const char *value; |
| |
| value = connman_network_get_string(network, network_key); |
| if (value != NULL) |
| connman_dbus_dict_append_variant(dict, supplicant_key, |
| DBUS_TYPE_STRING, &value); |
| |
| return value; |
| } |
| |
| |
| static void append_8021x(DBusMessageIter *dict, |
| struct connman_network *network) |
| { |
| const char *engine_id = "pkcs11"; |
| dbus_int32_t engine = 1; |
| gboolean use_pkcs11 = FALSE; |
| const char *value; |
| #ifdef SSL_ROOTS |
| const char *ca_path = SSL_ROOTS; |
| gboolean use_system_cas; |
| |
| use_system_cas = connman_network_get_uint8(network, |
| CONNMAN_WIFI_EAP_USESYSTEMCAS); |
| |
| if (use_system_cas == TRUE) |
| connman_dbus_dict_append_variant(dict, "ca_path", |
| DBUS_TYPE_STRING, &ca_path); |
| else |
| #endif |
| { |
| if (connman_network_get_string(network, |
| CONNMAN_WIFI_EAP_CACERT) == NULL && |
| connman_network_get_string(network, |
| CONNMAN_WIFI_EAP_CACERTID) == NULL) |
| connman_warn("Network \"%s\":" |
| " No certificate authorities are configured." |
| " Server certificates will be accepted" |
| " unconditionally.", |
| connman_network_get_string(network, "Name")); |
| } |
| if (append_from_network(dict, network, CONNMAN_WIFI_EAP_KEY_MGMT, |
| "key_mgmt") == NULL) { |
| /* NB: default to WPA-EAP if not specified */ |
| const char *key_mgmt = "WPA-EAP"; |
| connman_dbus_dict_append_variant(dict, "key_mgmt", |
| DBUS_TYPE_STRING, &key_mgmt); |
| } |
| append_from_network(dict, network, CONNMAN_WIFI_EAP_IDENTITY, |
| "identity"); |
| append_from_network(dict, network, CONNMAN_WIFI_EAP_EAP, "eap"); |
| append_from_network(dict, network, CONNMAN_WIFI_EAP_INNEREAP, "phase2"); |
| append_from_network(dict, network, CONNMAN_WIFI_EAP_ANONYMOUSIDENTITY, |
| "anonymous_identity"); |
| append_from_network(dict, network, CONNMAN_WIFI_EAP_CLIENTCERT, |
| "client_cert"); |
| append_from_network(dict, network, CONNMAN_WIFI_EAP_PRIVATEKEY, |
| "private_key"); |
| append_from_network(dict, network, CONNMAN_WIFI_EAP_PRIVATEKEYPASSWORD, |
| "private_key_passwd"); |
| append_from_network(dict, network, CONNMAN_WIFI_EAP_CACERT, "ca_cert"); |
| append_from_network(dict, network, CONNMAN_WIFI_EAP_PASSWORD, |
| "password"); |
| value = append_from_network(dict, network, CONNMAN_WIFI_EAP_CERTID, |
| "cert_id"); |
| if (value != NULL) |
| use_pkcs11 = TRUE; |
| value = append_from_network(dict, network, CONNMAN_WIFI_EAP_KEYID, |
| "key_id"); |
| if (value != NULL) |
| use_pkcs11 = TRUE; |
| value = append_from_network(dict, network, CONNMAN_WIFI_EAP_CACERTID, |
| "ca_cert_id"); |
| if (value != NULL) |
| use_pkcs11 = TRUE; |
| |
| if (use_pkcs11) { |
| append_from_network(dict, network, CONNMAN_WIFI_EAP_PIN, "pin"); |
| connman_dbus_dict_append_variant(dict, "engine", |
| DBUS_TYPE_INT32, &engine); |
| connman_dbus_dict_append_variant(dict, "engine_id", |
| DBUS_TYPE_STRING, &engine_id); |
| } |
| |
| #ifdef ENABLE_NSS |
| value = connman_network_get_string(network, |
| CONNMAN_WIFI_EAP_CACERTNSS); |
| if (value != NULL) { |
| char *filename; |
| const uint8_t *ssid; |
| unsigned int ssid_len; |
| |
| ssid = connman_network_get_blob(network, "WiFi.SSID", |
| &ssid_len); |
| |
| filename = nss_get_der_certfile(value, ssid, ssid_len); |
| if (filename != NULL) { |
| connman_dbus_dict_append_variant(dict, "ca_cert", |
| DBUS_TYPE_STRING, &filename); |
| } |
| g_free(filename); |
| } |
| #endif |
| |
| } |
| |
| static int check_psk(struct connman_network *network) |
| { |
| const char *passphrase = |
| connman_network_get_string(network, "WiFi.Passphrase"); |
| return (passphrase == NULL || strlen(passphrase) == 0 ? -EINVAL : 0); |
| } |
| |
| static void append_psk(DBusMessageIter *dict, |
| struct connman_network *network, const char *proto) |
| { |
| const char *key_mgmt = "WPA-PSK"; |
| const char *passphrase; |
| |
| connman_dbus_dict_append_variant(dict, "key_mgmt", |
| DBUS_TYPE_STRING, &key_mgmt); |
| connman_dbus_dict_append_variant(dict, "proto", |
| DBUS_TYPE_STRING, &proto); |
| |
| passphrase = connman_network_get_string(network, "WiFi.Passphrase"); |
| connman_dbus_dict_append_variant(dict, "psk", |
| DBUS_TYPE_STRING, &passphrase); |
| } |
| |
| static int check_wep(struct connman_network *network) |
| { |
| const char *passphrase = |
| connman_network_get_string(network, "WiFi.Passphrase"); |
| return (passphrase == NULL || strlen(passphrase) == 0 ? -EINVAL : 0); |
| } |
| |
| static void append_wep(DBusMessageIter *dict, |
| struct connman_network *network) |
| { |
| const char *key_mgmt = "NONE"; |
| /* |
| * NB: mac80211-capable devices can auto-select |
| * between open and shared key auth. |
| */ |
| const char *auth_alg = "OPEN SHARED"; |
| char key_name[12]; /* NB: "wep_keyN" */ |
| dbus_uint32_t key_index; |
| unsigned int key_len; |
| const void *key_matter; |
| |
| connman_dbus_dict_append_variant(dict, "auth_alg", |
| DBUS_TYPE_STRING, &auth_alg); |
| |
| connman_dbus_dict_append_variant(dict, "key_mgmt", |
| DBUS_TYPE_STRING, &key_mgmt); |
| |
| key_index = connman_network_get_uint8(network, "WiFi.WEPKeyIndex"); |
| snprintf(key_name, sizeof(key_name), "wep_key%d", key_index); |
| |
| key_matter = connman_network_get_blob(network, "WiFi.WEPKey", &key_len); |
| CONNMAN_ASSERT(key_matter != NULL); |
| append_byte_array(dict, key_name, key_matter, key_len); |
| |
| connman_dbus_dict_append_variant(dict, "wep_tx_keyidx", |
| DBUS_TYPE_UINT32, &key_index); |
| } |
| |
| static void append_mode(DBusMessageIter *dict, struct connman_network *network) |
| { |
| dbus_int32_t mode_val = SUPPLICANT_NETWORK_MODE_DEFAULT; |
| const char *mode_str = connman_network_get_string(network, "WiFi.Mode"); |
| |
| if (g_ascii_strcasecmp(mode_str, "managed") == 0) |
| mode_val = SUPPLICANT_NETWORK_MODE_MANAGED; |
| else if (g_ascii_strcasecmp(mode_str, "adhoc") == 0) { |
| /* |
| * NB: wpa_supplicant does not use scan results for |
| * configuring IBSS so we need to manually select the |
| * frequency. |
| */ |
| dbus_int32_t frequency = |
| connman_network_get_uint16(network, "Frequency"); |
| |
| if (frequency != 0) |
| connman_dbus_dict_append_variant(dict, "frequency", |
| DBUS_TYPE_INT32, |
| &frequency); |
| mode_val = SUPPLICANT_NETWORK_MODE_ADHOC; |
| } else if (g_ascii_strcasecmp(mode_str, "hostap") == 0) |
| mode_val = SUPPLICANT_NETWORK_MODE_HOSTAP; |
| |
| connman_dbus_dict_append_variant(dict, "mode", |
| DBUS_TYPE_INT32, &mode_val); |
| } |
| |
| static gboolean is_dynamic_wep(const char *security, |
| struct connman_network *network) |
| { |
| const char *key_mgmt; |
| |
| if (g_ascii_strcasecmp(security, "wep") != 0) |
| return FALSE; |
| key_mgmt = connman_network_get_string(network, |
| CONNMAN_WIFI_EAP_KEY_MGMT); |
| return (key_mgmt != NULL && |
| g_strcmp0(key_mgmt, CONNMAN_WIFI_EAP_KEY_MGMT_1X) == 0); |
| } |
| |
| /* |
| * Append properties for the specified network to a dbus message. |
| */ |
| static void append_network_properties(struct supplicant_task *task, |
| struct connman_network *network, DBusMessageIter *array) |
| { |
| DBusMessageIter dict; |
| dbus_uint32_t scan_ssid = 1; /* NB: use directed ProbReq */ |
| const void *ssid; |
| unsigned int ssid_len; |
| const char *security; |
| |
| dbus_message_iter_open_container(array, DBUS_TYPE_ARRAY, |
| DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING |
| DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING |
| DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); |
| |
| connman_dbus_dict_append_variant(&dict, "scan_ssid", |
| DBUS_TYPE_UINT32, &scan_ssid); |
| |
| ssid = connman_network_get_blob(network, "WiFi.SSID", &ssid_len); |
| append_byte_array(&dict, "ssid", ssid, ssid_len); |
| /* NB: do not set the bssid; this allows wpa_supplicant to roam */ |
| |
| /* must configure background scanning for each netblock */ |
| append_bgscan(&dict, network); |
| |
| security = connman_network_get_string(network, "WiFi.Security"); |
| if (g_ascii_strcasecmp(security, "802_1x") == 0) { |
| append_8021x(&dict, network); |
| } else if (g_ascii_strcasecmp(security, "psk") == 0) { |
| append_psk(&dict, network, "WPA RSN"); |
| } else if (g_ascii_strcasecmp(security, "wpa") == 0) { |
| append_psk(&dict, network, "WPA"); |
| } else if (g_ascii_strcasecmp(security, "rsn") == 0) { |
| append_psk(&dict, network, "RSN"); |
| } else if (is_dynamic_wep(security, network) == TRUE) { |
| append_8021x(&dict, network); |
| } else if (g_ascii_strcasecmp(security, "wep") == 0) { |
| append_wep(&dict, network); |
| } else { |
| const char *key_mgmt = "NONE"; |
| connman_dbus_dict_append_variant(&dict, "key_mgmt", |
| DBUS_TYPE_STRING, &key_mgmt); |
| } |
| |
| append_mode(&dict, network); |
| |
| dbus_message_iter_close_container(array, &dict); |
| } |
| |
| /* |
| * Add a new netblock with properties for the specified network. |
| */ |
| static char *network_add(struct supplicant_task *task, |
| struct connman_network *network) |
| { |
| const char *request = "AddNetwork"; |
| DBusMessage *message, *reply; |
| DBusMessageIter iter; |
| char *path; |
| |
| message = dbus_message_new_method_call(SUPPLICANT_NAME, task->ifpath, |
| SUPPLICANT_INTERFACE_INTF, request); |
| if (message == NULL) { |
| task_error(task, "%s: cannot allocate dbus msg", request); |
| return NULL; |
| } |
| dbus_message_set_auto_start(message, FALSE); |
| dbus_message_iter_init_append(message, &iter); |
| append_network_properties(task, network, &iter); |
| |
| reply = send_call_and_block(task, message, __func__, request); |
| if (reply == NULL) |
| return NULL; |
| |
| path = (char *) get_objpath(task, reply, __func__, request); |
| if (path != NULL) { |
| path = g_strdup(path); |
| _DBG_WIFI("path %s", path); |
| } |
| return path; |
| } |
| |
| static struct DBusMessage *get_network_set_property_msg( |
| struct supplicant_task *task, const char *func, const void *path, |
| DBusMessageIter *iter) |
| { |
| DBusMessage *message; |
| const char *interface = SUPPLICANT_NETWORK_INTF; |
| |
| message = dbus_message_new_method_call(SUPPLICANT_NAME, path, |
| DBUS_INTF ".Properties", "Set"); |
| if (message == NULL) { |
| task_error(task, "%s: cannot allocate dbus msg", func); |
| return NULL; |
| } |
| |
| dbus_message_set_auto_start(message, FALSE); |
| dbus_message_iter_init_append(message, iter); |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &interface); |
| return message; |
| } |
| |
| /* |
| * Set Network.Properties for the specified network. |
| */ |
| static int network_set_properties(struct supplicant_task *task, |
| struct connman_network *network, const void *handle) |
| { |
| DBusMessage *message, *reply; |
| const char *properties = "Properties"; |
| DBusMessageIter iter; |
| |
| _DBG_WIFI("task %p path %s", task, (const char *)handle); |
| |
| message = get_network_set_property_msg(task, __func__, handle, &iter); |
| if (message == NULL) |
| return -ENOMEM; |
| |
| dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &properties); |
| append_network_properties(task, network, &iter); |
| |
| reply = send_call_and_block(task, message, __func__, properties); |
| if (reply == NULL) |
| return -EIO; |
| dbus_message_unref(reply); |
| return 0; |
| } |
| |
| /* |
| * Set Network.<prop> for the specified network. |
| */ |
| static int network_set_property(struct supplicant_task *task, |
| const void *path, const char *prop, int type, const void *val) |
| { |
| DBusMessage *message, *reply; |
| DBusMessageIter iter; |
| |
| _DBG_WIFI("task %p path %s prop %s", task, (const char *) path, prop); |
| |
| message = get_network_set_property_msg(task, __func__, path, &iter); |
| if (message == NULL) |
| return -ENOMEM; |
| |
| connman_dbus_property_append_variant(&iter, prop, type, val); |
| |
| reply = send_call_and_block(task, message, __func__, prop); |
| if (reply == NULL) |
| return -EIO; |
| dbus_message_unref(reply); |
| return 0; |
| } |
| |
| /* |
| * Tell the supplicant to select a network block as |
| * the only one to use in processing scan results; |
| * this implicitly starts a connection attempt. |
| */ |
| static int network_select(struct supplicant_task *task, |
| const void *handle) |
| { |
| const char *request = "SelectNetwork"; |
| DBusMessage *reply; |
| |
| reply = interface_send_request(task, request, handle); |
| if (reply == NULL) |
| return -EIO; |
| dbus_message_unref(reply); |
| return 0; |
| } |
| |
| /* |
| * Tell the supplicant to remove a network block. |
| */ |
| static int network_remove(struct supplicant_task *task, |
| const void *handle) |
| { |
| const char *request = "RemoveNetwork"; |
| DBusMessage *reply; |
| |
| reply = interface_send_request(task, request, handle); |
| if (reply == NULL) |
| return -EIO; |
| dbus_message_unref(reply); |
| return 0; |
| } |
| |
| /* |
| * Tell the supplicant to remove all network blocks. |
| */ |
| static int interface_remove_all(struct supplicant_task *task) |
| { |
| const char *request = "RemoveAllNetworks"; |
| DBusMessage *reply; |
| |
| reply = interface_send_request(task, request, NULL); |
| if (reply == NULL) |
| return -EIO; |
| dbus_message_unref(reply); |
| return 0; |
| } |
| |
| static int interface_flush_bss(struct supplicant_task *task, int age) |
| { |
| const char *request = "FlushBSS"; |
| DBusMessage *message, *reply; |
| |
| message = dbus_message_new_method_call(SUPPLICANT_NAME, task->ifpath, |
| SUPPLICANT_INTERFACE_INTF, request); |
| if (message == NULL) { |
| task_error(task, "%s: cannot allocate dbus msg", request); |
| return -ENOMEM; |
| } |
| dbus_message_set_auto_start(message, FALSE); |
| dbus_message_append_args(message, |
| DBUS_TYPE_UINT32, &age, DBUS_TYPE_INVALID); |
| |
| _DBG_WIFI("Interface.%s age %d", request, age); |
| |
| reply = send_call_and_block(task, message, __func__, request); |
| if (reply == NULL) |
| return -EIO; |
| dbus_message_unref(reply); |
| return 0; |
| } |
| |
| /* |
| * Tell the supplicant to enable a network block when |
| * processing scan results. |
| */ |
| #if 0 |
| static int network_enable(struct supplicant_task *task, const void *handle) |
| { |
| dbus_bool_t enabled = TRUE; |
| return network_set_property(task, handle, "Enabled", |
| DBUS_TYPE_BOOLEAN, &enabled); |
| } |
| #endif |
| |
| /* |
| * Tell the supplicant to disable a network block when |
| * processing scan results. |
| */ |
| static int network_disable(struct supplicant_task *task, const void *handle) |
| { |
| dbus_bool_t enabled = FALSE; |
| return network_set_property(task, handle, "Enabled", |
| DBUS_TYPE_BOOLEAN, &enabled); |
| } |
| |
| /* |
| * Get a wpa_supplicant network block handle for a flimflam network reference |
| */ |
| static void *network_get_handle(struct supplicant_task *task, |
| struct connman_network *network, |
| const char **service_path_p) |
| { |
| struct connman_service *service; |
| const char *service_path; |
| |
| service = connman_service_lookup_from_network(network); |
| if (service == NULL) { /* XXX cannot happen */ |
| if (service_path_p != NULL) |
| *service_path_p = NULL; |
| return NULL; |
| } |
| |
| service_path = connman_service_get_identifier(service); |
| if (service_path_p != NULL) |
| *service_path_p = service_path; |
| |
| return g_hash_table_lookup(task->netblocks, service_path); |
| } |
| |
| static int interface_disconnect(struct supplicant_task *task) |
| { |
| const char *request = "Disconnect"; |
| DBusMessage *reply; |
| |
| reply = interface_send_request(task, request, NULL); |
| if (reply == NULL) |
| return -EIO; |
| dbus_message_unref(reply); |
| return 0; |
| } |
| |
| static int interface_clear_cached_credentials(struct supplicant_task *task) |
| { |
| const char *request = "ClearCachedCredentials"; |
| DBusMessage *reply; |
| |
| reply = interface_send_request(task, request, NULL); |
| if (reply == NULL) |
| return -EIO; |
| dbus_message_unref(reply); |
| return 0; |
| } |
| |
| static void scan_reply(DBusPendingCall *call, void *user_data) |
| { |
| struct supplicant_task *task = user_data; |
| DBusMessage *reply; |
| |
| _DBG_WIFI("task %p", task); |
| |
| task->scan_call = NULL; |
| |
| reply = dbus_pending_call_steal_reply(call); |
| dbus_pending_call_unref(call); |
| if (reply == NULL) { |
| connman_device_set_scanning_state(task->device, FALSE); |
| task_error(task, "%s: no reply", __func__); |
| return; |
| } |
| if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) { |
| connman_device_set_scanning_state(task->device, FALSE); |
| task_error(task, "%s: got error?", __func__); |
| goto done; |
| } |
| |
| /* scan actually started, mark our state to reflect */ |
| task->scanning = TRUE; |
| done: |
| dbus_message_unref(reply); |
| } |
| |
| static void append_blobs(DBusMessageIter *parent, void *arg) |
| { |
| const GSList *blobs; |
| DBusMessageIter array, element; |
| |
| _DBG_WIFI("parent = %p arg = %p", parent, arg); |
| |
| dbus_message_iter_open_container(parent, DBUS_TYPE_ARRAY, |
| DBUS_TYPE_ARRAY_AS_STRING |
| DBUS_TYPE_BYTE_AS_STRING, |
| &array); |
| |
| for (blobs = arg; blobs != NULL; blobs = g_slist_next(blobs)) { |
| struct blob *blob = blobs->data; |
| |
| _DBG_WIFI("blob = %.*s", (int) blob->len, blob->data); |
| |
| /* blob signature: "ay" */ |
| dbus_message_iter_open_container(&array, DBUS_TYPE_ARRAY, |
| DBUS_TYPE_BYTE_AS_STRING, &element); |
| dbus_message_iter_append_fixed_array(&element, DBUS_TYPE_BYTE, |
| &blob->data, blob->len); |
| dbus_message_iter_close_container(&array, &element); |
| } |
| |
| dbus_message_iter_close_container(parent, &array); |
| } |
| |
| /* |
| * Free all elements of a GSList starting with the (n+1)th element. |
| * @list: GSList containing blobs |
| * @n: n is an index, starting with 0 for the list head. |
| */ |
| static void g_slist_free_after(GSList *list, int n) |
| { |
| GSList *nth, *e; |
| |
| nth = g_slist_nth(list, n); |
| /* list is already shorter than n. */ |
| if (nth == NULL) |
| return; |
| |
| /* Keep deleting (n+1)th element until none left */ |
| for (e = g_slist_next(nth); e != NULL; e = g_slist_next(nth)) { |
| blob_free(e->data); |
| nth = g_slist_delete_link (nth, e); |
| } |
| } |
| |
| static int build_scan_request(struct supplicant_scan_request *request) |
| { |
| struct blob *broadcast_ssid; |
| int ret; |
| int ssid_count; |
| |
| _DBG_WIFI("request %p", request); |
| |
| request->scan_type = "active"; |
| |
| request->ssids = NULL; |
| connman_wifi_append_hidden_ssids(&request->ssids); |
| |
| if (request->ssids != NULL) { |
| /* |
| * When scanning for specific Hidden SSIDs, |
| * always send the Broadcast SSID, too. |
| */ |
| ret = blob_new(&broadcast_ssid, 0, NULL); |
| if (ret < 0) |
| return ret; |
| |
| /* |
| * supplicant allows only SUPPLICANT_MAX_SSID_PER_SCAN ssids. |
| * So, send the first (WPAS_MAX_SSID_PER_SCAN - 1) in this list |
| * plus the broadcast SSID. |
| */ |
| ssid_count = g_slist_length(request->ssids); |
| if (ssid_count >= SUPPLICANT_MAX_SSID_PER_SCAN) { |
| /* |
| * TODO(djkurtz): revisit which ssids are chosen if too |
| * many. This just arbitrarily includes first N in |
| * profile. |
| */ |
| connman_error("%s: %d ssids; using first %d only", |
| __func__, ssid_count, |
| SUPPLICANT_MAX_SSID_PER_SCAN-1); |
| g_slist_free_after(request->ssids, |
| SUPPLICANT_MAX_SSID_PER_SCAN-2); |
| } |
| |
| request->ssids = g_slist_append(request->ssids, |
| broadcast_ssid); |
| } |
| |
| return 0; |
| } |
| |
| static void free_scan_request(struct supplicant_scan_request *request) |
| { |
| GSList *e; |
| |
| _DBG_WIFI("request %p", request); |
| |
| for (e=request->ssids; e != NULL; e=g_slist_next(e)) |
| blob_free(e->data); |
| g_slist_free(request->ssids); |
| } |
| |
| /* |
| * Request the supplicant do an active scan on the interface. |
| */ |
| static int interface_scan(struct supplicant_task *task) |
| { |
| DBusMessage *message; |
| DBusMessageIter array, dict; |
| struct supplicant_scan_request request; |
| int ret; |
| |
| _DBG_WIFI("task %p", task); |
| |
| if (task->ifpath == NULL) |
| return -EINVAL; |
| |
| if (task->scan_call != NULL) |
| return -EALREADY; |
| |
| ret = build_scan_request(&request); |
| if (ret < 0) { |
| free_scan_request(&request); |
| return ret; |
| } |
| |
| message = dbus_message_new_method_call(SUPPLICANT_NAME, task->ifpath, |
| SUPPLICANT_INTERFACE_INTF, "Scan"); |
| if (message == NULL) { |
| free_scan_request(&request); |
| return -ENOMEM; |
| } |
| |
| dbus_message_set_auto_start(message, FALSE); |
| dbus_message_iter_init_append(message, &array); |
| dbus_message_iter_open_container(&array, DBUS_TYPE_ARRAY, |
| DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING |
| DBUS_TYPE_STRING_AS_STRING |
| DBUS_TYPE_VARIANT_AS_STRING |
| DBUS_DICT_ENTRY_END_CHAR_AS_STRING, |
| &dict); |
| |
| connman_dbus_dict_append_variant(&dict, "Type", DBUS_TYPE_STRING, |
| &request.scan_type); |
| |
| /* SSIDs: an array of byte arrays "aay" */ |
| if (request.ssids != NULL) |
| connman_dbus_dict_append_variant_container(&dict, "SSIDs", |
| DBUS_TYPE_ARRAY_AS_STRING |
| DBUS_TYPE_ARRAY_AS_STRING |
| DBUS_TYPE_BYTE_AS_STRING, |
| append_blobs, request.ssids); |
| |
| dbus_message_iter_close_container(&array, &dict); |
| |
| free_scan_request(&request); |
| |
| /* NB: do here, the reply is slow and we want the ui to update asap */ |
| connman_device_set_scanning_state(task->device, TRUE); |
| |
| return send_call2(task, message, scan_reply, &task->scan_call, |
| __func__, "interface scan"); |
| } |
| |
| /* |
| * Construct the identifier for a network from the bssid and ssid. |
| * We intentionally construct a valid d-bus object path because |
| * the constructed string is eventually used this way. |
| */ |
| static void make_objpath(struct supplicant_result *result) |
| { |
| int i, off; |
| |
| CONNMAN_ASSERT(2*(ETH_ALEN + result->ssid_len) < sizeof(result->path)); |
| |
| memset(result->path, 0, sizeof(result->path)); |
| /* |
| * NB: snprintf(.., 3) works 'cuz we know path has room |
| * for the trailing byte. |
| * */ |
| for (i = 0, off = 0; i < ETH_ALEN; i++, off += 2) |
| snprintf(&result->path[off], 3, "%02X", result->addr[i]); |
| for (i = 0; i < result->ssid_len; i++, off += 2) |
| snprintf(&result->path[off], 3, "%02x", result->ssid[i]); |
| } |
| |
| /* |
| * Extract address information from the scan results |
| * and construct the object path to be used later |
| * for the BSS. |
| */ |
| static void extract_addr(struct supplicant_task *task, |
| DBusMessageIter *value, struct supplicant_result *result) |
| { |
| DBusMessageIter array; |
| unsigned char *addr; |
| int addr_len; |
| |
| dbus_message_iter_recurse(value, &array); |
| dbus_message_iter_get_fixed_array(&array, &addr, &addr_len); |
| |
| if (addr_len != ETH_ALEN) { |
| task_error(task, "%s: unexpected address length %d, got %d", |
| __func__, ETH_ALEN, addr_len); |
| return; |
| } |
| |
| memcpy(result->addr, addr, ETH_ALEN); |
| make_objpath(result); |
| } |
| |
| /* |
| * Extract the SSID from the scan results and construct |
| * a name for the BSS by converting any non-printing |
| * characters to spaces. |
| */ |
| static void extract_ssid(struct supplicant_task *task, |
| DBusMessageIter *value, struct supplicant_result *result) |
| { |
| DBusMessageIter array; |
| unsigned char *ssid; |
| int ssid_len; |
| |
| dbus_message_iter_recurse(value, &array); |
| dbus_message_iter_get_fixed_array(&array, &ssid, &ssid_len); |
| |
| if (ssid_len > MAX_SSID_LEN) { |
| task_error(task, "%s: ssid len %d", __func__, ssid_len); |
| return; |
| } |
| if (ssid_len < 1 || ssid[0] == '\0') { |
| _DBG_WIFI("no/blank ssid"); |
| return; |
| } |
| |
| memcpy(result->ssid, ssid, ssid_len); |
| result->ssid_len = ssid_len; |
| |
| connman_network_get_name_from_id(ssid, ssid_len, result->name, |
| sizeof(result->name)); |
| } |
| |
| /* |
| * Check the key management support reported in the scan |
| * result and mark whether 802.1x support is present. The |
| * returned value indicates if WPA-PSK is supported and |
| * used by the caller to mark either WPA RSN PSK support. |
| * |
| * TODO(sleffler) there's no reason to distinguish between |
| * WPA and RSN. |
| */ |
| static gboolean check_keymgmt(struct supplicant_task *task, |
| DBusMessageIter *iter, struct supplicant_result *result) |
| { |
| DBusMessageIter array; |
| const char *key_mgmt; |
| gboolean has_802_1x = FALSE; |
| gboolean has_psk = FALSE; |
| |
| dbus_message_iter_recurse(iter, &array); |
| while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRING) { |
| dbus_message_iter_get_basic(&array, &key_mgmt); |
| if (g_strstr_len(key_mgmt, -1, "-eap") != NULL) |
| has_802_1x = TRUE; |
| if (g_strstr_len(key_mgmt, -1, "-psk") != NULL) |
| has_psk = TRUE; |
| /* TODO(sleffler) how should wpa-none be handled? */ |
| dbus_message_iter_next(&array); |
| } |
| if (has_802_1x) |
| result->has_802_1x = TRUE; |
| |
| return (has_802_1x || has_psk); |
| } |
| |
| /* |
| * Extract key management information from the scan results. |
| * The results are interpreted to identify if PSK and 802.1x |
| * support are present. |
| */ |
| static gboolean extract_keymgmt(struct supplicant_task *task, |
| DBusMessageIter *iter, struct supplicant_result *result) |
| { |
| DBusMessageIter dict; |
| |
| dbus_message_iter_recurse(iter, &dict); |
| |
| while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { |
| DBusMessageIter entry, value; |
| const char *key; |
| |
| dbus_message_iter_recurse(&dict, &entry); |
| dbus_message_iter_get_basic(&entry, &key); |
| if (g_str_equal(key, "KeyMgmt") == TRUE) { |
| dbus_message_iter_next(&entry); |
| dbus_message_iter_recurse(&entry, &value); |
| return check_keymgmt(task, &value, result); |
| } |
| dbus_message_iter_next(&entry); |
| } |
| return FALSE; /* IE present but no key mgmt */ |
| } |
| |
| /* |
| * Extract the setting of the PRIVACY capability for the |
| * BSS from the scan result. |
| */ |
| static dbus_bool_t extract_privacy(struct supplicant_task *task, |
| DBusMessageIter *value) |
| { |
| dbus_bool_t privacy; |
| |
| DBUS_EXPECT_ITER_ARG(value, DBUS_TYPE_BOOLEAN, return FALSE); |
| dbus_message_iter_get_basic(value, &privacy); |
| |
| return privacy; |
| } |
| |
| /* |
| * Extract the BSS operating mode for the BSS from the dbus msg. |
| * We only handle IBSS and Infrastructure modes (e.g. no mesh). |
| */ |
| static const char *extract_mode(struct supplicant_task *task, |
| DBusMessageIter *value) |
| { |
| const char *mode; |
| |
| DBUS_EXPECT_ITER_ARG(value, DBUS_TYPE_STRING, return NULL); |
| dbus_message_iter_get_basic(value, &mode); |
| |
| if (g_str_equal(mode, "infrastructure") == TRUE) |
| return "managed"; |
| else if (g_str_equal(mode, "ad-hoc") == TRUE) |
| return "adhoc"; |
| else { |
| connman_error("%s: unknown BSS mode %s", __func__, mode); |
| return NULL; /* XXX ? */ |
| } |
| } |
| |
| /* |
| * Extract the signal strength data for the BSS from the dbus msg. |
| */ |
| static dbus_int16_t extract_signal(struct supplicant_task *task, |
| DBusMessageIter *value) |
| { |
| dbus_int16_t signal; |
| |
| DBUS_EXPECT_ITER_ARG(value, DBUS_TYPE_INT16, return -1); |
| dbus_message_iter_get_basic(value, &signal); |
| |
| return signal; |
| } |
| |
| /* |
| * Extract the channel frequency for the BSS from the dbus msg. |
| */ |
| static dbus_uint16_t extract_frequency(struct supplicant_task *task, |
| DBusMessageIter *value) |
| { |
| dbus_uint16_t frequency; |
| |
| DBUS_EXPECT_ITER_ARG(value, DBUS_TYPE_UINT16, return 0); |
| dbus_message_iter_get_basic(value, &frequency); |
| |
| return frequency; |
| } |
| |
| /* |
| * Extract rate information for the BSS from the scan result. |
| * We use the rate set to calculate the max data rate. |
| */ |
| static void extract_rates(struct supplicant_task *task, |
| DBusMessageIter *value, struct supplicant_result *result) |
| { |
| DBusMessageIter array; |
| const dbus_uint32_t *rates; |
| int rates_len; |
| |
| /* TODO(sleffler) validate is fixed and type */ |
| dbus_message_iter_recurse(value, &array); |
| dbus_message_iter_get_fixed_array(&array, &rates, &rates_len); |
| |
| /* NB: rates array is sorted so max is first */ |
| result->maxrate = (rates_len > 0 ? rates[0] : 0); |
| } |
| |
| /* |
| * Extract miscellaneous info from the BSS's Information Elements. |
| * We scan these to determine capabilities such as HT and ERP; |
| * to calculate a "phy mode" for the BSS channel. |
| */ |
| static void extract_ie_info(struct supplicant_task *task, |
| DBusMessageIter *value, struct supplicant_result *result) |
| { |
| DBusMessageIter array; |
| const dbus_uint32_t *ie_data; |
| const uint8_t *ie, *ie_end; |
| int ie_data_len; |
| |
| /* TODO(sleffler) validate is fixed and type */ |
| dbus_message_iter_recurse(value, &array); |
| dbus_message_iter_get_fixed_array(&array, &ie_data, &ie_data_len); |
| |
| ie = (const uint8_t *) ie_data; |
| ie_end = ie + ie_data_len; |
| while (ie_end - ie > 1) { |
| if (ie_end - ie < 2 + ie[1]) { |
| connman_error("%s: ie length %d at offset %d " |
| "extends past container length %d", |
| __func__, ie[1], |
| (int) (ie - (const uint8_t *) ie_data), |
| ie_data_len); |
| return; |
| } |
| if (ie[0] == IEEE80211_ELEMID_ERP) { |
| result->phymode = CONNMAN_NETWORK_PHYMODE_11G; |
| /* NB: continue to check for HT */ |
| } |
| if (ie[0] == IEEE80211_ELEMID_HTCAP || |
| ie[0] == IEEE80211_ELEMID_HTINFO) { |
| result->phymode = CONNMAN_NETWORK_PHYMODE_11N; |
| return; |
| } |
| /* TODO(sleffler) check VENDOR ie's for compat HT and Atheros */ |
| ie += 2+ie[1]; |
| } |
| } |
| |
| /* |
| * Convert a signal (in dBm) to a strength value in the range [0..100]. |
| */ |
| static unsigned char calculate_strength(struct supplicant_task *task, |
| int signal) |
| { |
| unsigned char strength; |
| |
| if (signal > 0) |
| strength = 100 - signal; |
| else |
| strength = 120 + signal; |
| return (strength < 100 ? strength : 100); |
| } |
| |
| /* |
| * Calculate an IEEE channel # given a frequency. This is |
| * necessarily a bit heuristic as we do not have information |
| * such as the regulatory state to make certain decisions. |
| */ |
| static unsigned short calculate_channel(struct supplicant_result *result) |
| { |
| if (result->frequency < 0) |
| return 0; |
| |
| if (result->frequency == 2484) |
| return 14; |
| if (result->frequency < 2484) |
| return (result->frequency - 2407) / 5; |
| if (result->frequency < 5000) { |
| /* NB: Public Safety Band (PSB) is 4940..4990 */ |
| if (result->frequency >= 4940 && result->frequency <= 4990) |
| return 37 + ((result->frequency * 10) + |
| ((result->frequency % 5) == 2 ? 5 : 0) - 49400) / 5; |
| if (result->frequency > 4900) |
| return (result->frequency - 4000) / 5; |
| else |
| return 15 + (result->frequency - 2512) / 20; |
| } |
| return (result->frequency - 5000) / 5; |
| } |
| |
| /* |
| * Convert the BSS properties received over the DBus from wpa_supplicant |
| * to the supplicant_result state block. We return FALSE if something |
| * was wrong that makes the result unusable; otherwise TRUE. |
| */ |
| static gboolean extract_bss_properties(struct supplicant_task *task, |
| struct supplicant_result *result, DBusMessageIter *iter) |
| { |
| |
| memset(result, 0, sizeof(*result)); |
| result->frequency = -1; |
| result->signal = -1; |
| result->phymode = CONNMAN_NETWORK_PHYMODE_UNDEF; |
| |
| while (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_DICT_ENTRY) { |
| DBusMessageIter entry, value; |
| const char *key; |
| |
| dbus_message_iter_recurse(iter, &entry); |
| dbus_message_iter_get_basic(&entry, &key); |
| dbus_message_iter_next(&entry); |
| dbus_message_iter_recurse(&entry, &value); |
| |
| if (g_str_equal(key, "BSSID") == TRUE) |
| extract_addr(task, &value, result); |
| else if (g_str_equal(key, "SSID") == TRUE) |
| extract_ssid(task, &value, result); |
| else if (g_str_equal(key, "Mode") == TRUE) |
| result->mode = extract_mode(task, &value); |
| else if (g_str_equal(key, "Privacy") == TRUE) |
| result->privacy = extract_privacy(task, &value); |
| else if (g_str_equal(key, "Frequency") == TRUE) |
| result->frequency = extract_frequency(task, &value); |
| else if (g_str_equal(key, "Signal") == TRUE) |
| result->signal = extract_signal(task, &value); |
| else if (g_str_equal(key, "Rates") == TRUE) |
| extract_rates(task, &value, result); |
| else if (g_str_equal(key, "WPA") == TRUE) |
| result->has_wpa_psk = |
| extract_keymgmt(task, &value, result); |
| else if (g_str_equal(key, "RSN") == TRUE) |
| result->has_rsn_psk = |
| extract_keymgmt(task, &value, result); |
| else if (g_str_equal(key, "IEs") == TRUE) |
| extract_ie_info(task, &value, result); |
| |
| dbus_message_iter_next(iter); |
| } |
| if (result->path[0] == '\0') { |
| task_error(task, "%s: ignore BSS, no BSSID", __func__); |
| return FALSE; |
| } |
| if (result->mode == NULL) { |
| task_error(task, "%s: ignore BSS, no mode", __func__); |
| return FALSE; |
| } |
| if (result->frequency < 0) { |
| task_error(task, "%s: ignore BSS, invalid frequency %d", |
| __func__, result->frequency); |
| return FALSE; |
| } |
| |
| result->channel = calculate_channel(result); |
| if (result->phymode == CONNMAN_NETWORK_PHYMODE_UNDEF) { |
| /* |
| * Intuit a phy mode if extract_ie_info wasn't able to. |
| * NB: 3000 here is just slop, could be more precise. |
| */ |
| if (result->frequency < 3000) { |
| /* |
| * 2.4GHZ legacy, check tx rate for 11b-only |
| * (note 22M is valid) |
| */ |
| if (result->maxrate < 24000000) |
| result->phymode = CONNMAN_NETWORK_PHYMODE_11B; |
| else |
| result->phymode = CONNMAN_NETWORK_PHYMODE_11G; |
| } else |
| result->phymode = CONNMAN_NETWORK_PHYMODE_11A; |
| } |
| result->strength = calculate_strength(task, result->signal); |
| |
| if (result->has_802_1x == TRUE) |
| result->security = "802_1x"; |
| else if (result->has_rsn_psk == TRUE) |
| result->security = "rsn"; |
| else if (result->has_wpa_psk == TRUE) |
| result->security = "wpa"; |
| else if (result->privacy == TRUE) |
| result->security = "wep"; |
| else |
| result->security = "none"; |
| |
| return TRUE; |
| } |
| |
| static void update_strength(struct supplicant_task *task, |
| struct connman_network *network, unsigned short strength) |
| { |
| connman_network_set_scangen(network, task->scangen); |
| connman_network_set_strength(network, strength); |
| } |
| |
| /* |
| * Add/update a BSS entry based on the supplied scan result. |
| * We construct a network object (or use an existing one) |
| * and record relevant state. |
| */ |
| static struct connman_network *add_bss(struct supplicant_task *task, |
| struct supplicant_result *bss) |
| { |
| struct connman_network *network; |
| char *group; |
| |
| group = connman_wifi_build_group(bss->path, bss->name, |
| bss->ssid, bss->ssid_len, bss->mode, bss->security); |
| |
| network = connman_device_get_network(task->device, bss->path); |
| |
| _DBG_WIFI("%s %s \"%s\" (%s %s%s) signal %d strength %u freq %u", |
| network == NULL ? "add" : "update", bss->path, bss->name, |
| bss->mode, bss->security, (bss->has_wps == TRUE) ? " WPS" : "", |
| bss->signal, bss->strength, bss->frequency); |
| |
| if (network == NULL) { |
| network = connman_network_create(bss->path, |
| CONNMAN_NETWORK_TYPE_WIFI); |
| if (network == NULL) { |
| task_error(task, "%s: cannot create network %s", |
| __func__, bss->name); |
| goto done; |
| } |
| |
| connman_network_set_index(network, |
| connman_device_get_index(task->device)); |
| connman_network_set_protocol(network, |
| CONNMAN_NETWORK_PROTOCOL_IP); |
| connman_network_set_address(network, bss->addr, ETH_ALEN); |
| |
| if (connman_device_add_network(task->device, network) < 0) { |
| task_error(task, "%s: cannot add network %s", |
| __func__, bss->name); |
| connman_network_unref(network); |
| network = NULL; |
| goto done; |
| } |
| } |
| |
| if (bss->name[0] != '\0') |
| connman_network_set_name(network, bss->name); |
| /* TODO(sleffler) move to create case? */ |
| connman_network_set_blob(network, "WiFi.SSID", |
| bss->ssid, bss->ssid_len); |
| connman_network_set_string(network, "WiFi.Mode", bss->mode); |
| update_strength(task, network, bss->strength); |
| connman_network_set_uint16(network, "Frequency", bss->frequency); |
| connman_network_set_uint16(network, "WiFi.Channel", bss->channel); |
| connman_network_set_phymode(network, bss->phymode); |
| connman_network_set_string(network, "WiFi.Security", bss->security); |
| |
| connman_network_set_available(network, TRUE); |
| |
| /* NB: do last since this may cause the profile to be updated */ |
| if (bss->ssid[0] != 0) |
| connman_network_set_group(network, group); |
| done: |
| g_free(group); |
| |
| return network; |
| } |
| |
| /* |
| * Handle an Interface.BSSAdded signal from wpa_supplicant. |
| */ |
| static void interface_signal_bss_added(struct supplicant_task *task, |
| DBusMessage *msg) |
| { |
| DBusMessageIter iter, dict; |
| struct connman_network *network; |
| const char *path; |
| struct supplicant_result result; |
| |
| dbus_message_iter_init(msg, &iter); |
| DBUS_EXPECT_ITER_ARG(&iter, DBUS_TYPE_OBJECT_PATH, return); |
| dbus_message_iter_get_basic(&iter, &path); |
| dbus_message_iter_next(&iter); |
| DBUS_EXPECT_ITER_ARG(&iter, DBUS_TYPE_ARRAY, return); |
| dbus_message_iter_recurse(&iter, &dict); |
| |
| if (extract_bss_properties(task, &result, &dict) == FALSE) |
| return; |
| |
| network = add_bss(task, &result); |
| if (network != NULL) { |
| void *handle = g_hash_table_lookup(task->bss, path); |
| if (handle == NULL) { |
| _DBG_WIFI("add BSS %s network %p", path, network); |
| g_hash_table_insert(task->bss, |
| (gpointer) g_strdup(path), |
| connman_network_ref(network)); |
| } else if (handle != network) { |
| task_error(task, "%s: new BSS %s already in table " |
| "(replace %p by %p)!", __func__, path, |
| handle, network); |
| g_hash_table_replace(task->bss, |
| (gpointer) g_strdup(path), |
| connman_network_ref(network)); |
| } |
| } |
| } |
| |
| /* |
| * Handle an Interface.BSSRemoved signal from wpa_supplicant. |
| */ |
| static void interface_signal_bss_removed(struct supplicant_task *task, |
| DBusMessage *msg) |
| { |
| DBusError error; |
| const char *path; |
| void *handle; |
| |
| dbus_error_init(&error); |
| if (dbus_message_get_args(msg, &error, DBUS_TYPE_OBJECT_PATH, &path, |
| DBUS_TYPE_INVALID) == FALSE) { |
| if (dbus_error_is_set(&error) == TRUE) { |
| task_error(task, "%s: %s", __func__, error.message); |
| dbus_error_free(&error); |
| } else |
| task_error(task, "%s: wrong arguments", __func__); |
| return; |
| } |
| |
| handle = g_hash_table_lookup(task->bss, path); |
| if (handle != NULL) { |
| _DBG_WIFI("remove BSS %s network %p", path, handle); |
| /* |
| * Be careful to clear any reference outside the bss table. |
| * There may be a connection request in flight (e.g. due to |
| * auto connect) in which case we need to clear task->network |
| * but there's no need to otherwise alter its state (e.g. w/ |
| * connman_network_set_disconnected) because removing it |
| * from the hash table will cause it to be removed from the |
| * device and that in turns clears all state. The only other |
| * references (task->current_bss and task->pending_network) |
| * cannot be set without our being notified (e.g. a bss |
| * change signal for current_bss). |
| */ |
| if (task->network == handle) { |
| void *netblock; |
| |
| _DBG_WIFI("clear pending connect request"); |
| /* |
| * Make sure the network has its supplicant |
| * netblock disabled or it may try to associate |
| * on the next scan. |
| */ |
| netblock = network_get_handle(task, task->network, |
| NULL); |
| if (netblock != NULL) |
| network_disable(task, netblock); |
| |
| connman_network_unref(task->network); |
| task->network = NULL; |
| |
| /* |
| * NB: autconnect is deferred to the idle loop |
| * which is important as we want it to happen |
| * after we've released this BSS which we're trying |
| * to connect to (this insures if we try a new |
| * connection it'll be to a different service). |
| */ |
| connman_device_auto_connect(task->device); |
| } |
| g_hash_table_remove(task->bss, path); |
| } else |
| _DBG_WIFI("%s not in BSS table", path); |
| } |
| |
| static enum supplicant_state string2state(const char *state) |
| { |
| if (g_ascii_strcasecmp(state, "INACTIVE") == 0) |
| return WPA_INACTIVE; |
| else if (g_ascii_strcasecmp(state, "SCANNING") == 0) |
| return WPA_SCANNING; |
| else if (g_ascii_strcasecmp(state, "AUTHENTICATING") == 0) |
| return WPA_AUTHENTICATING; |
| else if (g_ascii_strcasecmp(state, "ASSOCIATING") == 0) |
| return WPA_ASSOCIATING; |
| else if (g_ascii_strcasecmp(state, "ASSOCIATED") == 0) |
| return WPA_ASSOCIATED; |
| else if (g_ascii_strcasecmp(state, "GROUP_HANDSHAKE") == 0) |
| return WPA_GROUP_HANDSHAKE; |
| else if (g_ascii_strcasecmp(state, "4WAY_HANDSHAKE") == 0) |
| return WPA_4WAY_HANDSHAKE; |
| else if (g_ascii_strcasecmp(state, "COMPLETED") == 0) |
| return WPA_COMPLETED; |
| else if (g_ascii_strcasecmp(state, "DISCONNECTED") == 0) |
| return WPA_DISCONNECTED; |
| else |
| return WPA_INVALID; |
| } |
| |
| static void |
| handle_pending(struct supplicant_task *task) |
| { |
| struct connman_network *network; |
| int err; |
| |
| _DBG_WIFI("current_bss %p network %p pending_network %p", |
| task->current_bss, task->network, task->pending_network); |
| |
| if (task->pending_network == NULL) |
| return; |
| network = task->pending_network; |
| task->pending_network = NULL; |
| err = task_connect(task, network); |
| /* NB: release pending_network ref */ |
| connman_network_unref(network); |
| |
| if (err < 0 && err != -EINPROGRESS) |
| task_error(task, "%s: task_connect failed, error %d", |
| __func__, err); |
| } |
| |
| static int is_psk(struct connman_network *network) |
| { |
| const char *security = |
| connman_network_get_string(network, "WiFi.Security"); |
| return (g_ascii_strcasecmp(security, "wpa") == 0 || |
| g_ascii_strcasecmp(security, "rsn") == 0); |
| } |
| |
| /* |
| * Handle a supplicant state change. |
| */ |
| static void state_change(struct supplicant_task *task, |
| enum supplicant_state newstate) |
| { |
| enum supplicant_state ostate = task->state; |
| struct connman_network *network; |
| |
| /* |
| * State changes refer to the network we're trying to connect to, |
| * otherwise the current BSS. This is a byproduct of our being |
| * signaled of CurrentBSS changes only once the state machine reaches |
| * the COMPLETED state. |
| */ |
| if (task->network != NULL) |
| network = task->network; |
| else |
| network = task->current_bss; |
| |
| task_info(task, "state change %s -> %s%s (BSSID %s)", |
| supplicant_state_names[ostate], |
| supplicant_state_names[newstate], |
| task->scanning == TRUE ? " (scanning)" : "", |
| network != NULL ? |
| connman_network_get_string(network, "Address") : "<NA>" |
| ); |
| |
| task->state = newstate; |
| |
| if (network == NULL) { |
| _DBG_WIFI("no network"); |
| return; |
| } |
| |
| /* |
| * Update internal (network) state machine. This follows the |
| * wpa_supplicant state machine but with some intermediate states |
| * ignored: |
| * |
| * WPA_DISCONNECTED NETWORK_STATE_IDLE |
| * WPA_INACTIVE NETWORK_STATE_IDLE |
| * WPA_SCANNING NETWORK_STATE_CONNECTING |
| * WPA_AUTHENTICATING NETWORK_STATE_ASSOCIATING |
| * WPA_ASSOCIATING NETWORK_STATE_ASSOCIATING |
| * WPA_ASSOCIATED NETWORK_STATE_ASSOCIATING |
| * WPA_4WAY_HANDSHAKE NETWORK_STATE_ASSOCIATING |
| * WPA_GROUP_HANDSHAKE NETWORK_STATE_ASSOCIATING |
| * WPA_COMPLETED NETWORK_STATE_CONNECTED |
| * |
| * Beware that wpa_supplicant will coalesce state changes so we |
| * only see the latest state. This makes our work almost a guess. |
| * |
| * Note that the UI operates based on the service state machine |
| * which is driven, for wifi networks, from the network state |
| * and, once connected, the ipconfig state (e.g. dhclient). |
| */ |
| switch (newstate) { |
| case WPA_COMPLETED: |
| /* carrier on */ |
| connman_network_set_connected(network); |
| break; |
| |
| case WPA_ASSOCIATED: |
| connman_network_set_associating(network); |
| break; |
| |
| case WPA_AUTHENTICATING: |
| case WPA_ASSOCIATING: |
| case WPA_4WAY_HANDSHAKE: |
| case WPA_GROUP_HANDSHAKE: |
| if (ostate != WPA_COMPLETED) |
| connman_network_set_associating(network); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| /* |
| * Handle an Interface.PropertiesChanged.Scanning signal from wpa_supplicant. |
| */ |
| static void interface_property_changed_scanning(struct supplicant_task *task, |
| dbus_bool_t scanning) |
| { |
| _DBG_WIFI("Scanning %d (task->scanning %d scangen %d)", scanning, |
| task->scanning, task->scangen); |
| |
| connman_device_set_scanning_state(task->device, scanning); |
| |
| if (scanning == TRUE) { |
| /* |
| * Scan started, bump the scangen so signal strength |
| * data get pushed from the network to the service. |
| */ |
| task->scangen++; |
| } |
| if (task->scanning == TRUE && scanning == FALSE) { |
| /* |
| * We requested the scan and it's just completed; |
| * potentially auto-connect using the results. |
| */ |
| task->scanning = FALSE; |
| connman_device_auto_connect(task->device); |
| } |
| |
| if (scanning == FALSE && task->flush_pending == TRUE) { |
| time_t age; |
| /* |
| * Scan completed and we have a pending flush BSS |
| * request. Age out entries using a time that is |
| * 10 secs more than the time since we resumed (10 |
| * is fairly arbitrary; it's based on the idea you |
| * won't move the device very far in that time). |
| * |
| * NB: We don't need to explicitly flush our local |
| * state (as done w/ the old dbus api) because we |
| * receive a signal for each BSS to be removed. |
| */ |
| task->flush_pending = FALSE; |
| age = (time(NULL) - task->resume_time) + 10; |
| (void) interface_flush_bss(task, age); |
| } |
| } |
| |
| static void unref_current_bss(struct supplicant_task *task) |
| { |
| connman_network_set_string(task->current_bss, |
| CONNMAN_WIFI_AUTHMODE, NULL); |
| connman_network_unref(task->current_bss); |
| task->current_bss = NULL; |
| } |
| |
| /* |
| * Check for a 4WAY_HANDSHAKE failure that might be due to |
| * an incorrect user-entered passphrase. |
| */ |
| static int check_4way_handshake(struct supplicant_task *task) |
| { |
| if (!(task->state == WPA_4WAY_HANDSHAKE && is_psk(task->current_bss))) |
| return FALSE; |
| if (connman_network_get_in_use(task->current_bss) == FALSE) { |
| /* |
| * This happens if we are called when disconnecting; e.g. |
| * on connect timeout the core calls in to disconnect and |
| * we may interpret the currentBSS change to be a 4-way |
| * handshake failure. Ignore the possible error as it's |
| * not meaningful (could've happened for any reason as we |
| * interrupted the state machine). |
| */ |
| return FALSE; |
| } |
| |
| task_error(task,"%s: 4-Way handshake failed - shared key may be " |
| "incorrect", __func__); |
| |
| if (connman_network_get_previously_connected(task->current_bss) |
| == FALSE) { |
| /* |
| * We've never successfully joined this network; treat |
| * the failure as a bad pasphrase and stop trying to |
| * connect. |
| */ |
| connman_network_set_error(task->current_bss, |
| CONNMAN_ELEMENT_ERROR_BAD_PASSPHRASE); |
| return FALSE; |
| } else { |
| /* |
| * We've previously connected with this passphrase, |
| * ignore the error and direct the caller to keep |
| * trying. |
| */ |
| return TRUE; |
| } |
| } |
| |
| /* |
| * Handle an Interface.PropertiesChanged.CurrentBSS signal from wpa_supplicant. |
| */ |
| static void interface_property_changed_current_bss(struct supplicant_task *task, |
| const char *path) |
| { |
| void *handle; |
| struct connman_network *network; |
| |
| if (g_str_equal(path, "/") == TRUE) { |
| /* |
| * A path of "/" means no current BSS. Clear the held |
| * reference and fulfill any connect request that got |
| * queued up behind a disconnect. |
| */ |
| _DBG_WIFI("CurrentBSS %s current_bss %p network %p " |
| "pending_network %p", path, task->current_bss, |
| task->network, task->pending_network); |
| if (task->current_bss != NULL) { |
| /* |
| * If there is no new network and we got here by way |
| * of 4WAY_HANDSHAKE, it is possible the user-entered |
| * passphrase was incorrect. |
| */ |
| if (task->network == NULL && |
| check_4way_handshake(task) == TRUE) { |
| /* ignore 4-way handshake failure */ |
| handle_pending(task); |
| return; |
| } |
| |
| /* |
| * Make sure the outgoing network has its supplicant |
| * netblock disabled. |
| */ |
| handle = network_get_handle(task, task->current_bss, |
| NULL); |
| if (handle != NULL) |
| network_disable(task, handle); |
| |
| connman_network_set_disconnected(task->current_bss); |
| unref_current_bss(task); |
| } |
| handle_pending(task); |
| return; |
| } |
| |
| handle = g_hash_table_lookup(task->bss, path); |
| if (handle == NULL) { |
| _DBG_WIFI("CurrentBSS %s not in BSS table", path); |
| /* XXX what do we do? */ |
| return; |
| } |
| network = handle; |
| |
| /* |
| * Roaming case: mark the old network disconnected |
| * (but not the associated service or device) and |
| * install the new network as ``current''. |
| */ |
| _DBG_WIFI("CurrentBSS %s current_bss %p -> %p (network %p)", path, |
| task->current_bss, network, task->network); |
| |
| if (network == task->current_bss) { |
| /* |
| * This seems to happen sometimes when roaming; we get |
| * notified of a BSS change after auth and then again |
| * after assoc. Just avoid the state churn. |
| */ |
| return; |
| } |
| |
| if (task->current_bss != NULL) { |
| connman_network_set_disconnected_only(task->current_bss); |
| unref_current_bss(task); |
| } |
| task->current_bss = connman_network_ref(network); |
| if (task->network != NULL) { |
| /* |
| * Cleanup connection request state held in task->network. |
| * We hold a ref to the network object between the time we |
| * initiate a connection and the point we actually join the |
| * network (at which point it's moved to the current_bss). |
| * In particular handle the case of a dbus request to connect |
| * that generates a ``hidden network'' that's constructed with |
| * the user-supplied parameters. This network object is |
| * different from the one we actually install as the |
| * current_bss and we need to discard the original hidden |
| * network so it doesn't get re-used for things like |
| * auto-connect. The core code recognizes a hidden network |
| * and cleans up the device and service state. |
| */ |
| if (task->network != task->current_bss) |
| connman_network_set_disconnected_only(task->network); |
| connman_network_unref(task->network); |
| task->network = NULL; |
| } |
| |
| /* sync service state to reflect ``current network'' */ |
| connman_service_set_current_network(task->current_bss); |
| } |
| |
| /* |
| * Handle an Interface.PropertiesChanged signal from wpa_supplicant. |
| */ |
| static void interface_signal_properties_changed(struct supplicant_task *task, |
| DBusMessage *msg) |
| { |
| DBusMessageIter array, dict; |
| const char *state; |
| |
| dbus_message_iter_init(msg, &array); |
| DBUS_EXPECT_ITER_ARG(&array, DBUS_TYPE_ARRAY, return); |
| dbus_message_iter_recurse(&array, &dict); |
| |
| state = NULL; |
| while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { |
| DBusMessageIter entry, value; |
| const char *key; |
| |
| dbus_message_iter_recurse(&dict, &entry); |
| dbus_message_iter_get_basic(&entry, &key); |
| dbus_message_iter_next(&entry); |
| dbus_message_iter_recurse(&entry, &value); |
| |
| if (g_str_equal(key, "Scanning") == TRUE) { |
| dbus_bool_t scanning; |
| |
| dbus_message_iter_get_basic(&value, &scanning); |
| interface_property_changed_scanning(task, scanning); |
| } else if (g_str_equal(key, "State") == TRUE) { |
| dbus_message_iter_get_basic(&value, &state); |
| _DBG_WIFI("State %s", state); |
| /* NB: defer so we handle any CurrentBSS change first */ |
| } else if (g_str_equal(key, "CurrentBSS") == TRUE) { |
| const char *path; |
| |
| dbus_message_iter_get_basic(&value, &path); |
| interface_property_changed_current_bss(task, path); |
| } else if (g_str_equal(key, "CurrentAuthMode") == TRUE) { |
| const char *authmode; |
| |
| dbus_message_iter_get_basic(&value, &authmode); |
| if (task->current_bss != NULL) |
| connman_network_set_string(task->current_bss, |
| CONNMAN_WIFI_AUTHMODE, authmode); |
| #if 0 |
| } else if (g_str_equal(key, "CurrentNetwork") == TRUE) { |
| } else if (g_str_equal(key, "Signal") == TRUE) { |
| #endif |
| } |
| |
| dbus_message_iter_next(&dict); |
| } |
| if (state != NULL) |
| state_change(task, string2state(state)); |
| } |
| |
| /* |
| * Filter fi.w1.wpa_supplicant1.Interface signals from the supplicant. |
| */ |
| static DBusHandlerResult interface_filter(DBusMessage *msg, void *data) |
| { |
| struct supplicant_task *task; |
| const char *member, *path; |
| |
| member = dbus_message_get_member(msg); |
| if (member == NULL) { |
| _DBG_WIFI("no msg member (interface=%s, sender=%s)", |
| dbus_message_get_interface(msg), |
| dbus_message_get_sender(msg)); |
| _DBG_WIFI("no msg member"); |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| } |
| |
| path = dbus_message_get_path(msg); |
| if (path == NULL) { |
| _DBG_WIFI("no path (interface=%s member=%s, sender=%s)", |
| dbus_message_get_interface(msg), member, |
| dbus_message_get_sender(msg)); |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| } |
| |
| task = find_task_by_path(path); |
| if (task == NULL) { |
| /* NB: this can fire a bunch after removing an interface */ |
| _DBG_WIFI("no task for path %s " |
| "(interface=%s, member=%s, sender=%s)", |
| path, dbus_message_get_interface(msg), member, |
| dbus_message_get_sender(msg)); |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| } |
| |
| /* NB: sorted alphabetically; not sure if it matters */ |
| if (g_str_equal(member, "BSSAdded") == TRUE) |
| interface_signal_bss_added(task, msg); |
| else if (g_str_equal(member, "BSSRemoved") == TRUE) |
| interface_signal_bss_removed(task, msg); |
| else if (g_str_equal(member, "PropertiesChanged") == TRUE) |
| interface_signal_properties_changed(task, msg); |
| else if (g_str_equal(member, "ScanDone") == TRUE) |
| /* NB: ignore, monitor Interface.Scanning property */; |
| else |
| _DBG_WIFI("Interface.%s not handled", member); |
| |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| } |
| |
| /* |
| * Handle a BSS.PropertiesChanged signal from wpa_supplicant. |
| */ |
| static void bss_signal_properties_changed(struct supplicant_task *task, |
| struct connman_network *network, DBusMessage *msg) |
| { |
| DBusMessageIter array, dict; |
| |
| dbus_message_iter_init(msg, &array); |
| DBUS_EXPECT_ITER_ARG(&array, DBUS_TYPE_ARRAY, return); |
| dbus_message_iter_recurse(&array, &dict); |
| |
| while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { |
| DBusMessageIter entry, value; |
| const char *key; |
| |
| dbus_message_iter_recurse(&dict, &entry); |
| dbus_message_iter_get_basic(&entry, &key); |
| dbus_message_iter_next(&entry); |
| dbus_message_iter_recurse(&entry, &value); |
| |
| if (g_str_equal(key, "Signal") == TRUE) { |
| dbus_int16_t signal = extract_signal(task, &value); |
| |
| _DBG_WIFI("%s (%s) signal %d", |
| connman_network_get_string(network, "Name"), |
| connman_network_get_string(network, "Address"), |
| signal); |
| if (signal != -1) |
| update_strength(task, network, |
| calculate_strength(task, signal)); |
| } else if (g_str_equal(key, "Frequency") == TRUE) { |
| dbus_int16_t frequency = extract_frequency(task, &value); |
| |
| _DBG_WIFI("%s (%s) frequency %d", |
| connman_network_get_string(network, "Name"), |
| connman_network_get_string(network, "Address"), |
| frequency); |
| if (frequency > 0) |
| connman_network_set_uint16(network, "Frequency", |
| frequency); |
| } |
| |
| dbus_message_iter_next(&dict); |
| } |
| } |
| |
| /* |
| * Filter fi.w1.wpa_supplicant1.BSS signals from the supplicant. |
| */ |
| static DBusHandlerResult bss_filter(DBusMessage *msg, void *data) |
| { |
| struct supplicant_task *task; |
| const char *member, *path; |
| struct connman_network *network; |
| |
| member = dbus_message_get_member(msg); |
| if (member == NULL) { |
| _DBG_WIFI("no msg member (interface=%s, sender=%s)", |
| dbus_message_get_interface(msg), |
| dbus_message_get_sender(msg)); |
| _DBG_WIFI("no msg member"); |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| } |
| |
| path = dbus_message_get_path(msg); |
| if (path == NULL) { |
| _DBG_WIFI("no path (interface=%s member=%s, sender=%s)", |
| dbus_message_get_interface(msg), member, |
| dbus_message_get_sender(msg)); |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| } |
| |
| task = find_task_by_path(path); |
| if (task == NULL) { |
| _DBG_WIFI("no task for path %s " |
| "(interface=%s, member=%s, sender=%s)", |
| path, dbus_message_get_interface(msg), member, |
| dbus_message_get_sender(msg)); |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| } |
| |
| network = g_hash_table_lookup(task->bss, path); |
| if (network == NULL) { |
| /* signal for unknown BSS */ |
| _DBG_WIFI("%s not in BSS table", path); |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| } |
| |
| if (g_str_equal(member, "PropertiesChanged") == TRUE) |
| bss_signal_properties_changed(task, network, msg); |
| else |
| _DBG_WIFI("BSS.%s not handled", member); |
| |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| } |
| |
| /* |
| * Filter signals from the supplicant. |
| */ |
| static DBusHandlerResult supplicant_filter(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| if (dbus_message_has_interface(msg, SUPPLICANT_INTERFACE_INTF) == TRUE) |
| return interface_filter(msg, data); |
| if (dbus_message_has_interface(msg, SUPPLICANT_BSS_INTF) == TRUE) |
| return bss_filter(msg, data); |
| |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| } |
| |
| /* |
| * Connman device driver support. |
| */ |
| |
| /* |
| * BSS g_hash_table callback on free; remove the network from the |
| * device and drop our ref on the object. |
| */ |
| static void bss_drop_ref(gpointer data) |
| { |
| struct connman_network *network = (struct connman_network *) data; |
| struct connman_device *device = connman_network_get_device(network); |
| |
| _DBG_WIFI("network %p identifier %s", network, |
| connman_network_get_identifier(network)); |
| |
| connman_device_remove_network(device, |
| connman_network_get_identifier(network)); |
| connman_network_unref(network); |
| } |
| |
| static int supplicant_device_probe(struct connman_device *device) |
| { |
| return 0; |
| } |
| |
| static int supplicant_device_enable(struct connman_device *device) |
| { |
| struct supplicant_task *task; |
| int err; |
| |
| _DBG_WIFI("device %p", device); |
| |
| task = g_try_new0(struct supplicant_task, 1); |
| if (task == NULL) |
| return -ENOMEM; |
| |
| task->netblocks = g_hash_table_new_full(g_str_hash, g_str_equal, |
| g_free, g_free); |
| if (task->netblocks == NULL) { |
| err = -ENOMEM; |
| goto failed; |
| } |
| |
| task->bss = g_hash_table_new_full(g_str_hash, g_str_equal, |
| g_free, bss_drop_ref); |
| if (task->bss == NULL) { |
| err = -ENOMEM; |
| goto failed; |
| } |
| |
| task->ifindex = connman_device_get_index(device); |
| task->ifname = connman_inet_ifname(task->ifindex); |
| if (task->ifname == NULL) { |
| err = -ENOMEM; |
| goto failed; |
| } |
| |
| task->device = connman_device_ref(device); |
| |
| task->scanning = FALSE; |
| task->state = WPA_INACTIVE; |
| /* NB: ptr's NULL by way of g_try_new0 */ |
| |
| err = interface_create(task); |
| failed: |
| if (err < 0 && err != -EINPROGRESS) |
| free_task(task); |
| return err; |
| } |
| |
| static int supplicant_device_disable(struct connman_device *device) |
| { |
| int index = connman_device_get_index(device); |
| struct supplicant_task *task; |
| |
| _DBG_WIFI("device %p", device); |
| |
| task = find_task_by_index(index); |
| if (task == NULL) |
| return -ENODEV; |
| /* |
| * NB: remove the task so nothing happens asynchronously |
| * (e.g. via D-Bus msgs) while we tear down the state. |
| */ |
| task_list = g_slist_remove(task_list, task); |
| if (task->scan_call != NULL) { |
| dbus_pending_call_cancel(task->scan_call); |
| task->scan_call = NULL; |
| } |
| |
| connman_device_set_scanning_state(task->device, FALSE); |
| |
| /* TODO(sleffler) reclaim local state on failure */ |
| return interface_remove(task); |
| } |
| |
| static int supplicant_device_scan(struct connman_device *device) |
| { |
| int index = connman_device_get_index(device); |
| struct supplicant_task *task; |
| int err; |
| |
| _DBG_WIFI("device %p", device); |
| |
| task = find_task_by_index(index); |
| if (task == NULL) |
| return -ENODEV; |
| |
| switch (task->state) { |
| case WPA_SCANNING: |
| return -EALREADY; |
| case WPA_ASSOCIATING: |
| case WPA_ASSOCIATED: |
| case WPA_4WAY_HANDSHAKE: |
| case WPA_GROUP_HANDSHAKE: |
| return -EBUSY; |
| default: |
| break; |
| } |
| |
| err = interface_scan(task); |
| return (err == -EINPROGRESS ? 0 : err); |
| } |
| |
| static struct connman_device_driver supplicant_device_driver = { |
| .name = "wifi", |
| .type = CONNMAN_DEVICE_TYPE_WIFI, |
| .probe = supplicant_device_probe, |
| .enable = supplicant_device_enable, |
| .disable = supplicant_device_disable, |
| .scan = supplicant_device_scan, |
| }; |
| |
| /* |
| * Connman network driver support. |
| */ |
| |
| static int supplicant_network_probe(struct connman_network *network) |
| { |
| return 0; |
| } |
| |
| static void supplicant_network_remove(struct connman_network *network) |
| { |
| } |
| |
| static int task_connect(struct supplicant_task *task, |
| struct connman_network *network) |
| { |
| const char *security, *service_path; |
| const void *ssid; |
| unsigned int ssid_len; |
| gpointer handle; |
| int err; |
| |
| security = connman_network_get_string(network, "WiFi.Security"); |
| ssid = connman_network_get_blob(network, "WiFi.SSID", &ssid_len); |
| |
| task_info(task, "connect SSID \"%.*s\" BSSID %s security %s", |
| ssid_len, ssid, connman_network_get_string(network, "Address"), |
| security); |
| |
| /* |
| * Validate security credentials here; doing it later when |
| * constructing netblock contents results in side effects |
| * or requires complicated back out of state. |
| */ |
| err = 0; |
| if (security == NULL) |
| err = -EINVAL; |
| else if (g_ascii_strcasecmp(security, "802_1x") == 0) |
| err = check_8021x(network); |
| else if (g_ascii_strcasecmp(security, "psk") == 0 || |
| g_ascii_strcasecmp(security, "wpa") == 0 || |
| g_ascii_strcasecmp(security, "rsn") == 0) |
| err = check_psk(network); |
| else if (is_dynamic_wep(security, network) == TRUE) |
| err = check_8021x(network); |
| else if (g_ascii_strcasecmp(security, "wep") == 0) |
| err = check_wep(network); |
| if (err != 0) { |
| _DBG_WIFI("security failed, network %p security %s err %d", |
| network, security, err); |
| return err; |
| } |
| |
| handle = network_get_handle(task, network, &service_path); |
| if (handle != NULL) { |
| if (service_path == NULL) { |
| _DBG_WIFI("no service for network %p", network); |
| return -EINVAL; |
| } |
| |
| /* XXX hack to work around network_set_properties not working */ |
| network_remove(task, handle); |
| g_hash_table_remove(task->netblocks, service_path); |
| handle = NULL; |
| } |
| if (handle == NULL) { |
| /* |
| * No supplicant network block; setup one. |
| */ |
| handle = network_add(task, network); |
| if (handle == NULL) { |
| _DBG_WIFI("no handle for network %p service path %s", |
| network, service_path); |
| return -EIO; |
| } |
| |
| _DBG_WIFI("add service %s handle %s", service_path, |
| (const char *) handle); |
| /* NB: copy service_path as it may get free'd */ |
| g_hash_table_insert(task->netblocks, |
| (gpointer) g_strdup(service_path), handle); |
| } else { |
| /* |
| * Re-write the netblock properties. |
| * TODO(sleffler) maybe optimize update |
| */ |
| network_set_properties(task, network, handle); |
| } |
| |
| /* select network block; the supplicant will automatically connect */ |
| err = network_select(task, handle); |
| if (err < 0) { |
| _DBG_WIFI("select failed, err %d", err); |
| return err; |
| } |
| |
| /* committed, hold a reference to the network */ |
| task->network = connman_network_ref(network); |
| _DBG_WIFI("network %p", task->network); |
| return -EINPROGRESS; |
| } |
| |
| static int supplicant_network_connect(struct connman_network *network) |
| { |
| struct supplicant_task *task; |
| int index = connman_network_get_index(network); |
| |
| task = find_task_by_index(index); |
| if (task == NULL) { |
| _DBG_WIFI("network %p no task", network); |
| return -ENODEV; |
| } |
| |
| if (task->network != NULL) { |
| if (task->network == network) |
| return -EINPROGRESS; |
| task_error(task, "%s: request %p but %p already connecting", |
| __func__, network, task->network); |
| return -EINVAL; |
| } |
| |
| connman_network_set_connecting(network); |
| |
| return task_connect(task, network); |
| } |
| |
| static int supplicant_network_disconnect(struct connman_network *network) |
| { |
| struct supplicant_task *task; |
| int index = connman_network_get_index(network); |
| void *handle; |
| |
| task = find_task_by_index(index); |
| if (task == NULL) |
| return -ENODEV; |
| |
| _DBG_WIFI("network %p current (bss %p network %p)", network, |
| task->current_bss, task->network); |
| |
| if (task->current_bss != network && task->network != network) { |
| /* |
| * Should not happen. We were asked to disconnect a network |
| * that is not currently active. Just return 0 so the core |
| * will mark it disconnected. This indicates a problem in |
| * the upper layers. |
| */ |
| task_error(task, "%s: network %p not active " |
| "(bss %p network %p)", __func__, network, |
| task->current_bss, task->network); |
| return 0; |
| } |
| |
| connman_network_set_disconnecting(network); |
| |
| if (task->network == network) { |
| /* NB: immediately clear state since we may not be notified */ |
| connman_network_unref(task->network); |
| task->network = NULL; |
| } |
| |
| if (interface_disconnect(task) < 0) { |
| /* |
| * Disable any netblock so we don't continue scanning. |
| */ |
| handle = network_get_handle(task, network, NULL); |
| if (handle != NULL) |
| network_disable(task, handle); |
| /* |
| * Clear our state in case of an error. Should not happen. |
| * TODO(sleffler) add diagnostic |
| */ |
| if (task->current_bss == network) |
| unref_current_bss(task); |
| } |
| /* NB: caller marks network disconnected on 0 return */ |
| return 0; |
| } |
| |
| static struct connman_network_driver supplicant_network_driver = { |
| .name = "wifi", |
| .type = CONNMAN_NETWORK_TYPE_WIFI, |
| .probe = supplicant_network_probe, |
| .remove = supplicant_network_remove, |
| .connect = supplicant_network_connect, |
| .disconnect = supplicant_network_disconnect, |
| }; |
| |
| /* |
| * Resume notifier callback. |
| */ |
| static void supplicant_system_resume_handler(void) |
| { |
| time_t now = time(0); |
| GSList *list; |
| |
| _DBG_WIFI("system resuming"); |
| |
| /* |
| * Handle resume. There are two tasks we need to perform: |
| * re-association to an AP (or roam to a new AP), and flush |
| * stale entries from supplicant's BSS cache. |
| * |
| * For re-association/roaming there are three cases: |
| * 1) we resume in range of the same AP and fast enough that |
| * the AP has not dropped us due to inactivity |
| * 2) we resume in range of the same AP but slow enough that |
| * the AP has dropped us due to inactivity |
| * 3) we resume out of range of the AP |
| * |
| * For 1) the kernel will probe the AP and user space knows |
| * nothing. For 2) supplicant notifies us the AP deauth/disassoc'd |
| * us and it will immediately scan. For 3) supplicant notifies |
| * us the connection was lost and will immediately scan. |
| * |
| * For 2) and 3) we use the BSS change signal to trigger autoconnect |
| * and either re-join the same AP or roam to a new AP. |
| * |
| * BSS cache flushing requires more care. We don't immediately |
| * flush the cache because this may conflict with a scan and/or |
| * connect request (for the latter we race against connect and |
| * may cause the request to be aborted). Instead we mark each |
| * task so a flush is done at the completion of the next scan. |
| * In cases 2) and 3) the flush will happen on scan complete. |
| * In case 1) the flush will happen at some later time but because |
| * we flush cache entries using an age that is calculated based |
| * on the resume time this is ok (typically it is a noop). |
| */ |
| for (list = task_list; list; list = list->next) { |
| struct supplicant_task *task = list->data; |
| /* |
| * Schedule the flush on completion of the next scan. |
| */ |
| task->resume_time = now; |
| task->flush_pending = TRUE; |
| |
| /* |
| * If we are not connected or trying to connect, trigger |
| * a scan since we may now be in range of a network we can |
| * autoconnect to but nothing will happen w/o scan results. |
| */ |
| if (task->current_bss == NULL && task->network == NULL) |
| (void) interface_scan(task); |
| } |
| } |
| |
| /* |
| * Country code changed notifier callback. |
| */ |
| static void supplicant_country_changed_handler(const char *country) |
| { |
| GSList *list; |
| |
| _DBG_WIFI("country %s", country); |
| |
| for (list = task_list; list; list = list->next) { |
| struct supplicant_task *task = list->data; |
| |
| interface_set_country(task, country); |
| } |
| } |
| |
| /* |
| * Profile pop notifier callback |
| * |
| * When a profile is removed, we want wpa_supplicant to remove any |
| * any cache credentials which it derived from previous connections. |
| */ |
| static void supplicant_profile_pop_handler(struct connman_profile *profile) |
| { |
| GSList *list; |
| |
| _DBG_WIFI("profile %p", profile); |
| |
| for (list = task_list; list; list = list->next) { |
| struct supplicant_task *task = list->data; |
| |
| interface_clear_cached_credentials(task); |
| } |
| } |
| |
| static struct connman_notifier wifi_notifier = { |
| .name = "wifi", |
| .priority = CONNMAN_NOTIFIER_PRIORITY_DEFAULT, |
| .system_resume = supplicant_system_resume_handler, |
| .country_changed = supplicant_country_changed_handler, |
| .profile_pop = supplicant_profile_pop_handler, |
| }; |
| |
| /* |
| * D-bus service watch callbacks. |
| */ |
| |
| static void supplicant_probe(DBusConnection *conn, void *user_data) |
| { |
| _DBG_WIFI("conn %p", conn); |
| |
| if (connman_device_driver_register(&supplicant_device_driver) < 0) |
| connman_error("Failed to register WiFi driver"); |
| } |
| |
| static void supplicant_remove(DBusConnection *conn, void *user_data) |
| { |
| _DBG_WIFI("conn %p", conn); |
| |
| connman_device_driver_unregister(&supplicant_device_driver); |
| } |
| |
| static const char *interface_rule = "type=signal," |
| "interface=" SUPPLICANT_INTERFACE_INTF; |
| static const char *bss_rule = "type=signal," |
| "interface=" SUPPLICANT_BSS_INTF; |
| static guint watch; |
| |
| static void new_wifi_finis(void); |
| |
| static int new_wifi_init(void) |
| { |
| DBusMessage *message; |
| int err; |
| |
| connection = connman_dbus_get_connection(); |
| if (connection == NULL) { |
| connman_error("%s: cannot get dbus connection", __func__); |
| return -EIO; |
| } |
| |
| _DBG_WIFI("connection %p", connection); |
| |
| if (dbus_connection_add_filter(connection, |
| supplicant_filter, NULL, NULL) == FALSE) { |
| connman_error("%s: cannot add dbus filter", __func__); |
| dbus_connection_unref(connection); |
| connection = NULL; |
| return -EIO; |
| } |
| |
| dbus_bus_add_match(connection, interface_rule, NULL); |
| dbus_bus_add_match(connection, bss_rule, NULL); |
| dbus_connection_flush(connection); |
| |
| watch = g_dbus_add_service_watch(connection, SUPPLICANT_NAME, |
| supplicant_probe, supplicant_remove, NULL, NULL); |
| |
| message = dbus_message_new_method_call(SUPPLICANT_NAME, "/", |
| DBUS_INTERFACE_INTROSPECTABLE, "Introspect"); |
| if (message != NULL) { |
| dbus_message_set_no_reply(message, TRUE); |
| dbus_connection_send(connection, message, NULL); |
| dbus_message_unref(message); |
| } |
| |
| err = connman_network_driver_register(&supplicant_network_driver); |
| if (err < 0) { |
| connman_error("%s: cannot register network driver (err %d)", |
| __func__, err); |
| new_wifi_finis(); |
| return err; |
| } |
| |
| err = connman_notifier_register(&wifi_notifier); |
| if (err < 0) { |
| connman_error("%s: cannot register wifi notifier (err %d)", |
| __func__, err); |
| new_wifi_finis(); |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| static void new_wifi_finis(void) |
| { |
| _DBG_WIFI("connection %p", connection); |
| |
| connman_device_driver_unregister(&supplicant_device_driver); |
| connman_notifier_unregister(&wifi_notifier); |
| connman_network_driver_unregister(&supplicant_network_driver); |
| |
| if (watch > 0) |
| g_dbus_remove_watch(connection, watch); |
| |
| dbus_bus_remove_match(connection, bss_rule, NULL); |
| dbus_bus_remove_match(connection, interface_rule, NULL); |
| dbus_connection_flush(connection); |
| dbus_connection_remove_filter(connection, supplicant_filter, NULL); |
| dbus_connection_unref(connection); |
| connection = NULL; |
| } |
| |
| CONNMAN_PLUGIN_DEFINE(newwifi, "New WiFi interface", VERSION, |
| CONNMAN_PLUGIN_PRIORITY_DEFAULT, new_wifi_init, new_wifi_finis) |