diff --git a/gtk+-2/pinentry-gtk-2.c b/gtk+-2/pinentry-gtk-2.c index 24d3a98..9d29f52 100644 --- a/gtk+-2/pinentry-gtk-2.c +++ b/gtk+-2/pinentry-gtk-2.c @@ -1,755 +1,763 @@ /* pinentry-gtk-2.c Copyright (C) 1999 Robert Bihlmeyer Copyright (C) 2001, 2002, 2007, 2015 g10 Code GmbH Copyright (C) 2004 by Albrecht Dreß pinentry-gtk-2 is a pinentry application for the Gtk+-2 widget set. It tries to follow the Gnome Human Interface Guide as close as possible. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7 ) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wstrict-prototypes" #endif #include #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7 ) # pragma GCC diagnostic pop #endif #include #include #include #include #include #ifdef HAVE_GETOPT_H #include #else #include "getopt.h" #endif /* HAVE_GETOPT_H */ #include "gtksecentry.h" #include "pinentry.h" #ifdef FALLBACK_CURSES #include "pinentry-curses.h" #endif #define PGMNAME "pinentry-gtk2" #ifndef VERSION # define VERSION #endif static pinentry_t pinentry; static int grab_failed; static int passphrase_ok; typedef enum { CONFIRM_CANCEL, CONFIRM_OK, CONFIRM_NOTOK } confirm_value_t; static confirm_value_t confirm_value; static GtkWidget *entry; static GtkWidget *repeat_entry; static GtkWidget *error_label; static GtkWidget *qualitybar; #ifdef ENABLE_ENHANCED static GtkWidget *insure; static GtkWidget *time_out; #endif static GtkTooltips *tooltips; static gboolean got_input; /* Gnome hig small and large space in pixels. */ #define HIG_SMALL 6 #define HIG_LARGE 12 /* The text shown in the quality bar when no text is shown. This is not * the empty string, because with an empty string the height of * the quality bar is less than with a non-empty string. This results * in ugly layout changes when the text changes from non-empty to empty * and vice versa. */ #define QUALITYBAR_EMPTY_TEXT " " /* Constrain size of the window the window should not shrink beyond the requisition, and should not grow vertically. */ static void constrain_size (GtkWidget *win, GtkRequisition *req, gpointer data) { static gint width, height; GdkGeometry geo; if (req->width == width && req->height == height) return; width = req->width; height = req->height; geo.min_width = width; /* This limit is arbitrary, but INT_MAX breaks other things */ geo.max_width = 10000; geo.min_height = geo.max_height = height; gtk_window_set_geometry_hints (GTK_WINDOW (win), NULL, &geo, GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE); } /* Realize the window as transient if we grab the keyboard. This makes the window a modal dialog to the root window, which helps the window manager. See the following quote from: http://standards.freedesktop.org/wm-spec/wm-spec-1.4.html#id2512420 Implementing enhanced support for application transient windows If the WM_TRANSIENT_FOR property is set to None or Root window, the window should be treated as a transient for all other windows in the same group. It has been noted that this is a slight ICCCM violation, but as this behavior is pretty standard for many toolkits and window managers, and is extremely unlikely to break anything, it seems reasonable to document it as standard. */ static void make_transient (GtkWidget *win, GdkEvent *event, gpointer data) { GdkScreen *screen; GdkWindow *root; if (! pinentry->grab) return; /* Make window transient for the root window. */ screen = gdk_screen_get_default (); root = gdk_screen_get_root_window (screen); gdk_window_set_transient_for (win->window, root); } /* Grab the keyboard for maximum security */ static int grab_keyboard (GtkWidget *win, GdkEvent *event, gpointer data) { if (! pinentry->grab) return FALSE; if (gdk_keyboard_grab (win->window, FALSE, gdk_event_get_time (event))) { g_critical ("could not grab keyboard"); grab_failed = 1; gtk_main_quit (); } return FALSE; } /* Remove grab. */ static int ungrab_keyboard (GtkWidget *win, GdkEvent *event, gpointer data) { gdk_keyboard_ungrab (gdk_event_get_time (event)); /* Unmake window transient for the root window. */ /* gdk_window_set_transient_for cannot be used with parent = NULL to unset transient hint (unlike gtk_ version which can). Replacement code is taken from gtk_window_transient_parent_unrealized. */ gdk_property_delete (win->window, gdk_atom_intern_static_string ("WM_TRANSIENT_FOR")); return FALSE; } static int delete_event (GtkWidget *widget, GdkEvent *event, gpointer data) { pinentry->close_button = 1; gtk_main_quit (); return TRUE; } static void button_clicked (GtkWidget *widget, gpointer data) { if (data) { const char *s, *s2; /* Okay button or enter used in text field. */ #ifdef ENABLE_ENHANCED /* FIXME: This is not compatible with assuan. We can't just print stuff on stdout. */ /* if (pinentry->enhanced) */ /* printf ("Options: %s\nTimeout: %d\n\n", */ /* gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (insure)) */ /* ? "insure" : "", */ /* gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (time_out))); */ #endif s = gtk_secure_entry_get_text (GTK_SECURE_ENTRY (entry)); if (!s) s = ""; if (pinentry->repeat_passphrase && repeat_entry) { s2 = gtk_secure_entry_get_text (GTK_SECURE_ENTRY (repeat_entry)); if (!s2) s2 = ""; if (strcmp (s, s2)) { gtk_label_set_text (GTK_LABEL (error_label), pinentry->repeat_error_string? pinentry->repeat_error_string: "not correctly repeated"); gtk_widget_grab_focus (entry); return; /* again */ } pinentry->repeat_okay = 1; } passphrase_ok = 1; pinentry_setbufferlen (pinentry, strlen (s) + 1); if (pinentry->pin) strcpy (pinentry->pin, s); } gtk_main_quit (); } static void enter_callback (GtkWidget *widget, GtkWidget *anentry) { button_clicked (widget, (gpointer) CONFIRM_OK); } static void confirm_button_clicked (GtkWidget *widget, gpointer data) { confirm_value = (confirm_value_t) data; gtk_main_quit (); } static void cancel_callback (GtkAccelGroup *acc, GObject *accelerable, guint keyval, GdkModifierType modifier, gpointer data) { int confirm_mode = !!data; if (confirm_mode) confirm_button_clicked (GTK_WIDGET (accelerable), (gpointer)CONFIRM_CANCEL); else button_clicked (GTK_WIDGET (accelerable), (gpointer)CONFIRM_CANCEL); } static gchar * pinentry_utf8_validate (gchar *text) { gchar *result; if (!text) return NULL; if (g_utf8_validate (text, -1, NULL)) return g_strdup (text); /* Failure: Assume that it was encoded in the current locale and convert it to utf-8. */ result = g_locale_to_utf8 (text, -1, NULL, NULL, NULL); if (!result) { gchar *p; result = p = g_strdup (text); while (!g_utf8_validate (p, -1, (const gchar **) &p)) *p = '?'; } return result; } /* Handler called for "changed". We use it to update the quality indicator. */ static void changed_text_handler (GtkWidget *widget) { char textbuf[50]; const char *s; int length; int percent; GdkColor color = { 0, 0, 0, 0}; got_input = TRUE; if (pinentry->repeat_passphrase && repeat_entry) { gtk_secure_entry_set_text (GTK_SECURE_ENTRY (repeat_entry), ""); gtk_label_set_text (GTK_LABEL (error_label), ""); } if (!qualitybar || !pinentry->quality_bar) return; s = gtk_secure_entry_get_text (GTK_SECURE_ENTRY (widget)); if (!s) s = ""; length = strlen (s); percent = length? pinentry_inq_quality (pinentry, s, length) : 0; if (!length) { strcpy(textbuf, QUALITYBAR_EMPTY_TEXT); color.red = 0xffff; } else if (percent < 0) { snprintf (textbuf, sizeof textbuf, "(%d%%)", -percent); color.red = 0xffff; percent = -percent; } else { snprintf (textbuf, sizeof textbuf, "%d%%", percent); color.green = 0xffff; } gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (qualitybar), (double)percent/100.0); gtk_progress_bar_set_text (GTK_PROGRESS_BAR (qualitybar), textbuf); gtk_widget_modify_bg (qualitybar, GTK_STATE_PRELIGHT, &color); } #ifdef HAVE_LIBSECRET static void may_save_passphrase_toggled (GtkWidget *widget, gpointer data) { GtkToggleButton *button = GTK_TOGGLE_BUTTON (widget); pinentry_t ctx = (pinentry_t) data; ctx->may_cache_password = gtk_toggle_button_get_active (button); } #endif static gboolean timeout_cb (gpointer data) { (void)data; if (!got_input) gtk_main_quit (); return FALSE; } static GtkWidget * create_window (pinentry_t ctx, int confirm_mode) { GtkWidget *w; GtkWidget *win, *box; GtkWidget *wvbox, *chbox, *bbox; GtkAccelGroup *acc; GClosure *acc_cl; gchar *msg; tooltips = gtk_tooltips_new (); /* FIXME: check the grabbing code against the one we used with the old gpg-agent */ win = gtk_window_new (GTK_WINDOW_TOPLEVEL); acc = gtk_accel_group_new (); g_signal_connect (G_OBJECT (win), "delete_event", G_CALLBACK (delete_event), NULL); #if 0 g_signal_connect (G_OBJECT (win), "destroy", G_CALLBACK (gtk_main_quit), NULL); #endif g_signal_connect (G_OBJECT (win), "size-request", G_CALLBACK (constrain_size), NULL); if (!confirm_mode) { if (pinentry->grab) g_signal_connect (G_OBJECT (win), "realize", G_CALLBACK (make_transient), NULL); /* We need to grab the keyboard when its visible! not when its mapped (there is a difference) */ g_object_set (G_OBJECT(win), "events", GDK_VISIBILITY_NOTIFY_MASK | GDK_STRUCTURE_MASK, NULL); g_signal_connect (G_OBJECT (win), pinentry->grab ? "visibility-notify-event" : "focus-in-event", G_CALLBACK (grab_keyboard), NULL); g_signal_connect (G_OBJECT (win), pinentry->grab ? "unmap-event" : "focus-out-event", G_CALLBACK (ungrab_keyboard), NULL); } gtk_window_add_accel_group (GTK_WINDOW (win), acc); wvbox = gtk_vbox_new (FALSE, HIG_LARGE * 2); gtk_container_add (GTK_CONTAINER (win), wvbox); gtk_container_set_border_width (GTK_CONTAINER (wvbox), HIG_LARGE); chbox = gtk_hbox_new (FALSE, HIG_LARGE); gtk_box_pack_start (GTK_BOX (wvbox), chbox, FALSE, FALSE, 0); w = gtk_image_new_from_stock (GTK_STOCK_DIALOG_AUTHENTICATION, GTK_ICON_SIZE_DIALOG); gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.0); gtk_box_pack_start (GTK_BOX (chbox), w, FALSE, FALSE, 0); box = gtk_vbox_new (FALSE, HIG_SMALL); gtk_box_pack_start (GTK_BOX (chbox), box, TRUE, TRUE, 0); if (pinentry->title) { msg = pinentry_utf8_validate (pinentry->title); gtk_window_set_title (GTK_WINDOW(win), msg); } if (pinentry->description) { msg = pinentry_utf8_validate (pinentry->description); w = gtk_label_new (msg); g_free (msg); gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5); gtk_label_set_line_wrap (GTK_LABEL (w), TRUE); gtk_box_pack_start (GTK_BOX (box), w, TRUE, FALSE, 0); } if (!confirm_mode && (pinentry->error || pinentry->repeat_passphrase)) { /* With the repeat passphrase option we need to create the label in any case so that it may later be updated by the error message. */ GdkColor color = { 0, 0xffff, 0, 0 }; if (pinentry->error) msg = pinentry_utf8_validate (pinentry->error); else msg = ""; error_label = gtk_label_new (msg); if (pinentry->error) g_free (msg); gtk_misc_set_alignment (GTK_MISC (error_label), 0.0, 0.5); gtk_label_set_line_wrap (GTK_LABEL (error_label), TRUE); gtk_box_pack_start (GTK_BOX (box), error_label, TRUE, FALSE, 0); gtk_widget_modify_fg (error_label, GTK_STATE_NORMAL, &color); } qualitybar = NULL; if (!confirm_mode) { int nrow; GtkWidget* table; nrow = 1; if (pinentry->quality_bar) nrow++; if (pinentry->repeat_passphrase) nrow++; table = gtk_table_new (nrow, 2, FALSE); nrow = 0; gtk_box_pack_start (GTK_BOX (box), table, FALSE, FALSE, 0); if (pinentry->prompt) { msg = pinentry_utf8_validate (pinentry->prompt); w = gtk_label_new_with_mnemonic (msg); g_free (msg); gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5); gtk_table_attach (GTK_TABLE (table), w, 0, 1, nrow, nrow+1, GTK_FILL, GTK_FILL, 4, 0); } entry = gtk_secure_entry_new (); gtk_widget_set_size_request (entry, 200, -1); g_signal_connect (G_OBJECT (entry), "activate", G_CALLBACK (enter_callback), entry); g_signal_connect (G_OBJECT (entry), "changed", G_CALLBACK (changed_text_handler), entry); gtk_table_attach (GTK_TABLE (table), entry, 1, 2, nrow, nrow+1, GTK_EXPAND|GTK_FILL, GTK_EXPAND|GTK_FILL, 0, 0); gtk_widget_grab_focus (entry); gtk_widget_show (entry); nrow++; if (pinentry->quality_bar) { msg = pinentry_utf8_validate (pinentry->quality_bar); w = gtk_label_new (msg); g_free (msg); gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5); gtk_table_attach (GTK_TABLE (table), w, 0, 1, nrow, nrow+1, GTK_FILL, GTK_FILL, 4, 0); qualitybar = gtk_progress_bar_new(); gtk_widget_add_events (qualitybar, GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK); gtk_progress_bar_set_text (GTK_PROGRESS_BAR (qualitybar), QUALITYBAR_EMPTY_TEXT); gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (qualitybar), 0.0); if (pinentry->quality_bar_tt) gtk_tooltips_set_tip (GTK_TOOLTIPS (tooltips), qualitybar, pinentry->quality_bar_tt, ""); gtk_table_attach (GTK_TABLE (table), qualitybar, 1, 2, nrow, nrow+1, GTK_EXPAND|GTK_FILL, GTK_EXPAND|GTK_FILL, 0, 0); nrow++; } if (pinentry->repeat_passphrase) { msg = pinentry_utf8_validate (pinentry->repeat_passphrase); w = gtk_label_new (msg); g_free (msg); gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5); gtk_table_attach (GTK_TABLE (table), w, 0, 1, nrow, nrow+1, GTK_FILL, GTK_FILL, 4, 0); repeat_entry = gtk_secure_entry_new (); gtk_widget_set_size_request (repeat_entry, 200, -1); g_signal_connect (G_OBJECT (entry), "activate", G_CALLBACK (enter_callback), repeat_entry); gtk_table_attach (GTK_TABLE (table), repeat_entry, 1, 2, nrow, nrow+1, GTK_EXPAND|GTK_FILL, GTK_EXPAND|GTK_FILL, 0, 0); gtk_widget_grab_focus (entry); gtk_widget_show (entry); nrow++; } #ifdef ENABLE_ENHANCED if (pinentry->enhanced) { GtkWidget *sbox = gtk_hbox_new (FALSE, HIG_SMALL); gtk_box_pack_start (GTK_BOX (box), sbox, FALSE, FALSE, 0); w = gtk_label_new ("Forget secret after"); gtk_box_pack_start (GTK_BOX (sbox), w, FALSE, FALSE, 0); gtk_widget_show (w); time_out = gtk_spin_button_new (GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, HUGE_VAL, 1, 60, 60)), 2, 0); gtk_box_pack_start (GTK_BOX (sbox), time_out, FALSE, FALSE, 0); gtk_widget_show (time_out); w = gtk_label_new ("seconds"); gtk_box_pack_start (GTK_BOX (sbox), w, FALSE, FALSE, 0); gtk_widget_show (w); gtk_widget_show (sbox); insure = gtk_check_button_new_with_label ("ask before giving out " "secret"); gtk_box_pack_start (GTK_BOX (box), insure, FALSE, FALSE, 0); gtk_widget_show (insure); } #endif } bbox = gtk_hbutton_box_new (); gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_END); gtk_box_set_spacing (GTK_BOX (bbox), 6); gtk_box_pack_start (GTK_BOX (wvbox), bbox, TRUE, FALSE, 0); #ifdef HAVE_LIBSECRET if (ctx->allow_external_password_cache && ctx->keyinfo) /* Only show this if we can cache passwords and we have a stable key identifier. */ { - w = gtk_check_button_new_with_label ("Save passphrase using libsecret"); + if (pinentry->default_pwmngr) + { + msg = pinentry_utf8_validate (pinentry->default_pwmngr); + w = gtk_check_button_new_with_mnemonic (msg); + g_free (msg); + } + else + w = gtk_check_button_new_with_label ("Save passphrase using libsecret"); + gtk_box_pack_start (GTK_BOX (box), w, TRUE, FALSE, 0); gtk_widget_show (w); g_signal_connect (G_OBJECT (w), "toggled", G_CALLBACK (may_save_passphrase_toggled), (gpointer) ctx); } #endif if (!pinentry->one_button) { if (pinentry->cancel) { msg = pinentry_utf8_validate (pinentry->cancel); w = gtk_button_new_with_mnemonic (msg); g_free (msg); } else if (pinentry->default_cancel) { GtkWidget *image; msg = pinentry_utf8_validate (pinentry->default_cancel); w = gtk_button_new_with_mnemonic (msg); g_free (msg); image = gtk_image_new_from_stock (GTK_STOCK_CANCEL, GTK_ICON_SIZE_BUTTON); if (image) gtk_button_set_image (GTK_BUTTON (w), image); } else w = gtk_button_new_from_stock (GTK_STOCK_CANCEL); gtk_container_add (GTK_CONTAINER (bbox), w); g_signal_connect (G_OBJECT (w), "clicked", G_CALLBACK (confirm_mode ? confirm_button_clicked : button_clicked), (gpointer) CONFIRM_CANCEL); acc_cl = g_cclosure_new (G_CALLBACK (cancel_callback), (confirm_mode? "":NULL), NULL); gtk_accel_group_connect (acc, GDK_KEY_Escape, 0, 0, acc_cl); GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT); } if (confirm_mode && !pinentry->one_button && pinentry->notok) { msg = pinentry_utf8_validate (pinentry->notok); w = gtk_button_new_with_mnemonic (msg); g_free (msg); gtk_container_add (GTK_CONTAINER (bbox), w); g_signal_connect (G_OBJECT (w), "clicked", G_CALLBACK (confirm_button_clicked), (gpointer) CONFIRM_NOTOK); GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT); } if (pinentry->ok) { msg = pinentry_utf8_validate (pinentry->ok); w = gtk_button_new_with_mnemonic (msg); g_free (msg); } else if (pinentry->default_ok) { GtkWidget *image; msg = pinentry_utf8_validate (pinentry->default_ok); w = gtk_button_new_with_mnemonic (msg); g_free (msg); image = gtk_image_new_from_stock (GTK_STOCK_OK, GTK_ICON_SIZE_BUTTON); if (image) gtk_button_set_image (GTK_BUTTON (w), image); } else w = gtk_button_new_from_stock (GTK_STOCK_OK); gtk_container_add (GTK_CONTAINER(bbox), w); if (!confirm_mode) { g_signal_connect (G_OBJECT (w), "clicked", G_CALLBACK (button_clicked), "ok"); GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT); gtk_widget_grab_default (w); g_signal_connect_object (G_OBJECT (entry), "focus_in_event", G_CALLBACK (gtk_widget_grab_default), G_OBJECT (w), 0); } else { g_signal_connect (G_OBJECT (w), "clicked", G_CALLBACK(confirm_button_clicked), (gpointer) CONFIRM_OK); GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT); } gtk_window_set_position (GTK_WINDOW (win), GTK_WIN_POS_CENTER); gtk_window_set_keep_above (GTK_WINDOW (win), TRUE); gtk_widget_show_all (win); gtk_window_present (GTK_WINDOW (win)); /* Make sure it has the focus. */ if (pinentry->timeout > 0) g_timeout_add (pinentry->timeout*1000, timeout_cb, NULL); return win; } static int gtk_cmd_handler (pinentry_t pe) { GtkWidget *w; int want_pass = !!pe->pin; got_input = FALSE; pinentry = pe; confirm_value = CONFIRM_CANCEL; passphrase_ok = 0; w = create_window (pe, want_pass ? 0 : 1); gtk_main (); gtk_widget_destroy (w); while (gtk_events_pending ()) gtk_main_iteration (); if (confirm_value == CONFIRM_CANCEL || grab_failed) pe->canceled = 1; pinentry = NULL; if (want_pass) { if (passphrase_ok && pe->pin) return strlen (pe->pin); else return -1; } else return (confirm_value == CONFIRM_OK) ? 1 : 0; } pinentry_cmd_handler_t pinentry_cmd_handler = gtk_cmd_handler; int main (int argc, char *argv[]) { static GMemVTable secure_mem = { secentry_malloc, secentry_realloc, secentry_free, NULL, NULL, NULL }; g_mem_set_vtable (&secure_mem); pinentry_init (PGMNAME); #ifdef FALLBACK_CURSES if (pinentry_have_display (argc, argv)) gtk_init (&argc, &argv); else pinentry_cmd_handler = curses_cmd_handler; #else gtk_init (&argc, &argv); #endif pinentry_parse_opts (argc, argv); if (pinentry_loop ()) return 1; return 0; } diff --git a/pinentry/pinentry.c b/pinentry/pinentry.c index 7b3fde5..16e634c 100644 --- a/pinentry/pinentry.c +++ b/pinentry/pinentry.c @@ -1,1304 +1,1311 @@ /* pinentry.c - The PIN entry support library Copyright (C) 2002, 2003, 2007, 2008, 2010, 2015 g10 Code GmbH This file is part of PINENTRY. PINENTRY is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. PINENTRY is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #ifdef HAVE_CONFIG_H #include #endif #ifndef HAVE_W32CE_SYSTEM # include #endif #include #include #include #ifndef HAVE_W32CE_SYSTEM # include #endif #ifdef HAVE_LANGINFO_H #include #endif #include #ifdef HAVE_W32CE_SYSTEM # include #endif #if defined FALLBACK_CURSES || defined PINENTRY_CURSES || defined PINENTRY_GTK #include #endif #include "assuan.h" #include "memory.h" #include "secmem-util.h" #include "argparse.h" #include "pinentry.h" #include "password-cache.h" #ifdef HAVE_W32CE_SYSTEM #define getpid() GetCurrentProcessId () #endif /* Keep the name of our program here. */ static char this_pgmname[50]; struct pinentry pinentry = { NULL, /* Title. */ NULL, /* Description. */ NULL, /* Error. */ NULL, /* Prompt. */ NULL, /* Ok button. */ NULL, /* Not-Ok button. */ NULL, /* Cancel button. */ NULL, /* PIN. */ 2048, /* PIN length. */ 0, /* pin_from_cache. */ 0, /* Display. */ 0, /* TTY name. */ 0, /* TTY type. */ 0, /* TTY LC_CTYPE. */ 0, /* TTY LC_MESSAGES. */ 0, /* Debug mode. */ 60, /* Pinentry timeout in seconds. */ #ifdef ENABLE_ENHANCED 0, /* Enhanced mode. */ #endif 1, /* Global grab. */ 0, /* Parent Window ID. */ NULL, /* Touch file. */ 0, /* Result. */ 0, /* Canceled. */ 0, /* Close button flag. */ 0, /* Locale error flag. */ 0, /* One-button flag. */ NULL, /* Repeat passphrase flag. */ NULL, /* Repeat error string. */ 0, /* Correctly repeated flag. */ NULL, /* Quality-Bar flag and description. */ NULL, /* Quality-Bar tooltip. */ PINENTRY_COLOR_DEFAULT, 0, PINENTRY_COLOR_DEFAULT, PINENTRY_COLOR_DEFAULT, 0, NULL, /* default_ok */ NULL, /* default_cancel */ NULL, /* default_prompt */ + NULL, /* default_pwmngr */ 0, /* allow_external_password_cache. */ 0, /* tried_password_cached. */ NULL, /* keyinfo */ 0, /* may_cache_password. */ NULL /* Assuan context. */ }; #if defined FALLBACK_CURSES || defined PINENTRY_CURSES || defined PINENTRY_GTK char * pinentry_utf8_to_local (const char *lc_ctype, const char *text) { iconv_t cd; const char *input = text; size_t input_len = strlen (text) + 1; char *output; size_t output_len; char *output_buf; size_t processed; char *old_ctype; char *target_encoding; /* If no locale setting could be determined, simply copy the string. */ if (!lc_ctype) { fprintf (stderr, "%s: no LC_CTYPE known - assuming UTF-8\n", this_pgmname); return strdup (text); } old_ctype = strdup (setlocale (LC_CTYPE, NULL)); if (!old_ctype) return NULL; setlocale (LC_CTYPE, lc_ctype); target_encoding = nl_langinfo (CODESET); if (!target_encoding) target_encoding = "?"; setlocale (LC_CTYPE, old_ctype); free (old_ctype); /* This is overkill, but simplifies the iconv invocation greatly. */ output_len = input_len * MB_LEN_MAX; output_buf = output = malloc (output_len); if (!output) return NULL; cd = iconv_open (target_encoding, "UTF-8"); if (cd == (iconv_t) -1) { fprintf (stderr, "%s: can't convert from UTF-8 to %s: %s\n", this_pgmname, target_encoding, strerror (errno)); free (output_buf); return NULL; } processed = iconv (cd, (ICONV_CONST char **)&input, &input_len, &output, &output_len); iconv_close (cd); if (processed == (size_t) -1 || input_len) { fprintf (stderr, "%s: error converting from UTF-8 to %s: %s\n", this_pgmname, target_encoding, strerror (errno)); free (output_buf); return NULL; } return output_buf; } /* Convert TEXT which is encoded according to LC_CTYPE to UTF-8. With SECURE set to true, use secure memory for the returned buffer. Return NULL on error. */ char * pinentry_local_to_utf8 (char *lc_ctype, char *text, int secure) { char *old_ctype; char *source_encoding; iconv_t cd; const char *input = text; size_t input_len = strlen (text) + 1; char *output; size_t output_len; char *output_buf; size_t processed; /* If no locale setting could be determined, simply copy the string. */ if (!lc_ctype) { fprintf (stderr, "%s: no LC_CTYPE known - assuming UTF-8\n", this_pgmname); output_buf = secure? secmem_malloc (input_len) : malloc (input_len); if (output_buf) strcpy (output_buf, input); return output_buf; } old_ctype = strdup (setlocale (LC_CTYPE, NULL)); if (!old_ctype) return NULL; setlocale (LC_CTYPE, lc_ctype); source_encoding = nl_langinfo (CODESET); setlocale (LC_CTYPE, old_ctype); free (old_ctype); /* This is overkill, but simplifies the iconv invocation greatly. */ output_len = input_len * MB_LEN_MAX; output_buf = output = secure? secmem_malloc (output_len):malloc (output_len); if (!output) return NULL; cd = iconv_open ("UTF-8", source_encoding); if (cd == (iconv_t) -1) { fprintf (stderr, "%s: can't convert from %s to UTF-8: %s\n", this_pgmname, source_encoding? source_encoding : "?", strerror (errno)); if (secure) secmem_free (output_buf); else free (output_buf); return NULL; } processed = iconv (cd, (ICONV_CONST char **)&input, &input_len, &output, &output_len); iconv_close (cd); if (processed == (size_t) -1 || input_len) { fprintf (stderr, "%s: error converting from %s to UTF-8: %s\n", this_pgmname, source_encoding? source_encoding : "?", strerror (errno)); if (secure) secmem_free (output_buf); else free (output_buf); return NULL; } return output_buf; } #endif /* Copy TEXT or TEXTLEN to BUFFER and escape as required. Return a pointer to the end of the new buffer. Note that BUFFER must be large enough to keep the entire text; allocataing it 3 times of TEXTLEN is sufficient. */ static char * copy_and_escape (char *buffer, const void *text, size_t textlen) { int i; const unsigned char *s = (unsigned char *)text; char *p = buffer; for (i=0; i < textlen; i++) { if (s[i] < ' ' || s[i] == '+') { snprintf (p, 4, "%%%02X", s[i]); p += 3; } else if (s[i] == ' ') *p++ = '+'; else *p++ = s[i]; } return p; } /* Run a quality inquiry for PASSPHRASE of LENGTH. (We need LENGTH because not all backends might be able to return a proper C-string.). Returns: A value between -100 and 100 to give an estimate of the passphrase's quality. Negative values are use if the caller won't even accept that passphrase. Note that we expect just one data line which should not be escaped in any represent a numeric signed decimal value. Extra data is currently ignored but should not be send at all. */ int pinentry_inq_quality (pinentry_t pin, const char *passphrase, size_t length) { ASSUAN_CONTEXT ctx = pin->ctx_assuan; const char prefix[] = "INQUIRE QUALITY "; char *command; char *line; size_t linelen; int gotvalue = 0; int value = 0; int rc; if (!ctx) return 0; /* Can't run the callback. */ if (length > 300) length = 300; /* Limit so that it definitely fits into an Assuan line. */ command = secmem_malloc (strlen (prefix) + 3*length + 1); if (!command) return 0; strcpy (command, prefix); copy_and_escape (command + strlen(command), passphrase, length); rc = assuan_write_line (ctx, command); secmem_free (command); if (rc) { fprintf (stderr, "ASSUAN WRITE LINE failed: rc=%d\n", rc); return 0; } for (;;) { do { rc = assuan_read_line (ctx, &line, &linelen); if (rc) { fprintf (stderr, "ASSUAN READ LINE failed: rc=%d\n", rc); return 0; } } while (*line == '#' || !linelen); if (line[0] == 'E' && line[1] == 'N' && line[2] == 'D' && (!line[3] || line[3] == ' ')) break; /* END command received*/ if (line[0] == 'C' && line[1] == 'A' && line[2] == 'N' && (!line[3] || line[3] == ' ')) break; /* CAN command received*/ if (line[0] == 'E' && line[1] == 'R' && line[2] == 'R' && (!line[3] || line[3] == ' ')) break; /* ERR command received*/ if (line[0] != 'D' || line[1] != ' ' || linelen < 3 || gotvalue) continue; gotvalue = 1; value = atoi (line+2); } if (value < -100) value = -100; else if (value > 100) value = 100; return value; } /* Try to make room for at least LEN bytes in the pinentry. Returns new buffer on success and 0 on failure or when the old buffer is sufficient. */ char * pinentry_setbufferlen (pinentry_t pin, int len) { char *newp; if (len < pinentry.pin_len) return NULL; newp = secmem_realloc (pin->pin, 2 * pin->pin_len); if (newp) { pin->pin = newp; pin->pin_len *= 2; } else { secmem_free (pin->pin); pin->pin = 0; pin->pin_len = 0; } return newp; } /* Initialize the secure memory subsystem, drop privileges and return. Must be called early. */ void pinentry_init (const char *pgmname) { /* Store away our name. */ if (strlen (pgmname) > sizeof this_pgmname - 2) abort (); strcpy (this_pgmname, pgmname); /* Initialize secure memory. 1 is too small, so the default size will be used. */ secmem_init (1); secmem_set_flags (SECMEM_WARN); drop_privs (); if (atexit (secmem_term)) /* FIXME: Could not register at-exit function, bail out. */ ; assuan_set_malloc_hooks (secmem_malloc, secmem_realloc, secmem_free); } /* Simple test to check whether DISPLAY is set or the option --display was given. Used to decide whether the GUI or curses should be initialized. */ int pinentry_have_display (int argc, char **argv) { #ifndef HAVE_W32CE_SYSTEM const char *s; s = getenv ("DISPLAY"); if (s && *s) return 1; #endif for (; argc; argc--, argv++) if (!strcmp (*argv, "--display") || !strncmp (*argv, "--display=", 10)) return 1; return 0; } /* Print usage information and and provide strings for help. */ static const char * my_strusage( int level ) { const char *p; switch (level) { case 11: p = this_pgmname; break; case 12: p = "pinentry"; break; case 13: p = PACKAGE_VERSION; break; case 14: p = "Copyright (C) 2015 g10 Code GmbH"; break; case 19: p = "Please report bugs to <" PACKAGE_BUGREPORT ">.\n"; break; case 1: case 40: { static char *str; if (!str) { size_t n = 50 + strlen (this_pgmname); str = malloc (n); if (str) snprintf (str, n, "Usage: %s [options] (-h for help)", this_pgmname); } p = str; } break; case 41: p = "Ask securely for a secret and print it to stdout."; break; case 42: p = "1"; /* Flag print 40 as part of 41. */ break; default: p = NULL; break; } return p; } char * parse_color (char *arg, pinentry_color_t *color_p, int *bright_p) { static struct { const char *name; pinentry_color_t color; } colors[] = { { "none", PINENTRY_COLOR_NONE }, { "default", PINENTRY_COLOR_DEFAULT }, { "black", PINENTRY_COLOR_BLACK }, { "red", PINENTRY_COLOR_RED }, { "green", PINENTRY_COLOR_GREEN }, { "yellow", PINENTRY_COLOR_YELLOW }, { "blue", PINENTRY_COLOR_BLUE }, { "magenta", PINENTRY_COLOR_MAGENTA }, { "cyan", PINENTRY_COLOR_CYAN }, { "white", PINENTRY_COLOR_WHITE } }; int i; char *new_arg; pinentry_color_t color = PINENTRY_COLOR_DEFAULT; if (!arg) return NULL; new_arg = strchr (arg, ','); if (new_arg) new_arg++; if (bright_p) { const char *bname[] = { "bright-", "bright", "bold-", "bold" }; *bright_p = 0; for (i = 0; i < sizeof (bname) / sizeof (bname[0]); i++) if (!strncasecmp (arg, bname[i], strlen (bname[i]))) { *bright_p = 1; arg += strlen (bname[i]); } } for (i = 0; i < sizeof (colors) / sizeof (colors[0]); i++) if (!strncasecmp (arg, colors[i].name, strlen (colors[i].name))) color = colors[i].color; *color_p = color; return new_arg; } /* Parse the command line options. May exit the program if only help or version output is requested. */ void pinentry_parse_opts (int argc, char *argv[]) { static ARGPARSE_OPTS opts[] = { ARGPARSE_s_n('d', "debug", "Turn on debugging output"), ARGPARSE_s_s('D', "display", "|DISPLAY|Set the X display"), ARGPARSE_s_s('T', "ttyname", "|FILE|Set the tty terminal node name"), ARGPARSE_s_s('N', "ttytype", "|NAME|Set the tty terminal type"), ARGPARSE_s_s('C', "lc-ctype", "|STRING|Set the tty LC_CTYPE value"), ARGPARSE_s_s('M', "lc-messages", "|STRING|Set the tty LC_MESSAGES value"), #ifdef ENABLE_ENHANCED ARGPARSE_s_n('e', "enhanced", "Ask for timeout and insurance, too"), #endif ARGPARSE_s_i('o', "timeout", "|SECS|Timeout waiting for input after this many seconds"), ARGPARSE_s_n('g', "no-global-grab", "Grab keyboard only while window is focused"), ARGPARSE_s_u('W', "parent-wid", "Parent window ID (for positioning)"), ARGPARSE_s_s('c', "colors", "|STRING|Set custom colors for ncurses"), ARGPARSE_end() }; ARGPARSE_ARGS pargs = { &argc, &argv, 0 }; set_strusage (my_strusage); while (arg_parse (&pargs, opts)) { switch (pargs.r_opt) { case 'd': pinentry.debug = 1; break; #ifdef ENABLE_ENHANCED case 'e': pinentry.enhanced = 1; break; #endif case 'g': pinentry.grab = 0; break; case 'D': /* Note, this is currently not used because the GUI engine has already been initialized when parsing these options. */ pinentry.display = strdup (pargs.r.ret_str); if (!pinentry.display) { #ifndef HAVE_W32CE_SYSTEM fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno)); #endif exit (EXIT_FAILURE); } break; case 'T': pinentry.ttyname = strdup (pargs.r.ret_str); if (!pinentry.ttyname) { #ifndef HAVE_W32CE_SYSTEM fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno)); #endif exit (EXIT_FAILURE); } break; case 'N': pinentry.ttytype = strdup (pargs.r.ret_str); if (!pinentry.ttytype) { #ifndef HAVE_W32CE_SYSTEM fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno)); #endif exit (EXIT_FAILURE); } break; case 'C': pinentry.lc_ctype = strdup (pargs.r.ret_str); if (!pinentry.lc_ctype) { #ifndef HAVE_W32CE_SYSTEM fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno)); #endif exit (EXIT_FAILURE); } break; case 'M': pinentry.lc_messages = strdup (pargs.r.ret_str); if (!pinentry.lc_messages) { #ifndef HAVE_W32CE_SYSTEM fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno)); #endif exit (EXIT_FAILURE); } break; case 'W': pinentry.parent_wid = pargs.r.ret_ulong; break; case 'c': { char *tmpstr = pargs.r.ret_str; tmpstr = parse_color (tmpstr, &pinentry.color_fg, &pinentry.color_fg_bright); tmpstr = parse_color (tmpstr, &pinentry.color_bg, NULL); tmpstr = parse_color (tmpstr, &pinentry.color_so, &pinentry.color_so_bright); } break; case 'o': pinentry.timeout = pargs.r.ret_int; break; default: pargs.err = ARGPARSE_PRINT_WARNING; break; } } } static int option_handler (ASSUAN_CONTEXT ctx, const char *key, const char *value) { if (!strcmp (key, "no-grab") && !*value) pinentry.grab = 0; else if (!strcmp (key, "grab") && !*value) pinentry.grab = 1; else if (!strcmp (key, "debug-wait")) { #ifndef HAVE_W32CE_SYSTEM fprintf (stderr, "%s: waiting for debugger - my pid is %u ...\n", this_pgmname, (unsigned int) getpid()); sleep (*value?atoi (value):5); fprintf (stderr, "%s: ... okay\n", this_pgmname); #endif } else if (!strcmp (key, "display")) { if (pinentry.display) free (pinentry.display); pinentry.display = strdup (value); if (!pinentry.display) return ASSUAN_Out_Of_Core; } else if (!strcmp (key, "ttyname")) { if (pinentry.ttyname) free (pinentry.ttyname); pinentry.ttyname = strdup (value); if (!pinentry.ttyname) return ASSUAN_Out_Of_Core; } else if (!strcmp (key, "ttytype")) { if (pinentry.ttytype) free (pinentry.ttytype); pinentry.ttytype = strdup (value); if (!pinentry.ttytype) return ASSUAN_Out_Of_Core; } else if (!strcmp (key, "lc-ctype")) { if (pinentry.lc_ctype) free (pinentry.lc_ctype); pinentry.lc_ctype = strdup (value); if (!pinentry.lc_ctype) return ASSUAN_Out_Of_Core; } else if (!strcmp (key, "lc-messages")) { if (pinentry.lc_messages) free (pinentry.lc_messages); pinentry.lc_messages = strdup (value); if (!pinentry.lc_messages) return ASSUAN_Out_Of_Core; } else if (!strcmp (key, "parent-wid")) { pinentry.parent_wid = atoi (value); /* FIXME: Use strtol and add some error handling. */ } else if (!strcmp (key, "touch-file")) { if (pinentry.touch_file) free (pinentry.touch_file); pinentry.touch_file = strdup (value); if (!pinentry.touch_file) return ASSUAN_Out_Of_Core; } else if (!strcmp (key, "default-ok")) { pinentry.default_ok = strdup (value); if (!pinentry.default_ok) return ASSUAN_Out_Of_Core; } else if (!strcmp (key, "default-cancel")) { pinentry.default_cancel = strdup (value); if (!pinentry.default_cancel) return ASSUAN_Out_Of_Core; } else if (!strcmp (key, "default-prompt")) { pinentry.default_prompt = strdup (value); if (!pinentry.default_prompt) return ASSUAN_Out_Of_Core; } + else if (!strcmp (key, "default-pwmngr")) + { + pinentry.default_pwmngr = strdup (value); + if (!pinentry.default_pwmngr) + return ASSUAN_Out_Of_Core; + } else if (!strcmp (key, "allow-external-password-cache") && !*value) { pinentry.allow_external_password_cache = 1; pinentry.tried_password_cache = 0; } else return ASSUAN_Invalid_Option; return 0; } /* Note, that it is sufficient to allocate the target string D as long as the source string S, i.e.: strlen(s)+1; */ static void strcpy_escaped (char *d, const char *s) { while (*s) { if (*s == '%' && s[1] && s[2]) { s++; *d++ = xtoi_2 ( s); s += 2; } else *d++ = *s++; } *d = 0; } static int cmd_setdesc (ASSUAN_CONTEXT ctx, char *line) { char *newd; newd = malloc (strlen (line) + 1); if (!newd) return ASSUAN_Out_Of_Core; strcpy_escaped (newd, line); if (pinentry.description) free (pinentry.description); pinentry.description = newd; return 0; } static int cmd_setprompt (ASSUAN_CONTEXT ctx, char *line) { char *newp; newp = malloc (strlen (line) + 1); if (!newp) return ASSUAN_Out_Of_Core; strcpy_escaped (newp, line); if (pinentry.prompt) free (pinentry.prompt); pinentry.prompt = newp; return 0; } /* The data provided at LINE may be used by pinentry implementations to identify a key for caching strategies of its own. The empty string and --clear mean that the key does not have a stable identifier. */ static int cmd_setkeyinfo (ASSUAN_CONTEXT ctx, char *line) { if (pinentry.keyinfo) free (pinentry.keyinfo); if (*line && strcmp(line, "--clear") != 0) pinentry.keyinfo = strdup (line); else pinentry.keyinfo = NULL; return 0; } static int cmd_setrepeat (ASSUAN_CONTEXT ctx, char *line) { char *p; p = malloc (strlen (line) + 1); if (!p) return ASSUAN_Out_Of_Core; strcpy_escaped (p, line); free (pinentry.repeat_passphrase); pinentry.repeat_passphrase = p; return 0; } static int cmd_setrepeaterror (ASSUAN_CONTEXT ctx, char *line) { char *p; p = malloc (strlen (line) + 1); if (!p) return ASSUAN_Out_Of_Core; strcpy_escaped (p, line); free (pinentry.repeat_error_string); pinentry.repeat_error_string = p; return 0; } static int cmd_seterror (ASSUAN_CONTEXT ctx, char *line) { char *newe; newe = malloc (strlen (line) + 1); if (!newe) return ASSUAN_Out_Of_Core; strcpy_escaped (newe, line); if (pinentry.error) free (pinentry.error); pinentry.error = newe; return 0; } static int cmd_setok (ASSUAN_CONTEXT ctx, char *line) { char *newo; newo = malloc (strlen (line) + 1); if (!newo) return ASSUAN_Out_Of_Core; strcpy_escaped (newo, line); if (pinentry.ok) free (pinentry.ok); pinentry.ok = newo; return 0; } static int cmd_setnotok (ASSUAN_CONTEXT ctx, char *line) { char *newo; newo = malloc (strlen (line) + 1); if (!newo) return ASSUAN_Out_Of_Core; strcpy_escaped (newo, line); if (pinentry.notok) free (pinentry.notok); pinentry.notok = newo; return 0; } static int cmd_setcancel (ASSUAN_CONTEXT ctx, char *line) { char *newc; newc = malloc (strlen (line) + 1); if (!newc) return ASSUAN_Out_Of_Core; strcpy_escaped (newc, line); if (pinentry.cancel) free (pinentry.cancel); pinentry.cancel = newc; return 0; } static int cmd_settimeout (ASSUAN_CONTEXT ctx, char *line) { if (line && *line) pinentry.timeout = atoi(line); return 0; } static int cmd_settitle (ASSUAN_CONTEXT ctx, char *line) { char *newt; newt = malloc (strlen (line) + 1); if (!newt) return ASSUAN_Out_Of_Core; strcpy_escaped (newt, line); if (pinentry.title) free (pinentry.title); pinentry.title = newt; return 0; } static int cmd_setqualitybar (ASSUAN_CONTEXT ctx, char *line) { char *newval; if (!*line) line = "Quality:"; newval = malloc (strlen (line) + 1); if (!newval) return ASSUAN_Out_Of_Core; strcpy_escaped (newval, line); if (pinentry.quality_bar) free (pinentry.quality_bar); pinentry.quality_bar = newval; return 0; } /* Set the tooltip to be used for a quality bar. */ static int cmd_setqualitybar_tt (ASSUAN_CONTEXT ctx, char *line) { char *newval; if (*line) { newval = malloc (strlen (line) + 1); if (!newval) return ASSUAN_Out_Of_Core; strcpy_escaped (newval, line); } else newval = NULL; if (pinentry.quality_bar_tt) free (pinentry.quality_bar_tt); pinentry.quality_bar_tt = newval; return 0; } static int cmd_getpin (ASSUAN_CONTEXT ctx, char *line) { int result; int set_prompt = 0; int just_read_password_from_cache = 0; pinentry.pin = secmem_malloc (pinentry.pin_len); if (!pinentry.pin) return ASSUAN_Out_Of_Core; /* Try reading from the password cache. */ if (/* If repeat passphrase is set, then we don't want to read from the cache. */ ! pinentry.repeat_passphrase /* Are we allowed to read from the cache? */ && pinentry.allow_external_password_cache && pinentry.keyinfo /* Only read from the cache if we haven't already tried it. */ && ! pinentry.tried_password_cache) { char *password; pinentry.tried_password_cache = 1; password = password_cache_lookup (pinentry.keyinfo); if (password) /* There is a cached password. Try it. */ { int len = strlen(password) + 1; if (len > pinentry.pin_len) len = pinentry.pin_len; memcpy (pinentry.pin, password, len); pinentry.pin[len] = '\0'; secmem_free (password); pinentry.pin_from_cache = 1; assuan_write_status (ctx, "PASSWORD_FROM_CACHE", ""); /* Result is the length of the password not including the NUL terminator. */ result = len - 1; just_read_password_from_cache = 1; goto out; } } /* The password was not cached (or we are not allowed to / cannot use the cache). Prompt the user. */ pinentry.pin_from_cache = 0; if (!pinentry.prompt) { pinentry.prompt = pinentry.default_prompt?pinentry.default_prompt:"PIN:"; set_prompt = 1; } pinentry.locale_err = 0; pinentry.close_button = 0; pinentry.repeat_okay = 0; pinentry.one_button = 0; pinentry.ctx_assuan = ctx; result = (*pinentry_cmd_handler) (&pinentry); pinentry.ctx_assuan = NULL; if (pinentry.error) { free (pinentry.error); pinentry.error = NULL; } if (pinentry.repeat_passphrase) { free (pinentry.repeat_passphrase); pinentry.repeat_passphrase = NULL; } if (set_prompt) pinentry.prompt = NULL; pinentry.quality_bar = 0; /* Reset it after the command. */ if (pinentry.close_button) assuan_write_status (ctx, "BUTTON_INFO", "close"); if (result < 0) { if (pinentry.pin) { secmem_free (pinentry.pin); pinentry.pin = NULL; } return pinentry.locale_err? ASSUAN_Locale_Problem: ASSUAN_Canceled; } out: if (result) { if (pinentry.repeat_okay) assuan_write_status (ctx, "PIN_REPEATED", ""); result = assuan_send_data (ctx, pinentry.pin, result); if (!result) result = assuan_send_data (ctx, NULL, 0); if (/* GPG Agent says it's okay. */ pinentry.allow_external_password_cache && pinentry.keyinfo /* We didn't just read it from the cache. */ && ! just_read_password_from_cache /* And the user said it's okay. */ && pinentry.may_cache_password) /* Cache the password. */ password_cache_save (pinentry.keyinfo, pinentry.pin); } if (pinentry.pin) { secmem_free (pinentry.pin); pinentry.pin = NULL; } return result; } /* Note that the option --one-button is a hack to allow the use of old pinentries while the caller is ignoring the result. Given that options have never been used or flagged as an error the new option is an easy way to enable the messsage mode while not requiring to update pinentry or to have the caller test for the message command. New applications which are free to require an updated pinentry should use MESSAGE instead. */ static int cmd_confirm (ASSUAN_CONTEXT ctx, char *line) { int result; pinentry.one_button = !!strstr (line, "--one-button"); pinentry.quality_bar = 0; pinentry.close_button = 0; pinentry.locale_err = 0; pinentry.canceled = 0; result = (*pinentry_cmd_handler) (&pinentry); if (pinentry.error) { free (pinentry.error); pinentry.error = NULL; } if (pinentry.close_button) assuan_write_status (ctx, "BUTTON_INFO", "close"); return result ? 0 : (pinentry.locale_err? ASSUAN_Locale_Problem : (pinentry.one_button ? 0 : (pinentry.canceled ? ASSUAN_Canceled : ASSUAN_Not_Confirmed))); } static int cmd_message (ASSUAN_CONTEXT ctx, char *line) { int result; pinentry.one_button = 1; pinentry.quality_bar = 0; pinentry.close_button = 0; pinentry.locale_err = 0; result = (*pinentry_cmd_handler) (&pinentry); if (pinentry.error) { free (pinentry.error); pinentry.error = NULL; } if (pinentry.close_button) assuan_write_status (ctx, "BUTTON_INFO", "close"); return result ? 0 : (pinentry.locale_err? ASSUAN_Locale_Problem : 0); } /* GETINFO Multipurpose function to return a variety of information. Supported values for WHAT are: version - Return the version of the program. pid - Return the process id of the server. */ static int cmd_getinfo (assuan_context_t ctx, char *line) { int rc; if (!strcmp (line, "version")) { const char *s = VERSION; rc = assuan_send_data (ctx, s, strlen (s)); } else if (!strcmp (line, "pid")) { char numbuf[50]; snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else rc = ASSUAN_Parameter_Error; return rc; } /* Tell the assuan library about our commands. */ static int register_commands (ASSUAN_CONTEXT ctx) { static struct { const char *name; int cmd_id; int (*handler) (ASSUAN_CONTEXT, char *line); } table[] = { { "SETDESC", 0, cmd_setdesc }, { "SETPROMPT", 0, cmd_setprompt }, { "SETKEYINFO", 0, cmd_setkeyinfo }, { "SETREPEAT", 0, cmd_setrepeat }, { "SETREPEATERROR",0, cmd_setrepeaterror }, { "SETERROR", 0, cmd_seterror }, { "SETOK", 0, cmd_setok }, { "SETNOTOK", 0, cmd_setnotok }, { "SETCANCEL", 0, cmd_setcancel }, { "GETPIN", 0, cmd_getpin }, { "CONFIRM", 0, cmd_confirm }, { "MESSAGE", 0, cmd_message }, { "SETQUALITYBAR", 0, cmd_setqualitybar }, { "SETQUALITYBAR_TT", 0, cmd_setqualitybar_tt }, { "GETINFO", 0, cmd_getinfo }, { "SETTITLE", 0, cmd_settitle }, { "SETTIMEOUT", 0, cmd_settimeout }, { NULL } }; int i, j, rc; for (i = j = 0; table[i].name; i++) { rc = assuan_register_command (ctx, table[i].cmd_id ? table[i].cmd_id : (ASSUAN_CMD_USER + j++), table[i].name, table[i].handler); if (rc) return rc; } return 0; } int pinentry_loop2 (int infd, int outfd) { int rc; int filedes[2]; ASSUAN_CONTEXT ctx; /* Extra check to make sure we have dropped privs. */ #ifndef HAVE_DOSISH_SYSTEM if (getuid() != geteuid()) abort (); #endif /* For now we use a simple pipe based server so that we can work from scripts. We will later add options to run as a daemon and wait for requests on a Unix domain socket. */ filedes[0] = infd; filedes[1] = outfd; rc = assuan_init_pipe_server (&ctx, filedes); if (rc) { fprintf (stderr, "%s: failed to initialize the server: %s\n", this_pgmname, assuan_strerror(rc)); return -1; } rc = register_commands (ctx); if (rc) { fprintf (stderr, "%s: failed to the register commands with Assuan: %s\n", this_pgmname, assuan_strerror(rc)); return -1; } assuan_register_option_handler (ctx, option_handler); #if 0 assuan_set_log_stream (ctx, stderr); #endif for (;;) { rc = assuan_accept (ctx); if (rc == -1) break; else if (rc) { fprintf (stderr, "%s: Assuan accept problem: %s\n", this_pgmname, assuan_strerror (rc)); break; } rc = assuan_process (ctx); if (rc) { fprintf (stderr, "%s: Assuan processing failed: %s\n", this_pgmname, assuan_strerror (rc)); continue; } } assuan_deinit_server (ctx); return 0; } /* Start the pinentry event loop. The program will start to process Assuan commands until it is finished or an error occurs. If an error occurs, -1 is returned. Otherwise, 0 is returned. */ int pinentry_loop (void) { return pinentry_loop2 (STDIN_FILENO, STDOUT_FILENO); } diff --git a/pinentry/pinentry.h b/pinentry/pinentry.h index 02f76a3..2b5ad27 100644 --- a/pinentry/pinentry.h +++ b/pinentry/pinentry.h @@ -1,247 +1,248 @@ /* pinentry.h - The interface for the PIN entry support library. Copyright (C) 2002, 2003, 2010, 2015 g10 Code GmbH This file is part of PINENTRY. PINENTRY is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. PINENTRY is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #ifndef PINENTRY_H #define PINENTRY_H #ifdef __cplusplus extern "C" { #if 0 } #endif #endif #undef ENABLE_ENHANCED typedef enum { PINENTRY_COLOR_NONE, PINENTRY_COLOR_DEFAULT, PINENTRY_COLOR_BLACK, PINENTRY_COLOR_RED, PINENTRY_COLOR_GREEN, PINENTRY_COLOR_YELLOW, PINENTRY_COLOR_BLUE, PINENTRY_COLOR_MAGENTA, PINENTRY_COLOR_CYAN, PINENTRY_COLOR_WHITE } pinentry_color_t; struct pinentry { /* The window title, or NULL. */ char *title; /* The description to display, or NULL. */ char *description; /* The error message to display, or NULL. */ char *error; /* The prompt to display, or NULL. */ char *prompt; /* The OK button text to display, or NULL. */ char *ok; /* The Not-OK button text to display, or NULL. */ char *notok; /* The Cancel button text to display, or NULL. */ char *cancel; /* The buffer to store the secret into. */ char *pin; /* The length of the buffer. */ int pin_len; /* Whether the pin was read from an external cache (1) or entered by the user (0). */ int pin_from_cache; /* The name of the X display to use if X is available and supported. */ char *display; /* The name of the terminal node to open if X not available or supported. */ char *ttyname; /* The type of the terminal. */ char *ttytype; /* The LC_CTYPE value for the terminal. */ char *lc_ctype; /* The LC_MESSAGES value for the terminal. */ char *lc_messages; /* True if debug mode is requested. */ int debug; /* The number of seconds before giving up while waiting for user input. */ int timeout; #ifdef ENABLE_ENHANCED /* True if enhanced mode is requested. */ int enhanced; #endif /* True if caller should grab the keyboard. */ int grab; /* The window ID of the parent window over which the pinentry window should be displayed. */ int parent_wid; /* The name of an optional file which will be touched after a curses entry has been displayed. */ char *touch_file; /* The user should set this to -1 if the user canceled the request, and to the length of the PIN stored in pin otherwise. */ int result; /* The user should set this if the NOTOK button was pressed. */ int canceled; /* The user should set this to true if an error with the local conversion occured. */ int locale_err; /* The user should set this to true if the window close button has been used. This flag is used in addition to a regular return value. */ int close_button; /* The caller should set this to true if only one button is required. This is useful for notification dialogs where only a dismiss button is required. */ int one_button; /* If true a second prompt for the passphrase is shown and the user is expected to enter the same passphrase again. Pinentry checks that both match. */ char *repeat_passphrase; /* The string to show if a repeated passphrase does not match. */ char *repeat_error_string; /* Set to true if the passphrase has been entered a second time and matches the first passphrase. */ int repeat_okay; /* If this is not NULL, a passphrase quality indicator is shown. There will also be an inquiry back to the caller to get an indication of the quality for the passphrase entered so far. The string is used as a label for the quality bar. */ char *quality_bar; /* The tooltip to be show for the qualitybar. Malloced or NULL. */ char *quality_bar_tt; /* For the curses pinentry, the color of error messages. */ pinentry_color_t color_fg; int color_fg_bright; pinentry_color_t color_bg; pinentry_color_t color_so; int color_so_bright; /* Malloced and i18ned default strings or NULL. These strings may include an underscore character to indicate an accelerator key. A double underscore represents a plain one. */ char *default_ok; char *default_cancel; char *default_prompt; + char *default_pwmngr; /* Whether we are allowed to read the password from an external cache. */ int allow_external_password_cache; /* We only try the cache once. */ int tried_password_cache; /* A stable identifier for the key. */ char *keyinfo; /* Whether we may cache the password (according to the user). */ int may_cache_password; /* NOTE: If you add any additional fields to this structure, be sure to update the initializer in pinentry/pinentry.c!!! */ /* For the quality indicator we need to do an inquiry. Thus we need to save the assuan ctx. */ void *ctx_assuan; }; typedef struct pinentry *pinentry_t; /* The pinentry command handler type processes the pinentry request PIN. If PIN->pin is zero, request a confirmation, otherwise a PIN entry. On confirmation, the function should return TRUE if confirmed, and FALSE otherwise. On PIN entry, the function should return -1 if an error occured or the user cancelled the operation and the length of the secret otherwise. */ typedef int (*pinentry_cmd_handler_t) (pinentry_t pin); /* Start the pinentry event loop. The program will start to process Assuan commands until it is finished or an error occurs. If an error occurs, -1 is returned and errno indicates the type of an error. Otherwise, 0 is returned. */ int pinentry_loop (void); /* The same as above but allows to specify the i/o descriptors. */ int pinentry_loop2 (int infd, int outfd); /* Convert the UTF-8 encoded string TEXT to the encoding given in LC_CTYPE. Return NULL on error. */ char *pinentry_utf8_to_local (const char *lc_ctype, const char *text); /* Convert TEXT which is encoded according to LC_CTYPE to UTF-8. With SECURE set to true, use secure memory for the returned buffer. Return NULL on error. */ char *pinentry_local_to_utf8 (char *lc_ctype, char *text, int secure); /* Run a quality inquiry for PASSPHRASE of LENGTH. */ int pinentry_inq_quality (pinentry_t pin, const char *passphrase, size_t length); /* Try to make room for at least LEN bytes for the pin in the pinentry PIN. Returns new buffer on success and 0 on failure. */ char *pinentry_setbufferlen (pinentry_t pin, int len); /* Initialize the secure memory subsystem, drop privileges and return. Must be called early. */ void pinentry_init (const char *pgmname); /* Return true if either DISPLAY is set or ARGV contains the string "--display". */ int pinentry_have_display (int argc, char **argv); /* Parse the command line options. May exit the program if only help or version output is requested. */ void pinentry_parse_opts (int argc, char *argv[]); /* The caller must define this variable to process assuan commands. */ extern pinentry_cmd_handler_t pinentry_cmd_handler; #ifdef HAVE_W32_SYSTEM /* Windows declares sleep as obsolete, but provides a definition for _sleep but non for the still existing sleep. */ #define sleep(a) _sleep ((a)) #endif /*HAVE_W32_SYSTEM*/ #if 0 { #endif #ifdef __cplusplus } #endif #endif /* PINENTRY_H */