/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ /* NetworkManager -- Network link manager * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Copyright (C) 2014 Red Hat, Inc. */ #include "nm-auth-manager.h" #include "nm-logging.h" #define POLKIT_SERVICE "org.freedesktop.PolicyKit1" #define POLKIT_OBJECT_PATH "/org/freedesktop/PolicyKit1/Authority" #define POLKIT_INTERFACE "org.freedesktop.PolicyKit1.Authority" #define _LOG_DEFAULT_DOMAIN LOGD_CORE #define _LOG(level, domain, ...) \ G_STMT_START { \ if (nm_logging_enabled ((level), (domain))) { \ char __prefix[30] = "auth"; \ \ if ((self) != _instance) \ g_snprintf (__prefix, sizeof (__prefix), "auth[%p]", (self)); \ nm_log ((level), (domain), \ "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ __prefix _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ } \ } G_STMT_END #define _LOGD(...) _LOG (LOGL_DEBUG, _LOG_DEFAULT_DOMAIN, __VA_ARGS__) #define _LOGI(...) _LOG (LOGL_INFO, _LOG_DEFAULT_DOMAIN, __VA_ARGS__) #define _LOGW(...) _LOG (LOGL_WARN, _LOG_DEFAULT_DOMAIN, __VA_ARGS__) #define _LOGE(...) _LOG (LOGL_ERR, _LOG_DEFAULT_DOMAIN, __VA_ARGS__) enum { PROP_0, PROP_POLKIT_ENABLED, LAST_PROP }; enum { CHANGED_SIGNAL, LAST_SIGNAL, }; static guint signals[LAST_SIGNAL] = {0}; typedef struct { gboolean polkit_enabled; guint call_id_counter; GCancellable *new_proxy_cancellable; GSList *queued_calls; GDBusProxy *proxy; } NMAuthManagerPrivate; static NMAuthManager *_instance = NULL; G_DEFINE_TYPE (NMAuthManager, nm_auth_manager, G_TYPE_OBJECT) #define NM_AUTH_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_AUTH_MANAGER, NMAuthManagerPrivate)) GQuark nm_auth_manager_error_quark (void) { static GQuark quark = 0; if (G_UNLIKELY (quark == 0)) quark = g_quark_from_static_string ("nm-auth-manager-error-quark"); return quark; } /*****************************************************************************/ 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)->polkit_enabled; } /*****************************************************************************/ typedef enum { POLKIT_CHECK_AUTHORIZATION_FLAGS_NONE = 0, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION = (1<<0), } PolkitCheckAuthorizationFlags; typedef struct { guint call_id; NMAuthManager *self; GSimpleAsyncResult *simple; gchar *cancellation_id; GVariant *dbus_parameters; GCancellable *cancellable; } CheckAuthData; static void _check_auth_data_free (CheckAuthData *data) { if (data->dbus_parameters) g_variant_unref (data->dbus_parameters); g_object_unref (data->self); g_object_unref (data->simple); g_clear_object (&data->cancellable); g_free (data->cancellation_id); g_free (data); } static void _call_check_authorization_complete_with_error (CheckAuthData *data, const char *error_message) { NMAuthManager *self = data->self; GError *error = NULL; _LOGD ("call[%u]: CheckAuthorization failed due to internal error: %s", data->call_id, error_message); g_set_error_literal (&error, NM_AUTH_MANAGER_ERROR, NM_AUTH_MANAGER_ERROR_DBUS_FAILURE, error_message); g_simple_async_result_set_from_error (data->simple, error); g_clear_error (&error); g_simple_async_result_complete_in_idle (data->simple); _check_auth_data_free (data); } static void cancel_check_authorization_cb (GDBusProxy *proxy, GAsyncResult *res, gpointer user_data) { NMAuthManager *self = user_data; GVariant *value; GError *error= NULL; value = g_dbus_proxy_call_finish (proxy, res, &error); if (value == NULL) { _LOGD ("Error cancelling authorization check: %s", error->message); g_error_free (error); } else g_variant_unref (value); g_object_unref (self); } typedef struct { gboolean is_authorized; gboolean is_challenge; } CheckAuthorizationResult; static void check_authorization_cb (GDBusProxy *proxy, GAsyncResult *res, gpointer user_data) { CheckAuthData *data = user_data; NMAuthManager *self = data->self; NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE (self); GVariant *value; GError *error = NULL; value = g_dbus_proxy_call_finish (proxy, res, &error); if (value == NULL) { if (data->cancellation_id != NULL && (!g_dbus_error_is_remote_error (error) && error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED)) { _LOGD ("call[%u]: CheckAuthorization cancelled", data->call_id); g_dbus_proxy_call (priv->proxy, "CancelCheckAuthorization", g_variant_new ("(s)", data->cancellation_id), G_DBUS_CALL_FLAGS_NONE, -1, NULL, /* GCancellable */ (GAsyncReadyCallback) cancel_check_authorization_cb, g_object_ref (self)); } else _LOGD ("call[%u]: CheckAuthorization failed: %s", data->call_id, error->message); g_simple_async_result_set_from_error (data->simple, error); g_error_free (error); } else { GVariant *result_value; CheckAuthorizationResult *result; result = g_new0 (CheckAuthorizationResult, 1); result_value = g_variant_get_child_value (value, 0); g_variant_get (result_value, "(bb@a{ss})", &result->is_authorized, &result->is_challenge, NULL); g_variant_unref (result_value); g_variant_unref (value); _LOGD ("call[%u]: CheckAuthorization succeeded: (is_authorized=%d, is_challenge=%d)", data->call_id, result->is_authorized, result->is_challenge); g_simple_async_result_set_op_res_gpointer (data->simple, result, g_free); } g_simple_async_result_complete (data->simple); _check_auth_data_free (data); } static void _call_check_authorization (CheckAuthData *data) { NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE (data->self); g_dbus_proxy_call (priv->proxy, "CheckAuthorization", data->dbus_parameters, G_DBUS_CALL_FLAGS_NONE, G_MAXINT, /* no timeout */ data->cancellable, (GAsyncReadyCallback) check_authorization_cb, data); g_clear_object (&data->cancellable); data->dbus_parameters = NULL; } void nm_auth_manager_polkit_authority_check_authorization (NMAuthManager *self, NMAuthSubject *subject, const char *action_id, gboolean allow_user_interaction, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { NMAuthManagerPrivate *priv; char subject_buf[64]; GVariantBuilder builder; PolkitCheckAuthorizationFlags flags; GVariant *subject_value; GVariant *details_value; CheckAuthData *data; g_return_if_fail (NM_IS_AUTH_MANAGER (self)); g_return_if_fail (NM_IS_AUTH_SUBJECT (subject)); g_return_if_fail (nm_auth_subject_is_unix_process (subject)); g_return_if_fail (action_id != NULL); g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); priv = NM_AUTH_MANAGER_GET_PRIVATE (self); g_return_if_fail (priv->polkit_enabled); flags = allow_user_interaction ? POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION : POLKIT_CHECK_AUTHORIZATION_FLAGS_NONE; subject_value = nm_auth_subject_unix_process_to_polkit_gvariant (subject); g_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); data = g_new0 (CheckAuthData, 1); data->call_id = ++priv->call_id_counter; data->self = g_object_ref (self); data->simple = g_simple_async_result_new (G_OBJECT (self), callback, user_data, nm_auth_manager_polkit_authority_check_authorization); if (cancellable != NULL) { data->cancellation_id = g_strdup_printf ("cancellation-id-%u", data->call_id); data->cancellable = g_object_ref (cancellable); } data->dbus_parameters = g_variant_new ("(@(sa{sv})s@a{ss}us)", subject_value, action_id, details_value, (guint32) flags, data->cancellation_id != NULL ? data->cancellation_id : ""); if (priv->new_proxy_cancellable) { _LOGD ("call[%u]: CheckAuthorization(%s), subject=%s (wait for proxy)", data->call_id, action_id, nm_auth_subject_to_string (subject, subject_buf, sizeof (subject_buf))); priv->queued_calls = g_slist_prepend (priv->queued_calls, data); } else if (!priv->proxy) { _LOGD ("call[%u]: CheckAuthorization(%s), subject=%s (fails due to invalid DBUS proxy)", data->call_id, action_id, nm_auth_subject_to_string (subject, subject_buf, sizeof (subject_buf))); _call_check_authorization_complete_with_error (data, "invalid DBUS proxy"); } else { _LOGD ("call[%u]: CheckAuthorization(%s), subject=%s", data->call_id, action_id, nm_auth_subject_to_string (subject, subject_buf, sizeof (subject_buf))); _call_check_authorization (data); } } gboolean nm_auth_manager_polkit_authority_check_authorization_finish (NMAuthManager *self, GAsyncResult *res, gboolean *out_is_authorized, gboolean *out_is_challenge, GError **error) { gboolean success = FALSE; gboolean is_authorized = FALSE; gboolean is_challenge = FALSE; g_return_val_if_fail (NM_IS_AUTH_MANAGER (self), FALSE); g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (res), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); if (!g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) { CheckAuthorizationResult *result; result = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res)); is_authorized = !!result->is_authorized; is_challenge = !!result->is_challenge; success = TRUE; } g_assert ((success && !error) || (!success || error)); if (out_is_authorized) *out_is_authorized = is_authorized; if (out_is_challenge) *out_is_challenge = is_challenge; return success; } /*****************************************************************************/ static void _emit_changed_signal (NMAuthManager *self) { _LOGD ("emit changed signal"); g_signal_emit_by_name (self, NM_AUTH_MANAGER_SIGNAL_CHANGED); } static void _log_name_owner (NMAuthManager *self, char **out_name_owner) { NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE (self); char *name_owner; name_owner = g_dbus_proxy_get_name_owner (priv->proxy); if (name_owner) _LOGD ("dbus name owner: '%s'", name_owner); else _LOGD ("dbus name owner: none"); if (out_name_owner) *out_name_owner = name_owner; else g_free (name_owner); } static void _dbus_on_name_owner_notify_cb (GObject *object, GParamSpec *pspec, gpointer user_data) { NMAuthManager *self = user_data; NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE (self); char *name_owner; g_return_if_fail (priv->proxy == (void *) object); _log_name_owner (self, &name_owner); if (!name_owner) { /* when the name disappears, we also want to raise a emit signal. * When it appears, we raise one already. */ _emit_changed_signal (self); } g_free (name_owner); } static void _dbus_on_g_signal_cb (GDBusProxy *proxy, const gchar *sender_name, const gchar *signal_name, GVariant *parameters, gpointer user_data) { NMAuthManager *self = user_data; NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE (self); g_return_if_fail (priv->proxy == proxy); _LOGD ("dbus signal: \"%s\"", signal_name ? signal_name : "(null)"); if (g_strcmp0 (signal_name, "Changed") == 0) _emit_changed_signal (self); } static void _dbus_new_proxy_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { NMAuthManager **p_self = user_data; NMAuthManager *self = NULL; NMAuthManagerPrivate *priv; GError *error = NULL; GDBusProxy *proxy; CheckAuthData *data; proxy = g_dbus_proxy_new_for_bus_finish (res, &error); if (!*p_self) { _LOGD ("_dbus_new_proxy_cb(): manager destroyed before callback finished. Abort"); g_clear_object (&proxy); g_clear_error (&error); g_free (p_self); return; } self = *p_self; g_object_remove_weak_pointer (G_OBJECT (self), (void **)p_self); g_free (p_self); priv = NM_AUTH_MANAGER_GET_PRIVATE (self); g_return_if_fail (priv->new_proxy_cancellable); g_return_if_fail (!priv->proxy); g_clear_object (&priv->new_proxy_cancellable); priv->queued_calls = g_slist_reverse (priv->queued_calls); priv->proxy = proxy; if (!priv->proxy) { _LOGE ("could not get polkit proxy: %s", error->message); g_clear_error (&error); while (priv->queued_calls) { data = priv->queued_calls->data; priv->queued_calls = g_slist_remove (priv->queued_calls, data); _call_check_authorization_complete_with_error (data, "error creating DBUS proxy"); } return; } g_signal_connect (priv->proxy, "notify::g-name-owner", G_CALLBACK (_dbus_on_name_owner_notify_cb), self); g_signal_connect (priv->proxy, "g-signal", G_CALLBACK (_dbus_on_g_signal_cb), self); _log_name_owner (self, NULL); while (priv->queued_calls) { data = priv->queued_calls->data; priv->queued_calls = g_slist_remove (priv->queued_calls, data); _LOGD ("call[%u]: CheckAuthorization invoke now", data->call_id); _call_check_authorization (data); } _emit_changed_signal (self); } /*****************************************************************************/ NMAuthManager * nm_auth_manager_get () { g_return_val_if_fail (_instance, NULL); return _instance; } NMAuthManager * nm_auth_manager_setup (gboolean polkit_enabled) { NMAuthManager *self; g_return_val_if_fail (!_instance, _instance); self = g_object_new (NM_TYPE_AUTH_MANAGER, NM_AUTH_MANAGER_POLKIT_ENABLED, polkit_enabled, NULL); _LOGD ("set instance"); return (_instance = self); } /*****************************************************************************/ static void get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE (object); switch (prop_id) { case PROP_POLKIT_ENABLED: g_value_set_boolean (value, priv->polkit_enabled); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE (object); switch (prop_id) { case PROP_POLKIT_ENABLED: /* construct only */ priv->polkit_enabled = !!g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void nm_auth_manager_init (NMAuthManager *self) { } static void constructed (GObject *object) { NMAuthManager *self = NM_AUTH_MANAGER (object); NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE (self); G_OBJECT_CLASS (nm_auth_manager_parent_class)->constructed (object); _LOGD ("create auth-manager: polkit %s", priv->polkit_enabled ? "enabled" : "disabled"); if (priv->polkit_enabled) { NMAuthManager **p_self; priv->new_proxy_cancellable = g_cancellable_new (); p_self = g_new (NMAuthManager *, 1); *p_self = self; g_object_add_weak_pointer (G_OBJECT (self), (void **) p_self); g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, NULL, POLKIT_SERVICE, POLKIT_OBJECT_PATH, POLKIT_INTERFACE, priv->new_proxy_cancellable, _dbus_new_proxy_cb, p_self); } } static void dispose (GObject *object) { NMAuthManager* self = NM_AUTH_MANAGER (object); NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE (self); _LOGD ("dispose"); /* since we take a reference for each queued call, we don't expect to have any queued calls in dispose() */ g_assert (!priv->queued_calls); if (priv->new_proxy_cancellable) { g_cancellable_cancel (priv->new_proxy_cancellable); g_clear_object (&priv->new_proxy_cancellable); } if (priv->proxy) { g_signal_handlers_disconnect_by_func (priv->proxy, _dbus_on_name_owner_notify_cb, self); g_signal_handlers_disconnect_by_func (priv->proxy, _dbus_on_g_signal_cb, self); g_clear_object (&priv->proxy); } G_OBJECT_CLASS (nm_auth_manager_parent_class)->dispose (object); } static void finalize (GObject *object) { NMAuthManager* self = NM_AUTH_MANAGER (object); G_OBJECT_CLASS (nm_auth_manager_parent_class)->finalize (object); if (self == _instance) { _instance = NULL; _LOGD ("unset instance"); } } static void nm_auth_manager_class_init (NMAuthManagerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); g_type_class_add_private (klass, sizeof (NMAuthManagerPrivate)); object_class->get_property = get_property; object_class->set_property = set_property; object_class->constructed = constructed; object_class->dispose = dispose; object_class->finalize = finalize; g_object_class_install_property (object_class, PROP_POLKIT_ENABLED, g_param_spec_boolean (NM_AUTH_MANAGER_POLKIT_ENABLED, "", "", FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); signals[CHANGED_SIGNAL] = g_signal_new (NM_AUTH_MANAGER_SIGNAL_CHANGED, NM_TYPE_AUTH_MANAGER, G_SIGNAL_RUN_LAST, 0, /* class offset */ NULL, /* accumulator */ NULL, /* accumulator data */ g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); }