g10: Add openpgp_protected flag to agent secret key export functions
[gnupg.git] / agent / command.c
index c875f55..de5b184 100644 (file)
@@ -1,6 +1,7 @@
 /* command.c - gpg-agent command handler
  * 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.
  *
@@ -40,6 +41,7 @@
 #include "cvt-openpgp.h"
 #include "../common/ssh-utils.h"
 #include "../common/asshelp.h"
+#include "../common/server-help.h"
 
 
 /* Maximum allowed size of the inquired ciphertext.  */
@@ -205,7 +207,7 @@ clear_nonce_cache (ctrl_t ctrl)
 }
 
 
-/* This function is called by Libassuan whenever thee client sends a
+/* This function is called by Libassuan whenever the client sends a
    reset.  It has been registered similar to the other Assuan
    commands.  */
 static gpg_error_t
@@ -228,86 +230,6 @@ reset_notify (assuan_context_t ctx, char *line)
 }
 
 
-/* Skip over options in LINE.
-
-   Blanks after the options are also removed.  Options are indicated
-   by two leading dashes followed by a string consisting of non-space
-   characters.  The special option "--" indicates an explicit end of
-   options; all what follows will not be considered an option.  The
-   first no-option string also indicates the end of option parsing. */
-static char *
-skip_options (const char *line)
-{
-  while (spacep (line))
-    line++;
-  while ( *line == '-' && line[1] == '-' )
-    {
-      while (*line && !spacep (line))
-        line++;
-      while (spacep (line))
-        line++;
-    }
-  return (char*)line;
-}
-
-
-/* Check whether the option NAME appears in LINE.  An example for a
-   line with options is:
-     --algo=42 --data foo bar
-   This function would then only return true if NAME is "data".  */
-static int
-has_option (const char *line, const char *name)
-{
-  const char *s;
-  int n = strlen (name);
-
-  s = strstr (line, name);
-  if (s && s >= skip_options (line))
-    return 0;
-  return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n)));
-}
-
-
-/* Same as has_option but does only test for the name of the option
-   and ignores an argument, i.e. with NAME being "--hash" it would
-   return true for "--hash" as well as for "--hash=foo". */
-static int
-has_option_name (const char *line, const char *name)
-{
-  const char *s;
-  int n = strlen (name);
-
-  s = strstr (line, name);
-  if (s && s >= skip_options (line))
-    return 0;
-  return (s && (s == line || spacep (s-1))
-          && (!s[n] || spacep (s+n) || s[n] == '='));
-}
-
-
-/* Return a pointer to the argument of the option with NAME.  If such
-   an option is not given, NULL is retruned. */
-static char *
-option_value (const char *line, const char *name)
-{
-  char *s;
-  int n = strlen (name);
-
-  s = strstr (line, name);
-  if (s && s >= skip_options (line))
-    return NULL;
-  if (s && (s == line || spacep (s-1))
-      && s[n] && (spacep (s+n) || s[n] == '='))
-    {
-      s += n + 1;
-      s += strspn (s, " ");
-      if (*s && !spacep(s))
-        return s;
-    }
-  return NULL;
-}
-
-
 /* Replace all '+' by a blank in the string S. */
 static void
 plus_to_blank (char *s)
@@ -445,6 +367,23 @@ agent_inq_pinentry_launched (ctrl_t ctrl, unsigned long pid)
 }
 
 
+/* An agent progress callback for Libgcrypt.  This has been registered
+ * to be called via the progress dispatcher mechanism from
+ * gpg-agent.c  */
+static void
+progress_cb (ctrl_t ctrl, const char *what, int printchar,
+             int current, int total)
+{
+  if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx)
+    ;
+  else if (printchar == '\n' && what && !strcmp (what, "primegen"))
+    agent_print_status (ctrl, "PROGRESS", "%.20s X 100 100", what);
+  else
+    agent_print_status (ctrl, "PROGRESS", "%.20s %c %d %d",
+                        what, printchar=='\n'?'X':printchar, current, total);
+}
+
+
 /* Helper to print a message while leaving a command.  */
 static gpg_error_t
 leave_cmd (assuan_context_t ctx, gpg_error_t err)
@@ -718,7 +657,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
@@ -730,8 +669,12 @@ cmd_setkeydesc (assuan_context_t ctx, char *line)
   xfree (ctrl->server_local->keydesc);
 
   if (ctrl->restricted)
-    ctrl->server_local->keydesc = strconcat
-      ("Note: Request from a remote site.\n\n", desc, NULL);
+    {
+      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)
@@ -914,22 +857,25 @@ cmd_pkdecrypt (assuan_context_t ctx, char *line)
 
 
 static const char hlp_genkey[] =
-  "GENKEY [--no-protection] [--preset] [<cache_nonce>]\n"
+  "GENKEY [--no-protection] [--preset] [--inq-passwd]\n"
+  "       [--passwd-nonce=<s>] [<cache_nonce>]\n"
   "\n"
   "Generate a new key, store the secret part and return the public\n"
   "part.  Here is an example transaction:\n"
   "\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"
   "When the --preset option is used the passphrase for the generated\n"
-  "key will be added to the cache.\n"
-  "\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.  When a --passwd-nonce is used, the corresponding cached\n"
+  "passphrase is used to protect the new key.";
 static gpg_error_t
 cmd_genkey (assuan_context_t ctx, char *line)
 {
@@ -938,16 +884,37 @@ cmd_genkey (assuan_context_t ctx, char *line)
   int no_protection;
   unsigned char *value;
   size_t valuelen;
+  unsigned char *newpasswd = NULL;
   membuf_t outbuf;
   char *cache_nonce = NULL;
+  char *passwd_nonce = NULL;
   int opt_preset;
-  char *p;
+  int opt_inq_passwd;
+  size_t n;
+  char *p, *pend;
+  int c;
 
   if (ctrl->restricted)
     return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
 
-  opt_preset = has_option (line, "--preset");
   no_protection = has_option (line, "--no-protection");
+  opt_preset = has_option (line, "--preset");
+  opt_inq_passwd = has_option (line, "--inq-passwd");
+  passwd_nonce = option_value (line, "--passwd-nonce");
+  if (passwd_nonce)
+    {
+      for (pend = passwd_nonce; *pend && !spacep (pend); pend++)
+        ;
+      c = *pend;
+      *pend = '\0';
+      passwd_nonce = xtrystrdup (passwd_nonce);
+      *pend = c;
+      if (!passwd_nonce)
+        {
+          rc = gpg_error_from_syserror ();
+          goto leave;
+        }
+    }
   line = skip_options (line);
 
   p = line;
@@ -966,14 +933,46 @@ cmd_genkey (assuan_context_t ctx, char *line)
 
   init_membuf (&outbuf, 512);
 
+  /* 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;
+        }
+
+    }
+  else if (passwd_nonce)
+    newpasswd = agent_get_cache (passwd_nonce, CACHE_MODE_NONCE);
+
   rc = agent_genkey (ctrl, cache_nonce, (char*)value, valuelen, no_protection,
-                     opt_preset, &outbuf);
+                     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);
   else
     rc = write_and_clear_outbuf (ctx, &outbuf);
   xfree (cache_nonce);
+  xfree (passwd_nonce);
   return leave_cmd (ctx, rc);
 }
 
@@ -1129,7 +1128,9 @@ do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip, assuan_context_t ctx,
     {
       switch (keytype)
         {
-        case PRIVATE_KEY_CLEAR: protectionstr = "C"; keytypestr = "D";
+        case PRIVATE_KEY_CLEAR:
+        case PRIVATE_KEY_OPENPGP_NONE:
+          protectionstr = "C"; keytypestr = "D";
           break;
         case PRIVATE_KEY_PROTECTED: protectionstr = "P"; keytypestr = "D";
           break;
@@ -1257,7 +1258,8 @@ cmd_keyinfo (assuan_context_t ctx, char *line)
       char *dirname;
       struct dirent *dir_entry;
 
-      dirname = make_filename_try (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, NULL);
+      dirname = make_filename_try (gnupg_homedir (),
+                                   GNUPG_PRIVATE_KEYS_DIR, NULL);
       if (!dirname)
         {
           err = gpg_error_from_syserror ();
@@ -1398,7 +1400,7 @@ cmd_get_passphrase (assuan_context_t ctx, char *line)
   char *p;
   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));
@@ -1445,7 +1447,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");
@@ -1459,7 +1461,7 @@ cmd_get_passphrase (assuan_context_t ctx, char *line)
   if (!strcmp (desc, "X"))
     desc = NULL;
 
-  pw = cacheid ? agent_get_cache (cacheid, CACHE_MODE_NORMAL) : NULL;
+  pw = cacheid ? agent_get_cache (cacheid, CACHE_MODE_USER) : NULL;
   if (pw)
     {
       rc = send_back_passphrase (ctx, opt_data, pw);
@@ -1482,15 +1484,16 @@ cmd_get_passphrase (assuan_context_t ctx, char *line)
 
     next_try:
       rc = agent_get_passphrase (ctrl, &response, desc, prompt,
-                                 repeat_errtext? repeat_errtext:errtext,
-                                 opt_qualbar);
-      xfree (repeat_errtext);
-      repeat_errtext = NULL;
+                                 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;
@@ -1499,17 +1502,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;
@@ -1560,11 +1567,15 @@ 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, 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;
 }
 
@@ -1599,7 +1610,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"))
@@ -1619,35 +1630,40 @@ cmd_get_confirmation (assuan_context_t ctx, char *line)
 
 \f
 static const char hlp_learn[] =
-  "LEARN [--send][--sendinfo]\n"
+  "LEARN [--send] [--sendinfo] [--force]\n"
   "\n"
   "Learn something about the currently inserted smartcard.  With\n"
   "--sendinfo information about the card is returned; with --send\n"
-  "the available certificates are returned as D lines.";
+  "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);
   gpg_error_t err;
-  int send, sendinfo;
+  int send, sendinfo, force;
 
   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);
+  err = agent_handle_learn (ctrl, send, sendinfo? ctx : NULL, force);
   return leave_cmd (ctx, err);
 }
 
 
 \f
 static const char hlp_passwd[] =
-  "PASSWD [--cache-nonce=<c>] [--passwd-nonce=<s>] [--preset] <hexkeygrip>\n"
+  "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. When\n"
-  "--preset is used then the new passphrase will be added to the cache.\n";
+  "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)
 {
@@ -1661,13 +1677,14 @@ cmd_passwd (assuan_context_t ctx, char *line)
   unsigned char *shadow_info = NULL;
   char *passphrase = NULL;
   char *pend;
-  int opt_preset;
+  int opt_preset, opt_verify;
 
   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++)
@@ -1706,7 +1723,9 @@ cmd_passwd (assuan_context_t ctx, char *line)
     goto leave;
 
   ctrl->in_passwd++;
-  err = agent_key_from_file (ctrl, cache_nonce, ctrl->server_local->keydesc,
+  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)
@@ -1716,6 +1735,28 @@ cmd_passwd (assuan_context_t ctx, char *line)
       log_error ("changing a smartcard PIN is not yet supported\n");
       err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
     }
+  else if (opt_verify)
+    {
+      /* All done.  */
+      if (passphrase)
+        {
+          if (!passwd_nonce)
+            {
+              char buf[12];
+              gcry_create_nonce (buf, 12);
+              passwd_nonce = bin2hex (buf, 12, NULL);
+            }
+          if (passwd_nonce
+              && !agent_put_cache (passwd_nonce, CACHE_MODE_NONCE,
+                                   passphrase, CACHE_TTL_NONCE))
+            {
+              assuan_write_status (ctx, "PASSWD_NONCE", passwd_nonce);
+              xfree (ctrl->server_local->last_passwd_nonce);
+              ctrl->server_local->last_passwd_nonce = passwd_nonce;
+              passwd_nonce = NULL;
+            }
+        }
+    }
   else
     {
       char *newpass = NULL;
@@ -1767,12 +1808,12 @@ cmd_passwd (assuan_context_t ctx, char *line)
             }
         }
       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--;
@@ -1785,6 +1826,7 @@ cmd_passwd (assuan_context_t ctx, char *line)
   gcry_sexp_release (s_skey);
   xfree (shadow_info);
   xfree (cache_nonce);
+  xfree (passwd_nonce);
   return leave_cmd (ctx, err);
 }
 
@@ -1916,7 +1958,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"
@@ -1966,7 +2008,7 @@ cmd_keywrap_key (assuan_context_t ctx, char *line)
 
 \f
 static const char hlp_import_key[] =
-  "IMPORT_KEY [--unattended] [<cache_nonce>]\n"
+  "IMPORT_KEY [--unattended] [--force] [<cache_nonce>]\n"
   "\n"
   "Import a secret key into the key store.  The key is expected to be\n"
   "encrypted using the current session's key wrapping key (cf. command\n"
@@ -1974,13 +2016,14 @@ static const char hlp_import_key[] =
   "no arguments but uses the inquiry \"KEYDATA\" to ask for the actual\n"
   "key data.  The unwrapped key must be a canonical S-expression.  The\n"
   "option --unattended tries to import the key as-is without any\n"
-  "re-encryption";
+  "re-encryption.  Existing key can be overwritten with --force.";
 static gpg_error_t
 cmd_import_key (assuan_context_t ctx, char *line)
 {
   ctrl_t ctrl = assuan_get_pointer (ctx);
   gpg_error_t err;
   int opt_unattended;
+  int force;
   unsigned char *wrappedkey = NULL;
   size_t wrappedkeylen;
   gcry_cipher_hd_t cipherhd = NULL;
@@ -2004,6 +2047,7 @@ cmd_import_key (assuan_context_t ctx, char *line)
     }
 
   opt_unattended = has_option (line, "--unattended");
+  force = has_option (line, "--force");
   line = skip_options (line);
 
   p = line;
@@ -2087,7 +2131,7 @@ cmd_import_key (assuan_context_t ctx, char *line)
 
       xfree (key);
       key = NULL;
-      err = convert_from_openpgp (ctrl, openpgp_sexp, grip,
+      err = convert_from_openpgp (ctrl, openpgp_sexp, force, grip,
                                   ctrl->server_local->keydesc, cache_nonce,
                                   &key, opt_unattended? NULL : &passphrase);
       if (err)
@@ -2118,7 +2162,7 @@ cmd_import_key (assuan_context_t ctx, char *line)
     }
   else
     {
-      if (!agent_key_available (grip))
+      if (!force && !agent_key_available (grip))
         err = gpg_error (GPG_ERR_EEXIST);
       else
         {
@@ -2138,12 +2182,12 @@ cmd_import_key (assuan_context_t ctx, char *line)
   if (passphrase)
     {
       err = agent_protect (key, passphrase, &finalkey, &finalkeylen,
-                           ctrl->s2k_count);
+                           ctrl->s2k_count, -1);
       if (!err)
-        err = agent_write_private_key (grip, finalkey, finalkeylen, 0);
+        err = agent_write_private_key (grip, finalkey, finalkeylen, force);
     }
   else
-    err = agent_write_private_key (grip, key, realkeylen, 0);
+    err = agent_write_private_key (grip, key, realkeylen, force);
 
  leave:
   gcry_sexp_release (openpgp_sexp);
@@ -2166,7 +2210,12 @@ static const char hlp_export_key[] =
   "Export a secret key from the key store.  The key will be encrypted\n"
   "using the current session's key wrapping key (cf. command KEYWRAP_KEY)\n"
   "using the AESWRAP-128 algorithm.  The caller needs to retrieve that key\n"
-  "prior to using this command.  The function takes the keygrip as argument.\n";
+  "prior to using this command.  The function takes the keygrip as argument.\n"
+  "\n"
+  "If --openpgp is used, the secret key material will be exported in RFC 4880\n"
+  "compatible passphrase-protected form.  Without --openpgp, the secret key\n"
+  "material will be exported in the clear (after prompting the user to unlock\n"
+  "it, if needed).\n";
 static gpg_error_t
 cmd_export_key (assuan_context_t ctx, char *line)
 {
@@ -2329,27 +2378,35 @@ cmd_export_key (assuan_context_t ctx, char *line)
 
 \f
 static const char hlp_delete_key[] =
-  "DELETE_KEY <hexstring_with_keygrip>\n"
+  "DELETE_KEY [--force] <hexstring_with_keygrip>\n"
   "\n"
-  "Delete a secret key from the key store.\n"
-  "As safeguard the agent asks the user for confirmation.\n";
+  "Delete a secret key from the key store.  If --force is used\n"
+  "and a loopback pinentry is allowed, the agent will not ask\n"
+  "the user for confirmation.";
 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);
 
+  /* If the use of a loopback pinentry has been disabled, we assume
+   * that a silent deletion of keys shall also not be allowed.  */
+  if (!opt.allow_loopback_pinentry)
+    force = 0;
+
   err = parse_keygrip (ctx, line, grip);
   if (err)
     goto leave;
 
-  err = agent_delete_key (ctrl, ctrl->server_local->keydesc, grip);
+  err = agent_delete_key (ctrl, ctrl->server_local->keydesc, grip, force );
   if (err)
     goto leave;
 
@@ -2373,12 +2430,10 @@ cmd_keytocard (assuan_context_t ctx, char *line)
   gpg_error_t err = 0;
   unsigned char grip[20];
   gcry_sexp_t s_skey = NULL;
-  gcry_sexp_t s_pkey = NULL;
   unsigned char *keydata;
   size_t keydatalen, timestamplen;
   const char *serialno, *timestamp_str, *id;
   unsigned char *shadow_info = NULL;
-  unsigned char *shdkey;
   time_t timestamp;
 
   if (ctrl->restricted)
@@ -2456,48 +2511,8 @@ cmd_keytocard (assuan_context_t ctx, char *line)
   snprintf (keydata+keydatalen-1, 30, "(10:created-at10:%010lu))", timestamp);
   keydatalen += 10 + 19 - 1;
   err = divert_writekey (ctrl, force, serialno, id, keydata, keydatalen);
-  if (err)
-    {
-      xfree (keydata);
-      goto leave;
-    }
   xfree (keydata);
 
-  err = agent_public_key_from_file (ctrl, grip, &s_pkey);
-  if (err)
-    goto leave;
-
-  shadow_info = make_shadow_info (serialno, id);
-  if (!shadow_info)
-    {
-      err = gpg_error (GPG_ERR_ENOMEM);
-      gcry_sexp_release (s_pkey);
-      goto leave;
-    }
-  keydatalen = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0);
-  keydata = xtrymalloc (keydatalen);
-  if (keydata == NULL)
-    {
-      err = gpg_error_from_syserror ();
-      gcry_sexp_release (s_pkey);
-      goto leave;
-    }
-  gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, keydata, keydatalen);
-  gcry_sexp_release (s_pkey);
-  err = agent_shadow_key (keydata, shadow_info, &shdkey);
-  xfree (keydata);
-  xfree (shadow_info);
-  if (err)
-    {
-      log_error ("shadowing the key failed: %s\n", gpg_strerror (err));
-      goto leave;
-    }
-
-  keydatalen = gcry_sexp_canon_len (shdkey, 0, NULL, NULL);
-  err = agent_write_private_key (grip, shdkey, keydatalen, 1);
-  xfree (shdkey);
-
- leave:
   return leave_cmd (ctx, err);
 }
 
@@ -2532,7 +2547,7 @@ cmd_getval (assuan_context_t ctx, char *line)
       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");
 
 
@@ -2563,7 +2578,7 @@ 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.";
@@ -2599,7 +2614,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");
 
 
@@ -2755,6 +2770,7 @@ 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"
@@ -2848,6 +2864,21 @@ cmd_getinfo (assuan_context_t ctx, char *line)
     {
       rc = agent_scd_check_running ()? 0 : gpg_error (GPG_ERR_GENERAL);
     }
+  else if (!strcmp (line, "std_env_names"))
+    {
+      int iterator;
+      const char *name;
+
+      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"))
     {
@@ -3113,6 +3144,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)
     {
@@ -3159,6 +3196,7 @@ start_command_handler (ctrl_t ctrl, gnupg_fd_t listen_fd, gnupg_fd_t fd)
   ctrl->digest.raw_value = 0;
 
   assuan_set_io_monitor (ctx, io_monitor, NULL);
+  agent_set_progress_cb (progress_cb, ctrl);
 
   for (;;)
     {