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); +} | 
