// 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); }