agent: Tell pinentry the hostname the agent is running on.
[gnupg.git] / agent / call-pinentry.c
index a96406f..9931665 100644 (file)
@@ -15,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>
@@ -31,6 +31,7 @@
 # include <sys/wait.h>
 # include <sys/types.h>
 # include <signal.h>
+# include <sys/utsname.h>
 #endif
 #include <npth.h>
 
@@ -47,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)
 
@@ -127,12 +128,40 @@ 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;
   err = npth_mutex_unlock (&entry_lock);
   if (err)
@@ -197,11 +226,12 @@ getinfo_pid_cb (void *opaque, const void *buffer, size_t length)
   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 = 0;
@@ -215,6 +245,7 @@ start_pinentry (ctrl_t ctrl)
   unsigned long pinentry_pid;
   const char *value;
   struct timespec abstime;
+  char *flavor_version;
   int err;
 
   npth_clock_gettime (&abstime);
@@ -251,8 +282,8 @@ 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
@@ -304,7 +335,7 @@ start_pinentry (ctrl_t ctrl)
      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, 1);
+  assuan_set_flag (ctx, ASSUAN_NO_LOGGING, !opt.debug_pinentry);
 #endif
 
   /* Connect to the pinentry and perform initial handshaking.  Note
@@ -323,9 +354,22 @@ start_pinentry (ctrl_t ctrl)
     }
   entry_ctx = ctx;
 
-  if (DBG_ASSUAN)
+  if (DBG_IPC)
     log_debug ("connection to PIN entry established\n");
 
+  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);
@@ -379,18 +423,53 @@ start_pinentry (ctrl_t ctrl)
        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; } tbl[] = {
+    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.  */
+         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;
@@ -399,7 +478,9 @@ start_pinentry (ctrl_t ctrl)
 
     for (idx=0; tbl[idx].key; idx++)
       {
-        s = _(tbl[idx].value);
+        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 )
@@ -410,6 +491,33 @@ start_pinentry (ctrl_t ctrl)
       }
   }
 
+  /* 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
@@ -433,6 +541,52 @@ start_pinentry (ctrl_t ctrl)
         }
     }
 
+  /* 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
@@ -450,7 +604,7 @@ start_pinentry (ctrl_t ctrl)
     log_error ("pinentry did not return a PID\n");
   else
     {
-      rc = agent_inq_pinentry_launched (ctrl, pinentry_pid);
+      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,
@@ -458,11 +612,13 @@ start_pinentry (ctrl_t ctrl)
       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
@@ -536,7 +692,7 @@ all_digitsp( const char *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)
@@ -611,7 +767,7 @@ inq_quality (void *opaque, const char *line)
       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));
@@ -629,19 +785,20 @@ inq_quality (void *opaque, const char *line)
 
 
 /* Helper for agent_askpin and agent_get_passphrase.  */
-static int
-setup_qualitybar (void)
+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 (_("Quality:"), "\t\r\n\f\v");
-  snprintf (line, DIM(line)-1, "SETQUALITYBAR %s", tmpstr? tmpstr:"");
-  line[DIM(line)-1] = 0;
+  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)*/
@@ -661,7 +818,7 @@ setup_qualitybar (void)
          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");
+      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 "
@@ -669,8 +826,7 @@ setup_qualitybar (void)
     }
   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;
+  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)*/
@@ -682,11 +838,17 @@ setup_qualitybar (void)
   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
-close_button_status_cb (void *opaque, const char *line)
+pinentry_status_cb (void *opaque, const char *line)
 {
   unsigned int *flag = opaque;
   const char *args;
@@ -694,11 +856,15 @@ close_button_status_cb (void *opaque, const char *line)
   if ((args = has_leading_keyword (line, "BUTTON_INFO")))
     {
       if (!strcmp (args, "close"))
-        *flag = 1;
+        *flag |= PINENTRY_STATUS_CLOSE_BUTTON;
     }
   else if (has_leading_keyword (line, "PIN_REPEATED"))
     {
-      *flag |= 256;
+      *flag |= PINENTRY_STATUS_PIN_REPEATED;
+    }
+  else if (has_leading_keyword (line, "PASSWORD_FROM_CACHE"))
+    {
+      *flag |= PINENTRY_STATUS_PASSWORD_FROM_CACHE;
     }
 
   return 0;
@@ -709,20 +875,22 @@ close_button_status_cb (void *opaque, const char *line)
 \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 close_button;
+  unsigned int pinentry_status;
 
   if (opt.batch)
     return 0; /* fixme: we should return BAD PIN */
@@ -738,7 +906,7 @@ agent_askpin (ctrl_t ctrl,
 
          *pininfo->pin = 0; /* Reset the PIN. */
          rc = pinentry_loopback(ctrl, "PASSPHRASE", &passphrase, &size,
-                 pininfo->max_length);
+                 pininfo->max_length - 1);
          if (rc)
            return rc;
 
@@ -759,11 +927,11 @@ agent_askpin (ctrl_t ctrl,
   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");
@@ -774,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);
@@ -792,15 +977,14 @@ agent_askpin (ctrl_t ctrl,
      to the pinentry.  */
   if (pininfo->with_qualitybar && opt.min_passphrase_len )
     {
-      rc = setup_qualitybar ();
+      rc = setup_qualitybar (ctrl);
       if (rc)
         return unlock_pinentry (rc);
     }
 
   if (initial_errtext)
     {
-      snprintf (line, DIM(line)-1, "SETERROR %s", initial_errtext);
-      line[DIM(line)-1] = 0;
+      snprintf (line, DIM(line), "SETERROR %s", initial_errtext);
       rc = assuan_transact (entry_ctx, line,
                             NULL, NULL, NULL, NULL, NULL, NULL);
       if (rc)
@@ -809,9 +993,8 @@ agent_askpin (ctrl_t ctrl,
 
   if (pininfo->with_repeat)
     {
-      snprintf (line, DIM(line)-1, "SETREPEATERROR %s",
-                _("does not match - try again"));
-      line[DIM(line)-1] = 0;
+      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)
@@ -831,9 +1014,8 @@ agent_askpin (ctrl_t ctrl,
           /* 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)
@@ -843,8 +1025,7 @@ agent_askpin (ctrl_t ctrl,
 
       if (pininfo->with_repeat)
         {
-          snprintf (line, DIM(line)-1, "SETREPEAT %s", _("Repeat:"));
-          line[DIM(line)-1] = 0;
+          snprintf (line, DIM(line), "SETREPEAT %s", L_("Repeat:"));
           rc = assuan_transact (entry_ctx, line,
                                 NULL, NULL, NULL, NULL, NULL, NULL);
           if (rc)
@@ -853,10 +1034,10 @@ agent_askpin (ctrl_t ctrl,
 
       saveflag = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL);
       assuan_begin_confidential (entry_ctx);
-      close_button = 0;
+      pinentry_status = 0;
       rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, &parm,
                             inq_quality, entry_ctx,
-                            close_button_status_cb, &close_button);
+                            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
@@ -868,12 +1049,13 @@ agent_askpin (ctrl_t ctrl,
 
       /* Change error code in case the window close button was clicked
          to cancel the operation.  */
-      if ((close_button & 1) && gpg_err_code (rc) == GPG_ERR_CANCELED)
+      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);
 
@@ -881,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)
@@ -894,22 +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)
         {
-          if (pininfo->with_repeat && (close_button & 256))
+          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
@@ -923,14 +1111,15 @@ agent_askpin (ctrl_t ctrl,
 int
 agent_get_passphrase (ctrl_t ctrl,
                       char **retpass, const char *desc, const char *prompt,
-                      const char *errtext, int with_qualitybar)
+                      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 close_button;
+  unsigned int pinentry_status;
 
   *retpass = NULL;
   if (opt.batch)
@@ -945,17 +1134,9 @@ agent_get_passphrase (ctrl_t ctrl,
         {
          size_t size;
          size_t len = ASSUAN_LINELENGTH/2;
-         unsigned char *buffer = gcry_malloc_secure (len);
 
-         rc = pinentry_loopback(ctrl, "PASSPHRASE", &buffer, &size, len);
-         if (rc)
-           xfree(buffer);
-         else
-           {
-             buffer[size] = 0;
-             *retpass = buffer;
-           }
-         return rc;
+         return pinentry_loopback (ctrl, "PASSPHRASE",
+                                   (unsigned char **)retpass, &size, len);
         }
       return gpg_error (GPG_ERR_NO_PIN_ENTRY);
     }
@@ -965,35 +1146,52 @@ agent_get_passphrase (ctrl_t ctrl,
     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 ();
+      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);
@@ -1007,10 +1205,10 @@ agent_get_passphrase (ctrl_t ctrl,
 
   saveflag = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL);
   assuan_begin_confidential (entry_ctx);
-  close_button = 0;
+  pinentry_status = 0;
   rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, &parm,
                         inq_quality, entry_ctx,
-                        close_button_status_cb, &close_button);
+                        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
@@ -1019,7 +1217,8 @@ agent_get_passphrase (ctrl_t ctrl,
     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 ((close_button & 1) && gpg_err_code (rc) == GPG_ERR_CANCELED)
+  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)
@@ -1059,10 +1258,9 @@ agent_get_confirmation (ctrl_t ctrl,
     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
@@ -1075,8 +1273,7 @@ agent_get_confirmation (ctrl_t ctrl,
 
   if (ok)
     {
-      snprintf (line, DIM(line)-1, "SETOK %s", ok);
-      line[DIM(line)-1] = 0;
+      snprintf (line, DIM(line), "SETOK %s", ok);
       rc = assuan_transact (entry_ctx,
                             line, NULL, NULL, NULL, NULL, NULL, NULL);
       if (rc)
@@ -1089,8 +1286,7 @@ agent_get_confirmation (ctrl_t ctrl,
          the standard cancel.  */
       if (with_cancel)
         {
-          snprintf (line, DIM(line)-1, "SETNOTOK %s", notok);
-          line[DIM(line)-1] = 0;
+          snprintf (line, DIM(line), "SETNOTOK %s", notok);
           rc = assuan_transact (entry_ctx,
                                 line, NULL, NULL, NULL, NULL, NULL, NULL);
         }
@@ -1099,8 +1295,7 @@ agent_get_confirmation (ctrl_t ctrl,
 
       if (gpg_err_code (rc) == GPG_ERR_ASS_UNKNOWN_CMD)
        {
-         snprintf (line, DIM(line)-1, "SETCANCEL %s", notok);
-         line[DIM(line)-1] = 0;
+         snprintf (line, DIM(line), "SETCANCEL %s", notok);
          rc = assuan_transact (entry_ctx, line,
                                 NULL, NULL, NULL, NULL, NULL, NULL);
        }
@@ -1136,10 +1331,9 @@ agent_show_message (ctrl_t ctrl, const char *desc, const char *ok_btn)
     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
@@ -1152,8 +1346,7 @@ 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)
@@ -1208,18 +1401,16 @@ agent_popup_message_start (ctrl_t ctrl, const char *desc, const char *ok_btn)
     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);
@@ -1302,3 +1493,29 @@ agent_popup_message_stop (ctrl_t ctrl)
   /* 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);
+}