diff options
Diffstat (limited to 'src/core/supplicant/nm-supplicant-interface.c')
| -rw-r--r-- | src/core/supplicant/nm-supplicant-interface.c | 3522 |
1 files changed, 3522 insertions, 0 deletions
diff --git a/src/core/supplicant/nm-supplicant-interface.c b/src/core/supplicant/nm-supplicant-interface.c new file mode 100644 index 00000000..6d9c604f --- /dev/null +++ b/src/core/supplicant/nm-supplicant-interface.c @@ -0,0 +1,3522 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2006 - 2017 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#include "src/core/nm-default-daemon.h" + +#include "nm-supplicant-interface.h" + +#include <stdio.h> +#include <linux/if_ether.h> + +#include "NetworkManagerUtils.h" +#include "nm-core-internal.h" +#include "nm-glib-aux/nm-c-list.h" +#include "nm-glib-aux/nm-ref-string.h" +#include "nm-std-aux/nm-dbus-compat.h" +#include "nm-supplicant-config.h" +#include "nm-supplicant-manager.h" +#include "shared/nm-glib-aux/nm-dbus-aux.h" + +#define DBUS_TIMEOUT_MSEC 20000 + +/*****************************************************************************/ + +typedef struct { + NMSupplicantInterface *self; + char * type; + char * bssid; + char * pin; + guint signal_id; + GCancellable * cancellable; + bool needs_cancelling : 1; + bool is_cancelling : 1; +} WpsData; + +struct _AddNetworkData; + +typedef struct { + NMSupplicantInterface * self; + NMSupplicantConfig * cfg; + GCancellable * cancellable; + NMSupplicantInterfaceAssocCb callback; + gpointer user_data; + guint fail_on_idle_id; + guint blobs_left; + guint calls_left; + struct _AddNetworkData * add_network_data; +} AssocData; + +typedef struct _AddNetworkData { + /* the assoc_data at the time when doing the call. */ + AssocData * assoc_data; + NMRefString *name_owner; + NMRefString *object_path; + GObject * shutdown_wait_obj; +} AddNetworkData; + +enum { + STATE, /* change in the interface's state */ + BSS_CHANGED, /* a new BSS appeared, was updated, or was removed. */ + PEER_CHANGED, /* a new Peer appeared, was updated, or was removed */ + WPS_CREDENTIALS, /* WPS credentials received */ + GROUP_STARTED, /* a new Group (interface) was created */ + GROUP_FINISHED, /* a Group (interface) has been finished */ + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = {0}; + +NM_GOBJECT_PROPERTIES_DEFINE(NMSupplicantInterface, + PROP_SUPPLICANT_MANAGER, + PROP_DBUS_OBJECT_PATH, + PROP_IFINDEX, + PROP_P2P_GROUP_JOINED, + PROP_P2P_GROUP_PATH, + PROP_P2P_GROUP_OWNER, + PROP_SCANNING, + PROP_CURRENT_BSS, + PROP_DRIVER, + PROP_P2P_AVAILABLE, + PROP_AUTH_STATE, ); + +typedef struct _NMSupplicantInterfacePrivate { + NMSupplicantManager *supplicant_manager; + + GDBusConnection *dbus_connection; + NMRefString * name_owner; + NMRefString * object_path; + + char *ifname; + + GCancellable *main_cancellable; + + NMRefString *p2p_group_path; + + GCancellable *p2p_group_properties_cancellable; + + WpsData *wps_data; + + AssocData *assoc_data; + + char *net_path; + + char *driver; + + GHashTable *bss_idx; + CList bss_lst_head; + CList bss_initializing_lst_head; + + NMRefString *current_bss; + + GHashTable *peer_idx; + CList peer_lst_head; + CList peer_initializing_lst_head; + + gint64 last_scan_msec; + + NMSupplicantAuthState auth_state; + + NMSupplicantDriver requested_driver; + NMSupplCapMask global_capabilities; + NMSupplCapMask iface_capabilities; + + guint properties_changed_id; + guint signal_id; + guint bss_properties_changed_id; + guint peer_properties_changed_id; + guint p2p_group_properties_changed_id; + + int ifindex; + + int starting_pending_count; + + guint32 max_scan_ssids; + + gint32 disconnect_reason; + + NMSupplicantInterfaceState state; + NMSupplicantInterfaceState supp_state; + + bool scanning_property : 1; + bool scanning_cached : 1; + + bool p2p_capable_property : 1; + bool p2p_capable_cached : 1; + + bool p2p_group_owner_property : 1; + bool p2p_group_owner_cached : 1; + + bool p2p_group_joined_cached : 1; + + bool is_ready_main : 1; + bool is_ready_p2p_device : 1; + + bool prop_scan_active : 1; + bool prop_scan_ssid : 1; + + bool ap_isolate_supported : 1; + bool ap_isolate_needs_reset : 1; +} NMSupplicantInterfacePrivate; + +struct _NMSupplicantInterfaceClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMSupplicantInterface, nm_supplicant_interface, G_TYPE_OBJECT) + +#define NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self) \ + _NM_GET_PRIVATE_PTR(self, NMSupplicantInterface, NM_IS_SUPPLICANT_INTERFACE) + +/*****************************************************************************/ + +static const char * +_log_pretty_object_path(NMSupplicantInterfacePrivate *priv) +{ + const char *s; + + nm_assert(priv); + nm_assert(NM_IS_REF_STRING(priv->object_path)); + + s = priv->object_path->str; + if (NM_STR_HAS_PREFIX(s, "/fi/w1/wpa_supplicant1/Interfaces/")) { + s += NM_STRLEN("/fi/w1/wpa_supplicant1/Interfaces/"); + if (s[0] && s[0] != '/') + return s; + } + return priv->object_path->str; +} + +#define _NMLOG_DOMAIN LOGD_SUPPLICANT +#define _NMLOG_PREFIX_NAME "sup-iface" +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + NMSupplicantInterface * _self = (self); \ + NMSupplicantInterfacePrivate *_priv = \ + _self ? NM_SUPPLICANT_INTERFACE_GET_PRIVATE(_self) : NULL; \ + char _sbuf[255]; \ + const char *_ifname = _priv ? _priv->ifname : NULL; \ + \ + nm_log((level), \ + _NMLOG_DOMAIN, \ + _ifname, \ + NULL, \ + "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + (_self ? nm_sprintf_buf(_sbuf, \ + "[" NM_HASH_OBFUSCATE_PTR_FMT ",%s,%s]", \ + NM_HASH_OBFUSCATE_PTR(_self), \ + _log_pretty_object_path(_priv), \ + _ifname ?: "???") \ + : "") _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static void _starting_check_ready(NMSupplicantInterface *self); + +static void assoc_return(NMSupplicantInterface *self, GError *error, const char *message); + +/*****************************************************************************/ + +NM_UTILS_LOOKUP_STR_DEFINE( + nm_supplicant_interface_state_to_string, + NMSupplicantInterfaceState, + NM_UTILS_LOOKUP_DEFAULT_WARN("internal-unknown"), + NM_UTILS_LOOKUP_STR_ITEM(NM_SUPPLICANT_INTERFACE_STATE_INVALID, "internal-invalid"), + NM_UTILS_LOOKUP_STR_ITEM(NM_SUPPLICANT_INTERFACE_STATE_STARTING, "internal-starting"), + + NM_UTILS_LOOKUP_STR_ITEM(NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE, "4way_handshake"), + NM_UTILS_LOOKUP_STR_ITEM(NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED, "associated"), + NM_UTILS_LOOKUP_STR_ITEM(NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING, "associating"), + NM_UTILS_LOOKUP_STR_ITEM(NM_SUPPLICANT_INTERFACE_STATE_AUTHENTICATING, "authenticating"), + NM_UTILS_LOOKUP_STR_ITEM(NM_SUPPLICANT_INTERFACE_STATE_COMPLETED, "completed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED, "disconnected"), + NM_UTILS_LOOKUP_STR_ITEM(NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE, "group_handshake"), + NM_UTILS_LOOKUP_STR_ITEM(NM_SUPPLICANT_INTERFACE_STATE_INACTIVE, "inactive"), + NM_UTILS_LOOKUP_STR_ITEM(NM_SUPPLICANT_INTERFACE_STATE_DISABLED, "interface_disabled"), + NM_UTILS_LOOKUP_STR_ITEM(NM_SUPPLICANT_INTERFACE_STATE_SCANNING, "scanning"), + + NM_UTILS_LOOKUP_STR_ITEM(NM_SUPPLICANT_INTERFACE_STATE_DOWN, "internal-down"), ); + +static NM_UTILS_STRING_TABLE_LOOKUP_DEFINE( + wpas_state_string_to_enum, + NMSupplicantInterfaceState, + { nm_assert(name); }, + { return NM_SUPPLICANT_INTERFACE_STATE_INVALID; }, + {"4way_handshake", NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE}, + {"associated", NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED}, + {"associating", NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING}, + {"authenticating", NM_SUPPLICANT_INTERFACE_STATE_AUTHENTICATING}, + {"completed", NM_SUPPLICANT_INTERFACE_STATE_COMPLETED}, + {"disconnected", NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED}, + {"group_handshake", NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE}, + {"inactive", NM_SUPPLICANT_INTERFACE_STATE_INACTIVE}, + {"interface_disabled", NM_SUPPLICANT_INTERFACE_STATE_DISABLED}, + {"scanning", NM_SUPPLICANT_INTERFACE_STATE_SCANNING}, ); + +/*****************************************************************************/ + +static NM80211ApSecurityFlags +security_from_vardict(GVariant *security) +{ + NM80211ApSecurityFlags flags = NM_802_11_AP_SEC_NONE; + const char ** array; + const char * tmp; + + nm_assert(g_variant_is_of_type(security, G_VARIANT_TYPE_VARDICT)); + + if (g_variant_lookup(security, "KeyMgmt", "^a&s", &array)) { + if (g_strv_contains(array, "wpa-psk") || g_strv_contains(array, "wpa-ft-psk")) + flags |= NM_802_11_AP_SEC_KEY_MGMT_PSK; + if (g_strv_contains(array, "wpa-eap") || g_strv_contains(array, "wpa-ft-eap") + || g_strv_contains(array, "wpa-fils-sha256") + || g_strv_contains(array, "wpa-fils-sha384")) + flags |= NM_802_11_AP_SEC_KEY_MGMT_802_1X; + if (g_strv_contains(array, "sae")) + flags |= NM_802_11_AP_SEC_KEY_MGMT_SAE; + if (g_strv_contains(array, "owe")) + flags |= NM_802_11_AP_SEC_KEY_MGMT_OWE; + if (g_strv_contains(array, "wpa-eap-suite-b-192")) + flags |= NM_802_11_AP_SEC_KEY_MGMT_EAP_SUITE_B_192; + g_free(array); + } + + if (g_variant_lookup(security, "Pairwise", "^a&s", &array)) { + if (g_strv_contains(array, "tkip")) + flags |= NM_802_11_AP_SEC_PAIR_TKIP; + if (g_strv_contains(array, "ccmp")) + flags |= NM_802_11_AP_SEC_PAIR_CCMP; + g_free(array); + } + + if (g_variant_lookup(security, "Group", "&s", &tmp)) { + if (nm_streq(tmp, "wep40")) + flags |= NM_802_11_AP_SEC_GROUP_WEP40; + else if (nm_streq(tmp, "wep104")) + flags |= NM_802_11_AP_SEC_GROUP_WEP104; + else if (nm_streq(tmp, "tkip")) + flags |= NM_802_11_AP_SEC_GROUP_TKIP; + else if (nm_streq(tmp, "ccmp")) + flags |= NM_802_11_AP_SEC_GROUP_CCMP; + } + + return flags; +} + +/*****************************************************************************/ + +/* Various conditions prevent _starting_check_ready() from completing. For example, + * bss_initializing_lst_head, peer_initializing_lst_head and p2p_group_properties_cancellable. + * At some places, these conditions might toggle, and it would seems we would have + * to call _starting_check_ready() at that point, to ensure we don't miss a state + * change that we are ready. However, these places are deep in the call stack and + * not suitable to perform this state change. Instead, the callers *MUST* have + * added their own starting_pending_count to delay _starting_check_ready(). + * + * Assert that is the case. */ +#define nm_assert_starting_has_pending_count(v) nm_assert((v) > 0) + +/*****************************************************************************/ + +static void +_dbus_connection_call(NMSupplicantInterface *self, + const char * interface_name, + const char * method_name, + GVariant * parameters, + const GVariantType * reply_type, + GDBusCallFlags flags, + int timeout_msec, + GCancellable * cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + g_dbus_connection_call(priv->dbus_connection, + priv->name_owner->str, + priv->object_path->str, + interface_name, + method_name, + parameters, + reply_type, + flags, + timeout_msec, + cancellable, + callback, + user_data); +} + +static void +_dbus_connection_call_simple_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMSupplicantInterface *self; + gs_unref_variant GVariant *res = NULL; + gs_free_error GError *error = NULL; + const char * log_reason; + gs_free char * remote_error = NULL; + + nm_utils_user_data_unpack(user_data, &self, &log_reason); + + res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); + if (nm_utils_error_is_cancelled(error)) + return; + + if (res) { + _LOGT("call-%s: success", log_reason); + return; + } + + remote_error = g_dbus_error_get_remote_error(error); + if (!nm_streq0(remote_error, "fi.w1.wpa_supplicant1.NotConnected")) { + g_dbus_error_strip_remote_error(error); + _LOGW("call-%s: failed with %s", log_reason, error->message); + return; + } + + _LOGT("call-%s: failed with %s", log_reason, error->message); +} + +static void +_dbus_connection_call_simple(NMSupplicantInterface *self, + const char * interface_name, + const char * method_name, + GVariant * parameters, + const GVariantType * reply_type, + const char * log_reason) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + _dbus_connection_call(self, + interface_name, + method_name, + parameters, + reply_type, + G_DBUS_CALL_FLAGS_NONE, + DBUS_TIMEOUT_MSEC, + priv->main_cancellable, + _dbus_connection_call_simple_cb, + nm_utils_user_data_pack(self, log_reason)); +} + +/*****************************************************************************/ + +static void +_emit_signal_state(NMSupplicantInterface * self, + NMSupplicantInterfaceState new_state, + NMSupplicantInterfaceState old_state, + gint32 disconnect_reason) +{ + g_signal_emit(self, + signals[STATE], + 0, + (int) new_state, + (int) old_state, + (int) disconnect_reason); +} + +/*****************************************************************************/ + +static void +_remove_network(NMSupplicantInterface *self) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + gs_free char * net_path = NULL; + + if (!priv->net_path) + return; + + net_path = g_steal_pointer(&priv->net_path); + _dbus_connection_call_simple(self, + NM_WPAS_DBUS_IFACE_INTERFACE, + "RemoveNetwork", + g_variant_new("(o)", net_path), + G_VARIANT_TYPE("()"), + "remove-network"); + + if (priv->ap_isolate_supported && priv->ap_isolate_needs_reset) { + _dbus_connection_call_simple(self, + DBUS_INTERFACE_PROPERTIES, + "Set", + g_variant_new("(ssv)", + NM_WPAS_DBUS_IFACE_INTERFACE, + "ApIsolate", + g_variant_new_string("0")), + G_VARIANT_TYPE("()"), + "reset-ap-isolation"); + } + priv->ap_isolate_needs_reset = FALSE; +} + +/*****************************************************************************/ + +static void +_notify_maybe_scanning(NMSupplicantInterface *self) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + gboolean scanning; + + scanning = nm_supplicant_interface_state_is_operational(priv->state) && priv->scanning_property; + + if (priv->scanning_cached == scanning) + return; + + if (!scanning && !c_list_is_empty(&priv->bss_initializing_lst_head)) { + /* we would change state to indicate we no longer scan. However, + * we still have BSS instances to be initialized. Delay the + * state change further. */ + return; + } + + _LOGT("scanning: %s", scanning ? "yes" : "no"); + + if (!scanning) + priv->last_scan_msec = nm_utils_get_monotonic_timestamp_msec(); + else { + /* while we are scanning, we set the timestamp to -1. */ + priv->last_scan_msec = -1; + } + priv->scanning_cached = scanning; + _notify(self, PROP_SCANNING); +} + +static void +_notify_maybe_p2p_available(NMSupplicantInterface *self) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + gboolean value; + + value = priv->is_ready_p2p_device && priv->p2p_capable_property; + + if (priv->p2p_capable_cached == value) + return; + + priv->p2p_capable_cached = value; + _notify(self, PROP_P2P_AVAILABLE); +} + +static void +_notify_maybe_p2p_group(NMSupplicantInterface *self) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + gboolean value_joined; + gboolean value_owner; + gboolean joined_changed; + gboolean owner_changed; + + value_joined = priv->p2p_group_path && !priv->p2p_group_properties_cancellable; + value_owner = value_joined && priv->p2p_group_owner_property; + + if ((joined_changed = (priv->p2p_group_joined_cached != value_joined))) + priv->p2p_group_joined_cached = value_joined; + + if ((owner_changed = (priv->p2p_group_owner_cached != value_owner))) + priv->p2p_group_owner_cached = value_owner; + + if (joined_changed) + _notify(self, PROP_P2P_GROUP_JOINED); + if (owner_changed) + _notify(self, PROP_P2P_GROUP_OWNER); +} + +/*****************************************************************************/ + +static void +_bss_info_destroy(NMSupplicantBssInfo *bss_info) +{ + c_list_unlink_stale(&bss_info->_bss_lst); + nm_clear_g_cancellable(&bss_info->_init_cancellable); + g_bytes_unref(bss_info->ssid); + nm_ref_string_unref(bss_info->bss_path); + nm_g_slice_free(bss_info); +} + +static void +_bss_info_changed_emit(NMSupplicantInterface *self, + NMSupplicantBssInfo * bss_info, + gboolean is_present) +{ + _LOGT("BSS %s %s", bss_info->bss_path->str, is_present ? "updated" : "deleted"); + g_signal_emit(self, signals[BSS_CHANGED], 0, bss_info, is_present); +} + +static void +_bss_info_properties_changed(NMSupplicantInterface *self, + NMSupplicantBssInfo * bss_info, + GVariant * properties, + gboolean initial) +{ + gboolean v_b; + GVariant * v_v; + const char * v_s; + gint16 v_i16; + guint16 v_u16; + guint32 v_u32; + NM80211ApFlags p_ap_flags; + NM80211Mode p_mode; + guint8 p_signal_percent; + const guint8 * arr_data; + gsize arr_len; + guint32 p_max_rate; + gboolean p_max_rate_has; + gint64 now_msec = 0; + + if (nm_g_variant_lookup(properties, "Age", "u", &v_u32)) { + bss_info->last_seen_msec = + nm_utils_get_monotonic_timestamp_msec_cached(&now_msec) - (((gint64) v_u32) * 1000); + } else if (initial) { + /* Unknown Age. Assume we just received it. */ + bss_info->last_seen_msec = nm_utils_get_monotonic_timestamp_msec_cached(&now_msec); + } + + p_ap_flags = bss_info->ap_flags; + if (nm_g_variant_lookup(properties, "Privacy", "b", &v_b)) + p_ap_flags = NM_FLAGS_ASSIGN(p_ap_flags, NM_802_11_AP_FLAGS_PRIVACY, v_b); + else { + nm_assert(!initial || !NM_FLAGS_HAS(p_ap_flags, NM_802_11_AP_FLAGS_PRIVACY)); + } + v_v = nm_g_variant_lookup_value(properties, "WPS", G_VARIANT_TYPE_VARDICT); + if (v_v || initial) { + NM80211ApFlags f = NM_802_11_AP_FLAGS_NONE; + + if (v_v) { + if (g_variant_lookup(v_v, "Type", "&s", &v_s)) { + f = NM_802_11_AP_FLAGS_WPS; + if (nm_streq(v_s, "pcb")) + f |= NM_802_11_AP_FLAGS_WPS_PBC; + else if (nm_streq(v_s, "pin")) + f |= NM_802_11_AP_FLAGS_WPS_PIN; + } + g_variant_unref(v_v); + } + p_ap_flags = NM_FLAGS_ASSIGN_MASK(p_ap_flags, + NM_802_11_AP_FLAGS_WPS | NM_802_11_AP_FLAGS_WPS_PBC + | NM_802_11_AP_FLAGS_WPS_PIN, + f); + } + if (bss_info->ap_flags != p_ap_flags) { + bss_info->ap_flags = p_ap_flags; + nm_assert(bss_info->ap_flags == p_ap_flags); + } + + if (nm_g_variant_lookup(properties, "Mode", "&s", &v_s)) { + if (nm_streq(v_s, "infrastructure")) + p_mode = NM_802_11_MODE_INFRA; + else if (nm_streq(v_s, "ad-hoc")) + p_mode = NM_802_11_MODE_ADHOC; + else if (nm_streq(v_s, "mesh")) + p_mode = NM_802_11_MODE_MESH; + else + p_mode = NM_802_11_MODE_UNKNOWN; + } else if (initial) + p_mode = NM_802_11_MODE_UNKNOWN; + else + p_mode = bss_info->mode; + if (bss_info->mode != p_mode) { + bss_info->mode = p_mode; + nm_assert(bss_info->mode == p_mode); + } + + if (nm_g_variant_lookup(properties, "Signal", "n", &v_i16)) + p_signal_percent = nm_wifi_utils_level_to_quality(v_i16); + else if (initial) + p_signal_percent = 0; + else + p_signal_percent = bss_info->signal_percent; + bss_info->signal_percent = p_signal_percent; + + if (nm_g_variant_lookup(properties, "Frequency", "q", &v_u16)) + bss_info->frequency = v_u16; + + v_v = nm_g_variant_lookup_value(properties, "SSID", G_VARIANT_TYPE_BYTESTRING); + if (v_v) { + arr_data = g_variant_get_fixed_array(v_v, &arr_len, 1); + arr_len = MIN(32, arr_len); + + /* Stupid ieee80211 layer uses <hidden> */ + if (arr_data && arr_len + && !(NM_IN_SET(arr_len, 8, 9) && memcmp(arr_data, "<hidden>", arr_len) == 0) + && !nm_utils_is_empty_ssid(arr_data, arr_len)) { + /* good */ + } else + arr_len = 0; + + if (!nm_utils_gbytes_equal_mem(bss_info->ssid, arr_data, arr_len)) { + _nm_unused gs_unref_bytes GBytes *old_free = g_steal_pointer(&bss_info->ssid); + + bss_info->ssid = (arr_len == 0) ? NULL : g_bytes_new(arr_data, arr_len); + } + + g_variant_unref(v_v); + } else { + nm_assert(!initial || !bss_info->ssid); + } + + v_v = nm_g_variant_lookup_value(properties, "BSSID", G_VARIANT_TYPE_BYTESTRING); + if (v_v) { + arr_data = g_variant_get_fixed_array(v_v, &arr_len, 1); + if (arr_len == ETH_ALEN && memcmp(arr_data, &nm_ether_addr_zero, ETH_ALEN) != 0 + && memcmp(arr_data, (char[ETH_ALEN]){0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, ETH_ALEN) + != 0) { + /* pass */ + } else + arr_len = 0; + + if (arr_len != 0) { + nm_assert(arr_len == sizeof(bss_info->bssid)); + bss_info->bssid_valid = TRUE; + memcpy(&bss_info->bssid, arr_data, sizeof(bss_info->bssid)); + } else if (bss_info->bssid_valid) { + bss_info->bssid_valid = FALSE; + memset(&bss_info->bssid, 0, sizeof(bss_info->bssid)); + } + g_variant_unref(v_v); + } else { + nm_assert(!initial || !bss_info->bssid_valid); + } + nm_assert((!!bss_info->bssid_valid) + == (!nm_utils_memeqzero(&bss_info->bssid, sizeof(bss_info->bssid)))); + + p_max_rate_has = FALSE; + p_max_rate = 0; + v_v = nm_g_variant_lookup_value(properties, "Rates", G_VARIANT_TYPE("au")); + if (v_v) { + const guint32 *rates = g_variant_get_fixed_array(v_v, &arr_len, sizeof(guint32)); + gsize i; + + for (i = 0; i < arr_len; i++) + p_max_rate = NM_MAX(p_max_rate, rates[i]); + p_max_rate_has = TRUE; + g_variant_unref(v_v); + } + + v_v = nm_g_variant_lookup_value(properties, "WPA", G_VARIANT_TYPE_VARDICT); + if (v_v) { + bss_info->wpa_flags = security_from_vardict(v_v); + g_variant_unref(v_v); + } + + v_v = nm_g_variant_lookup_value(properties, "RSN", G_VARIANT_TYPE_VARDICT); + if (v_v) { + bss_info->rsn_flags = security_from_vardict(v_v); + g_variant_unref(v_v); + } + + v_v = nm_g_variant_lookup_value(properties, "IEs", G_VARIANT_TYPE_BYTESTRING); + if (v_v) { + gboolean p_owe_transition_mode; + gboolean p_metered; + guint32 rate; + + arr_data = g_variant_get_fixed_array(v_v, &arr_len, 1); + nm_wifi_utils_parse_ies(arr_data, arr_len, &rate, &p_metered, &p_owe_transition_mode); + p_max_rate = NM_MAX(p_max_rate, rate); + p_max_rate_has = TRUE; + g_variant_unref(v_v); + + if (p_owe_transition_mode) + bss_info->rsn_flags |= NM_802_11_AP_SEC_KEY_MGMT_OWE_TM; + else + bss_info->rsn_flags &= ~NM_802_11_AP_SEC_KEY_MGMT_OWE_TM; + + bss_info->metered = p_metered; + } + + if (p_max_rate_has) + bss_info->max_rate = p_max_rate / 1000u; + + _bss_info_changed_emit(self, bss_info, TRUE); +} + +static void +_bss_info_get_all_cb(GVariant *result, GError *error, gpointer user_data) +{ + NMSupplicantBssInfo * bss_info; + NMSupplicantInterface * self; + NMSupplicantInterfacePrivate *priv; + gs_unref_variant GVariant *properties = NULL; + + if (nm_utils_error_is_cancelled(error)) + return; + + bss_info = user_data; + self = bss_info->_self; + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + g_clear_object(&bss_info->_init_cancellable); + nm_c_list_move_tail(&priv->bss_lst_head, &bss_info->_bss_lst); + + if (result) + g_variant_get(result, "(@a{sv})", &properties); + + _bss_info_properties_changed(self, bss_info, properties, TRUE); + + _starting_check_ready(self); + + _notify_maybe_scanning(self); +} + +static void +_bss_info_add(NMSupplicantInterface *self, const char *object_path) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + nm_auto_ref_string NMRefString *bss_path = NULL; + NMSupplicantBssInfo * bss_info; + + bss_path = nm_ref_string_new(nm_dbus_path_not_empty(object_path)); + if (!bss_path) + return; + + bss_info = g_hash_table_lookup(priv->bss_idx, &bss_path); + if (bss_info) { + bss_info->_bss_dirty = FALSE; + return; + } + + bss_info = g_slice_new(NMSupplicantBssInfo); + *bss_info = (NMSupplicantBssInfo){ + ._self = self, + .bss_path = g_steal_pointer(&bss_path), + ._init_cancellable = g_cancellable_new(), + }; + c_list_link_tail(&priv->bss_initializing_lst_head, &bss_info->_bss_lst); + g_hash_table_add(priv->bss_idx, bss_info); + + nm_dbus_connection_call_get_all(priv->dbus_connection, + priv->name_owner->str, + bss_info->bss_path->str, + NM_WPAS_DBUS_IFACE_BSS, + 5000, + bss_info->_init_cancellable, + _bss_info_get_all_cb, + bss_info); +} + +static gboolean +_bss_info_remove(NMSupplicantInterface *self, NMRefString **p_bss_path) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + NMSupplicantBssInfo * bss_info; + gpointer unused_but_required; + + if (!g_hash_table_steal_extended(priv->bss_idx, + p_bss_path, + (gpointer *) &bss_info, + &unused_but_required)) + return FALSE; + + c_list_unlink(&bss_info->_bss_lst); + if (!bss_info->_init_cancellable) + _bss_info_changed_emit(self, bss_info, FALSE); + _bss_info_destroy(bss_info); + + nm_assert_starting_has_pending_count(priv->starting_pending_count); + + return TRUE; +} + +/*****************************************************************************/ + +static void +_peer_info_destroy(NMSupplicantPeerInfo *peer_info) +{ + c_list_unlink(&peer_info->_peer_lst); + nm_clear_g_cancellable(&peer_info->_init_cancellable); + + g_free(peer_info->device_name); + g_free(peer_info->manufacturer); + g_free(peer_info->model); + g_free(peer_info->model_number); + g_free(peer_info->serial); + g_free(peer_info->groups); + g_bytes_unref(peer_info->ies); + + nm_ref_string_unref(peer_info->peer_path); + + nm_g_slice_free(peer_info); +} + +static void +_peer_info_changed_emit(NMSupplicantInterface *self, + NMSupplicantPeerInfo * peer_info, + gboolean is_present) +{ + g_signal_emit(self, signals[PEER_CHANGED], 0, peer_info, is_present); +} + +static void +_peer_info_properties_changed(NMSupplicantInterface *self, + NMSupplicantPeerInfo * peer_info, + GVariant * properties, + gboolean initial) +{ + GVariant * v_v; + const char * v_s; + const char ** v_strv; + gint32 v_i32; + const guint8 *arr_data; + gsize arr_len; + + peer_info->last_seen_msec = nm_utils_get_monotonic_timestamp_msec(); + + if (nm_g_variant_lookup(properties, "level", "i", &v_i32)) + peer_info->signal_percent = nm_wifi_utils_level_to_quality(v_i32); + + if (nm_g_variant_lookup(properties, "DeviceName", "&s", &v_s)) + nm_utils_strdup_reset(&peer_info->device_name, v_s); + + if (nm_g_variant_lookup(properties, "Manufacturer", "&s", &v_s)) + nm_utils_strdup_reset(&peer_info->manufacturer, v_s); + + if (nm_g_variant_lookup(properties, "Model", "&s", &v_s)) + nm_utils_strdup_reset(&peer_info->model, v_s); + + if (nm_g_variant_lookup(properties, "ModelNumber", "&s", &v_s)) + nm_utils_strdup_reset(&peer_info->model_number, v_s); + + if (nm_g_variant_lookup(properties, "Serial", "&s", &v_s)) + nm_utils_strdup_reset(&peer_info->serial, v_s); + + if (nm_g_variant_lookup(properties, "Groups", "^a&o", &v_strv)) { + g_free(peer_info->groups); + peer_info->groups = nm_utils_strv_dup_packed(v_strv, -1); + + g_free(v_strv); + } + + v_v = nm_g_variant_lookup_value(properties, "DeviceAddress", G_VARIANT_TYPE_BYTESTRING); + if (v_v) { + arr_data = g_variant_get_fixed_array(v_v, &arr_len, 1); + if (arr_len == ETH_ALEN && memcmp(arr_data, &nm_ether_addr_zero, ETH_ALEN) != 0 + && memcmp(arr_data, (char[ETH_ALEN]){0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, ETH_ALEN) + != 0) { + /* pass */ + } else + arr_len = 0; + + if (arr_len != 0) { + nm_assert(arr_len == sizeof(peer_info->address)); + peer_info->address_valid = TRUE; + memcpy(peer_info->address, arr_data, sizeof(peer_info->address)); + } else if (peer_info->address_valid) { + peer_info->address_valid = FALSE; + memset(peer_info->address, 0, sizeof(peer_info->address)); + } + g_variant_unref(v_v); + } else { + nm_assert(!initial || !peer_info->address_valid); + } + nm_assert((peer_info->address_valid + && !nm_utils_memeqzero(peer_info->address, sizeof(peer_info->address))) + || (!peer_info->address_valid + && nm_utils_memeqzero(peer_info->address, sizeof(peer_info->address)))); + + /* The IEs property contains the WFD R1 subelements */ + v_v = nm_g_variant_lookup_value(properties, "IEs", G_VARIANT_TYPE_BYTESTRING); + if (v_v) { + arr_data = g_variant_get_fixed_array(v_v, &arr_len, 1); + if (!nm_utils_gbytes_equal_mem(peer_info->ies, arr_data, arr_len)) { + _nm_unused gs_unref_bytes GBytes *old_free = g_steal_pointer(&peer_info->ies); + + peer_info->ies = g_bytes_new(arr_data, arr_len); + } else if (arr_len == 0 && !peer_info->ies) + peer_info->ies = g_bytes_new(NULL, 0); + g_variant_unref(v_v); + } + + _peer_info_changed_emit(self, peer_info, TRUE); +} + +static void +_peer_info_get_all_cb(GVariant *result, GError *error, gpointer user_data) +{ + NMSupplicantPeerInfo * peer_info; + NMSupplicantInterface * self; + NMSupplicantInterfacePrivate *priv; + gs_unref_variant GVariant *properties = NULL; + + if (nm_utils_error_is_cancelled(error)) + return; + + peer_info = user_data; + self = peer_info->_self; + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + g_clear_object(&peer_info->_init_cancellable); + nm_c_list_move_tail(&priv->peer_lst_head, &peer_info->_peer_lst); + + if (result) + g_variant_get(result, "(@a{sv})", &properties); + + _peer_info_properties_changed(self, peer_info, properties, TRUE); + + _starting_check_ready(self); +} + +static void +_peer_info_add(NMSupplicantInterface *self, const char *object_path) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + nm_auto_ref_string NMRefString *peer_path = NULL; + NMSupplicantPeerInfo * peer_info; + + peer_path = nm_ref_string_new(nm_dbus_path_not_empty(object_path)); + if (!peer_path) + return; + + peer_info = g_hash_table_lookup(priv->peer_idx, &peer_path); + + if (peer_info) { + peer_info->_peer_dirty = FALSE; + return; + } + + peer_info = g_slice_new(NMSupplicantPeerInfo); + *peer_info = (NMSupplicantPeerInfo){ + ._self = self, + .peer_path = g_steal_pointer(&peer_path), + ._init_cancellable = g_cancellable_new(), + }; + c_list_link_tail(&priv->peer_initializing_lst_head, &peer_info->_peer_lst); + g_hash_table_add(priv->peer_idx, peer_info); + + nm_dbus_connection_call_get_all(priv->dbus_connection, + priv->name_owner->str, + peer_info->peer_path->str, + NM_WPAS_DBUS_IFACE_PEER, + 5000, + peer_info->_init_cancellable, + _peer_info_get_all_cb, + peer_info); +} + +static gboolean +_peer_info_remove(NMSupplicantInterface *self, NMRefString **p_peer_path) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + NMSupplicantPeerInfo * peer_info; + gpointer unused_but_required; + + if (!g_hash_table_steal_extended(priv->peer_idx, + p_peer_path, + (gpointer *) &peer_info, + &unused_but_required)) + return FALSE; + + c_list_unlink(&peer_info->_peer_lst); + if (!peer_info->_init_cancellable) + _peer_info_changed_emit(self, peer_info, FALSE); + _peer_info_destroy(peer_info); + + nm_assert_starting_has_pending_count(priv->starting_pending_count); + + return TRUE; +} + +/*****************************************************************************/ + +static void +set_state_down(NMSupplicantInterface *self, + gboolean force_remove_from_supplicant, + const char * reason) +{ + _nm_unused gs_unref_object NMSupplicantInterface *self_keep_alive = g_object_ref(self); + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + NMSupplicantBssInfo * bss_info; + NMSupplicantPeerInfo * peer_info; + NMSupplicantInterfaceState old_state; + + nm_assert(priv->state != NM_SUPPLICANT_INTERFACE_STATE_DOWN); + nm_assert(!c_list_is_empty(&self->supp_lst)); + + _LOGD("remove interface \"%s\" on %s (%s)%s", + priv->object_path->str, + priv->name_owner->str, + reason, + force_remove_from_supplicant ? " (remove in wpa_supplicant)" : ""); + + old_state = priv->state; + + priv->state = NM_SUPPLICANT_INTERFACE_STATE_DOWN; + + _nm_supplicant_manager_unregister_interface(priv->supplicant_manager, self); + + nm_assert(c_list_is_empty(&self->supp_lst)); + + if (force_remove_from_supplicant) { + _nm_supplicant_manager_dbus_call_remove_interface(priv->supplicant_manager, + priv->name_owner->str, + priv->object_path->str); + } + + _emit_signal_state(self, priv->state, old_state, 0); + + nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->properties_changed_id); + nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->signal_id); + nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->bss_properties_changed_id); + nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->peer_properties_changed_id); + nm_clear_g_dbus_connection_signal(priv->dbus_connection, + &priv->p2p_group_properties_changed_id); + + nm_supplicant_interface_cancel_wps(self); + + if (priv->assoc_data) { + gs_free_error GError *error = NULL; + + nm_utils_error_set_cancelled(&error, TRUE, "NMSupplicantInterface"); + assoc_return(self, error, "cancelled because supplicant interface is going down"); + } + + while ( + (bss_info = + c_list_first_entry(&priv->bss_initializing_lst_head, NMSupplicantBssInfo, _bss_lst))) { + g_hash_table_remove(priv->bss_idx, bss_info); + _bss_info_destroy(bss_info); + } + while ((bss_info = c_list_first_entry(&priv->bss_lst_head, NMSupplicantBssInfo, _bss_lst))) { + g_hash_table_remove(priv->bss_idx, bss_info); + _bss_info_destroy(bss_info); + } + nm_assert(g_hash_table_size(priv->bss_idx) == 0); + + while ((peer_info = c_list_first_entry(&priv->peer_initializing_lst_head, + NMSupplicantPeerInfo, + _peer_lst))) { + g_hash_table_remove(priv->peer_idx, peer_info); + _peer_info_destroy(peer_info); + } + while ( + (peer_info = c_list_first_entry(&priv->peer_lst_head, NMSupplicantPeerInfo, _peer_lst))) { + g_hash_table_remove(priv->peer_idx, peer_info); + _peer_info_destroy(peer_info); + } + nm_assert(g_hash_table_size(priv->peer_idx) == 0); + + nm_clear_g_cancellable(&priv->main_cancellable); + nm_clear_g_cancellable(&priv->p2p_group_properties_cancellable); + + nm_clear_pointer(&priv->p2p_group_path, nm_ref_string_unref); + + _remove_network(self); + + nm_clear_pointer(&priv->current_bss, nm_ref_string_unref); + + _notify_maybe_scanning(self); +} + +static void +set_state(NMSupplicantInterface *self, NMSupplicantInterfaceState new_state) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + NMSupplicantInterfaceState old_state = priv->state; + + nm_assert(new_state > NM_SUPPLICANT_INTERFACE_STATE_STARTING); + nm_assert(new_state < NM_SUPPLICANT_INTERFACE_STATE_DOWN); + nm_assert(nm_supplicant_interface_state_is_operational(new_state)); + + nm_assert(priv->state >= NM_SUPPLICANT_INTERFACE_STATE_STARTING); + nm_assert(priv->state < NM_SUPPLICANT_INTERFACE_STATE_DOWN); + + if (new_state == priv->state) + return; + + _LOGT("state: set state \"%s\" (was \"%s\")", + nm_supplicant_interface_state_to_string(new_state), + nm_supplicant_interface_state_to_string(priv->state)); + + priv->state = new_state; + + _emit_signal_state( + self, + priv->state, + old_state, + priv->state != NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED ? 0u : priv->disconnect_reason); +} + +NMRefString * +nm_supplicant_interface_get_current_bss(NMSupplicantInterface *self) +{ + g_return_val_if_fail(self != NULL, FALSE); + + return NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self)->current_bss; +} + +gboolean +nm_supplicant_interface_get_scanning(NMSupplicantInterface *self) +{ + g_return_val_if_fail(NM_IS_SUPPLICANT_INTERFACE(self), FALSE); + + return NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self)->scanning_cached; +} + +gint64 +nm_supplicant_interface_get_last_scan(NMSupplicantInterface *self) +{ + g_return_val_if_fail(NM_IS_SUPPLICANT_INTERFACE(self), FALSE); + + return NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self)->last_scan_msec; +} + +#define MATCH_PROPERTY(p, n, v, t) (!strcmp(p, n) && g_variant_is_of_type(v, t)) + +static void +parse_capabilities(NMSupplicantInterface *self, GVariant *capabilities) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + const gboolean old_prop_scan_active = priv->prop_scan_active; + const gboolean old_prop_scan_ssid = priv->prop_scan_ssid; + const guint32 old_max_scan_ssids = priv->max_scan_ssids; + gboolean have_ft = FALSE; + gint32 max_scan_ssids; + const char ** array; + + nm_assert(capabilities && g_variant_is_of_type(capabilities, G_VARIANT_TYPE_VARDICT)); + + if (g_variant_lookup(capabilities, "KeyMgmt", "^a&s", &array)) { + have_ft = g_strv_contains(array, "wpa-ft-psk"); + g_free(array); + } + + priv->iface_capabilities = NM_SUPPL_CAP_MASK_SET(priv->iface_capabilities, + NM_SUPPL_CAP_TYPE_FT, + have_ft ? NM_TERNARY_TRUE : NM_TERNARY_FALSE); + + if (g_variant_lookup(capabilities, "Modes", "^a&s", &array)) { + /* Setting p2p_capable might toggle _prop_p2p_available_get(). However, + * we don't need to check for a property changed notification, because + * the caller did g_object_freeze_notify() and will perform the check. */ + priv->p2p_capable_property = g_strv_contains(array, "p2p"); + g_free(array); + } + + if (g_variant_lookup(capabilities, "Scan", "^a&s", &array)) { + const char **a; + + priv->prop_scan_active = FALSE; + priv->prop_scan_ssid = FALSE; + for (a = array; *a; a++) { + if (nm_streq(*a, "active")) + priv->prop_scan_active = TRUE; + else if (nm_streq(*a, "ssid")) + priv->prop_scan_ssid = TRUE; + } + g_free(array); + } + + if (g_variant_lookup(capabilities, "MaxScanSSID", "i", &max_scan_ssids)) { + const gint32 WPAS_MAX_SCAN_SSIDS = 16; + + /* Even if supplicant claims that 20 SSIDs are supported, the Scan request + * still only accepts WPAS_MAX_SCAN_SSIDS SSIDs. Otherwise, the D-Bus + * request will be rejected with "fi.w1.wpa_supplicant1.InvalidArgs" + * Body: ('Did not receive correct message arguments.', 'Too many ssids specified. Specify at most four') + * */ + priv->max_scan_ssids = CLAMP(max_scan_ssids, 0, WPAS_MAX_SCAN_SSIDS); + } + + if (old_max_scan_ssids != priv->max_scan_ssids || old_prop_scan_active != priv->prop_scan_active + || old_prop_scan_ssid != priv->prop_scan_ssid) { + _LOGD("supports %u scan SSIDs (scan: %cactive %cssid)", + (guint32) priv->max_scan_ssids, + priv->prop_scan_active ? '+' : '-', + priv->prop_scan_ssid ? '+' : '-'); + } +} + +static void +_starting_check_ready(NMSupplicantInterface *self) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + if (priv->state != NM_SUPPLICANT_INTERFACE_STATE_STARTING) + return; + + if (priv->starting_pending_count > 0) + return; + + if (!c_list_is_empty(&priv->bss_initializing_lst_head)) + return; + + if (!c_list_is_empty(&priv->peer_initializing_lst_head)) + return; + + if (priv->p2p_group_properties_cancellable) + return; + + nm_assert(priv->state == NM_SUPPLICANT_INTERFACE_STATE_STARTING); + + if (!nm_supplicant_interface_state_is_operational(priv->supp_state)) { + _LOGW("Supplicant state is unknown during initialization. Destroy the interface"); + set_state_down(self, TRUE, "failure to get valid interface state"); + return; + } + + set_state(self, priv->supp_state); +} + +static NMTernary +_get_capability(NMSupplicantInterfacePrivate *priv, NMSupplCapType type) +{ + NMTernary value; + NMTernary iface_value; + + switch (type) { + case NM_SUPPL_CAP_TYPE_AP: + iface_value = NM_SUPPL_CAP_MASK_GET(priv->iface_capabilities, type); + value = NM_SUPPL_CAP_MASK_GET(priv->global_capabilities, type); + value = MAX(iface_value, value); + break; + case NM_SUPPL_CAP_TYPE_FT: + value = NM_SUPPL_CAP_MASK_GET(priv->global_capabilities, type); + if (value != NM_TERNARY_FALSE) { + iface_value = NM_SUPPL_CAP_MASK_GET(priv->iface_capabilities, type); + if (iface_value != NM_TERNARY_DEFAULT) + value = iface_value; + } + break; + default: + nm_assert(NM_SUPPL_CAP_MASK_GET(priv->iface_capabilities, type) == NM_TERNARY_DEFAULT); + value = NM_SUPPL_CAP_MASK_GET(priv->global_capabilities, type); + break; + } + return value; +} + +NMTernary +nm_supplicant_interface_get_capability(NMSupplicantInterface *self, NMSupplCapType type) +{ + return _get_capability(NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self), type); +} + +NMSupplCapMask +nm_supplicant_interface_get_capabilities(NMSupplicantInterface *self) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + NMSupplCapMask caps; + + caps = priv->global_capabilities; + caps = NM_SUPPL_CAP_MASK_SET(caps, + NM_SUPPL_CAP_TYPE_AP, + _get_capability(priv, NM_SUPPL_CAP_TYPE_AP)); + caps = NM_SUPPL_CAP_MASK_SET(caps, + NM_SUPPL_CAP_TYPE_FT, + _get_capability(priv, NM_SUPPL_CAP_TYPE_FT)); + + nm_assert(!NM_FLAGS_ANY(priv->iface_capabilities, + ~(NM_SUPPL_CAP_MASK_T_AP_MASK | NM_SUPPL_CAP_MASK_T_FT_MASK))); + +#if NM_MORE_ASSERTS > 10 + { + NMSupplCapType type; + + for (type = 0; type < _NM_SUPPL_CAP_TYPE_NUM; type++) + nm_assert(NM_SUPPL_CAP_MASK_GET(caps, type) == _get_capability(priv, type)); + } +#endif + + return caps; +} + +static void +set_bridge_cb(GVariant *ret, GError *error, gpointer user_data) +{ + NMSupplicantInterface *self; + NMLogLevel level; + gs_free const char * bridge = NULL; + + nm_utils_user_data_unpack(user_data, &self, &bridge); + + if (nm_utils_error_is_cancelled(error)) + return; + + /* The supplicant supports writing the bridge property since + * version 2.10. Before that version, trying to set the property + * results in a InvalidArgs error. Don't log a warning unless we + * are trying to set a non-NULL bridge. */ + if (!error) + level = LOGL_DEBUG; + else if (bridge == NULL && g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS)) { + level = LOGL_DEBUG; + } else + level = LOGL_WARN; + + _NMLOG(level, + "set bridge %s%s%s result: %s", + NM_PRINT_FMT_QUOTE_STRING(bridge), + error ? error->message : "success"); +} + +void +nm_supplicant_interface_set_bridge(NMSupplicantInterface *self, const char *bridge) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + _LOGT("set bridge %s%s%s", NM_PRINT_FMT_QUOTE_STRING(bridge)); + + nm_dbus_connection_call_set(priv->dbus_connection, + priv->name_owner->str, + priv->object_path->str, + NM_WPAS_DBUS_IFACE_INTERFACE, + "BridgeIfname", + g_variant_new_string(bridge ?: ""), + DBUS_TIMEOUT_MSEC, + priv->main_cancellable, + set_bridge_cb, + nm_utils_user_data_pack(self, g_strdup(bridge))); +} + +void +nm_supplicant_interface_set_global_capabilities(NMSupplicantInterface *self, NMSupplCapMask value) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + priv->global_capabilities = value; +} + +NMSupplicantAuthState +nm_supplicant_interface_get_auth_state(NMSupplicantInterface *self) +{ + return NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self)->auth_state; +} + +/*****************************************************************************/ + +static void +_p2p_group_properties_changed(NMSupplicantInterface *self, GVariant *properties) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + const char * s; + + if (!properties) + priv->p2p_group_owner_property = FALSE; + else if (g_variant_lookup(properties, "Role", "&s", &s)) + priv->p2p_group_owner_property = nm_streq(s, "GO"); + + _notify_maybe_p2p_group(self); +} + +static void +_p2p_group_properties_changed_cb(GDBusConnection *connection, + const char * sender_name, + const char * object_path, + const char * signal_interface_name, + const char * signal_name, + GVariant * parameters, + gpointer user_data) +{ + NMSupplicantInterface * self = NM_SUPPLICANT_INTERFACE(user_data); + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + gs_unref_variant GVariant *changed_properties = NULL; + + if (priv->p2p_group_properties_cancellable) + return; + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sa{sv}as)"))) + return; + + g_variant_get(parameters, "(&s@a{sv}^a&s)", NULL, &changed_properties, NULL); + + _p2p_group_properties_changed(self, changed_properties); +} + +static void +_p2p_group_properties_get_all_cb(GVariant *result, GError *error, gpointer user_data) +{ + NMSupplicantInterface * self; + NMSupplicantInterfacePrivate *priv; + gs_unref_variant GVariant *properties = NULL; + + if (nm_utils_error_is_cancelled(error)) + return; + + self = NM_SUPPLICANT_INTERFACE(user_data); + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + g_object_freeze_notify(G_OBJECT(self)); + + nm_clear_g_cancellable(&priv->p2p_group_properties_cancellable); + + if (result) + g_variant_get(result, "(@a{sv})", &properties); + + _p2p_group_properties_changed(self, properties); + + _starting_check_ready(self); + + _notify_maybe_p2p_group(self); + + g_object_thaw_notify(G_OBJECT(self)); +} + +static void +_p2p_group_set_path(NMSupplicantInterface *self, const char *path) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + nm_auto_ref_string NMRefString *group_path = NULL; + + group_path = nm_ref_string_new(nm_dbus_path_not_empty(path)); + + if (priv->p2p_group_path == group_path) + return; + + nm_clear_g_dbus_connection_signal(priv->dbus_connection, + &priv->p2p_group_properties_changed_id); + nm_clear_g_cancellable(&priv->p2p_group_properties_cancellable); + + nm_ref_string_unref(priv->p2p_group_path); + priv->p2p_group_path = g_steal_pointer(&group_path); + + if (priv->p2p_group_path) { + priv->p2p_group_properties_cancellable = g_cancellable_new(); + priv->p2p_group_properties_changed_id = + nm_dbus_connection_signal_subscribe_properties_changed(priv->dbus_connection, + priv->name_owner->str, + priv->p2p_group_path->str, + NM_WPAS_DBUS_IFACE_GROUP, + _p2p_group_properties_changed_cb, + self, + NULL); + nm_dbus_connection_call_get_all(priv->dbus_connection, + priv->name_owner->str, + priv->p2p_group_path->str, + NM_WPAS_DBUS_IFACE_GROUP, + 5000, + priv->p2p_group_properties_cancellable, + _p2p_group_properties_get_all_cb, + self); + } + + _notify(self, PROP_P2P_GROUP_PATH); + _notify_maybe_p2p_group(self); + + nm_assert_starting_has_pending_count(priv->starting_pending_count); +} + +/*****************************************************************************/ + +static void +_wps_data_free(WpsData *wps_data, GDBusConnection *dbus_connection) +{ + nm_clear_g_dbus_connection_signal(dbus_connection, &wps_data->signal_id); + nm_clear_g_cancellable(&wps_data->cancellable); + g_free(wps_data->type); + g_free(wps_data->pin); + g_free(wps_data->bssid); + nm_g_slice_free(wps_data); +} + +static void +_wps_credentials_changed_cb(GDBusConnection *connection, + const char * sender_name, + const char * object_path, + const char * signal_interface_name, + const char * signal_name, + GVariant * parameters, + gpointer user_data) +{ + NMSupplicantInterface *self = user_data; + gs_unref_variant GVariant *props = NULL; + + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(a{sv})"))) + return; + + g_variant_get(parameters, "(@a{sv})", &props); + + _LOGT("wps: new credentials"); + g_signal_emit(self, signals[WPS_CREDENTIALS], 0, props); +} + +static void +_wps_handle_start_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMSupplicantInterface *self; + WpsData * wps_data; + gs_unref_variant GVariant *res = NULL; + gs_free_error GError *error = NULL; + + res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); + if (nm_utils_error_is_cancelled(error)) + return; + + wps_data = user_data; + self = wps_data->self; + + if (res) + _LOGT("wps: started with success"); + else + _LOGW("wps: start failed with %s", error->message); + + g_clear_object(&wps_data->cancellable); + nm_clear_g_free(&wps_data->type); + nm_clear_g_free(&wps_data->pin); + nm_clear_g_free(&wps_data->bssid); +} + +static void +_wps_handle_set_pc_cb(GVariant *res, GError *error, gpointer user_data) +{ + NMSupplicantInterface * self; + NMSupplicantInterfacePrivate *priv; + WpsData * wps_data; + GVariantBuilder start_args; + guint8 bssid_buf[ETH_ALEN]; + + if (nm_utils_error_is_cancelled(error)) + return; + + wps_data = user_data; + self = wps_data->self; + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + if (res) + _LOGT("wps: ProcessCredentials successfully set, starting..."); + else + _LOGW("wps: ProcessCredentials failed to set (%s), starting...", error->message); + + wps_data->signal_id = g_dbus_connection_signal_subscribe(priv->dbus_connection, + priv->name_owner->str, + NM_WPAS_DBUS_IFACE_INTERFACE_WPS, + "Credentials", + priv->object_path->str, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + _wps_credentials_changed_cb, + self, + NULL); + + g_variant_builder_init(&start_args, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add(&start_args, "{sv}", "Role", g_variant_new_string("enrollee")); + g_variant_builder_add(&start_args, "{sv}", "Type", g_variant_new_string(wps_data->type)); + if (wps_data->pin) + g_variant_builder_add(&start_args, "{sv}", "Pin", g_variant_new_string(wps_data->pin)); + if (wps_data->bssid) { + /* The BSSID is in fact not mandatory. If it is not set the supplicant would + * enroll with any BSS in range. */ + if (!nm_utils_hwaddr_aton(wps_data->bssid, bssid_buf, sizeof(bssid_buf))) + nm_assert_not_reached(); + g_variant_builder_add( + &start_args, + "{sv}", + "Bssid", + g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, bssid_buf, ETH_ALEN, sizeof(guint8))); + } + + wps_data->needs_cancelling = TRUE; + if (!wps_data->cancellable) + wps_data->cancellable = g_cancellable_new(); + + _dbus_connection_call(self, + NM_WPAS_DBUS_IFACE_INTERFACE_WPS, + "Start", + g_variant_new("(a{sv})", &start_args), + G_VARIANT_TYPE("(a{sv})"), + G_DBUS_CALL_FLAGS_NONE, + 5000, + wps_data->cancellable, + _wps_handle_start_cb, + wps_data); +} + +static void +_wps_call_set_pc(NMSupplicantInterface *self, WpsData *wps_data) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + if (!wps_data->cancellable) + wps_data->cancellable = g_cancellable_new(); + + nm_dbus_connection_call_set(priv->dbus_connection, + priv->name_owner->str, + priv->object_path->str, + NM_WPAS_DBUS_IFACE_INTERFACE_WPS, + "ProcessCredentials", + g_variant_new_boolean(TRUE), + 5000, + wps_data->cancellable, + _wps_handle_set_pc_cb, + wps_data); +} + +static void +_wps_handle_cancel_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + GDBusConnection * dbus_connection = G_DBUS_CONNECTION(source); + NMSupplicantInterface * self; + NMSupplicantInterfacePrivate *priv; + WpsData * wps_data; + gs_unref_variant GVariant *res = NULL; + gs_free_error GError *error = NULL; + + res = g_dbus_connection_call_finish(dbus_connection, result, &error); + nm_assert(!nm_utils_error_is_cancelled(error)); + + wps_data = user_data; + self = wps_data->self; + + if (!self) { + _wps_data_free(wps_data, dbus_connection); + if (res) + _LOGT("wps: cancel completed successfully, after supplicant interface is gone"); + else + _LOGW("wps: cancel failed (%s), after supplicant interface is gone", error->message); + return; + } + + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + wps_data->is_cancelling = FALSE; + + if (!wps_data->type) { + priv->wps_data = NULL; + _wps_data_free(wps_data, dbus_connection); + if (res) + _LOGT("wps: cancel completed successfully"); + else + _LOGW("wps: cancel failed (%s)", error->message); + return; + } + + if (res) + _LOGT("wps: cancel completed successfully, setting ProcessCredentials now..."); + else + _LOGW("wps: cancel failed (%s), setting ProcessCredentials now...", error->message); + + _wps_call_set_pc(self, wps_data); +} + +static void +_wps_start(NMSupplicantInterface *self, const char *type, const char *bssid, const char *pin) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + WpsData * wps_data; + + if (type) + _LOGI("wps: type %s start...", type); + + wps_data = priv->wps_data; + + if (!wps_data) { + if (!type) + return; + + if (priv->state == NM_SUPPLICANT_INTERFACE_STATE_DOWN) { + _LOGD("wps: interface is down. Cannot start with WPS"); + return; + } + + wps_data = g_slice_new(WpsData); + *wps_data = (WpsData){ + .self = self, + .type = g_strdup(type), + .bssid = g_strdup(bssid), + .pin = g_strdup(pin), + }; + priv->wps_data = wps_data; + } else { + g_free(wps_data->type); + g_free(wps_data->bssid); + g_free(wps_data->pin); + wps_data->type = g_strdup(type); + wps_data->bssid = g_strdup(bssid); + wps_data->pin = g_strdup(pin); + } + + if (wps_data->is_cancelling) { + /* we wait for cancellation to complete. */ + return; + } + + if (!type || wps_data->needs_cancelling) { + _LOGT("wps: cancel %senrollment...", wps_data->needs_cancelling ? "previous " : ""); + + wps_data->is_cancelling = TRUE; + wps_data->needs_cancelling = FALSE; + nm_clear_g_cancellable(&wps_data->cancellable); + nm_clear_g_dbus_connection_signal(priv->dbus_connection, &wps_data->signal_id); + + _dbus_connection_call(self, + NM_WPAS_DBUS_IFACE_INTERFACE_WPS, + "Cancel", + NULL, + G_VARIANT_TYPE("()"), + G_DBUS_CALL_FLAGS_NONE, + 5000, + NULL, + _wps_handle_cancel_cb, + wps_data); + return; + } + + _LOGT("wps: setting ProcessCredentials..."); + _wps_call_set_pc(self, wps_data); +} + +void +nm_supplicant_interface_enroll_wps(NMSupplicantInterface *self, + const char * type, + const char * bssid, + const char * pin) +{ + _wps_start(self, type, bssid, pin); +} + +void +nm_supplicant_interface_cancel_wps(NMSupplicantInterface *self) +{ + _wps_start(self, NULL, NULL, NULL); +} + +/*****************************************************************************/ + +static void +iface_introspect_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMSupplicantInterface * self; + NMSupplicantInterfacePrivate *priv; + gs_unref_variant GVariant *res = NULL; + gs_free_error GError *error = NULL; + const char * data; + NMTernary value; + + res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); + if (nm_utils_error_is_cancelled(error)) + return; + + self = NM_SUPPLICANT_INTERFACE(user_data); + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + nm_assert(NM_SUPPL_CAP_MASK_GET(priv->global_capabilities, NM_SUPPL_CAP_TYPE_AP) + == NM_TERNARY_DEFAULT); + + value = NM_TERNARY_DEFAULT; + if (res) { + g_variant_get(res, "(&s)", &data); + + /* The ProbeRequest method only exists if AP mode has been enabled */ + value = strstr(data, "ProbeRequest") ? NM_TERNARY_TRUE : NM_TERNARY_FALSE; + } + + priv->iface_capabilities = + NM_SUPPL_CAP_MASK_SET(priv->iface_capabilities, NM_SUPPL_CAP_TYPE_AP, value); + + priv->starting_pending_count--; + _starting_check_ready(self); +} + +static void +_properties_changed_main(NMSupplicantInterface *self, GVariant *properties) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + const char ** v_strv; + const char * v_s; + gboolean v_b; + gint32 v_i32; + GVariant * v_v; + gboolean do_log_driver_info = FALSE; + gboolean do_set_state = FALSE; + gboolean do_notify_current_bss = FALSE; + + nm_assert(properties || g_variant_is_of_type(properties, G_VARIANT_TYPE("a{sv}"))); + + v_v = g_variant_lookup_value(properties, "Capabilities", G_VARIANT_TYPE_VARDICT); + if (v_v) { + parse_capabilities(self, v_v); + g_variant_unref(v_v); + } + + if (nm_g_variant_lookup(properties, "Scanning", "b", &v_b)) { + if (priv->scanning_property != (!!v_b)) { + _LOGT("scanning: %s (plain property)", v_b ? "yes" : "no"); + priv->scanning_property = v_b; + } + } + + if (nm_g_variant_lookup(properties, "Ifname", "&s", &v_s)) { + if (nm_utils_strdup_reset(&priv->ifname, v_s)) + do_log_driver_info = TRUE; + } + if (nm_g_variant_lookup(properties, "Driver", "&s", &v_s)) { + if (nm_utils_strdup_reset(&priv->driver, v_s)) + do_log_driver_info = TRUE; + } + + if (nm_g_variant_lookup(properties, "DisconnectReason", "i", &v_i32)) { + /* Disconnect reason is currently only given for deauthentication events, + * not disassociation; currently they are IEEE 802.11 "reason codes", + * defined by (IEEE 802.11-2007, 7.3.1.7, Table 7-22). Any locally caused + * deauthentication will be negative, while authentications caused by the + * AP will be positive. + */ + priv->disconnect_reason = v_i32; + } + + if (nm_g_variant_lookup(properties, "State", "&s", &v_s)) { + NMSupplicantInterfaceState state; + + state = wpas_state_string_to_enum(v_s); + if (state == NM_SUPPLICANT_INTERFACE_STATE_INVALID) + _LOGT("state: ignore unknown supplicant state '%s' (is %s, plain property)", + v_s, + nm_supplicant_interface_state_to_string(priv->supp_state)); + else if (priv->supp_state != state) { + _LOGT("state: %s (was %s, plain property)", + nm_supplicant_interface_state_to_string(state), + nm_supplicant_interface_state_to_string(priv->supp_state)); + priv->supp_state = state; + if (priv->state > NM_SUPPLICANT_INTERFACE_STATE_STARTING) { + /* Only transition to actual wpa_supplicant interface states (ie, + * anything > STARTING) after the NMSupplicantInterface has had a + * chance to initialize, which is signalled by entering the STARTING + * state. + */ + do_set_state = TRUE; + } + } + } + + if (nm_g_variant_lookup(properties, "CurrentBSS", "&o", &v_s)) { + v_s = nm_dbus_path_not_empty(v_s); + if (!nm_ref_string_equals_str(priv->current_bss, v_s)) { + nm_ref_string_unref(priv->current_bss); + priv->current_bss = nm_ref_string_new(v_s); + do_notify_current_bss = TRUE; + } + } + + if (nm_g_variant_lookup(properties, "ApIsolate", "&s", &v_s)) + priv->ap_isolate_supported = TRUE; + + if (do_log_driver_info) { + _LOGD("supplicant interface for ifindex=%d, ifname=%s%s%s, driver=%s%s%s (requested %s)", + priv->ifindex, + NM_PRINT_FMT_QUOTE_STRING(priv->ifname), + NM_PRINT_FMT_QUOTE_STRING(priv->driver), + nm_supplicant_driver_to_string(priv->requested_driver)); + } + + if (nm_g_variant_lookup(properties, "BSSs", "^a&o", &v_strv)) { + NMSupplicantBssInfo *bss_info; + NMSupplicantBssInfo *bss_info_safe; + const char ** iter; + + c_list_for_each_entry (bss_info, &priv->bss_lst_head, _bss_lst) + bss_info->_bss_dirty = TRUE; + c_list_for_each_entry (bss_info, &priv->bss_initializing_lst_head, _bss_lst) + bss_info->_bss_dirty = TRUE; + + for (iter = v_strv; *iter; iter++) + _bss_info_add(self, *iter); + + g_free(v_strv); + + c_list_for_each_entry_safe (bss_info, + bss_info_safe, + &priv->bss_initializing_lst_head, + _bss_lst) { + if (bss_info->_bss_dirty) + _bss_info_remove(self, &bss_info->bss_path); + } + c_list_for_each_entry_safe (bss_info, bss_info_safe, &priv->bss_lst_head, _bss_lst) { + if (bss_info->_bss_dirty) + _bss_info_remove(self, &bss_info->bss_path); + } + } + + if (do_notify_current_bss) + _notify(self, PROP_CURRENT_BSS); + + if (do_set_state) + set_state(self, priv->supp_state); + + _notify_maybe_scanning(self); +} + +static void +_properties_changed_p2p_device(NMSupplicantInterface *self, GVariant *properties) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + const char ** v_strv; + const char * v_s; + + nm_assert(!properties || g_variant_is_of_type(properties, G_VARIANT_TYPE("a{sv}"))); + + if (nm_g_variant_lookup(properties, "Peers", "^a&o", &v_strv)) { + NMSupplicantPeerInfo *peer_info; + NMSupplicantPeerInfo *peer_info_safe; + const char *const * iter; + + c_list_for_each_entry (peer_info, &priv->peer_lst_head, _peer_lst) + peer_info->_peer_dirty = TRUE; + c_list_for_each_entry (peer_info, &priv->peer_initializing_lst_head, _peer_lst) + peer_info->_peer_dirty = TRUE; + + for (iter = v_strv; *iter; iter++) + _peer_info_add(self, *iter); + + g_free(v_strv); + + c_list_for_each_entry_safe (peer_info, + peer_info_safe, + &priv->peer_initializing_lst_head, + _peer_lst) { + if (peer_info->_peer_dirty) + _peer_info_remove(self, &peer_info->peer_path); + } + c_list_for_each_entry_safe (peer_info, peer_info_safe, &priv->peer_lst_head, _peer_lst) { + if (peer_info->_peer_dirty) + _peer_info_remove(self, &peer_info->peer_path); + } + } + + if (nm_g_variant_lookup(properties, "Group", "&o", &v_s)) + _p2p_group_set_path(self, v_s); +} + +/*****************************************************************************/ + +static void +assoc_return(NMSupplicantInterface *self, GError *error, const char *message) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + AssocData * assoc_data; + + assoc_data = g_steal_pointer(&priv->assoc_data); + if (!assoc_data) + return; + + if (error) { + g_dbus_error_strip_remote_error(error); + _LOGW("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: %s: %s", + NM_HASH_OBFUSCATE_PTR(assoc_data), + message, + error->message); + } else { + _LOGD("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: association request successful", + NM_HASH_OBFUSCATE_PTR(assoc_data)); + } + + if (assoc_data->add_network_data) { + /* signal that this request already completed */ + assoc_data->add_network_data->assoc_data = NULL; + } + + nm_clear_g_source(&assoc_data->fail_on_idle_id); + nm_clear_g_cancellable(&assoc_data->cancellable); + + if (assoc_data->callback) + assoc_data->callback(self, error, assoc_data->user_data); + + g_object_unref(assoc_data->cfg); + g_slice_free(AssocData, assoc_data); +} + +void +nm_supplicant_interface_disconnect(NMSupplicantInterface *self) +{ + NMSupplicantInterfacePrivate *priv; + + g_return_if_fail(NM_IS_SUPPLICANT_INTERFACE(self)); + + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + /* Disconnect from the current AP */ + if ((priv->state >= NM_SUPPLICANT_INTERFACE_STATE_SCANNING) + && (priv->state <= NM_SUPPLICANT_INTERFACE_STATE_COMPLETED)) { + _dbus_connection_call_simple(self, + NM_WPAS_DBUS_IFACE_INTERFACE, + "Disconnect", + NULL, + G_VARIANT_TYPE("()"), + "disconnect"); + } + + _remove_network(self); + + /* Cancel any WPS enrollment, if any */ + nm_supplicant_interface_cancel_wps(self); + + /* Cancel all pending calls related to a prior connection attempt */ + if (priv->assoc_data) { + gs_free_error GError *error = NULL; + + nm_utils_error_set_cancelled(&error, FALSE, "NMSupplicantInterface"); + assoc_return(self, error, "abort due to disconnect"); + } +} + +static void +disconnect_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + gs_unref_object NMSupplicantInterface *self = NULL; + gs_unref_variant GVariant *res = NULL; + gs_free_error GError * error = NULL; + NMSupplicantInterfaceDisconnectCb callback; + gpointer callback_user_data; + + nm_utils_user_data_unpack(user_data, &self, &callback, &callback_user_data); + + res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); + + if (!res && !strstr(error->message, "fi.w1.wpa_supplicant1.NotConnected")) { + /* an already disconnected interface is not an error*/ + g_clear_error(&error); + } + + callback(self, error, callback_user_data); +} + +void +nm_supplicant_interface_disconnect_async(NMSupplicantInterface * self, + GCancellable * cancellable, + NMSupplicantInterfaceDisconnectCb callback, + gpointer user_data) +{ + g_return_if_fail(NM_IS_SUPPLICANT_INTERFACE(self)); + g_return_if_fail(callback); + + _dbus_connection_call(self, + NM_WPAS_DBUS_IFACE_INTERFACE, + "Disconnect", + NULL, + G_VARIANT_TYPE("()"), + G_DBUS_CALL_FLAGS_NONE, + DBUS_TIMEOUT_MSEC, + cancellable, + disconnect_cb, + nm_utils_user_data_pack(g_object_ref(self), callback, user_data)); +} + +static void +assoc_select_network_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMSupplicantInterface *self; + gs_unref_variant GVariant *res = NULL; + gs_free_error GError *error = NULL; + + res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); + if (nm_utils_error_is_cancelled(error)) + return; + + self = NM_SUPPLICANT_INTERFACE(user_data); + if (error) + assoc_return(self, error, "failure to select network config"); + else + assoc_return(self, NULL, NULL); +} + +static void +assoc_call_select_network(NMSupplicantInterface *self) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + _dbus_connection_call(self, + NM_WPAS_DBUS_IFACE_INTERFACE, + "SelectNetwork", + g_variant_new("(o)", priv->net_path), + G_VARIANT_TYPE("()"), + G_DBUS_CALL_FLAGS_NONE, + DBUS_TIMEOUT_MSEC, + priv->assoc_data->cancellable, + assoc_select_network_cb, + self); +} + +static void +assoc_add_blob_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMSupplicantInterface * self; + NMSupplicantInterfacePrivate *priv; + gs_unref_variant GVariant *res = NULL; + gs_free_error GError *error = NULL; + + res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); + if (nm_utils_error_is_cancelled(error)) + return; + + self = NM_SUPPLICANT_INTERFACE(user_data); + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + if (error) { + assoc_return(self, error, "failure to set network certificates"); + return; + } + + priv->assoc_data->blobs_left--; + _LOGT("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: blob added (%u left)", + NM_HASH_OBFUSCATE_PTR(priv->assoc_data), + priv->assoc_data->blobs_left); + if (priv->assoc_data->blobs_left == 0) + assoc_call_select_network(self); +} + +static void +assoc_add_network_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + AddNetworkData * add_network_data = user_data; + AssocData * assoc_data; + NMSupplicantInterface * self; + NMSupplicantInterfacePrivate *priv; + gs_unref_variant GVariant *res = NULL; + gs_free_error GError *error = NULL; + GHashTable * blobs; + GHashTableIter iter; + const char * blob_name; + GBytes * blob_data; + nm_auto_ref_string NMRefString *name_owner = NULL; + nm_auto_ref_string NMRefString *object_path = NULL; + + g_clear_object(&add_network_data->shutdown_wait_obj); + + assoc_data = add_network_data->assoc_data; + if (assoc_data) + assoc_data->add_network_data = NULL; + name_owner = g_steal_pointer(&add_network_data->name_owner); + object_path = g_steal_pointer(&add_network_data->object_path); + nm_g_slice_free(add_network_data); + + res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); + + if (!assoc_data) { + if (!error) { + const char *net_path; + + /* the assoc-request was already cancelled, but the AddNetwork request succeeded. + * Cleanup the created network. + * + * This cleanup action does not work when NetworkManager is about to exit + * and leaves the mainloop. During program shutdown, we may orphan networks. */ + g_variant_get(res, "(&o)", &net_path); + g_dbus_connection_call(G_DBUS_CONNECTION(source), + name_owner->str, + object_path->str, + NM_WPAS_DBUS_IFACE_INTERFACE, + "RemoveNetwork", + g_variant_new("(o)", net_path), + G_VARIANT_TYPE("()"), + G_DBUS_CALL_FLAGS_NONE, + DBUS_TIMEOUT_MSEC, + NULL, + NULL, + NULL); + } + return; + } + + self = NM_SUPPLICANT_INTERFACE(assoc_data->self); + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + if (error) { + assoc_return(self, error, "failure to add network"); + return; + } + + nm_assert(!priv->net_path); + g_variant_get(res, "(o)", &priv->net_path); + + /* Send blobs first; otherwise jump to selecting the network */ + blobs = nm_supplicant_config_get_blobs(priv->assoc_data->cfg); + priv->assoc_data->blobs_left = blobs ? g_hash_table_size(blobs) : 0u; + + _LOGT("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: network added (%s) (%u blobs left)", + NM_HASH_OBFUSCATE_PTR(priv->assoc_data), + priv->net_path, + priv->assoc_data->blobs_left); + + if (priv->assoc_data->blobs_left == 0) { + assoc_call_select_network(self); + return; + } + + g_hash_table_iter_init(&iter, blobs); + while (g_hash_table_iter_next(&iter, (gpointer) &blob_name, (gpointer) &blob_data)) { + _dbus_connection_call( + self, + NM_WPAS_DBUS_IFACE_INTERFACE, + "AddBlob", + g_variant_new("(s@ay)", blob_name, nm_utils_gbytes_to_variant_ay(blob_data)), + G_VARIANT_TYPE("()"), + G_DBUS_CALL_FLAGS_NONE, + DBUS_TIMEOUT_MSEC, + priv->assoc_data->cancellable, + assoc_add_blob_cb, + self); + } +} + +static void +add_network(NMSupplicantInterface *self) +{ + NMSupplicantInterfacePrivate *priv; + AddNetworkData * add_network_data; + + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + /* the association does not keep @self alive. We want to be able to remove + * the network again, even if @self is already gone. Hence, track the data + * separately. + * + * For that we also have a shutdown_wait_obj so that on exit we still wait + * to handle the response. */ + add_network_data = g_slice_new(AddNetworkData); + *add_network_data = (AddNetworkData){ + .assoc_data = priv->assoc_data, + .name_owner = nm_ref_string_ref(priv->name_owner), + .object_path = nm_ref_string_ref(priv->object_path), + .shutdown_wait_obj = g_object_new(G_TYPE_OBJECT, NULL), + }; + nm_shutdown_wait_obj_register_object(add_network_data->shutdown_wait_obj, + "supplicant-add-network"); + priv->assoc_data->add_network_data = add_network_data; + + _dbus_connection_call( + self, + NM_WPAS_DBUS_IFACE_INTERFACE, + "AddNetwork", + g_variant_new("(@a{sv})", nm_supplicant_config_to_variant(priv->assoc_data->cfg)), + G_VARIANT_TYPE("(o)"), + G_DBUS_CALL_FLAGS_NONE, + DBUS_TIMEOUT_MSEC, + NULL, + assoc_add_network_cb, + add_network_data); +} + +static void +assoc_set_ap_isolation(GVariant *ret, GError *error, gpointer user_data) +{ + NMSupplicantInterface * self; + NMSupplicantInterfacePrivate *priv; + gboolean value; + + if (nm_utils_error_is_cancelled(error)) + return; + + self = NM_SUPPLICANT_INTERFACE(user_data); + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + if (error) { + assoc_return(self, error, "failure to set AP isolation"); + return; + } + + value = nm_supplicant_config_get_ap_isolation(priv->assoc_data->cfg); + _LOGT("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: interface AP isolation set to %d", + NM_HASH_OBFUSCATE_PTR(priv->assoc_data), + value); + + priv->ap_isolate_needs_reset = value; + + nm_assert(priv->assoc_data->calls_left > 0); + if (--priv->assoc_data->calls_left == 0) + add_network(self); +} + +static void +assoc_set_ap_scan_cb(GVariant *ret, GError *error, gpointer user_data) +{ + NMSupplicantInterface * self; + NMSupplicantInterfacePrivate *priv; + + if (nm_utils_error_is_cancelled(error)) + return; + + self = NM_SUPPLICANT_INTERFACE(user_data); + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + if (error) { + assoc_return(self, error, "failure to set AP scan mode"); + return; + } + + _LOGT("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: interface ap_scan set to %d", + NM_HASH_OBFUSCATE_PTR(priv->assoc_data), + nm_supplicant_config_get_ap_scan(priv->assoc_data->cfg)); + + nm_assert(priv->assoc_data->calls_left > 0); + if (--priv->assoc_data->calls_left == 0) + add_network(self); +} + +static gboolean +assoc_fail_on_idle_cb(gpointer user_data) +{ + NMSupplicantInterface * self = user_data; + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + gs_free_error GError *error = NULL; + + priv->assoc_data->fail_on_idle_id = 0; + g_set_error(&error, + NM_SUPPLICANT_ERROR, + NM_SUPPLICANT_ERROR_CONFIG, + "EAP-FAST is not supported by the supplicant"); + assoc_return(self, error, "failure due to missing supplicant support"); + return G_SOURCE_REMOVE; +} + +/** + * nm_supplicant_interface_assoc: + * @self: the supplicant interface instance + * @cfg: the configuration with the data for the association + * @callback: callback invoked when the association completes or fails. + * @user_data: data for the callback. + * + * Calls AddNetwork and SelectNetwork to start associating according to @cfg. + * + * The callback is invoked exactly once (always) and always asynchronously. + * The pending association can be aborted via nm_supplicant_interface_disconnect() + * or by destroying @self. In that case, the @callback is invoked synchronously with + * an error reason indicating cancellation/disposing (see nm_utils_error_is_cancelled()). + */ +void +nm_supplicant_interface_assoc(NMSupplicantInterface * self, + NMSupplicantConfig * cfg, + NMSupplicantInterfaceAssocCb callback, + gpointer user_data) +{ + NMSupplicantInterfacePrivate *priv; + AssocData * assoc_data; + gboolean ap_isolation; + + g_return_if_fail(NM_IS_SUPPLICANT_INTERFACE(self)); + g_return_if_fail(NM_IS_SUPPLICANT_CONFIG(cfg)); + + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + nm_supplicant_interface_disconnect(self); + + assoc_data = g_slice_new(AssocData); + *assoc_data = (AssocData){ + .self = self, + .cfg = g_object_ref(cfg), + .callback = callback, + .user_data = user_data, + }; + + priv->assoc_data = assoc_data; + + _LOGD("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: starting association...", + NM_HASH_OBFUSCATE_PTR(assoc_data)); + + if (_get_capability(priv, NM_SUPPL_CAP_TYPE_FAST) == NM_TERNARY_FALSE + && nm_supplicant_config_fast_required(cfg)) { + /* Make sure the supplicant supports EAP-FAST before trying to send + * it an EAP-FAST configuration. + */ + assoc_data->fail_on_idle_id = g_idle_add(assoc_fail_on_idle_cb, self); + return; + } + + assoc_data->cancellable = g_cancellable_new(); + assoc_data->calls_left++; + nm_dbus_connection_call_set( + priv->dbus_connection, + priv->name_owner->str, + priv->object_path->str, + NM_WPAS_DBUS_IFACE_INTERFACE, + "ApScan", + g_variant_new_uint32(nm_supplicant_config_get_ap_scan(priv->assoc_data->cfg)), + DBUS_TIMEOUT_MSEC, + assoc_data->cancellable, + assoc_set_ap_scan_cb, + self); + + ap_isolation = nm_supplicant_config_get_ap_isolation(priv->assoc_data->cfg); + if (!priv->ap_isolate_supported) { + if (ap_isolation) { + _LOGW("assoc[" NM_HASH_OBFUSCATE_PTR_FMT + "]: requested AP isolation but the supplicant doesn't support it", + NM_HASH_OBFUSCATE_PTR(assoc_data)); + } + } else { + assoc_data->calls_left++; + /* It would be smarter to change the property only when necessary. + * However, wpa_supplicant doesn't send the PropertiesChanged + * signal for ApIsolate, and so to know the current value we would + * need first a Get call. It seems simpler to just set the value + * we want. */ + nm_dbus_connection_call_set(priv->dbus_connection, + priv->name_owner->str, + priv->object_path->str, + NM_WPAS_DBUS_IFACE_INTERFACE, + "ApIsolate", + g_variant_new_string(ap_isolation ? "1" : "0"), + DBUS_TIMEOUT_MSEC, + assoc_data->cancellable, + assoc_set_ap_isolation, + self); + } +} + +/*****************************************************************************/ + +typedef struct { + NMSupplicantInterface * self; + GCancellable * cancellable; + NMSupplicantInterfaceRequestScanCallback callback; + gpointer user_data; +} ScanRequestData; + +static void +scan_request_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + gs_unref_object NMSupplicantInterface *self_keep_alive = NULL; + NMSupplicantInterface * self; + gs_unref_variant GVariant *res = NULL; + gs_free_error GError *error = NULL; + ScanRequestData * data = user_data; + gboolean cancelled = FALSE; + + res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); + if (nm_utils_error_is_cancelled(error)) { + if (!data->callback) { + /* the self instance was not kept alive. We also must not touch it. Return. */ + nm_g_object_unref(data->cancellable); + nm_g_slice_free(data); + return; + } + cancelled = TRUE; + } + + self = data->self; + if (data->callback) { + /* the self instance was kept alive. Balance the reference count. */ + self_keep_alive = self; + } + + /* we don't propagate the error/success. That is, because either answer is not + * reliable. What is important to us is whether the request completed, and + * the current nm_supplicant_interface_get_scanning() state. */ + if (cancelled) + _LOGD("request-scan: request cancelled"); + else { + if (error) { + if (_nm_dbus_error_has_name(error, "fi.w1.wpa_supplicant1.Interface.ScanError")) + _LOGD("request-scan: could not get scan request result: %s", error->message); + else { + g_dbus_error_strip_remote_error(error); + _LOGW("request-scan: could not get scan request result: %s", error->message); + } + } else + _LOGT("request-scan: request scanning success"); + } + + if (data->callback) + data->callback(self, data->cancellable, data->user_data); + + nm_g_object_unref(data->cancellable); + nm_g_slice_free(data); +} + +void +nm_supplicant_interface_request_scan(NMSupplicantInterface * self, + GBytes *const * ssids, + guint ssids_len, + GCancellable * cancellable, + NMSupplicantInterfaceRequestScanCallback callback, + gpointer user_data) +{ + NMSupplicantInterfacePrivate *priv; + GVariantBuilder builder; + ScanRequestData * data; + guint i; + + g_return_if_fail(NM_IS_SUPPLICANT_INTERFACE(self)); + + nm_assert((!cancellable && !callback) || (G_IS_CANCELLABLE(cancellable) && callback)); + + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + _LOGT("request-scan: request scanning (%u ssids)...", ssids_len); + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add(&builder, "{sv}", "Type", g_variant_new_string("active")); + g_variant_builder_add(&builder, "{sv}", "AllowRoam", g_variant_new_boolean(FALSE)); + if (ssids_len > 0) { + GVariantBuilder ssids_builder; + + g_variant_builder_init(&ssids_builder, G_VARIANT_TYPE_BYTESTRING_ARRAY); + for (i = 0; i < ssids_len; i++) { + nm_assert(ssids[i]); + g_variant_builder_add(&ssids_builder, "@ay", nm_utils_gbytes_to_variant_ay(ssids[i])); + } + g_variant_builder_add(&builder, "{sv}", "SSIDs", g_variant_builder_end(&ssids_builder)); + } + + data = g_slice_new(ScanRequestData); + *data = (ScanRequestData){ + .self = self, + .callback = callback, + .user_data = user_data, + .cancellable = nm_g_object_ref(cancellable), + }; + + if (callback) { + /* A callback was provided. This keeps @self alive. The caller + * must provide a cancellable as the caller must never leave an asynchronous + * operation pending indefinitely. */ + nm_assert(G_IS_CANCELLABLE(cancellable)); + g_object_ref(self); + } else { + /* We don't keep @self alive, and we don't accept a cancellable either. */ + nm_assert(!cancellable); + cancellable = priv->main_cancellable; + } + + _dbus_connection_call(self, + NM_WPAS_DBUS_IFACE_INTERFACE, + "Scan", + g_variant_new("(a{sv})", &builder), + G_VARIANT_TYPE("()"), + G_DBUS_CALL_FLAGS_NONE, + DBUS_TIMEOUT_MSEC, + cancellable, + scan_request_cb, + data); +} + +/*****************************************************************************/ + +NMSupplicantInterfaceState +nm_supplicant_interface_get_state(NMSupplicantInterface *self) +{ + g_return_val_if_fail(NM_IS_SUPPLICANT_INTERFACE(self), NM_SUPPLICANT_INTERFACE_STATE_DOWN); + + return NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self)->state; +} + +void +_nm_supplicant_interface_set_state_down(NMSupplicantInterface *self, + gboolean force_remove_from_supplicant, + const char * reason) +{ + set_state_down(self, force_remove_from_supplicant, reason); +} + +NMRefString * +nm_supplicant_interface_get_name_owner(NMSupplicantInterface *self) +{ + g_return_val_if_fail(NM_IS_SUPPLICANT_INTERFACE(self), NULL); + + return NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self)->name_owner; +} + +NMRefString * +nm_supplicant_interface_get_object_path(NMSupplicantInterface *self) +{ + g_return_val_if_fail(NM_IS_SUPPLICANT_INTERFACE(self), NULL); + + return NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self)->object_path; +} + +const char * +nm_supplicant_interface_get_ifname(NMSupplicantInterface *self) +{ + g_return_val_if_fail(NM_IS_SUPPLICANT_INTERFACE(self), NULL); + + return NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self)->ifname; +} + +guint +nm_supplicant_interface_get_max_scan_ssids(NMSupplicantInterface *self) +{ + NMSupplicantInterfacePrivate *priv; + + g_return_val_if_fail(NM_IS_SUPPLICANT_INTERFACE(self), 0); + + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + return priv->prop_scan_active && priv->prop_scan_ssid ? priv->max_scan_ssids : 0u; +} + +/*****************************************************************************/ + +void +nm_supplicant_interface_p2p_start_find(NMSupplicantInterface *self, guint timeout) +{ + GVariantBuilder builder; + + g_return_if_fail(NM_IS_SUPPLICANT_INTERFACE(self)); + g_return_if_fail(timeout > 0 && timeout <= 600); + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add(&builder, "{sv}", "Timeout", g_variant_new_int32(timeout)); + + _dbus_connection_call_simple(self, + NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE, + "Find", + g_variant_new("(a{sv})", &builder), + G_VARIANT_TYPE("()"), + "p2p-find"); +} + +void +nm_supplicant_interface_p2p_stop_find(NMSupplicantInterface *self) +{ + g_return_if_fail(NM_IS_SUPPLICANT_INTERFACE(self)); + + _dbus_connection_call_simple(self, + NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE, + "StopFind", + NULL, + G_VARIANT_TYPE("()"), + "p2p-stop-find"); +} + +/*****************************************************************************/ + +void +nm_supplicant_interface_p2p_connect(NMSupplicantInterface *self, + const char * peer, + const char * wps_method, + const char * wps_pin) +{ + GVariantBuilder builder; + + g_return_if_fail(NM_IS_SUPPLICANT_INTERFACE(self)); + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + + g_variant_builder_add(&builder, "{sv}", "wps_method", g_variant_new_string(wps_method)); + if (wps_pin) + g_variant_builder_add(&builder, "{sv}", "pin", g_variant_new_string(wps_pin)); + g_variant_builder_add(&builder, "{sv}", "peer", g_variant_new_object_path(peer)); + g_variant_builder_add(&builder, "{sv}", "join", g_variant_new_boolean(FALSE)); + g_variant_builder_add(&builder, "{sv}", "persistent", g_variant_new_boolean(FALSE)); + g_variant_builder_add(&builder, "{sv}", "go_intent", g_variant_new_int32(7)); + + _dbus_connection_call_simple(self, + NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE, + "Connect", + g_variant_new("(a{sv})", &builder), + G_VARIANT_TYPE("(s)"), + "p2p-connect"); +} + +void +nm_supplicant_interface_p2p_cancel_connect(NMSupplicantInterface *self) +{ + g_return_if_fail(NM_IS_SUPPLICANT_INTERFACE(self)); + + _dbus_connection_call_simple(self, + NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE, + "Cancel", + NULL, + G_VARIANT_TYPE("()"), + "p2p-cancel"); +} + +void +nm_supplicant_interface_p2p_disconnect(NMSupplicantInterface *self) +{ + g_return_if_fail(NM_IS_SUPPLICANT_INTERFACE(self)); + + _dbus_connection_call_simple(self, + NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE, + "Disconnect", + NULL, + G_VARIANT_TYPE("()"), + "p2p-disconnect"); +} + +/*****************************************************************************/ + +static void +_properties_changed(NMSupplicantInterface *self, + const char * interface_name, + GVariant * properties, + gboolean initial) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + gboolean is_main; + + nm_assert(!properties || g_variant_is_of_type(properties, G_VARIANT_TYPE("a{sv}"))); + + if (initial) + priv->starting_pending_count--; + + if ((initial || priv->is_ready_main) && nm_streq(interface_name, NM_WPAS_DBUS_IFACE_INTERFACE)) + is_main = TRUE; + else if ((initial || priv->is_ready_p2p_device) + && nm_streq(interface_name, NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE)) { + nm_assert(_get_capability(priv, NM_SUPPL_CAP_TYPE_P2P) == NM_TERNARY_TRUE); + is_main = FALSE; + } else + return; + + g_object_freeze_notify(G_OBJECT(self)); + + priv->starting_pending_count++; + + if (is_main) { + priv->is_ready_main = TRUE; + _properties_changed_main(self, properties); + } else { + priv->is_ready_p2p_device = TRUE; + _properties_changed_p2p_device(self, properties); + } + + priv->starting_pending_count--; + _starting_check_ready(self); + + _notify_maybe_scanning(self); + _notify_maybe_p2p_available(self); + + g_object_thaw_notify(G_OBJECT(self)); +} + +static void +_properties_changed_cb(GDBusConnection *connection, + const char * sender_name, + const char * object_path, + const char * signal_interface_name, + const char * signal_name, + GVariant * parameters, + gpointer user_data) +{ + NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE(user_data); + const char * interface_name; + gs_unref_variant GVariant *changed_properties = NULL; + + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sa{sv}as)"))) + return; + + g_variant_get(parameters, "(&s@a{sv}^a&s)", &interface_name, &changed_properties, NULL); + _properties_changed(self, interface_name, changed_properties, FALSE); +} + +static void +_bss_properties_changed_cb(GDBusConnection *connection, + const char * sender_name, + const char * object_path, + const char * signal_interface_name, + const char * signal_name, + GVariant * parameters, + gpointer user_data) +{ + NMSupplicantInterface * self = NM_SUPPLICANT_INTERFACE(user_data); + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + nm_auto_ref_string NMRefString *bss_path = NULL; + gs_unref_variant GVariant *changed_properties = NULL; + NMSupplicantBssInfo * bss_info; + + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sa{sv}as)"))) + return; + + bss_path = nm_ref_string_new(object_path); + + bss_info = g_hash_table_lookup(priv->bss_idx, &bss_path); + if (!bss_info) + return; + if (bss_info->_init_cancellable) + return; + + g_variant_get(parameters, "(&s@a{sv}^a&s)", NULL, &changed_properties, NULL); + _bss_info_properties_changed(self, bss_info, changed_properties, FALSE); +} + +static void +_peer_properties_changed_cb(GDBusConnection *connection, + const char * sender_name, + const char * object_path, + const char * signal_interface_name, + const char * signal_name, + GVariant * parameters, + gpointer user_data) +{ + NMSupplicantInterface * self = NM_SUPPLICANT_INTERFACE(user_data); + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + nm_auto_ref_string NMRefString *peer_path = NULL; + gs_unref_variant GVariant *changed_properties = NULL; + NMSupplicantPeerInfo * peer_info; + + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sa{sv}as)"))) + return; + + peer_path = nm_ref_string_new(object_path); + + peer_info = g_hash_table_lookup(priv->peer_idx, &peer_path); + if (!peer_info) + return; + if (peer_info->_init_cancellable) + return; + + g_variant_get(parameters, "(&s@a{sv}^a&s)", NULL, &changed_properties, NULL); + _peer_info_properties_changed(self, peer_info, changed_properties, FALSE); +} + +static void +_get_all_main_cb(GVariant *result, GError *error, gpointer user_data) +{ + gs_unref_variant GVariant *properties = NULL; + + if (nm_utils_error_is_cancelled(error)) + return; + + if (result) + g_variant_get(result, "(@a{sv})", &properties); + _properties_changed(user_data, NM_WPAS_DBUS_IFACE_INTERFACE, properties, TRUE); +} + +static void +_get_all_p2p_device_cb(GVariant *result, GError *error, gpointer user_data) +{ + gs_unref_variant GVariant *properties = NULL; + + if (nm_utils_error_is_cancelled(error)) + return; + + if (result) + g_variant_get(result, "(@a{sv})", &properties); + _properties_changed(user_data, NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE, properties, TRUE); +} + +static void +_signal_handle(NMSupplicantInterface *self, + const char * signal_interface_name, + const char * signal_name, + GVariant * parameters) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + const char * path; + + if (nm_streq(signal_interface_name, NM_WPAS_DBUS_IFACE_INTERFACE)) { + if (!priv->is_ready_main) + return; + + if (nm_streq(signal_name, "BSSAdded")) { + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(oa{sv})"))) + return; + + g_variant_get(parameters, "(&oa{sv})", &path, NULL); + _bss_info_add(self, path); + return; + } + + if (nm_streq(signal_name, "BSSRemoved")) { + nm_auto_ref_string NMRefString *bss_path = NULL; + + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(o)"))) + return; + + g_variant_get(parameters, "(&o)", &path); + bss_path = nm_ref_string_new(path); + _bss_info_remove(self, &bss_path); + return; + } + + if (nm_streq(signal_name, "EAP")) { + NMSupplicantAuthState auth_state = NM_SUPPLICANT_AUTH_STATE_UNKNOWN; + const char * status; + const char * parameter; + + if (g_variant_is_of_type(parameters, G_VARIANT_TYPE("(ss)"))) + return; + + g_variant_get(parameters, "(&s&s)", &status, ¶meter); + + if (nm_streq(status, "started")) + auth_state = NM_SUPPLICANT_AUTH_STATE_STARTED; + else if (nm_streq(status, "completion")) { + if (nm_streq(parameter, "success")) + auth_state = NM_SUPPLICANT_AUTH_STATE_SUCCESS; + else if (nm_streq(parameter, "failure")) + auth_state = NM_SUPPLICANT_AUTH_STATE_FAILURE; + } + + /* the state eventually reaches one of started, success or failure + * so ignore any other intermediate (unknown) state change. */ + if (auth_state != NM_SUPPLICANT_AUTH_STATE_UNKNOWN && auth_state != priv->auth_state) { + priv->auth_state = auth_state; + _notify(self, PROP_AUTH_STATE); + } + return; + } + + return; + } + + if (nm_streq(signal_interface_name, NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE)) { + if (!priv->is_ready_p2p_device) + return; + + if (nm_streq(signal_name, "DeviceFound")) { + if (g_variant_is_of_type(parameters, G_VARIANT_TYPE("(o)"))) { + g_variant_get(parameters, "(&o)", &path); + _peer_info_add(self, path); + } + return; + } + + if (nm_streq(signal_name, "DeviceLost")) { + if (g_variant_is_of_type(parameters, G_VARIANT_TYPE("(o)"))) { + nm_auto_ref_string NMRefString *peer_path = NULL; + + g_variant_get(parameters, "(&o)", &path); + peer_path = nm_ref_string_new(path); + _peer_info_remove(self, &peer_path); + } + return; + } + + if (nm_streq(signal_name, "GroupStarted")) { + if (g_variant_is_of_type(parameters, G_VARIANT_TYPE("(a{sv})"))) { + gs_unref_variant GVariant *args = NULL; + gs_unref_object NMSupplicantInterface *iface = NULL; + const char * group_path; + const char * iface_path; + + g_variant_get(parameters, "(@a{sv})", &args); + if (!g_variant_lookup(args, "group_object", "&o", &group_path)) + return; + if (!g_variant_lookup(args, "interface_object", "&o", &iface_path)) + return; + + if (nm_streq(iface_path, priv->object_path->str)) { + _LOGW("P2P: GroupStarted on existing interface"); + iface = g_object_ref(self); + } else { + iface = + nm_supplicant_manager_create_interface_from_path(priv->supplicant_manager, + iface_path); + if (iface == NULL) { + _LOGW("P2P: Group interface already exists in GroupStarted handler, " + "aborting further processing."); + return; + } + } + + /* Signal existence of the (new) interface. */ + g_signal_emit(self, signals[GROUP_STARTED], 0, iface); + } + return; + } + + if (nm_streq(signal_name, "GroupFinished")) { + if (g_variant_is_of_type(parameters, G_VARIANT_TYPE("(a{sv})"))) { + gs_unref_variant GVariant *args = NULL; + const char * iface_path; + + g_variant_get(parameters, "(@a{sv})", &args); + + /* TODO: Group finished is called on the management interface! + * This means the signal consumer will currently need to assume which + * interface is finishing or it needs to match the object paths. + */ + if (!g_variant_lookup(args, "interface_object", "&o", &iface_path)) + return; + + _LOGD("P2P: GroupFinished signal on interface %s for interface %s", + priv->object_path->str, + iface_path); + + /* Signal group finish interface (on management interface). */ + g_signal_emit(self, signals[GROUP_FINISHED], 0, iface_path); + } + return; + } + + return; + } +} + +static void +_signal_cb(GDBusConnection *connection, + const char * sender_name, + const char * object_path, + const char * signal_interface_name, + const char * signal_name, + GVariant * parameters, + gpointer user_data) +{ + NMSupplicantInterface * self = user_data; + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + priv->starting_pending_count++; + _signal_handle(self, signal_interface_name, signal_name, parameters); + priv->starting_pending_count--; + _starting_check_ready(self); + + _notify_maybe_scanning(self); +} + +/*****************************************************************************/ + +gboolean +nm_supplicant_interface_get_p2p_available(NMSupplicantInterface *self) +{ + return NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self)->p2p_capable_cached; +} + +gboolean +nm_supplicant_interface_get_p2p_group_joined(NMSupplicantInterface *self) +{ + return NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self)->p2p_group_joined_cached; +} + +const char * +nm_supplicant_interface_get_p2p_group_path(NMSupplicantInterface *self) +{ + return nm_ref_string_get_str(NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self)->p2p_group_path); +} + +gboolean +nm_supplicant_interface_get_p2p_group_owner(NMSupplicantInterface *self) +{ + return NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self)->p2p_group_owner_cached; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSupplicantInterface * self = NM_SUPPLICANT_INTERFACE(object); + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + switch (prop_id) { + case PROP_SCANNING: + g_value_set_boolean(value, nm_supplicant_interface_get_scanning(self)); + break; + case PROP_CURRENT_BSS: + g_value_set_string(value, + nm_ref_string_get_str(nm_supplicant_interface_get_current_bss(self))); + break; + case PROP_P2P_GROUP_JOINED: + g_value_set_boolean(value, nm_supplicant_interface_get_p2p_group_joined(self)); + break; + case PROP_P2P_GROUP_PATH: + g_value_set_string(value, nm_supplicant_interface_get_p2p_group_path(self)); + break; + case PROP_P2P_GROUP_OWNER: + g_value_set_boolean(value, nm_supplicant_interface_get_p2p_group_owner(self)); + break; + case PROP_P2P_AVAILABLE: + g_value_set_boolean(value, nm_supplicant_interface_get_p2p_available(self)); + break; + case PROP_AUTH_STATE: + g_value_set_uint(value, priv->auth_state); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_SUPPLICANT_MANAGER: + /* construct-only */ + priv->supplicant_manager = g_object_ref(g_value_get_pointer(value)); + nm_assert(NM_IS_SUPPLICANT_MANAGER(priv->supplicant_manager)); + + priv->dbus_connection = + g_object_ref(nm_supplicant_manager_get_dbus_connection(priv->supplicant_manager)); + nm_assert(G_IS_DBUS_CONNECTION(priv->dbus_connection)); + + priv->name_owner = + nm_ref_string_ref(nm_supplicant_manager_get_dbus_name_owner(priv->supplicant_manager)); + nm_assert(NM_IS_REF_STRING(priv->name_owner)); + + priv->global_capabilities = + nm_supplicant_manager_get_global_capabilities(priv->supplicant_manager); + break; + case PROP_DBUS_OBJECT_PATH: + /* construct-only */ + priv->object_path = nm_ref_string_ref(g_value_get_pointer(value)); + nm_assert(NM_IS_REF_STRING(priv->object_path)); + break; + case PROP_IFINDEX: + /* construct-only */ + priv->ifindex = g_value_get_int(value); + break; + case PROP_DRIVER: + /* construct-only */ + priv->requested_driver = g_value_get_uint(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_supplicant_interface_init(NMSupplicantInterface *self) +{ + NMSupplicantInterfacePrivate *priv; + + priv = G_TYPE_INSTANCE_GET_PRIVATE(self, + NM_TYPE_SUPPLICANT_INTERFACE, + NMSupplicantInterfacePrivate); + + self->_priv = priv; + + nm_assert(priv->global_capabilities == NM_SUPPL_CAP_MASK_NONE); + nm_assert(priv->iface_capabilities == NM_SUPPL_CAP_MASK_NONE); + + priv->state = NM_SUPPLICANT_INTERFACE_STATE_STARTING; + priv->supp_state = NM_SUPPLICANT_INTERFACE_STATE_INVALID; + priv->last_scan_msec = -1; + + c_list_init(&self->supp_lst); + + G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMSupplicantBssInfo, bss_path) == 0); + priv->bss_idx = g_hash_table_new(nm_pdirect_hash, nm_pdirect_equal); + + c_list_init(&priv->bss_lst_head); + c_list_init(&priv->bss_initializing_lst_head); + + G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMSupplicantPeerInfo, peer_path) == 0); + priv->peer_idx = g_hash_table_new(nm_pdirect_hash, nm_pdirect_equal); + + c_list_init(&priv->peer_lst_head); + c_list_init(&priv->peer_initializing_lst_head); + + priv->main_cancellable = g_cancellable_new(); +} + +static void +constructed(GObject *object) +{ + NMSupplicantInterface * self = NM_SUPPLICANT_INTERFACE(object); + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + G_OBJECT_CLASS(nm_supplicant_interface_parent_class)->constructed(object); + + _LOGD("new supplicant interface %s on %s", priv->object_path->str, priv->name_owner->str); + + priv->properties_changed_id = + nm_dbus_connection_signal_subscribe_properties_changed(priv->dbus_connection, + priv->name_owner->str, + priv->object_path->str, + NULL, + _properties_changed_cb, + self, + NULL); + + priv->bss_properties_changed_id = + nm_dbus_connection_signal_subscribe_properties_changed(priv->dbus_connection, + priv->name_owner->str, + NULL, + NM_WPAS_DBUS_IFACE_BSS, + _bss_properties_changed_cb, + self, + NULL); + + priv->signal_id = g_dbus_connection_signal_subscribe(priv->dbus_connection, + priv->name_owner->str, + NULL, + NULL, + priv->object_path->str, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + _signal_cb, + self, + NULL); + + /* Scan result aging parameters */ + nm_dbus_connection_call_set(priv->dbus_connection, + priv->name_owner->str, + priv->object_path->str, + NM_WPAS_DBUS_IFACE_INTERFACE, + "BSSExpireAge", + g_variant_new_uint32(250), + DBUS_TIMEOUT_MSEC, + NULL, + NULL, + NULL); + nm_dbus_connection_call_set(priv->dbus_connection, + priv->name_owner->str, + priv->object_path->str, + NM_WPAS_DBUS_IFACE_INTERFACE, + "BSSExpireCount", + g_variant_new_uint32(2), + DBUS_TIMEOUT_MSEC, + NULL, + NULL, + NULL); + + if (_get_capability(priv, NM_SUPPL_CAP_TYPE_PMF) == NM_TERNARY_TRUE) { + /* Initialize global PMF setting to 'optional' */ + nm_dbus_connection_call_set(priv->dbus_connection, + priv->name_owner->str, + priv->object_path->str, + NM_WPAS_DBUS_IFACE_INTERFACE, + "Pmf", + g_variant_new_string("1"), + DBUS_TIMEOUT_MSEC, + NULL, + NULL, + NULL); + } + + if (_get_capability(priv, NM_SUPPL_CAP_TYPE_AP) == NM_TERNARY_DEFAULT) { + /* If the global supplicant capabilities property is not present, we can + * fall back to checking whether the ProbeRequest method is supported. If + * neither of these works we have no way of determining if AP mode is + * supported or not. hostap 1.0 and earlier don't support either of these. + */ + priv->starting_pending_count++; + _dbus_connection_call(self, + DBUS_INTERFACE_INTROSPECTABLE, + "Introspect", + NULL, + G_VARIANT_TYPE("(s)"), + G_DBUS_CALL_FLAGS_NONE, + 5000, + priv->main_cancellable, + iface_introspect_cb, + self); + } + + priv->starting_pending_count++; + nm_dbus_connection_call_get_all(priv->dbus_connection, + priv->name_owner->str, + priv->object_path->str, + NM_WPAS_DBUS_IFACE_INTERFACE, + 5000, + priv->main_cancellable, + _get_all_main_cb, + self); + + if (_get_capability(priv, NM_SUPPL_CAP_TYPE_P2P) == NM_TERNARY_TRUE) { + priv->peer_properties_changed_id = + nm_dbus_connection_signal_subscribe_properties_changed(priv->dbus_connection, + priv->name_owner->str, + NULL, + NM_WPAS_DBUS_IFACE_PEER, + _peer_properties_changed_cb, + self, + NULL); + + priv->starting_pending_count++; + nm_dbus_connection_call_get_all(priv->dbus_connection, + priv->name_owner->str, + priv->object_path->str, + NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE, + 5000, + priv->main_cancellable, + _get_all_p2p_device_cb, + self); + } +} + +NMSupplicantInterface * +nm_supplicant_interface_new(NMSupplicantManager *supplicant_manager, + NMRefString * object_path, + int ifindex, + NMSupplicantDriver driver) +{ + nm_assert(NM_IS_SUPPLICANT_MANAGER(supplicant_manager)); + + return g_object_new(NM_TYPE_SUPPLICANT_INTERFACE, + NM_SUPPLICANT_INTERFACE_SUPPLICANT_MANAGER, + supplicant_manager, + NM_SUPPLICANT_INTERFACE_DBUS_OBJECT_PATH, + object_path, + NM_SUPPLICANT_INTERFACE_IFINDEX, + ifindex, + NM_SUPPLICANT_INTERFACE_DRIVER, + (guint) driver, + NULL); +} + +static void +dispose(GObject *object) +{ + NMSupplicantInterface * self = NM_SUPPLICANT_INTERFACE(object); + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self); + + if (priv->state != NM_SUPPLICANT_INTERFACE_STATE_DOWN) + set_state_down(self, TRUE, "NMSupplicantInterface is disposing"); + + nm_assert(c_list_is_empty(&self->supp_lst)); + + if (priv->wps_data) { + /* we shut down, but an asynchronous Cancel request is pending. + * We don't want to cancel it, so mark wps-data that @self is gone. + * This way, _wps_handle_cancel_cb() knows it must no longer touch + * @self */ + priv->wps_data->self = NULL; + priv->wps_data = NULL; + } + + nm_assert(!priv->assoc_data); + + nm_clear_pointer(&priv->bss_idx, g_hash_table_destroy); + nm_clear_pointer(&priv->peer_idx, g_hash_table_destroy); + + nm_clear_pointer(&priv->current_bss, nm_ref_string_unref); + + G_OBJECT_CLASS(nm_supplicant_interface_parent_class)->dispose(object); + + nm_clear_pointer(&priv->object_path, nm_ref_string_unref); + nm_clear_pointer(&priv->name_owner, nm_ref_string_unref); + g_clear_object(&priv->supplicant_manager); + g_clear_object(&priv->dbus_connection); + nm_clear_g_free(&priv->ifname); + nm_clear_g_free(&priv->driver); + nm_assert(!priv->net_path); +} + +static void +nm_supplicant_interface_class_init(NMSupplicantInterfaceClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + g_type_class_add_private(object_class, sizeof(NMSupplicantInterfacePrivate)); + + object_class->constructed = constructed; + object_class->dispose = dispose; + object_class->set_property = set_property; + object_class->get_property = get_property; + + obj_properties[PROP_SUPPLICANT_MANAGER] = + g_param_spec_pointer(NM_SUPPLICANT_INTERFACE_SUPPLICANT_MANAGER, + "", + "", + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_DBUS_OBJECT_PATH] = + g_param_spec_pointer(NM_SUPPLICANT_INTERFACE_DBUS_OBJECT_PATH, + "", + "", + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_IFINDEX] = + g_param_spec_int(NM_SUPPLICANT_INTERFACE_IFINDEX, + "", + "", + 0, + G_MAXINT, + 0, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_DRIVER] = + g_param_spec_uint(NM_SUPPLICANT_INTERFACE_DRIVER, + "", + "", + 0, + G_MAXUINT, + NM_SUPPLICANT_DRIVER_WIRELESS, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_SCANNING] = g_param_spec_boolean(NM_SUPPLICANT_INTERFACE_SCANNING, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_CURRENT_BSS] = + g_param_spec_string(NM_SUPPLICANT_INTERFACE_CURRENT_BSS, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_P2P_GROUP_JOINED] = + g_param_spec_boolean(NM_SUPPLICANT_INTERFACE_P2P_GROUP_JOINED, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_P2P_GROUP_PATH] = + g_param_spec_string(NM_SUPPLICANT_INTERFACE_P2P_GROUP_PATH, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_P2P_GROUP_OWNER] = + g_param_spec_boolean(NM_SUPPLICANT_INTERFACE_P2P_GROUP_OWNER, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_P2P_AVAILABLE] = + g_param_spec_boolean(NM_SUPPLICANT_INTERFACE_P2P_AVAILABLE, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_AUTH_STATE] = g_param_spec_uint(NM_SUPPLICANT_INTERFACE_AUTH_STATE, + "", + "", + NM_SUPPLICANT_AUTH_STATE_UNKNOWN, + _NM_SUPPLICANT_AUTH_STATE_NUM - 1, + NM_SUPPLICANT_AUTH_STATE_UNKNOWN, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + signals[STATE] = g_signal_new(NM_SUPPLICANT_INTERFACE_STATE, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 3, + G_TYPE_INT, + G_TYPE_INT, + G_TYPE_INT); + + signals[BSS_CHANGED] = g_signal_new(NM_SUPPLICANT_INTERFACE_BSS_CHANGED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_POINTER, + G_TYPE_BOOLEAN); + + signals[PEER_CHANGED] = g_signal_new(NM_SUPPLICANT_INTERFACE_PEER_CHANGED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_POINTER, + G_TYPE_BOOLEAN); + + signals[WPS_CREDENTIALS] = g_signal_new(NM_SUPPLICANT_INTERFACE_WPS_CREDENTIALS, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + G_TYPE_VARIANT); + + signals[GROUP_STARTED] = g_signal_new(NM_SUPPLICANT_INTERFACE_GROUP_STARTED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + NM_TYPE_SUPPLICANT_INTERFACE); + + signals[GROUP_FINISHED] = g_signal_new(NM_SUPPLICANT_INTERFACE_GROUP_FINISHED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + G_TYPE_STRING); +} |