/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2011 Red Hat, Inc. */ #include "src/core/nm-default-daemon.h" #include "nm-wifi-utils.h" #include #include #include #include "libnm-glib-aux/nm-str-buf.h" #include "nm-utils.h" #include "libnm-core-intern/nm-core-internal.h" #include "libnm-core-aux-intern/nm-libnm-core-utils.h" #include "libnm-core-aux-intern/nm-common-macros.h" #include "libnm-base/nm-config-base.h" static gboolean verify_no_wep(NMSettingWirelessSecurity *s_wsec, const char *tag, GError **error) { if (nm_setting_wireless_security_get_wep_key(s_wsec, 0) || nm_setting_wireless_security_get_wep_key(s_wsec, 1) || nm_setting_wireless_security_get_wep_key(s_wsec, 2) || nm_setting_wireless_security_get_wep_key(s_wsec, 3) || nm_setting_wireless_security_get_wep_tx_keyidx(s_wsec) || nm_setting_wireless_security_get_wep_key_type(s_wsec)) { /* Dynamic WEP cannot have any WEP keys set */ g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING, _("%s is incompatible with static WEP keys"), tag); g_prefix_error(error, "%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME); return FALSE; } return TRUE; } static gboolean verify_leap(NMSettingWirelessSecurity *s_wsec, NMSetting8021x *s_8021x, gboolean adhoc, GError **error) { const char *key_mgmt, *auth_alg, *leap_username; key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wsec); auth_alg = nm_setting_wireless_security_get_auth_alg(s_wsec); leap_username = nm_setting_wireless_security_get_leap_username(s_wsec); /* One (or both) of two things indicates we want LEAP: * 1) auth_alg == 'leap' * 2) valid leap_username * * LEAP always requires a LEAP username. */ if (auth_alg) { if (!strcmp(auth_alg, "leap")) { /* LEAP authentication requires at least a LEAP username */ if (!leap_username) { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_MISSING_PROPERTY, _("LEAP authentication requires a LEAP username")); g_prefix_error(error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME); return FALSE; } } else if (leap_username) { /* Leap username requires 'leap' auth */ g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("LEAP username requires 'leap' authentication")); g_prefix_error(error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME); return FALSE; } } if (leap_username) { if (key_mgmt && strcmp(key_mgmt, "ieee8021x")) { /* LEAP requires ieee8021x key management */ g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("LEAP authentication requires IEEE 802.1x key management")); g_prefix_error(error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT); return FALSE; } } /* At this point if auth_alg is set it must be 'leap', and if key_mgmt * is set it must be 'ieee8021x'. */ if (leap_username) { if (auth_alg) g_assert(strcmp(auth_alg, "leap") == 0); if (key_mgmt) g_assert(strcmp(key_mgmt, "ieee8021x") == 0); if (adhoc) { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING, _("LEAP authentication is incompatible with Ad-Hoc mode")); g_prefix_error(error, "%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME); return FALSE; } if (!verify_no_wep(s_wsec, "LEAP", error)) return FALSE; if (s_8021x) { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING, _("LEAP authentication is incompatible with 802.1x setting")); g_prefix_error(error, "%s: ", NM_SETTING_802_1X_SETTING_NAME); return FALSE; } } return TRUE; } static gboolean verify_no_wpa(NMSettingWirelessSecurity *s_wsec, const char *tag, GError **error) { const char *key_mgmt; int n, i; key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wsec); if (key_mgmt && !strncmp(key_mgmt, "wpa", 3)) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("a connection using '%s' authentication cannot use WPA key management"), tag); g_prefix_error(error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT); return FALSE; } if (nm_setting_wireless_security_get_num_protos(s_wsec)) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("a connection using '%s' authentication cannot specify WPA protocols"), tag); g_prefix_error(error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, NM_SETTING_WIRELESS_SECURITY_PROTO); return FALSE; } n = nm_setting_wireless_security_get_num_pairwise(s_wsec); for (i = 0; i < n; i++) { const char *pw; pw = nm_setting_wireless_security_get_pairwise(s_wsec, i); if (!strcmp(pw, "tkip") || !strcmp(pw, "ccmp")) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("a connection using '%s' authentication cannot specify WPA ciphers"), tag); g_prefix_error(error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, NM_SETTING_WIRELESS_SECURITY_PAIRWISE); return FALSE; } } n = nm_setting_wireless_security_get_num_groups(s_wsec); for (i = 0; i < n; i++) { const char *gr; gr = nm_setting_wireless_security_get_group(s_wsec, i); if (strcmp(gr, "wep40") && strcmp(gr, "wep104")) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("a connection using '%s' authentication cannot specify WPA ciphers"), tag); g_prefix_error(error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, NM_SETTING_WIRELESS_SECURITY_GROUP); return FALSE; } } if (nm_setting_wireless_security_get_psk(s_wsec)) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("a connection using '%s' authentication cannot specify a WPA password"), tag); g_prefix_error(error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, NM_SETTING_WIRELESS_SECURITY_PSK); return FALSE; } return TRUE; } static gboolean verify_dynamic_wep(NMSettingWirelessSecurity *s_wsec, NMSetting8021x *s_8021x, gboolean adhoc, GError **error) { const char *key_mgmt, *auth_alg, *leap_username; key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wsec); auth_alg = nm_setting_wireless_security_get_auth_alg(s_wsec); leap_username = nm_setting_wireless_security_get_leap_username(s_wsec); g_return_val_if_fail(leap_username == NULL, TRUE); if (key_mgmt) { if (!strcmp(key_mgmt, "ieee8021x")) { if (!s_8021x) { /* 802.1x key management requires an 802.1x setting */ g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_MISSING_SETTING, _("Dynamic WEP requires an 802.1x setting")); g_prefix_error(error, "%s: ", NM_SETTING_802_1X_SETTING_NAME); return FALSE; } if (auth_alg && strcmp(auth_alg, "open")) { /* 802.1x key management must use "open" authentication */ g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("Dynamic WEP requires 'open' authentication")); g_prefix_error(error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, NM_SETTING_WIRELESS_SECURITY_AUTH_ALG); return FALSE; } /* Dynamic WEP incompatible with anything static WEP related */ if (!verify_no_wep(s_wsec, "Dynamic WEP", error)) return FALSE; } else if (!strcmp(key_mgmt, "none")) { if (s_8021x) { /* 802.1x setting requires 802.1x key management */ g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("Dynamic WEP requires 'ieee8021x' key management")); g_prefix_error(error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT); return FALSE; } } } else if (s_8021x) { /* 802.1x setting incompatible with anything but 'open' auth */ if (auth_alg && strcmp(auth_alg, "open")) { /* 802.1x key management must use "open" authentication */ g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("Dynamic WEP requires 'open' authentication")); g_prefix_error(error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, NM_SETTING_WIRELESS_SECURITY_AUTH_ALG); return FALSE; } /* Dynamic WEP incompatible with anything static WEP related */ if (!verify_no_wep(s_wsec, "Dynamic WEP", error)) return FALSE; } return TRUE; } static gboolean verify_wpa_psk(NMSettingWirelessSecurity *s_wsec, NMSetting8021x *s_8021x, gboolean adhoc, guint32 wpa_flags, guint32 rsn_flags, GError **error) { const char *key_mgmt, *auth_alg; key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wsec); auth_alg = nm_setting_wireless_security_get_auth_alg(s_wsec); if (!nm_streq0(key_mgmt, "wpa-psk")) return TRUE; if (s_8021x) { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING, _("WPA-PSK authentication is incompatible with 802.1x")); g_prefix_error(error, "%s: ", NM_SETTING_802_1X_SETTING_NAME); return FALSE; } if (auth_alg && !nm_streq(auth_alg, "open")) { /* WPA must use "open" authentication */ g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("WPA-PSK requires 'open' authentication")); g_prefix_error(error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, NM_SETTING_WIRELESS_SECURITY_AUTH_ALG); return FALSE; } /* Make sure the AP's capabilities support WPA-PSK */ if (!(wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK) && !(rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK)) { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("Access point does not support PSK but setting requires it")); g_prefix_error(error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT); return FALSE; } if (adhoc) { /* Ad-Hoc RSN requires 'rsn' proto, 'ccmp' pairwise, and 'ccmp' group */ if (nm_setting_wireless_security_get_num_protos(s_wsec) != 1 || !nm_streq0(nm_setting_wireless_security_get_proto(s_wsec, 0), "rsn")) { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("WPA Ad-Hoc authentication requires 'rsn' protocol")); g_prefix_error(error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, NM_SETTING_WIRELESS_SECURITY_PROTO); return FALSE; } if (nm_setting_wireless_security_get_num_pairwise(s_wsec) != 1 || !nm_streq0(nm_setting_wireless_security_get_pairwise(s_wsec, 0), "ccmp")) { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("WPA Ad-Hoc authentication requires 'ccmp' pairwise cipher")); g_prefix_error(error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, NM_SETTING_WIRELESS_SECURITY_PAIRWISE); return FALSE; } if (nm_setting_wireless_security_get_num_groups(s_wsec) != 1 || !nm_streq0(nm_setting_wireless_security_get_group(s_wsec, 0), "ccmp")) { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("WPA Ad-Hoc requires 'ccmp' group cipher")); g_prefix_error(error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, NM_SETTING_WIRELESS_SECURITY_GROUP); return FALSE; } } return TRUE; } static gboolean verify_wpa_eap(NMSettingWirelessSecurity *s_wsec, NMSetting8021x *s_8021x, guint32 wpa_flags, guint32 rsn_flags, GError **error) { const char *key_mgmt, *auth_alg; gboolean is_wpa_eap = FALSE; key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wsec); auth_alg = nm_setting_wireless_security_get_auth_alg(s_wsec); if (key_mgmt) { if (NM_IN_STRSET(key_mgmt, "wpa-eap", "wpa-eap-suite-b-192")) { if (!s_8021x) { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_MISSING_SETTING, _("WPA-EAP authentication requires an 802.1x setting")); g_prefix_error(error, "%s: ", NM_SETTING_802_1X_SETTING_NAME); return FALSE; } if (auth_alg && strcmp(auth_alg, "open")) { /* WPA must use "open" authentication */ g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("WPA-EAP requires 'open' authentication")); g_prefix_error(error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, NM_SETTING_WIRELESS_SECURITY_AUTH_ALG); return FALSE; } is_wpa_eap = TRUE; } else if (s_8021x) { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING, _("802.1x setting requires 'wpa-eap' key management")); g_prefix_error(error, "%s: ", NM_SETTING_802_1X_SETTING_NAME); return FALSE; } } if (is_wpa_eap || s_8021x) { /* Make sure the AP's capabilities support WPA-EAP */ if (!(wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X) && !(rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X) && !(rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_EAP_SUITE_B_192)) { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING, _("Access point does not support 802.1x but setting requires it")); g_prefix_error(error, "%s: ", NM_SETTING_802_1X_SETTING_NAME); return FALSE; } } return TRUE; } static gboolean verify_adhoc(NMSettingWirelessSecurity *s_wsec, NMSetting8021x *s_8021x, gboolean adhoc, GError **error) { const char *key_mgmt = NULL, *leap_username = NULL, *auth_alg = NULL; if (!adhoc) return TRUE; if (s_wsec) { key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wsec); auth_alg = nm_setting_wireless_security_get_auth_alg(s_wsec); leap_username = nm_setting_wireless_security_get_leap_username(s_wsec); } if (key_mgmt && !NM_IN_STRSET(key_mgmt, "none", "wpa-psk")) { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("Ad-Hoc mode requires 'none' or 'wpa-psk' key management")); g_prefix_error(error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT); return FALSE; } if (s_8021x) { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING, _("Ad-Hoc mode is incompatible with 802.1x security")); g_prefix_error(error, "%s: ", NM_SETTING_802_1X_SETTING_NAME); return FALSE; } if (leap_username) { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("Ad-Hoc mode is incompatible with LEAP security")); g_prefix_error(error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, NM_SETTING_WIRELESS_SECURITY_AUTH_ALG); return FALSE; } if (auth_alg && !nm_streq(auth_alg, "open")) { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("Ad-Hoc mode requires 'open' authentication")); g_prefix_error(error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, NM_SETTING_WIRELESS_SECURITY_AUTH_ALG); return FALSE; } return TRUE; } gboolean nm_wifi_utils_complete_connection(GBytes *ap_ssid, const char *bssid, _NM80211Mode ap_mode, guint32 ap_freq, guint32 ap_flags, guint32 ap_wpa_flags, guint32 ap_rsn_flags, NMConnection *connection, gboolean lock_bssid, GError **error) { NMSettingWireless *s_wifi; NMSettingWirelessSecurity *s_wsec; NMSetting8021x *s_8021x; GBytes *ssid; const char *mode, *key_mgmt, *auth_alg, *leap_username; gboolean adhoc = FALSE; gboolean mesh = FALSE; s_wifi = nm_connection_get_setting_wireless(connection); g_assert(s_wifi); s_wsec = nm_connection_get_setting_wireless_security(connection); s_8021x = nm_connection_get_setting_802_1x(connection); /* Fill in missing SSID */ ssid = nm_setting_wireless_get_ssid(s_wifi); if (!ssid) g_object_set(G_OBJECT(s_wifi), NM_SETTING_WIRELESS_SSID, ap_ssid, NULL); else if (!ap_ssid || !g_bytes_equal(ssid, ap_ssid)) { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("connection does not match access point")); g_prefix_error(error, "%s.%s: ", NM_SETTING_WIRELESS_SETTING_NAME, NM_SETTING_WIRELESS_SSID); return FALSE; } if (lock_bssid && !nm_setting_wireless_get_bssid(s_wifi)) g_object_set(G_OBJECT(s_wifi), NM_SETTING_WIRELESS_BSSID, bssid, NULL); /* And mode */ mode = nm_setting_wireless_get_mode(s_wifi); if (mode) { gboolean valid = FALSE; /* Make sure the supplied mode matches the AP's */ if (!strcmp(mode, NM_SETTING_WIRELESS_MODE_INFRA) || !strcmp(mode, NM_SETTING_WIRELESS_MODE_AP)) { if (ap_mode == _NM_802_11_MODE_INFRA) valid = TRUE; } else if (!strcmp(mode, NM_SETTING_WIRELESS_MODE_ADHOC)) { if (ap_mode == _NM_802_11_MODE_ADHOC) valid = TRUE; adhoc = TRUE; } else if (!strcmp(mode, NM_SETTING_WIRELESS_MODE_MESH)) { if (ap_mode == _NM_802_11_MODE_MESH) valid = TRUE; mesh = TRUE; } if (valid == FALSE) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("connection does not match access point")); g_prefix_error(error, "%s.%s: ", NM_SETTING_WIRELESS_SETTING_NAME, NM_SETTING_WIRELESS_MODE); return FALSE; } } else { mode = NM_SETTING_WIRELESS_MODE_INFRA; if (ap_mode == _NM_802_11_MODE_ADHOC) { mode = NM_SETTING_WIRELESS_MODE_ADHOC; adhoc = TRUE; } else if (ap_mode == _NM_802_11_MODE_MESH) { mode = NM_SETTING_WIRELESS_MODE_MESH; mesh = TRUE; } g_object_set(G_OBJECT(s_wifi), NM_SETTING_WIRELESS_MODE, mode, NULL); } /* For now mesh requires channel and band, fill them only if both not present. * Do not check existing values against an existing ap/mesh point, * mesh join will start a new network if required */ if (mesh) { const char *band; guint32 channel; gboolean band_valid = TRUE; gboolean chan_valid = TRUE; gboolean valid; band = nm_setting_wireless_get_band(s_wifi); channel = nm_setting_wireless_get_channel(s_wifi); valid = ((band == NULL) && (channel == 0)) || ((band != NULL) && (channel != 0)); if ((band == NULL) && (channel == 0)) { channel = nm_utils_wifi_freq_to_channel(ap_freq); if (channel) { g_object_set(s_wifi, NM_SETTING_WIRELESS_CHANNEL, channel, NULL); } else { chan_valid = FALSE; } band = nm_utils_wifi_freq_to_band(ap_freq); if (band) { g_object_set(s_wifi, NM_SETTING_WIRELESS_BAND, band, NULL); } else { band_valid = FALSE; } } if (!valid || !chan_valid || !band_valid) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("connection does not match mesh point")); g_prefix_error(error, "%s.%s: ", NM_SETTING_WIRELESS_SETTING_NAME, NM_SETTING_WIRELESS_MODE); return FALSE; } } /* Security */ /* Open */ if (!(ap_flags & NM_802_11_AP_FLAGS_PRIVACY) && (ap_wpa_flags == NM_802_11_AP_SEC_NONE) && (ap_rsn_flags == NM_802_11_AP_SEC_NONE)) { /* Make sure the connection doesn't specify security */ if (s_wsec || s_8021x) { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING, _("Access point is unencrypted but setting specifies security")); if (s_wsec) g_prefix_error(error, "%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME); else g_prefix_error(error, "%s: ", NM_SETTING_802_1X_SETTING_NAME); return FALSE; } return TRUE; } /* Everything else requires security */ s_wsec = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY); key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wsec); auth_alg = nm_setting_wireless_security_get_auth_alg(s_wsec); leap_username = nm_setting_wireless_security_get_leap_username(s_wsec); /* Ad-Hoc checks */ if (!verify_adhoc(s_wsec, s_8021x, adhoc, error)) return FALSE; /* Static WEP, Dynamic WEP, or LEAP */ if ((ap_flags & NM_802_11_AP_FLAGS_PRIVACY) && (ap_wpa_flags == NM_802_11_AP_SEC_NONE) && (ap_rsn_flags == NM_802_11_AP_SEC_NONE)) { const char *tag = "WEP"; gboolean is_dynamic_wep = FALSE; if (!verify_leap(s_wsec, s_8021x, adhoc, error)) return FALSE; if (leap_username) { tag = "LEAP"; } else { /* Static or Dynamic WEP */ if (!verify_dynamic_wep(s_wsec, s_8021x, adhoc, error)) return FALSE; if (s_8021x || (key_mgmt && !strcmp(key_mgmt, "ieee8021x"))) { is_dynamic_wep = TRUE; tag = "Dynamic WEP"; } } /* Nothing WPA-related can be set */ if (!verify_no_wpa(s_wsec, tag, error)) return FALSE; if (leap_username) { /* LEAP */ g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "leap", NULL); } else if (is_dynamic_wep) { /* Dynamic WEP */ g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", NULL); if (s_8021x) { /* Dynamic WEP requires a valid 802.1x setting since we can't * autocomplete 802.1x. */ if (!nm_setting_verify(NM_SETTING(s_8021x), NULL, error)) return FALSE; } } else { /* Static WEP */ g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "none", NULL); } return TRUE; } /* WPA/RSN */ g_assert(ap_wpa_flags || ap_rsn_flags); /* Ensure key management is valid for WPA */ if ((key_mgmt && !strcmp(key_mgmt, "ieee8021x")) || leap_username) { g_set_error_literal( error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("WPA authentication is incompatible with non-EAP (original) LEAP or Dynamic WEP")); g_prefix_error(error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT); return FALSE; } /* 'shared' auth incompatible with any type of WPA */ if (auth_alg && strcmp(auth_alg, "open")) { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, _("WPA authentication is incompatible with Shared Key authentication")); g_prefix_error(error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, NM_SETTING_WIRELESS_SECURITY_AUTH_ALG); return FALSE; } if (!verify_no_wep(s_wsec, "WPA", error)) return FALSE; if (!verify_wpa_psk(s_wsec, s_8021x, adhoc, ap_wpa_flags, ap_rsn_flags, error)) return FALSE; if (!adhoc && !verify_wpa_eap(s_wsec, s_8021x, ap_wpa_flags, ap_rsn_flags, error)) return FALSE; if (adhoc) { g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-psk", NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", NULL); nm_setting_wireless_security_add_proto(s_wsec, "rsn"); nm_setting_wireless_security_add_pairwise(s_wsec, "ccmp"); nm_setting_wireless_security_add_group(s_wsec, "ccmp"); } else if (s_8021x) { g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-eap", NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", NULL); /* Leave proto/pairwise/group as client set them; if they are unset the * supplicant will figure out the best combination at connect time. */ /* 802.1x also requires the client to completely fill in the 8021x * setting. Since there's so much configuration required for it, there's * no way it can be automatically completed. */ } else if (ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_SAE && (ap_wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK || ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK)) { g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, nm_streq0(key_mgmt, "sae") ? "sae" : "wpa-psk", NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", NULL); } else if (nm_streq0(key_mgmt, "sae") || (ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_SAE)) { g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "sae", NULL); } else if (nm_streq0(key_mgmt, "owe") || NM_FLAGS_ANY(ap_rsn_flags, NM_802_11_AP_SEC_KEY_MGMT_OWE | NM_802_11_AP_SEC_KEY_MGMT_OWE_TM)) { g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "owe", NULL); } else if (nm_streq0(key_mgmt, "wpa-psk") || ap_wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK || ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK) { g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-psk", NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", NULL); /* Leave proto/pairwise/group as client set them; if they are unset the * supplicant will figure out the best combination at connect time. */ } else if (nm_streq0(key_mgmt, "wpa-eap-suite-b-192") || (ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_EAP_SUITE_B_192)) { g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-eap-suite-b-192", NULL); } else { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED, _("Failed to determine AP security information")); return FALSE; } return TRUE; } gboolean nm_wifi_utils_is_manf_default_ssid(GBytes *ssid) { const guint8 *ssid_p; gsize ssid_l; int i; /* * List of manufacturer default SSIDs that are often unchanged by users. * * NOTE: this list should *not* contain networks that you would like to * automatically roam to like "Starbucks" or "AT&T" or "T-Mobile HotSpot". */ static const char *manf_defaults[] = { "linksys", "linksys-a", "linksys-g", "default", "belkin54g", "NETGEAR", "o2DSL", "WLAN", "ALICE-WLAN", "Speedport W 501V", "TURBONETT", }; ssid_p = g_bytes_get_data(ssid, &ssid_l); for (i = 0; i < G_N_ELEMENTS(manf_defaults); i++) { if (ssid_l == strlen(manf_defaults[i])) { if (memcmp(manf_defaults[i], ssid_p, ssid_l) == 0) return TRUE; } } return FALSE; } /* To be used for connections where the SSID has been validated before */ gboolean nm_wifi_connection_get_iwd_ssid_and_security(NMConnection *connection, char **ssid, NMIwdNetworkSecurity *security) { NMSettingWireless *s_wireless; NMSettingWirelessSecurity *s_wireless_sec; const char *key_mgmt = NULL; s_wireless = nm_connection_get_setting_wireless(connection); if (!s_wireless) return FALSE; if (ssid) { GBytes *bytes = nm_setting_wireless_get_ssid(s_wireless); gsize ssid_len; const char *ssid_str = (const char *) g_bytes_get_data(bytes, &ssid_len); nm_assert(bytes && g_utf8_validate(ssid_str, ssid_len, NULL)); NM_SET_OUT(ssid, g_strndup(ssid_str, ssid_len)); } if (!security) return TRUE; s_wireless_sec = nm_connection_get_setting_wireless_security(connection); if (!s_wireless_sec) { NM_SET_OUT(security, NM_IWD_NETWORK_SECURITY_OPEN); return TRUE; } key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wireless_sec); nm_assert(key_mgmt); if (NM_IN_STRSET(key_mgmt, "none", "ieee8021x")) NM_SET_OUT(security, NM_IWD_NETWORK_SECURITY_WEP); else if (nm_streq(key_mgmt, "owe")) NM_SET_OUT(security, NM_IWD_NETWORK_SECURITY_OPEN); else if (NM_IN_STRSET(key_mgmt, "wpa-psk", "sae")) NM_SET_OUT(security, NM_IWD_NETWORK_SECURITY_PSK); else if (nm_streq(key_mgmt, "wpa-eap")) NM_SET_OUT(security, NM_IWD_NETWORK_SECURITY_8021X); else return FALSE; return TRUE; } /*****************************************************************************/ /* Builds the IWD network configuration file name for a given SSID * and security type pair. The SSID should be valid UTF-8 and in * any case must contain no NUL-bytes. If @ssid is NUL-terminated, * @ssid_len can be -1 instead of actual SSID length. */ char * nm_wifi_utils_get_iwd_config_filename(const char *ssid, gssize ssid_len, NMIwdNetworkSecurity security) { const char *security_suffix; const char *ptr; gboolean alnum_ssid = TRUE; for (ptr = ssid; ssid_len != 0 && *ptr != '\0'; ptr++, ssid_len--) if (!g_ascii_isalnum(*ptr) && !strchr("-_ ", *ptr)) alnum_ssid = FALSE; g_return_val_if_fail(ptr != ssid && ptr - ssid <= NM_IW_ESSID_MAX_SIZE, NULL); switch (security) { case NM_IWD_NETWORK_SECURITY_OPEN: security_suffix = "open"; break; case NM_IWD_NETWORK_SECURITY_PSK: security_suffix = "psk"; break; case NM_IWD_NETWORK_SECURITY_8021X: security_suffix = "8021x"; break; default: return NULL; } if (alnum_ssid) { return g_strdup_printf("%.*s.%s", (int) (ptr - ssid), ssid, security_suffix); } else { char ssid_buf[NM_IW_ESSID_MAX_SIZE * 2 + 1]; return g_strdup_printf("=%s.%s", nm_utils_bin2hexstr_full(ssid, ptr - ssid, '\0', FALSE, ssid_buf), security_suffix); } } /*****************************************************************************/ #define SECRETS_DONT_STORE_FLAGS \ (NM_SETTING_SECRET_FLAG_AGENT_OWNED | NM_SETTING_SECRET_FLAG_NOT_SAVED) static gboolean psk_setting_to_iwd_config(GKeyFile *file, NMSettingWirelessSecurity *s_wsec, GError **error) { NMSettingSecretFlags psk_flags = nm_setting_wireless_security_get_psk_flags(s_wsec); const char *psk = nm_setting_wireless_security_get_psk(s_wsec); gsize psk_len; guint8 buffer[32]; const char *key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wsec); if (!psk || NM_FLAGS_ANY(psk_flags, SECRETS_DONT_STORE_FLAGS)) { if (NM_FLAGS_ANY(psk_flags, SECRETS_DONT_STORE_FLAGS)) { nm_log_info( LOGD_WIFI, "IWD network config is being created without the PSK but IWD will save the PSK on " "successful activation not honoring the psk-flags property"); } return TRUE; } psk_len = strlen(psk); if (nm_streq0(key_mgmt, "sae")) { g_key_file_set_string(file, "Security", "Passphrase", psk); } else if (psk_len >= 8 && psk_len <= 63) { g_key_file_set_string(file, "Security", "Passphrase", psk); } else if (psk_len == 64 && nm_utils_hexstr2bin_buf(psk, FALSE, FALSE, NULL, buffer)) { g_key_file_set_string(file, "Security", "PreSharedKey", psk); } else { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, "Unknown PSK format"); return FALSE; } return TRUE; } static gboolean eap_certs_to_iwd_config(GKeyFile *file, NMSetting8021x *s_8021x, bool phase2, char *iwd_prefix, GError **error) { NMSetting8021xCKScheme ca_cert_scheme = phase2 ? nm_setting_802_1x_get_phase2_ca_cert_scheme(s_8021x) : nm_setting_802_1x_get_ca_cert_scheme(s_8021x); NMSetting8021xCKScheme client_cert_scheme = phase2 ? nm_setting_802_1x_get_phase2_client_cert_scheme(s_8021x) : nm_setting_802_1x_get_client_cert_scheme(s_8021x); NMSetting8021xCKScheme key_scheme; NMSettingSecretFlags key_password_flags; const char *ca_path = phase2 ? nm_setting_802_1x_get_phase2_ca_path(s_8021x) : nm_setting_802_1x_get_ca_path(s_8021x); const char *cert_path; const char *key_path = NULL; const char *key_password; const char *domain_suffix_match; const char *domain_match; char setting_buf[128]; /* TODO: should check that all certificates and the key are RSA */ /* Note: up to IWD 1.9 only the PEM encoding was supported for certificates * and only PKCS#8 PEM for keys but we don't know the IWD version here. * From IWD 1.10 raw (DER) X.509 certificates and PKCS#12 are also supported * for certificates but a certificate list or chain still has to be PEM * (i.e. if it contains more than one certificate.) Raw PKCS#12 and * old-style OpenSSL PEM formats are also supported for keys. Hopefully * this is in practice the same set of file:// formats as supported by * nm_crypto_* / wpa_supplicant so we need no conversions here. */ if (nm_setting_802_1x_get_system_ca_certs(s_8021x)) { /* Either overrides or is added to the certificates in (phase2-)ca-cert * and ca-path depending on whether it points to a file or a directory. * We can't ignore this property so it's an error if it is set. * Fortunately not used by nm-connection-editor. */ g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, "The system-ca-certs property is not supported"); return FALSE; } if (ca_path) { /* To support this (and this could be applied to system-ca-certs as * well) we'd have to scan the directory, parse the certificates and * write a new certificate-list file to point to in the IWD config. * This is going to create issues of where to store these files, for * how long and with what permission bits. Fortunately this doesn't * seem to be used by nm-connection-editor either. * * That file would also have to contain whatever the (phase2-)ca-cert * propterty points to because IWD has only one CACert setting per * phase. */ g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, "The (phase2-)ca-path property is not supported"); return FALSE; } if (ca_cert_scheme != NM_SETTING_802_1X_CK_SCHEME_UNKNOWN) { if (ca_cert_scheme != NM_SETTING_802_1X_CK_SCHEME_PATH) { /* To support the blob scheme we'd have to either convert the * certificate data into a PEM payload and embed the PEM file in * the IWD config file, which is not supported by GKeyFile, or write * it into a new file to point to in the IWD config. This is again * is going to create issues of where to store these files, for how * long and with what permission bits. Fortunately this scheme isn't * used in nm-connection-editor either. * * PKCS#11 is not supported by IWD in any way so we don't need to * support the PKCS#11 URI scheme. * * If scheme is unknown, assume no value is set. */ g_set_error_literal( error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, "(phase2-)ca-cert property schemes other than file:// not supported"); return FALSE; } cert_path = phase2 ? nm_setting_802_1x_get_phase2_ca_cert_path(s_8021x) : nm_setting_802_1x_get_ca_cert_path(s_8021x); if (cert_path) g_key_file_set_string(file, "Security", nm_sprintf_buf(setting_buf, "%s%s", iwd_prefix, "CACert"), cert_path); } if (client_cert_scheme == NM_SETTING_802_1X_CK_SCHEME_UNKNOWN) goto private_key_done; if (client_cert_scheme != NM_SETTING_802_1X_CK_SCHEME_PATH) { g_set_error_literal( error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, "(phase2-)client-cert property schemes other than file:// not supported"); return FALSE; } cert_path = phase2 ? nm_setting_802_1x_get_phase2_client_cert_path(s_8021x) : nm_setting_802_1x_get_client_cert_path(s_8021x); if (!cert_path) goto private_key_done; g_key_file_set_string(file, "Security", nm_sprintf_buf(setting_buf, "%s%s", iwd_prefix, "ClientCert"), cert_path); key_scheme = phase2 ? nm_setting_802_1x_get_phase2_private_key_scheme(s_8021x) : nm_setting_802_1x_get_private_key_scheme(s_8021x); if (key_scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) key_path = phase2 ? nm_setting_802_1x_get_phase2_private_key_path(s_8021x) : nm_setting_802_1x_get_private_key_path(s_8021x); if (key_scheme != NM_SETTING_802_1X_CK_SCHEME_PATH || !key_path) { /* The same comments apply to writing the key into a temporary file * as for the certificates (above), except this is even more * sensitive. */ g_set_error_literal( error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, "(phase2-)private-key property schemes other than file:// not supported"); return FALSE; } g_key_file_set_string(file, "Security", nm_sprintf_buf(setting_buf, "%s%s", iwd_prefix, "ClientKey"), key_path); key_password = phase2 ? nm_setting_802_1x_get_phase2_private_key_password(s_8021x) : nm_setting_802_1x_get_private_key_password(s_8021x); key_password_flags = phase2 ? nm_setting_802_1x_get_phase2_private_key_password_flags(s_8021x) : nm_setting_802_1x_get_private_key_password_flags(s_8021x); if (!key_password || NM_FLAGS_ANY(key_password_flags, SECRETS_DONT_STORE_FLAGS)) { g_key_file_set_comment( file, "Security", setting_buf, "ClientKeyPassphrase not to be saved, will be queried through the agent if needed", NULL); goto private_key_done; } g_key_file_set_string(file, "Security", nm_sprintf_buf(setting_buf, "%s%s", iwd_prefix, "ClientKeyPassphrase"), key_password); private_key_done: if (phase2 ? nm_setting_802_1x_get_phase2_subject_match(s_8021x) : nm_setting_802_1x_get_subject_match(s_8021x)) { g_set_error_literal( error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, "(phase2-)subject-match not supported, use domain-match or domain-suffix-match"); return FALSE; } if (phase2 ? nm_setting_802_1x_get_num_phase2_altsubject_matches(s_8021x) : nm_setting_802_1x_get_num_altsubject_matches(s_8021x)) { /* We could convert the "DNS:" entries into a ServerDomainMask but we'd * have to leave out the "EMAIL:" and "URI:" types or report error. * The interpretation still wouldn't be exactly the same as in * wpa_supplicant. */ g_set_error_literal( error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, "(phase2-)altsubject-matches not supported, use domain-match or domain-suffix-match"); return FALSE; } domain_suffix_match = phase2 ? nm_setting_802_1x_get_phase2_domain_suffix_match(s_8021x) : nm_setting_802_1x_get_domain_suffix_match(s_8021x); domain_match = phase2 ? nm_setting_802_1x_get_phase2_domain_match(s_8021x) : nm_setting_802_1x_get_domain_match(s_8021x); if (domain_suffix_match || domain_match) { GString *s = g_string_sized_new(128); const char *ptr; const char *end; for (ptr = domain_suffix_match; ptr; ptr = *end == ';' ? end + 1 : NULL) { if (s->len) g_string_append_c(s, ';'); end = strchrnul(ptr, ';'); /* Use *. to get the suffix match effect */ g_string_append(s, "*."); g_string_append_len(s, ptr, end - ptr); } /* domain-match can be appended as-is */ if (domain_match) { if (s->len) g_string_append_c(s, ';'); g_string_append(s, domain_match); } g_key_file_set_string(file, "Security", nm_sprintf_buf(setting_buf, "%s%s", iwd_prefix, "ServerDomainMask"), s->str); g_string_free(s, TRUE); } return TRUE; } static void eap_method_name_to_iwd_config(GKeyFile *file, const char *iwd_prefix, const char *method) { char setting_buf[128]; g_key_file_set_string(file, "Security", nm_sprintf_buf(setting_buf, "%s%s", iwd_prefix, "Method"), method); } static void eap_optional_identity_to_iwd_config(GKeyFile *file, const char *iwd_prefix, const char *identity) { char setting_buf[128]; /* The identity is optional for some methods where an authenticator may * in theory not ask for it. For our usage here we treat it as always * optional because it can be omitted in the config file if the user * wants IWD to query for it on every connection. */ if (identity) { g_key_file_set_string(file, "Security", nm_sprintf_buf(setting_buf, "%s%s", iwd_prefix, "Identity"), identity); } else { g_key_file_set_comment( file, "Security", nm_sprintf_buf(setting_buf, "%s%s", iwd_prefix, "Method"), "Identity not to be saved, will be queried through the agent if needed", NULL); } } static gboolean eap_optional_password_to_iwd_config(GKeyFile *file, const char *iwd_prefix, NMSetting8021x *s_8021x, GError **error) { char setting_buf[128]; const char *password = nm_setting_802_1x_get_password(s_8021x); NMSettingSecretFlags flags = nm_setting_802_1x_get_password_flags(s_8021x); if (!password && nm_setting_802_1x_get_password_raw(s_8021x)) { /* IWD doesn't support passwords that can't be encoded in the config * file, i.e. containing NUL characters. Those that don't have NULs * could in theory be written to the config file but GKeyFile may not * like that if they're no UTF-8, and the password-raw property is * not written by nm-connection-editor anyway. */ g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, "Non-UTF-8 passwords are not supported, if the password is UTF-8 set " "the \"password\" property"); return FALSE; } if (!password || NM_FLAGS_ANY(flags, SECRETS_DONT_STORE_FLAGS)) { return g_key_file_set_comment(file, "Security", nm_sprintf_buf(setting_buf, "%s%s", iwd_prefix, "Method"), "Password not to be saved, will be queried through the agent", error); } else { g_key_file_set_string(file, "Security", nm_sprintf_buf(setting_buf, "%s%s", iwd_prefix, "Password"), password); return TRUE; } } static void eap_phase1_identity_to_iwd_config(GKeyFile *file, const char *iwd_prefix, NMSetting8021x *s_8021x) { const char *phase1_identity = nm_setting_802_1x_get_anonymous_identity(s_8021x); if (!phase1_identity) { phase1_identity = nm_setting_802_1x_get_identity(s_8021x); if (phase1_identity) { nm_log_info(LOGD_WIFI, "IWD network config will send the same EAP Identity string in " "plaintext in phase 1 as in phase 2 (encrypted) to mimic legacy " "behavior, set [%s].%s=anonymous to prevent exposing the value", NM_SETTING_802_1X_SETTING_NAME, NM_SETTING_802_1X_ANONYMOUS_IDENTITY); } } eap_optional_identity_to_iwd_config(file, iwd_prefix, phase1_identity); } static gboolean eap_method_config_to_iwd_config(GKeyFile *file, NMSetting8021x *s_8021x, gboolean phase2, const char *method, const char *iwd_prefix, GError **error) { char prefix_buf[128]; if (nm_streq0(method, "tls")) { eap_method_name_to_iwd_config(file, iwd_prefix, "TLS"); eap_optional_identity_to_iwd_config(file, iwd_prefix, nm_setting_802_1x_get_identity(s_8021x)); return eap_certs_to_iwd_config(file, s_8021x, phase2, nm_sprintf_buf(prefix_buf, "%s%s", iwd_prefix, "TLS-"), error); } else if (nm_streq0(method, "ttls") && !phase2) { const char *noneap_method = nm_setting_802_1x_get_phase2_auth(s_8021x); eap_method_name_to_iwd_config(file, iwd_prefix, "TTLS"); eap_phase1_identity_to_iwd_config(file, iwd_prefix, s_8021x); if (!eap_certs_to_iwd_config(file, s_8021x, phase2, nm_sprintf_buf(prefix_buf, "%s%s", iwd_prefix, "TTLS-"), error)) return FALSE; nm_sprintf_buf(prefix_buf, "%s%s", iwd_prefix, "TTLS-Phase2-"); if (nm_setting_802_1x_get_phase2_autheap(s_8021x)) { if (noneap_method) { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, "Only one TTLS phase 2 method can be set"); return FALSE; } return eap_method_config_to_iwd_config(file, s_8021x, TRUE, nm_setting_802_1x_get_phase2_autheap(s_8021x), prefix_buf, error); } if (NM_IN_STRSET(noneap_method, "chap", "mschap", "mschapv2", "pap")) { const char *iwd_method; if (nm_streq0(noneap_method, "chap")) { iwd_method = "Tunneled-CHAP"; } else if (nm_streq0(noneap_method, "mschap")) { iwd_method = "Tunneled-MSCHAP"; } else if (nm_streq0(noneap_method, "mschapv2")) { iwd_method = "Tunneled-MSCHAPv2"; } else { iwd_method = "Tunneled-PAP"; } eap_method_name_to_iwd_config(file, prefix_buf, iwd_method); eap_optional_identity_to_iwd_config(file, prefix_buf, nm_setting_802_1x_get_identity(s_8021x)); return eap_optional_password_to_iwd_config(file, prefix_buf, s_8021x, error); } g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, "Unsupported TTLS non-EAP inner method"); return FALSE; } else if (nm_streq0(method, "peap") && !phase2) { eap_method_name_to_iwd_config(file, iwd_prefix, "PEAP"); eap_phase1_identity_to_iwd_config(file, iwd_prefix, s_8021x); if (!eap_certs_to_iwd_config(file, s_8021x, phase2, nm_sprintf_buf(prefix_buf, "%s%s", iwd_prefix, "PEAP-"), error)) return FALSE; if (nm_setting_802_1x_get_phase1_peapver(s_8021x) || nm_setting_802_1x_get_phase1_peaplabel(s_8021x)) nm_log_info(LOGD_WIFI, "IWD network config will not honour the PEAP version and label properties " "in the 802.1x setting (unsupported)"); if (!nm_setting_802_1x_get_phase2_auth(s_8021x)) { /* Apparently PEAP can be used without a phase 2 but this is not * supported by either NM or IWD. */ g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, "PEAP without an inner method is unsupported"); return FALSE; } return eap_method_config_to_iwd_config( file, s_8021x, TRUE, nm_setting_802_1x_get_phase2_auth(s_8021x), nm_sprintf_buf(prefix_buf, "%s%s", iwd_prefix, "PEAP-Phase2-"), error); } else if (nm_streq0(method, "md5") && phase2) { eap_method_name_to_iwd_config(file, iwd_prefix, "MD5"); eap_optional_identity_to_iwd_config(file, iwd_prefix, nm_setting_802_1x_get_identity(s_8021x)); return eap_optional_password_to_iwd_config(file, iwd_prefix, s_8021x, error); } else if (nm_streq0(method, "gtc") && phase2) { eap_method_name_to_iwd_config(file, iwd_prefix, "GTC"); eap_optional_identity_to_iwd_config(file, iwd_prefix, nm_setting_802_1x_get_identity(s_8021x)); return eap_optional_password_to_iwd_config(file, iwd_prefix, s_8021x, error); } else if (nm_streq0(method, "pwd")) { eap_method_name_to_iwd_config(file, iwd_prefix, "PWD"); eap_optional_identity_to_iwd_config(file, iwd_prefix, nm_setting_802_1x_get_identity(s_8021x)); return eap_optional_password_to_iwd_config(file, iwd_prefix, s_8021x, error); } else if (nm_streq0(method, "mschapv2")) { eap_method_name_to_iwd_config(file, iwd_prefix, "MSCHAPV2"); eap_optional_identity_to_iwd_config(file, iwd_prefix, nm_setting_802_1x_get_identity(s_8021x)); /* In this case we can support password-raw but would have to * MD4-hash it and set as Password-Hash */ return eap_optional_password_to_iwd_config(file, iwd_prefix, s_8021x, error); } else if (nm_streq0(method, "external")) { /* This may be a connection created by NMIwdManager in whch case there * may be no need to be convert it back to the IWD format. Ideally we * would still rewrite the other sections/groups in the IWD settings * file and preserve the [Security] group -- TODO. Possibly this should * also not be reported as an error. */ g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, "Connection contains no EAP method configuration"); return FALSE; } else { /* Some methods are only allowed in phase 1 or only phase 2. * OTP, LEAP and FAST are not supported by IWD at all. */ g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, phase2 ? "Unsupported phase 2 EAP method" : "Unsupported phase 1 EAP method"); return FALSE; } return TRUE; } static gboolean eap_setting_to_iwd_config(GKeyFile *file, NMSetting8021x *s_8021x, GError **error) { const char *method; if (!s_8021x || nm_setting_802_1x_get_num_eap_methods(s_8021x) == 0) { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, "The 802.1x setting is missing or no EAP method set"); return FALSE; } if (!nm_setting_verify(NM_SETTING(s_8021x), NULL, error)) return FALSE; method = nm_setting_802_1x_get_eap_method(s_8021x, 0); if (nm_setting_802_1x_get_num_eap_methods(s_8021x) > 1) nm_log_info(LOGD_WIFI, "IWD network config will only contain the first EAP method: %s", method); if (nm_setting_802_1x_get_phase1_auth_flags(s_8021x)) nm_log_info(LOGD_WIFI, "IWD network config will not honour the TLSv1.x-disable flags in the 802.1x " "setting (unsupported)"); if (nm_setting_802_1x_get_auth_timeout(s_8021x)) nm_log_info(LOGD_WIFI, "IWD network config will not honour the auth-timeout property in the 802.1x " "setting (unsupported)"); return eap_method_config_to_iwd_config(file, s_8021x, FALSE, method, "EAP-", error); } static gboolean ip_config_to_iwd_config(int addr_family, GKeyFile *file, NMSettingIPConfig *s_ip, GError **error) { const int IS_IPv4 = NM_IS_IPv4(addr_family); nm_auto_str_buf NMStrBuf strbuf = NM_STR_BUF_INIT_A(NM_UTILS_GET_NEXT_REALLOC_SIZE_488, FALSE); NMIPAddress *addr; guint num; guint i; char buf[NM_INET_ADDRSTRLEN + 10]; const char *kf_group = IS_IPv4 ? "IPv4" : "IPv6"; const char *gw; /* These settings are not acutally used unless global * [General].EnableNetworkConfiguration is true, which we don't support. * We add them for sake of completness, although many NMSettingIPConfig * configurations can't be mapped to IWD configs and we simply ignore * them. If they were to be used we'd need to add a few warnings. */ if (!s_ip) return TRUE; nm_assert(NM_IS_IPv4(addr_family) ? NM_IS_SETTING_IP4_CONFIG(s_ip) : NM_IS_SETTING_IP6_CONFIG(s_ip)); num = nm_setting_ip_config_get_num_dns(s_ip); if (num) { nm_str_buf_reset(&strbuf); for (i = 0; i < num; i++) { char sbuf[NM_INET_ADDRSTRLEN]; NMIPAddr a; if (!nm_utils_dnsname_parse_assert(addr_family, nm_setting_ip_config_get_dns(s_ip, i), NULL, &a, NULL)) continue; if (strbuf.len > 0) nm_str_buf_append_c(&strbuf, ' '); nm_str_buf_append(&strbuf, nm_inet_ntop(addr_family, &a, sbuf)); } /* It doesn't matter whether we add the DNS under [IPv4] or [IPv6] * except that with method=auto the list will override the * DNS addresses received over the DHCP version corresponing to * v4 or v6. * Note ignore-auto-dns=false isn't supported, this list always * overrides the DHCP DNSes. */ g_key_file_set_string(file, kf_group, "DNS", nm_str_buf_get_str(&strbuf)); } if (!IS_IPv4) { if (!NM_IN_STRSET(nm_setting_ip_config_get_method(s_ip), NM_SETTING_IP6_CONFIG_METHOD_AUTO, NM_SETTING_IP6_CONFIG_METHOD_DHCP, NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) return TRUE; g_key_file_set_boolean(file, kf_group, "Enabled", TRUE); } num = nm_setting_ip_config_get_num_addresses(s_ip); if (num == 0) return TRUE; addr = nm_setting_ip_config_get_address(s_ip, 0); gw = nm_setting_ip_config_get_gateway(s_ip); if (IS_IPv4) { in_addr_t ip; nm_ip_address_get_address_binary(addr, &ip); g_key_file_set_string(file, kf_group, "Address", nm_ip_address_get_address(addr)); g_key_file_set_string( file, kf_group, "Netmask", nm_inet4_ntop(nm_ip4_addr_netmask_from_prefix(nm_ip_address_get_prefix(addr)), buf)); if (!gw) { guint32 val; /* IWD won't enable static IP unless both Address and Gateway are * set so generate a gateway address if not known. */ val = (ntohl(ip) & 0xfffffff0) + 1; if (val == ntohl(ip)) val += 1; gw = nm_inet4_ntop(htonl(val), buf); } g_key_file_set_string(file, kf_group, "Gateway", gw); } else { g_key_file_set_string(file, kf_group, "Address", nm_sprintf_buf(buf, "%s/%u", nm_ip_address_get_address(addr), nm_ip_address_get_prefix(addr))); if (gw) g_key_file_set_string(file, kf_group, "Gateway", gw); } return TRUE; } GKeyFile * nm_wifi_utils_connection_to_iwd_config(NMConnection *connection, char **out_filename, GError **error) { NMSettingConnection *s_conn = nm_connection_get_setting_connection(connection); NMSettingWireless *s_wifi = nm_connection_get_setting_wireless(connection); GBytes *ssid; const guint8 *ssid_data; gsize ssid_len; NMIwdNetworkSecurity security; const char *cloned_mac_addr; gs_free char *comment = NULL; nm_auto_unref_keyfile GKeyFile *file = NULL; if (!s_conn || !s_wifi || !nm_streq(nm_setting_connection_get_connection_type(s_conn), NM_SETTING_WIRELESS_SETTING_NAME)) { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, "Connection and/or wireless settings are missing"); return NULL; } if (!NM_IN_STRSET(nm_setting_wireless_get_mode(s_wifi), NULL, NM_SETTING_WIRELESS_MODE_INFRA)) { g_set_error_literal( error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, "Non-infrastructure-mode connections don't have IWD profiles (or aren't supported)"); return NULL; } ssid = nm_setting_wireless_get_ssid(s_wifi); ssid_data = ssid ? g_bytes_get_data(ssid, &ssid_len) : NULL; if (!ssid_data || ssid_len <= 0 || ssid_len > NM_IW_ESSID_MAX_SIZE || !g_utf8_validate((const char *) ssid_data, ssid_len, NULL)) { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, "Empty or non-UTF-8 SSIDs not supported by IWD"); return NULL; } if (!nm_wifi_connection_get_iwd_ssid_and_security(connection, NULL, &security)) { g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, "Connection's security type unrecognised"); return NULL; } file = g_key_file_new(); comment = g_strdup_printf(" Auto-generated from NetworkManager connection \"%s\"\n" " Changes to that connection overwrite this file when " "enabled by NM's [%s].%s value", nm_setting_connection_get_id(s_conn), NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_IWD_CONFIG_PATH); g_key_file_set_comment(file, NULL, NULL, comment, NULL); if (!nm_setting_connection_get_autoconnect(s_conn)) g_key_file_set_boolean(file, "Settings", "AutoConnect", FALSE); if (nm_setting_wireless_get_hidden(s_wifi)) g_key_file_set_boolean(file, "Settings", "Hidden", TRUE); /* Only effective if IWD's global [General].AddressRandomization is set * to "network". "random" maps to [Settings].AlwaysRandomizeAddress=true, * "stable" is the default, specific address maps to * [Settings].AddressOverride set to that address. "permanent" is not * supported and "preserve" can only be achieved using the global * [General].AddressRandomization=disabled setting. We don't print * warnings when we can't map the value here because we don't know what * IWD's [General].AddressRandomization is set to. */ cloned_mac_addr = nm_setting_wireless_get_cloned_mac_address(s_wifi); if (nm_streq0(cloned_mac_addr, NM_CLONED_MAC_RANDOM)) g_key_file_set_boolean(file, "Settings", "AlwaysRandomizeAddress", TRUE); else if (cloned_mac_addr && nm_utils_hwaddr_valid(cloned_mac_addr, ETH_ALEN)) g_key_file_set_string(file, "Settings", "AddressOverride", cloned_mac_addr); if (!ip_config_to_iwd_config( AF_INET, file, NM_SETTING_IP_CONFIG(nm_connection_get_setting_ip4_config(connection)), error)) return NULL; if (!ip_config_to_iwd_config( AF_INET6, file, NM_SETTING_IP_CONFIG(nm_connection_get_setting_ip6_config(connection)), error)) return NULL; switch (security) { case NM_IWD_NETWORK_SECURITY_OPEN: break; case NM_IWD_NETWORK_SECURITY_PSK: if (!psk_setting_to_iwd_config(file, nm_connection_get_setting_wireless_security(connection), error)) return NULL; break; case NM_IWD_NETWORK_SECURITY_8021X: if (!eap_setting_to_iwd_config(file, nm_connection_get_setting_802_1x(connection), error)) return NULL; break; default: g_set_error_literal(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, "Connection security type is not supported"); return NULL; } if (out_filename) *out_filename = nm_wifi_utils_get_iwd_config_filename((const char *) ssid_data, ssid_len, security); return g_steal_pointer(&file); } /* Wi-Fi Display Technical Specification v2.1.0 Table 27 */ enum wfd_subelem_type { WFD_SUBELEM_WFD_DEVICE_INFORMATION = 0, WFD_SUBELEM_ASSOCIATED_BSSID = 1, WFD_SUBELEM_COUPLED_SINK_INFORMATION = 6, WFD_SUBELEM_EXTENDED_CAPABILITY = 7, WFD_SUBELEM_LOCAL_IP_ADDRESS = 8, WFD_SUBELEM_SESION_INFORMATION = 9, WFD_SUBELEM_ALTERNATIVE_MAC_ADDRESS = 10, WFD_SUBELEM_R2_DEVICE_INFORMATION = 11, }; bool nm_wifi_utils_parse_wfd_ies(GBytes *ies, NMIwdWfdInfo *out_wfd) { size_t len; const uint8_t *data = g_bytes_get_data(ies, &len); const uint8_t *dev_info = NULL; uint16_t dev_info_len = 0; uint16_t dev_info_flags; const uint8_t *ext_capability = NULL; uint16_t ext_capability_len = 0; /* The single WFD IEs array provided by the client is supposed to be sent to * the peer in the different frame types that may include the WFD IE: Probe * Request/Response, Beacon, (Re)Association Request/Response, GO * Negotiation Request/Response/Confirm and Provision Discovery * Request/Response. * * It's going to be a subset of the elements allowed in all those frames. * Validate that it contains at least a valid WFD Device Information (with * the Session Available bit true) and that the sequence of subelements is * valid. */ while (len) { uint8_t subelem_id; uint16_t subelem_len; /* Does the subelement header fit */ if (len < 3) return FALSE; subelem_id = data[0]; subelem_len = (data[1] << 8) | data[2]; data += 3; len -= 3; if (subelem_len > len) return FALSE; if (subelem_id == WFD_SUBELEM_WFD_DEVICE_INFORMATION) { /* Is there a duplicate WFD Device Information */ if (dev_info) return FALSE; dev_info = data; dev_info_len = subelem_len; } if (subelem_id == WFD_SUBELEM_EXTENDED_CAPABILITY) { /* Is there a duplicate WFD Extended Capability */ if (ext_capability) return FALSE; ext_capability = data; ext_capability_len = subelem_len; } data += subelem_len; len -= subelem_len; } if (!dev_info || dev_info_len != 6) return FALSE; dev_info_flags = (dev_info[0] << 8) | dev_info[1]; /* Secondary sink not supported */ if ((dev_info_flags & 3) == 2) return FALSE; /* Must be available for WFD Session */ if (((dev_info_flags >> 4) & 3) != 1) return FALSE; /* TDLS persistent group re-invocation not supported */ if ((dev_info_flags >> 13) & 1) return FALSE; /* All other flags indicate support but not a requirement for something * so not preserving them in the IEs IWD eventually sends doesn't break * basic functionality. */ if (ext_capability && ext_capability_len != 2) return FALSE; if (!out_wfd) return TRUE; out_wfd->source = NM_IN_SET(dev_info_flags & 3, 0, 3); out_wfd->sink = NM_IN_SET(dev_info_flags & 3, 1, 3); out_wfd->port = (dev_info[2] << 8) | dev_info[3]; out_wfd->has_audio = out_wfd->sink ? ((dev_info_flags >> 10) & 1) == 0 : (((dev_info_flags >> 11) & 1) == 1); out_wfd->has_uibc = ext_capability && (ext_capability[1] & 1) == 1; out_wfd->has_cp = ((dev_info_flags >> 8) & 1) == 1; return TRUE; } GBytes * nm_wifi_utils_build_wfd_ies(const NMIwdWfdInfo *wfd) { uint8_t data[64]; uint8_t *ptr = data; *ptr++ = WFD_SUBELEM_WFD_DEVICE_INFORMATION; *ptr++ = 0; /* WFD Subelement length */ *ptr++ = 6; *ptr++ = 0; /* WFD Device Information bitmap: */ *ptr++ = (wfd->source ? (wfd->sink ? 3 : 0) : 1) | 0x10 | /* WFD Session Available */ (wfd->has_cp ? 0x100 : 0) | (wfd->has_audio ? 0 : 0x400); *ptr++ = wfd->port >> 8; *ptr++ = wfd->port & 255; *ptr++ = 0; /* WFD Device Maximum throughput */ *ptr++ = 10; if (wfd->has_uibc) { *ptr++ = WFD_SUBELEM_EXTENDED_CAPABILITY; *ptr++ = 0; /* WFD Subelement length */ *ptr++ = 2; *ptr++ = 0x00; /* WFD Extended Capability Bitmap: */ *ptr++ = 0x10; /* UIBC Support */ } return g_bytes_new(data, ptr - data); } bool nm_wifi_utils_wfd_info_eq(const NMIwdWfdInfo *a, const NMIwdWfdInfo *b) { if (!a || !b) return a == b; return a->source == b->source && a->sink == b->sink && a->port == b->port && a->has_audio == b->has_audio && a->has_uibc == b->has_uibc && a->has_cp == b->has_cp; }