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