diff --git a/pinentry/pinentry-curses.c b/pinentry/pinentry-curses.c
index cc4cecb..ef1f141 100644
--- a/pinentry/pinentry-curses.c
+++ b/pinentry/pinentry-curses.c
@@ -1,1410 +1,1701 @@
 /* pinentry-curses.c - A secure curses dialog for PIN entry, library version
  * Copyright (C) 2002, 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/>.
  * SPDX-License-Identifier: GPL-2.0+
  */
 
 #ifdef HAVE_CONFIG_H
 #include <config.h>
 #endif
 #include <assert.h>
 #include <curses.h>
 #include <signal.h>
 #include <fcntl.h>
 #include <unistd.h>
 #include <stdlib.h>
 #include <locale.h>
 #include <iconv.h>
 #if defined(HAVE_LANGINFO_H)
 # include <langinfo.h>
 #elif defined(HAVE_W32_SYSTEM)
 # include <stdio.h>
 # ifndef WIN32_LEAN_AND_MEAN
 #  define WIN32_LEAN_AND_MEAN
 # endif
 # include <windows.h>
 /* A simple replacement for nl_langinfo that only understands
    CODESET.  */
 # define CODESET 1
 char *
 nl_langinfo (int ignore)
 {
   static char codepage[20];
   UINT cp = GetACP ();
 
   (void)ignore;
 
   sprintf (codepage, "CP%u", cp);
   return codepage;
 }
 #endif
 #include <limits.h>
 #include <string.h>
 #include <errno.h>
 #include <time.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #ifdef HAVE_UTIME_H
 #include <utime.h>
 #endif /*HAVE_UTIME_H*/
 
 #include <memory.h>
 
 #ifdef HAVE_WCHAR_H
 #include <wchar.h>
 #endif /*HAVE_WCHAR_H*/
 
 #include <assuan.h>
 
 #include "pinentry.h"
 
 #if GPG_ERROR_VERSION_NUMBER < 0x011900 /* 1.25 */
 # define GPG_ERR_WINDOW_TOO_SMALL 301
 # define GPG_ERR_MISSING_ENVVAR   303
 #endif
 
 
 /* FIXME: We should allow configuration of these button labels and in
    any case use the default_ok, default_cancel values if available.
    However, I have no clue about curses and localization.  */
 #define STRING_OK "<OK>"
 #define STRING_NOTOK "<No>"
 #define STRING_CANCEL "<Cancel>"
 
 #define USE_COLORS		(has_colors () && COLOR_PAIRS >= 2)
 static short pinentry_color[] = { -1, -1, COLOR_BLACK, COLOR_RED,
 				  COLOR_GREEN, COLOR_YELLOW, COLOR_BLUE,
 				  COLOR_MAGENTA, COLOR_CYAN, COLOR_WHITE };
 static int init_screen;
 #ifndef HAVE_DOSISH_SYSTEM
 static int timed_out;
 #endif
 
+
+#ifdef HAVE_NCURSESW
+typedef wchar_t CH;
+#define STRLEN(x) wcslen (x)
+#define STRWIDTH(x) wcswidth (x, wcslen (x))
+#define ADDCH(x) addnwstr (&x, 1);
+#define CHWIDTH(x) wcwidth (x)
+#define NULLCH L'\0'
+#define NLCH L'\n'
+#define SPCH L' '
+#else
+typedef char CH;
+#define STRLEN(x) strlen (x)
+#define STRWIDTH(x) strlen (x)
+#define ADDCH(x) addch ((unsigned char) x)
+#define CHWIDTH(x) 1
+#define NULLCH '\0'
+#define NLCH '\n'
+#define SPCH ' '
+#endif
+
 typedef enum
   {
     DIALOG_POS_NONE,
     DIALOG_POS_PIN,
+    DIALOG_POS_REPEAT_PIN,
     DIALOG_POS_OK,
     DIALOG_POS_NOTOK,
     DIALOG_POS_CANCEL
   }
 dialog_pos_t;
 
 struct dialog
 {
   dialog_pos_t pos;
+  char *repeat_pin;
   int pin_y;
   int pin_x;
+  int repeat_pin_y;
+  int repeat_pin_x;
   /* Width of the PIN field.  */
   int pin_size;
+  int repeat_pin_size;
   /* Cursor location in PIN field.  */
   int pin_loc;
+  int repeat_pin_loc;
   int pin_max;
   /* Length of PIN.  */
   int pin_len;
   int got_input;
   int no_echo;
+  int repeat_pin_len;
 
   int ok_y;
   int ok_x;
   char *ok;
   int cancel_y;
   int cancel_x;
   char *cancel;
   int notok_y;
   int notok_x;
   char *notok;
 
+  int error_y;
+  int error_x;
+  int error_height;
+  int width;
+  CH *error;
+  CH *repeat_error;
+
   pinentry_t pinentry;
 };
 typedef struct dialog *dialog_t;
 
 /* Flag to remember whether a warning has been printed.  */
 static int lc_ctype_unknown_warning;
 
 static char *
 pinentry_utf8_to_local (const char *lc_ctype, const char *text)
 {
   iconv_t cd;
   const char *input = text;
   size_t input_len = strlen (text) + 1;
   char *output;
   size_t output_len;
   char *output_buf;
   size_t processed;
   char *old_ctype;
   char *target_encoding;
   const char *pgmname = pinentry_get_pgmname ();
 
   /* If no locale setting could be determined, simply copy the
      string.  */
   if (!lc_ctype)
     {
       if (! lc_ctype_unknown_warning)
 	{
 	  fprintf (stderr, "%s: no LC_CTYPE known - assuming UTF-8\n",
 		   pgmname);
 	  lc_ctype_unknown_warning = 1;
 	}
       return strdup (text);
     }
 
   old_ctype = strdup (setlocale (LC_CTYPE, NULL));
   if (!old_ctype)
     return NULL;
   setlocale (LC_CTYPE, lc_ctype);
   target_encoding = nl_langinfo (CODESET);
   if (!target_encoding)
     target_encoding = "?";
   setlocale (LC_CTYPE, old_ctype);
   free (old_ctype);
 
   /* This is overkill, but simplifies the iconv invocation greatly.  */
   output_len = input_len * MB_LEN_MAX;
   output_buf = output = malloc (output_len);
   if (!output)
     return NULL;
 
   cd = iconv_open (target_encoding, "UTF-8");
   if (cd == (iconv_t) -1)
     {
       fprintf (stderr, "%s: can't convert from UTF-8 to %s: %s\n",
                pgmname, target_encoding, strerror (errno));
       free (output_buf);
       return NULL;
     }
   processed = iconv (cd, (ICONV_CONST char **)&input, &input_len,
                      &output, &output_len);
   iconv_close (cd);
   if (processed == (size_t) -1 || input_len)
     {
       fprintf (stderr, "%s: error converting from UTF-8 to %s: %s\n",
                pgmname, target_encoding, strerror (errno));
       free (output_buf);
       return NULL;
     }
   return output_buf;
 }
 
 /* Convert TEXT which is encoded according to LC_CTYPE to UTF-8.  With
    SECURE set to true, use secure memory for the returned buffer.
    Return NULL on error. */
 static char *
 pinentry_local_to_utf8 (char *lc_ctype, char *text, int secure)
 {
   char *old_ctype;
   char *source_encoding;
   iconv_t cd;
   const char *input = text;
   size_t input_len = strlen (text) + 1;
   char *output;
   size_t output_len;
   char *output_buf;
   size_t processed;
   const char *pgmname = pinentry_get_pgmname ();
 
   /* If no locale setting could be determined, simply copy the
      string.  */
   if (!lc_ctype)
     {
       if (! lc_ctype_unknown_warning)
 	{
 	  fprintf (stderr, "%s: no LC_CTYPE known - assuming UTF-8\n",
 		   pgmname);
 	  lc_ctype_unknown_warning = 1;
 	}
       output_buf = secure? secmem_malloc (input_len) : malloc (input_len);
       if (output_buf)
         strcpy (output_buf, input);
       return output_buf;
     }
 
   old_ctype = strdup (setlocale (LC_CTYPE, NULL));
   if (!old_ctype)
     return NULL;
   setlocale (LC_CTYPE, lc_ctype);
   source_encoding = nl_langinfo (CODESET);
   setlocale (LC_CTYPE, old_ctype);
   free (old_ctype);
 
   /* This is overkill, but simplifies the iconv invocation greatly.  */
   output_len = input_len * MB_LEN_MAX;
   output_buf = output = secure? secmem_malloc (output_len):malloc (output_len);
   if (!output)
     return NULL;
 
   cd = iconv_open ("UTF-8", source_encoding);
   if (cd == (iconv_t) -1)
     {
       fprintf (stderr, "%s: can't convert from %s to UTF-8: %s\n",
                pgmname, source_encoding? source_encoding : "?",
                strerror (errno));
       if (secure)
         secmem_free (output_buf);
       else
         free (output_buf);
       return NULL;
     }
   processed = iconv (cd, (ICONV_CONST char **)&input, &input_len,
                      &output, &output_len);
   iconv_close (cd);
   if (processed == (size_t) -1 || input_len)
     {
       fprintf (stderr, "%s: error converting from %s to UTF-8: %s\n",
                pgmname, source_encoding? source_encoding : "?",
                strerror (errno));
       if (secure)
         secmem_free (output_buf);
       else
         free (output_buf);
       return NULL;
     }
   return output_buf;
 }
-
-#ifdef HAVE_NCURSESW
-typedef wchar_t CH;
-#define STRLEN(x) wcslen (x)
-#define STRWIDTH(x) wcswidth (x, wcslen (x))
-#define ADDCH(x) addnwstr (&x, 1);
-#define CHWIDTH(x) wcwidth (x)
-#define NULLCH L'\0'
-#define NLCH L'\n'
-#define SPCH L' '
-#else
-typedef char CH;
-#define STRLEN(x) strlen (x)
-#define STRWIDTH(x) strlen (x)
-#define ADDCH(x) addch ((unsigned char) x)
-#define CHWIDTH(x) 1
-#define NULLCH '\0'
-#define NLCH '\n'
-#define SPCH ' '
-#endif
 
 /* Return the next line up to MAXWIDTH columns wide in START and LEN.
    Return value is the width needed for the line.
    The first invocation should have 0 as *LEN.  If the line ends with
    a \n, it is a normal line that will be continued.  If it is a '\0'
    the end of the text is reached after this line.  In all other cases
    there is a forced line break.  A full line is returned and will be
    continued in the next line.  */
 static int
 collect_line (int maxwidth, CH **start_p, int *len_p)
 {
   int last_space = 0;
   int len = *len_p;
   int width = 0;
   CH *end;
 
   /* Skip to next line.  */
   *start_p += len;
   /* Skip leading space.  */
   while (**start_p == SPCH)
     (*start_p)++;
 
   end = *start_p;
   len = 0;
 
   while (width < maxwidth - 1 && *end != NULLCH && *end != NLCH)
     {
       if (*end == SPCH)
 	last_space = len;
       width += CHWIDTH (*end);
       len++;
       end++;
     }
 
   if (*end != NULLCH && *end != NLCH && last_space != 0)
     {
       /* We reached the end of the available space, but still have
 	 characters to go in this line.  We can break the line into
 	 two parts at a space.  */
       len = last_space;
       (*start_p)[len] = NLCH;
     }
   *len_p = len + 1;
   return width;
 }
 
 #ifdef HAVE_NCURSESW
 static CH *
 utf8_to_local (char *lc_ctype, char *string)
 {
   mbstate_t ps;
   size_t len;
   char *local;
   const char *p;
   wchar_t *wcs = NULL;
   char *old_ctype = NULL;
 
   local = pinentry_utf8_to_local (lc_ctype, string);
   if (!local)
     return NULL;
 
   old_ctype = strdup (setlocale (LC_CTYPE, NULL));
   setlocale (LC_CTYPE, lc_ctype? lc_ctype : "");
 
   p = local;
   memset (&ps, 0, sizeof(mbstate_t));
   len = mbsrtowcs (NULL, &p, strlen (string), &ps);
   if (len == (size_t)-1)
     {
       free (local);
       goto leave;
     }
   wcs = calloc (len + 1, sizeof(wchar_t));
   if (!wcs)
     {
       free (local);
       goto leave;
     }
 
   p = local;
   memset (&ps, 0, sizeof(mbstate_t));
   mbsrtowcs (wcs, &p, len, &ps);
 
   free (local);
 
  leave:
   if (old_ctype)
     {
       setlocale (LC_CTYPE, old_ctype);
       free (old_ctype);
     }
 
   return wcs;
 }
 #else
 static CH *
 utf8_to_local (const char *lc_ctype, const char *string)
 {
   return pinentry_utf8_to_local (lc_ctype, string);
 }
 #endif
 
+static int test_repeat (dialog_t);
+static void
+draw_error (dialog_t dialog, int *xpos, int *ypos, int repeat_matches)
+{
+  CH *p = NULL, *error = NULL;
+  int i = 0;
+  int x = dialog->width;
+  int error_y = dialog->error_y;
+  pinentry_t pinentry = dialog->pinentry;
+
+  if (dialog->pinentry->confirm)
+    return;
+
+  if (repeat_matches != 0)
+    {
+      error = p = dialog->error && dialog->pinentry->error ? dialog->error : NULL;
+      repeat_matches = 0;
+
+      if (!p)
+        {
+          if (dialog->error_y || dialog->error_x)
+            {
+              int y;
+
+              for (y = 0; y < dialog->error_height; y++)
+                {
+                  move (dialog->error_y+y, dialog->error_x);
+                  addch (ACS_VLINE);
+                  addch (' ');
+                  while (i++ < x - 4)
+                    addch (' ');
+                  i = 0;
+                }
+            }
+          if (dialog->repeat_error && dialog->pinentry->repeat_passphrase)
+            {
+              move (dialog->error_y+1, dialog->error_x);
+              addch (ACS_VLINE);
+            }
+        }
+    }
+  else if (dialog->pinentry->repeat_passphrase)
+    p = dialog->repeat_error;
+
+  while (p && *p)
+    {
+      move (error_y, dialog->error_x);
+      addch (ACS_VLINE);
+      addch (' ');
+      if (USE_COLORS && pinentry->color_so != PINENTRY_COLOR_NONE)
+        {
+          attroff (COLOR_PAIR (1) | (pinentry->color_fg_bright ? A_BOLD : 0));
+          attron (COLOR_PAIR (2) | (pinentry->color_so_bright ? A_BOLD : 0));
+        }
+      else
+        standout ();
+      for (;*p && *p != NLCH; p++)
+        if (i < x - 4)
+          {
+            i++;
+            if (repeat_matches)
+              addch (' ');
+            else
+              ADDCH (*p);
+          }
+      while (i++ < x - 4)
+        addch (' ');
+
+      if (USE_COLORS && pinentry->color_so != PINENTRY_COLOR_NONE)
+        {
+          attroff (COLOR_PAIR (2) | (pinentry->color_so_bright ? A_BOLD : 0));
+          attron (COLOR_PAIR (1) | (pinentry->color_fg_bright ? A_BOLD : 0));
+        }
+      else
+        standend ();
+      if (*p == '\n')
+          p++;
+
+      i = 0;
+      (*ypos)++;
+      error_y++;
+    }
+
+  if (error)
+    {
+      move (*ypos, *xpos);
+      addch (ACS_VLINE);
+    }
+
+  for (error_y = 0; error_y < dialog->error_height; error_y++)
+    (*ypos)++;
+}
+
+static int
+test_repeat (dialog_t diag)
+{
+  int x = diag->error_x;
+  int y = diag->error_y;
+  int ox, oy;
+  int ret = 0;
+
+  if (!diag->pinentry->repeat_passphrase)
+    ret = 1;
+
+  if (!diag->pinentry->pin && !diag->repeat_pin)
+    ret = 1;
+
+  if ((diag->pinentry->pin && !*diag->pinentry->pin
+          && (!diag->repeat_pin || !*diag->repeat_pin)))
+    ret = 1;
+
+  if (diag->repeat_pin && !*diag->repeat_pin
+          && (!diag->pinentry->pin || !*diag->pinentry->pin))
+    ret = 1;
+
+  if (diag->pinentry->pin && diag->repeat_pin
+      && !strcmp (diag->pinentry->pin, diag->repeat_pin))
+    ret = 1;
+
+  getyx (stdscr, oy, ox);
+  draw_error (diag, &x, &y, ret);
+  wmove (stdscr, oy, ox);
+  return ret;
+}
+
 static int
 dialog_create (pinentry_t pinentry, dialog_t dialog)
 {
   int err = 0;
   int size_y;
   int size_x;
   int y;
   int x;
   int ypos;
   int xpos;
   int description_x = 0;
   int error_x = 0;
   CH *description = NULL;
   CH *error = NULL;
   CH *prompt = NULL;
+  CH *repeat_passphrase = NULL;
+  CH *repeat_error_string = NULL;
 
   dialog->pinentry = pinentry;
+  dialog->error_height = 0;
 
 #define COPY_OUT(what)							\
   do									\
     if (pinentry->what)							\
       {									\
         what = utf8_to_local (pinentry->lc_ctype, pinentry->what);	\
         if (!what)							\
 	  {								\
 	    err = 1;							\
             pinentry->specific_err = gpg_error (GPG_ERR_LOCALE_PROBLEM); \
             pinentry->specific_err_loc = "dialog_create_copy";          \
 	    goto out;							\
 	  }								\
       }									\
   while (0)
 
   COPY_OUT (description);
-  COPY_OUT (error);
   COPY_OUT (prompt);
+  COPY_OUT (repeat_passphrase);
+
+  COPY_OUT (error);
+  if (!error || !*error)
+    {
+      free (error);
+      error = NULL;
+    }
+  dialog->error = error;
+
+  if (repeat_passphrase)
+    {
+      COPY_OUT (repeat_error_string);
+      if (!repeat_error_string || !*repeat_error_string)
+        {
+          free (repeat_error_string);
+          repeat_error_string = NULL;
+        }
+      dialog->repeat_error = repeat_error_string;
+      if (repeat_error_string)
+        {
+          if (!error || (error && STRLEN (error) < STRLEN (repeat_error_string)))
+            error = repeat_error_string;
+        }
+    }
 
   /* There is no pinentry->default_notok.  Map it to
      pinentry->notok.  */
 #define default_notok notok
 #define MAKE_BUTTON(which,default)					\
   do									\
     {									\
       char *new = NULL;							\
       if (pinentry->default_##which || pinentry->which)			\
         {								\
 	  int len;							\
 	  char *msg;							\
 	  int i, j;							\
 									\
 	  msg = pinentry->which;					\
 	  if (! msg)							\
 	    msg = pinentry->default_##which;				\
           len = strlen (msg);						\
 									\
           new = malloc (len + 3);				       	\
 	  if (!new)							\
 	    {								\
 	      err = 1;							\
               pinentry->specific_err = gpg_error_from_syserror ();	\
               pinentry->specific_err_loc = "dialog_create_mk_button";   \
 	      goto out;							\
 	    }								\
 									\
 	  new[0] = '<'; 						\
 	  for (i = 0, j = 1; i < len; i ++, j ++)			\
 	    {								\
 	      if (msg[i] == '_')					\
 		{							\
 		  i ++;							\
 		  if (msg[i] == 0)					\
 		    /* _ at end of string.  */				\
 		    break;						\
 		}							\
 	      new[j] = msg[i];						\
 	    }								\
 									\
 	  new[j] = '>';							\
 	  new[j + 1] = 0;						\
         }								\
       dialog->which = pinentry_utf8_to_local (pinentry->lc_ctype,	\
 					      new ? new : default);	\
       free (new);							\
       if (!dialog->which)						\
         {								\
 	  err = 1;							\
           pinentry->specific_err = gpg_error (GPG_ERR_LOCALE_PROBLEM);	\
           pinentry->specific_err_loc = "dialog_create_utf8conv";        \
 	  goto out;							\
 	}								\
     }									\
   while (0)
 
   MAKE_BUTTON (ok, STRING_OK);
   if (!pinentry->one_button)
     MAKE_BUTTON (cancel, STRING_CANCEL);
   else
     dialog->cancel = NULL;
   if (!pinentry->one_button && pinentry->notok)
     MAKE_BUTTON (notok, STRING_NOTOK);
   else
     dialog->notok = NULL;
 
   getmaxyx (stdscr, size_y, size_x);
 
   /* Check if all required lines fit on the screen.  */
   y = 1;		/* Top frame.  */
   if (description)
     {
       CH *start = description;
       int len = 0;
 
       do
 	{
 	  int width = collect_line (size_x - 4, &start, &len);
 
 	  if (width > description_x)
 	    description_x = width;
 	  y++;
 	}
       while (start[len - 1]);
       y++;
     }
 
   if (pinentry->pin)
     {
       if (error)
 	{
-	  CH *p = error;
-	  int err_x = 0;
+          CH *start = error;
+          int len = 0;
 
-	  while (*p)
-	    {
-	      if (*(p++) == '\n')
-		{
-		  if (err_x > error_x)
-		    error_x = err_x;
-		  y++;
-		  err_x = 0;
-		}
-	      else
-		err_x++;
-	    }
-	  if (err_x > error_x)
-	    error_x = err_x;
-	  y += 2;	/* Error message.  */
-	}
+          if (*start)
+            dialog->error_height++;
+
+          do
+            {
+              int width = collect_line (size_x - 4, &start, &len);
+
+              if (width > error_x)
+                error_x = width;
+
+              dialog->error_height++;
+              y++;
+            }
+          while (start[len - 1]);
+          y++;
+        }
       y += 2;		/* Pin entry field.  */
+
+      if (repeat_passphrase)
+        y += 2;
     }
   y += 2;		/* OK/Cancel and bottom frame.  */
 
   if (y > size_y)
     {
       err = 1;
       pinentry->specific_err = gpg_error (size_y < 0? GPG_ERR_MISSING_ENVVAR
                                           /* */     : GPG_ERR_WINDOW_TOO_SMALL);
       pinentry->specific_err_loc = "dialog_create";
       goto out;
     }
 
   /* Check if all required columns fit on the screen.  */
   x = 0;
   if (description)
     {
       int new_x = description_x;
       if (new_x > size_x - 4)
 	new_x = size_x - 4;
       if (new_x > x)
 	x = new_x;
     }
   if (pinentry->pin)
     {
 #define MIN_PINENTRY_LENGTH 40
       int new_x;
 
       if (error)
 	{
 	  new_x = error_x;
 	  if (new_x > size_x - 4)
 	    new_x = size_x - 4;
 	  if (new_x > x)
 	    x = new_x;
 	}
 
       new_x = MIN_PINENTRY_LENGTH;
       if (prompt)
 	{
-	  new_x += STRWIDTH (prompt) + 1;	/* One space after prompt.  */
+          int n  = repeat_passphrase ? STRWIDTH (repeat_passphrase) : 0;
+	  new_x += STRWIDTH (prompt) + n + 1;	/* One space after prompt.  */
 	}
+      else if (repeat_passphrase)
+        new_x += STRWIDTH (repeat_passphrase) + 1;
+
       if (new_x > size_x - 4)
 	new_x = size_x - 4;
       if (new_x > x)
 	x = new_x;
     }
   /* We position the buttons after the first, second and third fourth
      of the width.  Account for rounding.  */
   if (x < 3 * strlen (dialog->ok))
     x = 3 * strlen (dialog->ok);
   if (dialog->cancel)
     if (x < 3 * strlen (dialog->cancel))
       x = 3 * strlen (dialog->cancel);
   if (dialog->notok)
     if (x < 3 * strlen (dialog->notok))
       x = 3 * strlen (dialog->notok);
 
   /* Add the frame.  */
   x += 4;
 
   if (x > size_x)
     {
       err = 1;
       pinentry->specific_err = gpg_error (size_x < 0? GPG_ERR_MISSING_ENVVAR
                                           /* */     : GPG_ERR_WINDOW_TOO_SMALL);
       pinentry->specific_err_loc = "dialog_create";
       goto out;
     }
 
   dialog->pos = DIALOG_POS_NONE;
   dialog->pin_max = pinentry->pin_len;
-  dialog->pin_loc = 0;
-  dialog->pin_len = 0;
+  dialog->pin_loc = dialog->repeat_pin_loc = 0;
+  dialog->pin_len = dialog->repeat_pin_len = 0;
   ypos = (size_y - y) / 2;
   xpos = (size_x - x) / 2;
   move (ypos, xpos);
   addch (ACS_ULCORNER);
   hline (0, x - 2);
   move (ypos, xpos + x - 1);
   addch (ACS_URCORNER);
   move (ypos + 1, xpos + x - 1);
   vline (0, y - 2);
   move (ypos + y - 1, xpos);
   addch (ACS_LLCORNER);
   hline (0, x - 2);
   move (ypos + y - 1, xpos + x - 1);
   addch (ACS_LRCORNER);
   ypos++;
+  dialog->width = x;
   if (description)
     {
       CH *start = description;
       int len = 0;
 
       do
 	{
 	  int i;
 
 	  move (ypos, xpos);
 	  addch (ACS_VLINE);
 	  addch (' ');
 	  collect_line (size_x - 4, &start, &len);
 	  for (i = 0; i < len - 1; i++)
 	    {
 	      ADDCH (start[i]);
 	    }
 	  if (start[len - 1] != NULLCH && start[len - 1] != NLCH)
 	    ADDCH (start[len - 1]);
 	  ypos++;
 	}
       while (start[len - 1]);
       move (ypos, xpos);
       addch (ACS_VLINE);
       ypos++;
     }
   if (pinentry->pin)
     {
       int i;
 
       if (error)
-	{
-	  CH *p = error;
-	  i = 0;
-
-	  while (*p)
-	    {
-	      move (ypos, xpos);
-	      addch (ACS_VLINE);
-	      addch (' ');
-	      if (USE_COLORS && pinentry->color_so != PINENTRY_COLOR_NONE)
-		{
-		  attroff (COLOR_PAIR (1) | (pinentry->color_fg_bright ? A_BOLD : 0));
-		  attron (COLOR_PAIR (2) | (pinentry->color_so_bright ? A_BOLD : 0));
-		}
-	      else
-		standout ();
-	      for (;*p && *p != NLCH; p++)
-		if (i < x - 4)
-		  {
-		    i++;
-		    ADDCH (*p);
-		  }
-	      if (USE_COLORS && pinentry->color_so != PINENTRY_COLOR_NONE)
-		{
-		  attroff (COLOR_PAIR (2) | (pinentry->color_so_bright ? A_BOLD : 0));
-		  attron (COLOR_PAIR (1) | (pinentry->color_fg_bright ? A_BOLD : 0));
-		}
-	      else
-		standend ();
-	      if (*p == '\n')
-		p++;
-	      i = 0;
-	      ypos++;
-	    }
-	  move (ypos, xpos);
-	  addch (ACS_VLINE);
-	  ypos++;
-	}
+        {
+          dialog->error_x = xpos;
+          dialog->error_y = ypos;
+          draw_error (dialog, &xpos, &ypos, -1);
+        }
 
       move (ypos, xpos);
       addch (ACS_VLINE);
       addch (' ');
 
       dialog->pin_y = ypos;
       dialog->pin_x = xpos + 2;
       dialog->pin_size = x - 4;
       if (prompt)
 	{
 	  CH *p = prompt;
 	  i = STRWIDTH (prompt);
 	  if (i > x - 4 - MIN_PINENTRY_LENGTH)
 	    i = x - 4 - MIN_PINENTRY_LENGTH;
 	  dialog->pin_x += i + 1;
 	  dialog->pin_size -= i + 1;
 	  i = STRLEN (prompt);
 	  while (i-- > 0)
 	    {
 	      ADDCH (*(p++));
 	    }
 	  addch (' ');
 	}
       for (i = 0; i < dialog->pin_size; i++)
 	addch ('_');
       ypos++;
       move (ypos, xpos);
       addch (ACS_VLINE);
       ypos++;
+
+      if (repeat_passphrase)
+        {
+	  CH *p = repeat_passphrase;
+
+          move (ypos, xpos);
+          addch (ACS_VLINE);
+          addch (' ');
+
+          dialog->repeat_pin_y = ypos;
+          dialog->repeat_pin_x = xpos + 2;
+          dialog->repeat_pin_size = x - 4;
+	  i = STRLEN (p);
+	  if (i > x - 4 - MIN_PINENTRY_LENGTH)
+	    i = x - 4 - MIN_PINENTRY_LENGTH;
+	  dialog->repeat_pin_x += i + 1;
+          dialog->repeat_pin_size -= i + 1;
+	  while (i-- > 0)
+	    {
+	      ADDCH (*(p++));
+	    }
+	  addch (' ');
+          for (i = 0; i < dialog->repeat_pin_size; i++)
+            addch ('_');
+          ypos++;
+          move (ypos, xpos);
+          addch (ACS_VLINE);
+          ypos++;
+        }
     }
   move (ypos, xpos);
   addch (ACS_VLINE);
 
   if (dialog->cancel || dialog->notok)
     {
       dialog->ok_y = ypos;
       /* Calculating the left edge of the left button, rounding down.  */
       dialog->ok_x = xpos + 2 + ((x - 4) / 3 - strlen (dialog->ok)) / 2;
       move (dialog->ok_y, dialog->ok_x);
       addstr (dialog->ok);
 
       if (! pinentry->pin && dialog->notok)
 	{
 	  dialog->notok_y = ypos;
 	  /* Calculating the left edge of the middle button, rounding up.  */
 	  dialog->notok_x = xpos + x / 2 - strlen (dialog->notok) / 2;
 	  move (dialog->notok_y, dialog->notok_x);
 	  addstr (dialog->notok);
 	}
       if (dialog->cancel)
 	{
 	  dialog->cancel_y = ypos;
 	  /* Calculating the left edge of the right button, rounding up.  */
 	  dialog->cancel_x = xpos + x - 2 - ((x - 4) / 3 + strlen (dialog->cancel)) / 2;
 	  move (dialog->cancel_y, dialog->cancel_x);
 	  addstr (dialog->cancel);
 	}
     }
   else
     {
       dialog->ok_y = ypos;
       /* Calculating the left edge of the OK button, rounding down.  */
       dialog->ok_x = xpos + x / 2 - strlen (dialog->ok) / 2;
       move (dialog->ok_y, dialog->ok_x);
       addstr (dialog->ok);
     }
 
   dialog->got_input = 0;
   dialog->no_echo = 0;
 
  out:
   if (description)
     free (description);
-  if (error)
-    free (error);
   if (prompt)
     free (prompt);
+  if (repeat_passphrase)
+    free (repeat_passphrase);
+
+  if (!err)
+    {
+      int ox = dialog->error_x;
+      int oy = dialog->error_y;
+
+      dialog->width = x;
+      draw_error (dialog, &ox, &oy, 1);
+    }
+
   return err;
 }
 
 
 static void
 set_cursor_state (int on)
 {
   static int normal_state = -1;
   static int on_last;
 
   if (normal_state < 0 && !on)
     {
       normal_state = curs_set (0);
       on_last = on;
     }
   else if (on != on_last)
     {
       curs_set (on ? normal_state : 0);
       on_last = on;
     }
 }
 
 static int
 dialog_switch_pos (dialog_t diag, dialog_pos_t new_pos)
 {
   if (new_pos != diag->pos)
     {
       switch (diag->pos)
 	{
 	case DIALOG_POS_OK:
-	  move (diag->ok_y, diag->ok_x);
-	  addstr (diag->ok);
+          move (diag->ok_y, diag->ok_x);
+          addstr (diag->ok);
 	  break;
 	case DIALOG_POS_NOTOK:
           if (diag->notok)
             {
               move (diag->notok_y, diag->notok_x);
               addstr (diag->notok);
             }
 	  break;
 	case DIALOG_POS_CANCEL:
           if (diag->cancel)
             {
               move (diag->cancel_y, diag->cancel_x);
               addstr (diag->cancel);
             }
 	  break;
 	default:
 	  break;
 	}
       diag->pos = new_pos;
       switch (diag->pos)
 	{
 	case DIALOG_POS_PIN:
 	  move (diag->pin_y, diag->pin_x + diag->pin_loc);
 	  set_cursor_state (1);
 	  break;
+	case DIALOG_POS_REPEAT_PIN:
+	  move (diag->repeat_pin_y, diag->repeat_pin_x + diag->repeat_pin_loc);
+          if (diag->no_echo)
+            {
+              move (diag->repeat_pin_y, diag->repeat_pin_x);
+              addstr ("[no echo]");
+            }
+	  set_cursor_state (1);
+	  break;
 	case DIALOG_POS_OK:
 	  set_cursor_state (0);
-	  move (diag->ok_y, diag->ok_x);
-	  standout ();
-	  addstr (diag->ok);
-	  standend ();
-	  move (diag->ok_y, diag->ok_x);
+          move (diag->ok_y, diag->ok_x);
+          standout ();
+          addstr (diag->ok);
+          standend ();
+          move (diag->ok_y, diag->ok_x);
 	  break;
 	case DIALOG_POS_NOTOK:
           if (diag->notok)
             {
               set_cursor_state (0);
               move (diag->notok_y, diag->notok_x);
               standout ();
               addstr (diag->notok);
               standend ();
               move (diag->notok_y, diag->notok_x);
             }
 	  break;
 	case DIALOG_POS_CANCEL:
           if (diag->cancel)
             {
               set_cursor_state (0);
               move (diag->cancel_y, diag->cancel_x);
               standout ();
               addstr (diag->cancel);
               standend ();
               move (diag->cancel_y, diag->cancel_x);
             }
 	  break;
 	case DIALOG_POS_NONE:
 	  set_cursor_state (0);
 	  break;
 	}
       refresh ();
     }
   return 0;
 }
 
 /* XXX Assume that field width is at least > 5.  */
 static void
 dialog_input (dialog_t diag, int alt, int chr)
 {
-  int old_loc = diag->pin_loc;
+  int old_loc = diag->pos == DIALOG_POS_PIN ? diag->pin_loc
+    : diag->repeat_pin_loc;
+  int *pin_len;
+  int *pin_loc;
+  int *pin_size;
+  int *pin_x, *pin_y;
+  int *pin_max = &diag->pin_max;
+  char *pin;
+
   assert (diag->pinentry->pin);
-  assert (diag->pos == DIALOG_POS_PIN);
+  assert (diag->pos == DIALOG_POS_PIN || diag->pos == DIALOG_POS_REPEAT_PIN);
+
+  if (diag->pos == DIALOG_POS_PIN)
+    {
+      pin_len = &diag->pin_len;
+      pin_loc = &diag->pin_loc;
+      pin_size = &diag->pin_size;
+      pin_x = &diag->pin_x;
+      pin_y = &diag->pin_y;
+      pin = diag->pinentry->pin;
+    }
+  else
+    {
+      pin_len = &diag->repeat_pin_len;
+      pin_loc = &diag->repeat_pin_loc;
+      pin_size = &diag->repeat_pin_size;
+      pin_x = &diag->repeat_pin_x;
+      pin_y = &diag->repeat_pin_y;
+      pin = diag->repeat_pin;
+    }
 
   if (alt && chr == KEY_BACKSPACE)
     /* Remap alt-backspace to control-W.  */
     chr = 'w' - 'a' + 1;
 
   switch (chr)
     {
     case KEY_BACKSPACE:
       /* control-h.  */
     case 'h' - 'a' + 1:
       /* ASCII DEL.  What Mac OS X apparently emits when the "delete"
 	 (backspace) key is pressed.  */
     case 127:
-      if (diag->pin_len > 0)
+      if (*pin_len > 0)
 	{
-	  diag->pin_len--;
-	  diag->pin_loc--;
-	  if (diag->pin_loc == 0 && diag->pin_len > 0)
+	  (*pin_len)--;
+	  (*pin_loc)--;
+	  if (*pin_loc == 0 && *pin_len > 0)
 	    {
-	      diag->pin_loc = diag->pin_size - 5;
-	      if (diag->pin_loc > diag->pin_len)
-		diag->pin_loc = diag->pin_len;
+	      *pin_loc = *pin_size - 5;
+	      if (*pin_loc > *pin_len)
+		*pin_loc = *pin_len;
 	    }
 	}
       else if (!diag->got_input)
 	{
 	  diag->no_echo = 1;
-	  move (diag->pin_y, diag->pin_x);
+	  move (*pin_y, *pin_x);
 	  addstr ("[no echo]");
 	}
       break;
 
     case 'l' - 'a' + 1: /* control-l */
       /* Refresh the screen.  */
       endwin ();
       refresh ();
       break;
 
     case 'u' - 'a' + 1: /* control-u */
       /* Erase the whole line.  */
-      if (diag->pin_len > 0)
+      if (*pin_len > 0)
 	{
-	  diag->pin_len = 0;
-	  diag->pin_loc = 0;
+	  *pin_len = 0;
+	  *pin_loc = 0;
 	}
       break;
 
     case 'w' - 'a' + 1: /* control-w.  */
-      while (diag->pin_len > 0
-	     && diag->pinentry->pin[diag->pin_len - 1] == ' ')
+      while (*pin_len > 0 && pin[*pin_len - 1] == ' ')
 	{
-	  diag->pin_len --;
-	  diag->pin_loc --;
-	  if (diag->pin_loc < 0)
+	  (*pin_len)--;
+	  (*pin_loc)--;
+	  if (*pin_loc < 0)
 	    {
-	      diag->pin_loc += diag->pin_size;
-	      if (diag->pin_loc > diag->pin_len)
-		diag->pin_loc = diag->pin_len;
+	      *pin_loc += *pin_size;
+	      if (*pin_loc > *pin_len)
+		*pin_loc = *pin_len;
 	    }
 	}
-      while (diag->pin_len > 0
-	     && diag->pinentry->pin[diag->pin_len - 1] != ' ')
+      while (*pin_len > 0 && pin[*pin_len - 1] != ' ')
 	{
-	  diag->pin_len --;
-	  diag->pin_loc --;
-	  if (diag->pin_loc < 0)
+	  (*pin_len)--;
+	  (*pin_loc)--;
+	  if (*pin_loc < 0)
 	    {
-	      diag->pin_loc += diag->pin_size;
-	      if (diag->pin_loc > diag->pin_len)
-		diag->pin_loc = diag->pin_len;
+	      *pin_loc += *pin_size;
+	      if (*pin_loc > *pin_len)
+		*pin_loc = *pin_len;
 	    }
 	}
 
       break;
 
     default:
-      if (chr > 0 && chr < 256 && diag->pin_len < diag->pin_max)
+      if (chr > 0 && chr < 256 && *pin_len < *pin_max)
 	{
 	  /* Make sure there is enough room for this character and a
 	     following NUL byte.  */
-	  if (! pinentry_setbufferlen (diag->pinentry, diag->pin_len + 2))
+	  if (! pinentry_setbufferlen (diag->pinentry, *pin_len + 2))
 	    {
 	      /* Bail.  Here we use a simple approach.  It would be
                  better to have a pinentry_bug function.  */
               assert (!"setbufferlen failed");
               abort ();
 	    }
 
-	  diag->pinentry->pin[diag->pin_len] = (char) chr;
-	  diag->pin_len++;
-	  diag->pin_loc++;
-	  if (diag->pin_loc == diag->pin_size && diag->pin_len < diag->pin_max)
+          if (diag->pos == DIALOG_POS_REPEAT_PIN)
+            {
+              if (!*pin_len || *pin_len > diag->repeat_pin_len)
+                {
+                  char *newp = secmem_realloc (diag->repeat_pin, *pin_len + 2);
+
+                  if (newp)
+                    pin = diag->repeat_pin = newp;
+                  else
+                    {
+                      secmem_free (diag->pinentry->pin);
+                      diag->pinentry->pin = 0;
+                      diag->pinentry->pin_len = 0;
+                      secmem_free (diag->repeat_pin);
+                      diag->repeat_pin = NULL;
+                      *pin_len = 0;
+                      /* Bail.  Here we use a simple approach.  It would be
+                         better to have a pinentry_bug function.  */
+                      assert (!"setbufferlen failed");
+                      abort ();
+                    }
+                }
+            }
+          else
+            pin = diag->pinentry->pin;
+
+	  pin[*pin_len] = (char) chr;
+	  (*pin_len)++;
+	  (*pin_loc)++;
+	  if (*pin_loc == *pin_size && *pin_len < *pin_max)
 	    {
-	      diag->pin_loc = 5;
-	      if (diag->pin_loc < diag->pin_size - (diag->pin_max + 1 - diag->pin_len))
-		diag->pin_loc = diag->pin_size - (diag->pin_max + 1 - diag->pin_len);
+	      *pin_loc = 5;
+	      if (*pin_loc < *pin_size - (*pin_max + 1 - *pin_len))
+		*pin_loc = *pin_size - (*pin_max + 1 - *pin_len);
 	    }
 	}
       break;
     }
 
   diag->got_input = 1;
 
   if (!diag->no_echo)
     {
-      if (old_loc < diag->pin_loc)
+      if (old_loc < *pin_loc)
 	{
-	  move (diag->pin_y, diag->pin_x + old_loc);
-	  while (old_loc++ < diag->pin_loc)
+	  move (*pin_y, *pin_x + old_loc);
+	  while (old_loc++ < *pin_loc)
 	    addch ('*');
 	}
-      else if (old_loc > diag->pin_loc)
+      else if (old_loc > *pin_loc)
 	{
-	  move (diag->pin_y, diag->pin_x + diag->pin_loc);
-	  while (old_loc-- > diag->pin_loc)
+	  move (*pin_y, *pin_x + *pin_loc);
+	  while (old_loc-- > *pin_loc)
 	    addch ('_');
 	}
-      move (diag->pin_y, diag->pin_x + diag->pin_loc);
+      move (*pin_y, *pin_x + *pin_loc);
     }
+
+  if (pin)
+    pin[*pin_len] = 0;
 }
 
 static int
 dialog_run (pinentry_t pinentry, const char *tty_name, const char *tty_type)
 {
   int confirm_mode = !pinentry->pin;
   struct dialog diag;
   FILE *ttyfi = NULL;
   FILE *ttyfo = NULL;
   SCREEN *screen = 0;
   int done = 0;
   char *pin_utf8;
   int alt = 0;
 #ifndef HAVE_DOSISH_SYSTEM
   int no_input = 1;
 #endif
 
+  memset (&diag, 0, sizeof (struct dialog));
+
 #ifdef HAVE_NCURSESW
   char *old_ctype = NULL;
 
   if (pinentry->lc_ctype)
     {
       old_ctype = strdup (setlocale (LC_CTYPE, NULL));
       setlocale (LC_CTYPE, pinentry->lc_ctype);
     }
   else
     setlocale (LC_CTYPE, "");
 #endif
 
   /* Open the desired terminal if necessary.  */
   if (tty_name)
     {
       ttyfi = fopen (tty_name, "r");
       if (!ttyfi)
         {
           pinentry->specific_err = gpg_error_from_syserror ();
           pinentry->specific_err_loc = "open_tty_for_read";
 #ifdef HAVE_NCURSESW
           free (old_ctype);
 #endif
           return confirm_mode? 0 : -1;
         }
       ttyfo = fopen (tty_name, "w");
       if (!ttyfo)
 	{
 	  int err = errno;
 	  fclose (ttyfi);
 	  errno = err;
           pinentry->specific_err = gpg_error_from_syserror ();
           pinentry->specific_err_loc = "open_tty_for_write";
 #ifdef HAVE_NCURSESW
           free (old_ctype);
 #endif
 	  return confirm_mode? 0 : -1;
 	}
       screen = newterm (tty_type, ttyfo, ttyfi);
       if (!screen)
         {
           pinentry->specific_err = gpg_error (GPG_ERR_WINDOW_TOO_SMALL);
           pinentry->specific_err_loc = "curses_init";
           fclose (ttyfo);
           fclose (ttyfi);
 #ifdef HAVE_NCURSESW
           free (old_ctype);
 #endif
           return confirm_mode? 0 : -1;
         }
       set_term (screen);
     }
   else
     {
       if (!init_screen)
 	{
           if (!(isatty(fileno(stdin)) && isatty(fileno(stdout))))
             {
               errno = ENOTTY;
               pinentry->specific_err = gpg_error_from_syserror ();
               pinentry->specific_err_loc = "isatty";
 #ifdef HAVE_NCURSESW
               free (old_ctype);
 #endif
               return confirm_mode? 0 : -1;
             }
 	  init_screen = 1;
 	  initscr ();
 	}
       else
 	clear ();
     }
 
   keypad (stdscr, TRUE); /* Enable keyboard mapping.  */
   nonl ();		/* Tell curses not to do NL->CR/NL on output.  */
   cbreak ();		/* Take input chars one at a time, no wait for \n.  */
   noecho ();		/* Don't echo input - in color.  */
 
   if (pinentry->ttyalert)
     {
       if (! strcmp(pinentry->ttyalert, "beep"))
 	beep ();
       else if (! strcmp(pinentry->ttyalert, "flash"))
 	flash ();
     }
 
   if (has_colors ())
     {
       start_color ();
 
       /* Ncurses has use_default_colors, an extentions to the curses
          library, which allows use of -1 to select default color.  */
 #ifdef NCURSES_VERSION
       use_default_colors ();
 #else
       /* With no extention, we need to specify color explicitly.  */
       if (pinentry->color_fg == PINENTRY_COLOR_DEFAULT)
         pinentry->color_fg = PINENTRY_COLOR_WHITE;
       if (pinentry->color_bg == PINENTRY_COLOR_DEFAULT)
         pinentry->color_bg = PINENTRY_COLOR_BLACK;
 #endif
 
       if (pinentry->color_so == PINENTRY_COLOR_DEFAULT)
 	{
 	  pinentry->color_so = PINENTRY_COLOR_RED;
 	  pinentry->color_so_bright = 1;
 	}
       if (COLOR_PAIRS >= 2)
 	{
 	  init_pair (1, pinentry_color[pinentry->color_fg],
 		     pinentry_color[pinentry->color_bg]);
 	  init_pair (2, pinentry_color[pinentry->color_so],
 		     pinentry_color[pinentry->color_bg]);
 
 	  bkgd (COLOR_PAIR (1));
 	  attron (COLOR_PAIR (1) | (pinentry->color_fg_bright ? A_BOLD : 0));
 	}
     }
   refresh ();
 
   /* Create the dialog.  */
   if (dialog_create (pinentry, &diag))
     {
       /* Note: pinentry->specific_err has already been set.  */
       endwin ();
       if (screen)
         delscreen (screen);
 
 #ifdef HAVE_NCURSESW
       if (old_ctype)
         {
           setlocale (LC_CTYPE, old_ctype);
           free (old_ctype);
         }
 #endif
       if (ttyfi)
         fclose (ttyfi);
       if (ttyfo)
         fclose (ttyfo);
       return -2;
     }
   dialog_switch_pos (&diag, confirm_mode? DIALOG_POS_OK : DIALOG_POS_PIN);
 
 #ifndef HAVE_DOSISH_SYSTEM
   wtimeout (stdscr, 70);
 #endif
 
   do
     {
       int c;
 
       c = wgetch (stdscr);     /* Refresh, accept single keystroke of input.  */
 #ifndef HAVE_DOSISH_SYSTEM
       if (timed_out && no_input)
 	{
 	  done = -2;
           pinentry->specific_err = gpg_error (GPG_ERR_TIMEOUT);
 	  break;
 	}
 #endif
 
       switch (c)
 	{
 	case ERR:
 #ifndef HAVE_DOSISH_SYSTEM
 	  continue;
 #else
           done = -2;
           break;
 #endif
 
 	case 27: /* Alt was pressed.  */
 	  alt = 1;
 	  /* Get the next key press.  */
 	  continue;
 
 	case KEY_LEFT:
 	case KEY_UP:
 	  switch (diag.pos)
 	    {
 	    case DIALOG_POS_OK:
 	      if (!confirm_mode)
-		dialog_switch_pos (&diag, DIALOG_POS_PIN);
+                {
+                  if (diag.pinentry->repeat_passphrase)
+                    dialog_switch_pos (&diag, DIALOG_POS_REPEAT_PIN);
+                  else if (diag.pinentry->pin)
+                    dialog_switch_pos (&diag, DIALOG_POS_PIN);
+                }
 	      break;
+            case DIALOG_POS_REPEAT_PIN:
+              if (diag.pinentry->pin)
+		dialog_switch_pos (&diag, DIALOG_POS_PIN);
+              break;
 	    case DIALOG_POS_NOTOK:
 	      dialog_switch_pos (&diag, DIALOG_POS_OK);
 	      break;
 	    case DIALOG_POS_CANCEL:
 	      if (diag.notok)
 		dialog_switch_pos (&diag, DIALOG_POS_NOTOK);
 	      else
-		dialog_switch_pos (&diag, DIALOG_POS_OK);
+                {
+                  if (!test_repeat (&diag))
+                    dialog_switch_pos (&diag, DIALOG_POS_REPEAT_PIN);
+                  else
+                    dialog_switch_pos (&diag, DIALOG_POS_OK);
+                }
 	      break;
 	    default:
 	      break;
 	    }
 	  break;
 
 	case KEY_RIGHT:
 	case KEY_DOWN:
 	  switch (diag.pos)
 	    {
 	    case DIALOG_POS_PIN:
-	      dialog_switch_pos (&diag, DIALOG_POS_OK);
+	      if (diag.pinentry->repeat_passphrase)
+                dialog_switch_pos (&diag, DIALOG_POS_REPEAT_PIN);
+              else
+                dialog_switch_pos (&diag, DIALOG_POS_OK);
 	      break;
+	    case DIALOG_POS_REPEAT_PIN:
+              if (test_repeat (&diag))
+                dialog_switch_pos (&diag, DIALOG_POS_OK);
+              else
+                dialog_switch_pos (&diag, DIALOG_POS_CANCEL);
+              break;
 	    case DIALOG_POS_OK:
 	      if (diag.notok)
 		dialog_switch_pos (&diag, DIALOG_POS_NOTOK);
 	      else
 		dialog_switch_pos (&diag, DIALOG_POS_CANCEL);
 	      break;
 	    case DIALOG_POS_NOTOK:
 	      dialog_switch_pos (&diag, DIALOG_POS_CANCEL);
 	      break;
 	    default:
 	      break;
 	    }
 	  break;
 
 	case '\t':
 	  switch (diag.pos)
 	    {
 	    case DIALOG_POS_PIN:
-	      dialog_switch_pos (&diag, DIALOG_POS_OK);
+	      if (diag.pinentry->repeat_passphrase)
+                dialog_switch_pos (&diag, DIALOG_POS_REPEAT_PIN);
+              else
+                dialog_switch_pos (&diag, DIALOG_POS_OK);
 	      break;
+	    case DIALOG_POS_REPEAT_PIN:
+              if (test_repeat (&diag))
+                dialog_switch_pos (&diag, DIALOG_POS_OK);
+              else
+                dialog_switch_pos (&diag, DIALOG_POS_CANCEL);
+              break;
 	    case DIALOG_POS_OK:
 	      if (diag.notok)
 		dialog_switch_pos (&diag, DIALOG_POS_NOTOK);
 	      else
 		dialog_switch_pos (&diag, DIALOG_POS_CANCEL);
 	      break;
 	    case DIALOG_POS_NOTOK:
 	      dialog_switch_pos (&diag, DIALOG_POS_CANCEL);
 	      break;
 	    case DIALOG_POS_CANCEL:
 	      if (confirm_mode)
 		dialog_switch_pos (&diag, DIALOG_POS_OK);
 	      else
 		dialog_switch_pos (&diag, DIALOG_POS_PIN);
 	      break;
 	    default:
 	      break;
 	    }
 	  break;
 
 	case '\005':
 	  done = -2;
 	  break;
 
 	case '\r':
 	  switch (diag.pos)
 	    {
 	    case DIALOG_POS_PIN:
+	    case DIALOG_POS_REPEAT_PIN:
 	    case DIALOG_POS_OK:
+              if (!test_repeat (&diag))
+                break;
 	      done = 1;
 	      break;
 	    case DIALOG_POS_NOTOK:
 	      done = -1;
 	      break;
 	    case DIALOG_POS_CANCEL:
 	      done = -2;
 	      break;
             case DIALOG_POS_NONE:
               break;
 	    }
 	  break;
 
 	default:
-	  if (diag.pos == DIALOG_POS_PIN)
-	    dialog_input (&diag, alt, c);
+	  if (diag.pos == DIALOG_POS_PIN || diag.pos == DIALOG_POS_REPEAT_PIN)
+            {
+              dialog_input (&diag, alt, c);
+              if (diag.pinentry->repeat_passphrase)
+                diag.pinentry->repeat_okay = test_repeat (&diag);
+            }
 	}
 #ifndef HAVE_DOSISH_SYSTEM
       no_input = 0;
 #endif
       if (c != -1)
 	alt = 0;
     }
   while (!done);
 
   if (!confirm_mode)
     {
       /* NUL terminate the passphrase.  dialog_run makes sure there is
          enough space for the terminating NUL byte.  */
       diag.pinentry->pin[diag.pin_len] = 0;
     }
 
   set_cursor_state (1);
   endwin ();
   if (screen)
     delscreen (screen);
 
 #ifdef HAVE_NCURSESW
   if (old_ctype)
     {
       setlocale (LC_CTYPE, old_ctype);
       free (old_ctype);
     }
 #endif
   if (ttyfi)
     fclose (ttyfi);
   if (ttyfo)
     fclose (ttyfo);
   /* XXX Factor out into dialog_release or something.  */
   free (diag.ok);
   if (diag.cancel)
     free (diag.cancel);
   if (diag.notok)
     free (diag.notok);
 
   if (!confirm_mode)
     {
       pinentry->locale_err = 1;
       pin_utf8 = pinentry_local_to_utf8 (pinentry->lc_ctype, pinentry->pin, 1);
       if (pin_utf8)
 	{
 	  pinentry_setbufferlen (pinentry, strlen (pin_utf8) + 1);
 	  if (pinentry->pin)
 	    strcpy (pinentry->pin, pin_utf8);
 	  secmem_free (pin_utf8);
 	  pinentry->locale_err = 0;
 	}
     }
 
+  if (diag.repeat_pin)
+    secmem_free (diag.repeat_pin);
+
+  if (diag.error)
+    free (diag.error);
+
   if (done == -2)
     pinentry->canceled = 1;
 
   /* In confirm mode return cancel instead of error.  */
   if (confirm_mode)
     return done < 0 ? 0 : 1;
 
   return done < 0 ? -1 : diag.pin_len;
 }
 
 
 /* If a touch has been registered, touch that file.  */
 static void
 do_touch_file (pinentry_t pinentry)
 {
 #ifdef HAVE_UTIME_H
   struct stat st;
   time_t tim;
 
   if (!pinentry->touch_file || !*pinentry->touch_file)
     return;
 
   if (stat (pinentry->touch_file, &st))
     return; /* Oops.  */
 
   /* Make sure that we actually update the mtime.  */
   while ( (tim = time (NULL)) == st.st_mtime )
     sleep (1);
 
   /* Update but ignore errors as we can't do anything in that case.
      Printing error messages may even clubber the display further. */
   utime (pinentry->touch_file, NULL);
 #endif /*HAVE_UTIME_H*/
 }
 
 #ifndef HAVE_DOSISH_SYSTEM
 static void
 catchsig (int sig)
 {
   if (sig == SIGALRM)
     timed_out = 1;
 }
 #endif
 
 int
 curses_cmd_handler (pinentry_t pinentry)
 {
   int rc;
 
 #ifndef HAVE_DOSISH_SYSTEM
   timed_out = 0;
 
   if (pinentry->timeout)
     {
       struct sigaction sa;
 
       memset (&sa, 0, sizeof(sa));
       sa.sa_handler = catchsig;
       sigaction (SIGALRM, &sa, NULL);
       alarm (pinentry->timeout);
     }
 #endif
 
   rc = dialog_run (pinentry, pinentry->ttyname, pinentry->ttytype_l);
   do_touch_file (pinentry);
   return rc;
 }
diff --git a/pinentry/pinentry.c b/pinentry/pinentry.c
index ea11b67..2d6ebb3 100644
--- a/pinentry/pinentry.c
+++ b/pinentry/pinentry.c
@@ -1,2067 +1,2070 @@
 /* pinentry.c - The PIN entry support library
  * Copyright (C) 2002, 2003, 2007, 2008, 2010, 2015, 2016, 2021 g10 Code GmbH
  *
  * This file is part of PINENTRY.
  *
  * PINENTRY is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
  * (at your option) any later version.
  *
  * PINENTRY is distributed in the hope that it will be useful, but
  * WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, see <https://www.gnu.org/licenses/>.
  * SPDX-License-Identifier: GPL-2.0+
  */
 
 #ifdef HAVE_CONFIG_H
 #include <config.h>
 #endif
 
 #ifndef HAVE_W32CE_SYSTEM
 # include <errno.h>
 #endif
 #include <stdlib.h>
 #include <string.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
 #include <assert.h>
 #ifndef HAVE_W32_SYSTEM
 # include <sys/utsname.h>
 #endif
 #ifndef HAVE_W32CE_SYSTEM
 # include <locale.h>
 #endif
 #include <limits.h>
 #ifdef HAVE_W32CE_SYSTEM
 # include <windows.h>
 #endif
 
 #include <assuan.h>
 
 #include "memory.h"
 #include "secmem-util.h"
 #include "argparse.h"
 #include "pinentry.h"
 #include "password-cache.h"
 
 #ifdef INSIDE_EMACS
 # include "pinentry-emacs.h"
 #endif
 #ifdef FALLBACK_CURSES
 # include "pinentry-curses.h"
 #endif
 
 #ifdef HAVE_W32CE_SYSTEM
 #define getpid() GetCurrentProcessId ()
 #endif
 
 /* Keep the name of our program here. */
 static char this_pgmname[50];
 
 struct pinentry pinentry;
 
 
 static const char *flavor_flag;
 
 /* Because gtk_init removes the --display arg from the command lines
  * and our command line parser is called after gtk_init (so that it
  * does not see gtk specific options) we don't have a way to get hold
  * of the --display option.  Our solution is to remember --display in
  * the call to pinentry_have_display and set it then in our
  * parser.  */
 static char *remember_display;
 
 static void
 pinentry_reset (int use_defaults)
 {
   /* GPG Agent sets these options once when it starts the pinentry.
      Don't reset them.  */
   int grab = pinentry.grab;
   char *ttyname = pinentry.ttyname;
   char *ttytype = pinentry.ttytype_l;
   char *ttyalert = pinentry.ttyalert;
   char *lc_ctype = pinentry.lc_ctype;
   char *lc_messages = pinentry.lc_messages;
   int allow_external_password_cache = pinentry.allow_external_password_cache;
   char *default_ok = pinentry.default_ok;
   char *default_cancel = pinentry.default_cancel;
   char *default_prompt = pinentry.default_prompt;
   char *default_pwmngr = pinentry.default_pwmngr;
   char *default_cf_visi = pinentry.default_cf_visi;
   char *default_tt_visi = pinentry.default_tt_visi;
   char *default_tt_hide = pinentry.default_tt_hide;
   char *default_capshint = pinentry.default_capshint;
   char *touch_file = pinentry.touch_file;
   unsigned long owner_pid = pinentry.owner_pid;
   int owner_uid = pinentry.owner_uid;
   char *owner_host = pinentry.owner_host;
   int constraints_enforce = pinentry.constraints_enforce;
   char *constraints_hint_short = pinentry.constraints_hint_short;
   char *constraints_hint_long = pinentry.constraints_hint_long;
   char *constraints_error_title = pinentry.constraints_error_title;
 
   /* These options are set from the command line.  Don't reset
      them.  */
   int debug = pinentry.debug;
   char *display = pinentry.display;
   int parent_wid = pinentry.parent_wid;
 
   pinentry_color_t color_fg = pinentry.color_fg;
   int color_fg_bright = pinentry.color_fg_bright;
   pinentry_color_t color_bg = pinentry.color_bg;
   pinentry_color_t color_so = pinentry.color_so;
   int color_so_bright = pinentry.color_so_bright;
 
   int timeout = pinentry.timeout;
 
   char *invisible_char = pinentry.invisible_char;
 
 
   /* Free any allocated memory.  */
   if (use_defaults)
     {
       free (pinentry.ttyname);
       free (pinentry.ttytype_l);
       free (pinentry.ttyalert);
       free (pinentry.lc_ctype);
       free (pinentry.lc_messages);
       free (pinentry.default_ok);
       free (pinentry.default_cancel);
       free (pinentry.default_prompt);
       free (pinentry.default_pwmngr);
       free (pinentry.default_cf_visi);
       free (pinentry.default_tt_visi);
       free (pinentry.default_tt_hide);
       free (pinentry.default_capshint);
       free (pinentry.touch_file);
       free (pinentry.owner_host);
       free (pinentry.display);
       free (pinentry.constraints_hint_short);
       free (pinentry.constraints_hint_long);
       free (pinentry.constraints_error_title);
     }
 
   free (pinentry.title);
   free (pinentry.description);
   free (pinentry.error);
   free (pinentry.prompt);
   free (pinentry.ok);
   free (pinentry.notok);
   free (pinentry.cancel);
   secmem_free (pinentry.pin);
   free (pinentry.repeat_passphrase);
   free (pinentry.repeat_error_string);
   free (pinentry.quality_bar);
   free (pinentry.quality_bar_tt);
   free (pinentry.formatted_passphrase_hint);
   free (pinentry.keyinfo);
   free (pinentry.specific_err_info);
 
   /* Reset the pinentry structure.  */
   memset (&pinentry, 0, sizeof (pinentry));
 
   /* Restore options without a default we want to preserve.  */
   pinentry.invisible_char = invisible_char;
 
   /* Restore other options or set defaults.  */
 
   if (use_defaults)
     {
       /* Pinentry timeout in seconds.  */
       pinentry.timeout = 60;
 
       /* Global grab.  */
       pinentry.grab = 1;
 
       pinentry.color_fg = PINENTRY_COLOR_DEFAULT;
       pinentry.color_fg_bright = 0;
       pinentry.color_bg = PINENTRY_COLOR_DEFAULT;
       pinentry.color_so = PINENTRY_COLOR_DEFAULT;
       pinentry.color_so_bright = 0;
 
       pinentry.owner_uid = -1;
     }
   else /* Restore the options.  */
     {
       pinentry.grab = grab;
       pinentry.ttyname = ttyname;
       pinentry.ttytype_l = ttytype;
       pinentry.ttyalert = ttyalert;
       pinentry.lc_ctype = lc_ctype;
       pinentry.lc_messages = lc_messages;
       pinentry.allow_external_password_cache = allow_external_password_cache;
       pinentry.default_ok = default_ok;
       pinentry.default_cancel = default_cancel;
       pinentry.default_prompt = default_prompt;
       pinentry.default_pwmngr = default_pwmngr;
       pinentry.default_cf_visi = default_cf_visi;
       pinentry.default_tt_visi = default_tt_visi;
       pinentry.default_tt_hide = default_tt_hide;
       pinentry.default_capshint = default_capshint;
       pinentry.touch_file = touch_file;
       pinentry.owner_pid = owner_pid;
       pinentry.owner_uid = owner_uid;
       pinentry.owner_host = owner_host;
       pinentry.constraints_enforce = constraints_enforce;
       pinentry.constraints_hint_short = constraints_hint_short;
       pinentry.constraints_hint_long = constraints_hint_long;
       pinentry.constraints_error_title = constraints_error_title;
 
       pinentry.debug = debug;
       pinentry.display = display;
       pinentry.parent_wid = parent_wid;
 
       pinentry.color_fg = color_fg;
       pinentry.color_fg_bright = color_fg_bright;
       pinentry.color_bg = color_bg;
       pinentry.color_so = color_so;
       pinentry.color_so_bright = color_so_bright;
 
       pinentry.timeout = timeout;
     }
 }
 
 static gpg_error_t
 pinentry_assuan_reset_handler (assuan_context_t ctx, char *line)
 {
   (void)ctx;
   (void)line;
 
   pinentry_reset (0);
 
   return 0;
 }
 
 
 
 /* Copy TEXT or TEXTLEN to BUFFER and escape as required.  Return a
    pointer to the end of the new buffer.  Note that BUFFER must be
    large enough to keep the entire text; allocataing it 3 times of
    TEXTLEN is sufficient.  */
 static char *
 copy_and_escape (char *buffer, const void *text, size_t textlen)
 {
   int i;
   const unsigned char *s = (unsigned char *)text;
   char *p = buffer;
 
   for (i=0; i < textlen; i++)
     {
       if (s[i] < ' ' || s[i] == '+')
         {
           snprintf (p, 4, "%%%02X", s[i]);
           p += 3;
         }
       else if (s[i] == ' ')
         *p++ = '+';
       else
         *p++ = s[i];
     }
   return p;
 }
 
 
 /* Perform percent unescaping in STRING and return the new valid length
    of the string.  A terminating Nul character is inserted at the end of
    the unescaped string.
  */
 static size_t
 do_unescape_inplace (char *s)
 {
   unsigned char *p, *p0;
 
   p = p0 = s;
   while (*s)
     {
       if (*s == '%' && s[1] && s[2])
         {
           s++;
           *p++ = xtoi_2 (s);
           s += 2;
         }
       else
         *p++ = *s++;
     }
   *p = 0;
 
   return (p - p0);
 }
 
 
 /* Return a malloced copy of the commandline for PID.  If this is not
  * possible NULL is returned.  */
 #ifndef HAVE_W32_SYSTEM
 static char *
 get_cmdline (unsigned long pid)
 {
   char buffer[200];
   FILE *fp;
   size_t i, n;
 
   snprintf (buffer, sizeof buffer, "/proc/%lu/cmdline", pid);
 
   fp = fopen (buffer, "rb");
   if (!fp)
     return NULL;
   n = fread (buffer, 1, sizeof buffer - 1, fp);
   if (n < sizeof buffer -1 && ferror (fp))
     {
       /* Some error occurred.  */
       fclose (fp);
       return NULL;
     }
   fclose (fp);
   if (n == 0)
     return NULL;
   /* Arguments are delimited by Nuls.  We should do proper quoting but
    * that can be a bit complicated, thus we simply replace the Nuls by
    * spaces.  */
   for (i=0; i < n; i++)
     if (!buffer[i] && i < n-1)
       buffer[i] = ' ';
   buffer[i] = 0; /* Make sure the last byte is the string terminator.  */
 
   return strdup (buffer);
 }
 #endif /*!HAVE_W32_SYSTEM*/
 
 
 /* Atomically ask the kernel for information about process PID.
  * Return a malloc'ed copy of the process name as long as the process
  * uid matches UID.  If it cannot determine that the process has uid
  * UID, it returns NULL.
  *
  * This is not as informative as get_cmdline, but it verifies that the
  * process does belong to the user in question.
  */
 #ifndef HAVE_W32_SYSTEM
 static char *
 get_pid_name_for_uid (unsigned long pid, int uid)
 {
   char buffer[400];
   FILE *fp;
   size_t end, n;
   char *uidstr;
 
   snprintf (buffer, sizeof buffer, "/proc/%lu/status", pid);
 
   fp = fopen (buffer, "rb");
   if (!fp)
     return NULL;
   n = fread (buffer, 1, sizeof buffer - 1, fp);
   if (n < sizeof buffer -1 && ferror (fp))
     {
       /* Some error occurred.  */
       fclose (fp);
       return NULL;
     }
   fclose (fp);
   if (n == 0)
     return NULL;
   buffer[n] = 0;
   /* Fixme: Is it specified that "Name" is always the first line?  For
    * robustness I would prefer to have a real parser here. -wk  */
   if (strncmp (buffer, "Name:\t", 6))
     return NULL;
   end = strcspn (buffer + 6, "\n") + 6;
   buffer[end] = 0;
 
   /* check that uid matches what we expect */
   uidstr = strstr (buffer + end + 1, "\nUid:\t");
   if (!uidstr)
     return NULL;
   if (atoi (uidstr + 6) != uid)
     return NULL;
 
   return strdup (buffer + 6);
 }
 #endif /*!HAVE_W32_SYSTEM*/
 
 
 const char *
 pinentry_get_pgmname (void)
 {
   return this_pgmname;
 }
 
 
 /* Return a malloced string with the title.  The caller mus free the
  * string.  If no title is available or the title string has an error
  * NULL is returned.  */
 char *
 pinentry_get_title (pinentry_t pe)
 {
   char *title;
 
   if (pe->title)
     title = strdup (pe->title);
 #ifndef HAVE_W32_SYSTEM
   else if (pe->owner_pid)
     {
       char buf[200];
       struct utsname utsbuf;
       char *pidname = NULL;
       char *cmdline = NULL;
 
       if (pe->owner_host &&
           !uname (&utsbuf) &&
           !strcmp (utsbuf.nodename, pe->owner_host))
         {
           pidname = get_pid_name_for_uid (pe->owner_pid, pe->owner_uid);
           if (pidname)
             cmdline = get_cmdline (pe->owner_pid);
         }
 
       if (pe->owner_host && (cmdline || pidname))
         snprintf (buf, sizeof buf, "[%lu]@%s (%s)",
                   pe->owner_pid, pe->owner_host, cmdline ? cmdline : pidname);
       else if (pe->owner_host)
         snprintf (buf, sizeof buf, "[%lu]@%s",
                   pe->owner_pid, pe->owner_host);
       else
         snprintf (buf, sizeof buf, "[%lu] <unknown host>",
                   pe->owner_pid);
       free (pidname);
       free (cmdline);
       title = strdup (buf);
     }
 #endif /*!HAVE_W32_SYSTEM*/
   else
     title = strdup (this_pgmname);
 
   return title;
 }
 
 
 /* Run a quality inquiry for PASSPHRASE of LENGTH.  (We need LENGTH
    because not all backends might be able to return a proper
    C-string.).  Returns: A value between -100 and 100 to give an
    estimate of the passphrase's quality.  Negative values are use if
    the caller won't even accept that passphrase.  Note that we expect
    just one data line which should not be escaped in any represent a
    numeric signed decimal value.  Extra data is currently ignored but
    should not be send at all.  */
 int
 pinentry_inq_quality (pinentry_t pin, const char *passphrase, size_t length)
 {
   assuan_context_t ctx = pin->ctx_assuan;
   const char prefix[] = "INQUIRE QUALITY ";
   char *command;
   char *line;
   size_t linelen;
   int gotvalue = 0;
   int value = 0;
   int rc;
 
   if (!ctx)
     return 0; /* Can't run the callback.  */
 
   if (length > 300)
     length = 300;  /* Limit so that it definitely fits into an Assuan
                       line.  */
 
   command = secmem_malloc (strlen (prefix) + 3*length + 1);
   if (!command)
     return 0;
   strcpy (command, prefix);
   copy_and_escape (command + strlen(command), passphrase, length);
   rc = assuan_write_line (ctx, command);
   secmem_free (command);
   if (rc)
     {
       fprintf (stderr, "ASSUAN WRITE LINE failed: rc=%d\n", rc);
       return 0;
     }
 
   for (;;)
     {
       do
         {
           rc = assuan_read_line (ctx, &line, &linelen);
           if (rc)
             {
               fprintf (stderr, "ASSUAN READ LINE failed: rc=%d\n", rc);
               return 0;
             }
         }
       while (*line == '#' || !linelen);
       if (line[0] == 'E' && line[1] == 'N' && line[2] == 'D'
           && (!line[3] || line[3] == ' '))
         break; /* END command received*/
       if (line[0] == 'C' && line[1] == 'A' && line[2] == 'N'
           && (!line[3] || line[3] == ' '))
         break; /* CAN command received*/
       if (line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
           && (!line[3] || line[3] == ' '))
         break; /* ERR command received*/
       if (line[0] != 'D' || line[1] != ' ' || linelen < 3 || gotvalue)
         continue;
       gotvalue = 1;
       value = atoi (line+2);
     }
   if (value < -100)
     value = -100;
   else if (value > 100)
     value = 100;
 
   return value;
 }
 
 
 /* Run a checkpin inquiry */
 char *
 pinentry_inq_checkpin (pinentry_t pin, const char *passphrase, size_t length)
 {
   assuan_context_t ctx = pin->ctx_assuan;
   const char prefix[] = "INQUIRE CHECKPIN ";
   char *command;
   char *line;
   size_t linelen;
   int gotvalue = 0;
   char *value = NULL;
   int rc;
 
   if (!ctx)
     return 0; /* Can't run the callback.  */
 
   if (length > 300)
     length = 300;  /* Limit so that it definitely fits into an Assuan
                       line.  */
 
   command = secmem_malloc (strlen (prefix) + 3*length + 1);
   if (!command)
     return 0;
   strcpy (command, prefix);
   copy_and_escape (command + strlen(command), passphrase, length);
   rc = assuan_write_line (ctx, command);
   secmem_free (command);
   if (rc)
     {
       fprintf (stderr, "ASSUAN WRITE LINE failed: rc=%d\n", rc);
       return 0;
     }
 
   for (;;)
     {
       do
         {
           rc = assuan_read_line (ctx, &line, &linelen);
           if (rc)
             {
               fprintf (stderr, "ASSUAN READ LINE failed: rc=%d\n", rc);
               return 0;
             }
         }
       while (*line == '#' || !linelen);
       if (line[0] == 'E' && line[1] == 'N' && line[2] == 'D'
           && (!line[3] || line[3] == ' '))
         break; /* END command received*/
       if (line[0] == 'C' && line[1] == 'A' && line[2] == 'N'
           && (!line[3] || line[3] == ' '))
         break; /* CAN command received*/
       if (line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
           && (!line[3] || line[3] == ' '))
         break; /* ERR command received*/
       if (line[0] != 'D' || line[1] != ' ' || linelen < 3 || gotvalue)
         continue;
       gotvalue = 1;
       value = strdup (line + 2);
     }
 
   return value;
 }
 
 
 /* Run a genpin inquiry */
 char *
 pinentry_inq_genpin (pinentry_t pin)
 {
   assuan_context_t ctx = pin->ctx_assuan;
   const char prefix[] = "INQUIRE GENPIN";
   char *line;
   size_t linelen;
   int gotvalue = 0;
   char *value = NULL;
   int rc;
 
   if (!ctx)
     return 0; /* Can't run the callback.  */
 
   rc = assuan_write_line (ctx, prefix);
   if (rc)
     {
       fprintf (stderr, "ASSUAN WRITE LINE failed: rc=%d\n", rc);
       return 0;
     }
 
   for (;;)
     {
       do
         {
           rc = assuan_read_line (ctx, &line, &linelen);
           if (rc)
             {
               fprintf (stderr, "ASSUAN READ LINE failed: rc=%d\n", rc);
               free (value);
               return 0;
             }
         }
       while (*line == '#' || !linelen);
       if (line[0] == 'E' && line[1] == 'N' && line[2] == 'D'
           && (!line[3] || line[3] == ' '))
         break; /* END command received*/
       if (line[0] == 'C' && line[1] == 'A' && line[2] == 'N'
           && (!line[3] || line[3] == ' '))
         break; /* CAN command received*/
       if (line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
           && (!line[3] || line[3] == ' '))
         break; /* ERR command received*/
       if (line[0] != 'D' || line[1] != ' ' || linelen < 3 || gotvalue)
         continue;
       gotvalue = 1;
       value = strdup (line + 2);
     }
 
   return value;
 }
 
 /* Try to make room for at least LEN bytes in the pinentry.  Returns
    new buffer on success and 0 on failure or when the old buffer is
    sufficient.  */
 char *
 pinentry_setbufferlen (pinentry_t pin, int len)
 {
   char *newp;
 
   if (pin->pin_len)
     assert (pin->pin);
   else
     assert (!pin->pin);
 
   if (len < 2048)
     len = 2048;
 
   if (len <= pin->pin_len)
     return pin->pin;
 
   newp = secmem_realloc (pin->pin, len);
   if (newp)
     {
       pin->pin = newp;
       pin->pin_len = len;
     }
   else
     {
       secmem_free (pin->pin);
       pin->pin = 0;
       pin->pin_len = 0;
     }
   return newp;
 }
 
 static void
 pinentry_setbuffer_clear (pinentry_t pin)
 {
   if (! pin->pin)
     {
       assert (pin->pin_len == 0);
       return;
     }
 
   assert (pin->pin_len > 0);
 
   secmem_free (pin->pin);
   pin->pin = NULL;
   pin->pin_len = 0;
 }
 
 static void
 pinentry_setbuffer_init (pinentry_t pin)
 {
   pinentry_setbuffer_clear (pin);
   pinentry_setbufferlen (pin, 0);
 }
 
 /* passphrase better be alloced with secmem_alloc.  */
 void
 pinentry_setbuffer_use (pinentry_t pin, char *passphrase, int len)
 {
   if (! passphrase)
     {
       assert (len == 0);
       pinentry_setbuffer_clear (pin);
 
       return;
     }
 
   if (passphrase && len == 0)
     len = strlen (passphrase) + 1;
 
   if (pin->pin)
     secmem_free (pin->pin);
 
   pin->pin = passphrase;
   pin->pin_len = len;
 }
 
 static struct assuan_malloc_hooks assuan_malloc_hooks = {
   secmem_malloc, secmem_realloc, secmem_free
 };
 
 /* Initialize the secure memory subsystem, drop privileges and return.
    Must be called early. */
 void
 pinentry_init (const char *pgmname)
 {
   /* Store away our name. */
   if (strlen (pgmname) > sizeof this_pgmname - 2)
     abort ();
   strcpy (this_pgmname, pgmname);
 
   gpgrt_check_version (NULL);
 
   /* Initialize secure memory.  1 is too small, so the default size
      will be used.  */
   secmem_init (1);
   secmem_set_flags (SECMEM_WARN);
   drop_privs ();
 
   if (atexit (secmem_term))
     {
       /* FIXME: Could not register at-exit function, bail out.  */
     }
 
   assuan_set_malloc_hooks (&assuan_malloc_hooks);
 }
 
 /* Simple test to check whether DISPLAY is set or the option --display
    was given.  Used to decide whether the GUI or curses should be
    initialized.  */
 int
 pinentry_have_display (int argc, char **argv)
 {
   int found = 0;
 
   for (; argc; argc--, argv++)
     {
       if (!strcmp (*argv, "--display"))
         {
           if (argv[1] && !remember_display)
             {
               remember_display = strdup (argv[1]);
               if (!remember_display)
                 {
 #ifndef HAVE_W32CE_SYSTEM
                   fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno));
 #endif
                   exit (EXIT_FAILURE);
                 }
             }
           found = 1;
           break;
         }
       else if (!strncmp (*argv, "--display=", 10))
         {
           if (!remember_display)
             {
               remember_display = strdup (*argv+10);
               if (!remember_display)
                 {
 #ifndef HAVE_W32CE_SYSTEM
                   fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno));
 #endif
                   exit (EXIT_FAILURE);
                 }
             }
           found = 1;
           break;
         }
     }
 
 #ifndef HAVE_W32CE_SYSTEM
   {
     const char *s;
     s = getenv ("DISPLAY");
     if (s && *s)
       found = 1;
   }
 #endif
 
   return found;
 }
 
 
 
 /* Print usage information and and provide strings for help. */
 static const char *
 my_strusage( int level )
 {
   const char *p;
 
   switch (level)
     {
     case 11: p = this_pgmname; break;
     case 12: p = "pinentry"; break;
     case 13: p = PACKAGE_VERSION; break;
     case 14: p = "Copyright (C) 2016 g10 Code GmbH"; break;
     case 19: p = "Please report bugs to <" PACKAGE_BUGREPORT ">.\n"; break;
     case 1:
     case 40:
       {
         static char *str;
 
         if (!str)
           {
             size_t n = 50 + strlen (this_pgmname);
             str = malloc (n);
             if (str)
               {
                 snprintf (str, n, "Usage: %s [options] (-h for help)",
                           this_pgmname);
               }
           }
         p = str;
       }
       break;
     case 41:
       p = "Ask securely for a secret and print it to stdout.";
       break;
 
     case 42:
       p = "1"; /* Flag print 40 as part of 41. */
       break;
 
     default: p = NULL; break;
     }
   return p;
 }
 
 
 char *
 parse_color (char *arg, pinentry_color_t *color_p, int *bright_p)
 {
   static struct
   {
     const char *name;
     pinentry_color_t color;
   } colors[] = { { "none", PINENTRY_COLOR_NONE },
 		 { "default", PINENTRY_COLOR_DEFAULT },
 		 { "black", PINENTRY_COLOR_BLACK },
 		 { "red", PINENTRY_COLOR_RED },
 		 { "green", PINENTRY_COLOR_GREEN },
 		 { "yellow", PINENTRY_COLOR_YELLOW },
 		 { "blue", PINENTRY_COLOR_BLUE },
 		 { "magenta", PINENTRY_COLOR_MAGENTA },
 		 { "cyan", PINENTRY_COLOR_CYAN },
 		 { "white", PINENTRY_COLOR_WHITE } };
 
   int i;
   char *new_arg;
   pinentry_color_t color = PINENTRY_COLOR_DEFAULT;
 
   if (!arg)
     return NULL;
 
   new_arg = strchr (arg, ',');
   if (new_arg)
     new_arg++;
 
   if (bright_p)
     {
       const char *bname[] = { "bright-", "bright", "bold-", "bold" };
 
       *bright_p = 0;
       for (i = 0; i < sizeof (bname) / sizeof (bname[0]); i++)
 	if (!strncasecmp (arg, bname[i], strlen (bname[i])))
 	  {
 	    *bright_p = 1;
 	    arg += strlen (bname[i]);
 	  }
     }
 
   for (i = 0; i < sizeof (colors) / sizeof (colors[0]); i++)
     if (!strncasecmp (arg, colors[i].name, strlen (colors[i].name)))
       color = colors[i].color;
 
   *color_p = color;
   return new_arg;
 }
 
 /* Parse the command line options.  May exit the program if only help
    or version output is requested.  */
 void
 pinentry_parse_opts (int argc, char *argv[])
 {
   static ARGPARSE_OPTS opts[] = {
     ARGPARSE_s_n('d', "debug",    "Turn on debugging output"),
     ARGPARSE_s_s('D', "display",  "|DISPLAY|Set the X display"),
     ARGPARSE_s_s('T', "ttyname",  "|FILE|Set the tty terminal node name"),
     ARGPARSE_s_s('N', "ttytype",  "|NAME|Set the tty terminal type"),
     ARGPARSE_s_s('C', "lc-ctype", "|STRING|Set the tty LC_CTYPE value"),
     ARGPARSE_s_s('M', "lc-messages", "|STRING|Set the tty LC_MESSAGES value"),
     ARGPARSE_s_i('o', "timeout",
                  "|SECS|Timeout waiting for input after this many seconds"),
     ARGPARSE_s_n('g', "no-global-grab",
                  "Grab keyboard only while window is focused"),
     ARGPARSE_s_u('W', "parent-wid", "Parent window ID (for positioning)"),
     ARGPARSE_s_s('c', "colors", "|STRING|Set custom colors for ncurses"),
     ARGPARSE_s_s('a', "ttyalert", "|STRING|Set the alert mode (none, beep or flash)"),
     ARGPARSE_end()
   };
   ARGPARSE_ARGS pargs = { &argc, &argv, 0 };
 
   set_strusage (my_strusage);
 
   pinentry_reset (1);
 
   while (arg_parse  (&pargs, opts))
     {
       switch (pargs.r_opt)
         {
         case 'd':
           pinentry.debug = 1;
           break;
         case 'g':
           pinentry.grab = 0;
           break;
 
 	case 'D':
           /* Note, this is currently not used because the GUI engine
              has already been initialized when parsing these options. */
 	  pinentry.display = strdup (pargs.r.ret_str);
 	  if (!pinentry.display)
 	    {
 #ifndef HAVE_W32CE_SYSTEM
 	      fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno));
 #endif
 	      exit (EXIT_FAILURE);
 	    }
 	  break;
 	case 'T':
 	  pinentry.ttyname = strdup (pargs.r.ret_str);
 	  if (!pinentry.ttyname)
 	    {
 #ifndef HAVE_W32CE_SYSTEM
 	      fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno));
 #endif
 	      exit (EXIT_FAILURE);
 	    }
 	  break;
 	case 'N':
 	  pinentry.ttytype_l = strdup (pargs.r.ret_str);
 	  if (!pinentry.ttytype_l)
 	    {
 #ifndef HAVE_W32CE_SYSTEM
 	      fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno));
 #endif
 	      exit (EXIT_FAILURE);
 	    }
 	  break;
 	case 'C':
 	  pinentry.lc_ctype = strdup (pargs.r.ret_str);
 	  if (!pinentry.lc_ctype)
 	    {
 #ifndef HAVE_W32CE_SYSTEM
 	      fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno));
 #endif
 	      exit (EXIT_FAILURE);
 	    }
 	  break;
 	case 'M':
 	  pinentry.lc_messages = strdup (pargs.r.ret_str);
 	  if (!pinentry.lc_messages)
 	    {
 #ifndef HAVE_W32CE_SYSTEM
 	      fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno));
 #endif
 	      exit (EXIT_FAILURE);
 	    }
 	  break;
 	case 'W':
 	  pinentry.parent_wid = pargs.r.ret_ulong;
 	  break;
 
 	case 'c':
           {
             char *tmpstr = pargs.r.ret_str;
 
             tmpstr = parse_color (tmpstr, &pinentry.color_fg,
                                   &pinentry.color_fg_bright);
             tmpstr = parse_color (tmpstr, &pinentry.color_bg, NULL);
             tmpstr = parse_color (tmpstr, &pinentry.color_so,
                                   &pinentry.color_so_bright);
           }
 	  break;
 
 	case 'o':
 	  pinentry.timeout = pargs.r.ret_int;
 	  break;
 
 	case 'a':
 	  pinentry.ttyalert = strdup (pargs.r.ret_str);
 	  if (!pinentry.ttyalert)
 	    {
 #ifndef HAVE_W32CE_SYSTEM
 	      fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno));
 #endif
 	      exit (EXIT_FAILURE);
 	    }
 	  break;
 
         default:
           pargs.err = ARGPARSE_PRINT_WARNING;
 	  break;
         }
     }
 
   if (!pinentry.display && remember_display)
     {
       pinentry.display = remember_display;
       remember_display = NULL;
     }
 }
 
 
 /* Set the optional flag used with getinfo. */
 void
 pinentry_set_flavor_flag (const char *string)
 {
   flavor_flag = string;
 }
 
 
 
 
 static gpg_error_t
 option_handler (assuan_context_t ctx, const char *key, const char *value)
 {
   (void)ctx;
 
   if (!strcmp (key, "no-grab") && !*value)
     pinentry.grab = 0;
   else if (!strcmp (key, "grab") && !*value)
     pinentry.grab = 1;
   else if (!strcmp (key, "debug-wait"))
     {
 #ifndef HAVE_W32_SYSTEM
       fprintf (stderr, "%s: waiting for debugger - my pid is %u ...\n",
 	       this_pgmname, (unsigned int) getpid());
       sleep (*value?atoi (value):5);
       fprintf (stderr, "%s: ... okay\n", this_pgmname);
 #endif
     }
   else if (!strcmp (key, "display"))
     {
       if (pinentry.display)
 	free (pinentry.display);
       pinentry.display = strdup (value);
       if (!pinentry.display)
 	return gpg_error_from_syserror ();
     }
   else if (!strcmp (key, "ttyname"))
     {
       if (pinentry.ttyname)
 	free (pinentry.ttyname);
       pinentry.ttyname = strdup (value);
       if (!pinentry.ttyname)
 	return gpg_error_from_syserror ();
     }
   else if (!strcmp (key, "ttytype"))
     {
       if (pinentry.ttytype_l)
 	free (pinentry.ttytype_l);
       pinentry.ttytype_l = strdup (value);
       if (!pinentry.ttytype_l)
 	return gpg_error_from_syserror ();
     }
   else if (!strcmp (key, "ttyalert"))
     {
       if (pinentry.ttyalert)
 	free (pinentry.ttyalert);
       pinentry.ttyalert = strdup (value);
       if (!pinentry.ttyalert)
 	return gpg_error_from_syserror ();
     }
   else if (!strcmp (key, "lc-ctype"))
     {
       if (pinentry.lc_ctype)
 	free (pinentry.lc_ctype);
       pinentry.lc_ctype = strdup (value);
       if (!pinentry.lc_ctype)
 	return gpg_error_from_syserror ();
     }
   else if (!strcmp (key, "lc-messages"))
     {
       if (pinentry.lc_messages)
 	free (pinentry.lc_messages);
       pinentry.lc_messages = strdup (value);
       if (!pinentry.lc_messages)
 	return gpg_error_from_syserror ();
     }
   else if (!strcmp (key, "owner"))
     {
       long along;
       char *endp;
 
       free (pinentry.owner_host);
       pinentry.owner_host = NULL;
       pinentry.owner_uid = -1;
       pinentry.owner_pid = 0;
 
       errno = 0;
       along = strtol (value, &endp, 10);
       if (along && !errno)
         {
           pinentry.owner_pid = (unsigned long)along;
           if (*endp)
             {
               errno = 0;
               if (*endp == '/') { /* we have a uid */
                 endp++;
                 along = strtol (endp, &endp, 10);
                 if (along >= 0 && !errno)
                   pinentry.owner_uid = (int)along;
               }
               if (endp)
                 {
                   while (*endp == ' ')
                     endp++;
                   if (*endp)
                     {
                       pinentry.owner_host = strdup (endp);
                       for (endp=pinentry.owner_host;
                            *endp && *endp != ' '; endp++)
                         ;
                       *endp = 0;
                     }
                 }
             }
         }
     }
   else if (!strcmp (key, "parent-wid"))
     {
       pinentry.parent_wid = atoi (value);
       /* FIXME: Use strtol and add some error handling.  */
     }
   else if (!strcmp (key, "touch-file"))
     {
       if (pinentry.touch_file)
         free (pinentry.touch_file);
       pinentry.touch_file = strdup (value);
       if (!pinentry.touch_file)
 	return gpg_error_from_syserror ();
     }
   else if (!strcmp (key, "default-ok"))
     {
       pinentry.default_ok = strdup (value);
       if (!pinentry.default_ok)
 	return gpg_error_from_syserror ();
     }
   else if (!strcmp (key, "default-cancel"))
     {
       pinentry.default_cancel = strdup (value);
       if (!pinentry.default_cancel)
 	return gpg_error_from_syserror ();
     }
   else if (!strcmp (key, "default-prompt"))
     {
       pinentry.default_prompt = strdup (value);
       if (!pinentry.default_prompt)
 	return gpg_error_from_syserror ();
     }
   else if (!strcmp (key, "default-pwmngr"))
     {
       pinentry.default_pwmngr = strdup (value);
       if (!pinentry.default_pwmngr)
 	return gpg_error_from_syserror ();
     }
   else if (!strcmp (key, "default-cf-visi"))
     {
       pinentry.default_cf_visi = strdup (value);
       if (!pinentry.default_cf_visi)
 	return gpg_error_from_syserror ();
     }
   else if (!strcmp (key, "default-tt-visi"))
     {
       pinentry.default_tt_visi = strdup (value);
       if (!pinentry.default_tt_visi)
 	return gpg_error_from_syserror ();
     }
   else if (!strcmp (key, "default-tt-hide"))
     {
       pinentry.default_tt_hide = strdup (value);
       if (!pinentry.default_tt_hide)
 	return gpg_error_from_syserror ();
     }
   else if (!strcmp (key, "default-capshint"))
     {
       pinentry.default_capshint = strdup (value);
       if (!pinentry.default_capshint)
 	return gpg_error_from_syserror ();
     }
   else if (!strcmp (key, "allow-external-password-cache") && !*value)
     {
       pinentry.allow_external_password_cache = 1;
       pinentry.tried_password_cache = 0;
     }
   else if (!strcmp (key, "allow-emacs-prompt") && !*value)
     {
 #ifdef INSIDE_EMACS
       pinentry_enable_emacs_cmd_handler ();
 #endif
     }
   else if (!strcmp (key, "invisible-char"))
     {
       if (pinentry.invisible_char)
         free (pinentry.invisible_char);
       pinentry.invisible_char = strdup (value);
       if (!pinentry.invisible_char)
 	return gpg_error_from_syserror ();
     }
   else if (!strcmp (key, "formatted-passphrase") && !*value)
     {
       pinentry.formatted_passphrase = 1;
     }
   else if (!strcmp (key, "formatted-passphrase-hint"))
     {
       if (pinentry.formatted_passphrase_hint)
         free (pinentry.formatted_passphrase_hint);
       pinentry.formatted_passphrase_hint = strdup (value);
       if (!pinentry.formatted_passphrase_hint)
 	return gpg_error_from_syserror ();
       do_unescape_inplace(pinentry.formatted_passphrase_hint);
     }
   else if (!strcmp (key, "constraints-enforce") && !*value)
     pinentry.constraints_enforce = 1;
   else if (!strcmp (key, "constraints-hint-short"))
     {
       if (pinentry.constraints_hint_short)
         free (pinentry.constraints_hint_short);
       pinentry.constraints_hint_short = strdup (value);
       if (!pinentry.constraints_hint_short)
 	return gpg_error_from_syserror ();
       do_unescape_inplace(pinentry.constraints_hint_short);
     }
   else if (!strcmp (key, "constraints-hint-long"))
     {
       if (pinentry.constraints_hint_long)
         free (pinentry.constraints_hint_long);
       pinentry.constraints_hint_long = strdup (value);
       if (!pinentry.constraints_hint_long)
 	return gpg_error_from_syserror ();
       do_unescape_inplace(pinentry.constraints_hint_long);
     }
   else if (!strcmp (key, "constraints-error-title"))
     {
       if (pinentry.constraints_error_title)
         free (pinentry.constraints_error_title);
       pinentry.constraints_error_title = strdup (value);
       if (!pinentry.constraints_error_title)
 	return gpg_error_from_syserror ();
       do_unescape_inplace(pinentry.constraints_error_title);
     }
   else
     return gpg_error (GPG_ERR_UNKNOWN_OPTION);
   return 0;
 }
 
 
 /* Note, that it is sufficient to allocate the target string D as
    long as the source string S, i.e.: strlen(s)+1; */
 static void
 strcpy_escaped (char *d, const char *s)
 {
   while (*s)
     {
       if (*s == '%' && s[1] && s[2])
         {
           s++;
           *d++ = xtoi_2 ( s);
           s += 2;
         }
       else
         *d++ = *s++;
     }
   *d = 0;
 }
 
 
 static void
 write_status_error (assuan_context_t ctx, pinentry_t pe)
 {
   char buf[500];
   const char *pgm;
 
   pgm = strchr (this_pgmname, '-');
   if (pgm && pgm[1])
     pgm++;
   else
     pgm = this_pgmname;
 
   snprintf (buf, sizeof buf, "%s.%s %d %s",
             pgm,
             pe->specific_err_loc? pe->specific_err_loc : "?",
             pe->specific_err,
             pe->specific_err_info? pe->specific_err_info : "");
   assuan_write_status (ctx, "ERROR", buf);
 }
 
 
 static gpg_error_t
 cmd_setdesc (assuan_context_t ctx, char *line)
 {
   char *newd;
 
   (void)ctx;
 
   newd = malloc (strlen (line) + 1);
   if (!newd)
     return gpg_error_from_syserror ();
 
   strcpy_escaped (newd, line);
   if (pinentry.description)
     free (pinentry.description);
   pinentry.description = newd;
   return 0;
 }
 
 
 static gpg_error_t
 cmd_setprompt (assuan_context_t ctx, char *line)
 {
   char *newp;
 
   (void)ctx;
 
   newp = malloc (strlen (line) + 1);
   if (!newp)
     return gpg_error_from_syserror ();
 
   strcpy_escaped (newp, line);
   if (pinentry.prompt)
     free (pinentry.prompt);
   pinentry.prompt = newp;
   return 0;
 }
 
 
 /* The data provided at LINE may be used by pinentry implementations
    to identify a key for caching strategies of its own.  The empty
    string and --clear mean that the key does not have a stable
    identifier.  */
 static gpg_error_t
 cmd_setkeyinfo (assuan_context_t ctx, char *line)
 {
   (void)ctx;
 
   if (pinentry.keyinfo)
     free (pinentry.keyinfo);
 
   if (*line && strcmp(line, "--clear") != 0)
     pinentry.keyinfo = strdup (line);
   else
     pinentry.keyinfo = NULL;
 
   return 0;
 }
 
 
 static gpg_error_t
 cmd_setrepeat (assuan_context_t ctx, char *line)
 {
   char *p;
 
   (void)ctx;
 
   p = malloc (strlen (line) + 1);
   if (!p)
     return gpg_error_from_syserror ();
 
   strcpy_escaped (p, line);
   free (pinentry.repeat_passphrase);
   pinentry.repeat_passphrase = p;
   return 0;
 }
 
 
 static gpg_error_t
 cmd_setrepeaterror (assuan_context_t ctx, char *line)
 {
   char *p;
 
   (void)ctx;
 
   p = malloc (strlen (line) + 1);
   if (!p)
     return gpg_error_from_syserror ();
 
   strcpy_escaped (p, line);
   free (pinentry.repeat_error_string);
   pinentry.repeat_error_string = p;
   return 0;
 }
 
 
 static gpg_error_t
 cmd_seterror (assuan_context_t ctx, char *line)
 {
   char *newe;
 
   (void)ctx;
 
   newe = malloc (strlen (line) + 1);
   if (!newe)
     return gpg_error_from_syserror ();
 
   strcpy_escaped (newe, line);
   if (pinentry.error)
     free (pinentry.error);
   pinentry.error = newe;
   return 0;
 }
 
 
 static gpg_error_t
 cmd_setok (assuan_context_t ctx, char *line)
 {
   char *newo;
 
   (void)ctx;
 
   newo = malloc (strlen (line) + 1);
   if (!newo)
     return gpg_error_from_syserror ();
 
   strcpy_escaped (newo, line);
   if (pinentry.ok)
     free (pinentry.ok);
   pinentry.ok = newo;
   return 0;
 }
 
 
 static gpg_error_t
 cmd_setnotok (assuan_context_t ctx, char *line)
 {
   char *newo;
 
   (void)ctx;
 
   newo = malloc (strlen (line) + 1);
   if (!newo)
     return gpg_error_from_syserror ();
 
   strcpy_escaped (newo, line);
   if (pinentry.notok)
     free (pinentry.notok);
   pinentry.notok = newo;
   return 0;
 }
 
 
 static gpg_error_t
 cmd_setcancel (assuan_context_t ctx, char *line)
 {
   char *newc;
 
   (void)ctx;
 
   newc = malloc (strlen (line) + 1);
   if (!newc)
     return gpg_error_from_syserror ();
 
   strcpy_escaped (newc, line);
   if (pinentry.cancel)
     free (pinentry.cancel);
   pinentry.cancel = newc;
   return 0;
 }
 
 
 static gpg_error_t
 cmd_settimeout (assuan_context_t ctx, char *line)
 {
   (void)ctx;
 
   if (line && *line)
     pinentry.timeout = atoi (line);
 
   return 0;
 }
 
 static gpg_error_t
 cmd_settitle (assuan_context_t ctx, char *line)
 {
   char *newt;
 
   (void)ctx;
 
   newt = malloc (strlen (line) + 1);
   if (!newt)
     return gpg_error_from_syserror ();
 
   strcpy_escaped (newt, line);
   if (pinentry.title)
     free (pinentry.title);
   pinentry.title = newt;
   return 0;
 }
 
 static gpg_error_t
 cmd_setqualitybar (assuan_context_t ctx, char *line)
 {
   char *newval;
 
   (void)ctx;
 
   if (!*line)
     line = "Quality:";
 
   newval = malloc (strlen (line) + 1);
   if (!newval)
     return gpg_error_from_syserror ();
 
   strcpy_escaped (newval, line);
   if (pinentry.quality_bar)
     free (pinentry.quality_bar);
   pinentry.quality_bar = newval;
   return 0;
 }
 
 /* Set the tooltip to be used for a quality bar.  */
 static gpg_error_t
 cmd_setqualitybar_tt (assuan_context_t ctx, char *line)
 {
   char *newval;
 
   (void)ctx;
 
   if (*line)
     {
       newval = malloc (strlen (line) + 1);
       if (!newval)
         return gpg_error_from_syserror ();
 
       strcpy_escaped (newval, line);
     }
   else
     newval = NULL;
   if (pinentry.quality_bar_tt)
     free (pinentry.quality_bar_tt);
   pinentry.quality_bar_tt = newval;
   return 0;
 }
 
 /* Set the tooltip to be used for a generate action.  */
 static gpg_error_t
 cmd_setgenpin_tt (assuan_context_t ctx, char *line)
 {
   char *newval;
 
   (void)ctx;
 
   if (*line)
     {
       newval = malloc (strlen (line) + 1);
       if (!newval)
         return gpg_error_from_syserror ();
 
       strcpy_escaped (newval, line);
     }
   else
     newval = NULL;
   if (pinentry.genpin_tt)
     free (pinentry.genpin_tt);
   pinentry.genpin_tt = newval;
   return 0;
 }
 
 /* Set the label to be used for a generate action.  */
 static gpg_error_t
 cmd_setgenpin_label (assuan_context_t ctx, char *line)
 {
   char *newval;
 
   (void)ctx;
 
   if (*line)
     {
       newval = malloc (strlen (line) + 1);
       if (!newval)
         return gpg_error_from_syserror ();
 
       strcpy_escaped (newval, line);
     }
   else
     newval = NULL;
   if (pinentry.genpin_label)
     free (pinentry.genpin_label);
   pinentry.genpin_label = newval;
   return 0;
 }
 
 static gpg_error_t
 cmd_getpin (assuan_context_t ctx, char *line)
 {
   int result;
   int set_prompt = 0;
   int just_read_password_from_cache = 0;
 
   (void)line;
 
   pinentry_setbuffer_init (&pinentry);
   if (!pinentry.pin)
     return gpg_error (GPG_ERR_ENOMEM);
 
+  pinentry.confirm = 0;
+
   /* Try reading from the password cache.  */
   if (/* If repeat passphrase is set, then we don't want to read from
 	 the cache.  */
       ! pinentry.repeat_passphrase
       /* Are we allowed to read from the cache?  */
       && pinentry.allow_external_password_cache
       && pinentry.keyinfo
       /* Only read from the cache if we haven't already tried it.  */
       && ! pinentry.tried_password_cache
       /* If the last read resulted in an error, then don't read from
 	 the cache.  */
       && ! pinentry.error)
     {
       char *password;
       int give_up_on_password_store = 0;
 
       pinentry.tried_password_cache = 1;
 
       password = password_cache_lookup (pinentry.keyinfo, &give_up_on_password_store);
       if (give_up_on_password_store)
 	pinentry.allow_external_password_cache = 0;
 
       if (password)
 	/* There is a cached password.  Try it.  */
 	{
 	  int len = strlen(password) + 1;
 	  if (len > pinentry.pin_len)
 	    len = pinentry.pin_len;
 
 	  memcpy (pinentry.pin, password, len);
 	  pinentry.pin[len] = '\0';
 
 	  secmem_free (password);
 
 	  pinentry.pin_from_cache = 1;
 
 	  assuan_write_status (ctx, "PASSWORD_FROM_CACHE", "");
 
 	  /* Result is the length of the password not including the
 	     NUL terminator.  */
 	  result = len - 1;
 
 	  just_read_password_from_cache = 1;
 
 	  goto out;
 	}
     }
 
   /* The password was not cached (or we are not allowed to / cannot
      use the cache).  Prompt the user.  */
   pinentry.pin_from_cache = 0;
 
   if (!pinentry.prompt)
     {
       pinentry.prompt = pinentry.default_prompt?pinentry.default_prompt:"PIN:";
       set_prompt = 1;
     }
   pinentry.locale_err = 0;
   pinentry.specific_err = 0;
   pinentry.specific_err_loc = NULL;
   free (pinentry.specific_err_info);
   pinentry.specific_err_info = NULL;
   pinentry.close_button = 0;
   pinentry.repeat_okay = 0;
   pinentry.one_button = 0;
   pinentry.ctx_assuan = ctx;
   result = (*pinentry_cmd_handler) (&pinentry);
   pinentry.ctx_assuan = NULL;
   if (pinentry.error)
     {
       free (pinentry.error);
       pinentry.error = NULL;
     }
   if (pinentry.repeat_passphrase)
     {
       free (pinentry.repeat_passphrase);
       pinentry.repeat_passphrase = NULL;
     }
   if (set_prompt)
     pinentry.prompt = NULL;
 
   pinentry.quality_bar = 0;  /* Reset it after the command.  */
 
   if (pinentry.close_button)
     assuan_write_status (ctx, "BUTTON_INFO", "close");
 
   if (result < 0)
     {
       pinentry_setbuffer_clear (&pinentry);
       if (pinentry.specific_err)
         {
           write_status_error (ctx, &pinentry);
 
           if (gpg_err_code (pinentry.specific_err) == GPG_ERR_FULLY_CANCELED)
             assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1);
 
           return pinentry.specific_err;
         }
       return (pinentry.locale_err
 	      ? gpg_error (GPG_ERR_LOCALE_PROBLEM)
 	      : gpg_error (GPG_ERR_CANCELED));
     }
 
  out:
   if (result)
     {
       if (pinentry.repeat_okay)
         assuan_write_status (ctx, "PIN_REPEATED", "");
       assuan_begin_confidential (ctx);
       result = assuan_send_data (ctx, pinentry.pin, strlen(pinentry.pin));
       if (!result)
 	result = assuan_send_data (ctx, NULL, 0);
       assuan_end_confidential (ctx);
 
       if (/* GPG Agent says it's okay.  */
 	  pinentry.allow_external_password_cache && pinentry.keyinfo
 	  /* We didn't just read it from the cache.  */
 	  && ! just_read_password_from_cache
 	  /* And the user said it's okay.  */
 	  && pinentry.may_cache_password)
 	/* Cache the password.  */
 	password_cache_save (pinentry.keyinfo, pinentry.pin);
     }
 
   pinentry_setbuffer_clear (&pinentry);
 
   return result;
 }
 
 
 /* Note that the option --one-button is a hack to allow the use of old
    pinentries while the caller is ignoring the result.  Given that
    options have never been used or flagged as an error the new option
    is an easy way to enable the messsage mode while not requiring to
    update pinentry or to have the caller test for the message
    command.  New applications which are free to require an updated
    pinentry should use MESSAGE instead. */
 static gpg_error_t
 cmd_confirm (assuan_context_t ctx, char *line)
 {
   int result;
 
   pinentry.one_button = !!strstr (line, "--one-button");
   pinentry.quality_bar = 0;
   pinentry.close_button = 0;
   pinentry.locale_err = 0;
   pinentry.specific_err = 0;
   pinentry.specific_err_loc = NULL;
   free (pinentry.specific_err_info);
   pinentry.specific_err_info = NULL;
   pinentry.canceled = 0;
+  pinentry.confirm = 1;
   pinentry_setbuffer_clear (&pinentry);
   result = (*pinentry_cmd_handler) (&pinentry);
   if (pinentry.error)
     {
       free (pinentry.error);
       pinentry.error = NULL;
     }
 
   if (pinentry.close_button)
     assuan_write_status (ctx, "BUTTON_INFO", "close");
 
   if (result > 0)
     return 0; /* OK */
 
   if (pinentry.specific_err)
     {
       write_status_error (ctx, &pinentry);
 
       if (gpg_err_code (pinentry.specific_err) == GPG_ERR_FULLY_CANCELED)
         assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1);
 
       return pinentry.specific_err;
     }
 
   if (pinentry.locale_err)
     return gpg_error (GPG_ERR_LOCALE_PROBLEM);
 
   if (pinentry.one_button)
     return 0; /* OK */
 
   if (pinentry.canceled)
     return gpg_error (GPG_ERR_CANCELED);
   return gpg_error (GPG_ERR_NOT_CONFIRMED);
 }
 
 
 static gpg_error_t
 cmd_message (assuan_context_t ctx, char *line)
 {
   (void)line;
 
   return cmd_confirm (ctx, "--one-button");
 }
 
 
 /* Return a staically allocated string with information on the mode,
  * uid, and gid of DEVICE.  On error "?" is returned if DEVICE is
  * NULL, "-" is returned.  */
 static const char *
 device_stat_string (const char *device)
 {
 #ifdef HAVE_STAT
   static char buf[40];
   struct stat st;
 
   if (!device || !*device)
     return "-";
 
   if (stat (device, &st))
     return "?";  /* Error */
   snprintf (buf, sizeof buf, "%lo/%lu/%lu",
             (unsigned long)st.st_mode,
             (unsigned long)st.st_uid,
             (unsigned long)st.st_gid);
   return buf;
 #else
   return "-";
 #endif
 }
 
 
 /* GETINFO <what>
 
    Multipurpose function to return a variety of information.
    Supported values for WHAT are:
 
      version     - Return the version of the program.
      pid         - Return the process id of the server.
      flavor      - Return information about the used pinentry flavor
      ttyinfo     - Return DISPLAY, ttyinfo and an emacs pinentry status
  */
 static gpg_error_t
 cmd_getinfo (assuan_context_t ctx, char *line)
 {
   int rc;
   const char *s;
   char buffer[150];
 
   if (!strcmp (line, "version"))
     {
       s = VERSION;
       rc = assuan_send_data (ctx, s, strlen (s));
     }
   else if (!strcmp (line, "pid"))
     {
 
       snprintf (buffer, sizeof buffer, "%lu", (unsigned long)getpid ());
       rc = assuan_send_data (ctx, buffer, strlen (buffer));
     }
   else if (!strcmp (line, "flavor"))
     {
       if (!strncmp (this_pgmname, "pinentry-", 9) && this_pgmname[9])
         s = this_pgmname + 9;
       else
         s = this_pgmname;
 
       snprintf (buffer, sizeof buffer, "%s%s%s",
                 s,
                 flavor_flag? ":":"",
                 flavor_flag? flavor_flag : "");
       rc = assuan_send_data (ctx, buffer, strlen (buffer));
       /* if (!rc) */
       /*   rc = assuan_write_status (ctx, "FEATURES", "tabbing foo bar"); */
     }
   else if (!strcmp (line, "ttyinfo"))
     {
       char emacs_status[10];
 #ifdef INSIDE_EMACS
       snprintf (emacs_status, sizeof emacs_status,
                 "%d", pinentry_emacs_status ());
 #else
       strcpy (emacs_status, "-");
 #endif
       snprintf (buffer, sizeof buffer, "%s %s %s %s %lu/%lu %s",
                 pinentry.ttyname? pinentry.ttyname : "-",
                 pinentry.ttytype_l? pinentry.ttytype_l : "-",
                 pinentry.display? pinentry.display : "-",
                 device_stat_string (pinentry.ttyname),
 #ifdef HAVE_DOSISH_SYSTEM
                 0l, 0l,
 #else
                 (unsigned long)geteuid (), (unsigned long)getegid (),
 #endif
                 emacs_status
                 );
       rc = assuan_send_data (ctx, buffer, strlen (buffer));
     }
   else
     rc = gpg_error (GPG_ERR_ASS_PARAMETER);
   return rc;
 }
 
 /* CLEARPASSPHRASE <cacheid>
 
    Clear the cache passphrase associated with the key identified by
    cacheid.
  */
 static gpg_error_t
 cmd_clear_passphrase (assuan_context_t ctx, char *line)
 {
   (void)ctx;
 
   if (! line)
     return gpg_error (GPG_ERR_ASS_INV_VALUE);
 
   /* Remove leading and trailing white space.  */
   while (*line == ' ')
     line ++;
   while (line[strlen (line) - 1] == ' ')
     line[strlen (line) - 1] = 0;
 
   switch (password_cache_clear (line))
     {
     case 1: return 0;
     case 0: return gpg_error (GPG_ERR_ASS_INV_VALUE);
     default: return gpg_error (GPG_ERR_ASS_GENERAL);
     }
 }
 
 /* Tell the assuan library about our commands.  */
 static gpg_error_t
 register_commands (assuan_context_t ctx)
 {
   static struct
   {
     const char *name;
     gpg_error_t (*handler) (assuan_context_t, char *line);
   } table[] =
     {
       { "SETDESC",    cmd_setdesc },
       { "SETPROMPT",  cmd_setprompt },
       { "SETKEYINFO", cmd_setkeyinfo },
       { "SETREPEAT",  cmd_setrepeat },
       { "SETREPEATERROR", cmd_setrepeaterror },
       { "SETERROR",   cmd_seterror },
       { "SETOK",      cmd_setok },
       { "SETNOTOK",   cmd_setnotok },
       { "SETCANCEL",  cmd_setcancel },
       { "GETPIN",     cmd_getpin },
       { "CONFIRM",    cmd_confirm },
       { "MESSAGE",    cmd_message },
       { "SETQUALITYBAR", cmd_setqualitybar },
       { "SETQUALITYBAR_TT", cmd_setqualitybar_tt },
       { "SETGENPIN",    cmd_setgenpin_label },
       { "SETGENPIN_TT", cmd_setgenpin_tt },
       { "GETINFO",    cmd_getinfo },
       { "SETTITLE",   cmd_settitle },
       { "SETTIMEOUT", cmd_settimeout },
       { "CLEARPASSPHRASE", cmd_clear_passphrase },
       { NULL }
     };
   int i, j;
   gpg_error_t rc;
 
   for (i = j = 0; table[i].name; i++)
     {
       rc = assuan_register_command (ctx, table[i].name, table[i].handler, NULL);
       if (rc)
         return rc;
     }
   return 0;
 }
 
 
 int
 pinentry_loop2 (int infd, int outfd)
 {
   gpg_error_t rc;
   assuan_fd_t filedes[2];
   assuan_context_t ctx;
 
   /* Extra check to make sure we have dropped privs. */
 #ifndef HAVE_DOSISH_SYSTEM
   if (getuid() != geteuid())
     abort ();
 #endif
 
   rc = assuan_new (&ctx);
   if (rc)
     {
       fprintf (stderr, "server context creation failed: %s\n",
 	       gpg_strerror (rc));
       return -1;
     }
 
   /* For now we use a simple pipe based server so that we can work
      from scripts.  We will later add options to run as a daemon and
      wait for requests on a Unix domain socket.  */
   filedes[0] = assuan_fdopen (infd);
   filedes[1] = assuan_fdopen (outfd);
   rc = assuan_init_pipe_server (ctx, filedes);
   if (rc)
     {
       fprintf (stderr, "%s: failed to initialize the server: %s\n",
                this_pgmname, gpg_strerror (rc));
       return -1;
     }
   rc = register_commands (ctx);
   if (rc)
     {
       fprintf (stderr, "%s: failed to the register commands with Assuan: %s\n",
                this_pgmname, gpg_strerror (rc));
       return -1;
     }
 
   assuan_register_option_handler (ctx, option_handler);
 #if 0
   assuan_set_log_stream (ctx, stderr);
 #endif
   assuan_register_reset_notify (ctx, pinentry_assuan_reset_handler);
 
   for (;;)
     {
       rc = assuan_accept (ctx);
       if (rc == -1)
           break;
       else if (rc)
         {
           fprintf (stderr, "%s: Assuan accept problem: %s\n",
                    this_pgmname, gpg_strerror (rc));
           break;
         }
 
       rc = assuan_process (ctx);
       if (rc)
         {
           fprintf (stderr, "%s: Assuan processing failed: %s\n",
                    this_pgmname, gpg_strerror (rc));
           continue;
         }
     }
 
   assuan_release (ctx);
   return 0;
 }
 
 
 /* Start the pinentry event loop.  The program will start to process
    Assuan commands until it is finished or an error occurs.  If an
    error occurs, -1 is returned.  Otherwise, 0 is returned.  */
 int
 pinentry_loop (void)
 {
   return pinentry_loop2 (STDIN_FILENO, STDOUT_FILENO);
 }
diff --git a/pinentry/pinentry.h b/pinentry/pinentry.h
index b97f069..3e95b84 100644
--- a/pinentry/pinentry.h
+++ b/pinentry/pinentry.h
@@ -1,364 +1,367 @@
 /* pinentry.h - The interface for the PIN entry support library.
  * Copyright (C) 2002, 2003, 2010, 2015, 2021 g10 Code GmbH
  *
  * This file is part of PINENTRY.
  *
  * PINENTRY is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
  * (at your option) any later version.
  *
  * PINENTRY is distributed in the hope that it will be useful, but
  * WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, see <https://www.gnu.org/licenses/>.
  * SPDX-License-Identifier: GPL-2.0+
  */
 
 #ifndef PINENTRY_H
 #define PINENTRY_H
 
 #ifdef __cplusplus
 extern "C" {
 #if 0
 }
 #endif
 #endif
 
 typedef enum {
   PINENTRY_COLOR_NONE, PINENTRY_COLOR_DEFAULT,
   PINENTRY_COLOR_BLACK, PINENTRY_COLOR_RED,
   PINENTRY_COLOR_GREEN, PINENTRY_COLOR_YELLOW,
   PINENTRY_COLOR_BLUE, PINENTRY_COLOR_MAGENTA,
   PINENTRY_COLOR_CYAN, PINENTRY_COLOR_WHITE
 } pinentry_color_t;
 
 struct pinentry
 {
   /* The window title, or NULL.  (Assuan: "SETTITLE TITLE".)  */
   char *title;
   /* The description to display, or NULL.  (Assuan: "SETDESC
      DESC".) */
   char *description;
   /* The error message to display, or NULL.  (Assuan: "SETERROR
      MESSAGE".) */
   char *error;
   /* The prompt to display, or NULL.  (Assuan: "SETPROMPT
      prompt".)  */
   char *prompt;
   /* The OK button text to display, or NULL.  (Assuan: "SETOK
      OK".)  */
   char *ok;
   /* The Not-OK button text to display, or NULL.  This is the text for
      the alternative option shown by the third button.  (Assuan:
      "SETNOTOK NOTOK".)  */
   char *notok;
   /* The Cancel button text to display, or NULL.  (Assuan: "SETCANCEL
      CANCEL".)  */
   char *cancel;
 
   /* The buffer to store the secret into.  */
   char *pin;
   /* The length of the buffer.  */
   int pin_len;
   /* Whether the pin was read from an external cache (1) or entered by
      the user (0). */
   int pin_from_cache;
 
   /* The name of the X display to use if X is available and supported.
      (Assuan: "OPTION display DISPLAY".)  */
   char *display;
   /* The name of the terminal node to open if X not available or
      supported.  (Assuan: "OPTION ttyname TTYNAME".)  */
   char *ttyname;
   /* The type of the terminal.  (Assuan: "OPTION ttytype TTYTYPE".)  */
   char *ttytype_l;
   /* Set the alert mode (none, beep or flash).  */
   char *ttyalert;
   /* The LC_CTYPE value for the terminal.  (Assuan: "OPTION lc-ctype
      LC_CTYPE".)  */
   char *lc_ctype;
   /* The LC_MESSAGES value for the terminal.  (Assuan: "OPTION
      lc-messages LC_MESSAGES".)  */
   char *lc_messages;
 
   /* True if debug mode is requested.  */
   int debug;
 
   /* The number of seconds before giving up while waiting for user input. */
   int timeout;
 
   /* True if caller should grab the keyboard.  (Assuan: "OPTION grab"
      or "OPTION no-grab".)  */
   int grab;
 
   /* The PID of the owner or 0 if not known.  The owner is the process
    * which actually triggered the the pinentry.  For example gpg.  */
   unsigned long owner_pid;
 
   /* The numeric uid (user ID) of the owner process or -1 if not
    * known. */
   int owner_uid;
 
   /* The malloced hostname of the owner or NULL.  */
   char *owner_host;
 
   /* The window ID of the parent window over which the pinentry window
      should be displayed.  (Assuan: "OPTION parent-wid WID".)  */
   int parent_wid;
 
   /* The name of an optional file which will be touched after a curses
      entry has been displayed.  (Assuan: "OPTION touch-file
      FILENAME".)  */
   char *touch_file;
 
   /* The frontend should set this to -1 if the user canceled the
      request, and to the length of the PIN stored in pin
      otherwise.  */
   int result;
 
   /* The frontend should set this if the NOTOK button was pressed.  */
   int canceled;
 
   /* The frontend should set this to true if an error with the local
      conversion occurred. */
   int locale_err;
 
   /* The frontend should set this to a gpg-error so that commands are
      able to return specific error codes.  This is an ugly hack due to
      the fact that pinentry_cmd_handler_t returns the length of the
      passphrase or a negative error code.  */
   int specific_err;
 
   /* The frontend may store a string with the error location here.  */
   const char *specific_err_loc;
 
   /* The frontend may store a malloced string here to emit an ERROR
    * status code with this extra info along with SPECIFIC_ERR.  */
   char *specific_err_info;
 
   /* The frontend should set this to true if the window close button
      has been used.  This flag is used in addition to a regular return
      value.  */
   int close_button;
 
   /* The caller should set this to true if only one button is
      required.  This is useful for notification dialogs where only a
      dismiss button is required. */
   int one_button;
 
+  /* Whether this is a CONFIRM pinentry. */
+  int confirm;
+
   /* If true a second prompt for the passphrase is shown and the user
      is expected to enter the same passphrase again.  Pinentry checks
      that both match.  (Assuan: "SETREPEAT".)  */
   char *repeat_passphrase;
 
   /* The string to show if a repeated passphrase does not match.
      (Assuan: "SETREPEATERROR ERROR".)  */
   char *repeat_error_string;
 
   /* Set to true if the passphrase has been entered a second time and
      matches the first passphrase.  */
   int repeat_okay;
 
   /* If this is not NULL, a passphrase quality indicator is shown.
      There will also be an inquiry back to the caller to get an
      indication of the quality for the passphrase entered so far.  The
      string is used as a label for the quality bar.  (Assuan:
      "SETQUALITYBAR LABEL".)  */
   char *quality_bar;
 
   /* The tooltip to be shown for the qualitybar.  Malloced or NULL.
      (Assuan: "SETQUALITYBAR_TT TOOLTIP".)  */
   char *quality_bar_tt;
 
   /* If this is not NULL, a generate action should be shown.
      There will be an inquiry back to the caller to get such a
      PIN. generate action.  Malloced or NULL.
      (Assuan: "SETGENPIN LABEL" .)  */
   char *genpin_label;
 
   /* The tooltip to be shown for the generate action.  Malloced or NULL.
      (Assuan: "SETGENPIN_TT TOOLTIP".)  */
   char *genpin_tt;
 
   /* Specifies whether passphrase formatting should be enabled.
      (Assuan: "OPTION formatted-passphrase")  */
   int formatted_passphrase;
 
   /* A hint to be shown near the passphrase input field if passphrase
      formatting is enabled.  Malloced or NULL.
      (Assuan: "OPTION formatted-passphrase-hint=HINT".)  */
   char *formatted_passphrase_hint;
 
   /* For the curses pinentry, the color of error messages.  */
   pinentry_color_t color_fg;
   int color_fg_bright;
   pinentry_color_t color_bg;
   pinentry_color_t color_so;
   int color_so_bright;
 
   /* Malloced and i18ned default strings or NULL.  These strings may
      include an underscore character to indicate an accelerator key.
      A double underscore represents a plain one.  */
   /* (Assuan: "OPTION default-ok OK").  */
   char *default_ok;
   /* (Assuan: "OPTION default-cancel CANCEL").  */
   char *default_cancel;
   /* (Assuan: "OPTION default-prompt PROMPT").  */
   char *default_prompt;
   /* (Assuan: "OPTION default-pwmngr
      SAVE_PASSWORD_WITH_PASSWORD_MANAGER?").  */
   char *default_pwmngr;
   /* (Assuan: "OPTION default-cf-visi
      Do you really want to make your passphrase visible?").  */
   char *default_cf_visi;
   /* (Assuan: "OPTION default-tt-visi
      Make passphrase visible?").  */
   char *default_tt_visi;
   /* (Assuan: "OPTION default-tt-hide
      Hide passphrase").  */
   char *default_tt_hide;
   /* (Assuan: "OPTION default-capshint
      Caps Lock is on").  */
   char *default_capshint;
 
   /* Whether we are allowed to read the password from an external
      cache.  (Assuan: "OPTION allow-external-password-cache")  */
   int allow_external_password_cache;
 
   /* We only try the cache once.  */
   int tried_password_cache;
 
   /* A stable identifier for the key.  (Assuan: "SETKEYINFO
      KEYINFO".)  */
   char *keyinfo;
 
   /* Whether we may cache the password (according to the user).  */
   int may_cache_password;
 
   /* NOTE: If you add any additional fields to this structure, be sure
      to update the initializer in pinentry/pinentry.c!!!  */
 
   /* For the quality indicator and genpin we need to do an inquiry.
      Thus we need to save the assuan ctx.  */
   void *ctx_assuan;
 
   /* An UTF-8 string with an invisible character used to override the
      default in some pinentries.  Only the first character is
      used.  */
   char *invisible_char;
 
   /* Whether the passphrase constraints are enforced by gpg-agent.
      (Assuan: "OPTION constraints-enforce")  */
   int constraints_enforce;
 
   /* A short translated hint for the user with the constraints for new
      passphrases to be displayed near the passphrase input field.
      Malloced or NULL.
      (Assuan: "OPTION constraints-hint-short=At least 8 characters".)  */
   char *constraints_hint_short;
 
   /* A longer translated hint for the user with the constraints for new
      passphrases to be displayed for example as tooltip.  Malloced or NULL.
      (Assuan: "OPTION constraints-hint-long=The passphrase must ...".)  */
   char *constraints_hint_long;
 
   /* A short translated title for an error dialog informing the user about
      unsatisfied passphrase constraints.  Malloced or NULL.
      (Assuan: "OPTION constraints-error-title=Passphrase Not Allowed".)  */
   char *constraints_error_title;
 
 };
 typedef struct pinentry *pinentry_t;
 
 
 /* The pinentry command handler type processes the pinentry request
    PIN.  If PIN->pin is zero, request a confirmation, otherwise a PIN
    entry.  On confirmation, the function should return TRUE if
    confirmed, and FALSE otherwise.  On PIN entry, the function should
    return -1 if an error occurred or the user cancelled the operation
    and 1 otherwise.  */
 typedef int (*pinentry_cmd_handler_t) (pinentry_t pin);
 
 /* Start the pinentry event loop.  The program will start to process
    Assuan commands until it is finished or an error occurs.  If an
    error occurs, -1 is returned and errno indicates the type of an
    error.  Otherwise, 0 is returned.  */
 int pinentry_loop (void);
 
 /* The same as above but allows to specify the i/o descriptors.
  * infd and outfd will be duplicated in this function so the caller
  * still has to close them if necessary.
  */
 int pinentry_loop2 (int infd, int outfd);
 
 const char *pinentry_get_pgmname (void);
 
 char *pinentry_get_title (pinentry_t pe);
 
 /* Run a quality inquiry for PASSPHRASE of LENGTH. */
 int pinentry_inq_quality (pinentry_t pin,
                           const char *passphrase, size_t length);
 
 /* Run a checkpin inquiry for PASSPHRASE of LENGTH.  Returns NULL, if the
    passphrase satisfies the constraints.  Otherwise, returns a malloced error
    string. */
 char *pinentry_inq_checkpin (pinentry_t pin,
                              const char *passphrase, size_t length);
 
 /* Run a genpin iquriry. Returns a malloced string or NULL */
 char *pinentry_inq_genpin (pinentry_t pin);
 
 /* Try to make room for at least LEN bytes for the pin in the pinentry
    PIN.  Returns new buffer on success and 0 on failure.  */
 char *pinentry_setbufferlen (pinentry_t pin, int len);
 
 /* Use the buffer at BUFFER for PIN->PIN.  BUFFER must be NULL or
    allocated using secmem_alloc.  LEN is the size of the buffer.  If
    it is unknown, but BUFFER is a NUL terminated string, you pass 0 to
    just use strlen(buffer)+1.  */
 void pinentry_setbuffer_use (pinentry_t pin, char *buffer, int len);
 
 /* Initialize the secure memory subsystem, drop privileges and
    return.  Must be called early.  */
 void pinentry_init (const char *pgmname);
 
 /* Return true if either DISPLAY is set or ARGV contains the string
    "--display". */
 int pinentry_have_display (int argc, char **argv);
 
 /* Parse the command line options.  May exit the program if only help
    or version output is requested.  */
 void pinentry_parse_opts (int argc, char *argv[]);
 
 /* Set the optional flag used with getinfo. */
 void pinentry_set_flavor_flag (const char *string);
 
 
 
 /* The caller must define this variable to process assuan commands.  */
 extern pinentry_cmd_handler_t pinentry_cmd_handler;
 
 
 
 
 
 #ifdef HAVE_W32_SYSTEM
 /* Windows declares sleep as obsolete, but provides a definition for
    _sleep but non for the still existing sleep.  */
 #define sleep(a) _sleep ((a))
 #endif /*HAVE_W32_SYSTEM*/
 
 
 
 #if 0
 {
 #endif
 #ifdef __cplusplus
 }
 #endif
 
 #endif	/* PINENTRY_H */