// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2017 Intel Corporation */ #include "nm-default.h" #include "nm-device-iwd.h" #include "devices/nm-device-private.h" #include "devices/nm-device.h" #include "nm-act-request.h" #include "nm-config.h" #include "nm-core-internal.h" #include "nm-dbus-manager.h" #include "nm-glib-aux/nm-ref-string.h" #include "nm-iwd-manager.h" #include "nm-libnm-core-intern/nm-common-macros.h" #include "nm-setting-8021x.h" #include "nm-setting-connection.h" #include "nm-setting-wireless-security.h" #include "nm-setting-wireless.h" #include "nm-std-aux/nm-dbus-compat.h" #include "nm-utils.h" #include "nm-wifi-common.h" #include "nm-wifi-utils.h" #include "settings/nm-settings-connection.h" #include "settings/nm-settings.h" #include "supplicant/nm-supplicant-types.h" #include "devices/nm-device-logging.h" _LOG_DECLARE_SELF(NMDeviceIwd); /*****************************************************************************/ NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceIwd, PROP_MODE, PROP_BITRATE, PROP_ACCESS_POINTS, PROP_ACTIVE_ACCESS_POINT, PROP_CAPABILITIES, PROP_SCANNING, PROP_LAST_SCAN, ); typedef struct { GDBusObject * dbus_obj; GDBusProxy * dbus_device_proxy; GDBusProxy * dbus_station_proxy; GDBusProxy * dbus_ap_proxy; GDBusProxy * dbus_adhoc_proxy; CList aps_lst_head; NMWifiAP * current_ap; GCancellable * cancellable; NMDeviceWifiCapabilities capabilities; NMActRequestGetSecretsCallId *wifi_secrets_id; guint periodic_scan_id; bool enabled:1; bool can_scan:1; bool can_connect:1; bool scanning:1; bool scan_requested:1; bool act_mode_switch:1; gint64 last_scan; } NMDeviceIwdPrivate; struct _NMDeviceIwd { NMDevice parent; NMDeviceIwdPrivate _priv; }; struct _NMDeviceIwdClass { NMDeviceClass parent; }; /*****************************************************************************/ G_DEFINE_TYPE (NMDeviceIwd, nm_device_iwd, NM_TYPE_DEVICE) #define NM_DEVICE_IWD_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMDeviceIwd, NM_IS_DEVICE_IWD, NMDevice) /*****************************************************************************/ static void schedule_periodic_scan (NMDeviceIwd *self, gboolean initial_scan); static gboolean check_scanning_prohibited (NMDeviceIwd *self, gboolean periodic); /*****************************************************************************/ static void _ap_dump (NMDeviceIwd *self, NMLogLevel log_level, const NMWifiAP *ap, const char *prefix) { char buf[1024]; buf[0] = '\0'; _NMLOG (log_level, LOGD_WIFI_SCAN, "wifi-ap: %-7s %s", prefix, nm_wifi_ap_to_string (ap, buf, sizeof (buf), 0)); } /* Callers ensure we're not removing current_ap */ static void ap_add_remove (NMDeviceIwd *self, gboolean is_adding, /* or else is removing */ NMWifiAP *ap, gboolean recheck_available_connections) { NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); if (is_adding) { g_object_ref (ap); ap->wifi_device = NM_DEVICE (self); c_list_link_tail (&priv->aps_lst_head, &ap->aps_lst); nm_dbus_object_export (NM_DBUS_OBJECT (ap)); _ap_dump (self, LOGL_DEBUG, ap, "added"); nm_device_wifi_emit_signal_access_point (NM_DEVICE (self), ap, TRUE); } else { ap->wifi_device = NULL; c_list_unlink (&ap->aps_lst); _ap_dump (self, LOGL_DEBUG, ap, "removed"); } _notify (self, PROP_ACCESS_POINTS); if (!is_adding) { nm_device_wifi_emit_signal_access_point (NM_DEVICE (self), ap, FALSE); nm_dbus_object_clear_and_unexport (&ap); } nm_device_emit_recheck_auto_activate (NM_DEVICE (self)); if (recheck_available_connections) nm_device_recheck_available_connections (NM_DEVICE (self)); } static void set_current_ap (NMDeviceIwd *self, NMWifiAP *new_ap, gboolean recheck_available_connections) { NMDeviceIwdPrivate *priv; NMWifiAP *old_ap; g_return_if_fail (NM_IS_DEVICE_IWD (self)); priv = NM_DEVICE_IWD_GET_PRIVATE (self); old_ap = priv->current_ap; if (old_ap == new_ap) return; if (new_ap) priv->current_ap = g_object_ref (new_ap); else priv->current_ap = NULL; if (old_ap) { if (nm_wifi_ap_get_fake (old_ap)) ap_add_remove (self, FALSE, old_ap, recheck_available_connections); g_object_unref (old_ap); } _notify (self, PROP_ACTIVE_ACCESS_POINT); _notify (self, PROP_MODE); } static void remove_all_aps (NMDeviceIwd *self) { NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); NMWifiAP *ap, *ap_safe; if (c_list_is_empty (&priv->aps_lst_head)) return; set_current_ap (self, NULL, FALSE); c_list_for_each_entry_safe (ap, ap_safe, &priv->aps_lst_head, aps_lst) ap_add_remove (self, FALSE, ap, FALSE); nm_device_emit_recheck_auto_activate (NM_DEVICE (self)); nm_device_recheck_available_connections (NM_DEVICE (self)); } static NM80211ApSecurityFlags ap_security_flags_from_network_type (const char *type) { NM80211ApSecurityFlags flags; if (nm_streq (type, "psk")) flags = NM_802_11_AP_SEC_KEY_MGMT_PSK; else if (nm_streq (type, "8021x")) flags = NM_802_11_AP_SEC_KEY_MGMT_802_1X; else return NM_802_11_AP_SEC_NONE; flags |= NM_802_11_AP_SEC_PAIR_CCMP; flags |= NM_802_11_AP_SEC_GROUP_CCMP; return flags; } static void insert_ap_from_network (NMDeviceIwd *self, GHashTable *aps, const char *path, gint64 last_seen_msec, int16_t signal, uint32_t ap_id) { gs_unref_object GDBusProxy *network_proxy = NULL; gs_unref_variant GVariant *name_value = NULL; gs_unref_variant GVariant *type_value = NULL; nm_auto_ref_string NMRefString *bss_path = NULL; const char *name; const char *type; NMSupplicantBssInfo bss_info; uint8_t bssid[6]; NMWifiAP *ap; gs_unref_bytes GBytes *ssid = NULL; bss_path = nm_ref_string_new (path); if (g_hash_table_lookup (aps, path)) { _LOGD (LOGD_WIFI, "Duplicate network at %s", path); return; } network_proxy = nm_iwd_manager_get_dbus_interface (nm_iwd_manager_get (), path, NM_IWD_NETWORK_INTERFACE); if (!network_proxy) return; name_value = g_dbus_proxy_get_cached_property (network_proxy, "Name"); type_value = g_dbus_proxy_get_cached_property (network_proxy, "Type"); if ( !name_value || !g_variant_is_of_type (name_value, G_VARIANT_TYPE_STRING) || !type_value || !g_variant_is_of_type (type_value, G_VARIANT_TYPE_STRING)) return; name = g_variant_get_string (name_value, NULL); type = g_variant_get_string (type_value, NULL); if (nm_streq (type, "wep")) { /* WEP not supported */ return; } /* What we get from IWD are networks, or ESSs, that may contain * multiple APs, or BSSs, each. We don't get information about any * specific BSSs within an ESS but we can safely present each ESS * as an individual BSS to NM, which will be seen as ESSs comprising * a single BSS each. NM won't be able to handle roaming but IWD * already does that. We fake the BSSIDs as they don't play any * role either. */ bssid[0] = 0x00; bssid[1] = 0x01; bssid[2] = 0x02; bssid[3] = ap_id >> 16; bssid[4] = ap_id >> 8; bssid[5] = ap_id; ssid = g_bytes_new (name, NM_MIN (32u, strlen (name))); bss_info = (NMSupplicantBssInfo) { .bss_path = bss_path, .last_seen_msec = last_seen_msec, .bssid_valid = TRUE, .mode = NM_802_11_MODE_INFRA, .rsn_flags = ap_security_flags_from_network_type (type), .ssid = ssid, .signal_percent = nm_wifi_utils_level_to_quality (signal / 100), .frequency = 2417, .max_rate = 65000, }; memcpy (bss_info.bssid, bssid, sizeof (bssid)); ap = nm_wifi_ap_new_from_properties (&bss_info); nm_assert (bss_path == nm_wifi_ap_get_supplicant_path (ap)); g_hash_table_insert (aps, bss_path, ap); } static void get_ordered_networks_cb (GObject *source, GAsyncResult *res, gpointer user_data) { NMDeviceIwd *self = user_data; NMDeviceIwdPrivate *priv; gs_free_error GError *error = NULL; gs_unref_variant GVariant *variant = NULL; GVariantIter *networks; const char *path, *name, *type; int16_t signal; NMWifiAP *ap, *ap_safe, *new_ap; gboolean changed = FALSE; GHashTableIter ap_iter; gs_unref_hashtable GHashTable *new_aps = NULL; gboolean compat; const char *return_sig; static uint32_t ap_id = 0; gint64 last_seen_msec; variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error); if (!variant) { _LOGE (LOGD_WIFI, "Station.GetOrderedNetworks failed: %s", error->message); return; } priv = NM_DEVICE_IWD_GET_PRIVATE (self); /* Depending on whether we're using the Station interface or the Device * interface for compatibility with IWD <= 0.7, the return signature of * GetOrderedNetworks will be different. */ compat = priv->dbus_station_proxy == priv->dbus_device_proxy; return_sig = compat ? "(a(osns))" : "(a(on))"; if (!g_variant_is_of_type (variant, G_VARIANT_TYPE (return_sig))) { _LOGE (LOGD_WIFI, "Station.GetOrderedNetworks returned type %s instead of %s", g_variant_get_type_string (variant), return_sig); return; } new_aps = g_hash_table_new_full (nm_direct_hash, NULL, NULL, g_object_unref); g_variant_get (variant, return_sig, &networks); last_seen_msec = nm_utils_get_monotonic_timestamp_msec (); if (compat) { while (g_variant_iter_next (networks, "(&o&sn&s)", &path, &name, &signal, &type)) insert_ap_from_network (self, new_aps, path, last_seen_msec, signal, ap_id++); } else { while (g_variant_iter_next (networks, "(&on)", &path, &signal)) insert_ap_from_network (self, new_aps, path, last_seen_msec, signal, ap_id++); } g_variant_iter_free (networks); c_list_for_each_entry_safe (ap, ap_safe, &priv->aps_lst_head, aps_lst) { new_ap = g_hash_table_lookup (new_aps, nm_wifi_ap_get_supplicant_path (ap)); if (new_ap) { if (nm_wifi_ap_set_strength (ap, nm_wifi_ap_get_strength (new_ap))) { _ap_dump (self, LOGL_TRACE, ap, "updated"); changed = TRUE; } g_hash_table_remove (new_aps, nm_wifi_ap_get_supplicant_path (ap)); continue; } if (ap == priv->current_ap) { /* Normally IWD will prevent the current AP from being * removed from the list and set a low signal strength, * but just making sure. */ continue; } ap_add_remove (self, FALSE, ap, FALSE); changed = TRUE; } g_hash_table_iter_init (&ap_iter, new_aps); while (g_hash_table_iter_next (&ap_iter, NULL, (gpointer) &ap)) { ap_add_remove (self, TRUE, ap, FALSE); g_hash_table_iter_remove (&ap_iter); changed = TRUE; } if (changed) { nm_device_emit_recheck_auto_activate (NM_DEVICE (self)); nm_device_recheck_available_connections (NM_DEVICE (self)); } } static void update_aps (NMDeviceIwd *self) { NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); if (!priv->cancellable) priv->cancellable = g_cancellable_new (); g_dbus_proxy_call (priv->dbus_station_proxy, "GetOrderedNetworks", NULL, G_DBUS_CALL_FLAGS_NONE, 2000, priv->cancellable, get_ordered_networks_cb, self); } static void send_disconnect (NMDeviceIwd *self) { NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); g_dbus_proxy_call (priv->dbus_station_proxy, "Disconnect", NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); } static void wifi_secrets_cancel (NMDeviceIwd *self) { NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); if (priv->wifi_secrets_id) nm_act_request_cancel_secrets (NULL, priv->wifi_secrets_id); nm_assert (!priv->wifi_secrets_id); } static void cleanup_association_attempt (NMDeviceIwd *self, gboolean disconnect) { NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); wifi_secrets_cancel (self); set_current_ap (self, NULL, TRUE); if (disconnect && priv->dbus_station_proxy) send_disconnect (self); } static void reset_mode (NMDeviceIwd *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); g_dbus_proxy_call (priv->dbus_device_proxy, DBUS_INTERFACE_PROPERTIES ".Set", g_variant_new ("(ssv)", NM_IWD_DEVICE_INTERFACE, "Mode", g_variant_new_string ("station")), G_DBUS_CALL_FLAGS_NONE, 2000, cancellable, callback, user_data); } static void deactivate (NMDevice *device) { NMDeviceIwd *self = NM_DEVICE_IWD (device); NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); if (!priv->dbus_obj) return; cleanup_association_attempt (self, TRUE); priv->act_mode_switch = FALSE; if (!priv->dbus_station_proxy) reset_mode (self, NULL, NULL, NULL); } static void disconnect_cb (GObject *source, GAsyncResult *res, gpointer user_data) { gs_unref_object NMDeviceIwd *self = NULL; NMDeviceDeactivateCallback callback; gpointer callback_user_data; gs_unref_variant GVariant *variant = NULL; gs_free_error GError *error = NULL; nm_utils_user_data_unpack (user_data, &self, &callback, &callback_user_data); variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error); callback (NM_DEVICE (self), error, callback_user_data); } static void disconnect_cb_on_idle (gpointer user_data, GCancellable *cancellable) { gs_unref_object NMDeviceIwd *self = NULL; NMDeviceDeactivateCallback callback; gpointer callback_user_data; gs_free_error GError *cancelled_error = NULL; nm_utils_user_data_unpack (user_data, &self, &callback, &callback_user_data); g_cancellable_set_error_if_cancelled (cancellable, &cancelled_error); callback (NM_DEVICE (self), cancelled_error, callback_user_data); } static void deactivate_async (NMDevice *device, GCancellable *cancellable, NMDeviceDeactivateCallback callback, gpointer callback_user_data) { NMDeviceIwd *self = NM_DEVICE_IWD (device); NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); gpointer user_data; nm_assert (G_IS_CANCELLABLE (cancellable)); nm_assert (callback); user_data = nm_utils_user_data_pack (g_object_ref (self), callback, callback_user_data); if (!priv->dbus_obj) { nm_utils_invoke_on_idle (cancellable, disconnect_cb_on_idle, user_data); return; } cleanup_association_attempt (self, FALSE); priv->act_mode_switch = FALSE; if (priv->dbus_station_proxy) { g_dbus_proxy_call (priv->dbus_station_proxy, "Disconnect", NULL, G_DBUS_CALL_FLAGS_NONE, -1, cancellable, disconnect_cb, user_data); } else reset_mode (self, cancellable, disconnect_cb, user_data); } static gboolean is_connection_known_network (NMConnection *connection) { NMSettingWireless *s_wireless; NMIwdNetworkSecurity security; gboolean security_ok; GBytes *ssid; gs_free char *ssid_utf8 = NULL; s_wireless = nm_connection_get_setting_wireless (connection); if (!s_wireless) return FALSE; ssid = nm_setting_wireless_get_ssid (s_wireless); if (!ssid) return FALSE; ssid_utf8 = _nm_utils_ssid_to_utf8 (ssid); security = nm_wifi_connection_get_iwd_security (connection, &security_ok); if (!security_ok) return FALSE; return nm_iwd_manager_is_known_network (nm_iwd_manager_get (), ssid_utf8, security); } static gboolean is_ap_known_network (NMWifiAP *ap) { gs_unref_object GDBusProxy *network_proxy = NULL; gs_unref_variant GVariant *known_network = NULL; network_proxy = nm_iwd_manager_get_dbus_interface (nm_iwd_manager_get (), nm_ref_string_get_str (nm_wifi_ap_get_supplicant_path (ap)), NM_IWD_NETWORK_INTERFACE); if (!network_proxy) return FALSE; known_network = g_dbus_proxy_get_cached_property (network_proxy, "KnownNetwork"); return nm_g_variant_is_of_type (known_network, G_VARIANT_TYPE_OBJECT_PATH); } static gboolean check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error) { NMDeviceIwd *self = NM_DEVICE_IWD (device); NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); NMSettingWireless *s_wireless; const char *mac; const char * const *mac_blacklist; int i; const char *perm_hw_addr; const char *mode; NMIwdNetworkSecurity security; gboolean mapped; if (!NM_DEVICE_CLASS (nm_device_iwd_parent_class)->check_connection_compatible (device, connection, error)) return FALSE; s_wireless = nm_connection_get_setting_wireless (connection); perm_hw_addr = nm_device_get_permanent_hw_address (device); mac = nm_setting_wireless_get_mac_address (s_wireless); if (perm_hw_addr) { if (mac && !nm_utils_hwaddr_matches (mac, -1, perm_hw_addr, -1)) { nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, "device MAC address does not match the profile"); return FALSE; } /* Check for MAC address blacklist */ mac_blacklist = nm_setting_wireless_get_mac_address_blacklist (s_wireless); for (i = 0; mac_blacklist[i]; i++) { nm_assert (nm_utils_hwaddr_valid (mac_blacklist[i], ETH_ALEN)); if (nm_utils_hwaddr_matches (mac_blacklist[i], -1, perm_hw_addr, -1)) { nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, "MAC address blacklisted"); return FALSE; } } } else if (mac) { nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, "device has no valid MAC address as required by profile"); return FALSE; } security = nm_wifi_connection_get_iwd_security (connection, &mapped); if (!mapped) { nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, "connection authentication type not supported by IWD backend"); return FALSE; } mode = nm_setting_wireless_get_mode (s_wireless); /* Hidden SSIDs only supported in client mode */ if ( nm_setting_wireless_get_hidden (s_wireless) && !NM_IN_STRSET (mode, NULL, NM_SETTING_WIRELESS_MODE_INFRA)) { nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, "non-infrastructure hidden networks not supported by the IWD backend"); return FALSE; } if (NM_IN_STRSET (mode, NULL, NM_SETTING_WIRELESS_MODE_INFRA)) { /* 8021x networks can only be used if they've been provisioned on the IWD side and * thus are Known Networks. */ if (security == NM_IWD_NETWORK_SECURITY_8021X) { if (!is_connection_known_network (connection)) { nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, "802.1x connections must have IWD provisioning files"); return FALSE; } } else if (!NM_IN_SET (security, NM_IWD_NETWORK_SECURITY_NONE, NM_IWD_NETWORK_SECURITY_PSK)) { nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, "IWD backend only supports Open, PSK and 802.1x network " "authentication in Infrastructure mode"); return FALSE; } } else if (nm_streq (mode, NM_SETTING_WIRELESS_MODE_AP)) { if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_AP)) { nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, "device does not support Access Point mode"); return FALSE; } if (!NM_IN_SET (security, NM_IWD_NETWORK_SECURITY_PSK)) { nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, "IWD backend only supports PSK authentication in AP mode"); return FALSE; } } else if (nm_streq (mode, NM_SETTING_WIRELESS_MODE_ADHOC)) { if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_ADHOC)) { nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, "device does not support Ad-Hoc mode"); return FALSE; } if (!NM_IN_SET (security, NM_IWD_NETWORK_SECURITY_NONE, NM_IWD_NETWORK_SECURITY_PSK)) { nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, "IWD backend only supports Open and PSK authentication in Ad-Hoc mode"); return FALSE; } } else { nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, "%s type profiles not supported by IWD backend"); return FALSE; } return TRUE; } static gboolean check_connection_available (NMDevice *device, NMConnection *connection, NMDeviceCheckConAvailableFlags flags, const char *specific_object, GError **error) { NMDeviceIwd *self = NM_DEVICE_IWD (device); NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); NMSettingWireless *s_wifi; const char *mode; NMWifiAP *ap = NULL; s_wifi = nm_connection_get_setting_wireless (connection); g_return_val_if_fail (s_wifi, FALSE); /* a connection that is available for a certain @specific_object, MUST * also be available in general (without @specific_object). */ if (specific_object) { ap = nm_wifi_ap_lookup_for_device (NM_DEVICE (self), specific_object); if (!ap) { nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, "requested access point not found"); return FALSE; } if (!nm_wifi_ap_check_compatible (ap, connection)) { nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, "requested access point is not compatible with profile"); return FALSE; } } /* AP and Ad-Hoc connections can be activated independent of the scan list */ mode = nm_setting_wireless_get_mode (s_wifi); if (NM_IN_STRSET (mode, NM_SETTING_WIRELESS_MODE_AP, NM_SETTING_WIRELESS_MODE_ADHOC)) return TRUE; if (NM_FLAGS_HAS (flags, _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_IGNORE_AP)) return TRUE; if (!ap) ap = nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection); if (!ap) { nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, "no compatible access point found"); return FALSE; } /* 8021x networks can only be used if they've been provisioned on the IWD side and * thus are Known Networks. */ if (nm_wifi_connection_get_iwd_security (connection, NULL) == NM_IWD_NETWORK_SECURITY_8021X) { if (!is_ap_known_network (ap)) { nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, "802.1x network is not an IWD Known Network (missing provisioning file?)"); return FALSE; } } return TRUE; } static gboolean complete_connection (NMDevice *device, NMConnection *connection, const char *specific_object, NMConnection *const*existing_connections, GError **error) { NMDeviceIwd *self = NM_DEVICE_IWD (device); NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); NMSettingWireless *s_wifi; gs_free char *ssid_utf8 = NULL; NMWifiAP *ap; GBytes *ssid; GBytes *setting_ssid = NULL; gboolean hidden = FALSE; const char *mode; s_wifi = nm_connection_get_setting_wireless (connection); mode = s_wifi ? nm_setting_wireless_get_mode (s_wifi) : NULL; if (nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_AP)) { if (!nm_setting_verify (NM_SETTING (s_wifi), connection, error)) return FALSE; ap = NULL; } else if (!specific_object) { /* If not given a specific object, we need at minimum an SSID */ if (!s_wifi) { g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_INVALID_CONNECTION, "A 'wireless' setting is required if no AP path was given."); return FALSE; } setting_ssid = nm_setting_wireless_get_ssid (s_wifi); if (!setting_ssid || g_bytes_get_size (setting_ssid) == 0) { g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_INVALID_CONNECTION, "A 'wireless' setting with a valid SSID is required if no AP path was given."); return FALSE; } /* Find a compatible AP in the scan list */ ap = nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection); if (!ap) { /* If we still don't have an AP, then the WiFI settings needs to be * fully specified by the client. Might not be able to find an AP * if the network isn't broadcasting the SSID for example. */ if (!nm_setting_verify (NM_SETTING (s_wifi), connection, error)) return FALSE; hidden = TRUE; } } else { ap = nm_wifi_ap_lookup_for_device (NM_DEVICE (self), specific_object); if (!ap) { g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_SPECIFIC_OBJECT_NOT_FOUND, "The access point %s was not in the scan list.", specific_object); return FALSE; } } /* Add a wifi setting if one doesn't exist yet */ if (!s_wifi) { s_wifi = (NMSettingWireless *) nm_setting_wireless_new (); nm_connection_add_setting (connection, NM_SETTING (s_wifi)); } ssid = nm_setting_wireless_get_ssid (s_wifi); if (!ssid && ap) ssid = nm_wifi_ap_get_ssid (ap); if (!ssid) { g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_INVALID_CONNECTION, "A 'wireless' setting with a valid SSID is required."); return FALSE; } if (ap) { if (!nm_wifi_ap_complete_connection (ap, connection, nm_wifi_utils_is_manf_default_ssid (ssid), error)) return FALSE; } ssid_utf8 = _nm_utils_ssid_to_utf8 (ssid); nm_utils_complete_generic (nm_device_get_platform (device), connection, NM_SETTING_WIRELESS_SETTING_NAME, existing_connections, ssid_utf8, ssid_utf8, NULL, nm_setting_wireless_get_mac_address (s_wifi) ? NULL : nm_device_get_iface (device), TRUE); if (hidden) g_object_set (s_wifi, NM_SETTING_WIRELESS_HIDDEN, TRUE, NULL); return TRUE; } static gboolean get_variant_boolean (GVariant *v, const char *property) { if (!v || !g_variant_is_of_type (v, G_VARIANT_TYPE_BOOLEAN)) { nm_log_warn (LOGD_DEVICE | LOGD_WIFI, "Property %s not cached or not boolean type", property); return FALSE; } return g_variant_get_boolean (v); } static const char * get_variant_state (GVariant *v) { if (!v || !g_variant_is_of_type (v, G_VARIANT_TYPE_STRING)) { nm_log_warn (LOGD_DEVICE | LOGD_WIFI, "State property not cached or not a string"); return "unknown"; } return g_variant_get_string (v, NULL); } static gboolean is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags) { NMDeviceIwd *self = NM_DEVICE_IWD (device); NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); NMDeviceState state = nm_device_get_state (device); /* Available if either the device is UP and in station mode * or in AP/Ad-Hoc modes while activating or activated. Device * may be temporarily DOWN while activating or deactivating and * we don't want it to be marked unavailable because of this. * * For reference: * We call nm_device_queue_recheck_available whenever * priv->enabled changes or priv->dbus_station_proxy changes. */ return priv->dbus_obj && priv->enabled && ( priv->dbus_station_proxy || (state >= NM_DEVICE_STATE_CONFIG && state <= NM_DEVICE_STATE_DEACTIVATING)); } static gboolean get_autoconnect_allowed (NMDevice *device) { NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (NM_DEVICE_IWD (device)); return priv->can_connect; } static gboolean can_auto_connect (NMDevice *device, NMSettingsConnection *sett_conn, char **specific_object) { NMDeviceIwd *self = NM_DEVICE_IWD (device); NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); NMConnection *connection; NMSettingWireless *s_wifi; NMWifiAP *ap; const char *mode; guint64 timestamp = 0; nm_assert (!specific_object || !*specific_object); if (!NM_DEVICE_CLASS (nm_device_iwd_parent_class)->can_auto_connect (device, sett_conn, NULL)) return FALSE; connection = nm_settings_connection_get_connection (sett_conn); s_wifi = nm_connection_get_setting_wireless (connection); g_return_val_if_fail (s_wifi, FALSE); /* Don't auto-activate AP or Ad-Hoc connections. * Note the wpa_supplicant backend has the opposite policy. */ mode = nm_setting_wireless_get_mode (s_wifi); if (mode && g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_INFRA) != 0) return FALSE; /* Don't autoconnect to networks that have been tried at least once * but haven't been successful, since these are often accidental choices * from the menu and the user may not know the password. */ if (nm_settings_connection_get_timestamp (sett_conn, ×tamp)) { if (timestamp == 0) return FALSE; } ap = nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection); if (ap) { /* All good; connection is usable */ NM_SET_OUT (specific_object, g_strdup (nm_dbus_object_get_path (NM_DBUS_OBJECT (ap)))); return TRUE; } return FALSE; } const CList * _nm_device_iwd_get_aps (NMDeviceIwd *self) { return &NM_DEVICE_IWD_GET_PRIVATE (self)->aps_lst_head; } static void scan_cb (GObject *source, GAsyncResult *res, gpointer user_data) { NMDeviceIwd *self = user_data; NMDeviceIwdPrivate *priv; gs_unref_variant GVariant *variant = NULL; gs_free_error GError *error = NULL; variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error); if (!variant && nm_utils_error_is_cancelled (error)) return; priv = NM_DEVICE_IWD_GET_PRIVATE (self); priv->scan_requested = FALSE; priv->last_scan = nm_utils_get_monotonic_timestamp_msec (); _notify (self, PROP_LAST_SCAN); /* On success, priv->scanning becomes true right before or right * after this callback, so the next automatic scan will be * scheduled when priv->scanning goes back to false. On error, * schedule a retry now. */ if (error && !priv->scanning) schedule_periodic_scan (self, FALSE); } static void dbus_request_scan_cb (NMDevice *device, GDBusMethodInvocation *context, NMAuthSubject *subject, GError *error, gpointer user_data) { NMDeviceIwd *self = NM_DEVICE_IWD (device); NMDeviceIwdPrivate *priv; gs_unref_variant GVariant *scan_options = user_data; if (error) { g_dbus_method_invocation_return_gerror (context, error); return; } if (check_scanning_prohibited (self, FALSE)) { g_dbus_method_invocation_return_error_literal (context, NM_DEVICE_ERROR, NM_DEVICE_ERROR_NOT_ALLOWED, "Scanning not allowed at this time"); return; } priv = NM_DEVICE_IWD_GET_PRIVATE (self); if (!priv->can_scan) { g_dbus_method_invocation_return_error_literal (context, NM_DEVICE_ERROR, NM_DEVICE_ERROR_NOT_ALLOWED, "Scanning not allowed while unavailable"); return; } if (scan_options) { gs_unref_variant GVariant *val = g_variant_lookup_value (scan_options, "ssids", NULL); if (val) { g_dbus_method_invocation_return_error_literal (context, NM_DEVICE_ERROR, NM_DEVICE_ERROR_NOT_ALLOWED, "'ssid' scan option not supported"); return; } } if (!priv->scanning && !priv->scan_requested) { g_dbus_proxy_call (priv->dbus_station_proxy, "Scan", NULL, G_DBUS_CALL_FLAGS_NONE, -1, priv->cancellable, scan_cb, self); priv->scan_requested = TRUE; } g_dbus_method_invocation_return_value (context, NULL); } void _nm_device_iwd_request_scan (NMDeviceIwd *self, GVariant *options, GDBusMethodInvocation *invocation) { NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); NMDevice *device = NM_DEVICE (self); if (!priv->can_scan) { g_dbus_method_invocation_return_error_literal (invocation, NM_DEVICE_ERROR, NM_DEVICE_ERROR_NOT_ALLOWED, "Scanning not allowed while unavailable"); return; } nm_device_auth_request (device, invocation, NULL, NM_AUTH_PERMISSION_WIFI_SCAN, TRUE, NULL, dbus_request_scan_cb, nm_g_variant_ref (options)); } static gboolean check_scanning_prohibited (NMDeviceIwd *self, gboolean periodic) { NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); g_return_val_if_fail (priv->dbus_obj != NULL, TRUE); switch (nm_device_get_state (NM_DEVICE (self))) { case NM_DEVICE_STATE_UNKNOWN: case NM_DEVICE_STATE_UNMANAGED: case NM_DEVICE_STATE_UNAVAILABLE: case NM_DEVICE_STATE_PREPARE: case NM_DEVICE_STATE_CONFIG: case NM_DEVICE_STATE_NEED_AUTH: case NM_DEVICE_STATE_IP_CONFIG: case NM_DEVICE_STATE_IP_CHECK: case NM_DEVICE_STATE_SECONDARIES: case NM_DEVICE_STATE_DEACTIVATING: /* Prohibit scans when unusable or activating */ return TRUE; case NM_DEVICE_STATE_DISCONNECTED: case NM_DEVICE_STATE_FAILED: case NM_DEVICE_STATE_ACTIVATED: break; } /* Prohibit scans if IWD is busy */ return !priv->can_scan; } /* * try_reply_agent_request * * Check if the connection settings already have the secrets corresponding * to the IWD agent method that was invoked. If they do, send the method reply * with the appropriate secrets. Otherwise return the missing secret's setting * name and key so the caller can send a NM secrets request with this data. * Return TRUE in either case, return FALSE if an error is detected. */ static gboolean try_reply_agent_request (NMDeviceIwd *self, NMConnection *connection, GDBusMethodInvocation *invocation, const char **setting_name, const char **setting_key, gboolean *replied) { const char *method_name = g_dbus_method_invocation_get_method_name (invocation); NMSettingWirelessSecurity *s_wireless_sec; NMSetting8021x *s_8021x; s_wireless_sec = nm_connection_get_setting_wireless_security (connection); s_8021x = nm_connection_get_setting_802_1x (connection); *replied = FALSE; if (nm_streq (method_name, "RequestPassphrase")) { const char *psk; if (!s_wireless_sec) return FALSE; psk = nm_setting_wireless_security_get_psk (s_wireless_sec); if (psk) { _LOGD (LOGD_DEVICE | LOGD_WIFI, "Returning the PSK to the IWD Agent"); g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", psk)); *replied = TRUE; return TRUE; } *setting_name = NM_SETTING_WIRELESS_SECURITY_SETTING_NAME; *setting_key = NM_SETTING_WIRELESS_SECURITY_PSK; return TRUE; } else if (nm_streq (method_name, "RequestPrivateKeyPassphrase")) { const char *password; if (!s_8021x) return FALSE; password = nm_setting_802_1x_get_private_key_password (s_8021x); if (password) { _LOGD (LOGD_DEVICE | LOGD_WIFI, "Returning the private key password to the IWD Agent"); g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", password)); *replied = TRUE; return TRUE; } *setting_name = NM_SETTING_802_1X_SETTING_NAME; *setting_key = NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD; return TRUE; } else if (nm_streq (method_name, "RequestUserNameAndPassword")) { const char *identity, *password; if (!s_8021x) return FALSE; identity = nm_setting_802_1x_get_identity (s_8021x); password = nm_setting_802_1x_get_password (s_8021x); if (identity && password) { _LOGD (LOGD_DEVICE | LOGD_WIFI, "Returning the username and password to the IWD Agent"); g_dbus_method_invocation_return_value (invocation, g_variant_new ("(ss)", identity, password)); *replied = TRUE; return TRUE; } *setting_name = NM_SETTING_802_1X_SETTING_NAME; if (!identity) *setting_key = NM_SETTING_802_1X_IDENTITY; else *setting_key = NM_SETTING_802_1X_PASSWORD; return TRUE; } else if (nm_streq (method_name, "RequestUserPassword")) { const char *password; if (!s_8021x) return FALSE; password = nm_setting_802_1x_get_password (s_8021x); if (password) { _LOGD (LOGD_DEVICE | LOGD_WIFI, "Returning the user password to the IWD Agent"); g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", password)); *replied = TRUE; return TRUE; } *setting_name = NM_SETTING_802_1X_SETTING_NAME; *setting_key = NM_SETTING_802_1X_PASSWORD; return TRUE; } else return FALSE; } static void wifi_secrets_get_one (NMDeviceIwd *self, const char *setting_name, NMSecretAgentGetSecretsFlags flags, const char *setting_key, GDBusMethodInvocation *invocation); static void wifi_secrets_cb (NMActRequest *req, NMActRequestGetSecretsCallId *call_id, NMSettingsConnection *s_connection, GError *error, gpointer user_data) { NMDeviceIwd *self; NMDeviceIwdPrivate *priv; NMDevice *device; GDBusMethodInvocation *invocation; const char *setting_name; const char *setting_key; gboolean replied; NMSecretAgentGetSecretsFlags get_secret_flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION; nm_utils_user_data_unpack (user_data, &self, &invocation); g_return_if_fail (NM_IS_DEVICE_IWD (self)); priv = NM_DEVICE_IWD_GET_PRIVATE (self); device = NM_DEVICE (self); g_return_if_fail (priv->wifi_secrets_id == call_id); priv->wifi_secrets_id = NULL; if (nm_utils_error_is_cancelled (error)) { g_dbus_method_invocation_return_error_literal (invocation, NM_DEVICE_ERROR, NM_DEVICE_ERROR_INVALID_CONNECTION, "NM secrets request cancelled"); return; } g_return_if_fail (req == nm_device_get_act_request (device)); g_return_if_fail (nm_act_request_get_settings_connection (req) == s_connection); if (nm_device_get_state (device) != NM_DEVICE_STATE_NEED_AUTH) goto secrets_error; if (error) { _LOGW (LOGD_WIFI, "%s", error->message); goto secrets_error; } if (!try_reply_agent_request (self, nm_act_request_get_applied_connection (req), invocation, &setting_name, &setting_key, &replied)) goto secrets_error; if (replied) { /* Change state back to what it was before NEED_AUTH */ nm_device_state_changed (device, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE); return; } if (nm_settings_connection_get_timestamp (nm_act_request_get_settings_connection (req), NULL)) get_secret_flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW; /* Request further secrets if we still need something */ wifi_secrets_get_one (self, setting_name, get_secret_flags, setting_key, invocation); return; secrets_error: g_dbus_method_invocation_return_error_literal (invocation, NM_DEVICE_ERROR, NM_DEVICE_ERROR_INVALID_CONNECTION, "NM secrets request failed"); /* Now wait for the Connect callback to update device state */ } static void wifi_secrets_get_one (NMDeviceIwd *self, const char *setting_name, NMSecretAgentGetSecretsFlags flags, const char *setting_key, GDBusMethodInvocation *invocation) { NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); NMActRequest *req; wifi_secrets_cancel (self); req = nm_device_get_act_request (NM_DEVICE (self)); g_return_if_fail (NM_IS_ACT_REQUEST (req)); priv->wifi_secrets_id = nm_act_request_get_secrets (req, TRUE, setting_name, flags, NM_MAKE_STRV (setting_key), wifi_secrets_cb, nm_utils_user_data_pack (self, invocation)); } static void network_connect_cb (GObject *source, GAsyncResult *res, gpointer user_data) { NMDeviceIwd *self = user_data; NMDevice *device = NM_DEVICE (self); NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); gs_unref_variant GVariant *variant = NULL; gs_free_error GError *error = NULL; NMConnection *connection; NMSettingWireless *s_wifi; GBytes *ssid; gs_free char *ssid_utf8 = NULL; NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED; GVariant *value; variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error); if (!variant) { gs_free char *dbus_error = NULL; /* Connection failed; radio problems or if the network wasn't * open, the passwords or certificates may be wrong. */ _LOGE (LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) Network.Connect failed: %s", error->message); if (nm_utils_error_is_cancelled (error)) return; if (!NM_IN_SET (nm_device_get_state (device), NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_NEED_AUTH)) return; connection = nm_device_get_applied_connection (device); if (!connection) goto failed; if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR)) dbus_error = g_dbus_error_get_remote_error (error); if (nm_streq0 (dbus_error, "net.connman.iwd.Failed")) { nm_connection_clear_secrets (connection); /* If secrets were wrong, we'd be getting a net.connman.iwd.Failed */ reason = NM_DEVICE_STATE_REASON_NO_SECRETS; } else if (nm_streq0 (dbus_error, "net.connman.iwd.Aborted")) { /* If agent call was cancelled we'd be getting a net.connman.iwd.Aborted */ reason = NM_DEVICE_STATE_REASON_NO_SECRETS; } goto failed; } nm_assert (nm_device_get_state (device) == NM_DEVICE_STATE_CONFIG); connection = nm_device_get_applied_connection (device); if (!connection) goto failed; s_wifi = nm_connection_get_setting_wireless (connection); if (!s_wifi) goto failed; ssid = nm_setting_wireless_get_ssid (s_wifi); if (!ssid) goto failed; ssid_utf8 = _nm_utils_ssid_to_utf8 (ssid); _LOGI (LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) Stage 2 of 5 (Device Configure) successful. Connected to '%s'.", ssid_utf8); nm_device_activate_schedule_stage3_ip_config_start (device); return; failed: /* Call Disconnect to make sure IWD's autoconnect is disabled */ cleanup_association_attempt (self, TRUE); nm_device_queue_state (device, NM_DEVICE_STATE_FAILED, reason); value = g_dbus_proxy_get_cached_property (priv->dbus_station_proxy, "State"); if (!priv->can_connect && nm_streq0 (get_variant_state (value), "disconnected")) { priv->can_connect = true; nm_device_emit_recheck_auto_activate (device); } g_variant_unref (value); } static void act_failed_cb (GObject *source, GAsyncResult *res, gpointer user_data) { NMDeviceIwd *self = user_data; NMDevice *device = NM_DEVICE (self); gs_unref_variant GVariant *variant = NULL; gs_free_error GError *error = NULL; variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error); if (!variant && nm_utils_error_is_cancelled (error)) return; /* Change state to FAILED unless already done by state_changed * which may have been triggered by the station interface * appearing on DBus. */ if (nm_device_get_state (device) == NM_DEVICE_STATE_CONFIG) nm_device_queue_state (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); } static void act_start_cb (GObject *source, GAsyncResult *res, gpointer user_data) { NMDeviceIwd *self = user_data; NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); NMDevice *device = NM_DEVICE (self); gs_unref_variant GVariant *variant = NULL; gs_free_error GError *error = NULL; NMSettingWireless *s_wireless; GBytes *ssid; gs_free char *ssid_utf8 = NULL; variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error); if (!variant) { _LOGE (LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) Network.Connect failed: %s", error->message); if (nm_utils_error_is_cancelled (error)) return; if (!NM_IN_SET (nm_device_get_state (device), NM_DEVICE_STATE_CONFIG)) return; goto error; } nm_assert (nm_device_get_state (device) == NM_DEVICE_STATE_CONFIG); s_wireless = nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRELESS); if (!s_wireless) goto error; ssid = nm_setting_wireless_get_ssid (s_wireless); if (!ssid) goto error; ssid_utf8 = _nm_utils_ssid_to_utf8 (ssid); _LOGI (LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) Stage 2 of 5 (Device Configure) successful. Started '%s'.", ssid_utf8); nm_device_activate_schedule_stage3_ip_config_start (device); return; error: reset_mode (self, priv->cancellable, act_failed_cb, self); } /* Check if we're activating an AP/AdHoc connection and if the target * DBus interface has appeared already. If so proceed to call Start or * StartOpen on that interface. */ static void act_check_interface (NMDeviceIwd *self) { NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); NMDevice *device = NM_DEVICE (self); NMSettingWireless *s_wireless; NMSettingWirelessSecurity *s_wireless_sec; GDBusProxy *proxy = NULL; GBytes *ssid; gs_free char *ssid_utf8 = NULL; const char *mode; if (!priv->act_mode_switch) return; s_wireless = (NMSettingWireless *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRELESS); mode = nm_setting_wireless_get_mode (s_wireless); if (nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_AP)) proxy = priv->dbus_ap_proxy; else if (nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_ADHOC)) proxy = priv->dbus_adhoc_proxy; if (!proxy) return; priv->act_mode_switch = FALSE; if (!NM_IN_SET (nm_device_get_state (device), NM_DEVICE_STATE_CONFIG)) return; ssid = nm_setting_wireless_get_ssid (s_wireless); if (!ssid) goto failed; ssid_utf8 = _nm_utils_ssid_to_utf8 (ssid); s_wireless_sec = (NMSettingWirelessSecurity *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRELESS_SECURITY); if (!s_wireless_sec) { g_dbus_proxy_call (proxy, "StartOpen", g_variant_new ("(s)", ssid_utf8), G_DBUS_CALL_FLAGS_NONE, G_MAXINT, priv->cancellable, act_start_cb, self); } else { const char *psk = nm_setting_wireless_security_get_psk (s_wireless_sec); if (!psk) { _LOGE (LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) No PSK for '%s'.", ssid_utf8); goto failed; } g_dbus_proxy_call (proxy, "Start", g_variant_new ("(ss)", ssid_utf8, psk), G_DBUS_CALL_FLAGS_NONE, G_MAXINT, priv->cancellable, act_start_cb, self); } _LOGD (LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) Called Start('%s').", ssid_utf8); return; failed: reset_mode (self, priv->cancellable, act_failed_cb, self); } static void act_set_mode_cb (GObject *source, GAsyncResult *res, gpointer user_data) { NMDeviceIwd *self = user_data; NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); NMDevice *device = NM_DEVICE (self); gs_unref_variant GVariant *variant = NULL; gs_free_error GError *error = NULL; variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error); if (!variant) { _LOGE (LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) Setting Device.Mode failed: %s", error->message); if (nm_utils_error_is_cancelled (error)) return; if ( !NM_IN_SET (nm_device_get_state (device), NM_DEVICE_STATE_CONFIG) || !priv->act_mode_switch) return; priv->act_mode_switch = FALSE; nm_device_queue_state (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); return; } _LOGD (LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) IWD Device.Mode set successfully"); act_check_interface (self); } static void act_set_mode (NMDeviceIwd *self) { NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); NMDevice *device = NM_DEVICE (self); const char *iwd_mode; const char *mode; NMSettingWireless *s_wireless; s_wireless = (NMSettingWireless *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRELESS); mode = nm_setting_wireless_get_mode (s_wireless); /* We need to first set interface mode (Device.Mode) to ap or ad-hoc. * We can't directly queue a call to the Start/StartOpen method on * the DBus interface that's going to be created after the property * set call returns. */ iwd_mode = nm_streq (mode, NM_SETTING_WIRELESS_MODE_AP) ? "ap" : "ad-hoc"; if (!priv->cancellable) priv->cancellable = g_cancellable_new (); g_dbus_proxy_call (priv->dbus_device_proxy, DBUS_INTERFACE_PROPERTIES ".Set", g_variant_new ("(ssv)", NM_IWD_DEVICE_INTERFACE, "Mode", g_variant_new ("s", iwd_mode)), G_DBUS_CALL_FLAGS_NONE, 2000, priv->cancellable, act_set_mode_cb, self); priv->act_mode_switch = TRUE; } static void act_psk_cb (NMActRequest *req, NMActRequestGetSecretsCallId *call_id, NMSettingsConnection *s_connection, GError *error, gpointer user_data) { NMDeviceIwd *self = user_data; NMDeviceIwdPrivate *priv; NMDevice *device; if (nm_utils_error_is_cancelled (error)) return; priv = NM_DEVICE_IWD_GET_PRIVATE (self); device = NM_DEVICE (self); g_return_if_fail (priv->wifi_secrets_id == call_id); priv->wifi_secrets_id = NULL; g_return_if_fail (req == nm_device_get_act_request (device)); g_return_if_fail (nm_act_request_get_settings_connection (req) == s_connection); if (nm_device_get_state (device) != NM_DEVICE_STATE_NEED_AUTH) goto secrets_error; if (error) { _LOGW (LOGD_WIFI, "%s", error->message); goto secrets_error; } _LOGD (LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) missing PSK request completed"); /* Change state back to what it was before NEED_AUTH */ nm_device_state_changed (device, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE); act_set_mode (self); return; secrets_error: nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_NO_SECRETS); cleanup_association_attempt (self, FALSE); } static void set_powered (NMDeviceIwd *self, gboolean powered) { NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); g_dbus_proxy_call (priv->dbus_device_proxy, DBUS_INTERFACE_PROPERTIES ".Set", g_variant_new ("(ssv)", NM_IWD_DEVICE_INTERFACE, "Powered", g_variant_new ("b", powered)), G_DBUS_CALL_FLAGS_NONE, 2000, NULL, NULL, NULL); } /*****************************************************************************/ static NMActStageReturn act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason) { NMDeviceIwd *self = NM_DEVICE_IWD (device); NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); NMWifiAP *ap = NULL; gs_unref_object NMWifiAP *ap_fake = NULL; NMActRequest *req; NMConnection *connection; NMSettingWireless *s_wireless; const char *mode; const char *ap_path; req = nm_device_get_act_request (device); g_return_val_if_fail (req, NM_ACT_STAGE_RETURN_FAILURE); connection = nm_act_request_get_applied_connection (req); g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE); s_wireless = nm_connection_get_setting_wireless (connection); g_return_val_if_fail (s_wireless, NM_ACT_STAGE_RETURN_FAILURE); /* AP mode never uses a specific object or existing scanned AP */ mode = nm_setting_wireless_get_mode (s_wireless); if (nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_AP)) goto add_new; ap_path = nm_active_connection_get_specific_object (NM_ACTIVE_CONNECTION (req)); ap = ap_path ? nm_wifi_ap_lookup_for_device (NM_DEVICE (self), ap_path) : NULL; if (ap) { set_current_ap (self, ap, TRUE); return NM_ACT_STAGE_RETURN_SUCCESS; } ap = nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection); if (ap) { nm_active_connection_set_specific_object (NM_ACTIVE_CONNECTION (req), nm_dbus_object_get_path (NM_DBUS_OBJECT (ap))); set_current_ap (self, ap, TRUE); return NM_ACT_STAGE_RETURN_SUCCESS; } if (nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_INFRA)) { /* Hidden networks not supported at this time */ return NM_ACT_STAGE_RETURN_FAILURE; } add_new: /* If the user is trying to connect to an AP that NM doesn't yet know about * (hidden network or something) or starting a Hotspot, create an fake AP * from the security settings in the connection. This "fake" AP gets used * until the real one is found in the scan list (Ad-Hoc or Hidden), or until * the device is deactivated (Ad-Hoc or Hotspot). */ ap_fake = nm_wifi_ap_new_fake_from_connection (connection); if (!ap_fake) g_return_val_if_reached (NM_ACT_STAGE_RETURN_FAILURE); if (nm_wifi_ap_is_hotspot (ap_fake)) nm_wifi_ap_set_address (ap_fake, nm_device_get_hw_address (device)); g_object_freeze_notify (G_OBJECT (self)); ap_add_remove (self, TRUE, ap_fake, FALSE); g_object_thaw_notify (G_OBJECT (self)); set_current_ap (self, ap_fake, FALSE); nm_active_connection_set_specific_object (NM_ACTIVE_CONNECTION (req), nm_dbus_object_get_path (NM_DBUS_OBJECT (ap_fake))); return NM_ACT_STAGE_RETURN_SUCCESS; } static NMActStageReturn act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason) { NMDeviceIwd *self = NM_DEVICE_IWD (device); NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); NMActRequest *req; NMConnection *connection; NMSettingWireless *s_wireless; const char *mode; req = nm_device_get_act_request (device); connection = nm_act_request_get_applied_connection (req); s_wireless = nm_connection_get_setting_wireless (connection); g_return_val_if_fail (s_wireless, NM_ACT_STAGE_RETURN_FAILURE); mode = nm_setting_wireless_get_mode (s_wireless); if (NM_IN_STRSET (mode, NULL, NM_SETTING_WIRELESS_MODE_INFRA)) { gs_unref_object GDBusProxy *network_proxy = NULL; NMWifiAP *ap = priv->current_ap; if (!ap) { NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); goto out_fail; } /* 802.1x networks that are not IWD Known Networks will definitely * fail, for other combinations we will let the Connect call fail * or ask us for any missing secrets through the Agent. */ if ( nm_connection_get_setting_802_1x (connection) && !is_ap_known_network (ap)) { _LOGI (LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) access point '%s' has 802.1x security but is not configured in IWD.", nm_connection_get_id (connection)); NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS); goto out_fail; } if ( !is_connection_known_network (connection) && nm_setting_wireless_get_hidden (s_wireless)) { gs_free char *ssid_str = NULL; /* Use Station.ConnectHiddenNetwork method instead of Network proxy. */ ssid_str = _nm_utils_ssid_to_utf8 (nm_setting_wireless_get_ssid (s_wireless)); g_dbus_proxy_call (priv->dbus_station_proxy, "ConnectHiddenNetwork", g_variant_new ("(s)", ssid_str), G_DBUS_CALL_FLAGS_NONE, G_MAXINT, priv->cancellable, network_connect_cb, self); return NM_ACT_STAGE_RETURN_POSTPONE; } if (!nm_wifi_ap_get_supplicant_path (ap)) { _LOGW (LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) network is provisioned but dbus supplicant path for AP unknown"); NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); goto out_fail; } network_proxy = nm_iwd_manager_get_dbus_interface (nm_iwd_manager_get (), nm_ref_string_get_str (nm_wifi_ap_get_supplicant_path (ap)), NM_IWD_NETWORK_INTERFACE); if (!network_proxy) { _LOGW (LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) could not get Network interface proxy for %s", nm_ref_string_get_str (nm_wifi_ap_get_supplicant_path (ap))); NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); goto out_fail; } if (!priv->cancellable) priv->cancellable = g_cancellable_new (); /* Call Network.Connect. No timeout because IWD already handles * timeouts. */ g_dbus_proxy_call (network_proxy, "Connect", NULL, G_DBUS_CALL_FLAGS_NONE, G_MAXINT, priv->cancellable, network_connect_cb, self); return NM_ACT_STAGE_RETURN_POSTPONE; } if (NM_IN_STRSET (mode, NM_SETTING_WIRELESS_MODE_AP, NM_SETTING_WIRELESS_MODE_ADHOC)) { NMSettingWirelessSecurity *s_wireless_sec; s_wireless_sec = nm_connection_get_setting_wireless_security (connection); if ( s_wireless_sec && !nm_setting_wireless_security_get_psk (s_wireless_sec)) { /* PSK is missing from the settings, have to request it */ wifi_secrets_cancel (self); priv->wifi_secrets_id = nm_act_request_get_secrets (req, TRUE, NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION, NM_MAKE_STRV (NM_SETTING_WIRELESS_SECURITY_PSK), act_psk_cb, self); nm_device_state_changed (device, NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NONE); } else act_set_mode (self); return NM_ACT_STAGE_RETURN_POSTPONE; } _LOGW (LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) iwd cannot handle mode %s", mode); NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); out_fail: cleanup_association_attempt (self, FALSE); return NM_ACT_STAGE_RETURN_FAILURE; } static guint32 get_configured_mtu (NMDevice *device, NMDeviceMtuSource *out_source, gboolean *out_force) { return nm_device_get_configured_mtu_from_connection (device, NM_TYPE_SETTING_WIRELESS, out_source); } static gboolean periodic_scan_timeout_cb (gpointer user_data) { NMDeviceIwd *self = user_data; NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); priv->periodic_scan_id = 0; if (priv->scanning || priv->scan_requested) return FALSE; g_dbus_proxy_call (priv->dbus_station_proxy, "Scan", NULL, G_DBUS_CALL_FLAGS_NONE, -1, priv->cancellable, scan_cb, self); priv->scan_requested = TRUE; return FALSE; } static void schedule_periodic_scan (NMDeviceIwd *self, gboolean initial_scan) { NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); GVariant *value; gboolean disconnected; guint interval; if (!priv->can_scan || priv->scan_requested) return; value = g_dbus_proxy_get_cached_property (priv->dbus_station_proxy, "State"); disconnected = nm_streq0 (get_variant_state (value), "disconnected"); g_variant_unref (value); /* Start scan immediately after a disconnect, mode change or * device UP, otherwise wait a period dependent on the current * state. * * (initial_scan && disconnected) override priv->scanning below * because of an IWD quirk where a device will often be in the * autoconnect state and scanning at the time of our initial_scan, * but our logic will the send it a Disconnect() causeing IWD to * exit autoconnect and interrupt the ongoing scan, meaning that * we still want a new scan ASAP. */ if (initial_scan && disconnected) interval = 0; else if (!priv->periodic_scan_id && !priv->scanning) interval = disconnected ? 10 : 20; else return; nm_clear_g_source (&priv->periodic_scan_id); priv->periodic_scan_id = g_timeout_add_seconds (interval, periodic_scan_timeout_cb, self); } static void set_can_scan (NMDeviceIwd *self, gboolean can_scan) { NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); if (priv->can_scan == can_scan) return; priv->can_scan = can_scan; schedule_periodic_scan (self, TRUE); } static void device_state_changed (NMDevice *device, NMDeviceState new_state, NMDeviceState old_state, NMDeviceStateReason reason) { NMDeviceIwd *self = NM_DEVICE_IWD (device); NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); switch (new_state) { case NM_DEVICE_STATE_UNMANAGED: break; case NM_DEVICE_STATE_UNAVAILABLE: /* * If the device is enabled and the IWD manager is ready, * transition to DISCONNECTED because the device is now * ready to use. */ if (priv->enabled && priv->dbus_station_proxy) { nm_device_queue_recheck_available (device, NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); } break; case NM_DEVICE_STATE_NEED_AUTH: break; case NM_DEVICE_STATE_IP_CHECK: break; case NM_DEVICE_STATE_ACTIVATED: break; case NM_DEVICE_STATE_FAILED: break; case NM_DEVICE_STATE_DISCONNECTED: break; default: break; } } static gboolean get_enabled (NMDevice *device) { return NM_DEVICE_IWD_GET_PRIVATE (device)->enabled; } static void set_enabled (NMDevice *device, gboolean enabled) { NMDeviceIwd *self = NM_DEVICE_IWD (device); NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); NMDeviceState state; enabled = !!enabled; if (priv->enabled == enabled) return; priv->enabled = enabled; _LOGD (LOGD_WIFI, "device now %s", enabled ? "enabled" : "disabled"); state = nm_device_get_state (device); if (state < NM_DEVICE_STATE_UNAVAILABLE) { _LOGD (LOGD_WIFI, "(%s): device blocked by UNMANAGED state", enabled ? "enable" : "disable"); return; } if (priv->dbus_obj) set_powered (self, enabled); if (enabled) { if (state != NM_DEVICE_STATE_UNAVAILABLE) _LOGW (LOGD_CORE, "not in expected unavailable state!"); if (priv->dbus_station_proxy) { nm_device_queue_recheck_available (device, NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); } } else { nm_device_state_changed (device, NM_DEVICE_STATE_UNAVAILABLE, NM_DEVICE_STATE_REASON_NONE); } } static gboolean can_reapply_change (NMDevice *device, const char *setting_name, NMSetting *s_old, NMSetting *s_new, GHashTable *diffs, GError **error) { NMDeviceClass *device_class; /* Only handle wireless setting here, delegate other settings to parent class */ if (nm_streq (setting_name, NM_SETTING_WIRELESS_SETTING_NAME)) { return nm_device_hash_check_invalid_keys (diffs, NM_SETTING_WIRELESS_SETTING_NAME, error, NM_SETTING_WIRELESS_SEEN_BSSIDS, /* ignored */ NM_SETTING_WIRELESS_MTU); /* reapplied with IP config */ } device_class = NM_DEVICE_CLASS (nm_device_iwd_parent_class); return device_class->can_reapply_change (device, setting_name, s_old, s_new, diffs, error); } /*****************************************************************************/ static void get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { NMDeviceIwd *self = NM_DEVICE_IWD (object); NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); const char **list; switch (prop_id) { case PROP_MODE: if (!priv->current_ap) g_value_set_uint (value, NM_802_11_MODE_UNKNOWN); else if (nm_wifi_ap_is_hotspot (priv->current_ap)) g_value_set_uint (value, NM_802_11_MODE_AP); else g_value_set_uint (value, nm_wifi_ap_get_mode (priv->current_ap)); break; case PROP_BITRATE: g_value_set_uint (value, 65000); break; case PROP_CAPABILITIES: g_value_set_uint (value, priv->capabilities); break; case PROP_ACCESS_POINTS: list = nm_wifi_aps_get_paths (&priv->aps_lst_head, TRUE); g_value_take_boxed (value, nm_utils_strv_make_deep_copied (list)); break; case PROP_ACTIVE_ACCESS_POINT: nm_dbus_utils_g_value_set_object_path (value, priv->current_ap); break; case PROP_SCANNING: g_value_set_boolean (value, priv->scanning); break; case PROP_LAST_SCAN: g_value_set_int64 (value, priv->last_scan > 0 ? nm_utils_monotonic_timestamp_as_boottime (priv->last_scan, NM_UTILS_NSEC_PER_MSEC) : (gint64) -1); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } /*****************************************************************************/ static void state_changed (NMDeviceIwd *self, const char *new_state) { NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); NMDevice *device = NM_DEVICE (self); NMDeviceState dev_state = nm_device_get_state (device); gboolean iwd_connection = FALSE; gboolean can_connect = priv->can_connect; _LOGI (LOGD_DEVICE | LOGD_WIFI, "new IWD device state is %s", new_state); if ( dev_state >= NM_DEVICE_STATE_CONFIG && dev_state <= NM_DEVICE_STATE_ACTIVATED) iwd_connection = TRUE; /* Don't allow scanning while connecting, disconnecting or roaming */ set_can_scan (self, NM_IN_STRSET (new_state, "connected", "disconnected")); priv->can_connect = FALSE; if (NM_IN_STRSET (new_state, "connecting", "connected", "roaming")) { /* If we were connecting, do nothing, the confirmation of * a connection success is handled in the Device.Connect * method return callback. Otherwise IWD must have connected * without Network Manager's will so for simplicity force a * disconnect. */ if (iwd_connection) return; _LOGW (LOGD_DEVICE | LOGD_WIFI, "Unsolicited connection success, asking IWD to disconnect"); send_disconnect (self); } else if (NM_IN_STRSET (new_state, "disconnecting", "disconnected")) { /* Call Disconnect on the IWD device object to make sure it * disables its own autoconnect. */ send_disconnect (self); /* * If IWD is still handling the Connect call, let our Connect * callback for the dbus method handle the failure. The main * reason we can't handle the failure here is because the method * callback will have more information on the specific failure * reason. */ if (NM_IN_SET (dev_state, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_NEED_AUTH)) return; if (iwd_connection) nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT); } else if (!nm_streq (new_state, "unknown")) { _LOGE (LOGD_WIFI, "State %s unknown", new_state); return; } /* Don't allow new connection until iwd exits disconnecting and no * Connect callback is pending. */ if (NM_IN_STRSET (new_state, "disconnected")) { priv->can_connect = TRUE; if (!can_connect) nm_device_emit_recheck_auto_activate (device); } } static void scanning_changed (NMDeviceIwd *self, gboolean new_scanning) { NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); if (new_scanning == priv->scanning) return; priv->scanning = new_scanning; _notify (self, PROP_SCANNING); if (!priv->scanning) { update_aps (self); if (!priv->scan_requested) schedule_periodic_scan (self, FALSE); } } static void station_properties_changed (GDBusProxy *proxy, GVariant *changed_properties, GStrv invalidate_properties, gpointer user_data) { NMDeviceIwd *self = user_data; const char *new_str; gboolean new_bool; if (g_variant_lookup (changed_properties, "State", "&s", &new_str)) state_changed (self, new_str); if (g_variant_lookup (changed_properties, "Scanning", "b", &new_bool)) scanning_changed (self, new_bool); } static void ap_adhoc_properties_changed (GDBusProxy *proxy, GVariant *changed_properties, GStrv invalidate_properties, gpointer user_data) { NMDeviceIwd *self = user_data; gboolean new_bool; if (g_variant_lookup (changed_properties, "Started", "b", &new_bool)) _LOGI (LOGD_DEVICE | LOGD_WIFI, "IWD AP/AdHoc state is now %s", new_bool ? "Started" : "Stopped"); } static void powered_changed (NMDeviceIwd *self, gboolean new_powered) { NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); GDBusInterface *interface; GVariant *value; nm_device_queue_recheck_available (NM_DEVICE (self), NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); interface = new_powered ? g_dbus_object_get_interface (priv->dbus_obj, NM_IWD_AP_INTERFACE) : NULL; if (priv->dbus_ap_proxy) { g_signal_handlers_disconnect_by_func (priv->dbus_ap_proxy, ap_adhoc_properties_changed, self); g_clear_object (&priv->dbus_ap_proxy); } if (interface) { priv->dbus_ap_proxy = G_DBUS_PROXY (interface); g_signal_connect (priv->dbus_ap_proxy, "g-properties-changed", G_CALLBACK (ap_adhoc_properties_changed), self); if (priv->act_mode_switch) act_check_interface (self); else reset_mode (self, NULL, NULL, NULL); } interface = new_powered ? g_dbus_object_get_interface (priv->dbus_obj, NM_IWD_ADHOC_INTERFACE) : NULL; if (priv->dbus_adhoc_proxy) { g_signal_handlers_disconnect_by_func (priv->dbus_adhoc_proxy, ap_adhoc_properties_changed, self); g_clear_object (&priv->dbus_adhoc_proxy); } if (interface) { priv->dbus_adhoc_proxy = G_DBUS_PROXY (interface); g_signal_connect (priv->dbus_adhoc_proxy, "g-properties-changed", G_CALLBACK (ap_adhoc_properties_changed), self); if (priv->act_mode_switch) act_check_interface (self); else reset_mode (self, NULL, NULL, NULL); } /* We expect one of the three interfaces to always be present when * device is Powered so if AP and AdHoc are not present we should * be in station mode. */ if (new_powered && !priv->dbus_ap_proxy && !priv->dbus_adhoc_proxy) { interface = g_dbus_object_get_interface (priv->dbus_obj, NM_IWD_STATION_INTERFACE); if (!interface) { /* No Station interface on the device object. Check if the * "State" property is present on the Device interface, that * would mean we're dealing with an IWD version from before the * Device/Station split (0.7 or earlier) and we can easily * handle that by making priv->dbus_device_proxy and * priv->dbus_station_proxy both point at the Device interface. */ value = g_dbus_proxy_get_cached_property (priv->dbus_device_proxy, "State"); if (value) { g_variant_unref (value); interface = g_object_ref (G_DBUS_INTERFACE (priv->dbus_device_proxy)); } else { _LOGE (LOGD_WIFI, "Interface %s not found on obj %s", NM_IWD_STATION_INTERFACE, g_dbus_object_get_object_path (priv->dbus_obj)); interface = NULL; } } } else interface = NULL; if (priv->dbus_station_proxy) { g_signal_handlers_disconnect_by_func (priv->dbus_station_proxy, station_properties_changed, self); g_clear_object (&priv->dbus_station_proxy); } if (interface) { priv->dbus_station_proxy = G_DBUS_PROXY (interface); g_signal_connect (priv->dbus_station_proxy, "g-properties-changed", G_CALLBACK (station_properties_changed), self); value = g_dbus_proxy_get_cached_property (priv->dbus_station_proxy, "Scanning"); priv->scanning = get_variant_boolean (value, "Scanning"); g_variant_unref (value); value = g_dbus_proxy_get_cached_property (priv->dbus_station_proxy, "State"); state_changed (self, get_variant_state (value)); g_variant_unref (value); update_aps (self); } else { set_can_scan (self, FALSE); nm_clear_g_source (&priv->periodic_scan_id); priv->scanning = FALSE; priv->scan_requested = FALSE; priv->can_connect = FALSE; cleanup_association_attempt (self, FALSE); remove_all_aps (self); } } static void device_properties_changed (GDBusProxy *proxy, GVariant *changed_properties, GStrv invalidate_properties, gpointer user_data) { NMDeviceIwd *self = user_data; gboolean new_bool; if (g_variant_lookup (changed_properties, "Powered", "b", &new_bool)) powered_changed (self, new_bool); } void nm_device_iwd_set_dbus_object (NMDeviceIwd *self, GDBusObject *object) { NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); GDBusInterface *interface; gs_unref_variant GVariant *value = NULL; gs_unref_object GDBusProxy *adapter_proxy = NULL; GVariantIter *iter; const char *mode; gboolean powered; NMDeviceWifiCapabilities capabilities; if (!nm_g_object_ref_set (&priv->dbus_obj, object)) return; if (priv->dbus_device_proxy) { g_signal_handlers_disconnect_by_func (priv->dbus_device_proxy, device_properties_changed, self); g_clear_object (&priv->dbus_device_proxy); powered_changed (self, FALSE); priv->act_mode_switch = FALSE; } if (!object) return; interface = g_dbus_object_get_interface (object, NM_IWD_DEVICE_INTERFACE); if (!interface) { _LOGE (LOGD_WIFI, "Interface %s not found on obj %s", NM_IWD_DEVICE_INTERFACE, g_dbus_object_get_object_path (object)); g_clear_object (&priv->dbus_obj); return; } priv->dbus_device_proxy = G_DBUS_PROXY (interface); g_signal_connect (priv->dbus_device_proxy, "g-properties-changed", G_CALLBACK (device_properties_changed), self); /* Parse list of interface modes supported by adapter (wiphy) */ value = g_dbus_proxy_get_cached_property (priv->dbus_device_proxy, "Adapter"); if (!value || !g_variant_is_of_type (value, G_VARIANT_TYPE_OBJECT_PATH)) { nm_log_warn (LOGD_DEVICE | LOGD_WIFI, "Adapter property not cached or not an object path"); goto error; } adapter_proxy = nm_iwd_manager_get_dbus_interface (nm_iwd_manager_get (), g_variant_get_string (value, NULL), NM_IWD_WIPHY_INTERFACE); if (!adapter_proxy) { nm_log_warn (LOGD_DEVICE | LOGD_WIFI, "Can't get DBus proxy for IWD Adapter for IWD Device"); goto error; } g_variant_unref (value); value = g_dbus_proxy_get_cached_property (adapter_proxy, "SupportedModes"); if (!value || !g_variant_is_of_type (value, G_VARIANT_TYPE_STRING_ARRAY)) { nm_log_warn (LOGD_DEVICE | LOGD_WIFI, "SupportedModes property not cached or not a string array"); goto error; } capabilities = NM_WIFI_DEVICE_CAP_CIPHER_CCMP | NM_WIFI_DEVICE_CAP_RSN; g_variant_get (value, "as", &iter); while (g_variant_iter_next (iter, "&s", &mode)) { if (nm_streq (mode, "ap")) capabilities |= NM_WIFI_DEVICE_CAP_AP; else if (nm_streq (mode, "ad-hoc")) capabilities |= NM_WIFI_DEVICE_CAP_ADHOC; } g_variant_iter_free (iter); if (priv->capabilities != capabilities) { priv->capabilities = capabilities; _notify (self, PROP_CAPABILITIES); } g_variant_unref (value); value = g_dbus_proxy_get_cached_property (priv->dbus_device_proxy, "Powered"); powered = get_variant_boolean (value, "Powered"); if (powered != priv->enabled) set_powered (self, priv->enabled); else if (powered) powered_changed (self, TRUE); return; error: g_signal_handlers_disconnect_by_func (priv->dbus_device_proxy, device_properties_changed, self); g_clear_object (&priv->dbus_device_proxy); } gboolean nm_device_iwd_agent_query (NMDeviceIwd *self, GDBusMethodInvocation *invocation) { NMActRequest *req; const char *setting_name; const char *setting_key; gboolean replied; NMSecretAgentGetSecretsFlags get_secret_flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION; req = nm_device_get_act_request (NM_DEVICE (self)); if (!req) return FALSE; if (!try_reply_agent_request (self, nm_act_request_get_applied_connection (req), invocation, &setting_name, &setting_key, &replied)) return FALSE; if (replied) return TRUE; /* Normally require new secrets every time IWD asks for them. * IWD only queries us if it has not saved the secrets (e.g. by policy) * or a previous attempt has failed with current secrets so it wants * a fresh set. However if this is a new connection it may include * all of the needed settings already so allow using these, too. * Connection timestamp is set after activation or after first * activation failure (to 0). */ if (nm_settings_connection_get_timestamp (nm_act_request_get_settings_connection (req), NULL)) get_secret_flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW; nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NO_SECRETS); wifi_secrets_get_one (self, setting_name, get_secret_flags, setting_key, invocation); return TRUE; } /*****************************************************************************/ static const char * get_type_description (NMDevice *device) { nm_assert (NM_IS_DEVICE_IWD (device)); return "wifi"; } /*****************************************************************************/ static void nm_device_iwd_init (NMDeviceIwd *self) { NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); c_list_init (&priv->aps_lst_head); /* Make sure the manager is running */ (void) nm_iwd_manager_get (); } NMDevice * nm_device_iwd_new (const char *iface) { return g_object_new (NM_TYPE_DEVICE_IWD, NM_DEVICE_IFACE, iface, NM_DEVICE_TYPE_DESC, "802.11 Wi-Fi", NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_WIFI, NM_DEVICE_LINK_TYPE, NM_LINK_TYPE_WIFI, NM_DEVICE_RFKILL_TYPE, RFKILL_TYPE_WLAN, NULL); } static void dispose (GObject *object) { NMDeviceIwd *self = NM_DEVICE_IWD (object); NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self); nm_clear_g_cancellable (&priv->cancellable); nm_clear_g_source (&priv->periodic_scan_id); cleanup_association_attempt (self, TRUE); g_clear_object (&priv->dbus_device_proxy); g_clear_object (&priv->dbus_station_proxy); g_clear_object (&priv->dbus_ap_proxy); g_clear_object (&priv->dbus_adhoc_proxy); g_clear_object (&priv->dbus_obj); remove_all_aps (self); G_OBJECT_CLASS (nm_device_iwd_parent_class)->dispose (object); nm_assert (c_list_is_empty (&priv->aps_lst_head)); } static void nm_device_iwd_class_init (NMDeviceIwdClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass); NMDeviceClass *device_class = NM_DEVICE_CLASS (klass); object_class->get_property = get_property; object_class->dispose = dispose; dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&nm_interface_info_device_wireless); device_class->connection_type_supported = NM_SETTING_WIRELESS_SETTING_NAME; device_class->connection_type_check_compatible = NM_SETTING_WIRELESS_SETTING_NAME; device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_WIFI); device_class->can_auto_connect = can_auto_connect; device_class->is_available = is_available; device_class->get_autoconnect_allowed = get_autoconnect_allowed; device_class->check_connection_compatible = check_connection_compatible; device_class->check_connection_available = check_connection_available; device_class->complete_connection = complete_connection; device_class->get_enabled = get_enabled; device_class->set_enabled = set_enabled; device_class->get_type_description = get_type_description; device_class->act_stage1_prepare = act_stage1_prepare; device_class->act_stage2_config = act_stage2_config; device_class->get_configured_mtu = get_configured_mtu; device_class->deactivate = deactivate; device_class->deactivate_async = deactivate_async; device_class->can_reapply_change = can_reapply_change; device_class->state_changed = device_state_changed; obj_properties[PROP_MODE] = g_param_spec_uint (NM_DEVICE_IWD_MODE, "", "", NM_802_11_MODE_UNKNOWN, NM_802_11_MODE_AP, NM_802_11_MODE_INFRA, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_BITRATE] = g_param_spec_uint (NM_DEVICE_IWD_BITRATE, "", "", 0, G_MAXUINT32, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_ACCESS_POINTS] = g_param_spec_boxed (NM_DEVICE_IWD_ACCESS_POINTS, "", "", G_TYPE_STRV, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_ACTIVE_ACCESS_POINT] = g_param_spec_string (NM_DEVICE_IWD_ACTIVE_ACCESS_POINT, "", "", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_CAPABILITIES] = g_param_spec_uint (NM_DEVICE_IWD_CAPABILITIES, "", "", 0, G_MAXUINT32, NM_WIFI_DEVICE_CAP_NONE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_SCANNING] = g_param_spec_boolean (NM_DEVICE_IWD_SCANNING, "", "", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_LAST_SCAN] = g_param_spec_int64 (NM_DEVICE_IWD_LAST_SCAN, "", "", -1, G_MAXINT64, -1, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties); }