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