diff options
Diffstat (limited to 'src/libnmc-base/nm-secret-agent-simple.c')
| -rw-r--r-- | src/libnmc-base/nm-secret-agent-simple.c | 1405 |
1 files changed, 1405 insertions, 0 deletions
diff --git a/src/libnmc-base/nm-secret-agent-simple.c b/src/libnmc-base/nm-secret-agent-simple.c new file mode 100644 index 00000000..a3d5062d --- /dev/null +++ b/src/libnmc-base/nm-secret-agent-simple.c @@ -0,0 +1,1405 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011 - 2015 Red Hat, Inc. + * Copyright (C) 2011 Giovanni Campagna <scampa.giovanni@gmail.com> + */ + +/** + * SECTION:nm-secret-agent-simple + * @short_description: A simple secret agent for NetworkManager + * + * #NMSecretAgentSimple is the secret agent used by nmtui-connect and nmcli. + * + * This is a stripped-down version of gnome-shell's ShellNetworkAgent, + * with bits of the corresponding JavaScript code squished down into + * it. It is intended to eventually be generic enough that it could + * replace ShellNetworkAgent. + */ + +#include "libnm-client-aux-extern/nm-default-client.h" + +#include "nm-secret-agent-simple.h" + +#include <gio/gunixoutputstream.h> +#include <gio/gunixinputstream.h> + +#include "nm-vpn-service-plugin.h" +#include "nm-vpn-helpers.h" +#include "libnm-glib-aux/nm-secret-utils.h" + +/*****************************************************************************/ + +typedef struct { + char *request_id; + + NMSecretAgentSimple *self; + + NMConnection * connection; + const char * setting_name; + char ** hints; + NMSecretAgentOldGetSecretsFunc callback; + gpointer callback_data; + GCancellable * cancellable; + NMSecretAgentGetSecretsFlags flags; +} RequestData; + +enum { + REQUEST_SECRETS, + + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = {0}; + +typedef struct { + GHashTable *requests; + + char * path; + gboolean enabled; +} NMSecretAgentSimplePrivate; + +struct _NMSecretAgentSimple { + NMSecretAgentOld parent; + NMSecretAgentSimplePrivate _priv; +}; + +struct _NMSecretAgentSimpleClass { + NMSecretAgentOldClass parent; +}; + +G_DEFINE_TYPE(NMSecretAgentSimple, nm_secret_agent_simple, NM_TYPE_SECRET_AGENT_OLD) + +#define NM_SECRET_AGENT_SIMPLE_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMSecretAgentSimple, NM_IS_SECRET_AGENT_SIMPLE, NMSecretAgentOld) + +/*****************************************************************************/ + +static void +_request_data_free(gpointer data) +{ + RequestData *request = data; + + g_free(request->request_id); + nm_clear_g_cancellable(&request->cancellable); + g_object_unref(request->connection); + g_strfreev(request->hints); + + g_slice_free(RequestData, request); +} + +static void +_request_data_complete(RequestData * request, + GVariant * secrets, + GError * error, + GHashTableIter *iter_to_remove) +{ + NMSecretAgentSimple * self = request->self; + NMSecretAgentSimplePrivate *priv = NM_SECRET_AGENT_SIMPLE_GET_PRIVATE(self); + + nm_assert((secrets != NULL) != (error != NULL)); + + request->callback(NM_SECRET_AGENT_OLD(request->self), + request->connection, + secrets, + error, + request->callback_data); + + if (iter_to_remove) + g_hash_table_iter_remove(iter_to_remove); + else + g_hash_table_remove(priv->requests, request); +} + +/*****************************************************************************/ + +/** + * NMSecretAgentSimpleSecret: + * @name: the user-visible name of the secret. Eg, "WEP Passphrase". + * @value: the value of the secret + * @password: %TRUE if this secret represents a password, %FALSE + * if it represents non-secret data. + * + * A single "secret" being requested. + */ + +typedef struct { + NMSecretAgentSimpleSecret base; + NMSetting * setting; + char * property; +} SecretReal; + +static void +_secret_real_free(NMSecretAgentSimpleSecret *secret) +{ + SecretReal *real = (SecretReal *) secret; + + g_free((char *) secret->pretty_name); + g_free((char *) secret->entry_id); + nm_free_secret(secret->value); + g_free((char *) secret->vpn_type); + g_free(real->property); + g_clear_object(&real->setting); + + g_slice_free(SecretReal, real); +} + +static NMSecretAgentSimpleSecret * +_secret_real_new_plain(NMSecretAgentSecretType secret_type, + const char * pretty_name, + NMSetting * setting, + const char * property) +{ + SecretReal * real; + gs_free char *value = NULL; + + nm_assert(property); + nm_assert(NM_IS_SETTING(setting)); + nm_assert(NM_IN_SET(secret_type, + NM_SECRET_AGENT_SECRET_TYPE_PROPERTY, + NM_SECRET_AGENT_SECRET_TYPE_SECRET)); + nm_assert(g_object_class_find_property(G_OBJECT_GET_CLASS(setting), property)); + nm_assert((secret_type == NM_SECRET_AGENT_SECRET_TYPE_SECRET) + == nm_setting_get_secret_flags(setting, property, NULL, NULL)); + + g_object_get(setting, property, &value, NULL); + + real = g_slice_new(SecretReal); + *real = (SecretReal){ + .base.secret_type = secret_type, + .base.pretty_name = g_strdup(pretty_name), + .base.entry_id = g_strdup_printf("%s.%s", nm_setting_get_name(setting), property), + .base.value = g_steal_pointer(&value), + .base.is_secret = (secret_type != NM_SECRET_AGENT_SECRET_TYPE_PROPERTY), + .setting = g_object_ref(setting), + .property = g_strdup(property), + }; + return &real->base; +} + +static NMSecretAgentSimpleSecret * +_secret_real_new_vpn_secret(const char *pretty_name, + NMSetting * setting, + const char *property, + const char *vpn_type) +{ + SecretReal *real; + const char *value; + + nm_assert(property); + nm_assert(NM_IS_SETTING_VPN(setting)); + nm_assert(vpn_type); + + value = nm_setting_vpn_get_secret(NM_SETTING_VPN(setting), property); + + real = g_slice_new(SecretReal); + *real = (SecretReal){ + .base.secret_type = NM_SECRET_AGENT_SECRET_TYPE_VPN_SECRET, + .base.pretty_name = g_strdup(pretty_name), + .base.entry_id = + g_strdup_printf("%s%s", NM_SECRET_AGENT_ENTRY_ID_PREFX_VPN_SECRETS, property), + .base.value = g_strdup(value), + .base.is_secret = TRUE, + .base.vpn_type = g_strdup(vpn_type), + .setting = g_object_ref(setting), + .property = g_strdup(property), + }; + return &real->base; +} + +static NMSecretAgentSimpleSecret * +_secret_real_new_wireguard_peer_psk(NMSettingWireGuard *s_wg, + const char * public_key, + const char * preshared_key) +{ + SecretReal *real; + + nm_assert(NM_IS_SETTING_WIREGUARD(s_wg)); + nm_assert(public_key); + + real = g_slice_new(SecretReal); + *real = (SecretReal){ + .base.secret_type = NM_SECRET_AGENT_SECRET_TYPE_WIREGUARD_PEER_PSK, + .base.pretty_name = g_strdup_printf(_("Preshared-key for %s"), public_key), + .base.entry_id = g_strdup_printf(NM_SETTING_WIREGUARD_SETTING_NAME + "." NM_SETTING_WIREGUARD_PEERS + ".%s." NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, + public_key), + .base.value = g_strdup(preshared_key), + .base.is_secret = TRUE, + .base.no_prompt_entry_id = TRUE, + .setting = NM_SETTING(g_object_ref(s_wg)), + .property = g_strdup(public_key), + }; + return &real->base; +} + +/*****************************************************************************/ + +static gboolean +add_8021x_secrets(RequestData *request, GPtrArray *secrets) +{ + NMSetting8021x * s_8021x = nm_connection_get_setting_802_1x(request->connection); + const char * eap_method; + NMSecretAgentSimpleSecret *secret; + + /* If hints are given, then always ask for what the hints require */ + if (request->hints && request->hints[0]) { + char **iter; + + for (iter = request->hints; *iter; iter++) { + secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_SECRET, + _(*iter), + NM_SETTING(s_8021x), + *iter); + g_ptr_array_add(secrets, secret); + } + + return TRUE; + } + + eap_method = nm_setting_802_1x_get_eap_method(s_8021x, 0); + if (!eap_method) + return FALSE; + + if (NM_IN_STRSET(eap_method, "md5", "leap", "ttls", "peap")) { + /* TTLS and PEAP are actually much more complicated, but this complication + * is not visible here since we only care about phase2 authentication + * (and don't even care of which one) + */ + secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_PROPERTY, + _("Username"), + NM_SETTING(s_8021x), + NM_SETTING_802_1X_IDENTITY); + g_ptr_array_add(secrets, secret); + secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_SECRET, + _("Password"), + NM_SETTING(s_8021x), + NM_SETTING_802_1X_PASSWORD); + g_ptr_array_add(secrets, secret); + return TRUE; + } + + if (nm_streq(eap_method, "tls")) { + secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_PROPERTY, + _("Identity"), + NM_SETTING(s_8021x), + NM_SETTING_802_1X_IDENTITY); + g_ptr_array_add(secrets, secret); + secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_SECRET, + _("Private key password"), + NM_SETTING(s_8021x), + NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD); + g_ptr_array_add(secrets, secret); + return TRUE; + } + + return FALSE; +} + +static gboolean +add_wireless_secrets(RequestData *request, GPtrArray *secrets) +{ + NMSettingWirelessSecurity *s_wsec = + nm_connection_get_setting_wireless_security(request->connection); + const char * key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wsec); + NMSecretAgentSimpleSecret *secret; + + if (!key_mgmt || nm_streq(key_mgmt, "owe")) + return FALSE; + + if (NM_IN_STRSET(key_mgmt, "wpa-psk", "sae")) { + secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_SECRET, + _("Password"), + NM_SETTING(s_wsec), + NM_SETTING_WIRELESS_SECURITY_PSK); + g_ptr_array_add(secrets, secret); + return TRUE; + } + + if (nm_streq(key_mgmt, "none")) { + guint32 index; + char key[100]; + + index = nm_setting_wireless_security_get_wep_tx_keyidx(s_wsec); + secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_SECRET, + _("Key"), + NM_SETTING(s_wsec), + nm_sprintf_buf(key, "wep-key%u", (guint) index)); + g_ptr_array_add(secrets, secret); + return TRUE; + } + + if (nm_streq(key_mgmt, "iee8021x")) { + if (nm_streq0(nm_setting_wireless_security_get_auth_alg(s_wsec), "leap")) { + secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_SECRET, + _("Password"), + NM_SETTING(s_wsec), + NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD); + g_ptr_array_add(secrets, secret); + return TRUE; + } else + return add_8021x_secrets(request, secrets); + } + + if (nm_streq(key_mgmt, "wpa-eap") || nm_streq(key_mgmt, "wpa-eap-suite-b-192")) + return add_8021x_secrets(request, secrets); + + return FALSE; +} + +static gboolean +add_pppoe_secrets(RequestData *request, GPtrArray *secrets) +{ + NMSettingPppoe * s_pppoe = nm_connection_get_setting_pppoe(request->connection); + NMSecretAgentSimpleSecret *secret; + + secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_PROPERTY, + _("Username"), + NM_SETTING(s_pppoe), + NM_SETTING_PPPOE_USERNAME); + g_ptr_array_add(secrets, secret); + secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_PROPERTY, + _("Service"), + NM_SETTING(s_pppoe), + NM_SETTING_PPPOE_SERVICE); + g_ptr_array_add(secrets, secret); + secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_SECRET, + _("Password"), + NM_SETTING(s_pppoe), + NM_SETTING_PPPOE_PASSWORD); + g_ptr_array_add(secrets, secret); + return TRUE; +} + +static NMSettingSecretFlags +get_vpn_secret_flags(NMSettingVpn *s_vpn, const char *secret_name) +{ + NMSettingSecretFlags flags = NM_SETTING_SECRET_FLAG_NONE; + GHashTable * vpn_data; + + g_object_get(s_vpn, NM_SETTING_VPN_DATA, &vpn_data, NULL); + nm_vpn_service_plugin_get_secret_flags(vpn_data, secret_name, &flags); + g_hash_table_unref(vpn_data); + + return flags; +} + +static void +add_vpn_secret_helper(GPtrArray * secrets, + NMSettingVpn *s_vpn, + const char * name, + const char * ui_name) +{ + NMSecretAgentSimpleSecret *secret; + NMSettingSecretFlags flags; + int i; + + flags = get_vpn_secret_flags(s_vpn, name); + if (flags & NM_SETTING_SECRET_FLAG_AGENT_OWNED || flags & NM_SETTING_SECRET_FLAG_NOT_SAVED) { + secret = _secret_real_new_vpn_secret(ui_name, + NM_SETTING(s_vpn), + name, + nm_setting_vpn_get_service_type(s_vpn)); + + /* Check for duplicates */ + for (i = 0; i < secrets->len; i++) { + NMSecretAgentSimpleSecret *s = secrets->pdata[i]; + + if (s->secret_type == secret->secret_type && nm_streq0(s->vpn_type, secret->vpn_type) + && nm_streq0(s->entry_id, secret->entry_id)) { + _secret_real_free(secret); + return; + } + } + + g_ptr_array_add(secrets, secret); + } +} + +#define VPN_MSG_TAG "x-vpn-message:" + +static gboolean +add_vpn_secrets(RequestData *request, GPtrArray *secrets, char **msg) +{ + NMSettingVpn * s_vpn = nm_connection_get_setting_vpn(request->connection); + const NmcVpnPasswordName *p; + const char * vpn_msg = NULL; + char ** iter; + + /* If hints are given, then always ask for what the hints require */ + if (request->hints) { + for (iter = request->hints; *iter; iter++) { + if (!vpn_msg && g_str_has_prefix(*iter, VPN_MSG_TAG)) + vpn_msg = &(*iter)[NM_STRLEN(VPN_MSG_TAG)]; + else + add_vpn_secret_helper(secrets, s_vpn, *iter, *iter); + } + } + + NM_SET_OUT(msg, g_strdup(vpn_msg)); + + /* Now add what client thinks might be required, because hints may be empty or incomplete */ + p = nm_vpn_get_secret_names(nm_setting_vpn_get_service_type(s_vpn)); + while (p && p->name) { + add_vpn_secret_helper(secrets, s_vpn, p->name, _(p->ui_name)); + p++; + } + + return TRUE; +} + +static gboolean +add_wireguard_secrets(RequestData *request, GPtrArray *secrets, char **msg, GError **error) +{ + NMSettingWireGuard * s_wg; + NMSecretAgentSimpleSecret *secret; + guint i; + + s_wg = NM_SETTING_WIREGUARD( + nm_connection_get_setting(request->connection, NM_TYPE_SETTING_WIREGUARD)); + if (!s_wg) { + g_set_error(error, + NM_SECRET_AGENT_ERROR, + NM_SECRET_AGENT_ERROR_FAILED, + "Cannot service a WireGuard secrets request %s for a connection without " + "WireGuard settings", + request->request_id); + return FALSE; + } + + if (!request->hints || !request->hints[0] + || g_strv_contains(NM_CAST_STRV_CC(request->hints), NM_SETTING_WIREGUARD_PRIVATE_KEY)) { + secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_SECRET, + _("WireGuard private-key"), + NM_SETTING(s_wg), + NM_SETTING_WIREGUARD_PRIVATE_KEY); + g_ptr_array_add(secrets, secret); + } + + if (request->hints) { + for (i = 0; request->hints[i]; i++) { + NMWireGuardPeer *peer; + const char * name = request->hints[i]; + gs_free char * public_key = NULL; + + if (nm_streq(name, NM_SETTING_WIREGUARD_PRIVATE_KEY)) + continue; + + if (NM_STR_HAS_PREFIX(name, NM_SETTING_WIREGUARD_PEERS ".")) { + const char *tmp; + + tmp = &name[NM_STRLEN(NM_SETTING_WIREGUARD_PEERS ".")]; + if (NM_STR_HAS_SUFFIX(tmp, "." NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY)) { + public_key = g_strndup( + tmp, + strlen(tmp) - NM_STRLEN("." NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY)); + } + } + + if (!public_key) + continue; + + peer = nm_setting_wireguard_get_peer_by_public_key(s_wg, public_key, NULL); + + g_ptr_array_add(secrets, + _secret_real_new_wireguard_peer_psk( + s_wg, + (peer ? nm_wireguard_peer_get_public_key(peer) : public_key), + (peer ? nm_wireguard_peer_get_preshared_key(peer) : NULL))); + } + } + + *msg = g_strdup_printf(_("Secrets are required to connect WireGuard VPN '%s'"), + nm_connection_get_id(request->connection)); + return TRUE; +} + +typedef struct { + GPid auth_dialog_pid; + GString * auth_dialog_response; + RequestData * request; + GPtrArray * secrets; + GCancellable * cancellable; + gulong cancellable_id; + guint child_watch_id; + GInputStream * input_stream; + GOutputStream *output_stream; + char read_buf[5]; +} AuthDialogData; + +static void +_auth_dialog_data_free(AuthDialogData *data) +{ + nm_clear_g_signal_handler(data->cancellable, &data->cancellable_id); + g_clear_object(&data->cancellable); + nm_clear_g_source(&data->child_watch_id); + g_ptr_array_unref(data->secrets); + g_spawn_close_pid(data->auth_dialog_pid); + g_string_free(data->auth_dialog_response, TRUE); + g_object_unref(data->input_stream); + g_object_unref(data->output_stream); + g_slice_free(AuthDialogData, data); +} + +static void +_auth_dialog_exited(GPid pid, int status, gpointer user_data) +{ + AuthDialogData * data = user_data; + RequestData * request = data->request; + GPtrArray * secrets = data->secrets; + NMSettingVpn * s_vpn = nm_connection_get_setting_vpn(request->connection); + nm_auto_unref_keyfile GKeyFile *keyfile = NULL; + gs_strfreev char ** groups = NULL; + gs_free char * title = NULL; + gs_free char * message = NULL; + int i; + gs_free_error GError *error = NULL; + + data->child_watch_id = 0; + + nm_clear_g_cancellable_disconnect(data->cancellable, &data->cancellable_id); + + if (status != 0) { + g_set_error(&error, + NM_SECRET_AGENT_ERROR, + NM_SECRET_AGENT_ERROR_FAILED, + "Auth dialog failed with error code %d\n", + status); + goto out; + } + + keyfile = g_key_file_new(); + if (!g_key_file_load_from_data(keyfile, + data->auth_dialog_response->str, + data->auth_dialog_response->len, + G_KEY_FILE_NONE, + &error)) { + goto out; + } + + groups = g_key_file_get_groups(keyfile, NULL); + if (!nm_streq0(groups[0], "VPN Plugin UI")) { + g_set_error(&error, + NM_SECRET_AGENT_ERROR, + NM_SECRET_AGENT_ERROR_FAILED, + "Expected [VPN Plugin UI] in auth dialog response"); + goto out; + } + + title = g_key_file_get_string(keyfile, "VPN Plugin UI", "Title", &error); + if (!title) + goto out; + + message = g_key_file_get_string(keyfile, "VPN Plugin UI", "Description", &error); + if (!message) + goto out; + + for (i = 1; groups[i]; i++) { + gs_free char *pretty_name = NULL; + + if (!g_key_file_get_boolean(keyfile, groups[i], "IsSecret", NULL)) + continue; + if (!g_key_file_get_boolean(keyfile, groups[i], "ShouldAsk", NULL)) + continue; + + pretty_name = g_key_file_get_string(keyfile, groups[i], "Label", NULL); + g_ptr_array_add(secrets, + _secret_real_new_vpn_secret(pretty_name, + NM_SETTING(s_vpn), + groups[i], + nm_setting_vpn_get_service_type(s_vpn))); + } + +out: + /* Try to fall back to the hardwired VPN support if the auth dialog fails. + * We may eventually get rid of the whole hardwired secrets handling at some point, + * when the auth helpers are goode enough.. */ + if (error && add_vpn_secrets(request, secrets, &message)) { + g_clear_error(&error); + if (!message) { + message = g_strdup_printf(_("A password is required to connect to '%s'."), + nm_connection_get_id(request->connection)); + } + } + + if (error) + _request_data_complete(request, NULL, error, NULL); + else { + g_signal_emit(request->self, + signals[REQUEST_SECRETS], + 0, + request->request_id, + title, + message, + secrets); + } + + _auth_dialog_data_free(data); +} + +static void +_request_cancelled(GObject *object, gpointer user_data) +{ + _auth_dialog_data_free(user_data); +} + +static void +_auth_dialog_read_done(GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + GInputStream * auth_dialog_out = G_INPUT_STREAM(source_object); + AuthDialogData *data = user_data; + gssize read_size; + gs_free_error GError *error = NULL; + + read_size = g_input_stream_read_finish(auth_dialog_out, res, &error); + switch (read_size) { + case -1: + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + _request_data_complete(data->request, NULL, error, NULL); + _auth_dialog_data_free(data); + break; + case 0: + /* Done reading. Let's wait for the auth dialog to exit so that we're able to collect the status. + * Remember we can be cancelled in between. */ + data->child_watch_id = g_child_watch_add(data->auth_dialog_pid, _auth_dialog_exited, data); + data->cancellable = g_object_ref(data->request->cancellable); + data->cancellable_id = + g_cancellable_connect(data->cancellable, G_CALLBACK(_request_cancelled), data, NULL); + break; + default: + g_string_append_len(data->auth_dialog_response, data->read_buf, read_size); + g_input_stream_read_async(auth_dialog_out, + data->read_buf, + sizeof(data->read_buf), + G_PRIORITY_DEFAULT, + NULL, + _auth_dialog_read_done, + data); + return; + } + + g_input_stream_close(auth_dialog_out, NULL, NULL); +} + +static void +_auth_dialog_write_done(GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + GOutputStream *auth_dialog_out = G_OUTPUT_STREAM(source_object); + _nm_unused gs_free char *auth_dialog_request_free = user_data; + + /* We don't care about write errors. If there are any problems, the + * reader shall notice. */ + g_output_stream_write_finish(auth_dialog_out, res, NULL); + g_output_stream_close(auth_dialog_out, NULL, NULL); +} + +static void +_add_to_string(GString *string, const char *key, const char *value) +{ + gs_strfreev char **lines = NULL; + int i; + + lines = g_strsplit(value, "\n", -1); + + g_string_append(string, key); + for (i = 0; lines[i]; i++) { + g_string_append_c(string, '='); + g_string_append(string, lines[i]); + g_string_append_c(string, '\n'); + } +} + +static void +_add_data_item_to_string(const char *key, const char *value, gpointer user_data) +{ + GString *string = user_data; + + _add_to_string(string, "DATA_KEY", key); + _add_to_string(string, "DATA_VAL", value); + g_string_append_c(string, '\n'); +} + +static void +_add_secret_to_string(const char *key, const char *value, gpointer user_data) +{ + GString *string = user_data; + + _add_to_string(string, "SECRET_KEY", key); + _add_to_string(string, "SECRET_VAL", value); + g_string_append_c(string, '\n'); +} + +static gboolean +try_spawn_vpn_auth_helper(RequestData *request, GPtrArray *secrets) +{ + NMSettingVpn * s_vpn = nm_connection_get_setting_vpn(request->connection); + gs_unref_ptrarray GPtrArray *auth_dialog_argv = NULL; + NMVpnPluginInfo * plugin_info; + const char * s; + GPid auth_dialog_pid; + int auth_dialog_in_fd; + int auth_dialog_out_fd; + GOutputStream * auth_dialog_in; + GInputStream * auth_dialog_out; + GError * error = NULL; + GString * auth_dialog_request; + gs_free char * auth_dialog_request_str = NULL; + gsize auth_dialog_request_len; + AuthDialogData * data; + int i; + + plugin_info = nm_vpn_plugin_info_list_find_by_service(nm_vpn_get_plugin_infos(), + nm_setting_vpn_get_service_type(s_vpn)); + if (!plugin_info) + return FALSE; + + s = nm_vpn_plugin_info_lookup_property(plugin_info, "GNOME", "supports-external-ui-mode"); + if (!_nm_utils_ascii_str_to_bool(s, FALSE)) + return FALSE; + + auth_dialog_argv = g_ptr_array_new(); + + s = nm_vpn_plugin_info_lookup_property(plugin_info, "GNOME", "auth-dialog"); + g_return_val_if_fail(s, FALSE); + g_ptr_array_add(auth_dialog_argv, (gpointer) s); + + g_ptr_array_add(auth_dialog_argv, "-u"); + g_ptr_array_add(auth_dialog_argv, (gpointer) nm_connection_get_uuid(request->connection)); + g_ptr_array_add(auth_dialog_argv, "-n"); + g_ptr_array_add(auth_dialog_argv, (gpointer) nm_connection_get_id(request->connection)); + g_ptr_array_add(auth_dialog_argv, "-s"); + g_ptr_array_add(auth_dialog_argv, (gpointer) nm_setting_vpn_get_service_type(s_vpn)); + g_ptr_array_add(auth_dialog_argv, "--external-ui-mode"); + g_ptr_array_add(auth_dialog_argv, "-i"); + + if (request->flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW) + g_ptr_array_add(auth_dialog_argv, "-r"); + + s = nm_vpn_plugin_info_lookup_property(plugin_info, "GNOME", "supports-hints"); + if (_nm_utils_ascii_str_to_bool(s, FALSE)) { + for (i = 0; request->hints[i]; i++) { + g_ptr_array_add(auth_dialog_argv, "-t"); + g_ptr_array_add(auth_dialog_argv, request->hints[i]); + } + } + + g_ptr_array_add(auth_dialog_argv, NULL); + if (!g_spawn_async_with_pipes(NULL, + (char **) auth_dialog_argv->pdata, + NULL, + G_SPAWN_DO_NOT_REAP_CHILD, + NULL, + NULL, + &auth_dialog_pid, + &auth_dialog_in_fd, + &auth_dialog_out_fd, + NULL, + &error)) { + g_warning("Failed to spawn the auth dialog%s\n", error->message); + return FALSE; + } + + auth_dialog_in = g_unix_output_stream_new(auth_dialog_in_fd, TRUE); + auth_dialog_out = g_unix_input_stream_new(auth_dialog_out_fd, TRUE); + + auth_dialog_request = g_string_new_len(NULL, 1024); + nm_setting_vpn_foreach_data_item(s_vpn, _add_data_item_to_string, auth_dialog_request); + nm_setting_vpn_foreach_secret(s_vpn, _add_secret_to_string, auth_dialog_request); + g_string_append(auth_dialog_request, "DONE\nQUIT\n"); + auth_dialog_request_len = auth_dialog_request->len; + auth_dialog_request_str = g_string_free(auth_dialog_request, FALSE); + + data = g_slice_new(AuthDialogData); + *data = (AuthDialogData){ + .auth_dialog_response = g_string_new_len(NULL, sizeof(data->read_buf)), + .auth_dialog_pid = auth_dialog_pid, + .request = request, + .secrets = g_ptr_array_ref(secrets), + .input_stream = auth_dialog_out, + .output_stream = auth_dialog_in, + }; + + g_output_stream_write_async(auth_dialog_in, + auth_dialog_request_str, + auth_dialog_request_len, + G_PRIORITY_DEFAULT, + request->cancellable, + _auth_dialog_write_done, + auth_dialog_request_str); + + /* Ownership of the pointer was passed on to g_output_stream_write_async(). */ + g_steal_pointer(&auth_dialog_request_str); + + g_input_stream_read_async(auth_dialog_out, + data->read_buf, + sizeof(data->read_buf), + G_PRIORITY_DEFAULT, + request->cancellable, + _auth_dialog_read_done, + data); + + return TRUE; +} + +static void +request_secrets_from_ui(RequestData *request) +{ + gs_unref_ptrarray GPtrArray *secrets = NULL; + gs_free_error GError * error = NULL; + NMSecretAgentSimplePrivate *priv; + NMSecretAgentSimpleSecret * secret; + const char * title; + gs_free char * msg = NULL; + + priv = NM_SECRET_AGENT_SIMPLE_GET_PRIVATE(request->self); + g_return_if_fail(priv->enabled); + + /* We only handle requests for connection with @path if set. */ + if (priv->path && !g_str_has_prefix(request->request_id, priv->path)) { + g_set_error(&error, + NM_SECRET_AGENT_ERROR, + NM_SECRET_AGENT_ERROR_FAILED, + "Request for %s secrets doesn't match path %s", + request->request_id, + priv->path); + goto out_fail_error; + } + + secrets = g_ptr_array_new_with_free_func((GDestroyNotify) _secret_real_free); + + if (nm_connection_is_type(request->connection, NM_SETTING_WIRELESS_SETTING_NAME)) { + NMSettingWireless *s_wireless; + GBytes * ssid; + gs_free char * ssid_utf8 = NULL; + + s_wireless = nm_connection_get_setting_wireless(request->connection); + ssid = nm_setting_wireless_get_ssid(s_wireless); + ssid_utf8 = nm_utils_ssid_to_utf8(g_bytes_get_data(ssid, NULL), g_bytes_get_size(ssid)); + + title = _("Authentication required by wireless network"); + msg = g_strdup_printf( + _("Passwords or encryption keys are required to access the wireless network '%s'."), + ssid_utf8); + + if (!add_wireless_secrets(request, secrets)) + goto out_fail; + } else if (nm_connection_is_type(request->connection, NM_SETTING_WIRED_SETTING_NAME)) { + title = _("Wired 802.1X authentication"); + msg = g_strdup_printf(_("Secrets are required to access the wired network '%s'"), + nm_connection_get_id(request->connection)); + + if (!add_8021x_secrets(request, secrets)) + goto out_fail; + } else if (nm_connection_is_type(request->connection, NM_SETTING_PPPOE_SETTING_NAME)) { + title = _("DSL authentication"); + msg = g_strdup_printf(_("Secrets are required for the DSL connection '%s'"), + nm_connection_get_id(request->connection)); + + if (!add_pppoe_secrets(request, secrets)) + goto out_fail; + } else if (nm_connection_is_type(request->connection, NM_SETTING_GSM_SETTING_NAME)) { + NMSettingGsm *s_gsm = nm_connection_get_setting_gsm(request->connection); + + if (g_strv_contains(NM_CAST_STRV_CC(request->hints), NM_SETTING_GSM_PIN)) { + title = _("PIN code required"); + msg = g_strdup(_("PIN code is needed for the mobile broadband device")); + + secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_SECRET, + _("PIN"), + NM_SETTING(s_gsm), + NM_SETTING_GSM_PIN); + g_ptr_array_add(secrets, secret); + } else { + title = _("Mobile broadband network password"); + msg = g_strdup_printf(_("A password is required to connect to '%s'."), + nm_connection_get_id(request->connection)); + + secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_SECRET, + _("Password"), + NM_SETTING(s_gsm), + NM_SETTING_GSM_PASSWORD); + g_ptr_array_add(secrets, secret); + } + } else if (nm_connection_is_type(request->connection, NM_SETTING_MACSEC_SETTING_NAME)) { + NMSettingMacsec *s_macsec = nm_connection_get_setting_macsec(request->connection); + + msg = g_strdup_printf(_("Secrets are required to access the MACsec network '%s'"), + nm_connection_get_id(request->connection)); + + if (nm_setting_macsec_get_mode(s_macsec) == NM_SETTING_MACSEC_MODE_PSK) { + title = _("MACsec PSK authentication"); + secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_SECRET, + _("MKA CAK"), + NM_SETTING(s_macsec), + NM_SETTING_MACSEC_MKA_CAK); + g_ptr_array_add(secrets, secret); + } else { + title = _("MACsec EAP authentication"); + if (!add_8021x_secrets(request, secrets)) + goto out_fail; + } + } else if (nm_connection_is_type(request->connection, NM_SETTING_WIREGUARD_SETTING_NAME)) { + title = _("WireGuard VPN secret"); + if (!add_wireguard_secrets(request, secrets, &msg, &error)) + goto out_fail_error; + } else if (nm_connection_is_type(request->connection, NM_SETTING_CDMA_SETTING_NAME)) { + NMSettingCdma *s_cdma = nm_connection_get_setting_cdma(request->connection); + + title = _("Mobile broadband network password"); + msg = g_strdup_printf(_("A password is required to connect to '%s'."), + nm_connection_get_id(request->connection)); + + secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_SECRET, + _("Password"), + NM_SETTING(s_cdma), + NM_SETTING_CDMA_PASSWORD); + g_ptr_array_add(secrets, secret); + } else if (nm_connection_is_type(request->connection, NM_SETTING_BLUETOOTH_SETTING_NAME)) { + NMSetting *setting = NULL; + + setting = nm_connection_get_setting_by_name(request->connection, + NM_SETTING_BLUETOOTH_SETTING_NAME); + if (setting + && !nm_streq0(nm_setting_bluetooth_get_connection_type(NM_SETTING_BLUETOOTH(setting)), + NM_SETTING_BLUETOOTH_TYPE_NAP)) { + setting = + nm_connection_get_setting_by_name(request->connection, NM_SETTING_GSM_SETTING_NAME); + if (!setting) + setting = nm_connection_get_setting_by_name(request->connection, + NM_SETTING_CDMA_SETTING_NAME); + } + + if (!setting) + goto out_fail; + + title = _("Mobile broadband network password"); + msg = g_strdup_printf(_("A password is required to connect to '%s'."), + nm_connection_get_id(request->connection)); + + secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_SECRET, + _("Password"), + setting, + "password"); + g_ptr_array_add(secrets, secret); + } else if (nm_connection_is_type(request->connection, NM_SETTING_VPN_SETTING_NAME)) { + title = _("VPN password required"); + + if (try_spawn_vpn_auth_helper(request, secrets)) { + /* This will emit REQUEST_SECRETS when ready */ + return; + } + + if (!add_vpn_secrets(request, secrets, &msg)) + goto out_fail; + if (!msg) { + msg = g_strdup_printf(_("A password is required to connect to '%s'."), + nm_connection_get_id(request->connection)); + } + } else + goto out_fail; + + if (secrets->len == 0) + goto out_fail; + + g_signal_emit(request->self, + signals[REQUEST_SECRETS], + 0, + request->request_id, + title, + msg, + secrets); + return; + +out_fail: + g_set_error(&error, + NM_SECRET_AGENT_ERROR, + NM_SECRET_AGENT_ERROR_FAILED, + "Cannot service a secrets request %s for a %s connection", + request->request_id, + nm_connection_get_connection_type(request->connection)); +out_fail_error: + _request_data_complete(request, NULL, error, NULL); +} + +static void +get_secrets(NMSecretAgentOld * agent, + NMConnection * connection, + const char * connection_path, + const char * setting_name, + const char ** hints, + NMSecretAgentGetSecretsFlags flags, + NMSecretAgentOldGetSecretsFunc callback, + gpointer callback_data) +{ + NMSecretAgentSimple * self = NM_SECRET_AGENT_SIMPLE(agent); + NMSecretAgentSimplePrivate *priv = NM_SECRET_AGENT_SIMPLE_GET_PRIVATE(self); + RequestData * request; + gs_free_error GError *error = NULL; + gs_free char * request_id = NULL; + const char * request_id_setting_name; + + request_id = g_strdup_printf("%s/%s", connection_path, setting_name); + + if (g_hash_table_contains(priv->requests, &request_id)) { + /* We already have a request pending for this (connection, setting) */ + error = g_error_new(NM_SECRET_AGENT_ERROR, + NM_SECRET_AGENT_ERROR_FAILED, + "Request for %s secrets already pending", + request_id); + callback(agent, connection, NULL, error, callback_data); + return; + } + + if (!(flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION)) { + /* We don't do stored passwords */ + error = g_error_new(NM_SECRET_AGENT_ERROR, + NM_SECRET_AGENT_ERROR_NO_SECRETS, + "Stored passwords not supported"); + callback(agent, connection, NULL, error, callback_data); + return; + } + + nm_assert(g_str_has_suffix(request_id, setting_name)); + request_id_setting_name = &request_id[strlen(request_id) - strlen(setting_name)]; + nm_assert(nm_streq(request_id_setting_name, setting_name)); + + request = g_slice_new(RequestData); + *request = (RequestData){ + .self = self, + .connection = g_object_ref(connection), + .setting_name = request_id_setting_name, + .hints = g_strdupv((char **) hints), + .callback = callback, + .callback_data = callback_data, + .request_id = g_steal_pointer(&request_id), + .flags = flags, + .cancellable = g_cancellable_new(), + }; + g_hash_table_add(priv->requests, request); + + if (priv->enabled) + request_secrets_from_ui(request); +} + +/** + * nm_secret_agent_simple_response: + * @self: the #NMSecretAgentSimple + * @request_id: the request ID being responded to + * @secrets: (allow-none): the array of secrets, or %NULL + * + * Response to a #NMSecretAgentSimple::get-secrets signal. + * + * If the user provided secrets, the caller should set the + * corresponding <literal>value</literal> fields in the + * #NMSecretAgentSimpleSecrets (freeing any initial values they had), and + * pass the array to nm_secret_agent_simple_response(). If the user + * cancelled the request, @secrets should be NULL. + */ +void +nm_secret_agent_simple_response(NMSecretAgentSimple *self, + const char * request_id, + GPtrArray * secrets) +{ + NMSecretAgentSimplePrivate *priv; + RequestData * request; + gs_unref_variant GVariant *secrets_dict = NULL; + gs_free_error GError *error = NULL; + int i; + + g_return_if_fail(NM_IS_SECRET_AGENT_SIMPLE(self)); + + priv = NM_SECRET_AGENT_SIMPLE_GET_PRIVATE(self); + request = g_hash_table_lookup(priv->requests, &request_id); + g_return_if_fail(request != NULL); + + if (secrets) { + GVariantBuilder conn_builder, *setting_builder; + GVariantBuilder vpn_secrets_builder; + GVariantBuilder wg_secrets_builder; + GVariantBuilder wg_peer_builder; + GHashTable * settings; + GHashTableIter iter; + const char * name; + gboolean has_vpn = FALSE; + gboolean has_wg = FALSE; + + settings = g_hash_table_new_full(nm_str_hash, + g_str_equal, + NULL, + (GDestroyNotify) g_variant_builder_unref); + for (i = 0; i < secrets->len; i++) { + SecretReal *secret = secrets->pdata[i]; + + setting_builder = g_hash_table_lookup(settings, nm_setting_get_name(secret->setting)); + if (!setting_builder) { + setting_builder = g_variant_builder_new(NM_VARIANT_TYPE_SETTING); + g_hash_table_insert(settings, + (char *) nm_setting_get_name(secret->setting), + setting_builder); + } + + switch (secret->base.secret_type) { + case NM_SECRET_AGENT_SECRET_TYPE_PROPERTY: + case NM_SECRET_AGENT_SECRET_TYPE_SECRET: + g_variant_builder_add(setting_builder, + "{sv}", + secret->property, + g_variant_new_string(secret->base.value)); + break; + case NM_SECRET_AGENT_SECRET_TYPE_VPN_SECRET: + if (!has_vpn) { + g_variant_builder_init(&vpn_secrets_builder, G_VARIANT_TYPE("a{ss}")); + has_vpn = TRUE; + } + g_variant_builder_add(&vpn_secrets_builder, + "{ss}", + secret->property, + secret->base.value); + break; + case NM_SECRET_AGENT_SECRET_TYPE_WIREGUARD_PEER_PSK: + if (!has_wg) { + g_variant_builder_init(&wg_secrets_builder, G_VARIANT_TYPE("aa{sv}")); + has_wg = TRUE; + } + g_variant_builder_init(&wg_peer_builder, G_VARIANT_TYPE("a{sv}")); + g_variant_builder_add(&wg_peer_builder, + "{sv}", + NM_WIREGUARD_PEER_ATTR_PUBLIC_KEY, + g_variant_new_string(secret->property)); + g_variant_builder_add(&wg_peer_builder, + "{sv}", + NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, + g_variant_new_string(secret->base.value)); + g_variant_builder_add(&wg_secrets_builder, "a{sv}", &wg_peer_builder); + break; + } + } + + if (has_vpn) { + g_variant_builder_add(setting_builder, + "{sv}", + "secrets", + g_variant_builder_end(&vpn_secrets_builder)); + } + + if (has_wg) { + g_variant_builder_add(setting_builder, + "{sv}", + NM_SETTING_WIREGUARD_PEERS, + g_variant_builder_end(&wg_secrets_builder)); + } + + g_variant_builder_init(&conn_builder, NM_VARIANT_TYPE_CONNECTION); + g_hash_table_iter_init(&iter, settings); + while (g_hash_table_iter_next(&iter, (gpointer *) &name, (gpointer *) &setting_builder)) + g_variant_builder_add(&conn_builder, "{sa{sv}}", name, setting_builder); + secrets_dict = g_variant_ref_sink(g_variant_builder_end(&conn_builder)); + g_hash_table_destroy(settings); + } else { + error = g_error_new(NM_SECRET_AGENT_ERROR, + NM_SECRET_AGENT_ERROR_USER_CANCELED, + "User cancelled"); + } + + _request_data_complete(request, secrets_dict, error, NULL); +} + +static void +cancel_get_secrets(NMSecretAgentOld *agent, const char *connection_path, const char *setting_name) +{ + NMSecretAgentSimple * self = NM_SECRET_AGENT_SIMPLE(agent); + NMSecretAgentSimplePrivate *priv = NM_SECRET_AGENT_SIMPLE_GET_PRIVATE(self); + gs_free_error GError *error = NULL; + gs_free char * request_id = NULL; + RequestData * request; + + request_id = g_strdup_printf("%s/%s", connection_path, setting_name); + request = g_hash_table_lookup(priv->requests, &request_id); + if (!request) { + /* this is really a bug of the caller (or us?). We cannot invoke a callback, + * hence the caller cannot cleanup the request. */ + g_return_if_reached(); + } + + g_set_error(&error, + NM_SECRET_AGENT_ERROR, + NM_SECRET_AGENT_ERROR_AGENT_CANCELED, + "The secret agent is going away"); + _request_data_complete(request, NULL, error, NULL); +} + +static void +save_secrets(NMSecretAgentOld * agent, + NMConnection * connection, + const char * connection_path, + NMSecretAgentOldSaveSecretsFunc callback, + gpointer callback_data) +{ + /* We don't support secret storage */ + callback(agent, connection, NULL, callback_data); +} + +static void +delete_secrets(NMSecretAgentOld * agent, + NMConnection * connection, + const char * connection_path, + NMSecretAgentOldDeleteSecretsFunc callback, + gpointer callback_data) +{ + /* We don't support secret storage, so there's nothing to delete. */ + callback(agent, connection, NULL, callback_data); +} + +/** + * nm_secret_agent_simple_enable: + * @self: the #NMSecretAgentSimple + * @path: (allow-none): the path of the connection (if any) to handle secrets + * for. If %NULL, secrets for any connection will be handled. + * + * Enables servicing the requests including the already queued ones. If @path + * is given, the agent will only handle requests for connections that match + * @path. + */ +void +nm_secret_agent_simple_enable(NMSecretAgentSimple *self, const char *path) +{ + NMSecretAgentSimplePrivate *priv = NM_SECRET_AGENT_SIMPLE_GET_PRIVATE(self); + gs_free RequestData **requests = NULL; + gsize i; + gs_free char * path_full = NULL; + + /* The path is only used to match a request_id with the current + * connection. Since the request_id is "${CONNECTION_PATH}/${SETTING}", + * add a trailing '/' to the path to match the full connection path. + */ + path_full = path ? g_strdup_printf("%s/", path) : NULL; + + if (!nm_streq0(path_full, priv->path)) { + g_free(priv->path); + priv->path = g_steal_pointer(&path_full); + } + + if (priv->enabled) + return; + priv->enabled = TRUE; + + /* Service pending secret requests. */ + requests = (RequestData **) g_hash_table_get_keys_as_array(priv->requests, NULL); + for (i = 0; requests[i]; i++) + request_secrets_from_ui(requests[i]); +} + +/*****************************************************************************/ + +static void +nm_secret_agent_simple_init(NMSecretAgentSimple *agent) +{ + NMSecretAgentSimplePrivate *priv = NM_SECRET_AGENT_SIMPLE_GET_PRIVATE(agent); + + G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(RequestData, request_id) == 0); + priv->requests = g_hash_table_new_full(nm_pstr_hash, nm_pstr_equal, NULL, _request_data_free); +} + +/** + * nm_secret_agent_simple_new: + * @name: the identifier of secret agent + * + * Creates a new #NMSecretAgentSimple. It does not serve any requests until + * nm_secret_agent_simple_enable() is called. + * + * Returns: a new #NMSecretAgentSimple if the agent creation is successful + * or %NULL in case of a failure. + */ +NMSecretAgentSimple * +nm_secret_agent_simple_new(const char *name) +{ + return g_initable_new(NM_TYPE_SECRET_AGENT_SIMPLE, + NULL, + NULL, + NM_SECRET_AGENT_OLD_IDENTIFIER, + name, + NM_SECRET_AGENT_OLD_CAPABILITIES, + NM_SECRET_AGENT_CAPABILITY_VPN_HINTS, + NULL); +} + +static void +dispose(GObject *object) +{ + NMSecretAgentSimplePrivate *priv = NM_SECRET_AGENT_SIMPLE_GET_PRIVATE(object); + gs_free_error GError *error = NULL; + GHashTableIter iter; + RequestData * request; + + g_hash_table_iter_init(&iter, priv->requests); + while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &request)) { + if (!error) + nm_utils_error_set_cancelled(&error, TRUE, "NMSecretAgentSimple"); + _request_data_complete(request, NULL, error, &iter); + } + + G_OBJECT_CLASS(nm_secret_agent_simple_parent_class)->dispose(object); +} + +static void +finalize(GObject *object) +{ + NMSecretAgentSimplePrivate *priv = NM_SECRET_AGENT_SIMPLE_GET_PRIVATE(object); + + g_hash_table_destroy(priv->requests); + + g_free(priv->path); + + G_OBJECT_CLASS(nm_secret_agent_simple_parent_class)->finalize(object); +} + +void +nm_secret_agent_simple_class_init(NMSecretAgentSimpleClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSecretAgentOldClass *agent_class = NM_SECRET_AGENT_OLD_CLASS(klass); + + object_class->dispose = dispose; + object_class->finalize = finalize; + + agent_class->get_secrets = get_secrets; + agent_class->cancel_get_secrets = cancel_get_secrets; + agent_class->save_secrets = save_secrets; + agent_class->delete_secrets = delete_secrets; + + /** + * NMSecretAgentSimple::request-secrets: + * @agent: the #NMSecretAgentSimple + * @request_id: request ID, to eventually pass to + * nm_secret_agent_simple_response(). + * @title: a title for the password dialog + * @prompt: a prompt message for the password dialog + * @secrets: (element-type #NMSecretAgentSimpleSecret): array of secrets + * being requested. + * + * Emitted when the agent requires secrets from the user. + * + * The application should ask user for the secrets. For example, + * nmtui should create a password dialog (#NmtPasswordDialog) + * with the given title and prompt, and an entry for each + * element of @secrets. If any of the secrets already have a + * <literal>value</literal> filled in, the corresponding entry + * should be initialized to that value. + * + * When the dialog is complete, the app must call + * nm_secret_agent_simple_response() with the results. + */ + signals[REQUEST_SECRETS] = g_signal_new(NM_SECRET_AGENT_SIMPLE_REQUEST_SECRETS, + G_TYPE_FROM_CLASS(klass), + 0, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 4, + G_TYPE_STRING, /* request_id */ + G_TYPE_STRING, /* title */ + G_TYPE_STRING, /* prompt */ + G_TYPE_PTR_ARRAY); +} |