askpass.c 19 KB

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