diff --git a/src/core/devices/wifi/nm-wifi-utils.c b/src/core/devices/wifi/nm-wifi-utils.c index bf34fe16ad..b02a66881d 100644 --- a/src/core/devices/wifi/nm-wifi-utils.c +++ b/src/core/devices/wifi/nm-wifi-utils.c @@ -7,10 +7,13 @@ #include "nm-wifi-utils.h" +#include +#include #include #include "nm-utils.h" #include "libnm-core-intern/nm-core-internal.h" +#include "libnm-core-aux-intern/nm-common-macros.h" static gboolean verify_no_wep(NMSettingWirelessSecurity *s_wsec, const char *tag, GError **error) @@ -943,3 +946,838 @@ nm_wifi_connection_get_iwd_ssid_and_security(NMConnection * connection, 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); + } +} + +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 || (psk_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED)) { + g_key_file_set_comment(file, + "Security", + NULL, + "The passphrase is to be queried through the agent", + NULL); + if (psk_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED) { + nm_log_info( + LOGD_WIFI, + "IWD network config is being created wihout 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_ca_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 || (key_password_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED)) { + 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 || (flags & NM_SETTING_SECRET_FLAG_NOT_SAVED)) { + 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 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_optional_identity_to_iwd_config(file, + iwd_prefix, + nm_setting_802_1x_get_anonymous_identity(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_optional_identity_to_iwd_config(file, + iwd_prefix, + nm_setting_802_1x_get_anonymous_identity(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 +ip4_config_to_iwd_config(GKeyFile *file, NMSettingIPConfig *s_ip, GError **error) +{ + guint num; + struct in_addr ip; + + /* 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; + + num = nm_setting_ip_config_get_num_dns(s_ip); + if (num) { + GString *s = g_string_sized_new(128); + int i; + + for (i = 0; i < num; i++) { + if (s->len) + g_string_append_c(s, ' '); + g_string_append(s, nm_setting_ip_config_get_dns(s_ip, i)); + } + /* 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, "IPv4", "DNS", s->str); + g_string_free(s, TRUE); + } + + if (!nm_streq0(nm_setting_ip_config_get_method(s_ip), NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) + return TRUE; + + num = nm_setting_ip_config_get_num_addresses(s_ip); + if (num) { + NMIPAddress *addr = nm_setting_ip_config_get_address(s_ip, 0); + guint prefix = nm_ip_address_get_prefix(addr); + in_addr_t netmask = htonl(0xffffffffu << (32 - prefix)); + char buf[INET_ADDRSTRLEN]; + + nm_ip_address_get_address_binary(addr, &ip); + g_key_file_set_string(file, "IPv4", "Address", nm_ip_address_get_address(addr)); + g_key_file_set_string(file, "IPv4", "Netmask", _nm_utils_inet4_ntop(netmask, buf)); + } else { + inet_pton(AF_INET, "10.42.0.100", &ip); + g_key_file_set_string(file, "IPv4", "Address", "10.42.0.100"); + } + + if (nm_setting_ip_config_get_gateway(s_ip)) { + g_key_file_set_string(file, "IPv4", "Gateway", nm_setting_ip_config_get_gateway(s_ip)); + } else { + uint32_t val; + char buf[INET_ADDRSTRLEN]; + + /* IWD won't enable static IP unless both Address and Gateway are + * set so generate a gateway address if not known. + */ + val = (ntohl(ip.s_addr) & 0xfffffff0) + 1; + if (val == ntohl(ip.s_addr)) + val += 1; + g_key_file_set_string(file, "IPv4", "Gateway", _nm_utils_inet4_ntop(htonl(val), buf)); + } + + return TRUE; +} + +static gboolean +ip6_config_to_iwd_config(GKeyFile *file, NMSettingIPConfig *s_ip, GError **error) +{ + guint num; + NMIPAddress *addr; + char buf[INET6_ADDRSTRLEN + 10]; + + if (!s_ip) + return TRUE; + + num = nm_setting_ip_config_get_num_dns(s_ip); + if (num) { + GString *s = g_string_sized_new(128); + int i; + + for (i = 0; i < num; i++) { + if (s->len) + g_string_append_c(s, ' '); + g_string_append(s, nm_setting_ip_config_get_dns(s_ip, i)); + } + g_key_file_set_string(file, "IPv6", "DNS", s->str); + g_string_free(s, TRUE); + } + + 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, "IPv6", "Enabled", TRUE); + + if (!nm_streq0(nm_setting_ip_config_get_method(s_ip), NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) + return TRUE; + + if (!nm_setting_ip_config_get_num_addresses(s_ip)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + "IP address required for IPv6 manual config"); + return FALSE; + } + + addr = nm_setting_ip_config_get_address(s_ip, 0); + g_key_file_set_string(file, + "IPv6", + "Address", + nm_sprintf_buf(buf, + "%s/%u", + nm_ip_address_get_address(addr), + nm_ip_address_get_prefix(addr))); + if (nm_setting_ip_config_get_gateway(s_ip)) + g_key_file_set_string(file, "IPv6", "Gateway", nm_setting_ip_config_get_gateway(s_ip)); + 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; + 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(); + + 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 (!ip4_config_to_iwd_config( + file, + NM_SETTING_IP_CONFIG(nm_connection_get_setting_ip4_config(connection)), + error)) + return NULL; + + if (!ip6_config_to_iwd_config( + 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); +} diff --git a/src/core/devices/wifi/nm-wifi-utils.h b/src/core/devices/wifi/nm-wifi-utils.h index 105c1450b5..2664c03ea5 100644 --- a/src/core/devices/wifi/nm-wifi-utils.h +++ b/src/core/devices/wifi/nm-wifi-utils.h @@ -36,5 +36,11 @@ gboolean nm_wifi_utils_is_manf_default_ssid(GBytes *ssid); gboolean nm_wifi_connection_get_iwd_ssid_and_security(NMConnection * connection, char ** ssid, NMIwdNetworkSecurity *security); +char * nm_wifi_utils_get_iwd_config_filename(const char * ssid, + gssize ssid_len, + NMIwdNetworkSecurity security); + +GKeyFile * +nm_wifi_utils_connection_to_iwd_config(NMConnection *conn, char **out_filename, GError **error); #endif /* __NM_WIFI_UTILS_H__ */