/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2010 - 2011 Red Hat, Inc. */ #include "src/core/nm-default-daemon.h" #include "nm-secret-agent.h" #include #include #include "libnm-glib-aux/nm-c-list.h" #include "libnm-glib-aux/nm-dbus-aux.h" #include "nm-dbus-interface.h" #include "libnm-core-intern/nm-core-internal.h" #include "libnm-core-aux-intern/nm-auth-subject.h" #include "nm-simple-connection.h" #include "NetworkManagerUtils.h" #include "c-list/src/c-list.h" /*****************************************************************************/ #define METHOD_GET_SECRETS "GetSecrets" #define METHOD_CANCEL_GET_SECRETS "CancelGetSecrets" #define METHOD_SAVE_SECRETS "SaveSecrets" #define METHOD_DELETE_SECRETS "DeleteSecrets" enum { DISCONNECTED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = {0}; typedef struct _NMSecretAgentPrivate { CList permissions; CList requests; GDBusConnection * dbus_connection; char * description; NMAuthSubject * subject; char * identifier; char * owner_username; char * dbus_owner; GCancellable * name_owner_cancellable; guint name_owner_changed_id; NMSecretAgentCapabilities capabilities; bool shutdown_wait_obj_registered : 1; } NMSecretAgentPrivate; struct _NMSecretAgentClass { GObjectClass parent; }; G_DEFINE_TYPE(NMSecretAgent, nm_secret_agent, G_TYPE_OBJECT) #define NM_SECRET_AGENT_GET_PRIVATE(self) \ _NM_GET_PRIVATE_PTR(self, NMSecretAgent, NM_IS_SECRET_AGENT) /*****************************************************************************/ #define _NMLOG_PREFIX_NAME "secret-agent" #define _NMLOG_DOMAIN LOGD_AGENTS #define _NMLOG(level, ...) \ G_STMT_START \ { \ if (nm_logging_enabled((level), (_NMLOG_DOMAIN))) { \ char _prefix[64]; \ \ if ((self)) { \ g_snprintf(_prefix, \ sizeof(_prefix), \ _NMLOG_PREFIX_NAME "[" NM_HASH_OBFUSCATE_PTR_FMT "]", \ NM_HASH_OBFUSCATE_PTR(self)); \ } else \ g_strlcpy(_prefix, _NMLOG_PREFIX_NAME, sizeof(_prefix)); \ \ _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 \ { \ NMSecretAgentCallId *const _call_id = (call_id); \ \ nm_assert(_call_id); \ \ nm_log((level), \ (_NMLOG_DOMAIN), \ NULL, \ NULL, \ "%s[" NM_HASH_OBFUSCATE_PTR_FMT "] request [" NM_HASH_OBFUSCATE_PTR_FMT \ ",%s,%s%s%s%s]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ _NMLOG_PREFIX_NAME, \ NM_HASH_OBFUSCATE_PTR(_call_id->self), \ NM_HASH_OBFUSCATE_PTR(_call_id), \ _call_id->method_name, \ NM_PRINT_FMT_QUOTE_STRING(_call_id->path), \ (_call_id->cancellable ? "" : " (cancelled)") _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ } \ G_STMT_END /*****************************************************************************/ static NM_UTILS_FLAGS2STR_DEFINE(_capabilities_to_string, NMSecretAgentCapabilities, NM_UTILS_FLAGS2STR(NM_SECRET_AGENT_CAPABILITY_NONE, "none"), NM_UTILS_FLAGS2STR(NM_SECRET_AGENT_CAPABILITY_VPN_HINTS, "vpn-hints"), ); /*****************************************************************************/ struct _NMSecretAgentCallId { CList lst; NMSecretAgent * self; GCancellable * cancellable; char * path; const char * method_name; char * setting_name; NMSecretAgentCallback callback; gpointer callback_data; }; static NMSecretAgentCallId * _call_id_new(NMSecretAgent * self, const char * method_name, /* this must be a static string. */ const char * path, const char * setting_name, NMSecretAgentCallback callback, gpointer callback_data) { NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE(self); NMSecretAgentCallId * call_id; call_id = g_slice_new(NMSecretAgentCallId); *call_id = (NMSecretAgentCallId){ .self = g_object_ref(self), .path = g_strdup(path), .setting_name = g_strdup(setting_name), .method_name = method_name, .callback = callback, .callback_data = callback_data, .cancellable = g_cancellable_new(), }; c_list_link_tail(&priv->requests, &call_id->lst); _LOG2T(call_id, "new request..."); if (!priv->shutdown_wait_obj_registered) { /* self has async requests (that keep self alive). As long as * we have pending requests, shutdown is blocked. */ priv->shutdown_wait_obj_registered = TRUE; nm_shutdown_wait_obj_register_object(G_OBJECT(self), "secret-agent"); } return call_id; } #define _call_id_new(self, method_name, path, setting_name, callback, callback_data) \ _call_id_new(self, "" method_name "", path, setting_name, callback, callback_data) static void _call_id_free(NMSecretAgentCallId *call_id) { c_list_unlink_stale(&call_id->lst); g_free(call_id->path); g_free(call_id->setting_name); nm_g_object_unref(call_id->cancellable); g_object_unref(call_id->self); nm_g_slice_free(call_id); } static void _call_id_invoke_callback(NMSecretAgentCallId *call_id, GVariant * secrets, GError * error, gboolean cancelled, gboolean free_call_id) { gs_free_error GError *error_cancelled = NULL; nm_assert(call_id); nm_assert(!c_list_is_empty(&call_id->lst)); c_list_unlink(&call_id->lst); if (cancelled) { nm_assert(!secrets); nm_assert(!error); if (call_id->callback) { nm_utils_error_set_cancelled(&error_cancelled, FALSE, "NMSecretAgent"); error = error_cancelled; } _LOG2T(call_id, "cancelled"); } else if (error) { nm_assert(!secrets); _LOG2T(call_id, "completed with failure: %s", error->message); } else { nm_assert(!secrets || g_variant_is_of_type(secrets, G_VARIANT_TYPE("a{sa{sv}}"))); nm_assert((!!secrets) == nm_streq0(call_id->method_name, METHOD_GET_SECRETS)); _LOG2T(call_id, "completed successfully"); } if (call_id->callback) call_id->callback(call_id->self, call_id, secrets, error, call_id->callback_data); if (free_call_id) _call_id_free(call_id); } /*****************************************************************************/ static char * _create_description(const char *dbus_owner, const char *identifier, gulong uid) { return g_strdup_printf("%s/%s/%lu", dbus_owner, identifier, uid); } const char * nm_secret_agent_get_description(NMSecretAgent *agent) { NMSecretAgentPrivate *priv; g_return_val_if_fail(NM_IS_SECRET_AGENT(agent), NULL); priv = NM_SECRET_AGENT_GET_PRIVATE(agent); if (!priv->description) { priv->description = _create_description(priv->dbus_owner, priv->identifier, nm_auth_subject_get_unix_process_uid(priv->subject)); } return priv->description; } /*****************************************************************************/ const char * nm_secret_agent_get_dbus_owner(NMSecretAgent *agent) { g_return_val_if_fail(NM_IS_SECRET_AGENT(agent), NULL); return NM_SECRET_AGENT_GET_PRIVATE(agent)->dbus_owner; } const char * nm_secret_agent_get_identifier(NMSecretAgent *agent) { g_return_val_if_fail(NM_IS_SECRET_AGENT(agent), NULL); return NM_SECRET_AGENT_GET_PRIVATE(agent)->identifier; } gulong nm_secret_agent_get_owner_uid(NMSecretAgent *agent) { g_return_val_if_fail(NM_IS_SECRET_AGENT(agent), G_MAXULONG); return nm_auth_subject_get_unix_process_uid(NM_SECRET_AGENT_GET_PRIVATE(agent)->subject); } const char * nm_secret_agent_get_owner_username(NMSecretAgent *agent) { g_return_val_if_fail(NM_IS_SECRET_AGENT(agent), NULL); return NM_SECRET_AGENT_GET_PRIVATE(agent)->owner_username; } gulong nm_secret_agent_get_pid(NMSecretAgent *agent) { g_return_val_if_fail(NM_IS_SECRET_AGENT(agent), G_MAXULONG); return nm_auth_subject_get_unix_process_pid(NM_SECRET_AGENT_GET_PRIVATE(agent)->subject); } NMSecretAgentCapabilities nm_secret_agent_get_capabilities(NMSecretAgent *agent) { g_return_val_if_fail(NM_IS_SECRET_AGENT(agent), NM_SECRET_AGENT_CAPABILITY_NONE); return NM_SECRET_AGENT_GET_PRIVATE(agent)->capabilities; } NMAuthSubject * nm_secret_agent_get_subject(NMSecretAgent *agent) { g_return_val_if_fail(NM_IS_SECRET_AGENT(agent), NULL); return NM_SECRET_AGENT_GET_PRIVATE(agent)->subject; } /*****************************************************************************/ /** * nm_secret_agent_add_permission: * @agent: A #NMSecretAgent. * @permission: The name of the permission * * Records whether or not the agent has a given permission. */ void nm_secret_agent_add_permission(NMSecretAgent *agent, const char *permission, gboolean allowed) { NMSecretAgentPrivate *priv; NMCListElem * elem; g_return_if_fail(agent != NULL); g_return_if_fail(permission != NULL); priv = NM_SECRET_AGENT_GET_PRIVATE(agent); elem = nm_c_list_elem_find_first(&priv->permissions, p, nm_streq(p, permission)); if (elem) { if (!allowed) nm_c_list_elem_free_full(elem, g_free); return; } if (allowed) { c_list_link_tail(&priv->permissions, &nm_c_list_elem_new_stale(g_strdup(permission))->lst); } } /** * nm_secret_agent_has_permission: * @agent: A #NMSecretAgent. * @permission: The name of the permission to check for * * Returns whether or not the agent has the given permission. * * Returns: %TRUE if the agent has the given permission, %FALSE if it does not * or if the permission was not previous recorded with * nm_secret_agent_add_permission(). */ gboolean nm_secret_agent_has_permission(NMSecretAgent *agent, const char *permission) { g_return_val_if_fail(agent != NULL, FALSE); g_return_val_if_fail(permission != NULL, FALSE); return !!nm_c_list_elem_find_first(&NM_SECRET_AGENT_GET_PRIVATE(agent)->permissions, p, nm_streq(p, permission)); } /*****************************************************************************/ static void _dbus_call_cb(GObject *source, GAsyncResult *result, gpointer user_data) { NMSecretAgentCallId *call_id; gs_unref_variant GVariant *ret = NULL; gs_unref_variant GVariant *secrets = NULL; gs_free_error GError *error = NULL; ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); if (!ret && nm_utils_error_is_cancelled(error)) return; call_id = user_data; if (!ret) g_dbus_error_strip_remote_error(error); else { if (nm_streq(call_id->method_name, METHOD_GET_SECRETS)) { g_variant_get(ret, "(@a{sa{sv}})", &secrets); } } _call_id_invoke_callback(call_id, secrets, error, FALSE, TRUE); } /*****************************************************************************/ NMSecretAgentCallId * nm_secret_agent_get_secrets(NMSecretAgent * self, const char * path, NMConnection * connection, const char * setting_name, const char ** hints, NMSecretAgentGetSecretsFlags flags, NMSecretAgentCallback callback, gpointer callback_data) { NMSecretAgentPrivate *priv; GVariant * dict; NMSecretAgentCallId * call_id; g_return_val_if_fail(NM_IS_SECRET_AGENT(self), NULL); g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL); g_return_val_if_fail(path && *path, NULL); g_return_val_if_fail(setting_name, NULL); g_return_val_if_fail(callback, NULL); priv = NM_SECRET_AGENT_GET_PRIVATE(self); dict = nm_connection_to_dbus(connection, NM_CONNECTION_SERIALIZE_ALL); /* Mask off the private flags if present */ flags &= ~(NM_SECRET_AGENT_GET_SECRETS_FLAG_ONLY_SYSTEM | NM_SECRET_AGENT_GET_SECRETS_FLAG_NO_ERRORS); call_id = _call_id_new(self, METHOD_GET_SECRETS, path, setting_name, callback, callback_data); g_dbus_connection_call(priv->dbus_connection, priv->dbus_owner, NM_DBUS_PATH_SECRET_AGENT, NM_DBUS_INTERFACE_SECRET_AGENT, call_id->method_name, g_variant_new("(@a{sa{sv}}os^asu)", dict, path, setting_name, hints ?: NM_PTRARRAY_EMPTY(const char *), (guint32) flags), G_VARIANT_TYPE("(a{sa{sv}})"), G_DBUS_CALL_FLAGS_NO_AUTO_START, 120000, call_id->cancellable, _dbus_call_cb, call_id); return call_id; } /*****************************************************************************/ static void _call_cancel_cb(GObject *source, GAsyncResult *result, gpointer user_data) { NMSecretAgentCallId *call_id = user_data; gs_free_error GError *error = NULL; gs_unref_variant GVariant *ret = NULL; ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); if (ret) _LOG2T(call_id, "success cancelling GetSecrets"); else if (g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)) _LOG2T(call_id, "cancelling GetSecrets no longer works as service disconnected"); else { _LOG2T(call_id, "failed to cancel GetSecrets: %s", error->message); } _call_id_free(call_id); } /** * nm_secret_agent_cancel_call: * @self: the #NMSecretAgent instance for the @call_id. * Maybe be %NULL if @call_id is %NULL. * @call_id: (allow-none): the call id to cancel. May be %NULL for convenience, * in which case it does nothing. * * It is an error to pass an invalid @call_id or a @call_id for an operation * that already completed. It is also an error to cancel the call from inside * the callback, at that point the call is already completed. * In case of nm_secret_agent_cancel_call() this will synchronously invoke the * callback before nm_secret_agent_cancel_call() returns. */ void nm_secret_agent_cancel_call(NMSecretAgent *self, NMSecretAgentCallId *call_id) { NMSecretAgentPrivate *priv; gboolean free_call_id = TRUE; if (!call_id) { /* for convenience, %NULL is accepted fine. */ nm_assert(!self || NM_IS_SECRET_AGENT(self)); return; } g_return_if_fail(NM_IS_SECRET_AGENT(call_id->self)); g_return_if_fail(!c_list_is_empty(&call_id->lst)); /* Theoretically, call-id already has a self pointer. But nm_secret_agent_cancel_call() has only * one user: NMAgentManager. And that one has the self-pointer at hand, so the only purpose of * the @self argument is to assert that we are cancelling the expected call. * * We could drop the @self argument, but that just remove an additional assert-check from * our code, without making a simplification for the only caller of this function. */ g_return_if_fail(self == call_id->self); priv = NM_SECRET_AGENT_GET_PRIVATE(self); nm_assert(c_list_contains(&priv->requests, &call_id->lst)); nm_clear_g_cancellable(&call_id->cancellable); if (nm_streq(call_id->method_name, METHOD_GET_SECRETS)) { g_dbus_connection_call( priv->dbus_connection, priv->dbus_owner, NM_DBUS_PATH_SECRET_AGENT, NM_DBUS_INTERFACE_SECRET_AGENT, METHOD_CANCEL_GET_SECRETS, g_variant_new("(os)", call_id->path, call_id->setting_name), G_VARIANT_TYPE("()"), G_DBUS_CALL_FLAGS_NO_AUTO_START, NM_SHUTDOWN_TIMEOUT_MS, NULL, /* this operation is not cancellable. We rely on the timeout. */ _call_cancel_cb, call_id); /* we keep call-id alive, but it will be unlinked from priv->requests. * _call_cancel_cb() will finally free it later. */ free_call_id = FALSE; } _call_id_invoke_callback(call_id, NULL, NULL, TRUE, free_call_id); } /*****************************************************************************/ NMSecretAgentCallId * nm_secret_agent_save_secrets(NMSecretAgent * self, const char * path, NMConnection * connection, NMSecretAgentCallback callback, gpointer callback_data) { NMSecretAgentPrivate *priv; GVariant * dict; NMSecretAgentCallId * call_id; g_return_val_if_fail(NM_IS_SECRET_AGENT(self), NULL); g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL); g_return_val_if_fail(path && *path, NULL); priv = NM_SECRET_AGENT_GET_PRIVATE(self); /* Caller should have ensured that only agent-owned secrets exist in 'connection' */ dict = nm_connection_to_dbus(connection, NM_CONNECTION_SERIALIZE_ALL); call_id = _call_id_new(self, METHOD_SAVE_SECRETS, path, NULL, callback, callback_data); g_dbus_connection_call(priv->dbus_connection, priv->dbus_owner, NM_DBUS_PATH_SECRET_AGENT, NM_DBUS_INTERFACE_SECRET_AGENT, call_id->method_name, g_variant_new("(@a{sa{sv}}o)", dict, path), G_VARIANT_TYPE("()"), G_DBUS_CALL_FLAGS_NO_AUTO_START, 60000, call_id->cancellable, _dbus_call_cb, call_id); return call_id; } /*****************************************************************************/ NMSecretAgentCallId * nm_secret_agent_delete_secrets(NMSecretAgent * self, const char * path, NMConnection * connection, NMSecretAgentCallback callback, gpointer callback_data) { NMSecretAgentPrivate *priv; GVariant * dict; NMSecretAgentCallId * call_id; g_return_val_if_fail(NM_IS_SECRET_AGENT(self), NULL); g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL); g_return_val_if_fail(path && *path, NULL); priv = NM_SECRET_AGENT_GET_PRIVATE(self); /* No secrets sent; agents must be smart enough to track secrets using the UUID or something */ dict = nm_connection_to_dbus(connection, NM_CONNECTION_SERIALIZE_WITH_NON_SECRET); call_id = _call_id_new(self, METHOD_DELETE_SECRETS, path, NULL, callback, callback_data); g_dbus_connection_call(priv->dbus_connection, priv->dbus_owner, NM_DBUS_PATH_SECRET_AGENT, NM_DBUS_INTERFACE_SECRET_AGENT, call_id->method_name, g_variant_new("(@a{sa{sv}}o)", dict, path), G_VARIANT_TYPE("()"), G_DBUS_CALL_FLAGS_NO_AUTO_START, 60000, call_id->cancellable, _dbus_call_cb, call_id); return call_id; } /*****************************************************************************/ static void name_owner_changed(NMSecretAgent *self, const char *owner) { NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE(self); nm_assert(!priv->name_owner_cancellable); owner = nm_str_not_empty(owner); _LOGT("name-owner-changed: %s%s%s", NM_PRINT_FMT_QUOTED(owner, "has ", owner, "", "disconnected")); if (owner) return; nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->name_owner_changed_id); g_signal_emit(self, signals[DISCONNECTED], 0); } static void name_owner_changed_cb(GDBusConnection *dbus_connection, const char * sender_name, const char * object_path, const char * interface_name, const char * signal_name, GVariant * parameters, gpointer user_data) { NMSecretAgent *self = NM_SECRET_AGENT(user_data); const char * new_owner = NULL; if (g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sss)"))) { g_variant_get(parameters, "(&s&s&s)", NULL, NULL, &new_owner); } nm_clear_g_cancellable(&NM_SECRET_AGENT_GET_PRIVATE(self)->name_owner_cancellable); name_owner_changed(self, new_owner); } static void get_name_owner_cb(const char *name_owner, GError *error, gpointer user_data) { NMSecretAgent *self; if (!name_owner && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) return; self = user_data; g_clear_object(&NM_SECRET_AGENT_GET_PRIVATE(self)->name_owner_cancellable); name_owner_changed(self, name_owner); } /*****************************************************************************/ NMSecretAgent * nm_secret_agent_new(GDBusMethodInvocation * context, NMAuthSubject * subject, const char * identifier, NMSecretAgentCapabilities capabilities) { NMSecretAgent * self; NMSecretAgentPrivate *priv; const char * dbus_owner; gs_free char * owner_username = NULL; char * description = NULL; char buf_subject[64]; char buf_caps[150]; gulong uid; GDBusConnection * dbus_connection; g_return_val_if_fail(context != NULL, NULL); g_return_val_if_fail(NM_IS_AUTH_SUBJECT(subject), NULL); g_return_val_if_fail(nm_auth_subject_get_subject_type(subject) == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, NULL); g_return_val_if_fail(identifier != NULL, NULL); dbus_connection = g_dbus_method_invocation_get_connection(context); g_return_val_if_fail(G_IS_DBUS_CONNECTION(dbus_connection), NULL); uid = nm_auth_subject_get_unix_process_uid(subject); owner_username = nm_utils_uid_to_name(uid); dbus_owner = nm_auth_subject_get_unix_process_dbus_sender(subject); self = g_object_new(NM_TYPE_SECRET_AGENT, NULL); priv = NM_SECRET_AGENT_GET_PRIVATE(self); priv->dbus_connection = g_object_ref(dbus_connection); _LOGT("constructed: %s, owner=%s%s%s (%s), unique-name=%s%s%s, capabilities=%s", (description = _create_description(dbus_owner, identifier, uid)), NM_PRINT_FMT_QUOTE_STRING(owner_username), nm_auth_subject_to_string(subject, buf_subject, sizeof(buf_subject)), NM_PRINT_FMT_QUOTE_STRING(g_dbus_connection_get_unique_name(priv->dbus_connection)), _capabilities_to_string(capabilities, buf_caps, sizeof(buf_caps))); priv->identifier = g_strdup(identifier); priv->owner_username = g_steal_pointer(&owner_username); priv->dbus_owner = g_strdup(dbus_owner); priv->description = description; priv->capabilities = capabilities; priv->subject = g_object_ref(subject); priv->name_owner_changed_id = nm_dbus_connection_signal_subscribe_name_owner_changed(priv->dbus_connection, priv->dbus_owner, name_owner_changed_cb, self, NULL); priv->name_owner_cancellable = g_cancellable_new(); nm_dbus_connection_call_get_name_owner(priv->dbus_connection, priv->dbus_owner, -1, priv->name_owner_cancellable, get_name_owner_cb, self); return self; } static void nm_secret_agent_init(NMSecretAgent *self) { NMSecretAgentPrivate *priv; priv = G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_SECRET_AGENT, NMSecretAgentPrivate); self->_priv = priv; c_list_init(&self->agent_lst); c_list_init(&priv->permissions); c_list_init(&priv->requests); } static void dispose(GObject *object) { NMSecretAgent * self = NM_SECRET_AGENT(object); NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE(self); nm_assert(c_list_is_empty(&self->agent_lst)); nm_assert(!self->auth_chain); nm_assert(c_list_is_empty(&priv->requests)); nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->name_owner_changed_id); nm_clear_g_cancellable(&priv->name_owner_cancellable); G_OBJECT_CLASS(nm_secret_agent_parent_class)->dispose(object); } static void finalize(GObject *object) { NMSecretAgent * self = NM_SECRET_AGENT(object); NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE(self); g_free(priv->description); g_free(priv->identifier); g_free(priv->owner_username); g_free(priv->dbus_owner); nm_c_list_elem_free_all(&priv->permissions, g_free); g_clear_object(&priv->subject); g_clear_object(&priv->dbus_connection); G_OBJECT_CLASS(nm_secret_agent_parent_class)->finalize(object); _LOGT("finalized"); } static void nm_secret_agent_class_init(NMSecretAgentClass *config_class) { GObjectClass *object_class = G_OBJECT_CLASS(config_class); g_type_class_add_private(object_class, sizeof(NMSecretAgentPrivate)); object_class->dispose = dispose; object_class->finalize = finalize; signals[DISCONNECTED] = g_signal_new(NM_SECRET_AGENT_DISCONNECTED, G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); }