card: Implement the bulk of OpenPGP stuff into gpg-card-tool.
authorWerner Koch <wk@gnupg.org>
Sun, 27 Jan 2019 19:12:00 +0000 (20:12 +0100)
committerWerner Koch <wk@gnupg.org>
Sun, 27 Jan 2019 19:12:00 +0000 (20:12 +0100)
* tools/card-call-scd.c: New.
* tools/card-tool.h: new.
* tools/gpg-card-tool.c: Largely extended.
--

gpg-card-tool will eventually replace the --card-edit command of gpg
because it makes more sense to have a multi-protocol aware tool and
not just one for OpenPGP cards.  Most OpenPGP card things works now
but a few, those which require close interaction with gpg, still need
to be implemented.  And of course the whole planned non-interactive
stuff needs to be written.

Signed-off-by: Werner Koch <wk@gnupg.org>
po/POTFILES.in
tools/Makefile.am
tools/card-call-scd.c [new file with mode: 0644]
tools/card-tool.h [new file with mode: 0644]
tools/gpg-card-tool.c

index fe8d45f..53a7dc8 100644 (file)
@@ -127,4 +127,5 @@ tools/gpgconf.c
 tools/no-libgcrypt.c
 tools/symcryptrun.c
 tools/gpg-check-pattern.c
-
+tools/gpg-card-tool.c
+tools/card-call-scd.c
index 4833bff..e29e6a2 100644 (file)
@@ -123,7 +123,7 @@ gpg_connect_agent_LDADD = ../common/libgpgrl.a $(common_libs) \
                           $(LIBREADLINE) $(LIBINTL) $(NETLIBS) $(LIBICONV) \
                           $(gpg_connect_agent_rc_objs)
 
-gpg_card_tool_SOURCES   = gpg-card-tool.c
+gpg_card_tool_SOURCES   = gpg-card-tool.c card-tool.h card-call-scd.c
 gpg_card_tool_LDADD     = ../common/libgpgrl.a $(common_libs) \
                          $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) \
                          $(GPG_ERROR_LIBS) \
diff --git a/tools/card-call-scd.c b/tools/card-call-scd.c
new file mode 100644 (file)
index 0000000..7df7861
--- /dev/null
@@ -0,0 +1,1355 @@
+/* card-call-scd.c - IPC calls to scdaemon.
+ * Copyright (C) 2019 g10 Code GmbH
+ * Copyright (C) 2001-2003, 2006-2011, 2013 Free Software Foundation, Inc.
+ * Copyright (C) 2013-2015  Werner Koch
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+#ifdef HAVE_LOCALE_H
+#include <locale.h>
+#endif
+
+#include "../common/util.h"
+#include "../common/membuf.h"
+#include "../common/i18n.h"
+#include "../common/asshelp.h"
+#include "../common/sysutils.h"
+#include "../common/status.h"
+#include "../common/host2net.h"
+#include "../common/openpgpdefs.h"
+#include "card-tool.h"
+
+#define CONTROL_D ('D' - 'A' + 1)
+
+#define START_AGENT_NO_STARTUP_CMDS 1
+#define START_AGENT_SUPPRESS_ERRORS 2
+
+struct default_inq_parm_s
+{
+  assuan_context_t ctx;
+  struct {
+    u32 *keyid;
+    u32 *mainkeyid;
+    int pubkey_algo;
+  } keyinfo;
+};
+
+struct cipher_parm_s
+{
+  struct default_inq_parm_s *dflt;
+  assuan_context_t ctx;
+  unsigned char *ciphertext;
+  size_t ciphertextlen;
+};
+
+struct writecert_parm_s
+{
+  struct default_inq_parm_s *dflt;
+  const unsigned char *certdata;
+  size_t certdatalen;
+};
+
+struct writekey_parm_s
+{
+  struct default_inq_parm_s *dflt;
+  const unsigned char *keydata;
+  size_t keydatalen;
+};
+
+struct genkey_parm_s
+{
+  struct default_inq_parm_s *dflt;
+  const char *keyparms;
+  const char *passphrase;
+};
+
+struct card_cardlist_parm_s
+{
+  gpg_error_t error;
+  strlist_t list;
+};
+
+struct import_key_parm_s
+{
+  struct default_inq_parm_s *dflt;
+  const void *key;
+  size_t keylen;
+};
+
+
+struct cache_nonce_parm_s
+{
+  char **cache_nonce_addr;
+  char **passwd_nonce_addr;
+};
+
+
+\f
+/*
+ * File local variables
+ */
+
+/* The established context to the agent.  Note that all calls to
+ * scdaemon are routed via the agent and thus we only need to care
+ * about the IPC with the agent.  */
+static assuan_context_t agent_ctx;
+
+
+\f
+/*
+ * Local prototypes
+ */
+static gpg_error_t learn_status_cb (void *opaque, const char *line);
+
+
+
+\f
+/* Release the card info structure INFO. */
+void
+release_card_info (card_info_t info)
+{
+  int i;
+
+  if (!info)
+    return;
+
+  xfree (info->reader); info->reader = NULL;
+  xfree (info->serialno); info->serialno = NULL;
+  xfree (info->dispserialno); info->dispserialno = NULL;
+  xfree (info->apptypestr); info->apptypestr = NULL;
+  info->apptype = APP_TYPE_NONE;
+  xfree (info->disp_name); info->disp_name = NULL;
+  xfree (info->disp_lang); info->disp_lang = NULL;
+  xfree (info->pubkey_url); info->pubkey_url = NULL;
+  xfree (info->login_data); info->login_data = NULL;
+  info->cafpr1len = info->cafpr2len = info->cafpr3len = 0;
+  info->fpr1len = info->fpr2len = info->fpr3len = 0;
+  for (i=0; i < DIM(info->private_do); i++)
+    {
+      xfree (info->private_do[i]);
+      info->private_do[i] = NULL;
+    }
+}
+
+
+/* Map an application type string to an integer.  */
+static app_type_t
+map_apptypestr (const char *string)
+{
+  app_type_t result;
+
+  if (!string)
+    result = APP_TYPE_NONE;
+  else if (!ascii_strcasecmp (string, "OPENPGP"))
+    result = APP_TYPE_OPENPGP;
+  else if (!ascii_strcasecmp (string, "NKS"))
+    result = APP_TYPE_NKS;
+  else if (!ascii_strcasecmp (string, "DINSIG"))
+    result = APP_TYPE_DINSIG;
+  else if (!ascii_strcasecmp (string, "P15"))
+    result = APP_TYPE_P15;
+  else if (!ascii_strcasecmp (string, "GELDKARTE"))
+    result = APP_TYPE_GELDKARTE;
+  else if (!ascii_strcasecmp (string, "SC-HSM"))
+    result = APP_TYPE_SC_HSM;
+  else if (!ascii_strcasecmp (string, "PIV"))
+    result = APP_TYPE_PIV;
+  else
+    result = APP_TYPE_UNKNOWN;
+
+  return result;
+}
+
+
+/* Return a string representation of the application type.  */
+const char *
+app_type_string (app_type_t app_type)
+{
+  const char *result = "?";
+  switch (app_type)
+    {
+    case APP_TYPE_NONE:      result = "None"; break;
+    case APP_TYPE_OPENPGP:   result = "OpenPGP"; break;
+    case APP_TYPE_NKS:       result = "NetKey"; break;
+    case APP_TYPE_DINSIG:    result = "DINSIG"; break;
+    case APP_TYPE_P15:       result = "PKCS#15"; break;
+    case APP_TYPE_GELDKARTE: result = "Geldkarte"; break;
+    case APP_TYPE_SC_HSM:    result = "SC-HSM"; break;
+    case APP_TYPE_PIV:       result = "PIV"; break;
+    case APP_TYPE_UNKNOWN:   result = "Unknown"; break;
+    }
+  return result;
+}
+
+
+
+/* If RC is not 0, write an appropriate status message. */
+static gpg_error_t
+status_sc_op_failure (gpg_error_t err)
+{
+  switch (gpg_err_code (err))
+    {
+    case 0:
+      break;
+    case GPG_ERR_CANCELED:
+    case GPG_ERR_FULLY_CANCELED:
+      gnupg_status_printf (STATUS_SC_OP_FAILURE, "1");
+      break;
+    case GPG_ERR_BAD_PIN:
+      gnupg_status_printf (STATUS_SC_OP_FAILURE, "2");
+      break;
+    default:
+      gnupg_status_printf (STATUS_SC_OP_FAILURE, NULL);
+      break;
+    }
+  return err;
+}
+
+
+/* This is the default inquiry callback.  It mainly handles the
+   Pinentry notifications.  */
+static gpg_error_t
+default_inq_cb (void *opaque, const char *line)
+{
+  gpg_error_t err = 0;
+  struct default_inq_parm_s *parm = opaque;
+
+  (void)parm;
+
+  if (has_leading_keyword (line, "PINENTRY_LAUNCHED"))
+    {
+      /* err = gpg_proxy_pinentry_notify (parm->ctrl, line); */
+      /* if (err) */
+      /*   log_error (_("failed to proxy %s inquiry to client\n"), */
+      /*              "PINENTRY_LAUNCHED"); */
+      /* We do not pass errors to avoid breaking other code.  */
+    }
+  else
+    log_debug ("ignoring gpg-agent inquiry '%s'\n", line);
+
+  return err;
+}
+
+
+/* Print a warning if the server's version number is less than our
+   version number.  Returns an error code on a connection problem.  */
+static gpg_error_t
+warn_version_mismatch (assuan_context_t ctx, const char *servername, int mode)
+{
+  gpg_error_t err;
+  char *serverversion;
+  const char *myversion = strusage (13);
+
+  err = get_assuan_server_version (ctx, mode, &serverversion);
+  if (err)
+    log_log (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED?
+             GPGRT_LOGLVL_INFO : GPGRT_LOGLVL_ERROR,
+             _("error getting version from '%s': %s\n"),
+             servername, gpg_strerror (err));
+  else if (compare_version_strings (serverversion, myversion) < 0)
+    {
+      char *warn;
+
+      warn = xtryasprintf (_("server '%s' is older than us (%s < %s)"),
+                           servername, serverversion, myversion);
+      if (!warn)
+        err = gpg_error_from_syserror ();
+      else
+        {
+          log_info (_("WARNING: %s\n"), warn);
+          if (!opt.quiet)
+            {
+              log_info (_("Note: Outdated servers may lack important"
+                          " security fixes.\n"));
+              log_info (_("Note: Use the command \"%s\" to restart them.\n"),
+                        "gpgconf --kill all");
+            }
+          gnupg_status_printf (STATUS_WARNING, "server_version_mismatch 0 %s",
+                               warn);
+          xfree (warn);
+        }
+    }
+  xfree (serverversion);
+  return err;
+}
+
+
+/* Try to connect to the agent via socket or fork it off and work by
+ * pipes.  Handle the server's initial greeting.  */
+static gpg_error_t
+start_agent (unsigned int flags)
+{
+  gpg_error_t err;
+
+  if (agent_ctx)
+    err = 0;
+  else
+    {
+      err = start_new_gpg_agent (&agent_ctx,
+                                 GPG_ERR_SOURCE_DEFAULT,
+                                 opt.agent_program,
+                                 opt.lc_ctype, opt.lc_messages,
+                                 opt.session_env,
+                                 opt.autostart, opt.verbose, DBG_IPC,
+                                 NULL, NULL);
+      if (!opt.autostart && gpg_err_code (err) == GPG_ERR_NO_AGENT)
+        {
+          static int shown;
+
+          if (!shown)
+            {
+              shown = 1;
+              log_info (_("no gpg-agent running in this session\n"));
+            }
+        }
+      else if (!err
+               && !(err = warn_version_mismatch (agent_ctx, GPG_AGENT_NAME, 0)))
+        {
+          /* Tell the agent that we support Pinentry notifications.
+             No error checking so that it will work also with older
+             agents.  */
+          assuan_transact (agent_ctx, "OPTION allow-pinentry-notify",
+                           NULL, NULL, NULL, NULL, NULL, NULL);
+          /* Tell the agent about what version we are aware.  This is
+             here used to indirectly enable GPG_ERR_FULLY_CANCELED.  */
+          assuan_transact (agent_ctx, "OPTION agent-awareness=2.1.0",
+                           NULL, NULL, NULL, NULL, NULL, NULL);
+        }
+    }
+
+  if (!err && !(flags & START_AGENT_NO_STARTUP_CMDS))
+    {
+      /* Request the serial number of the card for an early test.  */
+      struct card_info_s info;
+
+      memset (&info, 0, sizeof info);
+
+      if (!(flags & START_AGENT_SUPPRESS_ERRORS))
+        err = warn_version_mismatch (agent_ctx, SCDAEMON_NAME, 2);
+
+      if (!err)
+        err = assuan_transact (agent_ctx, "SCD SERIALNO",
+                               NULL, NULL, NULL, NULL,
+                               learn_status_cb, &info);
+      if (err && !(flags & START_AGENT_SUPPRESS_ERRORS))
+        {
+          switch (gpg_err_code (err))
+            {
+            case GPG_ERR_NOT_SUPPORTED:
+            case GPG_ERR_NO_SCDAEMON:
+              gnupg_status_printf (STATUS_CARDCTRL, "6"); /* No card support. */
+              break;
+            case GPG_ERR_OBJ_TERM_STATE:
+              /* Card is in termination state. */
+              gnupg_status_printf (STATUS_CARDCTRL, "7");
+              break;
+            default:
+              gnupg_status_printf (STATUS_CARDCTRL, "4");  /* No card.  */
+              break;
+            }
+        }
+
+      if (!err && info.serialno)
+        gnupg_status_printf (STATUS_CARDCTRL, "3 %s", info.serialno);
+
+      release_card_info (&info);
+    }
+
+  return err;
+}
+
+
+/* Return a new malloced string by unescaping the string S.  Escaping
+ * is percent escaping and '+'/space mapping.  A binary nul will
+ * silently be replaced by a 0xFF.  Function returns NULL to indicate
+ * an out of memory status. */
+static char *
+unescape_status_string (const unsigned char *s)
+{
+  return percent_plus_unescape (s, 0xff);
+}
+
+
+/* Take a 20 or 32 byte hexencoded string and put it into the provided
+ * FPRLEN byte long buffer FPR in binary format.  Returns the actual
+ * used length of the FPR buffer or 0 on error.  */
+static unsigned int
+unhexify_fpr (const char *hexstr, unsigned char *fpr, unsigned int fprlen)
+{
+  const char *s;
+  int n;
+
+  for (s=hexstr, n=0; hexdigitp (s); s++, n++)
+    ;
+  if ((*s && *s != ' ') || !(n == 40 || n == 64))
+    return 0; /* no fingerprint (invalid or wrong length). */
+  for (s=hexstr, n=0; *s && n < fprlen; s += 2, n++)
+    fpr[n] = xtoi_2 (s);
+
+  return (n == 20 || n == 32)? n : 0;
+}
+
+
+/* Take the serial number from LINE and return it verbatim in a newly
+ * allocated string.  We make sure that only hex characters are
+ * returned.  Returns NULL on error. */
+static char *
+store_serialno (const char *line)
+{
+  const char *s;
+  char *p;
+
+  for (s=line; hexdigitp (s); s++)
+    ;
+  p = xtrymalloc (s + 1 - line);
+  if (p)
+    {
+      memcpy (p, line, s-line);
+      p[s-line] = 0;
+    }
+  return p;
+}
+
+
+\f
+/* Send an APDU to the current card.  On success the status word is
+ * stored at R_SW inless R_SW is NULL.  With HEXAPDU being NULL only a
+ * RESET command is send to scd.  With HEXAPDU being the string
+ * "undefined" the command "SERIALNO undefined" is send to scd. */
+gpg_error_t
+scd_apdu (const char *hexapdu, unsigned int *r_sw)
+{
+  gpg_error_t err;
+
+  err = start_agent (START_AGENT_NO_STARTUP_CMDS);
+  if (err)
+    return err;
+
+  if (!hexapdu)
+    {
+      err = assuan_transact (agent_ctx, "SCD RESET",
+                             NULL, NULL, NULL, NULL, NULL, NULL);
+
+    }
+  else if (!strcmp (hexapdu, "undefined"))
+    {
+      err = assuan_transact (agent_ctx, "SCD SERIALNO undefined",
+                             NULL, NULL, NULL, NULL, NULL, NULL);
+    }
+  else
+    {
+      char line[ASSUAN_LINELENGTH];
+      membuf_t mb;
+      unsigned char *data;
+      size_t datalen;
+
+      init_membuf (&mb, 256);
+
+      snprintf (line, DIM(line), "SCD APDU %s", hexapdu);
+      err = assuan_transact (agent_ctx, line,
+                             put_membuf_cb, &mb, NULL, NULL, NULL, NULL);
+      if (!err)
+        {
+          data = get_membuf (&mb, &datalen);
+          if (!data)
+            err = gpg_error_from_syserror ();
+          else if (datalen < 2) /* Ooops */
+            err = gpg_error (GPG_ERR_CARD);
+          else
+            {
+              if (r_sw)
+                *r_sw = buf16_to_uint (data+datalen-2);
+            }
+          xfree (data);
+        }
+    }
+
+  return err;
+}
+
+
+/* This is a dummy data line callback.  */
+static gpg_error_t
+dummy_data_cb (void *opaque, const void *buffer, size_t length)
+{
+  (void)opaque;
+  (void)buffer;
+  (void)length;
+  return 0;
+}
+
+/* A simple callback used to return the serialnumber of a card.  */
+static gpg_error_t
+get_serialno_cb (void *opaque, const char *line)
+{
+  char **serialno = opaque;
+  const char *keyword = line;
+  const char *s;
+  int keywordlen, n;
+
+  for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
+    ;
+  while (spacep (line))
+    line++;
+
+  /* FIXME: Should we use has_leading_keyword? */
+  if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
+    {
+      if (*serialno)
+        return gpg_error (GPG_ERR_CONFLICT); /* Unexpected status line. */
+      for (n=0,s=line; hexdigitp (s); s++, n++)
+        ;
+      if (!n || (n&1)|| !(spacep (s) || !*s) )
+        return gpg_error (GPG_ERR_ASS_PARAMETER);
+      *serialno = xtrymalloc (n+1);
+      if (!*serialno)
+        return out_of_core ();
+      memcpy (*serialno, line, n);
+      (*serialno)[n] = 0;
+    }
+
+  return 0;
+}
+
+
+/* The status callback to handle the LEARN and GETATTR commands.  */
+static gpg_error_t
+learn_status_cb (void *opaque, const char *line)
+{
+  struct card_info_s *parm = opaque;
+  const char *keyword = line;
+  int keywordlen;
+  int i;
+
+  for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
+    ;
+  while (spacep (line))
+    line++;
+
+  switch (keywordlen)
+    {
+    case 3:
+      if (!memcmp (keyword, "KDF", 3))
+        {
+          parm->kdf_do_enabled = 1;
+        }
+      break;
+
+    case 5:
+      if (!memcmp (keyword, "UIF-", 4)
+          && strchr("123", keyword[4]))
+        {
+          unsigned char *data;
+          int no = keyword[4] - '1';
+
+          log_assert (no >= 0 && no <= 2);
+          data = unescape_status_string (line);
+          parm->uif[no] = (data[0] != 0xff);
+          xfree (data);
+        }
+      break;
+
+    case 6:
+      if (!memcmp (keyword, "READER", keywordlen))
+        {
+          xfree (parm->reader);
+          parm->reader = unescape_status_string (line);
+        }
+      else if (!memcmp (keyword, "EXTCAP", keywordlen))
+        {
+          char *p, *p2, *buf;
+          int abool;
+
+          buf = p = unescape_status_string (line);
+          if (buf)
+            {
+              for (p = strtok (buf, " "); p; p = strtok (NULL, " "))
+                {
+                  p2 = strchr (p, '=');
+                  if (p2)
+                    {
+                      *p2++ = 0;
+                      abool = (*p2 == '1');
+                      if (!strcmp (p, "ki"))
+                        parm->extcap.ki = abool;
+                      else if (!strcmp (p, "aac"))
+                        parm->extcap.aac = abool;
+                      else if (!strcmp (p, "bt"))
+                        parm->extcap.bt = abool;
+                      else if (!strcmp (p, "kdf"))
+                        parm->extcap.kdf = abool;
+                      else if (!strcmp (p, "si"))
+                        parm->status_indicator = strtoul (p2, NULL, 10);
+                    }
+                }
+              xfree (buf);
+            }
+        }
+      else if (!memcmp (keyword, "CA-FPR", keywordlen))
+        {
+          int no = atoi (line);
+          while (*line && !spacep (line))
+            line++;
+          while (spacep (line))
+            line++;
+          if (no == 1)
+            parm->cafpr1len = unhexify_fpr (line, parm->cafpr1,
+                                            sizeof parm->cafpr1);
+          else if (no == 2)
+            parm->cafpr2len = unhexify_fpr (line, parm->cafpr2,
+                                            sizeof parm->cafpr2);
+          else if (no == 3)
+            parm->cafpr3len = unhexify_fpr (line, parm->cafpr3,
+                                            sizeof parm->cafpr3);
+        }
+      break;
+
+    case 7:
+      if (!memcmp (keyword, "APPTYPE", keywordlen))
+        {
+          xfree (parm->apptypestr);
+          parm->apptypestr = unescape_status_string (line);
+          parm->apptype = map_apptypestr (parm->apptypestr);
+        }
+      else if (!memcmp (keyword, "KEY-FPR", keywordlen))
+        {
+          int no = atoi (line);
+
+          while (*line && !spacep (line))
+            line++;
+          while (spacep (line))
+            line++;
+          if (no == 1)
+            parm->fpr1len = unhexify_fpr (line, parm->fpr1, sizeof parm->fpr1);
+          else if (no == 2)
+            parm->fpr2len = unhexify_fpr (line, parm->fpr2, sizeof parm->fpr2);
+          else if (no == 3)
+            parm->fpr3len = unhexify_fpr (line, parm->fpr3, sizeof parm->fpr3);
+        }
+      break;
+
+    case 8:
+      if (!memcmp (keyword, "SERIALNO", keywordlen))
+        {
+          xfree (parm->serialno);
+          parm->serialno = store_serialno (line);
+          parm->is_v2 = (strlen (parm->serialno) >= 16
+                         && xtoi_2 (parm->serialno+12) >= 2 );
+        }
+      else if (!memcmp (keyword, "DISP-SEX", keywordlen))
+        {
+          parm->disp_sex = *line == '1'? 1 : *line == '2' ? 2: 0;
+        }
+      else if (!memcmp (keyword, "KEY-TIME", keywordlen))
+        {
+          int no = atoi (line);
+          while (* line && !spacep (line))
+            line++;
+          while (spacep (line))
+            line++;
+          if (no == 1)
+            parm->fpr1time = strtoul (line, NULL, 10);
+          else if (no == 2)
+            parm->fpr2time = strtoul (line, NULL, 10);
+          else if (no == 3)
+            parm->fpr3time = strtoul (line, NULL, 10);
+        }
+      else if (!memcmp (keyword, "KEY-ATTR", keywordlen))
+        {
+          int keyno = 0;
+          int algo = GCRY_PK_RSA;
+          int n = 0;
+
+          sscanf (line, "%d %d %n", &keyno, &algo, &n);
+          keyno--;
+          if (keyno < 0 || keyno >= DIM (parm->key_attr))
+            ; /* Out of range - ignore.  */
+          else
+            {
+              parm->key_attr[keyno].algo = algo;
+              if (algo == PUBKEY_ALGO_RSA)
+                parm->key_attr[keyno].nbits = strtoul (line+n+3, NULL, 10);
+              else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA
+                       || algo == PUBKEY_ALGO_EDDSA)
+                {
+                  parm->key_attr[keyno].curve =
+                    openpgp_is_curve_supported (line + n, NULL, NULL);
+                }
+            }
+        }
+      break;
+
+    case 9:
+        if (!memcmp (keyword, "DISP-NAME", keywordlen))
+          {
+            xfree (parm->disp_name);
+            parm->disp_name = unescape_status_string (line);
+          }
+        else if (!memcmp (keyword, "DISP-LANG", keywordlen))
+          {
+            xfree (parm->disp_lang);
+            parm->disp_lang = unescape_status_string (line);
+          }
+      break;
+
+    case 10:
+      if (!memcmp (keyword, "PUBKEY-URL", keywordlen))
+        {
+          xfree (parm->pubkey_url);
+          parm->pubkey_url = unescape_status_string (line);
+        }
+      else if (!memcmp (keyword, "LOGIN-DATA", keywordlen))
+        {
+          xfree (parm->login_data);
+          parm->login_data = unescape_status_string (line);
+        }
+      else if (!memcmp (keyword, "CHV-STATUS", keywordlen))
+        {
+          char *p, *buf;
+
+          buf = p = unescape_status_string (line);
+          if (buf)
+            {
+              while (spacep (p))
+                p++;
+              parm->chv1_cached = atoi (p);
+              while (*p && !spacep (p))
+                p++;
+              while (spacep (p))
+                p++;
+              for (i=0; *p && i < 3; i++)
+                {
+                  parm->chvmaxlen[i] = atoi (p);
+                  while (*p && !spacep (p))
+                    p++;
+                  while (spacep (p))
+                    p++;
+                }
+              for (i=0; *p && i < 3; i++)
+                {
+                  parm->chvretry[i] = atoi (p);
+                  while (*p && !spacep (p))
+                    p++;
+                  while (spacep (p))
+                    p++;
+                }
+              xfree (buf);
+            }
+        }
+      break;
+
+    case 11:
+      if (!memcmp (keyword, "SIG-COUNTER", keywordlen))
+        {
+          parm->sig_counter = strtoul (line, NULL, 0);
+        }
+      else if (!memcmp (keyword, "KEYPAIRINFO", keywordlen))
+        {
+          const char *hexgrp = line;
+          int no;
+
+          while (*line && !spacep (line))
+            line++;
+          while (spacep (line))
+            line++;
+          if (strncmp (line, "OPENPGP.", 8))
+            ;
+          else if ((no = atoi (line+8)) == 1)
+            unhexify_fpr (hexgrp, parm->grp1, sizeof parm->grp1);
+          else if (no == 2)
+            unhexify_fpr (hexgrp, parm->grp2, sizeof parm->grp2);
+          else if (no == 3)
+            unhexify_fpr (hexgrp, parm->grp3, sizeof parm->grp3);
+        }
+      break;
+
+    case 12:
+      if (!memcmp (keyword, "PRIVATE-DO-", 11)
+          && strchr("1234", keyword[11]))
+        {
+          int no = keyword[11] - '1';
+          log_assert (no >= 0 && no <= 3);
+          xfree (parm->private_do[no]);
+          parm->private_do[no] = unescape_status_string (line);
+        }
+      break;
+
+    case 13:
+      if (!memcmp (keyword, "$DISPSERIALNO", keywordlen))
+        {
+          xfree (parm->dispserialno);
+          parm->dispserialno = unescape_status_string (line);
+        }
+      break;
+
+    default:
+      /* Unknown.  */
+      break;
+    }
+
+  return 0;
+}
+
+
+/* Call the scdaemon to learn about a smartcard.  This fills INFO
+ * wioth data from the card. */
+gpg_error_t
+scd_learn (card_info_t info)
+{
+  gpg_error_t err;
+  struct default_inq_parm_s parm;
+  struct card_info_s dummyinfo;
+
+  if (!info)
+    info = &dummyinfo;
+
+  memset (info, 0, sizeof *info);
+  memset (&parm, 0, sizeof parm);
+
+  err = start_agent (0);
+  if (err)
+    return err;
+
+  parm.ctx = agent_ctx;
+  err = assuan_transact (agent_ctx, "SCD LEARN --force",
+                         dummy_data_cb, NULL, default_inq_cb, &parm,
+                         learn_status_cb, info);
+  /* Also try to get some other key attributes.  */
+  if (!err)
+    {
+      err = scd_getattr ("KEY-ATTR", info);
+      if (gpg_err_code (err) == GPG_ERR_INV_NAME
+          || gpg_err_code (err) == GPG_ERR_UNSUPPORTED_OPERATION)
+        err = 0; /* Not implemented or GETATTR not supported.  */
+      err = scd_getattr ("$DISPSERIALNO", info);
+      if (gpg_err_code (err) == GPG_ERR_INV_NAME
+          || gpg_err_code (err) == GPG_ERR_UNSUPPORTED_OPERATION)
+        err = 0; /* Not implemented or GETATTR not supported.  */
+
+    }
+
+  if (info == &dummyinfo)
+    release_card_info (info);
+
+  return err;
+}
+
+
+/* Call the agent to retrieve a data object.  This function returns
+ * the data in the same structure as used by the learn command.  It is
+ * allowed to update such a structure using this command. */
+gpg_error_t
+scd_getattr (const char *name, struct card_info_s *info)
+{
+  gpg_error_t err;
+  char line[ASSUAN_LINELENGTH];
+  struct default_inq_parm_s parm;
+
+  memset (&parm, 0, sizeof parm);
+
+  if (!*name)
+    return gpg_error (GPG_ERR_INV_VALUE);
+
+  /* We assume that NAME does not need escaping. */
+  if (12 + strlen (name) > DIM(line)-1)
+    return gpg_error (GPG_ERR_TOO_LARGE);
+  stpcpy (stpcpy (line, "SCD GETATTR "), name);
+
+  err = start_agent (0);
+  if (err)
+    return err;
+
+  parm.ctx = agent_ctx;
+  err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm,
+                        learn_status_cb, info);
+
+  return err;
+}
+
+
+/* Send an setattr command to the SCdaemon.  */
+gpg_error_t
+scd_setattr (const char *name,
+             const unsigned char *value, size_t valuelen)
+{
+  gpg_error_t err;
+  char *tmp;
+  char *line = NULL;
+  struct default_inq_parm_s parm;
+
+
+  if (!*name || !valuelen)
+    return gpg_error (GPG_ERR_INV_VALUE);
+
+  tmp = strconcat ("SCD SETATTR ", name, " ", NULL);
+  if (!tmp)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+  line = percent_data_escape (1, tmp, value, valuelen);
+  xfree (tmp);
+  if (!line)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+
+  if (strlen (line) + 10 > ASSUAN_LINELENGTH)
+    {
+      err = gpg_error (GPG_ERR_TOO_LARGE);
+      goto leave;
+    }
+
+  err = start_agent (0);
+  if (err )
+    goto leave;
+
+  memset (&parm, 0, sizeof parm);
+  parm.ctx = agent_ctx;
+  err = assuan_transact (agent_ctx, line, NULL, NULL,
+                         default_inq_cb, &parm, NULL, NULL);
+
+ leave:
+  xfree (line);
+  return status_sc_op_failure (err);
+}
+
+
+\f
+/* Handle a CERTDATA inquiry.  Note, we only send the data,
+ * assuan_transact takes care of flushing and writing the END
+ * command. */
+static gpg_error_t
+inq_writecert_parms (void *opaque, const char *line)
+{
+  gpg_error_t err;
+  struct writecert_parm_s *parm = opaque;
+
+  if (has_leading_keyword (line, "CERTDATA"))
+    {
+      err = assuan_send_data (parm->dflt->ctx,
+                              parm->certdata, parm->certdatalen);
+    }
+  else
+    err = default_inq_cb (parm->dflt, line);
+
+  return err;
+}
+
+
+/* Send a WRITECERT command to the SCdaemon. */
+gpg_error_t
+scd_writecert (const char *certidstr,
+               const unsigned char *certdata, size_t certdatalen)
+{
+  gpg_error_t err;
+  char line[ASSUAN_LINELENGTH];
+  struct writecert_parm_s parms;
+  struct default_inq_parm_s dfltparm;
+
+  memset (&dfltparm, 0, sizeof dfltparm);
+
+  err = start_agent (0);
+  if (err)
+    return err;
+
+  memset (&parms, 0, sizeof parms);
+
+  snprintf (line, sizeof line, "SCD WRITECERT %s", certidstr);
+  dfltparm.ctx = agent_ctx;
+  parms.dflt = &dfltparm;
+  parms.certdata = certdata;
+  parms.certdatalen = certdatalen;
+
+  err = assuan_transact (agent_ctx, line, NULL, NULL,
+                         inq_writecert_parms, &parms, NULL, NULL);
+
+  return status_sc_op_failure (err);
+}
+
+
+\f
+/* Handle a KEYDATA inquiry.  Note, we only send the data,
+   assuan_transact takes care of flushing and writing the end */
+static gpg_error_t
+inq_writekey_parms (void *opaque, const char *line)
+{
+  gpg_error_t err;
+  struct writekey_parm_s *parm = opaque;
+
+  if (has_leading_keyword (line, "KEYDATA"))
+    {
+      err = assuan_send_data (parm->dflt->ctx, parm->keydata, parm->keydatalen);
+    }
+  else
+    err = default_inq_cb (parm->dflt, line);
+
+  return err;
+}
+
+
+/* Send a WRITEKEY command to the SCdaemon. */
+gpg_error_t
+scd_writekey (int keyno, const unsigned char *keydata, size_t keydatalen)
+{
+  gpg_error_t err;
+  char line[ASSUAN_LINELENGTH];
+  struct writekey_parm_s parms;
+  struct default_inq_parm_s dfltparm;
+
+  memset (&parms, 0, sizeof parms);
+  memset (&dfltparm, 0, sizeof dfltparm);
+
+  err = start_agent (0);
+  if (err)
+    return err;
+
+  snprintf (line, sizeof line, "SCD WRITEKEY --force OPENPGP.%d", keyno);
+  dfltparm.ctx = agent_ctx;
+  parms.dflt = &dfltparm;
+  parms.keydata = keydata;
+  parms.keydatalen = keydatalen;
+
+  err = assuan_transact (agent_ctx, line, NULL, NULL,
+                         inq_writekey_parms, &parms, NULL, NULL);
+
+  return status_sc_op_failure (err);
+}
+
+
+\f
+/* Status callback for the SCD GENKEY command. */
+static gpg_error_t
+scd_genkey_cb (void *opaque, const char *line)
+{
+  u32 *createtime = opaque;
+  const char *keyword = line;
+  int keywordlen;
+
+  for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
+    ;
+  while (spacep (line))
+    line++;
+
+ if (keywordlen == 14 && !memcmp (keyword,"KEY-CREATED-AT", keywordlen))
+    {
+      *createtime = (u32)strtoul (line, NULL, 10);
+    }
+  else if (keywordlen == 8 && !memcmp (keyword, "PROGRESS", keywordlen))
+    {
+      gnupg_status_printf (STATUS_PROGRESS, "%s", line);
+    }
+
+  return 0;
+}
+
+/* Send a GENKEY command to the SCdaemon.  If *CREATETIME is not 0,
+ * the value will be passed to SCDAEMON with --timestamp option so that
+ * the key is created with this.  Otherwise, timestamp was generated by
+ * SCDEAMON.  On success, creation time is stored back to
+ * CREATETIME.  */
+gpg_error_t
+scd_genkey (int keyno, int force, u32 *createtime)
+{
+  gpg_error_t err;
+  char line[ASSUAN_LINELENGTH];
+  gnupg_isotime_t tbuf;
+  struct default_inq_parm_s dfltparm;
+
+  memset (&dfltparm, 0, sizeof dfltparm);
+
+  err = start_agent (0);
+  if (err)
+    return err;
+
+  if (*createtime)
+    epoch2isotime (tbuf, *createtime);
+  else
+    *tbuf = 0;
+
+  snprintf (line, sizeof line, "SCD GENKEY %s%s %s %d",
+            *tbuf? "--timestamp=":"", tbuf,
+            force? "--force":"",
+            keyno);
+
+  dfltparm.ctx = agent_ctx;
+  err = assuan_transact (agent_ctx, line,
+                         NULL, NULL, default_inq_cb, &dfltparm,
+                         scd_genkey_cb, createtime);
+
+  return status_sc_op_failure (err);
+}
+
+
+\f
+/* Return the serial number of the card or an appropriate error.  The
+ * serial number is returned as a hexstring.  If DEMAND is not NULL
+ * the reader with the a card of the serilanumber DEMAND is
+ * requested.  */
+gpg_error_t
+scd_serialno (char **r_serialno, const char *demand)
+{
+  int err;
+  char *serialno = NULL;
+  char line[ASSUAN_LINELENGTH];
+
+  err = start_agent (START_AGENT_SUPPRESS_ERRORS);
+  if (err)
+    return err;
+
+  if (!demand)
+    strcpy (line, "SCD SERIALNO");
+  else
+    snprintf (line, DIM(line), "SCD SERIALNO --demand=%s", demand);
+
+  err = assuan_transact (agent_ctx, line,
+                         NULL, NULL, NULL, NULL,
+                         get_serialno_cb, &serialno);
+  if (err)
+    {
+      xfree (serialno);
+      return err;
+    }
+
+  *r_serialno = serialno;
+  return 0;
+}
+
+
+\f
+/* Send a READCERT command to the SCdaemon. */
+gpg_error_t
+scd_readcert (const char *certidstr, void **r_buf, size_t *r_buflen)
+{
+  gpg_error_t err;
+  char line[ASSUAN_LINELENGTH];
+  membuf_t data;
+  size_t len;
+  struct default_inq_parm_s dfltparm;
+
+  memset (&dfltparm, 0, sizeof dfltparm);
+
+  *r_buf = NULL;
+  err = start_agent (0);
+  if (err)
+    return err;
+
+  dfltparm.ctx = agent_ctx;
+
+  init_membuf (&data, 2048);
+
+  snprintf (line, sizeof line, "SCD READCERT %s", certidstr);
+  err = assuan_transact (agent_ctx, line,
+                         put_membuf_cb, &data,
+                         default_inq_cb, &dfltparm,
+                         NULL, NULL);
+  if (err)
+    {
+      xfree (get_membuf (&data, &len));
+      return err;
+    }
+
+  *r_buf = get_membuf (&data, r_buflen);
+  if (!*r_buf)
+    return gpg_error_from_syserror ();
+
+  return 0;
+}
+
+
+\f
+/* Callback function for card_cardlist.  */
+static gpg_error_t
+card_cardlist_cb (void *opaque, const char *line)
+{
+  struct card_cardlist_parm_s *parm = opaque;
+  const char *keyword = line;
+  int keywordlen;
+
+  for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
+    ;
+  while (spacep (line))
+    line++;
+
+  if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
+    {
+      const char *s;
+      int n;
+
+      for (n=0,s=line; hexdigitp (s); s++, n++)
+        ;
+
+      if (!n || (n&1) || *s)
+        parm->error = gpg_error (GPG_ERR_ASS_PARAMETER);
+      else
+        add_to_strlist (&parm->list, line);
+    }
+
+  return 0;
+}
+
+
+/* Return the serial numbers of all cards currently inserted.  */
+gpg_error_t
+scd_cardlist (strlist_t *result)
+{
+  gpg_error_t err;
+  struct card_cardlist_parm_s parm;
+
+  memset (&parm, 0, sizeof parm);
+  *result = NULL;
+
+  err = start_agent (START_AGENT_SUPPRESS_ERRORS);
+  if (err)
+    return err;
+
+  err = assuan_transact (agent_ctx, "SCD GETINFO card_list",
+                         NULL, NULL, NULL, NULL,
+                         card_cardlist_cb, &parm);
+  if (!err && parm.error)
+    err = parm.error;
+
+  if (!err)
+    *result = parm.list;
+  else
+    free_strlist (parm.list);
+
+  return err;
+}
+
+
+\f
+/* Change the PIN of an OpenPGP card or reset the retry counter.
+ * CHVNO 1: Change the PIN
+ *       2: For v1 cards: Same as 1.
+ *          For v2 cards: Reset the PIN using the Reset Code.
+ *       3: Change the admin PIN
+ *     101: Set a new PIN and reset the retry counter
+ *     102: For v1 cars: Same as 101.
+ *          For v2 cards: Set a new Reset Code.
+ * SERIALNO is not used.
+ */
+gpg_error_t
+scd_change_pin (int chvno)
+{
+  gpg_error_t err;
+  char line[ASSUAN_LINELENGTH];
+  const char *reset = "";
+  struct default_inq_parm_s dfltparm;
+
+  memset (&dfltparm, 0, sizeof dfltparm);
+
+  if (chvno >= 100)
+    reset = "--reset";
+  chvno %= 100;
+
+  err = start_agent (0);
+  if (err)
+    return err;
+  dfltparm.ctx = agent_ctx;
+
+  snprintf (line, sizeof line, "SCD PASSWD %s %d", reset, chvno);
+  err = assuan_transact (agent_ctx, line,
+                         NULL, NULL,
+                         default_inq_cb, &dfltparm,
+                         NULL, NULL);
+
+  return status_sc_op_failure (err);
+}
+
+
+/* Perform a CHECKPIN operation.  SERIALNO should be the serial
+ * number of the card - optionally followed by the fingerprint;
+ * however the fingerprint is ignored here. */
+gpg_error_t
+scd_checkpin (const char *serialno)
+{
+  gpg_error_t err;
+  char line[ASSUAN_LINELENGTH];
+  struct default_inq_parm_s dfltparm;
+
+  memset (&dfltparm, 0, sizeof dfltparm);
+
+  err = start_agent (0);
+  if (err)
+    return err;
+  dfltparm.ctx = agent_ctx;
+
+  snprintf (line, sizeof line, "SCD CHECKPIN %s", serialno);
+  err = assuan_transact (agent_ctx, line,
+                         NULL, NULL,
+                         default_inq_cb, &dfltparm,
+                         NULL, NULL);
+  return status_sc_op_failure (err);
+}
+
+
+/* Return the S2K iteration count as computed by gpg-agent.  On error
+ * print a warning and return a default value. */
+unsigned long
+agent_get_s2k_count (void)
+{
+  gpg_error_t err;
+  membuf_t data;
+  char *buf;
+  unsigned long count = 0;
+
+  err = start_agent (0);
+  if (err)
+    goto leave;
+
+  init_membuf (&data, 32);
+  err = assuan_transact (agent_ctx, "GETINFO s2k_count",
+                        put_membuf_cb, &data,
+                        NULL, NULL, NULL, NULL);
+  if (err)
+    xfree (get_membuf (&data, NULL));
+  else
+    {
+      put_membuf (&data, "", 1);
+      buf = get_membuf (&data, NULL);
+      if (!buf)
+        err = gpg_error_from_syserror ();
+      else
+        {
+          count = strtoul (buf, NULL, 10);
+          xfree (buf);
+        }
+    }
+
+ leave:
+  if (err || count < 65536)
+    {
+      /* Don't print an error if an older agent is used.  */
+      if (err && gpg_err_code (err) != GPG_ERR_ASS_PARAMETER)
+        log_error (_("problem with the agent: %s\n"), gpg_strerror (err));
+
+      /* Default to 65536 which was used up to 2.0.13.  */
+      count = 65536;
+    }
+
+  return count;
+}
diff --git a/tools/card-tool.h b/tools/card-tool.h
new file mode 100644 (file)
index 0000000..d51698c
--- /dev/null
@@ -0,0 +1,161 @@
+/* card-tool.h - Common definitions for the gpg-card-tool
+ * Copyright (C) 2019 g10 Code GmbH
+ *
+ * This file is part of GnuPG.
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#ifndef GNUPG_CARD_TOOL_H
+#define GNUPG_CARD_TOOL_H
+
+#include "../common/session-env.h"
+
+
+/* We keep all global options in the structure OPT.  */
+struct
+{
+  int verbose;
+  unsigned int debug;
+  int quiet;
+  int with_colons;
+  const char *gpg_program;
+  const char *gpgsm_program;
+  const char *agent_program;
+  int autostart;
+
+  /* Options passed to the gpg-agent: */
+  session_env_t session_env;
+  char *lc_ctype;
+  char *lc_messages;
+
+} opt;
+
+/* Debug values and macros.  */
+#define DBG_IPC_VALUE      1024 /* Debug assuan communication.  */
+#define DBG_EXTPROG_VALUE 16384 /* Debug external program calls */
+
+#define DBG_IPC       (opt.debug & DBG_IPC_VALUE)
+#define DBG_EXTPROG   (opt.debug & DBG_EXTPROG_VALUE)
+
+
+/* Enumeration of the known card application types. */
+typedef enum
+  {
+   APP_TYPE_NONE,        /* Not yet known or for direct APDU sending.  */
+   APP_TYPE_OPENPGP,
+   APP_TYPE_NKS,
+   APP_TYPE_DINSIG,
+   APP_TYPE_P15,
+   APP_TYPE_GELDKARTE,
+   APP_TYPE_SC_HSM,
+   APP_TYPE_PIV,
+   APP_TYPE_UNKNOWN      /* Unknown by this tool.  */
+  } app_type_t;
+
+
+/* OpenPGP card key attributes.  */
+struct key_attr
+{
+  int algo;              /* Algorithm identifier.  */
+  union {
+    unsigned int nbits;  /* Supported keysize.  */
+    const char *curve;   /* Name of curve.  */
+  };
+};
+
+
+/*
+ * The object used to store information about a card.
+ */
+struct card_info_s
+{
+  int error;         /* private. */
+  char *reader;      /* Reader information.  */
+  char *apptypestr;  /* Malloced application type string.  */
+  app_type_t apptype;/* Translated from APPTYPESTR.  */
+  char *serialno;    /* malloced hex string. */
+  char *dispserialno;/* malloced string. */
+  char *disp_name;   /* malloced. */
+  char *disp_lang;   /* malloced. */
+  int  disp_sex;     /* 0 = unspecified, 1 = male, 2 = female */
+  char *pubkey_url;  /* malloced. */
+  char *login_data;  /* malloced. */
+  char *private_do[4]; /* malloced. */
+  char cafpr1len;     /* Length of the CA-fingerprint or 0 if invalid.  */
+  char cafpr2len;
+  char cafpr3len;
+  char cafpr1[20];
+  char cafpr2[20];
+  char cafpr3[20];
+  unsigned char fpr1len; /* Length of the fingerprint or 0 if invalid.  */
+  unsigned char fpr2len;
+  unsigned char fpr3len;
+  char fpr1[20];
+  char fpr2[20];
+  char fpr3[20];
+  u32  fpr1time;
+  u32  fpr2time;
+  u32  fpr3time;
+  char grp1[20];     /* The keygrip for OPENPGP.1 */
+  char grp2[20];     /* The keygrip for OPENPGP.2 */
+  char grp3[20];     /* The keygrip for OPENPGP.3 */
+  unsigned long sig_counter;
+  int chv1_cached;   /* True if a PIN is not required for each
+                        signing.  Note that the gpg-agent might cache
+                        it anyway. */
+  int is_v2;         /* True if this is a v2 card.  */
+  int chvmaxlen[3];  /* Maximum allowed length of a CHV. */
+  int chvretry[3];   /* Allowed retries for the CHV; 0 = blocked. */
+  struct key_attr key_attr[3]; /* OpenPGP card key attributes.  */
+  struct {
+    unsigned int ki:1;     /* Key import available.  */
+    unsigned int aac:1;    /* Algorithm attributes are changeable.  */
+    unsigned int kdf:1;    /* KDF object to support PIN hashing available.  */
+    unsigned int bt:1;     /* Button for confirmation available.  */
+  } extcap;
+  unsigned int status_indicator;
+  int kdf_do_enabled;      /* True if card has a KDF object.  */
+  int uif[3];              /* True if User Interaction Flag is on.  */
+};
+typedef struct card_info_s *card_info_t;
+
+
+/*-- card-call-scd.c --*/
+void release_card_info (card_info_t info);
+const char *app_type_string (app_type_t app_type);
+
+gpg_error_t scd_apdu (const char *hexapdu, unsigned int *r_sw);
+gpg_error_t scd_learn (card_info_t info);
+gpg_error_t scd_getattr (const char *name, struct card_info_s *info);
+gpg_error_t scd_setattr (const char *name,
+                         const unsigned char *value, size_t valuelen);
+gpg_error_t scd_writecert (const char *certidstr,
+                           const unsigned char *certdata, size_t certdatalen);
+gpg_error_t scd_writekey (int keyno,
+                          const unsigned char *keydata, size_t keydatalen);
+gpg_error_t scd_genkey (int keyno, int force, u32 *createtime);
+gpg_error_t scd_serialno (char **r_serialno, const char *demand);
+gpg_error_t scd_readcert (const char *certidstr,
+                          void **r_buf, size_t *r_buflen);
+gpg_error_t scd_cardlist (strlist_t *result);
+gpg_error_t scd_change_pin (int chvno);
+gpg_error_t scd_checkpin (const char *serialno);
+
+unsigned long agent_get_s2k_count (void);
+
+
+
+#endif /*GNUPG_CARD_TOOL_H*/
index 91f0410..b40914a 100644 (file)
@@ -1,5 +1,5 @@
 /* gpg-card-tool.c - An interactive tool to work with cards.
- * Copyright (C) 2019 g10 Code GmbH Werner Koch
+ * Copyright (C) 2019 g10 Code GmbH
  *
  * This file is part of GnuPG.
  *
@@ -15,7 +15,7 @@
  *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, see <https://gnu.org/licenses/>.
- * SPDX-License-Identifier: GPL-3.0+
+ * SPDX-License-Identifier: GPL-3.0-or-later
  */
 
 #include <config.h>
 #include "../common/ccparray.h"
 #include "../common/exectool.h"
 #include "../common/ttyio.h"
+#include "../common/server-help.h"
+#include "../common/openpgpdefs.h"
+
+#include "card-tool.h"
+
 
 #define CONTROL_D ('D' - 'A' + 1)
 
@@ -54,6 +59,15 @@ enum cmd_and_opt_values
     oGpgsmProgram,
     oStatusFD,
     oWithColons,
+    oNoAutostart,
+    oAgentProgram,
+
+    oDisplay,
+    oTTYname,
+    oTTYtype,
+    oXauthority,
+    oLCctype,
+    oLCmessages,
 
     oDummy
   };
@@ -72,15 +86,18 @@ static ARGPARSE_OPTS opts[] = {
   ARGPARSE_s_s (oGpgsmProgram, "gpgsm", "@"),
   ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")),
   ARGPARSE_s_n (oWithColons, "with-colons", "@"),
+  ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"),
+  ARGPARSE_s_s (oAgentProgram, "agent-program", "@"),
+  ARGPARSE_s_s (oDisplay,    "display",    "@"),
+  ARGPARSE_s_s (oTTYname,    "ttyname",    "@"),
+  ARGPARSE_s_s (oTTYtype,    "ttytype",    "@"),
+  ARGPARSE_s_s (oXauthority, "xauthority", "@"),
+  ARGPARSE_s_s (oLCctype,    "lc-ctype",   "@"),
+  ARGPARSE_s_s (oLCmessages, "lc-messages","@"),
 
   ARGPARSE_end ()
 };
 
-/* Debug values and macros.  */
-#define DBG_IPC_VALUE      1024 /* Debug assuan communication.  */
-#define DBG_EXTPROG_VALUE 16384 /* debug external program calls */
-
-
 /* The list of supported debug flags.  */
 static struct debug_flags_s debug_flags [] =
   {
@@ -90,19 +107,19 @@ static struct debug_flags_s debug_flags [] =
   };
 
 
+/* Limit of size of data we read from a file for certain commands.  */
+#define MAX_GET_DATA_FROM_FILE 16384
+
+/* Constats for OpenPGP cards.  */
+#define OPENPGP_USER_PIN_DEFAULT  "123456"
+#define OPENPGP_ADMIN_PIN_DEFAULT "12345678"
+#define OPENPGP_KDF_DATA_LENGTH_MIN  90
+#define OPENPGP_KDF_DATA_LENGTH_MAX 110
+
 
-/* We keep all global options in the structure OPT.  */
-struct
-{
-  int verbose;
-  unsigned int debug;
-  int quiet;
-  int with_colons;
-  const char *gpg_program;
-  const char *gpgsm_program;
-} opt;
 
 
+/* Local prototypes.  */
 static void wrong_args (const char *text) GPGRT_ATTR_NORETURN;
 static void interactive_loop (void);
 #ifdef HAVE_LIBREADLINE
@@ -148,6 +165,18 @@ wrong_args (const char *text)
 }
 
 
+static void
+set_opt_session_env (const char *name, const char *value)
+{
+  gpg_error_t err;
+
+  err = session_env_setenv (opt.session_env, name, value);
+  if (err)
+    log_fatal ("error setting session environment: %s\n",
+               gpg_strerror (err));
+}
+
+
 \f
 /* Command line parsing.  */
 static enum cmd_and_opt_values
@@ -170,18 +199,24 @@ parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts)
             }
           break;
 
-        case oGpgProgram:
-          opt.gpg_program = pargs->r.ret_str;
-          break;
-        case oGpgsmProgram:
-          opt.gpgsm_program = pargs->r.ret_str;
-          break;
+        case oGpgProgram:   opt.gpg_program = pargs->r.ret_str; break;
+        case oGpgsmProgram: opt.gpgsm_program = pargs->r.ret_str; break;
+        case oAgentProgram: opt.agent_program = pargs->r.ret_str; break;
+
         case oStatusFD:
           gnupg_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1));
           break;
-        case oWithColons:
-          opt.with_colons = 1;
-          break;
+
+        case oWithColons:  opt.with_colons = 1; break;
+        case oNoAutostart: opt.autostart = 0; break;
+
+        case oDisplay: set_opt_session_env ("DISPLAY", pargs->r.ret_str); break;
+        case oTTYname: set_opt_session_env ("GPG_TTY", pargs->r.ret_str); break;
+        case oTTYtype: set_opt_session_env ("TERM", pargs->r.ret_str); break;
+        case oXauthority: set_opt_session_env ("XAUTHORITY",
+                                               pargs->r.ret_str); break;
+        case oLCctype:     opt.lc_ctype = pargs->r.ret_str; break;
+        case oLCmessages:  opt.lc_messages = pargs->r.ret_str; break;
 
         default: pargs->err = 2; break;
        }
@@ -212,6 +247,14 @@ main (int argc, char **argv)
   assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
   setup_libassuan_logging (&opt.debug, NULL);
 
+  /* Setup default options.  */
+  opt.autostart = 1;
+  opt.session_env = session_env_new ();
+  if (!opt.session_env)
+    log_fatal ("error allocating session environment block: %s\n",
+               gpg_strerror (gpg_error_from_syserror ()));
+
+
   /* Parse the command line. */
   pargs.argc  = &argc;
   pargs.argv  = &argv;
@@ -256,522 +299,2316 @@ main (int argc, char **argv)
 }
 
 
+/* Read data from file FNAME up to MAX_GET_DATA_FROM_FILE characters.
+ * On error return an error code and stores NULL at R_BUFFER; on
+ * success returns 0, stpres the number of bytes read at R_BUFLEN and
+ * the address of a newly allocated buffer at R_BUFFER. */
+static gpg_error_t
+get_data_from_file (const char *fname, char **r_buffer, size_t *r_buflen)
+{
+  gpg_error_t err;
+  estream_t fp;
+  char *data;
+  int n;
 
-/* Print all available information about the current card. */
-static void
-print_card_status (char *serialno, size_t serialnobuflen)
-{
-  /* struct agent_card_info_s info; */
-  /* PKT_public_key *pk = xcalloc (1, sizeof *pk); */
-  /* kbnode_t keyblock = NULL; */
-  /* int rc; */
-  /* unsigned int uval; */
-  /* const unsigned char *thefpr; */
-  /* unsigned int thefprlen; */
-  /* int i; */
-
-  /* if (serialno && serialnobuflen) */
-  /*   *serialno = 0; */
-
-  /* rc = agent_scd_learn (&info, 0); */
-  /* if (rc) */
-  /*   { */
-  /*     if (opt.with_colons) */
-  /*       es_fputs ("AID:::\n", fp); */
-  /*     log_error (_("OpenPGP card not available: %s\n"), gpg_strerror (rc)); */
-  /*     xfree (pk); */
-  /*     return; */
-  /*   } */
-
-  /* if (opt.with_colons) */
-  /*   es_fprintf (fp, "Reader:%s:", info.reader? info.reader : ""); */
-  /* else */
-  /*   tty_fprintf (fp, "Reader ...........: %s\n", */
-  /*                info.reader? info.reader : "[none]"); */
-  /* if (opt.with_colons) */
-  /*   es_fprintf (fp, "AID:%s:", info.serialno? info.serialno : ""); */
-  /* else */
-  /*   tty_fprintf (fp, "Application ID ...: %s\n", */
-  /*                info.serialno? info.serialno : "[none]"); */
-  /* if (!info.serialno || strncmp (info.serialno, "D27600012401", 12) */
-  /*     || strlen (info.serialno) != 32 ) */
-  /*   { */
-  /*     if (info.apptype && !strcmp (info.apptype, "NKS")) */
-  /*       { */
-  /*         if (opt.with_colons) */
-  /*           es_fputs ("netkey-card:\n", fp); */
-  /*         log_info ("this is a NetKey card\n"); */
-  /*       } */
-  /*     else if (info.apptype && !strcmp (info.apptype, "DINSIG")) */
-  /*       { */
-  /*         if (opt.with_colons) */
-  /*           es_fputs ("dinsig-card:\n", fp); */
-  /*         log_info ("this is a DINSIG compliant card\n"); */
-  /*       } */
-  /*     else if (info.apptype && !strcmp (info.apptype, "P15")) */
-  /*       { */
-  /*         if (opt.with_colons) */
-  /*           es_fputs ("pkcs15-card:\n", fp); */
-  /*         log_info ("this is a PKCS#15 compliant card\n"); */
-  /*       } */
-  /*     else if (info.apptype && !strcmp (info.apptype, "GELDKARTE")) */
-  /*       { */
-  /*         if (opt.with_colons) */
-  /*           es_fputs ("geldkarte-card:\n", fp); */
-  /*         log_info ("this is a Geldkarte compliant card\n"); */
-  /*       } */
-  /*     else */
-  /*       { */
-  /*         if (opt.with_colons) */
-  /*           es_fputs ("unknown:\n", fp); */
-  /*       } */
-  /*     log_info ("not an OpenPGP card\n"); */
-  /*     agent_release_card_info (&info); */
-  /*     xfree (pk); */
-  /*     return; */
-  /*   } */
+  *r_buffer = NULL;
+  *r_buflen = 0;
 
-  /* if (!serialno) */
-  /*   ; */
-  /* else if (strlen (info.serialno)+1 > serialnobuflen) */
-  /*   log_error ("serial number longer than expected\n"); */
-  /* else */
-  /*   strcpy (serialno, info.serialno); */
-
-  /* if (opt.with_colons) */
-  /*   es_fputs ("openpgp-card:\n", fp); */
-
-
-  /*     tty_fprintf (fp, "Version ..........: %.1s%c.%.1s%c\n", */
-  /*                  info.serialno[12] == '0'?"":info.serialno+12, */
-  /*                  info.serialno[13], */
-  /*                  info.serialno[14] == '0'?"":info.serialno+14, */
-  /*                  info.serialno[15]); */
-  /*     tty_fprintf (fp, "Manufacturer .....: %s\n", */
-  /*                  get_manufacturer (xtoi_2(info.serialno+16)*256 */
-  /*                                    + xtoi_2 (info.serialno+18))); */
-  /*     tty_fprintf (fp, "Serial number ....: %.8s\n", info.serialno+20); */
-
-  /*     print_isoname (fp, "Name of cardholder: ", "name", info.disp_name); */
-  /*     print_name (fp, "Language prefs ...: ", info.disp_lang); */
-  /*     tty_fprintf (fp, "Salutation .......: %s\n", */
-  /*                  info.disp_sex == 1? _("Mr."): */
-  /*                  info.disp_sex == 2? _("Mrs.") : ""); */
-  /*     print_name (fp, "URL of public key : ", info.pubkey_url); */
-  /*     print_name (fp, "Login data .......: ", info.login_data); */
-  /*     if (info.private_do[0]) */
-  /*       print_name (fp, "Private DO 1 .....: ", info.private_do[0]); */
-  /*     if (info.private_do[1]) */
-  /*       print_name (fp, "Private DO 2 .....: ", info.private_do[1]); */
-  /*     if (info.private_do[2]) */
-  /*       print_name (fp, "Private DO 3 .....: ", info.private_do[2]); */
-  /*     if (info.private_do[3]) */
-  /*       print_name (fp, "Private DO 4 .....: ", info.private_do[3]); */
-  /*     if (info.cafpr1len) */
-  /*       { */
-  /*         tty_fprintf (fp, "CA fingerprint %d .:", 1); */
-  /*         print_shax_fpr (fp, info.cafpr1, info.cafpr1len); */
-  /*       } */
-  /*     if (info.cafpr2len) */
-  /*       { */
-  /*         tty_fprintf (fp, "CA fingerprint %d .:", 2); */
-  /*         print_shax_fpr (fp, info.cafpr2, info.cafpr2len); */
-  /*       } */
-  /*     if (info.cafpr3len) */
-  /*       { */
-  /*         tty_fprintf (fp, "CA fingerprint %d .:", 3); */
-  /*         print_shax_fpr (fp, info.cafpr3, info.cafpr3len); */
-  /*       } */
-  /*     tty_fprintf (fp,    "Signature PIN ....: %s\n", */
-  /*                  info.chv1_cached? _("not forced"): _("forced")); */
-  /*     if (info.key_attr[0].algo) */
-  /*       { */
-  /*         tty_fprintf (fp,    "Key attributes ...:"); */
-  /*         for (i=0; i < DIM (info.key_attr); i++) */
-  /*           if (info.key_attr[i].algo == PUBKEY_ALGO_RSA) */
-  /*             tty_fprintf (fp, " rsa%u", info.key_attr[i].nbits); */
-  /*           else if (info.key_attr[i].algo == PUBKEY_ALGO_ECDH */
-  /*                    || info.key_attr[i].algo == PUBKEY_ALGO_ECDSA */
-  /*                    || info.key_attr[i].algo == PUBKEY_ALGO_EDDSA) */
-  /*             { */
-  /*               const char *curve_for_print = "?"; */
-
-  /*               if (info.key_attr[i].curve) */
-  /*                 { */
-  /*                   const char *oid; */
-  /*                   oid = openpgp_curve_to_oid (info.key_attr[i].curve, NULL); */
-  /*                   if (oid) */
-  /*                     curve_for_print = openpgp_oid_to_curve (oid, 0); */
-  /*                 } */
-  /*               tty_fprintf (fp, " %s", curve_for_print); */
-  /*             } */
-  /*         tty_fprintf (fp, "\n"); */
-  /*       } */
-  /*     tty_fprintf (fp,    "Max. PIN lengths .: %d %d %d\n", */
-  /*                  info.chvmaxlen[0], info.chvmaxlen[1], info.chvmaxlen[2]); */
-  /*     tty_fprintf (fp,    "PIN retry counter : %d %d %d\n", */
-  /*                  info.chvretry[0], info.chvretry[1], info.chvretry[2]); */
-  /*     tty_fprintf (fp,    "Signature counter : %lu\n", info.sig_counter); */
-  /*     if (info.extcap.kdf) */
-  /*       { */
-  /*         tty_fprintf (fp, "KDF setting ......: %s\n", */
-  /*                      info.kdf_do_enabled ? "on" : "off"); */
-  /*       } */
-  /*     if (info.extcap.bt) */
-  /*       { */
-  /*         tty_fprintf (fp, "UIF setting ......: Sign=%s Decrypt=%s Auth=%s\n", */
-  /*                      info.uif[0] ? "on" : "off", info.uif[1] ? "on" : "off", */
-  /*                      info.uif[2] ? "on" : "off"); */
-  /*       } */
-  /*     tty_fprintf (fp, "Signature key ....:"); */
-  /*     print_shax_fpr (fp, info.fpr1len? info.fpr1:NULL, info.fpr1len); */
-  /*     if (info.fpr1len && info.fpr1time) */
-  /*       { */
-  /*         tty_fprintf (fp, "      created ....: %s\n", */
-  /*                      isotimestamp (info.fpr1time)); */
-  /*         print_keygrip (fp, info.grp1); */
-  /*       } */
-  /*     tty_fprintf (fp, "Encryption key....:"); */
-  /*     print_shax_fpr (fp, info.fpr2len? info.fpr2:NULL, info.fpr2len); */
-  /*     if (info.fpr2len && info.fpr2time) */
-  /*       { */
-  /*         tty_fprintf (fp, "      created ....: %s\n", */
-  /*                      isotimestamp (info.fpr2time)); */
-  /*         print_keygrip (fp, info.grp2); */
-  /*       } */
-  /*     tty_fprintf (fp, "Authentication key:"); */
-  /*     print_shax_fpr (fp, info.fpr3len? info.fpr3:NULL, info.fpr3len); */
-  /*     if (info.fpr3len && info.fpr3time) */
-  /*       { */
-  /*         tty_fprintf (fp, "      created ....: %s\n", */
-  /*                      isotimestamp (info.fpr3time)); */
-  /*         print_keygrip (fp, info.grp3); */
-  /*       } */
-  /*     tty_fprintf (fp, "General key info..: "); */
-
-  /*     thefpr = (info.fpr1len? info.fpr1 : info.fpr2len? info.fpr2 : */
-  /*               info.fpr3len? info.fpr3 : NULL); */
-  /*     thefprlen = (info.fpr1len? info.fpr1len : info.fpr2len? info.fpr2len : */
-  /*                  info.fpr3len? info.fpr3len : 0); */
-  /*     /\* If the fingerprint is all 0xff, the key has no associated */
-  /*        OpenPGP certificate.  *\/ */
-  /*     if ( thefpr && !fpr_is_ff (thefpr, thefprlen) */
-  /*          && !get_pubkey_byfprint (ctrl, pk, &keyblock, thefpr, thefprlen)) */
-  /*       { */
-  /*         print_pubkey_info (ctrl, fp, pk); */
-  /*         if (keyblock) */
-  /*           print_card_key_info (fp, keyblock); */
-  /*       } */
-  /*     else */
-  /*       tty_fprintf (fp, "[none]\n"); */
-
-  /* release_kbnode (keyblock); */
-  /* free_public_key (pk); */
-  /* agent_release_card_info (&info); */
-}
+  fp = es_fopen (fname, "rb");
+  if (!fp)
+    {
+      err = gpg_error_from_syserror ();
+      log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err));
+      return err;
+    }
 
+  data = xtrymalloc (MAX_GET_DATA_FROM_FILE);
+  if (!data)
+    {
+      err = gpg_error_from_syserror ();
+      log_error (_("error allocating enough memory: %s\n"), gpg_strerror (err));
+      es_fclose (fp);
+      return err;
+    }
 
-\f
-static void
-cmd_verify (void)
-{
-  /* agent_scd_checkpin (serialnobuf); */
+  n = es_fread (data, 1, MAX_GET_DATA_FROM_FILE, fp);
+  es_fclose (fp);
+  if (n < 0)
+    {
+      err = gpg_error_from_syserror ();
+      tty_printf (_("error reading '%s': %s\n"), fname, gpg_strerror (err));
+      xfree (data);
+      return err;
+    }
+  *r_buffer = data;
+  *r_buflen = n;
+  return 0;
 }
 
 
-static void
-cmd_name (void)
+/* Write LENGTH bytes from BUFFER to file FNAME.  Return 0 on
+ * success.  */
+static gpg_error_t
+put_data_to_file (const char *fname, const void *buffer, size_t length)
 {
-  /* change_name (); */
-}
+  gpg_error_t err;
+  estream_t fp;
 
+  fp = es_fopen (fname, "wb");
+  if (!fp)
+    {
+      err = gpg_error_from_syserror ();
+      log_error (_("can't create '%s': %s\n"), fname, gpg_strerror (err));
+      return err;
+    }
 
-static void
-cmd_url (void)
-{
-  /* change_url (); */
+  if (length && es_fwrite (buffer, length, 1, fp) != 1)
+    {
+      err = gpg_error_from_syserror ();
+      log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err));
+      es_fclose (fp);
+      return err;
+    }
+  if (es_fclose (fp))
+    {
+      err = gpg_error_from_syserror ();
+      log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err));
+      return err;
+    }
+  return 0;
 }
 
 
-static void
-cmd_fetch (void)
+\f
+/* Simply prints TEXT to the output.  Returns 0 as a convenience.
+ * This is a separate fucntion so that it can be extended to run
+ * less(1) or so.  The extra arguments are int values terminated by a
+ * 0 to indicate card application types supported with this command.
+ * If none are given (just teh final 0), this is a general
+ * command.  */
+static gpg_error_t
+print_help (const char *text, ...)
 {
-  /* fetch_url (); */
-}
+  va_list arg_ptr;
+  int value;
+  int any = 0;
 
+  tty_fprintf (NULL, "%s\n", text);
 
-static void
-cmd_login (char *arg_string)
-{
-  /* change_login (arg_string); */
-}
-
+  va_start (arg_ptr, text);
+  while ((value = va_arg (arg_ptr, int)))
+    {
+      if (!any)
+        tty_fprintf (NULL, "[Supported by: ");
+      tty_fprintf (NULL, "%s%s", any?", ":"", app_type_string (value));
+      any = 1;
+    }
+  if (any)
+    tty_fprintf (NULL, "]\n");
 
-static void
-cmd_lang (void)
-{
-  /* change_lang (); */
+  va_end (arg_ptr);
+  return 0;
 }
 
 
-static void
-cmd_salut (void)
+/* Return the OpenPGP card manufacturer name.  */
+static const char *
+get_manufacturer (unsigned int no)
 {
-  /* change_salut (); */
+  /* Note:  Make sure that there is no colon or linefeed in the string. */
+  switch (no)
+    {
+    case 0x0001: return "PPC Card Systems";
+    case 0x0002: return "Prism";
+    case 0x0003: return "OpenFortress";
+    case 0x0004: return "Wewid";
+    case 0x0005: return "ZeitControl";
+    case 0x0006: return "Yubico";
+    case 0x0007: return "OpenKMS";
+    case 0x0008: return "LogoEmail";
+    case 0x0009: return "Fidesmo";
+    case 0x000A: return "Dangerous Things";
+
+    case 0x002A: return "Magrathea";
+    case 0x0042: return "GnuPG e.V.";
+
+    case 0x1337: return "Warsaw Hackerspace";
+    case 0x2342: return "warpzone"; /* hackerspace Muenster.  */
+    case 0x4354: return "Confidential Technologies";   /* cotech.de */
+    case 0x63AF: return "Trustica";
+    case 0xBD0E: return "Paranoidlabs";
+    case 0xF517: return "FSIJ";
+
+      /* 0x0000 and 0xFFFF are defined as test cards per spec,
+       * 0xFF00 to 0xFFFE are assigned for use with randomly created
+       * serial numbers.  */
+    case 0x0000:
+    case 0xffff: return "test card";
+    default: return (no & 0xff00) == 0xff00? "unmanaged S/N range":"unknown";
+    }
 }
 
-
+/* Print an (OpenPGP) fingerprint.  */
 static void
-cmd_cafpr (int arg_number)
+print_shax_fpr (estream_t fp, const unsigned char *fpr, unsigned int fprlen)
 {
-  if ( arg_number < 1 || arg_number > 3 )
-    tty_printf ("usage: cafpr N\n"
-                "       1 <= N <= 3\n");
-  /* else */
-  /*   change_cafpr (arg_number); */
-}
-
+  int i;
 
-static void
-cmd_privatedo (int arg_number, char *arg_string)
-{
-  if ( arg_number < 1 || arg_number > 4 )
-    tty_printf ("usage: privatedo N\n"
-                "       1 <= N <= 4\n");
-  /* else */
-  /*   change_private_do (arg_string, arg_number); */
+  if (fpr)
+    {
+      /* FIXME: Fix formatting for FPRLEN != 20 */
+      for (i=0; i < fprlen ; i+=2, fpr += 2 )
+        {
+          if (i == 10 )
+            tty_fprintf (fp, " ");
+          tty_fprintf (fp, " %02X%02X", *fpr, fpr[1]);
+        }
+    }
+  else
+    tty_fprintf (fp, " [none]");
+  tty_fprintf (fp, "\n");
 }
 
-
+/* Print the keygrip GRP.  */
 static void
-cmd_writecert (int arg_number, char *arg_rest)
+print_keygrip (estream_t fp, const unsigned char *grp)
 {
-  if ( arg_number != 3 )
-    tty_printf ("usage: writecert 3 < FILE\n");
-  /* else */
-  /*   change_cert (arg_rest); */
+  int i;
+
+  for (i=0; i < 20 ; i++, grp++)
+    tty_fprintf (fp, "%02X", *grp);
+  tty_fprintf (fp, "\n");
 }
 
 
+/* Print a string but avoid printing control characters.  */
 static void
-cmd_readcert (int arg_number, char *arg_rest)
+print_string (estream_t fp, const char *text, const char *name)
 {
-  if ( arg_number != 3 )
-    tty_printf ("usage: readcert 3 > FILE\n");
-  /* else */
-  /*   read_cert (arg_rest); */
+  tty_fprintf (fp, "%s", text);
+
+  /* FIXME: tty_printf_utf8_string2 eats everything after and
+     including an @ - e.g. when printing an url. */
+  if (name && *name)
+    {
+      if (fp)
+        print_utf8_buffer2 (fp, name, strlen (name), '\n');
+      else
+        tty_print_utf8_string2 (NULL, name, strlen (name), 0);
+    }
+  else
+    tty_fprintf (fp, _("[not set]"));
+  tty_fprintf (fp, "\n");
 }
 
 
+/* Print an ISO formatted name or "[not set]".  */
 static void
-cmd_forcesig (void)
+print_isoname (estream_t fp, const char *name)
 {
-  /* toggle_forcesig (); */
-}
+  if (name && *name)
+    {
+      char *p, *given, *buf;
+
+      buf = xstrdup (name);
+      given = strstr (buf, "<<");
+      for (p=buf; *p; p++)
+        if (*p == '<')
+          *p = ' ';
+      if (given && given[2])
+        {
+          *given = 0;
+          given += 2;
+          if (fp)
+            print_utf8_buffer2 (fp, given, strlen (given), '\n');
+          else
+            tty_print_utf8_string2 (NULL, given, strlen (given), 0);
+
+          if (*buf)
+            tty_fprintf (fp, " ");
+        }
 
+      if (fp)
+        print_utf8_buffer2 (fp, buf, strlen (buf), '\n');
+      else
+        tty_print_utf8_string2 (NULL, buf, strlen (buf), 0);
 
-static void
-cmd_generate (void)
-{
-  /* generate_card_keys (); */
+      xfree (buf);
+    }
+  else
+    {
+      tty_fprintf (fp, _("[not set]"));
+    }
+
+  tty_fprintf (fp, "\n");
 }
 
 
-static void
-cmd_passwd (int allow_admin)
+/* Return true if the SHA1 fingerprint FPR consists only of zeroes. */
+static int
+fpr_is_zero (const char *fpr, unsigned int fprlen)
 {
-  /* change_pin (0, allow_admin); */
+  int i;
+
+  for (i=0; i < fprlen && !fpr[i]; i++)
+    ;
+  return (i == fprlen);
 }
 
 
-static void
-cmd_unblock (int allow_admin)
+/* Return true if the fingerprint FPR consists only of 0xFF. */
+static int
+fpr_is_ff (const char *fpr, unsigned int fprlen)
 {
-  /* change_pin (1, allow_admin); */
+  int i;
+
+  for (i=0; i < fprlen && fpr[i] == '\xff'; i++)
+    ;
+  return (i == fprlen);
 }
 
 
+\f
+/* List OpenPGP card specific data.  */
 static void
-cmd_factoryreset (void)
+list_openpgp (card_info_t info, estream_t fp)
 {
-  /* factory_reset (); */
-}
+  int i;
 
+  if (!info->serialno
+      || strncmp (info->serialno, "D27600012401", 12)
+      || strlen (info->serialno) != 32 )
+    {
+      tty_fprintf (fp, "invalid OpenPGP card\n");
+      return;
+    }
 
-static void
-cmd_kdfsetup (char *argstring)
-{
-  /* kdf_setup (arg_string); */
+  tty_fprintf (fp, "Version ..........: %.1s%c.%.1s%c\n",
+               info->serialno[12] == '0'?"":info->serialno+12,
+               info->serialno[13],
+               info->serialno[14] == '0'?"":info->serialno+14,
+               info->serialno[15]);
+  tty_fprintf (fp, "Manufacturer .....: %s\n",
+               get_manufacturer (xtoi_2(info->serialno+16)*256
+                                 + xtoi_2 (info->serialno+18)));
+  tty_fprintf (fp, "Name of cardholder: ");
+  print_isoname (fp, info->disp_name);
+
+  print_string (fp, "Language prefs ...: ", info->disp_lang);
+  tty_fprintf (fp, "Salutation .......: %s\n",
+               info->disp_sex == 1? _("Mr."):
+               info->disp_sex == 2? _("Mrs.") : "");
+  print_string (fp, "URL of public key : ", info->pubkey_url);
+  print_string (fp, "Login data .......: ", info->login_data);
+  if (info->private_do[0])
+    print_string (fp, "Private DO 1 .....: ", info->private_do[0]);
+  if (info->private_do[1])
+    print_string (fp, "Private DO 2 .....: ", info->private_do[1]);
+  if (info->private_do[2])
+    print_string (fp, "Private DO 3 .....: ", info->private_do[2]);
+  if (info->private_do[3])
+    print_string (fp, "Private DO 4 .....: ", info->private_do[3]);
+  if (info->cafpr1len)
+    {
+      tty_fprintf (fp, "CA fingerprint %d .:", 1);
+      print_shax_fpr (fp, info->cafpr1, info->cafpr1len);
+    }
+  if (info->cafpr2len)
+    {
+      tty_fprintf (fp, "CA fingerprint %d .:", 2);
+      print_shax_fpr (fp, info->cafpr2, info->cafpr2len);
+    }
+  if (info->cafpr3len)
+    {
+      tty_fprintf (fp, "CA fingerprint %d .:", 3);
+      print_shax_fpr (fp, info->cafpr3, info->cafpr3len);
+    }
+  tty_fprintf (fp, "Signature PIN ....: %s\n",
+               info->chv1_cached? _("not forced"): _("forced"));
+  if (info->key_attr[0].algo)
+    {
+      tty_fprintf (fp,    "Key attributes ...:");
+      for (i=0; i < DIM (info->key_attr); i++)
+        if (info->key_attr[i].algo == PUBKEY_ALGO_RSA)
+          tty_fprintf (fp, " rsa%u", info->key_attr[i].nbits);
+        else if (info->key_attr[i].algo == PUBKEY_ALGO_ECDH
+                 || info->key_attr[i].algo == PUBKEY_ALGO_ECDSA
+                 || info->key_attr[i].algo == PUBKEY_ALGO_EDDSA)
+          {
+            const char *curve_for_print = "?";
+            const char *oid;
+
+            if (info->key_attr[i].curve
+                && (oid = openpgp_curve_to_oid (info->key_attr[i].curve, NULL)))
+              curve_for_print = openpgp_oid_to_curve (oid, 0);
+            tty_fprintf (fp, " %s", curve_for_print);
+          }
+      tty_fprintf (fp, "\n");
+    }
+  tty_fprintf (fp, "Max. PIN lengths .: %d %d %d\n",
+               info->chvmaxlen[0], info->chvmaxlen[1], info->chvmaxlen[2]);
+  tty_fprintf (fp, "PIN retry counter : %d %d %d\n",
+               info->chvretry[0], info->chvretry[1], info->chvretry[2]);
+  tty_fprintf (fp, "Signature counter : %lu\n", info->sig_counter);
+  if (info->extcap.kdf)
+    {
+      tty_fprintf (fp, "KDF setting ......: %s\n",
+                   info->kdf_do_enabled ? "on" : "off");
+    }
+  if (info->extcap.bt)
+    {
+      tty_fprintf (fp, "UIF setting ......: Sign=%s Decrypt=%s Auth=%s\n",
+                   info->uif[0] ? "on" : "off", info->uif[1] ? "on" : "off",
+                   info->uif[2] ? "on" : "off");
+    }
+  tty_fprintf (fp, "Signature key ....:");
+  print_shax_fpr (fp, info->fpr1len? info->fpr1:NULL, info->fpr1len);
+  if (info->fpr1len && info->fpr1time)
+    {
+      tty_fprintf (fp, "      created ....: %s\n",
+                   isotimestamp (info->fpr1time));
+      tty_fprintf (fp, "      keygrip ....: ");
+      print_keygrip (fp, info->grp1);
+    }
+  tty_fprintf (fp, "Encryption key....:");
+  print_shax_fpr (fp, info->fpr2len? info->fpr2:NULL, info->fpr2len);
+  if (info->fpr2len && info->fpr2time)
+    {
+      tty_fprintf (fp, "      created ....: %s\n",
+                   isotimestamp (info->fpr2time));
+      tty_fprintf (fp, "      keygrip ....: ");
+      print_keygrip (fp, info->grp2);
+    }
+  tty_fprintf (fp, "Authentication key:");
+  print_shax_fpr (fp, info->fpr3len? info->fpr3:NULL, info->fpr3len);
+  if (info->fpr3len && info->fpr3time)
+    {
+      tty_fprintf (fp, "      created ....: %s\n",
+                   isotimestamp (info->fpr3time));
+      tty_fprintf (fp, "      keygrip ....: ");
+      print_keygrip (fp, info->grp3);
+    }
+
+  /* tty_fprintf (fp, "General key info->.: "); */
+  /* thefpr = (info->fpr1len? info->fpr1 : info->fpr2len? info->fpr2 : */
+  /*           info->fpr3len? info->fpr3 : NULL); */
+  /* thefprlen = (info->fpr1len? info->fpr1len : info->fpr2len? info->fpr2len : */
+  /*              info->fpr3len? info->fpr3len : 0); */
+  /* If the fingerprint is all 0xff, the key has no associated
+     OpenPGP certificate.  */
+  /* if ( thefpr && !fpr_is_ff (thefpr, thefprlen) */
+  /*      && !get_pubkey_byfprint (ctrl, pk, &keyblock, thefpr, thefprlen)) */
+  /*   { */
+      /* print_pubkey_info (ctrl, fp, pk); */
+      /* if (keyblock) */
+      /*   print_card_key_info (fp, keyblock); */
+  /*   } */
+  /* else */
+  /*   tty_fprintf (fp, "[none]\n"); */
 }
 
 
+/* Print all available information about the current card. */
 static void
-cmd_keyattr (void)
+list_card (card_info_t info)
 {
-  /* key_attr (); */
+  estream_t fp = NULL;
+
+  tty_fprintf (fp, "Reader ...........: %s\n",
+               info->reader? info->reader : "[none]");
+  tty_fprintf (fp, "Serial number ....: %s\n",
+               info->serialno? info->serialno : "[none]");
+  tty_fprintf (fp, "Application Type .: %s%s%s%s\n",
+               app_type_string (info->apptype),
+               info->apptype == APP_TYPE_UNKNOWN && info->apptypestr? "(":"",
+               info->apptype == APP_TYPE_UNKNOWN && info->apptypestr
+               ? info->apptypestr:"",
+               info->apptype == APP_TYPE_UNKNOWN && info->apptypestr? ")":"");
+  if (info->serialno && info->dispserialno
+      && strcmp (info->serialno, info->dispserialno))
+    tty_fprintf (fp, "Displayed S/N ....: %s\n", info->dispserialno);
+
+  switch (info->apptype)
+    {
+    case APP_TYPE_OPENPGP: list_openpgp (info, fp); break;
+    default: break;
+    }
 }
 
 
-static void
-cmd_uif (int arg_number, char *arg_rest)
+\f
+/* The VERIFY command.  */
+static gpg_error_t
+cmd_verify (card_info_t info, char *argstr)
 {
-  if ( arg_number < 1 || arg_number > 3 )
-    tty_printf ("usage: uif N [on|off|permanent]\n"
-                "       1 <= N <= 3\n");
-  /* else */
-  /*   uif (arg_number, arg_rest); */
-}
+  gpg_error_t err;
 
+  if (!info)
+    return print_help ("verify [chvid]", 0);
 
-\f
-/* Data used by the command parser.  This needs to be outside of the
- * function scope to allow readline based command completion.  */
-enum cmdids
-  {
-    cmdNOP = 0,
-    cmdQUIT, cmdADMIN, cmdHELP, cmdLIST, cmdDEBUG, cmdVERIFY,
-    cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSALUT, cmdCAFPR,
-    cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT,
-    cmdREADCERT, cmdUNBLOCK, cmdFACTORYRESET, cmdKDFSETUP,
-    cmdKEYATTR, cmdUIF,
-    cmdINVCMD
-  };
+  if (info->apptype == APP_TYPE_OPENPGP)
+    err = scd_checkpin (info->serialno);
+  else
+    err = scd_checkpin (argstr);
 
-static struct
-{
-  const char *name;
-  enum cmdids id;
-  int admin_only;
-  const char *desc;
-} cmds[] = {
-  { "quit"    , cmdQUIT  , 0, N_("quit this menu")},
-  { "q"       , cmdQUIT  , 0, NULL },
-  { "admin"   , cmdADMIN , 0, N_("show admin commands")},
-  { "help"    , cmdHELP  , 0, N_("show this help")},
-  { "?"       , cmdHELP  , 0, NULL },
-  { "list"    , cmdLIST  , 0, N_("list all available data")},
-  { "l"       , cmdLIST  , 0, NULL },
-  { "debug"   , cmdDEBUG , 0, NULL },
-  { "name"    , cmdNAME  , 1, N_("change card holder's name")},
-  { "url"     , cmdURL   , 1, N_("change URL to retrieve key")},
-  { "fetch"   , cmdFETCH , 0, N_("fetch the key specified in the card URL")},
-  { "login"   , cmdLOGIN , 1, N_("change the login name")},
-  { "lang"    , cmdLANG  , 1, N_("change the language preferences")},
-  { "salutation",cmdSALUT, 1, N_("change card holder's salutation")},
-  { "cafpr"   , cmdCAFPR , 1, N_("change a CA fingerprint")},
-  { "forcesig", cmdFORCESIG, 1, N_("toggle the signature force PIN flag")},
-  { "generate", cmdGENERATE, 1, N_("generate new keys")},
-  { "passwd"  , cmdPASSWD, 0, N_("menu to change or unblock the PIN")},
-  { "verify"  , cmdVERIFY, 0, N_("verify the PIN and list all data")},
-  { "unblock" , cmdUNBLOCK,0, N_("unblock the PIN using a Reset Code")},
-  { "factory-reset", cmdFACTORYRESET, 1, N_("destroy all keys and data")},
-  { "kdf-setup", cmdKDFSETUP, 1, N_("setup KDF for PIN authentication")},
-  { "key-attr", cmdKEYATTR, 1, N_("change the key attribute")},
-  { "uif", cmdUIF, 1, N_("change the User Interaction Flag")},
-  /* Note, that we do not announce these command yet. */
-  { "privatedo", cmdPRIVATEDO, 0, NULL },
-  { "readcert", cmdREADCERT, 0, NULL },
-  { "writecert", cmdWRITECERT, 1, NULL },
-  { NULL, cmdINVCMD, 0, NULL }
-};
+  if (err)
+    log_error ("verify failed: %s <%s>\n",
+               gpg_strerror (err), gpg_strsource (err));
+  return err;
+}
 
 
-/* The main loop.  */
-static void
-interactive_loop (void)
+/* Helper for cmd_name to qyery a part of name.  */
+static char *
+ask_one_name (const char *prompt)
 {
-  char *answer = NULL;         /* The input line.  */
-  enum cmdids cmd = cmdNOP;    /* The command.  */
-  int cmd_admin_only;          /* The command is an admin only command.  */
-  int arg_number;              /* The first argument as a number.  */
-  char *arg_string = "";       /* The first argument as a string.  */
-  char *arg_rest = "";         /* The remaining arguments.  */
-  int redisplay = 1;           /* Whether to redisplay the main info.  */
-  int allow_admin = 0;         /* Whether admin commands are allowed.  */
-  char serialnobuf[50];
-  char *p;
+  char *name;
   int i;
 
   for (;;)
     {
-
-      tty_printf ("\n");
-      if (redisplay)
+      name = tty_get (prompt);
+      trim_spaces (name);
+      tty_kill_prompt ();
+      if (!*name || *name == CONTROL_D)
         {
-          print_card_status (serialnobuf, DIM (serialnobuf));
-          tty_printf("\n");
-          redisplay = 0;
-       }
+          if (*name == CONTROL_D)
+            tty_fprintf (NULL, "\n");
+          xfree (name);
+          return NULL;
+        }
+      for (i=0; name[i] && name[i] >= ' ' && name[i] <= 126; i++)
+        ;
+
+      /* The name must be in Latin-1 and not UTF-8 - lacking the code
+       * to ensure this we restrict it to ASCII. */
+      if (name[i])
+        tty_printf (_("Error: Only plain ASCII is currently allowed.\n"));
+      else if (strchr (name, '<'))
+        tty_printf (_("Error: The \"<\" character may not be used.\n"));
+      else if (strstr (name, "  "))
+        tty_printf (_("Error: Double spaces are not allowed.\n"));
+      else
+        return name;
+      xfree (name);
+    }
+}
 
-      do
-        {
-          xfree (answer);
-          tty_enable_completion (command_completion);
-          answer = tty_get (_("gpg/card> "));
-          tty_kill_prompt();
-          tty_disable_completion ();
-          trim_spaces(answer);
-       }
-      while ( *answer == '#' );
 
-      arg_number = 0;
-      cmd_admin_only = 0;
-      if (!*answer)
-        cmd = cmdLIST; /* We default to the list command */
-      else if (*answer == CONTROL_D)
-        cmd = cmdQUIT;
-      else
-        {
-          if ((p=strchr (answer,' ')))
-            {
-              *p++ = 0;
-              trim_spaces (answer);
-              trim_spaces (p);
-              arg_number = atoi (p);
-              arg_string = p;
-              arg_rest = p;
-              while (digitp (arg_rest))
-                arg_rest++;
-              while (spacep (arg_rest))
-                arg_rest++;
-            }
+/* The NAME command.  */
+static gpg_error_t
+cmd_name (card_info_t info, const char *argstr)
+{
+  gpg_error_t err;
+  char *surname, *givenname;
+  char *isoname, *p;
 
-          for (i=0; cmds[i].name; i++ )
-            if (!ascii_strcasecmp (answer, cmds[i].name ))
-              break;
+  if (!info)
+    return print_help
+      ("name [--clear]\n\n"
+       "Set the name field of an OpenPGP card.  With --clear the stored\n"
+       "name is cleared off the card.", APP_TYPE_OPENPGP, APP_TYPE_NKS, 0);
 
-          cmd = cmds[i].id;
-          cmd_admin_only = cmds[i].admin_only;
-        }
+  if (info->apptype != APP_TYPE_OPENPGP)
+    {
+      log_info ("Note: This is an OpenPGP only command.\n");
+      return gpg_error (GPG_ERR_NOT_SUPPORTED);
+    }
 
-      if (!allow_admin && cmd_admin_only)
-       {
-          tty_printf ("\n");
-          tty_printf (_("Admin-only command\n"));
-          continue;
+ again:
+  if (!strcmp (argstr, "--clear"))
+    isoname = xstrdup (" "); /* No real way to clear; set to space instead. */
+  else
+    {
+      surname = ask_one_name (_("Cardholder's surname: "));
+      givenname = ask_one_name (_("Cardholder's given name: "));
+      if (!surname || !givenname || (!*surname && !*givenname))
+        {
+          xfree (surname);
+          xfree (givenname);
+          return gpg_error (GPG_ERR_CANCELED);
         }
 
-      switch (cmd)
+      isoname = xstrconcat (surname, "<<", givenname, NULL);
+      xfree (surname);
+      xfree (givenname);
+      for (p=isoname; *p; p++)
+        if (*p == ' ')
+          *p = '<';
+
+      if (strlen (isoname) > 39 )
         {
-        case cmdNOP:
-          break;
+          log_info (_("Error: Combined name too long "
+                      "(limit is %d characters).\n"), 39);
+          xfree (isoname);
+          goto again;
+        }
+    }
 
-        case cmdQUIT:
-          goto leave;
+  err = scd_setattr ("DISP-NAME", isoname, strlen (isoname));
 
-        case cmdHELP:
-          for (i=0; cmds[i].name; i++ )
-            if(cmds[i].desc
-              && (!cmds[i].admin_only || (cmds[i].admin_only && allow_admin)))
-              tty_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) );
-          break;
+  xfree (isoname);
+  return err;
+}
 
-       case cmdADMIN:
-          if ( !strcmp (arg_string, "on") )
+
+static gpg_error_t
+cmd_url (card_info_t info, const char *argstr)
+{
+  gpg_error_t err;
+  char *url;
+
+  if (!info)
+    return print_help
+      ("URL [--clear]\n\n"
+       "Set the URL data object.  That data object can be used by\n"
+       "the FETCH command to retrieve the full public key.  The\n"
+       "option --clear deletes the content of that data object.",
+       APP_TYPE_OPENPGP, 0);
+
+  if (info->apptype != APP_TYPE_OPENPGP)
+    {
+      log_info ("Note: This is an OpenPGP only command.\n");
+      return gpg_error (GPG_ERR_NOT_SUPPORTED);
+    }
+
+  if (!strcmp (argstr, "--clear"))
+    url = xstrdup (" "); /* No real way to clear; set to space instead. */
+  else
+    {
+      url = tty_get (_("URL to retrieve public key: "));
+      trim_spaces (url);
+      tty_kill_prompt ();
+      if (!*url || *url == CONTROL_D)
+        {
+          err = gpg_error (GPG_ERR_CANCELED);
+          goto leave;
+        }
+    }
+
+  err = scd_setattr ("PUBKEY-URL", url, strlen (url));
+
+ leave:
+  xfree (url);
+  return err;
+}
+
+
+/* Fetch the key from the URL given on the card or try to get it from
+ * the default keyserver.  */
+static gpg_error_t
+cmd_fetch (card_info_t info)
+{
+  gpg_error_t err;
+
+  if (!info)
+    return print_help
+      ("FETCH\n\n"
+       "Retrieve a key using the URL data object or if that is missing\n"
+       "using the fingerprint.", APP_TYPE_OPENPGP, 0);
+
+  if (info->pubkey_url && *info->pubkey_url)
+    {
+      /* strlist_t sl = NULL; */
+
+      /* add_to_strlist (&sl, info.pubkey_url); */
+      /* err = keyserver_fetch (ctrl, sl, KEYORG_URL); */
+      /* free_strlist (sl); */
+      err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);  /* FIXME */
+    }
+  else if (info->fpr1len)
+    {
+      /* rc = keyserver_import_fprint (ctrl, info.fpr1, info.fpr1len, */
+      /*                               opt.keyserver, 0); */
+      err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);  /* FIXME */
+    }
+  else
+    err = gpg_error (GPG_ERR_NO_DATA);
+
+  return err;
+}
+
+
+static gpg_error_t
+cmd_login (card_info_t info, char *argstr)
+{
+  gpg_error_t err;
+  char *data;
+  size_t datalen;
+
+  if (!info)
+    return print_help
+      ("LOGIN [--clear] [< FILE]\n\n"
+       "Set the login data object.  If FILE is given the data is\n"
+       "is read from that file.  This allows for binary data.\n"
+       "The option --clear deletes the login data.",
+       APP_TYPE_OPENPGP, 0);
+
+  if (!strcmp (argstr, "--clear"))
+    {
+      data = xstrdup (" "); /* kludge.  */
+      datalen = 1;
+    }
+  else if (*argstr == '<')  /* Read it from a file */
+    {
+      for (argstr++; spacep (argstr); argstr++)
+        ;
+      err = get_data_from_file (argstr, &data, &datalen);
+      if (err)
+        goto leave;
+    }
+  else
+    {
+      data = tty_get (_("Login data (account name): "));
+      trim_spaces (data);
+      tty_kill_prompt ();
+      if (!*data || *data == CONTROL_D)
+        {
+          err = gpg_error (GPG_ERR_CANCELED);
+          goto leave;
+        }
+      datalen = strlen (data);
+    }
+
+  err = scd_setattr ("LOGIN-DATA", data, datalen);
+
+ leave:
+  xfree (data);
+  return err;
+}
+
+
+static gpg_error_t
+cmd_lang (card_info_t info, const char *argstr)
+{
+  gpg_error_t err;
+  char *data, *p;
+
+  if (!info)
+    return print_help
+      ("LANG [--clear]\n\n"
+       "Change the language info for the card.  This info can be used\n"
+       "by applications for a personalized greeting.  Up to 4 two-digit\n"
+       "language identifiers can be entered as a preference.  The option\n"
+       "--clear removes all identifiers.  GnuPG does not use this info.",
+       APP_TYPE_OPENPGP, 0);
+
+  if (!strcmp (argstr, "--clear"))
+    data = xstrdup ("  "); /* Note that we need two spaces here.  */
+  else
+    {
+    again:
+      data = tty_get (_("Language preferences: "));
+      trim_spaces (data);
+      tty_kill_prompt ();
+      if (!*data || *data == CONTROL_D)
+        {
+          err = gpg_error (GPG_ERR_CANCELED);
+          goto leave;
+        }
+
+      if (strlen (data) > 8 || (strlen (data) & 1))
+        {
+          log_info (_("Error: invalid length of preference string.\n"));
+          xfree (data);
+          goto again;
+        }
+
+      for (p=data; *p && *p >= 'a' && *p <= 'z'; p++)
+        ;
+      if (*p)
+        {
+          log_info (_("Error: invalid characters in preference string.\n"));
+          xfree (data);
+          goto again;
+        }
+    }
+
+  err = scd_setattr ("DISP-LANG", data, strlen (data));
+
+ leave:
+  xfree (data);
+  return err;
+}
+
+
+static gpg_error_t
+cmd_salut (card_info_t info, const char *argstr)
+{
+  gpg_error_t err;
+  char *data = NULL;
+  const char *str;
+
+  if (!info)
+    return print_help
+      ("SALUT [--clear]\n\n"
+       "Change the salutation info for the card.  This info can be used\n"
+       "by applications for a personalized greeting.  The option --clear\n"
+       "removes this data object.  GnuPG does not use this info.",
+       APP_TYPE_OPENPGP, 0);
+
+ again:
+  if (!strcmp (argstr, "--clear"))
+    str = "9";
+  else
+    {
+      data = tty_get (_("Salutation (M = Mr., F = Mrs., or space): "));
+      trim_spaces (data);
+      tty_kill_prompt ();
+      if (*data == CONTROL_D)
+        {
+          err = gpg_error (GPG_ERR_CANCELED);
+          goto leave;
+        }
+
+      if (!*data)
+        str = "9";
+      else if ((*data == 'M' || *data == 'm') && !data[1])
+        str = "1";
+      else if ((*data == 'F' || *data == 'f') && !data[1])
+        str = "2";
+      else
+        {
+          tty_printf (_("Error: invalid response.\n"));
+          xfree (data);
+          goto again;
+        }
+    }
+
+  err = scd_setattr ("DISP-SEX", str, 1);
+ leave:
+  xfree (data);
+  return err;
+}
+
+
+static gpg_error_t
+cmd_cafpr (card_info_t info, char *argstr)
+{
+  gpg_error_t err;
+  char *data = NULL;
+  const char *s;
+  int i, c;
+  unsigned char fpr[32];
+  int fprlen;
+  int fprno;
+  int opt_clear = 0;
+
+  if (!info)
+    return print_help
+      ("CAFPR [--clear] N\n\n"
+       "Change the CA fingerprint number N.  N must be in the\n"
+       "range 1 to 3.  The option --clear clears the specified\n"
+       "CA fingerprint N or all of them if N is 0 or not given.",
+       APP_TYPE_OPENPGP, 0);
+
+
+  opt_clear = has_leading_option (argstr, "--clear");
+  argstr = skip_options (argstr);
+
+  if (digitp (argstr))
+    {
+      fprno = atoi (argstr);
+      while (digitp (argstr))
+        argstr++;
+      while (spacep (argstr))
+        argstr++;
+    }
+  else
+    fprno = 0;
+
+  if (opt_clear && !fprno)
+    ; /* Okay: clear all fprs.  */
+  else if (fprno < 1 || fprno > 3)
+    {
+      err = gpg_error (GPG_ERR_INV_ARG);
+      goto leave;
+    }
+
+ again:
+  if (opt_clear)
+    {
+      memset (fpr, 0, 20);
+      fprlen = 20;
+    }
+  else
+    {
+      xfree (data);
+      data = tty_get (_("CA fingerprint: "));
+      trim_spaces (data);
+      tty_kill_prompt ();
+      if (!*data || *data == CONTROL_D)
+        {
+          err = gpg_error (GPG_ERR_CANCELED);
+          goto leave;
+        }
+
+      for (i=0, s=data; i < sizeof fpr && *s; )
+        {
+          while (spacep(s))
+            s++;
+          if (*s == ':')
+            s++;
+          while (spacep(s))
+            s++;
+          c = hextobyte (s);
+          if (c == -1)
+            break;
+          fpr[i++] = c;
+          s += 2;
+        }
+      fprlen = i;
+      if ((fprlen != 20 && fprlen != 32) || *s)
+        {
+          log_error (_("Error: invalid formatted fingerprint.\n"));
+          goto again;
+        }
+    }
+
+  if (!fprno)
+    {
+      log_assert (opt_clear);
+      err = scd_setattr ("CA-FPR-1", fpr, fprlen);
+      if (!err)
+        err = scd_setattr ("CA-FPR-2", fpr, fprlen);
+      if (!err)
+        err = scd_setattr ("CA-FPR-3", fpr, fprlen);
+    }
+  else
+    err = scd_setattr (fprno==1?"CA-FPR-1":
+                       fprno==2?"CA-FPR-2":
+                       fprno==3?"CA-FPR-3":"x", fpr, fprlen);
+
+ leave:
+  xfree (data);
+  return err;
+}
+
+
+static gpg_error_t
+cmd_privatedo (card_info_t info, char *argstr)
+{
+  gpg_error_t err;
+  int opt_clear;
+  char *do_name = NULL;
+  char *data = NULL;
+  size_t datalen;
+  int do_no;
+
+  if (!info)
+    return print_help
+      ("PRIVATEDO [--clear] N [< FILE]\n\n"
+       "Change the private data object N.  N must be in the\n"
+       "range 1 to 4.  If FILE is given the data is is read\n"
+       "from that file.  The option --clear clears the data.",
+       APP_TYPE_OPENPGP, 0);
+
+  opt_clear = has_leading_option (argstr, "--clear");
+  argstr = skip_options (argstr);
+
+  if (digitp (argstr))
+    {
+      do_no = atoi (argstr);
+      while (digitp (argstr))
+        argstr++;
+      while (spacep (argstr))
+        argstr++;
+    }
+  else
+    do_no = 0;
+
+  if (do_no < 1 || do_no > 4)
+    {
+      err = gpg_error (GPG_ERR_INV_ARG);
+      goto leave;
+    }
+  do_name = xasprintf ("PRIVATE-DO-%d", do_no);
+
+  if (opt_clear)
+    {
+      data = xstrdup (" ");
+      datalen = 1;
+    }
+  else if (*argstr == '<')  /* Read it from a file */
+    {
+      for (argstr++; spacep (argstr); argstr++)
+        ;
+      err = get_data_from_file (argstr, &data, &datalen);
+      if (err)
+        goto leave;
+    }
+  else if (*argstr)
+    {
+      err = gpg_error (GPG_ERR_INV_ARG);
+      goto leave;
+    }
+  else
+    {
+      data = tty_get (_("Private DO data: "));
+      trim_spaces (data);
+      tty_kill_prompt ();
+      datalen = strlen (data);
+      if (!*data || *data == CONTROL_D)
+        {
+          err = gpg_error (GPG_ERR_CANCELED);
+          goto leave;
+        }
+    }
+
+  err = scd_setattr (do_name, data, datalen);
+
+ leave:
+  xfree (do_name);
+  xfree (data);
+  return err;
+}
+
+
+static gpg_error_t
+cmd_writecert (card_info_t info, char *argstr)
+{
+  gpg_error_t err;
+  int opt_clear;
+  int do_no;
+  char *data = NULL;
+  size_t datalen;
+
+  if (!info)
+    return print_help
+      ("WRITECERT [--clear] 3 < FILE\n\n"
+       "Write a certificate for key 3.  Unless --clear is given\n"
+       "the file argement is mandatory.  The option --clear removes\n"
+       "the certificate from the card.",
+       APP_TYPE_OPENPGP, 0);
+
+  opt_clear = has_leading_option (argstr, "--clear");
+  argstr = skip_options (argstr);
+
+  if (digitp (argstr))
+    {
+      do_no = atoi (argstr);
+      while (digitp (argstr))
+        argstr++;
+      while (spacep (argstr))
+        argstr++;
+    }
+  else
+    do_no = 0;
+
+  if (do_no != 3)
+    {
+      err = gpg_error (GPG_ERR_INV_ARG);
+      goto leave;
+    }
+
+  if (opt_clear)
+    {
+      data = xstrdup (" ");
+      datalen = 1;
+    }
+  else if (*argstr == '<')  /* Read it from a file */
+    {
+      for (argstr++; spacep (argstr); argstr++)
+        ;
+      err = get_data_from_file (argstr, &data, &datalen);
+      if (err)
+        goto leave;
+    }
+  else
+    {
+      err = gpg_error (GPG_ERR_INV_ARG);
+      goto leave;
+    }
+
+  err = scd_writecert ("OPENPGP.3", data, datalen);
+
+ leave:
+  xfree (data);
+  return err;
+}
+
+
+static gpg_error_t
+cmd_readcert (card_info_t info, char *argstr)
+{
+  gpg_error_t err;
+  int do_no;
+  void *data = NULL;
+  size_t datalen;
+  const char *fname;
+
+  if (!info)
+    return print_help
+      ("READCERT 3 > FILE\n\n"
+       "Read the certificate for key 3 and store it in FILE.",
+       APP_TYPE_OPENPGP, 0);
+
+  argstr = skip_options (argstr);
+
+  if (digitp (argstr))
+    {
+      do_no = atoi (argstr);
+      while (digitp (argstr))
+        argstr++;
+      while (spacep (argstr))
+        argstr++;
+    }
+  else
+    do_no = 0;
+
+  if (do_no != 3)
+    {
+      err = gpg_error (GPG_ERR_INV_ARG);
+      goto leave;
+    }
+
+  if (*argstr == '>')  /* Read it from a file */
+    {
+      for (argstr++; spacep (argstr); argstr++)
+        ;
+      fname = argstr;
+    }
+  else
+    {
+      err = gpg_error (GPG_ERR_INV_ARG);
+      goto leave;
+    }
+
+  err = scd_readcert ("OPENPGP.3", &data, &datalen);
+  if (err)
+    goto leave;
+
+  err = put_data_to_file (fname, data, datalen);
+
+ leave:
+  xfree (data);
+  return err;
+}
+
+
+static gpg_error_t
+cmd_forcesig (card_info_t info)
+{
+  gpg_error_t err;
+  int newstate;
+
+  if (!info)
+    return print_help
+      ("FORCESIG\n\n"
+       "Toggle the forcesig flag of an OpenPGP card.",
+       APP_TYPE_OPENPGP, 0);
+
+  if (info->apptype != APP_TYPE_OPENPGP)
+    {
+      log_info ("Note: This is an OpenPGP only command.\n");
+      return gpg_error (GPG_ERR_NOT_SUPPORTED);
+    }
+
+  newstate = !info->chv1_cached;
+
+  err = scd_setattr ("CHV-STATUS-1", newstate? "\x01":"", 1);
+  if (err)
+    goto leave;
+
+  /* Read it back to be sure we have the right toggle state the next
+   * time.  */
+  err = scd_getattr ("CHV-STATUS", info);
+
+ leave:
+  return err;
+}
+
+
+/* Helper for cmd_generate.  Noe that either 0 or 1 is stored at
+ * FORCED_CHV1. */
+static gpg_error_t
+check_pin_for_key_operation (card_info_t info, int *forced_chv1)
+{
+  gpg_error_t err = 0;
+
+  *forced_chv1 = !info->chv1_cached;
+  if (*forced_chv1)
+    { /* Switch off the forced mode so that during key generation we
+       * don't get bothered with PIN queries for each self-signature. */
+      err = scd_setattr ("CHV-STATUS-1", "\x01", 1);
+      if (err)
+        {
+          log_error ("error clearing forced signature PIN flag: %s\n",
+                     gpg_strerror (err));
+          *forced_chv1 = -1;  /* Not changed.  */
+          goto leave;
+        }
+    }
+
+  /* Check the PIN now, so that we won't get asked later for each
+   * binding signature.  */
+  err = scd_checkpin (info->serialno);
+  if (err)
+    log_error ("error checking the PIN: %s\n", gpg_strerror (err));
+
+ leave:
+  return err;
+}
+
+
+/* Helper for cmd_generate.  */
+static void
+restore_forced_chv1 (int *forced_chv1)
+{
+  gpg_error_t err;
+
+  /* Note the possible values stored at FORCED_CHV1:
+   *   0 - forcesig was not enabled.
+   *   1 - forcesig was enabled - enable it again.
+   *  -1 - We have not changed anything.  */
+  if (*forced_chv1 == 1)
+    { /* Switch back to forced state. */
+      err = scd_setattr ("CHV-STATUS-1", "", 1);
+      if (err)
+        log_error ("error setting forced signature PIN flag: %s\n",
+                   gpg_strerror (err));
+      *forced_chv1 = 0;
+    }
+}
+
+
+static gpg_error_t
+cmd_generate (card_info_t info)
+{
+  gpg_error_t err;
+  int forced_chv1 = -1;
+  int want_backup;
+  char *answer = NULL;
+
+  if (!info)
+    return print_help
+      ("GENERATE\n\n"
+       "Menu to generate a new keys.",
+       APP_TYPE_OPENPGP, 0);
+
+  if (info->apptype != APP_TYPE_OPENPGP)
+    {
+      log_info ("Note: This is an OpenPGP only command.\n");
+      return gpg_error (GPG_ERR_NOT_SUPPORTED);
+    }
+
+  if (info->extcap.ki)
+    {
+      xfree (answer);
+      answer = tty_get (_("Make off-card backup of encryption key? (Y/n) "));
+      want_backup = answer_is_yes_no_default (answer, 1/*(default to Yes)*/);
+      tty_kill_prompt ();
+      if (*answer == CONTROL_D)
+        {
+          err = gpg_error (GPG_ERR_CANCELED);
+          goto leave;
+        }
+    }
+  else
+    want_backup = 0;
+
+  if ( (info->fpr1len && !fpr_is_zero (info->fpr1, info->fpr1len))
+       || (info->fpr2len && !fpr_is_zero (info->fpr2, info->fpr2len))
+       || (info->fpr3len && !fpr_is_zero (info->fpr3, info->fpr3len)))
+    {
+      tty_printf ("\n");
+      log_info (_("Note: keys are already stored on the card!\n"));
+      tty_printf ("\n");
+      answer = tty_get (_("Replace existing keys? (y/N) "));
+      tty_kill_prompt ();
+      if (*answer == CONTROL_D)
+        {
+          err = gpg_error (GPG_ERR_CANCELED);
+          goto leave;
+        }
+
+      if (!answer_is_yes_no_default (answer, 0/*(default to No)*/))
+        {
+          err = gpg_error (GPG_ERR_CANCELED);
+          goto leave;
+        }
+    }
+
+  /* If no displayed name has been set, we assume that this is a fresh
+   * card and print a hint about the default PINs.  */
+  if (!info->disp_name || !*info->disp_name)
+    {
+      tty_printf ("\n");
+      tty_printf (_("Please note that the factory settings of the PINs are\n"
+                    "   PIN = '%s'     Admin PIN = '%s'\n"
+                    "You should change them using the command --change-pin\n"),
+                  OPENPGP_USER_PIN_DEFAULT, OPENPGP_ADMIN_PIN_DEFAULT);
+      tty_printf ("\n");
+    }
+
+  err = check_pin_for_key_operation (info, &forced_chv1);
+  if (err)
+    goto leave;
+
+  /* FIXME: We need to divert to a function which spwans gpg which
+   * will then create the key.  This also requires new features in
+   * gpg.  We might also first create the keys on the card and then
+   * tell gpg to use them to create the OpenPGP keyblock. */
+  /* generate_keypair (ctrl, 1, NULL, info.serialno, want_backup); */
+  err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+
+ leave:
+  restore_forced_chv1 (&forced_chv1);
+  xfree (answer);
+  return err;
+}
+
+
+/* Sub-menu to change a PIN.  The presented options may depend on the
+ * the ALLOW_ADMIN flag.  */
+static gpg_error_t
+cmd_passwd (card_info_t info, int allow_admin)
+{
+  gpg_error_t err;
+  char *answer = NULL;
+
+  if (!info)
+    return print_help
+      ("PASSWD\n\n"
+       "Menu to change or unblock the PINs.  Note that the\n"
+       "presented menu options depend on the type of card\n"
+       "and whether the admin mode is enabled.",
+       0);
+
+  /* Convenience message because we did this in gpg --card-edit too.  */
+  if (info->apptype == APP_TYPE_OPENPGP)
+    log_info (_("OpenPGP card no. %s detected\n"),
+              info->dispserialno? info->dispserialno : info->serialno);
+
+  if (!allow_admin)
+    {
+      err = scd_change_pin (1);
+      if (err)
+        goto leave;
+      log_info ("PIN changed.\n");
+    }
+  else if (info->apptype == APP_TYPE_OPENPGP)
+    {
+      for (;;)
+        {
+          tty_printf ("\n");
+          tty_printf ("1 - change PIN\n"
+                      "2 - unblock and set new PIN\n"
+                      "3 - change Admin PIN\n"
+                      "4 - set the Reset Code\n"
+                      "Q - quit\n");
+          tty_printf ("\n");
+
+          err = 0;
+          xfree (answer);
+          answer = tty_get (_("Your selection? "));
+          tty_kill_prompt ();
+          if (*answer == CONTROL_D)
+            break;  /* Quit.  */
+          if (strlen (answer) != 1)
+            continue;
+          if (*answer == 'q' || *answer == 'Q')
+            break;  /* Quit.  */
+
+          if (*answer == '1')
+            {
+              /* Change PIN (same as the direct thing in non-admin mode).  */
+              err = scd_change_pin (1);
+              if (err)
+                log_error ("Error changing the PIN: %s\n", gpg_strerror (err));
+              else
+                log_info ("PIN changed.\n");
+            }
+          else if (*answer == '2')
+            {
+              /* Unblock PIN by setting a new PIN.  */
+              err = scd_change_pin (101);
+              if (err)
+                log_error ("Error unblocking the PIN: %s\n", gpg_strerror(err));
+              else
+                log_info ("PIN unblocked and new PIN set.\n");
+            }
+          else if (*answer == '3')
+            {
+              /* Change Admin PIN.  */
+              err = scd_change_pin (3);
+              if (err)
+                log_error ("Error changing the PIN: %s\n", gpg_strerror (err));
+              else
+                log_info ("PIN changed.\n");
+         }
+          else if (*answer == '4')
+            {
+              /* Set a new Reset Code.  */
+              err = scd_change_pin (102);
+              if (err)
+                log_error ("Error setting the Reset Code: %s\n",
+                           gpg_strerror (err));
+              else
+                log_info ("Reset Code set.\n");
+            }
+
+        } /*end for loop*/
+    }
+  else
+    {
+      log_info ("Admin related passwd options not yet supported for '%s'\n",
+                app_type_string (info->apptype));
+      err = gpg_error (GPG_ERR_NOT_SUPPORTED);
+    }
+
+ leave:
+  xfree (answer);
+  return err;
+}
+
+
+static gpg_error_t
+cmd_unblock (card_info_t info)
+{
+  gpg_error_t err = 0;
+
+  if (!info)
+    return print_help
+      ("UNBLOCK\n\n"
+       "Unblock a PIN using a PUK or Reset Code.  Note that OpenPGP\n"
+       "cards prior to version 2 can't use this; instead the PASSWD\n"
+       "command can be used to set a new PIN.",
+       0);
+
+  if (info->apptype == APP_TYPE_OPENPGP)
+    log_info (_("OpenPGP card no. %s detected\n"),
+              info->dispserialno? info->dispserialno : info->serialno);
+
+  if (info->apptype == APP_TYPE_OPENPGP && !info->is_v2)
+    log_error (_("This command is only available for version 2 cards\n"));
+  else if (info->apptype == APP_TYPE_OPENPGP && !info->chvretry[1])
+    log_error (_("Reset Code not or not anymore available\n"));
+  else if (info->apptype == APP_TYPE_OPENPGP)
+    {
+      err = scd_change_pin (2);
+      if (!err)
+        log_info ("PIN changed.\n");
+    }
+  else
+    log_info ("Unblocking not yet supported for '%s'\n",
+              app_type_string (info->apptype));
+
+  return err;
+}
+
+
+/* Direct sending of an hex encoded APDU with error printing.  */
+static gpg_error_t
+send_apdu (const char *hexapdu, const char *desc, unsigned int ignore)
+{
+  gpg_error_t err;
+  unsigned int sw;
+
+  err = scd_apdu (hexapdu, &sw);
+  if (err)
+    log_error ("sending card command %s failed: %s\n", desc,
+               gpg_strerror (err));
+  else if (!hexapdu || !strcmp (hexapdu, "undefined"))
+    ;
+  else if (ignore == 0xffff)
+    ; /* Ignore all status words.  */
+  else if (sw != 0x9000)
+    {
+      switch (sw)
+        {
+        case 0x6285: err = gpg_error (GPG_ERR_OBJ_TERM_STATE); break;
+        case 0x6982: err = gpg_error (GPG_ERR_BAD_PIN); break;
+        case 0x6985: err = gpg_error (GPG_ERR_USE_CONDITIONS); break;
+        default: err = gpg_error (GPG_ERR_CARD);
+        }
+      if (!(ignore && ignore == sw))
+        log_error ("card command %s failed: %s (0x%04x)\n", desc,
+                   gpg_strerror (err),  sw);
+    }
+  return err;
+}
+
+
+/* Note: On successful execution a redisplay should be scheduled.  If
+ * this function fails the card may be in an unknown state. */
+static gpg_error_t
+cmd_factoryreset (card_info_t info)
+{
+  gpg_error_t err;
+  char *answer = NULL;
+  int termstate = 0;
+  int any_apdu = 0;
+  int i;
+
+
+  if (!info)
+    return print_help
+      ("FACTORY-RESET\n\n"
+       "Do a complete reset of an OpenPGP card.  This deletes all\n"
+       "data and keys and resets the PINs to their default.  This\n"
+       "mainly used by developers with scratch cards.  Don't worry,\n"
+       "you need to confirm before the command proceeds.",
+       APP_TYPE_OPENPGP, 0);
+
+  if (info->apptype != APP_TYPE_OPENPGP)
+    {
+      log_info ("Note: This is an OpenPGP only command.\n");
+      return gpg_error (GPG_ERR_NOT_SUPPORTED);
+    }
+
+  /* The code below basically does the same what this
+   * gpg-connect-agent script does:
+   *
+   *   scd reset
+   *   scd serialno undefined
+   *   scd apdu 00 A4 04 00 06 D2 76 00 01 24 01
+   *   scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
+   *   scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
+   *   scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
+   *   scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
+   *   scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
+   *   scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
+   *   scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
+   *   scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
+   *   scd apdu 00 e6 00 00
+   *   scd apdu 00 44 00 00
+   *   scd reset
+   *   /echo Card has been reset to factory defaults
+   *
+   * but tries to find out something about the card first.
+   */
+
+  err = scd_learn (info);
+  if (gpg_err_code (err) == GPG_ERR_OBJ_TERM_STATE
+      && gpg_err_source (err) == GPG_ERR_SOURCE_SCD)
+    termstate = 1;
+  else if (err)
+    {
+      log_error (_("OpenPGP card not available: %s\n"), gpg_strerror (err));
+      goto leave;
+    }
+
+  if (!termstate)
+    {
+      log_info (_("OpenPGP card no. %s detected\n"),
+                info->dispserialno? info->dispserialno : info->serialno);
+      if (!(info->status_indicator == 3 || info->status_indicator == 5))
+        {
+          /* Note: We won't see status-indicator 3 here because it is not
+           * possible to select a card application in termination state.  */
+          log_error (_("This command is not supported by this card\n"));
+          err = gpg_error (GPG_ERR_NOT_SUPPORTED);
+          goto leave;
+        }
+
+      tty_printf ("\n");
+      log_info
+        (_("Note: This command destroys all keys stored on the card!\n"));
+      tty_printf ("\n");
+      xfree (answer);
+      answer = tty_get (_("Continue? (y/N) "));
+      tty_kill_prompt ();
+      trim_spaces (answer);
+      if (*answer == CONTROL_D
+          || !answer_is_yes_no_default (answer, 0/*(default to no)*/))
+        {
+          err = gpg_error (GPG_ERR_CANCELED);
+          goto leave;
+        }
+
+      xfree (answer);
+      answer = tty_get (_("Really do a factory reset? (enter \"yes\") "));
+      tty_kill_prompt ();
+      trim_spaces (answer);
+      if (strcmp (answer, "yes") && strcmp (answer,_("yes")))
+        {
+          err = gpg_error (GPG_ERR_CANCELED);
+          goto leave;
+        }
+
+      any_apdu = 1;
+      /* We need to select a card application before we can send APDUs
+       * to the card without scdaemon doing anything on its own.  */
+      err = send_apdu (NULL, "RESET", 0);
+      if (err)
+        goto leave;
+      err = send_apdu ("undefined", "dummy select ", 0);
+      if (err)
+        goto leave;
+
+      /* Select the OpenPGP application.  */
+      err = send_apdu ("00A4040006D27600012401", "SELECT AID", 0);
+      if (err)
+        goto leave;
+
+      /* Do some dummy verifies with wrong PINs to set the retry
+       * counter to zero.  We can't easily use the card version 2.1
+       * feature of presenting the admin PIN to allow the terminate
+       * command because there is no machinery in scdaemon to catch
+       * the verify command and ask for the PIN when the "APDU"
+       * command is used.
+       * Here, the length of dummy wrong PIN is 32-byte, also
+       * supporting authentication with KDF DO.  */
+      for (i=0; i < 4; i++)
+        send_apdu ("0020008120"
+                   "40404040404040404040404040404040"
+                   "40404040404040404040404040404040", "VERIFY", 0xffff);
+      for (i=0; i < 4; i++)
+        send_apdu ("0020008320"
+                   "40404040404040404040404040404040"
+                   "40404040404040404040404040404040", "VERIFY", 0xffff);
+
+      /* Send terminate datafile command.  */
+      err = send_apdu ("00e60000", "TERMINATE DF", 0x6985);
+      if (err)
+        goto leave;
+    }
+
+  any_apdu = 1;
+  /* Send activate datafile command.  This is used without
+   * confirmation if the card is already in termination state.  */
+  err = send_apdu ("00440000", "ACTIVATE DF", 0);
+  if (err)
+    goto leave;
+
+  /* Finally we reset the card reader once more.  */
+  err = send_apdu (NULL, "RESET", 0);
+  if (err)
+    goto leave;
+
+  /* Then, connect the card again (answer used as a dummy).  */
+  xfree (answer); answer = NULL;
+  err = scd_serialno (&answer, NULL);
+
+ leave:
+  if (err && any_apdu)
+    {
+      log_info ("Due to an error the card might be in an inconsistent state\n"
+                "You should run the LIST command to check this.\n");
+      /* FIXME: We need a better solution in the case that the card is
+       * in a termination state, i.e. the card was removed before the
+       * activate was sent.  The best solution I found with v2.1
+       * Zeitcontrol card was to kill scdaemon and the issue this
+       * sequence with gpg-connect-agent:
+       *   scd reset
+       *   scd serialno undefined
+       *   scd apdu 00A4040006D27600012401 (returns error)
+       *   scd apdu 00440000
+       * Then kill scdaemon again and issue:
+       *   scd reset
+       *   scd serialno openpgp
+       */
+    }
+  xfree (answer);
+  return err;
+}
+
+
+/* Generate KDF data.  This is a helper for cmd_kdfsetup.  */
+static gpg_error_t
+gen_kdf_data (unsigned char *data, int single_salt)
+{
+  gpg_error_t err;
+  const unsigned char h0[] = { 0x81, 0x01, 0x03,
+                               0x82, 0x01, 0x08,
+                               0x83, 0x04 };
+  const unsigned char h1[] = { 0x84, 0x08 };
+  const unsigned char h2[] = { 0x85, 0x08 };
+  const unsigned char h3[] = { 0x86, 0x08 };
+  const unsigned char h4[] = { 0x87, 0x20 };
+  const unsigned char h5[] = { 0x88, 0x20 };
+  unsigned char *p, *salt_user, *salt_admin;
+  unsigned char s2k_char;
+  unsigned int iterations;
+  unsigned char count_4byte[4];
+
+  p = data;
+
+  s2k_char = encode_s2k_iterations (agent_get_s2k_count ());
+  iterations = S2K_DECODE_COUNT (s2k_char);
+  count_4byte[0] = (iterations >> 24) & 0xff;
+  count_4byte[1] = (iterations >> 16) & 0xff;
+  count_4byte[2] = (iterations >>  8) & 0xff;
+  count_4byte[3] = (iterations & 0xff);
+
+  memcpy (p, h0, sizeof h0);
+  p += sizeof h0;
+  memcpy (p, count_4byte, sizeof count_4byte);
+  p += sizeof count_4byte;
+  memcpy (p, h1, sizeof h1);
+  salt_user = (p += sizeof h1);
+  gcry_randomize (p, 8, GCRY_STRONG_RANDOM);
+  p += 8;
+
+  if (single_salt)
+    salt_admin = salt_user;
+  else
+    {
+      memcpy (p, h2, sizeof h2);
+      p += sizeof h2;
+      gcry_randomize (p, 8, GCRY_STRONG_RANDOM);
+      p += 8;
+      memcpy (p, h3, sizeof h3);
+      salt_admin = (p += sizeof h3);
+      gcry_randomize (p, 8, GCRY_STRONG_RANDOM);
+      p += 8;
+    }
+
+  memcpy (p, h4, sizeof h4);
+  p += sizeof h4;
+  err = gcry_kdf_derive (OPENPGP_USER_PIN_DEFAULT,
+                         strlen (OPENPGP_USER_PIN_DEFAULT),
+                         GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256,
+                         salt_user, 8, iterations, 32, p);
+  p += 32;
+  if (!err)
+    {
+      memcpy (p, h5, sizeof h5);
+      p += sizeof h5;
+      err = gcry_kdf_derive (OPENPGP_ADMIN_PIN_DEFAULT,
+                             strlen (OPENPGP_ADMIN_PIN_DEFAULT),
+                             GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256,
+                             salt_admin, 8, iterations, 32, p);
+    }
+
+  return err;
+}
+
+
+static gpg_error_t
+cmd_kdfsetup (card_info_t info, char *argstr)
+{
+  gpg_error_t err;
+  unsigned char kdf_data[OPENPGP_KDF_DATA_LENGTH_MAX];
+  int single = (*argstr != 0);
+
+  if (!info)
+    return print_help
+      ("KDF-SETUP\n\n"
+       "Prepare the OpenPGP card KDF feature for this card.",
+       APP_TYPE_OPENPGP, 0);
+
+  if (info->apptype != APP_TYPE_OPENPGP)
+    {
+      log_info ("Note: This is an OpenPGP only command.\n");
+      return gpg_error (GPG_ERR_NOT_SUPPORTED);
+    }
+
+  if (!info->extcap.kdf)
+    {
+      log_error (_("This command is not supported by this card\n"));
+      err = gpg_error (GPG_ERR_NOT_SUPPORTED);
+      goto leave;
+    }
+
+  err = gen_kdf_data (kdf_data, single);
+  if (err)
+    goto leave;
+
+  err = scd_setattr ("KDF", kdf_data,
+                     single ? OPENPGP_KDF_DATA_LENGTH_MIN
+                     /* */  : OPENPGP_KDF_DATA_LENGTH_MAX);
+  if (err)
+    goto leave;
+
+  err = scd_getattr ("KDF", info);
+
+ leave:
+  return err;
+}
+
+
+\f
+static void
+show_keysize_warning (void)
+{
+  static int shown;
+
+  if (shown)
+    return;
+  shown = 1;
+  tty_printf
+    (_("Note: There is no guarantee that the card supports the requested\n"
+       "      key type or size.  If the key generation does not succeed,\n"
+       "      please check the documentation of your card to see which\n"
+       "      key types and sizes are supported.\n")
+     );
+}
+
+
+/* Ask for the size of a card key.  NBITS is the current size
+ * configured for the card.  Returns 0 on success and stored the
+ * chosen key size at R_KEYSIZE; 0 is stored to indicate that the
+ * default size shall be used.  */
+static gpg_error_t
+ask_card_rsa_keysize (unsigned int nbits, unsigned int *r_keysize)
+{
+  unsigned int min_nbits = 1024;
+  unsigned int max_nbits = 4096;
+  char*answer;
+  unsigned int req_nbits;
+
+  for (;;)
+    {
+      answer = tty_getf (_("What keysize do you want? (%u) "), nbits);
+      trim_spaces (answer);
+      tty_kill_prompt ();
+      if (*answer == CONTROL_D)
+        {
+          xfree (answer);
+          return gpg_error (GPG_ERR_CANCELED);
+        }
+      req_nbits = *answer? atoi (answer): nbits;
+      xfree (answer);
+
+      if (req_nbits != nbits && (req_nbits % 32) )
+        {
+          req_nbits = ((req_nbits + 31) / 32) * 32;
+          tty_printf (_("rounded up to %u bits\n"), req_nbits);
+        }
+
+      if (req_nbits == nbits)
+        {
+          /* Use default.  */
+          *r_keysize = 0;
+          return 0;
+        }
+
+      if (req_nbits < min_nbits || req_nbits > max_nbits)
+        {
+          tty_printf (_("%s keysizes must be in the range %u-%u\n"),
+                      "RSA", min_nbits, max_nbits);
+        }
+      else
+        {
+          *r_keysize = req_nbits;
+          return 0;
+        }
+    }
+}
+
+
+/* Ask for the key attribute of a card key.  CURRENT is the current
+ * attribute configured for the card.  KEYNO is the number of the key
+ * used to select the prompt.  Stores NULL at result to use the
+ * default attribute or stores the selected attribute structure at
+ * RESULT.  On error an error code is returned.  */
+static gpg_error_t
+ask_card_keyattr (int keyno, const struct key_attr *current,
+                  struct key_attr **result)
+{
+  gpg_error_t err;
+  struct key_attr *key_attr = NULL;
+  char *answer = NULL;
+  int selection;
+
+  *result = NULL;
+
+  key_attr = xcalloc (1, sizeof *key_attr);
+
+  tty_printf (_("Changing card key attribute for: "));
+  if (keyno == 0)
+    tty_printf (_("Signature key\n"));
+  else if (keyno == 1)
+    tty_printf (_("Encryption key\n"));
+  else
+    tty_printf (_("Authentication key\n"));
+
+  tty_printf (_("Please select what kind of key you want:\n"));
+  tty_printf (_("   (%d) RSA\n"), 1 );
+  tty_printf (_("   (%d) ECC\n"), 2 );
+
+  for (;;)
+    {
+      xfree (answer);
+      answer = tty_get (_("Your selection? "));
+      trim_spaces (answer);
+      tty_kill_prompt ();
+      if (!*answer || *answer == CONTROL_D)
+        {
+          err = gpg_error (GPG_ERR_CANCELED);
+          goto leave;
+        }
+      selection = *answer? atoi (answer) : 0;
+
+      if (selection == 1 || selection == 2)
+        break;
+      else
+        tty_printf (_("Invalid selection.\n"));
+    }
+
+
+  if (selection == 1)
+    {
+      unsigned int nbits, result_nbits;
+
+      if (current->algo == PUBKEY_ALGO_RSA)
+        nbits = current->nbits;
+      else
+        nbits = 2048;
+
+      err = ask_card_rsa_keysize (nbits, &result_nbits);
+      if (err)
+        goto leave;
+      if (result_nbits == 0)
+        {
+          if (current->algo == PUBKEY_ALGO_RSA)
+            {
+              xfree (key_attr);
+              key_attr = NULL;
+            }
+          else
+            result_nbits = nbits;
+        }
+
+      if (key_attr)
+        {
+          key_attr->algo = PUBKEY_ALGO_RSA;
+          key_attr->nbits = result_nbits;
+        }
+    }
+  else if (selection == 2)
+    {
+      const char *curve;
+      /* const char *oid_str; */
+      int algo;
+
+      if (current->algo == PUBKEY_ALGO_RSA)
+        {
+          if (keyno == 1) /* Encryption key */
+            algo = PUBKEY_ALGO_ECDH;
+          else /* Signature key or Authentication key */
+            algo = PUBKEY_ALGO_ECDSA;
+          curve = NULL;
+        }
+      else
+        {
+          algo = current->algo;
+          curve = current->curve;
+        }
+
+      err = GPG_ERR_NOT_IMPLEMENTED;
+      goto leave;
+      /* FIXME: We need to mve the ask_cure code out to common or
+       * provide another sultion.  */
+      /* curve = ask_curve (&algo, NULL, curve); */
+      /* if (curve) */
+      /*   { */
+      /*     key_attr->algo = algo; */
+      /*     oid_str = openpgp_curve_to_oid (curve, NULL); */
+      /*     key_attr->curve = openpgp_oid_to_curve (oid_str, 0); */
+      /*   } */
+      /* else */
+      /*   { */
+      /*     xfree (key_attr); */
+      /*     key_attr = NULL; */
+      /*   } */
+    }
+  else
+    {
+      err = gpg_error (GPG_ERR_BUG);
+      goto leave;
+    }
+
+  /* Tell the user what we are going to do.  */
+  if (key_attr->algo == PUBKEY_ALGO_RSA)
+    {
+      tty_printf (_("The card will now be re-configured"
+                    " to generate a key of %u bits\n"), key_attr->nbits);
+    }
+  else if (key_attr->algo == PUBKEY_ALGO_ECDH
+           || key_attr->algo == PUBKEY_ALGO_ECDSA
+           || key_attr->algo == PUBKEY_ALGO_EDDSA)
+    {
+      tty_printf (_("The card will now be re-configured"
+                    " to generate a key of type: %s\n"), key_attr->curve);
+    }
+  show_keysize_warning ();
+
+  *result = key_attr;
+  key_attr = NULL;
+
+ leave:
+  xfree (key_attr);
+  xfree (answer);
+  return err;
+}
+
+
+/* Change the key attribute of key KEYNO (0..2) and show an error
+ * message if that fails.  */
+static gpg_error_t
+do_change_keyattr (int keyno, const struct key_attr *key_attr)
+{
+  gpg_error_t err = 0;
+  char args[100];
+
+  if (key_attr->algo == PUBKEY_ALGO_RSA)
+    snprintf (args, sizeof args, "--force %d 1 rsa%u", keyno+1,
+              key_attr->nbits);
+  else if (key_attr->algo == PUBKEY_ALGO_ECDH
+           || key_attr->algo == PUBKEY_ALGO_ECDSA
+           || key_attr->algo == PUBKEY_ALGO_EDDSA)
+    snprintf (args, sizeof args, "--force %d %d %s",
+              keyno+1, key_attr->algo, key_attr->curve);
+  else
+    {
+      /* FIXME: Above we use opnepgp algo names but in the error
+       * message we use the gcrypt names.  We should settle for a
+       * consistent solution. */
+      log_error (_("public key algorithm %d (%s) is not supported\n"),
+                 key_attr->algo, gcry_pk_algo_name (key_attr->algo));
+      err = gpg_error (GPG_ERR_PUBKEY_ALGO);
+      goto leave;
+    }
+
+  err = scd_setattr ("KEY-ATTR", args, strlen (args));
+  if (err)
+    log_error (_("error changing key attribute for key %d: %s\n"),
+               keyno+1, gpg_strerror (err));
+ leave:
+  return err;
+}
+
+
+static gpg_error_t
+cmd_keyattr (card_info_t info, char *argstr)
+{
+  gpg_error_t err = 0;
+  int keyno;
+  struct key_attr *key_attr = NULL;
+
+  (void)argstr;
+
+  if (!info)
+    return print_help
+      ("KEY-ATTR\n\n"
+       "Menu to change the key attributes of an OpenPGP card.",
+       APP_TYPE_OPENPGP, 0);
+
+  if (info->apptype != APP_TYPE_OPENPGP)
+    {
+      log_info ("Note: This is an OpenPGP only command.\n");
+      return gpg_error (GPG_ERR_NOT_SUPPORTED);
+    }
+
+  if (!(info->is_v2 && info->extcap.aac))
+    {
+      log_error (_("This command is not supported by this card\n"));
+      err = gpg_error (GPG_ERR_NOT_SUPPORTED);
+      goto leave;
+    }
+
+  for (keyno = 0; keyno < DIM (info->key_attr); keyno++)
+    {
+      xfree (key_attr);
+      key_attr = NULL;
+      err = ask_card_keyattr (keyno, &info->key_attr[keyno], &key_attr);
+      if (err)
+        goto leave;
+
+      err = do_change_keyattr (keyno, key_attr);
+      if (err)
+        {
+          /* Error: Better read the default key attribute again.  */
+          log_debug ("FIXME\n");
+          /* Ask again for this key. */
+          keyno--;
+        }
+    }
+
+ leave:
+  xfree (key_attr);
+  return err;
+}
+
+
+static gpg_error_t
+cmd_uif (card_info_t info, char *argstr)
+{
+  gpg_error_t err;
+  int keyno;
+
+  if (!info)
+    return print_help
+      ("UIF N [on|off|permanent]\n\n"
+       "Change the User Interaction Flag.  N must in the range 1 to 3.",
+       APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
+
+  argstr = skip_options (argstr);
+
+  if (digitp (argstr))
+    {
+      keyno = atoi (argstr);
+      while (digitp (argstr))
+        argstr++;
+      while (spacep (argstr))
+        argstr++;
+    }
+  else
+    keyno = 0;
+
+  if (keyno < 1 || keyno > 3)
+    {
+      err = gpg_error (GPG_ERR_INV_ARG);
+      goto leave;
+    }
+
+
+  err = GPG_ERR_NOT_IMPLEMENTED;
+
+ leave:
+  return err;
+}
+
+
+\f
+/* Data used by the command parser.  This needs to be outside of the
+ * function scope to allow readline based command completion.  */
+enum cmdids
+  {
+    cmdNOP = 0,
+    cmdQUIT, cmdADMIN, cmdHELP, cmdLIST, cmdRESET, cmdVERIFY,
+    cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSALUT, cmdCAFPR,
+    cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT,
+    cmdREADCERT, cmdUNBLOCK, cmdFACTORYRESET, cmdKDFSETUP,
+    cmdKEYATTR, cmdUIF,
+    cmdINVCMD
+  };
+
+static struct
+{
+  const char *name;
+  enum cmdids id;
+  int admin_only;
+  const char *desc;
+} cmds[] = {
+  { "quit"    , cmdQUIT  , 0, N_("quit this menu")},
+  { "q"       , cmdQUIT  , 0, NULL },
+  { "admin"   , cmdADMIN , 0, N_("show admin commands")},
+  { "help"    , cmdHELP  , 0, N_("show this help")},
+  { "?"       , cmdHELP  , 0, NULL },
+  { "list"    , cmdLIST  , 0, N_("list all available data")},
+  { "l"       , cmdLIST  , 0, NULL },
+  { "name"    , cmdNAME  , 1, N_("change card holder's name")},
+  { "url"     , cmdURL   , 1, N_("change URL to retrieve key")},
+  { "fetch"   , cmdFETCH , 0, N_("fetch the key specified in the card URL")},
+  { "login"   , cmdLOGIN , 1, N_("change the login name")},
+  { "lang"    , cmdLANG  , 1, N_("change the language preferences")},
+  { "salutation",cmdSALUT, 1, N_("change card holder's salutation")},
+  { "salut"   , cmdSALUT,  1, NULL },
+  { "cafpr"   , cmdCAFPR , 1, N_("change a CA fingerprint")},
+  { "forcesig", cmdFORCESIG, 1, N_("toggle the signature force PIN flag")},
+  { "generate", cmdGENERATE, 1, N_("generate new keys")},
+  { "passwd"  , cmdPASSWD, 0, N_("menu to change or unblock the PIN")},
+  { "verify"  , cmdVERIFY, 0, N_("verify the PIN and list all data")},
+  { "unblock" , cmdUNBLOCK,0, N_("unblock the PIN using a Reset Code")},
+  { "factory-reset", cmdFACTORYRESET, 1, N_("destroy all keys and data")},
+  { "kdf-setup", cmdKDFSETUP, 1, N_("setup KDF for PIN authentication")},
+  { "key-attr", cmdKEYATTR, 1, N_("change the key attribute")},
+  { "uif", cmdUIF, 1, N_("change the User Interaction Flag")},
+  /* Note, that we do not announce these command yet. */
+  { "privatedo", cmdPRIVATEDO, 0, N_("change a private data object")},
+  { "readcert",  cmdREADCERT,  0, N_("read a certificate from a data object")},
+  { "writecert", cmdWRITECERT, 1, N_("store a certificate to a data object")},
+  { NULL, cmdINVCMD, 0, NULL }
+};
+
+
+/* The main loop.  */
+static void
+interactive_loop (void)
+{
+  gpg_error_t err;
+  char *answer = NULL;         /* The input line.  */
+  enum cmdids cmd = cmdNOP;    /* The command.  */
+  int cmd_admin_only;          /* The command is an admin only command.  */
+  char *argstr;                /* The argument as a string.  */
+  int redisplay = 1;           /* Whether to redisplay the main info.  */
+  int allow_admin = 0;         /* Whether admin commands are allowed.  */
+  char *help_arg = NULL;       /* Argument of the HELP command.         */
+  struct card_info_s info_buffer;
+  card_info_t info = &info_buffer;
+  char *p;
+  int i;
+
+  /* In the interactive mode we do not want to print the program prefix.  */
+  log_set_prefix (NULL, 0);
+
+  for (;;)
+    {
+      if (help_arg)
+        {
+          /* Clear info to indicate helpmode */
+          info = NULL;
+        }
+      else if (!info)
+        {
+          /* Get out of help.  */
+          info = &info_buffer;
+          help_arg = NULL;
+          redisplay = 0;
+        }
+      else if (redisplay)
+        {
+          err = scd_learn (info);
+          if (err)
+            {
+              log_error ("Error reading card: %s\n", gpg_strerror (err));
+            }
+          else
+            {
+              list_card (info);
+              tty_printf("\n");
+              redisplay = 0;
+            }
+       }
+
+      if (!info)
+        {
+          /* Copy the pending help arg into our answer.  Noe that
+           * help_arg points into answer.  */
+          p = xstrdup (help_arg);
+          help_arg = NULL;
+          xfree (answer);
+          answer = p;
+        }
+      else
+        {
+          do
+            {
+              xfree (answer);
+              tty_enable_completion (command_completion);
+              answer = tty_get (_("gpg/card> "));
+              tty_kill_prompt();
+              tty_disable_completion ();
+              trim_spaces(answer);
+            }
+          while ( *answer == '#' );
+        }
+
+      argstr = NULL;
+      cmd_admin_only = 0;
+      if (!*answer)
+        cmd = cmdLIST; /* We default to the list command */
+      else if (*answer == CONTROL_D)
+        cmd = cmdQUIT;
+      else
+        {
+          if ((argstr = strchr (answer,' ')))
+            {
+              *argstr++ = 0;
+              trim_spaces (answer);
+              trim_spaces (argstr);
+            }
+
+          for (i=0; cmds[i].name; i++ )
+            if (!ascii_strcasecmp (answer, cmds[i].name ))
+              break;
+
+          cmd = cmds[i].id;
+          cmd_admin_only = cmds[i].admin_only;
+        }
+
+      /* Make sure we have valid strings for the args.  They are
+       * allowed to be modifed and must thus point to a buffer.  */
+      if (!argstr)
+        argstr = answer + strlen (answer);
+
+      if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP))
+        {
+          /* If redisplay is set we know that there was an error reading
+           * the card.  In this case we force a LIST command to retry.  */
+          if (!info)
+            ; /* In help mode.  */
+          else if (redisplay)
+            {
+              cmd = cmdLIST;
+              cmd_admin_only = 0;
+            }
+          else if (!info->serialno)
+            {
+              /* Without a serial number most commands won't work.
+               * Catch it here.  */
+              tty_printf ("\n");
+              tty_printf ("Serial number missing\n");
+              continue;
+            }
+          else if (!allow_admin && cmd_admin_only)
+            {
+              tty_printf ("\n");
+              tty_printf (_("Admin-only command\n"));
+              continue;
+            }
+        }
+
+      err = 0;
+      switch (cmd)
+        {
+        case cmdNOP:
+          if (!info)
+            print_help ("NOP\n\n"
+                        "Dummy command.", 0);
+          break;
+
+        case cmdQUIT:
+          if (!info)
+            print_help ("QUIT\n\n"
+                        "Leave this tool.", 0);
+          else
+            {
+              tty_printf ("\n");
+              goto leave;
+            }
+          break;
+
+        case cmdHELP:
+          if (!info)
+            print_help ("HELP [command]\n\n"
+                        "Show all commands.  With an argument show help\n"
+                        "for that command.", 0);
+          else if (*argstr)
+            help_arg = argstr; /* Trigger help for a command.  */
+          else
+            {
+              tty_printf
+                ("List of commands (\"help <command>\" for details):\n");
+              for (i=0; cmds[i].name; i++ )
+                if(cmds[i].desc
+                   && (!cmds[i].admin_only
+                       || (cmds[i].admin_only && allow_admin)))
+                  tty_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) );
+            }
+          break;
+
+        case cmdLIST:
+          if (!info)
+            print_help ("LIST\n\n"
+                        "Show content of the card.", 0);
+          else
+            {
+              /* Actual work is done by the redisplay code block.  */
+              redisplay = 1;
+            }
+          break;
+
+        case cmdRESET:
+          if (!info)
+            print_help ("RESET\n\n"
+                        "Send a RESET to the card daemon.", 0);
+          else
+            {
+              err = scd_apdu (NULL, NULL);
+            }
+          break;
+
+       case cmdADMIN:
+          if ( !strcmp (argstr, "on") )
             allow_admin = 1;
-          else if ( !strcmp (arg_string, "off") )
+          else if ( !strcmp (argstr, "off") )
             allow_admin = 0;
-          else if ( !strcmp (arg_string, "verify") )
+          else if ( !strcmp (argstr, "verify") )
             {
               /* Force verification of the Admin Command.  However,
                  this is only done if the retry counter is at initial
@@ -790,26 +2627,33 @@ interactive_loop (void)
            tty_printf(_("Admin commands are not allowed\n"));
          break;
 
-        case cmdVERIFY:    cmd_verify (); redisplay = 1; break;
-        case cmdLIST:                     redisplay = 1; break;
-        case cmdNAME:      cmd_name ();   break;
-        case cmdURL:       cmd_url ();    break;
-       case cmdFETCH:     cmd_fetch ();  break;
-        case cmdLOGIN:     cmd_login (arg_string); break;
-        case cmdLANG:      cmd_lang ();   break;
-        case cmdSALUT:     cmd_salut ();  break;
-        case cmdCAFPR:     cmd_cafpr (arg_number); break;
-        case cmdPRIVATEDO: cmd_privatedo (arg_number, arg_string); break;
-        case cmdWRITECERT: cmd_writecert (arg_number, arg_rest); break;
-        case cmdREADCERT:  cmd_readcert (arg_number, arg_rest); break;
-        case cmdFORCESIG:  cmd_forcesig (); break;
-        case cmdGENERATE:  cmd_generate (); break;
-        case cmdPASSWD:    cmd_passwd (allow_admin); break;
-        case cmdUNBLOCK:   cmd_unblock (allow_admin); break;
-        case cmdFACTORYRESET: cmd_factoryreset (); break;
-        case cmdKDFSETUP:  cmd_kdfsetup (arg_string); break;
-        case cmdKEYATTR:   cmd_keyattr (); break;
-        case cmdUIF:       cmd_uif (arg_number, arg_rest); break;
+        case cmdVERIFY:
+          err = cmd_verify (info, argstr);
+          if (!err)
+            redisplay = 1;
+          break;
+        case cmdNAME:      err = cmd_name (info, argstr); break;
+        case cmdURL:       err = cmd_url (info, argstr);  break;
+       case cmdFETCH:     err = cmd_fetch (info);  break;
+        case cmdLOGIN:     err = cmd_login (info, argstr); break;
+        case cmdLANG:      err = cmd_lang (info, argstr); break;
+        case cmdSALUT:     err = cmd_salut (info, argstr); break;
+        case cmdCAFPR:     err = cmd_cafpr (info, argstr); break;
+        case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break;
+        case cmdWRITECERT: err = cmd_writecert (info, argstr); break;
+        case cmdREADCERT:  err = cmd_readcert (info, argstr); break;
+        case cmdFORCESIG:  err = cmd_forcesig (info); break;
+        case cmdGENERATE:  err = cmd_generate (info); break;
+        case cmdPASSWD:    err = cmd_passwd (info, allow_admin); break;
+        case cmdUNBLOCK:   err = cmd_unblock (info); break;
+        case cmdFACTORYRESET:
+          err = cmd_factoryreset (info);
+          if (!err)
+            redisplay = 1;
+          break;
+        case cmdKDFSETUP:  err = cmd_kdfsetup (info, argstr); break;
+        case cmdKEYATTR:   err = cmd_keyattr (info, argstr); break;
+        case cmdUIF:       err = cmd_uif (info, argstr); break;
 
         case cmdINVCMD:
         default:
@@ -817,9 +2661,25 @@ interactive_loop (void)
           tty_printf (_("Invalid command  (try \"help\")\n"));
           break;
         } /* End command switch. */
+
+      if (gpg_err_code (err) == GPG_ERR_CANCELED)
+        tty_fprintf (NULL, "\n");
+      else if (err)
+        {
+          const char *s = "?";
+          for (i=0; cmds[i].name; i++ )
+            if (cmd == cmds[i].id)
+              {
+                s = cmds[i].name;
+                break;
+              }
+          log_error ("Command '%s' failed: %s\n", s, gpg_strerror (err));
+        }
+
     } /* End of main menu loop. */
 
  leave:
+  release_card_info (info);
   xfree (answer);
 }