sudo: introduce nm-sudo D-Bus service

NetworkManager runs as root and has lots of capabilities.
We want to reduce the attach surface by dropping capabilities,
but there is a genuine need to do certain things.

For example, we currently require dac_override capability, to open
the unix socket of ovsdb. Most users wouldn't use OVS, so we should
find a way to not require that dac_override capability. The solution
is to have a separate, D-Bus activate service (nm-sudo), which
has the capability to open and provide the file descriptor.

For authentication, we only rely on D-Bus. We watch the name owner
of NetworkManager, and only accept requests from that service. We trust
D-Bus to get it right a request from that name owner is really coming
from NetworkManager. If we couldn't trust that, how could PolicyKit
or any authentication via D-Bus work? For testing, the user can set
NM_SUDO_NO_AUTH_FOR_TESTING=1.

https://bugzilla.redhat.com/show_bug.cgi?id=1921826
This commit is contained in:
Thomas Haller 2021-07-18 08:53:43 +02:00
parent 684f2acffe
commit f137b32d31
No known key found for this signature in database
GPG Key ID: 29C2366E4DFC5728
17 changed files with 826 additions and 3 deletions

4
.gitignore vendored
View File

@ -68,6 +68,9 @@ test-*.trs
/src/nm-dispatcher/org.freedesktop.nm_dispatcher.service
/src/nm-dispatcher/tests/test-dispatcher-envp
/src/nm-sudo/nm-sudo
/src/nm-sudo/org.freedesktop.nm.sudo.service
/data/NetworkManager.service
/data/NetworkManager-wait-online.service
/data/NetworkManager-dispatcher.service
@ -75,6 +78,7 @@ test-*.trs
/data/server.conf
/data/org.freedesktop.NetworkManager.policy
/data/org.freedesktop.NetworkManager.policy.in
/data/nm-sudo.service
/docs/api/version.xml
/docs/api/settings-spec.html

View File

@ -504,6 +504,8 @@ src_libnm_base_libnm_base_la_SOURCES = \
src/libnm-base/nm-ethtool-utils-base.h \
src/libnm-base/nm-net-aux.c \
src/libnm-base/nm-net-aux.h \
src/libnm-base/nm-sudo-utils.c \
src/libnm-base/nm-sudo-utils.h \
$(NULL)
src_libnm_base_libnm_base_la_LDFLAGS = \
@ -2584,8 +2586,10 @@ src_core_libNetworkManager_la_SOURCES = \
src/core/nm-policy.h \
src/core/nm-rfkill-manager.c \
src/core/nm-rfkill-manager.h \
src/core/nm-session-monitor.h \
src/core/nm-session-monitor.c \
src/core/nm-session-monitor.h \
src/core/nm-sudo-call.c \
src/core/nm-sudo-call.h \
src/core/nm-keep-alive.c \
src/core/nm-keep-alive.h \
src/core/nm-sleep-monitor.c \
@ -4617,6 +4621,56 @@ EXTRA_DIST += \
src/nm-dispatcher/tests/meson.build \
$(NULL)
###############################################################################
# src/nm-sudo
###############################################################################
libexec_PROGRAMS += src/nm-sudo/nm-sudo
src_nm_sudo_nm_sudo_SOURCES = \
src/nm-sudo/nm-sudo.c \
$(NULL)
src_nm_sudo_nm_sudo_CPPFLAGS = \
$(dflt_cppflags) \
-I$(builddir)/src/libnm-core-public \
-I$(srcdir)/src/libnm-core-public \
-I$(builddir)/src/libnm-client-public \
-I$(srcdir)/src/libnm-client-public \
-I$(srcdir)/src \
-I$(builddir)/src \
$(GLIB_CFLAGS) \
$(NULL)
src_nm_sudo_nm_sudo_LDFLAGS = \
-Wl,--version-script="$(srcdir)/linker-script-binary.ver" \
$(SANITIZER_EXEC_LDFLAGS) \
$(NULL)
src_nm_sudo_nm_sudo_LDADD = \
src/libnm-base/libnm-base.la \
src/libnm-glib-aux/libnm-glib-aux.la \
src/libnm-std-aux/libnm-std-aux.la \
src/c-siphash/libc-siphash.la \
$(GLIB_LIBS) \
$(NULL)
src/nm-sudo/org.freedesktop.nm.sudo.service: $(srcdir)/src/nm-sudo/org.freedesktop.nm.sudo.service.in
@sed \
-e 's|@libexecdir[@]|$(libexecdir)|g' \
$< >$@
dbusactivation_DATA += src/nm-sudo/org.freedesktop.nm.sudo.service
CLEANFILES += src/nm-sudo/org.freedesktop.nm.sudo.service
dbusservice_DATA += src/nm-sudo/nm-sudo.conf
EXTRA_DIST += \
src/nm-sudo/nm-sudo.conf \
src/nm-sudo/org.freedesktop.nm.sudo.service.in \
src/nm-sudo/meson.build \
$(NULL)
###############################################################################
# src/nm-daemon-helper
###############################################################################
@ -5299,6 +5353,7 @@ systemdsystemunit_DATA += \
data/NetworkManager.service \
data/NetworkManager-wait-online.service \
data/NetworkManager-dispatcher.service \
data/nm-sudo.service \
$(NULL)
data/NetworkManager.service: $(srcdir)/data/NetworkManager.service.in
@ -5315,6 +5370,9 @@ endif
data/NetworkManager-dispatcher.service: $(srcdir)/data/NetworkManager-dispatcher.service.in
$(AM_V_GEN) $(data_edit) $< >$@
data/nm-sudo.service: $(srcdir)/data/nm-sudo.service.in
$(AM_V_GEN) $(data_edit) $< >$@
endif
examples_DATA += data/server.conf
@ -5344,6 +5402,7 @@ EXTRA_DIST += \
data/NetworkManager-wait-online-systemd-pre200.service.in \
data/NetworkManager-wait-online.service.in \
data/NetworkManager.service.in \
data/nm-sudo.service.in \
data/meson.build \
data/nm-shared.xml \
data/server.conf.in \
@ -5353,6 +5412,7 @@ CLEANFILES += \
data/NetworkManager-dispatcher.service \
data/NetworkManager-wait-online.service \
data/NetworkManager.service \
data/nm-sudo.service \
data/server.conf \
$(NULL)

View File

@ -40,7 +40,7 @@
%global real_version_major %(printf '%s' '%{real_version}' | sed -n 's/^\\([1-9][0-9]*\\.[0-9][0-9]*\\)\\.[0-9][0-9]*$/\\1/p')
%global systemd_units NetworkManager.service NetworkManager-wait-online.service NetworkManager-dispatcher.service
%global systemd_units NetworkManager.service NetworkManager-wait-online.service NetworkManager-dispatcher.service nm-sudo.service
%global systemd_units_cloud_setup nm-cloud-setup.service nm-cloud-setup.timer
@ -940,7 +940,7 @@ if [ $1 -eq 0 ]; then
/usr/sbin/update-alternatives --remove ifup %{_libexecdir}/nm-ifup >/dev/null 2>&1 || :
fi
%systemd_preun NetworkManager-wait-online.service NetworkManager-dispatcher.service
%systemd_preun NetworkManager-wait-online.service NetworkManager-dispatcher.service nm-sudo.service
%if %{with nm_cloud_setup}
@ -974,6 +974,7 @@ fi
%files
%{dbus_sys_dir}/org.freedesktop.NetworkManager.conf
%{dbus_sys_dir}/nm-dispatcher.conf
%{dbus_sys_dir}/nm-sudo.conf
%{dbus_sys_dir}/nm-ifcfg-rh.conf
%{_sbindir}/%{name}
%{_bindir}/nmcli
@ -999,6 +1000,7 @@ fi
%{_libexecdir}/nm-iface-helper
%{_libexecdir}/nm-initrd-generator
%{_libexecdir}/nm-daemon-helper
%{_libexecdir}/nm-sudo
%dir %{_libdir}/%{name}
%dir %{nmplugindir}
%{nmplugindir}/libnm-settings-plugin*.so
@ -1022,6 +1024,7 @@ fi
%dir %{_localstatedir}/lib/NetworkManager
%dir %{_sysconfdir}/sysconfig/network-scripts
%{_datadir}/dbus-1/system-services/org.freedesktop.nm_dispatcher.service
%{_datadir}/dbus-1/system-services/org.freedesktop.nm.sudo.service
%{_datadir}/polkit-1/actions/*.policy
%{_prefix}/lib/udev/rules.d/*.rules
%if %{with firewalld_zone}
@ -1031,6 +1034,7 @@ fi
%{systemd_dir}/NetworkManager.service
%{systemd_dir}/NetworkManager-wait-online.service
%{systemd_dir}/NetworkManager-dispatcher.service
%{systemd_dir}/nm-sudo.service
%dir %{_datadir}/doc/NetworkManager/examples
%{_datadir}/doc/NetworkManager/examples/server.conf
%doc NEWS AUTHORS README CONTRIBUTING.md TODO

View File

@ -11,6 +11,7 @@ if install_systemdunitdir
services = [
'NetworkManager-dispatcher.service.in',
'NetworkManager.service.in',
'nm-sudo.service.in',
]
if have_systemd_200

66
data/nm-sudo.service.in Normal file
View File

@ -0,0 +1,66 @@
[Unit]
Description=Network Manager Sudo Helper
#
# nm-sudo exists for privilege separation. It allows to run NetworkManager
# without certain capabilities, and ask nm-sudo for special operations
# where more privileges are required.
#
# While nm-sudo has privileges that NetworkManager has not, it does not
# mean that itself should run totally unconstrained. On the contrary, it
# also should only have permissions it requires.
#
# nm-sudo rejects all requests that come from any other than the name
# owner of "org.freedesktop.NetworkManager" (that is, NetworkManager process
# itself). It is thus only an implementation detail and provides no public
# API to the user.
[Service]
Type=dbus
BusName=org.freedesktop.nm.sudo
ExecStart=@libexecdir@/nm-sudo
# Environment=NM_SUDO_NO_AUTH_FOR_TESTING=0
# Environment=NM_SUDO_IDLE_TIMEOUT=10
# Environment=NM_SUDO_LOG=TRACE
# Environment=G_DEBUG=fatal-warnings
# Environment=G_DBUS_DEBUG=all
[Install]
Alias=dbus-org.freedesktop.nm.sudo.service
[Service]
# Restrict:
AmbientCapabilities=
CapabilityBoundingSet=
PrivateDevices=true
PrivateMounts=true
PrivateNetwork=true
PrivateTmp=true
ProtectClock=true
ProtectControlGroups=true
ProtectHome=true
ProtectHostname=true
ProtectKernelLogs=true
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectSystem=strict
RestrictAddressFamilies=
RestrictNamespaces=true
SystemCallFilter=~@clock
SystemCallFilter=~@cpu-emulation
SystemCallFilter=~@debug
SystemCallFilter=~@module
SystemCallFilter=~@mount
SystemCallFilter=~@obsolete
SystemCallFilter=~@privileged
SystemCallFilter=~@raw-io
SystemCallFilter=~@reboot
SystemCallFilter=~@swap
NoNewPrivileges=true
SupplementaryGroups=
# Grant:
CapabilityBoundingSet=CAP_DAC_OVERRIDE
PrivateUsers=no
RestrictAddressFamilies=AF_UNIX
SystemCallFilter=@resources

View File

@ -1,6 +1,7 @@
contrib/fedora/rpm/
data/NetworkManager-wait-online.service.in
data/NetworkManager.service.in
data/nm-sudo.service.in
data/org.freedesktop.NetworkManager.policy.in
examples/python/NetworkManager.py
examples/python/systray/eggtrayicon.c

View File

@ -172,6 +172,7 @@ libNetworkManager = static_library(
'nm-rfkill-manager.c',
'nm-session-monitor.c',
'nm-sleep-monitor.c',
'nm-sudo-call.c',
),
dependencies: nm_deps,
link_with: [

5
src/core/nm-sudo-call.c Normal file
View File

@ -0,0 +1,5 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "src/core/nm-default-daemon.h"
#include "nm-sudo-call.h"

6
src/core/nm-sudo-call.h Normal file
View File

@ -0,0 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef __NM_SUDO_CALL_H__
#define __NM_SUDO_CALL_H__
#endif /* __NM_SUDO_CALL_H__ */

View File

@ -5,6 +5,7 @@ libnm_base = static_library(
sources: files(
'nm-ethtool-base.c',
'nm-net-aux.c',
'nm-sudo-utils.c',
),
include_directories: [
src_inc,

View File

@ -0,0 +1,5 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "libnm-glib-aux/nm-default-glib-i18n-lib.h"
#include "nm-sudo-utils.h"

View File

@ -0,0 +1,14 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef __NM_SUDO_UTILS_H__
#define __NM_SUDO_UTILS_H__
/*****************************************************************************/
#define NM_SUDO_DBUS_BUS_NAME "org.freedesktop.nm.sudo"
#define NM_SUDO_DBUS_OBJECT_PATH "/org/freedesktop/nm/sudo"
#define NM_SUDO_DBUS_IFACE_NAME "org.freedesktop.nm.sudo"
/*****************************************************************************/
#endif /* __NM_SUDO_UTILS_H__ */

View File

@ -93,6 +93,7 @@ if enable_nmtui
endif
subdir('nmcli')
subdir('nm-dispatcher')
subdir('nm-sudo')
subdir('nm-daemon-helper')
subdir('nm-online')
if enable_nmtui

36
src/nm-sudo/meson.build Normal file
View File

@ -0,0 +1,36 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
configure_file(
input: 'org.freedesktop.nm.sudo.service.in',
output: '@BASENAME@',
install_dir: dbus_system_bus_services_dir,
configuration: data_conf,
)
install_data(
'nm-sudo.conf',
install_dir: dbus_conf_dir,
)
executable(
'nm-sudo',
'nm-sudo.c',
include_directories : [
src_inc,
top_inc,
],
dependencies: [
glib_dep,
],
link_with: [
libnm_base,
libnm_log_null,
libnm_glib_aux,
libnm_std_aux,
libc_siphash,
],
link_args: ldflags_linker_script_binary,
link_depends: linker_script_binary,
install: true,
install_dir: nm_libexecdir,
)

600
src/nm-sudo/nm-sudo.c Normal file
View File

@ -0,0 +1,600 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "libnm-glib-aux/nm-default-glib-i18n-prog.h"
#include "c-list/src/c-list.h"
#include "libnm-glib-aux/nm-logging-base.h"
#include "libnm-glib-aux/nm-shared-utils.h"
#include "libnm-glib-aux/nm-time-utils.h"
#include "libnm-glib-aux/nm-dbus-aux.h"
#include "libnm-base/nm-sudo-utils.h"
/* nm-sudo doesn't link with libnm-core nor libnm-base, but these headers
* can be used independently. */
#include "libnm-core-public/nm-dbus-interface.h"
/*****************************************************************************/
#define IDLE_TIMEOUT_MSEC 2000
#define IDLE_TIMEOUT_INFINITY G_MAXINT32
/*****************************************************************************/
/* Serves only the purpose to mark environment variables that are honored by
* the application. You can search for this macro, and find what options are supported. */
#define _ENV(var) ("" var "")
/*****************************************************************************/
typedef struct _GlobalData GlobalData;
typedef struct {
CList pending_jobs_lst;
GlobalData *gl;
} PendingJobData;
struct _GlobalData {
GCancellable * quit_cancellable;
GDBusConnection *dbus_connection;
GSource * source_sigterm;
CList pending_jobs_lst_head;
GSource *source_idle_timeout;
char * name_owner;
guint name_owner_changed_id;
guint service_regist_id;
gint64 start_timestamp_msec;
guint32 timeout_msec;
bool name_owner_initialized;
bool service_registered;
/* This is controlled by $NM_SUDO_NO_AUTH_FOR_TESTING. It disables authentication
* of the request, so it is ONLY for testing. */
bool no_auth_for_testing;
bool is_shutting_down_quitting;
bool is_shutting_down_timeout;
bool is_shutting_down_cleanup;
};
/*****************************************************************************/
static void _pending_job_register_object(GlobalData *gl, GObject *obj);
/*****************************************************************************/
#define _nm_log(level, ...) _nm_log_simple_printf((level), __VA_ARGS__);
#define _NMLOG(level, ...) \
G_STMT_START \
{ \
const NMLogLevel _level = (level); \
\
if (_nm_logging_enabled(_level)) { \
_nm_log(_level, __VA_ARGS__); \
} \
} \
G_STMT_END
/*****************************************************************************/
static void
_handle_ping(GlobalData *gl, GDBusMethodInvocation *invocation, const char *arg)
{
gs_free char *msg = NULL;
gint64 running_msec;
running_msec = nm_utils_clock_gettime_msec(CLOCK_BOOTTIME) - gl->start_timestamp_msec;
msg = g_strdup_printf("pid=%lu, unique-name=%s, nm-name-owner=%s, since=%ld.%03d%s, pong=%s",
(unsigned long) getpid(),
g_dbus_connection_get_unique_name(gl->dbus_connection),
gl->name_owner ?: "(none)",
running_msec / 1000,
(int) (running_msec % 1000),
gl->no_auth_for_testing ? ", no-auth-for-testing" : "",
arg);
g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", msg));
}
/*****************************************************************************/
static gboolean
_signal_callback_term(gpointer user_data)
{
GlobalData *gl = user_data;
_LOGD("sigterm received (%s)",
c_list_is_empty(&gl->pending_jobs_lst_head) ? "quit mainloop" : "cancel operations");
gl->is_shutting_down_quitting = TRUE;
g_cancellable_cancel(gl->quit_cancellable);
return G_SOURCE_CONTINUE;
}
/*****************************************************************************/
typedef struct {
GDBusConnection **p_dbus_connection;
GError ** p_error;
} BusGetData;
static void
_bus_get_cb(GObject *source, GAsyncResult *result, gpointer user_data)
{
BusGetData *data = user_data;
*data->p_dbus_connection = g_bus_get_finish(result, data->p_error);
}
static GDBusConnection *
_bus_get(GCancellable *cancellable, int *out_exit_code)
{
gs_free_error GError *error = NULL;
gs_unref_object GDBusConnection *dbus_connection = NULL;
BusGetData data = {
.p_dbus_connection = &dbus_connection,
.p_error = &error,
};
g_bus_get(G_BUS_TYPE_SYSTEM, cancellable, _bus_get_cb, &data);
while (!dbus_connection && !error)
g_main_context_iteration(NULL, TRUE);
if (!dbus_connection) {
gboolean was_cancelled = nm_utils_error_is_cancelled(error);
NM_SET_OUT(out_exit_code, was_cancelled ? EXIT_SUCCESS : EXIT_FAILURE);
if (!was_cancelled)
_LOGE("dbus: failure to get D-Bus connection: %s", error->message);
return NULL;
}
/* On bus-disconnect, GDBus will raise(SIGTERM), which we handle like a
* regular request to quit. */
g_dbus_connection_set_exit_on_close(dbus_connection, TRUE);
_LOGD("dbus: unique name: %s", g_dbus_connection_get_unique_name(dbus_connection));
return g_steal_pointer(&dbus_connection);
}
/*****************************************************************************/
static void
_name_owner_changed_cb(GDBusConnection *connection,
const char * sender_name,
const char * object_path,
const char * interface_name,
const char * signal_name,
GVariant * parameters,
gpointer user_data)
{
GlobalData *gl = user_data;
const char *new_owner;
if (!gl->name_owner_initialized)
return;
if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sss)")))
return;
g_variant_get(parameters, "(&s&s&s)", NULL, NULL, &new_owner);
new_owner = nm_str_not_empty(new_owner);
_LOGD("%s name-owner changed: %s -> %s",
NM_DBUS_SERVICE,
gl->name_owner ?: "(null)",
new_owner ?: "(null)");
nm_utils_strdup_reset(&gl->name_owner, new_owner);
}
typedef struct {
GlobalData *gl;
char ** p_name_owner;
gboolean is_cancelled;
} BusFindNMNameOwnerData;
static void
_bus_find_nm_nameowner_cb(const char *name_owner, GError *error, gpointer user_data)
{
BusFindNMNameOwnerData *data = user_data;
*data->p_name_owner = nm_strdup_not_empty(name_owner);
data->is_cancelled = nm_utils_error_is_cancelled(error);
data->gl->name_owner_initialized = TRUE;
}
static gboolean
_bus_find_nm_nameowner(GlobalData *gl)
{
BusFindNMNameOwnerData data;
guint name_owner_changed_id;
gs_free char * name_owner = NULL;
name_owner_changed_id =
nm_dbus_connection_signal_subscribe_name_owner_changed(gl->dbus_connection,
NM_DBUS_SERVICE,
_name_owner_changed_cb,
gl,
NULL);
data = (BusFindNMNameOwnerData){
.gl = gl,
.is_cancelled = FALSE,
.p_name_owner = &name_owner,
};
nm_dbus_connection_call_get_name_owner(gl->dbus_connection,
NM_DBUS_SERVICE,
10000,
gl->quit_cancellable,
_bus_find_nm_nameowner_cb,
&data);
while (!gl->name_owner_initialized)
g_main_context_iteration(NULL, TRUE);
if (data.is_cancelled) {
g_dbus_connection_signal_unsubscribe(gl->dbus_connection, name_owner_changed_id);
return FALSE;
}
gl->name_owner_changed_id = name_owner_changed_id;
gl->name_owner = g_steal_pointer(&name_owner);
return TRUE;
}
/*****************************************************************************/
static void
_bus_method_call(GDBusConnection * connection,
const char * sender,
const char * object_path,
const char * interface_name,
const char * method_name,
GVariant * parameters,
GDBusMethodInvocation *invocation,
gpointer user_data)
{
GlobalData *gl = user_data;
const char *arg_s;
nm_assert(nm_streq(object_path, NM_SUDO_DBUS_OBJECT_PATH));
nm_assert(nm_streq(interface_name, NM_SUDO_DBUS_IFACE_NAME));
if (!gl->no_auth_for_testing && !nm_streq0(sender, gl->name_owner)) {
_LOGT("dbus: request sender=%s, %s%s, ACCESS DENIED",
sender,
method_name,
g_variant_get_type_string(parameters));
g_dbus_method_invocation_return_error(invocation,
G_DBUS_ERROR,
G_DBUS_ERROR_ACCESS_DENIED,
"Access denied");
return;
}
_pending_job_register_object(gl, G_OBJECT(invocation));
_LOGT("dbus: request sender=%s, %s%s",
sender,
method_name,
g_variant_get_type_string(parameters));
if (nm_streq(method_name, "Ping")) {
g_variant_get(parameters, "(&s)", &arg_s);
_handle_ping(gl, invocation, arg_s);
} else
nm_assert_not_reached();
}
static GDBusInterfaceInfo *const interface_info = NM_DEFINE_GDBUS_INTERFACE_INFO(
NM_SUDO_DBUS_IFACE_NAME,
.methods = NM_DEFINE_GDBUS_METHOD_INFOS(
NM_DEFINE_GDBUS_METHOD_INFO(
"Ping",
.in_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("arg", "s"), ),
.out_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("arg", "s"), ), ), ), );
typedef struct {
GlobalData *gl;
gboolean is_waiting;
} BusRegisterServiceRequestNameData;
static void
_bus_register_service_request_name_cb(GObject *source, GAsyncResult *res, gpointer user_data)
{
BusRegisterServiceRequestNameData *data = user_data;
gs_free_error GError *error = NULL;
gs_unref_variant GVariant *ret = NULL;
gboolean success = FALSE;
ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), res, &error);
if (nm_utils_error_is_cancelled(error))
goto out;
if (error)
_LOGE("d-bus: failed to request name %s: %s", NM_SUDO_DBUS_BUS_NAME, error->message);
else {
guint32 ret_val;
g_variant_get(ret, "(u)", &ret_val);
if (ret_val != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
_LOGW("dbus: request name for %s failed to take name (response %u)",
NM_SUDO_DBUS_BUS_NAME,
ret_val);
} else {
_LOGD("dbus: request name for %s succeeded", NM_SUDO_DBUS_BUS_NAME);
success = TRUE;
}
}
out:
if (success)
data->gl->service_registered = TRUE;
data->is_waiting = FALSE;
}
static void
_bus_register_service(GlobalData *gl)
{
static const GDBusInterfaceVTable interface_vtable = {
.method_call = _bus_method_call,
};
gs_free_error GError * error = NULL;
BusRegisterServiceRequestNameData data;
nm_assert(!gl->service_registered);
gl->service_regist_id =
g_dbus_connection_register_object(gl->dbus_connection,
NM_SUDO_DBUS_OBJECT_PATH,
interface_info,
NM_UNCONST_PTR(GDBusInterfaceVTable, &interface_vtable),
gl,
NULL,
&error);
if (gl->service_regist_id == 0) {
_LOGE("dbus: error registering object %s: %s", NM_SUDO_DBUS_OBJECT_PATH, error->message);
return;
}
_LOGD("dbus: object %s registered", NM_SUDO_DBUS_OBJECT_PATH);
data = (BusRegisterServiceRequestNameData){
.gl = gl,
.is_waiting = TRUE,
};
g_dbus_connection_call(
gl->dbus_connection,
DBUS_SERVICE_DBUS,
DBUS_PATH_DBUS,
DBUS_INTERFACE_DBUS,
"RequestName",
g_variant_new("(su)",
NM_SUDO_DBUS_BUS_NAME,
(guint) (DBUS_NAME_FLAG_ALLOW_REPLACEMENT | DBUS_NAME_FLAG_REPLACE_EXISTING)),
G_VARIANT_TYPE("(u)"),
G_DBUS_CALL_FLAGS_NONE,
-1,
gl->quit_cancellable,
_bus_register_service_request_name_cb,
&data);
/* Note that with D-Bus activation, the first request will already hit us before RequestName
* completes. */
while (data.is_waiting)
g_main_context_iteration(NULL, TRUE);
}
/*****************************************************************************/
static gboolean
_idle_timeout_cb(gpointer user_data)
{
GlobalData *gl = user_data;
_LOGT("idle-timeout: expired");
gl->is_shutting_down_timeout = TRUE;
return G_SOURCE_CONTINUE;
}
static void
_idle_timeout_restart(GlobalData *gl)
{
nm_clear_g_source_inst(&gl->source_idle_timeout);
if (gl->is_shutting_down_quitting)
return;
if (gl->is_shutting_down_cleanup)
return;
if (!c_list_is_empty(&gl->pending_jobs_lst_head))
return;
if (gl->timeout_msec == IDLE_TIMEOUT_INFINITY)
return;
nm_assert(gl->timeout_msec < G_MAXINT32);
G_STATIC_ASSERT_EXPR(G_MAXINT32 < G_MAXUINT);
_LOGT("idle-timeout: start (%u msec)", gl->timeout_msec);
gl->source_idle_timeout = nm_g_timeout_add_source(gl->timeout_msec, _idle_timeout_cb, gl);
}
/*****************************************************************************/
static gboolean
_pending_job_register_object_release_on_idle_cb(gpointer data)
{
PendingJobData *idle_data = data;
GlobalData * gl = idle_data->gl;
c_list_unlink_stale(&idle_data->pending_jobs_lst);
nm_g_slice_free(idle_data);
_idle_timeout_restart(gl);
return G_SOURCE_REMOVE;
}
static void
_pending_job_register_object_weak_cb(gpointer data, GObject *where_the_object_was)
{
/* The object might be destroyed on another thread. We need
* to sync with the main GMainContext by scheduling an idle action
* there. */
nm_g_idle_add(_pending_job_register_object_release_on_idle_cb, data);
}
static void
_pending_job_register_object(GlobalData *gl, GObject *obj)
{
PendingJobData *idle_data;
/* if we just hit the timeout, we can ignore it. */
gl->is_shutting_down_timeout = FALSE;
if (nm_clear_g_source_inst(&gl->source_idle_timeout))
_LOGT("idle-timeout: suspend timeout for pending request");
idle_data = g_slice_new(PendingJobData);
idle_data->gl = gl;
c_list_link_tail(&gl->pending_jobs_lst_head, &idle_data->pending_jobs_lst);
g_object_weak_ref(obj, _pending_job_register_object_weak_cb, idle_data);
}
/*****************************************************************************/
static void
_initial_setup(GlobalData *gl)
{
gl->no_auth_for_testing =
_nm_utils_ascii_str_to_int64(g_getenv(_ENV("NM_SUDO_NO_AUTH_FOR_TESTING")), 0, 0, 1, 0);
gl->timeout_msec = _nm_utils_ascii_str_to_int64(g_getenv(_ENV("NM_SUDO_IDLE_TIMEOUT_MSEC")),
0,
0,
G_MAXINT32,
IDLE_TIMEOUT_MSEC);
gl->quit_cancellable = g_cancellable_new();
signal(SIGPIPE, SIG_IGN);
gl->source_sigterm = nm_g_unix_signal_add_source(SIGTERM, _signal_callback_term, gl);
}
int
main(int argc, char **argv)
{
GlobalData _gl = {
.quit_cancellable = NULL,
.pending_jobs_lst_head = C_LIST_INIT(_gl.pending_jobs_lst_head),
};
GlobalData *const gl = &_gl;
int exit_code;
int r = 0;
_nm_logging_enabled_init(g_getenv(_ENV("NM_SUDO_LOG")));
gl->start_timestamp_msec = nm_utils_clock_gettime_msec(CLOCK_BOOTTIME);
_LOGD("starting nm-sudo (%s)", NM_DIST_VERSION);
_initial_setup(gl);
if (gl->no_auth_for_testing) {
_LOGW("WARNING: running in debug mode without authentication "
"(NM_SUDO_NO_AUTH_FOR_TESTING). ");
}
if (gl->timeout_msec != IDLE_TIMEOUT_INFINITY)
_LOGT("idle-timeout: %u msec", gl->timeout_msec);
else
_LOGT("idle-timeout: disabled");
gl->dbus_connection = _bus_get(gl->quit_cancellable, &r);
if (!gl->dbus_connection) {
exit_code = r;
goto done;
}
if (!_bus_find_nm_nameowner(gl)) {
/* abort due to cancellation. That is success. */
exit_code = EXIT_SUCCESS;
goto done;
}
_LOGD("%s name-owner: %s", NM_DBUS_SERVICE, gl->name_owner ?: "(null)");
_idle_timeout_restart(gl);
exit_code = EXIT_SUCCESS;
_bus_register_service(gl);
if (!gl->service_registered) {
/* We failed to RequestName, but due to D-Bus activation we
* might have a pending request still (on the unique name).
* Process it below.
*
* Let's fake a shutdown signal, and still process the request below. */
exit_code = EXIT_FAILURE;
gl->is_shutting_down_quitting = TRUE;
}
while (TRUE) {
if (!c_list_is_empty(&gl->pending_jobs_lst_head)) {
/* we must first reply to all requests. No matter what. */
} else {
if (gl->is_shutting_down_quitting || gl->is_shutting_down_timeout) {
/* we either hit the idle timeout or received SIGTERM. Note that
* if we received an idle-timeout and the very moment afterwards
* a new request, then _bus_method_call() will clear gl->is_shutting_down_timeout
* (via _pending_job_register_object()). */
break;
}
}
g_main_context_iteration(NULL, TRUE);
}
done:
gl->is_shutting_down_cleanup = TRUE;
_LOGD("exiting...");
nm_assert(c_list_is_empty(&gl->pending_jobs_lst_head));
if (gl->service_regist_id != 0) {
g_dbus_connection_unregister_object(gl->dbus_connection,
nm_steal_int(&gl->service_regist_id));
}
if (gl->name_owner_changed_id != 0) {
g_dbus_connection_signal_unsubscribe(gl->dbus_connection,
nm_steal_int(&gl->name_owner_changed_id));
}
nm_clear_g_cancellable(&gl->quit_cancellable);
nm_clear_g_source_inst(&gl->source_sigterm);
nm_clear_g_source_inst(&gl->source_idle_timeout);
nm_clear_g_free(&gl->name_owner);
while (g_main_context_iteration(NULL, FALSE)) {
;
}
if (gl->dbus_connection) {
g_dbus_connection_flush_sync(gl->dbus_connection, NULL, NULL);
g_clear_object(&gl->dbus_connection);
while (g_main_context_iteration(NULL, FALSE)) {
;
}
}
_LOGD("exit (%d)", exit_code);
return exit_code;
}

13
src/nm-sudo/nm-sudo.conf Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE busconfig PUBLIC
"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<policy user="root">
<allow own="org.freedesktop.nm.sudo"/>
<allow send_destination="org.freedesktop.nm.sudo"/>
</policy>
<policy context="default">
<deny own="org.freedesktop.nm.sudo"/>
<deny send_destination="org.freedesktop.nm.sudo"/>
</policy>
</busconfig>

View File

@ -0,0 +1,5 @@
[D-BUS Service]
Name=org.freedesktop.nm.sudo
Exec=@libexecdir@/nm-sudo
User=root
SystemdService=dbus-org.freedesktop.nm.sudo.service