mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-05-17 00:38:07 +02:00
nm_supplicant_config_add_bgscan() picks "simple:30:-65:300" for any
profile that has been seen on more than one BSSID, on the
assumption that this is a multi-AP ESS where periodic roam-candidate
scanning is desirable. Each scan blocks the data path on the radio
for the full scan duration (5-7 s on rtw89 USB at MCS 13), causing
audible/visible stalls in real-time UDP applications such as Teams
and Discord every 5 minutes.
For single-AP Wi-Fi 7 MLO, this heuristic misfires: the AP
advertises one BSSID per link, so a 2-link or 3-link MLD looks
like a 2-3 AP ESS to the seen-bssids count. Verified locally on
RTL8922AU + Bazzite Linux 6.19.11 against TP-Link Deco BE63 mesh;
the connection's seen-bssids list contains only the per-link
BSSIDs of the single physical AP it has been associated with
(Link 1 and Link 2 of the MLD), but bgscan flips to the multi-AP
value with the user-visible stall pattern.
Add a conservative heuristic to detect MLO per-link BSSIDs and
exempt them from the multi-AP path. The detection requires ALL of:
- 2 to 3 seen BSSIDs (802.11be defines tri-link 2.4 + 5 + 6 GHz
as the maximum)
- every BSSID has the locally-administered bit set (vendors use
LAA-flagged virtual MACs for per-link addresses; verified on
TP-Link Deco BE63: Link 1 f6:75:0c:74:4b:75 and Link 2
ca:75:0c:74:4b:70 both have bit 1 set)
- every BSSID shares octets 1-4 with the others (per-link MACs
derived from a common base, varying only the first and last
octets)
Real multi-AP ESSes (mesh, enterprise) typically use vendor-assigned
MACs (LAA bit unset) and unrelated address blocks per AP, so the
heuristic should not false-positive on legitimate multi-AP networks.
The connection's seen-bssids list is bounded at 30 entries (LRU) per
NM_SETTINGS_CONNECTION_SEEN_BSSIDS_MAX; the strv passed in from
build_supplicant_config() is the daemon-managed authoritative copy
loaded from /var/lib/NetworkManager/seen-bssids, not the unreliable
NMSettingWireless property.
Extend nm_supplicant_config_add_bgscan() to take the BSSID strv as
a parameter alongside the existing num_seen_bssids count (mirroring
the 2023 fix in commit 07c6f933d1 ('wifi: fix aggressively
roaming (background Wi-Fi scanning) based on seen-bssids') for
num_seen_bssids), so the heuristic can inspect actual addresses
rather than only the count.
Add a regression test test_wifi_bgscan_mlo_dedup() in
test-supplicant-config.c covering four cases: 2-link MLO BSSIDs
(LAA + shared octets 1-4) producing the long-interval bgscan;
tri-link MLO BSSIDs (3-BSSID variant matching 802.11be's tri-link
maximum) also producing the long-interval bgscan; real multi-AP
BSSIDs (UAA, unrelated blocks) producing the short interval; and
all-LAA BSSIDs with unrelated octets 1-4 still producing the short
interval (no false-positive on LAA alone).
Empirical A/B on the local rig (2026-04-29):
- With this dedup applied: bgscan = "simple:30:-70:86400";
0 scans observed in 18-min capture; 0 audio/video stalls in
a 30-min Discord call
- Without (stock 1.54): bgscan = "simple:30:-65:300";
6 full-band scans at exactly 305 s cadence; 4 stall bursts
coinciding with scan windows in 30 min
Heuristic was calibrated against TP-Link Deco BE63; non-Deco MLO
hardware was not available for testing. The conservative
all-of-three-conditions check should reject most non-MLO traffic,
but vendor diversity in MLO per-link MAC derivation has not been
characterised. Testers with non-Deco MLO hardware welcome.
Signed-off-by: Louis Kotze <loukot@gmail.com>
2085 lines
81 KiB
C
2085 lines
81 KiB
C
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
/*
|
|
* Copyright (C) 2006 - 2012 Red Hat, Inc.
|
|
* Copyright (C) 2007 - 2008 Novell, Inc.
|
|
*/
|
|
|
|
#include "src/core/nm-default-daemon.h"
|
|
|
|
#include "nm-supplicant-config.h"
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include "libnm-glib-aux/nm-str-buf.h"
|
|
#include "libnm-core-intern/nm-core-internal.h"
|
|
#include "nm-supplicant-settings-verify.h"
|
|
#include "nm-setting.h"
|
|
#include "libnm-core-aux-intern/nm-auth-subject.h"
|
|
#include "NetworkManagerUtils.h"
|
|
#include "nm-utils.h"
|
|
#include "nm-setting-ip4-config.h"
|
|
|
|
typedef struct {
|
|
char *value;
|
|
guint32 len;
|
|
NMSupplOptType type;
|
|
} ConfigOption;
|
|
|
|
/*****************************************************************************/
|
|
|
|
typedef struct {
|
|
GHashTable *config;
|
|
GHashTable *blobs;
|
|
char *private_user;
|
|
NMSupplCapMask capabilities;
|
|
guint32 ap_scan;
|
|
bool fast_required : 1;
|
|
bool dispose_has_run : 1;
|
|
bool ap_isolation : 1;
|
|
} NMSupplicantConfigPrivate;
|
|
|
|
struct _NMSupplicantConfig {
|
|
GObject parent;
|
|
NMSupplicantConfigPrivate _priv;
|
|
};
|
|
|
|
struct _NMSupplicantConfigClass {
|
|
GObjectClass parent;
|
|
};
|
|
|
|
G_DEFINE_TYPE(NMSupplicantConfig, nm_supplicant_config, G_TYPE_OBJECT)
|
|
|
|
#define NM_SUPPLICANT_CONFIG_GET_PRIVATE(self) \
|
|
_NM_GET_PRIVATE(self, NMSupplicantConfig, NM_IS_SUPPLICANT_CONFIG)
|
|
|
|
/*****************************************************************************/
|
|
|
|
static gboolean
|
|
_get_capability(NMSupplicantConfigPrivate *priv, NMSupplCapType type)
|
|
{
|
|
return NM_SUPPL_CAP_MASK_GET(priv->capabilities, type) == NM_TERNARY_TRUE;
|
|
}
|
|
|
|
NMSupplicantConfig *
|
|
nm_supplicant_config_new(NMSupplCapMask capabilities, const char *private_user)
|
|
{
|
|
NMSupplicantConfigPrivate *priv;
|
|
NMSupplicantConfig *self;
|
|
|
|
self = g_object_new(NM_TYPE_SUPPLICANT_CONFIG, NULL);
|
|
priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self);
|
|
|
|
priv->capabilities = capabilities;
|
|
priv->private_user = g_strdup(private_user);
|
|
|
|
return self;
|
|
}
|
|
|
|
static void
|
|
config_option_free(ConfigOption *opt)
|
|
{
|
|
g_free(opt->value);
|
|
g_slice_free(ConfigOption, opt);
|
|
}
|
|
|
|
static void
|
|
nm_supplicant_config_init(NMSupplicantConfig *self)
|
|
{
|
|
NMSupplicantConfigPrivate *priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self);
|
|
|
|
priv->config = g_hash_table_new_full(nm_str_hash,
|
|
g_str_equal,
|
|
g_free,
|
|
(GDestroyNotify) config_option_free);
|
|
|
|
priv->ap_scan = 1;
|
|
priv->dispose_has_run = FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
nm_supplicant_config_add_option_with_type(NMSupplicantConfig *self,
|
|
const char *key,
|
|
const char *value,
|
|
gint32 len,
|
|
NMSupplOptType opt_type,
|
|
const char *display_value,
|
|
GError **error)
|
|
{
|
|
NMSupplicantConfigPrivate *priv;
|
|
ConfigOption *old_opt;
|
|
ConfigOption *opt;
|
|
NMSupplOptType type;
|
|
|
|
g_return_val_if_fail(NM_IS_SUPPLICANT_CONFIG(self), FALSE);
|
|
g_return_val_if_fail(key != NULL, FALSE);
|
|
g_return_val_if_fail(value != NULL, FALSE);
|
|
nm_assert(!error || !*error);
|
|
|
|
priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self);
|
|
|
|
if (len < 0)
|
|
len = strlen(value);
|
|
|
|
if (opt_type != NM_SUPPL_OPT_TYPE_INVALID)
|
|
type = opt_type;
|
|
else {
|
|
type = nm_supplicant_settings_verify_setting(key, value, len);
|
|
if (type == NM_SUPPL_OPT_TYPE_INVALID) {
|
|
gs_free char *str_free = NULL;
|
|
const char *str;
|
|
|
|
str = nm_utils_buf_utf8safe_escape(value,
|
|
len,
|
|
NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL,
|
|
&str_free);
|
|
|
|
str = nm_strquote_a(255, str);
|
|
|
|
g_set_error(error,
|
|
NM_SUPPLICANT_ERROR,
|
|
NM_SUPPLICANT_ERROR_CONFIG,
|
|
"key '%s' and/or value %s invalid",
|
|
key,
|
|
display_value ?: str);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
old_opt = (ConfigOption *) g_hash_table_lookup(priv->config, key);
|
|
if (old_opt) {
|
|
g_set_error(error,
|
|
NM_SUPPLICANT_ERROR,
|
|
NM_SUPPLICANT_ERROR_CONFIG,
|
|
"key '%s' already configured",
|
|
key);
|
|
return FALSE;
|
|
}
|
|
|
|
opt = g_slice_new(ConfigOption);
|
|
*opt = (ConfigOption) {
|
|
.value = nm_memdup_nul(value, len),
|
|
.len = len,
|
|
.type = type,
|
|
};
|
|
|
|
{
|
|
char buf[255];
|
|
|
|
memset(&buf[0], 0, sizeof(buf));
|
|
memcpy(&buf[0], opt->value, opt->len > 254 ? 254 : opt->len);
|
|
nm_log_info(LOGD_SUPPLICANT,
|
|
"Config: added '%s' value '%s'",
|
|
key,
|
|
display_value ?: &buf[0]);
|
|
}
|
|
|
|
g_hash_table_insert(priv->config, g_strdup(key), opt);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
nm_supplicant_config_add_option(NMSupplicantConfig *self,
|
|
const char *key,
|
|
const char *value,
|
|
gint32 len,
|
|
const char *display_value,
|
|
GError **error)
|
|
{
|
|
return nm_supplicant_config_add_option_with_type(self,
|
|
key,
|
|
value,
|
|
len,
|
|
NM_SUPPL_OPT_TYPE_INVALID,
|
|
display_value,
|
|
error);
|
|
}
|
|
|
|
static gboolean
|
|
nm_supplicant_config_add_blob(NMSupplicantConfig *self,
|
|
const char *key,
|
|
GBytes *value,
|
|
const char *blobid,
|
|
GError **error)
|
|
{
|
|
NMSupplicantConfigPrivate *priv;
|
|
ConfigOption *old_opt;
|
|
ConfigOption *opt;
|
|
NMSupplOptType type;
|
|
gsize data_len;
|
|
gs_free char *full_value = NULL;
|
|
|
|
g_return_val_if_fail(NM_IS_SUPPLICANT_CONFIG(self), FALSE);
|
|
g_return_val_if_fail(key != NULL, FALSE);
|
|
g_return_val_if_fail(value != NULL, FALSE);
|
|
g_return_val_if_fail(blobid != NULL, FALSE);
|
|
|
|
g_bytes_get_data(value, &data_len);
|
|
g_return_val_if_fail(data_len > 0, FALSE);
|
|
|
|
if (data_len > 32 * 1024 * 1024) {
|
|
g_set_error(error,
|
|
NM_SUPPLICANT_ERROR,
|
|
NM_SUPPLICANT_ERROR_CONFIG,
|
|
"blob '%s' is larger than 32MiB",
|
|
key);
|
|
return FALSE;
|
|
}
|
|
|
|
priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self);
|
|
full_value = g_strdup_printf("blob://%s", blobid);
|
|
|
|
type = nm_supplicant_settings_verify_setting(key, full_value, strlen(full_value));
|
|
if (type == NM_SUPPL_OPT_TYPE_INVALID) {
|
|
g_set_error(error,
|
|
NM_SUPPLICANT_ERROR,
|
|
NM_SUPPLICANT_ERROR_CONFIG,
|
|
"key '%s' and/or its contained value is invalid",
|
|
key);
|
|
return FALSE;
|
|
}
|
|
|
|
old_opt = (ConfigOption *) g_hash_table_lookup(priv->config, key);
|
|
if (old_opt) {
|
|
g_set_error(error,
|
|
NM_SUPPLICANT_ERROR,
|
|
NM_SUPPLICANT_ERROR_CONFIG,
|
|
"key '%s' already configured",
|
|
key);
|
|
return FALSE;
|
|
}
|
|
|
|
opt = g_slice_new0(ConfigOption);
|
|
opt->value = g_steal_pointer(&full_value);
|
|
opt->len = strlen(opt->value);
|
|
opt->type = type;
|
|
|
|
nm_log_info(LOGD_SUPPLICANT, "Config: added '%s' value '%s'", key, opt->value);
|
|
|
|
g_hash_table_insert(priv->config, g_strdup(key), opt);
|
|
if (!priv->blobs) {
|
|
priv->blobs =
|
|
g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, (GDestroyNotify) g_bytes_unref);
|
|
}
|
|
g_hash_table_insert(priv->blobs, g_strdup(blobid), g_bytes_ref(value));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
nm_supplicant_config_add_blob_for_connection(NMSupplicantConfig *self,
|
|
GBytes *field,
|
|
const char *name,
|
|
const char *con_uuid,
|
|
GError **error)
|
|
{
|
|
if (field && g_bytes_get_size(field)) {
|
|
gs_free char *blob_id = NULL;
|
|
char *p;
|
|
|
|
blob_id = g_strdup_printf("%s-%s", con_uuid, name);
|
|
for (p = blob_id; *p; p++) {
|
|
if (*p == '/')
|
|
*p = '-';
|
|
}
|
|
if (!nm_supplicant_config_add_blob(self, name, field, blob_id, error))
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
nm_supplicant_config_finalize(GObject *object)
|
|
{
|
|
NMSupplicantConfigPrivate *priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(object);
|
|
|
|
g_hash_table_destroy(priv->config);
|
|
nm_clear_pointer(&priv->blobs, g_hash_table_destroy);
|
|
nm_clear_pointer(&priv->private_user, g_free);
|
|
|
|
G_OBJECT_CLASS(nm_supplicant_config_parent_class)->finalize(object);
|
|
}
|
|
|
|
static void
|
|
nm_supplicant_config_class_init(NMSupplicantConfigClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS(klass);
|
|
|
|
object_class->finalize = nm_supplicant_config_finalize;
|
|
}
|
|
|
|
guint32
|
|
nm_supplicant_config_get_ap_scan(NMSupplicantConfig *self)
|
|
{
|
|
g_return_val_if_fail(NM_IS_SUPPLICANT_CONFIG(self), 1);
|
|
|
|
return NM_SUPPLICANT_CONFIG_GET_PRIVATE(self)->ap_scan;
|
|
}
|
|
|
|
gboolean
|
|
nm_supplicant_config_fast_required(NMSupplicantConfig *self)
|
|
{
|
|
g_return_val_if_fail(NM_IS_SUPPLICANT_CONFIG(self), FALSE);
|
|
|
|
return NM_SUPPLICANT_CONFIG_GET_PRIVATE(self)->fast_required;
|
|
}
|
|
|
|
GVariant *
|
|
nm_supplicant_config_to_variant(NMSupplicantConfig *self)
|
|
{
|
|
NMSupplicantConfigPrivate *priv;
|
|
GVariantBuilder builder;
|
|
GHashTableIter iter;
|
|
ConfigOption *option;
|
|
const char *key;
|
|
|
|
g_return_val_if_fail(NM_IS_SUPPLICANT_CONFIG(self), NULL);
|
|
|
|
priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self);
|
|
|
|
g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
|
|
|
|
g_hash_table_iter_init(&iter, priv->config);
|
|
while (g_hash_table_iter_next(&iter, (gpointer) &key, (gpointer) &option)) {
|
|
switch (option->type) {
|
|
case NM_SUPPL_OPT_TYPE_INT:
|
|
g_variant_builder_add(&builder, "{sv}", key, g_variant_new_int32(atoi(option->value)));
|
|
break;
|
|
case NM_SUPPL_OPT_TYPE_BYTES:
|
|
case NM_SUPPL_OPT_TYPE_UTF8:
|
|
g_variant_builder_add(&builder,
|
|
"{sv}",
|
|
key,
|
|
nm_g_variant_new_ay((const guint8 *) option->value, option->len));
|
|
break;
|
|
case NM_SUPPL_OPT_TYPE_KEYWORD:
|
|
case NM_SUPPL_OPT_TYPE_STRING:
|
|
g_variant_builder_add(&builder, "{sv}", key, g_variant_new_string(option->value));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return g_variant_builder_end(&builder);
|
|
}
|
|
|
|
GHashTable *
|
|
nm_supplicant_config_get_blobs(NMSupplicantConfig *self)
|
|
{
|
|
g_return_val_if_fail(NM_IS_SUPPLICANT_CONFIG(self), NULL);
|
|
|
|
return NM_SUPPLICANT_CONFIG_GET_PRIVATE(self)->blobs;
|
|
}
|
|
|
|
static const char *
|
|
wifi_freqs_to_string(const char *band)
|
|
{
|
|
static const char *str_2ghz = NULL;
|
|
static const char *str_5ghz = NULL;
|
|
static const char *str_6ghz = NULL;
|
|
const char **f_p;
|
|
const char *f;
|
|
|
|
if (nm_streq0(band, "a"))
|
|
f_p = &str_5ghz;
|
|
else if (nm_streq0(band, "bg"))
|
|
f_p = &str_2ghz;
|
|
else if (nm_streq0(band, "6GHz"))
|
|
f_p = &str_6ghz;
|
|
else {
|
|
nm_assert_not_reached();
|
|
return NULL;
|
|
}
|
|
|
|
again:
|
|
f = g_atomic_pointer_get(f_p);
|
|
|
|
if (G_UNLIKELY(!f)) {
|
|
nm_auto_str_buf NMStrBuf strbuf = NM_STR_BUF_INIT(400, FALSE);
|
|
const guint *freqs;
|
|
int i;
|
|
|
|
if (f_p == &str_2ghz)
|
|
freqs = nm_utils_wifi_2ghz_freqs();
|
|
else if (f_p == &str_5ghz)
|
|
freqs = nm_utils_wifi_5ghz_freqs();
|
|
else
|
|
freqs = nm_utils_wifi_6ghz_freqs();
|
|
|
|
for (i = 0; freqs[i]; i++) {
|
|
if (i > 0)
|
|
nm_str_buf_append_c(&strbuf, ' ');
|
|
nm_str_buf_append_printf(&strbuf, "%u", freqs[i]);
|
|
}
|
|
|
|
f = g_strdup(nm_str_buf_get_str(&strbuf));
|
|
|
|
if (!g_atomic_pointer_compare_and_exchange(f_p, NULL, f)) {
|
|
g_free((char *) f);
|
|
goto again;
|
|
}
|
|
}
|
|
|
|
return f;
|
|
}
|
|
|
|
gboolean
|
|
nm_supplicant_config_add_setting_macsec(NMSupplicantConfig *self,
|
|
NMSettingMacsec *setting,
|
|
NMSettingMacsecOffload offload,
|
|
GError **error)
|
|
{
|
|
const char *value;
|
|
char buf[32];
|
|
int port;
|
|
gsize key_len;
|
|
const char *offload_str = NULL;
|
|
|
|
g_return_val_if_fail(NM_IS_SUPPLICANT_CONFIG(self), FALSE);
|
|
g_return_val_if_fail(setting != NULL, FALSE);
|
|
g_return_val_if_fail(!error || !*error, FALSE);
|
|
|
|
if (!nm_supplicant_config_add_option(self, "macsec_policy", "1", -1, NULL, error))
|
|
return FALSE;
|
|
|
|
value = nm_setting_macsec_get_encrypt(setting) ? "0" : "1";
|
|
if (!nm_supplicant_config_add_option(self, "macsec_integ_only", value, -1, NULL, error))
|
|
return FALSE;
|
|
|
|
port = nm_setting_macsec_get_port(setting);
|
|
if (port > 0 && port < 65534) {
|
|
g_snprintf(buf, sizeof(buf), "%d", port);
|
|
if (!nm_supplicant_config_add_option(self, "macsec_port", buf, -1, NULL, error))
|
|
return FALSE;
|
|
}
|
|
|
|
if (nm_setting_macsec_get_mode(setting) == NM_SETTING_MACSEC_MODE_PSK) {
|
|
guint8 buffer_cak[NM_SETTING_MACSEC_MKA_CAK_LENGTH / 2];
|
|
guint8 buffer_ckn[NM_SETTING_MACSEC_MKA_CKN_LENGTH / 2];
|
|
|
|
if (!nm_supplicant_config_add_option(self, "key_mgmt", "NONE", -1, NULL, error))
|
|
return FALSE;
|
|
|
|
value = nm_setting_macsec_get_mka_cak(setting);
|
|
if (!value || !nm_utils_hexstr2bin_buf(value, FALSE, FALSE, NULL, buffer_cak)) {
|
|
g_set_error_literal(error,
|
|
NM_SUPPLICANT_ERROR,
|
|
NM_SUPPLICANT_ERROR_CONFIG,
|
|
value ? "invalid MKA CAK" : "missing MKA CAK");
|
|
return FALSE;
|
|
}
|
|
if (!nm_supplicant_config_add_option(self,
|
|
"mka_cak",
|
|
(char *) buffer_cak,
|
|
sizeof(buffer_cak),
|
|
"<hidden>",
|
|
error))
|
|
return FALSE;
|
|
|
|
value = nm_setting_macsec_get_mka_ckn(setting);
|
|
if (!value
|
|
|| !nm_utils_hexstr2bin_full(value,
|
|
FALSE,
|
|
FALSE,
|
|
FALSE,
|
|
NULL,
|
|
0,
|
|
buffer_ckn,
|
|
G_N_ELEMENTS(buffer_ckn),
|
|
&key_len)) {
|
|
g_set_error_literal(error,
|
|
NM_SUPPLICANT_ERROR,
|
|
NM_SUPPLICANT_ERROR_CONFIG,
|
|
value ? "invalid MKA CKN" : "missing MKA CKN");
|
|
return FALSE;
|
|
}
|
|
if (!nm_supplicant_config_add_option(self,
|
|
"mka_ckn",
|
|
(char *) buffer_ckn,
|
|
key_len,
|
|
value,
|
|
error))
|
|
return FALSE;
|
|
}
|
|
|
|
switch (offload) {
|
|
case NM_SETTING_MACSEC_OFFLOAD_OFF:
|
|
/* This is the default in wpa_supplicant. Don't set the option,
|
|
* so that if user doesn't enable offload, the connection still
|
|
* works with previous versions of the supplicant.
|
|
*/
|
|
break;
|
|
case NM_SETTING_MACSEC_OFFLOAD_PHY:
|
|
offload_str = "1";
|
|
break;
|
|
case NM_SETTING_MACSEC_OFFLOAD_MAC:
|
|
offload_str = "2";
|
|
break;
|
|
case NM_SETTING_MACSEC_OFFLOAD_DEFAULT:
|
|
nm_assert_not_reached();
|
|
break;
|
|
}
|
|
if (offload_str
|
|
&& !nm_supplicant_config_add_option(self, "macsec_offload", offload_str, -1, NULL, error)) {
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
get_ap_params(guint freq,
|
|
NMSettingWirelessChannelWidth width,
|
|
guint *out_ht40,
|
|
int *out_max_oper_chwidth,
|
|
guint *out_center_freq)
|
|
{
|
|
*out_ht40 = 0;
|
|
*out_max_oper_chwidth = -1;
|
|
*out_center_freq = 0;
|
|
|
|
switch (width) {
|
|
case NM_SETTING_WIRELESS_CHANNEL_WIDTH_40MHZ:
|
|
*out_ht40 = 1;
|
|
*out_max_oper_chwidth = 0;
|
|
return;
|
|
case NM_SETTING_WIRELESS_CHANNEL_WIDTH_80MHZ:
|
|
{
|
|
guint channel;
|
|
guint center_channel = 0;
|
|
|
|
/* Determine the center channel according to the table at
|
|
* https://en.wikipedia.org/wiki/List_of_WLAN_channels */
|
|
|
|
if (freq > 5950) {
|
|
/* 6 GHz */
|
|
channel = (freq - 5950) / 5;
|
|
channel = ((channel - 1) / 16) * 16 + 7;
|
|
|
|
*out_ht40 = 1;
|
|
*out_max_oper_chwidth = 1;
|
|
*out_center_freq = 5950 + 5 * channel;
|
|
} else {
|
|
/* 5 GHz */
|
|
if (freq < 5000) {
|
|
/* the setting is not valid */
|
|
nm_assert_not_reached();
|
|
return;
|
|
}
|
|
channel = (freq - 5000) / 5;
|
|
|
|
if (channel >= 36 && channel <= 48)
|
|
center_channel = 42;
|
|
else if (channel >= 52 && channel <= 64)
|
|
center_channel = 58;
|
|
else if (channel >= 100 && channel <= 112)
|
|
center_channel = 106;
|
|
else if (channel >= 116 && channel <= 128)
|
|
center_channel = 122;
|
|
else if (channel >= 132 && channel <= 144)
|
|
center_channel = 138;
|
|
else if (channel >= 149 && channel <= 161)
|
|
center_channel = 155;
|
|
else if (channel >= 165 && channel <= 177)
|
|
center_channel = 171;
|
|
|
|
if (center_channel) {
|
|
*out_ht40 = 1;
|
|
*out_max_oper_chwidth = 1;
|
|
*out_center_freq = 5000 + 5 * center_channel;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
case NM_SETTING_WIRELESS_CHANNEL_WIDTH_AUTO:
|
|
case NM_SETTING_WIRELESS_CHANNEL_WIDTH_20MHZ:
|
|
default:
|
|
/* in case of unknown enum value, fall back to the safest parameters */
|
|
return;
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
nm_supplicant_config_add_setting_wireless(NMSupplicantConfig *self,
|
|
NMSettingWireless *setting,
|
|
guint32 fixed_freq,
|
|
GError **error)
|
|
{
|
|
NMSupplicantConfigPrivate *priv;
|
|
gboolean is_adhoc, is_ap, is_mesh;
|
|
const char *mode, *band;
|
|
guint32 channel;
|
|
GBytes *ssid;
|
|
const char *bssid;
|
|
|
|
g_return_val_if_fail(NM_IS_SUPPLICANT_CONFIG(self), FALSE);
|
|
g_return_val_if_fail(setting != NULL, FALSE);
|
|
g_return_val_if_fail(!error || !*error, FALSE);
|
|
|
|
priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self);
|
|
|
|
mode = nm_setting_wireless_get_mode(setting);
|
|
is_adhoc = nm_streq0(mode, "adhoc");
|
|
is_ap = nm_streq0(mode, "ap");
|
|
is_mesh = nm_streq0(mode, "mesh");
|
|
if (is_adhoc || is_ap)
|
|
priv->ap_scan = 2;
|
|
else
|
|
priv->ap_scan = 1;
|
|
|
|
ssid = nm_setting_wireless_get_ssid(setting);
|
|
if (!nm_supplicant_config_add_option(self,
|
|
"ssid",
|
|
(char *) g_bytes_get_data(ssid, NULL),
|
|
g_bytes_get_size(ssid),
|
|
NULL,
|
|
error))
|
|
return FALSE;
|
|
|
|
if (is_adhoc) {
|
|
if (!nm_supplicant_config_add_option(self, "mode", "1", -1, NULL, error))
|
|
return FALSE;
|
|
}
|
|
|
|
if (is_ap) {
|
|
if (!nm_supplicant_config_add_option(self, "mode", "2", -1, NULL, error))
|
|
return FALSE;
|
|
|
|
if (nm_setting_wireless_get_hidden(setting)
|
|
&& !nm_supplicant_config_add_option(self,
|
|
"ignore_broadcast_ssid",
|
|
"1",
|
|
-1,
|
|
NULL,
|
|
error))
|
|
return FALSE;
|
|
}
|
|
|
|
if (is_mesh) {
|
|
if (!nm_supplicant_config_add_option(self, "mode", "5", -1, NULL, error))
|
|
return FALSE;
|
|
}
|
|
|
|
if ((is_adhoc || is_ap || is_mesh) && fixed_freq) {
|
|
gs_free char *str_freq = NULL;
|
|
guint ht40;
|
|
int max_oper_chwidth;
|
|
guint center_freq;
|
|
|
|
str_freq = g_strdup_printf("%u", fixed_freq);
|
|
if (!nm_supplicant_config_add_option(self, "frequency", str_freq, -1, NULL, error))
|
|
return FALSE;
|
|
|
|
if (is_ap) {
|
|
get_ap_params(fixed_freq,
|
|
nm_setting_wireless_get_channel_width(setting),
|
|
&ht40,
|
|
&max_oper_chwidth,
|
|
¢er_freq);
|
|
|
|
if (!nm_supplicant_config_add_option(self, "ht40", ht40 ? "1" : "0", -1, NULL, error))
|
|
return FALSE;
|
|
|
|
if (center_freq != 0) {
|
|
g_free(str_freq);
|
|
str_freq = g_strdup_printf("%u", center_freq);
|
|
if (!nm_supplicant_config_add_option(self,
|
|
"vht_center_freq1",
|
|
str_freq,
|
|
-1,
|
|
NULL,
|
|
error))
|
|
return FALSE;
|
|
}
|
|
|
|
if (max_oper_chwidth >= 0) {
|
|
g_free(str_freq);
|
|
str_freq = g_strdup_printf("%u", max_oper_chwidth);
|
|
if (!nm_supplicant_config_add_option(self,
|
|
"max_oper_chwidth",
|
|
str_freq,
|
|
-1,
|
|
NULL,
|
|
error))
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Except for Ad-Hoc, Hotspot and Mesh, request that the driver probe for the
|
|
* specific SSID we want to associate with.
|
|
*/
|
|
if (!(is_adhoc || is_ap || is_mesh)) {
|
|
if (!nm_supplicant_config_add_option(self, "scan_ssid", "1", -1, NULL, error))
|
|
return FALSE;
|
|
}
|
|
|
|
bssid = nm_setting_wireless_get_bssid(setting);
|
|
if (bssid) {
|
|
if (!nm_supplicant_config_add_option(self, "bssid", bssid, strlen(bssid), NULL, error))
|
|
return FALSE;
|
|
}
|
|
|
|
band = nm_setting_wireless_get_band(setting);
|
|
channel = nm_setting_wireless_get_channel(setting);
|
|
if (band) {
|
|
if (channel) {
|
|
guint32 freq;
|
|
gs_free char *str_freq = NULL;
|
|
|
|
freq = nm_utils_wifi_channel_to_freq(channel, band);
|
|
str_freq = g_strdup_printf("%u", freq);
|
|
if (!nm_supplicant_config_add_option(self, "freq_list", str_freq, -1, NULL, error))
|
|
return FALSE;
|
|
} else {
|
|
const char *freqs = NULL;
|
|
|
|
freqs = wifi_freqs_to_string(band);
|
|
|
|
if (freqs
|
|
&& !nm_supplicant_config_add_option(self,
|
|
"freq_list",
|
|
freqs,
|
|
strlen(freqs),
|
|
NULL,
|
|
error))
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* Maximum number of seen BSSIDs we treat as evidence of a single Wi-Fi 7
|
|
* MLO AP. 802.11be defines tri-link (2.4 + 5 + 6 GHz) as the maximum;
|
|
* beyond 3 the heuristic does not apply and the multi-AP path runs.
|
|
*/
|
|
#define _BGSCAN_MLO_HEURISTIC_MAX_BSSIDS 3u
|
|
|
|
/* _seen_bssids_look_like_mlo_per_link:
|
|
*
|
|
* Heuristic returning TRUE when @seen_bssids appears to be the per-link
|
|
* BSSIDs of one Wi-Fi 7 MLO-capable AP rather than several physical APs.
|
|
*
|
|
* In MLO, one physical AP advertises one BSSID per link (2 for 5+6 GHz,
|
|
* 3 for tri-link). Vendors observed in the wild derive the per-link
|
|
* BSSIDs as locally-administered virtual MAC addresses sharing octets
|
|
* 1-4 with each other. The MLD address itself may or may not be in the
|
|
* seen-bssids list.
|
|
*
|
|
* Real multi-AP ESSes (mesh, enterprise) typically use vendor-assigned
|
|
* MACs (LAA bit unset) and unrelated address blocks per AP, so the
|
|
* heuristic should not false-positive on them.
|
|
*/
|
|
static gboolean
|
|
_seen_bssids_look_like_mlo_per_link(const char *const *seen_bssids, guint num_seen_bssids)
|
|
{
|
|
guint8 common_octets_1_to_4[4];
|
|
guint i;
|
|
|
|
if (num_seen_bssids < 2u || num_seen_bssids > _BGSCAN_MLO_HEURISTIC_MAX_BSSIDS)
|
|
return FALSE;
|
|
if (!seen_bssids)
|
|
return FALSE;
|
|
|
|
for (i = 0; i < num_seen_bssids; i++) {
|
|
const char *bssid = seen_bssids[i];
|
|
guint8 addr[6];
|
|
|
|
if (!bssid || !nm_utils_hwaddr_aton(bssid, addr, sizeof(addr)))
|
|
return FALSE;
|
|
|
|
/* Locally-administered bit is bit 1 (value 0x02) of the first octet. */
|
|
if (!(addr[0] & 0x02u))
|
|
return FALSE;
|
|
|
|
/* Octets 1-4 must be identical across all seen BSSIDs (only octets
|
|
* 0 and 5 vary across per-link MAC addresses for the same MLD).
|
|
*/
|
|
if (i == 0)
|
|
memcpy(common_octets_1_to_4, &addr[1], 4);
|
|
else if (memcmp(common_octets_1_to_4, &addr[1], 4) != 0)
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
nm_supplicant_config_add_bgscan(NMSupplicantConfig *self,
|
|
NMConnection *connection,
|
|
guint num_seen_bssids,
|
|
const char *const *seen_bssids,
|
|
GError **error)
|
|
{
|
|
NMSettingWireless *s_wifi;
|
|
NMSettingWirelessSecurity *s_wsec;
|
|
const char *bgscan;
|
|
|
|
s_wifi = nm_connection_get_setting_wireless(connection);
|
|
g_assert(s_wifi);
|
|
|
|
/* Don't scan when a shared connection (either AP or Ad-Hoc) is active;
|
|
* it will disrupt connected clients.
|
|
*/
|
|
if (NM_IN_STRSET(nm_setting_wireless_get_mode(s_wifi),
|
|
NM_SETTING_WIRELESS_MODE_AP,
|
|
NM_SETTING_WIRELESS_MODE_ADHOC))
|
|
return TRUE;
|
|
|
|
/* Don't scan when the connection is locked to a specific AP, since
|
|
* intra-ESS roaming (which requires periodic scanning) isn't being
|
|
* used due to the specific AP lock. (bgo #513820)
|
|
*/
|
|
if (nm_setting_wireless_get_bssid(s_wifi))
|
|
return TRUE;
|
|
|
|
/* Default to a very long bgscan interval when signal is OK on the assumption
|
|
* that either (a) there aren't multiple APs and we don't need roaming, or
|
|
* (b) since EAP/802.1x isn't used and thus there are fewer steps to fail
|
|
* during a roam, we can wait longer before scanning for roam candidates.
|
|
*/
|
|
bgscan = "simple:30:-70:86400";
|
|
|
|
/* If using WPA Enterprise, Dynamic WEP or we have seen more than one AP use
|
|
* a shorter bgscan interval on the assumption that this is a multi-AP ESS
|
|
* in which we want more reliable roaming between APs. Thus trigger scans
|
|
* when the signal is still somewhat OK so we have an up-to-date roam
|
|
* candidate list when the signal gets bad.
|
|
*
|
|
* Exception: when @seen_bssids look like the per-link addresses of a
|
|
* single WiFi-7 MLO AP (locally-administered bit set + shared octets
|
|
* 1-4), keep the long interval. See _seen_bssids_look_like_mlo_per_link().
|
|
*/
|
|
if ((num_seen_bssids > 1u && !_seen_bssids_look_like_mlo_per_link(seen_bssids, num_seen_bssids))
|
|
|| ((s_wsec = nm_connection_get_setting_wireless_security(connection))
|
|
&& NM_IN_STRSET(nm_setting_wireless_security_get_key_mgmt(s_wsec),
|
|
"ieee8021x",
|
|
"wpa-eap",
|
|
"wpa-eap-suite-b-192")))
|
|
bgscan = "simple:30:-65:300";
|
|
|
|
return nm_supplicant_config_add_option(self, "bgscan", bgscan, -1, FALSE, error);
|
|
}
|
|
|
|
static gboolean
|
|
add_string_val(NMSupplicantConfig *self,
|
|
const char *field,
|
|
const char *name,
|
|
gboolean ucase,
|
|
const char *display_value,
|
|
GError **error)
|
|
{
|
|
if (field) {
|
|
gs_free char *value = NULL;
|
|
|
|
if (ucase) {
|
|
value = g_ascii_strup(field, -1);
|
|
field = value;
|
|
}
|
|
return nm_supplicant_config_add_option(self,
|
|
name,
|
|
field,
|
|
strlen(field),
|
|
display_value,
|
|
error);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
#define ADD_STRING_LIST_VAL(self, \
|
|
setting, \
|
|
setting_name, \
|
|
field, \
|
|
field_plural, \
|
|
name, \
|
|
separator, \
|
|
ucase, \
|
|
display_value, \
|
|
error) \
|
|
({ \
|
|
typeof(setting) _setting = (setting); \
|
|
gboolean _success = TRUE; \
|
|
\
|
|
if (nm_setting_##setting_name##_get_num_##field_plural(_setting)) { \
|
|
const char _separator = (separator); \
|
|
GString *_str = g_string_new(NULL); \
|
|
guint _k, _n; \
|
|
\
|
|
_n = nm_setting_##setting_name##_get_num_##field_plural(_setting); \
|
|
for (_k = 0; _k < _n; _k++) { \
|
|
const char *item = nm_setting_##setting_name##_get_##field(_setting, _k); \
|
|
\
|
|
if (!_str->len) { \
|
|
g_string_append(_str, item); \
|
|
} else { \
|
|
g_string_append_c(_str, _separator); \
|
|
g_string_append(_str, item); \
|
|
} \
|
|
} \
|
|
if ((ucase)) \
|
|
g_string_ascii_up(_str); \
|
|
if (_str->len) { \
|
|
if (!nm_supplicant_config_add_option((self), \
|
|
(name), \
|
|
_str->str, \
|
|
-1, \
|
|
(display_value), \
|
|
(error))) \
|
|
_success = FALSE; \
|
|
} \
|
|
g_string_free(_str, TRUE); \
|
|
} \
|
|
_success; \
|
|
})
|
|
|
|
static void
|
|
wep128_passphrase_hash(const char *input, gsize input_len, guint8 *digest /* 13 bytes */)
|
|
{
|
|
nm_auto_free_checksum GChecksum *sum = NULL;
|
|
guint8 md5[NM_UTILS_CHECKSUM_LENGTH_MD5];
|
|
guint8 data[64];
|
|
int i;
|
|
|
|
nm_assert(input);
|
|
nm_assert(input_len);
|
|
nm_assert(digest);
|
|
|
|
/* Get at least 64 bytes by repeating the passphrase into the buffer */
|
|
for (i = 0; i < sizeof(data); i++)
|
|
data[i] = input[i % input_len];
|
|
|
|
sum = g_checksum_new(G_CHECKSUM_MD5);
|
|
g_checksum_update(sum, data, sizeof(data));
|
|
nm_utils_checksum_get_digest(sum, md5);
|
|
|
|
/* WEP104 keys are 13 bytes in length (26 hex characters) */
|
|
memcpy(digest, md5, 13);
|
|
}
|
|
|
|
static gboolean
|
|
add_wep_key(NMSupplicantConfig *self,
|
|
const char *key,
|
|
const char *name,
|
|
NMWepKeyType wep_type,
|
|
GError **error)
|
|
{
|
|
gsize key_len;
|
|
|
|
if (!key || (key_len = strlen(key)) == 0)
|
|
return TRUE;
|
|
|
|
if (wep_type == NM_WEP_KEY_TYPE_UNKNOWN) {
|
|
if (nm_utils_wep_key_valid(key, NM_WEP_KEY_TYPE_KEY))
|
|
wep_type = NM_WEP_KEY_TYPE_KEY;
|
|
else if (nm_utils_wep_key_valid(key, NM_WEP_KEY_TYPE_PASSPHRASE))
|
|
wep_type = NM_WEP_KEY_TYPE_PASSPHRASE;
|
|
}
|
|
|
|
if ((wep_type == NM_WEP_KEY_TYPE_UNKNOWN) || (wep_type == NM_WEP_KEY_TYPE_KEY)) {
|
|
if ((key_len == 10) || (key_len == 26)) {
|
|
guint8 buffer[26 / 2];
|
|
|
|
if (!nm_utils_hexstr2bin_full(key,
|
|
FALSE,
|
|
FALSE,
|
|
FALSE,
|
|
NULL,
|
|
key_len / 2,
|
|
buffer,
|
|
sizeof(buffer),
|
|
NULL)) {
|
|
g_set_error(error,
|
|
NM_SUPPLICANT_ERROR,
|
|
NM_SUPPLICANT_ERROR_CONFIG,
|
|
"cannot add wep-key %s to supplicant config because key is not hex",
|
|
name);
|
|
return FALSE;
|
|
}
|
|
if (!nm_supplicant_config_add_option(self,
|
|
name,
|
|
(char *) buffer,
|
|
key_len / 2,
|
|
"<hidden>",
|
|
error))
|
|
return FALSE;
|
|
} else if ((key_len == 5) || (key_len == 13)) {
|
|
if (!nm_supplicant_config_add_option(self, name, key, key_len, "<hidden>", error))
|
|
return FALSE;
|
|
} else {
|
|
g_set_error(
|
|
error,
|
|
NM_SUPPLICANT_ERROR,
|
|
NM_SUPPLICANT_ERROR_CONFIG,
|
|
"Cannot add wep-key %s to supplicant config because key-length %u is invalid",
|
|
name,
|
|
(guint) key_len);
|
|
return FALSE;
|
|
}
|
|
} else if (wep_type == NM_WEP_KEY_TYPE_PASSPHRASE) {
|
|
guint8 digest[13];
|
|
|
|
wep128_passphrase_hash(key, key_len, digest);
|
|
if (!nm_supplicant_config_add_option(self,
|
|
name,
|
|
(const char *) digest,
|
|
sizeof(digest),
|
|
"<hidden>",
|
|
error))
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
nm_supplicant_config_add_setting_wireless_security(NMSupplicantConfig *self,
|
|
NMSettingWirelessSecurity *setting,
|
|
NMSetting8021x *setting_8021x,
|
|
const char *con_uuid,
|
|
const char *mode,
|
|
guint32 mtu,
|
|
NMSettingWirelessSecurityPmf pmf,
|
|
NMSettingWirelessSecurityFils fils,
|
|
GHashTable *files,
|
|
GError **error)
|
|
{
|
|
NMSupplicantConfigPrivate *priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self);
|
|
nm_auto_free_gstring GString *key_mgmt_conf = NULL;
|
|
const char *key_mgmt, *auth_alg;
|
|
const char *psk;
|
|
gboolean set_pmf, wps_disabled;
|
|
gboolean is_ap;
|
|
|
|
g_return_val_if_fail(NM_IS_SUPPLICANT_CONFIG(self), FALSE);
|
|
g_return_val_if_fail(setting != NULL, FALSE);
|
|
g_return_val_if_fail(con_uuid != NULL, FALSE);
|
|
g_return_val_if_fail(!error || !*error, FALSE);
|
|
|
|
/* Currently wpa_supplicant doesn't support FT in AP mode. Even
|
|
* if it did, it would require additional parameters as the nas
|
|
* identifier and the mobility domain. Therefore we disable all
|
|
* FT key-mgmts in AP mode.
|
|
*/
|
|
is_ap = nm_streq0(mode, NM_SETTING_WIRELESS_MODE_AP);
|
|
|
|
/* Check if we actually support FILS */
|
|
if (!_get_capability(priv, NM_SUPPL_CAP_TYPE_FILS)) {
|
|
if (fils == NM_SETTING_WIRELESS_SECURITY_FILS_REQUIRED) {
|
|
g_set_error_literal(error,
|
|
NM_SUPPLICANT_ERROR,
|
|
NM_SUPPLICANT_ERROR_CONFIG,
|
|
"Supplicant does not support FILS");
|
|
return FALSE;
|
|
} else if (fils == NM_SETTING_WIRELESS_SECURITY_FILS_OPTIONAL)
|
|
fils = NM_SETTING_WIRELESS_SECURITY_FILS_DISABLE;
|
|
}
|
|
|
|
key_mgmt = nm_setting_wireless_security_get_key_mgmt(setting);
|
|
key_mgmt_conf = g_string_new("");
|
|
|
|
if (nm_streq(key_mgmt, "none")) {
|
|
g_string_append(key_mgmt_conf, "NONE");
|
|
|
|
} else if (nm_streq(key_mgmt, "ieee8021x")) {
|
|
g_string_append(key_mgmt_conf, "IEEE8021X");
|
|
|
|
} else if (nm_streq(key_mgmt, "owe")) {
|
|
pmf = NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED;
|
|
|
|
g_string_append(key_mgmt_conf, "OWE");
|
|
|
|
} else if (nm_streq(key_mgmt, "wpa-psk")) {
|
|
if (pmf != NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED)
|
|
g_string_append(key_mgmt_conf, "WPA-PSK");
|
|
if (_get_capability(priv, NM_SUPPL_CAP_TYPE_PMF))
|
|
g_string_append(key_mgmt_conf, " WPA-PSK-SHA256");
|
|
if (!is_ap && _get_capability(priv, NM_SUPPL_CAP_TYPE_FT))
|
|
g_string_append(key_mgmt_conf, " FT-PSK");
|
|
|
|
/* For NM "key-mgmt=wpa-psk" doesn't strictly mean WPA1/wPA2 only,
|
|
* but also allows WPA3 (SAE), so that existing connections can
|
|
* benefit from the improved security when the AP gets upgraded.
|
|
*
|
|
* According to WPA3_Specification_v3.0 section 2.3, when operating
|
|
* in WPA3-Personal transition mode a STA:
|
|
*
|
|
* - should allow AKM suite selector: 00-0F-AC:6 (WPA-PSK-SHA256) to
|
|
* be selected for an association;
|
|
* - shall negotiate PMF when associating to an AP using SAE.
|
|
*
|
|
* Those conditions are met when the interface has capabilities
|
|
* SAE, PMF, BIP.
|
|
*
|
|
* According to WPA3_Specification_v3.0 section 2.3, when operating
|
|
* in WPA3-Personal transition mode an AP:
|
|
*
|
|
* - shall set MFPC to 1, MFPR to 0.
|
|
*
|
|
* Therefore, do not operate in WPA3-Personal transition mode when PMF
|
|
* is set to disabled. This also provides a way to be compatible with
|
|
* some devices that are not fully compatible with WPA3-Personal
|
|
* transition mode.
|
|
*/
|
|
if (_get_capability(priv, NM_SUPPL_CAP_TYPE_SAE)
|
|
&& _get_capability(priv, NM_SUPPL_CAP_TYPE_PMF)
|
|
&& _get_capability(priv, NM_SUPPL_CAP_TYPE_BIP)
|
|
&& (pmf != NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE)) {
|
|
g_string_append(key_mgmt_conf, " SAE");
|
|
if (!is_ap && _get_capability(priv, NM_SUPPL_CAP_TYPE_FT))
|
|
g_string_append(key_mgmt_conf, " FT-SAE");
|
|
}
|
|
|
|
} else if (nm_streq(key_mgmt, "sae")) {
|
|
pmf = NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED;
|
|
|
|
g_string_append(key_mgmt_conf, "SAE");
|
|
if (!is_ap && _get_capability(priv, NM_SUPPL_CAP_TYPE_FT))
|
|
g_string_append(key_mgmt_conf, " FT-SAE");
|
|
|
|
} else if (nm_streq(key_mgmt, "wpa-eap")) {
|
|
if (pmf != NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED)
|
|
g_string_append(key_mgmt_conf, "WPA-EAP");
|
|
if (!is_ap && _get_capability(priv, NM_SUPPL_CAP_TYPE_FT)) {
|
|
g_string_append(key_mgmt_conf, " FT-EAP");
|
|
if (_get_capability(priv, NM_SUPPL_CAP_TYPE_SHA384))
|
|
g_string_append(key_mgmt_conf, " FT-EAP-SHA384");
|
|
}
|
|
if (_get_capability(priv, NM_SUPPL_CAP_TYPE_PMF)) {
|
|
g_string_append(key_mgmt_conf, " WPA-EAP-SHA256");
|
|
if (_get_capability(priv, NM_SUPPL_CAP_TYPE_SUITEB192)
|
|
&& pmf == NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED)
|
|
g_string_append(key_mgmt_conf, " WPA-EAP-SUITE-B-192");
|
|
}
|
|
|
|
switch (fils) {
|
|
case NM_SETTING_WIRELESS_SECURITY_FILS_REQUIRED:
|
|
g_string_truncate(key_mgmt_conf, 0);
|
|
if (!_get_capability(priv, NM_SUPPL_CAP_TYPE_PMF))
|
|
g_string_assign(key_mgmt_conf, "FILS-SHA256 FILS-SHA384");
|
|
/* fall-through */
|
|
case NM_SETTING_WIRELESS_SECURITY_FILS_OPTIONAL:
|
|
if (_get_capability(priv, NM_SUPPL_CAP_TYPE_PMF)) {
|
|
g_string_append(key_mgmt_conf, " FILS-SHA256 FILS-SHA384");
|
|
if (!is_ap && _get_capability(priv, NM_SUPPL_CAP_TYPE_FT)) {
|
|
g_string_append(key_mgmt_conf, " FT-FILS-SHA256");
|
|
if (_get_capability(priv, NM_SUPPL_CAP_TYPE_SHA384))
|
|
g_string_append(key_mgmt_conf, " FT-FILS-SHA384");
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
} else if (nm_streq(key_mgmt, "wpa-eap-suite-b-192")) {
|
|
pmf = NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED;
|
|
|
|
g_string_append(key_mgmt_conf, "WPA-EAP-SUITE-B-192");
|
|
if (!is_ap && _get_capability(priv, NM_SUPPL_CAP_TYPE_FT)
|
|
&& _get_capability(priv, NM_SUPPL_CAP_TYPE_SHA384))
|
|
g_string_append(key_mgmt_conf, " FT-EAP-SHA384");
|
|
}
|
|
|
|
if (!add_string_val(self, key_mgmt_conf->str, "key_mgmt", TRUE, NULL, error))
|
|
return FALSE;
|
|
|
|
auth_alg = nm_setting_wireless_security_get_auth_alg(setting);
|
|
if (!add_string_val(self, auth_alg, "auth_alg", TRUE, NULL, error))
|
|
return FALSE;
|
|
|
|
psk = nm_setting_wireless_security_get_psk(setting);
|
|
if (psk) {
|
|
size_t psk_len = strlen(psk);
|
|
|
|
if (psk_len >= 8 && psk_len <= 63) {
|
|
/* Use NM_SUPPL_OPT_TYPE_STRING here so that it gets pushed to the
|
|
* supplicant as a string, and therefore gets quoted,
|
|
* and therefore the supplicant will interpret it as a
|
|
* passphrase and not a hex key.
|
|
*/
|
|
if (!nm_supplicant_config_add_option_with_type(self,
|
|
"psk",
|
|
psk,
|
|
-1,
|
|
NM_SUPPL_OPT_TYPE_STRING,
|
|
"<hidden>",
|
|
error))
|
|
return FALSE;
|
|
} else if (nm_streq(key_mgmt, "sae")) {
|
|
/* If the SAE password doesn't comply with WPA-PSK limitation,
|
|
* we need to call it "sae_password" instead of "psk".
|
|
*/
|
|
if (!nm_supplicant_config_add_option_with_type(self,
|
|
"sae_password",
|
|
psk,
|
|
-1,
|
|
NM_SUPPL_OPT_TYPE_STRING,
|
|
"<hidden>",
|
|
error))
|
|
return FALSE;
|
|
} else if (psk_len == 64) {
|
|
guint8 buffer[32];
|
|
|
|
/* Hex PSK */
|
|
if (!nm_utils_hexstr2bin_buf(psk, FALSE, FALSE, NULL, buffer)) {
|
|
g_set_error(error,
|
|
NM_SUPPLICANT_ERROR,
|
|
NM_SUPPLICANT_ERROR_CONFIG,
|
|
"Cannot add psk to supplicant config due to invalid hex");
|
|
return FALSE;
|
|
}
|
|
if (!nm_supplicant_config_add_option(self,
|
|
"psk",
|
|
(char *) buffer,
|
|
sizeof(buffer),
|
|
"<hidden>",
|
|
error))
|
|
return FALSE;
|
|
} else {
|
|
g_set_error(error,
|
|
NM_SUPPLICANT_ERROR,
|
|
NM_SUPPLICANT_ERROR_CONFIG,
|
|
"Cannot add psk to supplicant config due to invalid PSK length %u (not "
|
|
"between 8 and 63 characters)",
|
|
(guint) psk_len);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* Check if we actually support PMF */
|
|
set_pmf = TRUE;
|
|
if (!_get_capability(priv, NM_SUPPL_CAP_TYPE_PMF)) {
|
|
if (pmf == NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED) {
|
|
g_set_error_literal(error,
|
|
NM_SUPPLICANT_ERROR,
|
|
NM_SUPPLICANT_ERROR_CONFIG,
|
|
"Supplicant does not support PMF");
|
|
return FALSE;
|
|
}
|
|
set_pmf = FALSE;
|
|
}
|
|
|
|
/* Only WPA-specific things when using WPA */
|
|
if (NM_IN_STRSET(key_mgmt, "owe", "wpa-psk", "sae", "wpa-eap", "wpa-eap-suite-b-192")) {
|
|
if (!ADD_STRING_LIST_VAL(self,
|
|
setting,
|
|
wireless_security,
|
|
proto,
|
|
protos,
|
|
"proto",
|
|
' ',
|
|
TRUE,
|
|
NULL,
|
|
error))
|
|
return FALSE;
|
|
|
|
if (nm_streq(key_mgmt, "wpa-eap-suite-b-192")) {
|
|
if (!nm_supplicant_config_add_option(self, "pairwise", "GCMP-256", -1, NULL, error))
|
|
return FALSE;
|
|
if (!nm_supplicant_config_add_option(self, "group", "GCMP-256", -1, NULL, error))
|
|
return FALSE;
|
|
} else {
|
|
if (!ADD_STRING_LIST_VAL(self,
|
|
setting,
|
|
wireless_security,
|
|
pairwise,
|
|
pairwise,
|
|
"pairwise",
|
|
' ',
|
|
TRUE,
|
|
NULL,
|
|
error))
|
|
return FALSE;
|
|
if (!ADD_STRING_LIST_VAL(self,
|
|
setting,
|
|
wireless_security,
|
|
group,
|
|
groups,
|
|
"group",
|
|
' ',
|
|
TRUE,
|
|
NULL,
|
|
error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* We set the supplicants global "pmf" config value to "1" (optional),
|
|
* so no need to set it network-specific again if PMF_OPTIONAL is set.
|
|
*/
|
|
if (set_pmf
|
|
&& NM_IN_SET(pmf,
|
|
NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE,
|
|
NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED)) {
|
|
if (!nm_supplicant_config_add_option(
|
|
self,
|
|
"ieee80211w",
|
|
pmf == NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE ? "0" : "2",
|
|
-1,
|
|
NULL,
|
|
error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* In case the connection is saved as OWE / Enhanced Open, prevent
|
|
* unencrypted downgrade
|
|
*/
|
|
if (nm_streq(key_mgmt, "owe")) {
|
|
if (!nm_supplicant_config_add_option(self, "owe_only", "1", -1, NULL, error))
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* WEP keys if required */
|
|
if (nm_streq(key_mgmt, "none")) {
|
|
NMWepKeyType wep_type = nm_setting_wireless_security_get_wep_key_type(setting);
|
|
const char *wep0 = nm_setting_wireless_security_get_wep_key(setting, 0);
|
|
const char *wep1 = nm_setting_wireless_security_get_wep_key(setting, 1);
|
|
const char *wep2 = nm_setting_wireless_security_get_wep_key(setting, 2);
|
|
const char *wep3 = nm_setting_wireless_security_get_wep_key(setting, 3);
|
|
|
|
if (!add_wep_key(self, wep0, "wep_key0", wep_type, error))
|
|
return FALSE;
|
|
if (!add_wep_key(self, wep1, "wep_key1", wep_type, error))
|
|
return FALSE;
|
|
if (!add_wep_key(self, wep2, "wep_key2", wep_type, error))
|
|
return FALSE;
|
|
if (!add_wep_key(self, wep3, "wep_key3", wep_type, error))
|
|
return FALSE;
|
|
|
|
if (wep0 || wep1 || wep2 || wep3) {
|
|
gs_free char *value = NULL;
|
|
|
|
value = g_strdup_printf("%d", nm_setting_wireless_security_get_wep_tx_keyidx(setting));
|
|
if (!nm_supplicant_config_add_option(self, "wep_tx_keyidx", value, -1, NULL, error))
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (nm_streq0(auth_alg, "leap")) {
|
|
/* LEAP */
|
|
if (nm_streq(key_mgmt, "ieee8021x")) {
|
|
const char *tmp;
|
|
|
|
tmp = nm_setting_wireless_security_get_leap_username(setting);
|
|
if (!add_string_val(self, tmp, "identity", FALSE, NULL, error))
|
|
return FALSE;
|
|
|
|
tmp = nm_setting_wireless_security_get_leap_password(setting);
|
|
if (!add_string_val(self, tmp, "password", FALSE, "<hidden>", error))
|
|
return FALSE;
|
|
|
|
if (!add_string_val(self, "leap", "eap", TRUE, NULL, error))
|
|
return FALSE;
|
|
} else {
|
|
g_set_error(error,
|
|
NM_SUPPLICANT_ERROR,
|
|
NM_SUPPLICANT_ERROR_CONFIG,
|
|
"Invalid key-mgmt \"%s\" for leap",
|
|
key_mgmt);
|
|
return FALSE;
|
|
}
|
|
} else {
|
|
/* 802.1x for Dynamic WEP and WPA-Enterprise */
|
|
if (NM_IN_STRSET(key_mgmt, "ieee8021x", "wpa-eap", "wpa-eap-suite-b-192")) {
|
|
if (!setting_8021x) {
|
|
g_set_error(error,
|
|
NM_SUPPLICANT_ERROR,
|
|
NM_SUPPLICANT_ERROR_CONFIG,
|
|
"Cannot set key-mgmt %s with missing 8021x setting",
|
|
key_mgmt);
|
|
return FALSE;
|
|
}
|
|
if (!nm_supplicant_config_add_setting_8021x(self,
|
|
setting_8021x,
|
|
con_uuid,
|
|
mtu,
|
|
FALSE,
|
|
files,
|
|
error))
|
|
return FALSE;
|
|
}
|
|
|
|
if (NM_IN_STRSET(key_mgmt, "wpa-eap", "wpa-eap-suite-b-192")) {
|
|
/* When using WPA-Enterprise, we want to use Proactive Key Caching (also
|
|
* called Opportunistic Key Caching) to avoid full EAP exchanges when
|
|
* roaming between access points in the same mobility group.
|
|
*/
|
|
if (!nm_supplicant_config_add_option(self,
|
|
"proactive_key_caching",
|
|
"1",
|
|
-1,
|
|
NULL,
|
|
error))
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
wps_disabled = (nm_setting_wireless_security_get_wps_method(setting)
|
|
== NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DISABLED);
|
|
if (wps_disabled) {
|
|
if (!nm_supplicant_config_add_option(self, "wps_disabled", "1", 1, NULL, error))
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
add_pkcs11_uri_with_pin(NMSupplicantConfig *self,
|
|
const char *name,
|
|
const char *uri,
|
|
const char *pin,
|
|
const NMSettingSecretFlags pin_flags,
|
|
GError **error)
|
|
{
|
|
gs_strfreev char **split = NULL;
|
|
gs_free char *tmp = NULL;
|
|
gs_free char *tmp_log = NULL;
|
|
gs_free char *pin_qattr = NULL;
|
|
char *escaped = NULL;
|
|
|
|
if (uri == NULL)
|
|
return TRUE;
|
|
|
|
/* We ignore the attributes -- RFC 7512 suggests that some of them
|
|
* might be unsafe and we want to be on the safe side. Also, we're
|
|
* installing our attributes, so this makes things a bit easier for us. */
|
|
split = g_strsplit(uri, "&", 2);
|
|
if (split[1])
|
|
nm_log_info(LOGD_SUPPLICANT, "URI attributes ignored");
|
|
|
|
/* Fill in the PIN if required. */
|
|
if (pin) {
|
|
escaped = g_uri_escape_string(pin, NULL, TRUE);
|
|
pin_qattr = g_strdup_printf("pin-value=%s", escaped);
|
|
g_free(escaped);
|
|
} else if (!(pin_flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED)) {
|
|
/* Include an empty PIN to indicate the login is still needed.
|
|
* Probably a token that has a PIN path and the actual PIN will
|
|
* be entered using a protected path. */
|
|
pin_qattr = g_strdup("pin-value=");
|
|
}
|
|
|
|
tmp = g_strdup_printf("%s%s%s", split[0], (pin_qattr ? "?" : ""), (pin_qattr ?: ""));
|
|
|
|
tmp_log = g_strdup_printf("%s%s%s",
|
|
split[0],
|
|
(pin_qattr ? "?" : ""),
|
|
(pin_qattr ? "pin-value=<hidden>" : ""));
|
|
|
|
return add_string_val(self, tmp, name, FALSE, tmp_log, error);
|
|
}
|
|
|
|
gboolean
|
|
nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
|
|
NMSetting8021x *setting,
|
|
const char *con_uuid,
|
|
guint32 mtu,
|
|
gboolean wired,
|
|
GHashTable *files,
|
|
GError **error)
|
|
{
|
|
NMSupplicantConfigPrivate *priv;
|
|
char *tmp;
|
|
const char *peapver, *value, *path;
|
|
gboolean added;
|
|
GString *phase1, *phase2;
|
|
GBytes *bytes;
|
|
gboolean fast = FALSE;
|
|
guint32 i, num_eap;
|
|
gboolean fast_provisoning_allowed = FALSE;
|
|
const char *ca_path_override = NULL, *ca_cert_override = NULL;
|
|
guint32 frag, hdrs;
|
|
gs_free char *frag_str = NULL;
|
|
NMSetting8021xAuthFlags phase1_auth_flags;
|
|
nm_auto_free_gstring GString *eap_str = NULL;
|
|
|
|
g_return_val_if_fail(NM_IS_SUPPLICANT_CONFIG(self), FALSE);
|
|
g_return_val_if_fail(setting != NULL, FALSE);
|
|
g_return_val_if_fail(con_uuid != NULL, FALSE);
|
|
|
|
priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self);
|
|
|
|
value = nm_setting_802_1x_get_password(setting);
|
|
if (value) {
|
|
if (!add_string_val(self, value, "password", FALSE, "<hidden>", error))
|
|
return FALSE;
|
|
} else {
|
|
bytes = nm_setting_802_1x_get_password_raw(setting);
|
|
if (bytes) {
|
|
if (!nm_supplicant_config_add_option(self,
|
|
"password",
|
|
(const char *) g_bytes_get_data(bytes, NULL),
|
|
g_bytes_get_size(bytes),
|
|
"<hidden>",
|
|
error))
|
|
return FALSE;
|
|
}
|
|
}
|
|
value = nm_setting_802_1x_get_pin(setting);
|
|
if (!add_string_val(self, value, "pin", FALSE, "<hidden>", error))
|
|
return FALSE;
|
|
|
|
if (wired) {
|
|
if (!add_string_val(self, "IEEE8021X", "key_mgmt", FALSE, NULL, error))
|
|
return FALSE;
|
|
/* Wired 802.1x must always use eapol_flags=0 */
|
|
if (!add_string_val(self, "0", "eapol_flags", FALSE, NULL, error))
|
|
return FALSE;
|
|
priv->ap_scan = 0;
|
|
}
|
|
|
|
/* Build the "eap" option string while we check for EAP methods needing
|
|
* special handling: PEAP + GTC, FAST, external */
|
|
eap_str = g_string_new(NULL);
|
|
num_eap = nm_setting_802_1x_get_num_eap_methods(setting);
|
|
for (i = 0; i < num_eap; i++) {
|
|
const char *method = nm_setting_802_1x_get_eap_method(setting, i);
|
|
|
|
if (nm_streq(method, "fast")) {
|
|
fast = TRUE;
|
|
priv->fast_required = TRUE;
|
|
}
|
|
|
|
if (nm_streq(method, "external")) {
|
|
if (num_eap == 1) {
|
|
g_set_error(error,
|
|
NM_SUPPLICANT_ERROR,
|
|
NM_SUPPLICANT_ERROR_CONFIG,
|
|
"Connection settings managed externally to NM, connection"
|
|
" cannot be used with wpa_supplicant");
|
|
return FALSE;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (eap_str->len)
|
|
g_string_append_c(eap_str, ' ');
|
|
g_string_append(eap_str, method);
|
|
}
|
|
|
|
g_string_ascii_up(eap_str);
|
|
if (eap_str->len
|
|
&& !nm_supplicant_config_add_option(self, "eap", eap_str->str, -1, NULL, error))
|
|
return FALSE;
|
|
|
|
/* Adjust the fragment size according to MTU, but do not set it higher than 1280-14
|
|
* for better compatibility */
|
|
hdrs = 14; /* EAPOL + EAP-TLS */
|
|
frag = 1280 - hdrs;
|
|
if (mtu > hdrs)
|
|
frag = CLAMP(mtu - hdrs, 100, frag);
|
|
frag_str = g_strdup_printf("%u", frag);
|
|
|
|
if (!nm_supplicant_config_add_option(self, "fragment_size", frag_str, -1, NULL, error))
|
|
return FALSE;
|
|
|
|
phase1 = g_string_new(NULL);
|
|
peapver = nm_setting_802_1x_get_phase1_peapver(setting);
|
|
if (peapver) {
|
|
if (nm_streq(peapver, "0"))
|
|
g_string_append(phase1, "peapver=0");
|
|
else if (nm_streq(peapver, "1"))
|
|
g_string_append(phase1, "peapver=1");
|
|
}
|
|
|
|
if (nm_setting_802_1x_get_phase1_peaplabel(setting)) {
|
|
if (phase1->len)
|
|
g_string_append_c(phase1, ' ');
|
|
g_string_append_printf(phase1,
|
|
"peaplabel=%s",
|
|
nm_setting_802_1x_get_phase1_peaplabel(setting));
|
|
}
|
|
|
|
value = nm_setting_802_1x_get_phase1_fast_provisioning(setting);
|
|
if (value) {
|
|
if (phase1->len)
|
|
g_string_append_c(phase1, ' ');
|
|
g_string_append_printf(phase1, "fast_provisioning=%s", value);
|
|
|
|
if (!nm_streq(value, "0"))
|
|
fast_provisoning_allowed = TRUE;
|
|
}
|
|
|
|
phase1_auth_flags = nm_setting_802_1x_get_phase1_auth_flags(setting);
|
|
if (NM_FLAGS_HAS(phase1_auth_flags, NM_SETTING_802_1X_AUTH_FLAGS_TLS_1_0_ENABLE))
|
|
g_string_append_printf(phase1, "%stls_disable_tlsv1_0=0", (phase1->len ? " " : ""));
|
|
else if (NM_FLAGS_HAS(phase1_auth_flags, NM_SETTING_802_1X_AUTH_FLAGS_TLS_1_0_DISABLE))
|
|
g_string_append_printf(phase1, "%stls_disable_tlsv1_0=1", (phase1->len ? " " : ""));
|
|
if (NM_FLAGS_HAS(phase1_auth_flags, NM_SETTING_802_1X_AUTH_FLAGS_TLS_1_1_ENABLE))
|
|
g_string_append_printf(phase1, "%stls_disable_tlsv1_1=0", (phase1->len ? " " : ""));
|
|
else if (NM_FLAGS_HAS(phase1_auth_flags, NM_SETTING_802_1X_AUTH_FLAGS_TLS_1_1_DISABLE))
|
|
g_string_append_printf(phase1, "%stls_disable_tlsv1_1=1", (phase1->len ? " " : ""));
|
|
if (NM_FLAGS_HAS(phase1_auth_flags, NM_SETTING_802_1X_AUTH_FLAGS_TLS_1_2_ENABLE))
|
|
g_string_append_printf(phase1, "%stls_disable_tlsv1_2=0", (phase1->len ? " " : ""));
|
|
else if (NM_FLAGS_HAS(phase1_auth_flags, NM_SETTING_802_1X_AUTH_FLAGS_TLS_1_2_DISABLE))
|
|
g_string_append_printf(phase1, "%stls_disable_tlsv1_2=1", (phase1->len ? " " : ""));
|
|
if (NM_FLAGS_HAS(phase1_auth_flags, NM_SETTING_802_1X_AUTH_FLAGS_TLS_1_3_ENABLE))
|
|
g_string_append_printf(phase1, "%stls_disable_tlsv1_3=0", (phase1->len ? " " : ""));
|
|
else if (NM_FLAGS_HAS(phase1_auth_flags, NM_SETTING_802_1X_AUTH_FLAGS_TLS_1_3_DISABLE))
|
|
g_string_append_printf(phase1, "%stls_disable_tlsv1_3=1", (phase1->len ? " " : ""));
|
|
if (NM_FLAGS_HAS(phase1_auth_flags, NM_SETTING_802_1X_AUTH_FLAGS_TLS_DISABLE_TIME_CHECKS))
|
|
g_string_append_printf(phase1, "%stls_disable_time_checks=1", (phase1->len ? " " : ""));
|
|
|
|
if (phase1->len) {
|
|
if (!add_string_val(self, phase1->str, "phase1", FALSE, NULL, error)) {
|
|
g_string_free(phase1, TRUE);
|
|
return FALSE;
|
|
}
|
|
}
|
|
g_string_free(phase1, TRUE);
|
|
|
|
phase2 = g_string_new(NULL);
|
|
if (nm_setting_802_1x_get_phase2_auth(setting) && !fast_provisoning_allowed) {
|
|
tmp = g_ascii_strup(nm_setting_802_1x_get_phase2_auth(setting), -1);
|
|
g_string_append_printf(phase2, "auth=%s", tmp);
|
|
g_free(tmp);
|
|
}
|
|
|
|
if (nm_setting_802_1x_get_phase2_autheap(setting)) {
|
|
if (phase2->len)
|
|
g_string_append_c(phase2, ' ');
|
|
tmp = g_ascii_strup(nm_setting_802_1x_get_phase2_autheap(setting), -1);
|
|
g_string_append_printf(phase2, "autheap=%s", tmp);
|
|
g_free(tmp);
|
|
}
|
|
|
|
if (phase2->len) {
|
|
if (!add_string_val(self, phase2->str, "phase2", FALSE, NULL, error)) {
|
|
g_string_free(phase2, TRUE);
|
|
return FALSE;
|
|
}
|
|
}
|
|
g_string_free(phase2, TRUE);
|
|
|
|
/* PAC file */
|
|
path = nm_setting_802_1x_get_pac_file(setting);
|
|
if (path) {
|
|
if (!add_string_val(self, path, "pac_file", FALSE, NULL, error))
|
|
return FALSE;
|
|
} else {
|
|
/* PAC file is not specified.
|
|
* If provisioning is allowed, use an blob format.
|
|
*/
|
|
if (fast_provisoning_allowed) {
|
|
gs_free char *blob_name = NULL;
|
|
|
|
blob_name = g_strdup_printf("blob://pac-blob-%s", con_uuid);
|
|
if (!add_string_val(self, blob_name, "pac_file", FALSE, NULL, error))
|
|
return FALSE;
|
|
} else {
|
|
/* This is only error for EAP-FAST; don't disturb other methods. */
|
|
if (fast) {
|
|
g_set_error(error,
|
|
NM_SUPPLICANT_ERROR,
|
|
NM_SUPPLICANT_ERROR_CONFIG,
|
|
"EAP-FAST error: no PAC file provided and "
|
|
"automatic PAC provisioning is disabled");
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If user wants to use system CA certs, either populate ca_path (if the path
|
|
* is a directory) or ca_cert (the path is a file name) */
|
|
if (nm_setting_802_1x_get_system_ca_certs(setting)) {
|
|
if (g_file_test(SYSTEM_CA_PATH, G_FILE_TEST_IS_DIR))
|
|
ca_path_override = SYSTEM_CA_PATH;
|
|
else
|
|
ca_cert_override = SYSTEM_CA_PATH;
|
|
}
|
|
|
|
/* CA path */
|
|
path = nm_setting_802_1x_get_ca_path(setting);
|
|
path = ca_path_override ?: path;
|
|
if (path) {
|
|
if (!add_string_val(self, path, "ca_path", FALSE, NULL, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* Phase2 CA path */
|
|
path = nm_setting_802_1x_get_phase2_ca_path(setting);
|
|
path = ca_path_override ?: path;
|
|
if (path) {
|
|
if (!add_string_val(self, path, "ca_path2", FALSE, NULL, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* CA certificate */
|
|
path = NULL;
|
|
bytes = NULL;
|
|
if (ca_cert_override) {
|
|
/* This is a build-time-configured system-wide file path, no need to pass
|
|
* it as a blob */
|
|
path = ca_cert_override;
|
|
} else {
|
|
switch (nm_setting_802_1x_get_ca_cert_scheme(setting)) {
|
|
case NM_SETTING_802_1X_CK_SCHEME_BLOB:
|
|
bytes = nm_setting_802_1x_get_ca_cert_blob(setting);
|
|
break;
|
|
case NM_SETTING_802_1X_CK_SCHEME_PATH:
|
|
path = nm_setting_802_1x_get_ca_cert_path(setting);
|
|
if (priv->private_user)
|
|
bytes = nm_g_hash_table_lookup(files, path);
|
|
break;
|
|
case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
|
|
if (!add_pkcs11_uri_with_pin(self,
|
|
"ca_cert",
|
|
nm_setting_802_1x_get_ca_cert_uri(setting),
|
|
nm_setting_802_1x_get_ca_cert_password(setting),
|
|
nm_setting_802_1x_get_ca_cert_password_flags(setting),
|
|
error)) {
|
|
return FALSE;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (bytes) {
|
|
if (!nm_supplicant_config_add_blob_for_connection(self, bytes, "ca_cert", con_uuid, error))
|
|
return FALSE;
|
|
} else if (path) {
|
|
/* Private connections cannot use paths other than the system CA store */
|
|
g_return_val_if_fail(ca_cert_override || !priv->private_user, FALSE);
|
|
if (!add_string_val(self, path, "ca_cert", FALSE, NULL, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* Phase 2 CA certificate */
|
|
path = NULL;
|
|
bytes = NULL;
|
|
if (ca_cert_override) {
|
|
/* This is a build-time-configured system-wide file path, no need to pass
|
|
* it as a blob */
|
|
path = ca_cert_override;
|
|
} else {
|
|
switch (nm_setting_802_1x_get_phase2_ca_cert_scheme(setting)) {
|
|
case NM_SETTING_802_1X_CK_SCHEME_BLOB:
|
|
bytes = nm_setting_802_1x_get_phase2_ca_cert_blob(setting);
|
|
break;
|
|
case NM_SETTING_802_1X_CK_SCHEME_PATH:
|
|
path = nm_setting_802_1x_get_phase2_ca_cert_path(setting);
|
|
if (priv->private_user)
|
|
bytes = nm_g_hash_table_lookup(files, path);
|
|
break;
|
|
case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
|
|
if (!add_pkcs11_uri_with_pin(
|
|
self,
|
|
"ca_cert2",
|
|
nm_setting_802_1x_get_phase2_ca_cert_uri(setting),
|
|
nm_setting_802_1x_get_phase2_ca_cert_password(setting),
|
|
nm_setting_802_1x_get_phase2_ca_cert_password_flags(setting),
|
|
error)) {
|
|
return FALSE;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (bytes) {
|
|
if (!nm_supplicant_config_add_blob_for_connection(self, bytes, "ca_cert2", con_uuid, error))
|
|
return FALSE;
|
|
} else if (path) {
|
|
/* Private connections cannot use paths other than the system CA store */
|
|
g_return_val_if_fail(ca_cert_override || !priv->private_user, FALSE);
|
|
if (!add_string_val(self, path, "ca_cert2", FALSE, NULL, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* Subject match */
|
|
value = nm_setting_802_1x_get_subject_match(setting);
|
|
if (!add_string_val(self, value, "subject_match", FALSE, NULL, error))
|
|
return FALSE;
|
|
value = nm_setting_802_1x_get_phase2_subject_match(setting);
|
|
if (!add_string_val(self, value, "subject_match2", FALSE, NULL, error))
|
|
return FALSE;
|
|
|
|
/* altSubjectName match */
|
|
if (!ADD_STRING_LIST_VAL(self,
|
|
setting,
|
|
802_1x,
|
|
altsubject_match,
|
|
altsubject_matches,
|
|
"altsubject_match",
|
|
';',
|
|
FALSE,
|
|
NULL,
|
|
error))
|
|
return FALSE;
|
|
if (!ADD_STRING_LIST_VAL(self,
|
|
setting,
|
|
802_1x,
|
|
phase2_altsubject_match,
|
|
phase2_altsubject_matches,
|
|
"altsubject_match2",
|
|
';',
|
|
FALSE,
|
|
NULL,
|
|
error))
|
|
return FALSE;
|
|
|
|
/* Domain suffix match */
|
|
value = nm_setting_802_1x_get_domain_suffix_match(setting);
|
|
if (!add_string_val(self, value, "domain_suffix_match", FALSE, NULL, error))
|
|
return FALSE;
|
|
value = nm_setting_802_1x_get_phase2_domain_suffix_match(setting);
|
|
if (!add_string_val(self, value, "domain_suffix_match2", FALSE, NULL, error))
|
|
return FALSE;
|
|
|
|
/* domain match */
|
|
value = nm_setting_802_1x_get_domain_match(setting);
|
|
if (!add_string_val(self, value, "domain_match", FALSE, NULL, error))
|
|
return FALSE;
|
|
value = nm_setting_802_1x_get_phase2_domain_match(setting);
|
|
if (!add_string_val(self, value, "domain_match2", FALSE, NULL, error))
|
|
return FALSE;
|
|
|
|
/* Private key */
|
|
added = FALSE;
|
|
path = NULL;
|
|
bytes = NULL;
|
|
switch (nm_setting_802_1x_get_private_key_scheme(setting)) {
|
|
case NM_SETTING_802_1X_CK_SCHEME_BLOB:
|
|
bytes = nm_setting_802_1x_get_private_key_blob(setting);
|
|
added = TRUE;
|
|
break;
|
|
case NM_SETTING_802_1X_CK_SCHEME_PATH:
|
|
path = nm_setting_802_1x_get_private_key_path(setting);
|
|
if (priv->private_user)
|
|
bytes = nm_g_hash_table_lookup(files, path);
|
|
added = TRUE;
|
|
break;
|
|
case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
|
|
if (!add_pkcs11_uri_with_pin(self,
|
|
"private_key",
|
|
nm_setting_802_1x_get_private_key_uri(setting),
|
|
nm_setting_802_1x_get_private_key_password(setting),
|
|
nm_setting_802_1x_get_private_key_password_flags(setting),
|
|
error)) {
|
|
return FALSE;
|
|
}
|
|
added = TRUE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (bytes) {
|
|
if (!nm_supplicant_config_add_blob_for_connection(self,
|
|
bytes,
|
|
"private_key",
|
|
con_uuid,
|
|
error))
|
|
return FALSE;
|
|
} else if (path) {
|
|
/* Private connections cannot use paths */
|
|
g_return_val_if_fail(!priv->private_user, FALSE);
|
|
if (!add_string_val(self, path, "private_key", FALSE, NULL, error))
|
|
return FALSE;
|
|
}
|
|
|
|
if (added) {
|
|
NMSetting8021xCKFormat format;
|
|
NMSetting8021xCKScheme scheme;
|
|
|
|
format = nm_setting_802_1x_get_private_key_format(setting);
|
|
scheme = nm_setting_802_1x_get_private_key_scheme(setting);
|
|
|
|
if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH
|
|
|| format == NM_SETTING_802_1X_CK_FORMAT_PKCS12) {
|
|
/* Only add the private key password for PKCS#12 blobs and
|
|
* all path schemes, since in both of these cases the private key
|
|
* isn't decrypted at all.
|
|
*/
|
|
value = nm_setting_802_1x_get_private_key_password(setting);
|
|
if (!add_string_val(self, value, "private_key_passwd", FALSE, "<hidden>", error))
|
|
return FALSE;
|
|
}
|
|
|
|
if (format != NM_SETTING_802_1X_CK_FORMAT_PKCS12) {
|
|
/* Only add the client cert if the private key is not PKCS#12, as
|
|
* wpa_supplicant configuration directs us to do.
|
|
*/
|
|
path = NULL;
|
|
bytes = NULL;
|
|
switch (nm_setting_802_1x_get_client_cert_scheme(setting)) {
|
|
case NM_SETTING_802_1X_CK_SCHEME_BLOB:
|
|
bytes = nm_setting_802_1x_get_client_cert_blob(setting);
|
|
break;
|
|
case NM_SETTING_802_1X_CK_SCHEME_PATH:
|
|
path = nm_setting_802_1x_get_client_cert_path(setting);
|
|
if (priv->private_user)
|
|
bytes = nm_g_hash_table_lookup(files, path);
|
|
break;
|
|
case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
|
|
if (!add_pkcs11_uri_with_pin(
|
|
self,
|
|
"client_cert",
|
|
nm_setting_802_1x_get_client_cert_uri(setting),
|
|
nm_setting_802_1x_get_client_cert_password(setting),
|
|
nm_setting_802_1x_get_client_cert_password_flags(setting),
|
|
error)) {
|
|
return FALSE;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (bytes) {
|
|
if (!nm_supplicant_config_add_blob_for_connection(self,
|
|
bytes,
|
|
"client_cert",
|
|
con_uuid,
|
|
error))
|
|
return FALSE;
|
|
} else if (path) {
|
|
/* Private connections cannot use paths */
|
|
g_return_val_if_fail(!priv->private_user, FALSE);
|
|
if (!add_string_val(self, path, "client_cert", FALSE, NULL, error))
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Phase 2 private key */
|
|
added = FALSE;
|
|
path = NULL;
|
|
bytes = NULL;
|
|
switch (nm_setting_802_1x_get_phase2_private_key_scheme(setting)) {
|
|
case NM_SETTING_802_1X_CK_SCHEME_BLOB:
|
|
bytes = nm_setting_802_1x_get_phase2_private_key_blob(setting);
|
|
added = TRUE;
|
|
break;
|
|
case NM_SETTING_802_1X_CK_SCHEME_PATH:
|
|
path = nm_setting_802_1x_get_phase2_private_key_path(setting);
|
|
if (priv->private_user)
|
|
bytes = nm_g_hash_table_lookup(files, path);
|
|
added = TRUE;
|
|
break;
|
|
case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
|
|
if (!add_pkcs11_uri_with_pin(
|
|
self,
|
|
"private_key2",
|
|
nm_setting_802_1x_get_phase2_private_key_uri(setting),
|
|
nm_setting_802_1x_get_phase2_private_key_password(setting),
|
|
nm_setting_802_1x_get_phase2_private_key_password_flags(setting),
|
|
error)) {
|
|
return FALSE;
|
|
}
|
|
added = TRUE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (bytes) {
|
|
if (!nm_supplicant_config_add_blob_for_connection(self,
|
|
bytes,
|
|
"private_key2",
|
|
con_uuid,
|
|
error))
|
|
return FALSE;
|
|
} else if (path) {
|
|
/* Private connections cannot use paths */
|
|
g_return_val_if_fail(!priv->private_user, FALSE);
|
|
if (!add_string_val(self, path, "private_key2", FALSE, NULL, error))
|
|
return FALSE;
|
|
}
|
|
|
|
if (added) {
|
|
NMSetting8021xCKFormat format;
|
|
NMSetting8021xCKScheme scheme;
|
|
|
|
format = nm_setting_802_1x_get_phase2_private_key_format(setting);
|
|
scheme = nm_setting_802_1x_get_phase2_private_key_scheme(setting);
|
|
|
|
if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH
|
|
|| format == NM_SETTING_802_1X_CK_FORMAT_PKCS12) {
|
|
/* Only add the private key password for PKCS#12 blobs and
|
|
* all path schemes, since in both of these cases the private key
|
|
* isn't decrypted at all.
|
|
*/
|
|
value = nm_setting_802_1x_get_phase2_private_key_password(setting);
|
|
if (!add_string_val(self, value, "private_key2_passwd", FALSE, "<hidden>", error))
|
|
return FALSE;
|
|
}
|
|
|
|
if (format != NM_SETTING_802_1X_CK_FORMAT_PKCS12) {
|
|
/* Only add the client cert if the private key is not PKCS#12, as
|
|
* wpa_supplicant configuration directs us to do.
|
|
*/
|
|
path = NULL;
|
|
bytes = NULL;
|
|
switch (nm_setting_802_1x_get_phase2_client_cert_scheme(setting)) {
|
|
case NM_SETTING_802_1X_CK_SCHEME_BLOB:
|
|
bytes = nm_setting_802_1x_get_phase2_client_cert_blob(setting);
|
|
break;
|
|
case NM_SETTING_802_1X_CK_SCHEME_PATH:
|
|
path = nm_setting_802_1x_get_phase2_client_cert_path(setting);
|
|
if (priv->private_user)
|
|
bytes = nm_g_hash_table_lookup(files, path);
|
|
break;
|
|
case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
|
|
if (!add_pkcs11_uri_with_pin(
|
|
self,
|
|
"client_cert2",
|
|
nm_setting_802_1x_get_phase2_client_cert_uri(setting),
|
|
nm_setting_802_1x_get_phase2_client_cert_password(setting),
|
|
nm_setting_802_1x_get_phase2_client_cert_password_flags(setting),
|
|
error)) {
|
|
return FALSE;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (bytes) {
|
|
if (!nm_supplicant_config_add_blob_for_connection(self,
|
|
bytes,
|
|
"client_cert2",
|
|
con_uuid,
|
|
error))
|
|
return FALSE;
|
|
} else if (path) {
|
|
/* Private connections cannot use paths */
|
|
g_return_val_if_fail(!priv->private_user, FALSE);
|
|
if (!add_string_val(self, path, "client_cert2", FALSE, NULL, error))
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
value = nm_setting_802_1x_get_identity(setting);
|
|
if (!add_string_val(self, value, "identity", FALSE, NULL, error))
|
|
return FALSE;
|
|
value = nm_setting_802_1x_get_anonymous_identity(setting);
|
|
if (!add_string_val(self, value, "anonymous_identity", FALSE, NULL, error))
|
|
return FALSE;
|
|
value = nm_setting_802_1x_get_openssl_ciphers(setting);
|
|
if (value && !add_string_val(self, value, "openssl_ciphers", FALSE, NULL, error))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
nm_supplicant_config_add_no_security(NMSupplicantConfig *self, GError **error)
|
|
{
|
|
return nm_supplicant_config_add_option(self, "key_mgmt", "NONE", -1, NULL, error);
|
|
}
|
|
|
|
gboolean
|
|
nm_supplicant_config_get_ap_isolation(NMSupplicantConfig *self)
|
|
{
|
|
return self->_priv.ap_isolation;
|
|
}
|
|
|
|
void
|
|
nm_supplicant_config_set_ap_isolation(NMSupplicantConfig *self, gboolean ap_isolation)
|
|
{
|
|
self->_priv.ap_isolation = ap_isolation;
|
|
}
|