File NetworkManager-CVE-2025-9615.patch of Package NetworkManager
From 8b8c624b4a4198f3098af26ac73ffd5298d51fdb Mon Sep 17 00:00:00 2001
From: Jonathan Kang <jonathankang@gnome.org>
Date: Wed, 11 Mar 2026 11:56:47 +0800
Subject: [PATCH] backport CVE-2025-9615 fixes
---
src/core/devices/nm-device-ethernet.c | 11 +-
src/core/devices/nm-device-macsec.c | 11 +-
src/core/devices/nm-device-private.h | 2 +
src/core/devices/nm-device-utils.c | 3 +-
src/core/devices/nm-device.c | 125 ++++++++++
src/core/devices/wifi/nm-device-wifi.c | 4 +-
src/core/nm-core-utils.c | 218 +++++++++++++++++-
src/core/nm-core-utils.h | 19 +-
src/core/supplicant/nm-supplicant-config.c | 170 +++++++++-----
src/core/supplicant/nm-supplicant-config.h | 4 +-
src/core/supplicant/nm-supplicant-interface.c | 191 ++++++++++++---
.../supplicant/tests/test-supplicant-config.c | 4 +-
src/libnm-core-impl/nm-setting-8021x.c | 97 +++++++-
src/libnm-core-impl/nm-setting-connection.c | 41 ++++
src/libnm-core-impl/nm-setting-private.h | 17 +-
src/libnm-core-impl/nm-setting.c | 29 +++
src/libnm-core-intern/nm-core-internal.h | 7 +
src/libnm-std-aux/nm-std-utils.c | 114 ++++++++-
src/libnm-std-aux/nm-std-utils.h | 4 +
src/meson.build | 3 +-
src/nm-daemon-helper/README.md | 11 -
src/nm-daemon-helper/meson.build | 15 --
src/{nm-priv-helper => nm-helpers}/README.md | 21 +-
.../meson.build | 20 ++
.../nm-daemon-helper.c | 33 +++
.../nm-priv-helper.c | 0
.../nm-priv-helper.conf | 0
.../org.freedesktop.nm_priv_helper.service.in | 0
28 files changed, 1031 insertions(+), 143 deletions(-)
delete mode 100644 src/nm-daemon-helper/README.md
delete mode 100644 src/nm-daemon-helper/meson.build
rename src/{nm-priv-helper => nm-helpers}/README.md (60%)
rename src/{nm-priv-helper => nm-helpers}/meson.build (67%)
rename src/{nm-daemon-helper => nm-helpers}/nm-daemon-helper.c (76%)
rename src/{nm-priv-helper => nm-helpers}/nm-priv-helper.c (100%)
rename src/{nm-priv-helper => nm-helpers}/nm-priv-helper.conf (100%)
rename src/{nm-priv-helper => nm-helpers}/org.freedesktop.nm_priv_helper.service.in (100%)
diff --git a/src/core/devices/nm-device-ethernet.c b/src/core/devices/nm-device-ethernet.c
index aedacc2..e4cf66d 100644
--- a/src/core/devices/nm-device-ethernet.c
+++ b/src/core/devices/nm-device-ethernet.c
@@ -630,10 +630,17 @@ build_supplicant_config(NMDeviceEthernet *self, GError **error)
mtu = nm_platform_link_get_mtu(nm_device_get_platform(NM_DEVICE(self)),
nm_device_get_ifindex(NM_DEVICE(self)));
- config = nm_supplicant_config_new(NM_SUPPL_CAP_MASK_NONE);
+ config = nm_supplicant_config_new(NM_SUPPL_CAP_MASK_NONE,
+ nm_utils_get_connection_first_permissions_user(connection));
security = nm_connection_get_setting_802_1x(connection);
- if (!nm_supplicant_config_add_setting_8021x(config, security, con_uuid, mtu, TRUE, error)) {
+ if (!nm_supplicant_config_add_setting_8021x(config,
+ security,
+ con_uuid,
+ mtu,
+ TRUE,
+ nm_device_get_private_files(NM_DEVICE(self)),
+ error)) {
g_prefix_error(error, "802-1x-setting: ");
g_clear_object(&config);
}
diff --git a/src/core/devices/nm-device-macsec.c b/src/core/devices/nm-device-macsec.c
index 130708b..ae12447 100644
--- a/src/core/devices/nm-device-macsec.c
+++ b/src/core/devices/nm-device-macsec.c
@@ -199,7 +199,8 @@ build_supplicant_config(NMDeviceMacsec *self, GError **error)
mtu = nm_platform_link_get_mtu(nm_device_get_platform(NM_DEVICE(self)),
nm_device_get_ifindex(NM_DEVICE(self)));
- config = nm_supplicant_config_new(NM_SUPPL_CAP_MASK_NONE);
+ config = nm_supplicant_config_new(NM_SUPPL_CAP_MASK_NONE,
+ nm_utils_get_connection_first_permissions_user(connection));
s_macsec = nm_device_get_applied_setting(NM_DEVICE(self), NM_TYPE_SETTING_MACSEC);
@@ -212,7 +213,13 @@ build_supplicant_config(NMDeviceMacsec *self, GError **error)
if (nm_setting_macsec_get_mode(s_macsec) == NM_SETTING_MACSEC_MODE_EAP) {
s_8021x = nm_connection_get_setting_802_1x(connection);
- if (!nm_supplicant_config_add_setting_8021x(config, s_8021x, con_uuid, mtu, TRUE, error)) {
+ if (!nm_supplicant_config_add_setting_8021x(config,
+ s_8021x,
+ con_uuid,
+ mtu,
+ TRUE,
+ nm_device_get_private_files(NM_DEVICE(self)),
+ error)) {
g_prefix_error(error, "802-1x-setting: ");
return NULL;
}
diff --git a/src/core/devices/nm-device-private.h b/src/core/devices/nm-device-private.h
index 013bc7f..f2c8ec8 100644
--- a/src/core/devices/nm-device-private.h
+++ b/src/core/devices/nm-device-private.h
@@ -178,4 +178,6 @@ void nm_device_auth_request(NMDevice *self,
void nm_device_link_properties_set(NMDevice *self, gboolean reapply);
+GHashTable *nm_device_get_private_files(NMDevice *self);
+
#endif /* NM_DEVICE_PRIVATE_H */
diff --git a/src/core/devices/nm-device-utils.c b/src/core/devices/nm-device-utils.c
index 2bf24ae..cbc3f40 100644
--- a/src/core/devices/nm-device-utils.c
+++ b/src/core/devices/nm-device-utils.c
@@ -222,7 +222,7 @@ resolve_addr_helper_cb(GObject *source, GAsyncResult *result, gpointer user_data
gs_free_error GError *error = NULL;
gs_free char *output = NULL;
- output = nm_utils_spawn_helper_finish(result, &error);
+ output = nm_utils_spawn_helper_finish_string(result, &error);
if (nm_utils_error_is_cancelled(error))
return;
@@ -239,6 +239,7 @@ resolve_addr_spawn_helper(ResolveAddrInfo *info)
nm_inet_ntop(info->addr_family, &info->address, addr_str);
_LOG2D(info, "start lookup via nm-daemon-helper");
nm_utils_spawn_helper(NM_MAKE_STRV("resolve-address", addr_str),
+ FALSE,
g_task_get_cancellable(info->task),
resolve_addr_helper_cb,
info);
diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c
index 3ab6185..2d13c07 100644
--- a/src/core/devices/nm-device.c
+++ b/src/core/devices/nm-device.c
@@ -321,6 +321,12 @@ typedef struct {
int addr_family;
} HostnameResolver;
+typedef enum {
+ PRIVATE_FILES_STATE_UNKNOWN = 0,
+ PRIVATE_FILES_STATE_READING,
+ PRIVATE_FILES_STATE_DONE,
+} PrivateFilesState;
+
/*****************************************************************************/
enum {
@@ -761,6 +767,13 @@ typedef struct _NMDevicePrivate {
guint64 rx_bytes;
} stats;
+ struct {
+ GHashTable *table;
+ GCancellable *cancellable;
+ char *user;
+ PrivateFilesState state;
+ } private_files;
+
bool mtu_force_set_done : 1;
bool needs_ip6_subnet : 1;
@@ -9986,6 +9999,49 @@ tc_commit(NMDevice *self)
return TRUE;
}
+static void
+read_private_files_cb(GObject *source_object, GAsyncResult *result, gpointer data)
+{
+ gs_unref_hashtable GHashTable *table = NULL;
+ gs_free_error GError *error = NULL;
+ NMDevice *self;
+ NMDevicePrivate *priv;
+
+ table = nm_utils_read_private_files_finish(result, &error);
+ if (nm_utils_error_is_cancelled(error))
+ return;
+
+ self = NM_DEVICE(data);
+ priv = NM_DEVICE_GET_PRIVATE(self);
+
+ if (error) {
+ NMConnection *connection = nm_device_get_applied_connection(self);
+
+ _LOGW(LOGD_DEVICE,
+ "could not read files for private connection %s owned by user '%s': %s",
+ connection ? nm_connection_get_uuid(connection) : NULL,
+ priv->private_files.user,
+ error->message);
+ nm_device_state_changed(self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+ return;
+ }
+
+ _LOGD(LOGD_DEVICE, "private files successfully read");
+
+ priv->private_files.state = PRIVATE_FILES_STATE_DONE;
+ priv->private_files.table = g_steal_pointer(&table);
+ g_clear_pointer(&priv->private_files.user, g_free);
+ g_clear_object(&priv->private_files.cancellable);
+
+ nm_device_activate_schedule_stage2_device_config(self, FALSE);
+}
+
+GHashTable *
+nm_device_get_private_files(NMDevice *self)
+{
+ return NM_DEVICE_GET_PRIVATE(self)->private_files.table;
+}
+
/*
* activate_stage2_device_config
*
@@ -9998,6 +10054,7 @@ activate_stage2_device_config(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMDeviceClass *klass;
+ NMConnection *applied;
NMActStageReturn ret;
NMSettingWired *s_wired;
gboolean no_firmware = FALSE;
@@ -10006,6 +10063,68 @@ activate_stage2_device_config(NMDevice *self)
nm_device_state_changed(self, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE);
+ applied = nm_device_get_applied_connection(self);
+
+ /* If the connection is private (owned by a specific user), we need to
+ * verify that the user has permission to access any files specified in
+ * the connection, such as certificates and keys. We do that by calling
+ * nm_utils_read_private_files() and saving the file contents in a hash
+ * table that can be accessed later during the activation. It is important
+ * to never access the files again to avoid TOCTOU bugs.
+ */
+ switch (priv->private_files.state) {
+ case PRIVATE_FILES_STATE_UNKNOWN:
+ {
+ gs_free const char **paths = NULL;
+ NMSettingConnection *s_con;
+ const char *user;
+
+ s_con = nm_connection_get_setting_connection(applied);
+ nm_assert(s_con);
+ user = _nm_setting_connection_get_first_permissions_user(s_con);
+
+ priv->private_files.user = g_strdup(user);
+ if (!priv->private_files.user) {
+ priv->private_files.state = PRIVATE_FILES_STATE_DONE;
+ break;
+ }
+
+ paths = nm_utils_get_connection_private_files_paths(applied);
+ if (!paths) {
+ priv->private_files.state = PRIVATE_FILES_STATE_DONE;
+ break;
+ }
+
+ if (_nm_setting_connection_get_num_permissions_users(s_con) > 1) {
+ _LOGW(LOGD_DEVICE,
+ "private connections with multiple users are not allowed to reference "
+ "certificates and keys on the filesystem. Specify only one user in the "
+ "connection.permissions property.");
+ nm_device_state_changed(self,
+ NM_DEVICE_STATE_FAILED,
+ NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+ return;
+ }
+
+ priv->private_files.state = PRIVATE_FILES_STATE_READING;
+ priv->private_files.cancellable = g_cancellable_new();
+
+ _LOGD(LOGD_DEVICE, "reading private files");
+ nm_utils_read_private_files(paths,
+ priv->private_files.user,
+ priv->private_files.cancellable,
+ read_private_files_cb,
+ self);
+ return;
+ }
+ case PRIVATE_FILES_STATE_READING:
+ /* wait */
+ return;
+ case PRIVATE_FILES_STATE_DONE:
+ /* proceed */
+ break;
+ }
+
if (!nm_device_sys_iface_state_is_external(self)) {
_ethtool_state_set(self);
nm_device_link_properties_set(self, FALSE);
@@ -15842,6 +15961,12 @@ nm_device_cleanup(NMDevice *self, NMDeviceStateReason reason, CleanupType cleanu
if (NM_DEVICE_GET_CLASS(self)->deactivate)
NM_DEVICE_GET_CLASS(self)->deactivate(self);
+ /* Clean up private files */
+ nm_clear_g_cancellable(&priv->private_files.cancellable);
+ g_clear_pointer(&priv->private_files.table, g_hash_table_unref);
+ g_clear_pointer(&priv->private_files.user, g_free);
+ priv->private_files.state = PRIVATE_FILES_STATE_UNKNOWN;
+
ifindex = nm_device_get_ip_ifindex(self);
if (cleanup_type == CLEANUP_TYPE_DECONFIGURE) {
diff --git a/src/core/devices/wifi/nm-device-wifi.c b/src/core/devices/wifi/nm-device-wifi.c
index 4377283..00f1289 100644
--- a/src/core/devices/wifi/nm-device-wifi.c
+++ b/src/core/devices/wifi/nm-device-wifi.c
@@ -2934,7 +2934,8 @@ build_supplicant_config(NMDeviceWifi *self,
s_wireless = nm_connection_get_setting_wireless(connection);
g_return_val_if_fail(s_wireless != NULL, NULL);
- config = nm_supplicant_config_new(nm_supplicant_interface_get_capabilities(priv->sup_iface));
+ config = nm_supplicant_config_new(nm_supplicant_interface_get_capabilities(priv->sup_iface),
+ nm_utils_get_connection_first_permissions_user(connection));
/* Warn if AP mode may not be supported */
if (nm_streq0(nm_setting_wireless_get_mode(s_wireless), NM_SETTING_WIRELESS_MODE_AP)
@@ -3010,6 +3011,7 @@ build_supplicant_config(NMDeviceWifi *self,
mtu,
pmf,
fils,
+ nm_device_get_private_files(NM_DEVICE(self)),
error)) {
g_prefix_error(error, "802-11-wireless-security: ");
goto error;
diff --git a/src/core/nm-core-utils.c b/src/core/nm-core-utils.c
index 5442efb..48f7dce 100644
--- a/src/core/nm-core-utils.c
+++ b/src/core/nm-core-utils.c
@@ -4884,6 +4884,7 @@ typedef struct {
int child_stdin;
int child_stdout;
int child_stderr;
+ gboolean binary_output;
GSource *input_source;
GSource *output_source;
GSource *error_source;
@@ -4963,7 +4964,16 @@ helper_complete(HelperInfo *info, GError *error)
}
nm_clear_g_cancellable_disconnect(g_task_get_cancellable(info->task), &info->cancellable_id);
- g_task_return_pointer(info->task, nm_str_buf_finalize(&info->in_buffer, NULL), g_free);
+ if (info->binary_output) {
+ g_task_return_pointer(
+ info->task,
+ g_bytes_new(nm_str_buf_get_str_unsafe(&info->in_buffer), info->in_buffer.len),
+ (GDestroyNotify) (g_bytes_unref));
+ } else {
+ g_task_return_pointer(info->task,
+ nm_str_buf_finalize(&info->in_buffer, NULL) ?: g_new0(char, 1),
+ g_free);
+ }
helper_info_free(info);
}
@@ -5106,6 +5116,7 @@ helper_cancelled(GObject *object, gpointer user_data)
void
nm_utils_spawn_helper(const char *const *args,
+ gboolean binary_output,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer cb_data)
@@ -5121,9 +5132,14 @@ nm_utils_spawn_helper(const char *const *args,
info = g_new(HelperInfo, 1);
*info = (HelperInfo){
- .task = nm_g_task_new(NULL, cancellable, nm_utils_spawn_helper, callback, cb_data),
+ .task = nm_g_task_new(NULL, cancellable, nm_utils_spawn_helper, callback, cb_data),
+ .binary_output = binary_output,
};
+ /* Store if the caller requested binary output so that we can check later
+ * that the right result function is called. */
+ g_task_set_task_data(info->task, GINT_TO_POINTER(binary_output), NULL);
+
if (!g_spawn_async_with_pipes("/",
(char **) NM_MAKE_STRV(LIBEXECDIR "/nm-daemon-helper"),
(char **) NM_MAKE_STRV(),
@@ -5234,11 +5250,25 @@ nm_utils_spawn_helper(const char *const *args,
}
char *
-nm_utils_spawn_helper_finish(GAsyncResult *result, GError **error)
+nm_utils_spawn_helper_finish_string(GAsyncResult *result, GError **error)
{
GTask *task = G_TASK(result);
nm_assert(nm_g_task_is_valid(result, NULL, nm_utils_spawn_helper));
+ /* Check binary_output */
+ nm_assert(GPOINTER_TO_INT(g_task_get_task_data(task)) == FALSE);
+
+ return g_task_propagate_pointer(task, error);
+}
+
+GBytes *
+nm_utils_spawn_helper_finish_binary(GAsyncResult *result, GError **error)
+{
+ GTask *task = G_TASK(result);
+
+ nm_assert(nm_g_task_is_valid(result, NULL, nm_utils_spawn_helper));
+ /* Check binary_output */
+ nm_assert(GPOINTER_TO_INT(g_task_get_task_data(task)) == TRUE);
return g_task_propagate_pointer(task, error);
}
@@ -5345,3 +5375,185 @@ nm_utils_shorten_hostname(const char *hostname, char **shortened)
*shortened = g_steal_pointer(&s);
return TRUE;
}
+
+const char *
+nm_utils_get_connection_first_permissions_user(NMConnection *connection)
+{
+ NMSettingConnection *s_con;
+
+ s_con = nm_connection_get_setting_connection(connection);
+ nm_assert(s_con);
+
+ return _nm_setting_connection_get_first_permissions_user(s_con);
+}
+
+/*****************************************************************************/
+
+const char **
+nm_utils_get_connection_private_files_paths(NMConnection *connection)
+{
+ GPtrArray *files;
+ gs_free NMSetting **settings = NULL;
+ guint num_settings;
+ guint i;
+
+ files = g_ptr_array_new();
+ settings = nm_connection_get_settings(connection, &num_settings);
+ for (i = 0; i < num_settings; i++) {
+ _nm_setting_get_private_files(settings[i], files);
+ }
+ g_ptr_array_add(files, NULL);
+
+ return (const char **) g_ptr_array_free(files, files->len == 1);
+}
+
+typedef struct _ReadInfo ReadInfo;
+
+typedef struct {
+ char *path;
+ ReadInfo *read_info;
+} FileInfo;
+
+struct _ReadInfo {
+ GTask *task;
+ GHashTable *table;
+ GPtrArray *file_infos; /* of FileInfo */
+ GError *first_error;
+ guint num_pending;
+};
+
+static void
+read_file_helper_cb(GObject *source, GAsyncResult *result, gpointer user_data)
+{
+ FileInfo *file_info = user_data;
+ ReadInfo *read_info = file_info->read_info;
+ gs_unref_bytes GBytes *output = NULL;
+ gs_free_error GError *error = NULL;
+
+ output = nm_utils_spawn_helper_finish_binary(result, &error);
+
+ nm_assert(read_info->num_pending > 0);
+ read_info->num_pending--;
+
+ if (nm_utils_error_is_cancelled(error)) {
+ /* nop */
+ } else if (error) {
+ nm_log_dbg(LOGD_CORE,
+ "read-private-files: failed to read file '%s': %s",
+ file_info->path,
+ error->message);
+ if (!read_info->first_error) {
+ /* @error just says "helper process exited with status X".
+ * Return a more human-friendly one. */
+ read_info->first_error = g_error_new(NM_UTILS_ERROR,
+ NM_UTILS_ERROR_UNKNOWN,
+ "error reading file '%s'",
+ file_info->path);
+ }
+ } else {
+ nm_log_dbg(LOGD_SUPPLICANT,
+ "read-private-files: successfully read file '%s'",
+ file_info->path);
+
+ /* Store the file contents in the hash table */
+ if (!read_info->table) {
+ read_info->table = g_hash_table_new_full(nm_str_hash,
+ g_str_equal,
+ g_free,
+ (GDestroyNotify) g_bytes_unref);
+ }
+ g_hash_table_insert(read_info->table,
+ g_steal_pointer(&file_info->path),
+ g_steal_pointer(&output));
+ }
+
+ g_clear_pointer(&file_info->path, g_free);
+
+ /* If all operations are completed, return */
+ if (read_info->num_pending == 0) {
+ if (read_info->first_error) {
+ g_task_return_error(read_info->task, g_steal_pointer(&read_info->first_error));
+ } else {
+ g_task_return_pointer(read_info->task,
+ g_steal_pointer(&read_info->table),
+ (GDestroyNotify) g_hash_table_unref);
+ }
+
+ if (read_info->table)
+ g_hash_table_unref(read_info->table);
+ if (read_info->file_infos)
+ g_ptr_array_unref(read_info->file_infos);
+
+ g_object_unref(read_info->task);
+ g_free(read_info);
+ }
+}
+
+/**
+ * nm_utils_read_private_files:
+ * @paths: array of file paths to be read
+ * @user: name of the user to impersonate when reading the files
+ * @cancellable: cancellable to cancel the operation
+ * @callback: callback to invoke on completion
+ * @cb_data: data for @callback
+ *
+ * Reads the given list of files @paths on behalf of user @user. Invokes
+ * @callback asynchronously on completion. The callback must use
+ * nm_utils_read_private_files_finish() to obtain the result.
+ */
+void
+nm_utils_read_private_files(const char *const *paths,
+ const char *user,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer cb_data)
+{
+ ReadInfo *read_info;
+ FileInfo *file_info;
+ guint i;
+
+ g_return_if_fail(paths && paths[0]);
+ g_return_if_fail(cancellable);
+ g_return_if_fail(callback);
+ g_return_if_fail(cb_data);
+
+ read_info = g_new(ReadInfo, 1);
+ *read_info = (ReadInfo) {
+ .task = nm_g_task_new(NULL, cancellable, nm_utils_read_private_files, callback, cb_data),
+ .file_infos = g_ptr_array_new_with_free_func(g_free),
+ };
+
+ for (i = 0; paths[i]; i++) {
+ file_info = g_new(FileInfo, 1);
+ *file_info = (FileInfo) {
+ .path = g_strdup(paths[i]),
+ .read_info = read_info,
+ };
+ g_ptr_array_add(read_info->file_infos, file_info);
+ read_info->num_pending++;
+
+ nm_utils_spawn_helper(NM_MAKE_STRV("read-file-as-user", user, paths[i]),
+ TRUE,
+ cancellable,
+ read_file_helper_cb,
+ file_info);
+ }
+}
+
+/**
+ * nm_utils_read_private_files_finish:
+ * @result: the GAsyncResult
+ * @error: on return, the error
+ *
+ * Returns the files read by nm_utils_read_private_files(). The return value
+ * is a hash table {char * -> GBytes *}. Free it with g_hash_table_unref().
+ */
+GHashTable *
+nm_utils_read_private_files_finish(GAsyncResult *result, GError **error)
+{
+ GTask *task = G_TASK(result);
+
+ nm_assert(nm_g_task_is_valid(result, NULL, nm_utils_read_private_files));
+
+ return g_task_propagate_pointer(task, error);
+}
diff --git a/src/core/nm-core-utils.h b/src/core/nm-core-utils.h
index 5511250..cd900a6 100644
--- a/src/core/nm-core-utils.h
+++ b/src/core/nm-core-utils.h
@@ -471,11 +471,13 @@ guint8 nm_wifi_utils_level_to_quality(int val);
/*****************************************************************************/
void nm_utils_spawn_helper(const char *const *args,
+ gboolean binary_output,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer cb_data);
-char *nm_utils_spawn_helper_finish(GAsyncResult *result, GError **error);
+char *nm_utils_spawn_helper_finish_string(GAsyncResult *result, GError **error);
+GBytes *nm_utils_spawn_helper_finish_binary(GAsyncResult *result, GError **error);
/*****************************************************************************/
@@ -483,4 +485,19 @@ uid_t nm_utils_get_nm_uid(void);
gid_t nm_utils_get_nm_gid(void);
+/*****************************************************************************/
+
+const char *nm_utils_get_connection_first_permissions_user(NMConnection *connection);
+
+/*****************************************************************************/
+
+const char **nm_utils_get_connection_private_files_paths(NMConnection *connection);
+
+void nm_utils_read_private_files(const char *const *paths,
+ const char *user,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer cb_data);
+GHashTable *nm_utils_read_private_files_finish(GAsyncResult *result, GError **error);
+
#endif /* __NM_CORE_UTILS_H__ */
diff --git a/src/core/supplicant/nm-supplicant-config.c b/src/core/supplicant/nm-supplicant-config.c
index 1d9372e..bdde994 100644
--- a/src/core/supplicant/nm-supplicant-config.c
+++ b/src/core/supplicant/nm-supplicant-config.c
@@ -30,6 +30,7 @@ typedef struct {
typedef struct {
GHashTable *config;
GHashTable *blobs;
+ char *private_user;
NMSupplCapMask capabilities;
guint32 ap_scan;
bool fast_required : 1;
@@ -60,7 +61,7 @@ _get_capability(NMSupplicantConfigPrivate *priv, NMSupplCapType type)
}
NMSupplicantConfig *
-nm_supplicant_config_new(NMSupplCapMask capabilities)
+nm_supplicant_config_new(NMSupplCapMask capabilities, const char *private_user)
{
NMSupplicantConfigPrivate *priv;
NMSupplicantConfig *self;
@@ -69,6 +70,7 @@ nm_supplicant_config_new(NMSupplCapMask capabilities)
priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self);
priv->capabilities = capabilities;
+ priv->private_user = g_strdup(private_user);
return self;
}
@@ -258,19 +260,19 @@ static gboolean
nm_supplicant_config_add_blob_for_connection(NMSupplicantConfig *self,
GBytes *field,
const char *name,
- const char *con_uid,
+ const char *con_uuid,
GError **error)
{
if (field && g_bytes_get_size(field)) {
- gs_free char *uid = NULL;
+ gs_free char *blob_id = NULL;
char *p;
- uid = g_strdup_printf("%s-%s", con_uid, name);
- for (p = uid; *p; p++) {
+ blob_id = g_strdup_printf("%s-%s", con_uuid, name);
+ for (p = blob_id; *p; p++) {
if (*p == '/')
*p = '-';
}
- if (!nm_supplicant_config_add_blob(self, name, field, uid, error))
+ if (!nm_supplicant_config_add_blob(self, name, field, blob_id, error))
return FALSE;
}
return TRUE;
@@ -283,6 +285,7 @@ nm_supplicant_config_finalize(GObject *object)
g_hash_table_destroy(priv->config);
nm_clear_pointer(&priv->blobs, g_hash_table_destroy);
+ nm_clear_pointer(&priv->private_user, g_free);
G_OBJECT_CLASS(nm_supplicant_config_parent_class)->finalize(object);
}
@@ -822,6 +825,7 @@ nm_supplicant_config_add_setting_wireless_security(NMSupplicantConfig
guint32 mtu,
NMSettingWirelessSecurityPmf pmf,
NMSettingWirelessSecurityFils fils,
+ GHashTable *files,
GError **error)
{
NMSupplicantConfigPrivate *priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self);
@@ -1176,6 +1180,7 @@ nm_supplicant_config_add_setting_wireless_security(NMSupplicantConfig
con_uuid,
mtu,
FALSE,
+ files,
error))
return FALSE;
}
@@ -1257,6 +1262,7 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
const char *con_uuid,
guint32 mtu,
gboolean wired,
+ GHashTable *files,
GError **error)
{
NMSupplicantConfigPrivate *priv;
@@ -1486,24 +1492,21 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
}
/* CA certificate */
+ path = NULL;
+ bytes = NULL;
if (ca_cert_override) {
- if (!add_string_val(self, ca_cert_override, "ca_cert", FALSE, NULL, error))
- return FALSE;
+ /* This is a build-time-configured system-wide file path, no need to pass
+ * it as a blob */
+ path = ca_cert_override;
} else {
switch (nm_setting_802_1x_get_ca_cert_scheme(setting)) {
case NM_SETTING_802_1X_CK_SCHEME_BLOB:
bytes = nm_setting_802_1x_get_ca_cert_blob(setting);
- if (!nm_supplicant_config_add_blob_for_connection(self,
- bytes,
- "ca_cert",
- con_uuid,
- error))
- return FALSE;
break;
case NM_SETTING_802_1X_CK_SCHEME_PATH:
path = nm_setting_802_1x_get_ca_cert_path(setting);
- if (!add_string_val(self, path, "ca_cert", FALSE, NULL, error))
- return FALSE;
+ if (priv->private_user)
+ bytes = nm_g_hash_table_lookup(files, path);
break;
case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
if (!add_pkcs11_uri_with_pin(self,
@@ -1519,26 +1522,32 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
break;
}
}
+ if (bytes) {
+ if (!nm_supplicant_config_add_blob_for_connection(self, bytes, "ca_cert", con_uuid, error))
+ return FALSE;
+ } else if (path) {
+ /* Private connections cannot use paths other than the system CA store */
+ g_return_val_if_fail(ca_cert_override || !priv->private_user, FALSE);
+ if (!add_string_val(self, path, "ca_cert", FALSE, NULL, error))
+ return FALSE;
+ }
/* Phase 2 CA certificate */
+ path = NULL;
+ bytes = NULL;
if (ca_cert_override) {
- if (!add_string_val(self, ca_cert_override, "ca_cert2", FALSE, NULL, error))
- return FALSE;
+ /* This is a build-time-configured system-wide file path, no need to pass
+ * it as a blob */
+ path = ca_cert_override;
} else {
switch (nm_setting_802_1x_get_phase2_ca_cert_scheme(setting)) {
case NM_SETTING_802_1X_CK_SCHEME_BLOB:
bytes = nm_setting_802_1x_get_phase2_ca_cert_blob(setting);
- if (!nm_supplicant_config_add_blob_for_connection(self,
- bytes,
- "ca_cert2",
- con_uuid,
- error))
- return FALSE;
break;
case NM_SETTING_802_1X_CK_SCHEME_PATH:
path = nm_setting_802_1x_get_phase2_ca_cert_path(setting);
- if (!add_string_val(self, path, "ca_cert2", FALSE, NULL, error))
- return FALSE;
+ if (priv->private_user)
+ bytes = nm_g_hash_table_lookup(files, path);
break;
case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
if (!add_pkcs11_uri_with_pin(
@@ -1555,6 +1564,15 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
break;
}
}
+ if (bytes) {
+ if (!nm_supplicant_config_add_blob_for_connection(self, bytes, "ca_cert2", con_uuid, error))
+ return FALSE;
+ } else if (path) {
+ /* Private connections cannot use paths other than the system CA store */
+ g_return_val_if_fail(ca_cert_override || !priv->private_user, FALSE);
+ if (!add_string_val(self, path, "ca_cert2", FALSE, NULL, error))
+ return FALSE;
+ }
/* Subject match */
value = nm_setting_802_1x_get_subject_match(setting);
@@ -1606,21 +1624,17 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
/* Private key */
added = FALSE;
+ path = NULL;
+ bytes = NULL;
switch (nm_setting_802_1x_get_private_key_scheme(setting)) {
case NM_SETTING_802_1X_CK_SCHEME_BLOB:
bytes = nm_setting_802_1x_get_private_key_blob(setting);
- if (!nm_supplicant_config_add_blob_for_connection(self,
- bytes,
- "private_key",
- con_uuid,
- error))
- return FALSE;
added = TRUE;
break;
case NM_SETTING_802_1X_CK_SCHEME_PATH:
path = nm_setting_802_1x_get_private_key_path(setting);
- if (!add_string_val(self, path, "private_key", FALSE, NULL, error))
- return FALSE;
+ if (priv->private_user)
+ bytes = nm_g_hash_table_lookup(files, path);
added = TRUE;
break;
case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
@@ -1637,6 +1651,19 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
default:
break;
}
+ if (bytes) {
+ if (!nm_supplicant_config_add_blob_for_connection(self,
+ bytes,
+ "private_key",
+ con_uuid,
+ error))
+ return FALSE;
+ } else if (path) {
+ /* Private connections cannot use paths */
+ g_return_val_if_fail(!priv->private_user, FALSE);
+ if (!add_string_val(self, path, "private_key", FALSE, NULL, error))
+ return FALSE;
+ }
if (added) {
NMSetting8021xCKFormat format;
@@ -1660,20 +1687,16 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
/* Only add the client cert if the private key is not PKCS#12, as
* wpa_supplicant configuration directs us to do.
*/
+ path = NULL;
+ bytes = NULL;
switch (nm_setting_802_1x_get_client_cert_scheme(setting)) {
case NM_SETTING_802_1X_CK_SCHEME_BLOB:
bytes = nm_setting_802_1x_get_client_cert_blob(setting);
- if (!nm_supplicant_config_add_blob_for_connection(self,
- bytes,
- "client_cert",
- con_uuid,
- error))
- return FALSE;
break;
case NM_SETTING_802_1X_CK_SCHEME_PATH:
path = nm_setting_802_1x_get_client_cert_path(setting);
- if (!add_string_val(self, path, "client_cert", FALSE, NULL, error))
- return FALSE;
+ if (priv->private_user)
+ bytes = nm_g_hash_table_lookup(files, path);
break;
case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
if (!add_pkcs11_uri_with_pin(
@@ -1689,26 +1712,35 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
default:
break;
}
+ if (bytes) {
+ if (!nm_supplicant_config_add_blob_for_connection(self,
+ bytes,
+ "client_cert",
+ con_uuid,
+ error))
+ return FALSE;
+ } else if (path) {
+ /* Private connections cannot use paths */
+ g_return_val_if_fail(!priv->private_user, FALSE);
+ if (!add_string_val(self, path, "client_cert", FALSE, NULL, error))
+ return FALSE;
+ }
}
}
/* Phase 2 private key */
added = FALSE;
+ path = NULL;
+ bytes = NULL;
switch (nm_setting_802_1x_get_phase2_private_key_scheme(setting)) {
case NM_SETTING_802_1X_CK_SCHEME_BLOB:
bytes = nm_setting_802_1x_get_phase2_private_key_blob(setting);
- if (!nm_supplicant_config_add_blob_for_connection(self,
- bytes,
- "private_key2",
- con_uuid,
- error))
- return FALSE;
added = TRUE;
break;
case NM_SETTING_802_1X_CK_SCHEME_PATH:
path = nm_setting_802_1x_get_phase2_private_key_path(setting);
- if (!add_string_val(self, path, "private_key2", FALSE, NULL, error))
- return FALSE;
+ if (priv->private_user)
+ bytes = nm_g_hash_table_lookup(files, path);
added = TRUE;
break;
case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
@@ -1726,6 +1758,19 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
default:
break;
}
+ if (bytes) {
+ if (!nm_supplicant_config_add_blob_for_connection(self,
+ bytes,
+ "private_key2",
+ con_uuid,
+ error))
+ return FALSE;
+ } else if (path) {
+ /* Private connections cannot use paths */
+ g_return_val_if_fail(!priv->private_user, FALSE);
+ if (!add_string_val(self, path, "private_key2", FALSE, NULL, error))
+ return FALSE;
+ }
if (added) {
NMSetting8021xCKFormat format;
@@ -1749,20 +1794,16 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
/* Only add the client cert if the private key is not PKCS#12, as
* wpa_supplicant configuration directs us to do.
*/
+ path = NULL;
+ bytes = NULL;
switch (nm_setting_802_1x_get_phase2_client_cert_scheme(setting)) {
case NM_SETTING_802_1X_CK_SCHEME_BLOB:
bytes = nm_setting_802_1x_get_phase2_client_cert_blob(setting);
- if (!nm_supplicant_config_add_blob_for_connection(self,
- bytes,
- "client_cert2",
- con_uuid,
- error))
- return FALSE;
break;
case NM_SETTING_802_1X_CK_SCHEME_PATH:
path = nm_setting_802_1x_get_phase2_client_cert_path(setting);
- if (!add_string_val(self, path, "client_cert2", FALSE, NULL, error))
- return FALSE;
+ if (priv->private_user)
+ bytes = nm_g_hash_table_lookup(files, path);
break;
case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
if (!add_pkcs11_uri_with_pin(
@@ -1778,6 +1819,19 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
default:
break;
}
+ if (bytes) {
+ if (!nm_supplicant_config_add_blob_for_connection(self,
+ bytes,
+ "client_cert2",
+ con_uuid,
+ error))
+ return FALSE;
+ } else if (path) {
+ /* Private connections cannot use paths */
+ g_return_val_if_fail(!priv->private_user, FALSE);
+ if (!add_string_val(self, path, "client_cert2", FALSE, NULL, error))
+ return FALSE;
+ }
}
}
diff --git a/src/core/supplicant/nm-supplicant-config.h b/src/core/supplicant/nm-supplicant-config.h
index 585cf95..7033fe7 100644
--- a/src/core/supplicant/nm-supplicant-config.h
+++ b/src/core/supplicant/nm-supplicant-config.h
@@ -29,7 +29,7 @@ typedef struct _NMSupplicantConfigClass NMSupplicantConfigClass;
GType nm_supplicant_config_get_type(void);
-NMSupplicantConfig *nm_supplicant_config_new(NMSupplCapMask capabilities);
+NMSupplicantConfig *nm_supplicant_config_new(NMSupplCapMask capabilities, const char *private_user);
guint32 nm_supplicant_config_get_ap_scan(NMSupplicantConfig *self);
@@ -57,6 +57,7 @@ gboolean nm_supplicant_config_add_setting_wireless_security(NMSupplicantConfig
guint32 mtu,
NMSettingWirelessSecurityPmf pmf,
NMSettingWirelessSecurityFils fils,
+ GHashTable *files,
GError **error);
gboolean nm_supplicant_config_add_no_security(NMSupplicantConfig *self, GError **error);
@@ -66,6 +67,7 @@ gboolean nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
const char *con_uuid,
guint32 mtu,
gboolean wired,
+ GHashTable *files,
GError **error);
gboolean nm_supplicant_config_add_setting_macsec(NMSupplicantConfig *self,
diff --git a/src/core/supplicant/nm-supplicant-interface.c b/src/core/supplicant/nm-supplicant-interface.c
index 1852572..247cc81 100644
--- a/src/core/supplicant/nm-supplicant-interface.c
+++ b/src/core/supplicant/nm-supplicant-interface.c
@@ -46,6 +46,7 @@ typedef struct {
gpointer user_data;
guint fail_on_idle_id;
guint blobs_left;
+ guint remove_blobs_left;
guint calls_left;
struct _AddNetworkData *add_network_data;
} AssocData;
@@ -2257,6 +2258,7 @@ assoc_add_blob_cb(GObject *source, GAsyncResult *result, gpointer user_data)
return;
}
+ nm_assert(priv->assoc_data->blobs_left > 0);
priv->assoc_data->blobs_left--;
_LOGT("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: blob added (%u left)",
NM_HASH_OBFUSCATE_PTR(priv->assoc_data),
@@ -2265,6 +2267,148 @@ assoc_add_blob_cb(GObject *source, GAsyncResult *result, gpointer user_data)
assoc_call_select_network(self);
}
+static void
+assoc_add_blobs(NMSupplicantInterface *self)
+{
+ NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self);
+ GHashTable *blobs;
+ GHashTableIter iter;
+ const char *blob_name;
+ GBytes *blob_data;
+
+ blobs = nm_supplicant_config_get_blobs(priv->assoc_data->cfg);
+ priv->assoc_data->blobs_left = nm_g_hash_table_size(blobs);
+
+ _LOGT("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: need to add %u blobs",
+ NM_HASH_OBFUSCATE_PTR(priv->assoc_data),
+ priv->assoc_data->blobs_left);
+
+ if (priv->assoc_data->blobs_left == 0) {
+ assoc_call_select_network(self);
+ return;
+ }
+
+ g_hash_table_iter_init(&iter, blobs);
+ while (g_hash_table_iter_next(&iter, (gpointer) &blob_name, (gpointer) &blob_data)) {
+ _LOGT("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: adding blob '%s'",
+ NM_HASH_OBFUSCATE_PTR(priv->assoc_data),
+ blob_name);
+ _dbus_connection_call(
+ self,
+ NM_WPAS_DBUS_IFACE_INTERFACE,
+ "AddBlob",
+ g_variant_new("(s@ay)", blob_name, nm_g_bytes_to_variant_ay(blob_data)),
+ G_VARIANT_TYPE("()"),
+ G_DBUS_CALL_FLAGS_NONE,
+ DBUS_TIMEOUT_MSEC,
+ priv->assoc_data->cancellable,
+ assoc_add_blob_cb,
+ self);
+ }
+}
+
+static void
+assoc_remove_blob_cb(GObject *source, GAsyncResult *result, gpointer user_data)
+{
+ NMSupplicantInterface *self;
+ NMSupplicantInterfacePrivate *priv;
+ gs_free_error GError *error = NULL;
+ gs_unref_variant GVariant *res = NULL;
+
+ res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error);
+ if (nm_utils_error_is_cancelled(error))
+ return;
+
+ self = NM_SUPPLICANT_INTERFACE(user_data);
+ priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self);
+
+ /* We don't consider a failure fatal. The new association might be able
+ * to proceed even with the existing blobs, if they don't conflict with new
+ * ones. */
+
+ nm_assert(priv->assoc_data->remove_blobs_left > 0);
+ priv->assoc_data->remove_blobs_left--;
+
+ if (error) {
+ g_dbus_error_strip_remote_error(error);
+ _LOGD("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: failed to delete blob: %s",
+ NM_HASH_OBFUSCATE_PTR(priv->assoc_data),
+ error->message);
+ } else {
+ _LOGT("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: blob removed (%u left)",
+ NM_HASH_OBFUSCATE_PTR(priv->assoc_data),
+ priv->assoc_data->remove_blobs_left);
+ }
+
+ if (priv->assoc_data->remove_blobs_left == 0)
+ assoc_add_blobs(self);
+}
+
+static void
+assoc_get_blobs_cb(GObject *source, GAsyncResult *result, gpointer user_data)
+{
+ NMSupplicantInterface *self;
+ NMSupplicantInterfacePrivate *priv;
+ gs_free_error GError *error = NULL;
+ gs_unref_variant GVariant *res = NULL;
+ gs_unref_variant GVariant *value = NULL;
+ GVariantIter iter;
+ const char *blob_name;
+ GVariant *blob_data;
+
+ res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error);
+ if (nm_utils_error_is_cancelled(error))
+ return;
+
+ self = NM_SUPPLICANT_INTERFACE(user_data);
+ priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self);
+
+ if (error) {
+ _LOGD("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: failed to get blob list: %s",
+ NM_HASH_OBFUSCATE_PTR(priv->assoc_data),
+ error->message);
+ assoc_add_blobs(self);
+ return;
+ }
+
+ g_variant_get(res, "(v)", &value);
+
+ /* While the "Blobs" property is documented as type "as", it is actually "a{say}" */
+ if (!value || !g_variant_is_of_type(value, G_VARIANT_TYPE("a{say}"))) {
+ _LOGD("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: failed to get blob list: wrong return type %s",
+ NM_HASH_OBFUSCATE_PTR(priv->assoc_data),
+ value ? g_variant_get_type_string(value) : "NULL");
+ assoc_add_blobs(self);
+ return;
+ }
+
+ g_variant_iter_init(&iter, value);
+ priv->assoc_data->remove_blobs_left = g_variant_iter_n_children(&iter);
+ _LOGT("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: need to delete %u blobs",
+ NM_HASH_OBFUSCATE_PTR(priv->assoc_data),
+ priv->assoc_data->remove_blobs_left);
+
+ if (priv->assoc_data->remove_blobs_left == 0) {
+ assoc_add_blobs(self);
+ } else {
+ while (g_variant_iter_loop(&iter, "{&s@ay}", &blob_name, &blob_data)) {
+ _LOGT("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: removing blob '%s'",
+ NM_HASH_OBFUSCATE_PTR(priv->assoc_data),
+ blob_name);
+ _dbus_connection_call(self,
+ NM_WPAS_DBUS_IFACE_INTERFACE,
+ "RemoveBlob",
+ g_variant_new("(s)", blob_name),
+ G_VARIANT_TYPE("()"),
+ G_DBUS_CALL_FLAGS_NONE,
+ DBUS_TIMEOUT_MSEC,
+ priv->assoc_data->cancellable,
+ assoc_remove_blob_cb,
+ self);
+ }
+ }
+}
+
static void
assoc_add_network_cb(GObject *source, GAsyncResult *result, gpointer user_data)
{
@@ -2272,12 +2416,8 @@ assoc_add_network_cb(GObject *source, GAsyncResult *result, gpointer user_data)
AssocData *assoc_data;
NMSupplicantInterface *self;
NMSupplicantInterfacePrivate *priv;
- gs_unref_variant GVariant *res = NULL;
- gs_free_error GError *error = NULL;
- GHashTable *blobs;
- GHashTableIter iter;
- const char *blob_name;
- GBytes *blob_data;
+ gs_unref_variant GVariant *res = NULL;
+ gs_free_error GError *error = NULL;
nm_auto_ref_string NMRefString *name_owner = NULL;
nm_auto_ref_string NMRefString *object_path = NULL;
@@ -2329,34 +2469,21 @@ assoc_add_network_cb(GObject *source, GAsyncResult *result, gpointer user_data)
nm_assert(!priv->net_path);
g_variant_get(res, "(o)", &priv->net_path);
- /* Send blobs first; otherwise jump to selecting the network */
- blobs = nm_supplicant_config_get_blobs(priv->assoc_data->cfg);
- priv->assoc_data->blobs_left = blobs ? g_hash_table_size(blobs) : 0u;
-
- _LOGT("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: network added (%s) (%u blobs left)",
+ _LOGT("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: network added (%s)",
NM_HASH_OBFUSCATE_PTR(priv->assoc_data),
- priv->net_path,
- priv->assoc_data->blobs_left);
-
- if (priv->assoc_data->blobs_left == 0) {
- assoc_call_select_network(self);
- return;
- }
+ priv->net_path);
- g_hash_table_iter_init(&iter, blobs);
- while (g_hash_table_iter_next(&iter, (gpointer) &blob_name, (gpointer) &blob_data)) {
- _dbus_connection_call(
- self,
- NM_WPAS_DBUS_IFACE_INTERFACE,
- "AddBlob",
- g_variant_new("(s@ay)", blob_name, nm_g_bytes_to_variant_ay(blob_data)),
- G_VARIANT_TYPE("()"),
- G_DBUS_CALL_FLAGS_NONE,
- DBUS_TIMEOUT_MSEC,
- priv->assoc_data->cancellable,
- assoc_add_blob_cb,
- self);
- }
+ /* Delete any existing blobs before adding new ones */
+ _dbus_connection_call(self,
+ DBUS_INTERFACE_PROPERTIES,
+ "Get",
+ g_variant_new("(ss)", NM_WPAS_DBUS_IFACE_INTERFACE, "Blobs"),
+ G_VARIANT_TYPE("(v)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ DBUS_TIMEOUT_MSEC,
+ priv->assoc_data->cancellable,
+ assoc_get_blobs_cb,
+ self);
}
static void
diff --git a/src/core/supplicant/tests/test-supplicant-config.c b/src/core/supplicant/tests/test-supplicant-config.c
index 1ca5b26..416fe00 100644
--- a/src/core/supplicant/tests/test-supplicant-config.c
+++ b/src/core/supplicant/tests/test-supplicant-config.c
@@ -98,7 +98,8 @@ build_supplicant_config(NMConnection *connection,
NMSetting8021x *s_8021x;
gboolean success;
- config = nm_supplicant_config_new(capabilities);
+ config = nm_supplicant_config_new(capabilities,
+ nm_utils_get_connection_first_permissions_user(connection));
s_wifi = nm_connection_get_setting_wireless(connection);
g_assert(s_wifi);
@@ -120,6 +121,7 @@ build_supplicant_config(NMConnection *connection,
mtu,
pmf,
fils,
+ NULL,
&error);
} else {
success = nm_supplicant_config_add_no_security(config, &error);
diff --git a/src/libnm-core-impl/nm-setting-8021x.c b/src/libnm-core-impl/nm-setting-8021x.c
index 626673e..999166b 100644
--- a/src/libnm-core-impl/nm-setting-8021x.c
+++ b/src/libnm-core-impl/nm-setting-8021x.c
@@ -3115,6 +3115,86 @@ need_secrets(NMSetting *setting, gboolean check_rerequest)
/*****************************************************************************/
+static void
+get_private_files(NMSetting *setting, GPtrArray *files)
+{
+ const struct {
+ const char *property;
+ NMSetting8021xCKScheme (*get_scheme_func)(NMSetting8021x *);
+ const char *(*get_path_func)(NMSetting8021x *);
+ } cert_props[] = {
+ {NM_SETTING_802_1X_CA_CERT,
+ nm_setting_802_1x_get_ca_cert_scheme,
+ nm_setting_802_1x_get_ca_cert_path},
+ {NM_SETTING_802_1X_CLIENT_CERT,
+ nm_setting_802_1x_get_client_cert_scheme,
+ nm_setting_802_1x_get_client_cert_path},
+ {NM_SETTING_802_1X_PRIVATE_KEY,
+ nm_setting_802_1x_get_private_key_scheme,
+ nm_setting_802_1x_get_private_key_path},
+ {NM_SETTING_802_1X_PHASE2_CA_CERT,
+ nm_setting_802_1x_get_phase2_ca_cert_scheme,
+ nm_setting_802_1x_get_phase2_ca_cert_path},
+ {NM_SETTING_802_1X_PHASE2_CLIENT_CERT,
+ nm_setting_802_1x_get_phase2_client_cert_scheme,
+ nm_setting_802_1x_get_phase2_client_cert_path},
+ {NM_SETTING_802_1X_PHASE2_PRIVATE_KEY,
+ nm_setting_802_1x_get_phase2_private_key_scheme,
+ nm_setting_802_1x_get_phase2_private_key_path},
+ };
+ NMSetting8021x *s_8021x = NM_SETTING_802_1X(setting);
+ const char *path;
+ guint i;
+
+ if (NM_MORE_ASSERT_ONCE(5)) {
+ GObjectClass *klass;
+ gs_free GParamSpec **properties = NULL;
+ guint n_properties;
+ gboolean found;
+ guint j;
+
+ /* Check that all the properties in the setting with flag CERT_KEY_FILE
+ * are listed in the table, and vice versa. */
+
+ klass = G_OBJECT_GET_CLASS(setting);
+
+ properties = g_object_class_list_properties(klass, &n_properties);
+ for (i = 0; i < n_properties; i++) {
+ if (!(properties[i]->flags & NM_SETTING_PARAM_CERT_KEY_FILE))
+ continue;
+
+ found = FALSE;
+ for (j = 0; j < G_N_ELEMENTS(cert_props); j++) {
+ if (nm_streq0(properties[i]->name, cert_props[j].property)) {
+ found = TRUE;
+ break;
+ }
+ }
+
+ nm_assert(found);
+ }
+
+ for (i = 0; i < G_N_ELEMENTS(cert_props); i++) {
+ GParamSpec *prop;
+
+ prop = g_object_class_find_property(klass, cert_props[i].property);
+ nm_assert(prop);
+ nm_assert(prop->flags & NM_SETTING_PARAM_CERT_KEY_FILE);
+ }
+ }
+
+ for (i = 0; i < G_N_ELEMENTS(cert_props); i++) {
+ if (cert_props[i].get_scheme_func(s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH) {
+ path = cert_props[i].get_path_func(s_8021x);
+ if (path) {
+ g_ptr_array_add(files, (gpointer) path);
+ }
+ }
+ }
+}
+
+/*****************************************************************************/
+
static void
get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
{
@@ -3207,8 +3287,9 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
object_class->set_property = set_property;
object_class->finalize = finalize;
- setting_class->verify = verify;
- setting_class->need_secrets = need_secrets;
+ setting_class->verify = verify;
+ setting_class->need_secrets = need_secrets;
+ setting_class->get_private_files = get_private_files;
/**
* NMSetting8021x:eap:
@@ -3327,7 +3408,7 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
obj_properties,
NM_SETTING_802_1X_CA_CERT,
PROP_CA_CERT,
- NM_SETTING_PARAM_NONE,
+ NM_SETTING_PARAM_CERT_KEY_FILE,
NMSetting8021xPrivate,
ca_cert);
@@ -3513,7 +3594,7 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
obj_properties,
NM_SETTING_802_1X_CLIENT_CERT,
PROP_CLIENT_CERT,
- NM_SETTING_PARAM_NONE,
+ NM_SETTING_PARAM_CERT_KEY_FILE,
NMSetting8021xPrivate,
client_cert);
@@ -3740,7 +3821,7 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
obj_properties,
NM_SETTING_802_1X_PHASE2_CA_CERT,
PROP_PHASE2_CA_CERT,
- NM_SETTING_PARAM_NONE,
+ NM_SETTING_PARAM_CERT_KEY_FILE,
NMSetting8021xPrivate,
phase2_ca_cert);
@@ -3931,7 +4012,7 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
obj_properties,
NM_SETTING_802_1X_PHASE2_CLIENT_CERT,
PROP_PHASE2_CLIENT_CERT,
- NM_SETTING_PARAM_NONE,
+ NM_SETTING_PARAM_CERT_KEY_FILE,
NMSetting8021xPrivate,
phase2_client_cert);
@@ -4092,7 +4173,7 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
obj_properties,
NM_SETTING_802_1X_PRIVATE_KEY,
PROP_PRIVATE_KEY,
- NM_SETTING_PARAM_NONE,
+ NM_SETTING_PARAM_CERT_KEY_FILE,
NMSetting8021xPrivate,
private_key);
@@ -4177,7 +4258,7 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
obj_properties,
NM_SETTING_802_1X_PHASE2_PRIVATE_KEY,
PROP_PHASE2_PRIVATE_KEY,
- NM_SETTING_PARAM_NONE,
+ NM_SETTING_PARAM_CERT_KEY_FILE,
NMSetting8021xPrivate,
phase2_private_key);
diff --git a/src/libnm-core-impl/nm-setting-connection.c b/src/libnm-core-impl/nm-setting-connection.c
index 3c26ab5..7a50fe0 100644
--- a/src/libnm-core-impl/nm-setting-connection.c
+++ b/src/libnm-core-impl/nm-setting-connection.c
@@ -393,6 +393,47 @@ nm_setting_connection_permissions_user_allowed(NMSettingConnection *setting, con
return FALSE;
}
+guint
+_nm_setting_connection_get_num_permissions_users(NMSettingConnection *setting)
+{
+ NMSettingConnectionPrivate *priv;
+ guint i;
+ guint count = 0;
+
+ nm_assert(NM_IS_SETTING_CONNECTION(setting));
+ priv = NM_SETTING_CONNECTION_GET_PRIVATE(setting);
+
+ for (i = 0; priv->permissions && i < priv->permissions->len; i++) {
+ const Permission *permission = &nm_g_array_index(priv->permissions, Permission, i);
+
+ if (permission->ptype == PERM_TYPE_USER) {
+ count++;
+ }
+ }
+
+ return count;
+}
+
+const char *
+_nm_setting_connection_get_first_permissions_user(NMSettingConnection *setting)
+{
+ NMSettingConnectionPrivate *priv;
+ guint i;
+
+ nm_assert(NM_IS_SETTING_CONNECTION(setting));
+ priv = NM_SETTING_CONNECTION_GET_PRIVATE(setting);
+
+ for (i = 0; priv->permissions && i < priv->permissions->len; i++) {
+ const Permission *permission = &nm_g_array_index(priv->permissions, Permission, i);
+
+ if (permission->ptype == PERM_TYPE_USER) {
+ return permission->item;
+ }
+ }
+
+ return NULL;
+}
+
/**
* nm_setting_connection_add_permission:
* @setting: the #NMSettingConnection
diff --git a/src/libnm-core-impl/nm-setting-private.h b/src/libnm-core-impl/nm-setting-private.h
index fff6c1c..857f966 100644
--- a/src/libnm-core-impl/nm-setting-private.h
+++ b/src/libnm-core-impl/nm-setting-private.h
@@ -148,6 +148,11 @@ struct _NMSettingClass {
guint /* NMSettingParseFlags */ parse_flags,
GError **error);
+ /* returns a list of certificate/key files referenced in the connection.
+ * When the connection is private, we need to verify that the owner of
+ * the connection has access to them. */
+ void (*get_private_files)(NMSetting *setting, GPtrArray *files);
+
const struct _NMMetaSettingInfo *setting_info;
};
@@ -346,6 +351,11 @@ struct _NMRange {
*/
#define NM_SETTING_PARAM_TO_DBUS_IGNORE_FLAGS (1 << (7 + G_PARAM_USER_SHIFT))
+/* The property can refer to a certificate or key stored on disk. As such,
+ * special care is needed when accessing the file for private connections.
+ */
+#define NM_SETTING_PARAM_CERT_KEY_FILE (1 << (8 + G_PARAM_USER_SHIFT))
+
extern const NMSettInfoPropertType nm_sett_info_propert_type_setting_name;
extern const NMSettInfoPropertType nm_sett_info_propert_type_deprecated_interface_name;
extern const NMSettInfoPropertType nm_sett_info_propert_type_deprecated_ignore_i;
@@ -835,9 +845,10 @@ _nm_properties_override(GArray *properties_override, const NMSettInfoProperty *p
{ \
GParamSpec *_param_spec; \
\
- G_STATIC_ASSERT(!NM_FLAGS_ANY((param_flags), \
- ~(NM_SETTING_PARAM_SECRET | NM_SETTING_PARAM_INFERRABLE \
- | NM_SETTING_PARAM_FUZZY_IGNORE))); \
+ G_STATIC_ASSERT( \
+ !NM_FLAGS_ANY((param_flags), \
+ ~(NM_SETTING_PARAM_SECRET | NM_SETTING_PARAM_INFERRABLE \
+ | NM_SETTING_PARAM_FUZZY_IGNORE | NM_SETTING_PARAM_CERT_KEY_FILE))); \
\
_param_spec = \
g_param_spec_boxed("" prop_name "", \
diff --git a/src/libnm-core-impl/nm-setting.c b/src/libnm-core-impl/nm-setting.c
index 276dfd3..0728679 100644
--- a/src/libnm-core-impl/nm-setting.c
+++ b/src/libnm-core-impl/nm-setting.c
@@ -2201,6 +2201,34 @@ init_from_dbus(NMSetting *setting,
return TRUE;
}
+static void
+get_private_files(NMSetting *setting, GPtrArray *files)
+{
+ if (NM_MORE_ASSERTS) {
+ GParamSpec **properties;
+ guint n_properties;
+ int i;
+
+ properties = g_object_class_list_properties(G_OBJECT_GET_CLASS(setting), &n_properties);
+ for (i = 0; i < n_properties; i++) {
+ if (properties[i]->flags & NM_SETTING_PARAM_CERT_KEY_FILE) {
+ /* Certificates and keys needs special handling, see setting 802.1X */
+ nm_assert_not_reached();
+ }
+ }
+ g_free(properties);
+ }
+}
+
+void
+_nm_setting_get_private_files(NMSetting *setting, GPtrArray *files)
+{
+ g_return_if_fail(NM_IS_SETTING(setting));
+ g_return_if_fail(files);
+
+ NM_SETTING_GET_CLASS(setting)->get_private_files(setting, files);
+}
+
/**
* nm_setting_get_dbus_property_type:
* @setting: an #NMSetting
@@ -4440,6 +4468,7 @@ nm_setting_class_init(NMSettingClass *setting_class)
setting_class->enumerate_values = enumerate_values;
setting_class->aggregate = aggregate;
setting_class->init_from_dbus = init_from_dbus;
+ setting_class->get_private_files = get_private_files;
/**
* NMSetting:name:
diff --git a/src/libnm-core-intern/nm-core-internal.h b/src/libnm-core-intern/nm-core-internal.h
index dbb5a7f..ce2a53e 100644
--- a/src/libnm-core-intern/nm-core-internal.h
+++ b/src/libnm-core-intern/nm-core-internal.h
@@ -1089,4 +1089,11 @@ gboolean nm_connection_need_secrets_for_rerequest(NMConnection *connection);
const GPtrArray *_nm_setting_ovs_port_get_trunks_arr(NMSettingOvsPort *self);
+/*****************************************************************************/
+
+guint _nm_setting_connection_get_num_permissions_users(NMSettingConnection *setting);
+const char *_nm_setting_connection_get_first_permissions_user(NMSettingConnection *setting);
+
+void _nm_setting_get_private_files(NMSetting *setting, GPtrArray *files);
+
#endif
diff --git a/src/libnm-std-aux/nm-std-utils.c b/src/libnm-std-aux/nm-std-utils.c
index 8901378..2a3389a 100644
--- a/src/libnm-std-aux/nm-std-utils.c
+++ b/src/libnm-std-aux/nm-std-utils.c
@@ -4,15 +4,127 @@
#include "nm-std-utils.h"
-#include <stdint.h>
#include <assert.h>
+#include <fcntl.h>
+#include <grp.h>
#include <limits.h>
#include <net/if.h>
+#include <pwd.h>
+#include <stdint.h>
+#include <sys/types.h>
/*****************************************************************************/
NM_STATIC_ASSERT(NM_IFNAMSIZ == IFNAMSIZ);
+/*****************************************************************************/
+
+bool
+nm_utils_set_effective_user(const char *user, char *errbuf, size_t errbuf_len)
+{
+ struct passwd *pwentry;
+ int errsv;
+ char error[1024];
+
+ errno = 0;
+ pwentry = getpwnam(user);
+ if (!pwentry) {
+ errsv = errno;
+ if (errsv == 0) {
+ snprintf(errbuf, errbuf_len, "user not found");
+ } else {
+ snprintf(errbuf,
+ errbuf_len,
+ "error getting user entry: %d (%s)\n",
+ errsv,
+ strerror_r(errsv, error, sizeof(error)));
+ }
+ return false;
+ }
+
+ if (setgid(pwentry->pw_gid) != 0) {
+ errsv = errno;
+ snprintf(errbuf,
+ errbuf_len,
+ "failed to change group to %u: %d (%s)\n",
+ pwentry->pw_gid,
+ errsv,
+ strerror_r(errsv, error, sizeof(error)));
+ return false;
+ }
+
+ if (initgroups(user, pwentry->pw_gid) != 0) {
+ errsv = errno;
+ snprintf(errbuf,
+ errbuf_len,
+ "failed to reset supplementary group list to %u: %d (%s)\n",
+ pwentry->pw_gid,
+ errsv,
+ strerror_r(errsv, error, sizeof(error)));
+ return false;
+ }
+
+ if (setuid(pwentry->pw_uid) != 0) {
+ errsv = errno;
+ snprintf(errbuf,
+ errbuf_len,
+ "failed to change user to %u: %d (%s)\n",
+ pwentry->pw_uid,
+ errsv,
+ strerror_r(errsv, error, sizeof(error)));
+ return false;
+ }
+
+ return true;
+}
+
+/*****************************************************************************/
+
+bool
+nm_utils_read_file_to_stdout(const char *filename, char *errbuf, size_t errbuf_len)
+{
+ nm_auto_close int fd = -1;
+ char buffer[4096];
+ char error[1024];
+ ssize_t bytes_read;
+ int errsv;
+
+ fd = open(filename, O_RDONLY);
+ if (fd == -1) {
+ errsv = errno;
+ snprintf(errbuf,
+ errbuf_len,
+ "error opening the file: %d (%s)",
+ errsv,
+ strerror_r(errsv, error, sizeof(error)));
+ return false;
+ }
+
+ while ((bytes_read = read(fd, buffer, sizeof(buffer))) > 0) {
+ if (fwrite(buffer, 1, bytes_read, stdout) != (size_t) bytes_read) {
+ errsv = errno;
+ snprintf(errbuf,
+ errbuf_len,
+ "error writing to stdout: %d (%s)",
+ errsv,
+ strerror_r(errsv, error, sizeof(error)));
+ return false;
+ }
+ }
+
+ if (bytes_read < 0) {
+ errsv = errno;
+ snprintf(errbuf,
+ errbuf_len,
+ "error reading the file: %d (%s)",
+ errsv,
+ strerror_r(errsv, error, sizeof(error)));
+ return false;
+ }
+
+ return true;
+}
+
/*****************************************************************************/
size_t
nm_utils_get_next_realloc_size(bool true_realloc, size_t requested)
diff --git a/src/libnm-std-aux/nm-std-utils.h b/src/libnm-std-aux/nm-std-utils.h
index 6aa787e..9d3c5df 100644
--- a/src/libnm-std-aux/nm-std-utils.h
+++ b/src/libnm-std-aux/nm-std-utils.h
@@ -35,4 +35,8 @@
size_t nm_utils_get_next_realloc_size(bool true_realloc, size_t requested);
+bool nm_utils_set_effective_user(const char *user, char *errbuf, size_t errbuf_size);
+
+bool nm_utils_read_file_to_stdout(const char *filename, char *errbuf, size_t errbuf_len);
+
#endif /* __NM_STD_UTILS_H__ */
diff --git a/src/meson.build b/src/meson.build
index 92e95e6..5593d95 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -95,8 +95,7 @@ if enable_nmtui
endif
subdir('nmcli')
subdir('nm-dispatcher')
-subdir('nm-priv-helper')
-subdir('nm-daemon-helper')
+subdir('nm-helpers')
subdir('nm-online')
if enable_nmtui
subdir('nmtui')
diff --git a/src/nm-daemon-helper/README.md b/src/nm-daemon-helper/README.md
deleted file mode 100644
index 695f533..0000000
--- a/src/nm-daemon-helper/README.md
+++ /dev/null
@@ -1,11 +0,0 @@
-nm-daemon-helper
-================
-
-A internal helper application that is spawned by NetworkManager
-to perform certain actions.
-
-Currently all it does is doing a reverse DNS lookup, which
-cannot be done by NetworkManager because the operation requires
-to reconfigure the libc resolver (which is a process-wide operation).
-
-This is not directly useful to the user.
diff --git a/src/nm-daemon-helper/meson.build b/src/nm-daemon-helper/meson.build
deleted file mode 100644
index da0d657..0000000
--- a/src/nm-daemon-helper/meson.build
+++ /dev/null
@@ -1,15 +0,0 @@
-executable(
- 'nm-daemon-helper',
- 'nm-daemon-helper.c',
- include_directories : [
- src_inc,
- top_inc,
- ],
- link_with: [
- libnm_std_aux,
- ],
- link_args: ldflags_linker_script_binary,
- link_depends: linker_script_binary,
- install: true,
- install_dir: nm_libexecdir,
-)
diff --git a/src/nm-priv-helper/README.md b/src/nm-helpers/README.md
similarity index 60%
rename from src/nm-priv-helper/README.md
rename to src/nm-helpers/README.md
index 576da7a..ab0ea02 100644
--- a/src/nm-priv-helper/README.md
+++ b/src/nm-helpers/README.md
@@ -1,5 +1,24 @@
+nm-helpers
+==========
+
+This directory contains stand-alone helper programs used by various
+components.
+
+nm-daemon-helper
+----------------
+
+A internal helper application that is spawned by NetworkManager to
+perform certain actions which can't be done in the daemon.
+
+Currently it's used to do a reverse DNS lookup after reconfiguring the
+libc resolver (which is a process-wide operation), and to read files
+on behalf of unprivileged users (which requires a seteuid that affects
+all the threads of the process).
+
+This is not directly useful to the user.
+
nm-priv-helper
-==============
+--------------
This is a D-Bus activatable, exit-on-idle service, which
provides an internal API to NetworkManager daemon.
diff --git a/src/nm-priv-helper/meson.build b/src/nm-helpers/meson.build
similarity index 67%
rename from src/nm-priv-helper/meson.build
rename to src/nm-helpers/meson.build
index 6141e0e..5f330cb 100644
--- a/src/nm-priv-helper/meson.build
+++ b/src/nm-helpers/meson.build
@@ -1,5 +1,25 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
+# nm-daemon-helper
+
+executable(
+ 'nm-daemon-helper',
+ 'nm-daemon-helper.c',
+ include_directories : [
+ src_inc,
+ top_inc,
+ ],
+ link_with: [
+ libnm_std_aux,
+ ],
+ link_args: ldflags_linker_script_binary,
+ link_depends: linker_script_binary,
+ install: true,
+ install_dir: nm_libexecdir,
+)
+
+# nm-priv-helper
+
configure_file(
input: 'org.freedesktop.nm_priv_helper.service.in',
output: '@BASENAME@',
diff --git a/src/nm-daemon-helper/nm-daemon-helper.c b/src/nm-helpers/nm-daemon-helper.c
similarity index 76%
rename from src/nm-daemon-helper/nm-daemon-helper.c
rename to src/nm-helpers/nm-daemon-helper.c
index a447d63..340a633 100644
--- a/src/nm-daemon-helper/nm-daemon-helper.c
+++ b/src/nm-helpers/nm-daemon-helper.c
@@ -111,6 +111,37 @@ cmd_resolve_address(void)
return RETURN_SUCCESS;
}
+static int
+cmd_read_file_as_user(void)
+{
+ nm_auto_free char *user = NULL;
+ nm_auto_free char *filename = NULL;
+ char error[1024];
+
+ user = read_arg();
+ if (!user)
+ return RETURN_INVALID_ARGS;
+
+ filename = read_arg();
+ if (!filename)
+ return RETURN_INVALID_ARGS;
+
+ if (more_args())
+ return RETURN_INVALID_ARGS;
+
+ if (!nm_utils_set_effective_user(user, error, sizeof(error))) {
+ fprintf(stderr, "Failed to set effective user '%s': %s", user, error);
+ return RETURN_ERROR;
+ }
+
+ if (!nm_utils_read_file_to_stdout(filename, error, sizeof(error))) {
+ fprintf(stderr, "Failed to read file '%s' as user '%s': %s", filename, user, error);
+ return RETURN_ERROR;
+ }
+
+ return RETURN_SUCCESS;
+}
+
int
main(int argc, char **argv)
{
@@ -124,6 +155,8 @@ main(int argc, char **argv)
return cmd_version();
if (nm_streq(cmd, "resolve-address"))
return cmd_resolve_address();
+ if (nm_streq(cmd, "read-file-as-user"))
+ return cmd_read_file_as_user();
return RETURN_INVALID_CMD;
}
diff --git a/src/nm-priv-helper/nm-priv-helper.c b/src/nm-helpers/nm-priv-helper.c
similarity index 100%
rename from src/nm-priv-helper/nm-priv-helper.c
rename to src/nm-helpers/nm-priv-helper.c
diff --git a/src/nm-priv-helper/nm-priv-helper.conf b/src/nm-helpers/nm-priv-helper.conf
similarity index 100%
rename from src/nm-priv-helper/nm-priv-helper.conf
rename to src/nm-helpers/nm-priv-helper.conf
diff --git a/src/nm-priv-helper/org.freedesktop.nm_priv_helper.service.in b/src/nm-helpers/org.freedesktop.nm_priv_helper.service.in
similarity index 100%
rename from src/nm-priv-helper/org.freedesktop.nm_priv_helper.service.in
rename to src/nm-helpers/org.freedesktop.nm_priv_helper.service.in
--
2.52.0