Page MenuHome GnuPG

No OneTemporary

diff --git a/gnome3/pinentry-gnome3.c b/gnome3/pinentry-gnome3.c
index c148fd9..27d2132 100644
--- a/gnome3/pinentry-gnome3.c
+++ b/gnome3/pinentry-gnome3.c
@@ -1,541 +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, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <gcr/gcr-base.h>
#include <string.h>
#include <stdlib.h>
#include <assuan.h>
#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;
+ 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. */
- msg = pinentry_utf8_validate (pe->title ? pe->title : PGMNAME);
- gcr_prompt_set_title (prompt, msg);
- g_free (msg);
+ 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 : "<no GError>");
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,
0,
NULL,
&error);
g_object_unref(dbus);
if (!reply)
{
/* G_IO_ERROR_TIMED_OUT 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_TIMED_OUT))
fprintf (stderr, "Failed to get d-bus reply for org.gnome.ScreenSaver.GetActive (%d): %s\n",
error ? error->code : -1,
error ? error->message : "<no GError>");
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 : "<no GError>");
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 79cecda..dee0360 100644
--- a/gtk+-2/pinentry-gtk-2.c
+++ b/gtk+-2/pinentry-gtk-2.c
@@ -1,978 +1,973 @@
/* pinentry-gtk-2.c
Copyright (C) 1999 Robert Bihlmeyer <robbe@orcus.priv.at>
Copyright (C) 2001, 2002, 2007, 2015 g10 Code GmbH
Copyright (C) 2004 by Albrecht Dreß <albrecht.dress@arcor.de>
pinentry-gtk-2 is a pinentry application for the Gtk+-2 widget set.
It tries to follow the Gnome Human Interface Guide as close as
possible.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gdk/gdkkeysyms.h>
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7 )
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wstrict-prototypes"
#endif
#include <gtk/gtk.h>
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7 )
# pragma GCC diagnostic pop
#endif
#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <gpg-error.h>
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#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;
#if !GTK_CHECK_VERSION (2, 12, 0)
static GtkTooltips *tooltips;
#endif
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 if we grab the keyboard. This
makes the window a modal dialog to the root window, which helps the
window manager. See the following quote from:
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;
if (! pinentry->grab)
return;
/* Make window transient for the root window. */
screen = gdk_screen_get_default ();
root = gdk_screen_get_root_window (screen);
gdk_window_set_transient_for (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);
}
#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 = "<span font=\"Monospace\" size=\"xx-small\">abc</span>";
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 = "<span font=\"Monospace\" size=\"xx-small\">***</span>";
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);
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;
#if !GTK_CHECK_VERSION (2, 12, 0)
tooltips = gtk_tooltips_new ();
#endif
/* 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);
if (!confirm_mode)
{
if (pinentry->grab)
g_signal_connect (G_OBJECT (win),
"realize", G_CALLBACK (make_transient), NULL);
/* We need to grab the keyboard when its visible! not when its
mapped (there is a difference) */
g_object_set (G_OBJECT(win), "events",
GDK_VISIBILITY_NOTIFY_MASK | GDK_STRUCTURE_MASK, NULL);
g_signal_connect (G_OBJECT (win),
pinentry->grab
? "visibility-notify-event"
: "focus-in-event",
G_CALLBACK (grab_keyboard), NULL);
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);
- if (pinentry->title)
+ p = pinentry_get_title (pinentry);
+ if (p)
{
- msg = pinentry_utf8_validate (pinentry->title);
- gtk_window_set_title (GTK_WINDOW(win), msg);
- }
- else if (pinentry->owner_pid)
- {
- char buf[100];
- snprintf (buf, sizeof buf, "%s [%lu]",
- pinentry->owner_host? pinentry->owner_host:"",
- pinentry->owner_pid);
- buf[sizeof buf - 1] = 0;
- gtk_window_set_title (GTK_WINDOW(win), buf);
+ 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);
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)
{
#if !GTK_CHECK_VERSION (2, 12, 0)
gtk_tooltips_set_tip (GTK_TOOLTIPS (tooltips), qualitybar,
pinentry->quality_bar_tt, "");
#else
gtk_widget_set_tooltip_text (qualitybar,
pinentry->quality_bar_tt);
#endif
}
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_FLAGS (w, GTK_CAN_DEFAULT);
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-emacs.c b/pinentry/pinentry-emacs.c
index 50ba406..2f7693b 100644
--- a/pinentry/pinentry-emacs.c
+++ b/pinentry/pinentry-emacs.c
@@ -1,698 +1,704 @@
/* pinentry-emacs.c - A secure emacs dialog for PIN entry, library version
Copyright (C) 2015 Daiki Ueno
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 <https://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif
#ifdef HAVE_INTTYPES_H
#include <inttypes.h>
#endif
#include <assert.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#ifdef HAVE_UTIME_H
#include <utime.h>
#endif /*HAVE_UTIME_H*/
#include <assuan.h>
#include "pinentry-emacs.h"
#include "memory.h"
#include "secmem-util.h"
/* The communication mechanism is similar to emacsclient, but there
are a few differences:
- To avoid unnecessary character escaping and encoding conversion,
we use a subset of the Pinentry Assuan protocol, instead of the
emacsclient protocol.
- We only use a Unix domain socket, while emacsclient has an
ability to use a TCP socket. The socket file is located at
${TMPDIR-/tmp}/emacs$(id -u)/pinentry (i.e., under the same
directory as the socket file used by emacsclient, so the same
permission and file owner settings apply).
- The server implementation can be found in pinentry.el, which is
available in Emacs 25+ or from ELPA. */
#define LINELENGTH ASSUAN_LINELENGTH
#define SEND_BUFFER_SIZE 4096
#define INITIAL_TIMEOUT 60
static int initial_timeout = INITIAL_TIMEOUT;
#undef MIN
#define MIN(x, y) ((x) < (y) ? (x) : (y))
#undef MAX
#define MAX(x, y) ((x) < (y) ? (y) : (x))
#ifndef SUN_LEN
# define SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) \
+ strlen ((ptr)->sun_path))
#endif
/* FIXME: We could use the I/O functions in Assuan directly, once
Pinentry links to libassuan. */
static int emacs_socket = -1;
static char send_buffer[SEND_BUFFER_SIZE + 1];
static int send_buffer_length; /* Fill pointer for the send buffer. */
static pinentry_cmd_handler_t fallback_cmd_handler;
#ifndef HAVE_DOSISH_SYSTEM
static int timed_out;
#endif
static int
set_socket (const char *socket_name)
{
struct sockaddr_un unaddr;
struct stat statbuf;
const char *tmpdir;
char *tmpdir_storage = NULL;
char *socket_name_storage = NULL;
uid_t uid;
unaddr.sun_family = AF_UNIX;
/* We assume 32-bit UIDs, which can be represented with 10 decimal
digits. */
uid = getuid ();
if (uid != (uint32_t) uid)
{
fprintf (stderr, "UID is too large\n");
return 0;
}
tmpdir = getenv ("TMPDIR");
if (!tmpdir)
{
#ifdef _CS_DARWIN_USER_TEMP_DIR
size_t n = confstr (_CS_DARWIN_USER_TEMP_DIR, NULL, (size_t) 0);
if (n > 0)
{
tmpdir = tmpdir_storage = malloc (n);
if (!tmpdir)
{
fprintf (stderr, "out of core\n");
return 0;
}
confstr (_CS_DARWIN_USER_TEMP_DIR, tmpdir_storage, n);
}
else
#endif
tmpdir = "/tmp";
}
socket_name_storage = malloc (strlen (tmpdir)
+ strlen ("/emacs") + 10 + strlen ("/")
+ strlen (socket_name)
+ 1);
if (!socket_name_storage)
{
fprintf (stderr, "out of core\n");
free (tmpdir_storage);
return 0;
}
sprintf (socket_name_storage, "%s/emacs%u/%s", tmpdir,
(uint32_t) uid, socket_name);
free (tmpdir_storage);
if (strlen (socket_name_storage) >= sizeof (unaddr.sun_path))
{
fprintf (stderr, "socket name is too long\n");
free (socket_name_storage);
return 0;
}
strcpy (unaddr.sun_path, socket_name_storage);
free (socket_name_storage);
/* See if the socket exists, and if it's owned by us. */
if (stat (unaddr.sun_path, &statbuf) == -1)
{
perror ("stat");
return 0;
}
if (statbuf.st_uid != geteuid ())
{
fprintf (stderr, "socket is not owned by the same user\n");
return 0;
}
emacs_socket = socket (AF_UNIX, SOCK_STREAM, 0);
if (emacs_socket < 0)
{
perror ("socket");
return 0;
}
if (connect (emacs_socket, (struct sockaddr *) &unaddr,
SUN_LEN (&unaddr)) < 0)
{
perror ("connect");
close (emacs_socket);
emacs_socket = -1;
return 0;
}
return 1;
}
/* Percent-escape control characters in DATA. Return a newly
allocated string. */
static char *
escape (const char *data)
{
char *buffer, *out_p;
size_t length, buffer_length;
size_t offset;
size_t count = 0;
length = strlen (data);
for (offset = 0; offset < length; offset++)
{
switch (data[offset])
{
case '%': case '\n': case '\r':
count++;
break;
default:
break;
}
}
buffer_length = length + count * 2;
buffer = malloc (buffer_length + 1);
if (!buffer)
return NULL;
out_p = buffer;
for (offset = 0; offset < length; offset++)
{
int c = data[offset];
switch (c)
{
case '%': case '\n': case '\r':
sprintf (out_p, "%%%02X", c);
out_p += 3;
break;
default:
*out_p++ = c;
break;
}
}
*out_p = '\0';
return buffer;
}
/* The inverse of escape. Unlike escape, it removes quoting in string
DATA by modifying the string in place, to avoid copying of secret
data sent from Emacs. */
static char *
unescape (char *data)
{
char *p = data, *q = data;
while (*p)
{
if (*p == '%' && p[1] && p[2])
{
p++;
*q++ = xtoi_2 (p);
p += 2;
}
else
*q++ = *p++;
}
*q = 0;
return data;
}
/* Let's send the data to Emacs when either
- the data ends in "\n", or
- the buffer is full (but this shouldn't happen)
Otherwise, we just accumulate it. */
static int
send_to_emacs (int s, const char *buffer)
{
size_t length;
length = strlen (buffer);
while (*buffer)
{
size_t part = MIN (length, SEND_BUFFER_SIZE - send_buffer_length);
memcpy (&send_buffer[send_buffer_length], buffer, part);
buffer += part;
send_buffer_length += part;
if (send_buffer_length == SEND_BUFFER_SIZE
|| (send_buffer_length > 0
&& send_buffer[send_buffer_length-1] == '\n'))
{
int sent = send (s, send_buffer, send_buffer_length, 0);
if (sent < 0)
{
fprintf (stderr, "failed to send %d bytes to socket: %s\n",
send_buffer_length, strerror (errno));
send_buffer_length = 0;
return 0;
}
if (sent != send_buffer_length)
memmove (send_buffer, &send_buffer[sent],
send_buffer_length - sent);
send_buffer_length -= sent;
}
length -= part;
}
return 1;
}
/* Read a server response. If the response contains data, it will be
stored in BUFFER with a terminating NUL byte. BUFFER must be
at least as large as CAPACITY. */
static gpg_error_t
read_from_emacs (int s, int timeout, char *buffer, size_t capacity)
{
struct timeval tv;
fd_set rfds;
int retval;
/* Offset in BUFFER. */
size_t offset = 0;
int got_response = 0;
char read_buffer[LINELENGTH + 1];
/* Offset in READ_BUFFER. */
size_t read_offset = 0;
gpg_error_t result = 0;
tv.tv_sec = timeout;
tv.tv_usec = 0;
FD_ZERO (&rfds);
FD_SET (s, &rfds);
retval = select (s + 1, &rfds, NULL, NULL, &tv);
if (retval == -1)
{
perror ("select");
return gpg_error (GPG_ERR_ASS_GENERAL);
}
else if (retval == 0)
{
timed_out = 1;
return gpg_error (GPG_ERR_TIMEOUT);
}
/* Loop until we get either OK or ERR. */
while (!got_response)
{
int rl = 0;
char *p, *end_p;
do
{
errno = 0;
rl = recv (s, read_buffer + read_offset, LINELENGTH - read_offset, 0);
}
/* If we receive a signal (e.g. SIGWINCH, which we pass
through to Emacs), on some OSes we get EINTR and must retry. */
while (rl < 0 && errno == EINTR);
if (rl < 0)
{
perror ("recv");
return gpg_error (GPG_ERR_ASS_GENERAL);;
}
if (rl == 0)
break;
read_offset += rl;
read_buffer[read_offset] = '\0';
end_p = strchr (read_buffer, '\n');
/* If the buffer is filled without NL, throw away the content
and start over the buffering.
FIXME: We could return ASSUAN_Line_Too_Long or
ASSUAN_Line_Not_Terminated here. */
if (!end_p && read_offset == sizeof (read_buffer) - 1)
{
read_offset = 0;
continue;
}
/* Loop over all NL-terminated messages. */
for (p = read_buffer; end_p; p = end_p + 1, end_p = strchr (p, '\n'))
{
*end_p = '\0';
if (!strncmp ("D ", p, 2))
{
char *data;
size_t data_length;
size_t needed_capacity;
data = p + 2;
data_length = end_p - data;
if (data_length > 0)
{
needed_capacity = offset + data_length + 1;
/* Check overflow. This is unrealistic but can
happen since OFFSET is cumulative. */
if (needed_capacity < offset)
return gpg_error (GPG_ERR_ASS_GENERAL);;
if (needed_capacity > capacity)
return gpg_error (GPG_ERR_ASS_GENERAL);;
memcpy (&buffer[offset], data, data_length);
offset += data_length;
buffer[offset] = 0;
}
}
else if (!strcmp ("OK", p) || !strncmp ("OK ", p, 3))
{
got_response = 1;
break;
}
else if (!strncmp ("ERR ", p, 4))
{
unsigned long code = strtoul (p + 4, NULL, 10);
if (code == ULONG_MAX && errno == ERANGE)
return gpg_error (GPG_ERR_ASS_GENERAL);
else
result = code;
got_response = 1;
break;
}
else if (*p == '#')
;
else
fprintf (stderr, "invalid response: %s\n", p);
}
if (!got_response)
{
size_t length = &read_buffer[read_offset] - p;
memmove (read_buffer, p, length);
read_offset = length;
}
}
return result;
}
int
set_label (pinentry_t pe, const char *name, const char *value)
{
char buffer[16], *escaped;
gpg_error_t error;
int retval;
if (!send_to_emacs (emacs_socket, name)
|| !send_to_emacs (emacs_socket, " "))
return 0;
escaped = escape (value);
if (!escaped)
return 0;
retval = send_to_emacs (emacs_socket, escaped)
&& send_to_emacs (emacs_socket, "\n");
free (escaped);
if (!retval)
return 0;
error = read_from_emacs (emacs_socket, pe->timeout, buffer, sizeof (buffer));
return error == 0;
}
static void
set_labels (pinentry_t pe)
{
- if (pe->title)
- set_label (pe, "SETTITLE", pe->title);
+ char *p;
+
+ p = pinentry_get_title (pe);
+ if (p)
+ {
+ set_label (pe, "SETTITLE", p);
+ free (p);
+ }
if (pe->description)
set_label (pe, "SETDESC", pe->description);
if (pe->error)
set_label (pe, "SETERROR", pe->error);
if (pe->prompt)
set_label (pe, "SETPROMPT", pe->prompt);
else if (pe->default_prompt)
set_label (pe, "SETPROMPT", pe->default_prompt);
if (pe->repeat_passphrase)
set_label (pe, "SETREPEAT", pe->repeat_passphrase);
if (pe->repeat_error_string)
set_label (pe, "SETREPEATERROR", pe->repeat_error_string);
/* XXX: pe->quality_bar and pe->quality_bar_tt are not supported. */
/* Buttons. */
if (pe->ok)
set_label (pe, "SETOK", pe->ok);
else if (pe->default_ok)
set_label (pe, "SETOK", pe->default_ok);
if (pe->cancel)
set_label (pe, "SETCANCEL", pe->cancel);
else if (pe->default_ok)
set_label (pe, "SETCANCEL", pe->default_cancel);
if (pe->notok)
set_label (pe, "SETNOTOK", pe->notok);
}
static int
do_password (pinentry_t pe)
{
char *buffer, *password;
size_t length = LINELENGTH;
gpg_error_t error;
set_labels (pe);
if (!send_to_emacs (emacs_socket, "GETPIN\n"))
return -1;
buffer = secmem_malloc (length);
if (!buffer)
{
pe->specific_err = gpg_error (GPG_ERR_ENOMEM);
return -1;
}
error = read_from_emacs (emacs_socket, pe->timeout, buffer, length);
if (error != 0)
{
if (gpg_err_code (error) == GPG_ERR_CANCELED)
pe->canceled = 1;
secmem_free (buffer);
pe->specific_err = error;
return -1;
}
password = unescape (buffer);
pinentry_setbufferlen (pe, strlen (password) + 1);
if (pe->pin)
strcpy (pe->pin, password);
secmem_free (buffer);
if (pe->repeat_passphrase)
pe->repeat_okay = 1;
/* XXX: we don't support external password cache (yet). */
return 1;
}
static int
do_confirm (pinentry_t pe)
{
char buffer[16];
gpg_error_t error;
set_labels (pe);
if (!send_to_emacs (emacs_socket, "CONFIRM\n"))
return 0;
error = read_from_emacs (emacs_socket, pe->timeout, buffer, sizeof (buffer));
if (error != 0)
{
if (gpg_err_code (error) == GPG_ERR_CANCELED)
pe->canceled = 1;
pe->specific_err = error;
return 0;
}
return 1;
}
/* If a touch has been registered, touch that file. */
static void
do_touch_file (pinentry_t pinentry)
{
#ifdef HAVE_UTIME_H
struct stat st;
time_t tim;
if (!pinentry->touch_file || !*pinentry->touch_file)
return;
if (stat (pinentry->touch_file, &st))
return; /* Oops. */
/* Make sure that we actually update the mtime. */
while ( (tim = time (NULL)) == st.st_mtime )
sleep (1);
/* Update but ignore errors as we can't do anything in that case.
Printing error messages may even clubber the display further. */
utime (pinentry->touch_file, NULL);
#endif /*HAVE_UTIME_H*/
}
#ifndef HAVE_DOSISH_SYSTEM
static void
catchsig (int sig)
{
if (sig == SIGALRM)
timed_out = 1;
}
#endif
int
emacs_cmd_handler (pinentry_t pe)
{
int rc;
#ifndef HAVE_DOSISH_SYSTEM
timed_out = 0;
if (pe->timeout)
{
struct sigaction sa;
memset (&sa, 0, sizeof(sa));
sa.sa_handler = catchsig;
sigaction (SIGALRM, &sa, NULL);
alarm (pe->timeout);
}
#endif
if (pe->pin)
rc = do_password (pe);
else
rc = do_confirm (pe);
do_touch_file (pe);
return rc;
}
static int
initial_emacs_cmd_handler (pinentry_t pe)
{
/* Let the select() call in pinentry_emacs_init honor the timeout
value set through an Assuan option. */
initial_timeout = pe->timeout;
if (emacs_socket < 0)
pinentry_emacs_init ();
/* If we have successfully connected to Emacs, swap
pinentry_cmd_handler to emacs_cmd_handler, so further
interactions will be forwarded to Emacs. Otherwise, set it back
to the original command handler saved as
fallback_cmd_handler. */
if (emacs_socket < 0)
pinentry_cmd_handler = fallback_cmd_handler;
else
{
pinentry_cmd_handler = emacs_cmd_handler;
pinentry_set_flavor_flag ("emacs");
}
return (* pinentry_cmd_handler) (pe);
}
void
pinentry_enable_emacs_cmd_handler (void)
{
const char *envvar;
/* Check if pinentry_cmd_handler is already prepared for Emacs. */
if (pinentry_cmd_handler == initial_emacs_cmd_handler
|| pinentry_cmd_handler == emacs_cmd_handler)
return;
/* Check if INSIDE_EMACS envvar is set. */
envvar = getenv ("INSIDE_EMACS");
if (!envvar || !*envvar)
return;
/* Save the original command handler as fallback_cmd_handler, and
swap pinentry_cmd_handler to initial_emacs_cmd_handler. */
fallback_cmd_handler = pinentry_cmd_handler;
pinentry_cmd_handler = initial_emacs_cmd_handler;
}
int
pinentry_emacs_init (void)
{
char buffer[256];
gpg_error_t error;
assert (emacs_socket < 0);
/* Check if we can connect to the Emacs server socket. */
if (!set_socket ("pinentry"))
return 0;
/* Check if the server responds. */
error = read_from_emacs (emacs_socket, initial_timeout,
buffer, sizeof (buffer));
if (error != 0)
{
close (emacs_socket);
emacs_socket = -1;
return 0;
}
return 1;
}
diff --git a/pinentry/pinentry.c b/pinentry/pinentry.c
index d33ebe9..7251899 100644
--- a/pinentry/pinentry.c
+++ b/pinentry/pinentry.c
@@ -1,1722 +1,1749 @@
/* pinentry.c - The PIN entry support library
Copyright (C) 2002, 2003, 2007, 2008, 2010, 2015, 2016 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 <https://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifndef HAVE_W32CE_SYSTEM
# include <errno.h>
#endif
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#ifndef HAVE_W32CE_SYSTEM
# include <locale.h>
#endif
#ifdef HAVE_LANGINFO_H
#include <langinfo.h>
#endif
#include <limits.h>
#ifdef HAVE_W32CE_SYSTEM
# include <windows.h>
#endif
#if defined FALLBACK_CURSES || defined PINENTRY_CURSES || defined PINENTRY_GTK
#include <iconv.h>
#endif
#include <assuan.h>
#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 --disable 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;
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 *touch_file = pinentry.touch_file;
unsigned long owner_pid = pinentry.owner_pid;
char *owner_host = pinentry.owner_host;
/* 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);
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.touch_file);
free (pinentry.owner_host);
free (pinentry.display);
}
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.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;
}
else /* Restore the options. */
{
pinentry.grab = grab;
pinentry.ttyname = ttyname;
pinentry.ttytype = ttytype;
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.touch_file = touch_file;
pinentry.owner_pid = owner_pid;
pinentry.owner_host = owner_host;
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;
}
static int lc_ctype_unknown_warning = 0;
#if defined FALLBACK_CURSES || defined PINENTRY_CURSES || defined PINENTRY_GTK
char *
pinentry_utf8_to_local (const char *lc_ctype, const char *text)
{
iconv_t cd;
const char *input = text;
size_t input_len = strlen (text) + 1;
char *output;
size_t output_len;
char *output_buf;
size_t processed;
char *old_ctype;
char *target_encoding;
/* If no locale setting could be determined, simply copy the
string. */
if (!lc_ctype)
{
if (! lc_ctype_unknown_warning)
{
fprintf (stderr, "%s: no LC_CTYPE known - assuming UTF-8\n",
this_pgmname);
lc_ctype_unknown_warning = 1;
}
return strdup (text);
}
old_ctype = strdup (setlocale (LC_CTYPE, NULL));
if (!old_ctype)
return NULL;
setlocale (LC_CTYPE, lc_ctype);
target_encoding = nl_langinfo (CODESET);
if (!target_encoding)
target_encoding = "?";
setlocale (LC_CTYPE, old_ctype);
free (old_ctype);
/* This is overkill, but simplifies the iconv invocation greatly. */
output_len = input_len * MB_LEN_MAX;
output_buf = output = malloc (output_len);
if (!output)
return NULL;
cd = iconv_open (target_encoding, "UTF-8");
if (cd == (iconv_t) -1)
{
fprintf (stderr, "%s: can't convert from UTF-8 to %s: %s\n",
this_pgmname, target_encoding, strerror (errno));
free (output_buf);
return NULL;
}
processed = iconv (cd, (ICONV_CONST char **)&input, &input_len,
&output, &output_len);
iconv_close (cd);
if (processed == (size_t) -1 || input_len)
{
fprintf (stderr, "%s: error converting from UTF-8 to %s: %s\n",
this_pgmname, target_encoding, strerror (errno));
free (output_buf);
return NULL;
}
return output_buf;
}
/* Convert TEXT which is encoded according to LC_CTYPE to UTF-8. With
SECURE set to true, use secure memory for the returned buffer.
Return NULL on error. */
char *
pinentry_local_to_utf8 (char *lc_ctype, char *text, int secure)
{
char *old_ctype;
char *source_encoding;
iconv_t cd;
const char *input = text;
size_t input_len = strlen (text) + 1;
char *output;
size_t output_len;
char *output_buf;
size_t processed;
/* If no locale setting could be determined, simply copy the
string. */
if (!lc_ctype)
{
if (! lc_ctype_unknown_warning)
{
fprintf (stderr, "%s: no LC_CTYPE known - assuming UTF-8\n",
this_pgmname);
lc_ctype_unknown_warning = 1;
}
output_buf = secure? secmem_malloc (input_len) : malloc (input_len);
if (output_buf)
strcpy (output_buf, input);
return output_buf;
}
old_ctype = strdup (setlocale (LC_CTYPE, NULL));
if (!old_ctype)
return NULL;
setlocale (LC_CTYPE, lc_ctype);
source_encoding = nl_langinfo (CODESET);
setlocale (LC_CTYPE, old_ctype);
free (old_ctype);
/* This is overkill, but simplifies the iconv invocation greatly. */
output_len = input_len * MB_LEN_MAX;
output_buf = output = secure? secmem_malloc (output_len):malloc (output_len);
if (!output)
return NULL;
cd = iconv_open ("UTF-8", source_encoding);
if (cd == (iconv_t) -1)
{
fprintf (stderr, "%s: can't convert from %s to UTF-8: %s\n",
this_pgmname, source_encoding? source_encoding : "?",
strerror (errno));
if (secure)
secmem_free (output_buf);
else
free (output_buf);
return NULL;
}
processed = iconv (cd, (ICONV_CONST char **)&input, &input_len,
&output, &output_len);
iconv_close (cd);
if (processed == (size_t) -1 || input_len)
{
fprintf (stderr, "%s: error converting from %s to UTF-8: %s\n",
this_pgmname, source_encoding? source_encoding : "?",
strerror (errno));
if (secure)
secmem_free (output_buf);
else
free (output_buf);
return NULL;
}
return output_buf;
}
#endif
/* Copy TEXT or TEXTLEN to BUFFER and escape as required. Return a
pointer to the end of the new buffer. Note that BUFFER must be
large enough to keep the entire text; allocataing it 3 times of
TEXTLEN is sufficient. */
static char *
copy_and_escape (char *buffer, const void *text, size_t textlen)
{
int i;
const unsigned char *s = (unsigned char *)text;
char *p = buffer;
for (i=0; i < textlen; i++)
{
if (s[i] < ' ' || s[i] == '+')
{
snprintf (p, 4, "%%%02X", s[i]);
p += 3;
}
else if (s[i] == ' ')
*p++ = '+';
else
*p++ = s[i];
}
return p;
}
+/* 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);
+ else if (pe->owner_pid)
+ {
+ char buf[100];
+ 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;
+ title = strdup (buf);
+ }
+ 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;
}
/* 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_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 = strdup (pargs.r.ret_str);
if (!pinentry.ttytype)
{
#ifndef HAVE_W32CE_SYSTEM
fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno));
#endif
exit (EXIT_FAILURE);
}
break;
case 'C':
pinentry.lc_ctype = strdup (pargs.r.ret_str);
if (!pinentry.lc_ctype)
{
#ifndef HAVE_W32CE_SYSTEM
fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno));
#endif
exit (EXIT_FAILURE);
}
break;
case 'M':
pinentry.lc_messages = strdup (pargs.r.ret_str);
if (!pinentry.lc_messages)
{
#ifndef HAVE_W32CE_SYSTEM
fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno));
#endif
exit (EXIT_FAILURE);
}
break;
case 'W':
pinentry.parent_wid = pargs.r.ret_ulong;
break;
case 'c':
{
char *tmpstr = pargs.r.ret_str;
tmpstr = parse_color (tmpstr, &pinentry.color_fg,
&pinentry.color_fg_bright);
tmpstr = parse_color (tmpstr, &pinentry.color_bg, NULL);
tmpstr = parse_color (tmpstr, &pinentry.color_so,
&pinentry.color_so_bright);
}
break;
case 'o':
pinentry.timeout = pargs.r.ret_int;
break;
default:
pargs.err = ARGPARSE_PRINT_WARNING;
break;
}
}
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_W32CE_SYSTEM
fprintf (stderr, "%s: waiting for debugger - my pid is %u ...\n",
this_pgmname, (unsigned int) getpid());
sleep (*value?atoi (value):5);
fprintf (stderr, "%s: ... okay\n", this_pgmname);
#endif
}
else if (!strcmp (key, "display"))
{
if (pinentry.display)
free (pinentry.display);
pinentry.display = strdup (value);
if (!pinentry.display)
return 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)
free (pinentry.ttytype);
pinentry.ttytype = strdup (value);
if (!pinentry.ttytype)
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;
errno = 0;
along = strtol (value, &endp, 10);
if (along < 0 || errno)
pinentry.owner_pid = 0;
else
{
pinentry.owner_pid = (unsigned long)along;
while (endp && *endp == ' ')
endp++;
if (*endp)
{
pinentry.owner_host = strdup (endp);
if (pinentry.owner_host)
{
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, "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
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;
}
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;
pinentry.tried_password_cache = 1;
password = password_cache_lookup (pinentry.keyinfo);
if (password)
/* There is a cached password. Try it. */
{
int len = strlen(password) + 1;
if (len > pinentry.pin_len)
len = pinentry.pin_len;
memcpy (pinentry.pin, password, len);
pinentry.pin[len] = '\0';
secmem_free (password);
pinentry.pin_from_cache = 1;
assuan_write_status (ctx, "PASSWORD_FROM_CACHE", "");
/* Result is the length of the password not including the
NUL terminator. */
result = len - 1;
just_read_password_from_cache = 1;
goto out;
}
}
/* The password was not cached (or we are not allowed to / cannot
use the cache). Prompt the user. */
pinentry.pin_from_cache = 0;
if (!pinentry.prompt)
{
pinentry.prompt = pinentry.default_prompt?pinentry.default_prompt:"PIN:";
set_prompt = 1;
}
pinentry.locale_err = 0;
pinentry.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);
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", "");
result = assuan_send_data (ctx, pinentry.pin, strlen(pinentry.pin));
if (!result)
result = assuan_send_data (ctx, NULL, 0);
if (/* GPG Agent says it's okay. */
pinentry.allow_external_password_cache && pinentry.keyinfo
/* We didn't just read it from the cache. */
&& ! just_read_password_from_cache
/* And the user said it's okay. */
&& pinentry.may_cache_password)
/* Cache the password. */
password_cache_save (pinentry.keyinfo, pinentry.pin);
}
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);
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");
}
/* GETINFO <what>
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 and ttyinfo.
*/
static gpg_error_t
cmd_getinfo (assuan_context_t ctx, char *line)
{
int rc;
const char *s;
char buffer[100];
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));
}
else if (!strcmp (line, "ttyinfo"))
{
snprintf (buffer, sizeof buffer, "%s %s %s",
pinentry.ttyname? pinentry.ttyname : "-",
pinentry.ttytype? pinentry.ttytype : "-",
pinentry.display? pinentry.display : "-" );
buffer[sizeof buffer -1] = 0;
rc = assuan_send_data (ctx, buffer, strlen (buffer));
}
else
rc = gpg_error (GPG_ERR_ASS_PARAMETER);
return rc;
}
/* CLEARPASSPHRASE <cacheid>
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 },
{ "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/pinentry/pinentry.h b/pinentry/pinentry.h
index 1e891b7..868b4d8 100644
--- a/pinentry/pinentry.h
+++ b/pinentry/pinentry.h
@@ -1,313 +1,314 @@
/* pinentry.h - The interface for the PIN entry support library.
Copyright (C) 2002, 2003, 2010, 2015 g10 Code GmbH
This file is part of PINENTRY.
PINENTRY is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
PINENTRY is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef PINENTRY_H
#define PINENTRY_H
#ifdef __cplusplus
extern "C" {
#if 0
}
#endif
#endif
typedef enum {
PINENTRY_COLOR_NONE, PINENTRY_COLOR_DEFAULT,
PINENTRY_COLOR_BLACK, PINENTRY_COLOR_RED,
PINENTRY_COLOR_GREEN, PINENTRY_COLOR_YELLOW,
PINENTRY_COLOR_BLUE, PINENTRY_COLOR_MAGENTA,
PINENTRY_COLOR_CYAN, PINENTRY_COLOR_WHITE
} pinentry_color_t;
struct pinentry
{
/* The window title, or NULL. (Assuan: "SETTITLE TITLE".) */
char *title;
/* The description to display, or NULL. (Assuan: "SETDESC
DESC".) */
char *description;
/* The error message to display, or NULL. (Assuan: "SETERROR
MESSAGE".) */
char *error;
/* The prompt to display, or NULL. (Assuan: "SETPROMPT
prompt".) */
char *prompt;
/* The OK button text to display, or NULL. (Assuan: "SETOK
OK".) */
char *ok;
/* The Not-OK button text to display, or NULL. This is the text for
the alternative option shown by the third button. (Assuan:
"SETNOTOK NOTOK".) */
char *notok;
/* The Cancel button text to display, or NULL. (Assuan: "SETCANCEL
CANCEL".) */
char *cancel;
/* The buffer to store the secret into. */
char *pin;
/* The length of the buffer. */
int pin_len;
/* Whether the pin was read from an external cache (1) or entered by
the user (0). */
int pin_from_cache;
/* The name of the X display to use if X is available and supported.
(Assuan: "OPTION display DISPLAY".) */
char *display;
/* The name of the terminal node to open if X not available or
supported. (Assuan: "OPTION ttyname TTYNAME".) */
char *ttyname;
/* The type of the terminal. (Assuan: "OPTION ttytype TTYTYPE".) */
char *ttytype;
/* The LC_CTYPE value for the terminal. (Assuan: "OPTION lc-ctype
LC_CTYPE".) */
char *lc_ctype;
/* The LC_MESSAGES value for the terminal. (Assuan: "OPTION
lc-messages LC_MESSAGES".) */
char *lc_messages;
/* True if debug mode is requested. */
int debug;
/* The number of seconds before giving up while waiting for user input. */
int timeout;
/* True if caller should grab the keyboard. (Assuan: "OPTION grab"
or "OPTION no-grab".) */
int grab;
/* The PID of the owner or 0 if not known. The owner is the process
* which actually triggered the the pinentry. For example gpg. */
unsigned long owner_pid;
/* The malloced hostname of the owener or NULL. */
char *owner_host;
/* The window ID of the parent window over which the pinentry window
should be displayed. (Assuan: "OPTION parent-wid WID".) */
int parent_wid;
/* The name of an optional file which will be touched after a curses
entry has been displayed. (Assuan: "OPTION touch-file
FILENAME".) */
char *touch_file;
/* The frontend should set this to -1 if the user canceled the
request, and to the length of the PIN stored in pin
otherwise. */
int result;
/* The frontend should set this if the NOTOK button was pressed. */
int canceled;
/* The frontend should set this to true if an error with the local
conversion occurred. */
int locale_err;
/* The frontend should set this to a gpg-error so that commands are
able to return specific error codes. This is an ugly hack due to
the fact that pinentry_cmd_handler_t returns the length of the
passphrase or a negative error code. */
int specific_err;
/* The frontend may store a string with the error location here. */
const char *specific_err_loc;
/* The frontend may store a malloced string here to emit an ERROR
* status code with this extra info along with SPECIFIC_ERR. */
char *specific_err_info;
/* The frontend should set this to true if the window close button
has been used. This flag is used in addition to a regular return
value. */
int close_button;
/* The caller should set this to true if only one button is
required. This is useful for notification dialogs where only a
dismiss button is required. */
int one_button;
/* If true a second prompt for the passphrase is shown and the user
is expected to enter the same passphrase again. Pinentry checks
that both match. (Assuan: "SETREPEAT".) */
char *repeat_passphrase;
/* The string to show if a repeated passphrase does not match.
(Assuan: "SETREPEATERROR ERROR".) */
char *repeat_error_string;
/* Set to true if the passphrase has been entered a second time and
matches the first passphrase. */
int repeat_okay;
/* If this is not NULL, a passphrase quality indicator is shown.
There will also be an inquiry back to the caller to get an
indication of the quality for the passphrase entered so far. The
string is used as a label for the quality bar. (Assuan:
"SETQUALITYBAR LABEL".) */
char *quality_bar;
/* The tooltip to be show for the qualitybar. Malloced or NULL.
(Assuan: "SETQUALITYBAR_TT TOOLTIP".) */
char *quality_bar_tt;
/* For the curses pinentry, the color of error messages. */
pinentry_color_t color_fg;
int color_fg_bright;
pinentry_color_t color_bg;
pinentry_color_t color_so;
int color_so_bright;
/* Malloced and i18ned default strings or NULL. These strings may
include an underscore character to indicate an accelerator key.
A double underscore represents a plain one. */
/* (Assuan: "OPTION default-ok OK"). */
char *default_ok;
/* (Assuan: "OPTION default-cancel CANCEL"). */
char *default_cancel;
/* (Assuan: "OPTION default-prompt PROMPT"). */
char *default_prompt;
/* (Assuan: "OPTION default-pwmngr
SAVE_PASSWORD_WITH_PASSWORD_MANAGER?"). */
char *default_pwmngr;
/* (Assuan: "OPTION default-cf-visi
Do you really want to make your passphrase visible?"). */
char *default_cf_visi;
/* (Assuan: "OPTION default-tt-visi
Make passphrase visible?"). */
char *default_tt_visi;
/* (Assuan: "OPTION default-tt-hide
Hide passphrase"). */
char *default_tt_hide;
/* Whether we are allowed to read the password from an external
cache. (Assuan: "OPTION allow-external-password-cache") */
int allow_external_password_cache;
/* We only try the cache once. */
int tried_password_cache;
/* A stable identifier for the key. (Assuan: "SETKEYINFO
KEYINFO".) */
char *keyinfo;
/* Whether we may cache the password (according to the user). */
int may_cache_password;
/* NOTE: If you add any additional fields to this structure, be sure
to update the initializer in pinentry/pinentry.c!!! */
/* For the quality indicator we need to do an inquiry. Thus we need
to save the assuan ctx. */
void *ctx_assuan;
/* An UTF-8 string with an invisible character used to override the
default in some pinentries. Only the first character is
used. */
char *invisible_char;
};
typedef struct pinentry *pinentry_t;
/* The pinentry command handler type processes the pinentry request
PIN. If PIN->pin is zero, request a confirmation, otherwise a PIN
entry. On confirmation, the function should return TRUE if
confirmed, and FALSE otherwise. On PIN entry, the function should
return -1 if an error occurred or the user cancelled the operation
and 1 otherwise. */
typedef int (*pinentry_cmd_handler_t) (pinentry_t pin);
/* Start the pinentry event loop. The program will start to process
Assuan commands until it is finished or an error occurs. If an
error occurs, -1 is returned and errno indicates the type of an
error. Otherwise, 0 is returned. */
int pinentry_loop (void);
/* The same as above but allows to specify the i/o descriptors.
* infd and outfd will be duplicated in this function so the caller
* still has to close them if necessary.
*/
int pinentry_loop2 (int infd, int outfd);
/* Convert the UTF-8 encoded string TEXT to the encoding given in
LC_CTYPE. Return NULL on error. */
char *pinentry_utf8_to_local (const char *lc_ctype, const char *text);
/* Convert TEXT which is encoded according to LC_CTYPE to UTF-8. With
SECURE set to true, use secure memory for the returned buffer.
Return NULL on error. */
char *pinentry_local_to_utf8 (char *lc_ctype, char *text, int secure);
+char *pinentry_get_title (pinentry_t pe);
/* Run a quality inquiry for PASSPHRASE of LENGTH. */
int pinentry_inq_quality (pinentry_t pin,
const char *passphrase, size_t length);
/* Try to make room for at least LEN bytes for the pin in the pinentry
PIN. Returns new buffer on success and 0 on failure. */
char *pinentry_setbufferlen (pinentry_t pin, int len);
/* Use the buffer at BUFFER for PIN->PIN. BUFFER must be NULL or
allocated using secmem_alloc. LEN is the size of the buffer. If
it is unknown, but BUFFER is a NUL terminated string, you pass 0 to
just use strlen(buffer)+1. */
void pinentry_setbuffer_use (pinentry_t pin, char *buffer, int len);
/* Initialize the secure memory subsystem, drop privileges and
return. Must be called early. */
void pinentry_init (const char *pgmname);
/* Return true if either DISPLAY is set or ARGV contains the string
"--display". */
int pinentry_have_display (int argc, char **argv);
/* Parse the command line options. May exit the program if only help
or version output is requested. */
void pinentry_parse_opts (int argc, char *argv[]);
/* Set the optional flag used with getinfo. */
void pinentry_set_flavor_flag (const char *string);
/* The caller must define this variable to process assuan commands. */
extern pinentry_cmd_handler_t pinentry_cmd_handler;
#ifdef HAVE_W32_SYSTEM
/* Windows declares sleep as obsolete, but provides a definition for
_sleep but non for the still existing sleep. */
#define sleep(a) _sleep ((a))
#endif /*HAVE_W32_SYSTEM*/
#if 0
{
#endif
#ifdef __cplusplus
}
#endif
#endif /* PINENTRY_H */
diff --git a/qt/main.cpp b/qt/main.cpp
index 225c06b..e2af686 100644
--- a/qt/main.cpp
+++ b/qt/main.cpp
@@ -1,356 +1,366 @@
/* main.cpp - A Qt dialog for PIN entry.
Copyright (C) 2002, 2008 Klarälvdalens Datakonsult AB (KDAB)
Copyright (C) 2003 g10 Code GmbH
Copyright 2007 Ingo Klöcker
Written by Steffen Hansen <steffen@klaralvdalens-datakonsult.se>.
Modified by Marcus Brinkmann <marcus@g10code.de>.
Modified by Marc Mutz <marc@kdab.com>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "pinentryconfirm.h"
#include "pinentrydialog.h"
#include "pinentry.h"
#include <qapplication.h>
#include <QIcon>
#include <QString>
#include <qwidget.h>
#include <qmessagebox.h>
#include <QPushButton>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <memory>
#include <stdexcept>
#include <gpg-error.h>
#ifdef FALLBACK_CURSES
#include <pinentry-curses.h>
#endif
#if QT_VERSION >= 0x050000 && defined(QT_STATIC)
#include <QtPlugin>
#ifdef Q_OS_WIN
#include <windows.h>
#include <shlobj.h>
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)
#elif defined(Q_OS_MAC)
Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin)
#else
Q_IMPORT_PLUGIN(QXcbIntegrationPlugin)
#endif
#endif
static QString escape_accel(const QString &s)
{
QString result;
result.reserve(s.size());
bool afterUnderscore = false;
for (unsigned int i = 0, end = s.size() ; i != end ; ++i) {
const QChar ch = s[i];
if (ch == QLatin1Char('_')) {
if (afterUnderscore) { // escaped _
result += QLatin1Char('_');
afterUnderscore = false;
} else { // accel
afterUnderscore = true;
}
} else {
if (afterUnderscore || // accel
ch == QLatin1Char('&')) { // escape & from being interpreted by Qt
result += QLatin1Char('&');
}
result += ch;
afterUnderscore = false;
}
}
if (afterUnderscore)
// trailing single underscore: shouldn't happen, but deal with it robustly:
{
result += QLatin1Char('_');
}
return result;
}
/* Hack for creating a QWidget with a "foreign" window ID */
class ForeignWidget : public QWidget
{
public:
explicit ForeignWidget(WId wid) : QWidget(0)
{
QWidget::destroy();
create(wid, false, false);
}
~ForeignWidget()
{
destroy(false, false);
}
};
namespace
{
class InvalidUtf8 : public std::invalid_argument
{
public:
InvalidUtf8() : std::invalid_argument("invalid utf8") {}
~InvalidUtf8() throw() {}
};
}
static const bool GPG_AGENT_IS_PORTED_TO_ONLY_SEND_UTF8 = false;
static QString from_utf8(const char *s)
{
const QString result = QString::fromUtf8(s);
if (result.contains(QChar::ReplacementCharacter)) {
if (GPG_AGENT_IS_PORTED_TO_ONLY_SEND_UTF8) {
throw InvalidUtf8();
} else {
return QString::fromLocal8Bit(s);
}
}
return result;
}
static int
qt_cmd_handler(pinentry_t pe)
{
QWidget *parent = 0;
+ char *str;
/* FIXME: Add parent window ID to pinentry and GTK. */
if (pe->parent_wid) {
parent = new ForeignWidget((WId) pe->parent_wid);
}
int want_pass = !!pe->pin;
const QString ok =
pe->ok ? escape_accel(from_utf8(pe->ok)) :
pe->default_ok ? escape_accel(from_utf8(pe->default_ok)) :
/* else */ QLatin1String("&OK") ;
const QString cancel =
pe->cancel ? escape_accel(from_utf8(pe->cancel)) :
pe->default_cancel ? escape_accel(from_utf8(pe->default_cancel)) :
/* else */ QLatin1String("&Cancel") ;
+
+ str = pinentry_get_title (pe);
const QString title =
- pe->title ? from_utf8(pe->title) :
+ str ? from_utf8(str) :
/* else */ QLatin1String("pinentry-qt") ;
+ free (str);
+
const QString repeatError =
pe->repeat_error_string ? from_utf8(pe->repeat_error_string) :
QLatin1String("Passphrases do not match");
const QString repeatString =
pe->repeat_passphrase ? from_utf8(pe->repeat_passphrase) :
QString();
const QString visibilityTT =
pe->default_tt_visi ? from_utf8(pe->default_tt_visi) :
QLatin1String("Show passphrase");
const QString hideTT =
pe->default_tt_hide ? from_utf8(pe->default_tt_hide) :
QLatin1String("Hide passphrase");
if (want_pass) {
+ char *str;
+
PinEntryDialog pinentry(parent, 0, pe->timeout, true, !!pe->quality_bar,
repeatString, visibilityTT, hideTT);
pinentry.setPinentryInfo(pe);
pinentry.setPrompt(escape_accel(from_utf8(pe->prompt)));
pinentry.setDescription(from_utf8(pe->description));
pinentry.setRepeatErrorText(repeatError);
- if (pe->title) {
- pinentry.setWindowTitle(from_utf8(pe->title));
+
+ str = pinentry_get_title (pe);
+ if (str) {
+ pinentry.setWindowTitle(from_utf8(str));
+ free (str);
}
/* If we reuse the same dialog window. */
pinentry.setPin(QString());
pinentry.setOkText(ok);
pinentry.setCancelText(cancel);
if (pe->error) {
pinentry.setError(from_utf8(pe->error));
}
if (pe->quality_bar) {
pinentry.setQualityBar(from_utf8(pe->quality_bar));
}
if (pe->quality_bar_tt) {
pinentry.setQualityBarTT(from_utf8(pe->quality_bar_tt));
}
bool ret = pinentry.exec();
if (!ret) {
if (pinentry.timedOut())
pe->specific_err = gpg_error (GPG_ERR_TIMEOUT);
return -1;
}
const QString pinStr = pinentry.pin();
QByteArray pin = pinStr.toUtf8();
if (!!pe->repeat_passphrase) {
/* Should not have been possible to accept
the dialog in that case but we do a safety
check here */
pe->repeat_okay = (pinStr == pinentry.repeatedPin());
}
int len = strlen(pin.constData());
if (len >= 0) {
pinentry_setbufferlen(pe, len + 1);
if (pe->pin) {
strcpy(pe->pin, pin.constData());
return len;
}
}
return -1;
} else {
const QString desc = pe->description ? from_utf8(pe->description) : QString();
const QString notok = pe->notok ? escape_accel(from_utf8(pe->notok)) : QString();
const QMessageBox::StandardButtons buttons =
pe->one_button ? QMessageBox::Ok :
pe->notok ? QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel :
/* else */ QMessageBox::Ok | QMessageBox::Cancel ;
PinentryConfirm box(QMessageBox::Information, pe->timeout, title, desc, buttons, parent);
const struct {
QMessageBox::StandardButton button;
QString label;
} buttonLabels[] = {
{ QMessageBox::Ok, ok },
{ QMessageBox::Yes, ok },
{ QMessageBox::No, notok },
{ QMessageBox::Cancel, cancel },
};
for (size_t i = 0 ; i < sizeof buttonLabels / sizeof * buttonLabels ; ++i)
if ((buttons & buttonLabels[i].button) && !buttonLabels[i].label.isEmpty()) {
box.button(buttonLabels[i].button)->setText(buttonLabels[i].label);
#ifndef QT_NO_ACCESSIBILITY
box.button(buttonLabels[i].button)->setAccessibleDescription(buttonLabels[i].label);
#endif
}
box.setIconPixmap(icon());
if (!pe->one_button) {
box.setDefaultButton(QMessageBox::Cancel);
}
box.show();
raiseWindow(&box);
const int rc = box.exec();
if (rc == QMessageBox::Cancel) {
pe->canceled = true;
}
if (box.timedOut()) {
pe->specific_err = gpg_error (GPG_ERR_TIMEOUT);
}
return rc == QMessageBox::Ok || rc == QMessageBox::Yes ;
}
}
static int
qt_cmd_handler_ex(pinentry_t pe)
{
try {
return qt_cmd_handler(pe);
} catch (const InvalidUtf8 &) {
pe->locale_err = true;
return pe->pin ? -1 : false ;
} catch (...) {
pe->canceled = true;
return pe->pin ? -1 : false ;
}
}
pinentry_cmd_handler_t pinentry_cmd_handler = qt_cmd_handler_ex;
int
main(int argc, char *argv[])
{
pinentry_init("pinentry-qt");
std::auto_ptr<QApplication> app;
#ifdef FALLBACK_CURSES
if (!pinentry_have_display(argc, argv)) {
pinentry_cmd_handler = curses_cmd_handler;
pinentry_set_flavor_flag ("curses");
} else
#endif
{
/* Qt does only understand -display but not --display; thus we
are fixing that here. The code is pretty simply and may get
confused if an argument is called "--display". */
char **new_argv, *p;
size_t n;
int i, done;
for (n = 0, i = 0; i < argc; i++) {
n += strlen(argv[i]) + 1;
}
n++;
new_argv = (char **)calloc(argc + 1, sizeof * new_argv);
if (new_argv) {
*new_argv = (char *)malloc(n);
}
if (!new_argv || !*new_argv) {
fprintf(stderr, "pinentry-qt: can't fixup argument list: %s\n",
strerror(errno));
exit(EXIT_FAILURE);
}
for (done = 0, p = *new_argv, i = 0; i < argc; i++)
if (!done && !strcmp(argv[i], "--display")) {
new_argv[i] = strcpy(p, argv[i] + 1);
p += strlen(argv[i] + 1) + 1;
done = 1;
} else {
new_argv[i] = strcpy(p, argv[i]);
p += strlen(argv[i]) + 1;
}
/* We use a modal dialog window, so we don't need the application
window anymore. */
i = argc;
app.reset(new QApplication(i, new_argv));
app->setWindowIcon(QIcon(QLatin1String(":/document-encrypt.png")));
}
pinentry_parse_opts(argc, argv);
return pinentry_loop() ? EXIT_FAILURE : EXIT_SUCCESS ;
}
diff --git a/tty/pinentry-tty.c b/tty/pinentry-tty.c
index a509d79..8b37340 100644
--- a/tty/pinentry-tty.c
+++ b/tty/pinentry-tty.c
@@ -1,573 +1,579 @@
/* pinentry-curses.c - A secure curses dialog for PIN entry, library version
Copyright (C) 2014 Serge Voilokov
Copyright (C) 2015 Daniel Kahn Gillmor <dkg@fifthhorseman.net>
Copyright (C) 2015 g10 Code GmbH
This file is part of PINENTRY.
PINENTRY is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
PINENTRY is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <termios.h>
#ifdef HAVE_UTIME_H
#include <utime.h>
#endif /*HAVE_UTIME_H*/
#include <sys/types.h>
#include <sys/stat.h>
#include <ctype.h>
#include <gpg-error.h>
#include "pinentry.h"
#include "memory.h"
#ifndef HAVE_DOSISH_SYSTEM
static int timed_out;
#endif
static struct termios n_term;
static struct termios o_term;
static int
cbreak (int fd)
{
if ((tcgetattr(fd, &o_term)) == -1)
return -1;
n_term = o_term;
n_term.c_lflag = n_term.c_lflag & ~(ECHO|ICANON);
n_term.c_cc[VMIN] = 1;
n_term.c_cc[VTIME]= 0;
if ((tcsetattr(fd, TCSAFLUSH, &n_term)) == -1)
return -1;
return 1;
}
#define UNDERLINE_START "\033[4m"
/* Bold, red. */
#define ALERT_START "\033[1;31m"
#define NORMAL_RESTORE "\033[0m"
static char
button (char *text, char *default_text, FILE *ttyfo)
{
char *highlight;
if (! text)
return 0;
/* Skip any leading white space. */
while (*text == ' ')
text ++;
highlight = text;
while ((highlight = strchr (highlight, '_')))
{
highlight = highlight + 1;
if (*highlight == '_')
{
/* Escaped underscore. Skip both characters. */
highlight++;
continue;
}
if (!isalnum (*highlight))
/* Unusable accelerator. */
continue;
break;
}
if (! highlight)
/* Not accelerator. Take the first alpha-numeric character. */
{
highlight = text;
while (*highlight && !isalnum (*highlight))
highlight ++;
}
if (! *highlight)
/* Hmm, no alpha-numeric characters. */
{
if (! default_text)
return 0;
text = highlight = default_text;
}
fputs (" ", ttyfo);
for (; *text; text ++)
{
/* Skip accelerator prefix. */
if (*text == '_')
{
text ++;
if (! *text)
break;
}
if (text == highlight)
fputs (UNDERLINE_START, ttyfo);
fputc (*text, ttyfo);
if (text == highlight)
fputs (NORMAL_RESTORE, ttyfo);
}
fputc ('\n', ttyfo);
return tolower (*highlight);
}
static void
dump_error_text (FILE *ttyfo, const char *text)
{
int lines = 0;
if (! text || ! *text)
return;
for (;;)
{
const char *eol = strchr (text, '\n');
if (! eol)
eol = text + strlen (text);
lines ++;
fwrite ("\n *** ", 6, 1, ttyfo);
fputs (ALERT_START, ttyfo);
fwrite (text, (size_t) (eol - text), 1, ttyfo);
fputs (NORMAL_RESTORE, ttyfo);
if (! *eol)
break;
text = eol + 1;
}
if (lines > 1)
fputc ('\n', ttyfo);
else
fwrite (" ***\n", 5, 1, ttyfo);
fputc ('\n', ttyfo);
}
static int
confirm (pinentry_t pinentry, FILE *ttyfi, FILE *ttyfo)
{
char *msg;
+ char *msgbuffer = NULL;
char ok = 0;
char notok = 0;
char cancel = 0;
int ret;
dump_error_text (ttyfo, pinentry->error);
msg = pinentry->description;
if (! msg)
- /* If there is no description, fallback to the title. */
- msg = pinentry->title;
+ {
+ /* If there is no description, fallback to the title. */
+ msg = msgbuffer = pinentry_get_title (pinentry);
+ }
if (! msg)
msg = "Confirm:";
if (msg)
{
fputs (msg, ttyfo);
fputc ('\n', ttyfo);
}
+ free (msgbuffer);
fflush (ttyfo);
if (pinentry->ok)
ok = button (pinentry->ok, "OK", ttyfo);
else if (pinentry->default_ok)
ok = button (pinentry->default_ok, "OK", ttyfo);
else
ok = button ("OK", NULL, ttyfo);
if (! pinentry->one_button)
{
if (pinentry->cancel)
cancel = button (pinentry->cancel, "Cancel", ttyfo);
else if (pinentry->default_cancel)
cancel = button (pinentry->default_cancel, "Cancel", ttyfo);
if (pinentry->notok)
notok = button (pinentry->notok, "No", ttyfo);
}
if (cbreak (fileno (ttyfi)) == -1)
{
int err = errno;
fprintf (stderr, "cbreak failure, exiting\n");
errno = err;
return -1;
}
while (1)
{
int input;
if (pinentry->one_button)
fprintf (ttyfo, "Press any key to continue.");
else
{
fputc ('[', ttyfo);
if (ok)
fputc (ok, ttyfo);
if (cancel)
fputc (cancel, ttyfo);
if (notok)
fputc (notok, ttyfo);
fputs("]? ", ttyfo);
}
fflush (ttyfo);
input = fgetc (ttyfi);
fprintf (ttyfo, "%c\n", input);
input = tolower (input);
if (input == EOF || input == 0x4)
/* End of file or control-d (= end of file). */
{
pinentry->close_button = 1;
pinentry->canceled = 1;
ret = 0;
break;
}
if (pinentry->one_button)
{
ret = 1;
break;
}
if (cancel && input == cancel)
{
pinentry->canceled = 1;
ret = 0;
break;
}
else if (notok && input == notok)
{
ret = 0;
break;
}
else if (ok && input == ok)
{
ret = 1;
break;
}
else
{
fprintf (ttyfo, "Invalid selection.\n");
}
}
#ifndef HAVE_DOSISH_SYSTEM
if (timed_out)
pinentry->specific_err = gpg_error (GPG_ERR_TIMEOUT);
#endif
tcsetattr (fileno(ttyfi), TCSANOW, &o_term);
return ret;
}
static char *
read_password (FILE *ttyfi, FILE *ttyfo)
{
int done = 0;
int len = 128;
int count = 0;
char *buffer;
(void) ttyfo;
if (cbreak (fileno (ttyfi)) == -1)
{
int err = errno;
fprintf (stderr, "cbreak failure, exiting\n");
errno = err;
return NULL;
}
buffer = secmem_malloc (len);
if (! buffer)
return NULL;
while (!done)
{
int c;
if (count == len - 1)
/* Double the buffer's size. Note: we check if count is len -
1 and not len so that we always have space for the NUL
character. */
{
int new_len = 2 * len;
char *tmp = secmem_realloc (buffer, new_len);
if (! tmp)
{
secmem_free (tmp);
return NULL;
}
buffer = tmp;
len = new_len;
}
c = fgetc (ttyfi);
switch (c)
{
case 0x4: case EOF:
/* Control-d (i.e., end of file) or a real EOF. */
done = -1;
break;
case '\n':
done = 1;
break;
case 0x7f:
/* Backspace. */
if (count > 0)
count --;
break;
default:
buffer[count ++] = c;
break;
}
}
buffer[count] = '\0';
tcsetattr (fileno(ttyfi), TCSANOW, &o_term);
if (done == -1)
{
secmem_free (buffer);
return NULL;
}
return buffer;
}
static int
password (pinentry_t pinentry, FILE *ttyfi, FILE *ttyfo)
{
char *msg;
+ char *msgbuffer = NULL;
int done = 0;
msg = pinentry->description;
if (! msg)
- msg = pinentry->title;
+ msg = msgbuffer = pinentry_get_title (pinentry);
if (! msg)
msg = "Enter your passphrase.";
dump_error_text (ttyfo, pinentry->error);
fprintf (ttyfo, "%s\n", msg);
+ free (msgbuffer);
while (! done)
{
char *passphrase;
char *prompt = pinentry->prompt;
if (! prompt || !*prompt)
prompt = "PIN";
fprintf (ttyfo, "%s%s ",
prompt,
/* Make sure the prompt ends in a : or a question mark. */
(prompt[strlen(prompt) - 1] == ':'
|| prompt[strlen(prompt) - 1] == '?') ? "" : ":");
fflush (ttyfo);
passphrase = read_password (ttyfi, ttyfo);
fputc ('\n', ttyfo);
if (! passphrase)
{
done = -1;
break;
}
if (! pinentry->repeat_passphrase)
done = 1;
else
{
char *passphrase2;
prompt = pinentry->repeat_passphrase;
fprintf (ttyfo, "%s%s ",
prompt,
/* Make sure the prompt ends in a : or a question mark. */
(prompt[strlen(prompt) - 1] == ':'
|| prompt[strlen(prompt) - 1] == '?') ? "" : ":");
fflush (ttyfo);
passphrase2 = read_password (ttyfi, ttyfo);
fputc ('\n', ttyfo);
if (! passphrase2)
{
done = -1;
break;
}
if (strcmp (passphrase, passphrase2) == 0)
{
pinentry->repeat_okay = 1;
done = 1;
}
else
dump_error_text (ttyfo,
pinentry->repeat_error_string
?: "Passphrases don't match.");
secmem_free (passphrase2);
}
if (done == 1)
pinentry_setbuffer_use (pinentry, passphrase, 0);
else
secmem_free (passphrase);
}
#ifndef HAVE_DOSISH_SYSTEM
if (timed_out)
pinentry->specific_err = gpg_error (GPG_ERR_TIMEOUT);
#endif
return done;
}
/* If a touch has been registered, touch that file. */
static void
do_touch_file(pinentry_t pinentry)
{
#ifdef HAVE_UTIME_H
struct stat st;
time_t tim;
if (!pinentry->touch_file || !*pinentry->touch_file)
return;
if (stat(pinentry->touch_file, &st))
return; /* Oops. */
/* Make sure that we actually update the mtime. */
while ((tim = time(NULL)) == st.st_mtime)
sleep(1);
/* Update but ignore errors as we can't do anything in that case.
Printing error messages may even clubber the display further. */
utime (pinentry->touch_file, NULL);
#endif /*HAVE_UTIME_H*/
}
#ifndef HAVE_DOSISH_SYSTEM
static void
catchsig(int sig)
{
if (sig == SIGALRM)
timed_out = 1;
}
#endif
int
tty_cmd_handler(pinentry_t pinentry)
{
int rc = 0;
FILE *ttyfi = stdin;
FILE *ttyfo = stdout;
#ifndef HAVE_DOSISH_SYSTEM
timed_out = 0;
if (pinentry->timeout)
{
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = catchsig;
sigaction(SIGALRM, &sa, NULL);
alarm(pinentry->timeout);
}
#endif
if (pinentry->ttyname)
{
ttyfi = fopen (pinentry->ttyname, "r");
if (!ttyfi)
rc = -1;
else
{
ttyfo = fopen (pinentry->ttyname, "w");
if (!ttyfo)
{
int err = errno;
fclose (ttyfi);
errno = err;
rc = -1;
}
}
}
if (! rc)
{
if (pinentry->pin)
rc = password (pinentry, ttyfi, ttyfo);
else
rc = confirm (pinentry, ttyfi, ttyfo);
}
do_touch_file (pinentry);
if (pinentry->ttyname)
{
fclose (ttyfi);
fclose (ttyfo);
}
return rc;
}
pinentry_cmd_handler_t pinentry_cmd_handler = tty_cmd_handler;
int
main (int argc, char *argv[])
{
pinentry_init ("pinentry-tty");
/* Consumes all arguments. */
pinentry_parse_opts(argc, argv);
if (pinentry_loop ())
return 1;
return 0;
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Apr 25, 3:12 PM (1 d, 14 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
80/77/45df54b83f3ede71bf53d102c363

Event Timeline