s/AES/AES128/ in diagnostics and --list-config
[gnupg.git] / agent / command.c
index 1c0f574..8ae313e 100644 (file)
@@ -1,6 +1,6 @@
 /* command.c - gpg-agent command handler
  * Copyright (C) 2001, 2002, 2003, 2004, 2005,
- *               2006, 2008, 2009  Free Software Foundation, Inc.
+ *               2006, 2008, 2009, 2010  Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
 #include "agent.h"
 #include <assuan.h>
 #include "i18n.h"
+#include "cvt-openpgp.h"
 
-/* maximum allowed size of the inquired ciphertext */
+
+/* Maximum allowed size of the inquired ciphertext.  */
 #define MAXLEN_CIPHERTEXT 4096
-/* maximum allowed size of the key parameters */
+/* 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
+/* The size of the import/export KEK key (in bytes).  */
+#define KEYWRAP_KEYSIZE (128/8)
 
 #define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
 
@@ -59,12 +65,15 @@ struct server_local_s
   char *keydesc;  /* Allocated description for the next key
                      operation. */
   int pause_io_logging; /* Used to suppress I/O logging during a command */
-#ifdef HAVE_W32_SYSTEM
   int stopme;    /* If set to true the agent will be terminated after
                     the end of this session.  */
-#endif
   int allow_pinentry_notify; /* Set if pinentry notifications should
                                 be done. */
+  void *import_key;  /* Malloced KEK for the import_key command.  */
+  void *export_key;  /* Malloced KEK for the export_key command.  */
+  int allow_fully_canceled; /* Client is aware of GPG_ERR_FULLY_CANCELED.  */
+  char *last_cache_nonce;   /* Last CACHE_NOCNE sent as status (malloced).  */
+  char *last_passwd_nonce;  /* Last PASSWD_NOCNE sent as status (malloced). */
 };
 
 
@@ -146,6 +155,26 @@ write_and_clear_outbuf (assuan_context_t ctx, membuf_t *mb)
 }
 
 
+static void
+clear_nonce_cache (ctrl_t ctrl)
+{
+  if (ctrl->server_local->last_cache_nonce)
+    {
+      agent_put_cache (ctrl->server_local->last_cache_nonce,
+                       CACHE_MODE_NONCE, NULL, 0);
+      xfree (ctrl->server_local->last_cache_nonce);
+      ctrl->server_local->last_cache_nonce = NULL;
+    }
+  if (ctrl->server_local->last_passwd_nonce)
+    {
+      agent_put_cache (ctrl->server_local->last_passwd_nonce,
+                       CACHE_MODE_NONCE, NULL, 0);
+      xfree (ctrl->server_local->last_passwd_nonce);
+      ctrl->server_local->last_passwd_nonce = NULL;
+    }
+}
+
+
 static gpg_error_t
 reset_notify (assuan_context_t ctx, char *line)
 {
@@ -159,10 +188,30 @@ reset_notify (assuan_context_t ctx, char *line)
 
   xfree (ctrl->server_local->keydesc);
   ctrl->server_local->keydesc = NULL;
+
+  clear_nonce_cache (ctrl);
+
   return 0;
 }
 
 
+/* Skip over options.  
+   Blanks after the options are also removed. */
+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 */
 static int
 has_option (const char *line, const char *name)
@@ -171,6 +220,8 @@ has_option (const char *line, const char *name)
   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)));
 }
 
@@ -184,6 +235,8 @@ has_option_name (const char *line, const char *name)
   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] == '='));
 }
@@ -197,6 +250,8 @@ option_value (const char *line, const char *name)
   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] == '='))
     {
@@ -209,23 +264,6 @@ option_value (const char *line, const char *name)
 }
 
 
-/* Skip over options.  It is assumed that leading spaces have been
-   removed (this is the case for lines passed to a handler from
-   assuan).  Blanks after the options are also removed. */
-static char *
-skip_options (char *line)
-{
-  while ( *line == '-' && line[1] == '-' )
-    {
-      while (*line && !spacep (line))
-        line++;
-      while (spacep (line))
-        line++;
-    }
-  return line;
-}
-
-
 /* Replace all '+' by a blank. */
 static void
 plus_to_blank (char *s)
@@ -342,6 +380,42 @@ agent_inq_pinentry_launched (ctrl_t ctrl, unsigned long pid)
 }
 
 
+/* Helper to print a message while leaving a command.  */
+static gpg_error_t
+leave_cmd (assuan_context_t ctx, gpg_error_t err)
+{
+  if (err)
+    {
+      const char *name = assuan_get_command_name (ctx);
+      if (!name)
+        name = "?";
+
+      /* Not all users of gpg-agent know about the fully canceled
+         error code; map it back if needed.  */
+      if (gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
+        {
+          ctrl_t ctrl = assuan_get_pointer (ctx);
+
+          if (!ctrl->server_local->allow_fully_canceled)
+            err = gpg_err_make (gpg_err_source (err), GPG_ERR_CANCELED);
+        }
+
+      /* Most code from common/ does not know the error source, thus
+         we fix this here.  */
+      if (gpg_err_source (err) == GPG_ERR_SOURCE_UNKNOWN)
+        err = gpg_err_make (GPG_ERR_SOURCE_DEFAULT, gpg_err_code (err));
+
+      if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT)
+        log_error ("command '%s' failed: %s\n", name,
+                   gpg_strerror (err));
+      else
+        log_error ("command '%s' failed: %s <%s>\n", name,
+                   gpg_strerror (err), gpg_strsource (err));
+    }
+  return err;
+}
+
+
 \f
 static const char hlp_geteventcounter[] = 
   "GETEVENTCOUNTER\n"
@@ -434,10 +508,7 @@ cmd_istrusted (assuan_context_t ctx, char *line)
   else if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF )
     return gpg_error (GPG_ERR_NOT_TRUSTED);
   else
-    {
-      log_error ("command is_trusted failed: %s\n", gpg_strerror (rc));
-      return rc;
-    }
+    return leave_cmd (ctx, rc);
 }
 
 
@@ -453,9 +524,7 @@ cmd_listtrusted (assuan_context_t ctx, char *line)
   (void)line;
 
   rc = agent_listtrusted (ctx);
-  if (rc)
-    log_error ("command listtrusted failed: %s\n", gpg_strerror (rc));
-  return rc;
+  return leave_cmd (ctx, rc);
 }
 
 
@@ -496,32 +565,42 @@ cmd_marktrusted (assuan_context_t ctx, char *line)
     p++;
 
   rc = agent_marktrusted (ctrl, p, fpr, flag);
-  if (rc)
-    log_error ("command marktrusted failed: %s\n", gpg_strerror (rc));
-  return rc;
+  return leave_cmd (ctx, rc);
 }
 
 
 
 \f
 static const char hlp_havekey[] =
-  "HAVEKEY <hexstring_with_keygrip>\n"
+  "HAVEKEY <hexstrings_with_keygrips>\n"
   "\n"
-  "Return success when the secret key is available.";
+  "Return success if at least one of the secret keys with the given\n"
+  "keygrips is available.";
 static gpg_error_t
 cmd_havekey (assuan_context_t ctx, char *line)
 {
-  int rc;
+  gpg_error_t err;
   unsigned char buf[20];
 
-  rc = parse_keygrip (ctx, line, buf);
-  if (rc)
-    return rc;
-
-  if (agent_key_available (buf))
-    return gpg_error (GPG_ERR_NO_SECKEY);
+  do 
+    {
+      err = parse_keygrip (ctx, line, buf);
+      if (err)
+        return err;
+      
+      if (!agent_key_available (buf))
+        return 0; /* Found.  */
 
-  return 0;
+      while (*line && *line != ' ' && *line != '\t')
+        line++;
+      while (*line == ' ' || *line == '\t')
+        line++;
+    }
+  while (*line);
+    
+  /* No leave_cmd() here because errors are expected and would clutter
+     the log.  */
+  return gpg_error (GPG_ERR_NO_SECKEY);
 }
 
 
@@ -547,8 +626,8 @@ cmd_sigkey (assuan_context_t ctx, char *line)
 static const char hlp_setkeydesc[] = 
   "SETKEYDESC plus_percent_escaped_string\n"
   "\n"
-  "Set a description to be used for the next PKSIGN or PKDECRYPT\n"
-  "operation if this operation requires the entry of a passphrase.  If\n"
+  "Set a description to be used for the next PKSIGN, PKDECRYPT, IMPORT_KEY\n"
+  "or EXPORT_KEY operation if this operation requires a passphrase.  If\n"
   "this command is not used a default text will be used.  Note, that\n"
   "this description implictly selects the label used for the entry\n"
   "box; if the string contains the string PIN (which in general will\n"
@@ -556,8 +635,8 @@ static const char hlp_setkeydesc[] =
   "\"passphrase\" is used.  The description string should not contain\n"
   "blanks unless they are percent or '+' escaped.\n"
   "\n"
-  "The description is only valid for the next PKSIGN or PKDECRYPT\n"
-  "operation.";
+  "The description is only valid for the next PKSIGN, PKDECRYPT,\n"
+  "IMPORT_KEY or EXPORT_KEY operation.";
 static gpg_error_t
 cmd_setkeydesc (assuan_context_t ctx, char *line)
 {
@@ -589,7 +668,7 @@ cmd_setkeydesc (assuan_context_t ctx, char *line)
 
 
 static const char hlp_sethash[] =
-  "SETHASH --hash=<name>|<algonumber> <hexstring>\n"
+  "SETHASH (--hash=<name>)|(<algonumber>) <hexstring>\n"
   "\n"
   "The client can use this command to tell the server about the data\n"
   "(which usually is a hash) to be signed.";
@@ -642,6 +721,7 @@ cmd_sethash (assuan_context_t ctx, char *line)
         return set_error (GPG_ERR_UNSUPPORTED_ALGORITHM, NULL);
     }
   ctrl->digest.algo = algo;
+  ctrl->digest.raw_value = 0;
 
   /* Parse the hash value. */
   n = 0;
@@ -669,7 +749,7 @@ cmd_sethash (assuan_context_t ctx, char *line)
 
 
 static const char hlp_pksign[] = 
-  "PKSIGN [options]\n"
+  "PKSIGN [<options>] [<cache_nonce>]\n"
   "\n"
   "Perform the actual sign operation.  Neither input nor output are\n"
   "sensitive to eavesdropping.";
@@ -680,9 +760,18 @@ cmd_pksign (assuan_context_t ctx, char *line)
   cache_mode_t cache_mode = CACHE_MODE_NORMAL;
   ctrl_t ctrl = assuan_get_pointer (ctx);
   membuf_t outbuf;
+  char *cache_nonce = NULL;
+  char *p;
   
-  (void)line;
+  line = skip_options (line);
   
+  p = line;
+  for (p=line; *p && *p != ' ' && *p != '\t'; p++)
+    ;
+  *p = '\0';
+  if (*line)
+    cache_nonce = xtrystrdup (line);
+
   if (opt.ignore_cache_for_signing)
     cache_mode = CACHE_MODE_IGNORE;
   else if (!ctrl->server_local->use_cache_for_signing)
@@ -690,22 +779,22 @@ cmd_pksign (assuan_context_t ctx, char *line)
 
   init_membuf (&outbuf, 512);
 
-  rc = agent_pksign (ctrl, ctrl->server_local->keydesc,
+  rc = agent_pksign (ctrl, cache_nonce, ctrl->server_local->keydesc,
                      &outbuf, cache_mode);
   if (rc)
     clear_outbuf (&outbuf);
   else
     rc = write_and_clear_outbuf (ctx, &outbuf);
-  if (rc)
-    log_error ("command pksign failed: %s\n", gpg_strerror (rc));
+
+  xfree (cache_nonce);
   xfree (ctrl->server_local->keydesc);
   ctrl->server_local->keydesc = NULL;
-  return rc;
+  return leave_cmd (ctx, rc);
 }
 
 
 static const char hlp_pkdecrypt[] = 
-  "PKDECRYPT <options>\n"
+  "PKDECRYPT [<options>]\n"
   "\n"
   "Perform the actual decrypt operation.  Input is not\n"
   "sensitive to eavesdropping.";
@@ -735,16 +824,14 @@ cmd_pkdecrypt (assuan_context_t ctx, char *line)
     clear_outbuf (&outbuf);
   else
     rc = write_and_clear_outbuf (ctx, &outbuf);
-  if (rc)
-    log_error ("command pkdecrypt failed: %s\n", gpg_strerror (rc));
   xfree (ctrl->server_local->keydesc);
   ctrl->server_local->keydesc = NULL;
-  return rc;
+  return leave_cmd (ctx, rc);
 }
 
 
 static const char hlp_genkey[] = 
-  "GENKEY\n"
+  "GENKEY [--no-protection] [<cache_nonce>]\n"
   "\n"
   "Generate a new key, store the secret part and return the public\n"
   "part.  Here is an example transaction:\n"
@@ -762,11 +849,22 @@ cmd_genkey (assuan_context_t ctx, char *line)
 {
   ctrl_t ctrl = assuan_get_pointer (ctx);
   int rc;
+  int no_protection;
   unsigned char *value;
   size_t valuelen;
   membuf_t outbuf;
+  char *cache_nonce = NULL;
+  char *p;
+  
+  no_protection = has_option (line, "--no-protection");
+  line = skip_options (line);
 
-  (void)line;
+  p = line;
+  for (p=line; *p && *p != ' ' && *p != '\t'; p++)
+    ;
+  *p = '\0';
+  if (*line)
+    cache_nonce = xtrystrdup (line);
 
   /* First inquire the parameters */
   rc = assuan_inquire (ctx, "KEYPARAM", &value, &valuelen, MAXLEN_KEYPARAM);
@@ -775,15 +873,15 @@ cmd_genkey (assuan_context_t ctx, char *line)
 
   init_membuf (&outbuf, 512);
 
-  rc = agent_genkey (ctrl, (char*)value, valuelen, &outbuf);
+  rc = agent_genkey (ctrl, cache_nonce, (char*)value, valuelen, no_protection,
+                     &outbuf);
   xfree (value);
   if (rc)
     clear_outbuf (&outbuf);
   else
     rc = write_and_clear_outbuf (ctx, &outbuf);
-  if (rc)
-    log_error ("command genkey failed: %s\n", gpg_strerror (rc));
-  return rc;
+  xfree (cache_nonce);
+  return leave_cmd (ctx, rc);
 }
 
 
@@ -826,9 +924,7 @@ cmd_readkey (assuan_context_t ctx, char *line)
       gcry_sexp_release (s_pkey);
     }
 
-  if (rc)
-    log_error ("command readkey failed: %s\n", gpg_strerror (rc));
-  return rc;
+  return leave_cmd (ctx, rc);
 }
 
 
@@ -848,7 +944,7 @@ static const char hlp_keyinfo[] =
   "\n"
   "TYPE is describes the type of the key:\n"
   "    'D' - Regular key stored on disk,\n"
-  "    'T' - Key is stored on a smartcard (token).\n"
+  "    'T' - Key is stored on a smartcard (token),\n"
   "    '-' - Unknown type.\n"
   "\n"
   "SERIALNO is an ASCII string with the serial number of the\n"
@@ -968,7 +1064,7 @@ cmd_keyinfo (assuan_context_t ctx, char *line)
   if (dir)
     closedir (dir);
   if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)
-    log_error ("command keyinfo failed: %s\n", gpg_strerror (err));
+    leave_cmd (ctx, err);
   return err;
 }
 
@@ -1032,12 +1128,11 @@ cmd_get_passphrase (assuan_context_t ctx, char *line)
 {
   ctrl_t ctrl = assuan_get_pointer (ctx);
   int rc;
-  const char *pw;
+  char *pw;
   char *response;
   char *cacheid = NULL, *desc = NULL, *prompt = NULL, *errtext = NULL;
   const char *desc2 = _("Please re-enter this passphrase");
   char *p;
-  void *cache_marker;
   int opt_data, opt_check, opt_no_ask, opt_qualbar;
   int opt_repeat = 0;
   char *repeat_errtext = NULL;
@@ -1098,12 +1193,11 @@ cmd_get_passphrase (assuan_context_t ctx, char *line)
   if (!strcmp (desc, "X"))
     desc = NULL;
 
-  pw = cacheid ? agent_get_cache (cacheid, CACHE_MODE_NORMAL, &cache_marker)
-               : NULL;
+  pw = cacheid ? agent_get_cache (cacheid, CACHE_MODE_NORMAL) : NULL;
   if (pw)
     {
       rc = send_back_passphrase (ctx, opt_data, pw);
-      agent_unlock_cache_entry (&cache_marker);
+      xfree (pw);
     }
   else if (opt_no_ask)
     rc = gpg_error (GPG_ERR_NO_DATA);
@@ -1168,9 +1262,7 @@ cmd_get_passphrase (assuan_context_t ctx, char *line)
         }
     }
 
-  if (rc)
-    log_error ("command get_passphrase failed: %s\n", gpg_strerror (rc));
-  return rc;
+  return leave_cmd (ctx, rc);
 }
 
 
@@ -1241,9 +1333,7 @@ cmd_get_confirmation (assuan_context_t ctx, char *line)
     plus_to_blank (desc);
 
   rc = agent_get_confirmation (ctrl, desc, NULL, NULL, 0);
-  if (rc)
-    log_error ("command get_confirmation failed: %s\n", gpg_strerror (rc));
-  return rc;
+  return leave_cmd (ctx, rc);
 }
 
 
@@ -1260,54 +1350,141 @@ cmd_learn (assuan_context_t ctx, char *line)
   int rc;
 
   rc = agent_handle_learn (ctrl, has_option (line, "--send")? ctx : NULL);
-  if (rc)
-    log_error ("command learn failed: %s\n", gpg_strerror (rc));
-  return rc;
+  return leave_cmd (ctx, rc);
 }
 
 
 \f
 static const char hlp_passwd[] = 
-  "PASSWD <hexstring_with_keygrip>\n"
+  "PASSWD [--cache-nonce=<c>] [--passwd-nonce=<s>] <hexstring_with_keygrip>\n"
   "\n"
   "Change the passphrase/PIN for the key identified by keygrip in LINE.";
 static gpg_error_t
 cmd_passwd (assuan_context_t ctx, char *line)
 {
   ctrl_t ctrl = assuan_get_pointer (ctx);
-  int rc;
+  gpg_error_t err;
+  int c;
+  char *cache_nonce = NULL;
+  char *passwd_nonce = NULL;
   unsigned char grip[20];
   gcry_sexp_t s_skey = NULL;
   unsigned char *shadow_info = NULL;
+  char *passphrase = NULL;
+  char *pend;
 
-  rc = parse_keygrip (ctx, line, grip);
-  if (rc)
+  cache_nonce = option_value (line, "--cache-nonce");
+  if (cache_nonce)
+    {
+      for (pend = cache_nonce; *pend && !spacep (pend); pend++)
+        ;
+      c = *pend;
+      *pend = '\0';
+      cache_nonce = xtrystrdup (cache_nonce);
+      *pend = c;
+      if (!cache_nonce)
+        {
+          err = gpg_error_from_syserror ();
+          goto leave;
+        }
+    }
+
+  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)
+        {
+          err = gpg_error_from_syserror ();
+          goto leave;
+        }
+    }
+
+  line = skip_options (line);
+
+  err = parse_keygrip (ctx, line, grip);
+  if (err)
     goto leave;
 
   ctrl->in_passwd++;
-  rc = agent_key_from_file (ctrl, ctrl->server_local->keydesc,
-                            grip, &shadow_info, CACHE_MODE_IGNORE, NULL, 
-                            &s_skey);
-  if (rc)
+  err = agent_key_from_file (ctrl, cache_nonce, ctrl->server_local->keydesc,
+                             grip, &shadow_info, CACHE_MODE_IGNORE, NULL, 
+                             &s_skey, &passphrase);
+  if (err)
     ;
   else if (!s_skey)
     {
       log_error ("changing a smartcard PIN is not yet supported\n");
-      rc = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+      err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
     }
   else
-    rc = agent_protect_and_store (ctrl, s_skey);
+    {
+      char *newpass = NULL;
+
+      if (passwd_nonce)
+        newpass = agent_get_cache (passwd_nonce, CACHE_MODE_NONCE);
+      err = agent_protect_and_store (ctrl, s_skey, &newpass);
+      if (!err && passphrase)
+        {
+          /* A passphrase existed on the old key and the change was
+             successful.  Return a nonce for that old passphrase to
+             let the caller try to unprotect the other subkeys with
+             the same key.  */
+          if (!cache_nonce)
+            {
+              char buf[12];
+              gcry_create_nonce (buf, 12);
+              cache_nonce = bin2hex (buf, 12, NULL);
+            }
+          if (cache_nonce 
+              && !agent_put_cache (cache_nonce, CACHE_MODE_NONCE,
+                                   passphrase, 120 /*seconds*/))
+            {
+              assuan_write_status (ctx, "CACHE_NONCE", cache_nonce);
+              xfree (ctrl->server_local->last_cache_nonce);
+              ctrl->server_local->last_cache_nonce = cache_nonce;
+              cache_nonce = NULL;
+            }
+          if (newpass)
+            {
+              /* If we have a new passphrase (which might be empty) we
+                 store it under a passwd nonce so that the caller may
+                 send that nonce again to use it for another key. */
+              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,
+                                       newpass, 120 /*seconds*/))
+                {
+                  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;
+                }
+            }
+        }
+      xfree (newpass);
+    }
   ctrl->in_passwd--;
 
   xfree (ctrl->server_local->keydesc);
   ctrl->server_local->keydesc = NULL;
 
  leave:
+  xfree (passphrase);
   gcry_sexp_release (s_skey);
   xfree (shadow_info);
-  if (rc)
-    log_error ("command passwd failed: %s\n", gpg_strerror (rc));
-  return rc;
+  xfree (cache_nonce);
+  return leave_cmd (ctx, err);
 }
 
 
@@ -1372,10 +1549,7 @@ cmd_preset_passphrase (assuan_context_t ctx, char *line)
   if (!rc)
     rc = agent_put_cache (grip_clear, CACHE_MODE_ANY, passphrase, ttl);
 
-  if (rc)
-    log_error ("command preset_passphrase failed: %s\n", gpg_strerror (rc));
-
-  return rc;
+  return leave_cmd (ctx, rc);
 }
 
 
@@ -1398,6 +1572,377 @@ cmd_scd (assuan_context_t ctx, char *line)
 
 
 \f
+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"
+  "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"
+  "\n"
+  "Supported modes are:\n"
+  "  --import  - Return a key to import a key into gpg-agent\n"
+  "  --export  - Return a key to export a key from gpg-agent";
+static gpg_error_t
+cmd_keywrap_key (assuan_context_t ctx, char *line)
+{
+  ctrl_t ctrl = assuan_get_pointer (ctx);
+  gpg_error_t err = 0;
+  int clearopt = has_option (line, "--clear");
+
+
+  assuan_begin_confidential (ctx);
+  if (has_option (line, "--import"))
+    {
+      xfree (ctrl->server_local->import_key);
+      if (clearopt)
+        ctrl->server_local->import_key = NULL;
+      else if (!(ctrl->server_local->import_key = 
+                 gcry_random_bytes (KEYWRAP_KEYSIZE, GCRY_STRONG_RANDOM)))
+        err = gpg_error_from_syserror ();
+      else
+        err = assuan_send_data (ctx, ctrl->server_local->import_key,
+                                KEYWRAP_KEYSIZE);
+    }
+  else if (has_option (line, "--export"))
+    {
+      xfree (ctrl->server_local->export_key);
+      if (clearopt)
+        ctrl->server_local->export_key = NULL;
+      else if (!(ctrl->server_local->export_key = 
+            gcry_random_bytes (KEYWRAP_KEYSIZE, GCRY_STRONG_RANDOM)))
+        err = gpg_error_from_syserror ();
+      else
+        err = assuan_send_data (ctx, ctrl->server_local->export_key,
+                                KEYWRAP_KEYSIZE);
+    }
+  else
+    err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for MODE");
+  assuan_end_confidential (ctx);
+  
+  return leave_cmd (ctx, err);
+}
+
+
+\f
+static const char hlp_import_key[] =
+  "IMPORT_KEY [<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"
+  "KEYWRAP_KEY) using the AESWRAP-128 algorithm.  This function takes\n"
+  "no arguments but uses the inquiry \"KEYDATA\" to ask for the actual\n"
+  "key data.  The unwrapped key must be a canonical S-expression.";
+static gpg_error_t
+cmd_import_key (assuan_context_t ctx, char *line)
+{
+  ctrl_t ctrl = assuan_get_pointer (ctx);
+  gpg_error_t err;
+  unsigned char *wrappedkey = NULL;
+  size_t wrappedkeylen;
+  gcry_cipher_hd_t cipherhd = NULL;
+  unsigned char *key = NULL;
+  size_t keylen, realkeylen;
+  char *passphrase = NULL;
+  unsigned char *finalkey = NULL;
+  size_t finalkeylen;
+  unsigned char grip[20];
+  gcry_sexp_t openpgp_sexp = NULL;
+  char *cache_nonce = NULL;
+  char *p;
+  
+  if (!ctrl->server_local->import_key)
+    {
+      err = gpg_error (GPG_ERR_MISSING_KEY);
+      goto leave;
+    }
+
+  p = line;
+  for (p=line; *p && *p != ' ' && *p != '\t'; p++)
+    ;
+  *p = '\0';
+  if (*line)
+    cache_nonce = xtrystrdup (line);
+
+  assuan_begin_confidential (ctx);
+  err = assuan_inquire (ctx, "KEYDATA",
+                        &wrappedkey, &wrappedkeylen, MAXLEN_KEYDATA);
+  assuan_end_confidential (ctx);
+  if (err)
+    goto leave;
+  if (wrappedkeylen < 24)
+    {
+      err = gpg_error (GPG_ERR_INV_LENGTH);
+      goto leave;
+    }
+  keylen = wrappedkeylen - 8;
+  key = xtrymalloc_secure (keylen);
+  if (!key)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+
+  err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
+                          GCRY_CIPHER_MODE_AESWRAP, 0);
+  if (err)
+    goto leave;
+  err = gcry_cipher_setkey (cipherhd,
+                            ctrl->server_local->import_key, KEYWRAP_KEYSIZE);
+  if (err)
+    goto leave;
+  err = gcry_cipher_decrypt (cipherhd, key, keylen, wrappedkey, wrappedkeylen);
+  if (err)
+    goto leave;
+  gcry_cipher_close (cipherhd);
+  cipherhd = NULL;
+  xfree (wrappedkey);
+  wrappedkey = NULL;
+
+  realkeylen = gcry_sexp_canon_len (key, keylen, NULL, &err);
+  if (!realkeylen)
+    goto leave; /* Invalid canonical encoded S-expression.  */
+  
+  err = keygrip_from_canon_sexp (key, realkeylen, grip);
+  if (err)
+    {
+      /* This might be due to an unsupported S-expression format.
+         Check whether this is openpgp-private-key and trigger that
+         import code.  */
+      if (!gcry_sexp_sscan (&openpgp_sexp, NULL, key, realkeylen))
+        {
+          const char *tag;
+          size_t taglen;
+          
+          tag = gcry_sexp_nth_data (openpgp_sexp, 0, &taglen);
+          if (tag && taglen == 19 && !memcmp (tag, "openpgp-private-key", 19))
+            ;
+          else
+            {
+              gcry_sexp_release (openpgp_sexp);
+              openpgp_sexp = NULL;
+            }
+        }
+      if (!openpgp_sexp)
+        goto leave; /* Note that ERR is still set.  */
+    }
+
+
+  if (openpgp_sexp)
+    {
+      /* In most cases the key is encrypted and thus the conversion
+         function from the OpenPGP format to our internal format will
+         ask for a passphrase.  That passphrase will be returned and
+         used to protect the key using the same code as for regular
+         key import. */
+      
+      err = convert_from_openpgp (ctrl, openpgp_sexp, grip,
+                                  ctrl->server_local->keydesc, cache_nonce,
+                                  &key, &passphrase);
+      if (err)
+        goto leave;
+      realkeylen = gcry_sexp_canon_len (key, keylen, NULL, &err);
+      if (!realkeylen)
+        goto leave; /* Invalid canonical encoded S-expression.  */
+      if (passphrase)
+        {
+          if (!cache_nonce)
+            {
+              char buf[12];
+              gcry_create_nonce (buf, 12);
+              cache_nonce = bin2hex (buf, 12, NULL);
+            }
+          if (cache_nonce 
+              && !agent_put_cache (cache_nonce, CACHE_MODE_NONCE,
+                                   passphrase, 120 /*seconds*/))
+            assuan_write_status (ctx, "CACHE_NONCE", cache_nonce);
+        }
+    }
+  else
+    {
+      if (!agent_key_available (grip))
+        err = gpg_error (GPG_ERR_EEXIST);
+      else
+        err = agent_ask_new_passphrase 
+          (ctrl, _("Please enter the passphrase to protect the "
+                   "imported object within the GnuPG system."),
+           &passphrase);
+      if (err)
+        goto leave;
+    }
+
+  if (passphrase)
+    {
+      err = agent_protect (key, passphrase, &finalkey, &finalkeylen);
+      if (!err)
+        err = agent_write_private_key (grip, finalkey, finalkeylen, 0);
+    }
+  else
+    err = agent_write_private_key (grip, key, realkeylen, 0);
+
+ leave:
+  gcry_sexp_release (openpgp_sexp);
+  xfree (finalkey);
+  xfree (passphrase);
+  xfree (key);
+  gcry_cipher_close (cipherhd);
+  xfree (wrappedkey);
+  xfree (cache_nonce);
+  xfree (ctrl->server_local->keydesc);
+  ctrl->server_local->keydesc = NULL;
+  return leave_cmd (ctx, err);
+}
+
+
+\f
+static const char hlp_export_key[] =
+  "EXPORT_KEY [--cache-nonce=<nonce>] [--openpgp] <hexstring_with_keygrip>\n"
+  "\n"
+  "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";
+static gpg_error_t
+cmd_export_key (assuan_context_t ctx, char *line)
+{
+  ctrl_t ctrl = assuan_get_pointer (ctx);
+  gpg_error_t err;
+  unsigned char grip[20];
+  gcry_sexp_t s_skey = NULL;
+  unsigned char *key = NULL;
+  size_t keylen;
+  gcry_cipher_hd_t cipherhd = NULL;
+  unsigned char *wrappedkey = NULL;
+  size_t wrappedkeylen;
+  int openpgp;
+  char *cache_nonce;
+  char *passphrase = NULL;
+  
+  openpgp = has_option (line, "--openpgp");
+  cache_nonce = option_value (line, "--cache-nonce");
+  if (cache_nonce)
+    {
+      for (; *line && !spacep (line); line++)
+        ;
+      if (*line)
+        *line++ = '\0';
+      cache_nonce = xtrystrdup (cache_nonce);
+      if (!cache_nonce)
+        {
+          err = gpg_error_from_syserror ();
+          goto leave;
+        }
+    }
+  line = skip_options (line);
+
+  if (!ctrl->server_local->export_key)
+    {
+      err = gpg_error (GPG_ERR_MISSING_KEY);
+      goto leave;
+    }
+
+  err = parse_keygrip (ctx, line, grip);
+  if (err)
+    goto leave;
+
+  if (agent_key_available (grip))
+    {
+      err = gpg_error (GPG_ERR_NO_SECKEY);
+      goto leave;
+    }
+
+  /* Get the key from the file.  With the openpgp flag we also ask for
+     the passphrase so that we can use it to re-encrypt it.  */
+  err = agent_key_from_file (ctrl, NULL, ctrl->server_local->keydesc, grip,
+                             NULL, CACHE_MODE_IGNORE, NULL, &s_skey,
+                             openpgp ? &passphrase : NULL);
+  if (err)
+    goto leave;
+  if (!s_skey)
+    {
+      /* Key is on a smartcard.  Actually we should not see this here
+         because we do not pass a shadow_info variable to the above
+         function, thus it will return this error directly.  */
+      err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
+      goto leave;
+    }
+  
+  if (openpgp)
+    {
+      /* The openpgp option changes the key format into the OpenPGP
+         key transfer format.  The result is already a padded
+         canonical S-expression.  */
+      if (!passphrase)
+        {
+          err = agent_ask_new_passphrase 
+            (ctrl, _("This key (or subkey) is not protected with a passphrase."
+                     "  Please enter a new passphrase to export it."),
+             &passphrase);
+          if (err)
+            goto leave;
+        }
+      err = convert_to_openpgp (ctrl, s_skey, passphrase, &key, &keylen);
+    }
+  else
+    {
+      /* Convert into a canonical S-expression and wrap that.  */
+      err = make_canon_sexp_pad (s_skey, 1, &key, &keylen);
+    }
+  if (err)
+    goto leave;
+  gcry_sexp_release (s_skey);
+  s_skey = NULL;
+
+  err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
+                          GCRY_CIPHER_MODE_AESWRAP, 0);
+  if (err)
+    goto leave;
+  err = gcry_cipher_setkey (cipherhd,
+                            ctrl->server_local->export_key, KEYWRAP_KEYSIZE);
+  if (err)
+    goto leave;
+
+  wrappedkeylen = keylen + 8;
+  wrappedkey = xtrymalloc (wrappedkeylen);
+  if (!wrappedkey)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+
+  err = gcry_cipher_encrypt (cipherhd, wrappedkey, wrappedkeylen, key, keylen);
+  if (err)
+    goto leave;
+  xfree (key);
+  key = NULL;
+  gcry_cipher_close (cipherhd);
+  cipherhd = NULL;
+
+  assuan_begin_confidential (ctx);
+  err = assuan_send_data (ctx, wrappedkey, wrappedkeylen);
+  assuan_end_confidential (ctx);
+  
+
+ leave:
+  xfree (cache_nonce);
+  xfree (passphrase);
+  xfree (wrappedkey);
+  gcry_cipher_close (cipherhd);
+  xfree (key);
+  gcry_sexp_release (s_skey);
+  xfree (ctrl->server_local->keydesc);
+  ctrl->server_local->keydesc = NULL;
+
+  return leave_cmd (ctx, err);
+}
+
+
+
+\f
 static const char hlp_getval[] = 
   "GETVAL <key>\n"
   "\n"
@@ -1436,9 +1981,7 @@ cmd_getval (assuan_context_t ctx, char *line)
   else
     return gpg_error (GPG_ERR_NO_DATA);
 
-  if (rc)
-    log_error ("command getval failed: %s\n", gpg_strerror (rc));
-  return rc;
+  return leave_cmd (ctx, rc);
 }
 
 
@@ -1521,9 +2064,7 @@ cmd_putval (assuan_context_t ctx, char *line)
         }
     }
 
-  if (rc)
-    log_error ("command putval failed: %s\n", gpg_strerror (rc));
-  return rc;
+  return leave_cmd (ctx, rc);
 }
 
 
@@ -1590,18 +2131,20 @@ cmd_updatestartuptty (assuan_context_t ctx, char *line)
 
 
 \f
-#ifdef HAVE_W32_SYSTEM
 static const char hlp_killagent[] =
   "KILLAGENT\n"
   "\n"
-  "Under Windows we start the agent on the fly.  Thus it also make\n"
-  "sense to allow a client to stop the agent.";
+  "If the agent has been started using a standard socket\n"
+  "we allow a client to stop the agent.";
 static gpg_error_t
 cmd_killagent (assuan_context_t ctx, char *line)
 {
   ctrl_t ctrl = assuan_get_pointer (ctx);
 
   (void)line;
+  
+  if (!opt.use_standard_socket)
+    return set_error (GPG_ERR_NOT_SUPPORTED, "no --use-standard-socket");
 
   ctrl->server_local->stopme = 1;
   return gpg_error (GPG_ERR_EOF);
@@ -1611,8 +2154,8 @@ cmd_killagent (assuan_context_t ctx, char *line)
 static const char hlp_reloadagent[] =
   "RELOADAGENT\n"
   "\n"
-  "As signals are inconvenient under Windows, we provide this command\n"
-  "to allow reloading of the configuration.";
+  "This command is an alternative to SIGHUP\n"
+  "to reload the configuration.";
 static gpg_error_t
 cmd_reloadagent (assuan_context_t ctx, char *line)
 {
@@ -1622,7 +2165,6 @@ cmd_reloadagent (assuan_context_t ctx, char *line)
   agent_sighup_action ();
   return 0;
 }
-#endif /*HAVE_W32_SYSTEM*/
 
 
 \f
@@ -1637,11 +2179,15 @@ static const char hlp_getinfo[] =
   "  socket_name - Return the name of the socket.\n"
   "  ssh_socket_name - Return the name of the ssh socket.\n"
   "  scd_running - Return OK if the SCdaemon is already running.\n"
+  "  s2k_count   - Return the calibrated S2K count.\n"
+  "  std_session_env - List the standard session environment.\n"
+  "  std_startup_env - List the standard startup environment.\n"
   "  cmd_has_option\n"
-  "              - Returns OK if the command CMD implements the option OPT.";
+  "              - Returns OK if the command CMD implements the option OPT\n.";
 static gpg_error_t
 cmd_getinfo (assuan_context_t ctx, char *line)
 {
+  ctrl_t ctrl = assuan_get_pointer (ctx);
   int rc = 0;
 
   if (!strcmp (line, "version"))
@@ -1678,6 +2224,41 @@ cmd_getinfo (assuan_context_t ctx, char *line)
     {
       rc = agent_scd_check_running ()? 0 : gpg_error (GPG_ERR_GENERAL);
     }
+  else if (!strcmp (line, "s2k_count"))
+    {
+      char numbuf[50];
+
+      snprintf (numbuf, sizeof numbuf, "%lu", get_standard_s2k_count ());
+      rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
+    }
+  else if (!strcmp (line, "std_session_env")
+           || !strcmp (line, "std_startup_env"))
+    {
+      int iterator;
+      const char *name, *value;
+      char *string;
+      
+      iterator = 0; 
+      while ((name = session_env_list_stdenvnames (&iterator, NULL)))
+        {
+          value = session_env_getenv_or_default
+            (line[5] == 't'? opt.startup_env:ctrl->session_env, name, NULL);
+          if (value)
+            {
+              string = xtryasprintf ("%s=%s", name, value); 
+              if (!string)
+                rc = gpg_error_from_syserror ();
+              else
+                {
+                  rc = assuan_send_data (ctx, string, strlen (string)+1);
+                  if (!rc)
+                    rc = assuan_send_data (ctx, NULL, 0);
+                }
+              if (rc)
+                break;
+            }
+        }
+    }
   else if (!strncmp (line, "cmd_has_option", 14)
            && (line[14] == ' ' || line[14] == '\t' || !line[14]))
     {
@@ -1723,7 +2304,14 @@ option_handler (assuan_context_t ctx, const char *key, const char *value)
   ctrl_t ctrl = assuan_get_pointer (ctx);
   gpg_error_t err = 0;
 
-  if (!strcmp (key, "putenv"))
+  if (!strcmp (key, "agent-awareness"))
+    {
+      /* The value is a version string telling us of which agent
+         version the caller is aware of.  */
+      ctrl->server_local->allow_fully_canceled = 
+        gnupg_compare_version (value, "2.1.0");
+    }
+  else if (!strcmp (key, "putenv"))
     {
       /* Change the session's environment to be used for the
          Pinentry.  Valid values are:
@@ -1871,13 +2459,14 @@ register_commands (assuan_context_t ctx)
     { "INPUT",          NULL }, 
     { "OUTPUT",         NULL }, 
     { "SCD",            cmd_scd,       hlp_scd },
+    { "KEYWRAP_KEY",    cmd_keywrap_key, hlp_keywrap_key },
+    { "IMPORT_KEY",     cmd_import_key, hlp_import_key },
+    { "EXPORT_KEY",     cmd_export_key, hlp_export_key },
     { "GETVAL",         cmd_getval,    hlp_getval },
     { "PUTVAL",         cmd_putval,    hlp_putval },
     { "UPDATESTARTUPTTY",  cmd_updatestartuptty, hlp_updatestartuptty },
-#ifdef HAVE_W32_SYSTEM
     { "KILLAGENT",      cmd_killagent,  hlp_killagent },
     { "RELOADAGENT",    cmd_reloadagent,hlp_reloadagent },
-#endif
     { "GETINFO",        cmd_getinfo,   hlp_getinfo },
     { NULL }
   };
@@ -1916,10 +2505,10 @@ start_command_handler (ctrl_t ctrl, gnupg_fd_t listen_fd, gnupg_fd_t fd)
 
   if (listen_fd == GNUPG_INVALID_FD && fd == GNUPG_INVALID_FD)
     {
-      int filedes[2];
+      assuan_fd_t filedes[2];
 
-      filedes[0] = 0;
-      filedes[1] = 1;
+      filedes[0] = assuan_fdopen (0);
+      filedes[1] = assuan_fdopen (1);
       rc = assuan_init_pipe_server (ctx, filedes);
     }
   else if (listen_fd != GNUPG_INVALID_FD)
@@ -1953,9 +2542,6 @@ start_command_handler (ctrl_t ctrl, gnupg_fd_t listen_fd, gnupg_fd_t fd)
   ctrl->server_local->use_cache_for_signing = 1;
   ctrl->digest.raw_value = 0;
 
-  if (DBG_ASSUAN)
-    assuan_set_log_stream (ctx, log_get_stream ());
-
   assuan_set_io_monitor (ctx, io_monitor, NULL);
 
   for (;;)
@@ -1979,6 +2565,9 @@ start_command_handler (ctrl_t ctrl, gnupg_fd_t listen_fd, gnupg_fd_t fd)
         }
     }
 
+  /* Reset the nonce caches.  */
+  clear_nonce_cache (ctrl);
+
   /* Reset the SCD if needed. */
   agent_reset_scd (ctrl);
 
@@ -1987,10 +2576,11 @@ start_command_handler (ctrl_t ctrl, gnupg_fd_t listen_fd, gnupg_fd_t fd)
 
   /* Cleanup.  */
   assuan_release (ctx);
-#ifdef HAVE_W32_SYSTEM
+  xfree (ctrl->server_local->keydesc);
+  xfree (ctrl->server_local->import_key);
+  xfree (ctrl->server_local->export_key);
   if (ctrl->server_local->stopme)
     agent_exit (0);
-#endif
   xfree (ctrl->server_local);
   ctrl->server_local = NULL;
 }