gnome-ssh-askpass2.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. /*
  2. * Copyright (c) 2000-2002 Damien Miller. All rights reserved.
  3. *
  4. * Redistribution and use in source and binary forms, with or without
  5. * modification, are permitted provided that the following conditions
  6. * are met:
  7. * 1. Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * 2. Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. *
  13. * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
  14. * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  15. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  16. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
  17. * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  18. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  19. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  20. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  21. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  22. * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  23. */
  24. /* GTK2 support by Nalin Dahyabhai <nalin@redhat.com> */
  25. /*
  26. * This is a simple GNOME SSH passphrase grabber. To use it, set the
  27. * environment variable SSH_ASKPASS to point to the location of
  28. * gnome-ssh-askpass before calling "ssh-add < /dev/null".
  29. *
  30. * There is only two run-time options: if you set the environment variable
  31. * "GNOME_SSH_ASKPASS_GRAB_SERVER=true" then gnome-ssh-askpass will grab
  32. * the X server. If you set "GNOME_SSH_ASKPASS_GRAB_POINTER=true", then the
  33. * pointer will be grabbed too. These may have some benefit to security if
  34. * you don't trust your X server. We grab the keyboard always.
  35. */
  36. #define GRAB_TRIES 16
  37. #define GRAB_WAIT 250 /* milliseconds */
  38. #define PROMPT_ENTRY 0
  39. #define PROMPT_CONFIRM 1
  40. #define PROMPT_NONE 2
  41. /*
  42. * Compile with:
  43. *
  44. * cc -Wall `pkg-config --cflags gtk+-2.0` \
  45. * gnome-ssh-askpass2.c -o gnome-ssh-askpass \
  46. * `pkg-config --libs gtk+-2.0`
  47. *
  48. */
  49. #include <stdlib.h>
  50. #include <stdio.h>
  51. #include <string.h>
  52. #include <unistd.h>
  53. #include <X11/Xlib.h>
  54. #include <glib.h>
  55. #include <gtk/gtk.h>
  56. #include <gdk/gdkx.h>
  57. #include <gdk/gdkkeysyms.h>
  58. static void
  59. report_failed_grab (GtkWidget *parent_window, const char *what)
  60. {
  61. GtkWidget *err;
  62. err = gtk_message_dialog_new(GTK_WINDOW(parent_window), 0,
  63. GTK_MESSAGE_ERROR,
  64. GTK_BUTTONS_CLOSE,
  65. "SSH password dialog could not grab the %s input.\n"
  66. "This might be caused by application such as screensaver, "
  67. "however it could also mean that someone may be eavesdropping "
  68. "on your session.\n"
  69. "Either close the application which grabs the %s or "
  70. "log out and log in again to prevent this from happening.", what, what);
  71. gtk_window_set_position(GTK_WINDOW(err), GTK_WIN_POS_CENTER);
  72. gtk_dialog_run(GTK_DIALOG(err));
  73. gtk_widget_destroy(err);
  74. }
  75. static void
  76. ok_dialog(GtkWidget *entry, gpointer dialog)
  77. {
  78. g_return_if_fail(GTK_IS_DIALOG(dialog));
  79. gtk_dialog_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
  80. }
  81. static gboolean
  82. check_none(GtkWidget *widget, GdkEventKey *event, gpointer dialog)
  83. {
  84. switch (event->keyval) {
  85. case GDK_KEY_Escape:
  86. /* esc -> close dialog */
  87. gtk_dialog_response(GTK_DIALOG(dialog), GTK_RESPONSE_CLOSE);
  88. return TRUE;
  89. case GDK_KEY_Tab:
  90. /* tab -> focus close button */
  91. gtk_widget_grab_focus(gtk_dialog_get_widget_for_response(
  92. dialog, GTK_RESPONSE_CLOSE));
  93. return TRUE;
  94. default:
  95. /* eat all other key events */
  96. return TRUE;
  97. }
  98. }
  99. static int
  100. parse_env_hex_color(const char *env, GdkColor *c)
  101. {
  102. const char *s;
  103. unsigned long ul;
  104. char *ep;
  105. size_t n;
  106. if ((s = getenv(env)) == NULL)
  107. return 0;
  108. memset(c, 0, sizeof(*c));
  109. /* Permit hex rgb or rrggbb optionally prefixed by '#' or '0x' */
  110. if (*s == '#')
  111. s++;
  112. else if (strncmp(s, "0x", 2) == 0)
  113. s += 2;
  114. n = strlen(s);
  115. if (n != 3 && n != 6)
  116. goto bad;
  117. ul = strtoul(s, &ep, 16);
  118. if (*ep != '\0' || ul > 0xffffff) {
  119. bad:
  120. fprintf(stderr, "Invalid $%s - invalid hex color code\n", env);
  121. return 0;
  122. }
  123. /* Valid hex sequence; expand into a GdkColor */
  124. if (n == 3) {
  125. /* 4-bit RGB */
  126. c->red = ((ul >> 8) & 0xf) << 12;
  127. c->green = ((ul >> 4) & 0xf) << 12;
  128. c->blue = (ul & 0xf) << 12;
  129. } else {
  130. /* 8-bit RGB */
  131. c->red = ((ul >> 16) & 0xff) << 8;
  132. c->green = ((ul >> 8) & 0xff) << 8;
  133. c->blue = (ul & 0xff) << 8;
  134. }
  135. return 1;
  136. }
  137. static void
  138. move_progress(GtkWidget *entry, gpointer progress)
  139. {
  140. gdouble step;
  141. g_return_if_fail(GTK_IS_PROGRESS_BAR(progress));
  142. step = g_random_double_range(0.03, 0.1);
  143. gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(progress), step);
  144. gtk_progress_bar_pulse(GTK_PROGRESS_BAR(progress));
  145. }
  146. static int
  147. passphrase_dialog(char *message, int prompt_type)
  148. {
  149. const char *failed;
  150. char *passphrase, *local;
  151. int result, grab_tries, grab_server, grab_pointer;
  152. int buttons, default_response;
  153. GtkWidget *parent_window, *dialog, *entry, *progress, *hbox;
  154. GdkGrabStatus status;
  155. GdkColor fg, bg;
  156. int fg_set = 0, bg_set = 0;
  157. grab_server = (getenv("GNOME_SSH_ASKPASS_GRAB_SERVER") != NULL);
  158. grab_pointer = (getenv("GNOME_SSH_ASKPASS_GRAB_POINTER") != NULL);
  159. grab_tries = 0;
  160. fg_set = parse_env_hex_color("GNOME_SSH_ASKPASS_FG_COLOR", &fg);
  161. bg_set = parse_env_hex_color("GNOME_SSH_ASKPASS_BG_COLOR", &bg);
  162. /* Create an invisible parent window so that GtkDialog doesn't
  163. * complain. */
  164. parent_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  165. switch (prompt_type) {
  166. case PROMPT_CONFIRM:
  167. buttons = GTK_BUTTONS_YES_NO;
  168. default_response = GTK_RESPONSE_YES;
  169. break;
  170. case PROMPT_NONE:
  171. buttons = GTK_BUTTONS_CLOSE;
  172. default_response = GTK_RESPONSE_CLOSE;
  173. break;
  174. default:
  175. buttons = GTK_BUTTONS_OK_CANCEL;
  176. default_response = GTK_RESPONSE_OK;
  177. break;
  178. }
  179. dialog = gtk_message_dialog_new(GTK_WINDOW(parent_window), 0,
  180. GTK_MESSAGE_QUESTION, buttons, "%s", message);
  181. gtk_window_set_title(GTK_WINDOW(dialog), "OpenSSH");
  182. gtk_window_set_position (GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
  183. gtk_window_set_keep_above(GTK_WINDOW(dialog), TRUE);
  184. gtk_dialog_set_default_response(GTK_DIALOG(dialog), default_response);
  185. gtk_window_set_keep_above(GTK_WINDOW(dialog), TRUE);
  186. if (fg_set)
  187. gtk_widget_modify_fg(dialog, GTK_STATE_NORMAL, &fg);
  188. if (bg_set)
  189. gtk_widget_modify_bg(dialog, GTK_STATE_NORMAL, &bg);
  190. if (prompt_type == PROMPT_ENTRY || prompt_type == PROMPT_NONE) {
  191. hbox = gtk_hbox_new(FALSE, 0);
  192. gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox, FALSE,
  193. FALSE, 0);
  194. gtk_widget_show(hbox);
  195. entry = gtk_entry_new();
  196. if (fg_set)
  197. gtk_widget_modify_fg(entry, GTK_STATE_NORMAL, &fg);
  198. if (bg_set)
  199. gtk_widget_modify_bg(entry, GTK_STATE_NORMAL, &bg);
  200. gtk_box_pack_start(
  201. GTK_BOX(hbox), entry, TRUE, FALSE, 0);
  202. gtk_entry_set_width_chars(GTK_ENTRY(entry), 2);
  203. gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
  204. gtk_widget_grab_focus(entry);
  205. if (prompt_type == PROMPT_ENTRY) {
  206. gtk_widget_show(entry);
  207. /* Make <enter> close dialog */
  208. g_signal_connect(G_OBJECT(entry), "activate",
  209. G_CALLBACK(ok_dialog), dialog);
  210. } else {
  211. /*
  212. * Ensure the 'close' button is not focused by default
  213. * but is still reachable via tab. This is a bit of a
  214. * hack - it uses a hidden entry that responds to a
  215. * couple of keypress events (escape and tab only).
  216. */
  217. gtk_widget_realize(entry);
  218. g_signal_connect(G_OBJECT(entry), "key_press_event",
  219. G_CALLBACK(check_none), dialog);
  220. }
  221. hbox = gtk_hbox_new(FALSE, 0);
  222. gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
  223. hbox, FALSE, FALSE, 8);
  224. gtk_widget_show(hbox);
  225. progress = gtk_progress_bar_new();
  226. gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress),
  227. "Passphrase length hidden intentionally");
  228. gtk_box_pack_start(GTK_BOX(hbox), progress, TRUE,
  229. TRUE, 5);
  230. gtk_widget_show(progress);
  231. g_signal_connect(G_OBJECT(entry), "changed",
  232. G_CALLBACK(move_progress), progress);
  233. }
  234. /* Grab focus */
  235. gtk_widget_show_now(dialog);
  236. if (grab_pointer) {
  237. for(;;) {
  238. status = gdk_pointer_grab(
  239. (gtk_widget_get_window(GTK_WIDGET(dialog))), TRUE,
  240. 0, NULL, NULL, GDK_CURRENT_TIME);
  241. if (status == GDK_GRAB_SUCCESS)
  242. break;
  243. usleep(GRAB_WAIT * 1000);
  244. if (++grab_tries > GRAB_TRIES) {
  245. failed = "mouse";
  246. goto nograb;
  247. }
  248. }
  249. }
  250. for(;;) {
  251. status = gdk_keyboard_grab(
  252. gtk_widget_get_window(GTK_WIDGET(dialog)), FALSE,
  253. GDK_CURRENT_TIME);
  254. if (status == GDK_GRAB_SUCCESS)
  255. break;
  256. usleep(GRAB_WAIT * 1000);
  257. if (++grab_tries > GRAB_TRIES) {
  258. failed = "keyboard";
  259. goto nograbkb;
  260. }
  261. }
  262. if (grab_server) {
  263. gdk_x11_grab_server();
  264. }
  265. result = gtk_dialog_run(GTK_DIALOG(dialog));
  266. /* Ungrab */
  267. if (grab_server)
  268. XUngrabServer(gdk_x11_get_default_xdisplay());
  269. if (grab_pointer)
  270. gdk_pointer_ungrab(GDK_CURRENT_TIME);
  271. gdk_keyboard_ungrab(GDK_CURRENT_TIME);
  272. gdk_flush();
  273. /* Report passphrase if user selected OK */
  274. if (prompt_type == PROMPT_ENTRY) {
  275. passphrase = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
  276. if (result == GTK_RESPONSE_OK) {
  277. local = g_locale_from_utf8(passphrase,
  278. strlen(passphrase), NULL, NULL, NULL);
  279. if (local != NULL) {
  280. puts(local);
  281. memset(local, '\0', strlen(local));
  282. g_free(local);
  283. } else {
  284. puts(passphrase);
  285. }
  286. }
  287. /* Zero passphrase in memory */
  288. memset(passphrase, '\b', strlen(passphrase));
  289. gtk_entry_set_text(GTK_ENTRY(entry), passphrase);
  290. memset(passphrase, '\0', strlen(passphrase));
  291. g_free(passphrase);
  292. }
  293. gtk_widget_destroy(dialog);
  294. if (result != GTK_RESPONSE_OK && result != GTK_RESPONSE_YES)
  295. return -1;
  296. return 0;
  297. nograbkb:
  298. /*
  299. * At least one grab failed - ungrab what we got, and report
  300. * the failure to the user. Note that XGrabServer() cannot
  301. * fail.
  302. */
  303. gdk_pointer_ungrab(GDK_CURRENT_TIME);
  304. nograb:
  305. if (grab_server)
  306. XUngrabServer(gdk_x11_get_default_xdisplay());
  307. gtk_widget_destroy(dialog);
  308. report_failed_grab(parent_window, failed);
  309. return (-1);
  310. }
  311. int
  312. main(int argc, char **argv)
  313. {
  314. char *message, *prompt_mode;
  315. int result, prompt_type = PROMPT_ENTRY;
  316. gtk_init(&argc, &argv);
  317. gtk_window_set_default_icon_from_file ("/usr/share/pixmaps/ssh-askpass-gnome.png", NULL);
  318. if (argc > 1) {
  319. message = g_strjoinv(" ", argv + 1);
  320. } else {
  321. message = g_strdup("Enter your OpenSSH passphrase:");
  322. }
  323. if ((prompt_mode = getenv("SSH_ASKPASS_PROMPT")) != NULL) {
  324. if (strcasecmp(prompt_mode, "confirm") == 0)
  325. prompt_type = PROMPT_CONFIRM;
  326. else if (strcasecmp(prompt_mode, "none") == 0)
  327. prompt_type = PROMPT_NONE;
  328. }
  329. setvbuf(stdout, 0, _IONBF, 0);
  330. result = passphrase_dialog(message, prompt_type);
  331. g_free(message);
  332. return (result);
  333. }