mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2025-12-20 03:30:09 +01:00
merge: branch 'issue1809'
CVE-2025-9615: avoid that non-admin user using other users' certificates. Closes #1809 https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2324
This commit is contained in:
commit
1756ec54e3
47 changed files with 1529 additions and 185 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -81,7 +81,6 @@ test-*.trs
|
|||
/data/org.freedesktop.NetworkManager.service
|
||||
/data/server.conf
|
||||
/data/org.freedesktop.NetworkManager.policy
|
||||
/data/org.freedesktop.NetworkManager.policy.in
|
||||
/data/nm-sudo.service
|
||||
/data/nm-priv-helper.service
|
||||
/data/NetworkManager-config-initrd.service
|
||||
|
|
|
|||
9
NEWS
9
NEWS
|
|
@ -14,6 +14,15 @@ USE AT YOUR OWN RISK. NOT RECOMMENDED FOR PRODUCTION USE!
|
|||
* Install the systemd units in the initramfs using a systemd generator.
|
||||
* A new "check-connectivity" configuration option is available to disable the
|
||||
connectivity check for selected interfaces.
|
||||
* Remove the modify_system build option that allowed setting up the
|
||||
polkit permissions to allow non-admin users to create system-wide
|
||||
connection. That configuration is discouraged because it can be used
|
||||
to bypass filesystem permissions.
|
||||
* For private connections (the ones that specify a user in the
|
||||
"connection.permissions" property), verify that the user can access
|
||||
the 802.1X certificates and keys set in the connection.
|
||||
* Introduce a libnm function that can be used by VPN plugins to check
|
||||
user permissions on certificate and keys.
|
||||
|
||||
=============================================
|
||||
NetworkManager-1.56
|
||||
|
|
|
|||
|
|
@ -107,6 +107,11 @@
|
|||
%else
|
||||
%bcond_without iwd
|
||||
%endif
|
||||
%if 0%{?fedora} <= 43 || 0%{?rhel} <= 10
|
||||
%bcond_without polkit_noauth_group
|
||||
%else
|
||||
%bcond_with polkit_noauth_group
|
||||
%endif
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
|
@ -665,7 +670,9 @@ Preferably use nmcli instead.
|
|||
-Dselinux=true \
|
||||
-Dpolkit=true \
|
||||
-Dconfig_auth_polkit_default=true \
|
||||
-Dmodify_system=true \
|
||||
%if %{with polkit_noauth_group}
|
||||
-Dpolkit_noauth_group=wheel \
|
||||
%endif
|
||||
-Dconcheck=true \
|
||||
%if 0%{?fedora}
|
||||
-Dlibpsl=true \
|
||||
|
|
@ -882,6 +889,7 @@ fi
|
|||
%{_libexecdir}/nm-dispatcher
|
||||
%{_libexecdir}/nm-initrd-generator
|
||||
%{_libexecdir}/nm-daemon-helper
|
||||
%{_libexecdir}/nm-libnm-helper
|
||||
%{_libexecdir}/nm-priv-helper
|
||||
%dir %{_libdir}/%{name}
|
||||
%dir %{nmplugindir}
|
||||
|
|
@ -913,6 +921,9 @@ fi
|
|||
%{_datadir}/dbus-1/system-services/org.freedesktop.nm_dispatcher.service
|
||||
%{_datadir}/dbus-1/system-services/org.freedesktop.nm_priv_helper.service
|
||||
%{_datadir}/polkit-1/actions/*.policy
|
||||
%if %{with polkit_noauth_group}
|
||||
%{_datadir}/polkit-1/rules.d/org.freedesktop.NetworkManager.rules
|
||||
%endif
|
||||
%{_prefix}/lib/udev/rules.d/*.rules
|
||||
%{_prefix}/lib/firewalld/zones/nm-shared.xml
|
||||
# systemd stuff
|
||||
|
|
|
|||
|
|
@ -401,7 +401,6 @@ meson setup\
|
|||
-Dselinux=true \
|
||||
-Dpolkit=true \
|
||||
-Dconfig_auth_polkit_default=true \
|
||||
-Dmodify_system=true \
|
||||
-Dconcheck=true \
|
||||
-Dlibpsl="$(bool_true "$P_FEDORA")" \
|
||||
-Dsession_tracking=systemd \
|
||||
|
|
|
|||
|
|
@ -55,21 +55,22 @@ if install_udevdir
|
|||
endif
|
||||
|
||||
if enable_polkit
|
||||
policy = 'org.freedesktop.NetworkManager.policy'
|
||||
|
||||
policy_in = configure_file(
|
||||
input: policy + '.in.in',
|
||||
output: '@BASENAME@',
|
||||
configuration: data_conf,
|
||||
)
|
||||
|
||||
i18n.merge_file(
|
||||
input: policy_in,
|
||||
input: 'org.freedesktop.NetworkManager.policy.in',
|
||||
output: '@BASENAME@',
|
||||
po_dir: po_dir,
|
||||
install: true,
|
||||
install_dir: polkit_gobject_policydir,
|
||||
install_dir: polkit_policydir,
|
||||
)
|
||||
|
||||
if polkit_noauth_group != ''
|
||||
configure_file(
|
||||
input: 'org.freedesktop.NetworkManager.rules.in',
|
||||
output: '@BASENAME@',
|
||||
install_dir: polkit_rulesdir,
|
||||
configuration: {'NM_POLKIT_NOAUTH_GROUP': polkit_noauth_group},
|
||||
)
|
||||
endif
|
||||
endif
|
||||
|
||||
if enable_firewalld_zone
|
||||
|
|
|
|||
|
|
@ -117,8 +117,8 @@
|
|||
<message>System policy prevents modification of network settings for all users</message>
|
||||
<defaults>
|
||||
<allow_any>auth_admin_keep</allow_any>
|
||||
<allow_inactive>@NM_MODIFY_SYSTEM_POLICY@</allow_inactive>
|
||||
<allow_active>@NM_MODIFY_SYSTEM_POLICY@</allow_active>
|
||||
<allow_inactive>auth_admin_keep</allow_inactive>
|
||||
<allow_active>auth_admin_keep</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
17
data/org.freedesktop.NetworkManager.rules.in
Normal file
17
data/org.freedesktop.NetworkManager.rules.in
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// NetworkManager authorizations/policy for the @NM_POLKIT_NOAUTH_GROUP@ group.
|
||||
//
|
||||
// DO NOT EDIT THIS FILE, it will be overwritten on update.
|
||||
//
|
||||
// Allow users in the @NM_POLKIT_NOAUTH_GROUP@ group to create system-wide connections without being
|
||||
// prompted for a password if they are in a local console.
|
||||
// This is optional and is only recommended to maintain backwards compatibility
|
||||
// in systems where it was already working in this way. It is discouraged
|
||||
// otherwise.
|
||||
|
||||
polkit.addRule(function(action, subject) {
|
||||
if (action.id == "org.freedesktop.NetworkManager.settings.modify.system" &&
|
||||
subject.isInGroup("@NM_POLKIT_NOAUTH_GROUP@") &&
|
||||
subject.local) {
|
||||
return polkit.Result.YES;
|
||||
}
|
||||
});
|
||||
22
meson.build
22
meson.build
|
|
@ -509,7 +509,8 @@ config_h.set10('WITH_TEAMDCTL', enable_teamdctl)
|
|||
enable_polkit = get_option('polkit')
|
||||
if enable_polkit
|
||||
# FIXME: policydir should be relative to `datadir`, not `prefix`. Fixed in https://gitlab.freedesktop.org/polkit/polkit/merge_requests/2
|
||||
polkit_gobject_policydir = dependency('polkit-gobject-1').get_variable(pkgconfig: 'policydir', pkgconfig_define: ['prefix', nm_prefix])
|
||||
polkit_policydir = dependency('polkit-gobject-1').get_variable(pkgconfig: 'policydir', pkgconfig_define: ['prefix', nm_prefix])
|
||||
polkit_rulesdir = join_paths(fs.parent(polkit_policydir), 'rules.d')
|
||||
endif
|
||||
|
||||
config_auth_polkit_default = get_option('config_auth_polkit_default')
|
||||
|
|
@ -519,6 +520,12 @@ endif
|
|||
config_h.set_quoted('NM_CONFIG_DEFAULT_MAIN_AUTH_POLKIT', config_auth_polkit_default)
|
||||
|
||||
enable_modify_system = get_option('modify_system')
|
||||
if enable_modify_system
|
||||
# FIXME: remove this after everyone has stopped using modify_system
|
||||
error('modify_system=true is no longer allowed due to security reasons')
|
||||
endif
|
||||
|
||||
polkit_noauth_group = get_option('polkit_noauth_group')
|
||||
|
||||
polkit_agent_helper_1_path = get_option('polkit_agent_helper_1')
|
||||
foreach p : [ '/usr/libexec/polkit-agent-helper-1',
|
||||
|
|
@ -951,7 +958,6 @@ data_conf.set('NM_DHCP_CLIENTS_ENABLED', ', '.join(config_dhcp_c
|
|||
data_conf.set('NM_MAJOR_VERSION', nm_major_version)
|
||||
data_conf.set('NM_MICRO_VERSION', nm_micro_version)
|
||||
data_conf.set('NM_MINOR_VERSION', nm_minor_version)
|
||||
data_conf.set('NM_MODIFY_SYSTEM_POLICY', (enable_modify_system ? 'yes' : 'auth_admin_keep'))
|
||||
data_conf.set('NM_VERSION', nm_version)
|
||||
data_conf.set('VERSION', nm_version)
|
||||
data_conf.set('bindir', nm_bindir)
|
||||
|
|
@ -1082,17 +1088,7 @@ output += ' dbus_conf_dir: ' + dbus_conf_dir + '\n'
|
|||
output += '\nPlatform:\n'
|
||||
output += ' session tracking: ' + ','.join(session_trackers) + '\n'
|
||||
output += ' suspend/resume: ' + suspend_resume + '\n'
|
||||
output += ' policykit: ' + enable_polkit.to_string() + ' (default: ' + config_auth_polkit_default + ')'
|
||||
if enable_polkit
|
||||
output += ' ('
|
||||
if enable_modify_system
|
||||
output += 'permissive'
|
||||
else
|
||||
output += 'restrictive'
|
||||
endif
|
||||
output += ' modify.system)'
|
||||
endif
|
||||
output += '\n'
|
||||
output += ' policykit: ' + enable_polkit.to_string() + ' (default: ' + config_auth_polkit_default + ', noauth_group: "' + polkit_noauth_group + '")\n'
|
||||
output += ' polkit-agent-helper-1: ' + polkit_agent_helper_1_path + '\n'
|
||||
output += ' selinux: ' + enable_selinux.to_string() + '\n'
|
||||
output += ' systemd-journald: ' + enable_systemd_journal.to_string() + ' (default: logging.backend=' + config_logging_backend_default + ')\n'
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ option('session_tracking', type: 'combo', choices: ['systemd', 'elogind', 'no'],
|
|||
option('suspend_resume', type: 'combo', choices: ['systemd', 'elogind', 'consolekit', 'auto'], value: 'auto', description: 'Build NetworkManager with specific suspend/resume support')
|
||||
option('polkit', type: 'boolean', value: true, description: 'User auth-polkit configuration option.')
|
||||
option('config_auth_polkit_default', type: 'combo', choices: ['default', 'true', 'false', 'root-only'], value: 'default', description: 'Default value for configuration main.auth-polkit.')
|
||||
option('modify_system', type: 'boolean', value: false, description: 'Allow users to modify system connections')
|
||||
option('modify_system', type: 'boolean', value: false, description: 'Allow users to modify system connections (option no longer supported, don\'t use)')
|
||||
option('polkit_noauth_group', type: 'string', value: '', description: 'Allow users of the selected group, typically sudo or wheel, to modify system connections without introducing a password (discouraged)')
|
||||
option('polkit_agent_helper_1', type: 'string', value: '', description: 'Path name to the polkit-agent-helper-1 binary from polkit')
|
||||
option('selinux', type: 'boolean', value: true, description: 'Build with SELinux')
|
||||
option('systemd_journal', type: 'boolean', value: true, description: 'Use systemd journal for logging')
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# List of source files containing translatable strings.
|
||||
# Please keep this file sorted alphabetically.
|
||||
data/org.freedesktop.NetworkManager.policy.in.in
|
||||
data/org.freedesktop.NetworkManager.policy.in
|
||||
src/core/NetworkManagerUtils.c
|
||||
src/core/devices/adsl/nm-device-adsl.c
|
||||
src/core/devices/bluetooth/nm-bluez-manager.c
|
||||
|
|
|
|||
|
|
@ -629,10 +629,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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -201,7 +201,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);
|
||||
|
||||
|
|
@ -227,7 +228,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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -176,4 +176,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 */
|
||||
|
|
|
|||
|
|
@ -239,7 +239,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;
|
||||
|
||||
|
|
@ -278,6 +278,7 @@ resolve_addr_spawn_helper(ResolveAddrInfo *info, ResolveAddrService services)
|
|||
nm_inet_ntop(info->addr_family, &info->address, addr_str);
|
||||
_LOG2D(info, "start lookup via nm-daemon-helper using services: %s", str);
|
||||
nm_utils_spawn_helper(NM_MAKE_STRV("resolve-address", addr_str, str),
|
||||
FALSE,
|
||||
g_task_get_cancellable(info->task),
|
||||
resolve_addr_helper_cb,
|
||||
info);
|
||||
|
|
|
|||
|
|
@ -348,6 +348,12 @@ typedef struct {
|
|||
int addr_family;
|
||||
} HostnameResolver;
|
||||
|
||||
typedef enum {
|
||||
PRIVATE_FILES_STATE_UNKNOWN = 0,
|
||||
PRIVATE_FILES_STATE_READING,
|
||||
PRIVATE_FILES_STATE_DONE,
|
||||
} PrivateFilesState;
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
enum {
|
||||
|
|
@ -784,6 +790,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;
|
||||
|
|
@ -10860,6 +10873,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
|
||||
*
|
||||
|
|
@ -10872,6 +10928,7 @@ activate_stage2_device_config(NMDevice *self)
|
|||
{
|
||||
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
|
||||
NMDeviceClass *klass = NM_DEVICE_GET_CLASS(self);
|
||||
NMConnection *applied;
|
||||
NMActStageReturn ret;
|
||||
NMSettingWired *s_wired;
|
||||
gboolean no_firmware = FALSE;
|
||||
|
|
@ -10880,6 +10937,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_managed_type_is_external(self)) {
|
||||
_ethtool_state_set(self);
|
||||
nm_device_link_properties_set(self, FALSE);
|
||||
|
|
@ -10896,7 +11015,7 @@ activate_stage2_device_config(NMDevice *self)
|
|||
priv->tc_committed = TRUE;
|
||||
}
|
||||
|
||||
nm_routing_rules_sync(nm_device_get_applied_connection(self),
|
||||
nm_routing_rules_sync(applied,
|
||||
NM_TERNARY_TRUE,
|
||||
klass->get_extra_rules,
|
||||
self,
|
||||
|
|
@ -17209,6 +17328,12 @@ nm_device_cleanup(NMDevice *self, NMDeviceStateReason reason, CleanupType cleanu
|
|||
if (klass->deactivate)
|
||||
klass->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) {
|
||||
|
|
|
|||
|
|
@ -3019,7 +3019,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)
|
||||
|
|
@ -3095,6 +3096,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;
|
||||
|
|
|
|||
|
|
@ -5012,6 +5012,7 @@ typedef struct {
|
|||
int child_stdin;
|
||||
int child_stdout;
|
||||
int child_stderr;
|
||||
gboolean binary_output;
|
||||
GSource *input_source;
|
||||
GSource *output_source;
|
||||
GSource *error_source;
|
||||
|
|
@ -5091,9 +5092,17 @@ helper_complete(HelperInfo *info, GError *error)
|
|||
}
|
||||
|
||||
nm_clear_g_cancellable_disconnect(g_task_get_cancellable(info->task), &info->cancellable_id);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -5236,6 +5245,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)
|
||||
|
|
@ -5252,8 +5262,13 @@ 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),
|
||||
.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(),
|
||||
|
|
@ -5364,11 +5379,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);
|
||||
}
|
||||
|
|
@ -5624,3 +5653,185 @@ nm_rate_limit_check(NMRateLimit *rate_limit, gint32 window_sec, gint32 burst)
|
|||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -478,11 +478,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);
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
|
|
@ -503,4 +505,19 @@ typedef struct {
|
|||
|
||||
gboolean nm_rate_limit_check(NMRateLimit *rate_limit, gint32 window_sec, gint32 burst);
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
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__ */
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -930,6 +933,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);
|
||||
|
|
@ -1284,6 +1288,7 @@ nm_supplicant_config_add_setting_wireless_security(NMSupplicantConfig
|
|||
con_uuid,
|
||||
mtu,
|
||||
FALSE,
|
||||
files,
|
||||
error))
|
||||
return FALSE;
|
||||
}
|
||||
|
|
@ -1365,6 +1370,7 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
|
|||
const char *con_uuid,
|
||||
guint32 mtu,
|
||||
gboolean wired,
|
||||
GHashTable *files,
|
||||
GError **error)
|
||||
{
|
||||
NMSupplicantConfigPrivate *priv;
|
||||
|
|
@ -1594,24 +1600,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,
|
||||
|
|
@ -1627,26 +1630,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(
|
||||
|
|
@ -1663,6 +1672,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);
|
||||
|
|
@ -1714,21 +1732,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:
|
||||
|
|
@ -1745,6 +1759,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;
|
||||
|
|
@ -1768,20 +1795,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(
|
||||
|
|
@ -1797,26 +1820,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:
|
||||
|
|
@ -1834,6 +1866,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;
|
||||
|
|
@ -1857,20 +1902,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(
|
||||
|
|
@ -1886,6 +1927,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -2266,6 +2267,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),
|
||||
|
|
@ -2274,6 +2276,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)
|
||||
{
|
||||
|
|
@ -2283,10 +2427,6 @@ assoc_add_network_cb(GObject *source, GAsyncResult *result, gpointer user_data)
|
|||
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;
|
||||
nm_auto_ref_string NMRefString *name_owner = NULL;
|
||||
nm_auto_ref_string NMRefString *object_path = NULL;
|
||||
|
||||
|
|
@ -2338,35 +2478,22 @@ 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);
|
||||
priv->net_path);
|
||||
|
||||
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)) {
|
||||
_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("()"),
|
||||
/* 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_add_blob_cb,
|
||||
assoc_get_blobs_cb,
|
||||
self);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
add_network(NMSupplicantInterface *self)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -60,8 +60,10 @@ nm_vpn_manager_activate_connection(NMVpnManager *manager, NMVpnConnection *vpn,
|
|||
{
|
||||
NMVpnManagerPrivate *priv;
|
||||
NMVpnPluginInfo *plugin_info;
|
||||
NMConnection *applied;
|
||||
const char *service_name;
|
||||
NMDevice *device;
|
||||
const char *user;
|
||||
|
||||
g_return_val_if_fail(NM_IS_VPN_MANAGER(manager), FALSE);
|
||||
g_return_val_if_fail(NM_IS_VPN_CONNECTION(vpn), FALSE);
|
||||
|
|
@ -69,7 +71,10 @@ nm_vpn_manager_activate_connection(NMVpnManager *manager, NMVpnConnection *vpn,
|
|||
|
||||
priv = NM_VPN_MANAGER_GET_PRIVATE(manager);
|
||||
device = nm_active_connection_get_device(NM_ACTIVE_CONNECTION(vpn));
|
||||
g_assert(device);
|
||||
applied = nm_active_connection_get_applied_connection(NM_ACTIVE_CONNECTION(vpn));
|
||||
nm_assert(device);
|
||||
nm_assert(applied);
|
||||
|
||||
if (nm_device_get_state(device) != NM_DEVICE_STATE_ACTIVATED
|
||||
&& nm_device_get_state(device) != NM_DEVICE_STATE_SECONDARIES) {
|
||||
g_set_error_literal(error,
|
||||
|
|
@ -101,6 +106,30 @@ nm_vpn_manager_activate_connection(NMVpnManager *manager, NMVpnConnection *vpn,
|
|||
return FALSE;
|
||||
}
|
||||
|
||||
user = nm_utils_get_connection_first_permissions_user(applied);
|
||||
if (user) {
|
||||
NMSettingConnection *s_con;
|
||||
|
||||
s_con = nm_connection_get_setting_connection(applied);
|
||||
nm_assert(s_con);
|
||||
if (_nm_setting_connection_get_num_permissions_users(s_con) > 1) {
|
||||
g_set_error_literal(error,
|
||||
NM_MANAGER_ERROR,
|
||||
NM_MANAGER_ERROR_CONNECTION_NOT_AVAILABLE,
|
||||
"private VPN connections with multiple users are not allowed.");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!nm_vpn_plugin_info_supports_safe_private_file_access(plugin_info)) {
|
||||
g_set_error(error,
|
||||
NM_MANAGER_ERROR,
|
||||
NM_MANAGER_ERROR_CONNECTION_NOT_AVAILABLE,
|
||||
"The '%s' plugin doesn't support private connections.",
|
||||
nm_vpn_plugin_info_get_name(plugin_info));
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
nm_vpn_connection_activate(vpn, plugin_info);
|
||||
|
||||
if (!nm_vpn_plugin_info_supports_multiple(plugin_info)) {
|
||||
|
|
|
|||
|
|
@ -2090,4 +2090,6 @@ global:
|
|||
nm_setting_gsm_get_device_uid;
|
||||
nm_setting_connection_get_dnssec;
|
||||
nm_setting_connection_dnssec_get_type;
|
||||
nm_utils_copy_cert_as_user;
|
||||
nm_vpn_plugin_info_supports_safe_private_file_access;
|
||||
} libnm_1_54_0;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ test_units = [
|
|||
'test-nm-client',
|
||||
'test-remote-settings-client',
|
||||
'test-secret-agent',
|
||||
'test-copy-cert-as-user'
|
||||
]
|
||||
|
||||
foreach test_unit: test_units
|
||||
|
|
@ -37,12 +38,15 @@ foreach test_unit: test_units
|
|||
],
|
||||
)
|
||||
|
||||
# test-copy-cert-as-user is a manual test, don't run it automatically
|
||||
if test_unit != 'test-copy-cert-as-user'
|
||||
test(
|
||||
'src/libnm-client-impl/tests/' + test_unit,
|
||||
test_script,
|
||||
timeout: 90,
|
||||
args: test_args + [exe.full_path()],
|
||||
)
|
||||
endif
|
||||
endforeach
|
||||
|
||||
if enable_introspection
|
||||
|
|
|
|||
32
src/libnm-client-impl/tests/test-copy-cert-as-user.c
Normal file
32
src/libnm-client-impl/tests/test-copy-cert-as-user.c
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
/*
|
||||
* This is a program to manually test the
|
||||
* nm_utils_copy_cert_as_user() libnm function.
|
||||
*/
|
||||
|
||||
#include "libnm-client-impl/nm-default-libnm.h"
|
||||
|
||||
#include "nm-utils.h"
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
gs_free_error GError *error = NULL;
|
||||
gs_free char *filename = NULL;
|
||||
|
||||
if (argc != 3) {
|
||||
g_printerr("Usage: %s <FILE> <USER>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
filename = nm_utils_copy_cert_as_user(argv[1], argv[2], &error);
|
||||
if (!filename) {
|
||||
g_printerr("Error: %s\n", error->message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
g_print("%s\n", filename);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -3133,6 +3133,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)
|
||||
{
|
||||
|
|
@ -3225,6 +3305,7 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
|
|||
|
||||
setting_class->verify = verify;
|
||||
setting_class->need_secrets = need_secrets;
|
||||
setting_class->get_private_files = get_private_files;
|
||||
|
||||
/**
|
||||
* NMSetting8021x:eap:
|
||||
|
|
@ -3359,7 +3440,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);
|
||||
|
||||
|
|
@ -3556,7 +3637,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);
|
||||
|
||||
|
|
@ -3803,7 +3884,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);
|
||||
|
||||
|
|
@ -4006,7 +4087,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);
|
||||
|
||||
|
|
@ -4175,7 +4256,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);
|
||||
|
||||
|
|
@ -4276,7 +4357,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);
|
||||
|
||||
|
|
|
|||
|
|
@ -433,6 +433,47 @@ nm_setting_connection_permissions_user_allowed_by_uid(NMSettingConnection *setti
|
|||
return _permissions_user_allowed(setting, NULL, uid);
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -154,6 +154,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;
|
||||
};
|
||||
|
||||
|
|
@ -334,6 +339,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;
|
||||
|
|
@ -859,9 +869,10 @@ _nm_properties_override(GArray *properties_override, const NMSettInfoProperty *p
|
|||
{ \
|
||||
GParamSpec *_param_spec; \
|
||||
\
|
||||
G_STATIC_ASSERT(!NM_FLAGS_ANY((param_flags), \
|
||||
G_STATIC_ASSERT( \
|
||||
!NM_FLAGS_ANY((param_flags), \
|
||||
~(NM_SETTING_PARAM_SECRET | NM_SETTING_PARAM_INFERRABLE \
|
||||
| NM_SETTING_PARAM_FUZZY_IGNORE))); \
|
||||
| NM_SETTING_PARAM_FUZZY_IGNORE | NM_SETTING_PARAM_CERT_KEY_FILE))); \
|
||||
\
|
||||
_param_spec = g_param_spec_boxed("" prop_name "", \
|
||||
"", \
|
||||
|
|
|
|||
|
|
@ -2262,6 +2262,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
|
||||
|
|
@ -4672,6 +4700,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:
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
#include <linux/pkt_sched.h>
|
||||
#include <linux/if_infiniband.h>
|
||||
|
||||
#include "libnm-glib-aux/nm-io-utils.h"
|
||||
#include "libnm-glib-aux/nm-uuid.h"
|
||||
#include "libnm-glib-aux/nm-json-aux.h"
|
||||
#include "libnm-glib-aux/nm-str-buf.h"
|
||||
|
|
@ -6195,3 +6196,258 @@ nm_utils_ensure_gtypes(void)
|
|||
for (meta_type = 0; meta_type < _NM_META_SETTING_TYPE_NUM; meta_type++)
|
||||
nm_meta_setting_infos[meta_type].get_setting_gtype();
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
typedef struct {
|
||||
GPid pid;
|
||||
GSource *child_watch_source;
|
||||
GMainLoop *loop;
|
||||
GError *error;
|
||||
|
||||
int child_stdout;
|
||||
int child_stderr;
|
||||
|
||||
GSource *output_source;
|
||||
GSource *error_source;
|
||||
|
||||
NMStrBuf output_buffer;
|
||||
NMStrBuf error_buffer;
|
||||
} HelperInfo;
|
||||
|
||||
static void
|
||||
helper_complete(HelperInfo *info, GError *error_take)
|
||||
{
|
||||
if (error_take) {
|
||||
if (!info->error)
|
||||
info->error = error_take;
|
||||
else
|
||||
g_error_free(error_take);
|
||||
}
|
||||
|
||||
if (info->output_source || info->error_source || info->pid != -1) {
|
||||
/* Wait that the pipe is closed and process has terminated */
|
||||
return;
|
||||
}
|
||||
|
||||
if (info->error && info->error_buffer.len > 0) {
|
||||
/* Prefer the message from stderr as it's more informative */
|
||||
g_error_free(info->error);
|
||||
info->error = g_error_new(NM_CONNECTION_ERROR,
|
||||
NM_CONNECTION_ERROR_FAILED,
|
||||
"%s",
|
||||
nm_str_buf_get_str(&info->error_buffer));
|
||||
}
|
||||
|
||||
g_main_loop_quit(info->loop);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
helper_have_err_data(int fd, GIOCondition condition, gpointer user_data)
|
||||
{
|
||||
HelperInfo *info = user_data;
|
||||
gssize n_read;
|
||||
GError *error = NULL;
|
||||
|
||||
n_read = nm_utils_fd_read(fd, &info->error_buffer);
|
||||
|
||||
if (n_read > 0)
|
||||
return G_SOURCE_CONTINUE;
|
||||
|
||||
nm_clear_g_source_inst(&info->error_source);
|
||||
nm_clear_fd(&info->child_stderr);
|
||||
|
||||
if (n_read < 0) {
|
||||
error = g_error_new(NM_UTILS_ERROR,
|
||||
NM_UTILS_ERROR_UNKNOWN,
|
||||
"read from process returned %d (%s)",
|
||||
(int) -n_read,
|
||||
nm_strerror_native((int) -n_read));
|
||||
}
|
||||
|
||||
helper_complete(info, error);
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
helper_have_data(int fd, GIOCondition condition, gpointer user_data)
|
||||
{
|
||||
HelperInfo *info = user_data;
|
||||
gssize n_read;
|
||||
GError *error = NULL;
|
||||
|
||||
n_read = nm_utils_fd_read(fd, &info->output_buffer);
|
||||
|
||||
if (n_read > 0)
|
||||
return G_SOURCE_CONTINUE;
|
||||
|
||||
nm_clear_g_source_inst(&info->output_source);
|
||||
nm_clear_fd(&info->child_stdout);
|
||||
|
||||
if (n_read < 0) {
|
||||
error = g_error_new(NM_UTILS_ERROR,
|
||||
NM_UTILS_ERROR_UNKNOWN,
|
||||
"read from process returned %d (%s)",
|
||||
(int) -n_read,
|
||||
nm_strerror_native((int) -n_read));
|
||||
}
|
||||
|
||||
helper_complete(info, error);
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static void
|
||||
helper_child_terminated(GPid pid, int status, gpointer user_data)
|
||||
{
|
||||
HelperInfo *info = user_data;
|
||||
gs_free char *status_desc = NULL;
|
||||
GError *error = NULL;
|
||||
|
||||
info->pid = -1;
|
||||
nm_clear_g_source_inst(&info->child_watch_source);
|
||||
|
||||
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
|
||||
if (!status_desc)
|
||||
status_desc = nm_utils_get_process_exit_status_desc(status);
|
||||
error =
|
||||
g_error_new(NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "helper process %s", status_desc);
|
||||
}
|
||||
|
||||
helper_complete(info, error);
|
||||
}
|
||||
|
||||
#define RUN_CERT_DIR NMRUNDIR "/cert"
|
||||
|
||||
/**
|
||||
* nm_utils_copy_cert_as_user:
|
||||
* @filename: the file name of the certificate or key to copy
|
||||
* @user: the user to impersonate when reading the file
|
||||
* @error: (nullable): return location for a #GError, or %NULL
|
||||
*
|
||||
* Reads @filename on behalf of user @user and writes the
|
||||
* content to a new file in /run/NetworkManager/cert/.
|
||||
* The new file has permission 600 and is owned by root.
|
||||
*
|
||||
* This function is useful for VPN plugins that run as root and need
|
||||
* to verify that the user owning the connection (the one listed in the
|
||||
* connection.permissions property) can access the file.
|
||||
*
|
||||
* Returns: (transfer full): the name of the new temporary file. Or %NULL
|
||||
* if an error occurred, including when the given user can't access the
|
||||
* file.
|
||||
*
|
||||
* Since: 1.56
|
||||
*/
|
||||
char *
|
||||
nm_utils_copy_cert_as_user(const char *filename, const char *user, GError **error)
|
||||
{
|
||||
gs_unref_bytes GBytes *bytes = NULL;
|
||||
char dst_path[] = RUN_CERT_DIR "/XXXXXX";
|
||||
HelperInfo info = {
|
||||
.child_stdout = -1,
|
||||
.child_stderr = -1,
|
||||
};
|
||||
GMainContext *context;
|
||||
int fd = -1;
|
||||
|
||||
g_return_val_if_fail(filename, NULL);
|
||||
g_return_val_if_fail(user, NULL);
|
||||
g_return_val_if_fail(!error || !*error, NULL);
|
||||
|
||||
if (geteuid() != 0) {
|
||||
g_set_error_literal(error,
|
||||
NM_CONNECTION_ERROR,
|
||||
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
||||
_("This function needs to be called by root"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!g_spawn_async_with_pipes(
|
||||
"/",
|
||||
(char **)
|
||||
NM_MAKE_STRV(LIBEXECDIR "/nm-libnm-helper", "read-file-as-user", filename, user),
|
||||
(char **) NM_MAKE_STRV(),
|
||||
G_SPAWN_CLOEXEC_PIPES | G_SPAWN_DO_NOT_REAP_CHILD,
|
||||
NULL,
|
||||
NULL,
|
||||
&info.pid,
|
||||
NULL,
|
||||
&info.child_stdout,
|
||||
&info.child_stderr,
|
||||
error)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
context = g_main_context_new();
|
||||
info.loop = g_main_loop_new(context, FALSE);
|
||||
|
||||
/* Watch process */
|
||||
info.child_watch_source = nm_g_child_watch_source_new(info.pid,
|
||||
G_PRIORITY_DEFAULT,
|
||||
helper_child_terminated,
|
||||
&info,
|
||||
NULL);
|
||||
g_source_attach(info.child_watch_source, context);
|
||||
|
||||
/* Watch stdout */
|
||||
info.output_buffer = NM_STR_BUF_INIT(0, FALSE);
|
||||
info.output_source = nm_g_unix_fd_source_new(info.child_stdout,
|
||||
G_IO_IN | G_IO_ERR | G_IO_HUP,
|
||||
G_PRIORITY_DEFAULT,
|
||||
helper_have_data,
|
||||
&info,
|
||||
NULL);
|
||||
g_source_attach(info.output_source, context);
|
||||
|
||||
/* Watch stderr */
|
||||
info.error_buffer = NM_STR_BUF_INIT(0, FALSE);
|
||||
info.error_source = nm_g_unix_fd_source_new(info.child_stderr,
|
||||
G_IO_IN | G_IO_ERR | G_IO_HUP,
|
||||
G_PRIORITY_DEFAULT,
|
||||
helper_have_err_data,
|
||||
&info,
|
||||
NULL);
|
||||
g_source_attach(info.error_source, context);
|
||||
|
||||
/* Wait termination */
|
||||
g_main_loop_run(info.loop);
|
||||
g_clear_pointer(&info.loop, g_main_loop_unref);
|
||||
g_clear_pointer(&context, g_main_context_unref);
|
||||
|
||||
if (info.error) {
|
||||
nm_str_buf_destroy(&info.output_buffer);
|
||||
nm_str_buf_destroy(&info.error_buffer);
|
||||
g_propagate_error(error, g_steal_pointer(&info.error));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Write the data to a new file */
|
||||
|
||||
bytes = g_bytes_new(nm_str_buf_get_str_unsafe(&info.output_buffer), info.output_buffer.len);
|
||||
nm_str_buf_destroy(&info.output_buffer);
|
||||
nm_str_buf_destroy(&info.error_buffer);
|
||||
|
||||
mkdir(RUN_CERT_DIR, 0600);
|
||||
fd = mkstemp(dst_path);
|
||||
if (fd < 0) {
|
||||
g_set_error_literal(error,
|
||||
NM_CONNECTION_ERROR,
|
||||
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
||||
_("Failure creating the temporary file"));
|
||||
return NULL;
|
||||
}
|
||||
nm_close(fd);
|
||||
|
||||
if (!nm_utils_file_set_contents(dst_path,
|
||||
g_bytes_get_data(bytes, NULL),
|
||||
g_bytes_get_size(bytes),
|
||||
0600,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
error)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return g_strdup(dst_path);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -913,6 +913,29 @@ nm_vpn_plugin_info_supports_multiple(NMVpnPluginInfo *self)
|
|||
return _nm_utils_ascii_str_to_bool(s, FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* nm_vpn_plugin_info_supports_safe_private_file_access:
|
||||
* @self: plugin info instance
|
||||
*
|
||||
* Returns: %TRUE if the service supports reading files (certificates, keys) of
|
||||
* private connections in a safe way (i.e. checking user permissions), or
|
||||
if the service doesn't need to read any file from disk.
|
||||
*
|
||||
* Since: 1.56
|
||||
*/
|
||||
gboolean
|
||||
nm_vpn_plugin_info_supports_safe_private_file_access(NMVpnPluginInfo *self)
|
||||
{
|
||||
const char *s;
|
||||
|
||||
g_return_val_if_fail(NM_IS_VPN_PLUGIN_INFO(self), FALSE);
|
||||
|
||||
s = nm_vpn_plugin_info_lookup_property(self,
|
||||
NM_VPN_PLUGIN_INFO_KF_GROUP_CONNECTION,
|
||||
"supports-safe-private-file-access");
|
||||
return _nm_utils_ascii_str_to_bool(s, FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* nm_vpn_plugin_info_get_aliases:
|
||||
* @self: plugin info instance
|
||||
|
|
|
|||
|
|
@ -1187,4 +1187,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
|
||||
|
|
|
|||
|
|
@ -261,6 +261,9 @@ nm_utils_base64secret_decode(const char *base64_key, gsize required_key_len, gui
|
|||
NM_AVAILABLE_IN_1_42
|
||||
void nm_utils_ensure_gtypes(void);
|
||||
|
||||
NM_AVAILABLE_IN_1_56
|
||||
char *nm_utils_copy_cert_as_user(const char *filename, const char *user, GError **error);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __NM_UTILS_H__ */
|
||||
|
|
|
|||
|
|
@ -64,6 +64,8 @@ NM_AVAILABLE_IN_1_4
|
|||
gboolean nm_vpn_plugin_info_supports_hints(NMVpnPluginInfo *self);
|
||||
NM_AVAILABLE_IN_1_42
|
||||
gboolean nm_vpn_plugin_info_supports_multiple(NMVpnPluginInfo *self);
|
||||
NM_AVAILABLE_IN_1_56
|
||||
gboolean nm_vpn_plugin_info_supports_safe_private_file_access(NMVpnPluginInfo *self);
|
||||
NM_AVAILABLE_IN_1_4
|
||||
const char *const *nm_vpn_plugin_info_get_aliases(NMVpnPluginInfo *self);
|
||||
NM_AVAILABLE_IN_1_2
|
||||
|
|
|
|||
|
|
@ -4,10 +4,14 @@
|
|||
|
||||
#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>
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
|
|
@ -95,6 +99,114 @@ out_huge:
|
|||
|
||||
/*****************************************************************************/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/**
|
||||
* _nm_strerror_r:
|
||||
* @errsv: the errno passed to strerror_r()
|
||||
|
|
|
|||
|
|
@ -37,4 +37,8 @@ size_t nm_utils_get_next_realloc_size(bool true_realloc, size_t requested);
|
|||
|
||||
const char *_nm_strerror_r(int errsv, char *buf, size_t buf_size);
|
||||
|
||||
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__ */
|
||||
|
|
|
|||
|
|
@ -103,8 +103,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')
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -1,5 +1,32 @@
|
|||
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-libnm-helper
|
||||
---------------
|
||||
|
||||
A internal helper application that is spawned by libnm to perform
|
||||
certain actions without impacting the calling 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.
|
||||
|
|
@ -1,5 +1,45 @@
|
|||
# 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-libnm-helper
|
||||
|
||||
executable(
|
||||
'nm-libnm-helper',
|
||||
['nm-libnm-helper.c'],
|
||||
include_directories : [
|
||||
src_inc,
|
||||
top_inc,
|
||||
],
|
||||
dependencies: [
|
||||
glib_dep,
|
||||
],
|
||||
link_with: [
|
||||
libnm_glib_aux,
|
||||
libnm_std_aux,
|
||||
],
|
||||
install: true,
|
||||
install_dir: nm_libexecdir,
|
||||
)
|
||||
|
||||
# nm-priv-helper
|
||||
|
||||
configure_file(
|
||||
input: 'org.freedesktop.nm_priv_helper.service.in',
|
||||
output: '@BASENAME@',
|
||||
|
|
@ -137,6 +137,37 @@ cmd_resolve_address(void)
|
|||
return RETURN_ERROR;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
|
@ -150,6 +181,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;
|
||||
}
|
||||
45
src/nm-helpers/nm-libnm-helper.c
Normal file
45
src/nm-helpers/nm-libnm-helper.c
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "libnm-std-aux/nm-default-std.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
enum {
|
||||
RETURN_SUCCESS = 0,
|
||||
RETURN_INVALID_CMD = 1,
|
||||
RETURN_INVALID_ARGS = 2,
|
||||
RETURN_ERROR = 3,
|
||||
};
|
||||
|
||||
static int
|
||||
read_file_as_user(const char *filename, const char *user)
|
||||
{
|
||||
char error[1024];
|
||||
|
||||
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)
|
||||
{
|
||||
if (argc <= 1)
|
||||
return RETURN_INVALID_CMD;
|
||||
|
||||
if (nm_streq(argv[1], "read-file-as-user")) {
|
||||
if (argc != 4)
|
||||
return RETURN_INVALID_ARGS;
|
||||
return read_file_as_user(argv[2], argv[3]);
|
||||
}
|
||||
|
||||
return RETURN_INVALID_CMD;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue