|
|
|
|
/* pinentry.c - The PIN entry support library
|
|
|
|
|
Copyright (C) 2002, 2003, 2007, 2008, 2010, 2015 g10 Code GmbH
|
|
|
|
|
|
|
|
|
|
This file is part of PINENTRY.
|
|
|
|
|
|
|
|
|
|
PINENTRY is free software; you can redistribute it and/or modify it
|
|
|
|
|
under the terms of the GNU General Public License as published by
|
|
|
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
|
|
PINENTRY is distributed in the hope that it will be useful, but
|
|
|
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
|
General Public License for more details.
|
|
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
|
along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <assert.h>
|
|
|
|
|
|
|
|
|
|
#include <assuan.h>
|
|
|
|
|
|
|
|
|
|
#include "memory.h"
|
|
|
|
|
#include "secmem-util.h"
|
|
|
|
|
#include "argparse.h"
|
|
|
|
|
#include "pinentry.h"
|
|
|
|
|
#include "password-cache.h"
|
|
|
|
|
|
|
|
|
|
/* Keep the name of our program here. */
|
|
|
|
|
static char this_pgmname[50];
|
|
|
|
|
|
|
|
|
|
struct pinentry pinentry;
|
|
|
|
|
|
|
|
|
|
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 *touch_file = pinentry.touch_file;
|
|
|
|
|
|
|
|
|
|
/* 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 timout = pinentry.timeout;
|
|
|
|
|
|
|
|
|
|
/* 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.touch_file);
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
/* Reset the pinentry structure. */
|
|
|
|
|
memset (&pinentry, 0, sizeof (pinentry));
|
|
|
|
|
|
|
|
|
|
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.touch_file = touch_file;
|
|
|
|
|
|
|
|
|
|
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 = timout;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
/* Copy TEXT or TEXTLEN to BUFFER and escape as required. Return a
|
|
|
|
|
pointer to the end of the new buffer. Note that BUFFER must be
|
|
|
|
|
large enough to keep the entire text; allocataing it 3 times of
|
|
|
|
|
TEXTLEN is sufficient. */
|
|
|
|
|
static char *
|
|
|
|
|
copy_and_escape (char *buffer, const void *text, size_t textlen)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
const unsigned char *s = (unsigned char *)text;
|
|
|
|
|
char *p = buffer;
|
|
|
|
|
|
|
|
|
|
for (i=0; i < textlen; i++)
|
|
|
|
|
{
|
|
|
|
|
if (s[i] < ' ' || s[i] == '+')
|
|
|
|
|
{
|
|
|
|
|
snprintf (p, 4, "%%%02X", s[i]);
|
|
|
|
|
p += 3;
|
|
|
|
|
}
|
|
|
|
|
else if (s[i] == ' ')
|
|
|
|
|
*p++ = '+';
|
|
|
|
|
else
|
|
|
|
|
*p++ = s[i];
|
|
|
|
|
}
|
|
|
|
|
return p;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Run a quality inquiry for PASSPHRASE of LENGTH. (We need LENGTH
|
|
|
|
|
because not all backends might be able to return a proper
|
|
|
|
|
C-string.). Returns: A value between -100 and 100 to give an
|
|
|
|
|
estimate of the passphrase's quality. Negative values are use if
|
|
|
|
|
the caller won't even accept that passphrase. Note that we expect
|
|
|
|
|
just one data line which should not be escaped in any represent a
|
|
|
|
|
numeric signed decimal value. Extra data is currently ignored but
|
|
|
|
|
should not be send at all. */
|
|
|
|
|
int
|
|
|
|
|
pinentry_inq_quality (pinentry_t pin, const char *passphrase, size_t length)
|
|
|
|
|
{
|
|
|
|
|
assuan_context_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)
|
|
|
|
|
{
|
|
|
|
|
for (; argc; argc--, argv++)
|
|
|
|
|
if (!strcmp (*argv, "--display") || !strncmp (*argv, "--display=", 10))
|
|
|
|
|
return 1;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Print usage information and and provide strings for help. */
|
|
|
|
|
static const char *
|
|
|
|
|
my_strusage( int level )
|
|
|
|
|
{
|
|
|
|
|
const char *p;
|
|
|
|
|
|
|
|
|
|
switch (level)
|
|
|
|
|
{
|
|
|
|
|
case 11: p = this_pgmname; break;
|
|
|
|
|
case 12: p = "pinentry"; break;
|
|
|
|
|
case 13: p = "*REDACTED*"; break;
|
|
|
|
|
case 14: p = "Copyright (C) 2015 g10 Code GmbH"; break;
|
|
|
|
|
case 19: p = "Please report bugs to <" "*REDACTED*" ">.\n"; break;
|
|
|
|
|
case 1:
|
|
|
|
|
case 40:
|
|
|
|
|
{
|
|
|
|
|
static char *str;
|
|
|
|
|
|
|
|
|
|
if (!str)
|
|
|
|
|
{
|
|
|
|
|
size_t n = 50 + strlen (this_pgmname);
|
|
|
|
|
str = malloc (n);
|
|
|
|
|
if (str)
|
|
|
|
|
snprintf (str, n, "Usage: %s [options] (-h for help)",
|
|
|
|
|
this_pgmname);
|
|
|
|
|
}
|
|
|
|
|
p = str;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 41:
|
|
|
|
|
p = "Ask securely for a secret and print it to stdout.";
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 42:
|
|
|
|
|
p = "1"; /* Flag print 40 as part of 41. */
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default: p = NULL; break;
|
|
|
|
|
}
|
|
|
|
|
return p;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
char *
|
|
|
|
|
parse_color (char *arg, pinentry_color_t *color_p, int *bright_p)
|
|
|
|
|
{
|
|
|
|
|
static struct
|
|
|
|
|
{
|
|
|
|
|
const char *name;
|
|
|
|
|
pinentry_color_t color;
|
|
|
|
|
} colors[] = { { "none", PINENTRY_COLOR_NONE },
|
|
|
|
|
{ "default", PINENTRY_COLOR_DEFAULT },
|
|
|
|
|
{ "black", PINENTRY_COLOR_BLACK },
|
|
|
|
|
{ "red", PINENTRY_COLOR_RED },
|
|
|
|
|
{ "green", PINENTRY_COLOR_GREEN },
|
|
|
|
|
{ "yellow", PINENTRY_COLOR_YELLOW },
|
|
|
|
|
{ "blue", PINENTRY_COLOR_BLUE },
|
|
|
|
|
{ "magenta", PINENTRY_COLOR_MAGENTA },
|
|
|
|
|
{ "cyan", PINENTRY_COLOR_CYAN },
|
|
|
|
|
{ "white", PINENTRY_COLOR_WHITE } };
|
|
|
|
|
|
|
|
|
|
int i;
|
|
|
|
|
char *new_arg;
|
|
|
|
|
pinentry_color_t color = PINENTRY_COLOR_DEFAULT;
|
|
|
|
|
|
|
|
|
|
if (!arg)
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
new_arg = strchr (arg, ',');
|
|
|
|
|
if (new_arg)
|
|
|
|
|
new_arg++;
|
|
|
|
|
|
|
|
|
|
if (bright_p)
|
|
|
|
|
{
|
|
|
|
|
const char *bname[] = { "bright-", "bright", "bold-", "bold" };
|
|
|
|
|
|
|
|
|
|
*bright_p = 0;
|
|
|
|
|
for (i = 0; i < sizeof (bname) / sizeof (bname[0]); i++)
|
|
|
|
|
if (!strncasecmp (arg, bname[i], strlen (bname[i])))
|
|
|
|
|
{
|
|
|
|
|
*bright_p = 1;
|
|
|
|
|
arg += strlen (bname[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < sizeof (colors) / sizeof (colors[0]); i++)
|
|
|
|
|
if (!strncasecmp (arg, colors[i].name, strlen (colors[i].name)))
|
|
|
|
|
color = colors[i].color;
|
|
|
|
|
|
|
|
|
|
*color_p = color;
|
|
|
|
|
return new_arg;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Parse the command line options. May exit the program if only help
|
|
|
|
|
or version output is requested. */
|
|
|
|
|
void
|
|
|
|
|
pinentry_parse_opts (int argc, char *argv[])
|
|
|
|
|
{
|
|
|
|
|
static ARGPARSE_OPTS opts[] = {
|
|
|
|
|
ARGPARSE_s_n('d', "debug", "Turn on debugging output"),
|
|
|
|
|
ARGPARSE_s_s('D', "display", "|DISPLAY|Set the X display"),
|
|
|
|
|
ARGPARSE_s_s('T', "ttyname", "|FILE|Set the tty terminal node name"),
|
|
|
|
|
ARGPARSE_s_s('N', "ttytype", "|NAME|Set the tty terminal type"),
|
|
|
|
|
ARGPARSE_s_s('C', "lc-ctype", "|STRING|Set the tty LC_CTYPE value"),
|
|
|
|
|
ARGPARSE_s_s('M', "lc-messages", "|STRING|Set the tty LC_MESSAGES value"),
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
exit (EXIT_FAILURE);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'T':
|
|
|
|
|
pinentry.ttyname = strdup (pargs.r.ret_str);
|
|
|
|
|
if (!pinentry.ttyname)
|
|
|
|
|
{
|
|
|
|
|
exit (EXIT_FAILURE);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'N':
|
|
|
|
|
pinentry.ttytype = strdup (pargs.r.ret_str);
|
|
|
|
|
if (!pinentry.ttytype)
|
|
|
|
|
{
|
|
|
|
|
exit (EXIT_FAILURE);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'C':
|
|
|
|
|
pinentry.lc_ctype = strdup (pargs.r.ret_str);
|
|
|
|
|
if (!pinentry.lc_ctype)
|
|
|
|
|
{
|
|
|
|
|
exit (EXIT_FAILURE);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'M':
|
|
|
|
|
pinentry.lc_messages = strdup (pargs.r.ret_str);
|
|
|
|
|
if (!pinentry.lc_messages)
|
|
|
|
|
{
|
|
|
|
|
exit (EXIT_FAILURE);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'W':
|
|
|
|
|
pinentry.parent_wid = pargs.r.ret_ulong;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'c':
|
|
|
|
|
{
|
|
|
|
|
char *tmpstr = pargs.r.ret_str;
|
|
|
|
|
|
|
|
|
|
tmpstr = parse_color (tmpstr, &pinentry.color_fg,
|
|
|
|
|
&pinentry.color_fg_bright);
|
|
|
|
|
tmpstr = parse_color (tmpstr, &pinentry.color_bg, NULL);
|
|
|
|
|
tmpstr = parse_color (tmpstr, &pinentry.color_so,
|
|
|
|
|
&pinentry.color_so_bright);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'o':
|
|
|
|
|
pinentry.timeout = pargs.r.ret_int;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
pargs.err = ARGPARSE_PRINT_WARNING;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static 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"))
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
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, "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, "allow-external-password-cache") && !*value)
|
|
|
|
|
{
|
|
|
|
|
pinentry.allow_external_password_cache = 1;
|
|
|
|
|
pinentry.tried_password_cache = 0;
|
|
|
|
|
}
|
|
|
|
|
else if (!strcmp (key, "allow-emacs-prompt") && !*value)
|
|
|
|
|
{
|
|
|
|
|
return gpg_error (GPG_ERR_NOT_SUPPORTED);
|
|
|
|
|
}
|
|
|
|
|
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 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.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)
|
|
|
|
|
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.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)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
if (pinentry.specific_err)
|
|
|
|
|
return pinentry.specific_err;
|
|
|
|
|
|
|
|
|
|
if (pinentry.locale_err)
|
|
|
|
|
return gpg_error (GPG_ERR_LOCALE_PROBLEM);
|
|
|
|
|
|
|
|
|
|
if (pinentry.one_button)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
*/
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_getinfo (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
|
|
if (!strcmp (line, "version"))
|
|
|
|
|
{
|
|
|
|
|
const char *s = VERSION;
|
|
|
|
|
rc = assuan_send_data (ctx, s, strlen (s));
|
|
|
|
|
}
|
|
|
|
|
else if (!strcmp (line, "pid"))
|
|
|
|
|
{
|
|
|
|
|
char numbuf[50];
|
|
|
|
|
|
|
|
|
|
snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
|
|
|
|
|
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
rc = 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. */
|
|
|
|
|
if (getuid() != geteuid())
|
|
|
|
|
abort ();
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
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);
|
|
|
|
|
}
|