diff --git a/gnome3/pinentry-gnome3.c b/gnome3/pinentry-gnome3.c index 158fbe1..0b8d8d1 100644 --- a/gnome3/pinentry-gnome3.c +++ b/gnome3/pinentry-gnome3.c @@ -1,550 +1,549 @@ /* pinentry-gnome3.c * Copyright (C) 2015 g10 Code GmbH * * pinentry-gnome-3 is a pinentry application for GNOME 3. 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, see . * SPDX-License-Identifier: GPL-2.0+ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include "memory.h" #include "pinentry.h" #ifdef FALLBACK_CURSES #include "pinentry-curses.h" #endif #define PGMNAME "pinentry-gnome3" #ifndef VERSION # define VERSION #endif struct pe_gnome3_run_s { pinentry_t pinentry; GcrPrompt *prompt; GMainLoop *main_loop; int ret; guint timeout_id; int timed_out; }; static void pe_gcr_prompt_password_done (GObject *source_object, GAsyncResult *res, gpointer user_data); static void pe_gcr_prompt_confirm_done (GObject *source_object, GAsyncResult *res, gpointer user_data); static gboolean pe_gcr_timeout_done (gpointer user_data); 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; } static void _propagate_g_error_to_pinentry (pinentry_t pe, GError *error, gpg_err_code_t code, const char *loc) { char *t; /* We can't return the result of g_strdup_printf directly, because * this needs to be g_free'd, but the users of PE (e.g., * pinentry_reset in pinentry/pinentry.c) use free. */ t = g_strdup_printf ("%d: %s", error->code, error->message); if (t) { /* If strdup fails, then PE->SPECIFIC_ERR_INFO will be NULL, * which is exactly what we want if strdup fails. So, there is * no need to check for failure. */ pe->specific_err_info = strdup (t); g_free (t); } else { pe->specific_err_info = NULL; } pe->specific_err = gpg_error (code); pe->specific_err_loc = loc; } static GcrPrompt * create_prompt (pinentry_t pe, int confirm) { GcrPrompt *prompt; GError *error = NULL; char *msg, *p; char window_id[32]; /* Create the prompt. */ prompt = GCR_PROMPT (gcr_system_prompt_open (pe->timeout ? pe->timeout : -1, NULL, &error)); if (! prompt) { /* this means the timeout elapsed, but no prompt was ever shown. */ if (error->code == GCR_SYSTEM_PROMPT_IN_PROGRESS) { fprintf (stderr, "Timeout: the Gcr system prompter was already in use.\n"); pe->specific_err_info = strdup ("Timeout: the Gcr system prompter was already in use."); /* not using GPG_ERR_TIMEOUT here because the user never saw a prompt: */ pe->specific_err = gpg_error (GPG_ERR_PIN_ENTRY); } else { fprintf (stderr, "couldn't create prompt for gnupg passphrase: %s\n", error->message); _propagate_g_error_to_pinentry (pe, error, GPG_ERR_CONFIGURATION, "gcr_system_prompt_open"); } g_error_free (error); return NULL; } /* Set the messages for the various buttons, etc. */ p = pinentry_get_title (pe); if (p) { msg = pinentry_utf8_validate (p); if (msg) { gcr_prompt_set_title (prompt, msg); g_free (msg); } free (p); } if (pe->description) { msg = pinentry_utf8_validate (pe->description); gcr_prompt_set_description (prompt, msg); g_free (msg); } /* An error occurred during the last prompt. */ if (pe->error) { msg = pinentry_utf8_validate (pe->error); gcr_prompt_set_warning (prompt, msg); g_free (msg); } if (! pe->prompt && confirm) gcr_prompt_set_message (prompt, "Message"); else if (! pe->prompt && ! confirm) gcr_prompt_set_message (prompt, "Enter Passphrase"); else { msg = pinentry_utf8_validate (pe->prompt); gcr_prompt_set_message (prompt, msg); g_free (msg); } if (! confirm) gcr_prompt_set_password_new (prompt, !!pe->repeat_passphrase); if (pe->ok || pe->default_ok) { msg = pinentry_utf8_validate (pe->ok ?: pe->default_ok); gcr_prompt_set_continue_label (prompt, msg); g_free (msg); } /* XXX: Disable this button if pe->one_button is set. */ if (pe->cancel || pe->default_cancel) { msg = pinentry_utf8_validate (pe->cancel ?: pe->default_cancel); gcr_prompt_set_cancel_label (prompt, msg); g_free (msg); } if (confirm && pe->notok) { /* XXX: Add support for the third option. */ } /* gcr expects a string; we have a int. see gcr's ui/frob-system-prompt.c for example conversion using %lu */ snprintf (window_id, sizeof (window_id), "%lu", (long unsigned int)pe->parent_wid); - window_id[sizeof (window_id) - 1] = '\0'; gcr_prompt_set_caller_window (prompt, window_id); #ifdef HAVE_LIBSECRET if (! confirm && pe->allow_external_password_cache && pe->keyinfo) { if (pe->default_pwmngr) { msg = pinentry_utf8_validate (pe->default_pwmngr); gcr_prompt_set_choice_label (prompt, msg); g_free (msg); } else gcr_prompt_set_choice_label (prompt, "Automatically unlock this key, whenever I'm logged in"); } #endif return prompt; } static int gnome3_cmd_handler (pinentry_t pe) { struct pe_gnome3_run_s state; state.main_loop = g_main_loop_new (NULL, FALSE); if (!state.main_loop) { pe->specific_err_info = strdup ("Failed to create GMainLoop"); pe->specific_err = gpg_error (GPG_ERR_PIN_ENTRY); pe->specific_err_loc = "g_main_loop_new"; pe->canceled = 1; return -1; } state.pinentry = pe; state.ret = 0; state.timeout_id = 0; state.timed_out = 0; state.prompt = create_prompt (pe, !(pe->pin)); if (!state.prompt) { pe->canceled = 1; return -1; } if (pe->pin) gcr_prompt_password_async (state.prompt, NULL, pe_gcr_prompt_password_done, &state); else gcr_prompt_confirm_async (state.prompt, NULL, pe_gcr_prompt_confirm_done, &state); if (pe->timeout) state.timeout_id = g_timeout_add_seconds (pe->timeout, pe_gcr_timeout_done, &state); g_main_loop_run (state.main_loop); /* clean up state: */ if (state.timeout_id && !state.timed_out) g_source_destroy (g_main_context_find_source_by_id (NULL, state.timeout_id)); g_clear_object (&state.prompt); g_main_loop_unref (state.main_loop); return state.ret; }; static void pe_gcr_prompt_password_done (GObject *source_object, GAsyncResult *res, gpointer user_data) { struct pe_gnome3_run_s *state = user_data; GcrPrompt *prompt = GCR_PROMPT (source_object); if (state && prompt && state->prompt == prompt) { const char *password; GError *error = NULL; pinentry_t pe = state->pinentry; int ret = -1; /* "The returned password is valid until the next time a method is called to display another prompt." */ password = gcr_prompt_password_finish (prompt, res, &error); if ((! password && ! error) || (error && error->code == G_IO_ERROR_CANCELLED)) { /* operation was cancelled or timed out. */ ret = -1; if (state->timed_out) state->pinentry->specific_err = gpg_error (GPG_ERR_TIMEOUT); if (error) g_error_free (error); } else if (error) { _propagate_g_error_to_pinentry (pe, error, GPG_ERR_PIN_ENTRY, "gcr_system_password_finish"); g_error_free (error); ret = -1; } else { pinentry_setbufferlen (pe, strlen (password) + 1); if (pe->pin) strcpy (pe->pin, password); if (pe->repeat_passphrase) pe->repeat_okay = 1; #ifdef HAVE_LIBSECRET if (pe->allow_external_password_cache && pe->keyinfo) pe->may_cache_password = gcr_prompt_get_choice_chosen (prompt); #endif ret = 1; } state->ret = ret; } if (state) g_main_loop_quit (state->main_loop); } static void pe_gcr_prompt_confirm_done (GObject *source_object, GAsyncResult *res, gpointer user_data) { struct pe_gnome3_run_s *state = user_data; GcrPrompt *prompt = GCR_PROMPT (source_object); if (state && prompt && state->prompt == prompt) { GcrPromptReply reply; GError *error = NULL; pinentry_t pe = state->pinentry; int ret = -1; /* XXX: We don't support a third button! */ reply = gcr_prompt_confirm_finish (prompt, res, &error); if (error) { if (error->code == G_IO_ERROR_CANCELLED) { pe->canceled = 1; if (state->timed_out) state->pinentry->specific_err = gpg_error (GPG_ERR_TIMEOUT); } else _propagate_g_error_to_pinentry (state->pinentry, error, GPG_ERR_PIN_ENTRY, "gcr_system_confirm_finish"); g_error_free (error); ret = 0; } else if (reply == GCR_PROMPT_REPLY_CONTINUE /* XXX: Hack since gcr doesn't yet support one button message boxes treat cancel the same as okay. */ || pe->one_button) { /* Confirmation. */ ret = 1; } else /* GCR_PROMPT_REPLY_CANCEL */ { pe->canceled = 1; if (state->timed_out) state->pinentry->specific_err = gpg_error (GPG_ERR_TIMEOUT); ret = 0; } state->ret = ret; } if (state) g_main_loop_quit (state->main_loop); } static gboolean pe_gcr_timeout_done (gpointer user_data) { struct pe_gnome3_run_s *state = user_data; if (!state) return FALSE; state->timed_out = 1; gcr_prompt_close (state->prompt); return FALSE; } pinentry_cmd_handler_t pinentry_cmd_handler = gnome3_cmd_handler; /* Test whether there is a GNOME screensaver running that happens to * be locked. Note that if there is no GNOME screensaver running at * all the answer is still FALSE. */ static gboolean pe_gnome_screen_locked (void) { GDBusConnection *dbus; GError *error = NULL; GVariant *reply, *reply_bool; gboolean ret; dbus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); if (!dbus) { fprintf (stderr, "failed to connect to user session D-Bus (%d): %s", error ? error->code : -1, error ? error->message : ""); if (error) g_error_free (error); return FALSE; } /* this is intended to be the equivalent of: * dbus-send --print-reply=literal --session \ * --dest=org.gnome.ScreenSaver \ * /org/gnome/ScreenSaver \ * org.gnome.ScreenSaver.GetActive */ reply = g_dbus_connection_call_sync (dbus, "org.gnome.ScreenSaver", "/org/gnome/ScreenSaver", "org.gnome.ScreenSaver", "GetActive", NULL, ((const GVariantType *) "(b)"), G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, NULL, &error); g_object_unref(dbus); if (!reply) { /* G_IO_ERROR_IS_DIRECTORY is the expected response when there is * no gnome screensaver at all, don't be noisy in that case: */ if (!(error && error->code == G_IO_ERROR_IS_DIRECTORY)) fprintf (stderr, "Failed to get d-bus reply for org.gnome.ScreenSaver.GetActive (%d): %s\n", error ? error->code : -1, error ? error->message : ""); if (error) g_error_free (error); return FALSE; } reply_bool = g_variant_get_child_value (reply, 0); if (!reply_bool) { fprintf (stderr, "Failed to get d-bus boolean from org.gnome.ScreenSaver.GetActive; assuming screensaver is not locked\n"); ret = FALSE; } else { ret = g_variant_get_boolean (reply_bool); g_variant_unref (reply_bool); } g_variant_unref (reply); return ret; } /* Test whether we can create a system prompt or not. This briefly * does create a system prompt, which blocks other tools from making * the same request concurrently, so we just create it to test if it is * available, and quickly close it. */ static int pe_gcr_system_prompt_available (void) { GcrSystemPrompt *prompt; GError *error = NULL; int ret = 0; prompt = GCR_SYSTEM_PROMPT (gcr_system_prompt_open (0, NULL, &error)); if (prompt) { ret = 1; if (!gcr_system_prompt_close (prompt, NULL, &error)) fprintf (stderr, "failed to close test Gcr System Prompt (%d): %s\n", error ? error->code : -1, error ? error->message : ""); g_clear_object (&prompt); } else if (error && error->code == GCR_SYSTEM_PROMPT_IN_PROGRESS) { /* This one particular failure is OK; we're clearly capable of * making a system prompt, even though someone else has the * system prompter right now: */ ret = 1; } if (error) g_error_free (error); return ret; } int main (int argc, char *argv[]) { pinentry_init (PGMNAME); #ifdef FALLBACK_CURSES if (!getenv ("DBUS_SESSION_BUS_ADDRESS")) { fprintf (stderr, "No $DBUS_SESSION_BUS_ADDRESS found," " falling back to curses\n"); pinentry_cmd_handler = curses_cmd_handler; pinentry_set_flavor_flag ("curses"); } else if (!pe_gcr_system_prompt_available ()) { fprintf (stderr, "No Gcr System Prompter available," " falling back to curses\n"); pinentry_cmd_handler = curses_cmd_handler; pinentry_set_flavor_flag ("curses"); } else if (pe_gnome_screen_locked ()) { fprintf (stderr, "GNOME screensaver is locked," " falling back to curses\n"); pinentry_cmd_handler = curses_cmd_handler; pinentry_set_flavor_flag ("curses"); } #endif pinentry_parse_opts (argc, argv); if (pinentry_loop ()) return 1; return 0; } diff --git a/gtk+-2/pinentry-gtk-2.c b/gtk+-2/pinentry-gtk-2.c index 6d626cc..32112ae 100644 --- a/gtk+-2/pinentry-gtk-2.c +++ b/gtk+-2/pinentry-gtk-2.c @@ -1,987 +1,985 @@ /* 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, see . * SPDX-License-Identifier: GPL-2.0+ */ #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 #include #ifdef HAVE_GETOPT_H #include #else #include "getopt.h" #endif /* HAVE_GETOPT_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 GtkWindow *mainwindow; static GtkWidget *entry; static GtkWidget *repeat_entry; static GtkWidget *error_label; static GtkWidget *qualitybar; static gboolean got_input; static guint timeout_source; static int confirm_mode; /* Gnome hig small and large space in pixels. */ #define HIG_TINY 2 #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; (void)data; 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. This makes the window a modal dialog to the root window, which helps the window manager. See the following quote from: https://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; (void)event; (void)data; /* Make window transient for the root window. */ screen = gdk_screen_get_default (); root = gdk_screen_get_root_window (screen); gdk_window_set_transient_for (gtk_widget_get_window (win), root); } /* Convert GdkGrabStatus to string. */ static const char * grab_strerror (GdkGrabStatus status) { switch (status) { case GDK_GRAB_SUCCESS: return "success"; case GDK_GRAB_ALREADY_GRABBED: return "already grabbed"; case GDK_GRAB_INVALID_TIME: return "invalid time"; case GDK_GRAB_NOT_VIEWABLE: return "not viewable"; case GDK_GRAB_FROZEN: return "frozen"; } return "unknown"; } /* Grab the keyboard for maximum security */ static int grab_keyboard (GtkWidget *win, GdkEvent *event, gpointer data) { GdkGrabStatus err; int tries = 0, max_tries = 4096; (void)data; if (! pinentry->grab) return FALSE; do err = gdk_keyboard_grab (gtk_widget_get_window (win), FALSE, gdk_event_get_time (event)); while (tries++ < max_tries && err == GDK_GRAB_NOT_VIEWABLE); if (err) { g_critical ("could not grab keyboard: %s (%d)", grab_strerror (err), err); grab_failed = 1; gtk_main_quit (); } if (tries > 1) g_warning ("it took %d tries to grab the keyboard", tries); return FALSE; } /* Grab the pointer to prevent the user from accidentally locking herself out of her graphical interface. */ static int grab_pointer (GtkWidget *win, GdkEvent *event, gpointer data) { GdkGrabStatus err; GdkCursor *cursor; int tries = 0, max_tries = 4096; (void)data; /* Change the cursor for the duration of the grab to indicate that * something is going on. The fvwm window manager grabs the pointer * for a short time and thus we may end up with the already grabbed * error code. Actually this error code should be used to detect a * malicious grabbing application but with fvwm this renders * Pinentry only unusable. Thus we try again several times also for * that error code. See Debian bug 850708 for details. */ /* XXX: It would be nice to have a key cursor, unfortunately there is none readily available. */ cursor = gdk_cursor_new_for_display (gtk_widget_get_display (win), GDK_DOT); do err = gdk_pointer_grab (gtk_widget_get_window (win), TRUE, 0 /* event mask */, NULL /* confine to */, cursor, gdk_event_get_time (event)); while (tries++ < max_tries && (err == GDK_GRAB_NOT_VIEWABLE || err == GDK_GRAB_ALREADY_GRABBED)); if (err) { g_critical ("could not grab pointer: %s (%d)", grab_strerror (err), err); grab_failed = 1; gtk_main_quit (); } if (tries > 1) g_warning ("it took %d tries to grab the pointer", tries); return FALSE; } /* Remove all grabs and restore the windows transient state. */ static int ungrab_inputs (GtkWidget *win, GdkEvent *event, gpointer data) { (void)data; gdk_keyboard_ungrab (gdk_event_get_time (event)); gdk_pointer_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 (gtk_widget_get_window (win), gdk_atom_intern_static_string ("WM_TRANSIENT_FOR")); return FALSE; } static int delete_event (GtkWidget *widget, GdkEvent *event, gpointer data) { (void)widget; (void)event; (void)data; pinentry->close_button = 1; gtk_main_quit (); return TRUE; } /* A button was clicked. DATA indicates which button was clicked (i.e., the appropriate action) and is either CONFIRM_CANCEL, CONFIRM_OK or CONFIRM_NOTOK. */ static void button_clicked (GtkWidget *widget, gpointer data) { (void)widget; if (confirm_mode) { confirm_value = (confirm_value_t) data; gtk_main_quit (); return; } if (data) { const char *s, *s2; /* Okay button or enter used in text field. */ s = gtk_entry_get_text (GTK_ENTRY (entry)); if (!s) s = ""; if (pinentry->repeat_passphrase && repeat_entry) { s2 = gtk_entry_get_text (GTK_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 *next_widget) { if (next_widget) gtk_widget_grab_focus (next_widget); else button_clicked (widget, (gpointer) CONFIRM_OK); } static void cancel_callback (GtkAccelGroup *acc, GObject *accelerable, guint keyval, GdkModifierType modifier, gpointer data) { (void)acc; (void)keyval; (void)modifier; (void)data; 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_entry_set_text (GTK_ENTRY (repeat_entry), ""); gtk_label_set_text (GTK_LABEL (error_label), ""); } if (!qualitybar || !pinentry->quality_bar) return; s = gtk_entry_get_text (GTK_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); - textbuf[sizeof textbuf -1] = 0; color.red = 0xffff; percent = -percent; } else { snprintf (textbuf, sizeof textbuf, "%d%%", percent); - textbuf[sizeof textbuf -1] = 0; 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); } /* Called upon a press on Backspace in the entry widget. Used to completely disable echoing if we got no prior input. */ static void backspace_handler (GtkWidget *widget, gpointer data) { (void)widget; (void)data; if (!got_input) { gtk_entry_set_invisible_char (GTK_ENTRY (entry), 0); if (repeat_entry) gtk_entry_set_invisible_char (GTK_ENTRY (repeat_entry), 0); } } #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 /* Return TRUE if it is okay to unhide the entry. */ static int confirm_unhiding (void) { const char *s; GtkWidget *dialog; int result; char *message, *show_btn_label; s = gtk_entry_get_text (GTK_ENTRY (entry)); if (!s || !*s) return TRUE; /* Nothing entered - go ahead an unhide. */ message = pinentry_utf8_validate (pinentry->default_cf_visi); if (!message) { message = g_strdup ("Do you really want to make " "your passphrase visible on the screen?"); } show_btn_label = pinentry_utf8_validate (pinentry->default_tt_visi); if (!show_btn_label) { show_btn_label = g_strdup ("Make passphrase visible"); } dialog = gtk_message_dialog_new (GTK_WINDOW (mainwindow), GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE, "%s", message); gtk_dialog_add_buttons (GTK_DIALOG (dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, show_btn_label, GTK_RESPONSE_OK, NULL); result = (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK); gtk_widget_destroy (dialog); g_free (message); g_free (show_btn_label); return result; } static void show_hide_button_toggled (GtkWidget *widget, gpointer data) { GtkToggleButton *button = GTK_TOGGLE_BUTTON (widget); GtkWidget *label = data; const char *text; char *tooltip; gboolean reveal; if (!gtk_toggle_button_get_active (button) || !confirm_unhiding ()) { text = "abc"; tooltip = pinentry_utf8_validate (pinentry->default_tt_visi); if (!tooltip) { tooltip = g_strdup ("Make the passphrase visible"); } gtk_toggle_button_set_active (button, FALSE); reveal = FALSE; } else { text = "***"; tooltip = pinentry_utf8_validate (pinentry->default_tt_hide); if (!tooltip) { tooltip = g_strdup ("Hide the passphrase"); } reveal = TRUE; } gtk_entry_set_visibility (GTK_ENTRY (entry), reveal); if (repeat_entry) { gtk_entry_set_visibility (GTK_ENTRY (repeat_entry), reveal); } gtk_label_set_markup (GTK_LABEL(label), text); if (!pinentry->grab) { gtk_widget_set_tooltip_text (GTK_WIDGET(button), tooltip); } g_free (tooltip); } static gboolean timeout_cb (gpointer data) { pinentry_t pe = (pinentry_t)data; if (!got_input) { gtk_main_quit (); if (pe) pe->specific_err = gpg_error (GPG_ERR_TIMEOUT); } /* Don't run again. */ timeout_source = 0; return FALSE; } static GtkWidget * create_show_hide_button (void) { GtkWidget *button, *label; label = gtk_label_new (NULL); button = gtk_toggle_button_new (); show_hide_button_toggled (button, label); gtk_container_add (GTK_CONTAINER (button), label); g_signal_connect (G_OBJECT (button), "toggled", G_CALLBACK (show_hide_button_toggled), label); return button; } static GtkWidget * create_window (pinentry_t ctx) { GtkWidget *w; GtkWidget *win, *box; GtkWidget *wvbox, *chbox, *bbox; GtkAccelGroup *acc; gchar *msg; char *p; repeat_entry = NULL; /* FIXME: check the grabbing code against the one we used with the old gpg-agent */ win = gtk_window_new (GTK_WINDOW_TOPLEVEL); mainwindow = GTK_WINDOW (win); 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); g_signal_connect (G_OBJECT (win), "realize", G_CALLBACK (make_transient), NULL); if (!confirm_mode) { /* 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); if (pinentry->grab) g_signal_connect (G_OBJECT (win), "visibility-notify-event", G_CALLBACK (grab_pointer), NULL); g_signal_connect (G_OBJECT (win), pinentry->grab ? "unmap-event" : "focus-out-event", G_CALLBACK (ungrab_inputs), 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); p = pinentry_get_title (pinentry); if (p) { msg = pinentry_utf8_validate (p); if (msg) gtk_window_set_title (GTK_WINDOW(win), msg); g_free (msg); free (p); } 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, *hbox; 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_entry_new (); gtk_entry_set_visibility (GTK_ENTRY (entry), FALSE); /* Allow the user to set a narrower invisible character than the large dot currently used by GTK. Examples are "•★Ⓐ" */ if (pinentry->invisible_char) { gunichar *uch; /*""*/ uch = g_utf8_to_ucs4 (pinentry->invisible_char, -1, NULL, NULL, NULL); if (uch) { gtk_entry_set_invisible_char (GTK_ENTRY (entry), *uch); g_free (uch); } } gtk_widget_set_size_request (entry, 200, -1); g_signal_connect (G_OBJECT (entry), "changed", G_CALLBACK (changed_text_handler), entry); /* Enable disabling echo if we're not asking for a PIN. */ if (pinentry->prompt && !strstr (pinentry->prompt, "PIN")) { g_signal_connect (G_OBJECT (entry), "backspace", G_CALLBACK (backspace_handler), entry); } hbox = gtk_hbox_new (FALSE, HIG_TINY); gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0); /* There was a wish in issue #2139 that this button should not be part of the tab order (focus_order). This should still be added. */ w = create_show_hide_button (); gtk_box_pack_end (GTK_BOX (hbox), w, FALSE, FALSE, 0); gtk_table_attach (GTK_TABLE (table), hbox, 1, 2, nrow, nrow+1, GTK_EXPAND|GTK_FILL, GTK_EXPAND|GTK_FILL, 0, 0); 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_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 && !pinentry->grab) { gtk_widget_set_tooltip_text (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_entry_new (); gtk_entry_set_visibility (GTK_ENTRY (repeat_entry), FALSE); gtk_widget_set_size_request (repeat_entry, 200, -1); 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_show (repeat_entry); nrow++; g_signal_connect (G_OBJECT (repeat_entry), "activate", G_CALLBACK (enter_callback), NULL); } /* When the user presses enter in the entry widget, the widget is activated. If we have a repeat entry, send the focus to it. Otherwise, activate the "Ok" button. */ g_signal_connect (G_OBJECT (entry), "activate", G_CALLBACK (enter_callback), repeat_entry); } 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. */ { 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"); /* Make sure it is off by default. */ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w), FALSE); 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 (button_clicked), (gpointer) CONFIRM_CANCEL); gtk_accel_group_connect (acc, GDK_KEY_Escape, 0, 0, g_cclosure_new (G_CALLBACK (cancel_callback), NULL, NULL)); } 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 (button_clicked), (gpointer) CONFIRM_NOTOK); } 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) { gtk_widget_set_can_default (w, TRUE); gtk_widget_grab_default (w); } g_signal_connect (G_OBJECT (w), "clicked", G_CALLBACK(button_clicked), (gpointer) CONFIRM_OK); 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) timeout_source = g_timeout_add (pinentry->timeout*1000, timeout_cb, pinentry); 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; confirm_mode = want_pass ? 0 : 1; w = create_window (pe); gtk_main (); gtk_widget_destroy (w); while (gtk_events_pending ()) gtk_main_iteration (); if (timeout_source) /* There is a timer running. Cancel it. */ { g_source_remove (timeout_source); timeout_source = 0; } 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[]) { pinentry_init (PGMNAME); #ifdef FALLBACK_CURSES if (pinentry_have_display (argc, argv)) { if (! gtk_init_check (&argc, &argv)) { pinentry_cmd_handler = curses_cmd_handler; pinentry_set_flavor_flag ("curses"); } } else { pinentry_cmd_handler = curses_cmd_handler; pinentry_set_flavor_flag ("curses"); } #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 2fdc2e4..ea11b67 100644 --- a/pinentry/pinentry.c +++ b/pinentry/pinentry.c @@ -1,2074 +1,2067 @@ /* pinentry.c - The PIN entry support library * Copyright (C) 2002, 2003, 2007, 2008, 2010, 2015, 2016, 2021 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 . * SPDX-License-Identifier: GPL-2.0+ */ #ifdef HAVE_CONFIG_H #include #endif #ifndef HAVE_W32CE_SYSTEM # include #endif #include #include #include #include #include #include #ifndef HAVE_W32_SYSTEM # include #endif #ifndef HAVE_W32CE_SYSTEM # include #endif #include #ifdef HAVE_W32CE_SYSTEM # include #endif #include #include "memory.h" #include "secmem-util.h" #include "argparse.h" #include "pinentry.h" #include "password-cache.h" #ifdef INSIDE_EMACS # include "pinentry-emacs.h" #endif #ifdef FALLBACK_CURSES # include "pinentry-curses.h" #endif #ifdef HAVE_W32CE_SYSTEM #define getpid() GetCurrentProcessId () #endif /* Keep the name of our program here. */ static char this_pgmname[50]; struct pinentry pinentry; static const char *flavor_flag; /* Because gtk_init removes the --display arg from the command lines * and our command line parser is called after gtk_init (so that it * does not see gtk specific options) we don't have a way to get hold * of the --display option. Our solution is to remember --display in * the call to pinentry_have_display and set it then in our * parser. */ static char *remember_display; static void pinentry_reset (int use_defaults) { /* GPG Agent sets these options once when it starts the pinentry. Don't reset them. */ int grab = pinentry.grab; char *ttyname = pinentry.ttyname; char *ttytype = pinentry.ttytype_l; char *ttyalert = pinentry.ttyalert; char *lc_ctype = pinentry.lc_ctype; char *lc_messages = pinentry.lc_messages; int allow_external_password_cache = pinentry.allow_external_password_cache; char *default_ok = pinentry.default_ok; char *default_cancel = pinentry.default_cancel; char *default_prompt = pinentry.default_prompt; char *default_pwmngr = pinentry.default_pwmngr; char *default_cf_visi = pinentry.default_cf_visi; char *default_tt_visi = pinentry.default_tt_visi; char *default_tt_hide = pinentry.default_tt_hide; char *default_capshint = pinentry.default_capshint; char *touch_file = pinentry.touch_file; unsigned long owner_pid = pinentry.owner_pid; int owner_uid = pinentry.owner_uid; char *owner_host = pinentry.owner_host; int constraints_enforce = pinentry.constraints_enforce; char *constraints_hint_short = pinentry.constraints_hint_short; char *constraints_hint_long = pinentry.constraints_hint_long; char *constraints_error_title = pinentry.constraints_error_title; /* These options are set from the command line. Don't reset them. */ int debug = pinentry.debug; char *display = pinentry.display; int parent_wid = pinentry.parent_wid; pinentry_color_t color_fg = pinentry.color_fg; int color_fg_bright = pinentry.color_fg_bright; pinentry_color_t color_bg = pinentry.color_bg; pinentry_color_t color_so = pinentry.color_so; int color_so_bright = pinentry.color_so_bright; int timeout = pinentry.timeout; char *invisible_char = pinentry.invisible_char; /* Free any allocated memory. */ if (use_defaults) { free (pinentry.ttyname); free (pinentry.ttytype_l); free (pinentry.ttyalert); free (pinentry.lc_ctype); free (pinentry.lc_messages); free (pinentry.default_ok); free (pinentry.default_cancel); free (pinentry.default_prompt); free (pinentry.default_pwmngr); free (pinentry.default_cf_visi); free (pinentry.default_tt_visi); free (pinentry.default_tt_hide); free (pinentry.default_capshint); free (pinentry.touch_file); free (pinentry.owner_host); free (pinentry.display); free (pinentry.constraints_hint_short); free (pinentry.constraints_hint_long); free (pinentry.constraints_error_title); } free (pinentry.title); free (pinentry.description); free (pinentry.error); free (pinentry.prompt); free (pinentry.ok); free (pinentry.notok); free (pinentry.cancel); secmem_free (pinentry.pin); free (pinentry.repeat_passphrase); free (pinentry.repeat_error_string); free (pinentry.quality_bar); free (pinentry.quality_bar_tt); free (pinentry.formatted_passphrase_hint); free (pinentry.keyinfo); free (pinentry.specific_err_info); /* Reset the pinentry structure. */ memset (&pinentry, 0, sizeof (pinentry)); /* Restore options without a default we want to preserve. */ pinentry.invisible_char = invisible_char; /* Restore other options or set defaults. */ if (use_defaults) { /* Pinentry timeout in seconds. */ pinentry.timeout = 60; /* Global grab. */ pinentry.grab = 1; pinentry.color_fg = PINENTRY_COLOR_DEFAULT; pinentry.color_fg_bright = 0; pinentry.color_bg = PINENTRY_COLOR_DEFAULT; pinentry.color_so = PINENTRY_COLOR_DEFAULT; pinentry.color_so_bright = 0; pinentry.owner_uid = -1; } else /* Restore the options. */ { pinentry.grab = grab; pinentry.ttyname = ttyname; pinentry.ttytype_l = ttytype; pinentry.ttyalert = ttyalert; pinentry.lc_ctype = lc_ctype; pinentry.lc_messages = lc_messages; pinentry.allow_external_password_cache = allow_external_password_cache; pinentry.default_ok = default_ok; pinentry.default_cancel = default_cancel; pinentry.default_prompt = default_prompt; pinentry.default_pwmngr = default_pwmngr; pinentry.default_cf_visi = default_cf_visi; pinentry.default_tt_visi = default_tt_visi; pinentry.default_tt_hide = default_tt_hide; pinentry.default_capshint = default_capshint; pinentry.touch_file = touch_file; pinentry.owner_pid = owner_pid; pinentry.owner_uid = owner_uid; pinentry.owner_host = owner_host; pinentry.constraints_enforce = constraints_enforce; pinentry.constraints_hint_short = constraints_hint_short; pinentry.constraints_hint_long = constraints_hint_long; pinentry.constraints_error_title = constraints_error_title; pinentry.debug = debug; pinentry.display = display; pinentry.parent_wid = parent_wid; pinentry.color_fg = color_fg; pinentry.color_fg_bright = color_fg_bright; pinentry.color_bg = color_bg; pinentry.color_so = color_so; pinentry.color_so_bright = color_so_bright; pinentry.timeout = timeout; } } static gpg_error_t pinentry_assuan_reset_handler (assuan_context_t ctx, char *line) { (void)ctx; (void)line; pinentry_reset (0); return 0; } /* 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; } /* Perform percent unescaping in STRING and return the new valid length of the string. A terminating Nul character is inserted at the end of the unescaped string. */ static size_t do_unescape_inplace (char *s) { unsigned char *p, *p0; p = p0 = s; while (*s) { if (*s == '%' && s[1] && s[2]) { s++; *p++ = xtoi_2 (s); s += 2; } else *p++ = *s++; } *p = 0; return (p - p0); } /* Return a malloced copy of the commandline for PID. If this is not * possible NULL is returned. */ #ifndef HAVE_W32_SYSTEM static char * get_cmdline (unsigned long pid) { char buffer[200]; FILE *fp; size_t i, n; snprintf (buffer, sizeof buffer, "/proc/%lu/cmdline", pid); - buffer[sizeof buffer - 1] = 0; fp = fopen (buffer, "rb"); if (!fp) return NULL; n = fread (buffer, 1, sizeof buffer - 1, fp); if (n < sizeof buffer -1 && ferror (fp)) { /* Some error occurred. */ fclose (fp); return NULL; } fclose (fp); if (n == 0) return NULL; /* Arguments are delimited by Nuls. We should do proper quoting but * that can be a bit complicated, thus we simply replace the Nuls by * spaces. */ for (i=0; i < n; i++) if (!buffer[i] && i < n-1) buffer[i] = ' '; buffer[i] = 0; /* Make sure the last byte is the string terminator. */ return strdup (buffer); } #endif /*!HAVE_W32_SYSTEM*/ /* Atomically ask the kernel for information about process PID. * Return a malloc'ed copy of the process name as long as the process * uid matches UID. If it cannot determine that the process has uid * UID, it returns NULL. * * This is not as informative as get_cmdline, but it verifies that the * process does belong to the user in question. */ #ifndef HAVE_W32_SYSTEM static char * get_pid_name_for_uid (unsigned long pid, int uid) { char buffer[400]; FILE *fp; size_t end, n; char *uidstr; snprintf (buffer, sizeof buffer, "/proc/%lu/status", pid); fp = fopen (buffer, "rb"); if (!fp) return NULL; n = fread (buffer, 1, sizeof buffer - 1, fp); if (n < sizeof buffer -1 && ferror (fp)) { /* Some error occurred. */ fclose (fp); return NULL; } fclose (fp); if (n == 0) return NULL; buffer[n] = 0; /* Fixme: Is it specified that "Name" is always the first line? For * robustness I would prefer to have a real parser here. -wk */ if (strncmp (buffer, "Name:\t", 6)) return NULL; end = strcspn (buffer + 6, "\n") + 6; buffer[end] = 0; /* check that uid matches what we expect */ uidstr = strstr (buffer + end + 1, "\nUid:\t"); if (!uidstr) return NULL; if (atoi (uidstr + 6) != uid) return NULL; return strdup (buffer + 6); } #endif /*!HAVE_W32_SYSTEM*/ const char * pinentry_get_pgmname (void) { return this_pgmname; } /* Return a malloced string with the title. The caller mus free the * string. If no title is available or the title string has an error * NULL is returned. */ char * pinentry_get_title (pinentry_t pe) { char *title; if (pe->title) title = strdup (pe->title); #ifndef HAVE_W32_SYSTEM else if (pe->owner_pid) { char buf[200]; struct utsname utsbuf; char *pidname = NULL; char *cmdline = NULL; if (pe->owner_host && !uname (&utsbuf) && !strcmp (utsbuf.nodename, pe->owner_host)) { pidname = get_pid_name_for_uid (pe->owner_pid, pe->owner_uid); if (pidname) cmdline = get_cmdline (pe->owner_pid); } if (pe->owner_host && (cmdline || pidname)) snprintf (buf, sizeof buf, "[%lu]@%s (%s)", pe->owner_pid, pe->owner_host, cmdline ? cmdline : pidname); else if (pe->owner_host) snprintf (buf, sizeof buf, "[%lu]@%s", pe->owner_pid, pe->owner_host); else snprintf (buf, sizeof buf, "[%lu] ", pe->owner_pid); - buf[sizeof buf - 1] = 0; free (pidname); free (cmdline); title = strdup (buf); } #endif /*!HAVE_W32_SYSTEM*/ else title = strdup (this_pgmname); return title; } /* 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_t 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; } /* Run a checkpin inquiry */ char * pinentry_inq_checkpin (pinentry_t pin, const char *passphrase, size_t length) { assuan_context_t ctx = pin->ctx_assuan; const char prefix[] = "INQUIRE CHECKPIN "; char *command; char *line; size_t linelen; int gotvalue = 0; char *value = NULL; 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 = strdup (line + 2); } return value; } /* Run a genpin inquiry */ char * pinentry_inq_genpin (pinentry_t pin) { assuan_context_t ctx = pin->ctx_assuan; const char prefix[] = "INQUIRE GENPIN"; char *line; size_t linelen; int gotvalue = 0; char *value = NULL; int rc; if (!ctx) return 0; /* Can't run the callback. */ rc = assuan_write_line (ctx, prefix); 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); free (value); 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 = strdup (line + 2); } 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 (pin->pin_len) assert (pin->pin); else assert (!pin->pin); if (len < 2048) len = 2048; if (len <= pin->pin_len) return pin->pin; newp = secmem_realloc (pin->pin, len); if (newp) { pin->pin = newp; pin->pin_len = len; } else { secmem_free (pin->pin); pin->pin = 0; pin->pin_len = 0; } return newp; } static void pinentry_setbuffer_clear (pinentry_t pin) { if (! pin->pin) { assert (pin->pin_len == 0); return; } assert (pin->pin_len > 0); secmem_free (pin->pin); pin->pin = NULL; pin->pin_len = 0; } static void pinentry_setbuffer_init (pinentry_t pin) { pinentry_setbuffer_clear (pin); pinentry_setbufferlen (pin, 0); } /* passphrase better be alloced with secmem_alloc. */ void pinentry_setbuffer_use (pinentry_t pin, char *passphrase, int len) { if (! passphrase) { assert (len == 0); pinentry_setbuffer_clear (pin); return; } if (passphrase && len == 0) len = strlen (passphrase) + 1; if (pin->pin) secmem_free (pin->pin); pin->pin = passphrase; pin->pin_len = len; } static struct assuan_malloc_hooks assuan_malloc_hooks = { secmem_malloc, secmem_realloc, secmem_free }; /* 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); gpgrt_check_version (NULL); /* 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 (&assuan_malloc_hooks); } /* 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) { int found = 0; for (; argc; argc--, argv++) { if (!strcmp (*argv, "--display")) { if (argv[1] && !remember_display) { remember_display = strdup (argv[1]); if (!remember_display) { #ifndef HAVE_W32CE_SYSTEM fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno)); #endif exit (EXIT_FAILURE); } } found = 1; break; } else if (!strncmp (*argv, "--display=", 10)) { if (!remember_display) { remember_display = strdup (*argv+10); if (!remember_display) { #ifndef HAVE_W32CE_SYSTEM fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno)); #endif exit (EXIT_FAILURE); } } found = 1; break; } } #ifndef HAVE_W32CE_SYSTEM { const char *s; s = getenv ("DISPLAY"); if (s && *s) found = 1; } #endif return found; } /* 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) 2016 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); - str[n-1] = 0; } } 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"), 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_s_s('a', "ttyalert", "|STRING|Set the alert mode (none, beep or flash)"), ARGPARSE_end() }; ARGPARSE_ARGS pargs = { &argc, &argv, 0 }; set_strusage (my_strusage); pinentry_reset (1); while (arg_parse (&pargs, opts)) { switch (pargs.r_opt) { case 'd': pinentry.debug = 1; break; 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_l = strdup (pargs.r.ret_str); if (!pinentry.ttytype_l) { #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; case 'a': pinentry.ttyalert = strdup (pargs.r.ret_str); if (!pinentry.ttyalert) { #ifndef HAVE_W32CE_SYSTEM fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno)); #endif exit (EXIT_FAILURE); } break; default: pargs.err = ARGPARSE_PRINT_WARNING; break; } } if (!pinentry.display && remember_display) { pinentry.display = remember_display; remember_display = NULL; } } /* Set the optional flag used with getinfo. */ void pinentry_set_flavor_flag (const char *string) { flavor_flag = string; } static gpg_error_t option_handler (assuan_context_t ctx, const char *key, const char *value) { (void)ctx; 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_W32_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 gpg_error_from_syserror (); } else if (!strcmp (key, "ttyname")) { if (pinentry.ttyname) free (pinentry.ttyname); pinentry.ttyname = strdup (value); if (!pinentry.ttyname) return gpg_error_from_syserror (); } else if (!strcmp (key, "ttytype")) { if (pinentry.ttytype_l) free (pinentry.ttytype_l); pinentry.ttytype_l = strdup (value); if (!pinentry.ttytype_l) return gpg_error_from_syserror (); } else if (!strcmp (key, "ttyalert")) { if (pinentry.ttyalert) free (pinentry.ttyalert); pinentry.ttyalert = strdup (value); if (!pinentry.ttyalert) return gpg_error_from_syserror (); } else if (!strcmp (key, "lc-ctype")) { if (pinentry.lc_ctype) free (pinentry.lc_ctype); pinentry.lc_ctype = strdup (value); if (!pinentry.lc_ctype) return gpg_error_from_syserror (); } else if (!strcmp (key, "lc-messages")) { if (pinentry.lc_messages) free (pinentry.lc_messages); pinentry.lc_messages = strdup (value); if (!pinentry.lc_messages) return gpg_error_from_syserror (); } else if (!strcmp (key, "owner")) { long along; char *endp; free (pinentry.owner_host); pinentry.owner_host = NULL; pinentry.owner_uid = -1; pinentry.owner_pid = 0; errno = 0; along = strtol (value, &endp, 10); if (along && !errno) { pinentry.owner_pid = (unsigned long)along; if (*endp) { errno = 0; if (*endp == '/') { /* we have a uid */ endp++; along = strtol (endp, &endp, 10); if (along >= 0 && !errno) pinentry.owner_uid = (int)along; } if (endp) { while (*endp == ' ') endp++; if (*endp) { pinentry.owner_host = strdup (endp); for (endp=pinentry.owner_host; *endp && *endp != ' '; endp++) ; *endp = 0; } } } } } 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 gpg_error_from_syserror (); } else if (!strcmp (key, "default-ok")) { pinentry.default_ok = strdup (value); if (!pinentry.default_ok) return gpg_error_from_syserror (); } else if (!strcmp (key, "default-cancel")) { pinentry.default_cancel = strdup (value); if (!pinentry.default_cancel) return gpg_error_from_syserror (); } else if (!strcmp (key, "default-prompt")) { pinentry.default_prompt = strdup (value); if (!pinentry.default_prompt) return gpg_error_from_syserror (); } else if (!strcmp (key, "default-pwmngr")) { pinentry.default_pwmngr = strdup (value); if (!pinentry.default_pwmngr) return gpg_error_from_syserror (); } else if (!strcmp (key, "default-cf-visi")) { pinentry.default_cf_visi = strdup (value); if (!pinentry.default_cf_visi) return gpg_error_from_syserror (); } else if (!strcmp (key, "default-tt-visi")) { pinentry.default_tt_visi = strdup (value); if (!pinentry.default_tt_visi) return gpg_error_from_syserror (); } else if (!strcmp (key, "default-tt-hide")) { pinentry.default_tt_hide = strdup (value); if (!pinentry.default_tt_hide) return gpg_error_from_syserror (); } else if (!strcmp (key, "default-capshint")) { pinentry.default_capshint = strdup (value); if (!pinentry.default_capshint) return gpg_error_from_syserror (); } else if (!strcmp (key, "allow-external-password-cache") && !*value) { pinentry.allow_external_password_cache = 1; pinentry.tried_password_cache = 0; } else if (!strcmp (key, "allow-emacs-prompt") && !*value) { #ifdef INSIDE_EMACS pinentry_enable_emacs_cmd_handler (); #endif } else if (!strcmp (key, "invisible-char")) { if (pinentry.invisible_char) free (pinentry.invisible_char); pinentry.invisible_char = strdup (value); if (!pinentry.invisible_char) return gpg_error_from_syserror (); } else if (!strcmp (key, "formatted-passphrase") && !*value) { pinentry.formatted_passphrase = 1; } else if (!strcmp (key, "formatted-passphrase-hint")) { if (pinentry.formatted_passphrase_hint) free (pinentry.formatted_passphrase_hint); pinentry.formatted_passphrase_hint = strdup (value); if (!pinentry.formatted_passphrase_hint) return gpg_error_from_syserror (); do_unescape_inplace(pinentry.formatted_passphrase_hint); } else if (!strcmp (key, "constraints-enforce") && !*value) pinentry.constraints_enforce = 1; else if (!strcmp (key, "constraints-hint-short")) { if (pinentry.constraints_hint_short) free (pinentry.constraints_hint_short); pinentry.constraints_hint_short = strdup (value); if (!pinentry.constraints_hint_short) return gpg_error_from_syserror (); do_unescape_inplace(pinentry.constraints_hint_short); } else if (!strcmp (key, "constraints-hint-long")) { if (pinentry.constraints_hint_long) free (pinentry.constraints_hint_long); pinentry.constraints_hint_long = strdup (value); if (!pinentry.constraints_hint_long) return gpg_error_from_syserror (); do_unescape_inplace(pinentry.constraints_hint_long); } else if (!strcmp (key, "constraints-error-title")) { if (pinentry.constraints_error_title) free (pinentry.constraints_error_title); pinentry.constraints_error_title = strdup (value); if (!pinentry.constraints_error_title) return gpg_error_from_syserror (); do_unescape_inplace(pinentry.constraints_error_title); } else return gpg_error (GPG_ERR_UNKNOWN_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 void write_status_error (assuan_context_t ctx, pinentry_t pe) { char buf[500]; const char *pgm; pgm = strchr (this_pgmname, '-'); if (pgm && pgm[1]) pgm++; else pgm = this_pgmname; snprintf (buf, sizeof buf, "%s.%s %d %s", pgm, pe->specific_err_loc? pe->specific_err_loc : "?", pe->specific_err, pe->specific_err_info? pe->specific_err_info : ""); - buf[sizeof buf -1] = 0; assuan_write_status (ctx, "ERROR", buf); } static gpg_error_t cmd_setdesc (assuan_context_t ctx, char *line) { char *newd; (void)ctx; newd = malloc (strlen (line) + 1); if (!newd) return gpg_error_from_syserror (); strcpy_escaped (newd, line); if (pinentry.description) free (pinentry.description); pinentry.description = newd; return 0; } static gpg_error_t cmd_setprompt (assuan_context_t ctx, char *line) { char *newp; (void)ctx; newp = malloc (strlen (line) + 1); if (!newp) return gpg_error_from_syserror (); 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 gpg_error_t cmd_setkeyinfo (assuan_context_t ctx, char *line) { (void)ctx; if (pinentry.keyinfo) free (pinentry.keyinfo); if (*line && strcmp(line, "--clear") != 0) pinentry.keyinfo = strdup (line); else pinentry.keyinfo = NULL; return 0; } static gpg_error_t cmd_setrepeat (assuan_context_t ctx, char *line) { char *p; (void)ctx; p = malloc (strlen (line) + 1); if (!p) return gpg_error_from_syserror (); strcpy_escaped (p, line); free (pinentry.repeat_passphrase); pinentry.repeat_passphrase = p; return 0; } static gpg_error_t cmd_setrepeaterror (assuan_context_t ctx, char *line) { char *p; (void)ctx; p = malloc (strlen (line) + 1); if (!p) return gpg_error_from_syserror (); strcpy_escaped (p, line); free (pinentry.repeat_error_string); pinentry.repeat_error_string = p; return 0; } static gpg_error_t cmd_seterror (assuan_context_t ctx, char *line) { char *newe; (void)ctx; newe = malloc (strlen (line) + 1); if (!newe) return gpg_error_from_syserror (); strcpy_escaped (newe, line); if (pinentry.error) free (pinentry.error); pinentry.error = newe; return 0; } static gpg_error_t cmd_setok (assuan_context_t ctx, char *line) { char *newo; (void)ctx; newo = malloc (strlen (line) + 1); if (!newo) return gpg_error_from_syserror (); strcpy_escaped (newo, line); if (pinentry.ok) free (pinentry.ok); pinentry.ok = newo; return 0; } static gpg_error_t cmd_setnotok (assuan_context_t ctx, char *line) { char *newo; (void)ctx; newo = malloc (strlen (line) + 1); if (!newo) return gpg_error_from_syserror (); strcpy_escaped (newo, line); if (pinentry.notok) free (pinentry.notok); pinentry.notok = newo; return 0; } static gpg_error_t cmd_setcancel (assuan_context_t ctx, char *line) { char *newc; (void)ctx; newc = malloc (strlen (line) + 1); if (!newc) return gpg_error_from_syserror (); strcpy_escaped (newc, line); if (pinentry.cancel) free (pinentry.cancel); pinentry.cancel = newc; return 0; } static gpg_error_t cmd_settimeout (assuan_context_t ctx, char *line) { (void)ctx; if (line && *line) pinentry.timeout = atoi (line); return 0; } static gpg_error_t cmd_settitle (assuan_context_t ctx, char *line) { char *newt; (void)ctx; newt = malloc (strlen (line) + 1); if (!newt) return gpg_error_from_syserror (); strcpy_escaped (newt, line); if (pinentry.title) free (pinentry.title); pinentry.title = newt; return 0; } static gpg_error_t cmd_setqualitybar (assuan_context_t ctx, char *line) { char *newval; (void)ctx; if (!*line) line = "Quality:"; newval = malloc (strlen (line) + 1); if (!newval) return gpg_error_from_syserror (); 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 gpg_error_t cmd_setqualitybar_tt (assuan_context_t ctx, char *line) { char *newval; (void)ctx; if (*line) { newval = malloc (strlen (line) + 1); if (!newval) return gpg_error_from_syserror (); strcpy_escaped (newval, line); } else newval = NULL; if (pinentry.quality_bar_tt) free (pinentry.quality_bar_tt); pinentry.quality_bar_tt = newval; return 0; } /* Set the tooltip to be used for a generate action. */ static gpg_error_t cmd_setgenpin_tt (assuan_context_t ctx, char *line) { char *newval; (void)ctx; if (*line) { newval = malloc (strlen (line) + 1); if (!newval) return gpg_error_from_syserror (); strcpy_escaped (newval, line); } else newval = NULL; if (pinentry.genpin_tt) free (pinentry.genpin_tt); pinentry.genpin_tt = newval; return 0; } /* Set the label to be used for a generate action. */ static gpg_error_t cmd_setgenpin_label (assuan_context_t ctx, char *line) { char *newval; (void)ctx; if (*line) { newval = malloc (strlen (line) + 1); if (!newval) return gpg_error_from_syserror (); strcpy_escaped (newval, line); } else newval = NULL; if (pinentry.genpin_label) free (pinentry.genpin_label); pinentry.genpin_label = newval; return 0; } static gpg_error_t cmd_getpin (assuan_context_t ctx, char *line) { int result; int set_prompt = 0; int just_read_password_from_cache = 0; (void)line; pinentry_setbuffer_init (&pinentry); if (!pinentry.pin) return gpg_error (GPG_ERR_ENOMEM); /* 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 /* If the last read resulted in an error, then don't read from the cache. */ && ! pinentry.error) { char *password; int give_up_on_password_store = 0; pinentry.tried_password_cache = 1; password = password_cache_lookup (pinentry.keyinfo, &give_up_on_password_store); if (give_up_on_password_store) pinentry.allow_external_password_cache = 0; 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.specific_err = 0; pinentry.specific_err_loc = NULL; free (pinentry.specific_err_info); pinentry.specific_err_info = NULL; 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) { pinentry_setbuffer_clear (&pinentry); if (pinentry.specific_err) { write_status_error (ctx, &pinentry); if (gpg_err_code (pinentry.specific_err) == GPG_ERR_FULLY_CANCELED) assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1); return pinentry.specific_err; } return (pinentry.locale_err ? gpg_error (GPG_ERR_LOCALE_PROBLEM) : gpg_error (GPG_ERR_CANCELED)); } out: if (result) { if (pinentry.repeat_okay) assuan_write_status (ctx, "PIN_REPEATED", ""); assuan_begin_confidential (ctx); result = assuan_send_data (ctx, pinentry.pin, strlen(pinentry.pin)); if (!result) result = assuan_send_data (ctx, NULL, 0); assuan_end_confidential (ctx); 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); } pinentry_setbuffer_clear (&pinentry); 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 gpg_error_t cmd_confirm (assuan_context_t 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.specific_err = 0; pinentry.specific_err_loc = NULL; free (pinentry.specific_err_info); pinentry.specific_err_info = NULL; pinentry.canceled = 0; pinentry_setbuffer_clear (&pinentry); 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"); if (result > 0) return 0; /* OK */ if (pinentry.specific_err) { write_status_error (ctx, &pinentry); if (gpg_err_code (pinentry.specific_err) == GPG_ERR_FULLY_CANCELED) assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1); return pinentry.specific_err; } if (pinentry.locale_err) return gpg_error (GPG_ERR_LOCALE_PROBLEM); if (pinentry.one_button) return 0; /* OK */ if (pinentry.canceled) return gpg_error (GPG_ERR_CANCELED); return gpg_error (GPG_ERR_NOT_CONFIRMED); } static gpg_error_t cmd_message (assuan_context_t ctx, char *line) { (void)line; return cmd_confirm (ctx, "--one-button"); } /* Return a staically allocated string with information on the mode, * uid, and gid of DEVICE. On error "?" is returned if DEVICE is * NULL, "-" is returned. */ static const char * device_stat_string (const char *device) { #ifdef HAVE_STAT static char buf[40]; struct stat st; if (!device || !*device) return "-"; if (stat (device, &st)) return "?"; /* Error */ snprintf (buf, sizeof buf, "%lo/%lu/%lu", (unsigned long)st.st_mode, (unsigned long)st.st_uid, (unsigned long)st.st_gid); return buf; #else return "-"; #endif } /* 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. flavor - Return information about the used pinentry flavor ttyinfo - Return DISPLAY, ttyinfo and an emacs pinentry status */ static gpg_error_t cmd_getinfo (assuan_context_t ctx, char *line) { int rc; const char *s; char buffer[150]; if (!strcmp (line, "version")) { s = VERSION; rc = assuan_send_data (ctx, s, strlen (s)); } else if (!strcmp (line, "pid")) { snprintf (buffer, sizeof buffer, "%lu", (unsigned long)getpid ()); - buffer[sizeof buffer -1] = 0; rc = assuan_send_data (ctx, buffer, strlen (buffer)); } else if (!strcmp (line, "flavor")) { if (!strncmp (this_pgmname, "pinentry-", 9) && this_pgmname[9]) s = this_pgmname + 9; else s = this_pgmname; snprintf (buffer, sizeof buffer, "%s%s%s", s, flavor_flag? ":":"", flavor_flag? flavor_flag : ""); - buffer[sizeof buffer -1] = 0; rc = assuan_send_data (ctx, buffer, strlen (buffer)); /* if (!rc) */ /* rc = assuan_write_status (ctx, "FEATURES", "tabbing foo bar"); */ } else if (!strcmp (line, "ttyinfo")) { char emacs_status[10]; #ifdef INSIDE_EMACS snprintf (emacs_status, sizeof emacs_status, "%d", pinentry_emacs_status ()); #else strcpy (emacs_status, "-"); #endif snprintf (buffer, sizeof buffer, "%s %s %s %s %lu/%lu %s", pinentry.ttyname? pinentry.ttyname : "-", pinentry.ttytype_l? pinentry.ttytype_l : "-", pinentry.display? pinentry.display : "-", device_stat_string (pinentry.ttyname), #ifdef HAVE_DOSISH_SYSTEM 0l, 0l, #else (unsigned long)geteuid (), (unsigned long)getegid (), #endif emacs_status ); - buffer[sizeof buffer -1] = 0; rc = assuan_send_data (ctx, buffer, strlen (buffer)); } else rc = gpg_error (GPG_ERR_ASS_PARAMETER); return rc; } /* CLEARPASSPHRASE Clear the cache passphrase associated with the key identified by cacheid. */ static gpg_error_t cmd_clear_passphrase (assuan_context_t ctx, char *line) { (void)ctx; if (! line) return gpg_error (GPG_ERR_ASS_INV_VALUE); /* Remove leading and trailing white space. */ while (*line == ' ') line ++; while (line[strlen (line) - 1] == ' ') line[strlen (line) - 1] = 0; switch (password_cache_clear (line)) { case 1: return 0; case 0: return gpg_error (GPG_ERR_ASS_INV_VALUE); default: return gpg_error (GPG_ERR_ASS_GENERAL); } } /* Tell the assuan library about our commands. */ static gpg_error_t register_commands (assuan_context_t ctx) { static struct { const char *name; gpg_error_t (*handler) (assuan_context_t, char *line); } table[] = { { "SETDESC", cmd_setdesc }, { "SETPROMPT", cmd_setprompt }, { "SETKEYINFO", cmd_setkeyinfo }, { "SETREPEAT", cmd_setrepeat }, { "SETREPEATERROR", cmd_setrepeaterror }, { "SETERROR", cmd_seterror }, { "SETOK", cmd_setok }, { "SETNOTOK", cmd_setnotok }, { "SETCANCEL", cmd_setcancel }, { "GETPIN", cmd_getpin }, { "CONFIRM", cmd_confirm }, { "MESSAGE", cmd_message }, { "SETQUALITYBAR", cmd_setqualitybar }, { "SETQUALITYBAR_TT", cmd_setqualitybar_tt }, { "SETGENPIN", cmd_setgenpin_label }, { "SETGENPIN_TT", cmd_setgenpin_tt }, { "GETINFO", cmd_getinfo }, { "SETTITLE", cmd_settitle }, { "SETTIMEOUT", cmd_settimeout }, { "CLEARPASSPHRASE", cmd_clear_passphrase }, { NULL } }; int i, j; gpg_error_t rc; for (i = j = 0; table[i].name; i++) { rc = assuan_register_command (ctx, table[i].name, table[i].handler, NULL); if (rc) return rc; } return 0; } int pinentry_loop2 (int infd, int outfd) { gpg_error_t rc; assuan_fd_t filedes[2]; assuan_context_t ctx; /* Extra check to make sure we have dropped privs. */ #ifndef HAVE_DOSISH_SYSTEM if (getuid() != geteuid()) abort (); #endif rc = assuan_new (&ctx); if (rc) { fprintf (stderr, "server context creation failed: %s\n", gpg_strerror (rc)); return -1; } /* 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] = assuan_fdopen (infd); filedes[1] = assuan_fdopen (outfd); rc = assuan_init_pipe_server (ctx, filedes); if (rc) { fprintf (stderr, "%s: failed to initialize the server: %s\n", this_pgmname, gpg_strerror (rc)); return -1; } rc = register_commands (ctx); if (rc) { fprintf (stderr, "%s: failed to the register commands with Assuan: %s\n", this_pgmname, gpg_strerror (rc)); return -1; } assuan_register_option_handler (ctx, option_handler); #if 0 assuan_set_log_stream (ctx, stderr); #endif assuan_register_reset_notify (ctx, pinentry_assuan_reset_handler); for (;;) { rc = assuan_accept (ctx); if (rc == -1) break; else if (rc) { fprintf (stderr, "%s: Assuan accept problem: %s\n", this_pgmname, gpg_strerror (rc)); break; } rc = assuan_process (ctx); if (rc) { fprintf (stderr, "%s: Assuan processing failed: %s\n", this_pgmname, gpg_strerror (rc)); continue; } } assuan_release (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/w32/main.c b/w32/main.c index 0b3d702..d8a4833 100644 --- a/w32/main.c +++ b/w32/main.c @@ -1,720 +1,719 @@ /* main.c - Secure W32 dialog for PIN entry. * Copyright (C) 2004, 2007 g10 Code GmbH * * 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, see . * SPDX-License-Identifier: GPL-2.0+ */ #include #include #include #if WINVER < 0x0403 # define WINVER 0x0403 /* Required for SendInput. */ #endif #include #ifdef HAVE_W32CE_SYSTEM # include # include #endif #include "pinentry.h" #include "memory.h" #include "resource.h" /* #include "msgcodes.h" */ #define PGMNAME "pinentry-w32" #ifndef LSFW_LOCK # define LSFW_LOCK 1 # define LSFW_UNLOCK 2 #endif #ifndef debugfp #define debugfp stderr #endif /* This function pointer gets initialized in main. */ #ifndef HAVE_W32CE_SYSTEM static BOOL WINAPI (*lock_set_foreground_window)(UINT); #endif static int w32_cmd_handler (pinentry_t pe); static void ok_button_clicked (HWND dlg, pinentry_t pe); /* We use global variables for the state, because there should never ever be a second instance. */ static HWND dialog_handle; static int confirm_mode; static int passphrase_ok; static int confirm_yes; /* The file descriptors for the loop. */ static int w32_infd; static int w32_outfd; /* Connect this module to the pinentry framework. */ pinentry_cmd_handler_t pinentry_cmd_handler = w32_cmd_handler; const char * w32_strerror (int ec) { static char strerr[256]; if (ec == -1) ec = (int)GetLastError (); #ifdef HAVE_W32CE_SYSTEM /* There is only a wchar_t FormatMessage. It does not make much sense to play the conversion game; we print only the code. */ snprintf (strerr, sizeof strerr, "ec=%d", ec); - strerr[sizeof strerr -1] = 0; #else FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, ec, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), strerr, sizeof strerr - 1, NULL); #endif return strerr; } #ifdef HAVE_W32CE_SYSTEM /* Create a pipe. WRITE_END shall have the opposite value of the one pssed to _assuan_w32ce_prepare_pipe; see there for more details. */ #define GPGCEDEV_IOCTL_MAKE_PIPE \ CTL_CODE (FILE_DEVICE_STREAMS, 2049, METHOD_BUFFERED, FILE_ANY_ACCESS) static HANDLE w32ce_finish_pipe (int rvid, int write_end) { HANDLE hd; hd = CreateFile (L"GPG1:", write_end? GENERIC_WRITE : GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL,NULL); if (hd != INVALID_HANDLE_VALUE) { if (!DeviceIoControl (hd, GPGCEDEV_IOCTL_MAKE_PIPE, &rvid, sizeof rvid, NULL, 0, NULL, NULL)) { DWORD lastrc = GetLastError (); CloseHandle (hd); hd = INVALID_HANDLE_VALUE; SetLastError (lastrc); } } return hd; } #endif /*HAVE_W32CE_SYSTEM*/ /* static HWND */ /* show_window_hierarchy (HWND parent, int level) */ /* { */ /* HWND child; */ /* child = GetWindow (parent, GW_CHILD); */ /* while (child) */ /* { */ /* char buf[1024+1]; */ /* char name[200]; */ /* int nname; */ /* char *pname; */ /* memset (buf, 0, sizeof (buf)); */ /* GetWindowText (child, buf, sizeof (buf)-1); */ /* nname = GetClassName (child, name, sizeof (name)-1); */ /* if (nname) */ /* pname = name; */ /* else */ /* pname = NULL; */ /* fprintf (debugfp, "### %*shwnd=%p (%s) `%s'\n", level*2, "", child, */ /* pname? pname:"", buf); */ /* show_window_hierarchy (child, level+1); */ /* child = GetNextWindow (child, GW_HWNDNEXT); */ /* } */ /* return NULL; */ /* } */ /* Convert a wchar to UTF8. Caller needs to release the string. Returns NULL on error. */ static char * wchar_to_utf8 (const wchar_t *string, size_t len, int secure) { int n; char *result; /* Note, that CP_UTF8 is not defined in Windows versions earlier than NT. */ n = WideCharToMultiByte (CP_UTF8, 0, string, len, NULL, 0, NULL, NULL); if (n < 0) return NULL; result = secure? secmem_malloc (n+1) : malloc (n+1); if (!result) return NULL; n = WideCharToMultiByte (CP_UTF8, 0, string, len, result, n, NULL, NULL); if (n < 0) { if (secure) secmem_free (result); else free (result); return NULL; } return result; } /* Convert a UTF8 string to wchar. Returns NULL on error. Caller needs to free the returned value. */ wchar_t * utf8_to_wchar (const char *string) { int n; wchar_t *result; size_t len = strlen (string); n = MultiByteToWideChar (CP_UTF8, 0, string, len, NULL, 0); if (n < 0) return NULL; result = calloc ((n+1), sizeof *result); if (!result) return NULL; n = MultiByteToWideChar (CP_UTF8, 0, string, len, result, n); if (n < 0) { free (result); return NULL; } result[n] = 0; return result; } /* Raise the software input panel. */ static void raise_sip (HWND dlg) { #ifdef HAVE_W32CE_SYSTEM SIPINFO si; SetForegroundWindow (dlg); memset (&si, 0, sizeof si); si.cbSize = sizeof si; if (SipGetInfo (&si)) { si.fdwFlags |= SIPF_ON; SipSetInfo (&si); } #else (void)dlg; #endif } /* Center the window CHILDWND with the desktop as its parent window. STYLE is passed as second arg to SetWindowPos.*/ static void center_window (HWND childwnd, HWND style) { #ifndef HAVE_W32CE_SYSTEM HWND parwnd; RECT rchild, rparent; HDC hdc; int wchild, hchild, wparent, hparent; int wscreen, hscreen, xnew, ynew; int flags = SWP_NOSIZE | SWP_NOZORDER; parwnd = GetDesktopWindow (); GetWindowRect (childwnd, &rchild); wchild = rchild.right - rchild.left; hchild = rchild.bottom - rchild.top; GetWindowRect (parwnd, &rparent); wparent = rparent.right - rparent.left; hparent = rparent.bottom - rparent.top; hdc = GetDC (childwnd); wscreen = GetDeviceCaps (hdc, HORZRES); hscreen = GetDeviceCaps (hdc, VERTRES); ReleaseDC (childwnd, hdc); xnew = rparent.left + ((wparent - wchild) / 2); if (xnew < 0) xnew = 0; else if ((xnew+wchild) > wscreen) xnew = wscreen - wchild; ynew = rparent.top + ((hparent - hchild) / 2); if (ynew < 0) ynew = 0; else if ((ynew+hchild) > hscreen) ynew = hscreen - hchild; if (style == HWND_TOPMOST || style == HWND_NOTOPMOST) flags = SWP_NOMOVE | SWP_NOSIZE; SetWindowPos (childwnd, style? style : NULL, xnew, ynew, 0, 0, flags); #endif } static void move_mouse_and_click (HWND hwnd) { #ifndef HAVE_W32CE_SYSTEM RECT rect; HDC hdc; int wscreen, hscreen, x, y, normx, normy; INPUT inp[3]; int idx; hdc = GetDC (hwnd); wscreen = GetDeviceCaps (hdc, HORZRES); hscreen = GetDeviceCaps (hdc, VERTRES); ReleaseDC (hwnd, hdc); if (wscreen < 10 || hscreen < 10) return; GetWindowRect (hwnd, &rect); x = rect.left; y = rect.bottom; normx = x * (65535 / wscreen); if (normx < 0 || normx > 65535) return; normy = y * (65535 / hscreen); if (normy < 0 || normy > 65535) return; for (idx=0; idx < 3; idx++) memset (&inp[idx], 0, sizeof inp[idx]); idx=0; inp[idx].type = INPUT_MOUSE; inp[idx].mi.dx = normx; inp[idx].mi.dy = normy; inp[idx].mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; idx++; inp[idx].type = INPUT_MOUSE; inp[idx].mi.dwFlags = MOUSEEVENTF_LEFTDOWN; idx++; inp[idx].type = INPUT_MOUSE; inp[idx].mi.dwFlags = MOUSEEVENTF_LEFTUP; idx++; if ( (SendInput (idx, inp, sizeof (INPUT)) != idx) && debugfp) fprintf (debugfp, "SendInput failed: %s\n", w32_strerror (-1)); #endif } /* Resize the button so that STRING fits into it. */ static void resize_button (HWND hwnd, const char *string) { if (!hwnd) return; /* FIXME: Need to figure out how to convert dialog coorddnates to screen coordinates and how buttons should be placed. */ /* SetWindowPos (hbutton, NULL, */ /* 10, 180, */ /* strlen (string+2), 14, */ /* (SWP_NOZORDER)); */ } /* Call SetDlgItemTextW with an UTF8 string. */ static void set_dlg_item_text (HWND dlg, int item, const char *string) { if (!string || !*string) SetDlgItemTextW (dlg, item, L""); else { wchar_t *wbuf; wbuf = utf8_to_wchar (string); if (!wbuf) SetDlgItemTextW (dlg, item, L"[out of core]"); else { SetDlgItemTextW (dlg, item, wbuf); free (wbuf); } } } /* Load our butmapped icon from the resource and display it. */ static void set_bitmap (HWND dlg, int item) { HWND hwnd; HBITMAP bitmap; RECT rect; int resid; hwnd = GetDlgItem (dlg, item); if (!hwnd) return; rect.left = 0; rect.top = 0; rect.right = 32; rect.bottom = 32; if (!MapDialogRect (dlg, &rect)) { fprintf (stderr, "MapDialogRect failed: %s\n", w32_strerror (-1)); return; } /* fprintf (stderr, "MapDialogRect: %d/%d\n", rect.right, rect.bottom); */ switch (rect.right) { case 32: resid = IDB_ICON_32; break; case 48: resid = IDB_ICON_48; break; case 64: resid = IDB_ICON_64; break; case 96: resid = IDB_ICON_96; break; default: resid = IDB_ICON_128;break; } bitmap = LoadImage (GetModuleHandle (NULL), MAKEINTRESOURCE (resid), IMAGE_BITMAP, rect.right, rect.bottom, (LR_SHARED | LR_LOADTRANSPARENT | LR_LOADMAP3DCOLORS)); if (!bitmap) { fprintf (stderr, "LoadImage failed: %s\n", w32_strerror (-1)); return; } SendMessage(hwnd, STM_SETIMAGE, (WPARAM)IMAGE_BITMAP, (LPARAM)bitmap); } /* Dialog processing loop. */ static BOOL CALLBACK dlg_proc (HWND dlg, UINT msg, WPARAM wparam, LPARAM lparam) { static pinentry_t pe; /* static int item; */ /* { */ /* int idx; */ /* for (idx=0; msgcodes[idx].string; idx++) */ /* if (msg == msgcodes[idx].msg) */ /* break; */ /* if (msgcodes[idx].string) */ /* fprintf (debugfp, "received %s\n", msgcodes[idx].string); */ /* else */ /* fprintf (debugfp, "received WM_%u\n", msg); */ /* } */ switch (msg) { case WM_INITDIALOG: dialog_handle = dlg; pe = (pinentry_t)lparam; if (!pe) abort (); set_dlg_item_text (dlg, IDC_PINENT_PROMPT, pe->prompt); set_dlg_item_text (dlg, IDC_PINENT_DESC, pe->description); set_dlg_item_text (dlg, IDC_PINENT_TEXT, ""); set_bitmap (dlg, IDC_PINENT_ICON); if (pe->ok) { set_dlg_item_text (dlg, IDOK, pe->ok); resize_button (GetDlgItem (dlg, IDOK), pe->ok); } if (pe->cancel) { set_dlg_item_text (dlg, IDCANCEL, pe->cancel); resize_button (GetDlgItem (dlg, IDCANCEL), pe->cancel); } if (pe->error) set_dlg_item_text (dlg, IDC_PINENT_ERR, pe->error); if (confirm_mode) { EnableWindow (GetDlgItem (dlg, IDC_PINENT_TEXT), FALSE); SetWindowPos (GetDlgItem (dlg, IDC_PINENT_TEXT), NULL, 0, 0, 0, 0, (SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_HIDEWINDOW)); /* item = IDOK; */ } /* else */ /* item = IDC_PINENT_TEXT; */ center_window (dlg, HWND_TOP); /* Unfortunately we can't use SetForegroundWindow because there is no easy eay to have all the calling processes do an AllowSetForegroundWindow. What we do instead is to bad hack by simulating a click to the Window. */ /* if (SetForegroundWindow (dlg) && lock_set_foreground_window) */ /* { */ /* lock_set_foreground_window (LSFW_LOCK); */ /* } */ /* show_window_hierarchy (GetDesktopWindow (), 0); */ ShowWindow (dlg, SW_SHOW); move_mouse_and_click ( GetDlgItem (dlg, IDC_PINENT_PROMPT) ); raise_sip (dlg); break; case WM_COMMAND: switch (LOWORD (wparam)) { case IDOK: if (confirm_mode) confirm_yes = 1; else ok_button_clicked (dlg, pe); EndDialog (dlg, TRUE); break; case IDCANCEL: pe->result = -1; EndDialog (dlg, FALSE); break; } break; case WM_KEYDOWN: if (wparam == VK_RETURN) { if (confirm_mode) confirm_yes = 1; else ok_button_clicked (dlg, pe); EndDialog (dlg, TRUE); } break; case WM_CTLCOLORSTATIC: if ((HWND)lparam == GetDlgItem (dlg, IDC_PINENT_ERR)) { /* Display the error prompt in red. */ SetTextColor ((HDC)wparam, RGB (255, 0, 0)); SetBkMode ((HDC)wparam, TRANSPARENT); return (BOOL)GetStockObject (NULL_BRUSH); } break; } return FALSE; } /* The okay button has been clicked or the enter enter key in the text field. */ static void ok_button_clicked (HWND dlg, pinentry_t pe) { char *s_utf8; wchar_t *w_buffer; size_t w_buffer_size = 255; unsigned int nchar; pe->locale_err = 1; w_buffer = secmem_malloc ((w_buffer_size + 1) * sizeof *w_buffer); if (!w_buffer) return; nchar = GetDlgItemTextW (dlg, IDC_PINENT_TEXT, w_buffer, w_buffer_size); s_utf8 = wchar_to_utf8 (w_buffer, nchar, 1); secmem_free (w_buffer); if (s_utf8) { passphrase_ok = 1; pinentry_setbufferlen (pe, strlen (s_utf8) + 1); if (pe->pin) strcpy (pe->pin, s_utf8); secmem_free (s_utf8); pe->locale_err = 0; pe->result = pe->pin? strlen (pe->pin) : 0; } } static int w32_cmd_handler (pinentry_t pe) { /* HWND lastwindow = GetForegroundWindow (); */ confirm_mode = !pe->pin; passphrase_ok = confirm_yes = 0; dialog_handle = NULL; DialogBoxParam (GetModuleHandle (NULL), MAKEINTRESOURCE (IDD_PINENT), GetDesktopWindow (), dlg_proc, (LPARAM)pe); if (dialog_handle) { /* if (lock_set_foreground_window) */ /* lock_set_foreground_window (LSFW_UNLOCK); */ /* if (lastwindow) */ /* SetForegroundWindow (lastwindow); */ } else return -1; if (confirm_mode) return confirm_yes; else if (passphrase_ok && pe->pin) return strlen (pe->pin); else return -1; } /* WindowsCE uses a very strange way of handling the standard streams. There is a function SetStdioPath to associate a standard stream with a file or a device but what we really want is to use pipes as standard streams. Despite that we implement pipes using a device, we would have some limitations on the number of open pipes due to the 3 character limit of device file name. Thus we don't take this path. Another option would be to install a file system driver with support for pipes; this would allow us to get rid of the device name length limitation. However, with GnuPG we can get away be redefining the standard streams and passing the handles to be used on the command line. This has also the advantage that it makes creating a process much easier and does not require the SetStdioPath set and restore game. The caller needs to pass the rendezvous ids using up to three options: -&S0= -&S1= -&S2= They are all optional but they must be the first arguments on the command line. Parsing stops as soon as an invalid option is found. These rendezvous ids are then used to finish the pipe creation.*/ #ifdef HAVE_W32CE_SYSTEM static void parse_std_file_handles (int *argcp, char ***argvp) { int argc = *argcp; char **argv = *argvp; const char *s; int fd; int i; int fixup = 0; if (!argc) return; for (argc--, argv++; argc; argc--, argv++) { s = *argv; if (*s == '-' && s[1] == '&' && s[2] == 'S' && (s[3] == '0' || s[3] == '1' || s[3] == '2') && s[4] == '=' && (strchr ("-01234567890", s[5]) || !strcmp (s+5, "null"))) { if (s[5] == 'n') fd = (int)(-1); else fd = (int)w32ce_finish_pipe (atoi (s+5), s[3] != '0'); if (s[3] == '0' && fd != -1) w32_infd = fd; else if (s[3] == '1' && fd != -1) w32_outfd = fd; fixup++; } else break; } if (fixup) { argc = *argcp; argc -= fixup; *argcp = argc; argv = *argvp; for (i=1; i < argc; i++) argv[i] = argv[i + fixup]; for (; i < argc + fixup; i++) argv[i] = NULL; } } #endif /*HAVE_W32CE_SYSTEM*/ int main (int argc, char **argv) { #ifndef HAVE_W32CE_SYSTEM void *handle; #endif w32_infd = STDIN_FILENO; w32_outfd = STDOUT_FILENO; #ifdef HAVE_W32CE_SYSTEM parse_std_file_handles (&argc, &argv); #endif pinentry_init (PGMNAME); pinentry_parse_opts (argc, argv); /* debugfp = fopen ("pinentry.log", "w"); */ /* if (!debugfp) */ /* debugfp = stderr; */ /* We need to load a function because that one is only available since W2000 but not in older NTs. */ #ifndef HAVE_W32CE_SYSTEM handle = LoadLibrary ("user32.dll"); if (handle) { void *foo; foo = GetProcAddress (handle, "LockSetForegroundWindow"); if (foo) lock_set_foreground_window = foo; else CloseHandle (handle); } #endif if (pinentry_loop2 (w32_infd, w32_outfd)) return 1; #ifdef HAVE_W32CE_SYSTEM Sleep (400); #endif return 0; }