/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2014 Red Hat, Inc. */ /** * SECTION:nm-polkit-listener * @short_description: A polkit agent listener * * #NMPolkitListener is the polkit agent listener used by nmcli and nmtui. * http://www.freedesktop.org/software/polkit/docs/latest/index.html * * For an example polkit agent you can look at polkit source tree: * http://cgit.freedesktop.org/polkit/tree/src/polkitagent/polkitagenttextlistener.c * http://cgit.freedesktop.org/polkit/tree/src/programs/pkttyagent.c * or LXDE polkit agent: * http://git.lxde.org/gitweb/?p=debian/lxpolkit.git;a=blob;f=src/lxpolkit-listener.c * https://github.com/lxde/lxqt-policykit/tree/master/src */ #include "libnm-client-aux-extern/nm-default-client.h" #include "nm-polkit-listener.h" #include #include #include #include "libnm-glib-aux/nm-dbus-aux.h" #include "libnm-glib-aux/nm-str-buf.h" #include "libnm-glib-aux/nm-secret-utils.h" #include "libnm-glib-aux/nm-io-utils.h" #include "libnm-core-aux-intern/nm-auth-subject.h" #include "c-list/src/c-list.h" #define LOGIND_BUS_NAME "org.freedesktop.login1" #define POLKIT_BUS_NAME "org.freedesktop.PolicyKit1" #define POLKIT_AUTHORITY_OBJ_PATH "/org/freedesktop/PolicyKit1/Authority" #define POLKIT_AUTHORITY_IFACE_NAME "org.freedesktop.PolicyKit1.Authority" #define POLKIT_AGENT_OBJ_PATH "/org/freedesktop/PolicyKit1/AuthenticationAgent" #define POLKIT_AGENT_DBUS_INTERFACE "org.freedesktop.PolicyKit1.AuthenticationAgent" #define LOGIND_OBJ_PATH "/org/freedesktop/login1" #define LOGIND_MANAGER_INTERFACE "org.freedesktop.login1.Manager" #define NM_POLKIT_LISTENER_DBUS_CONNECTION "dbus-connection" #define NM_POLKIT_LISTENER_SESSION_AGENT "session-agent" #define POLKIT_DBUS_ERROR_FAILED "org.freedesktop.PolicyKit1.Error.Failed" /*****************************************************************************/ enum { REGISTERED, REQUEST_SYNC, ERROR, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = {0}; struct _NMPolkitListener { GObject parent; GDBusConnection *dbus_connection; char *name_owner; GCancellable *cancellable; GMainContext *main_context; CList request_lst_head; guint pk_auth_agent_reg_id; guint name_owner_changed_id; bool session_agent : 1; }; struct _NMPolkitListenerClass { GObjectClass parent; }; G_DEFINE_TYPE(NMPolkitListener, nm_polkit_listener, G_TYPE_OBJECT); /*****************************************************************************/ typedef struct { CList request_lst; NMPolkitListener *listener; GSource *child_stdout_watch_source; GSource *child_stdin_watch_source; GDBusMethodInvocation *dbus_invocation; char *action_id; char *message; char *username; char *cookie; NMStrBuf in_buffer; NMStrBuf out_buffer; gsize out_buffer_offset; int child_stdout; int child_stdin; bool request_any_response : 1; bool request_is_completed : 1; } AuthRequest; static const GDBusInterfaceInfo interface_info = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( POLKIT_AGENT_DBUS_INTERFACE, .methods = NM_DEFINE_GDBUS_METHOD_INFOS( NM_DEFINE_GDBUS_METHOD_INFO("BeginAuthentication", .in_args = NM_DEFINE_GDBUS_ARG_INFOS( NM_DEFINE_GDBUS_ARG_INFO("action_id", "s"), NM_DEFINE_GDBUS_ARG_INFO("message", "s"), NM_DEFINE_GDBUS_ARG_INFO("icon_name", "s"), NM_DEFINE_GDBUS_ARG_INFO("details", "a{ss}"), NM_DEFINE_GDBUS_ARG_INFO("cookie", "s"), NM_DEFINE_GDBUS_ARG_INFO("identities", "a(sa{sv})"), ), ), NM_DEFINE_GDBUS_METHOD_INFO("CancelAuthentication", .in_args = NM_DEFINE_GDBUS_ARG_INFOS( NM_DEFINE_GDBUS_ARG_INFO("cookie", "s"), ), ), ), ); static void auth_request_complete(AuthRequest *request, gboolean success) { c_list_unlink(&request->request_lst); if (success) g_dbus_method_invocation_return_value(request->dbus_invocation, NULL); else { g_dbus_method_invocation_return_dbus_error(request->dbus_invocation, POLKIT_DBUS_ERROR_FAILED, ""); } nm_clear_g_free(&request->action_id); nm_clear_g_free(&request->message); nm_clear_g_free(&request->username); nm_clear_g_free(&request->cookie); nm_clear_g_source_inst(&request->child_stdout_watch_source); nm_clear_g_source_inst(&request->child_stdin_watch_source); nm_str_buf_destroy(&request->in_buffer); nm_str_buf_destroy(&request->out_buffer); if (request->child_stdout != -1) { nm_close(request->child_stdout); request->child_stdout = -1; } if (request->child_stdin != -1) { nm_close(request->child_stdin); request->child_stdin = -1; } nm_g_slice_free(request); } static gboolean uid_to_name(uid_t uid, gboolean *out_cached, char **out_name) { if (!*out_cached) { *out_cached = TRUE; *out_name = nm_utils_uid_to_name(uid); } return !!(*out_name); } static char * choose_identity(GVariant *identities) { GVariantIter identity_iter; GVariant *details_tmp; const char *kind; gs_free char *username_first = NULL; gs_free char *username_root = NULL; const char *user; /* Choose identity. First try current user, then root, and else * take the first one we find. */ user = getenv("USER"); g_variant_iter_init(&identity_iter, identities); while (g_variant_iter_next(&identity_iter, "(&s@a{sv})", &kind, &details_tmp)) { gs_unref_variant GVariant *details = g_steal_pointer(&details_tmp); if (nm_streq(kind, "unix-user")) { gs_unref_variant GVariant *v = NULL; v = g_variant_lookup_value(details, "uid", G_VARIANT_TYPE_UINT32); if (v) { guint32 uid = g_variant_get_uint32(v); gs_free char *u = NULL; gboolean cached = FALSE; if (user) { if (!uid_to_name(uid, &cached, &u)) continue; if (nm_streq(u, user)) return g_steal_pointer(&u); } if (!username_root && uid == 0) { if (!uid_to_name(uid, &cached, &u)) continue; username_root = g_strdup(u); if (!user) break; } if (!username_root && !username_first) { if (!uid_to_name(uid, &cached, &u)) continue; username_first = g_strdup(u); } } } } if (username_root) return g_steal_pointer(&username_root); if (username_first) return g_steal_pointer(&username_first); return NULL; } static void agent_register_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) { NMPolkitListener *listener = NM_POLKIT_LISTENER(user_data); GDBusConnection *dbus_connection = G_DBUS_CONNECTION(source_object); gs_free_error GError *error = NULL; gs_unref_variant GVariant *ret = NULL; ret = g_dbus_connection_call_finish(dbus_connection, res, &error); if (nm_utils_error_is_cancelled(error)) { return; } if (ret) { g_signal_emit(listener, signals[REGISTERED], 0); } else { g_signal_emit(listener, signals[ERROR], 0, error->message); } } static void agent_register(NMPolkitListener *self, const char *session_id) { const char *locale = NULL; gs_unref_object NMAuthSubject *subject = NULL; GVariant *subject_variant = NULL; locale = g_getenv("LANG"); if (locale == NULL) { locale = "en_US.UTF-8"; } if (self->session_agent) { subject = nm_auth_subject_new_unix_session(session_id); } else { subject = nm_auth_subject_new_unix_process_self(); } subject_variant = nm_auth_subject_unix_to_polkit_gvariant(subject); g_dbus_connection_call( self->dbus_connection, self->name_owner, POLKIT_AUTHORITY_OBJ_PATH, POLKIT_AUTHORITY_IFACE_NAME, "RegisterAuthenticationAgent", g_variant_new("(@(sa{sv})ss)", subject_variant, locale, POLKIT_AGENT_OBJ_PATH), NULL, G_DBUS_CALL_FLAGS_NONE, -1, self->cancellable, agent_register_cb, self); } static void agent_unregister(NMPolkitListener *self) { gs_unref_object NMAuthSubject *subject = NULL; GVariant *subject_variant = NULL; subject = nm_auth_subject_new_unix_process_self(); subject_variant = nm_auth_subject_unix_to_polkit_gvariant(subject); g_dbus_connection_call(self->dbus_connection, self->name_owner, POLKIT_AUTHORITY_OBJ_PATH, POLKIT_AUTHORITY_IFACE_NAME, "UnregisterAuthenticationAgent", g_variant_new("(@(sa{sv})s)", subject_variant, POLKIT_AGENT_OBJ_PATH), NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, self); } static void retrieve_session_id_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) { NMPolkitListener *listener = NM_POLKIT_LISTENER(user_data); char *session_id; guint32 session_uid; nm_auto_free_variant_iter GVariantIter *iter = NULL; gs_unref_variant GVariant *ret = NULL; gs_free_error GError *error = NULL; gs_free char *err_str = NULL; uid_t uid = getuid(); ret = g_dbus_connection_call_finish(listener->dbus_connection, res, &error); if (nm_utils_error_is_cancelled(error)) { return; } if (ret) { g_variant_get_child(ret, 0, "a(susso)", &iter); while ( g_variant_iter_next(iter, "(&su@s@s@o)", &session_id, &session_uid, NULL, NULL, NULL)) { if (session_uid == uid) { agent_register(listener, session_id); return; } } err_str = g_strdup_printf(_("Could not find any session id for uid %d"), uid); } else { err_str = g_strdup_printf(_("Could not retrieve session id: %s"), error->message); } g_signal_emit(listener, signals[ERROR], 0, err_str); } static void retrieve_session_id(NMPolkitListener *self) { g_dbus_connection_call(self->dbus_connection, LOGIND_BUS_NAME, LOGIND_OBJ_PATH, LOGIND_MANAGER_INTERFACE, "ListSessions", NULL, G_VARIANT_TYPE("(a(susso))"), G_DBUS_CALL_FLAGS_NONE, -1, self->cancellable, retrieve_session_id_cb, self); } static gboolean io_watch_can_write(int fd, GIOCondition condition, gpointer user_data) { AuthRequest *request = user_data; gssize n_written; if (NM_FLAGS_ANY(condition, (G_IO_HUP | G_IO_ERR))) goto done; n_written = write(request->child_stdin, &((nm_str_buf_get_str_unsafe(&request->out_buffer))[request->out_buffer_offset]), request->out_buffer.len - request->out_buffer_offset); if (n_written < 0 && errno != EAGAIN) goto done; if (n_written > 0) { if ((gsize) n_written >= (request->out_buffer.len - request->out_buffer_offset)) { nm_assert((gsize) n_written == (request->out_buffer.len - request->out_buffer_offset)); goto done; } request->out_buffer_offset += (gsize) n_written; } return G_SOURCE_CONTINUE; done: nm_str_buf_set_size(&request->out_buffer, 0, TRUE, FALSE); request->out_buffer_offset = 0; nm_clear_g_source_inst(&request->child_stdin_watch_source); if (request->request_is_completed) auth_request_complete(request, TRUE); return G_SOURCE_CONTINUE; } static void queue_string_to_helper(AuthRequest *request, const char *response) { g_return_if_fail(response); if (!nm_str_buf_is_initalized(&request->out_buffer)) request->out_buffer = NM_STR_BUF_INIT(strlen(response) + 2u, TRUE); nm_str_buf_append(&request->out_buffer, response); nm_str_buf_ensure_trailing_c(&request->out_buffer, '\n'); if (!request->child_stdin_watch_source) { request->child_stdin_watch_source = nm_g_unix_fd_source_new(request->child_stdin, G_IO_OUT | G_IO_ERR | G_IO_HUP, G_PRIORITY_DEFAULT, io_watch_can_write, request, NULL); g_source_attach(request->child_stdin_watch_source, request->listener->main_context); } } static gboolean io_watch_have_data(int fd, GIOCondition condition, gpointer user_data) { AuthRequest *request = user_data; gboolean auth_result; gssize n_read; if (NM_FLAGS_ANY(condition, G_IO_HUP | G_IO_ERR)) n_read = -EIO; else n_read = nm_utils_fd_read(fd, &request->in_buffer); if (n_read <= 0) { if (n_read == -EAGAIN) { /* wait longer. */ return G_SOURCE_CONTINUE; } /* Either an error or EOF happened. The data we parsed so far was not relevant. * Regardless of what we still have unprocessed in the receive buffers, we are done. * * We would expect that the other side completed with SUCCESS or FAILURE. Apparently * it didn't. If we had any good request, we assume success. */ auth_result = request->request_any_response; goto out; } while (TRUE) { char *line_terminator; const char *line; line = nm_str_buf_get_str(&request->in_buffer); line_terminator = (char *) strchr(line, '\n'); if (!line_terminator) { /* We don't have a complete line. Wait longer. */ return G_SOURCE_CONTINUE; } line_terminator[0] = '\0'; if (NM_STR_HAS_PREFIX(line, "PAM_PROMPT_ECHO_OFF ") || NM_STR_HAS_PREFIX(line, "PAM_PROMPT_ECHO_ON ")) { nm_auto_free_secret char *response = NULL; /* FIXME(cli-async): emit signal and wait for response (blocking) */ g_signal_emit(request->listener, signals[REQUEST_SYNC], 0, request->action_id, request->message, request->username, &response); if (response) { queue_string_to_helper(request, response); request->request_any_response = TRUE; goto erase_line; } auth_result = FALSE; } else if (nm_streq(line, "SUCCESS")) auth_result = TRUE; else if (nm_streq(line, "FAILURE")) auth_result = FALSE; else if (NM_STR_HAS_PREFIX(line, "PAM_ERROR_MSG ") || NM_STR_HAS_PREFIX(line, "PAM_TEXT_INFO ")) { /* ignore. */ goto erase_line; } else { /* unknown command. Fail. */ auth_result = FALSE; } break; erase_line: nm_str_buf_erase(&request->in_buffer, 0, line_terminator - line + 1u, TRUE); } out: request->request_is_completed = TRUE; nm_clear_g_source_inst(&request->child_stdout_watch_source); if (auth_result && request->child_stdin_watch_source) { /* we need to wait for the buffer to send the response. */ } else auth_request_complete(request, auth_result); return G_SOURCE_CONTINUE; } static void begin_authentication(AuthRequest *request) { const char *helper_argv[] = { POLKIT_AGENT_HELPER_1_PATH, request->username, NULL, }; if (!request->username) { auth_request_complete(request, FALSE); return; } if (!g_spawn_async_with_pipes(NULL, (char **) helper_argv, NULL, G_SPAWN_CLOEXEC_PIPES | G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL, NULL, &request->child_stdin, &request->child_stdout, NULL, NULL)) { /* not findind the PolicyKit setuid helper is a critical error */ request->child_stdin = -1; request->child_stdout = -1; g_signal_emit(request->listener, signals[ERROR], 0, "The PolicyKit setuid helper 'polkit-agent-helper-1' has not been found"); auth_request_complete(request, FALSE); return; } nm_io_fcntl_setfl_update_nonblock(request->child_stdin); nm_io_fcntl_setfl_update_nonblock(request->child_stdout); request->child_stdout_watch_source = nm_g_unix_fd_source_new(request->child_stdout, G_IO_IN | G_IO_ERR | G_IO_HUP, G_PRIORITY_DEFAULT, io_watch_have_data, request, NULL); g_source_attach(request->child_stdout_watch_source, request->listener->main_context); /* Write the cookie on stdin so it can't be seen by other processes */ queue_string_to_helper(request, request->cookie); return; } static AuthRequest * get_request(NMPolkitListener *listener, const char *cookie) { AuthRequest *request; c_list_for_each_entry (request, &listener->request_lst_head, request_lst) { if (nm_streq0(cookie, request->cookie)) { return request; } } return NULL; } static AuthRequest * create_request(NMPolkitListener *listener, GDBusMethodInvocation *invocation, const char *action_id, const char *message, char *username_take, const char *cookie) { AuthRequest *request; request = g_slice_new(AuthRequest); *request = (AuthRequest) { .listener = listener, .dbus_invocation = invocation, .action_id = g_strdup(action_id), .message = g_strdup(message), .username = g_steal_pointer(&username_take), .cookie = g_strdup(cookie), .request_any_response = FALSE, .request_is_completed = FALSE, .in_buffer = NM_STR_BUF_INIT(NM_UTILS_GET_NEXT_REALLOC_SIZE_1000, FALSE), }; c_list_link_tail(&listener->request_lst_head, &request->request_lst); return request; } static void dbus_method_call_cb(GDBusConnection *connection, const char *sender, const char *object_path, const char *interface_name, const char *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { NMPolkitListener *listener = NM_POLKIT_LISTENER(user_data); const char *action_id; const char *message; const char *cookie; AuthRequest *request; gs_unref_variant GVariant *identities_gvariant = NULL; if (nm_streq(method_name, "BeginAuthentication")) { g_variant_get(parameters, "(&s&s&s@a{ss}&s@a(sa{sv}))", &action_id, &message, NULL, NULL, &cookie, &identities_gvariant); request = create_request(listener, invocation, action_id, message, choose_identity(identities_gvariant), cookie); begin_authentication(request); return; } if (nm_streq(method_name, "CancelAuthentication")) { g_variant_get(parameters, "&s", &cookie); request = get_request(listener, cookie); if (!request) { gs_free char *msg = NULL; msg = g_strdup_printf("No pending authentication request for cookie '%s'", cookie); g_dbus_method_invocation_return_dbus_error(invocation, POLKIT_DBUS_ERROR_FAILED, msg); return; } /* Complete a cancelled request with success. */ auth_request_complete(request, TRUE); return; } g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD, "Unknown method %s", method_name); } static gboolean export_dbus_iface(NMPolkitListener *self, GError **error) { GDBusInterfaceVTable interface_vtable = { .method_call = dbus_method_call_cb, .set_property = NULL, .get_property = NULL, }; g_return_val_if_fail(NM_IS_POLKIT_LISTENER(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* Agent listener iface has been exported already */ if (self->pk_auth_agent_reg_id) { return TRUE; } self->pk_auth_agent_reg_id = g_dbus_connection_register_object(self->dbus_connection, POLKIT_AGENT_OBJ_PATH, (GDBusInterfaceInfo *) &interface_info, &interface_vtable, self, NULL, error); if (!self->pk_auth_agent_reg_id) { g_signal_emit(self, signals[ERROR], 0, "Could not register as a PolicyKit Authentication Agent"); } return self->pk_auth_agent_reg_id; } static void name_owner_changed(NMPolkitListener *self, const char *name_owner) { gs_free_error GError *error = NULL; name_owner = nm_str_not_empty(name_owner); if (nm_streq0(self->name_owner, name_owner)) { return; } g_free(self->name_owner); self->name_owner = g_strdup(name_owner); if (!self->name_owner) { return; } if (export_dbus_iface(self, &error)) { if (self->session_agent) { retrieve_session_id(self); } else { agent_register(self, NULL); } } else { g_signal_emit(self, signals[ERROR], 0, "Could not export the PolicyKit Authentication Agent DBus interface"); } } 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) { NMPolkitListener *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); } static void get_name_owner_cb(const char *name_owner, GError *error, gpointer user_data) { if (!name_owner && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { return; } name_owner_changed(user_data, name_owner); } /*****************************************************************************/ NM_GOBJECT_PROPERTIES_DEFINE(NMPolkitListener, PROP_DBUS_CONNECTION, PROP_SESSION_AGENT, ); static void set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { NMPolkitListener *self = NM_POLKIT_LISTENER(object); switch (prop_id) { case PROP_DBUS_CONNECTION: self->dbus_connection = g_value_dup_object(value); break; case PROP_SESSION_AGENT: self->session_agent = g_value_get_boolean(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void nm_polkit_listener_init(NMPolkitListener *self) { c_list_init(&self->request_lst_head); self->main_context = g_main_context_ref_thread_default(); } static void constructed(GObject *object) { NMPolkitListener *self = NM_POLKIT_LISTENER(object); self->cancellable = g_cancellable_new(); self->name_owner_changed_id = nm_dbus_connection_signal_subscribe_name_owner_changed(self->dbus_connection, POLKIT_BUS_NAME, name_owner_changed_cb, self, NULL); nm_dbus_connection_call_get_name_owner(self->dbus_connection, POLKIT_BUS_NAME, -1, self->cancellable, get_name_owner_cb, self); G_OBJECT_CLASS(nm_polkit_listener_parent_class)->constructed(object); } /** * nm_polkit_listener_new: * @dbus_connection: a open DBus connection * @session_agent: TRUE if a session agent is wanted, FALSE for a process agent * * Creates a new #NMPolkitListener and registers it as a polkit agent. * * Returns: a new #NMPolkitListener */ NMPolkitListener * nm_polkit_listener_new(GDBusConnection *dbus_connection, gboolean session_agent) { return g_object_new(NM_TYPE_POLKIT_LISTENER, NM_POLKIT_LISTENER_DBUS_CONNECTION, dbus_connection, NM_POLKIT_LISTENER_SESSION_AGENT, session_agent, NULL); } static void dispose(GObject *object) { NMPolkitListener *self = NM_POLKIT_LISTENER(object); AuthRequest *request; nm_clear_g_cancellable(&self->cancellable); while ((request = c_list_first_entry(&self->request_lst_head, AuthRequest, request_lst))) auth_request_complete(request, FALSE); if (self->dbus_connection) { nm_clear_g_dbus_connection_signal(self->dbus_connection, &self->name_owner_changed_id); g_dbus_connection_unregister_object(self->dbus_connection, self->pk_auth_agent_reg_id); agent_unregister(self); nm_clear_g_free(&self->name_owner); g_clear_object(&self->dbus_connection); } nm_clear_pointer(&self->main_context, g_main_context_unref); G_OBJECT_CLASS(nm_polkit_listener_parent_class)->dispose(object); } static void nm_polkit_listener_class_init(NMPolkitListenerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->set_property = set_property; object_class->constructed = constructed; object_class->dispose = dispose; obj_properties[PROP_DBUS_CONNECTION] = g_param_spec_object(NM_POLKIT_LISTENER_DBUS_CONNECTION, "", "", G_TYPE_DBUS_CONNECTION, G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_SESSION_AGENT] = g_param_spec_boolean(NM_POLKIT_LISTENER_SESSION_AGENT, "", "", FALSE, G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); signals[REQUEST_SYNC] = g_signal_new(NM_POLKIT_LISTENER_SIGNAL_REQUEST_SYNC, NM_TYPE_POLKIT_LISTENER, G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_STRING, 3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); signals[REGISTERED] = g_signal_new(NM_POLKIT_LISTENER_SIGNAL_REGISTERED, NM_TYPE_POLKIT_LISTENER, G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); signals[ERROR] = g_signal_new(NM_POLKIT_LISTENER_SIGNAL_ERROR, NM_TYPE_POLKIT_LISTENER, G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING); }