/* 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(gboolean bg_band)
{
    static const char *str_2ghz = NULL;
    static const char *str_5ghz = NULL;
    const char       **f_p;
    const char        *f;

    f_p = bg_band ? &str_2ghz : &str_5ghz;

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;

        freqs = bg_band ? nm_utils_wifi_2ghz_freqs() : nm_utils_wifi_5ghz_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;

        if (freq < 5000) {
            /* the setting is not valid */
            nm_assert_not_reached();
            return;
        }

        /* Determine the center channel according to the table at
         * https://en.wikipedia.org/wiki/List_of_WLAN_channels */

        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,
                          &center_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;

            if (nm_streq(band, "a"))
                freqs = wifi_freqs_to_string(FALSE);
            else if (nm_streq(band, "bg"))
                freqs = wifi_freqs_to_string(TRUE);

            if (freqs
                && !nm_supplicant_config_add_option(self,
                                                    "freq_list",
                                                    freqs,
                                                    strlen(freqs),
                                                    NULL,
                                                    error))
                return FALSE;
        }
    }

    return TRUE;
}

gboolean
nm_supplicant_config_add_bgscan(NMSupplicantConfig *self,
                                NMConnection       *connection,
                                guint               num_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.
     */
    if (num_seen_bssids > 1u
        || ((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;
}
