diff options
Diffstat (limited to 'pinentry/pinentry.c')
-rw-r--r-- | pinentry/pinentry.c | 1308 |
1 files changed, 1308 insertions, 0 deletions
diff --git a/pinentry/pinentry.c b/pinentry/pinentry.c new file mode 100644 index 0000000..68a7910 --- /dev/null +++ b/pinentry/pinentry.c @@ -0,0 +1,1308 @@ +/* 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); +} |