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