// SPDX-License-Identifier: GPL-2.0+ /* * Copyright 2014 Red Hat, Inc. */ /** * SECTION:nm-polkit-listener * @short_description: A polkit agent listener * * #NMPolkitListener is the polkit agent listener used by nmcli and nmtui. * http://www.freedesktop.org/software/polkit/docs/latest/index.html * * For an example polkit agent you can look at polkit source tree: * http://cgit.freedesktop.org/polkit/tree/src/polkitagent/polkitagenttextlistener.c * http://cgit.freedesktop.org/polkit/tree/src/programs/pkttyagent.c * or LXDE polkit agent: * http://git.lxde.org/gitweb/?p=debian/lxpolkit.git;a=blob;f=src/lxpolkit-listener.c * https://github.com/lxde/lxqt-policykit/tree/master/src */ #include "nm-default.h" #include "nm-polkit-listener.h" #include #include #if WITH_POLKIT_AGENT /*****************************************************************************/ typedef struct { gpointer reg_handle; /* handle of polkit agent registration */ GSimpleAsyncResult *simple; PolkitAgentSession *active_session; gulong cancel_id; GCancellable *cancellable; char *action_id; char *message; char *icon_name; char *identity; const NMPolkitListenVtable *vtable; gpointer vtable_user_data; } NMPolkitListenerPrivate; G_DEFINE_TYPE (NMPolkitListener, nm_polkit_listener, POLKIT_AGENT_TYPE_LISTENER) #define NM_POLKIT_LISTENER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_POLKIT_LISTENER, NMPolkitListenerPrivate)) /*****************************************************************************/ void nm_polkit_listener_set_vtable (NMPolkitListener *self, const NMPolkitListenVtable *vtable, gpointer user_data) { NMPolkitListenerPrivate *priv = NM_POLKIT_LISTENER_GET_PRIVATE (self); priv->vtable = vtable; priv->vtable_user_data = user_data; } /*****************************************************************************/ static void on_request (PolkitAgentSession *session, const char *request, gboolean echo_on, gpointer user_data) { NMPolkitListener *self = NM_POLKIT_LISTENER (user_data); NMPolkitListenerPrivate *priv = NM_POLKIT_LISTENER_GET_PRIVATE (self); gs_free char *response = NULL; if (priv->vtable && priv->vtable->on_request) { response = priv->vtable->on_request (self, request, priv->action_id, priv->message, priv->icon_name, priv->identity, echo_on, priv->vtable_user_data); } if (response) polkit_agent_session_response (session, response); else { //FIXME: polkit_agent_session_cancel() should emit "completed", but it doesn't work for me ??? //polkit_agent_session_cancel (session); polkit_agent_session_response (session, ""); } } static void on_show_info (PolkitAgentSession *session, const char *text, gpointer user_data) { NMPolkitListener *self = NM_POLKIT_LISTENER (user_data); NMPolkitListenerPrivate *priv = NM_POLKIT_LISTENER_GET_PRIVATE (self); if (priv->vtable && priv->vtable->on_show_info) { priv->vtable->on_show_info (self, text, priv->vtable_user_data); } } static void on_show_error (PolkitAgentSession *session, const char *text, gpointer user_data) { NMPolkitListener *self = NM_POLKIT_LISTENER (user_data); NMPolkitListenerPrivate *priv = NM_POLKIT_LISTENER_GET_PRIVATE (self); if (priv->vtable && priv->vtable->on_show_error) { priv->vtable->on_show_error (self, text, priv->vtable_user_data); } } static void on_completed (PolkitAgentSession *session, gboolean gained_authorization, gpointer user_data) { NMPolkitListener *self = NM_POLKIT_LISTENER (user_data); NMPolkitListenerPrivate *priv = NM_POLKIT_LISTENER_GET_PRIVATE (self); if (priv->vtable->on_completed) { priv->vtable->on_completed (self, gained_authorization, priv->vtable_user_data); } g_simple_async_result_complete_in_idle (priv->simple); g_object_unref (priv->simple); g_object_unref (priv->active_session); if (priv->cancellable) { g_cancellable_disconnect (priv->cancellable, priv->cancel_id); g_object_unref (priv->cancellable); } priv->simple = NULL; priv->active_session = NULL; priv->cancel_id = 0; g_clear_pointer (&priv->action_id, g_free); g_clear_pointer (&priv->message, g_free); g_clear_pointer (&priv->icon_name, g_free); g_clear_pointer (&priv->identity, g_free); } static void on_cancelled (GCancellable *cancellable, gpointer user_data) { NMPolkitListener *self = NM_POLKIT_LISTENER (user_data); NMPolkitListenerPrivate *priv = NM_POLKIT_LISTENER_GET_PRIVATE (self); polkit_agent_session_cancel (priv->active_session); } static int compare_users (gconstpointer a, gconstpointer b) { char *user; int ret; if (POLKIT_IS_UNIX_USER (a)) user = g_strdup (polkit_unix_user_get_name (POLKIT_UNIX_USER (a))); else user = polkit_identity_to_string (POLKIT_IDENTITY (a)); ret = g_strcmp0 ((const char *) user, (const char *) b); g_free (user); return ret; } static PolkitIdentity * choose_identity (GList *identities) { const char *user; GList *elem; /* Choose identity. First try current user, then root, and else * take the first one */ user = getenv("USER"); elem = g_list_find_custom (identities, user, (GCompareFunc) compare_users); if (!elem) { elem = g_list_find_custom (identities, "root", (GCompareFunc) compare_users); if (!elem) elem = identities; } return elem->data; } static void initiate_authentication (PolkitAgentListener *listener, const char *action_id, const char *message, const char *icon_name, PolkitDetails *details, const char *cookie, GList *identities, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { NMPolkitListenerPrivate *priv = NM_POLKIT_LISTENER_GET_PRIVATE (listener); GSimpleAsyncResult *simple; PolkitIdentity *identity; simple = g_simple_async_result_new (G_OBJECT (listener), callback, user_data, initiate_authentication); if (cancellable) g_simple_async_result_set_check_cancellable (simple, cancellable); if (priv->active_session != NULL) { g_simple_async_result_set_error (simple, POLKIT_ERROR, POLKIT_ERROR_FAILED, _("An authentication session is already underway.")); g_simple_async_result_complete_in_idle (simple); g_object_unref (simple); return; } /* Choose identity */ identity = choose_identity (identities); priv->active_session = polkit_agent_session_new (identity, cookie); g_signal_connect (priv->active_session, "completed", G_CALLBACK (on_completed), listener); g_signal_connect (priv->active_session, "request", G_CALLBACK (on_request), listener); g_signal_connect (priv->active_session, "show-info", G_CALLBACK (on_show_info), listener); g_signal_connect (priv->active_session, "show-error", G_CALLBACK (on_show_error), listener); priv->action_id = g_strdup (action_id); priv->message = g_strdup (message); priv->icon_name = g_strdup (icon_name); if (POLKIT_IS_UNIX_USER (identity)) priv->identity = g_strdup (polkit_unix_user_get_name (POLKIT_UNIX_USER (identity))); else priv->identity = polkit_identity_to_string (identity); priv->simple = simple; priv->cancellable = g_object_ref (cancellable); priv->cancel_id = g_cancellable_connect (cancellable, G_CALLBACK (on_cancelled), listener, NULL); polkit_agent_session_initiate (priv->active_session); } static gboolean initiate_authentication_finish (PolkitAgentListener *listener, GAsyncResult *result, GError **error) { return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error); } /*****************************************************************************/ static void nm_polkit_listener_init (NMPolkitListener *agent) { } /** * nm_polkit_listener_new: * @for_session: %TRUE for registering the polkit agent for the user session, * %FALSE for registering it for the running process * @vtable: mandatory callbacks * @user_data: user-data pointer for callbacks * @error: location to store error, or %NULL * * Creates a new #NMPolkitListener and registers it as a polkit agent. * * Returns: a new #NMPolkitListener */ NMPolkitListener * nm_polkit_listener_new (gboolean for_session, GError **error) { NMPolkitListener *listener; PolkitSubject* session; NMPolkitListenerPrivate *priv; g_return_val_if_fail (!error || !*error, NULL); listener = g_object_new (NM_TYPE_POLKIT_LISTENER, NULL); priv = NM_POLKIT_LISTENER_GET_PRIVATE (listener); if (for_session) { session = polkit_unix_session_new_for_process_sync (getpid (), NULL, error); if (!session) return NULL; } else session = polkit_unix_process_new_for_owner (getpid (), 0, getuid ()); priv->reg_handle = polkit_agent_listener_register (POLKIT_AGENT_LISTENER (listener), POLKIT_AGENT_REGISTER_FLAGS_NONE, session, NULL, NULL, error); if (!priv->reg_handle) { g_object_unref (listener); g_object_unref (session); return NULL; } return listener; } static void nm_polkit_listener_finalize (GObject *object) { NMPolkitListenerPrivate *priv = NM_POLKIT_LISTENER_GET_PRIVATE (object); if (priv->reg_handle) polkit_agent_listener_unregister (priv->reg_handle); g_free (priv->action_id); g_free (priv->message); g_free (priv->icon_name); g_free (priv->identity); G_OBJECT_CLASS (nm_polkit_listener_parent_class)->finalize (object); } static void nm_polkit_listener_class_init (NMPolkitListenerClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); PolkitAgentListenerClass *pkal_class = POLKIT_AGENT_LISTENER_CLASS (klass); g_type_class_add_private (klass, sizeof (NMPolkitListenerPrivate)); gobject_class->finalize = nm_polkit_listener_finalize; pkal_class->initiate_authentication = initiate_authentication; pkal_class->initiate_authentication_finish = initiate_authentication_finish; } #endif /* WITH_POLKIT_AGENT */