common: Consolidate Assuan server argument handling.
[gnupg.git] / agent / call-pinentry.c
index 64c64a9..0f24086 100644 (file)
@@ -127,8 +127,8 @@ 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;
@@ -226,10 +226,10 @@ getinfo_pid_cb (void *opaque, const void *buffer, size_t length)
 }
 
 /* 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;
@@ -408,34 +408,48 @@ start_pinentry (ctrl_t ctrl)
     }
 
 
-  /* Indicate to the pinentry that it may read from an external cache.
+  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);
+    }
 
-     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 mode; } 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") },
+      { "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") },
@@ -448,7 +462,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 )
@@ -459,6 +475,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
@@ -660,7 +703,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));
@@ -678,17 +721,19 @@ 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");
+  tmpstr = try_percent_escape (L_("Quality:"), "\t\r\n\f\v");
   snprintf (line, DIM(line)-1, "SETQUALITYBAR %s", tmpstr? tmpstr:"");
   line[DIM(line)-1] = 0;
   xfree (tmpstr);
@@ -710,7 +755,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 "
@@ -770,14 +815,14 @@ pinentry_status_cb (void *opaque, const char *line)
    number here and repeat it as long as we have invalid formed
    numbers.  KEYINFO and CACHE_MODE are used to tell pinentry something
    about the key. */
-int
+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,
               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;
@@ -799,7 +844,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;
 
@@ -820,11 +865,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");
@@ -861,7 +906,7 @@ agent_askpin (ctrl_t ctrl,
     return unlock_pinentry (rc);
 
   snprintf (line, DIM(line)-1, "SETPROMPT %s",
-            prompt_text? prompt_text : is_pin? "PIN:" : "Passphrase:");
+            prompt_text? prompt_text : is_pin? L_("PIN:") : L_("Passphrase:"));
   line[DIM(line)-1] = 0;
   rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
   if (rc)
@@ -872,7 +917,7 @@ 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);
     }
@@ -890,7 +935,7 @@ agent_askpin (ctrl_t ctrl,
   if (pininfo->with_repeat)
     {
       snprintf (line, DIM(line)-1, "SETREPEATERROR %s",
-                _("does not match - try again"));
+                L_("does not match - try again"));
       line[DIM(line)-1] = 0;
       rc = assuan_transact (entry_ctx, line,
                             NULL, NULL, NULL, NULL, NULL, NULL);
@@ -911,7 +956,7 @@ 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)-1, 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,
@@ -923,7 +968,7 @@ agent_askpin (ctrl_t ctrl,
 
       if (pininfo->with_repeat)
         {
-          snprintf (line, DIM(line)-1, "SETREPEAT %s", _("Repeat:"));
+          snprintf (line, DIM(line)-1, "SETREPEAT %s", L_("Repeat:"));
           line[DIM(line)-1] = 0;
           rc = assuan_transact (entry_ctx, line,
                                 NULL, NULL, NULL, NULL, NULL, NULL);
@@ -953,8 +998,8 @@ agent_askpin (ctrl_t ctrl,
         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);
 
@@ -962,12 +1007,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)
@@ -975,12 +1020,12 @@ 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);
         }
@@ -1010,7 +1055,8 @@ 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;
@@ -1032,17 +1078,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);
     }
@@ -1052,7 +1090,27 @@ 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)-1, "SETKEYINFO %c/%s",
+             cache_mode == CACHE_MODE_USER? 'u' :
+             cache_mode == CACHE_MODE_SSH? 's' : 'n',
+             keyinfo);
+  else
+    snprintf (line, DIM(line)-1, "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)
@@ -1072,7 +1130,7 @@ agent_get_passphrase (ctrl_t ctrl,
 
   if (with_qualitybar && opt.min_passphrase_len)
     {
-      rc = setup_qualitybar ();
+      rc = setup_qualitybar (ctrl);
       if (rc)
         return unlock_pinentry (rc);
     }
@@ -1390,3 +1448,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)-1, "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);
+}