diff options
| author | Michael Biebl <biebl@debian.org> | 2021-10-01 23:05:04 +0200 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2021-10-01 23:05:04 +0200 |
| commit | e74c568b07b50b97873fb4ee1d776dedefbd54d6 (patch) | |
| tree | 3469f17ea9af91f7ff169b890633bda68b0cf76e /src/libnm-client-impl/nm-secret-agent-old.c | |
| parent | bfe522304da217296e2a61040f58e35ec5d6f3f2 (diff) | |
New upstream version 1.32.12 upstream/1.32.12
Diffstat (limited to 'src/libnm-client-impl/nm-secret-agent-old.c')
| -rw-r--r-- | src/libnm-client-impl/nm-secret-agent-old.c | 1995 |
1 files changed, 1995 insertions, 0 deletions
diff --git a/src/libnm-client-impl/nm-secret-agent-old.c b/src/libnm-client-impl/nm-secret-agent-old.c new file mode 100644 index 00000000..881dfd54 --- /dev/null +++ b/src/libnm-client-impl/nm-secret-agent-old.c @@ -0,0 +1,1995 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2010 - 2011 Red Hat, Inc. + */ + +#include "libnm-client-impl/nm-default-libnm.h" + +#include "nm-secret-agent-old.h" + +#include "c-list/src/c-list.h" +#include "libnm-core-intern/nm-core-internal.h" +#include "nm-dbus-helpers.h" +#include "nm-dbus-interface.h" +#include "nm-enum-types.h" +#include "libnm-glib-aux/nm-c-list.h" +#include "libnm-glib-aux/nm-dbus-aux.h" +#include "libnm-glib-aux/nm-time-utils.h" +#include "nm-simple-connection.h" + +#define REGISTER_RETRY_TIMEOUT_MSEC 3000 +#define _CALL_REGISTER_TIMEOUT_MSEC 15000 + +/*****************************************************************************/ + +typedef struct { + char * connection_path; + char * setting_name; + GDBusMethodInvocation *context; + CList gsi_lst; + bool is_cancelling : 1; +} GetSecretsInfo; + +NM_GOBJECT_PROPERTIES_DEFINE(NMSecretAgentOld, + PROP_IDENTIFIER, + PROP_AUTO_REGISTER, + PROP_REGISTERED, + PROP_CAPABILITIES, + PROP_DBUS_CONNECTION, ); + +typedef struct { + GDBusConnection *dbus_connection; + GMainContext * main_context; + GMainContext * dbus_context; + GObject * context_busy_watcher; + GCancellable * name_owner_cancellable; + GCancellable * registering_cancellable; + GSource * registering_retry_source; + + NMLInitData *init_data; + + CList gsi_lst_head; + + CList pending_tasks_register_lst_head; + + char *identifier; + + NMRefString *name_owner_curr; + NMRefString *name_owner_next; + + gint64 registering_timeout_msec; + + guint name_owner_changed_id; + + guint exported_id; + + guint capabilities; + + guint8 registering_try_count; + + guint8 register_state_change_reenter : 2; + + bool session_bus : 1; + + bool auto_register : 1; + + bool is_registered : 1; + + bool is_enabled : 1; + + bool registration_force_unregister : 1; + + /* This is true, if we either are in the process of RegisterWithCapabilities() or + * are already successfully registered. + * + * This is only TRUE, if the name owner was authenticated to run as root user. + * + * It also means, we should follow up with an Unregister() call during shutdown. */ + bool registered_against_server : 1; + + bool is_initialized : 1; + bool is_destroyed : 1; +} NMSecretAgentOldPrivate; + +static void nm_secret_agent_old_initable_iface_init(GInitableIface *iface); +static void nm_secret_agent_old_async_initable_iface_init(GAsyncInitableIface *iface); + +G_DEFINE_ABSTRACT_TYPE_WITH_CODE( + NMSecretAgentOld, + nm_secret_agent_old, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE(G_TYPE_INITABLE, nm_secret_agent_old_initable_iface_init); + G_IMPLEMENT_INTERFACE(G_TYPE_ASYNC_INITABLE, nm_secret_agent_old_async_initable_iface_init);) + +#define NM_SECRET_AGENT_OLD_GET_PRIVATE(self) \ + (G_TYPE_INSTANCE_GET_PRIVATE((self), NM_TYPE_SECRET_AGENT_OLD, NMSecretAgentOldPrivate)) + +/*****************************************************************************/ + +#define _NMLOG(level, ...) \ + NML_DBUS_LOG((level), \ + "secret-agent[" NM_HASH_OBFUSCATE_PTR_FMT \ + "]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + NM_HASH_OBFUSCATE_PTR(self) _NM_UTILS_MACRO_REST(__VA_ARGS__)) + +/*****************************************************************************/ + +static const GDBusInterfaceInfo interface_info = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_SECRET_AGENT, + .methods = NM_DEFINE_GDBUS_METHOD_INFOS( + NM_DEFINE_GDBUS_METHOD_INFO("GetSecrets", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("connection", "a{sa{sv}}"), + NM_DEFINE_GDBUS_ARG_INFO("connection_path", "o"), + NM_DEFINE_GDBUS_ARG_INFO("setting_name", "s"), + NM_DEFINE_GDBUS_ARG_INFO("hints", "as"), + NM_DEFINE_GDBUS_ARG_INFO("flags", "u"), ), + .out_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("secrets", "a{sa{sv}}"), ), ), + NM_DEFINE_GDBUS_METHOD_INFO("CancelGetSecrets", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("connection_path", "o"), + NM_DEFINE_GDBUS_ARG_INFO("setting_name", "s"), ), ), + NM_DEFINE_GDBUS_METHOD_INFO("SaveSecrets", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("connection", "a{sa{sv}}"), + NM_DEFINE_GDBUS_ARG_INFO("connection_path", "o"), ), ), + NM_DEFINE_GDBUS_METHOD_INFO( + "DeleteSecrets", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("connection", "a{sa{sv}}"), + NM_DEFINE_GDBUS_ARG_INFO("connection_path", "o"), ), ), ), ); + +/*****************************************************************************/ + +static void _register_state_change(NMSecretAgentOld *self); + +static void _register_dbus_call(NMSecretAgentOld *self); + +static void _init_complete(NMSecretAgentOld *self, GError *error_take); + +static void _register_state_complete(NMSecretAgentOld *self); + +/*****************************************************************************/ + +/** + * nm_secret_agent_old_get_dbus_connection: + * @self: the #NMSecretAgentOld instance + * + * Returns: (transfer none): the #GDBusConnection used by the secret agent. + * You may either set this as construct property %NM_SECRET_AGENT_OLD_DBUS_CONNECTION, + * or it will automatically set during initialization. + * + * Since: 1.24 + */ +GDBusConnection * +nm_secret_agent_old_get_dbus_connection(NMSecretAgentOld *self) +{ + g_return_val_if_fail(NM_IS_SECRET_AGENT_OLD(self), NULL); + + return NM_SECRET_AGENT_OLD_GET_PRIVATE(self)->dbus_connection; +} + +/** + * nm_secret_agent_old_get_main_context: + * @self: the #NMSecretAgentOld instance + * + * Returns: (transfer none): the #GMainContext instance associate with the + * instance. This is the g_main_context_get_thread_default() at the time + * when creating the instance. + * + * Since: 1.24 + */ +GMainContext * +nm_secret_agent_old_get_main_context(NMSecretAgentOld *self) +{ + g_return_val_if_fail(NM_IS_SECRET_AGENT_OLD(self), NULL); + + return NM_SECRET_AGENT_OLD_GET_PRIVATE(self)->main_context; +} + +/** + * nm_secret_agent_old_get_context_busy_watcher: + * @self: the #NMSecretAgentOld instance + * + * Returns a #GObject that stays alive as long as there are pending + * requests in the #GDBusConnection. Such requests keep the #GMainContext + * alive, and thus you may want to keep iterating the context as long + * until a weak reference indicates that this object is gone. This is + * useful because even when you destroy the instance right away (and all + * the internally pending requests get cancelled), any pending g_dbus_connection_call() + * requests will still invoke the result on the #GMainContext. Hence, this + * allows you to know how long you must iterate the context to know + * that all remains are cleaned up. + * + * Returns: (transfer none): a #GObject that you may register a weak pointer + * to know that the #GMainContext is still kept busy by @self. + * + * Since: 1.24 + */ +GObject * +nm_secret_agent_old_get_context_busy_watcher(NMSecretAgentOld *self) +{ + g_return_val_if_fail(NM_IS_SECRET_AGENT_OLD(self), NULL); + + return NM_SECRET_AGENT_OLD_GET_PRIVATE(self)->context_busy_watcher; +} + +/** + * nm_secret_agent_old_get_dbus_name_owner: + * @self: the #NMSecretAgentOld instance + * + * Returns: the current D-Bus name owner. While this property + * is set while registering, it really only makes sense when + * the nm_secret_agent_old_get_registered() indicates that + * registration is successful. + * + * Since: 1.24 + */ +const char * +nm_secret_agent_old_get_dbus_name_owner(NMSecretAgentOld *self) +{ + g_return_val_if_fail(NM_IS_SECRET_AGENT_OLD(self), NULL); + + return nm_ref_string_get_str(NM_SECRET_AGENT_OLD_GET_PRIVATE(self)->name_owner_curr); +} + +/** + * nm_secret_agent_old_get_registered: + * @self: a #NMSecretAgentOld + * + * Note that the secret agent transparently registers and re-registers + * as the D-Bus name owner appears. Hence, this property is not really + * useful. Also, to be graceful against races during registration, the + * instance will already accept requests while being in the process of + * registering. + * If you need to avoid races and want to wait until @self is registered, + * call nm_secret_agent_old_register_async(). If that function completes + * with success, you know the instance is registered. + * + * Returns: a %TRUE if the agent is registered, %FALSE if it is not. + **/ +gboolean +nm_secret_agent_old_get_registered(NMSecretAgentOld *self) +{ + g_return_val_if_fail(NM_IS_SECRET_AGENT_OLD(self), FALSE); + + return NM_SECRET_AGENT_OLD_GET_PRIVATE(self)->is_registered; +} + +/*****************************************************************************/ + +static void +get_secret_info_free(GetSecretsInfo *info) +{ + nm_assert(info); + nm_assert(!info->context); + + c_list_unlink_stale(&info->gsi_lst); + g_free(info->connection_path); + g_free(info->setting_name); + nm_g_slice_free(info); +} + +static void +get_secret_info_complete_and_free(GetSecretsInfo *info, GVariant *secrets, GError *error) +{ + if (error) { + if (secrets) + nm_g_variant_unref_floating(secrets); + g_dbus_method_invocation_return_gerror(g_steal_pointer(&info->context), error); + } else { + g_dbus_method_invocation_return_value(g_steal_pointer(&info->context), + g_variant_new("(@a{sa{sv}})", secrets)); + } + get_secret_info_free(info); +} + +static void +get_secret_info_complete_and_free_error(GetSecretsInfo *info, + GQuark error_domain, + int error_code, + const char * error_message) +{ + g_dbus_method_invocation_return_error_literal(g_steal_pointer(&info->context), + error_domain, + error_code, + error_message); + get_secret_info_free(info); +} + +/*****************************************************************************/ + +static void +_dbus_connection_call_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + gs_unref_object GObject *context_busy_watcher = NULL; + GAsyncReadyCallback callback; + gpointer callback_user_data; + + nm_utils_user_data_unpack(user_data, &context_busy_watcher, &callback, &callback_user_data); + callback(source, result, callback_user_data); +} + +static void +_dbus_connection_call(NMSecretAgentOld * self, + const char * bus_name, + const char * object_path, + const char * interface_name, + const char * method_name, + GVariant * parameters, + const GVariantType *reply_type, + GDBusCallFlags flags, + int timeout_msec, + GCancellable * cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + NMSecretAgentOldPrivate *priv = NM_SECRET_AGENT_OLD_GET_PRIVATE(self); + + nm_assert(nm_g_main_context_is_thread_default(priv->dbus_context)); + + g_dbus_connection_call( + priv->dbus_connection, + bus_name, + object_path, + interface_name, + method_name, + parameters, + reply_type, + flags, + timeout_msec, + cancellable, + callback ? _dbus_connection_call_cb : NULL, + callback + ? nm_utils_user_data_pack(g_object_ref(priv->context_busy_watcher), callback, user_data) + : NULL); +} + +/*****************************************************************************/ + +static GetSecretsInfo * +find_get_secrets_info(NMSecretAgentOldPrivate *priv, + const char * connection_path, + const char * setting_name) +{ + GetSecretsInfo *info; + + c_list_for_each_entry (info, &priv->gsi_lst_head, gsi_lst) { + if (nm_streq(connection_path, info->connection_path) + && nm_streq(setting_name, info->setting_name)) + return info; + } + return NULL; +} + +static void +_cancel_get_secret_request(NMSecretAgentOld *self, GetSecretsInfo *info, const char *message) +{ + c_list_unlink(&info->gsi_lst); + info->is_cancelling = TRUE; + + _LOGT("cancel get-secrets request \"%s\", \"%s\": %s", + info->connection_path, + info->setting_name, + message); + + NM_SECRET_AGENT_OLD_GET_CLASS(self)->cancel_get_secrets(self, + info->connection_path, + info->setting_name); + + get_secret_info_complete_and_free_error(info, + NM_SECRET_AGENT_ERROR, + NM_SECRET_AGENT_ERROR_AGENT_CANCELED, + message); +} + +static gboolean +verify_request(NMSecretAgentOld * self, + GDBusMethodInvocation *context, + GVariant * connection_dict, + const char * connection_path, + NMConnection ** out_connection, + GError ** error) +{ + gs_unref_object NMConnection *connection = NULL; + gs_free_error GError *local = NULL; + + if (!nm_dbus_path_not_empty(connection_path)) { + g_set_error_literal(error, + NM_SECRET_AGENT_ERROR, + NM_SECRET_AGENT_ERROR_INVALID_CONNECTION, + "Invalid connection: no connection path given."); + return FALSE; + } + + connection = _nm_simple_connection_new_from_dbus(connection_dict, + NM_SETTING_PARSE_FLAGS_BEST_EFFORT, + &local); + if (!connection) { + g_set_error(error, + NM_SECRET_AGENT_ERROR, + NM_SECRET_AGENT_ERROR_INVALID_CONNECTION, + "Invalid connection: %s", + local->message); + return FALSE; + } + + nm_connection_set_path(connection, connection_path); + NM_SET_OUT(out_connection, g_steal_pointer(&connection)); + return TRUE; +} + +static void +get_secrets_cb(NMSecretAgentOld *self, + NMConnection * connection, + GVariant * secrets, + GError * error, + gpointer user_data) +{ + GetSecretsInfo *info = user_data; + + if (info->is_cancelling) { + if (secrets) + nm_g_variant_unref_floating(secrets); + return; + } + + _LOGT("request: get-secrets request \"%s\", \"%s\" complete with %s%s%s", + info->connection_path, + info->setting_name, + NM_PRINT_FMT_QUOTED(error, "error: ", error->message, "", "success")); + + get_secret_info_complete_and_free(info, secrets, error); +} + +static void +impl_get_secrets(NMSecretAgentOld *self, GVariant *parameters, GDBusMethodInvocation *context) +{ + NMSecretAgentOldPrivate *priv = NM_SECRET_AGENT_OLD_GET_PRIVATE(self); + GError * error = NULL; + gs_unref_object NMConnection *connection = NULL; + GetSecretsInfo * info; + gs_unref_variant GVariant *arg_connection = NULL; + const char * arg_connection_path; + const char * arg_setting_name; + gs_free const char ** arg_hints = NULL; + guint32 arg_flags; + + g_variant_get(parameters, + "(@a{sa{sv}}&o&s^a&su)", + &arg_connection, + &arg_connection_path, + &arg_setting_name, + &arg_hints, + &arg_flags); + + if (!verify_request(self, context, arg_connection, arg_connection_path, &connection, &error)) { + g_dbus_method_invocation_take_error(context, error); + return; + } + + _LOGT("request: get-secrets(\"%s\", \"%s\")", arg_connection_path, arg_setting_name); + + info = find_get_secrets_info(priv, arg_connection_path, arg_setting_name); + if (info) + _cancel_get_secret_request(self, info, "Request aborted due to new request"); + + info = g_slice_new(GetSecretsInfo); + *info = (GetSecretsInfo){ + .context = context, + .connection_path = g_strdup(arg_connection_path), + .setting_name = g_strdup(arg_setting_name), + }; + c_list_link_tail(&priv->gsi_lst_head, &info->gsi_lst); + + NM_SECRET_AGENT_OLD_GET_CLASS(self)->get_secrets(self, + connection, + info->connection_path, + info->setting_name, + arg_hints, + arg_flags, + get_secrets_cb, + info); +} + +static void +impl_cancel_get_secrets(NMSecretAgentOld * self, + GVariant * parameters, + GDBusMethodInvocation *context) +{ + NMSecretAgentOldPrivate *priv = NM_SECRET_AGENT_OLD_GET_PRIVATE(self); + GetSecretsInfo * info; + const char * arg_connection_path; + const char * arg_setting_name; + + g_variant_get(parameters, "(&o&s)", &arg_connection_path, &arg_setting_name); + + info = find_get_secrets_info(priv, arg_connection_path, arg_setting_name); + if (!info) { + g_dbus_method_invocation_return_error_literal( + context, + NM_SECRET_AGENT_ERROR, + NM_SECRET_AGENT_ERROR_FAILED, + "No secrets request in progress for this connection."); + return; + } + + _cancel_get_secret_request(self, info, "Request cancelled by NetworkManager"); + + g_dbus_method_invocation_return_value(context, NULL); +} + +static void +save_secrets_cb(NMSecretAgentOld *self, NMConnection *connection, GError *error, gpointer user_data) +{ + GDBusMethodInvocation *context = user_data; + + if (error) + g_dbus_method_invocation_return_gerror(context, error); + else + g_dbus_method_invocation_return_value(context, NULL); +} + +static void +impl_save_secrets(NMSecretAgentOld *self, GVariant *parameters, GDBusMethodInvocation *context) +{ + gs_unref_object NMConnection *connection = NULL; + gs_unref_variant GVariant *arg_connection = NULL; + const char * arg_connection_path; + GError * error = NULL; + + g_variant_get(parameters, "(@a{sa{sv}}&o)", &arg_connection, &arg_connection_path); + + if (!verify_request(self, context, arg_connection, arg_connection_path, &connection, &error)) { + g_dbus_method_invocation_take_error(context, error); + return; + } + + _LOGT("request: save-secrets(\"%s\")", arg_connection_path); + + NM_SECRET_AGENT_OLD_GET_CLASS(self)->save_secrets(self, + connection, + arg_connection_path, + save_secrets_cb, + context); +} + +static void +delete_secrets_cb(NMSecretAgentOld *self, + NMConnection * connection, + GError * error, + gpointer user_data) +{ + GDBusMethodInvocation *context = user_data; + + if (error) + g_dbus_method_invocation_return_gerror(context, error); + else + g_dbus_method_invocation_return_value(context, NULL); +} + +static void +impl_delete_secrets(NMSecretAgentOld *self, GVariant *parameters, GDBusMethodInvocation *context) +{ + gs_unref_object NMConnection *connection = NULL; + gs_unref_variant GVariant *arg_connection = NULL; + const char * arg_connection_path; + GError * error = NULL; + + g_variant_get(parameters, "(@a{sa{sv}}&o)", &arg_connection, &arg_connection_path); + + if (!verify_request(self, context, arg_connection, arg_connection_path, &connection, &error)) { + g_dbus_method_invocation_take_error(context, error); + return; + } + + _LOGT("request: delete-secrets(\"%s\")", arg_connection_path); + + NM_SECRET_AGENT_OLD_GET_CLASS(self)->delete_secrets(self, + connection, + arg_connection_path, + delete_secrets_cb, + context); +} + +/*****************************************************************************/ + +/** + * nm_secret_agent_old_enable: + * @self: the #NMSecretAgentOld instance + * @enable: whether to enable or disable the listener. + * + * This has the same effect as setting %NM_SECRET_AGENT_OLD_AUTO_REGISTER + * property. + * + * Unlike most other functions, you may already call this function before + * initialization completes. + * + * Since: 1.24 + */ +void +nm_secret_agent_old_enable(NMSecretAgentOld *self, gboolean enable) +{ + NMSecretAgentOldPrivate *priv; + + g_return_if_fail(NM_IS_SECRET_AGENT_OLD(self)); + + priv = NM_SECRET_AGENT_OLD_GET_PRIVATE(self); + + enable = (!!enable); + + if (priv->auto_register != enable) { + priv->auto_register = enable; + priv->is_enabled = enable; + _notify(self, PROP_AUTO_REGISTER); + } + _register_state_change(self); +} + +static void +_secret_agent_old_destroy(NMSecretAgentOld *self) +{ + NMSecretAgentOldPrivate *priv = NM_SECRET_AGENT_OLD_GET_PRIVATE(self); + + priv->is_destroyed = TRUE; + + if (priv->exported_id != 0) { + g_dbus_connection_unregister_object(priv->dbus_connection, + nm_steal_int(&priv->exported_id)); + } + + _register_state_change(self); + + nm_assert(!priv->name_owner_changed_id); + nm_assert(!priv->name_owner_curr); + nm_assert(!priv->name_owner_next); + nm_assert(!priv->name_owner_cancellable); + nm_assert(!priv->registering_retry_source); + nm_assert(!priv->registering_cancellable); + nm_assert(!priv->init_data); + nm_assert(c_list_is_empty(&priv->gsi_lst_head)); + nm_assert(c_list_is_empty(&priv->pending_tasks_register_lst_head)); +} + +/** + * nm_secret_agent_old_destroy: + * @self: the #NMSecretAgentOld instance. + * + * Since 1.24, the instance will already register a D-Bus object on the + * D-Bus connection during initialization. That object will stay registered + * until @self gets unrefed (destroyed) or this function is called. This + * function performs the necessary cleanup to tear down the instance. Afterwards, + * the function can not longer be used. This is optional, but necessary to + * ensure unregistering the D-Bus object at a define point, when other users + * might still have a reference on @self. + * + * You may call this function any time and repeatedly. However, after destroying + * the instance, it is a bug to still use the instance for other purposes. The + * instance becomes defunct and cannot re-register. + * + * Since: 1.24 + */ +void +nm_secret_agent_old_destroy(NMSecretAgentOld *self) +{ + g_return_if_fail(NM_IS_SECRET_AGENT_OLD(self)); + + _LOGT("destroying"); + + _secret_agent_old_destroy(self); +} + +/*****************************************************************************/ + +/** + * nm_secret_agent_old_register: + * @self: a #NMSecretAgentOld + * @cancellable: a #GCancellable, or %NULL + * @error: return location for a #GError, or %NULL + * + * Registers the #NMSecretAgentOld with the NetworkManager secret manager, + * indicating to NetworkManager that the agent is able to provide and save + * secrets for connections on behalf of its user. + * + * Returns: %TRUE if registration was successful, %FALSE on error. + * + * Since 1.24, this can no longer fail unless the @cancellable gets + * cancelled. Contrary to nm_secret_agent_old_register_async(), this also + * does not wait for the registration to succeed. You cannot synchronously + * (without iterating the caller's GMainContext) wait for registration. + * + * Since 1.24, registration is idempotent. It has the same effect as setting + * %NM_SECRET_AGENT_OLD_AUTO_REGISTER to %TRUE or nm_secret_agent_old_enable(). + * + * Deprecated: 1.24: Use nm_secret_agent_old_enable() or nm_secret_agent_old_register_async(). + **/ +gboolean +nm_secret_agent_old_register(NMSecretAgentOld *self, GCancellable *cancellable, GError **error) +{ + NMSecretAgentOldPrivate *priv; + + g_return_val_if_fail(NM_IS_SECRET_AGENT_OLD(self), FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + + priv = NM_SECRET_AGENT_OLD_GET_PRIVATE(self); + + g_return_val_if_fail(priv->is_initialized && !priv->is_destroyed, FALSE); + + priv->is_enabled = TRUE; + _register_state_change(self); + + if (g_cancellable_set_error_if_cancelled(cancellable, error)) + return FALSE; + + /* This is a synchronous function, meaning: we are not allowed to iterate + * the caller's GMainContext. This is a catch 22, because we don't want + * to perform synchronous calls that bypasses the ordering of our otherwise + * asynchronous mode of operation. Hence, we always signal success. + * That's why this function is deprecated. + * + * So despite claiming success, we might still be in the process of registering + * or NetworkManager might not be available. + * + * This is a change in behavior with respect to libnm before 1.24. + */ + return TRUE; +} + +static void +_register_cancelled_cb(GObject *object, gpointer user_data) +{ + GTask * task0 = user_data; + gs_unref_object GTask * task = NULL; + NMSecretAgentOld * self = g_task_get_source_object(task0); + NMSecretAgentOldPrivate *priv = NM_SECRET_AGENT_OLD_GET_PRIVATE(self); + gulong * p_cancelled_id; + NMCListElem * elem; + gs_free_error GError *error = NULL; + + elem = nm_c_list_elem_find_first(&priv->pending_tasks_register_lst_head, x, x == task0); + + g_return_if_fail(elem); + + task = nm_c_list_elem_free_steal(elem); + + p_cancelled_id = g_task_get_task_data(task); + if (p_cancelled_id) { + g_signal_handler_disconnect(g_task_get_cancellable(task), *p_cancelled_id); + g_task_set_task_data(task, NULL, NULL); + } + + nm_utils_error_set_cancelled(&error, FALSE, NULL); + g_task_return_error(task, error); +} + +/** + * nm_secret_agent_old_register_async: + * @self: a #NMSecretAgentOld + * @cancellable: a #GCancellable, or %NULL + * @callback: callback to call when the agent is registered + * @user_data: data for @callback + * + * Asynchronously registers the #NMSecretAgentOld with the NetworkManager secret + * manager, indicating to NetworkManager that the agent is able to provide and + * save secrets for connections on behalf of its user. + * + * Since 1.24, registration cannot fail and is idempotent. It has + * the same effect as setting %NM_SECRET_AGENT_OLD_AUTO_REGISTER to %TRUE + * or nm_secret_agent_old_enable(). + * + * Since 1.24, the asynchronous result indicates whether the instance is successfully + * registered. In any case, this call enables the agent and it will automatically + * try to register and handle secret requests. A failure of this function only indicates + * that currently the instance might not be ready (but since it will automatically + * try to recover, it might be ready in a moment afterwards). Use this function if + * you want to check and ensure that the agent is registered. + **/ +void +nm_secret_agent_old_register_async(NMSecretAgentOld * self, + GCancellable * cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + NMSecretAgentOldPrivate *priv; + + g_return_if_fail(NM_IS_SECRET_AGENT_OLD(self)); + g_return_if_fail(!cancellable || G_IS_CANCELLABLE(cancellable)); + + priv = NM_SECRET_AGENT_OLD_GET_PRIVATE(self); + + g_return_if_fail(priv->is_initialized && !priv->is_destroyed); + + if (callback) { + GTask *task; + + task = nm_g_task_new(self, + cancellable, + nm_secret_agent_old_register_async, + callback, + user_data); + + c_list_link_tail(&priv->pending_tasks_register_lst_head, + &nm_c_list_elem_new_stale(task)->lst); + + if (cancellable) { + gulong cancelled_id; + + cancelled_id = + g_cancellable_connect(cancellable, G_CALLBACK(_register_cancelled_cb), task, NULL); + if (cancelled_id != 0) { + g_task_set_task_data(task, g_memdup(&cancelled_id, sizeof(cancelled_id)), g_free); + } + } + } + + priv->is_enabled = TRUE; + _register_state_change(self); +} + +/** + * nm_secret_agent_old_register_finish: + * @self: a #NMSecretAgentOld + * @result: the result passed to the #GAsyncReadyCallback + * @error: return location for a #GError, or %NULL + * + * Gets the result of a call to nm_secret_agent_old_register_async(). + * + * Returns: %TRUE if registration was successful, %FALSE on error. + * + * Since 1.24, registration cannot fail and is idempotent. It has + * the same effect as setting %NM_SECRET_AGENT_OLD_AUTO_REGISTER to %TRUE + * or nm_secret_agent_old_enable(). + **/ +gboolean +nm_secret_agent_old_register_finish(NMSecretAgentOld *self, GAsyncResult *result, GError **error) +{ + g_return_val_if_fail(NM_IS_SECRET_AGENT_OLD(self), FALSE); + g_return_val_if_fail(nm_g_task_is_valid(result, self, nm_secret_agent_old_register_async), + FALSE); + + return g_task_propagate_boolean(G_TASK(result), error); +} + +/** + * nm_secret_agent_old_unregister: + * @self: a #NMSecretAgentOld + * @cancellable: a #GCancellable, or %NULL + * @error: return location for a #GError, or %NULL + * + * Unregisters the #NMSecretAgentOld with the NetworkManager secret manager, + * indicating to NetworkManager that the agent will no longer provide or + * store secrets on behalf of this user. + * + * Returns: %TRUE if unregistration was successful, %FALSE on error + * + * Since 1.24, registration cannot fail and is idempotent. It has + * the same effect as setting %NM_SECRET_AGENT_OLD_AUTO_REGISTER to %FALSE + * or nm_secret_agent_old_enable(). + * + * Deprecated: 1.24: Use nm_secret_agent_old_enable(). + **/ +gboolean +nm_secret_agent_old_unregister(NMSecretAgentOld *self, GCancellable *cancellable, GError **error) +{ + NMSecretAgentOldPrivate *priv; + + g_return_val_if_fail(NM_IS_SECRET_AGENT_OLD(self), FALSE); + g_return_val_if_fail(!cancellable || G_IS_CANCELLABLE(cancellable), FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + + priv = NM_SECRET_AGENT_OLD_GET_PRIVATE(self); + + g_return_val_if_fail(priv->is_initialized && !priv->is_destroyed, FALSE); + + priv->is_enabled = FALSE; + _register_state_change(self); + + return !g_cancellable_set_error_if_cancelled(cancellable, error); +} + +/** + * nm_secret_agent_old_unregister_async: + * @self: a #NMSecretAgentOld + * @cancellable: a #GCancellable, or %NULL + * @callback: callback to call when the agent is unregistered + * @user_data: data for @callback + * + * Asynchronously unregisters the #NMSecretAgentOld with the NetworkManager secret + * manager, indicating to NetworkManager that the agent will no longer provide + * or store secrets on behalf of this user. + * + * Since 1.24, registration cannot fail and is idempotent. It has + * the same effect as setting %NM_SECRET_AGENT_OLD_AUTO_REGISTER to %FALSE + * or nm_secret_agent_old_enable(). + * + * Deprecated: 1.24: Use nm_secret_agent_old_enable(). + **/ +void +nm_secret_agent_old_unregister_async(NMSecretAgentOld * self, + GCancellable * cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + NMSecretAgentOldPrivate *priv; + + g_return_if_fail(NM_IS_SECRET_AGENT_OLD(self)); + g_return_if_fail(!cancellable || G_IS_CANCELLABLE(cancellable)); + + priv = NM_SECRET_AGENT_OLD_GET_PRIVATE(self); + + g_return_if_fail(priv->is_initialized && !priv->is_destroyed); + + if (callback) { + gs_unref_object GTask *task = NULL; + + task = nm_g_task_new(self, + cancellable, + nm_secret_agent_old_unregister_async, + callback, + user_data); + g_task_return_boolean(task, TRUE); + } + + priv->is_enabled = FALSE; + _register_state_change(self); +} + +/** + * nm_secret_agent_old_unregister_finish: + * @self: a #NMSecretAgentOld + * @result: the result passed to the #GAsyncReadyCallback + * @error: return location for a #GError, or %NULL + * + * Gets the result of a call to nm_secret_agent_old_unregister_async(). + * + * Returns: %TRUE if unregistration was successful, %FALSE on error. + * + * Since 1.24, registration cannot fail and is idempotent. It has + * the same effect as setting %NM_SECRET_AGENT_OLD_AUTO_REGISTER to %FALSE + * or nm_secret_agent_old_enable(). + * + * Deprecated: 1.24: Use nm_secret_agent_old_enable(). + **/ +gboolean +nm_secret_agent_old_unregister_finish(NMSecretAgentOld *self, GAsyncResult *result, GError **error) +{ + g_return_val_if_fail(NM_IS_SECRET_AGENT_OLD(self), FALSE); + g_return_val_if_fail(nm_g_task_is_valid(result, self, nm_secret_agent_old_unregister_async), + FALSE); + + return g_task_propagate_boolean(G_TASK(result), error); +} + +/*****************************************************************************/ + +/** + * nm_secret_agent_old_get_secrets: (virtual get_secrets): + * @self: a #NMSecretAgentOld + * @connection: the #NMConnection for which we're asked secrets + * @setting_name: the name of the secret setting + * @hints: (array zero-terminated=1): hints to the agent + * @flags: flags that modify the behavior of the request + * @callback: (scope async): a callback, to be invoked when the operation is done + * @user_data: (closure): caller-specific data to be passed to @callback + * + * Asynchronously retrieves secrets belonging to @connection for the + * setting @setting_name. @flags indicate specific behavior that the secret + * agent should use when performing the request, for example returning only + * existing secrets without user interaction, or requesting entirely new + * secrets from the user. + */ +void +nm_secret_agent_old_get_secrets(NMSecretAgentOld * self, + NMConnection * connection, + const char * setting_name, + const char ** hints, + NMSecretAgentGetSecretsFlags flags, + NMSecretAgentOldGetSecretsFunc callback, + gpointer user_data) +{ + g_return_if_fail(NM_IS_SECRET_AGENT_OLD(self)); + g_return_if_fail(NM_IS_CONNECTION(connection)); + g_return_if_fail(nm_connection_get_path(connection)); + g_return_if_fail(setting_name && setting_name[0]); + g_return_if_fail(!(flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_ONLY_SYSTEM)); + g_return_if_fail(!(flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_NO_ERRORS)); + g_return_if_fail(callback != NULL); + + NM_SECRET_AGENT_OLD_GET_CLASS(self)->get_secrets(self, + connection, + nm_connection_get_path(connection), + setting_name, + hints, + flags, + callback, + user_data); +} + +/** + * nm_secret_agent_old_save_secrets: (virtual save_secrets): + * @self: a #NMSecretAgentOld + * @connection: a #NMConnection + * @callback: (scope async): a callback, to be invoked when the operation is done + * @user_data: (closure): caller-specific data to be passed to @callback + * + * Asynchronously ensures that all secrets inside @connection are stored to + * disk. + */ +void +nm_secret_agent_old_save_secrets(NMSecretAgentOld * self, + NMConnection * connection, + NMSecretAgentOldSaveSecretsFunc callback, + gpointer user_data) +{ + g_return_if_fail(NM_IS_SECRET_AGENT_OLD(self)); + g_return_if_fail(NM_IS_CONNECTION(connection)); + g_return_if_fail(nm_connection_get_path(connection)); + + NM_SECRET_AGENT_OLD_GET_CLASS(self)->save_secrets(self, + connection, + nm_connection_get_path(connection), + callback, + user_data); +} + +/** + * nm_secret_agent_old_delete_secrets: (virtual delete_secrets): + * @self: a #NMSecretAgentOld + * @connection: a #NMConnection + * @callback: (scope async): a callback, to be invoked when the operation is done + * @user_data: (closure): caller-specific data to be passed to @callback + * + * Asynchronously asks the agent to delete all saved secrets belonging to + * @connection. + */ +void +nm_secret_agent_old_delete_secrets(NMSecretAgentOld * self, + NMConnection * connection, + NMSecretAgentOldDeleteSecretsFunc callback, + gpointer user_data) +{ + g_return_if_fail(NM_IS_SECRET_AGENT_OLD(self)); + g_return_if_fail(NM_IS_CONNECTION(connection)); + g_return_if_fail(nm_connection_get_path(connection)); + + NM_SECRET_AGENT_OLD_GET_CLASS(self)->delete_secrets(self, + connection, + nm_connection_get_path(connection), + callback, + user_data); +} + +/*****************************************************************************/ + +static gboolean +validate_identifier(const char *identifier) +{ + const char *p = identifier; + size_t id_len; + + /* Length between 3 and 255 characters inclusive */ + id_len = strlen(identifier); + if (id_len < 3 || id_len > 255) + return FALSE; + + if ((identifier[0] == '.') || (identifier[id_len - 1] == '.')) + return FALSE; + + /* FIXME: do complete validation here */ + while (p && *p) { + if (!g_ascii_isalnum(*p) && (*p != '_') && (*p != '-') && (*p != '.')) + return FALSE; + if ((*p == '.') && (*(p + 1) == '.')) + return FALSE; + p++; + } + + return TRUE; +} + +/*****************************************************************************/ + +static gboolean +_register_retry_cb(gpointer user_data) +{ + NMSecretAgentOld * self = user_data; + NMSecretAgentOldPrivate *priv = NM_SECRET_AGENT_OLD_GET_PRIVATE(self); + nm_auto_pop_gmaincontext GMainContext *dbus_context = NULL; + + dbus_context = nm_g_main_context_push_thread_default_if_necessary(priv->dbus_context); + + nm_clear_g_source_inst(&priv->registering_retry_source); + _register_dbus_call(self); + return G_SOURCE_CONTINUE; +} + +static void +_register_call_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMSecretAgentOld * self = user_data; + NMSecretAgentOldPrivate *priv = NM_SECRET_AGENT_OLD_GET_PRIVATE(self); + gs_unref_variant GVariant *ret = NULL; + gs_free_error GError *error = NULL; + + ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); + + if (nm_utils_error_is_cancelled(error)) + return; + + nm_assert(!priv->registering_retry_source); + nm_assert(!priv->is_registered); + nm_assert(priv->registering_cancellable); + + if (nm_dbus_error_is(error, NM_DBUS_ERROR_NAME_UNKNOWN_METHOD) + && nm_utils_get_monotonic_timestamp_msec() < priv->registering_timeout_msec) { + guint timeout_msec; + + timeout_msec = (2u << NM_MIN(6u, ++priv->registering_try_count)); + + _LOGT("register: registration failed with error \"%s\". Retry in %u msec...", + error->message, + timeout_msec); + + priv->registering_retry_source = + nm_g_source_attach(nm_g_timeout_source_new(timeout_msec, + G_PRIORITY_DEFAULT, + _register_retry_cb, + self, + NULL), + priv->dbus_context); + return; + } + + g_clear_object(&priv->registering_cancellable); + + if (error) { + /* registration apparently failed. However we still keep priv->registered_against_server TRUE, because + * + * - eventually we want to still make an Unregister() call. Even if it probably has no effect, + * better be sure. + * + * - we actually accept secret request (from the right name owner). We register so that + * NetworkManager knows that we are here. We don't require the registration to succeed + * for our purpose. If NetworkManager makes requests for us, despite the registration + * failing, that is fine. */ + _LOGT("register: registration failed with error \"%s\"", error->message); + goto out; + } + + _LOGT("register: registration succeeded"); + priv->is_registered = TRUE; + _notify(self, PROP_REGISTERED); + +out: + _register_state_complete(self); +} + +static void +_register_dbus_call(NMSecretAgentOld *self) +{ + NMSecretAgentOldPrivate *priv = NM_SECRET_AGENT_OLD_GET_PRIVATE(self); + + _dbus_connection_call(self, + nm_ref_string_get_str(priv->name_owner_curr), + NM_DBUS_PATH_AGENT_MANAGER, + NM_DBUS_INTERFACE_AGENT_MANAGER, + "RegisterWithCapabilities", + g_variant_new("(su)", priv->identifier, (guint32) priv->capabilities), + G_VARIANT_TYPE("()"), + G_DBUS_CALL_FLAGS_NONE, + _CALL_REGISTER_TIMEOUT_MSEC, + priv->registering_cancellable, + _register_call_cb, + self); +} + +static void +_get_connection_unix_user_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMSecretAgentOld * self; + NMSecretAgentOldPrivate *priv; + gs_unref_variant GVariant *ret = NULL; + gs_free_error GError *error = NULL; + guint32 sender_uid = 0; + + ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); + if (nm_utils_error_is_cancelled(error)) + return; + + self = user_data; + priv = NM_SECRET_AGENT_OLD_GET_PRIVATE(self); + + nm_assert(priv->registering_cancellable); + nm_assert(!priv->registered_against_server); + + if (ret) + g_variant_get(ret, "(u)", &sender_uid); + + if (ret && sender_uid == 0) + _LOGT("register: peer %s is owned by root. Validated to accept requests.", + priv->name_owner_curr->str); + else if (ret && priv->session_bus) { + _LOGT( + "register: peer %s is owned by user %d for session bus. Validated to accept requests.", + priv->name_owner_curr->str, + sender_uid); + } else { + /* the peer is not validated. We don't actually register. */ + if (ret) + _LOGT("register: peer %s is owned by user %u. Not validated as NetworkManager service.", + priv->name_owner_curr->str, + sender_uid); + else + _LOGT("register: failed to get user id for peer %s: %s. Not validated as " + "NetworkManager service.", + priv->name_owner_curr->str, + error->message); + + /* we actually don't do anything and keep the agent unregistered. + * + * We keep priv->registering_cancellable set to not retry this again, until we loose the + * name owner. But the state of the agent is lingering and won't accept any requests. */ + return; + } + + priv->registering_timeout_msec = + nm_utils_get_monotonic_timestamp_msec() + REGISTER_RETRY_TIMEOUT_MSEC; + priv->registering_try_count = 0; + priv->registered_against_server = TRUE; + _register_dbus_call(self); +} + +/*****************************************************************************/ + +static void +_name_owner_changed(NMSecretAgentOld *self, const char *name_owner, gboolean is_event) +{ + NMSecretAgentOldPrivate *priv = NM_SECRET_AGENT_OLD_GET_PRIVATE(self); + + if (is_event) { + if (priv->name_owner_cancellable) { + /* we are still fetching the name-owner. Ignore this event. */ + return; + } + } else + g_clear_object(&priv->name_owner_cancellable); + + nm_ref_string_unref(priv->name_owner_next); + priv->name_owner_next = nm_ref_string_new(nm_str_not_empty(name_owner)); + + _LOGT("name-owner changed: %s%s%s -> %s%s%s", + NM_PRINT_FMT_QUOTED(priv->name_owner_curr, + "\"", + priv->name_owner_curr->str, + "\"", + "(null)"), + NM_PRINT_FMT_QUOTED(priv->name_owner_next, + "\"", + priv->name_owner_next->str, + "\"", + "(null)")); + + _register_state_change(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) +{ + NMSecretAgentOld *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, TRUE); +} + +static void +_name_owner_get_cb(const char *name_owner, GError *error, gpointer user_data) +{ + if (name_owner || !nm_utils_error_is_cancelled(error)) + _name_owner_changed(user_data, name_owner, FALSE); +} + +/*****************************************************************************/ + +static void +_method_call(GDBusConnection * connection, + const char * sender, + const char * object_path, + const char * interface_name, + const char * method_name, + GVariant * parameters, + GDBusMethodInvocation *context, + gpointer user_data) +{ + NMSecretAgentOld * self = user_data; + NMSecretAgentOldPrivate *priv = NM_SECRET_AGENT_OLD_GET_PRIVATE(self); + + nm_assert(nm_streq0(object_path, NM_DBUS_PATH_SECRET_AGENT)); + nm_assert(nm_streq0(interface_name, NM_DBUS_INTERFACE_SECRET_AGENT)); + nm_assert(sender); + nm_assert(nm_streq0(sender, g_dbus_method_invocation_get_sender(context))); + + if (!priv->name_owner_curr || !priv->registered_against_server) { + /* priv->registered_against_server means that we started to register, but not necessarily + * that the registration fully succeeded. However, we already authenticated the request + * and so we accept it, even if the registration is not yet complete. */ + g_dbus_method_invocation_return_error_literal(context, + NM_SECRET_AGENT_ERROR, + NM_SECRET_AGENT_ERROR_PERMISSION_DENIED, + "Request by non authenticated peer rejected"); + return; + } + + if (nm_streq(method_name, "GetSecrets")) + impl_get_secrets(self, parameters, context); + else if (nm_streq(method_name, "CancelGetSecrets")) + impl_cancel_get_secrets(self, parameters, context); + else if (nm_streq(method_name, "SaveSecrets")) + impl_save_secrets(self, parameters, context); + else if (nm_streq(method_name, "DeleteSecrets")) + impl_delete_secrets(self, parameters, context); + else + nm_assert_not_reached(); +} + +/*****************************************************************************/ + +static void +_register_state_complete(NMSecretAgentOld *self) +{ + NMSecretAgentOldPrivate *priv = NM_SECRET_AGENT_OLD_GET_PRIVATE(self); + NMCListElem * elem; + gboolean any_tasks_to_complete = FALSE; + + if (!c_list_is_empty(&priv->pending_tasks_register_lst_head)) { + /* add a dummy sentinel. We want to complete all the task we started + * so far, but as we invoke user callbacks, the user might register + * new tasks. Those we don't complete in this run. */ + g_object_ref(self); + any_tasks_to_complete = TRUE; + c_list_link_tail(&priv->pending_tasks_register_lst_head, + &nm_c_list_elem_new_stale(&any_tasks_to_complete)->lst); + } + + _init_complete(self, NULL); + + if (any_tasks_to_complete) { + while ( + (elem = c_list_first_entry(&priv->pending_tasks_register_lst_head, NMCListElem, lst))) { + gpointer data = nm_c_list_elem_free_steal(elem); + gs_unref_object GTask *task = NULL; + + if (data == &any_tasks_to_complete) { + any_tasks_to_complete = FALSE; + break; + } + + task = data; + + if (!priv->is_registered) { + g_task_return_error(task, + g_error_new_literal(NM_SECRET_AGENT_ERROR, + NM_SECRET_AGENT_ERROR_FAILED, + _("registration failed"))); + continue; + } + g_task_return_boolean(task, TRUE); + } + nm_assert(!any_tasks_to_complete); + g_object_unref(self); + } +} + +static void +_register_state_change_do(NMSecretAgentOld *self) +{ + NMSecretAgentOldPrivate *priv = NM_SECRET_AGENT_OLD_GET_PRIVATE(self); + + if (priv->is_destroyed) + priv->is_enabled = FALSE; + + if (!priv->is_enabled || priv->registration_force_unregister + || priv->name_owner_curr != priv->name_owner_next) { + GetSecretsInfo *info; + + while ((info = c_list_first_entry(&priv->gsi_lst_head, GetSecretsInfo, gsi_lst))) { + _cancel_get_secret_request(self, info, "The secret agent is going away"); + _register_state_change(self); + return; + } + + priv->registration_force_unregister = FALSE; + + nm_clear_g_cancellable(&priv->registering_cancellable); + nm_clear_g_source_inst(&priv->registering_retry_source); + + if (priv->registered_against_server) { + priv->registered_against_server = FALSE; + if (priv->name_owner_curr) { + _LOGT("register: unregister from %s", priv->name_owner_curr->str); + _dbus_connection_call(self, + priv->name_owner_curr->str, + NM_DBUS_PATH_AGENT_MANAGER, + NM_DBUS_INTERFACE_AGENT_MANAGER, + "Unregister", + g_variant_new("()"), + G_VARIANT_TYPE("()"), + G_DBUS_CALL_FLAGS_NONE, + _CALL_REGISTER_TIMEOUT_MSEC, + NULL, + NULL, + NULL); + } + } + + if (!priv->is_enabled) { + nm_clear_g_cancellable(&priv->name_owner_cancellable); + nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->name_owner_changed_id); + nm_clear_pointer(&priv->name_owner_curr, nm_ref_string_unref); + nm_clear_pointer(&priv->name_owner_next, nm_ref_string_unref); + } + + if (priv->is_registered) { + priv->is_registered = FALSE; + if (!priv->is_destroyed) { + _LOGT("register: now unregistered"); + _notify(self, PROP_REGISTERED); + _register_state_change(self); + return; + } + } + + if (!priv->is_enabled) { + _register_state_complete(self); + return; + } + + if (priv->name_owner_curr != priv->name_owner_next) { + nm_ref_string_unref(priv->name_owner_curr); + priv->name_owner_curr = nm_ref_string_ref(priv->name_owner_next); + } + } + + if (priv->name_owner_changed_id == 0) { + nm_assert(!priv->name_owner_cancellable); + nm_assert(!priv->name_owner_curr); + nm_assert(!priv->name_owner_next); + priv->name_owner_cancellable = g_cancellable_new(); + priv->name_owner_changed_id = + nm_dbus_connection_signal_subscribe_name_owner_changed(priv->dbus_connection, + NM_DBUS_SERVICE, + _name_owner_changed_cb, + self, + NULL); + nm_dbus_connection_call_get_name_owner(priv->dbus_connection, + NM_DBUS_SERVICE, + -1, + priv->name_owner_cancellable, + _name_owner_get_cb, + self); + return; + } + + if (priv->name_owner_cancellable) { + /* we still wait for the name owner. Nothing to do for now. */ + return; + } + + if (!priv->name_owner_curr) { + /* we don't have a name owner. We are done and wait. */ + _register_state_complete(self); + return; + } + + if (priv->registering_cancellable) { + /* we are already registering... wait longer. */ + return; + } + + nm_assert(!priv->registering_retry_source); + + if (!priv->is_registered) { + /* start registering... */ + priv->registering_cancellable = g_cancellable_new(); + _dbus_connection_call(self, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "GetConnectionUnixUser", + g_variant_new("(s)", priv->name_owner_curr->str), + G_VARIANT_TYPE("(u)"), + G_DBUS_CALL_FLAGS_NONE, + _CALL_REGISTER_TIMEOUT_MSEC, + priv->registering_cancellable, + _get_connection_unix_user_cb, + self); + return; + } + + /* we are fully registered and done. */ + _register_state_complete(self); +} + +static void +_register_state_change(NMSecretAgentOld *self) +{ + _nm_unused gs_unref_object NMSecretAgentOld *self_keep_alive = g_object_ref(self); + NMSecretAgentOldPrivate * priv = NM_SECRET_AGENT_OLD_GET_PRIVATE(self); + nm_auto_pop_gmaincontext GMainContext *dbus_context = NULL; + + if (priv->register_state_change_reenter == 0) { + /* We are not yet initialized. Do nothing. */ + return; + } + + if (priv->register_state_change_reenter != 1) { + /* Recursive calls are prevented. Do nothing for now, but repeat + * the state change afterwards. */ + priv->register_state_change_reenter = 3; + return; + } + + dbus_context = nm_g_main_context_push_thread_default_if_necessary(priv->dbus_context); + +again: + priv->register_state_change_reenter = 2; + + _register_state_change_do(self); + + if (priv->register_state_change_reenter != 2) + goto again; + + priv->register_state_change_reenter = 1; +} + +/*****************************************************************************/ + +static void +_init_complete(NMSecretAgentOld *self, GError *error_take) +{ + NMSecretAgentOldPrivate *priv = NM_SECRET_AGENT_OLD_GET_PRIVATE(self); + gs_free_error GError *error = g_steal_pointer(&error_take); + GError * error_cancelled = NULL; + + if (!priv->init_data) + return; + + if (g_cancellable_set_error_if_cancelled(priv->init_data->cancellable, &error_cancelled)) { + g_clear_error(&error); + g_propagate_error(&error, error_cancelled); + } + + priv->is_initialized = (!error); + + _LOGT("%s init complete with %s%s%s", + priv->init_data->is_sync ? "sync" : "async", + NM_PRINT_FMT_QUOTED(error_take, "error: ", error_take->message, "", "success")); + + nml_init_data_return(g_steal_pointer(&priv->init_data), g_steal_pointer(&error)); +} + +static void +_init_register_object(NMSecretAgentOld *self) +{ + NMSecretAgentOldPrivate *priv = NM_SECRET_AGENT_OLD_GET_PRIVATE(self); + gs_free_error GError *error = NULL; + GDBusInterfaceVTable interface_vtable = { + .method_call = _method_call, + }; + + if (g_cancellable_set_error_if_cancelled(priv->init_data->cancellable, &error)) { + _init_complete(self, g_steal_pointer(&error)); + return; + } + + priv->exported_id = g_dbus_connection_register_object(priv->dbus_connection, + NM_DBUS_PATH_SECRET_AGENT, + (GDBusInterfaceInfo *) &interface_info, + &interface_vtable, + self, + NULL, + &error); + if (priv->exported_id == 0) { + _init_complete(self, g_steal_pointer(&error)); + return; + } + + priv->register_state_change_reenter = 1; + + _register_state_change(self); +} + +static void +_init_got_bus(GObject *initable, GAsyncResult *result, gpointer user_data) +{ + NMSecretAgentOld * self = user_data; + NMSecretAgentOldPrivate *priv = NM_SECRET_AGENT_OLD_GET_PRIVATE(self); + gs_free_error GError *error = NULL; + + priv->dbus_connection = g_bus_get_finish(result, &error); + if (!priv->dbus_connection) { + _init_complete(self, g_steal_pointer(&error)); + return; + } + + _LOGT("init: got GDBusConnection"); + + _notify(self, PROP_DBUS_CONNECTION); + + _init_register_object(self); +} + +static void +_init_start(NMSecretAgentOld *self) +{ + NMSecretAgentOldPrivate *priv = NM_SECRET_AGENT_OLD_GET_PRIVATE(self); + + if (!priv->dbus_connection) { + GBusType bus_type; + + bus_type = _nm_dbus_bus_type(); + + priv->session_bus = (bus_type == G_BUS_TYPE_SESSION); + + g_bus_get(bus_type, priv->init_data->cancellable, _init_got_bus, self); + return; + } + + _init_register_object(self); +} + +static void +init_async(GAsyncInitable * initable, + int io_priority, + GCancellable * cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + NMSecretAgentOld * self; + NMSecretAgentOldClass * klass; + NMSecretAgentOldPrivate *priv; + nm_auto_pop_gmaincontext GMainContext *dbus_context = NULL; + gs_unref_object GTask *task = NULL; + + g_return_if_fail(NM_IS_SECRET_AGENT_OLD(initable)); + + self = NM_SECRET_AGENT_OLD(initable); + priv = NM_SECRET_AGENT_OLD_GET_PRIVATE(self); + + g_return_if_fail(!priv->dbus_context); + g_return_if_fail(!priv->is_destroyed); + + klass = NM_SECRET_AGENT_OLD_GET_CLASS(self); + g_return_if_fail(klass->get_secrets); + g_return_if_fail(klass->cancel_get_secrets); + g_return_if_fail(klass->save_secrets); + g_return_if_fail(klass->delete_secrets); + + _LOGT("init-async starting..."); + + priv->dbus_context = g_main_context_ref(priv->main_context); + + dbus_context = nm_g_main_context_push_thread_default_if_necessary(priv->dbus_context); + + task = nm_g_task_new(self, cancellable, init_async, callback, user_data); + g_task_set_priority(task, io_priority); + + priv->init_data = nml_init_data_new_async(cancellable, g_steal_pointer(&task)); + + _init_start(self); +} + +static gboolean +init_finish(GAsyncInitable *initable, GAsyncResult *result, GError **error) +{ + g_return_val_if_fail(NM_IS_SECRET_AGENT_OLD(initable), FALSE); + g_return_val_if_fail(nm_g_task_is_valid(result, initable, init_async), FALSE); + + return g_task_propagate_boolean(G_TASK(result), error); +} + +/*****************************************************************************/ + +static gboolean +init_sync(GInitable *initable, GCancellable *cancellable, GError **error) +{ + gs_unref_object NMSecretAgentOld *self = NULL; + NMSecretAgentOldPrivate * priv; + NMSecretAgentOldClass * klass; + GMainLoop * main_loop; + GError * local_error = NULL; + + g_return_val_if_fail(NM_IS_SECRET_AGENT_OLD(initable), FALSE); + + self = g_object_ref(NM_SECRET_AGENT_OLD(initable)); + priv = NM_SECRET_AGENT_OLD_GET_PRIVATE(self); + + g_return_val_if_fail(!priv->dbus_context, FALSE); + g_return_val_if_fail(!priv->is_destroyed, FALSE); + + klass = NM_SECRET_AGENT_OLD_GET_CLASS(self); + g_return_val_if_fail(klass->get_secrets, FALSE); + g_return_val_if_fail(klass->cancel_get_secrets, FALSE); + g_return_val_if_fail(klass->save_secrets, FALSE); + g_return_val_if_fail(klass->delete_secrets, FALSE); + + _LOGT("init-sync"); + + /* See NMClient's sync-init method for explanation about why we create + * an internal GMainContext priv->dbus_context. */ + + priv->dbus_context = g_main_context_new(); + + g_main_context_push_thread_default(priv->dbus_context); + + main_loop = g_main_loop_new(priv->dbus_context, FALSE); + + priv->init_data = nml_init_data_new_sync(cancellable, main_loop, &local_error); + + _init_start(self); + + g_main_loop_run(main_loop); + + g_main_loop_unref(main_loop); + + g_main_context_pop_thread_default(priv->dbus_context); + + nm_context_busy_watcher_integrate_source(priv->main_context, + priv->dbus_context, + priv->context_busy_watcher); + + if (local_error) { + g_propagate_error(error, local_error); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSecretAgentOldPrivate *priv = NM_SECRET_AGENT_OLD_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_DBUS_CONNECTION: + g_value_set_object(value, priv->dbus_connection); + break; + case PROP_IDENTIFIER: + g_value_set_string(value, priv->identifier); + break; + case PROP_AUTO_REGISTER: + g_value_set_boolean(value, priv->auto_register); + break; + case PROP_REGISTERED: + g_value_set_boolean(value, priv->is_registered); + break; + case PROP_CAPABILITIES: + g_value_set_flags(value, priv->capabilities); + 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) +{ + NMSecretAgentOld * self = NM_SECRET_AGENT_OLD(object); + NMSecretAgentOldPrivate *priv = NM_SECRET_AGENT_OLD_GET_PRIVATE(self); + guint u; + + switch (prop_id) { + case PROP_DBUS_CONNECTION: + /* construct-only */ + priv->dbus_connection = g_value_dup_object(value); + break; + case PROP_IDENTIFIER: + /* construct-only */ + priv->identifier = g_value_dup_string(value); + g_return_if_fail(validate_identifier(priv->identifier)); + break; + case PROP_AUTO_REGISTER: + /* construct */ + priv->auto_register = g_value_get_boolean(value); + priv->is_enabled = priv->auto_register; + _register_state_change(self); + break; + case PROP_CAPABILITIES: + /* construct */ + u = g_value_get_flags(value); + if (u != priv->capabilities) { + priv->capabilities = u; + priv->registration_force_unregister = TRUE; + _register_state_change(self); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_secret_agent_old_init(NMSecretAgentOld *self) +{ + NMSecretAgentOldPrivate *priv = NM_SECRET_AGENT_OLD_GET_PRIVATE(self); + + _LOGT("create new instance"); + + c_list_init(&priv->gsi_lst_head); + c_list_init(&priv->pending_tasks_register_lst_head); + + priv->main_context = g_main_context_ref_thread_default(); + priv->context_busy_watcher = g_object_new(G_TYPE_OBJECT, NULL); +} + +static void +dispose(GObject *object) +{ + NMSecretAgentOld *self = NM_SECRET_AGENT_OLD(object); + + _LOGT("disposing"); + + _secret_agent_old_destroy(self); + + G_OBJECT_CLASS(nm_secret_agent_old_parent_class)->dispose(object); +} + +static void +finalize(GObject *object) +{ + NMSecretAgentOld * self = NM_SECRET_AGENT_OLD(object); + NMSecretAgentOldPrivate *priv = NM_SECRET_AGENT_OLD_GET_PRIVATE(self); + + _LOGT("finalizing"); + + if (priv->dbus_context) { + nml_cleanup_context_busy_watcher_on_idle(g_steal_pointer(&priv->context_busy_watcher), + priv->dbus_context); + } + + g_clear_object(&priv->dbus_connection); + nm_clear_pointer(&priv->dbus_context, g_main_context_unref); + nm_clear_pointer(&priv->main_context, g_main_context_unref); + + g_clear_object(&priv->context_busy_watcher); + + g_free(priv->identifier); + + G_OBJECT_CLASS(nm_secret_agent_old_parent_class)->finalize(object); +} + +static void +nm_secret_agent_old_class_init(NMSecretAgentOldClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS(class); + + g_type_class_add_private(class, sizeof(NMSecretAgentOldPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->dispose = dispose; + object_class->finalize = finalize; + + /** + * NMSecretAgentOld:dbus-connection: + * + * The #GDBusConnection used by the instance. You may either set this + * as construct-only property, or otherwise #NMSecretAgentOld will choose + * a connection via g_bus_get() during initialization. + * + * Since: 1.24 + **/ + obj_properties[PROP_DBUS_CONNECTION] = + g_param_spec_object(NM_SECRET_AGENT_OLD_DBUS_CONNECTION, + "", + "", + G_TYPE_DBUS_CONNECTION, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + /** + * NMSecretAgentOld:identifier: + * + * Identifies this agent; only one agent in each user session may use the + * same identifier. Identifier formatting follows the same rules as + * D-Bus bus names with the exception that the ':' character is not + * allowed. The valid set of characters is "[A-Z][a-z][0-9]_-." and the + * identifier is limited in length to 255 characters with a minimum + * of 3 characters. An example valid identifier is 'org.gnome.nm-applet' + * (without quotes). + **/ + obj_properties[PROP_IDENTIFIER] = + g_param_spec_string(NM_SECRET_AGENT_OLD_IDENTIFIER, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + /** + * NMSecretAgentOld:auto-register: + * + * If %TRUE (the default), the agent will always be registered when + * NetworkManager is running; if NetworkManager exits and restarts, the + * agent will re-register itself automatically. + * + * In particular, if this property is %TRUE at construct time, then the + * agent will register itself with NetworkManager during + * construction/initialization and initialization will only complete + * after registration is completed (either successfully or unsuccessfully). + * Since 1.24, a failure to register will no longer cause initialization + * of #NMSecretAgentOld to fail. + * + * If the property is %FALSE, the agent will not automatically register with + * NetworkManager, and nm_secret_agent_old_enable() or + * nm_secret_agent_old_register_async() must be called to register it. + * + * Calling nm_secret_agent_old_enable() has the same effect as setting this + * property. + **/ + obj_properties[PROP_AUTO_REGISTER] = + g_param_spec_boolean(NM_SECRET_AGENT_OLD_AUTO_REGISTER, + "", + "", + TRUE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); + + /** + * NMSecretAgentOld:registered: + * + * %TRUE if the agent is registered with NetworkManager, %FALSE if not. + **/ + obj_properties[PROP_REGISTERED] = + g_param_spec_boolean(NM_SECRET_AGENT_OLD_REGISTERED, + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSecretAgentOld:capabilities: + * + * A bitfield of %NMSecretAgentCapabilities. + * + * Changing this property is possible at any time. In case the secret + * agent is currently registered, this will cause a re-registration. + **/ + obj_properties[PROP_CAPABILITIES] = + g_param_spec_flags(NM_SECRET_AGENT_OLD_CAPABILITIES, + "", + "", + NM_TYPE_SECRET_AGENT_CAPABILITIES, + NM_SECRET_AGENT_CAPABILITY_NONE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} + +static void +nm_secret_agent_old_initable_iface_init(GInitableIface *iface) +{ + iface->init = init_sync; +} + +static void +nm_secret_agent_old_async_initable_iface_init(GAsyncInitableIface *iface) +{ + iface->init_async = init_async; + iface->init_finish = init_finish; +} |