askpass.c 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663
  1. /*
  2. * GTK implementation of a GUI password/passphrase prompt.
  3. */
  4. #include <assert.h>
  5. #include <time.h>
  6. #include <stdlib.h>
  7. #include <unistd.h>
  8. #include <gtk/gtk.h>
  9. #include <gdk/gdk.h>
  10. #if !GTK_CHECK_VERSION(3,0,0)
  11. #include <gdk/gdkkeysyms.h>
  12. #endif
  13. #include "defs.h"
  14. #include "unifont.h"
  15. #include "gtkcompat.h"
  16. #include "gtkmisc.h"
  17. #include "putty.h"
  18. #include "ssh.h"
  19. #include "misc.h"
  20. #define N_DRAWING_AREAS 3
  21. struct drawing_area_ctx {
  22. GtkWidget *area;
  23. #ifndef DRAW_DEFAULT_CAIRO
  24. GdkColor *cols;
  25. #endif
  26. int width, height;
  27. enum { NOT_CURRENT, CURRENT, GREYED_OUT } state;
  28. };
  29. struct askpass_ctx {
  30. GtkWidget *dialog, *promptlabel;
  31. struct drawing_area_ctx drawingareas[N_DRAWING_AREAS];
  32. int active_area;
  33. #if GTK_CHECK_VERSION(2,0,0)
  34. GtkIMContext *imc;
  35. #endif
  36. #ifndef DRAW_DEFAULT_CAIRO
  37. GdkColormap *colmap;
  38. GdkColor cols[3];
  39. #endif
  40. char *error_message; /* if we finish without a passphrase */
  41. char *passphrase; /* if we finish with one */
  42. int passlen, passsize;
  43. #if GTK_CHECK_VERSION(3,20,0)
  44. GdkSeat *seat; /* for gdk_seat_grab */
  45. #elif GTK_CHECK_VERSION(3,0,0)
  46. GdkDevice *keyboard; /* for gdk_device_grab */
  47. #endif
  48. int nattempts;
  49. };
  50. static prng *keypress_prng = NULL;
  51. static void feed_keypress_prng(void *data, int size)
  52. {
  53. put_data(keypress_prng, data, size);
  54. }
  55. void random_add_noise(NoiseSourceId source, const void *noise, int length)
  56. {
  57. if (keypress_prng)
  58. prng_add_entropy(keypress_prng, source, make_ptrlen(noise, length));
  59. }
  60. static void setup_keypress_prng(void)
  61. {
  62. keypress_prng = prng_new(&ssh_sha256);
  63. prng_seed_begin(keypress_prng);
  64. noise_get_heavy(feed_keypress_prng);
  65. prng_seed_finish(keypress_prng);
  66. }
  67. static void cleanup_keypress_prng(void)
  68. {
  69. prng_free(keypress_prng);
  70. }
  71. static uint64_t keypress_prng_value(void)
  72. {
  73. /*
  74. * Don't actually put the passphrase keystrokes themselves into
  75. * the PRNG; that doesn't seem like the course of wisdom when
  76. * that's precisely what the information displayed on the screen
  77. * is trying _not_ to be correlated to.
  78. */
  79. noise_ultralight(NOISE_SOURCE_KEY, 0);
  80. uint8_t data[8];
  81. prng_read(keypress_prng, data, 8);
  82. return GET_64BIT_MSB_FIRST(data);
  83. }
  84. static int choose_new_area(int prev_area)
  85. {
  86. int reduced = keypress_prng_value() % (N_DRAWING_AREAS - 1);
  87. return (prev_area + 1 + reduced) % N_DRAWING_AREAS;
  88. }
  89. static void visually_acknowledge_keypress(struct askpass_ctx *ctx)
  90. {
  91. int new_active = choose_new_area(ctx->active_area);
  92. ctx->drawingareas[ctx->active_area].state = NOT_CURRENT;
  93. gtk_widget_queue_draw(ctx->drawingareas[ctx->active_area].area);
  94. ctx->drawingareas[new_active].state = CURRENT;
  95. gtk_widget_queue_draw(ctx->drawingareas[new_active].area);
  96. ctx->active_area = new_active;
  97. }
  98. static int last_char_len(struct askpass_ctx *ctx)
  99. {
  100. /*
  101. * GTK always encodes in UTF-8, so we can do this in a fixed way.
  102. */
  103. int i;
  104. assert(ctx->passlen > 0);
  105. i = ctx->passlen - 1;
  106. while ((unsigned)((unsigned char)ctx->passphrase[i] - 0x80) < 0x40) {
  107. if (i == 0)
  108. break;
  109. i--;
  110. }
  111. return ctx->passlen - i;
  112. }
  113. static void add_text_to_passphrase(struct askpass_ctx *ctx, gchar *str)
  114. {
  115. int len = strlen(str);
  116. if (ctx->passlen + len >= ctx->passsize) {
  117. /* Take some care with buffer expansion, because there are
  118. * pieces of passphrase in the old buffer so we should ensure
  119. * realloc doesn't leave a copy lying around in the address
  120. * space. */
  121. int oldsize = ctx->passsize;
  122. char *newbuf;
  123. ctx->passsize = (ctx->passlen + len) * 5 / 4 + 1024;
  124. newbuf = snewn(ctx->passsize, char);
  125. memcpy(newbuf, ctx->passphrase, oldsize);
  126. smemclr(ctx->passphrase, oldsize);
  127. sfree(ctx->passphrase);
  128. ctx->passphrase = newbuf;
  129. }
  130. strcpy(ctx->passphrase + ctx->passlen, str);
  131. ctx->passlen += len;
  132. visually_acknowledge_keypress(ctx);
  133. }
  134. static void cancel_askpass(struct askpass_ctx *ctx, const char *msg)
  135. {
  136. smemclr(ctx->passphrase, ctx->passsize);
  137. ctx->passphrase = NULL;
  138. ctx->error_message = dupstr(msg);
  139. gtk_main_quit();
  140. }
  141. static gboolean askpass_dialog_closed(GtkWidget *widget, GdkEvent *event,
  142. gpointer data)
  143. {
  144. struct askpass_ctx *ctx = (struct askpass_ctx *)data;
  145. cancel_askpass(ctx, "passphrase input cancelled");
  146. /* Don't destroy dialog yet, so gtk_askpass_cleanup() can do its work */
  147. return true;
  148. }
  149. static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
  150. {
  151. struct askpass_ctx *ctx = (struct askpass_ctx *)data;
  152. if (event->keyval == GDK_KEY_Return &&
  153. event->type == GDK_KEY_PRESS) {
  154. gtk_main_quit();
  155. } else if (event->keyval == GDK_KEY_Escape &&
  156. event->type == GDK_KEY_PRESS) {
  157. cancel_askpass(ctx, "passphrase input cancelled");
  158. } else {
  159. #if GTK_CHECK_VERSION(2,0,0)
  160. if (gtk_im_context_filter_keypress(ctx->imc, event))
  161. return true;
  162. #endif
  163. if (event->type == GDK_KEY_PRESS) {
  164. if (!strcmp(event->string, "\x15")) {
  165. /* Ctrl-U. Wipe out the whole line */
  166. ctx->passlen = 0;
  167. visually_acknowledge_keypress(ctx);
  168. } else if (!strcmp(event->string, "\x17")) {
  169. /* Ctrl-W. Delete back to the last space->nonspace
  170. * boundary. We interpret 'space' in a really simple
  171. * way (mimicking terminal drivers), and don't attempt
  172. * to second-guess exciting Unicode space
  173. * characters. */
  174. while (ctx->passlen > 0) {
  175. char deleted, prior;
  176. ctx->passlen -= last_char_len(ctx);
  177. deleted = ctx->passphrase[ctx->passlen];
  178. prior = (ctx->passlen == 0 ? ' ' :
  179. ctx->passphrase[ctx->passlen-1]);
  180. if (!g_ascii_isspace(deleted) && g_ascii_isspace(prior))
  181. break;
  182. }
  183. visually_acknowledge_keypress(ctx);
  184. } else if (event->keyval == GDK_KEY_BackSpace) {
  185. /* Backspace. Delete one character. */
  186. if (ctx->passlen > 0)
  187. ctx->passlen -= last_char_len(ctx);
  188. visually_acknowledge_keypress(ctx);
  189. #if !GTK_CHECK_VERSION(2,0,0)
  190. } else if (event->string[0]) {
  191. add_text_to_passphrase(ctx, event->string);
  192. #endif
  193. }
  194. }
  195. }
  196. return true;
  197. }
  198. #if GTK_CHECK_VERSION(2,0,0)
  199. static void input_method_commit_event(GtkIMContext *imc, gchar *str,
  200. gpointer data)
  201. {
  202. struct askpass_ctx *ctx = (struct askpass_ctx *)data;
  203. add_text_to_passphrase(ctx, str);
  204. }
  205. #endif
  206. static gint configure_area(GtkWidget *widget, GdkEventConfigure *event,
  207. gpointer data)
  208. {
  209. struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data;
  210. ctx->width = event->width;
  211. ctx->height = event->height;
  212. gtk_widget_queue_draw(widget);
  213. return true;
  214. }
  215. #ifdef DRAW_DEFAULT_CAIRO
  216. static void askpass_redraw_cairo(cairo_t *cr, struct drawing_area_ctx *ctx)
  217. {
  218. double rgbval = (ctx->state == CURRENT ? 0 :
  219. ctx->state == NOT_CURRENT ? 1 : 0.5);
  220. cairo_set_source_rgb(cr, rgbval, rgbval, rgbval);
  221. cairo_paint(cr);
  222. }
  223. #else
  224. static void askpass_redraw_gdk(GdkWindow *win, struct drawing_area_ctx *ctx)
  225. {
  226. GdkGC *gc = gdk_gc_new(win);
  227. gdk_gc_set_foreground(gc, &ctx->cols[ctx->state]);
  228. gdk_draw_rectangle(win, gc, true, 0, 0, ctx->width, ctx->height);
  229. gdk_gc_unref(gc);
  230. }
  231. #endif
  232. #if GTK_CHECK_VERSION(3,0,0)
  233. static gint draw_area(GtkWidget *widget, cairo_t *cr, gpointer data)
  234. {
  235. struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data;
  236. askpass_redraw_cairo(cr, ctx);
  237. return true;
  238. }
  239. #else
  240. static gint expose_area(GtkWidget *widget, GdkEventExpose *event,
  241. gpointer data)
  242. {
  243. struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data;
  244. #ifdef DRAW_DEFAULT_CAIRO
  245. cairo_t *cr = gdk_cairo_create(gtk_widget_get_window(ctx->area));
  246. askpass_redraw_cairo(cr, ctx);
  247. cairo_destroy(cr);
  248. #else
  249. askpass_redraw_gdk(gtk_widget_get_window(ctx->area), ctx);
  250. #endif
  251. return true;
  252. }
  253. #endif
  254. static gboolean try_grab_keyboard(gpointer vctx)
  255. {
  256. struct askpass_ctx *ctx = (struct askpass_ctx *)vctx;
  257. int i, ret;
  258. #if GTK_CHECK_VERSION(3,20,0)
  259. /*
  260. * Grabbing the keyboard in GTK 3.20 requires the new notion of
  261. * GdkSeat.
  262. */
  263. GdkSeat *seat;
  264. GdkWindow *gdkw = gtk_widget_get_window(ctx->dialog);
  265. if (!GDK_IS_WINDOW(gdkw) || !gdk_window_is_visible(gdkw))
  266. goto fail;
  267. seat = gdk_display_get_default_seat(
  268. gtk_widget_get_display(ctx->dialog));
  269. if (!seat)
  270. goto fail;
  271. ctx->seat = seat;
  272. ret = gdk_seat_grab(seat, gdkw, GDK_SEAT_CAPABILITY_KEYBOARD,
  273. true, NULL, NULL, NULL, NULL);
  274. /*
  275. * For some reason GDK 3.22 hides the GDK window as a side effect
  276. * of a failed grab. I've no idea why. But if we're going to retry
  277. * the grab, then we need to unhide it again or else we'll just
  278. * get GDK_GRAB_NOT_VIEWABLE on every subsequent attempt.
  279. */
  280. if (ret != GDK_GRAB_SUCCESS)
  281. gdk_window_show(gdkw);
  282. #elif GTK_CHECK_VERSION(3,0,0)
  283. /*
  284. * And it has to be done differently again prior to GTK 3.20.
  285. */
  286. GdkDeviceManager *dm;
  287. GdkDevice *pointer, *keyboard;
  288. dm = gdk_display_get_device_manager(
  289. gtk_widget_get_display(ctx->dialog));
  290. if (!dm)
  291. goto fail;
  292. pointer = gdk_device_manager_get_client_pointer(dm);
  293. if (!pointer)
  294. goto fail;
  295. keyboard = gdk_device_get_associated_device(pointer);
  296. if (!keyboard)
  297. goto fail;
  298. if (gdk_device_get_source(keyboard) != GDK_SOURCE_KEYBOARD)
  299. goto fail;
  300. ctx->keyboard = keyboard;
  301. ret = gdk_device_grab(ctx->keyboard,
  302. gtk_widget_get_window(ctx->dialog),
  303. GDK_OWNERSHIP_NONE,
  304. true,
  305. GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK,
  306. NULL,
  307. GDK_CURRENT_TIME);
  308. #else
  309. /*
  310. * It's much simpler in GTK 1 and 2!
  311. */
  312. ret = gdk_keyboard_grab(gtk_widget_get_window(ctx->dialog),
  313. false, GDK_CURRENT_TIME);
  314. #endif
  315. if (ret != GDK_GRAB_SUCCESS)
  316. goto fail;
  317. /*
  318. * Now that we've got the keyboard grab, connect up our keyboard
  319. * handlers.
  320. */
  321. #if GTK_CHECK_VERSION(2,0,0)
  322. g_signal_connect(G_OBJECT(ctx->imc), "commit",
  323. G_CALLBACK(input_method_commit_event), ctx);
  324. #endif
  325. g_signal_connect(G_OBJECT(ctx->dialog), "key_press_event",
  326. G_CALLBACK(key_event), ctx);
  327. g_signal_connect(G_OBJECT(ctx->dialog), "key_release_event",
  328. G_CALLBACK(key_event), ctx);
  329. #if GTK_CHECK_VERSION(2,0,0)
  330. gtk_im_context_set_client_window(ctx->imc,
  331. gtk_widget_get_window(ctx->dialog));
  332. #endif
  333. /*
  334. * And repaint the key-acknowledgment drawing areas as not greyed
  335. * out.
  336. */
  337. ctx->active_area = keypress_prng_value() % N_DRAWING_AREAS;
  338. for (i = 0; i < N_DRAWING_AREAS; i++) {
  339. ctx->drawingareas[i].state =
  340. (i == ctx->active_area ? CURRENT : NOT_CURRENT);
  341. gtk_widget_queue_draw(ctx->drawingareas[i].area);
  342. }
  343. return false;
  344. fail:
  345. /*
  346. * If we didn't get the grab, reschedule ourself on a timer to try
  347. * again later.
  348. *
  349. * We have to do this rather than just trying once, because there
  350. * is at least one important situation in which the grab may fail
  351. * the first time: any user who is launching an add-key operation
  352. * off some kind of window manager hotkey will almost by
  353. * definition be running this script with a keyboard grab already
  354. * active, namely the one-key grab that the WM (or whatever) uses
  355. * to detect presses of the hotkey. So at the very least we have
  356. * to give the user time to release that key.
  357. */
  358. if (++ctx->nattempts >= 4) {
  359. cancel_askpass(ctx, "unable to grab keyboard after 5 seconds");
  360. } else {
  361. g_timeout_add(1000/8, try_grab_keyboard, ctx);
  362. }
  363. return false;
  364. }
  365. void realize(GtkWidget *widget, gpointer vctx)
  366. {
  367. struct askpass_ctx *ctx = (struct askpass_ctx *)vctx;
  368. gtk_grab_add(ctx->dialog);
  369. /*
  370. * Schedule the first attempt at the keyboard grab.
  371. */
  372. ctx->nattempts = 0;
  373. #if GTK_CHECK_VERSION(3,20,0)
  374. ctx->seat = NULL;
  375. #elif GTK_CHECK_VERSION(3,0,0)
  376. ctx->keyboard = NULL;
  377. #endif
  378. g_idle_add(try_grab_keyboard, ctx);
  379. }
  380. static const char *gtk_askpass_setup(struct askpass_ctx *ctx,
  381. const char *window_title,
  382. const char *prompt_text)
  383. {
  384. int i;
  385. GtkBox *action_area;
  386. ctx->passlen = 0;
  387. ctx->passsize = 2048;
  388. ctx->passphrase = snewn(ctx->passsize, char);
  389. /*
  390. * Create widgets.
  391. */
  392. ctx->dialog = our_dialog_new();
  393. gtk_window_set_title(GTK_WINDOW(ctx->dialog), window_title);
  394. gtk_window_set_position(GTK_WINDOW(ctx->dialog), GTK_WIN_POS_CENTER);
  395. g_signal_connect(G_OBJECT(ctx->dialog), "delete-event",
  396. G_CALLBACK(askpass_dialog_closed), ctx);
  397. ctx->promptlabel = gtk_label_new(prompt_text);
  398. align_label_left(GTK_LABEL(ctx->promptlabel));
  399. gtk_widget_show(ctx->promptlabel);
  400. gtk_label_set_line_wrap(GTK_LABEL(ctx->promptlabel), true);
  401. #if GTK_CHECK_VERSION(3,0,0)
  402. gtk_label_set_width_chars(GTK_LABEL(ctx->promptlabel), 48);
  403. #endif
  404. int margin = string_width("MM");
  405. #if GTK_CHECK_VERSION(3,12,0)
  406. gtk_widget_set_margin_start(ctx->promptlabel, margin);
  407. gtk_widget_set_margin_end(ctx->promptlabel, margin);
  408. #else
  409. gtk_misc_set_padding(GTK_MISC(ctx->promptlabel), margin, 0);
  410. #endif
  411. our_dialog_add_to_content_area(GTK_WINDOW(ctx->dialog),
  412. ctx->promptlabel, true, true, 0);
  413. #if GTK_CHECK_VERSION(2,0,0)
  414. ctx->imc = gtk_im_multicontext_new();
  415. #endif
  416. #ifndef DRAW_DEFAULT_CAIRO
  417. {
  418. gboolean success[2];
  419. ctx->colmap = gdk_colormap_get_system();
  420. ctx->cols[0].red = ctx->cols[0].green = ctx->cols[0].blue = 0xFFFF;
  421. ctx->cols[1].red = ctx->cols[1].green = ctx->cols[1].blue = 0;
  422. ctx->cols[2].red = ctx->cols[2].green = ctx->cols[2].blue = 0x8000;
  423. gdk_colormap_alloc_colors(ctx->colmap, ctx->cols, 2,
  424. false, true, success);
  425. if (!success[0] || !success[1])
  426. return "unable to allocate colours";
  427. }
  428. #endif
  429. action_area = our_dialog_make_action_hbox(GTK_WINDOW(ctx->dialog));
  430. for (i = 0; i < N_DRAWING_AREAS; i++) {
  431. ctx->drawingareas[i].area = gtk_drawing_area_new();
  432. #ifndef DRAW_DEFAULT_CAIRO
  433. ctx->drawingareas[i].cols = ctx->cols;
  434. #endif
  435. ctx->drawingareas[i].state = GREYED_OUT;
  436. ctx->drawingareas[i].width = ctx->drawingareas[i].height = 0;
  437. /* It would be nice to choose this size in some more
  438. * context-sensitive way, like measuring the size of some
  439. * piece of template text. */
  440. gtk_widget_set_size_request(ctx->drawingareas[i].area, 32, 32);
  441. gtk_box_pack_end(action_area, ctx->drawingareas[i].area,
  442. true, true, 5);
  443. g_signal_connect(G_OBJECT(ctx->drawingareas[i].area),
  444. "configure_event",
  445. G_CALLBACK(configure_area),
  446. &ctx->drawingareas[i]);
  447. #if GTK_CHECK_VERSION(3,0,0)
  448. g_signal_connect(G_OBJECT(ctx->drawingareas[i].area),
  449. "draw",
  450. G_CALLBACK(draw_area),
  451. &ctx->drawingareas[i]);
  452. #else
  453. g_signal_connect(G_OBJECT(ctx->drawingareas[i].area),
  454. "expose_event",
  455. G_CALLBACK(expose_area),
  456. &ctx->drawingareas[i]);
  457. #endif
  458. #if GTK_CHECK_VERSION(3,0,0)
  459. g_object_set(G_OBJECT(ctx->drawingareas[i].area),
  460. "margin-bottom", 8, (const char *)NULL);
  461. #endif
  462. gtk_widget_show(ctx->drawingareas[i].area);
  463. }
  464. ctx->active_area = -1;
  465. /*
  466. * Arrange to receive key events. We don't really need to worry
  467. * from a UI perspective about which widget gets the events, as
  468. * long as we know which it is so we can catch them. So we'll pick
  469. * the prompt label at random, and we'll use gtk_grab_add to
  470. * ensure key events go to it.
  471. */
  472. gtk_widget_set_sensitive(ctx->dialog, true);
  473. #if GTK_CHECK_VERSION(2,0,0)
  474. gtk_window_set_keep_above(GTK_WINDOW(ctx->dialog), true);
  475. #endif
  476. /*
  477. * Wait for the key-receiving widget to actually be created, in
  478. * order to call gtk_grab_add on it.
  479. */
  480. g_signal_connect(G_OBJECT(ctx->dialog), "realize",
  481. G_CALLBACK(realize), ctx);
  482. /*
  483. * Show the window.
  484. */
  485. gtk_widget_show(ctx->dialog);
  486. return NULL;
  487. }
  488. static void gtk_askpass_cleanup(struct askpass_ctx *ctx)
  489. {
  490. #if GTK_CHECK_VERSION(3,20,0)
  491. if (ctx->seat)
  492. gdk_seat_ungrab(ctx->seat);
  493. #elif GTK_CHECK_VERSION(3,0,0)
  494. if (ctx->keyboard)
  495. gdk_device_ungrab(ctx->keyboard, GDK_CURRENT_TIME);
  496. #else
  497. gdk_keyboard_ungrab(GDK_CURRENT_TIME);
  498. #endif
  499. gtk_grab_remove(ctx->promptlabel);
  500. if (ctx->passphrase) {
  501. assert(ctx->passlen < ctx->passsize);
  502. ctx->passphrase[ctx->passlen] = '\0';
  503. }
  504. gtk_widget_destroy(ctx->dialog);
  505. }
  506. static bool setup_gtk(const char *display)
  507. {
  508. static bool gtk_initialised = false;
  509. int argc;
  510. char *real_argv[3];
  511. char **argv = real_argv;
  512. bool ret;
  513. if (gtk_initialised)
  514. return true;
  515. argc = 0;
  516. argv[argc++] = dupstr("dummy");
  517. argv[argc++] = dupprintf("--display=%s", display);
  518. argv[argc] = NULL;
  519. ret = gtk_init_check(&argc, &argv);
  520. while (argc > 0)
  521. sfree(argv[--argc]);
  522. gtk_initialised = ret;
  523. return ret;
  524. }
  525. const bool buildinfo_gtk_relevant = true;
  526. char *gtk_askpass_main(const char *display, const char *wintitle,
  527. const char *prompt, bool *success)
  528. {
  529. struct askpass_ctx ctx[1];
  530. const char *err;
  531. ctx->passphrase = NULL;
  532. ctx->error_message = NULL;
  533. /* In case gtk_init hasn't been called yet by the program */
  534. if (!setup_gtk(display)) {
  535. *success = false;
  536. return dupstr("unable to initialise GTK");
  537. }
  538. if ((err = gtk_askpass_setup(ctx, wintitle, prompt)) != NULL) {
  539. *success = false;
  540. return dupprintf("%s", err);
  541. }
  542. setup_keypress_prng();
  543. gtk_main();
  544. cleanup_keypress_prng();
  545. gtk_askpass_cleanup(ctx);
  546. if (ctx->passphrase) {
  547. *success = true;
  548. return ctx->passphrase;
  549. } else {
  550. *success = false;
  551. return ctx->error_message;
  552. }
  553. }
  554. #ifdef TEST_ASKPASS
  555. void modalfatalbox(const char *p, ...)
  556. {
  557. va_list ap;
  558. fprintf(stderr, "FATAL ERROR: ");
  559. va_start(ap, p);
  560. vfprintf(stderr, p, ap);
  561. va_end(ap);
  562. fputc('\n', stderr);
  563. exit(1);
  564. }
  565. int main(int argc, char **argv)
  566. {
  567. bool success;
  568. int exitcode;
  569. char *ret;
  570. gtk_init(&argc, &argv);
  571. if (argc != 2) {
  572. success = false;
  573. ret = dupprintf("usage: %s <prompt text>", argv[0]);
  574. } else {
  575. ret = gtk_askpass_main(NULL, "Enter passphrase", argv[1], &success);
  576. }
  577. if (!success) {
  578. fputs(ret, stderr);
  579. fputc('\n', stderr);
  580. exitcode = 1;
  581. } else {
  582. fputs(ret, stdout);
  583. fputc('\n', stdout);
  584. exitcode = 0;
  585. }
  586. smemclr(ret, strlen(ret));
  587. return exitcode;
  588. }
  589. #endif