main.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
  2. /* NetworkManager Wireless Applet -- Display wireless access points and allow user control
  3. *
  4. * Dan Fruehauf <malkodan@gmail.com>
  5. *
  6. * This program is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation; either version 2 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License along
  17. * with this program; if not, write to the Free Software Foundation, Inc.,
  18. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  19. *
  20. * (C) Copyright 2013 Dan Fruehauf <malkodan@gmail.com>
  21. */
  22. #ifdef HAVE_CONFIG_H
  23. #include <config.h>
  24. #endif
  25. #include <string.h>
  26. #include <stdlib.h>
  27. #include <errno.h>
  28. #include <glib/gi18n.h>
  29. #include <gtk/gtk.h>
  30. #include <libsecret/secret.h>
  31. #include <nm-setting-vpn.h>
  32. #include <nm-vpn-plugin-utils.h>
  33. #include <nm-vpn-password-dialog.h>
  34. #include "src/nm-ssh-service.h"
  35. #define KEYRING_UUID_TAG "connection-uuid"
  36. #define KEYRING_SN_TAG "setting-name"
  37. #define KEYRING_SK_TAG "setting-key"
  38. #define UI_KEYFILE_GROUP "VPN Plugin UI"
  39. static const SecretSchema network_manager_secret_schema = {
  40. "org.freedesktop.NetworkManager.Connection",
  41. SECRET_SCHEMA_DONT_MATCH_NAME,
  42. {
  43. { KEYRING_UUID_TAG, SECRET_SCHEMA_ATTRIBUTE_STRING },
  44. { KEYRING_SN_TAG, SECRET_SCHEMA_ATTRIBUTE_STRING },
  45. { KEYRING_SK_TAG, SECRET_SCHEMA_ATTRIBUTE_STRING },
  46. { NULL, 0 },
  47. }
  48. };
  49. static char *
  50. keyring_lookup_secret (const char *uuid, const char *secret_name)
  51. {
  52. GHashTable *attrs;
  53. GList *list;
  54. char *secret = NULL;
  55. attrs = secret_attributes_build (&network_manager_secret_schema,
  56. KEYRING_UUID_TAG, uuid,
  57. KEYRING_SN_TAG, NM_SETTING_VPN_SETTING_NAME,
  58. KEYRING_SK_TAG, secret_name,
  59. NULL);
  60. list = secret_service_search_sync (NULL, &network_manager_secret_schema, attrs,
  61. SECRET_SEARCH_ALL |
  62. SECRET_SEARCH_UNLOCK |
  63. SECRET_SEARCH_LOAD_SECRETS,
  64. NULL, NULL);
  65. if (list && list->data) {
  66. SecretItem *item = list->data;
  67. SecretValue *value = secret_item_get_secret (item);
  68. if (value) {
  69. secret = g_strdup (secret_value_get (value, NULL));
  70. secret_value_unref (value);
  71. }
  72. }
  73. g_list_free_full (list, g_object_unref);
  74. g_hash_table_unref (attrs);
  75. return secret;
  76. }
  77. static void
  78. keyfile_add_entry_info (GKeyFile *keyfile,
  79. const gchar *key,
  80. const gchar *value,
  81. const gchar *label,
  82. gboolean is_secret,
  83. gboolean should_ask)
  84. {
  85. g_key_file_set_string (keyfile, key, "Value", value);
  86. g_key_file_set_string (keyfile, key, "Label", label);
  87. g_key_file_set_boolean (keyfile, key, "IsSecret", is_secret);
  88. g_key_file_set_boolean (keyfile, key, "ShouldAsk", should_ask);
  89. }
  90. static void
  91. keyfile_print_stdout (GKeyFile *keyfile)
  92. {
  93. gchar *data;
  94. gsize length;
  95. data = g_key_file_to_data (keyfile, &length, NULL);
  96. fputs (data, stdout);
  97. g_free (data);
  98. }
  99. static gboolean
  100. get_secrets (const char *vpn_uuid,
  101. const char *vpn_name,
  102. gboolean retry,
  103. gboolean allow_interaction,
  104. gboolean external_ui_mode,
  105. const char *in_pw,
  106. char **out_pw,
  107. NMSettingSecretFlags pw_flags)
  108. {
  109. NMAVpnPasswordDialog *dialog;
  110. char *prompt, *pw = NULL;
  111. const char *new_password = NULL;
  112. g_return_val_if_fail (vpn_uuid != NULL, FALSE);
  113. g_return_val_if_fail (vpn_name != NULL, FALSE);
  114. g_return_val_if_fail (out_pw != NULL, FALSE);
  115. g_return_val_if_fail (*out_pw == NULL, FALSE);
  116. /* Get the existing secret, if any */
  117. if ( !(pw_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED)
  118. && !(pw_flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED)) {
  119. if (in_pw)
  120. pw = g_strdup (in_pw);
  121. else
  122. pw = keyring_lookup_secret (vpn_uuid, NM_SSH_KEY_PASSWORD);
  123. }
  124. /* Don't ask if the passwords is unused */
  125. if (pw_flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED) {
  126. g_free (pw);
  127. return TRUE;
  128. }
  129. /* Otherwise, we have no saved password, or the password flags indicated
  130. * that the password should never be saved.
  131. */
  132. prompt = g_strdup_printf (_("You need to authenticate to access the Virtual Private Network '%s'."), vpn_name);
  133. if (external_ui_mode) {
  134. GKeyFile *keyfile;
  135. keyfile = g_key_file_new ();
  136. g_key_file_set_integer (keyfile, UI_KEYFILE_GROUP, "Version", 2);
  137. g_key_file_set_string (keyfile, UI_KEYFILE_GROUP, "Description", prompt);
  138. g_key_file_set_string (keyfile, UI_KEYFILE_GROUP, "Title", _("Authenticate VPN"));
  139. keyfile_add_entry_info (keyfile, NM_SSH_KEY_PASSWORD, pw ? pw : "", _("Password:"), TRUE, allow_interaction);
  140. keyfile_print_stdout (keyfile);
  141. g_key_file_unref (keyfile);
  142. goto out;
  143. } else if ( allow_interaction == FALSE
  144. || (!retry && pw && !(pw_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED))) {
  145. /* If interaction isn't allowed, just return existing secrets.
  146. * Also, don't ask the user if we don't need a new password (ie, !retry),
  147. * we have an existing PW, and the password is saved.
  148. */
  149. *out_pw = pw;
  150. g_free (prompt);
  151. return TRUE;
  152. }
  153. dialog = NMA_VPN_PASSWORD_DIALOG (
  154. nma_vpn_password_dialog_new (_("Authenticate VPN"), prompt, NULL));
  155. nma_vpn_password_dialog_set_show_password_secondary (dialog, FALSE);
  156. /* pre-fill dialog with the password */
  157. if (pw && !(pw_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED))
  158. nma_vpn_password_dialog_set_password (dialog, pw);
  159. gtk_widget_show (GTK_WIDGET (dialog));
  160. if (nma_vpn_password_dialog_run_and_block (dialog)) {
  161. new_password = nma_vpn_password_dialog_get_password (dialog);
  162. if (new_password)
  163. *out_pw = g_strdup (new_password);
  164. }
  165. gtk_widget_hide (GTK_WIDGET (dialog));
  166. gtk_widget_destroy (GTK_WIDGET (dialog));
  167. out:
  168. g_free (prompt);
  169. return TRUE;
  170. }
  171. static void
  172. wait_for_quit (void)
  173. {
  174. GString *str;
  175. char c;
  176. ssize_t n;
  177. time_t start;
  178. str = g_string_sized_new (10);
  179. start = time (NULL);
  180. do {
  181. errno = 0;
  182. n = read (0, &c, 1);
  183. if (n == 0 || (n < 0 && errno == EAGAIN))
  184. g_usleep (G_USEC_PER_SEC / 10);
  185. else if (n == 1) {
  186. g_string_append_c (str, c);
  187. if (strstr (str->str, "QUIT") || (str->len > 10))
  188. break;
  189. } else
  190. break;
  191. } while (time (NULL) < start + 20);
  192. g_string_free (str, TRUE);
  193. }
  194. int
  195. main (int argc, char *argv[])
  196. {
  197. gboolean retry = FALSE, allow_interaction = FALSE, external_ui_mode = FALSE;
  198. char *vpn_name = NULL, *vpn_uuid = NULL, *vpn_service = NULL, *password = NULL;
  199. const char *auth_type, *password_key;
  200. GHashTable *data = NULL, *secrets = NULL;
  201. NMSettingSecretFlags pw_flags = NM_SETTING_SECRET_FLAG_NONE;
  202. GOptionContext *context;
  203. GOptionEntry entries[] = {
  204. { "reprompt", 'r', 0, G_OPTION_ARG_NONE, &retry, "Reprompt for passwords", NULL},
  205. { "uuid", 'u', 0, G_OPTION_ARG_STRING, &vpn_uuid, "UUID of VPN connection", NULL},
  206. { "name", 'n', 0, G_OPTION_ARG_STRING, &vpn_name, "Name of VPN connection", NULL},
  207. { "service", 's', 0, G_OPTION_ARG_STRING, &vpn_service, "VPN service type", NULL},
  208. { "allow-interaction", 'i', 0, G_OPTION_ARG_NONE, &allow_interaction, "Allow user interaction", NULL},
  209. { "external-ui-mode", 0, 0, G_OPTION_ARG_NONE, &external_ui_mode, "External UI mode", NULL},
  210. { NULL }
  211. };
  212. bindtextdomain (GETTEXT_PACKAGE, NULL);
  213. bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
  214. textdomain (GETTEXT_PACKAGE);
  215. gtk_init (&argc, &argv);
  216. context = g_option_context_new ("- ssh auth dialog");
  217. g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
  218. g_option_context_parse (context, &argc, &argv, NULL);
  219. g_option_context_free (context);
  220. if (!vpn_uuid || !vpn_service || !vpn_name) {
  221. fprintf (stderr, "Have to supply ID, name, and service\n");
  222. return 1;
  223. }
  224. if (strncmp (vpn_service, NM_DBUS_SERVICE_SSH, strlen(NM_DBUS_SERVICE_SSH)) != 0) {
  225. fprintf (stderr, "This dialog only works with the '%s' service\n", NM_DBUS_SERVICE_SSH);
  226. return 1;
  227. }
  228. if (!nm_vpn_plugin_utils_read_vpn_details (0, &data, &secrets)) {
  229. fprintf (stderr, "Failed to read '%s' (%s) data and secrets from stdin.\n",
  230. vpn_name, vpn_uuid);
  231. return 1;
  232. }
  233. /* Avoid awkwardness if auth_type equals NULL, which can happen:
  234. * https://bugzilla.redhat.com/show_bug.cgi?id=1056810 */
  235. auth_type = g_hash_table_lookup (data, NM_SSH_KEY_AUTH_TYPE);
  236. if (!auth_type) {
  237. fprintf (stderr, "Authentication type not specified in configuration\n");
  238. return 1;
  239. }
  240. /* Depending on auth type see if we need a password */
  241. if (strncmp (auth_type, NM_SSH_AUTH_TYPE_PASSWORD, strlen(NM_SSH_AUTH_TYPE_PASSWORD)) == 0) {
  242. /* FIXME one day... */
  243. nm_vpn_plugin_utils_get_secret_flags (secrets, NM_SSH_KEY_PASSWORD, &pw_flags);
  244. password_key = NM_SSH_KEY_PASSWORD;
  245. if (!get_secrets (vpn_uuid, vpn_name, retry, allow_interaction, external_ui_mode,
  246. g_hash_table_lookup (secrets, NM_SSH_KEY_PASSWORD),
  247. &password,
  248. pw_flags))
  249. return 1;
  250. } else if (strncmp (auth_type, NM_SSH_AUTH_TYPE_KEY, strlen(NM_SSH_AUTH_TYPE_KEY)) == 0) {
  251. /* FIXME ask for password if key is encrypted */
  252. } else if (strncmp (auth_type, NM_SSH_AUTH_TYPE_SSH_AGENT, strlen(NM_SSH_AUTH_TYPE_SSH_AGENT)) == 0) {
  253. /* Probe the SSH agent socket */
  254. password = g_strdup (getenv (SSH_AUTH_SOCK));
  255. if (password && strlen (password)) {
  256. password_key = NM_SSH_KEY_SSH_AUTH_SOCK;
  257. } else {
  258. GtkWidget *dialog;
  259. dialog = gtk_message_dialog_new(NULL,
  260. GTK_DIALOG_MODAL,
  261. GTK_MESSAGE_WARNING,
  262. GTK_BUTTONS_OK,
  263. _("Couldn't find '%s' environment variable.\n\nIs ssh-agent running?"), SSH_AUTH_SOCK);
  264. gtk_window_set_title(GTK_WINDOW(dialog), "Warning");
  265. gtk_dialog_run(GTK_DIALOG(dialog));
  266. gtk_widget_destroy(dialog);
  267. return 1;
  268. }
  269. } else {
  270. fprintf (stderr, "Unknown authentication method required: '%s'.\n", auth_type);
  271. return 1;
  272. }
  273. if (!external_ui_mode) {
  274. /* dump the passwords to stdout */
  275. if (password)
  276. printf ("%s\n%s\n", password_key, password);
  277. printf ("\n\n");
  278. g_free (password);
  279. /* for good measure, flush stdout since Kansas is going Bye-Bye */
  280. fflush (stdout);
  281. /* Wait for quit signal */
  282. wait_for_quit ();
  283. }
  284. if (data)
  285. g_hash_table_unref (data);
  286. if (secrets)
  287. g_hash_table_unref (secrets);
  288. return 0;
  289. }