/* 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 #include #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 */ if (arr_data && arr_len && !(NM_IN_SET(arr_len, 8, 9) && memcmp(arr_data, "", 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); }