/* -*- 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) 2010 - 2011 Red Hat, Inc. */ #include "nm-default.h" #include "nm-secret-agent.h" #include #include #include "nm-dbus-interface.h" #include "nm-bus-manager.h" #include "nm-auth-subject.h" #include "nm-simple-connection.h" #include "NetworkManagerUtils.h" #include "nm-utils/c-list.h" #include "introspection/org.freedesktop.NetworkManager.SecretAgent.h" /*****************************************************************************/ enum { DISCONNECTED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; typedef struct { char *description; NMAuthSubject *subject; char *identifier; char *owner_username; char *dbus_owner; NMSecretAgentCapabilities capabilities; GSList *permissions; NMDBusSecretAgent *proxy; NMBusManager *bus_mgr; GDBusConnection *connection; gboolean connection_is_private; gulong on_disconnected_id; CList requests; } NMSecretAgentPrivate; struct _NMSecretAgent { GObject parent; NMSecretAgentPrivate _priv; }; struct _NMSecretAgentClass { GObjectClass parent; }; G_DEFINE_TYPE (NMSecretAgent, nm_secret_agent, G_TYPE_OBJECT) #define NM_SECRET_AGENT_GET_PRIVATE(self) _NM_GET_PRIVATE (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[32]; \ \ if ((self)) \ g_snprintf (__prefix, sizeof (__prefix), "%s[%p]", ""_NMLOG_PREFIX_NAME"", (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 LOG_REQ_FMT "req[%p,%s,%s%s%s%s]" #define LOG_REQ_ARG(req) (req), (req)->dbus_command, NM_PRINT_FMT_QUOTE_STRING ((req)->path), ((req)->cancellable ? "" : " (cancelled)") /*****************************************************************************/ NM_UTILS_FLAGS2STR_DEFINE_STATIC (_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 *agent; GCancellable *cancellable; char *path; const char *dbus_command; char *setting_name; gboolean is_get_secrets; NMSecretAgentCallback callback; gpointer callback_data; }; static NMSecretAgentCallId * request_new (NMSecretAgent *self, const char *dbus_command, /* this must be a static string. */ const char *path, const char *setting_name, NMSecretAgentCallback callback, gpointer callback_data) { NMSecretAgentCallId *r; r = g_slice_new0 (NMSecretAgentCallId); r->agent = self; r->path = g_strdup (path); r->setting_name = g_strdup (setting_name); r->dbus_command = dbus_command, r->callback = callback; r->callback_data = callback_data; r->cancellable = g_cancellable_new (); c_list_link_tail (&NM_SECRET_AGENT_GET_PRIVATE (self)->requests, &r->lst); _LOGt ("request "LOG_REQ_FMT": created", LOG_REQ_ARG (r)); return r; } #define request_new(self,dbus_command,path,setting_name,callback,callback_data) request_new(self,""dbus_command"",path,setting_name,callback,callback_data) static void request_free (NMSecretAgentCallId *r) { NMSecretAgent *self = r->agent; _LOGt ("request "LOG_REQ_FMT": destroyed", LOG_REQ_ARG (r)); c_list_unlink (&r->lst); g_free (r->path); g_free (r->setting_name); if (r->cancellable) g_object_unref (r->cancellable); g_slice_free (NMSecretAgentCallId, r); } static gboolean request_check_return (NMSecretAgentCallId *r) { if (!r->cancellable) return FALSE; g_return_val_if_fail (NM_IS_SECRET_AGENT (r->agent), FALSE); nm_assert (c_list_contains (&NM_SECRET_AGENT_GET_PRIVATE (r->agent)->requests, &r->lst)); c_list_unlink_init (&r->lst); return TRUE; } /*****************************************************************************/ 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; GSList *iter; g_return_if_fail (agent != NULL); g_return_if_fail (permission != NULL); priv = NM_SECRET_AGENT_GET_PRIVATE (agent); /* Check if the permission is already in the list */ for (iter = priv->permissions; iter; iter = g_slist_next (iter)) { if (g_strcmp0 (permission, iter->data) == 0) { /* If the permission is no longer allowed, remove it from the * list. If it is now allowed, do nothing since it's already * in the list. */ if (allowed == FALSE) { g_free (iter->data); priv->permissions = g_slist_delete_link (priv->permissions, iter); } return; } } /* New permission that's allowed */ if (allowed) priv->permissions = g_slist_prepend (priv->permissions, g_strdup (permission)); } /** * 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) { NMSecretAgentPrivate *priv; GSList *iter; g_return_val_if_fail (agent != NULL, FALSE); g_return_val_if_fail (permission != NULL, FALSE); priv = NM_SECRET_AGENT_GET_PRIVATE (agent); /* Check if the permission is already in the list */ for (iter = priv->permissions; iter; iter = g_slist_next (iter)) { if (g_strcmp0 (permission, iter->data) == 0) return TRUE; } return FALSE; } /*****************************************************************************/ static void get_callback (GObject *proxy, GAsyncResult *result, gpointer user_data) { NMSecretAgentCallId *r = user_data; if (request_check_return (r)) { NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE (r->agent); gs_free_error GError *error = NULL; gs_unref_variant GVariant *secrets = NULL; nmdbus_secret_agent_call_get_secrets_finish (priv->proxy, &secrets, result, &error); if (error) g_dbus_error_strip_remote_error (error); r->callback (r->agent, r, secrets, error, r->callback_data); } request_free (r); } 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; static const char *no_hints[] = { NULL }; GVariant *dict; NMSecretAgentCallId *r; 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, NULL); priv = NM_SECRET_AGENT_GET_PRIVATE (self); g_return_val_if_fail (priv->proxy != NULL, NULL); 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; flags &= ~NM_SECRET_AGENT_GET_SECRETS_FLAG_NO_ERRORS; r = request_new (self, "GetSecrets", path, setting_name, callback, callback_data); r->is_get_secrets = TRUE; /* Increase the timeout only for this call */ g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (priv->proxy), 120000); nmdbus_secret_agent_call_get_secrets (priv->proxy, dict, path, setting_name, hints ? hints : no_hints, flags, r->cancellable, get_callback, r); g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (priv->proxy), -1); return r; } /*****************************************************************************/ static void cancel_done (GObject *proxy, GAsyncResult *result, gpointer user_data) { char *description = user_data; GError *error = NULL; if (!nmdbus_secret_agent_call_cancel_get_secrets_finish (NMDBUS_SECRET_AGENT (proxy), result, &error)) { nm_log_dbg (LOGD_AGENTS, "%s%s%s: agent failed to cancel secrets: %s", NM_PRINT_FMT_QUOTED (description, "(", description, ")", "???"), error->message); g_clear_error (&error); } g_free (description); } static void do_cancel_secrets (NMSecretAgent *self, NMSecretAgentCallId *r, gboolean disposing) { NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE (self); GCancellable *cancellable; NMSecretAgentCallback callback; gpointer callback_data; g_return_if_fail (r->agent == self); g_return_if_fail (r->cancellable); if ( r->is_get_secrets && priv->proxy) { /* for GetSecrets call, we must cancel the request. */ nmdbus_secret_agent_call_cancel_get_secrets (priv->proxy, r->path, r->setting_name, NULL, cancel_done, g_strdup (nm_secret_agent_get_description (self))); } cancellable = r->cancellable; callback = r->callback; callback_data = r->callback_data; /* During g_cancellable_cancel() the d-bus method might return synchronously. * Clear r->cancellable first, so that it doesn't actually do anything. * After that, @r might be already freed. */ r->cancellable = NULL; g_cancellable_cancel (cancellable); g_object_unref (cancellable); /* Don't free the request @r. It will be freed when the d-bus call returns. * Only clear r->cancellable to indicate that the request was cancelled. */ if (callback) { gs_free_error GError *error = NULL; nm_utils_error_set_cancelled (&error, disposing, "NMSecretAgent"); /* @r might be a dangling pointer at this point. However, that is no problem * to pass it as (opaque) call_id. */ callback (self, r, NULL, error, callback_data); } } /** * nm_secret_agent_cancel_secrets: * @self: #NMSecretAgent instance * @call_id: the call id to cancel * * It is an error to pass an invalid @call_id or a @call_id for an operation * that already completed. NMSecretAgent will always invoke the callback, * also for cancel() and dispose(). * In case of nm_secret_agent_cancel_secrets() this will synchronously invoke the * callback before nm_secret_agent_cancel_secrets() returns. */ void nm_secret_agent_cancel_secrets (NMSecretAgent *self, NMSecretAgentCallId *call_id) { NMSecretAgentCallId *r = call_id; g_return_if_fail (NM_IS_SECRET_AGENT (self)); g_return_if_fail (r); nm_assert (c_list_contains (&NM_SECRET_AGENT_GET_PRIVATE (self)->requests, &r->lst)); c_list_unlink_init (&r->lst); do_cancel_secrets (self, r, FALSE); } /*****************************************************************************/ static void agent_save_cb (GObject *proxy, GAsyncResult *result, gpointer user_data) { NMSecretAgentCallId *r = user_data; if (request_check_return (r)) { NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE (r->agent); gs_free_error GError *error = NULL; nmdbus_secret_agent_call_save_secrets_finish (priv->proxy, result, &error); if (error) g_dbus_error_strip_remote_error (error); r->callback (r->agent, r, NULL, error, r->callback_data); } request_free (r); } NMSecretAgentCallId * nm_secret_agent_save_secrets (NMSecretAgent *self, const char *path, NMConnection *connection, NMSecretAgentCallback callback, gpointer callback_data) { NMSecretAgentPrivate *priv; GVariant *dict; NMSecretAgentCallId *r; 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); r = request_new (self, "SaveSecrets", path, NULL, callback, callback_data); nmdbus_secret_agent_call_save_secrets (priv->proxy, dict, path, NULL, /* cancelling the request does *not* cancel the D-Bus call. */ agent_save_cb, r); return r; } /*****************************************************************************/ static void agent_delete_cb (GObject *proxy, GAsyncResult *result, gpointer user_data) { NMSecretAgentCallId *r = user_data; if (request_check_return (r)) { NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE (r->agent); gs_free_error GError *error = NULL; nmdbus_secret_agent_call_delete_secrets_finish (priv->proxy, result, &error); if (error) g_dbus_error_strip_remote_error (error); r->callback (r->agent, r, NULL, error, r->callback_data); } request_free (r); } NMSecretAgentCallId * nm_secret_agent_delete_secrets (NMSecretAgent *self, const char *path, NMConnection *connection, NMSecretAgentCallback callback, gpointer callback_data) { NMSecretAgentPrivate *priv; GVariant *dict; NMSecretAgentCallId *r; 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_NO_SECRETS); r = request_new (self, "DeleteSecrets", path, NULL, callback, callback_data); nmdbus_secret_agent_call_delete_secrets (priv->proxy, dict, path, NULL, /* cancelling the request does *not* cancel the D-Bus call. */ agent_delete_cb, r); return r; } /*****************************************************************************/ static void _on_disconnected_cleanup (NMSecretAgentPrivate *priv) { if (priv->on_disconnected_id) { if (priv->connection_is_private) { g_signal_handler_disconnect (priv->bus_mgr, priv->on_disconnected_id); } else { g_dbus_connection_signal_unsubscribe (priv->connection, priv->on_disconnected_id); } priv->on_disconnected_id = 0; } g_clear_object (&priv->connection); g_clear_object (&priv->proxy); g_clear_object (&priv->bus_mgr); } static void _on_disconnected_private_connection (NMBusManager *mgr, GDBusConnection *connection, NMSecretAgent *self) { NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE (self); if (priv->connection != connection) return; _LOGt ("private connection disconnected"); _on_disconnected_cleanup (priv); g_signal_emit (self, signals[DISCONNECTED], 0); } static void _on_disconnected_name_owner_changed (GDBusConnection *connection, const gchar *sender_name, const gchar *object_path, const gchar *interface_name, const gchar *signal_name, GVariant *parameters, gpointer user_data) { NMSecretAgent *self = NM_SECRET_AGENT (user_data); NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE (self); const char *old_owner = NULL, *new_owner = NULL; g_variant_get (parameters, "(&s&s&s)", NULL, &old_owner, &new_owner); _LOGt ("name-owner-changed: %s%s%s => %s%s%s", NM_PRINT_FMT_QUOTE_STRING (old_owner), NM_PRINT_FMT_QUOTE_STRING (new_owner)); if (!*new_owner) { _on_disconnected_cleanup (priv); g_signal_emit (self, signals[DISCONNECTED], 0); } } /*****************************************************************************/ NMSecretAgent * nm_secret_agent_new (GDBusMethodInvocation *context, NMAuthSubject *subject, const char *identifier, NMSecretAgentCapabilities capabilities) { NMSecretAgent *self; NMSecretAgentPrivate *priv; const char *dbus_owner; struct passwd *pw; GDBusProxy *proxy; char *owner_username = NULL; char *description = NULL; char buf_subject[64]; char buf_caps[150]; gulong uid; GDBusConnection *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_is_unix_process (subject), NULL); g_return_val_if_fail (identifier != NULL, NULL); connection = g_dbus_method_invocation_get_connection (context); g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); uid = nm_auth_subject_get_unix_process_uid (subject); pw = getpwuid (uid); if (pw && pw->pw_name && pw->pw_name[0]) owner_username = g_strdup (pw->pw_name); dbus_owner = nm_auth_subject_get_unix_process_dbus_sender (subject); self = (NMSecretAgent *) g_object_new (NM_TYPE_SECRET_AGENT, NULL); priv = NM_SECRET_AGENT_GET_PRIVATE (self); priv->bus_mgr = g_object_ref (nm_bus_manager_get ()); priv->connection = g_object_ref (connection); priv->connection_is_private = !!nm_bus_manager_connection_get_private_name (priv->bus_mgr, connection); _LOGt ("constructed: %s, owner=%s%s%s (%s), private-connection=%d, 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)), priv->connection_is_private, NM_PRINT_FMT_QUOTE_STRING (g_dbus_connection_get_unique_name (priv->connection)), _capabilities_to_string (capabilities, buf_caps, sizeof (buf_caps))); priv->identifier = g_strdup (identifier); priv->owner_username = owner_username; priv->dbus_owner = g_strdup (dbus_owner); priv->description = description; priv->capabilities = capabilities; priv->subject = g_object_ref (subject); proxy = nm_bus_manager_new_proxy (priv->bus_mgr, priv->connection, NMDBUS_TYPE_SECRET_AGENT_PROXY, priv->dbus_owner, NM_DBUS_PATH_SECRET_AGENT, NM_DBUS_INTERFACE_SECRET_AGENT); g_assert (proxy); priv->proxy = NMDBUS_SECRET_AGENT (proxy); /* we cannot subscribe to notify::g-name-owner because that doesn't work * for unique names and it doesn't work for private connections. */ if (priv->connection_is_private) { priv->on_disconnected_id = g_signal_connect (priv->bus_mgr, NM_BUS_MANAGER_PRIVATE_CONNECTION_DISCONNECTED, G_CALLBACK (_on_disconnected_private_connection), self); } else { priv->on_disconnected_id = g_dbus_connection_signal_subscribe (priv->connection, "org.freedesktop.DBus", /* name */ "org.freedesktop.DBus", /* interface */ "NameOwnerChanged", /* signal name */ "/org/freedesktop/DBus", /* path */ priv->dbus_owner, /* arg0 */ G_DBUS_SIGNAL_FLAGS_NONE, _on_disconnected_name_owner_changed, self, NULL); } return self; } static void nm_secret_agent_init (NMSecretAgent *self) { NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE (self); c_list_init (&priv->requests); } static void dispose (GObject *object) { NMSecretAgent *self = NM_SECRET_AGENT (object); NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE (self); CList *iter; again: c_list_for_each (iter, &priv->requests) { c_list_unlink_init (iter); do_cancel_secrets (self, c_list_entry (iter, NMSecretAgentCallId, lst), TRUE); goto again; } _on_disconnected_cleanup (priv); g_clear_object (&priv->subject); 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); g_slist_free_full (priv->permissions, g_free); 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); 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); }