agent: Minor fix for Windows.
[gnupg.git] / agent / command.c
index 62a4628..e387db5 100644 (file)
@@ -16,7 +16,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/>.
  */
 
 /* FIXME: we should not use the default assuan buffering but setup
 
 #include "agent.h"
 #include <assuan.h>
-#include "i18n.h"
+#include "../common/i18n.h"
 #include "cvt-openpgp.h"
 #include "../common/ssh-utils.h"
 #include "../common/asshelp.h"
+#include "../common/server-help.h"
 
 
 /* Maximum allowed size of the inquired ciphertext.  */
@@ -48,7 +49,7 @@
 /* Maximum allowed size of the key parameters.  */
 #define MAXLEN_KEYPARAM 1024
 /* Maximum allowed size of key data as used in inquiries (bytes). */
-#define MAXLEN_KEYDATA 4096
+#define MAXLEN_KEYDATA 8192
 /* The size of the import/export KEK key (in bytes).  */
 #define KEYWRAP_KEYSIZE (128/8)
 
@@ -73,21 +74,28 @@ struct server_local_s
      operations.  It defaults to true but may be set on a per
      connection base.  The global option opt.ignore_cache_for_signing
      takes precedence over this flag.  */
-  int use_cache_for_signing;
+  unsigned int use_cache_for_signing : 1;
 
-  /* An allocated description for the next key operation.  This is
-     used if a pinnetry needs to be popped up.  */
-  char *keydesc;
+  /* Flag to suppress I/O logging during a command.  */
+  unsigned int pause_io_logging : 1;
 
-  /* Flags to suppress I/O logging during a command.  */
-  int pause_io_logging;
+  /* Flag indicating that the connection is from ourselves.  */
+  unsigned int connect_from_self : 1;
 
-  /* If this flags is set to true the agent will be terminated after
+  /* Helper flag for io_monitor to allow suppressing of our own
+   * greeting in some cases.  See io_monitor for details.  */
+  unsigned int greeting_seen : 1;
+
+  /* If this flag is set to true the agent will be terminated after
      the end of the current session.  */
-  int stopme;
+  unsigned int stopme : 1;
 
   /* Flag indicating whether pinentry notifications shall be done. */
-  int allow_pinentry_notify;
+  unsigned int allow_pinentry_notify : 1;
+
+  /* An allocated description for the next key operation.  This is
+     used if a pinnetry needs to be popped up.  */
+  char *keydesc;
 
   /* Malloced KEK (Key-Encryption-Key) for the import_key command.  */
   void *import_key;
@@ -206,7 +214,7 @@ clear_nonce_cache (ctrl_t ctrl)
 }
 
 
-/* This function is called by Libassuan whenever thee client sends a
+/* This function is called by Libassuan whenever the client sends a
    reset.  It has been registered similar to the other Assuan
    commands.  */
 static gpg_error_t
@@ -229,86 +237,6 @@ reset_notify (assuan_context_t ctx, char *line)
 }
 
 
-/* Skip over options in LINE.
-
-   Blanks after the options are also removed.  Options are indicated
-   by two leading dashes followed by a string consisting of non-space
-   characters.  The special option "--" indicates an explicit end of
-   options; all what follows will not be considered an option.  The
-   first no-option string also indicates the end of option parsing. */
-static char *
-skip_options (const char *line)
-{
-  while (spacep (line))
-    line++;
-  while ( *line == '-' && line[1] == '-' )
-    {
-      while (*line && !spacep (line))
-        line++;
-      while (spacep (line))
-        line++;
-    }
-  return (char*)line;
-}
-
-
-/* Check whether the option NAME appears in LINE.  An example for a
-   line with options is:
-     --algo=42 --data foo bar
-   This function would then only return true if NAME is "data".  */
-static int
-has_option (const char *line, const char *name)
-{
-  const char *s;
-  int n = strlen (name);
-
-  s = strstr (line, name);
-  if (s && s >= skip_options (line))
-    return 0;
-  return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n)));
-}
-
-
-/* Same as has_option but does only test for the name of the option
-   and ignores an argument, i.e. with NAME being "--hash" it would
-   return true for "--hash" as well as for "--hash=foo". */
-static int
-has_option_name (const char *line, const char *name)
-{
-  const char *s;
-  int n = strlen (name);
-
-  s = strstr (line, name);
-  if (s && s >= skip_options (line))
-    return 0;
-  return (s && (s == line || spacep (s-1))
-          && (!s[n] || spacep (s+n) || s[n] == '='));
-}
-
-
-/* Return a pointer to the argument of the option with NAME.  If such
-   an option is not given, NULL is retruned. */
-static char *
-option_value (const char *line, const char *name)
-{
-  char *s;
-  int n = strlen (name);
-
-  s = strstr (line, name);
-  if (s && s >= skip_options (line))
-    return NULL;
-  if (s && (s == line || spacep (s-1))
-      && s[n] && (spacep (s+n) || s[n] == '='))
-    {
-      s += n + 1;
-      s += strspn (s, " ");
-      if (*s && !spacep(s))
-        return s;
-    }
-  return NULL;
-}
-
-
 /* Replace all '+' by a blank in the string S. */
 static void
 plus_to_blank (char *s)
@@ -434,19 +362,39 @@ agent_print_status (ctrl_t ctrl, const char *keyword, const char *format, ...)
    that might disturb some older clients, this is only done if enabled
    via an option.  Returns an gpg error code. */
 gpg_error_t
-agent_inq_pinentry_launched (ctrl_t ctrl, unsigned long pid)
+agent_inq_pinentry_launched (ctrl_t ctrl, unsigned long pid, const char *extra)
 {
-  char line[100];
+  char line[256];
 
   if (!ctrl || !ctrl->server_local
       || !ctrl->server_local->allow_pinentry_notify)
     return 0;
-  snprintf (line, DIM(line)-1, "PINENTRY_LAUNCHED %lu", pid);
+  snprintf (line, DIM(line), "PINENTRY_LAUNCHED %lu%s%s",
+            pid, extra?" ":"", extra? extra:"");
   return assuan_inquire (ctrl->server_local->assuan_ctx, line, NULL, NULL, 0);
 }
 
 
-/* Helper to print a message while leaving a command.  */
+/* An agent progress callback for Libgcrypt.  This has been registered
+ * to be called via the progress dispatcher mechanism from
+ * gpg-agent.c  */
+static void
+progress_cb (ctrl_t ctrl, const char *what, int printchar,
+             int current, int total)
+{
+  if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx)
+    ;
+  else if (printchar == '\n' && what && !strcmp (what, "primegen"))
+    agent_print_status (ctrl, "PROGRESS", "%.20s X 100 100", what);
+  else
+    agent_print_status (ctrl, "PROGRESS", "%.20s %c %d %d",
+                        what, printchar=='\n'?'X':printchar, current, total);
+}
+
+
+/* Helper to print a message while leaving a command.  Note that this
+ * function does not call assuan_set_error; the caller may do this
+ * prior to calling us.  */
 static gpg_error_t
 leave_cmd (assuan_context_t ctx, gpg_error_t err)
 {
@@ -486,7 +434,7 @@ leave_cmd (assuan_context_t ctx, gpg_error_t err)
 static const char hlp_geteventcounter[] =
   "GETEVENTCOUNTER\n"
   "\n"
-  "Return a status line named EVENTCOUNTER with the current values\n"
+  "Return a status line named EVENTCOUNTER with the current values\n"
   "of all event counters.  The values are decimal numbers in the range\n"
   "0 to UINT_MAX and wrapping around to 0.  The actual values should\n"
   "not be relied upon, they shall only be used to detect a change.\n"
@@ -843,7 +791,6 @@ cmd_pksign (assuan_context_t ctx, char *line)
 
   line = skip_options (line);
 
-  p = line;
   for (p=line; *p && *p != ' ' && *p != '\t'; p++)
     ;
   *p = '\0';
@@ -919,7 +866,8 @@ cmd_pkdecrypt (assuan_context_t ctx, char *line)
 
 
 static const char hlp_genkey[] =
-  "GENKEY [--no-protection] [--preset] [--inq-passwd] [<cache_nonce>]\n"
+  "GENKEY [--no-protection] [--preset] [--inq-passwd]\n"
+  "       [--passwd-nonce=<s>] [<cache_nonce>]\n"
   "\n"
   "Generate a new key, store the secret part and return the public\n"
   "part.  Here is an example transaction:\n"
@@ -935,7 +883,8 @@ static const char hlp_genkey[] =
   "When the --preset option is used the passphrase for the generated\n"
   "key will be added to the cache.  When --inq-passwd is used an inquire\n"
   "with the keyword NEWPASSWD is used to request the passphrase for the\n"
-  "new key.\n";
+  "new key.  When a --passwd-nonce is used, the corresponding cached\n"
+  "passphrase is used to protect the new key.";
 static gpg_error_t
 cmd_genkey (assuan_context_t ctx, char *line)
 {
@@ -947,10 +896,12 @@ cmd_genkey (assuan_context_t ctx, char *line)
   unsigned char *newpasswd = NULL;
   membuf_t outbuf;
   char *cache_nonce = NULL;
+  char *passwd_nonce = NULL;
   int opt_preset;
   int opt_inq_passwd;
   size_t n;
-  char *p;
+  char *p, *pend;
+  int c;
 
   if (ctrl->restricted)
     return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
@@ -958,9 +909,23 @@ cmd_genkey (assuan_context_t ctx, char *line)
   no_protection = has_option (line, "--no-protection");
   opt_preset = has_option (line, "--preset");
   opt_inq_passwd = has_option (line, "--inq-passwd");
+  passwd_nonce = option_value (line, "--passwd-nonce");
+  if (passwd_nonce)
+    {
+      for (pend = passwd_nonce; *pend && !spacep (pend); pend++)
+        ;
+      c = *pend;
+      *pend = '\0';
+      passwd_nonce = xtrystrdup (passwd_nonce);
+      *pend = c;
+      if (!passwd_nonce)
+        {
+          rc = gpg_error_from_syserror ();
+          goto leave;
+        }
+    }
   line = skip_options (line);
 
-  p = line;
   for (p=line; *p && *p != ' ' && *p != '\t'; p++)
     ;
   *p = '\0';
@@ -995,6 +960,8 @@ cmd_genkey (assuan_context_t ctx, char *line)
         }
 
     }
+  else if (passwd_nonce)
+    newpasswd = agent_get_cache (passwd_nonce, CACHE_MODE_NONCE);
 
   rc = agent_genkey (ctrl, cache_nonce, (char*)value, valuelen, no_protection,
                      newpasswd, opt_preset, &outbuf);
@@ -1013,6 +980,7 @@ cmd_genkey (assuan_context_t ctx, char *line)
   else
     rc = write_and_clear_outbuf (ctx, &outbuf);
   xfree (cache_nonce);
+  xfree (passwd_nonce);
   return leave_cmd (ctx, rc);
 }
 
@@ -1021,8 +989,10 @@ cmd_genkey (assuan_context_t ctx, char *line)
 \f
 static const char hlp_readkey[] =
   "READKEY <hexstring_with_keygrip>\n"
+  "        --card <keyid>\n"
   "\n"
-  "Return the public key for the given keygrip.";
+  "Return the public key for the given keygrip or keyid.\n"
+  "With --card, private key file with card information will be created.";
 static gpg_error_t
 cmd_readkey (assuan_context_t ctx, char *line)
 {
@@ -1030,35 +1000,78 @@ cmd_readkey (assuan_context_t ctx, char *line)
   int rc;
   unsigned char grip[20];
   gcry_sexp_t s_pkey = NULL;
+  unsigned char *pkbuf = NULL;
+  char *serialno = NULL;
+  size_t pkbuflen;
+  const char *opt_card;
 
   if (ctrl->restricted)
     return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
 
-  rc = parse_keygrip (ctx, line, grip);
-  if (rc)
-    return rc; /* Return immediately as this is already an Assuan error code.*/
+  opt_card = has_option_name (line, "--card");
+  line = skip_options (line);
 
-  rc = agent_public_key_from_file (ctrl, grip, &s_pkey);
-  if (!rc)
+  if (opt_card)
     {
-      size_t len;
-      unsigned char *buf;
+      const char *keyid = opt_card;
 
-      len = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0);
-      assert (len);
-      buf = xtrymalloc (len);
-      if (!buf)
-        rc = gpg_error_from_syserror ();
-      else
+      rc = agent_card_getattr (ctrl, "SERIALNO", &serialno);
+      if (rc)
         {
-          len = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, buf, len);
-          assert (len);
-          rc = assuan_send_data (ctx, buf, len);
-          xfree (buf);
+          log_error (_("error getting serial number of card: %s\n"),
+                     gpg_strerror (rc));
+          goto leave;
+        }
+
+      rc = agent_card_readkey (ctrl, keyid, &pkbuf);
+      if (rc)
+        goto leave;
+      pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL);
+      rc = gcry_sexp_sscan (&s_pkey, NULL, (char*)pkbuf, pkbuflen);
+      if (rc)
+        goto leave;
+
+      if (!gcry_pk_get_keygrip (s_pkey, grip))
+        {
+          rc = gcry_pk_testkey (s_pkey);
+          if (rc == 0)
+            rc = gpg_error (GPG_ERR_INTERNAL);
+
+          goto leave;
+        }
+
+      rc = agent_write_shadow_key (grip, serialno, keyid, pkbuf, 0);
+      if (rc)
+        goto leave;
+
+      rc = assuan_send_data (ctx, pkbuf, pkbuflen);
+    }
+  else
+    {
+      rc = parse_keygrip (ctx, line, grip);
+      if (rc)
+        goto leave;
+
+      rc = agent_public_key_from_file (ctrl, grip, &s_pkey);
+      if (!rc)
+        {
+          pkbuflen = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0);
+          log_assert (pkbuflen);
+          pkbuf = xtrymalloc (pkbuflen);
+          if (!pkbuf)
+            rc = gpg_error_from_syserror ();
+          else
+            {
+              gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, pkbuf, pkbuflen);
+              rc = assuan_send_data (ctx, pkbuf, pkbuflen);
+            }
         }
-      gcry_sexp_release (s_pkey);
     }
 
+ leave:
+  xfree (serialno);
+  xfree (pkbuf);
+  gcry_sexp_release (s_pkey);
   return leave_cmd (ctx, rc);
 }
 
@@ -1188,7 +1201,7 @@ do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip, assuan_context_t ctx,
 
       if (!agent_raw_key_from_file (ctrl, grip, &key))
         {
-          ssh_get_fingerprint_string (key, &fpr);
+          ssh_get_fingerprint_string (key, GCRY_MD_MD5, &fpr);
           gcry_sexp_release (key);
         }
     }
@@ -1298,7 +1311,8 @@ cmd_keyinfo (assuan_context_t ctx, char *line)
       char *dirname;
       struct dirent *dir_entry;
 
-      dirname = make_filename_try (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, NULL);
+      dirname = make_filename_try (gnupg_homedir (),
+                                   GNUPG_PRIVATE_KEYS_DIR, NULL);
       if (!dirname)
         {
           err = gpg_error_from_syserror ();
@@ -1541,6 +1555,9 @@ cmd_get_passphrase (assuan_context_t ctx, char *line)
             {
               char *response2;
 
+              if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
+                break;
+
               rc = agent_get_passphrase (ctrl, &response2, desc2, prompt,
                                          errtext, 0,
                                         cacheid, CACHE_MODE_USER);
@@ -1774,6 +1791,24 @@ cmd_passwd (assuan_context_t ctx, char *line)
   else if (opt_verify)
     {
       /* All done.  */
+      if (passphrase)
+        {
+          if (!passwd_nonce)
+            {
+              char buf[12];
+              gcry_create_nonce (buf, 12);
+              passwd_nonce = bin2hex (buf, 12, NULL);
+            }
+          if (passwd_nonce
+              && !agent_put_cache (passwd_nonce, CACHE_MODE_NONCE,
+                                   passphrase, CACHE_TTL_NONCE))
+            {
+              assuan_write_status (ctx, "PASSWD_NONCE", passwd_nonce);
+              xfree (ctrl->server_local->last_passwd_nonce);
+              ctrl->server_local->last_passwd_nonce = passwd_nonce;
+              passwd_nonce = NULL;
+            }
+        }
     }
   else
     {
@@ -1844,6 +1879,7 @@ cmd_passwd (assuan_context_t ctx, char *line)
   gcry_sexp_release (s_skey);
   xfree (shadow_info);
   xfree (cache_nonce);
+  xfree (passwd_nonce);
   return leave_cmd (ctx, err);
 }
 
@@ -1969,13 +2005,13 @@ static const char hlp_keywrap_key[] =
   "KEYWRAP_KEY [--clear] <mode>\n"
   "\n"
   "Return a key to wrap another key.  For now the key is returned\n"
-  "verbatim and and thus makes not much sense because an eavesdropper on\n"
+  "verbatim and thus makes not much sense because an eavesdropper on\n"
   "the gpg-agent connection will see the key as well as the wrapped key.\n"
   "However, this function may either be equipped with a public key\n"
   "mechanism or not used at all if the key is a pre-shared key.  In any\n"
   "case wrapping the import and export of keys is a requirement for\n"
   "certain cryptographic validations and thus useful.  The key persists\n"
-  "a RESET command but may be cleared using the option --clear.\n"
+  "until a RESET command but may be cleared using the option --clear.\n"
   "\n"
   "Supported modes are:\n"
   "  --import  - Return a key to import a key into gpg-agent\n"
@@ -2025,7 +2061,7 @@ cmd_keywrap_key (assuan_context_t ctx, char *line)
 
 \f
 static const char hlp_import_key[] =
-  "IMPORT_KEY [--unattended] [<cache_nonce>]\n"
+  "IMPORT_KEY [--unattended] [--force] [<cache_nonce>]\n"
   "\n"
   "Import a secret key into the key store.  The key is expected to be\n"
   "encrypted using the current session's key wrapping key (cf. command\n"
@@ -2033,13 +2069,14 @@ static const char hlp_import_key[] =
   "no arguments but uses the inquiry \"KEYDATA\" to ask for the actual\n"
   "key data.  The unwrapped key must be a canonical S-expression.  The\n"
   "option --unattended tries to import the key as-is without any\n"
-  "re-encryption";
+  "re-encryption.  Existing key can be overwritten with --force.";
 static gpg_error_t
 cmd_import_key (assuan_context_t ctx, char *line)
 {
   ctrl_t ctrl = assuan_get_pointer (ctx);
   gpg_error_t err;
   int opt_unattended;
+  int force;
   unsigned char *wrappedkey = NULL;
   size_t wrappedkeylen;
   gcry_cipher_hd_t cipherhd = NULL;
@@ -2063,9 +2100,9 @@ cmd_import_key (assuan_context_t ctx, char *line)
     }
 
   opt_unattended = has_option (line, "--unattended");
+  force = has_option (line, "--force");
   line = skip_options (line);
 
-  p = line;
   for (p=line; *p && *p != ' ' && *p != '\t'; p++)
     ;
   *p = '\0';
@@ -2146,7 +2183,7 @@ cmd_import_key (assuan_context_t ctx, char *line)
 
       xfree (key);
       key = NULL;
-      err = convert_from_openpgp (ctrl, openpgp_sexp, grip,
+      err = convert_from_openpgp (ctrl, openpgp_sexp, force, grip,
                                   ctrl->server_local->keydesc, cache_nonce,
                                   &key, opt_unattended? NULL : &passphrase);
       if (err)
@@ -2177,7 +2214,7 @@ cmd_import_key (assuan_context_t ctx, char *line)
     }
   else
     {
-      if (!agent_key_available (grip))
+      if (!force && !agent_key_available (grip))
         err = gpg_error (GPG_ERR_EEXIST);
       else
         {
@@ -2197,12 +2234,12 @@ cmd_import_key (assuan_context_t ctx, char *line)
   if (passphrase)
     {
       err = agent_protect (key, passphrase, &finalkey, &finalkeylen,
-                           ctrl->s2k_count);
+                           ctrl->s2k_count, -1);
       if (!err)
-        err = agent_write_private_key (grip, finalkey, finalkeylen, 0);
+        err = agent_write_private_key (grip, finalkey, finalkeylen, force);
     }
   else
-    err = agent_write_private_key (grip, key, realkeylen, 0);
+    err = agent_write_private_key (grip, key, realkeylen, force);
 
  leave:
   gcry_sexp_release (openpgp_sexp);
@@ -2225,7 +2262,12 @@ static const char hlp_export_key[] =
   "Export a secret key from the key store.  The key will be encrypted\n"
   "using the current session's key wrapping key (cf. command KEYWRAP_KEY)\n"
   "using the AESWRAP-128 algorithm.  The caller needs to retrieve that key\n"
-  "prior to using this command.  The function takes the keygrip as argument.\n";
+  "prior to using this command.  The function takes the keygrip as argument.\n"
+  "\n"
+  "If --openpgp is used, the secret key material will be exported in RFC 4880\n"
+  "compatible passphrase-protected form.  Without --openpgp, the secret key\n"
+  "material will be exported in the clear (after prompting the user to unlock\n"
+  "it, if needed).\n";
 static gpg_error_t
 cmd_export_key (assuan_context_t ctx, char *line)
 {
@@ -2388,29 +2430,38 @@ cmd_export_key (assuan_context_t ctx, char *line)
 
 \f
 static const char hlp_delete_key[] =
-  "DELETE_KEY [--force] <hexstring_with_keygrip>\n"
+  "DELETE_KEY [--force|--stub-only] <hexstring_with_keygrip>\n"
   "\n"
-  "Delete a secret key from the key store.\n"
-  "Unless --force is used the agent asks the user for confirmation.\n";
+  "Delete a secret key from the key store.  If --force is used\n"
+  "and a loopback pinentry is allowed, the agent will not ask\n"
+  "the user for confirmation.  If --stub-only is used the key will\n"
+  "only be deleted if it is a reference to a token.";
 static gpg_error_t
 cmd_delete_key (assuan_context_t ctx, char *line)
 {
   ctrl_t ctrl = assuan_get_pointer (ctx);
   gpg_error_t err;
-  int force;
+  int force, stub_only;
   unsigned char grip[20];
 
   if (ctrl->restricted)
     return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
 
   force = has_option (line, "--force");
+  stub_only = has_option (line, "--stub-only");
   line = skip_options (line);
 
+  /* If the use of a loopback pinentry has been disabled, we assume
+   * that a silent deletion of keys shall also not be allowed.  */
+  if (!opt.allow_loopback_pinentry)
+    force = 0;
+
   err = parse_keygrip (ctx, line, grip);
   if (err)
     goto leave;
 
-  err = agent_delete_key (ctrl, ctrl->server_local->keydesc, grip, force );
+  err = agent_delete_key (ctrl, ctrl->server_local->keydesc, grip,
+                          force, stub_only);
   if (err)
     goto leave;
 
@@ -2423,6 +2474,12 @@ cmd_delete_key (assuan_context_t ctx, char *line)
 
 
 \f
+#if SIZEOF_TIME_T > SIZEOF_UNSIGNED_LONG
+#define KEYTOCARD_TIMESTAMP_FORMAT "(10:created-at10:%010llu))"
+#else
+#define KEYTOCARD_TIMESTAMP_FORMAT "(10:created-at10:%010lu))"
+#endif
+
 static const char hlp_keytocard[] =
   "KEYTOCARD [--force] <hexstring_with_keygrip> <serialno> <id> <timestamp>\n"
   "\n";
@@ -2435,7 +2492,7 @@ cmd_keytocard (assuan_context_t ctx, char *line)
   unsigned char grip[20];
   gcry_sexp_t s_skey = NULL;
   unsigned char *keydata;
-  size_t keydatalen, timestamplen;
+  size_t keydatalen;
   const char *serialno, *timestamp_str, *id;
   unsigned char *shadow_info = NULL;
   time_t timestamp;
@@ -2448,11 +2505,15 @@ cmd_keytocard (assuan_context_t ctx, char *line)
 
   err = parse_keygrip (ctx, line, grip);
   if (err)
-    return err;
+    goto leave;
 
   if (agent_key_available (grip))
-    return gpg_error (GPG_ERR_NO_SECKEY);
+    {
+      err =gpg_error (GPG_ERR_NO_SECKEY);
+      goto leave;
+    }
 
+  /* Fixme: Replace the parsing code by split_fields().  */
   line += 40;
   while (*line && (*line == ' ' || *line == '\t'))
     line++;
@@ -2460,7 +2521,10 @@ cmd_keytocard (assuan_context_t ctx, char *line)
   while (*line && (*line != ' ' && *line != '\t'))
     line++;
   if (!*line)
-    return gpg_error (GPG_ERR_MISSING_VALUE);
+    {
+      err = gpg_error (GPG_ERR_MISSING_VALUE);
+      goto leave;
+    }
   *line = '\0';
   line++;
   while (*line && (*line == ' ' || *line == '\t'))
@@ -2469,7 +2533,10 @@ cmd_keytocard (assuan_context_t ctx, char *line)
   while (*line && (*line != ' ' && *line != '\t'))
     line++;
   if (!*line)
-    return gpg_error (GPG_ERR_MISSING_VALUE);
+    {
+      err = gpg_error (GPG_ERR_MISSING_VALUE);
+      goto leave;
+    }
   *line = '\0';
   line++;
   while (*line && (*line == ' ' || *line == '\t'))
@@ -2479,9 +2546,12 @@ cmd_keytocard (assuan_context_t ctx, char *line)
     line++;
   if (*line)
     *line = '\0';
-  timestamplen = line - timestamp_str;
-  if (timestamplen != 15)
-    return gpg_error (GPG_ERR_INV_VALUE);
+
+  if ((timestamp = isotime2epoch (timestamp_str)) == (time_t)(-1))
+    {
+      err = gpg_error (GPG_ERR_INV_TIME);
+      goto leave;
+    }
 
   err = agent_key_from_file (ctrl, NULL, ctrl->server_local->keydesc, grip,
                              &shadow_info, CACHE_MODE_IGNORE, NULL,
@@ -2489,34 +2559,36 @@ cmd_keytocard (assuan_context_t ctx, char *line)
   if (err)
     {
       xfree (shadow_info);
-      return err;
+      goto leave;
     }
   if (shadow_info)
     {
       /* Key is on a smartcard already.  */
       xfree (shadow_info);
       gcry_sexp_release (s_skey);
-      return gpg_error (GPG_ERR_UNUSABLE_SECKEY);
+      err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
+      goto leave;
     }
 
   keydatalen =  gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0);
   keydata = xtrymalloc_secure (keydatalen + 30);
   if (keydata == NULL)
     {
+      err = gpg_error_from_syserror ();
       gcry_sexp_release (s_skey);
-      return gpg_error_from_syserror ();
+      goto leave;
     }
 
   gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, keydata, keydatalen);
   gcry_sexp_release (s_skey);
   keydatalen--;                        /* Decrement for last '\0'.  */
   /* Add timestamp "created-at" in the private key */
-  timestamp = isotime2epoch (timestamp_str);
-  snprintf (keydata+keydatalen-1, 30, "(10:created-at10:%010lu))", timestamp);
+  snprintf (keydata+keydatalen-1, 30, KEYTOCARD_TIMESTAMP_FORMAT, timestamp);
   keydatalen += 10 + 19 - 1;
   err = divert_writekey (ctrl, force, serialno, id, keydata, keydatalen);
   xfree (keydata);
 
+ leave:
   return leave_cmd (ctx, err);
 }
 
@@ -2580,9 +2652,9 @@ static const char hlp_putval[] =
   "try to connect to that daemon.  Only if that fails they may start\n"
   "an own instance of the service daemon. \n"
   "\n"
-  "KEY is an an arbitrary symbol with the same syntax rules as keys\n"
+  "KEY is an arbitrary symbol with the same syntax rules as keys\n"
   "for shell environment variables.  PERCENT_ESCAPED_VALUE is the\n"
-  "corresponsing value; they should be similar to the values of\n"
+  "corresponding value; they should be similar to the values of\n"
   "envronment variables but gpg-agent does not enforce any\n"
   "restrictions.  If that value is not given any value under that KEY\n"
   "is removed from this special environment.";
@@ -2667,14 +2739,13 @@ static const char hlp_updatestartuptty[] =
 static gpg_error_t
 cmd_updatestartuptty (assuan_context_t ctx, char *line)
 {
-  static const char *names[] =
-    { "GPG_TTY", "DISPLAY", "TERM", "XAUTHORITY", "PINENTRY_USER_DATA", NULL };
   ctrl_t ctrl = assuan_get_pointer (ctx);
   gpg_error_t err = 0;
   session_env_t se;
-  int idx;
   char *lc_ctype = NULL;
   char *lc_messages = NULL;
+  int iterator;
+  const char *name;
 
   (void)line;
 
@@ -2685,11 +2756,12 @@ cmd_updatestartuptty (assuan_context_t ctx, char *line)
   if (!se)
     err = gpg_error_from_syserror ();
 
-  for (idx=0; !err && names[idx]; idx++)
+  iterator = 0;
+  while (!err && (name = session_env_list_stdenvnames (&iterator, NULL)))
     {
-      const char *value = session_env_getenv (ctrl->session_env, names[idx]);
+      const char *value = session_env_getenv (ctrl->session_env, name);
       if (value)
-        err = session_env_setenv (se, names[idx], value);
+        err = session_env_setenv (se, name, value);
     }
 
   if (!err && ctrl->lc_ctype)
@@ -2779,6 +2851,8 @@ static const char hlp_getinfo[] =
   "  std_startup_env - List the standard startup environment.\n"
   "  cmd_has_option\n"
   "              - Returns OK if the command CMD implements the option OPT.\n"
+  "  connections - Return number of active connections.\n"
+  "  jent_active - Returns OK if Libgcrypt's JENT is active.\n"
   "  restricted  - Returns OK if the connection is in restricted mode.\n";
 static gpg_error_t
 cmd_getinfo (assuan_context_t ctx, char *line)
@@ -2911,6 +2985,32 @@ cmd_getinfo (assuan_context_t ctx, char *line)
             }
         }
     }
+  else if (!strcmp (line, "connections"))
+    {
+      char numbuf[20];
+
+      snprintf (numbuf, sizeof numbuf, "%d",
+                get_agent_active_connection_count ());
+      rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
+    }
+  else if (!strcmp (line, "jent_active"))
+    {
+#if GCRYPT_VERSION_NUMBER >= 0x010800
+      char *buf;
+      char *fields[5];
+
+      buf = gcry_get_config (0, "rng-type");
+      if (buf
+          && split_fields_colon (buf, fields, DIM (fields)) >= 5
+          && atoi (fields[4]) > 0)
+        rc = 0;
+      else
+        rc = gpg_error (GPG_ERR_FALSE);
+      gcry_free (buf);
+#else
+      rc = gpg_error (GPG_ERR_FALSE);
+#endif
+    }
   else
     rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
   return rc;
@@ -2987,7 +3087,7 @@ option_handler (assuan_context_t ctx, const char *key, const char *value)
       err = session_env_setenv (ctrl->session_env, "PINENTRY_USER_DATA", value);
     }
   else if (!strcmp (key, "use-cache-for-signing"))
-    ctrl->server_local->use_cache_for_signing = *value? atoi (value) : 0;
+    ctrl->server_local->use_cache_for_signing = *value? !!atoi (value) : 0;
   else if (!strcmp (key, "allow-pinentry-notify"))
     ctrl->server_local->allow_pinentry_notify = 1;
   else if (!strcmp (key, "pinentry-mode"))
@@ -3047,7 +3147,30 @@ io_monitor (assuan_context_t ctx, void *hook, int direction,
 
   (void) hook;
 
-  /* Note that we only check for the uppercase name.  This allows to
+  /* We want to suppress all Assuan log messages for connections from
+   * self.  However, assuan_get_pid works only after
+   * assuan_accept. Now, assuan_accept already logs a line ending with
+   * the process id.  We use this hack here to get the peers pid so
+   * that we can compare it to our pid.  We should add an assuan
+   * function to return the pid for a file descriptor and use that to
+   * detect connections to self.  */
+  if (ctx && !ctrl->server_local->greeting_seen
+      && direction == ASSUAN_IO_TO_PEER)
+    {
+      ctrl->server_local->greeting_seen = 1;
+      if (linelen > 32
+          && !strncmp (line, "OK Pleased to meet you, process ", 32)
+          && strtoul (line+32, NULL, 10) == getpid ())
+        return ASSUAN_IO_MONITOR_NOLOG;
+    }
+
+
+  /* Do not log self-connections.  This makes the log cleaner because
+   * we won't see the check-our-own-socket calls.  */
+  if (ctx && ctrl->server_local->connect_from_self)
+    return ASSUAN_IO_MONITOR_NOLOG;
+
+  /* Note that we only check for the uppercase name.  This allows the user to
      see the logging for debugging if using a non-upercase command
      name. */
   if (ctx && direction == ASSUAN_IO_FROM_PEER
@@ -3141,7 +3264,7 @@ register_commands (assuan_context_t ctx)
 /* Startup the server.  If LISTEN_FD and FD is given as -1, this is a
    simple piper server, otherwise it is a regular server.  CTRL is the
    control structure for this connection; it has only the basic
-   intialization. */
+   initialization. */
 void
 start_command_handler (ctrl_t ctrl, gnupg_fd_t listen_fd, gnupg_fd_t fd)
 {
@@ -3197,12 +3320,16 @@ start_command_handler (ctrl_t ctrl, gnupg_fd_t listen_fd, gnupg_fd_t fd)
   ctrl->server_local = xcalloc (1, sizeof *ctrl->server_local);
   ctrl->server_local->assuan_ctx = ctx;
   ctrl->server_local->use_cache_for_signing = 1;
+
   ctrl->digest.raw_value = 0;
 
   assuan_set_io_monitor (ctx, io_monitor, NULL);
+  agent_set_progress_cb (progress_cb, ctrl);
 
   for (;;)
     {
+      pid_t client_pid;
+
       rc = assuan_accept (ctx);
       if (gpg_err_code (rc) == GPG_ERR_EOF || rc == -1)
         {
@@ -3214,6 +3341,13 @@ start_command_handler (ctrl_t ctrl, gnupg_fd_t listen_fd, gnupg_fd_t fd)
           break;
         }
 
+      client_pid = assuan_get_pid (ctx);
+      ctrl->server_local->connect_from_self = (client_pid == getpid ());
+      if (client_pid != ASSUAN_INVALID_PID)
+        ctrl->client_pid = (unsigned long)client_pid;
+      else
+        ctrl->client_pid = 0;
+
       rc = assuan_process (ctx);
       if (rc)
         {