mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-01-10 23:20:22 +01:00
wifi/iwd: merge branch 'balrog-kun:write-iwd-configs'
https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/695
This commit is contained in:
commit
bf564937bb
8 changed files with 1134 additions and 26 deletions
|
|
@ -474,6 +474,26 @@ no-auto-default=*
|
|||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>iwd-config-path</varname></term>
|
||||
<listitem>
|
||||
<para>
|
||||
If the value points to an existing directory, Network
|
||||
Manager will attempt to write copies of new or modified
|
||||
Wi-Fi connection profiles, converted into the IWD
|
||||
format, into this directory thus making IWD connection
|
||||
properties editable. This will only happen if the IWD
|
||||
backend is active meaning that at least one Wi-Fi device
|
||||
must exist.
|
||||
</para>
|
||||
<para>
|
||||
This allows editing connection profile settings such as
|
||||
the 802.1x configuration using Network Manager clients.
|
||||
Without it such changes have no effect in IWD.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
#include "nm-iwd-manager.h"
|
||||
|
||||
#include <net/if.h>
|
||||
#include <glib/gstdio.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "libnm-core-intern/nm-core-internal.h"
|
||||
#include "nm-manager.h"
|
||||
|
|
@ -16,6 +18,7 @@
|
|||
#include "libnm-glib-aux/nm-random-utils.h"
|
||||
#include "settings/nm-settings.h"
|
||||
#include "libnm-std-aux/nm-dbus-compat.h"
|
||||
#include "nm-config.h"
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
|
|
@ -28,6 +31,7 @@ typedef struct {
|
|||
typedef struct {
|
||||
GDBusProxy * known_network;
|
||||
NMSettingsConnection *mirror_connection;
|
||||
const KnownNetworkId *id;
|
||||
} KnownNetworkData;
|
||||
|
||||
typedef struct {
|
||||
|
|
@ -419,7 +423,7 @@ known_network_update_cb(GObject *source, GAsyncResult *res, gpointer user_data)
|
|||
variant = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error);
|
||||
if (!variant) {
|
||||
nm_log_warn(LOGD_WIFI,
|
||||
"Updating %s on IWD known network %s failed: %s",
|
||||
"iwd: updating %s on IWD known network %s failed: %s",
|
||||
(const char *) user_data,
|
||||
g_dbus_proxy_get_object_path(G_DBUS_PROXY(source)),
|
||||
error->message);
|
||||
|
|
@ -427,17 +431,31 @@ known_network_update_cb(GObject *source, GAsyncResult *res, gpointer user_data)
|
|||
}
|
||||
|
||||
static void
|
||||
sett_conn_changed(NMSettingsConnection *sett_conn, guint update_reason, KnownNetworkData *data)
|
||||
sett_conn_changed(NMSettingsConnection * sett_conn,
|
||||
guint update_reason,
|
||||
const KnownNetworkData *data)
|
||||
{
|
||||
NMSettingsConnectionIntFlags flags;
|
||||
NMConnection * conn = nm_settings_connection_get_connection(sett_conn);
|
||||
NMSettingConnection * s_conn = nm_connection_get_setting_connection(conn);
|
||||
gboolean nm_autoconnectable = nm_setting_connection_get_autoconnect(s_conn);
|
||||
gboolean iwd_autoconnectable = get_property_bool(data->known_network, "AutoConnect", TRUE);
|
||||
NMConnection * conn = nm_settings_connection_get_connection(sett_conn);
|
||||
NMSettingConnection * s_conn = nm_connection_get_setting_connection(conn);
|
||||
NMSettingWireless * s_wifi = nm_connection_get_setting_wireless(conn);
|
||||
nm_auto_unref_keyfile GKeyFile *iwd_config = NULL;
|
||||
const char * iwd_dir;
|
||||
gs_free char * filename = NULL;
|
||||
gs_free char * full_path = NULL;
|
||||
gs_free_error GError *error = NULL;
|
||||
NMIwdNetworkSecurity security;
|
||||
GBytes * ssid;
|
||||
const guint8 * ssid_data;
|
||||
gsize ssid_len;
|
||||
gboolean removed;
|
||||
|
||||
nm_assert(sett_conn == data->mirror_connection);
|
||||
|
||||
if (iwd_autoconnectable == nm_autoconnectable)
|
||||
if (update_reason
|
||||
& (NM_SETTINGS_CONNECTION_UPDATE_REASON_CLEAR_SYSTEM_SECRETS
|
||||
| NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS
|
||||
| NM_SETTINGS_CONNECTION_UPDATE_REASON_BLOCK_AUTOCONNECT))
|
||||
return;
|
||||
|
||||
/* If this is a generated connection it may be ourselves updating it */
|
||||
|
|
@ -445,21 +463,113 @@ sett_conn_changed(NMSettingsConnection *sett_conn, guint update_reason, KnownNet
|
|||
if (NM_FLAGS_HAS(flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED))
|
||||
return;
|
||||
|
||||
iwd_dir = nm_config_data_get_iwd_config_path(NM_CONFIG_GET_DATA);
|
||||
if (!iwd_dir || iwd_dir[0] == '\0' || !g_file_test(iwd_dir, G_FILE_TEST_IS_DIR)) {
|
||||
gboolean nm_autoconnectable = nm_setting_connection_get_autoconnect(s_conn);
|
||||
gboolean iwd_autoconnectable = get_property_bool(data->known_network, "AutoConnect", TRUE);
|
||||
|
||||
if (iwd_autoconnectable == nm_autoconnectable) {
|
||||
nm_log_dbg(LOGD_WIFI,
|
||||
"iwd: updating AutoConnect on known network at %s based on connection %s",
|
||||
g_dbus_proxy_get_object_path(data->known_network),
|
||||
nm_settings_connection_get_id(data->mirror_connection));
|
||||
g_dbus_proxy_call(data->known_network,
|
||||
DBUS_INTERFACE_PROPERTIES ".Set",
|
||||
g_variant_new("(ssv)",
|
||||
NM_IWD_KNOWN_NETWORK_INTERFACE,
|
||||
"AutoConnect",
|
||||
g_variant_new_boolean(nm_autoconnectable)),
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
-1,
|
||||
NULL,
|
||||
known_network_update_cb,
|
||||
"AutoConnect");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* If the SSID and the security type in the NMSettingsConnection haven't
|
||||
* changed, we just need to overwrite the original IWD config file.
|
||||
* Otherwise we need to call Forget on the original KnownNetwork or
|
||||
* remove its file. IWD will have to delete one D-Bus object and
|
||||
* create another anyway because the SSID and security type are in the
|
||||
* D-Bus object path, so no point renaming the file.
|
||||
*/
|
||||
ssid = nm_setting_wireless_get_ssid(s_wifi);
|
||||
ssid_data = ssid ? g_bytes_get_data(ssid, &ssid_len) : NULL;
|
||||
removed = FALSE;
|
||||
|
||||
if (!nm_wifi_connection_get_iwd_ssid_and_security(conn, NULL, &security)
|
||||
|| security != data->id->security || !ssid_data || ssid_len != strlen(data->id->name)
|
||||
|| memcmp(ssid_data, data->id->name, ssid_len)) {
|
||||
gs_free char *orig_filename =
|
||||
nm_wifi_utils_get_iwd_config_filename(data->id->name, -1, data->id->security);
|
||||
gs_free char *orig_full_path = g_strdup_printf("%s/%s", iwd_dir, orig_filename);
|
||||
|
||||
if (g_remove(orig_full_path) == 0)
|
||||
nm_log_dbg(LOGD_WIFI, "iwd: profile at %s removed", orig_full_path);
|
||||
else if (errno != ENOENT)
|
||||
nm_log_dbg(LOGD_WIFI,
|
||||
"iwd: profile at %s not removed: %s (%i)",
|
||||
orig_full_path,
|
||||
strerror(errno),
|
||||
errno);
|
||||
|
||||
removed = TRUE;
|
||||
}
|
||||
|
||||
if (!nm_streq(nm_settings_connection_get_connection_type(sett_conn), "802-11-wireless")
|
||||
|| !s_wifi)
|
||||
return;
|
||||
|
||||
/* If the connection has any permissions other than the default we don't
|
||||
* want to save it as an IWD profile. IWD will make it available for
|
||||
* everybody to attempt a connection, remove, or toggle "autoconnectable".
|
||||
*/
|
||||
if (s_conn && nm_setting_connection_get_num_permissions(s_conn)) {
|
||||
nm_log_dbg(
|
||||
LOGD_WIFI,
|
||||
"iwd: changed Wi-Fi connection %s not mirrored as IWD profile because of non-default "
|
||||
"permissions",
|
||||
nm_settings_connection_get_id(sett_conn));
|
||||
return;
|
||||
}
|
||||
|
||||
iwd_config = nm_wifi_utils_connection_to_iwd_config(conn, &filename, &error);
|
||||
if (!iwd_config) {
|
||||
/* The error message here is not translated and it only goes in
|
||||
* the logs.
|
||||
*/
|
||||
nm_log_dbg(LOGD_WIFI,
|
||||
"iwd: changed Wi-Fi connection %s not mirrored as IWD profile: %s",
|
||||
nm_settings_connection_get_id(sett_conn),
|
||||
error->message);
|
||||
return;
|
||||
}
|
||||
|
||||
full_path = g_strdup_printf("%s/%s", iwd_dir, filename);
|
||||
if (removed && g_file_test(full_path, G_FILE_TEST_EXISTS)) {
|
||||
nm_log_dbg(LOGD_WIFI,
|
||||
"iwd: changed Wi-Fi connection %s not mirrored as IWD profile because %s "
|
||||
"already exists",
|
||||
nm_settings_connection_get_id(sett_conn),
|
||||
full_path);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!g_key_file_save_to_file(iwd_config, full_path, &error)) {
|
||||
nm_log_dbg(LOGD_WIFI,
|
||||
"iwd: changed Wi-Fi connection %s not mirrored as IWD profile: save error: %s",
|
||||
nm_settings_connection_get_id(sett_conn),
|
||||
error->message);
|
||||
return;
|
||||
}
|
||||
|
||||
nm_log_dbg(LOGD_WIFI,
|
||||
"Updating AutoConnect on known network at %s based on connection %s",
|
||||
g_dbus_proxy_get_object_path(data->known_network),
|
||||
nm_settings_connection_get_id(data->mirror_connection));
|
||||
g_dbus_proxy_call(data->known_network,
|
||||
DBUS_INTERFACE_PROPERTIES ".Set",
|
||||
g_variant_new("(ssv)",
|
||||
NM_IWD_KNOWN_NETWORK_INTERFACE,
|
||||
"AutoConnect",
|
||||
g_variant_new_boolean(nm_autoconnectable)),
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
-1,
|
||||
NULL,
|
||||
known_network_update_cb,
|
||||
"AutoConnect");
|
||||
"iwd: changed Wi-Fi connection %s mirrored as IWD profile %s",
|
||||
nm_settings_connection_get_id(sett_conn),
|
||||
full_path);
|
||||
}
|
||||
|
||||
/* Look up an existing NMSettingsConnection for a network that has been
|
||||
|
|
@ -590,7 +700,7 @@ mirror_connection(NMIwdManager * self,
|
|||
NULL);
|
||||
g_object_set(G_OBJECT(s_wifi), NM_SETTING_WIRELESS_HIDDEN, hidden, NULL);
|
||||
} else {
|
||||
KnownNetworkData data = {known_network, settings_connection};
|
||||
KnownNetworkData data = {known_network, settings_connection, id};
|
||||
sett_conn_changed(settings_connection, 0, &data);
|
||||
}
|
||||
}
|
||||
|
|
@ -779,6 +889,7 @@ interface_added(GDBusObjectManager *object_manager,
|
|||
} else {
|
||||
data = g_slice_new0(KnownNetworkData);
|
||||
data->known_network = g_object_ref(proxy);
|
||||
data->id = id;
|
||||
g_hash_table_insert(priv->known_networks, id, data);
|
||||
}
|
||||
|
||||
|
|
@ -907,6 +1018,9 @@ connection_removed(NMSettings *settings, NMSettingsConnection *sett_conn, gpoint
|
|||
const guint8 * ssid_bytes;
|
||||
gsize ssid_len;
|
||||
NMSettingsConnection *new_mirror_conn;
|
||||
const char * iwd_dir;
|
||||
gs_free char * filename = NULL;
|
||||
gs_free char * full_path = NULL;
|
||||
|
||||
if (!nm_wifi_connection_get_iwd_ssid_and_security(conn, NULL, &id.security))
|
||||
return;
|
||||
|
|
@ -923,8 +1037,12 @@ connection_removed(NMSettings *settings, NMSettingsConnection *sett_conn, gpoint
|
|||
ssid_buf[ssid_len] = '\0';
|
||||
id.name = ssid_buf;
|
||||
data = g_hash_table_lookup(priv->known_networks, &id);
|
||||
if (!data)
|
||||
return;
|
||||
if (!data) {
|
||||
if (!g_utf8_validate((const char *) ssid_bytes, ssid_len, NULL))
|
||||
return;
|
||||
|
||||
goto try_delete_file;
|
||||
}
|
||||
|
||||
if (data->mirror_connection != sett_conn)
|
||||
return;
|
||||
|
|
@ -941,7 +1059,7 @@ connection_removed(NMSettings *settings, NMSettingsConnection *sett_conn, gpoint
|
|||
}
|
||||
|
||||
if (!priv->running)
|
||||
return;
|
||||
goto try_delete_file;
|
||||
|
||||
g_dbus_proxy_call(data->known_network,
|
||||
"Forget",
|
||||
|
|
@ -951,6 +1069,91 @@ connection_removed(NMSettings *settings, NMSettingsConnection *sett_conn, gpoint
|
|||
NULL,
|
||||
NULL,
|
||||
NULL);
|
||||
return;
|
||||
|
||||
try_delete_file:
|
||||
if (mirror_connection(self, &id, FALSE, NULL))
|
||||
return;
|
||||
|
||||
iwd_dir = nm_config_data_get_iwd_config_path(NM_CONFIG_GET_DATA);
|
||||
if (!iwd_dir || iwd_dir[0] == '\0' || !g_file_test(iwd_dir, G_FILE_TEST_IS_DIR))
|
||||
return;
|
||||
|
||||
filename = nm_wifi_utils_get_iwd_config_filename(id.name, ssid_len, id.security);
|
||||
full_path = g_strdup_printf("%s/%s", iwd_dir, filename);
|
||||
if (g_remove(full_path) == 0)
|
||||
_LOGD("IWD profile at %s removed", full_path);
|
||||
else if (errno != ENOENT)
|
||||
_LOGD("IWD profile at %s not removed: %s (%i)", full_path, strerror(errno), errno);
|
||||
}
|
||||
|
||||
static void
|
||||
connection_added(NMSettings *settings, NMSettingsConnection *sett_conn, gpointer user_data)
|
||||
{
|
||||
NMIwdManager * self = user_data;
|
||||
NMConnection * conn = nm_settings_connection_get_connection(sett_conn);
|
||||
NMSettingConnection *s_conn = nm_connection_get_setting_connection(conn);
|
||||
const char * iwd_dir;
|
||||
gs_free char * filename = NULL;
|
||||
gs_free char * full_path = NULL;
|
||||
gs_free_error GError *error = NULL;
|
||||
nm_auto_unref_keyfile GKeyFile *iwd_config = NULL;
|
||||
NMSettingsConnectionIntFlags flags;
|
||||
|
||||
if (!nm_streq(nm_settings_connection_get_connection_type(sett_conn), "802-11-wireless"))
|
||||
return;
|
||||
|
||||
iwd_dir = nm_config_data_get_iwd_config_path(NM_CONFIG_GET_DATA);
|
||||
if (!iwd_dir || iwd_dir[0] == '\0' || !g_file_test(iwd_dir, G_FILE_TEST_IS_DIR))
|
||||
return;
|
||||
|
||||
/* If this is a generated connection it may be ourselves creating it and
|
||||
* directly assigning it to a KnownNetwork's .mirror_connection.
|
||||
*/
|
||||
flags = nm_settings_connection_get_flags(sett_conn);
|
||||
if (NM_FLAGS_HAS(flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED))
|
||||
return;
|
||||
|
||||
/* If the connection has any permissions other than the default we don't
|
||||
* want to save it as an IWD profile. IWD will make it available for
|
||||
* everybody to attempt a connection, remove, or toggle "autoconnectable".
|
||||
*/
|
||||
if (s_conn && nm_setting_connection_get_num_permissions(s_conn)) {
|
||||
_LOGD("New Wi-Fi connection %s not mirrored as IWD profile because of non-default "
|
||||
"permissions",
|
||||
nm_settings_connection_get_id(sett_conn));
|
||||
return;
|
||||
}
|
||||
|
||||
iwd_config = nm_wifi_utils_connection_to_iwd_config(conn, &filename, &error);
|
||||
if (!iwd_config) {
|
||||
/* The error message here is not translated and it only goes in
|
||||
* the logs.
|
||||
*/
|
||||
_LOGD("New Wi-Fi connection %s not mirrored as IWD profile: %s",
|
||||
nm_settings_connection_get_id(sett_conn),
|
||||
error->message);
|
||||
return;
|
||||
}
|
||||
|
||||
full_path = g_strdup_printf("%s/%s", iwd_dir, filename);
|
||||
if (g_file_test(full_path, G_FILE_TEST_EXISTS)) {
|
||||
_LOGD("New Wi-Fi connection %s not mirrored as IWD profile because %s already exists",
|
||||
nm_settings_connection_get_id(sett_conn),
|
||||
full_path);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!g_key_file_save_to_file(iwd_config, full_path, &error)) {
|
||||
_LOGD("New Wi-Fi connection %s not mirrored as IWD profile: save error: %s",
|
||||
nm_settings_connection_get_id(sett_conn),
|
||||
error->message);
|
||||
return;
|
||||
}
|
||||
|
||||
_LOGD("New Wi-Fi connection %s mirrored as IWD profile %s",
|
||||
nm_settings_connection_get_id(sett_conn),
|
||||
full_path);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
|
|
@ -1305,11 +1508,31 @@ nm_iwd_manager_init(NMIwdManager *self)
|
|||
g_signal_connect(priv->manager, NM_MANAGER_DEVICE_ADDED, G_CALLBACK(device_added), self);
|
||||
g_signal_connect(priv->manager, NM_MANAGER_DEVICE_REMOVED, G_CALLBACK(device_removed), self);
|
||||
|
||||
/* The current logic is that we track all creations and removals but
|
||||
* for modifications we only listen to those connections that are
|
||||
* currently a KnownNetwork's mirror_connection. There may be multiple
|
||||
* NMSettingsConnections referring to the same SSID+Security type tuple
|
||||
* so to the same KnownNetwork. So to make connection profile editing
|
||||
* work at least for the simple cases, we track one NMSettingsConnection
|
||||
* out of those, and we map its changes to the IWD KnownNetwork.
|
||||
*
|
||||
* When an NMSettingsConnection is created by a user for a completely
|
||||
* new network and the settings are compatible with IWD, we create an
|
||||
* IWD KnownNetwork config file for it. IWD will notice that and a
|
||||
* KnownNetwork objects pops up on D-Bus. We look up a suitable
|
||||
* mirror_connection for it and only then subscribe to modification
|
||||
* signals. There are various different ways that this could be done,
|
||||
* it's not clear which one's the best.
|
||||
*/
|
||||
priv->settings = g_object_ref(NM_SETTINGS_GET);
|
||||
g_signal_connect(priv->settings,
|
||||
NM_SETTINGS_SIGNAL_CONNECTION_REMOVED,
|
||||
G_CALLBACK(connection_removed),
|
||||
self);
|
||||
g_signal_connect(priv->settings,
|
||||
NM_SETTINGS_SIGNAL_CONNECTION_ADDED,
|
||||
G_CALLBACK(connection_added),
|
||||
self);
|
||||
|
||||
priv->cancellable = g_cancellable_new();
|
||||
|
||||
|
|
|
|||
|
|
@ -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__ */
|
||||
|
|
|
|||
|
|
@ -97,6 +97,8 @@ typedef struct {
|
|||
NMGlobalDnsConfig *global_dns;
|
||||
|
||||
bool systemd_resolved : 1;
|
||||
|
||||
char *iwd_config_path;
|
||||
} NMConfigDataPrivate;
|
||||
|
||||
struct _NMConfigData {
|
||||
|
|
@ -341,6 +343,12 @@ nm_config_data_get_systemd_resolved(const NMConfigData *self)
|
|||
return NM_CONFIG_DATA_GET_PRIVATE(self)->systemd_resolved;
|
||||
}
|
||||
|
||||
const char *
|
||||
nm_config_data_get_iwd_config_path(const NMConfigData *self)
|
||||
{
|
||||
return NM_CONFIG_DATA_GET_PRIVATE(self)->iwd_config_path;
|
||||
}
|
||||
|
||||
gboolean
|
||||
nm_config_data_get_ignore_carrier(const NMConfigData *self, NMDevice *device)
|
||||
{
|
||||
|
|
@ -684,6 +692,7 @@ static const struct {
|
|||
NM_CONFIG_KEYFILE_KEY_MAIN_AUTH_POLKIT,
|
||||
NM_CONFIG_DEFAULT_MAIN_AUTH_POLKIT},
|
||||
{NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_DHCP, NM_CONFIG_DEFAULT_MAIN_DHCP},
|
||||
{NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_IWD_CONFIG_PATH, ""},
|
||||
{NM_CONFIG_KEYFILE_GROUP_LOGGING, "backend", NM_CONFIG_DEFAULT_LOGGING_BACKEND},
|
||||
{NM_CONFIG_KEYFILE_GROUP_LOGGING, "audit", NM_CONFIG_DEFAULT_LOGGING_AUDIT},
|
||||
};
|
||||
|
|
@ -1910,6 +1919,12 @@ constructed(GObject *object)
|
|||
if (!priv->global_dns)
|
||||
priv->global_dns = load_global_dns(priv->keyfile_intern, TRUE);
|
||||
|
||||
priv->iwd_config_path =
|
||||
nm_strstrip(g_key_file_get_string(priv->keyfile,
|
||||
NM_CONFIG_KEYFILE_GROUP_MAIN,
|
||||
NM_CONFIG_KEYFILE_KEY_MAIN_IWD_CONFIG_PATH,
|
||||
NULL));
|
||||
|
||||
G_OBJECT_CLASS(nm_config_data_parent_class)->constructed(object);
|
||||
}
|
||||
|
||||
|
|
@ -1996,6 +2011,8 @@ finalize(GObject *gobject)
|
|||
|
||||
nm_global_dns_config_free(priv->global_dns);
|
||||
|
||||
g_free(priv->iwd_config_path);
|
||||
|
||||
_match_section_infos_free(priv->connection_infos);
|
||||
_match_section_infos_free(priv->device_infos);
|
||||
|
||||
|
|
|
|||
|
|
@ -190,6 +190,8 @@ int nm_config_data_get_sriov_num_vfs(const NMConfigData *self, NMDevice *de
|
|||
|
||||
NMGlobalDnsConfig *nm_config_data_get_global_dns_config(const NMConfigData *self);
|
||||
|
||||
const char *nm_config_data_get_iwd_config_path(const NMConfigData *self);
|
||||
|
||||
extern const char *__start_connection_defaults[];
|
||||
extern const char *__stop_connection_defaults[];
|
||||
|
||||
|
|
|
|||
|
|
@ -843,7 +843,8 @@ static const ConfigGroup config_groups[] = {
|
|||
NM_CONFIG_KEYFILE_KEY_MAIN_PLUGINS,
|
||||
NM_CONFIG_KEYFILE_KEY_MAIN_RC_MANAGER,
|
||||
NM_CONFIG_KEYFILE_KEY_MAIN_SLAVES_ORDER,
|
||||
NM_CONFIG_KEYFILE_KEY_MAIN_SYSTEMD_RESOLVED, ),
|
||||
NM_CONFIG_KEYFILE_KEY_MAIN_SYSTEMD_RESOLVED,
|
||||
NM_CONFIG_KEYFILE_KEY_MAIN_IWD_CONFIG_PATH, ),
|
||||
},
|
||||
{
|
||||
.group = NM_CONFIG_KEYFILE_GROUP_LOGGING,
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
#define NM_CONFIG_KEYFILE_KEY_MAIN_RC_MANAGER "rc-manager"
|
||||
#define NM_CONFIG_KEYFILE_KEY_MAIN_SLAVES_ORDER "slaves-order"
|
||||
#define NM_CONFIG_KEYFILE_KEY_MAIN_SYSTEMD_RESOLVED "systemd-resolved"
|
||||
#define NM_CONFIG_KEYFILE_KEY_MAIN_IWD_CONFIG_PATH "iwd-config-path"
|
||||
|
||||
#define NM_CONFIG_KEYFILE_KEY_LOGGING_AUDIT "audit"
|
||||
#define NM_CONFIG_KEYFILE_KEY_LOGGING_BACKEND "backend"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue