common: New put_membuf_cb to replace static membuf_data_cb.
[gnupg.git] / agent / command.c
index 1446b90..a09da60 100644 (file)
@@ -1,6 +1,7 @@
 /* command.c - gpg-agent command handler
- * Copyright (C) 2001, 2002, 2003, 2004, 2005,
- *               2006, 2008, 2009, 2010  Free Software Foundation, Inc.
+ * Copyright (C) 2001-2011 Free Software Foundation, Inc.
+ * Copyright (C) 2001-2013 Werner Koch
+ * Copyright (C) 2015 g10 Code GmbH.
  *
  * This file is part of GnuPG.
  *
@@ -38,6 +39,8 @@
 #include <assuan.h>
 #include "i18n.h"
 #include "cvt-openpgp.h"
+#include "../common/ssh-utils.h"
+#include "../common/asshelp.h"
 
 
 /* Maximum allowed size of the inquired ciphertext.  */
 /* The size of the import/export KEK key (in bytes).  */
 #define KEYWRAP_KEYSIZE (128/8)
 
+/* A shortcut to call assuan_set_error using an gpg_err_code_t and a
+   text string.  */
 #define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
 
-
+/* Check that the maximum digest length we support has at least the
+   length of the keygrip.  */
 #if MAX_DIGEST_LEN < 20
 #error MAX_DIGEST_LEN shorter than keygrip
 #endif
 
-/* Data used to associate an Assuan context with local server data */
+/* Data used to associate an Assuan context with local server data.
+   This is this modules local part of the server_control_s struct.  */
 struct server_local_s
 {
+  /* Our Assuan context.  */
   assuan_context_t assuan_ctx;
-  int message_fd;
+
+  /* If this flag is true, the passphrase cache is used for signing
+     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;
-  char *keydesc;  /* Allocated description for the next key
-                     operation. */
-  int pause_io_logging; /* Used to suppress I/O logging during a command */
-  int stopme;    /* If set to true the agent will be terminated after
-                    the end of this session.  */
-  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.  */
+
+  /* An allocated description for the next key operation.  This is
+     used if a pinnetry needs to be popped up.  */
+  char *keydesc;
+
+  /* Flags to suppress I/O logging during a command.  */
+  int pause_io_logging;
+
+  /* If this flags is set to true the agent will be terminated after
+     the end of the current session.  */
+  int stopme;
+
+  /* Flag indicating whether pinentry notifications shall be done. */
+  int allow_pinentry_notify;
+
+  /* Malloced KEK (Key-Encryption-Key) for the import_key command.  */
+  void *import_key;
+
+  /* Malloced KEK for the export_key command.  */
+  void *export_key;
+
+  /* Client is aware of the error code GPG_ERR_FULLY_CANCELED.  */
+  int allow_fully_canceled;
+
+  /* Last CACHE_NONCE sent as status (malloced).  */
+  char *last_cache_nonce;
+
+  /* Last PASSWD_NONCE sent as status (malloced). */
+  char *last_passwd_nonce;
 };
 
 
@@ -80,7 +112,7 @@ struct putval_item_s
   struct putval_item_s *next;
   size_t off;  /* Offset to the value into DATA.  */
   size_t len;  /* Length of the value.  */
-  char d[1];   /* Key | Nul | value.  */ 
+  char d[1];   /* Key | Nul | value.  */
 };
 
 
@@ -94,14 +126,14 @@ static struct putval_item_s *putval_list;
    integers and there should be no problem if they are overflowing as
    callers need to check only whether a counter changed.  The actual
    values are not meaningful. */
-struct 
+struct
 {
   /* Incremented if any of the other counters below changed. */
   unsigned int any;
 
   /* Incremented if a key is added or removed from the internal privat
      key database. */
-  unsigned int key; 
+  unsigned int key;
 
   /* Incremented if a change of the card readers stati has been
      detected. */
@@ -127,7 +159,7 @@ clear_outbuf (membuf_t *mb)
   p = get_membuf (mb, &n);
   if (p)
     {
-      memset (p, 0, n);
+      wipememory (p, n);
       xfree (p);
     }
 }
@@ -152,6 +184,31 @@ write_and_clear_outbuf (assuan_context_t ctx, membuf_t *mb)
 }
 
 
+/* Clear the nonces used to enable the passphrase cache for certain
+   multi-command command sequences.  */
+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;
+    }
+}
+
+
+/* This function is called by Libassuan whenever thee client sends a
+   reset.  It has been registered similar to the other Assuan
+   commands.  */
 static gpg_error_t
 reset_notify (assuan_context_t ctx, char *line)
 {
@@ -165,11 +222,40 @@ reset_notify (assuan_context_t ctx, char *line)
 
   xfree (ctrl->server_local->keydesc);
   ctrl->server_local->keydesc = NULL;
+
+  clear_nonce_cache (ctrl);
+
   return 0;
 }
 
 
-/* Check whether the option NAME appears in 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)
 {
@@ -177,9 +263,12 @@ 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)));
 }
 
+
 /* 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". */
@@ -190,12 +279,15 @@ 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] == '='));
 }
 
+
 /* Return a pointer to the argument of the option with NAME.  If such
-   an option is not given, it returns NULL. */
+   an option is not given, NULL is retruned. */
 static char *
 option_value (const char *line, const char *name)
 {
@@ -203,6 +295,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] == '='))
     {
@@ -215,24 +309,7 @@ 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. */
+/* Replace all '+' by a blank in the string S. */
 static void
 plus_to_blank (char *s)
 {
@@ -263,8 +340,9 @@ parse_hexstring (assuan_context_t ctx, const char *string, size_t *len)
   return 0;
 }
 
+
 /* Parse the keygrip in STRING into the provided buffer BUF.  BUF must
-   provide space for 20 bytes. BUF is not changed if the function
+   provide space for 20 bytes.  BUF is not changed if the function
    returns an error. */
 static int
 parse_keygrip (assuan_context_t ctx, const char *string, unsigned char *buf)
@@ -286,7 +364,11 @@ parse_keygrip (assuan_context_t ctx, const char *string, unsigned char *buf)
 }
 
 
-/* Write an assuan status line. */
+/* Write an Assuan status line.  KEYWORD is the first item on the
+   status line.  The following arguments are all separated by a space
+   in the output.  The last argument must be a NULL.  Linefeeds and
+   carriage returns characters (which are not allowed in an Assuan
+   status line) are silently quoted in C-style.  */
 gpg_error_t
 agent_write_status (ctrl_t ctrl, const char *keyword, ...)
 {
@@ -299,7 +381,7 @@ agent_write_status (ctrl_t ctrl, const char *keyword, ...)
 
   va_start (arg_ptr, keyword);
 
-  p = buf; 
+  p = buf;
   n = 0;
   while ( (text = va_arg (arg_ptr, const char *)) )
     {
@@ -332,6 +414,22 @@ agent_write_status (ctrl_t ctrl, const char *keyword, ...)
 }
 
 
+/* This function is similar to print_assuan_status but takes a CTRL
+   arg instead of an assuan context as first argument.  */
+gpg_error_t
+agent_print_status (ctrl_t ctrl, const char *keyword, const char *format, ...)
+{
+  gpg_error_t err;
+  va_list arg_ptr;
+  assuan_context_t ctx = ctrl->server_local->assuan_ctx;
+
+  va_start (arg_ptr, format);
+  err = vprint_assuan_status (ctx, keyword, format, arg_ptr);
+  va_end (arg_ptr);
+  return err;
+}
+
+
 /* Helper to notify the client about a launched Pinentry.  Because
    that might disturb some older clients, this is only done if enabled
    via an option.  Returns an gpg error code. */
@@ -340,7 +438,7 @@ agent_inq_pinentry_launched (ctrl_t ctrl, unsigned long pid)
 {
   char line[100];
 
-  if (!ctrl || !ctrl->server_local 
+  if (!ctrl || !ctrl->server_local
       || !ctrl->server_local->allow_pinentry_notify)
     return 0;
   snprintf (line, DIM(line)-1, "PINENTRY_LAUNCHED %lu", pid);
@@ -358,6 +456,16 @@ leave_cmd (assuan_context_t ctx, gpg_error_t err)
       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)
@@ -375,7 +483,7 @@ leave_cmd (assuan_context_t ctx, gpg_error_t err)
 
 
 \f
-static const char hlp_geteventcounter[] = 
+static const char hlp_geteventcounter[] =
   "GETEVENTCOUNTER\n"
   "\n"
   "Return a a status line named EVENTCOUNTER with the current values\n"
@@ -392,21 +500,16 @@ static gpg_error_t
 cmd_geteventcounter (assuan_context_t ctx, char *line)
 {
   ctrl_t ctrl = assuan_get_pointer (ctx);
-  char any_counter[25];
-  char key_counter[25];
-  char card_counter[25];
 
   (void)line;
 
-  snprintf (any_counter, sizeof any_counter, "%u", eventcounter.any);
-  snprintf (key_counter, sizeof key_counter, "%u", eventcounter.key);
-  snprintf (card_counter, sizeof card_counter, "%u", eventcounter.card);
+  if (ctrl->restricted)
+    return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
 
-  return agent_write_status (ctrl, "EVENTCOUNTER",
-                             any_counter,
-                             key_counter,
-                             card_counter,
-                             NULL);
+  return agent_print_status (ctrl, "EVENTCOUNTER", "%u %u %u",
+                             eventcounter.any,
+                             eventcounter.key,
+                             eventcounter.card);
 }
 
 
@@ -420,6 +523,7 @@ bump_key_eventcounter (void)
   eventcounter.any++;
 }
 
+
 /* This function should be called for all card reader status
    changes.  This function is assured not to do any context
    switches. */
@@ -433,7 +537,7 @@ bump_card_eventcounter (void)
 
 
 \f
-static const char hlp_istrusted[] = 
+static const char hlp_istrusted[] =
   "ISTRUSTED <hexstring_with_fingerprint>\n"
   "\n"
   "Return OK when we have an entry with this fingerprint in our\n"
@@ -470,23 +574,27 @@ cmd_istrusted (assuan_context_t ctx, char *line)
 }
 
 
-static const char hlp_listtrusted[] = 
+static const char hlp_listtrusted[] =
   "LISTTRUSTED\n"
   "\n"
   "List all entries from the trustlist.";
 static gpg_error_t
 cmd_listtrusted (assuan_context_t ctx, char *line)
 {
+  ctrl_t ctrl = assuan_get_pointer (ctx);
   int rc;
-  
+
   (void)line;
 
+  if (ctrl->restricted)
+    return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
   rc = agent_listtrusted (ctx);
   return leave_cmd (ctx, rc);
 }
 
 
-static const char hlp_martrusted[] = 
+static const char hlp_martrusted[] =
   "MARKTRUSTED <hexstring_with_fingerprint> <flag> <display_name>\n"
   "\n"
   "Store a new key in into the trustlist.";
@@ -499,6 +607,9 @@ cmd_marktrusted (assuan_context_t ctx, char *line)
   char fpr[41];
   int flag;
 
+  if (ctrl->restricted)
+    return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
   /* parse the fingerprint value */
   for (p=line,n=0; hexdigitp (p); p++, n++)
     ;
@@ -513,7 +624,7 @@ cmd_marktrusted (assuan_context_t ctx, char *line)
   for (p=line; i < 40; p++, i++)
     fpr[i] = *p >= 'a'? (*p & 0xdf): *p;
   fpr[i] = 0;
-  
+
   while (spacep (p))
     p++;
   flag = *p++;
@@ -530,23 +641,35 @@ cmd_marktrusted (assuan_context_t ctx, char *line)
 
 \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;
+  do
+    {
+      err = parse_keygrip (ctx, line, buf);
+      if (err)
+        return err;
 
-  if (agent_key_available (buf))
-    return gpg_error (GPG_ERR_NO_SECKEY);
+      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);
 }
 
 
@@ -569,7 +692,7 @@ cmd_sigkey (assuan_context_t ctx, char *line)
 }
 
 
-static const char hlp_setkeydesc[] = 
+static const char hlp_setkeydesc[] =
   "SETKEYDESC plus_percent_escaped_string\n"
   "\n"
   "Set a description to be used for the next PKSIGN, PKDECRYPT, IMPORT_KEY\n"
@@ -582,7 +705,7 @@ static const char hlp_setkeydesc[] =
   "blanks unless they are percent or '+' escaped.\n"
   "\n"
   "The description is only valid for the next PKSIGN, PKDECRYPT,\n"
-  "IMPORT_KEY or EXPORT_KEY operation.";
+  "IMPORT_KEY, EXPORT_KEY, or DELETE_KEY operation.";
 static gpg_error_t
 cmd_setkeydesc (assuan_context_t ctx, char *line)
 {
@@ -596,7 +719,7 @@ cmd_setkeydesc (assuan_context_t ctx, char *line)
   if (p)
     *p = 0; /* We ignore any garbage; we might late use it for other args. */
 
-  if (!desc || !*desc)
+  if (!*desc)
     return set_error (GPG_ERR_ASS_PARAMETER, "no description given");
 
   /* Note, that we only need to replace the + characters and should
@@ -606,7 +729,16 @@ cmd_setkeydesc (assuan_context_t ctx, char *line)
   plus_to_blank (desc);
 
   xfree (ctrl->server_local->keydesc);
-  ctrl->server_local->keydesc = xtrystrdup (desc);
+
+  if (ctrl->restricted)
+    {
+      ctrl->server_local->keydesc = strconcat
+        ((ctrl->restricted == 2
+         ? _("Note: Request from the web browser.")
+         : _("Note: Request from a remote site.")  ), "%0A%0A", desc, NULL);
+    }
+  else
+    ctrl->server_local->keydesc = xtrystrdup (desc);
   if (!ctrl->server_local->keydesc)
     return out_of_core ();
   return 0;
@@ -656,7 +788,7 @@ cmd_sethash (assuan_context_t ctx, char *line)
     algo = 0;
 
   line = skip_options (line);
-  
+
   if (!algo)
     {
       /* No hash option has been given: require an algo number instead  */
@@ -677,7 +809,7 @@ cmd_sethash (assuan_context_t ctx, char *line)
   n /= 2;
   if (algo == MD_USER_TLS_MD5SHA1 && n == 36)
     ;
-  else if (n != 16 && n != 20 && n != 24 
+  else if (n != 16 && n != 20 && n != 24
            && n != 28 && n != 32 && n != 48 && n != 64)
     return set_error (GPG_ERR_ASS_PARAMETER, "unsupported length of hash");
 
@@ -694,7 +826,7 @@ cmd_sethash (assuan_context_t ctx, char *line)
 }
 
 
-static const char hlp_pksign[] = 
+static const char hlp_pksign[] =
   "PKSIGN [<options>] [<cache_nonce>]\n"
   "\n"
   "Perform the actual sign operation.  Neither input nor output are\n"
@@ -708,9 +840,9 @@ cmd_pksign (assuan_context_t ctx, char *line)
   membuf_t outbuf;
   char *cache_nonce = NULL;
   char *p;
-  
+
   line = skip_options (line);
-  
+
   p = line;
   for (p=line; *p && *p != ' ' && *p != '\t'; p++)
     ;
@@ -739,7 +871,7 @@ cmd_pksign (assuan_context_t ctx, char *line)
 }
 
 
-static const char hlp_pkdecrypt[] = 
+static const char hlp_pkdecrypt[] =
   "PKDECRYPT [<options>]\n"
   "\n"
   "Perform the actual decrypt operation.  Input is not\n"
@@ -752,55 +884,82 @@ cmd_pkdecrypt (assuan_context_t ctx, char *line)
   unsigned char *value;
   size_t valuelen;
   membuf_t outbuf;
+  int padding;
 
   (void)line;
 
   /* First inquire the data to decrypt */
-  rc = assuan_inquire (ctx, "CIPHERTEXT",
-                       &value, &valuelen, MAXLEN_CIPHERTEXT);
+  rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u", MAXLEN_CIPHERTEXT);
+  if (!rc)
+    rc = assuan_inquire (ctx, "CIPHERTEXT",
+                       &value, &valuelen, MAXLEN_CIPHERTEXT);
   if (rc)
     return rc;
 
   init_membuf (&outbuf, 512);
 
   rc = agent_pkdecrypt (ctrl, ctrl->server_local->keydesc,
-                        value, valuelen, &outbuf);
+                        value, valuelen, &outbuf, &padding);
   xfree (value);
   if (rc)
     clear_outbuf (&outbuf);
   else
-    rc = write_and_clear_outbuf (ctx, &outbuf);
+    {
+      if (padding != -1)
+        rc = print_assuan_status (ctx, "PADDING", "%d", padding);
+      else
+        rc = 0;
+      if (!rc)
+        rc = write_and_clear_outbuf (ctx, &outbuf);
+    }
   xfree (ctrl->server_local->keydesc);
   ctrl->server_local->keydesc = NULL;
   return leave_cmd (ctx, rc);
 }
 
 
-static const char hlp_genkey[] = 
-  "GENKEY [<cache_nonce>]\n"
+static const char hlp_genkey[] =
+  "GENKEY [--no-protection] [--preset] [--inq-passwd] [<cache_nonce>]\n"
   "\n"
   "Generate a new key, store the secret part and return the public\n"
   "part.  Here is an example transaction:\n"
   "\n"
   "  C: GENKEY\n"
   "  S: INQUIRE KEYPARAM\n"
-  "  C: D (genkey (rsa (nbits  1024)))\n"
+  "  C: D (genkey (rsa (nbits  2048)))\n"
   "  C: END\n"
   "  S: D (public-key\n"
   "  S: D   (rsa (n 326487324683264) (e 10001)))\n"
   "  S: OK key created\n"
-  "\n";
+  "\n"
+  "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";
 static gpg_error_t
 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;
+  unsigned char *newpasswd = NULL;
   membuf_t outbuf;
   char *cache_nonce = NULL;
+  int opt_preset;
+  int opt_inq_passwd;
+  size_t n;
   char *p;
-  
+
+  if (ctrl->restricted)
+    return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+  no_protection = has_option (line, "--no-protection");
+  opt_preset = has_option (line, "--preset");
+  opt_inq_passwd = has_option (line, "--inq-passwd");
+  line = skip_options (line);
+
   p = line;
   for (p=line; *p && *p != ' ' && *p != '\t'; p++)
     ;
@@ -809,13 +968,45 @@ cmd_genkey (assuan_context_t ctx, char *line)
     cache_nonce = xtrystrdup (line);
 
   /* First inquire the parameters */
-  rc = assuan_inquire (ctx, "KEYPARAM", &value, &valuelen, MAXLEN_KEYPARAM);
+  rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u", MAXLEN_KEYPARAM);
+  if (!rc)
+    rc = assuan_inquire (ctx, "KEYPARAM", &value, &valuelen, MAXLEN_KEYPARAM);
   if (rc)
     return rc;
 
   init_membuf (&outbuf, 512);
 
-  rc = agent_genkey (ctrl, cache_nonce, (char*)value, valuelen, &outbuf);
+  /* If requested, ask for the password to be used for the key.  If
+     this is not used the regular Pinentry mechanism is used.  */
+  if (opt_inq_passwd && !no_protection)
+    {
+      /* (N is used as a dummy) */
+      assuan_begin_confidential (ctx);
+      rc = assuan_inquire (ctx, "NEWPASSWD", &newpasswd, &n, 256);
+      assuan_end_confidential (ctx);
+      if (rc)
+        goto leave;
+      if (!*newpasswd)
+        {
+          /* Empty password given - switch to no-protection mode.  */
+          xfree (newpasswd);
+          newpasswd = NULL;
+          no_protection = 1;
+        }
+
+    }
+
+  rc = agent_genkey (ctrl, cache_nonce, (char*)value, valuelen, no_protection,
+                     newpasswd, opt_preset, &outbuf);
+
+ leave:
+  if (newpasswd)
+    {
+      /* Assuan_inquire does not allow us to read into secure memory
+         thus we need to wipe it ourself.  */
+      wipememory (newpasswd, strlen (newpasswd));
+      xfree (newpasswd);
+    }
   xfree (value);
   if (rc)
     clear_outbuf (&outbuf);
@@ -828,7 +1019,7 @@ cmd_genkey (assuan_context_t ctx, char *line)
 
 
 \f
-static const char hlp_readkey[] = 
+static const char hlp_readkey[] =
   "READKEY <hexstring_with_keygrip>\n"
   "\n"
   "Return the public key for the given keygrip.";
@@ -840,6 +1031,9 @@ cmd_readkey (assuan_context_t ctx, char *line)
   unsigned char grip[20];
   gcry_sexp_t s_pkey = NULL;
 
+  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.*/
@@ -870,23 +1064,26 @@ cmd_readkey (assuan_context_t ctx, char *line)
 
 
 \f
-static const char hlp_keyinfo[] = 
-  "KEYINFO [--list] <keygrip>\n"
+static const char hlp_keyinfo[] =
+  "KEYINFO [--[ssh-]list] [--data] [--ssh-fpr] [--with-ssh] <keygrip>\n"
   "\n"
   "Return information about the key specified by the KEYGRIP.  If the\n"
   "key is not available GPG_ERR_NOT_FOUND is returned.  If the option\n"
   "--list is given the keygrip is ignored and information about all\n"
-  "available keys are returned.  The information is returned as a\n"
-  "status line with this format:\n"
+  "available keys are returned.  If --ssh-list is given information\n"
+  "about all keys listed in the sshcontrol are returned.  With --with-ssh\n"
+  "information from sshcontrol is always added to the info. Unless --data\n"
+  "is given, the information is returned as a status line using the format:\n"
   "\n"
-  "  KEYINFO <keygrip> <type> <serialno> <idstr>\n"
+  "  KEYINFO <keygrip> <type> <serialno> <idstr> <cached> <protection> <fpr>\n"
   "\n"
   "KEYGRIP is the keygrip.\n"
   "\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"
-  "    '-' - Unknown type.\n"
+  "    'X' - Unknown type,\n"
+  "    '-' - Key is missing.\n"
   "\n"
   "SERIALNO is an ASCII string with the serial number of the\n"
   "         smartcard.  If the serial number is not known a single\n"
@@ -895,47 +1092,153 @@ static const char hlp_keyinfo[] =
   "IDSTR is the IDSTR used to distinguish keys on a smartcard.  If it\n"
   "      is not known a dash is used instead.\n"
   "\n"
+  "CACHED is 1 if the passphrase for the key was found in the key cache.\n"
+  "       If not, a '-' is used instead.\n"
+  "\n"
+  "PROTECTION describes the key protection type:\n"
+  "    'P' - The key is protected with a passphrase,\n"
+  "    'C' - The key is not protected,\n"
+  "    '-' - Unknown protection.\n"
+  "\n"
+  "FPR returns the formatted ssh-style fingerprint of the key.  It is only\n"
+  "    printed if the option --ssh-fpr has been used.  It defaults to '-'.\n"
+  "\n"
+  "TTL is the TTL in seconds for that key or '-' if n/a.\n"
+  "\n"
+  "FLAGS is a word consisting of one-letter flags:\n"
+  "      'D' - The key has been disabled,\n"
+  "      'S' - The key is listed in sshcontrol (requires --with-ssh),\n"
+  "      'c' - Use of the key needs to be confirmed,\n"
+  "      '-' - No flags given.\n"
+  "\n"
   "More information may be added in the future.";
 static gpg_error_t
-do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip)
+do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip, assuan_context_t ctx,
+                int data, int with_ssh_fpr, int in_ssh,
+                int ttl, int disabled, int confirm)
 {
   gpg_error_t err;
   char hexgrip[40+1];
+  char *fpr = NULL;
   int keytype;
   unsigned char *shadow_info = NULL;
   char *serialno = NULL;
   char *idstr = NULL;
   const char *keytypestr;
+  const char *cached;
+  const char *protectionstr;
+  char *pw;
+  int missing_key = 0;
+  char ttlbuf[20];
+  char flagsbuf[5];
 
   err = agent_key_info_from_file (ctrl, grip, &keytype, &shadow_info);
   if (err)
-    goto leave;
+    {
+      if (in_ssh && gpg_err_code (err) == GPG_ERR_NOT_FOUND)
+        missing_key = 1;
+      else
+        goto leave;
+    }
 
   /* Reformat the grip so that we use uppercase as good style. */
   bin2hex (grip, 20, hexgrip);
-      
-  if (keytype == PRIVATE_KEY_CLEAR 
-      || keytype == PRIVATE_KEY_PROTECTED)
-    keytypestr = "D";
-  else if (keytype == PRIVATE_KEY_SHADOWED)
-    keytypestr = "T";
-  else 
-    keytypestr = "-";
-      
+
+  if (ttl > 0)
+    snprintf (ttlbuf, sizeof ttlbuf, "%d", ttl);
+  else
+    strcpy (ttlbuf, "-");
+
+  *flagsbuf = 0;
+  if (disabled)
+    strcat (flagsbuf, "D");
+  if (in_ssh)
+    strcat (flagsbuf, "S");
+  if (confirm)
+    strcat (flagsbuf, "c");
+  if (!*flagsbuf)
+    strcpy (flagsbuf, "-");
+
+
+  if (missing_key)
+    {
+      protectionstr = "-"; keytypestr = "-";
+    }
+  else
+    {
+      switch (keytype)
+        {
+        case PRIVATE_KEY_CLEAR:
+        case PRIVATE_KEY_OPENPGP_NONE:
+          protectionstr = "C"; keytypestr = "D";
+          break;
+        case PRIVATE_KEY_PROTECTED: protectionstr = "P"; keytypestr = "D";
+          break;
+        case PRIVATE_KEY_SHADOWED: protectionstr = "-"; keytypestr = "T";
+          break;
+        default: protectionstr = "-"; keytypestr = "X";
+          break;
+        }
+    }
+
+  /* Compute the ssh fingerprint if requested.  */
+  if (with_ssh_fpr)
+    {
+      gcry_sexp_t key;
+
+      if (!agent_raw_key_from_file (ctrl, grip, &key))
+        {
+          ssh_get_fingerprint_string (key, &fpr);
+          gcry_sexp_release (key);
+        }
+    }
+
+  /* Here we have a little race by doing the cache check separately
+     from the retrieval function.  Given that the cache flag is only a
+     hint, it should not really matter.  */
+  pw = agent_get_cache (hexgrip, CACHE_MODE_NORMAL);
+  cached = pw ? "1" : "-";
+  xfree (pw);
+
   if (shadow_info)
     {
-      err = parse_shadow_info (shadow_info, &serialno, &idstr);
+      err = parse_shadow_info (shadow_info, &serialno, &idstr, NULL);
       if (err)
         goto leave;
     }
-      
-  err = agent_write_status (ctrl, "KEYINFO",
-                            hexgrip,
-                            keytypestr,
-                            serialno? serialno : "-",
-                            idstr? idstr : "-",
-                            NULL);
+
+  if (!data)
+    err = agent_write_status (ctrl, "KEYINFO",
+                              hexgrip,
+                              keytypestr,
+                              serialno? serialno : "-",
+                              idstr? idstr : "-",
+                              cached,
+                             protectionstr,
+                              fpr? fpr : "-",
+                              ttlbuf,
+                              flagsbuf,
+                              NULL);
+  else
+    {
+      char *string;
+
+      string = xtryasprintf ("%s %s %s %s %s %s %s %s %s\n",
+                             hexgrip, keytypestr,
+                             serialno? serialno : "-",
+                             idstr? idstr : "-", cached, protectionstr,
+                             fpr? fpr : "-",
+                             ttlbuf,
+                             flagsbuf);
+      if (!string)
+        err = gpg_error_from_syserror ();
+      else
+        err = assuan_send_data (ctx, string, strlen(string));
+      xfree (string);
+    }
+
  leave:
+  xfree (fpr);
   xfree (shadow_info);
   xfree (serialno);
   xfree (idstr);
@@ -943,6 +1246,8 @@ do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip)
 }
 
 
+/* Entry int for the command KEYINFO.  This function handles the
+   command option processing.  For details see hlp_keyinfo above.  */
 static gpg_error_t
 cmd_keyinfo (assuan_context_t ctx, char *line)
 {
@@ -951,16 +1256,48 @@ cmd_keyinfo (assuan_context_t ctx, char *line)
   unsigned char grip[20];
   DIR *dir = NULL;
   int list_mode;
+  int opt_data, opt_ssh_fpr, opt_with_ssh;
+  ssh_control_file_t cf = NULL;
+  char hexgrip[41];
+  int disabled, ttl, confirm, is_ssh;
 
-  list_mode = has_option (line, "--list");
+  if (ctrl->restricted)
+    return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+  if (has_option (line, "--ssh-list"))
+    list_mode = 2;
+  else
+    list_mode = has_option (line, "--list");
+  opt_data = has_option (line, "--data");
+  opt_ssh_fpr = has_option (line, "--ssh-fpr");
+  opt_with_ssh = has_option (line, "--with-ssh");
   line = skip_options (line);
 
-  if (list_mode)
+  if (opt_with_ssh || list_mode == 2)
+    cf = ssh_open_control_file ();
+
+  if (list_mode == 2)
+    {
+      if (cf)
+        {
+          while (!ssh_read_control_file (cf, hexgrip,
+                                         &disabled, &ttl, &confirm))
+            {
+              if (hex2bin (hexgrip, grip, 20) < 0 )
+                continue; /* Bad hex string.  */
+              err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, 1,
+                                    ttl, disabled, confirm);
+              if (err)
+                goto leave;
+            }
+        }
+      err = 0;
+    }
+  else if (list_mode)
     {
       char *dirname;
       struct dirent *dir_entry;
-      char hexgrip[41];
-      
+
       dirname = make_filename_try (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, NULL);
       if (!dirname)
         {
@@ -987,7 +1324,19 @@ cmd_keyinfo (assuan_context_t ctx, char *line)
           if ( hex2bin (hexgrip, grip, 20) < 0 )
             continue; /* Bad hex string.  */
 
-          err = do_one_keyinfo (ctrl, grip);
+          disabled = ttl = confirm = is_ssh = 0;
+          if (opt_with_ssh)
+            {
+              err = ssh_search_control_file (cf, hexgrip,
+                                             &disabled, &ttl, &confirm);
+              if (!err)
+                is_ssh = 1;
+              else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
+                goto leave;
+            }
+
+          err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, is_ssh,
+                                ttl, disabled, confirm);
           if (err)
             goto leave;
         }
@@ -998,10 +1347,23 @@ cmd_keyinfo (assuan_context_t ctx, char *line)
       err = parse_keygrip (ctx, line, grip);
       if (err)
         goto leave;
-      err = do_one_keyinfo (ctrl, grip);
+      disabled = ttl = confirm = is_ssh = 0;
+      if (opt_with_ssh)
+        {
+          err = ssh_search_control_file (cf, line,
+                                         &disabled, &ttl, &confirm);
+          if (!err)
+            is_ssh = 1;
+          else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
+            goto leave;
+        }
+
+      err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, is_ssh,
+                            ttl, disabled, confirm);
     }
-      
+
  leave:
+  ssh_close_control_file (cf);
   if (dir)
     closedir (dir);
   if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)
@@ -1011,6 +1373,7 @@ cmd_keyinfo (assuan_context_t ctx, char *line)
 
 
 \f
+/* Helper for cmd_get_passphrase.  */
 static int
 send_back_passphrase (assuan_context_t ctx, int via_data, const char *pw)
 {
@@ -1037,7 +1400,7 @@ send_back_passphrase (assuan_context_t ctx, int via_data, const char *pw)
 }
 
 
-static const char hlp_get_passphrase[] = 
+static const char hlp_get_passphrase[] =
   "GET_PASSPHRASE [--data] [--check] [--no-ask] [--repeat[=N]]\n"
   "               [--qualitybar] <cache_id>\n"
   "               [<error_message> <prompt> <description>]\n"
@@ -1069,15 +1432,17 @@ 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;
+  char *entry_errtext = NULL;
+
+  if (ctrl->restricted)
+    return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
 
   opt_data = has_option (line, "--data");
   opt_check = has_option (line, "--check");
@@ -1121,7 +1486,7 @@ cmd_get_passphrase (assuan_context_t ctx, char *line)
             }
         }
     }
-  if (!cacheid || !*cacheid || strlen (cacheid) > 50)
+  if (!*cacheid || strlen (cacheid) > 50)
     return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of cacheID");
   if (!desc)
     return set_error (GPG_ERR_ASS_PARAMETER, "no description given");
@@ -1135,12 +1500,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_USER) : 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);
@@ -1158,16 +1522,17 @@ cmd_get_passphrase (assuan_context_t ctx, char *line)
         plus_to_blank (desc);
 
     next_try:
-      rc = agent_get_passphrase (ctrl, &response, desc, prompt, 
-                                 repeat_errtext? repeat_errtext:errtext, 
-                                 opt_qualbar);
-      xfree (repeat_errtext);
-      repeat_errtext = NULL;
+      rc = agent_get_passphrase (ctrl, &response, desc, prompt,
+                                 entry_errtext? entry_errtext:errtext,
+                                 opt_qualbar, cacheid, CACHE_MODE_USER);
+      xfree (entry_errtext);
+      entry_errtext = NULL;
       if (!rc)
         {
           int i;
 
-          if (opt_check && check_passphrase_constraints (ctrl, response, 0))
+          if (opt_check
+             && check_passphrase_constraints (ctrl, response, &entry_errtext))
             {
               xfree (response);
               goto next_try;
@@ -1176,17 +1541,21 @@ 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);
+                                         errtext, 0,
+                                        cacheid, CACHE_MODE_USER);
               if (rc)
                 break;
               if (strcmp (response2, response))
                 {
                   xfree (response2);
                   xfree (response);
-                  repeat_errtext = try_percent_escape 
+                  entry_errtext = try_percent_escape
                     (_("does not match - try again"), NULL);
-                  if (!repeat_errtext)
+                  if (!entry_errtext)
                     {
                       rc = gpg_error_from_syserror ();
                       break;
@@ -1209,16 +1578,26 @@ cmd_get_passphrase (assuan_context_t ctx, char *line)
 }
 
 
-static const char hlp_clear_passphrase[] = 
-  "CLEAR_PASSPHRASE <cache_id>\n"
+static const char hlp_clear_passphrase[] =
+  "CLEAR_PASSPHRASE [--mode=normal] <cache_id>\n"
   "\n"
   "may be used to invalidate the cache entry for a passphrase.  The\n"
-  "function returns with OK even when there is no cached passphrase.";
+  "function returns with OK even when there is no cached passphrase.\n"
+  "The --mode=normal option is used to clear an entry for a cacheid\n"
+  "added by the agent.\n";
 static gpg_error_t
 cmd_clear_passphrase (assuan_context_t ctx, char *line)
 {
+  ctrl_t ctrl = assuan_get_pointer (ctx);
   char *cacheid = NULL;
   char *p;
+  int opt_normal;
+
+  if (ctrl->restricted)
+    return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+  opt_normal = has_option (line, "--mode=normal");
+  line = skip_options (line);
 
   /* parse the stuff */
   for (p=line; *p == ' '; p++)
@@ -1227,15 +1606,20 @@ cmd_clear_passphrase (assuan_context_t ctx, char *line)
   p = strchr (cacheid, ' ');
   if (p)
     *p = 0; /* ignore garbage */
-  if (!cacheid || !*cacheid || strlen (cacheid) > 50)
+  if (!*cacheid || strlen (cacheid) > 50)
     return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of cacheID");
 
-  agent_put_cache (cacheid, CACHE_MODE_USER, NULL, 0);
+  agent_put_cache (cacheid, opt_normal ? CACHE_MODE_NORMAL : CACHE_MODE_USER,
+                   NULL, 0);
+
+  agent_clear_passphrase (ctrl, cacheid,
+                         opt_normal ? CACHE_MODE_NORMAL : CACHE_MODE_USER);
+
   return 0;
 }
 
 
-static const char hlp_get_confirmation[] = 
+static const char hlp_get_confirmation[] =
   "GET_CONFIRMATION <description>\n"
   "\n"
   "This command may be used to ask for a simple confirmation.\n"
@@ -1254,6 +1638,9 @@ cmd_get_confirmation (assuan_context_t ctx, char *line)
   char *desc = NULL;
   char *p;
 
+  if (ctrl->restricted)
+    return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
   /* parse the stuff */
   for (p=line; *p == ' '; p++)
     ;
@@ -1262,7 +1649,7 @@ cmd_get_confirmation (assuan_context_t ctx, char *line)
   if (p)
     *p = 0; /* We ignore any garbage -may be later used for other args. */
 
-  if (!desc || !*desc)
+  if (!*desc)
     return set_error (GPG_ERR_ASS_PARAMETER, "no description given");
 
   if (!strcmp (desc, "X"))
@@ -1282,84 +1669,216 @@ cmd_get_confirmation (assuan_context_t ctx, char *line)
 
 \f
 static const char hlp_learn[] =
-  "LEARN [--send]\n"
+  "LEARN [--send] [--sendinfo] [--force]\n"
   "\n"
   "Learn something about the currently inserted smartcard.  With\n"
-  "--send the new certificates are send back.";
+  "--sendinfo information about the card is returned; with --send\n"
+  "the available certificates are returned as D lines; with --force\n"
+  "private key storage will be updated by the result.";
 static gpg_error_t
 cmd_learn (assuan_context_t ctx, char *line)
 {
   ctrl_t ctrl = assuan_get_pointer (ctx);
-  int rc;
+  gpg_error_t err;
+  int send, sendinfo, force;
 
-  rc = agent_handle_learn (ctrl, has_option (line, "--send")? ctx : NULL);
-  return leave_cmd (ctx, rc);
+  send = has_option (line, "--send");
+  sendinfo = send? 1 : has_option (line, "--sendinfo");
+  force = has_option (line, "--force");
+
+  if (ctrl->restricted)
+    return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+  err = agent_handle_learn (ctrl, send, sendinfo? ctx : NULL, force);
+  return leave_cmd (ctx, err);
 }
 
 
 \f
-static const char hlp_passwd[] = 
-  "PASSWD <hexstring_with_keygrip>\n"
+static const char hlp_passwd[] =
+  "PASSWD [--cache-nonce=<c>] [--passwd-nonce=<s>] [--preset]\n"
+  "       [--verify] <hexkeygrip>\n"
   "\n"
-  "Change the passphrase/PIN for the key identified by keygrip in LINE.";
+  "Change the passphrase/PIN for the key identified by keygrip in LINE.  If\n"
+  "--preset is used then the new passphrase will be added to the cache.\n"
+  "If --verify is used the command asks for the passphrase and verifies\n"
+  "that the passphrase valid.\n";
 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;
+  int opt_preset, opt_verify;
 
-  rc = parse_keygrip (ctx, line, grip);
-  if (rc)
+  if (ctrl->restricted)
+    return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+  opt_preset = has_option (line, "--preset");
+  cache_nonce = option_value (line, "--cache-nonce");
+  opt_verify = has_option (line, "--verify");
+  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, NULL, ctrl->server_local->keydesc,
-                            grip, &shadow_info, CACHE_MODE_IGNORE, NULL, 
-                            &s_skey);
-  if (rc)
+  err = agent_key_from_file (ctrl,
+                             opt_verify? NULL : cache_nonce,
+                             ctrl->server_local->keydesc,
+                             grip, &shadow_info, CACHE_MODE_IGNORE, NULL,
+                             &s_skey, &passphrase);
+  if (err)
     ;
-  else if (!s_skey)
+  else if (shadow_info)
     {
       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 if (opt_verify)
+    {
+      /* All done.  */
     }
   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, CACHE_TTL_NONCE))
+            {
+              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, 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;
+                }
+            }
+        }
+      if (!err && opt_preset)
+        {
+         char hexgrip[40+1];
+         bin2hex(grip, 20, hexgrip);
+         err = agent_put_cache (hexgrip, CACHE_MODE_ANY, newpass,
+                                 ctrl->cache_ttl_opt_preset);
+        }
+      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);
-  return leave_cmd (ctx, rc);
-}
+  xfree (cache_nonce);
+  return leave_cmd (ctx, err);
+}
 
 
-static const char hlp_preset_passphrase[] = 
-  "PRESET_PASSPHRASE <string_or_keygrip> <timeout> <hexstring>\n"
+static const char hlp_preset_passphrase[] =
+  "PRESET_PASSPHRASE [--inquire] <string_or_keygrip> <timeout> [<hexstring>]\n"
   "\n"
   "Set the cached passphrase/PIN for the key identified by the keygrip\n"
   "to passwd for the given time, where -1 means infinite and 0 means\n"
   "the default (currently only a timeout of -1 is allowed, which means\n"
   "to never expire it).  If passwd is not provided, ask for it via the\n"
-  "pinentry module.";
+  "pinentry module unless --inquire is passed in which case the passphrase\n"
+  "is retrieved from the client via a server inquire.\n";
 static gpg_error_t
 cmd_preset_passphrase (assuan_context_t ctx, char *line)
 {
+  ctrl_t ctrl = assuan_get_pointer (ctx);
   int rc;
   char *grip_clear = NULL;
-  char *passphrase = NULL;
+  unsigned char *passphrase = NULL;
   int ttl;
   size_t len;
+  int opt_inquire;
+
+  if (ctrl->restricted)
+    return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
 
   if (!opt.allow_preset_passphrase)
     return set_error (GPG_ERR_NOT_SUPPORTED, "no --allow-preset-passphrase");
 
+  opt_inquire = has_option (line, "--inquire");
+  line = skip_options (line);
   grip_clear = line;
   while (*line && (*line != ' ' && *line != '\t'))
     line++;
@@ -1369,7 +1888,7 @@ cmd_preset_passphrase (assuan_context_t ctx, char *line)
   line++;
   while (*line && (*line == ' ' || *line == '\t'))
     line++;
-  
+
   /* Currently, only infinite timeouts are allowed.  */
   ttl = -1;
   if (line[0] != '-' || line[1] != '1')
@@ -1390,23 +1909,45 @@ cmd_preset_passphrase (assuan_context_t ctx, char *line)
      required.  */
   if (*line)
     {
+      if (opt_inquire)
+        {
+         rc = set_error (GPG_ERR_ASS_PARAMETER,
+                          "both --inquire and passphrase specified");
+         goto leave;
+       }
+
       /* Do in-place conversion.  */
       passphrase = line;
       if (!hex2str (passphrase, passphrase, strlen (passphrase)+1, NULL))
         rc = set_error (GPG_ERR_ASS_PARAMETER, "invalid hexstring");
     }
+  else if (opt_inquire)
+    {
+      /* Note that the passphrase will be truncated at any null byte and the
+       * limit is 480 characters. */
+      size_t maxlen = 480;
+
+      rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%zu", maxlen);
+      if (!rc)
+       rc = assuan_inquire (ctx, "PASSPHRASE", &passphrase, &len, maxlen);
+    }
   else
     rc = set_error (GPG_ERR_NOT_IMPLEMENTED, "passphrase is required");
 
   if (!rc)
-    rc = agent_put_cache (grip_clear, CACHE_MODE_ANY, passphrase, ttl);
+    {
+      rc = agent_put_cache (grip_clear, CACHE_MODE_ANY, passphrase, ttl);
+      if (opt_inquire)
+       xfree (passphrase);
+    }
 
+leave:
   return leave_cmd (ctx, rc);
 }
 
 
 \f
-static const char hlp_scd[] = 
+static const char hlp_scd[] =
   "SCD <commands to pass to the scdaemon>\n"
   " \n"
   "This is a general quote command to redirect everything to the\n"
@@ -1417,6 +1958,9 @@ cmd_scd (assuan_context_t ctx, char *line)
   ctrl_t ctrl = assuan_get_pointer (ctx);
   int rc;
 
+  if (ctrl->restricted)
+    return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
   rc = divert_generic_cmd (ctrl, line, ctx);
 
   return rc;
@@ -1434,7 +1978,7 @@ static const char hlp_keywrap_key[] =
   "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"
@@ -1446,6 +1990,8 @@ cmd_keywrap_key (assuan_context_t ctx, char *line)
   gpg_error_t err = 0;
   int clearopt = has_option (line, "--clear");
 
+  if (ctrl->restricted)
+    return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
 
   assuan_begin_confidential (ctx);
   if (has_option (line, "--import"))
@@ -1453,7 +1999,7 @@ cmd_keywrap_key (assuan_context_t ctx, char *line)
       xfree (ctrl->server_local->import_key);
       if (clearopt)
         ctrl->server_local->import_key = NULL;
-      else if (!(ctrl->server_local->import_key = 
+      else if (!(ctrl->server_local->import_key =
                  gcry_random_bytes (KEYWRAP_KEYSIZE, GCRY_STRONG_RANDOM)))
         err = gpg_error_from_syserror ();
       else
@@ -1465,7 +2011,7 @@ cmd_keywrap_key (assuan_context_t ctx, char *line)
       xfree (ctrl->server_local->export_key);
       if (clearopt)
         ctrl->server_local->export_key = NULL;
-      else if (!(ctrl->server_local->export_key = 
+      else if (!(ctrl->server_local->export_key =
             gcry_random_bytes (KEYWRAP_KEYSIZE, GCRY_STRONG_RANDOM)))
         err = gpg_error_from_syserror ();
       else
@@ -1475,25 +2021,29 @@ cmd_keywrap_key (assuan_context_t ctx, char *line)
   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"
+  "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"
   "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.";
+  "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.  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;
@@ -1506,13 +2056,20 @@ cmd_import_key (assuan_context_t ctx, char *line)
   gcry_sexp_t openpgp_sexp = NULL;
   char *cache_nonce = NULL;
   char *p;
-  
+
+  if (ctrl->restricted)
+    return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
   if (!ctrl->server_local->import_key)
     {
       err = gpg_error (GPG_ERR_MISSING_KEY);
       goto leave;
     }
 
+  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++)
     ;
@@ -1558,7 +2115,7 @@ cmd_import_key (assuan_context_t ctx, char *line)
   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)
     {
@@ -1569,7 +2126,7 @@ cmd_import_key (assuan_context_t ctx, char *line)
         {
           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))
             ;
@@ -1591,50 +2148,66 @@ cmd_import_key (assuan_context_t ctx, char *line)
          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_openpgp (ctrl, openpgp_sexp, grip,
-                             ctrl->server_local->keydesc, cache_nonce,
-                             &key, &passphrase);
+
+      xfree (key);
+      key = NULL;
+      err = convert_from_openpgp (ctrl, openpgp_sexp, force, grip,
+                                  ctrl->server_local->keydesc, cache_nonce,
+                                  &key, opt_unattended? NULL : &passphrase);
       if (err)
         goto leave;
-      realkeylen = gcry_sexp_canon_len (key, keylen, NULL, &err);
+      realkeylen = gcry_sexp_canon_len (key, 0, NULL, &err);
       if (!realkeylen)
         goto leave; /* Invalid canonical encoded S-expression.  */
       if (passphrase)
         {
+          assert (!opt_unattended);
           if (!cache_nonce)
             {
               char buf[12];
               gcry_create_nonce (buf, 12);
               cache_nonce = bin2hex (buf, 12, NULL);
             }
-          if (cache_nonce 
+          if (cache_nonce
               && !agent_put_cache (cache_nonce, CACHE_MODE_NONCE,
-                                   passphrase, 120 /*seconds*/))
+                                   passphrase, CACHE_TTL_NONCE))
             assuan_write_status (ctx, "CACHE_NONCE", cache_nonce);
         }
     }
+  else if (opt_unattended)
+    {
+      err = set_error (GPG_ERR_ASS_PARAMETER,
+                       "\"--unattended\" may only be used with OpenPGP keys");
+      goto leave;
+    }
   else
     {
-      if (!agent_key_available (grip))
+      if (!force && !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);
+        {
+          char *prompt = xtryasprintf
+            (_("Please enter the passphrase to protect the "
+               "imported object within the %s system."), GNUPG_NAME);
+          if (!prompt)
+            err = gpg_error_from_syserror ();
+          else
+            err = agent_ask_new_passphrase (ctrl, prompt, &passphrase);
+          xfree (prompt);
+        }
       if (err)
         goto leave;
     }
 
   if (passphrase)
     {
-      err = agent_protect (key, passphrase, &finalkey, &finalkeylen);
+      err = agent_protect (key, passphrase, &finalkey, &finalkeylen,
+                           ctrl->s2k_count);
       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);
@@ -1652,7 +2225,7 @@ cmd_import_key (assuan_context_t ctx, char *line)
 
 \f
 static const char hlp_export_key[] =
-  "EXPORT_KEY <hexstring_with_keygrip>\n"
+  "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"
@@ -1670,10 +2243,37 @@ cmd_export_key (assuan_context_t ctx, char *line)
   gcry_cipher_hd_t cipherhd = NULL;
   unsigned char *wrappedkey = NULL;
   size_t wrappedkeylen;
+  int openpgp;
+  char *cache_nonce;
+  char *passphrase = NULL;
+  unsigned char *shadow_info = NULL;
+  char *pend;
+  int c;
+
+  if (ctrl->restricted)
+    return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+  openpgp = has_option (line, "--openpgp");
+  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;
+        }
+    }
+  line = skip_options (line);
 
   if (!ctrl->server_local->export_key)
     {
-      err = gpg_error (GPG_ERR_MISSING_KEY);
+      err = set_error (GPG_ERR_MISSING_KEY, "did you run KEYWRAP_KEY ?");
       goto leave;
     }
 
@@ -1687,20 +2287,60 @@ cmd_export_key (assuan_context_t ctx, char *line)
       goto leave;
     }
 
-  err = agent_key_from_file (ctrl, NULL, ctrl->server_local->keydesc, grip,
-                             NULL, CACHE_MODE_IGNORE, NULL, &s_skey);
+  /* 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, cache_nonce,
+                             ctrl->server_local->keydesc, grip,
+                             &shadow_info, CACHE_MODE_IGNORE, NULL, &s_skey,
+                             openpgp ? &passphrase : NULL);
   if (err)
     goto leave;
-  if (!s_skey)
+  if (shadow_info)
     {
-      /* 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.  */
+      /* Key is on a smartcard.  */
       err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
       goto leave;
     }
 
-  err = make_canon_sexp_pad (s_skey, 1, &key, &keylen);
+  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);
+      if (!err && 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, CACHE_TTL_NONCE))
+            {
+              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;
+            }
+        }
+    }
+  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);
@@ -1734,22 +2374,160 @@ cmd_export_key (assuan_context_t ctx, char *line)
   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;
+  xfree (shadow_info);
+
+  return leave_cmd (ctx, err);
+}
+
+
+\f
+static const char hlp_delete_key[] =
+  "DELETE_KEY [--force] <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";
+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;
+  unsigned char grip[20];
+
+  if (ctrl->restricted)
+    return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+  force = has_option (line, "--force");
+  line = skip_options (line);
+
+  err = parse_keygrip (ctx, line, grip);
+  if (err)
+    goto leave;
+
+  err = agent_delete_key (ctrl, ctrl->server_local->keydesc, grip, force );
+  if (err)
+    goto leave;
+
+ leave:
+  xfree (ctrl->server_local->keydesc);
+  ctrl->server_local->keydesc = NULL;
+
   return leave_cmd (ctx, err);
 }
 
 
+\f
+static const char hlp_keytocard[] =
+  "KEYTOCARD [--force] <hexstring_with_keygrip> <serialno> <id> <timestamp>\n"
+  "\n";
+static gpg_error_t
+cmd_keytocard (assuan_context_t ctx, char *line)
+{
+  ctrl_t ctrl = assuan_get_pointer (ctx);
+  int force;
+  gpg_error_t err = 0;
+  unsigned char grip[20];
+  gcry_sexp_t s_skey = NULL;
+  unsigned char *keydata;
+  size_t keydatalen, timestamplen;
+  const char *serialno, *timestamp_str, *id;
+  unsigned char *shadow_info = NULL;
+  time_t timestamp;
+
+  if (ctrl->restricted)
+    return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
+  force = has_option (line, "--force");
+  line = skip_options (line);
+
+  err = parse_keygrip (ctx, line, grip);
+  if (err)
+    return err;
+
+  if (agent_key_available (grip))
+    return gpg_error (GPG_ERR_NO_SECKEY);
+
+  line += 40;
+  while (*line && (*line == ' ' || *line == '\t'))
+    line++;
+  serialno = line;
+  while (*line && (*line != ' ' && *line != '\t'))
+    line++;
+  if (!*line)
+    return gpg_error (GPG_ERR_MISSING_VALUE);
+  *line = '\0';
+  line++;
+  while (*line && (*line == ' ' || *line == '\t'))
+    line++;
+  id = line;
+  while (*line && (*line != ' ' && *line != '\t'))
+    line++;
+  if (!*line)
+    return gpg_error (GPG_ERR_MISSING_VALUE);
+  *line = '\0';
+  line++;
+  while (*line && (*line == ' ' || *line == '\t'))
+    line++;
+  timestamp_str = line;
+  while (*line && (*line != ' ' && *line != '\t'))
+    line++;
+  if (*line)
+    *line = '\0';
+  timestamplen = line - timestamp_str;
+  if (timestamplen != 15)
+    return gpg_error (GPG_ERR_INV_VALUE);
+
+  err = agent_key_from_file (ctrl, NULL, ctrl->server_local->keydesc, grip,
+                             &shadow_info, CACHE_MODE_IGNORE, NULL,
+                             &s_skey, NULL);
+  if (err)
+    {
+      xfree (shadow_info);
+      return err;
+    }
+  if (shadow_info)
+    {
+      /* Key is on a smartcard already.  */
+      xfree (shadow_info);
+      gcry_sexp_release (s_skey);
+      return gpg_error (GPG_ERR_UNUSABLE_SECKEY);
+    }
+
+  keydatalen =  gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0);
+  keydata = xtrymalloc_secure (keydatalen + 30);
+  if (keydata == NULL)
+    {
+      gcry_sexp_release (s_skey);
+      return gpg_error_from_syserror ();
+    }
+
+  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);
+  keydatalen += 10 + 19 - 1;
+  err = divert_writekey (ctrl, force, serialno, id, keydata, keydatalen);
+  xfree (keydata);
+
+  return leave_cmd (ctx, err);
+}
+
 
 \f
-static const char hlp_getval[] = 
+static const char hlp_getval[] =
   "GETVAL <key>\n"
   "\n"
   "Return the value for KEY from the special environment as created by\n"
@@ -1757,24 +2535,28 @@ static const char hlp_getval[] =
 static gpg_error_t
 cmd_getval (assuan_context_t ctx, char *line)
 {
+  ctrl_t ctrl = assuan_get_pointer (ctx);
   int rc = 0;
   char *key = NULL;
   char *p;
   struct putval_item_s *vl;
 
+  if (ctrl->restricted)
+    return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
   for (p=line; *p == ' '; p++)
     ;
   key = p;
   p = strchr (key, ' ');
   if (p)
     {
-      *p++ = 0; 
+      *p++ = 0;
       for (; *p == ' '; p++)
         ;
       if (*p)
         return set_error (GPG_ERR_ASS_PARAMETER, "too many arguments");
     }
-  if (!key || !*key)
+  if (!*key)
     return set_error (GPG_ERR_ASS_PARAMETER, "no key given");
 
 
@@ -1791,7 +2573,7 @@ cmd_getval (assuan_context_t ctx, char *line)
 }
 
 
-static const char hlp_putval[] = 
+static const char hlp_putval[] =
   "PUTVAL <key> [<percent_escaped_value>]\n"
   "\n"
   "The gpg-agent maintains a kind of environment which may be used to\n"
@@ -1805,13 +2587,14 @@ static const char hlp_putval[] =
   "\n"
   "KEY is an 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.";
 static gpg_error_t
 cmd_putval (assuan_context_t ctx, char *line)
 {
+  ctrl_t ctrl = assuan_get_pointer (ctx);
   int rc = 0;
   char *key = NULL;
   char *value = NULL;
@@ -1819,13 +2602,16 @@ cmd_putval (assuan_context_t ctx, char *line)
   char *p;
   struct putval_item_s *vl, *vlprev;
 
+  if (ctrl->restricted)
+    return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
   for (p=line; *p == ' '; p++)
     ;
   key = p;
   p = strchr (key, ' ');
   if (p)
     {
-      *p++ = 0; 
+      *p++ = 0;
       for (; *p == ' '; p++)
         ;
       if (*p)
@@ -1837,7 +2623,7 @@ cmd_putval (assuan_context_t ctx, char *line)
           valuelen = percent_plus_unescape_inplace (value, 0);
         }
     }
-  if (!key || !*key)
+  if (!*key)
     return set_error (GPG_ERR_ASS_PARAMETER, "no key given");
 
 
@@ -1854,7 +2640,7 @@ cmd_putval (assuan_context_t ctx, char *line)
       xfree (vl);
     }
 
-  if (valuelen) /* Add entry. */  
+  if (valuelen) /* Add entry. */
     {
       vl = xtrymalloc (sizeof *vl + strlen (key) + valuelen);
       if (!vl)
@@ -1876,7 +2662,7 @@ cmd_putval (assuan_context_t ctx, char *line)
 
 
 \f
-static const char hlp_updatestartuptty[] = 
+static const char hlp_updatestartuptty[] =
   "UPDATESTARTUPTTY\n"
   "\n"
   "Set startup TTY and X11 DISPLAY variables to the values of this\n"
@@ -1886,7 +2672,7 @@ static const char hlp_updatestartuptty[] =
 static gpg_error_t
 cmd_updatestartuptty (assuan_context_t ctx, char *line)
 {
-  static const char *names[] = 
+  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;
@@ -1894,9 +2680,12 @@ cmd_updatestartuptty (assuan_context_t ctx, char *line)
   int idx;
   char *lc_ctype = NULL;
   char *lc_messages = NULL;
-  
+
   (void)line;
 
+  if (ctrl->restricted)
+    return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
   se = session_env_new ();
   if (!se)
     err = gpg_error_from_syserror ();
@@ -1908,14 +2697,14 @@ cmd_updatestartuptty (assuan_context_t ctx, char *line)
         err = session_env_setenv (se, names[idx], value);
     }
 
-  if (!err && ctrl->lc_ctype) 
+  if (!err && ctrl->lc_ctype)
     if (!(lc_ctype = xtrystrdup (ctrl->lc_ctype)))
       err = gpg_error_from_syserror ();
 
   if (!err && ctrl->lc_messages)
     if (!(lc_messages = xtrystrdup (ctrl->lc_messages)))
       err = gpg_error_from_syserror ();
-   
+
   if (err)
     {
       session_env_release (se);
@@ -1940,20 +2729,20 @@ cmd_updatestartuptty (assuan_context_t ctx, char *line)
 static const char hlp_killagent[] =
   "KILLAGENT\n"
   "\n"
-  "If the agent has been started using a standard socket\n"
-  "we allow a client to stop the agent.";
+  "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");
+
+  if (ctrl->restricted)
+    return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
 
   ctrl->server_local->stopme = 1;
-  return gpg_error (GPG_ERR_EOF);
+  assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1);
+  return 0;
 }
 
 
@@ -1965,16 +2754,20 @@ static const char hlp_reloadagent[] =
 static gpg_error_t
 cmd_reloadagent (assuan_context_t ctx, char *line)
 {
-  (void)ctx;
+  ctrl_t ctrl = assuan_get_pointer (ctx);
+
   (void)line;
 
+  if (ctrl->restricted)
+    return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
+
   agent_sighup_action ();
   return 0;
 }
 
 
 \f
-static const char hlp_getinfo[] = 
+static const char hlp_getinfo[] =
   "GETINFO <what>\n"
   "\n"
   "Multipurpose function to return a variety of information.\n"
@@ -1986,10 +2779,12 @@ static const char hlp_getinfo[] =
   "  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_env_names   - List the names of the standard environment.\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\n.";
+  "              - Returns OK if the command CMD implements the option OPT.\n"
+  "  restricted  - Returns OK if the connection is in restricted mode.\n";
 static gpg_error_t
 cmd_getinfo (assuan_context_t ctx, char *line)
 {
@@ -2001,6 +2796,54 @@ cmd_getinfo (assuan_context_t ctx, char *line)
       const char *s = VERSION;
       rc = assuan_send_data (ctx, s, strlen (s));
     }
+  else if (!strncmp (line, "cmd_has_option", 14)
+           && (line[14] == ' ' || line[14] == '\t' || !line[14]))
+    {
+      char *cmd, *cmdopt;
+      line += 14;
+      while (*line == ' ' || *line == '\t')
+        line++;
+      if (!*line)
+        rc = gpg_error (GPG_ERR_MISSING_VALUE);
+      else
+        {
+          cmd = line;
+          while (*line && (*line != ' ' && *line != '\t'))
+            line++;
+          if (!*line)
+            rc = gpg_error (GPG_ERR_MISSING_VALUE);
+          else
+            {
+              *line++ = 0;
+              while (*line == ' ' || *line == '\t')
+                line++;
+              if (!*line)
+                rc = gpg_error (GPG_ERR_MISSING_VALUE);
+              else
+                {
+                  cmdopt = line;
+                  if (!command_has_option (cmd, cmdopt))
+                    rc = 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, "restricted"))
+    {
+      rc = ctrl->restricted? 0 : gpg_error (GPG_ERR_GENERAL);
+    }
+  else if (ctrl->restricted)
+    {
+      rc = gpg_error (GPG_ERR_FORBIDDEN);
+    }
+  /* All sub-commands below are not allowed in restricted mode.  */
   else if (!strcmp (line, "pid"))
     {
       char numbuf[50];
@@ -2030,12 +2873,20 @@ 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"))
+  else if (!strcmp (line, "std_env_names"))
     {
-      char numbuf[50];
+      int iterator;
+      const char *name;
 
-      snprintf (numbuf, sizeof numbuf, "%lu", get_standard_s2k_count ());
-      rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
+      iterator = 0;
+      while ((name = session_env_list_stdenvnames (&iterator, NULL)))
+        {
+          rc = assuan_send_data (ctx, name, strlen (name)+1);
+          if (!rc)
+            rc = assuan_send_data (ctx, NULL, 0);
+          if (rc)
+            break;
+        }
     }
   else if (!strcmp (line, "std_session_env")
            || !strcmp (line, "std_startup_env"))
@@ -2043,15 +2894,15 @@ cmd_getinfo (assuan_context_t ctx, char *line)
       int iterator;
       const char *name, *value;
       char *string;
-      
-      iterator = 0; 
+
+      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); 
+              string = xtryasprintf ("%s=%s", name, value);
               if (!string)
                 rc = gpg_error_from_syserror ();
               else
@@ -2065,38 +2916,6 @@ cmd_getinfo (assuan_context_t ctx, char *line)
             }
         }
     }
-  else if (!strncmp (line, "cmd_has_option", 14)
-           && (line[14] == ' ' || line[14] == '\t' || !line[14]))
-    {
-      char *cmd, *cmdopt;
-      line += 14;
-      while (*line == ' ' || *line == '\t')
-        line++;
-      if (!*line)
-        rc = gpg_error (GPG_ERR_MISSING_VALUE);
-      else
-        {
-          cmd = line;
-          while (*line && (*line != ' ' && *line != '\t'))
-            line++;
-          if (!*line)
-            rc = gpg_error (GPG_ERR_MISSING_VALUE);
-          else
-            {
-              *line++ = 0;
-              while (*line == ' ' || *line == '\t')
-                line++;
-              if (!*line)
-                rc = gpg_error (GPG_ERR_MISSING_VALUE);
-              else
-                {
-                  cmdopt = line;
-                  if (!command_has_option (cmd, cmdopt))
-                    rc = gpg_error (GPG_ERR_GENERAL);
-                }
-            }
-        }
-    }
   else
     rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
   return rc;
@@ -2104,13 +2923,27 @@ cmd_getinfo (assuan_context_t ctx, char *line)
 
 
 \f
+/* This function is called by Libassuan to parse the OPTION command.
+   It has been registered similar to the other Assuan commands.  */
 static gpg_error_t
 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 (ctrl->restricted)
+    {
+      err = gpg_error (GPG_ERR_FORBIDDEN);
+    }
+  /* All options below are not allowed in restricted mode.  */
+  else if (!strcmp (key, "putenv"))
     {
       /* Change the session's environment to be used for the
          Pinentry.  Valid values are:
@@ -2162,6 +2995,28 @@ option_handler (assuan_context_t ctx, const char *key, const char *value)
     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"))
+    {
+      int tmp = parse_pinentry_mode (value);
+      if (tmp == -1)
+        err = gpg_error (GPG_ERR_INV_VALUE);
+      else if (tmp == PINENTRY_MODE_LOOPBACK && !opt.allow_loopback_pinentry)
+        err = gpg_error (GPG_ERR_NOT_SUPPORTED);
+      else
+        ctrl->pinentry_mode = tmp;
+    }
+  else if (!strcmp (key, "cache-ttl-opt-preset"))
+    {
+      ctrl->cache_ttl_opt_preset = *value? atoi (value) : 0;
+    }
+  else if (!strcmp (key, "s2k-count"))
+    {
+      ctrl->s2k_count = *value? strtoul(value, NULL, 10) : 0;
+      if (ctrl->s2k_count && ctrl->s2k_count < 65536)
+        {
+         ctrl->s2k_count = 0;
+        }
+    }
   else
     err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
 
@@ -2177,7 +3032,7 @@ static void
 post_cmd_notify (assuan_context_t ctx, gpg_error_t err)
 {
   ctrl_t ctrl = assuan_get_pointer (ctx);
-  
+
   (void)err;
 
   /* Switch off any I/O monitor controlled logging pausing. */
@@ -2194,7 +3049,7 @@ io_monitor (assuan_context_t ctx, void *hook, int direction,
             const char *line, size_t linelen)
 {
   ctrl_t ctrl = assuan_get_pointer (ctx);
-  
+
   (void) hook;
 
   /* Note that we only check for the uppercase name.  This allows to
@@ -2221,12 +3076,13 @@ command_has_option (const char *cmd, const char *cmdopt)
       if (!strcmp (cmdopt, "repeat"))
           return 1;
     }
-      
+
   return 0;
 }
 
 
-/* Tell the assuan library about our commands */
+/* Tell Libassuan about our commands.  Also register the other Assuan
+   handlers. */
 static int
 register_commands (assuan_context_t ctx)
 {
@@ -2255,18 +3111,20 @@ register_commands (assuan_context_t ctx)
     { "MARKTRUSTED",    cmd_marktrusted, hlp_martrusted },
     { "LEARN",          cmd_learn,     hlp_learn },
     { "PASSWD",         cmd_passwd,    hlp_passwd },
-    { "INPUT",          NULL }, 
-    { "OUTPUT",         NULL }, 
+    { "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 },
+    { "DELETE_KEY",     cmd_delete_key, hlp_delete_key },
     { "GETVAL",         cmd_getval,    hlp_getval },
     { "PUTVAL",         cmd_putval,    hlp_putval },
     { "UPDATESTARTUPTTY",  cmd_updatestartuptty, hlp_updatestartuptty },
     { "KILLAGENT",      cmd_killagent,  hlp_killagent },
     { "RELOADAGENT",    cmd_reloadagent,hlp_reloadagent },
     { "GETINFO",        cmd_getinfo,   hlp_getinfo },
+    { "KEYTOCARD",      cmd_keytocard, hlp_keytocard },
     { NULL }
   };
   int i, rc;
@@ -2277,7 +3135,7 @@ register_commands (assuan_context_t ctx)
                                     table[i].help);
       if (rc)
         return rc;
-    } 
+    }
   assuan_register_post_cmd_notify (ctx, post_cmd_notify);
   assuan_register_reset_notify (ctx, reset_notify);
   assuan_register_option_handler (ctx, option_handler);
@@ -2295,6 +3153,12 @@ start_command_handler (ctrl_t ctrl, gnupg_fd_t listen_fd, gnupg_fd_t fd)
   int rc;
   assuan_context_t ctx = NULL;
 
+  if (ctrl->restricted)
+    {
+      if (agent_copy_startup_env (ctrl))
+        return;
+    }
+
   rc = assuan_new (&ctx);
   if (rc)
     {
@@ -2316,7 +3180,7 @@ start_command_handler (ctrl_t ctrl, gnupg_fd_t listen_fd, gnupg_fd_t fd)
       /* FIXME: Need to call assuan_sock_set_nonce for Windows.  But
         this branch is currently not used.  */
     }
-  else 
+  else
     {
       rc = assuan_init_socket_server (ctx, fd, ASSUAN_SOCKET_SERVER_ACCEPTED);
     }
@@ -2337,7 +3201,6 @@ start_command_handler (ctrl_t ctrl, gnupg_fd_t listen_fd, gnupg_fd_t fd)
   assuan_set_pointer (ctx, ctrl);
   ctrl->server_local = xcalloc (1, sizeof *ctrl->server_local);
   ctrl->server_local->assuan_ctx = ctx;
-  ctrl->server_local->message_fd = -1;
   ctrl->server_local->use_cache_for_signing = 1;
   ctrl->digest.raw_value = 0;
 
@@ -2355,7 +3218,7 @@ start_command_handler (ctrl_t ctrl, gnupg_fd_t listen_fd, gnupg_fd_t fd)
           log_info ("Assuan accept problem: %s\n", gpg_strerror (rc));
           break;
         }
-      
+
       rc = assuan_process (ctx);
       if (rc)
         {
@@ -2364,6 +3227,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);
 
@@ -2381,3 +3247,23 @@ start_command_handler (ctrl_t ctrl, gnupg_fd_t listen_fd, gnupg_fd_t fd)
   ctrl->server_local = NULL;
 }
 
+
+/* Helper for the pinentry loopback mode.  It merely passes the
+   parameters on to the client.  */
+gpg_error_t
+pinentry_loopback(ctrl_t ctrl, const char *keyword,
+                  unsigned char **buffer, size_t *size,
+                  size_t max_length)
+{
+  gpg_error_t rc;
+  assuan_context_t ctx = ctrl->server_local->assuan_ctx;
+
+  rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%zu", max_length);
+  if (rc)
+    return rc;
+
+  assuan_begin_confidential (ctx);
+  rc = assuan_inquire (ctx, keyword, buffer, size, max_length);
+  assuan_end_confidential (ctx);
+  return rc;
+}