blob: 5dc23f538dfda4b22cd158ea29e59f9c5a22eabf [file] [log] [blame]
/*
* 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)