diff options
| author | Michael Biebl <biebl@debian.org> | 2021-10-01 23:05:04 +0200 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2021-10-01 23:05:04 +0200 |
| commit | e74c568b07b50b97873fb4ee1d776dedefbd54d6 (patch) | |
| tree | 3469f17ea9af91f7ff169b890633bda68b0cf76e /src/nmtui/nmtui-connect.c | |
| parent | bfe522304da217296e2a61040f58e35ec5d6f3f2 (diff) | |
New upstream version 1.32.12 upstream/1.32.12
Diffstat (limited to 'src/nmtui/nmtui-connect.c')
| -rw-r--r-- | src/nmtui/nmtui-connect.c | 488 |
1 files changed, 488 insertions, 0 deletions
diff --git a/src/nmtui/nmtui-connect.c b/src/nmtui/nmtui-connect.c new file mode 100644 index 00000000..4aa91acb --- /dev/null +++ b/src/nmtui/nmtui-connect.c @@ -0,0 +1,488 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 Red Hat, Inc. + */ + +/** + * SECTION:nmtui-connect + * @short_description: nm-applet-like functionality + * + * nmtui-connect implements activating and deactivating #NMConnections, + * including presenting a password dialog if necessary. + */ + +#include "libnm-client-aux-extern/nm-default-client.h" + +#include <stdlib.h> + +#include "libnmt-newt/nmt-newt.h" + +#include "nmtui.h" +#include "nmtui-connect.h" +#include "nmt-connect-connection-list.h" +#include "nmt-password-dialog.h" +#include "libnmc-base/nm-secret-agent-simple.h" +#include "libnmc-base/nm-vpn-helpers.h" +#include "libnmc-base/nm-client-utils.h" +#include "nmt-utils.h" + +/** + * Runs openconnect to authenticate. The current screen state is saved + * before starting the command and restored after it returns. + */ +static gboolean +openconnect_authenticate(NMConnection *connection, char **cookie, char **gateway, char **gwcert) +{ + GError * error = NULL; + NMSettingVpn *s_vpn; + gboolean ret; + int status = 0; + const char * gw, *port; + + nmt_newt_message_dialog( + _("openconnect will be run to authenticate.\nIt will return to nmtui when completed.")); + + /* Get port */ + s_vpn = nm_connection_get_setting_vpn(connection); + gw = nm_setting_vpn_get_data_item(s_vpn, "gateway"); + port = gw ? strrchr(gw, ':') : NULL; + + newtSuspend(); + + ret = nm_vpn_openconnect_authenticate_helper(gw, cookie, gateway, gwcert, &status, &error); + + newtResume(); + + if (!ret) { + nmt_newt_message_dialog(_("Error: openconnect failed: %s"), error->message); + g_clear_error(&error); + return FALSE; + } + + if (WIFEXITED(status)) { + if (WEXITSTATUS(status) != 0) { + nmt_newt_message_dialog(_("openconnect failed with status %d"), WEXITSTATUS(status)); + return FALSE; + } + } else if (WIFSIGNALED(status)) { + nmt_newt_message_dialog(_("openconnect failed with signal %d"), WTERMSIG(status)); + return FALSE; + } + + if (gateway && *gateway && port) { + char *tmp = *gateway; + *gateway = g_strdup_printf("%s%s", *gateway, port); + g_free(tmp); + } + + return TRUE; +} + +static void +secrets_requested(NMSecretAgentSimple *agent, + const char * request_id, + const char * title, + const char * msg, + GPtrArray * secrets, + gpointer user_data) +{ + NmtNewtForm * form; + NMConnection *connection = NM_CONNECTION(user_data); + int i; + + /* Get secrets for OpenConnect VPN */ + if (connection && nm_connection_is_type(connection, NM_SETTING_VPN_SETTING_NAME)) { + NMSettingVpn *s_vpn = nm_connection_get_setting_vpn(connection); + + if (nm_streq0(nm_setting_vpn_get_service_type(s_vpn), + NM_SECRET_AGENT_VPN_TYPE_OPENCONNECT)) { + gs_free char *cookie = NULL; + gs_free char *gateway = NULL; + gs_free char *gwcert = NULL; + + openconnect_authenticate(connection, &cookie, &gateway, &gwcert); + + for (i = 0; i < secrets->len; i++) { + NMSecretAgentSimpleSecret *secret = secrets->pdata[i]; + + if (secret->secret_type != NM_SECRET_AGENT_SECRET_TYPE_VPN_SECRET) + continue; + if (!nm_streq0(secret->vpn_type, NM_SECRET_AGENT_VPN_TYPE_OPENCONNECT)) + continue; + if (nm_streq0(secret->entry_id, + NM_SECRET_AGENT_ENTRY_ID_PREFX_VPN_SECRETS "cookie")) { + g_free(secret->value); + secret->value = g_steal_pointer(&cookie); + } else if (nm_streq0(secret->entry_id, + NM_SECRET_AGENT_ENTRY_ID_PREFX_VPN_SECRETS "gateway")) { + g_free(secret->value); + secret->value = g_steal_pointer(&gateway); + } else if (nm_streq0(secret->entry_id, + NM_SECRET_AGENT_ENTRY_ID_PREFX_VPN_SECRETS "gwcert")) { + g_free(secret->value); + secret->value = g_steal_pointer(&gwcert); + } + } + } + } + + form = nmt_password_dialog_new(request_id, title, msg, secrets); + nmt_newt_form_run_sync(form); + + if (nmt_password_dialog_succeeded(NMT_PASSWORD_DIALOG(form))) + nm_secret_agent_simple_response(agent, request_id, secrets); + else + nm_secret_agent_simple_response(agent, request_id, NULL); + + g_object_unref(form); +} + +typedef struct { + NMDevice * device; + NMActiveConnection *active; + NmtSyncOp * op; +} ActivateConnectionInfo; + +static void +connect_cancelled(NmtNewtForm *form, gpointer user_data) +{ + ActivateConnectionInfo *info = user_data; + GError * error = NULL; + + error = g_error_new_literal(G_IO_ERROR, G_IO_ERROR_CANCELLED, "Cancelled"); + nmt_sync_op_complete_boolean(info->op, FALSE, error); + g_clear_error(&error); +} + +static void +check_activated(ActivateConnectionInfo *info) +{ + NMActiveConnectionState ac_state; + const char * reason = NULL; + gs_free_error GError *error = NULL; + + ac_state = nmc_activation_get_effective_state(info->active, info->device, &reason); + if (!NM_IN_SET(ac_state, + NM_ACTIVE_CONNECTION_STATE_ACTIVATED, + NM_ACTIVE_CONNECTION_STATE_DEACTIVATED)) + return; + + if (ac_state == NM_ACTIVE_CONNECTION_STATE_DEACTIVATED) { + nm_assert(reason); + error = g_error_new(NM_CLIENT_ERROR, + NM_CLIENT_ERROR_FAILED, + _("Activation failed: %s"), + reason); + } + + nmt_sync_op_complete_boolean(info->op, error == NULL, error); +} + +static void +activate_ac_state_changed(GObject *object, GParamSpec *pspec, gpointer user_data) +{ + check_activated(user_data); +} + +static void +activate_device_state_changed(GObject *object, GParamSpec *pspec, gpointer user_data) +{ + check_activated(user_data); +} + +static void +activate_callback(GObject *client, GAsyncResult *result, gpointer user_data) +{ + NmtSyncOp * op = user_data; + NMActiveConnection *ac; + GError * error = NULL; + + ac = nm_client_activate_connection_finish(NM_CLIENT(client), result, &error); + if (error) + nmt_sync_op_complete_pointer(op, NULL, error); + else + nmt_sync_op_complete_pointer(op, ac, NULL); +} + +static void +add_and_activate_callback(GObject *client, GAsyncResult *result, gpointer user_data) +{ + NmtSyncOp * op = user_data; + NMActiveConnection *ac; + GError * error = NULL; + + ac = nm_client_add_and_activate_connection_finish(NM_CLIENT(client), result, &error); + if (error) + nmt_sync_op_complete_pointer(op, NULL, error); + else + nmt_sync_op_complete_pointer(op, ac, NULL); +} + +static void +deactivate_connection(NMActiveConnection *ac) +{ + GError *error = NULL; + + if (!nm_client_deactivate_connection(nm_client, ac, NULL, &error)) { + nmt_newt_message_dialog(_("Could not deactivate connection: %s"), error->message); + g_clear_error(&error); + } +} + +static void +activate_connection(NMConnection *connection, NMDevice *device, NMObject *specific_object) +{ + NmtNewtForm * form; + gs_unref_object NMSecretAgentSimple *agent = NULL; + NmtNewtWidget * label; + NmtSyncOp op; + const char * specific_object_path; + NMActiveConnection * ac; + GError * error = NULL; + ActivateConnectionInfo info = {}; + + form = g_object_new(NMT_TYPE_NEWT_FORM, "escape-exits", TRUE, NULL); + label = nmt_newt_label_new(_("Connecting...")); + nmt_newt_form_set_content(form, label); + + agent = nm_secret_agent_simple_new("nmtui"); + if (agent) { + if (connection) { + nm_secret_agent_simple_enable(agent, nm_object_get_path(NM_OBJECT(connection))); + } + g_signal_connect(agent, + NM_SECRET_AGENT_SIMPLE_REQUEST_SECRETS, + G_CALLBACK(secrets_requested), + connection); + } + + specific_object_path = specific_object ? nm_object_get_path(specific_object) : NULL; + + /* There's no way to cancel an nm_client_activate_connection() / + * nm_client_add_and_activate_connection() call, so we always let them + * complete, even if the user hits Esc; they shouldn't normally take long + * to complete anyway. + */ + + nmt_sync_op_init(&op); + if (connection) { + nm_client_activate_connection_async(nm_client, + connection, + device, + specific_object_path, + NULL, + activate_callback, + &op); + } else { + nm_client_add_and_activate_connection_async(nm_client, + NULL, + device, + specific_object_path, + NULL, + add_and_activate_callback, + &op); + } + + nmt_newt_form_show(form); + + ac = nmt_sync_op_wait_pointer(&op, &error); + if (!ac) { + nmt_newt_message_dialog(_("Could not activate connection: %s"), error->message); + g_clear_error(&error); + goto done; + } else if (nm_active_connection_get_state(ac) == NM_ACTIVE_CONNECTION_STATE_ACTIVATED) { + /* Already active */ + goto done; + } else if (!nmt_newt_widget_get_realized(NMT_NEWT_WIDGET(form))) { + /* User already hit Esc */ + goto done; + } + + if (agent && !connection) { + connection = NM_CONNECTION(nm_active_connection_get_connection(ac)); + if (connection) { + nm_secret_agent_simple_enable(agent, nm_object_get_path(NM_OBJECT(connection))); + } + } + + /* Now wait for the connection to actually reach the ACTIVATED state, + * allowing the user to cancel if it takes too long. + */ + nmt_sync_op_init(&op); + info.active = ac; + info.device = device; + info.op = &op; + + g_signal_connect(form, "quit", G_CALLBACK(connect_cancelled), &info); + g_signal_connect(ac, + "notify::" NM_ACTIVE_CONNECTION_STATE, + G_CALLBACK(activate_ac_state_changed), + &info); + if (device) { + g_signal_connect(device, + "notify::" NM_DEVICE_STATE, + G_CALLBACK(activate_device_state_changed), + &info); + } + + if (!nmt_sync_op_wait_boolean(&op, &error)) { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + nmt_newt_message_dialog(_("Could not activate connection: %s"), error->message); + g_clear_error(&error); + } + + g_signal_handlers_disconnect_by_func(form, G_CALLBACK(connect_cancelled), &info); + g_signal_handlers_disconnect_by_func(ac, G_CALLBACK(activate_ac_state_changed), &info); + if (device) + g_signal_handlers_disconnect_by_func(device, + G_CALLBACK(activate_device_state_changed), + &info); + +done: + if (nmt_newt_widget_get_realized(NMT_NEWT_WIDGET(form))) + nmt_newt_form_quit(form); + g_object_unref(form); + + if (agent) + nm_secret_agent_old_unregister(NM_SECRET_AGENT_OLD(agent), NULL, NULL); +} + +static void +listbox_activated(NmtNewtListbox *listbox, gpointer user_data) +{ + NmtConnectConnectionList *list = NMT_CONNECT_CONNECTION_LIST(listbox); + NMConnection * connection; + NMDevice * device; + NMObject * specific_object; + NMActiveConnection * ac; + + if (!nmt_connect_connection_list_get_selection(list, + &connection, + &device, + &specific_object, + &ac)) + return; + + if (ac) + deactivate_connection(ac); + else + activate_connection(connection, device, specific_object); +} + +static void +activate_clicked(NmtNewtButton *button, gpointer listbox) +{ + listbox_activated(listbox, NULL); +} + +static void +listbox_active_changed(GObject *object, GParamSpec *pspec, gpointer button) +{ + NmtConnectConnectionList *list = NMT_CONNECT_CONNECTION_LIST(object); + static const char * activate, *deactivate; + static int deactivate_padding, activate_padding; + NMActiveConnection * ac; + gboolean has_selection; + + if (G_UNLIKELY(activate == NULL)) { + int activate_width, deactivate_width; + + activate = _("Activate"); + activate_width = nmt_newt_text_width(activate); + deactivate = _("Deactivate"); + deactivate_width = nmt_newt_text_width(deactivate); + + activate_padding = MAX(0, deactivate_width - activate_width); + deactivate_padding = MAX(0, activate_width - deactivate_width); + } + + has_selection = nmt_connect_connection_list_get_selection(list, NULL, NULL, NULL, &ac); + + nmt_newt_component_set_sensitive(button, has_selection); + if (has_selection && ac) { + nmt_newt_button_set_label(button, deactivate); + nmt_newt_widget_set_padding(button, 0, 0, deactivate_padding, 0); + } else { + nmt_newt_button_set_label(button, activate); + nmt_newt_widget_set_padding(button, 0, 0, activate_padding, 0); + } +} + +static NmtNewtForm * +nmt_connect_connection_list(gboolean is_top) +{ + int screen_width, screen_height; + NmtNewtForm * form; + NmtNewtWidget *list, *activate, *quit, *bbox, *grid; + + newtGetScreenSize(&screen_width, &screen_height); + + form = g_object_new(NMT_TYPE_NEWT_FORM, + "y", + 2, + "height", + screen_height - 4, + "escape-exits", + TRUE, + NULL); + + grid = nmt_newt_grid_new(); + + list = nmt_connect_connection_list_new(); + nmt_newt_grid_add(NMT_NEWT_GRID(grid), list, 0, 0); + nmt_newt_grid_set_flags(NMT_NEWT_GRID(grid), + list, + NMT_NEWT_GRID_FILL_X | NMT_NEWT_GRID_FILL_Y | NMT_NEWT_GRID_EXPAND_X + | NMT_NEWT_GRID_EXPAND_Y); + g_signal_connect(list, "activated", G_CALLBACK(listbox_activated), NULL); + + bbox = nmt_newt_button_box_new(NMT_NEWT_BUTTON_BOX_VERTICAL); + nmt_newt_grid_add(NMT_NEWT_GRID(grid), bbox, 1, 0); + nmt_newt_widget_set_padding(bbox, 1, 1, 0, 1); + + activate = nmt_newt_button_box_add_start(NMT_NEWT_BUTTON_BOX(bbox), _("Activate")); + g_signal_connect(list, "notify::active", G_CALLBACK(listbox_active_changed), activate); + listbox_active_changed(G_OBJECT(list), NULL, activate); + g_signal_connect(activate, "clicked", G_CALLBACK(activate_clicked), list); + + quit = nmt_newt_button_box_add_end(NMT_NEWT_BUTTON_BOX(bbox), is_top ? _("Quit") : _("Back")); + nmt_newt_widget_set_exit_on_activate(quit, TRUE); + + nmt_newt_form_set_content(form, grid); + return form; +} + +static NmtNewtForm * +nmt_connect_connection(const char *identifier) +{ + NmtNewtWidget * list; + NMConnection * connection; + NMDevice * device; + NMObject * specific_object; + NMActiveConnection *ac; + + list = nmt_connect_connection_list_new(); + if (!nmt_connect_connection_list_get_connection(NMT_CONNECT_CONNECTION_LIST(list), + identifier, + &connection, + &device, + &specific_object, + &ac)) + nmt_newt_message_dialog(_("No such connection '%s'"), identifier); + else if (ac) + nmt_newt_message_dialog(_("Connection is already active")); + else + activate_connection(connection, device, specific_object); + g_object_unref(list); + + return NULL; +} + +NmtNewtForm * +nmtui_connect(gboolean is_top, int argc, char **argv) +{ + if (argc == 2) + return nmt_connect_connection(argv[1]); + else + return nmt_connect_connection_list(is_top); +} |