agent: Tell pinentry the hostname the agent is running on.
[gnupg.git] / agent / call-pinentry.c
index 9c350a6..9931665 100644 (file)
@@ -1,5 +1,6 @@
-/* call-pinentry.c - fork of the pinentry to query stuff from the user
- * Copyright (C) 2001, 2002, 2004, 2007 Free Software Foundation, Inc.
+/* call-pinentry.c - Spawn the pinentry to query stuff from the user
+ * Copyright (C) 2001, 2002, 2004, 2007, 2008,
+ *               2010  Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
@@ -14,7 +15,7 @@
  * 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/>.
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
  */
 
 #include <config.h>
 #include <unistd.h>
 #include <sys/stat.h>
 #ifndef HAVE_W32_SYSTEM
-#include <sys/wait.h>
+# include <sys/wait.h>
+# include <sys/types.h>
+# include <signal.h>
+# include <sys/utsname.h>
 #endif
-#include <pth.h>
-#include <assuan.h>
+#include <npth.h>
 
 #include "agent.h"
-#include "setenv.h"
+#include <assuan.h>
+#include "sysutils.h"
 #include "i18n.h"
 
 #ifdef _POSIX_OPEN_MAX
@@ -44,8 +48,8 @@
 
 
 /* Because access to the pinentry must be serialized (it is and shall
-   be a global mutual dialog) we should better timeout further
-   requests after some time.  2 minutes seem to be a reasonable
+   be a global mutually exclusive dialog) we better timeout pending
+   requests after some time.  1 minute seem to be a reasonable
    time. */
 #define LOCK_TIMEOUT  (1*60)
 
@@ -59,10 +63,10 @@ static assuan_context_t entry_ctx;
 static ctrl_t entry_owner;
 
 /* A mutex used to serialize access to the pinentry. */
-static pth_mutex_t entry_lock;
+static npth_mutex_t entry_lock;
 
 /* The thread ID of the popup working thread. */
-static pth_t  popup_tid;
+static npth_t  popup_tid;
 
 /* A flag used in communication between the popup working thread and
    its stop function. */
@@ -92,39 +96,20 @@ initialize_module_call_pinentry (void)
 
   if (!initialized)
     {
-      if (pth_mutex_init (&entry_lock))
+      if (npth_mutex_init (&entry_lock, NULL))
         initialized = 1;
     }
 }
 
 
 
-static void
-dump_mutex_state (pth_mutex_t *m)
-{
-#ifdef _W32_PTH_H
-  log_printf ("unknown under W32");
-#else
-  if (!(m->mx_state & PTH_MUTEX_INITIALIZED))
-    log_printf ("not_initialized");
-  else if (!(m->mx_state & PTH_MUTEX_LOCKED))
-    log_printf ("not_locked");
-  else
-    log_printf ("locked tid=0x%lx count=%lu", (long)m->mx_owner, m->mx_count);
-#endif
-}
-
-
 /* This function may be called to print infromation pertaining to the
    current state of this module to the log. */
 void
 agent_query_dump_state (void)
 {
-  log_info ("agent_query_dump_state: entry_lock=");
-  dump_mutex_state (&entry_lock);
-  log_printf ("\n");
   log_info ("agent_query_dump_state: entry_ctx=%p pid=%ld popup_tid=%p\n",
-            entry_ctx, (long)assuan_get_pid (entry_ctx), popup_tid);
+            entry_ctx, (long)assuan_get_pid (entry_ctx), (void*)popup_tid);
 }
 
 /* Called to make sure that a popup window owned by the current
@@ -143,19 +128,49 @@ agent_reset_query (ctrl_t ctrl)
    disconnect that pinentry - we do this after the unlock so that a
    stalled pinentry does not block other threads.  Fixme: We should
    have a timeout in Assuan for the disconnect operation. */
-static int 
-unlock_pinentry (int rc)
+static gpg_error_t
+unlock_pinentry (gpg_error_t rc)
 {
   assuan_context_t ctx = entry_ctx;
+  int err;
+
+  if (rc)
+    {
+      if (DBG_IPC)
+        log_debug ("error calling pinentry: %s <%s>\n",
+                   gpg_strerror (rc), gpg_strsource (rc));
+
+      /* Change the source of the error to pinentry so that the final
+         consumer of the error code knows that the problem is with
+         pinentry.  For backward compatibility we do not do that for
+         some common error codes.  */
+      switch (gpg_err_code (rc))
+        {
+        case GPG_ERR_NO_PIN_ENTRY:
+        case GPG_ERR_CANCELED:
+        case GPG_ERR_FULLY_CANCELED:
+        case GPG_ERR_ASS_UNKNOWN_INQUIRE:
+        case GPG_ERR_ASS_TOO_MUCH_DATA:
+        case GPG_ERR_NO_PASSPHRASE:
+        case GPG_ERR_BAD_PASSPHRASE:
+        case GPG_ERR_BAD_PIN:
+          break;
+
+        default:
+          rc = gpg_err_make (GPG_ERR_SOURCE_PINENTRY, gpg_err_code (rc));
+          break;
+        }
+    }
 
   entry_ctx = NULL;
-  if (!pth_mutex_release (&entry_lock))
+  err = npth_mutex_unlock (&entry_lock);
+  if (err)
     {
-      log_error ("failed to release the entry lock\n");
+      log_error ("failed to release the entry lock: %s\n", strerror (err));
       if (!rc)
-        rc = gpg_error (GPG_ERR_INTERNAL);
+        rc = gpg_error_from_errno (err);
     }
-  assuan_disconnect (ctx);
+  assuan_release (ctx);
   return rc;
 }
 
@@ -169,54 +184,93 @@ atfork_cb (void *opaque, int where)
 
   if (!where)
     {
+      int iterator = 0;
+      const char *name, *assname, *value;
+
       gcry_control (GCRYCTL_TERM_SECMEM);
-      if (ctrl->xauthority)
-        setenv ("XAUTHORITY", ctrl->xauthority, 1);
-      if (ctrl->pinentry_user_data)
-        setenv ("PINENTRY_USER_DATA", ctrl->pinentry_user_data, 1 );
+
+      while ((name = session_env_list_stdenvnames (&iterator, &assname)))
+        {
+          /* For all new envvars (!ASSNAME) and the two medium old
+             ones which do have an assuan name but are conveyed using
+             environment variables, update the environment of the
+             forked process.  */
+          if (!assname
+              || !strcmp (name, "XAUTHORITY")
+              || !strcmp (name, "PINENTRY_USER_DATA"))
+            {
+              value = session_env_getenv (ctrl->session_env, name);
+              if (value)
+                gnupg_setenv (name, value, 1);
+            }
+        }
+    }
+}
+
+
+static gpg_error_t
+getinfo_pid_cb (void *opaque, const void *buffer, size_t length)
+{
+  unsigned long *pid = opaque;
+  char pidbuf[50];
+
+  /* There is only the pid in the server's response.  */
+  if (length >= sizeof pidbuf)
+    length = sizeof pidbuf -1;
+  if (length)
+    {
+      strncpy (pidbuf, buffer, length);
+      pidbuf[length] = 0;
+      *pid = strtoul (pidbuf, NULL, 10);
     }
+  return 0;
 }
 
 
 /* Fork off the pin entry if this has not already been done.  Note,
-   that this function must always be used to aquire the lock for the
+   that this function must always be used to acquire the lock for the
    pinentry - we will serialize _all_ pinentry calls.
  */
-static int
+static gpg_error_t
 start_pinentry (ctrl_t ctrl)
 {
-  int rc;
+  int rc = 0;
+  const char *full_pgmname;
   const char *pgmname;
   assuan_context_t ctx;
   const char *argv[5];
-  int no_close_list[3];
+  assuan_fd_t no_close_list[3];
   int i;
-  pth_event_t evt;
   const char *tmpstr;
-
-  evt = pth_event (PTH_EVENT_TIME, pth_timeout (LOCK_TIMEOUT, 0));
-  if (!pth_mutex_acquire (&entry_lock, 0, evt))
+  unsigned long pinentry_pid;
+  const char *value;
+  struct timespec abstime;
+  char *flavor_version;
+  int err;
+
+  npth_clock_gettime (&abstime);
+  abstime.tv_sec += LOCK_TIMEOUT;
+  err = npth_mutex_timedlock (&entry_lock, &abstime);
+  if (err)
     {
-      if (pth_event_occurred (evt))
-        rc = gpg_error (GPG_ERR_TIMEOUT);
+      if (err == ETIMEDOUT)
+       rc = gpg_error (GPG_ERR_TIMEOUT);
       else
-        rc = gpg_error (GPG_ERR_INTERNAL);
-      pth_event_free (evt, PTH_FREE_THIS);
+       rc = gpg_error_from_errno (rc);
       log_error (_("failed to acquire the pinentry lock: %s\n"),
                  gpg_strerror (rc));
       return rc;
     }
-  pth_event_free (evt, PTH_FREE_THIS);
 
   entry_owner = ctrl;
 
   if (entry_ctx)
-    return 0; 
+    return 0;
 
   if (opt.verbose)
     log_info ("starting a new PIN Entry\n");
 
-#ifdef HAVE_W32_SYSTEM      
+#ifdef HAVE_W32_SYSTEM
   fflush (stdout);
   fflush (stderr);
 #endif
@@ -228,18 +282,18 @@ start_pinentry (ctrl_t ctrl)
       log_error ("error flushing pending output: %s\n", strerror (errno));
       /* At least Windows XP fails here with EBADF.  According to docs
          and Wine an fflush(NULL) is the same as _flushall.  However
-         the Wime implementaion does not flush stdin,stdout and stderr
-         - see above.  Lets try to ignore the error. */
+         the Wine implementaion does not flush stdin,stdout and stderr
+         - see above.  Let's try to ignore the error. */
 #ifndef HAVE_W32_SYSTEM
       return unlock_pinentry (tmperr);
 #endif
     }
 
-  if (!opt.pinentry_program || !*opt.pinentry_program)
-    opt.pinentry_program = gnupg_module_name (GNUPG_MODULE_NAME_PINENTRY);
-    pgmname = opt.pinentry_program;
-  if ( !(pgmname = strrchr (opt.pinentry_program, '/')))
-    pgmname = opt.pinentry_program;
+  full_pgmname = opt.pinentry_program;
+  if (!full_pgmname || !*full_pgmname)
+    full_pgmname = gnupg_module_name (GNUPG_MODULE_NAME_PINENTRY);
+  if ( !(pgmname = strrchr (full_pgmname, '/')))
+    pgmname = full_pgmname;
   else
     pgmname++;
 
@@ -247,69 +301,102 @@ start_pinentry (ctrl_t ctrl)
      the resource bundle.  For other systems we stick to the usual
      convention of supplying only the name of the program.  */
 #ifdef __APPLE__
-  argv[0] = opt.pinentry_program;
+  argv[0] = full_pgmname;
 #else /*!__APPLE__*/
   argv[0] = pgmname;
 #endif /*__APPLE__*/
 
-  if (ctrl->display && !opt.keep_display)
+  if (!opt.keep_display
+      && (value = session_env_getenv (ctrl->session_env, "DISPLAY")))
     {
       argv[1] = "--display";
-      argv[2] = ctrl->display;
+      argv[2] = value;
       argv[3] = NULL;
     }
   else
     argv[1] = NULL;
-  
+
   i=0;
   if (!opt.running_detached)
     {
       if (log_get_fd () != -1)
-        no_close_list[i++] = log_get_fd ();
-      no_close_list[i++] = fileno (stderr);
+        no_close_list[i++] = assuan_fd_from_posix_fd (log_get_fd ());
+      no_close_list[i++] = assuan_fd_from_posix_fd (fileno (stderr));
     }
-  no_close_list[i] = -1;
+  no_close_list[i] = ASSUAN_INVALID_FD;
+
+  rc = assuan_new (&ctx);
+  if (rc)
+    {
+      log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc));
+      return rc;
+    }
+  /* We don't want to log the pinentry communication to make the logs
+     easier to read.  We might want to add a new debug option to enable
+     pinentry logging.  */
+#ifdef ASSUAN_NO_LOGGING
+  assuan_set_flag (ctx, ASSUAN_NO_LOGGING, !opt.debug_pinentry);
+#endif
 
   /* Connect to the pinentry and perform initial handshaking.  Note
      that atfork is used to change the environment for pinentry.  We
      start the server in detached mode to suppress the console window
      under Windows.  */
-  rc = assuan_pipe_connect_ext (&ctx, opt.pinentry_program, argv,
-                                no_close_list, atfork_cb, ctrl, 128);
+  rc = assuan_pipe_connect (ctx, full_pgmname, argv,
+                           no_close_list, atfork_cb, ctrl,
+                           ASSUAN_PIPE_CONNECT_DETACHED);
   if (rc)
     {
-      log_error ("can't connect to the PIN entry module: %s\n",
-                 gpg_strerror (rc));
+      log_error ("can't connect to the PIN entry module '%s': %s\n",
+                 full_pgmname, gpg_strerror (rc));
+      assuan_release (ctx);
       return unlock_pinentry (gpg_error (GPG_ERR_NO_PIN_ENTRY));
     }
   entry_ctx = ctx;
 
-  if (DBG_ASSUAN)
+  if (DBG_IPC)
     log_debug ("connection to PIN entry established\n");
 
-  rc = assuan_transact (entry_ctx, 
+  value = session_env_getenv (ctrl->session_env, "PINENTRY_USER_DATA");
+  if (value != NULL)
+    {
+      char *optstr;
+      if (asprintf (&optstr, "OPTION pinentry-user-data=%s", value) < 0 )
+       return unlock_pinentry (out_of_core ());
+      rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
+                           NULL);
+      xfree (optstr);
+      if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION)
+        return unlock_pinentry (rc);
+    }
+
+  rc = assuan_transact (entry_ctx,
                         opt.no_grab? "OPTION no-grab":"OPTION grab",
                         NULL, NULL, NULL, NULL, NULL, NULL);
   if (rc)
     return unlock_pinentry (rc);
-  if (ctrl->ttyname)
+
+  value = session_env_getenv (ctrl->session_env, "GPG_TTY");
+  if (value)
     {
       char *optstr;
-      if (asprintf (&optstr, "OPTION ttyname=%s", ctrl->ttyname) < 0 )
+      if (asprintf (&optstr, "OPTION ttyname=%s", value) < 0 )
        return unlock_pinentry (out_of_core ());
       rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
                            NULL);
-      free (optstr);
+      xfree (optstr);
       if (rc)
        return unlock_pinentry (rc);
     }
-  if (ctrl->ttytype)
+  value = session_env_getenv (ctrl->session_env, "TERM");
+  if (value)
     {
       char *optstr;
-      if (asprintf (&optstr, "OPTION ttytype=%s", ctrl->ttytype) < 0 )
+      if (asprintf (&optstr, "OPTION ttytype=%s", value) < 0 )
        return unlock_pinentry (out_of_core ());
       rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
                            NULL);
+      xfree (optstr);
       if (rc)
        return unlock_pinentry (rc);
     }
@@ -320,6 +407,7 @@ start_pinentry (ctrl_t ctrl)
        return unlock_pinentry (out_of_core ());
       rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
                            NULL);
+      xfree (optstr);
       if (rc)
        return unlock_pinentry (rc);
     }
@@ -330,11 +418,107 @@ start_pinentry (ctrl_t ctrl)
        return unlock_pinentry (out_of_core ());
       rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
                            NULL);
+      xfree (optstr);
       if (rc)
        return unlock_pinentry (rc);
     }
 
-  
+
+  if (opt.allow_external_cache)
+    {
+      /* Indicate to the pinentry that it may read from an external cache.
+
+         It is essential that the pinentry respect this.  If the
+         cached password is not up to date and retry == 1, then, using
+         a version of GPG Agent that doesn't support this, won't issue
+         another pin request and the user won't get a chance to
+         correct the password.  */
+      rc = assuan_transact (entry_ctx, "OPTION allow-external-password-cache",
+                            NULL, NULL, NULL, NULL, NULL, NULL);
+      if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION)
+        return unlock_pinentry (rc);
+    }
+
+  if (opt.allow_emacs_pinentry)
+    {
+      /* Indicate to the pinentry that it may read passphrase through
+        Emacs minibuffer, if possible.  */
+      rc = assuan_transact (entry_ctx, "OPTION allow-emacs-prompt",
+                            NULL, NULL, NULL, NULL, NULL, NULL);
+      if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION)
+        return unlock_pinentry (rc);
+    }
+
+
+  {
+    /* Provide a few default strings for use by the pinentries.  This
+       may help a pinentry to avoid implementing localization code.  */
+    static struct { const char *key, *value; int what; } tbl[] = {
+      /* TRANSLATORS: These are labels for buttons etc used in
+         Pinentries.  An underscore indicates that the next letter
+         should be used as an accelerator.  Double the underscore for
+         a literal one.  The actual to be translated text starts after
+         the second vertical bar.  Note that gpg-agent has been set to
+         utf-8 so that the strings are in the expected encoding.  */
+      { "ok",     N_("|pinentry-label|_OK") },
+      { "cancel", N_("|pinentry-label|_Cancel") },
+      { "yes",    N_("|pinentry-label|_Yes") },
+      { "no",     N_("|pinentry-label|_No") },
+      { "prompt", N_("|pinentry-label|PIN:") },
+      { "pwmngr", N_("|pinentry-label|_Save in password manager"), 1 },
+      { "cf-visi",N_("Do you really want to make your "
+                     "passphrase visible on the screen?") },
+      { "tt-visi",N_("|pinentry-tt|Make passphrase visible") },
+      { "tt-hide",N_("|pinentry-tt|Hide passphrase") },
+      { NULL, NULL}
+    };
+    char *optstr;
+    int idx;
+    const char *s, *s2;
+
+    for (idx=0; tbl[idx].key; idx++)
+      {
+        if (!opt.allow_external_cache && tbl[idx].what == 1)
+          continue;  /* No need for it.  */
+        s = L_(tbl[idx].value);
+        if (*s == '|' && (s2=strchr (s+1,'|')))
+          s = s2+1;
+        if (asprintf (&optstr, "OPTION default-%s=%s", tbl[idx].key, s) < 0 )
+          return unlock_pinentry (out_of_core ());
+        assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
+                         NULL);
+        xfree (optstr);
+      }
+  }
+
+  /* Tell the pinentry that we would prefer that the given character
+     is used as the invisible character by the entry widget.  */
+  if (opt.pinentry_invisible_char)
+    {
+      char *optstr;
+      if ((optstr = xtryasprintf ("OPTION invisible-char=%s",
+                                  opt.pinentry_invisible_char)))
+        {
+          assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
+                           NULL);
+          /* We ignore errors because this is just a fancy thing and
+             older pinentries do not support this feature.  */
+          xfree (optstr);
+        }
+    }
+
+  if (opt.pinentry_timeout)
+    {
+      char *optstr;
+      if ((optstr = xtryasprintf ("SETTIMEOUT %lu", opt.pinentry_timeout)))
+        {
+          assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
+                           NULL);
+          /* We ignore errors because this is just a fancy thing.  */
+          xfree (optstr);
+        }
+    }
+
   /* Tell the pinentry the name of a file it shall touch after having
      messed with the tty.  This is optional and only supported by
      newer pinentries and thus we do no error checking. */
@@ -346,56 +530,136 @@ start_pinentry (ctrl_t ctrl)
   if (tmpstr)
     {
       char *optstr;
-      
+
       if (asprintf (&optstr, "OPTION touch-file=%s", tmpstr ) < 0 )
         ;
       else
         {
           assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
                            NULL);
-          free (optstr);
+          xfree (optstr);
+        }
+    }
+
+  /* Tell Pinentry about our client.  */
+  if (ctrl->client_pid)
+    {
+      char *optstr;
+      const char *nodename = "";
+
+#ifndef HAVE_W32_SYSTEM
+      struct utsname utsbuf;
+      if (!uname (&utsbuf))
+        nodename = utsbuf.nodename;
+#endif /*!HAVE_W32_SYSTEM*/
+
+      if ((optstr = xtryasprintf ("OPTION owner=%lu %s",
+                                  ctrl->client_pid, nodename)))
+        {
+          assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
+                           NULL);
+          /* We ignore errors because this is just a fancy thing and
+             older pinentries do not support this feature.  */
+          xfree (optstr);
         }
     }
 
+
+  /* Ask the pinentry for its version and flavor and store that as a
+   * string in MB.  This information is useful for helping users to
+   * figure out Pinentry problems.  */
+  {
+    membuf_t mb;
+
+    init_membuf (&mb, 256);
+    if (assuan_transact (entry_ctx, "GETINFO flavor",
+                         put_membuf_cb, &mb, NULL, NULL, NULL, NULL))
+      put_membuf_str (&mb, "unknown");
+    put_membuf_str (&mb, " ");
+    if (assuan_transact (entry_ctx, "GETINFO version",
+                         put_membuf_cb, &mb, NULL, NULL, NULL, NULL))
+      put_membuf_str (&mb, "unknown");
+    put_membuf_str (&mb, " ");
+    if (assuan_transact (entry_ctx, "GETINFO ttyinfo",
+                         put_membuf_cb, &mb, NULL, NULL, NULL, NULL))
+      put_membuf_str (&mb, "? ? ?");
+    put_membuf (&mb, "", 1);
+    flavor_version = get_membuf (&mb, NULL);
+  }
+
+
+  /* Now ask the Pinentry for its PID.  If the Pinentry is new enough
+     it will send the pid back and we will use an inquire to notify
+     our client.  The client may answer the inquiry either with END or
+     with CAN to cancel the pinentry. */
+  rc = assuan_transact (entry_ctx, "GETINFO pid",
+                        getinfo_pid_cb, &pinentry_pid,
+                        NULL, NULL, NULL, NULL);
+  if (rc)
+    {
+      log_info ("You may want to update to a newer pinentry\n");
+      rc = 0;
+    }
+  else if (!rc && (pid_t)pinentry_pid == (pid_t)(-1))
+    log_error ("pinentry did not return a PID\n");
+  else
+    {
+      rc = agent_inq_pinentry_launched (ctrl, pinentry_pid, flavor_version);
+      if (gpg_err_code (rc) == GPG_ERR_CANCELED
+          || gpg_err_code (rc) == GPG_ERR_FULLY_CANCELED)
+        return unlock_pinentry (gpg_err_make (GPG_ERR_SOURCE_DEFAULT,
+                                              gpg_err_code (rc)));
+      rc = 0;
+    }
+
+  xfree (flavor_version);
+
   return 0;
 }
 
-/* Returns True is the pinentry is currently active. If WAITSECONDS is
+
+/* Returns True if the pinentry is currently active. If WAITSECONDS is
    greater than zero the function will wait for this many seconds
    before returning.  */
 int
 pinentry_active_p (ctrl_t ctrl, int waitseconds)
 {
+  int err;
+  (void)ctrl;
+
   if (waitseconds > 0)
     {
-      pth_event_t evt;
+      struct timespec abstime;
       int rc;
 
-      evt = pth_event (PTH_EVENT_TIME, pth_timeout (waitseconds, 0));
-      if (!pth_mutex_acquire (&entry_lock, 0, evt))
+      npth_clock_gettime (&abstime);
+      abstime.tv_sec += waitseconds;
+      err = npth_mutex_timedlock (&entry_lock, &abstime);
+      if (err)
         {
-          if (pth_event_occurred (evt))
+          if (err == ETIMEDOUT)
             rc = gpg_error (GPG_ERR_TIMEOUT);
           else
             rc = gpg_error (GPG_ERR_INTERNAL);
-          pth_event_free (evt, PTH_FREE_THIS);
           return rc;
         }
-      pth_event_free (evt, PTH_FREE_THIS);
     }
   else
     {
-      if (!pth_mutex_acquire (&entry_lock, 1, NULL))
+      err = npth_mutex_trylock (&entry_lock);
+      if (err)
         return gpg_error (GPG_ERR_LOCKED);
     }
 
-  if (!pth_mutex_release (&entry_lock))
-    log_error ("failed to release the entry lock at %d\n", __LINE__);
+  err = npth_mutex_unlock (&entry_lock);
+  if (err)
+    log_error ("failed to release the entry lock at %d: %s\n", __LINE__,
+              strerror (errno));
   return 0;
 }
 
 
-static int
+static gpg_error_t
 getpin_cb (void *opaque, const void *buffer, size_t length)
 {
   struct entry_parm_s *parm = opaque;
@@ -422,13 +686,13 @@ all_digitsp( const char *s)
   for (; *s && *s >= '0' && *s <= '9'; s++)
     ;
   return !*s;
-}  
+}
 
 
 /* Return a new malloced string by unescaping the string S.  Escaping
    is percent escaping and '+'/space mapping.  A binary Nul will
    silently be replaced by a 0xFF.  Function returns NULL to indicate
-   an out of memory status.  PArsing stops at the end of the string or
+   an out of memory status.  Parsing stops at the end of the string or
    a white space character. */
 static char *
 unescape_passphrase_string (const unsigned char *s)
@@ -441,7 +705,7 @@ unescape_passphrase_string (const unsigned char *s)
   while (*s && !spacep (s))
     {
       if (*s == '%' && s[1] && s[2])
-        { 
+        {
           s++;
           *d = xtoi_2 (s);
           if (!*d)
@@ -457,7 +721,7 @@ unescape_passphrase_string (const unsigned char *s)
       else
         *d++ = *s++;
     }
-  *d = 0; 
+  *d = 0;
   return buffer;
 }
 
@@ -485,28 +749,25 @@ estimate_passphrase_quality (const char *pw)
 
 
 /* Handle the QUALITY inquiry. */
-static int
+static gpg_error_t
 inq_quality (void *opaque, const char *line)
 {
   assuan_context_t ctx = opaque;
+  const char *s;
   char *pin;
   int rc;
   int percent;
   char numbuf[20];
 
-  if (!strncmp (line, "QUALITY", 7) && (line[7] == ' ' || !line[7]))
+  if ((s = has_leading_keyword (line, "QUALITY")))
     {
-      line += 7;
-      while (*line == ' ')
-        line++;
-      
-      pin = unescape_passphrase_string (line);
+      pin = unescape_passphrase_string (s);
       if (!pin)
         rc = gpg_error_from_syserror ();
       else
         {
           percent = estimate_passphrase_quality (pin);
-          if (check_passphrase_constraints (NULL, pin, 1))
+          if (check_passphrase_constraints (NULL, pin, NULL))
             percent = -percent;
           snprintf (numbuf, sizeof numbuf, "%d", percent);
           rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
@@ -515,7 +776,7 @@ inq_quality (void *opaque, const char *line)
     }
   else
     {
-      log_error ("unsupported inquiry `%s' from pinentry\n", line);
+      log_error ("unsupported inquiry '%s' from pinentry\n", line);
       rc = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE);
     }
 
@@ -523,35 +784,154 @@ inq_quality (void *opaque, const char *line)
 }
 
 
+/* Helper for agent_askpin and agent_get_passphrase.  */
+static gpg_error_t
+setup_qualitybar (ctrl_t ctrl)
+{
+  int rc;
+  char line[ASSUAN_LINELENGTH];
+  char *tmpstr, *tmpstr2;
+  const char *tooltip;
+
+  (void)ctrl;
+
+  /* TRANSLATORS: This string is displayed by Pinentry as the label
+     for the quality bar.  */
+  tmpstr = try_percent_escape (L_("Quality:"), "\t\r\n\f\v");
+  snprintf (line, DIM(line), "SETQUALITYBAR %s", tmpstr? tmpstr:"");
+  xfree (tmpstr);
+  rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+  if (rc == 103 /*(Old assuan error code)*/
+      || gpg_err_code (rc) == GPG_ERR_ASS_UNKNOWN_CMD)
+    ; /* Ignore Unknown Command from old Pinentry versions.  */
+  else if (rc)
+    return rc;
+
+  tmpstr2 = gnupg_get_help_string ("pinentry.qualitybar.tooltip", 0);
+  if (tmpstr2)
+    tooltip = tmpstr2;
+  else
+    {
+      /* TRANSLATORS: This string is a tooltip, shown by pinentry when
+         hovering over the quality bar.  Please use an appropriate
+         string to describe what this is about.  The length of the
+         tooltip is limited to about 900 characters.  If you do not
+         translate this entry, a default english text (see source)
+         will be used. */
+      tooltip =  L_("pinentry.qualitybar.tooltip");
+      if (!strcmp ("pinentry.qualitybar.tooltip", tooltip))
+        tooltip = ("The quality of the text entered above.\n"
+                   "Please ask your administrator for "
+                   "details about the criteria.");
+    }
+  tmpstr = try_percent_escape (tooltip, "\t\r\n\f\v");
+  xfree (tmpstr2);
+  snprintf (line, DIM(line), "SETQUALITYBAR_TT %s", tmpstr? tmpstr:"");
+  xfree (tmpstr);
+  rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+  if (rc == 103 /*(Old assuan error code)*/
+          || gpg_err_code (rc) == GPG_ERR_ASS_UNKNOWN_CMD)
+    ; /* Ignore Unknown Command from old pinentry versions.  */
+  else if (rc)
+    return rc;
+
+  return 0;
+}
+
+enum
+  {
+    PINENTRY_STATUS_CLOSE_BUTTON = 1 << 0,
+    PINENTRY_STATUS_PIN_REPEATED = 1 << 8,
+    PINENTRY_STATUS_PASSWORD_FROM_CACHE = 1 << 9
+  };
+
+/* Check the button_info line for a close action.  Also check for the
+   PIN_REPEATED flag.  */
+static gpg_error_t
+pinentry_status_cb (void *opaque, const char *line)
+{
+  unsigned int *flag = opaque;
+  const char *args;
+
+  if ((args = has_leading_keyword (line, "BUTTON_INFO")))
+    {
+      if (!strcmp (args, "close"))
+        *flag |= PINENTRY_STATUS_CLOSE_BUTTON;
+    }
+  else if (has_leading_keyword (line, "PIN_REPEATED"))
+    {
+      *flag |= PINENTRY_STATUS_PIN_REPEATED;
+    }
+  else if (has_leading_keyword (line, "PASSWORD_FROM_CACHE"))
+    {
+      *flag |= PINENTRY_STATUS_PASSWORD_FROM_CACHE;
+    }
+
+  return 0;
+}
+
 
 
 \f
 /* Call the Entry and ask for the PIN.  We do check for a valid PIN
    number here and repeat it as long as we have invalid formed
-   numbers. */
-int
+   numbers.  KEYINFO and CACHE_MODE are used to tell pinentry something
+   about the key. */
+gpg_error_t
 agent_askpin (ctrl_t ctrl,
               const char *desc_text, const char *prompt_text,
               const char *initial_errtext,
-              struct pin_entry_info_s *pininfo)
+              struct pin_entry_info_s *pininfo,
+              const char *keyinfo, cache_mode_t cache_mode)
 {
-  int rc;
+  gpg_error_t rc;
   char line[ASSUAN_LINELENGTH];
   struct entry_parm_s parm;
   const char *errtext = NULL;
   int is_pin = 0;
+  int saveflag;
+  unsigned int pinentry_status;
 
   if (opt.batch)
     return 0; /* fixme: we should return BAD PIN */
 
+  if (ctrl->pinentry_mode != PINENTRY_MODE_ASK)
+    {
+      if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL)
+        return gpg_error (GPG_ERR_CANCELED);
+      if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
+        {
+         unsigned char *passphrase;
+         size_t size;
+
+         *pininfo->pin = 0; /* Reset the PIN. */
+         rc = pinentry_loopback(ctrl, "PASSPHRASE", &passphrase, &size,
+                 pininfo->max_length - 1);
+         if (rc)
+           return rc;
+
+         memcpy(&pininfo->pin, passphrase, size);
+         xfree(passphrase);
+         pininfo->pin[size] = 0;
+         if (pininfo->check_cb)
+           {
+             /* More checks by utilizing the optional callback. */
+             pininfo->cb_errtext = NULL;
+             rc = pininfo->check_cb (pininfo);
+           }
+         return rc;
+       }
+      return gpg_error(GPG_ERR_NO_PIN_ENTRY);
+    }
+
   if (!pininfo || pininfo->max_length < 1)
     return gpg_error (GPG_ERR_INV_VALUE);
   if (!desc_text && pininfo->min_digits)
-    desc_text = _("Please enter your PIN, so that the secret key "
-                  "can be unlocked for this session");
+    desc_text = L_("Please enter your PIN, so that the secret key "
+                   "can be unlocked for this session");
   else if (!desc_text)
-    desc_text = _("Please enter your passphrase, so that the secret key "
-                  "can be unlocked for this session");
+    desc_text = L_("Please enter your passphrase, so that the secret key "
+                   "can be unlocked for this session");
 
   if (prompt_text)
     is_pin = !!strstr (prompt_text, "PIN");
@@ -562,15 +942,32 @@ agent_askpin (ctrl_t ctrl,
   if (rc)
     return rc;
 
-  snprintf (line, DIM(line)-1, "SETDESC %s", desc_text);
-  line[DIM(line)-1] = 0;
+  /* If we have a KEYINFO string and are normal, user, or ssh cache
+     mode, we tell that the Pinentry so it may use it for own caching
+     purposes.  Most pinentries won't have this implemented and thus
+     we do not error out in this case.  */
+  if (keyinfo && (cache_mode == CACHE_MODE_NORMAL
+                  || cache_mode == CACHE_MODE_USER
+                  || cache_mode == CACHE_MODE_SSH))
+    snprintf (line, DIM(line), "SETKEYINFO %c/%s",
+             cache_mode == CACHE_MODE_USER? 'u' :
+             cache_mode == CACHE_MODE_SSH? 's' : 'n',
+             keyinfo);
+  else
+    snprintf (line, DIM(line), "SETKEYINFO --clear");
+
+  rc = assuan_transact (entry_ctx, line,
+                       NULL, NULL, NULL, NULL, NULL, NULL);
+  if (rc && gpg_err_code (rc) != GPG_ERR_ASS_UNKNOWN_CMD)
+    return unlock_pinentry (rc);
+
+  snprintf (line, DIM(line), "SETDESC %s", desc_text);
   rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
   if (rc)
     return unlock_pinentry (rc);
 
-  snprintf (line, DIM(line)-1, "SETPROMPT %s",
-            prompt_text? prompt_text : is_pin? "PIN:" : "Passphrase:");
-  line[DIM(line)-1] = 0;
+  snprintf (line, DIM(line), "SETPROMPT %s",
+            prompt_text? prompt_text : is_pin? L_("PIN:") : L_("Passphrase:"));
   rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
   if (rc)
     return unlock_pinentry (rc);
@@ -580,63 +977,30 @@ agent_askpin (ctrl_t ctrl,
      to the pinentry.  */
   if (pininfo->with_qualitybar && opt.min_passphrase_len )
     {
-      char *tmpstr, *tmpstr2;
-      const char *tooltip;
-
-      /* TRANSLATORS: This string is displayed by pinentry as the
-         label for the quality bar.  */
-      tmpstr = try_percent_escape (_("Quality:"), "\t\r\n\f\v");
-      snprintf (line, DIM(line)-1, "SETQUALITYBAR %s", tmpstr? tmpstr:"");
-      line[DIM(line)-1] = 0;
-      xfree (tmpstr);
-      rc = assuan_transact (entry_ctx, line,
-                            NULL, NULL, NULL, NULL, NULL, NULL);
-      if (rc == 103 /*(Old assuan error code)*/
-          || gpg_err_code (rc) == GPG_ERR_ASS_UNKNOWN_CMD)
-        ; /* Ignore Unknown Command from old pinentry versions.  */
-      else if (rc)
+      rc = setup_qualitybar (ctrl);
+      if (rc)
         return unlock_pinentry (rc);
+    }
 
-      tmpstr2 = gnupg_get_help_string ("pinentry.qualitybar.tooltip", 0);
-      if (tmpstr2)
-        tooltip = tmpstr2;
-      else
-        {
-          /* TRANSLATORS: This string is a tooltip, shown by pinentry
-             when hovering over the quality bar.  Please use an
-             appropriate string to describe what this is about.  The
-             length of the tooltip is limited to about 900 characters.
-             If you do not translate this entry, a default english
-             text (see source) will be used. */
-          tooltip =  _("pinentry.qualitybar.tooltip");
-          if (!strcmp ("pinentry.qualitybar.tooltip", tooltip))
-            tooltip = ("The quality of the text entered above.\n"
-                       "Please ask your administrator for "
-                       "details about the criteria.");
-        }
-      tmpstr = try_percent_escape (tooltip, "\t\r\n\f\v");
-      xfree (tmpstr2);
-      snprintf (line, DIM(line)-1, "SETQUALITYBAR_TT %s", tmpstr? tmpstr:"");
-      line[DIM(line)-1] = 0;
-      xfree (tmpstr);
+  if (initial_errtext)
+    {
+      snprintf (line, DIM(line), "SETERROR %s", initial_errtext);
       rc = assuan_transact (entry_ctx, line,
                             NULL, NULL, NULL, NULL, NULL, NULL);
-      if (rc == 103 /*(Old assuan error code)*/
-          || gpg_err_code (rc) == GPG_ERR_ASS_UNKNOWN_CMD)
-        ; /* Ignore Unknown Command from old pinentry versions.  */
-      else if (rc)
+      if (rc)
         return unlock_pinentry (rc);
     }
 
-  if (initial_errtext)
-    { 
-      snprintf (line, DIM(line)-1, "SETERROR %s", initial_errtext);
-      line[DIM(line)-1] = 0;
+  if (pininfo->with_repeat)
+    {
+      snprintf (line, DIM(line), "SETREPEATERROR %s",
+                L_("does not match - try again"));
       rc = assuan_transact (entry_ctx, line,
                             NULL, NULL, NULL, NULL, NULL, NULL);
       if (rc)
-        return unlock_pinentry (rc);
+        pininfo->with_repeat = 0; /* Pinentry does not support it.  */
     }
+  pininfo->repeat_okay = 0;
 
   for (;pininfo->failed_tries < pininfo->max_tries; pininfo->failed_tries++)
     {
@@ -646,22 +1010,35 @@ agent_askpin (ctrl_t ctrl,
       parm.buffer = (unsigned char*)pininfo->pin;
 
       if (errtext)
-        { 
-          /* TRANLATORS: The string is appended to an error message in
+        {
+          /* TRANSLATORS: The string is appended to an error message in
              the pinentry.  The %s is the actual error message, the
              two %d give the current and maximum number of tries. */
-          snprintf (line, DIM(line)-1, _("SETERROR %s (try %d of %d)"),
+          snprintf (line, DIM(line), L_("SETERROR %s (try %d of %d)"),
                     errtext, pininfo->failed_tries+1, pininfo->max_tries);
-          line[DIM(line)-1] = 0;
           rc = assuan_transact (entry_ctx, line,
                                 NULL, NULL, NULL, NULL, NULL, NULL);
           if (rc)
             return unlock_pinentry (rc);
           errtext = NULL;
         }
-      
+
+      if (pininfo->with_repeat)
+        {
+          snprintf (line, DIM(line), "SETREPEAT %s", L_("Repeat:"));
+          rc = assuan_transact (entry_ctx, line,
+                                NULL, NULL, NULL, NULL, NULL, NULL);
+          if (rc)
+            return unlock_pinentry (rc);
+        }
+
+      saveflag = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL);
+      assuan_begin_confidential (entry_ctx);
+      pinentry_status = 0;
       rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, &parm,
-                            inq_quality, entry_ctx, NULL, NULL);
+                            inq_quality, entry_ctx,
+                            pinentry_status_cb, &pinentry_status);
+      assuan_set_flag (entry_ctx, ASSUAN_CONFIDENTIAL, saveflag);
       /* Most pinentries out in the wild return the old Assuan error code
          for canceled which gets translated to an assuan Cancel error and
          not to the code for a user cancel.  Fix this here. */
@@ -669,9 +1046,16 @@ agent_askpin (ctrl_t ctrl,
           && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED)
         rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED);
 
+
+      /* Change error code in case the window close button was clicked
+         to cancel the operation.  */
+      if ((pinentry_status & PINENTRY_STATUS_CLOSE_BUTTON)
+         && gpg_err_code (rc) == GPG_ERR_CANCELED)
+        rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_FULLY_CANCELED);
+
       if (gpg_err_code (rc) == GPG_ERR_ASS_TOO_MUCH_DATA)
-        errtext = is_pin? _("PIN too long")
-                        : _("Passphrase too long");
+        errtext = is_pin? L_("PIN too long")
+                        : L_("Passphrase too long");
       else if (rc)
         return unlock_pinentry (rc);
 
@@ -679,12 +1063,12 @@ agent_askpin (ctrl_t ctrl,
         {
           /* do some basic checks on the entered PIN. */
           if (!all_digitsp (pininfo->pin))
-            errtext = _("Invalid characters in PIN");
+            errtext = L_("Invalid characters in PIN");
           else if (pininfo->max_digits
                    && strlen (pininfo->pin) > pininfo->max_digits)
-            errtext = _("PIN too long");
+            errtext = L_("PIN too long");
           else if (strlen (pininfo->pin) < pininfo->min_digits)
-            errtext = _("PIN too short");
+            errtext = L_("PIN too short");
         }
 
       if (!errtext && pininfo->check_cb)
@@ -692,18 +1076,28 @@ agent_askpin (ctrl_t ctrl,
           /* More checks by utilizing the optional callback. */
           pininfo->cb_errtext = NULL;
           rc = pininfo->check_cb (pininfo);
-          if (rc == -1 && pininfo->cb_errtext)
+          if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE
+              && pininfo->cb_errtext)
             errtext = pininfo->cb_errtext;
           else if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE
                    || gpg_err_code (rc) == GPG_ERR_BAD_PIN)
-            errtext = (is_pin? _("Bad PIN")
-                       : _("Bad Passphrase"));
+            errtext = (is_pin? L_("Bad PIN") : L_("Bad Passphrase"));
           else if (rc)
             return unlock_pinentry (rc);
         }
 
       if (!errtext)
-        return unlock_pinentry (0); /* okay, got a PIN or passphrase */
+        {
+          if (pininfo->with_repeat
+             && (pinentry_status & PINENTRY_STATUS_PIN_REPEATED))
+            pininfo->repeat_okay = 1;
+          return unlock_pinentry (0); /* okay, got a PIN or passphrase */
+        }
+
+      if ((pinentry_status & PINENTRY_STATUS_PASSWORD_FROM_CACHE))
+       /* The password was read from the cache.  Don't count this
+          against the retry count.  */
+       pininfo->failed_tries --;
     }
 
   return unlock_pinentry (gpg_error (pininfo->min_digits? GPG_ERR_BAD_PIN
@@ -714,47 +1108,90 @@ agent_askpin (ctrl_t ctrl,
 \f
 /* Ask for the passphrase using the supplied arguments.  The returned
    passphrase needs to be freed by the caller. */
-int 
+int
 agent_get_passphrase (ctrl_t ctrl,
                       char **retpass, const char *desc, const char *prompt,
-                      const char *errtext)
+                      const char *errtext, int with_qualitybar,
+                     const char *keyinfo, cache_mode_t cache_mode)
 {
 
   int rc;
   char line[ASSUAN_LINELENGTH];
   struct entry_parm_s parm;
+  int saveflag;
+  unsigned int pinentry_status;
 
   *retpass = NULL;
   if (opt.batch)
-    return gpg_error (GPG_ERR_BAD_PASSPHRASE); 
+    return gpg_error (GPG_ERR_BAD_PASSPHRASE);
+
+  if (ctrl->pinentry_mode != PINENTRY_MODE_ASK)
+    {
+      if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL)
+        return gpg_error (GPG_ERR_CANCELED);
+
+      if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
+        {
+         size_t size;
+         size_t len = ASSUAN_LINELENGTH/2;
+
+         return pinentry_loopback (ctrl, "PASSPHRASE",
+                                   (unsigned char **)retpass, &size, len);
+        }
+      return gpg_error (GPG_ERR_NO_PIN_ENTRY);
+    }
 
   rc = start_pinentry (ctrl);
   if (rc)
     return rc;
 
   if (!prompt)
-    prompt = desc && strstr (desc, "PIN")? "PIN": _("Passphrase");
+    prompt = desc && strstr (desc, "PIN")? L_("PIN:"): L_("Passphrase:");
+
+
+  /* If we have a KEYINFO string and are normal, user, or ssh cache
+     mode, we tell that the Pinentry so it may use it for own caching
+     purposes.  Most pinentries won't have this implemented and thus
+     we do not error out in this case.  */
+  if (keyinfo && (cache_mode == CACHE_MODE_NORMAL
+                  || cache_mode == CACHE_MODE_USER
+                  || cache_mode == CACHE_MODE_SSH))
+    snprintf (line, DIM(line), "SETKEYINFO %c/%s",
+             cache_mode == CACHE_MODE_USER? 'u' :
+             cache_mode == CACHE_MODE_SSH? 's' : 'n',
+             keyinfo);
+  else
+    snprintf (line, DIM(line), "SETKEYINFO --clear");
+
+  rc = assuan_transact (entry_ctx, line,
+                       NULL, NULL, NULL, NULL, NULL, NULL);
+  if (rc && gpg_err_code (rc) != GPG_ERR_ASS_UNKNOWN_CMD)
+    return unlock_pinentry (rc);
 
 
   if (desc)
-    snprintf (line, DIM(line)-1, "SETDESC %s", desc);
+    snprintf (line, DIM(line), "SETDESC %s", desc);
   else
-    snprintf (line, DIM(line)-1, "RESET");
-  line[DIM(line)-1] = 0;
+    snprintf (line, DIM(line), "RESET");
   rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
   if (rc)
     return unlock_pinentry (rc);
 
-  snprintf (line, DIM(line)-1, "SETPROMPT %s", prompt);
-  line[DIM(line)-1] = 0;
+  snprintf (line, DIM(line), "SETPROMPT %s", prompt);
   rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
   if (rc)
     return unlock_pinentry (rc);
 
+  if (with_qualitybar && opt.min_passphrase_len)
+    {
+      rc = setup_qualitybar (ctrl);
+      if (rc)
+        return unlock_pinentry (rc);
+    }
+
   if (errtext)
     {
-      snprintf (line, DIM(line)-1, "SETERROR %s", errtext);
-      line[DIM(line)-1] = 0;
+      snprintf (line, DIM(line), "SETERROR %s", errtext);
       rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
       if (rc)
         return unlock_pinentry (rc);
@@ -766,14 +1203,24 @@ agent_get_passphrase (ctrl_t ctrl,
   if (!parm.buffer)
     return unlock_pinentry (out_of_core ());
 
+  saveflag = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL);
   assuan_begin_confidential (entry_ctx);
+  pinentry_status = 0;
   rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, &parm,
-                        NULL, NULL, NULL, NULL);
+                        inq_quality, entry_ctx,
+                        pinentry_status_cb, &pinentry_status);
+  assuan_set_flag (entry_ctx, ASSUAN_CONFIDENTIAL, saveflag);
   /* Most pinentries out in the wild return the old Assuan error code
      for canceled which gets translated to an assuan Cancel error and
      not to the code for a user cancel.  Fix this here. */
   if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED)
     rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED);
+  /* Change error code in case the window close button was clicked
+     to cancel the operation.  */
+  if ((pinentry_status & PINENTRY_STATUS_CLOSE_BUTTON)
+      && gpg_err_code (rc) == GPG_ERR_CANCELED)
+    rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_FULLY_CANCELED);
+
   if (rc)
     xfree (parm.buffer);
   else
@@ -786,23 +1233,34 @@ agent_get_passphrase (ctrl_t ctrl,
 /* Pop up the PIN-entry, display the text and the prompt and ask the
    user to confirm this.  We return 0 for success, ie. the user
    confirmed it, GPG_ERR_NOT_CONFIRMED for what the text says or an
-   other error. */
-int 
+   other error.  If WITH_CANCEL it true an extra cancel button is
+   displayed to allow the user to easily return a GPG_ERR_CANCELED.
+   if the Pinentry does not support this, the user can still cancel by
+   closing the Pinentry window.  */
+int
 agent_get_confirmation (ctrl_t ctrl,
-                        const char *desc, const char *ok, const char *cancel)
+                        const char *desc, const char *ok,
+                        const char *notok, int with_cancel)
 {
   int rc;
   char line[ASSUAN_LINELENGTH];
 
+  if (ctrl->pinentry_mode != PINENTRY_MODE_ASK)
+    {
+      if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL)
+        return gpg_error (GPG_ERR_CANCELED);
+
+      return gpg_error (GPG_ERR_NO_PIN_ENTRY);
+    }
+
   rc = start_pinentry (ctrl);
   if (rc)
     return rc;
 
   if (desc)
-    snprintf (line, DIM(line)-1, "SETDESC %s", desc);
+    snprintf (line, DIM(line), "SETDESC %s", desc);
   else
-    snprintf (line, DIM(line)-1, "RESET");
-  line[DIM(line)-1] = 0;
+    snprintf (line, DIM(line), "RESET");
   rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
   /* Most pinentries out in the wild return the old Assuan error code
      for canceled which gets translated to an assuan Cancel error and
@@ -815,22 +1273,38 @@ agent_get_confirmation (ctrl_t ctrl,
 
   if (ok)
     {
-      snprintf (line, DIM(line)-1, "SETOK %s", ok);
-      line[DIM(line)-1] = 0;
-      rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+      snprintf (line, DIM(line), "SETOK %s", ok);
+      rc = assuan_transact (entry_ctx,
+                            line, NULL, NULL, NULL, NULL, NULL, NULL);
       if (rc)
         return unlock_pinentry (rc);
     }
-  if (cancel)
+  if (notok)
     {
-      snprintf (line, DIM(line)-1, "SETCANCEL %s", cancel);
-      line[DIM(line)-1] = 0;
-      rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+      /* Try to use the newer NOTOK feature if a cancel button is
+         requested.  If no cancel button is requested we keep on using
+         the standard cancel.  */
+      if (with_cancel)
+        {
+          snprintf (line, DIM(line), "SETNOTOK %s", notok);
+          rc = assuan_transact (entry_ctx,
+                                line, NULL, NULL, NULL, NULL, NULL, NULL);
+        }
+      else
+        rc = GPG_ERR_ASS_UNKNOWN_CMD;
+
+      if (gpg_err_code (rc) == GPG_ERR_ASS_UNKNOWN_CMD)
+       {
+         snprintf (line, DIM(line), "SETCANCEL %s", notok);
+         rc = assuan_transact (entry_ctx, line,
+                                NULL, NULL, NULL, NULL, NULL, NULL);
+       }
       if (rc)
         return unlock_pinentry (rc);
     }
 
-  rc = assuan_transact (entry_ctx, "CONFIRM", NULL, NULL, NULL, NULL, NULL, NULL);
+  rc = assuan_transact (entry_ctx, "CONFIRM",
+                        NULL, NULL, NULL, NULL, NULL, NULL);
   if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED)
     rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED);
 
@@ -840,24 +1314,26 @@ agent_get_confirmation (ctrl_t ctrl,
 
 \f
 /* Pop up the PINentry, display the text DESC and a button with the
-   text OK_BTN (which may be NULL to use the default of "OK") and waut
+   text OK_BTN (which may be NULL to use the default of "OK") and wait
    for the user to hit this button.  The return value is not
    relevant.  */
-int 
+int
 agent_show_message (ctrl_t ctrl, const char *desc, const char *ok_btn)
 {
   int rc;
   char line[ASSUAN_LINELENGTH];
 
+  if (ctrl->pinentry_mode != PINENTRY_MODE_ASK)
+    return gpg_error (GPG_ERR_CANCELED);
+
   rc = start_pinentry (ctrl);
   if (rc)
     return rc;
 
   if (desc)
-    snprintf (line, DIM(line)-1, "SETDESC %s", desc);
+    snprintf (line, DIM(line), "SETDESC %s", desc);
   else
-    snprintf (line, DIM(line)-1, "RESET");
-  line[DIM(line)-1] = 0;
+    snprintf (line, DIM(line), "RESET");
   rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
   /* Most pinentries out in the wild return the old Assuan error code
      for canceled which gets translated to an assuan Cancel error and
@@ -870,14 +1346,13 @@ agent_show_message (ctrl_t ctrl, const char *desc, const char *ok_btn)
 
   if (ok_btn)
     {
-      snprintf (line, DIM(line)-1, "SETOK %s", ok_btn);
-      line[DIM(line)-1] = 0;
+      snprintf (line, DIM(line), "SETOK %s", ok_btn);
       rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL,
                             NULL, NULL, NULL);
       if (rc)
         return unlock_pinentry (rc);
     }
-  
+
   rc = assuan_transact (entry_ctx, "CONFIRM --one-button", NULL, NULL, NULL,
                         NULL, NULL, NULL);
   if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED)
@@ -891,11 +1366,13 @@ agent_show_message (ctrl_t ctrl, const char *desc, const char *ok_btn)
 static void *
 popup_message_thread (void *arg)
 {
+  (void)arg;
+
   /* We use the --one-button hack instead of the MESSAGE command to
      allow the use of old Pinentries.  Those old Pinentries will then
      show an additional Cancel button but that is mostly a visual
      annoyance. */
-  assuan_transact (entry_ctx, "CONFIRM --one-button", 
+  assuan_transact (entry_ctx, "CONFIRM --one-button",
                    NULL, NULL, NULL, NULL, NULL, NULL);
   popup_finished = 1;
   return NULL;
@@ -908,51 +1385,53 @@ popup_message_thread (void *arg)
    as the message is not anymore required because the message is
    system modal and all other attempts to use the pinentry will fail
    (after a timeout). */
-int 
+int
 agent_popup_message_start (ctrl_t ctrl, const char *desc, const char *ok_btn)
 {
   int rc;
   char line[ASSUAN_LINELENGTH];
-  pth_attr_t tattr;
+  npth_attr_t tattr;
+  int err;
+
+  if (ctrl->pinentry_mode != PINENTRY_MODE_ASK)
+    return gpg_error (GPG_ERR_CANCELED);
 
   rc = start_pinentry (ctrl);
   if (rc)
     return rc;
 
   if (desc)
-    snprintf (line, DIM(line)-1, "SETDESC %s", desc);
+    snprintf (line, DIM(line), "SETDESC %s", desc);
   else
-    snprintf (line, DIM(line)-1, "RESET");
-  line[DIM(line)-1] = 0;
+    snprintf (line, DIM(line), "RESET");
   rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
   if (rc)
     return unlock_pinentry (rc);
 
   if (ok_btn)
     {
-      snprintf (line, DIM(line)-1, "SETOK %s", ok_btn);
-      line[DIM(line)-1] = 0;
+      snprintf (line, DIM(line), "SETOK %s", ok_btn);
       rc = assuan_transact (entry_ctx, line, NULL,NULL,NULL,NULL,NULL,NULL);
       if (rc)
         return unlock_pinentry (rc);
     }
 
-  tattr = pth_attr_new();
-  pth_attr_set (tattr, PTH_ATTR_JOINABLE, 1);
-  pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 256*1024);
-  pth_attr_set (tattr, PTH_ATTR_NAME, "popup-message");
+  err = npth_attr_init (&tattr);
+  if (err)
+    return unlock_pinentry (gpg_error_from_errno (err));
+  npth_attr_setdetachstate (&tattr, NPTH_CREATE_JOINABLE);
 
   popup_finished = 0;
-  popup_tid = pth_spawn (tattr, popup_message_thread, NULL);
-  if (!popup_tid)
+  err = npth_create (&popup_tid, &tattr, popup_message_thread, NULL);
+  npth_attr_destroy (&tattr);
+  if (err)
     {
-      rc = gpg_error_from_syserror ();
+      rc = gpg_error_from_errno (err);
       log_error ("error spawning popup message handler: %s\n",
-                 strerror (errno) );
-      pth_attr_destroy (tattr);
+                 strerror (err) );
       return unlock_pinentry (rc);
     }
-  pth_attr_destroy (tattr);
+  npth_setname_np (popup_tid, "popup-message");
 
   return 0;
 }
@@ -964,10 +1443,12 @@ agent_popup_message_stop (ctrl_t ctrl)
   int rc;
   pid_t pid;
 
+  (void)ctrl;
+
   if (!popup_tid || !entry_ctx)
     {
       log_debug ("agent_popup_message_stop called with no active popup\n");
-      return; 
+      return;
     }
 
   pid = assuan_get_pid (entry_ctx);
@@ -982,7 +1463,7 @@ agent_popup_message_stop (ctrl_t ctrl)
           && pid != 0)
     {
       HANDLE process = (HANDLE) pid;
-      
+
       /* Arbitrary error code.  */
       TerminateProcess (process, 1);
     }
@@ -996,20 +1477,45 @@ agent_popup_message_stop (ctrl_t ctrl)
         assuan_set_flag (entry_ctx, ASSUAN_NO_WAITPID, 1);
     }
   else if (pid > 0)
-    kill (pid, SIGKILL);  /* Need to use SIGKILL due to bad
-                             interaction of SIGINT with Pth. */
+    kill (pid, SIGINT);
 #endif
 
   /* Now wait for the thread to terminate. */
-  rc = pth_join (popup_tid, NULL);
-  if (!rc)
+  rc = npth_join (popup_tid, NULL);
+  if (rc)
     log_debug ("agent_popup_message_stop: pth_join failed: %s\n",
-               strerror (errno));
-  popup_tid = NULL;
+               strerror (rc));
+  /* Thread IDs are opaque, but we try our best here by resetting it
+     to the same content that a static global variable has.  */
+  memset (&popup_tid, '\0', sizeof (popup_tid));
   entry_owner = NULL;
 
   /* Now we can close the connection. */
   unlock_pinentry (0);
 }
 
+int
+agent_clear_passphrase (ctrl_t ctrl,
+                       const char *keyinfo, cache_mode_t cache_mode)
+{
+  int rc;
+  char line[ASSUAN_LINELENGTH];
+
+  if (! (keyinfo && (cache_mode == CACHE_MODE_NORMAL
+                    || cache_mode == CACHE_MODE_USER
+                    || cache_mode == CACHE_MODE_SSH)))
+    return gpg_error (GPG_ERR_NOT_SUPPORTED);
 
+  rc = start_pinentry (ctrl);
+  if (rc)
+    return rc;
+
+  snprintf (line, DIM(line), "CLEARPASSPHRASE %c/%s",
+           cache_mode == CACHE_MODE_USER? 'u' :
+           cache_mode == CACHE_MODE_SSH? 's' : 'n',
+           keyinfo);
+  rc = assuan_transact (entry_ctx, line,
+                       NULL, NULL, NULL, NULL, NULL, NULL);
+
+  return unlock_pinentry (rc);
+}