// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2014 Red Hat, Inc. */ #include "nm-default.h" #include "nm-auth-manager.h" #include "c-list/src/c-list.h" #include "nm-glib-aux/nm-dbus-aux.h" #include "nm-errors.h" #include "nm-core-internal.h" #include "nm-dbus-manager.h" #include "NetworkManagerUtils.h" #define POLKIT_SERVICE "org.freedesktop.PolicyKit1" #define POLKIT_OBJECT_PATH "/org/freedesktop/PolicyKit1/Authority" #define POLKIT_INTERFACE "org.freedesktop.PolicyKit1.Authority" #define CANCELLATION_ID_PREFIX "cancellation-id-" #define CANCELLATION_TIMEOUT_MS 5000 /*****************************************************************************/ NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_POLKIT_ENABLED, ); enum { CHANGED_SIGNAL, LAST_SIGNAL, }; static guint signals[LAST_SIGNAL] = {0}; typedef struct { CList calls_lst_head; GDBusConnection *dbus_connection; GCancellable * main_cancellable; char * name_owner; guint64 call_numid_counter; guint changed_id; guint name_owner_changed_id; bool disposing : 1; bool shutting_down : 1; bool got_name_owner : 1; NMAuthPolkitMode auth_polkit_mode : 3; } NMAuthManagerPrivate; struct _NMAuthManager { GObject parent; NMAuthManagerPrivate _priv; }; struct _NMAuthManagerClass { GObjectClass parent; }; G_DEFINE_TYPE(NMAuthManager, nm_auth_manager, G_TYPE_OBJECT) #define NM_AUTH_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMAuthManager, NM_IS_AUTH_MANAGER) NM_DEFINE_SINGLETON_REGISTER(NMAuthManager); /*****************************************************************************/ #define _NMLOG_PREFIX_NAME "auth" #define _NMLOG_DOMAIN LOGD_CORE #define _NMLOG(level, ...) \ G_STMT_START \ { \ if (nm_logging_enabled((level), (_NMLOG_DOMAIN))) { \ char __prefix[30] = _NMLOG_PREFIX_NAME; \ \ if ((self) != singleton_instance) \ g_snprintf(__prefix, \ sizeof(__prefix), \ ""_NMLOG_PREFIX_NAME \ "[%p]", \ (self)); \ _nm_log((level), \ (_NMLOG_DOMAIN), \ 0, \ NULL, \ NULL, \ "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ __prefix _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ } \ } \ G_STMT_END #define _NMLOG2(level, call_id, ...) \ G_STMT_START \ { \ if (nm_logging_enabled((level), (_NMLOG_DOMAIN))) { \ NMAuthManagerCallId *_call_id = (call_id); \ char __prefix[30] = _NMLOG_PREFIX_NAME; \ \ if (_call_id->self != singleton_instance) \ g_snprintf(__prefix, \ sizeof(__prefix), \ ""_NMLOG_PREFIX_NAME \ "[%p]", \ _call_id->self); \ _nm_log((level), \ (_NMLOG_DOMAIN), \ 0, \ NULL, \ NULL, \ "%s: call[%" G_GUINT64_FORMAT "]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ __prefix, \ _call_id->call_numid _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ } \ } \ G_STMT_END /*****************************************************************************/ gboolean nm_auth_manager_get_polkit_enabled(NMAuthManager *self) { g_return_val_if_fail(NM_IS_AUTH_MANAGER(self), FALSE); return NM_AUTH_MANAGER_GET_PRIVATE(self)->dbus_connection != NULL; } /*****************************************************************************/ static void _emit_changed_signal(NMAuthManager *self) { g_signal_emit(self, signals[CHANGED_SIGNAL], 0); } typedef enum { POLKIT_CHECK_AUTHORIZATION_FLAGS_NONE = 0, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION = (1 << 0), } PolkitCheckAuthorizationFlags; struct _NMAuthManagerCallId { CList calls_lst; NMAuthManager * self; GCancellable * dbus_cancellable; NMAuthManagerCheckAuthorizationCallback callback; gpointer user_data; guint64 call_numid; guint idle_id; bool idle_is_authorized : 1; }; #define cancellation_id_to_str_a(call_numid) \ nm_sprintf_bufa(NM_STRLEN(CANCELLATION_ID_PREFIX) + 60, \ CANCELLATION_ID_PREFIX "%" G_GUINT64_FORMAT, \ (call_numid)) static void _call_id_free(NMAuthManagerCallId *call_id) { c_list_unlink(&call_id->calls_lst); nm_clear_g_source(&call_id->idle_id); if (call_id->dbus_cancellable) { /* we have a pending D-Bus call. We keep the call-id instance alive * for _call_check_authorize_cb() */ g_cancellable_cancel(call_id->dbus_cancellable); return; } g_object_unref(call_id->self); g_slice_free(NMAuthManagerCallId, call_id); } static void _call_id_invoke_callback(NMAuthManagerCallId *call_id, gboolean is_authorized, gboolean is_challenge, GError * error) { c_list_unlink(&call_id->calls_lst); call_id ->callback(call_id->self, call_id, is_authorized, is_challenge, error, call_id->user_data); _call_id_free(call_id); } static void cancel_check_authorization_cb(GObject *source, GAsyncResult *res, gpointer user_data) { NMAuthManagerCallId *call_id = user_data; gs_unref_variant GVariant *value = NULL; gs_free_error GError *error = NULL; value = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), res, &error); if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) _LOG2T(call_id, "cancel request was cancelled"); else if (error) _LOG2T(call_id, "cancel request failed: %s", error->message); else _LOG2T(call_id, "cancel request succeeded"); _call_id_free(call_id); } static void _call_check_authorize_cb(GObject *proxy, GAsyncResult *res, gpointer user_data) { NMAuthManagerCallId * call_id = user_data; NMAuthManager * self; NMAuthManagerPrivate *priv; gs_unref_variant GVariant *value = NULL; gs_free_error GError *error = NULL; gboolean is_authorized = FALSE; gboolean is_challenge = FALSE; /* we need to clear the cancelable, to signal for _call_id_free() that we * are not in a pending call. * * Note how _call_id_free() kept call-id alive, even if the request was * already cancelled. */ g_clear_object(&call_id->dbus_cancellable); self = call_id->self; priv = NM_AUTH_MANAGER_GET_PRIVATE(self); value = g_dbus_connection_call_finish(G_DBUS_CONNECTION(proxy), res, &error); if (nm_utils_error_is_cancelled(error)) { /* call_id was cancelled externally, but _call_id_free() kept call_id * alive (and it has still the reference on @self. */ if (!priv->main_cancellable) { /* we do a forced shutdown. There is no more time for cancelling... */ _call_id_free(call_id); /* this shouldn't really happen, because: * nm_auth_manager_check_authorization() only scheduled the D-Bus request at a time when * main_cancellable was still set. It means, somebody called force-shutdown * after call-id was schedule. * force-shutdown should only be called after: * - cancel all pending requests * - give enough time to cancel the request and schedule a D-Bus call * to CancelCheckAuthorization (below), before issuing force-shutdown. */ g_return_if_reached(); } g_dbus_connection_call(priv->dbus_connection, POLKIT_SERVICE, POLKIT_OBJECT_PATH, POLKIT_INTERFACE, "CancelCheckAuthorization", g_variant_new("(s)", cancellation_id_to_str_a(call_id->call_numid)), G_VARIANT_TYPE("()"), G_DBUS_CALL_FLAGS_NONE, CANCELLATION_TIMEOUT_MS, priv->main_cancellable, cancel_check_authorization_cb, call_id); return; } if (!error) { g_variant_get(value, "((bb@a{ss}))", &is_authorized, &is_challenge, NULL); _LOG2T(call_id, "completed: authorized=%d, challenge=%d", is_authorized, is_challenge); } else _LOG2T(call_id, "completed: failed: %s", error->message); _call_id_invoke_callback(call_id, is_authorized, is_challenge, error); } static gboolean _call_on_idle(gpointer user_data) { NMAuthManagerCallId *call_id = user_data; gboolean is_authorized; gboolean is_challenge = FALSE; is_authorized = call_id->idle_is_authorized; call_id->idle_id = 0; _LOG2T(call_id, "completed: authorized=%d, challenge=%d (simulated)", is_authorized, is_challenge); _call_id_invoke_callback(call_id, is_authorized, is_challenge, NULL); return G_SOURCE_REMOVE; } /* * @callback must never be invoked synchronously. * * @callback is always invoked exactly once, and never synchronously. * You may cancel the invocation with nm_auth_manager_check_authorization_cancel(), * but: you may only do so exactly once, and only before @callback is * invoked. Even if you cancel the request, @callback will still be invoked * (synchronously, during the _cancel() callback). * * The request keeps @self alive (it needs to do so, because when cancelling a * request we might need to do an additional CancelCheckAuthorization call, for * which @self must be live long enough). */ NMAuthManagerCallId * nm_auth_manager_check_authorization(NMAuthManager * self, NMAuthSubject * subject, const char * action_id, gboolean allow_user_interaction, NMAuthManagerCheckAuthorizationCallback callback, gpointer user_data) { NMAuthManagerPrivate * priv; PolkitCheckAuthorizationFlags flags; char subject_buf[64]; NMAuthManagerCallId * call_id; g_return_val_if_fail(NM_IS_AUTH_MANAGER(self), NULL); g_return_val_if_fail(NM_IN_SET(nm_auth_subject_get_subject_type(subject), NM_AUTH_SUBJECT_TYPE_INTERNAL, NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS), NULL); g_return_val_if_fail(action_id, NULL); priv = NM_AUTH_MANAGER_GET_PRIVATE(self); g_return_val_if_fail(!priv->disposing, NULL); g_return_val_if_fail(!priv->shutting_down, NULL); flags = allow_user_interaction ? POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION : POLKIT_CHECK_AUTHORIZATION_FLAGS_NONE; call_id = g_slice_new(NMAuthManagerCallId); *call_id = (NMAuthManagerCallId){ .self = g_object_ref(self), .callback = callback, .user_data = user_data, .call_numid = ++priv->call_numid_counter, .idle_is_authorized = TRUE, }; c_list_link_tail(&priv->calls_lst_head, &call_id->calls_lst); if (nm_auth_subject_get_subject_type(subject) == NM_AUTH_SUBJECT_TYPE_INTERNAL) { _LOG2T(call_id, "CheckAuthorization(%s), subject=%s (succeeding for internal request)", action_id, nm_auth_subject_to_string(subject, subject_buf, sizeof(subject_buf))); call_id->idle_id = g_idle_add(_call_on_idle, call_id); } else if (nm_auth_subject_get_unix_process_uid(subject) == 0) { _LOG2T(call_id, "CheckAuthorization(%s), subject=%s (succeeding for root)", action_id, nm_auth_subject_to_string(subject, subject_buf, sizeof(subject_buf))); call_id->idle_id = g_idle_add(_call_on_idle, call_id); } else if (priv->auth_polkit_mode != NM_AUTH_POLKIT_MODE_USE_POLKIT) { _LOG2T(call_id, "CheckAuthorization(%s), subject=%s (PolicyKit disabled and always %s authorization " "to non-root user)", action_id, nm_auth_subject_to_string(subject, subject_buf, sizeof(subject_buf)), priv->auth_polkit_mode == NM_AUTH_POLKIT_MODE_ALLOW_ALL ? "grant" : "deny"); call_id->idle_is_authorized = (priv->auth_polkit_mode == NM_AUTH_POLKIT_MODE_ALLOW_ALL); call_id->idle_id = g_idle_add(_call_on_idle, call_id); } else { GVariant * parameters; GVariantBuilder builder; GVariant * subject_value; GVariant * details_value; subject_value = nm_auth_subject_unix_to_polkit_gvariant(subject); nm_assert(g_variant_is_floating(subject_value)); /* ((PolkitDetails *)NULL) */ g_variant_builder_init(&builder, G_VARIANT_TYPE("a{ss}")); details_value = g_variant_builder_end(&builder); parameters = g_variant_new("(@(sa{sv})s@a{ss}us)", subject_value, action_id, details_value, (guint32) flags, cancellation_id_to_str_a(call_id->call_numid)); _LOG2T(call_id, "CheckAuthorization(%s), subject=%s", action_id, nm_auth_subject_to_string(subject, subject_buf, sizeof(subject_buf))); call_id->dbus_cancellable = g_cancellable_new(); nm_assert(priv->main_cancellable); g_dbus_connection_call(priv->dbus_connection, POLKIT_SERVICE, POLKIT_OBJECT_PATH, POLKIT_INTERFACE, "CheckAuthorization", parameters, G_VARIANT_TYPE("((bba{ss}))"), G_DBUS_CALL_FLAGS_NONE, G_MAXINT, /* no timeout */ call_id->dbus_cancellable, _call_check_authorize_cb, call_id); } return call_id; } void nm_auth_manager_check_authorization_cancel(NMAuthManagerCallId *call_id) { NMAuthManager *self; gs_free_error GError *error = NULL; g_return_if_fail(call_id); self = call_id->self; g_return_if_fail(NM_IS_AUTH_MANAGER(self)); g_return_if_fail(!c_list_is_empty(&call_id->calls_lst)); nm_assert( c_list_contains(&NM_AUTH_MANAGER_GET_PRIVATE(self)->calls_lst_head, &call_id->calls_lst)); nm_utils_error_set_cancelled(&error, FALSE, "NMAuthManager"); _LOG2T(call_id, "completed: failed due to call cancelled"); _call_id_invoke_callback(call_id, FALSE, FALSE, error); } /*****************************************************************************/ static void changed_signal_cb(GDBusConnection *connection, const char * sender_name, const char * object_path, const char * interface_name, const char * signal_name, GVariant * parameters, gpointer user_data) { NMAuthManager * self = user_data; NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE(self); gboolean valid_sender; nm_assert(nm_streq0(signal_name, "Changed")); valid_sender = nm_streq0(priv->name_owner, sender_name); _LOGD("dbus-signal: \"Changed\" notification%s", valid_sender ? "" : " (ignore)"); if (valid_sender) _emit_changed_signal(self); } static void _name_owner_changed(NMAuthManager *self, const char *name_owner, gboolean is_initial) { NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE(self); gboolean is_changed; gs_free char * old_name_owner = NULL; if (is_initial) priv->got_name_owner = TRUE; else { if (!priv->got_name_owner) return; } name_owner = nm_str_not_empty(name_owner); is_changed = !nm_streq0(priv->name_owner, name_owner); if (is_changed) { old_name_owner = g_steal_pointer(&priv->name_owner); priv->name_owner = g_strdup(name_owner); } else { if (!is_initial) return; } if (!priv->name_owner) { if (is_initial) _LOGT("name-owner: polkit not running"); else _LOGT("name-owner: polkit stopped (was %s)", old_name_owner); } else { if (is_initial) _LOGT("name-owner: polkit is running (now %s)", priv->name_owner); else if (old_name_owner) _LOGT("name-owner: polkit restarted (now %s, was %s)", priv->name_owner, old_name_owner); else _LOGT("name-owner: polkit started (now %s)", priv->name_owner); } if (priv->name_owner) _emit_changed_signal(self); } 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) { NMAuthManager *self = user_data; const char * new_owner; if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sss)"))) return; g_variant_get(parameters, "(&s&s&s)", NULL, NULL, &new_owner); _name_owner_changed(self, new_owner, FALSE); } static void _name_owner_get_cb(const char *name_owner, GError *error, gpointer user_data) { if (!nm_utils_error_is_cancelled(error)) _name_owner_changed(user_data, name_owner, TRUE); } /*****************************************************************************/ NMAuthManager * nm_auth_manager_get() { g_return_val_if_fail(singleton_instance, NULL); return singleton_instance; } void nm_auth_manager_force_shutdown(NMAuthManager *self) { NMAuthManagerPrivate *priv; g_return_if_fail(NM_IS_AUTH_MANAGER(self)); priv = NM_AUTH_MANAGER_GET_PRIVATE(self); /* FIXME(shutdown): ensure we properly call this API during shutdown as * described next. */ /* while we have pending requests (NMAuthManagerCallId), the instance * is kept alive. * * Even if the caller cancels all pending call-ids, we still need to keep * a reference to self, in order to handle pending CancelCheckAuthorization * requests. * * To do a coordinated shutdown, do the following: * - cancel all pending NMAuthManagerCallId requests. * - ensure everybody unrefs the NMAuthManager instance. If by that, the instance * gets destroyed, the shutdown already completed successfully. * - Otherwise, the object is kept alive by pending CancelCheckAuthorization requests. * wait a certain timeout (1 second) for all requests to complete (by watching * for destruction of NMAuthManager). * - if that doesn't happen within timeout, issue nm_auth_manager_force_shutdown() and * wait longer. After that, soon the instance should be destroyed and you * did a successful shutdown. * - if the instance was still not destroyed within a short timeout, you leaked * resources. You cannot properly shutdown. */ priv->shutting_down = TRUE; nm_clear_g_cancellable(&priv->main_cancellable); } /*****************************************************************************/ static void set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE(object); int v_int; switch (prop_id) { case PROP_POLKIT_ENABLED: /* construct-only */ v_int = g_value_get_int(value); g_return_if_fail(NM_IN_SET(v_int, NM_AUTH_POLKIT_MODE_ROOT_ONLY, NM_AUTH_POLKIT_MODE_ALLOW_ALL, NM_AUTH_POLKIT_MODE_USE_POLKIT)); priv->auth_polkit_mode = v_int; nm_assert(priv->auth_polkit_mode == v_int); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /*****************************************************************************/ static void nm_auth_manager_init(NMAuthManager *self) { NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE(self); c_list_init(&priv->calls_lst_head); priv->auth_polkit_mode = NM_AUTH_POLKIT_MODE_ROOT_ONLY; } static void constructed(GObject *object) { NMAuthManager * self = NM_AUTH_MANAGER(object); NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE(self); NMLogLevel logl = LOGL_DEBUG; const char * create_message; G_OBJECT_CLASS(nm_auth_manager_parent_class)->constructed(object); if (priv->auth_polkit_mode != NM_AUTH_POLKIT_MODE_USE_POLKIT) { if (priv->auth_polkit_mode == NM_AUTH_POLKIT_MODE_ROOT_ONLY) create_message = "polkit disabled, root-only"; else create_message = "polkit disabled, allow-all"; goto out; } priv->dbus_connection = nm_g_object_ref(NM_MAIN_DBUS_CONNECTION_GET); if (!priv->dbus_connection) { /* This warrants an info level message. */ logl = LOGL_INFO; create_message = "D-Bus connection not available. Polkit is disabled and only root will be authorized."; priv->auth_polkit_mode = NM_AUTH_POLKIT_MODE_ROOT_ONLY; goto out; } priv->main_cancellable = g_cancellable_new(); priv->name_owner_changed_id = nm_dbus_connection_signal_subscribe_name_owner_changed(priv->dbus_connection, POLKIT_SERVICE, _name_owner_changed_cb, self, NULL); priv->changed_id = g_dbus_connection_signal_subscribe(priv->dbus_connection, POLKIT_SERVICE, POLKIT_INTERFACE, "Changed", POLKIT_OBJECT_PATH, NULL, G_DBUS_SIGNAL_FLAGS_NONE, changed_signal_cb, self, NULL); nm_dbus_connection_call_get_name_owner(priv->dbus_connection, POLKIT_SERVICE, -1, priv->main_cancellable, _name_owner_get_cb, self); create_message = "polkit enabled"; out: _NMLOG(logl, "create auth-manager: %s", create_message); } NMAuthManager * nm_auth_manager_setup(NMAuthPolkitMode auth_polkit_mode) { NMAuthManager *self; g_return_val_if_fail(!singleton_instance, singleton_instance); nm_assert(NM_IN_SET(auth_polkit_mode, NM_AUTH_POLKIT_MODE_ROOT_ONLY, NM_AUTH_POLKIT_MODE_ALLOW_ALL, NM_AUTH_POLKIT_MODE_USE_POLKIT)); self = g_object_new(NM_TYPE_AUTH_MANAGER, NM_AUTH_MANAGER_POLKIT_ENABLED, (int) auth_polkit_mode, NULL); _LOGD("set instance"); singleton_instance = self; nm_singleton_instance_register(); nm_log_dbg(LOGD_CORE, "setup %s singleton (" NM_HASH_OBFUSCATE_PTR_FMT ")", "NMAuthManager", NM_HASH_OBFUSCATE_PTR(singleton_instance)); return self; } static void dispose(GObject *object) { NMAuthManager * self = NM_AUTH_MANAGER(object); NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE(self); _LOGD("dispose"); nm_assert(c_list_is_empty(&priv->calls_lst_head)); priv->disposing = TRUE; nm_clear_g_cancellable(&priv->main_cancellable); nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->name_owner_changed_id); nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->changed_id); G_OBJECT_CLASS(nm_auth_manager_parent_class)->dispose(object); g_clear_object(&priv->dbus_connection); nm_clear_g_free(&priv->name_owner); } static void nm_auth_manager_class_init(NMAuthManagerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->set_property = set_property; object_class->constructed = constructed; object_class->dispose = dispose; obj_properties[PROP_POLKIT_ENABLED] = g_param_spec_int(NM_AUTH_MANAGER_POLKIT_ENABLED, "", "", NM_AUTH_POLKIT_MODE_ROOT_ONLY, NM_AUTH_POLKIT_MODE_USE_POLKIT, NM_AUTH_POLKIT_MODE_USE_POLKIT, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); signals[CHANGED_SIGNAL] = g_signal_new(NM_AUTH_MANAGER_SIGNAL_CHANGED, NM_TYPE_AUTH_MANAGER, G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); }