summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLubomir Rintel <lkundrak@v3.sk>2018-07-27 15:01:19 +0200
committerThomas Haller <thaller@redhat.com>2018-09-10 16:56:46 +0200
commita0c848b864b5679d978155deebab1be35af68074 (patch)
treed1306c4ac7e62019299dba799ba28a7396269663
parent8f27633a780af42a49fe3f36f7f6902062dd98c1 (diff)
downloadNetworkManager-th/various.tar.gz
clients/secret-agent-simple: support auth helpersth/various
This makes it possible to utilize agents in the "external UI" mode instead of hardcoded handling of VPN secrets requests. Ideally this would be turned into a library so that nm-applet can share the code, but figuring out the right API might me a non-trivial undertaking.
-rw-r--r--clients/common/nm-secret-agent-simple.c316
1 files changed, 310 insertions, 6 deletions
diff --git a/clients/common/nm-secret-agent-simple.c b/clients/common/nm-secret-agent-simple.c
index 7048e0ef3e..c62bfb5c39 100644
--- a/clients/common/nm-secret-agent-simple.c
+++ b/clients/common/nm-secret-agent-simple.c
@@ -31,6 +31,8 @@
#include "nm-default.h"
+#include <gio/gunixoutputstream.h>
+#include <gio/gunixinputstream.h>
#include <string.h>
#include "nm-vpn-service-plugin.h"
@@ -58,6 +60,8 @@ typedef struct {
char **hints;
NMSecretAgentOldGetSecretsFunc callback;
gpointer callback_data;
+ GCancellable *cancellable;
+ NMSecretAgentGetSecretsFlags flags;
} NMSecretAgentSimpleRequest;
typedef struct {
@@ -73,6 +77,7 @@ nm_secret_agent_simple_request_free (gpointer data)
{
NMSecretAgentSimpleRequest *request = data;
+ g_clear_object (&request->cancellable);
g_object_unref (request->self);
g_object_unref (request->connection);
g_strfreev (request->hints);
@@ -81,6 +86,18 @@ nm_secret_agent_simple_request_free (gpointer data)
}
static void
+nm_secret_agent_simple_request_cancel (NMSecretAgentSimpleRequest *request,
+ GError *error)
+{
+ NMSecretAgentSimplePrivate *priv = NM_SECRET_AGENT_SIMPLE_GET_PRIVATE (request->self);
+
+ request->callback (NM_SECRET_AGENT_OLD (request->self), request->connection,
+ NULL, error, request->callback_data);
+ g_hash_table_remove (priv->requests, request->request_id);
+}
+
+
+static void
nm_secret_agent_simple_init (NMSecretAgentSimple *agent)
{
NMSecretAgentSimplePrivate *priv = NM_SECRET_AGENT_SIMPLE_GET_PRIVATE (agent);
@@ -443,6 +460,290 @@ add_vpn_secrets (NMSecretAgentSimpleRequest *request,
return TRUE;
}
+typedef struct {
+ GPid auth_dialog_pid;
+ char read_buf[5];
+ GString *auth_dialog_response;
+ NMSecretAgentSimpleRequest *request;
+ GPtrArray *secrets;
+ guint child_watch_id;
+ gulong cancellable_id;
+} AuthDialogData;
+
+static void
+_auth_dialog_data_free (AuthDialogData *data)
+{
+ g_ptr_array_unref (data->secrets);
+ g_spawn_close_pid (data->auth_dialog_pid);
+ g_string_free (data->auth_dialog_response, TRUE);
+ g_slice_free (AuthDialogData, data);
+}
+
+static void
+_auth_dialog_exited (GPid pid, gint status, gpointer user_data)
+{
+ AuthDialogData *data = user_data;
+ NMSecretAgentSimpleRequest *request = data->request;
+ GPtrArray *secrets = data->secrets;
+ NMSettingVpn *s_vpn = nm_connection_get_setting_vpn (request->connection);
+ GKeyFile *keyfile = NULL;
+ gs_strfreev char **groups = NULL;
+ const char *title = NULL;
+ const char *message = NULL;
+ int i;
+ gs_free_error GError *error = NULL;
+
+ g_cancellable_disconnect (request->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)) {
+ g_set_error (&error, NM_SECRET_AGENT_ERROR, NM_SECRET_AGENT_ERROR_FAILED,
+ "Bad response from the auth dialog: %s\n", error->message);
+ goto out;
+ }
+
+ groups = g_key_file_get_groups (keyfile, NULL);
+ if (g_strcmp0 (groups[0], "VPN Plugin UI") != 0) {
+ 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) {
+ g_set_error (&error, NM_SECRET_AGENT_ERROR, NM_SECRET_AGENT_ERROR_FAILED,
+ "Missing Title in auth dialog response: %s\n", error->message);
+ goto out;
+ }
+
+ message = g_key_file_get_string (keyfile, "VPN Plugin UI", "Description", &error);
+ if (!message) {
+ g_set_error (&error, NM_SECRET_AGENT_ERROR, NM_SECRET_AGENT_ERROR_FAILED,
+ "Missing Description in auth dialog response: %s\n", error->message);
+ goto out;
+ }
+
+ for (i = 1; groups[i]; i++) {
+ if (!g_key_file_get_boolean (keyfile, groups[i], "IsSecret", NULL))
+ continue;
+ if (!g_key_file_get_boolean (keyfile, groups[i], "ShouldAsk", NULL))
+ continue;
+
+ g_ptr_array_add (secrets,
+ nm_secret_agent_simple_secret_new (NM_SECRET_AGENT_SECRET_TYPE_VPN_SECRET,
+ g_key_file_get_string (keyfile, groups[i], "Label", NULL),
+ NM_SETTING (s_vpn),
+ groups[i],
+ nm_setting_vpn_get_service_type (s_vpn)));
+
+ }
+
+out:
+ if (error) {
+ nm_secret_agent_simple_request_cancel (request, error);
+ } else {
+ g_signal_emit (request->self, signals[REQUEST_SECRETS], 0,
+ request->request_id, title, message, secrets);
+ }
+
+ g_clear_pointer (&keyfile, g_key_file_unref);
+ _auth_dialog_data_free (data);
+}
+
+static void
+_request_cancelled (GObject *object, gpointer user_data)
+{
+ AuthDialogData *data = user_data;
+
+ g_source_remove (data->child_watch_id);
+ _auth_dialog_data_free (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))
+ nm_secret_agent_simple_request_cancel (data->request, error);
+ _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_id = g_cancellable_connect (data->request->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);
+ GString *auth_dialog_request = 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_string_free (auth_dialog_request, TRUE);
+ 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", key);
+ 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", key);
+ g_string_append_c (string, '\n');
+}
+
+static gboolean
+try_spawn_vpn_auth_helper (NMSecretAgentSimpleRequest *request,
+ GPtrArray *secrets)
+{
+ NMSettingVpn *s_vpn = nm_connection_get_setting_vpn (request->connection);
+ NMVpnPluginInfo *plugin_info;
+ gboolean supports_external;
+ const char *auth_dialog_argv[] = { NULL,
+ "-u", nm_connection_get_uuid (request->connection),
+ "-n", nm_connection_get_id (request->connection),
+ "-s", nm_setting_vpn_get_service_type (s_vpn),
+ "--external-ui-mode",
+ "-i",
+ NULL, /* [9], slot for "-r" */
+ NULL };
+ 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;
+ AuthDialogData *data;
+
+ 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");
+ supports_external = _nm_utils_ascii_str_to_bool (s, FALSE);
+ if (!supports_external)
+ return FALSE;
+
+ auth_dialog_argv[0] = nm_vpn_plugin_info_lookup_property (plugin_info, "GNOME", "auth-dialog");
+ g_return_val_if_fail (auth_dialog_argv[0], FALSE);
+
+ if (request->flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW)
+ auth_dialog_argv[9] = "-r";
+
+ if (!g_spawn_async_with_pipes (NULL, (char **)auth_dialog_argv, 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");
+
+ data = g_slice_alloc0 (sizeof (AuthDialogData));
+ data->auth_dialog_response = g_string_new_len (NULL, sizeof (data->read_buf));
+ data->auth_dialog_pid = auth_dialog_pid;
+ data->request = request;
+ data->secrets = secrets;
+
+ 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);
+
+ 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 (NMSecretAgentSimpleRequest *request)
{
@@ -463,9 +764,7 @@ request_secrets_from_ui (NMSecretAgentSimpleRequest *request)
error = g_error_new (NM_SECRET_AGENT_ERROR, NM_SECRET_AGENT_ERROR_FAILED,
"Request for %s secrets doesn't match path %s",
request->request_id, priv->path);
- request->callback (NM_SECRET_AGENT_OLD (request->self), request->connection,
- NULL, error, request->callback_data);
- g_hash_table_remove (priv->requests, request->request_id);
+ nm_secret_agent_simple_request_cancel (request, error);
return;
}
@@ -581,6 +880,11 @@ request_secrets_from_ui (NMSecretAgentSimpleRequest *request)
title = _("VPN password required");
msg = NULL;
+ if (try_spawn_vpn_auth_helper (request, secrets)) {
+ /* This will emit REQUEST_SECRETS when ready */
+ return;
+ }
+
ok = add_vpn_secrets (request, secrets, &msg);
if (!msg)
msg = g_strdup_printf (_("A password is required to connect to '%s'."),
@@ -595,9 +899,7 @@ request_secrets_from_ui (NMSecretAgentSimpleRequest *request)
"Cannot service a secrets request %s for a %s connection",
request->request_id,
nm_connection_get_connection_type (request->connection));
- request->callback (NM_SECRET_AGENT_OLD (request->self), request->connection,
- NULL, error, request->callback_data);
- g_hash_table_remove (priv->requests, request->request_id);
+ nm_secret_agent_simple_request_cancel (request, error);
g_ptr_array_unref (secrets);
return;
}
@@ -648,6 +950,8 @@ nm_secret_agent_simple_get_secrets (NMSecretAgentOld *agent,
request->callback = callback;
request->callback_data = callback_data;
request->request_id = request_id;
+ request->flags = flags;
+ request->cancellable = g_cancellable_new ();
g_hash_table_replace (priv->requests, request->request_id, request);
if (priv->enabled)