wifi: Add utilities for writing IWD connection profiles
Add code that can take an NMConnection and convert it to the IWD network config file format so as to be able to mirror NM connection profiles to IWD connection profiles and make basic editing IWD profile possible from nm-connection-editor. The focus here is on 802.1x settings.
This commit is contained in:

committed by
Thomas Haller

parent
62cd7682d9
commit
9d22ae7981
@@ -7,10 +7,13 @@
|
||||
|
||||
#include "nm-wifi-utils.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/if_ether.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#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 *.<suffix> 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 <iwd_prefix>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);
|
||||
}
|
||||
|
@@ -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__ */
|
||||
|
Reference in New Issue
Block a user