/* * * Connection Manager * * Copyright (C) 2007-2012 Intel Corporation. All rights reserved. * * 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 #endif #include #include #include #include #include #include #include "supplicant-dbus.h" #include "supplicant.h" #define DBG(fmt, arg...) do { \ syslog(LOG_DEBUG, "%s() " fmt, __FUNCTION__ , ## arg); \ } while (0) #define TIMEOUT 5000 #define IEEE80211_CAP_ESS 0x0001 #define IEEE80211_CAP_IBSS 0x0002 #define IEEE80211_CAP_PRIVACY 0x0010 static DBusConnection *connection; static const struct supplicant_callbacks *callbacks_pointer; static dbus_bool_t system_available = FALSE; static dbus_bool_t system_ready = FALSE; static dbus_int32_t debug_level = 0; static dbus_bool_t debug_timestamp = FALSE; static dbus_bool_t debug_showkeys = FALSE; static const char *debug_strings[] = { "msgdump", "debug", "info", "warning", "error", NULL }; static unsigned int eap_methods; struct strvalmap { const char *str; unsigned int val; }; static struct strvalmap eap_method_map[] = { { "MD5", SUPPLICANT_EAP_METHOD_MD5 }, { "TLS", SUPPLICANT_EAP_METHOD_TLS }, { "MSCHAPV2", SUPPLICANT_EAP_METHOD_MSCHAPV2 }, { "PEAP", SUPPLICANT_EAP_METHOD_PEAP }, { "TTLS", SUPPLICANT_EAP_METHOD_TTLS }, { "GTC", SUPPLICANT_EAP_METHOD_GTC }, { "OTP", SUPPLICANT_EAP_METHOD_OTP }, { "LEAP", SUPPLICANT_EAP_METHOD_LEAP }, { "WSC", SUPPLICANT_EAP_METHOD_WSC }, { } }; static struct strvalmap keymgmt_capa_map[] = { { "none", SUPPLICANT_CAPABILITY_KEYMGMT_NONE }, { "ieee8021x", SUPPLICANT_CAPABILITY_KEYMGMT_IEEE8021X }, { "wpa-none", SUPPLICANT_CAPABILITY_KEYMGMT_WPA_NONE }, { "wpa-psk", SUPPLICANT_CAPABILITY_KEYMGMT_WPA_PSK }, { "wpa-eap", SUPPLICANT_CAPABILITY_KEYMGMT_WPA_EAP }, { "wps", SUPPLICANT_CAPABILITY_KEYMGMT_WPS }, { } }; static struct strvalmap authalg_capa_map[] = { { "open", SUPPLICANT_CAPABILITY_AUTHALG_OPEN }, { "shared", SUPPLICANT_CAPABILITY_AUTHALG_SHARED }, { "leap", SUPPLICANT_CAPABILITY_AUTHALG_LEAP }, { } }; static struct strvalmap proto_capa_map[] = { { "wpa", SUPPLICANT_CAPABILITY_PROTO_WPA }, { "rsn", SUPPLICANT_CAPABILITY_PROTO_RSN }, { } }; static struct strvalmap group_capa_map[] = { { "wep40", SUPPLICANT_CAPABILITY_GROUP_WEP40 }, { "wep104", SUPPLICANT_CAPABILITY_GROUP_WEP104 }, { "tkip", SUPPLICANT_CAPABILITY_GROUP_TKIP }, { "ccmp", SUPPLICANT_CAPABILITY_GROUP_CCMP }, { } }; static struct strvalmap pairwise_capa_map[] = { { "none", SUPPLICANT_CAPABILITY_PAIRWISE_NONE }, { "tkip", SUPPLICANT_CAPABILITY_PAIRWISE_TKIP }, { "ccmp", SUPPLICANT_CAPABILITY_PAIRWISE_CCMP }, { } }; static struct strvalmap scan_capa_map[] = { { "active", SUPPLICANT_CAPABILITY_SCAN_ACTIVE }, { "passive", SUPPLICANT_CAPABILITY_SCAN_PASSIVE }, { "ssid", SUPPLICANT_CAPABILITY_SCAN_SSID }, { } }; static struct strvalmap mode_capa_map[] = { { "infrastructure", SUPPLICANT_CAPABILITY_MODE_INFRA }, { "ad-hoc", SUPPLICANT_CAPABILITY_MODE_IBSS }, { "ap", SUPPLICANT_CAPABILITY_MODE_AP }, { } }; static GHashTable *interface_table; static GHashTable *bss_mapping; struct supplicant_interface { char *path; unsigned int keymgmt_capa; unsigned int authalg_capa; unsigned int proto_capa; unsigned int group_capa; unsigned int pairwise_capa; unsigned int scan_capa; unsigned int mode_capa; dbus_bool_t ready; enum supplicant_state state; dbus_bool_t scanning; supplicant_interface_scan_callback scan_callback; void *scan_data; int apscan; char *ifname; char *driver; char *bridge; GHashTable *network_table; GHashTable *net_mapping; GHashTable *bss_mapping; }; struct supplicant_network { struct supplicant_interface *interface; char *path; char *group; char *name; enum supplicant_mode mode; GHashTable *bss_table; GHashTable *config_table; }; struct supplicant_bss { struct supplicant_interface *interface; char *path; unsigned char bssid[6]; unsigned char ssid[32]; unsigned int ssid_len; dbus_uint16_t frequency; dbus_uint32_t maxrate; enum supplicant_mode mode; enum supplicant_security security; dbus_bool_t privacy; dbus_bool_t psk; dbus_bool_t ieee8021x; }; static enum supplicant_mode string2mode(const char *mode) { if (!mode) return SUPPLICANT_MODE_UNKNOWN; if (g_str_equal(mode, "infrastructure")) return SUPPLICANT_MODE_INFRA; else if (g_str_equal(mode, "ad-hoc")) return SUPPLICANT_MODE_IBSS; return SUPPLICANT_MODE_UNKNOWN; } static const char *mode2string(enum supplicant_mode mode) { switch (mode) { case SUPPLICANT_MODE_UNKNOWN: break; case SUPPLICANT_MODE_INFRA: return "infra"; case SUPPLICANT_MODE_IBSS: return "adhoc"; } return NULL; } static const char *security2string(enum supplicant_security security) { switch (security) { case SUPPLICANT_SECURITY_UNKNOWN: break; case SUPPLICANT_SECURITY_NONE: return "none"; case SUPPLICANT_SECURITY_WEP: return "wep"; case SUPPLICANT_SECURITY_PSK: return "psk"; case SUPPLICANT_SECURITY_IEEE8021X: return "ieee8021x"; } return NULL; } static enum supplicant_state string2state(const char *state) { if (!state) return SUPPLICANT_STATE_UNKNOWN; if (g_str_equal(state, "unknown")) return SUPPLICANT_STATE_UNKNOWN; else if (g_str_equal(state, "disconnected")) return SUPPLICANT_STATE_DISCONNECTED; else if (g_str_equal(state, "inactive")) return SUPPLICANT_STATE_INACTIVE; else if (g_str_equal(state, "scanning")) return SUPPLICANT_STATE_SCANNING; else if (g_str_equal(state, "authenticating")) return SUPPLICANT_STATE_AUTHENTICATING; else if (g_str_equal(state, "associating")) return SUPPLICANT_STATE_ASSOCIATING; else if (g_str_equal(state, "associated")) return SUPPLICANT_STATE_ASSOCIATED; else if (g_str_equal(state, "group_handshake")) return SUPPLICANT_STATE_GROUP_HANDSHAKE; else if (g_str_equal(state, "4way_handshake")) return SUPPLICANT_STATE_4WAY_HANDSHAKE; else if (g_str_equal(state, "completed")) return SUPPLICANT_STATE_COMPLETED; return SUPPLICANT_STATE_UNKNOWN; } static void callback_system_ready(void) { if (system_ready) return; system_ready = TRUE; if (!callbacks_pointer) return; if (!callbacks_pointer->system_ready) return; callbacks_pointer->system_ready(); } static void callback_system_killed(void) { system_ready = FALSE; if (!callbacks_pointer) return; if (!callbacks_pointer->system_killed) return; callbacks_pointer->system_killed(); } static void callback_interface_added(struct supplicant_interface *interface) { if (!callbacks_pointer) return; if (!callbacks_pointer->interface_added) return; callbacks_pointer->interface_added(interface); } static void callback_interface_removed(struct supplicant_interface *interface) { if (!callbacks_pointer) return; if (!callbacks_pointer->interface_removed) return; callbacks_pointer->interface_removed(interface); } static void callback_scan_started(struct supplicant_interface *interface) { if (!callbacks_pointer) return; if (!callbacks_pointer->scan_started) return; callbacks_pointer->scan_started(interface); } static void callback_scan_finished(struct supplicant_interface *interface) { if (!callbacks_pointer) return; if (!callbacks_pointer->scan_finished) return; callbacks_pointer->scan_finished(interface); } static void callback_network_added(struct supplicant_network *network) { if (!callbacks_pointer) return; if (!callbacks_pointer->network_added) return; callbacks_pointer->network_added(network); } static void callback_network_removed(struct supplicant_network *network) { if (!callbacks_pointer) return; if (!callbacks_pointer->network_removed) return; callbacks_pointer->network_removed(network); } static void remove_interface(gpointer data) { struct supplicant_interface *interface = data; g_hash_table_destroy(interface->bss_mapping); g_hash_table_destroy(interface->net_mapping); g_hash_table_destroy(interface->network_table); callback_interface_removed(interface); g_free(interface->path); g_free(interface->ifname); g_free(interface->driver); g_free(interface->bridge); g_free(interface); } static void remove_network(gpointer data) { struct supplicant_network *network = data; g_hash_table_destroy(network->bss_table); callback_network_removed(network); g_hash_table_destroy(network->config_table); g_free(network->group); g_free(network->name); g_free(network); } static void remove_bss(gpointer data) { struct supplicant_bss *bss = data; g_free(bss->path); g_free(bss); } static void debug_strvalmap(const char *label, struct strvalmap *map, unsigned int val) { int i; for (i = 0; map[i].str; i++) { if (val & map[i].val) DBG("%s: %s", label, map[i].str); } } static void interface_capability_keymgmt(DBusMessageIter *iter, void *user_data) { struct supplicant_interface *interface = user_data; const char *str = NULL; int i; dbus_message_iter_get_basic(iter, &str); if (!str) return; for (i = 0; keymgmt_capa_map[i].str; i++) if (strcmp(str, keymgmt_capa_map[i].str) == 0) { interface->keymgmt_capa |= keymgmt_capa_map[i].val; break; } } static void interface_capability_authalg(DBusMessageIter *iter, void *user_data) { struct supplicant_interface *interface = user_data; const char *str = NULL; int i; dbus_message_iter_get_basic(iter, &str); if (!str) return; for (i = 0; authalg_capa_map[i].str; i++) if (strcmp(str, authalg_capa_map[i].str) == 0) { interface->authalg_capa |= authalg_capa_map[i].val; break; } } static void interface_capability_proto(DBusMessageIter *iter, void *user_data) { struct supplicant_interface *interface = user_data; const char *str = NULL; int i; dbus_message_iter_get_basic(iter, &str); if (!str) return; for (i = 0; proto_capa_map[i].str; i++) if (strcmp(str, proto_capa_map[i].str) == 0) { interface->proto_capa |= proto_capa_map[i].val; break; } } static void interface_capability_pairwise(DBusMessageIter *iter, void *user_data) { struct supplicant_interface *interface = user_data; const char *str = NULL; int i; dbus_message_iter_get_basic(iter, &str); if (!str) return; for (i = 0; pairwise_capa_map[i].str; i++) if (strcmp(str, pairwise_capa_map[i].str) == 0) { interface->pairwise_capa |= pairwise_capa_map[i].val; break; } } static void interface_capability_group(DBusMessageIter *iter, void *user_data) { struct supplicant_interface *interface = user_data; const char *str = NULL; int i; dbus_message_iter_get_basic(iter, &str); if (!str) return; for (i = 0; group_capa_map[i].str; i++) if (strcmp(str, group_capa_map[i].str) == 0) { interface->group_capa |= group_capa_map[i].val; break; } } static void interface_capability_scan(DBusMessageIter *iter, void *user_data) { struct supplicant_interface *interface = user_data; const char *str = NULL; int i; dbus_message_iter_get_basic(iter, &str); if (!str) return; for (i = 0; scan_capa_map[i].str; i++) if (strcmp(str, scan_capa_map[i].str) == 0) { interface->scan_capa |= scan_capa_map[i].val; break; } } static void interface_capability_mode(DBusMessageIter *iter, void *user_data) { struct supplicant_interface *interface = user_data; const char *str = NULL; int i; dbus_message_iter_get_basic(iter, &str); if (!str) return; for (i = 0; mode_capa_map[i].str; i++) if (strcmp(str, mode_capa_map[i].str) == 0) { interface->mode_capa |= mode_capa_map[i].val; break; } } static void interface_capability(const char *key, DBusMessageIter *iter, void *user_data) { struct supplicant_interface *interface = user_data; if (!key) return; if (g_strcmp0(key, "KeyMgmt") == 0) supplicant_dbus_array_foreach(iter, interface_capability_keymgmt, interface); else if (g_strcmp0(key, "AuthAlg") == 0) supplicant_dbus_array_foreach(iter, interface_capability_authalg, interface); else if (g_strcmp0(key, "Protocol") == 0) supplicant_dbus_array_foreach(iter, interface_capability_proto, interface); else if (g_strcmp0(key, "Pairwise") == 0) supplicant_dbus_array_foreach(iter, interface_capability_pairwise, interface); else if (g_strcmp0(key, "Group") == 0) supplicant_dbus_array_foreach(iter, interface_capability_group, interface); else if (g_strcmp0(key, "Scan") == 0) supplicant_dbus_array_foreach(iter, interface_capability_scan, interface); else if (g_strcmp0(key, "Modes") == 0) supplicant_dbus_array_foreach(iter, interface_capability_mode, interface); else DBG("key %s type %c", key, dbus_message_iter_get_arg_type(iter)); } const char *supplicant_interface_get_ifname(struct supplicant_interface *interface) { if (!interface) return NULL; return interface->ifname; } const char *supplicant_interface_get_driver(struct supplicant_interface *interface) { if (!interface) return NULL; return interface->driver; } struct supplicant_interface *supplicant_network_get_interface(struct supplicant_network *network) { if (!network) return NULL; return network->interface; } const char *supplicant_network_get_name(struct supplicant_network *network) { if (!network || !network->name) return ""; return network->name; } const char *supplicant_network_get_identifier(struct supplicant_network *network) { if (!network || !network->group) return ""; return network->group; } enum supplicant_mode supplicant_network_get_mode(struct supplicant_network *network) { if (!network) return SUPPLICANT_MODE_UNKNOWN; return network->mode; } static void merge_network(struct supplicant_network *network) { GString *str; const char *ssid, *mode, *key_mgmt; unsigned int i, ssid_len; char *group; ssid = g_hash_table_lookup(network->config_table, "ssid"); mode = g_hash_table_lookup(network->config_table, "mode"); key_mgmt = g_hash_table_lookup(network->config_table, "key_mgmt"); DBG("ssid %s mode %s", ssid, mode); if (ssid) ssid_len = strlen(ssid); else ssid_len = 0; str = g_string_sized_new((ssid_len * 2) + 24); if (!str) return; for (i = 0; i < ssid_len; i++) g_string_append_printf(str, "%02x", ssid[i]); if (g_strcmp0(mode, "0") == 0) g_string_append_printf(str, "_infra"); else if (g_strcmp0(mode, "1") == 0) g_string_append_printf(str, "_adhoc"); if (g_strcmp0(key_mgmt, "WPA-PSK") == 0) g_string_append_printf(str, "_psk"); group = g_string_free(str, FALSE); DBG("%s", group); g_free(group); g_hash_table_destroy(network->config_table); g_free(network->path); g_free(network); } static void network_property(const char *key, DBusMessageIter *iter, void *user_data) { struct supplicant_network *network = user_data; if (!network->interface) return; if (!key) { merge_network(network); return; } if (g_strcmp0(key, "Enabled") == 0) { dbus_bool_t enabled = FALSE; dbus_message_iter_get_basic(iter, &enabled); } else if (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_STRING) { const char *str = NULL; dbus_message_iter_get_basic(iter, &str); if (str) g_hash_table_replace(network->config_table, g_strdup(key), g_strdup(str)); } else DBG("key %s type %c", key, dbus_message_iter_get_arg_type(iter)); } static void interface_network_added(DBusMessageIter *iter, void *user_data) { struct supplicant_interface *interface = user_data; struct supplicant_network *network; const char *path = NULL; dbus_message_iter_get_basic(iter, &path); if (!path) return; if (g_strcmp0(path, "/") == 0) return; network = g_hash_table_lookup(interface->net_mapping, path); if (network) return; network = g_try_new0(struct supplicant_network, 1); if (!network) return; network->interface = interface; network->path = g_strdup(path); network->config_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); dbus_message_iter_next(iter); if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_INVALID) { supplicant_dbus_property_foreach(iter, network_property, network); network_property(NULL, NULL, network); return; } supplicant_dbus_property_get_all(path, SUPPLICANT_INTERFACE ".Interface.Network", network_property, network); } static void interface_network_removed(DBusMessageIter *iter, void *user_data) { struct supplicant_interface *interface = user_data; struct supplicant_network *network; const char *path = NULL; dbus_message_iter_get_basic(iter, &path); if (!path) return; network = g_hash_table_lookup(interface->net_mapping, path); if (!network) return; g_hash_table_remove(interface->net_mapping, path); } static char *create_name(unsigned char *ssid, int ssid_len) { char *name; int i; if (ssid_len < 1 || ssid[0] == '\0') name = NULL; else name = g_try_malloc0(ssid_len + 1); if (!name) return g_strdup(""); for (i = 0; i < ssid_len; i++) { if (g_ascii_isprint(ssid[i])) name[i] = ssid[i]; else name[i] = ' '; } return name; } static char *create_group(struct supplicant_bss *bss) { GString *str; unsigned int i; const char *mode, *security; str = g_string_sized_new((bss->ssid_len * 2) + 24); if (!str) return NULL; if (bss->ssid_len > 0 && bss->ssid[0] != '\0') { for (i = 0; i < bss->ssid_len; i++) g_string_append_printf(str, "%02x", bss->ssid[i]); } else g_string_append_printf(str, "hidden"); mode = mode2string(bss->mode); if (mode) g_string_append_printf(str, "_%s", mode); security = security2string(bss->security); if (security) g_string_append_printf(str, "_%s", security); return g_string_free(str, FALSE); } static void add_bss_to_network(struct supplicant_bss *bss) { struct supplicant_interface *interface = bss->interface; struct supplicant_network *network; char *group; group = create_group(bss); if (!group) return; network = g_hash_table_lookup(interface->network_table, group); if (network) { g_free(group); goto done; } network = g_try_new0(struct supplicant_network, 1); if (!network) { g_free(group); return; } network->group = group; network->name = create_name(bss->ssid, bss->ssid_len); network->mode = bss->mode; network->bss_table = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, remove_bss); network->config_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); g_hash_table_replace(interface->network_table, network->group, network); callback_network_added(network); done: g_hash_table_replace(interface->bss_mapping, bss->path, network); g_hash_table_replace(network->bss_table, bss->path, bss); g_hash_table_replace(bss_mapping, bss->path, interface); } static unsigned char wifi_oui[3] = { 0x00, 0x50, 0xf2 }; static unsigned char ieee80211_oui[3] = { 0x00, 0x0f, 0xac }; static void extract_rsn(struct supplicant_bss *bss, const unsigned char *buf, int len) { uint16_t count; int i; /* Version */ if (len < 2) return; buf += 2; len -= 2; /* Group cipher */ if (len < 4) return; buf += 4; len -= 4; /* Pairwise cipher */ if (len < 2) return; count = buf[0] | (buf[1] << 8); if (2 + (count * 4) > len) return; buf += 2 + (count * 4); len -= 2 + (count * 4); /* Authentication */ if (len < 2) return; count = buf[0] | (buf[1] << 8); if (2 + (count * 4) > len) return; for (i = 0; i < count; i++) { const unsigned char *ptr = buf + 2 + (i * 4); if (memcmp(ptr, wifi_oui, 3) == 0) { switch (ptr[3]) { case 1: bss->ieee8021x = TRUE; break; case 2: bss->psk = TRUE; break; } } else if (memcmp(ptr, ieee80211_oui, 3) == 0) { switch (ptr[3]) { case 1: bss->ieee8021x = TRUE; break; case 2: bss->psk = TRUE; break; } } } } static void bss_rates(DBusMessageIter *iter, void *user_data) { struct supplicant_bss *bss = user_data; dbus_uint32_t rate = 0; dbus_message_iter_get_basic(iter, &rate); if (rate == 0) return; if (rate > bss->maxrate) bss->maxrate = rate; } static void bss_property(const char *key, DBusMessageIter *iter, void *user_data) { struct supplicant_bss *bss = user_data; if (!bss->interface) return; if (!key) { if (bss->ieee8021x) bss->security = SUPPLICANT_SECURITY_IEEE8021X; else if (bss->psk) bss->security = SUPPLICANT_SECURITY_PSK; else if (bss->privacy) bss->security = SUPPLICANT_SECURITY_WEP; else bss->security = SUPPLICANT_SECURITY_NONE; add_bss_to_network(bss); return; } if (g_strcmp0(key, "BSSID") == 0) { DBusMessageIter array; unsigned char *addr; int addr_len; dbus_message_iter_recurse(iter, &array); dbus_message_iter_get_fixed_array(&array, &addr, &addr_len); if (addr_len == 6) memcpy(bss->bssid, addr, addr_len); } else if (g_strcmp0(key, "SSID") == 0) { DBusMessageIter array; unsigned char *ssid; int ssid_len; dbus_message_iter_recurse(iter, &array); dbus_message_iter_get_fixed_array(&array, &ssid, &ssid_len); if (ssid_len > 0 && ssid_len < 33) { memcpy(bss->ssid, ssid, ssid_len); bss->ssid_len = ssid_len; } else { memset(bss->ssid, 0, sizeof(bss->ssid)); bss->ssid_len = 0; } } else if (g_strcmp0(key, "Capabilities") == 0) { dbus_uint16_t capabilities = 0x0000; dbus_message_iter_get_basic(iter, &capabilities); if (capabilities & IEEE80211_CAP_ESS) bss->mode = SUPPLICANT_MODE_INFRA; else if (capabilities & IEEE80211_CAP_IBSS) bss->mode = SUPPLICANT_MODE_IBSS; if (capabilities & IEEE80211_CAP_PRIVACY) bss->privacy = TRUE; } else if (g_strcmp0(key, "Mode") == 0) { const char *mode = NULL; dbus_message_iter_get_basic(iter, &mode); bss->mode = string2mode(mode); } else if (g_strcmp0(key, "Frequency") == 0) { dbus_uint16_t frequency = 0; dbus_message_iter_get_basic(iter, &frequency); bss->frequency = frequency; } else if (g_strcmp0(key, "Signal") == 0) { dbus_int16_t signal = 0; dbus_message_iter_get_basic(iter, &signal); } else if (g_strcmp0(key, "Level") == 0) { dbus_int32_t level = 0; dbus_message_iter_get_basic(iter, &level); } else if (g_strcmp0(key, "Rates") == 0) { supplicant_dbus_array_foreach(iter, bss_rates, bss); } else if (g_strcmp0(key, "MaxRate") == 0) { dbus_uint32_t maxrate = 0; dbus_message_iter_get_basic(iter, &maxrate); if (maxrate != 0) bss->maxrate =maxrate; } else if (g_strcmp0(key, "Privacy") == 0) { dbus_bool_t privacy = FALSE; dbus_message_iter_get_basic(iter, &privacy); bss->privacy = privacy; } else if (g_strcmp0(key, "RSNIE") == 0) { DBusMessageIter array; unsigned char *ie; int ie_len; dbus_message_iter_recurse(iter, &array); dbus_message_iter_get_fixed_array(&array, &ie, &ie_len); if (ie_len > 2) extract_rsn(bss, ie + 2, ie_len - 2); } else if (g_strcmp0(key, "WPAIE") == 0) { DBusMessageIter array; unsigned char *ie; int ie_len; dbus_message_iter_recurse(iter, &array); dbus_message_iter_get_fixed_array(&array, &ie, &ie_len); if (ie_len > 6) extract_rsn(bss, ie + 6, ie_len - 6); } else if (g_strcmp0(key, "WPSIE") == 0) { DBusMessageIter array; unsigned char *ie; int ie_len; dbus_message_iter_recurse(iter, &array); dbus_message_iter_get_fixed_array(&array, &ie, &ie_len); } else DBG("key %s type %c", key, dbus_message_iter_get_arg_type(iter)); } static void interface_bss_added(DBusMessageIter *iter, void *user_data) { struct supplicant_interface *interface = user_data; struct supplicant_network *network; struct supplicant_bss *bss; const char *path = NULL; dbus_message_iter_get_basic(iter, &path); if (!path) return; if (g_strcmp0(path, "/") == 0) return; network = g_hash_table_lookup(interface->bss_mapping, path); if (network) { bss = g_hash_table_lookup(network->bss_table, path); if (bss) return; } bss = g_try_new0(struct supplicant_bss, 1); if (!bss) return; bss->interface = interface; bss->path = g_strdup(path); dbus_message_iter_next(iter); if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_INVALID) { supplicant_dbus_property_foreach(iter, bss_property, bss); bss_property(NULL, NULL, bss); return; } supplicant_dbus_property_get_all(path, SUPPLICANT_INTERFACE ".Interface.BSS", bss_property, bss); } static void interface_bss_removed(DBusMessageIter *iter, void *user_data) { struct supplicant_interface *interface = user_data; struct supplicant_network *network; const char *path = NULL; dbus_message_iter_get_basic(iter, &path); if (!path) return; network = g_hash_table_lookup(interface->bss_mapping, path); if (!network) return; g_hash_table_remove(bss_mapping, path); g_hash_table_remove(interface->bss_mapping, path); g_hash_table_remove(network->bss_table, path); if (g_hash_table_size(network->bss_table) == 0) g_hash_table_remove(interface->network_table, network->group); } static void interface_property(const char *key, DBusMessageIter *iter, void *user_data) { struct supplicant_interface *interface = user_data; if (!interface) return; if (!key) { debug_strvalmap("KeyMgmt capability", keymgmt_capa_map, interface->keymgmt_capa); debug_strvalmap("AuthAlg capability", authalg_capa_map, interface->authalg_capa); debug_strvalmap("Protocol capability", proto_capa_map, interface->proto_capa); debug_strvalmap("Pairwise capability", pairwise_capa_map, interface->pairwise_capa); debug_strvalmap("Group capability", group_capa_map, interface->group_capa); debug_strvalmap("Scan capability", scan_capa_map, interface->scan_capa); debug_strvalmap("Mode capability", mode_capa_map, interface->mode_capa); interface->ready = TRUE; callback_interface_added(interface); return; } if (g_strcmp0(key, "Capabilities") == 0) { supplicant_dbus_property_foreach(iter, interface_capability, interface); } else if (g_strcmp0(key, "State") == 0) { const char *str = NULL; dbus_message_iter_get_basic(iter, &str); if (str) interface->state = string2state(str); DBG("state %s (%d)", str, interface->state); } else if (g_strcmp0(key, "Scanning") == 0) { dbus_bool_t scanning = FALSE; dbus_message_iter_get_basic(iter, &scanning); interface->scanning = scanning; if (interface->ready) { if (interface->scanning) callback_scan_started(interface); else callback_scan_finished(interface); } } else if (g_strcmp0(key, "ApScan") == 0) { int apscan = 1; dbus_message_iter_get_basic(iter, &apscan); interface->apscan = apscan; } else if (g_strcmp0(key, "Ifname") == 0) { const char *str = NULL; dbus_message_iter_get_basic(iter, &str); if (str) interface->ifname = g_strdup(str); } else if (g_strcmp0(key, "Driver") == 0) { const char *str = NULL; dbus_message_iter_get_basic(iter, &str); if (str) interface->driver = g_strdup(str); } else if (g_strcmp0(key, "BridgeIfname") == 0) { const char *str = NULL; dbus_message_iter_get_basic(iter, &str); if (str) interface->bridge = g_strdup(str); } else if (g_strcmp0(key, "CurrentBSS") == 0) { interface_bss_added(iter, interface); } else if (g_strcmp0(key, "CurrentNetwork") == 0) { interface_network_added(iter, interface); } else if (g_strcmp0(key, "BSSs") == 0) { supplicant_dbus_array_foreach(iter, interface_bss_added, interface); } else if (g_strcmp0(key, "Blobs") == 0) { } else if (g_strcmp0(key, "Networks") == 0) { supplicant_dbus_array_foreach(iter, interface_network_added, interface); } else DBG("key %s type %c", key, dbus_message_iter_get_arg_type(iter)); } static struct supplicant_interface *interface_alloc(const char *path) { struct supplicant_interface *interface; interface = g_try_new0(struct supplicant_interface, 1); if (!interface) return NULL; interface->path = g_strdup(path); interface->network_table = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, remove_network); interface->net_mapping = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); interface->bss_mapping = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); g_hash_table_replace(interface_table, interface->path, interface); return interface; } static void interface_added(DBusMessageIter *iter, void *user_data) { struct supplicant_interface *interface; const char *path = NULL; dbus_message_iter_get_basic(iter, &path); if (!path) return; if (g_strcmp0(path, "/") == 0) return; interface = g_hash_table_lookup(interface_table, path); if (interface) return; interface = interface_alloc(path); if (!interface) return; dbus_message_iter_next(iter); if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_INVALID) { supplicant_dbus_property_foreach(iter, interface_property, interface); interface_property(NULL, NULL, interface); return; } supplicant_dbus_property_get_all(path, SUPPLICANT_INTERFACE ".Interface", interface_property, interface); } static void interface_removed(DBusMessageIter *iter, void *user_data) { const char *path = NULL; dbus_message_iter_get_basic(iter, &path); if (!path) return; g_hash_table_remove(interface_table, path); } static void eap_method(DBusMessageIter *iter, void *user_data) { const char *str = NULL; int i; dbus_message_iter_get_basic(iter, &str); if (!str) return; for (i = 0; eap_method_map[i].str; i++) if (strcmp(str, eap_method_map[i].str) == 0) { eap_methods |= eap_method_map[i].val; break; } } static void service_property(const char *key, DBusMessageIter *iter, void *user_data) { if (!key) { callback_system_ready(); return; } if (g_strcmp0(key, "DebugLevel") == 0) { const char *str = NULL; int i; dbus_message_iter_get_basic(iter, &str); for (i = 0; debug_strings[i]; i++) if (g_strcmp0(debug_strings[i], str) == 0) { debug_level = i; break; } DBG("Debug level %d", debug_level); } else if (g_strcmp0(key, "DebugTimestamp") == 0) { dbus_message_iter_get_basic(iter, &debug_timestamp); DBG("Debug timestamp %u", debug_timestamp); } else if (g_strcmp0(key, "DebugShowKeys") == 0) { dbus_message_iter_get_basic(iter, &debug_showkeys); DBG("Debug show keys %u", debug_showkeys); } else if (g_strcmp0(key, "Interfaces") == 0) { supplicant_dbus_array_foreach(iter, interface_added, NULL); } else if (g_strcmp0(key, "EapMethods") == 0) { supplicant_dbus_array_foreach(iter, eap_method, NULL); debug_strvalmap("EAP method", eap_method_map, eap_methods); } else DBG("key %s type %c", key, dbus_message_iter_get_arg_type(iter)); } static void supplicant_bootstrap(void) { supplicant_dbus_property_get_all(SUPPLICANT_PATH, SUPPLICANT_INTERFACE, service_property, NULL); } static void signal_name_owner_changed(const char *path, DBusMessageIter *iter) { const char *name = NULL, *old = NULL, *new = NULL; if (g_strcmp0(path, DBUS_PATH_DBUS) != 0) return; dbus_message_iter_get_basic(iter, &name); if (!name) return; if (g_strcmp0(name, SUPPLICANT_SERVICE) != 0) return; dbus_message_iter_next(iter); dbus_message_iter_get_basic(iter, &old); dbus_message_iter_next(iter); dbus_message_iter_get_basic(iter, &new); if (!old || !new) return; if (strlen(old) > 0 && strlen(new) == 0) { system_available = FALSE; g_hash_table_remove_all(bss_mapping); g_hash_table_remove_all(interface_table); callback_system_killed(); } if (strlen(new) > 0 && strlen(old) == 0) { system_available = TRUE; supplicant_bootstrap(); } } static void signal_properties_changed(const char *path, DBusMessageIter *iter) { if (g_strcmp0(path, SUPPLICANT_PATH) != 0) return; supplicant_dbus_property_foreach(iter, service_property, NULL); } static void signal_interface_added(const char *path, DBusMessageIter *iter) { if (g_strcmp0(path, SUPPLICANT_PATH) == 0) interface_added(iter, NULL); } static void signal_interface_removed(const char *path, DBusMessageIter *iter) { if (g_strcmp0(path, SUPPLICANT_PATH) == 0) interface_removed(iter, NULL); } static void signal_interface_changed(const char *path, DBusMessageIter *iter) { struct supplicant_interface *interface; interface = g_hash_table_lookup(interface_table, path); if (!interface) return; supplicant_dbus_property_foreach(iter, interface_property, interface); } static void signal_scan_done(const char *path, DBusMessageIter *iter) { struct supplicant_interface *interface; dbus_bool_t success = FALSE; interface = g_hash_table_lookup(interface_table, path); if (!interface) return; dbus_message_iter_get_basic(iter, &success); if (interface->scan_callback) { int result = 0; if (!success) result = -EIO; interface->scan_callback(result, interface->scan_data); } interface->scan_callback = NULL; interface->scan_data = NULL; } static void signal_bss_added(const char *path, DBusMessageIter *iter) { struct supplicant_interface *interface; interface = g_hash_table_lookup(interface_table, path); if (!interface) return; interface_bss_added(iter, interface); } static void signal_bss_removed(const char *path, DBusMessageIter *iter) { struct supplicant_interface *interface; interface = g_hash_table_lookup(interface_table, path); if (!interface) return; interface_bss_removed(iter, interface); } static void signal_network_added(const char *path, DBusMessageIter *iter) { struct supplicant_interface *interface; interface = g_hash_table_lookup(interface_table, path); if (!interface) return; interface_network_added(iter, interface); } static void signal_network_removed(const char *path, DBusMessageIter *iter) { struct supplicant_interface *interface; interface = g_hash_table_lookup(interface_table, path); if (!interface) return; interface_network_removed(iter, interface); } static void signal_bss_changed(const char *path, DBusMessageIter *iter) { struct supplicant_interface *interface; struct supplicant_network *network; struct supplicant_bss *bss; interface = g_hash_table_lookup(bss_mapping, path); if (!interface) return; network = g_hash_table_lookup(interface->bss_mapping, path); if (!network) return; bss = g_hash_table_lookup(network->bss_table, path); if (!bss) return; supplicant_dbus_property_foreach(iter, bss_property, bss); } static struct { const char *interface; const char *member; void (*function) (const char *path, DBusMessageIter *iter); } signal_map[] = { { DBUS_INTERFACE_DBUS, "NameOwnerChanged", signal_name_owner_changed }, { SUPPLICANT_INTERFACE, "PropertiesChanged", signal_properties_changed }, { SUPPLICANT_INTERFACE, "InterfaceAdded", signal_interface_added }, { SUPPLICANT_INTERFACE, "InterfaceCreated", signal_interface_added }, { SUPPLICANT_INTERFACE, "InterfaceRemoved", signal_interface_removed }, { SUPPLICANT_INTERFACE ".Interface", "PropertiesChanged", signal_interface_changed }, { SUPPLICANT_INTERFACE ".Interface", "ScanDone", signal_scan_done }, { SUPPLICANT_INTERFACE ".Interface", "BSSAdded", signal_bss_added }, { SUPPLICANT_INTERFACE ".Interface", "BSSRemoved", signal_bss_removed }, { SUPPLICANT_INTERFACE ".Interface", "NetworkAdded", signal_network_added }, { SUPPLICANT_INTERFACE ".Interface", "NetworkRemoved", signal_network_removed }, { SUPPLICANT_INTERFACE ".Interface.BSS", "PropertiesChanged", signal_bss_changed }, { } }; static DBusHandlerResult supplicant_filter(DBusConnection *conn, DBusMessage *message, void *data) { DBusMessageIter iter; const char *path; int i; path = dbus_message_get_path(message); if (!path) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; if (!dbus_message_iter_init(message, &iter)) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; for (i = 0; signal_map[i].interface; i++) { if (!dbus_message_has_interface(message, signal_map[i].interface)) continue; if (!dbus_message_has_member(message, signal_map[i].member)) continue; signal_map[i].function(path, &iter); break; } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static const char *supplicant_rule0 = "type=signal," "path=" DBUS_PATH_DBUS "," "sender=" DBUS_SERVICE_DBUS "," "interface=" DBUS_INTERFACE_DBUS "," "member=NameOwnerChanged," "arg0=" SUPPLICANT_SERVICE; static const char *supplicant_rule1 = "type=signal," "interface=" SUPPLICANT_INTERFACE; static const char *supplicant_rule2 = "type=signal," "interface=" SUPPLICANT_INTERFACE ".Interface"; static const char *supplicant_rule3 = "type=signal," "interface=" SUPPLICANT_INTERFACE ".Interface.WPS"; static const char *supplicant_rule4 = "type=signal," "interface=" SUPPLICANT_INTERFACE ".Interface.BSS"; static const char *supplicant_rule5 = "type=signal," "interface=" SUPPLICANT_INTERFACE ".Interface.Network"; static const char *supplicant_rule6 = "type=signal," "interface=" SUPPLICANT_INTERFACE ".Interface.Blob"; int supplicant_register(const struct supplicant_callbacks *callbacks) { connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL); if (!connection) return -EIO; if (!dbus_connection_add_filter(connection, supplicant_filter, NULL, NULL)) { dbus_connection_unref(connection); connection = NULL; return -EIO; } callbacks_pointer = callbacks; eap_methods = 0; interface_table = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, remove_interface); bss_mapping = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); supplicant_dbus_setup(connection); dbus_bus_add_match(connection, supplicant_rule0, NULL); dbus_bus_add_match(connection, supplicant_rule1, NULL); dbus_bus_add_match(connection, supplicant_rule2, NULL); dbus_bus_add_match(connection, supplicant_rule3, NULL); dbus_bus_add_match(connection, supplicant_rule4, NULL); dbus_bus_add_match(connection, supplicant_rule5, NULL); dbus_bus_add_match(connection, supplicant_rule6, NULL); dbus_connection_flush(connection); if (dbus_bus_name_has_owner(connection, SUPPLICANT_SERVICE, NULL)) { system_available = TRUE; supplicant_bootstrap(); } return 0; } void supplicant_unregister(const struct supplicant_callbacks *callbacks) { if (connection) { dbus_bus_remove_match(connection, supplicant_rule6, NULL); dbus_bus_remove_match(connection, supplicant_rule5, NULL); dbus_bus_remove_match(connection, supplicant_rule4, NULL); dbus_bus_remove_match(connection, supplicant_rule3, NULL); dbus_bus_remove_match(connection, supplicant_rule2, NULL); dbus_bus_remove_match(connection, supplicant_rule1, NULL); dbus_bus_remove_match(connection, supplicant_rule0, NULL); dbus_connection_flush(connection); dbus_connection_remove_filter(connection, supplicant_filter, NULL); } if (bss_mapping) { g_hash_table_destroy(bss_mapping); bss_mapping = NULL; } if (interface_table) { g_hash_table_destroy(interface_table); interface_table = NULL; } if (system_available) callback_system_killed(); if (connection) { dbus_connection_unref(connection); connection = NULL; } callbacks_pointer = NULL; eap_methods = 0; } static void debug_level_result(const char *error, DBusMessageIter *iter, void *user_data) { if (error) DBG("debug level failure: %s", error); } static void debug_level_params(DBusMessageIter *iter, void *user_data) { guint level = GPOINTER_TO_UINT(user_data); const char *str; if (level > 4) level = 4; str = debug_strings[level]; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &str); } void supplicant_set_debug_level(unsigned int level) { if (!system_available) return; supplicant_dbus_property_set(SUPPLICANT_PATH, SUPPLICANT_INTERFACE, "DebugLevel", DBUS_TYPE_STRING_AS_STRING, debug_level_params, debug_level_result, GUINT_TO_POINTER(level)); } struct interface_create_data { const char *ifname; const char *driver; struct supplicant_interface *interface; supplicant_interface_create_callback callback; void *user_data; }; static void interface_create_property(const char *key, DBusMessageIter *iter, void *user_data) { struct interface_create_data *data = user_data; struct supplicant_interface *interface = data->interface; if (!key) { if (data->callback) data->callback(0, data->interface, data->user_data); dbus_free(data); } interface_property(key, iter, interface); } static void interface_create_result(const char *error, DBusMessageIter *iter, void *user_data) { struct interface_create_data *data = user_data; const char *path = NULL; int err; if (error) { err = -EIO; goto done; } dbus_message_iter_get_basic(iter, &path); if (!path) { err = -EINVAL; goto done; } if (!system_available) { err = -EFAULT; goto done; } data->interface = g_hash_table_lookup(interface_table, path); if (!data->interface) { data->interface = interface_alloc(path); if (!data->interface) { err = -ENOMEM; goto done; } } err = supplicant_dbus_property_get_all(path, SUPPLICANT_INTERFACE ".Interface", interface_create_property, data); if (err == 0) return; done: if (data->callback) data->callback(err, NULL, data->user_data); dbus_free(data); } static void interface_create_params(DBusMessageIter *iter, void *user_data) { struct interface_create_data *data = user_data; DBusMessageIter dict; supplicant_dbus_dict_open(iter, &dict); supplicant_dbus_dict_append_basic(&dict, "Ifname", DBUS_TYPE_STRING, &data->ifname); if (data->driver) supplicant_dbus_dict_append_basic(&dict, "Driver", DBUS_TYPE_STRING, &data->driver); supplicant_dbus_dict_close(iter, &dict); } static void interface_get_result(const char *error, DBusMessageIter *iter, void *user_data) { struct interface_create_data *data = user_data; struct supplicant_interface *interface; const char *path = NULL; int err; if (error) goto create; dbus_message_iter_get_basic(iter, &path); if (!path) { err = -EINVAL; goto done; } interface = g_hash_table_lookup(interface_table, path); if (!interface) { err = -ENOENT; goto done; } if (data->callback) data->callback(0, interface, data->user_data); dbus_free(data); return; create: if (!system_available) { err = -EFAULT; goto done; } err = supplicant_dbus_method_call(SUPPLICANT_PATH, SUPPLICANT_INTERFACE, "CreateInterface", interface_create_params, interface_create_result, data); if (err == 0) return; done: if (data->callback) data->callback(err, NULL, data->user_data); dbus_free(data); } static void interface_get_params(DBusMessageIter *iter, void *user_data) { struct interface_create_data *data = user_data; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &data->ifname); } int supplicant_interface_create(const char *ifname, const char *driver, supplicant_interface_create_callback callback, void *user_data) { struct interface_create_data *data; if (!ifname) return -EINVAL; if (!system_available) return -EFAULT; data = dbus_malloc0(sizeof(*data)); if (!data) return -ENOMEM; data->ifname = ifname; data->driver = driver; data->callback = callback; data->user_data = user_data; return supplicant_dbus_method_call(SUPPLICANT_PATH, SUPPLICANT_INTERFACE, "GetInterface", interface_get_params, interface_get_result, data); } int supplicant_interface_remove(struct supplicant_interface *interface, supplicant_interface_remove_callback callback, void *user_data) { if (!interface) return -EINVAL; if (!system_available) return -EFAULT; return 0; } struct interface_scan_data { struct supplicant_interface *interface; supplicant_interface_scan_callback callback; void *user_data; }; static void interface_scan_result(const char *error, DBusMessageIter *iter, void *user_data) { struct interface_scan_data *data = user_data; if (error) { if (data->callback) data->callback(-EIO, data->user_data); } else { data->interface->scan_callback = data->callback; data->interface->scan_data = data->user_data; } dbus_free(data); } static void interface_scan_params(DBusMessageIter *iter, void *user_data) { DBusMessageIter dict; const char *type = "passive"; supplicant_dbus_dict_open(iter, &dict); supplicant_dbus_dict_append_basic(&dict, "Type", DBUS_TYPE_STRING, &type); supplicant_dbus_dict_close(iter, &dict); } int supplicant_interface_scan(struct supplicant_interface *interface, supplicant_interface_scan_callback callback, void *user_data) { struct interface_scan_data *data; if (!interface) return -EINVAL; if (!system_available) return -EFAULT; if (interface->scanning) return -EALREADY; data = dbus_malloc0(sizeof(*data)); if (!data) return -ENOMEM; data->interface = interface; data->callback = callback; data->user_data = user_data; return supplicant_dbus_method_call(interface->path, SUPPLICANT_INTERFACE ".Interface", "Scan", interface_scan_params, interface_scan_result, data); } struct interface_disconnect_data { supplicant_interface_disconnect_callback callback; void *user_data; }; static void interface_disconnect_result(const char *error, DBusMessageIter *iter, void *user_data) { struct interface_disconnect_data *data = user_data; int result = 0; if (error) result = -EIO; if (data->callback) data->callback(result, data->user_data); dbus_free(data); } int supplicant_interface_disconnect(struct supplicant_interface *interface, supplicant_interface_disconnect_callback callback, void *user_data) { struct interface_disconnect_data *data; if (!interface) return -EINVAL; if (!system_available) return -EFAULT; data = dbus_malloc0(sizeof(*data)); if (!data) return -ENOMEM; data->callback = callback; data->user_data = user_data; return supplicant_dbus_method_call(interface->path, SUPPLICANT_INTERFACE ".Interface", "Disconnect", NULL, interface_disconnect_result, data); }