* gpg.sgml: Clarify new notation delete feature.
[gnupg.git] / g10 / cardglue.c
index cff025c..d850b56 100644 (file)
@@ -1,5 +1,5 @@
 /* cardglue.c - mainly dispatcher for card related functions.
- * Copyright (C) 2003, 2004 Free Software Foundation, Inc.
+ * Copyright (C) 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
@@ -15,7 +15,8 @@
  *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
  */
 
 #include <config.h>
@@ -28,7 +29,6 @@
 #include <errno.h>
 #include <stdarg.h>
 #include <assert.h>
-
 #include "options.h"
 #include "packet.h"
 #include "errors.h"
 #include "apdu.h"
 #include "app-common.h"
 
-struct ctrl_ctx_s {
-  int (*status_cb)(void *opaque, const char *line);
+
+
+struct ctrl_ctx_s 
+{
+  assuan_error_t (*status_cb)(void *opaque, const char *line);
   void *status_cb_arg;
 };
 
 
+struct pincb_parm_s
+{
+  const char *sn;
+};
+
+
+struct writekey_parm_s
+{
+  assuan_context_t ctx;
+  const unsigned char *keydata;
+  size_t keydatalen;
+};
+
+
+
 static char *default_reader_port;
-static APP current_app;
+static app_t current_app;
+
+
+/* Local prototypes. */
+static assuan_error_t learn_status_cb (void *opaque, const char *line);
+
+
+/* To avoid cluttering the code with bunches of ifdefs we use a few
+   dummy functions instead and defines. */
+#ifndef ENABLE_AGENT_SUPPORT
+
+#define ASSUAN_LINELENGTH 100
 
+static assuan_context_t 
+agent_open (int try, const char *orig_codeset)
+{
+  return NULL;
+}
+
+void 
+agent_close (assuan_context_t ctx)
+{
+}
+
+const char *
+assuan_strerror (assuan_error_t err)
+{
+  return "no Assuan support";
+}
 
+assuan_error_t 
+assuan_transact (assuan_context_t ctx,
+                 const char *command,
+                 assuan_error_t (*data_cb)(void *, const void *, size_t),
+                 void *data_cb_arg,
+                 assuan_error_t (*inquire_cb)(void*, const char *),
+                 void *inquire_cb_arg,
+                 assuan_error_t (*status_cb)(void*, const char *),
+                 void *status_cb_arg)
+{
+  return 100; /* ASSUAN_NOT_IMPLEMENTED */
+}
+assuan_error_t 
+assuan_send_data (assuan_context_t ctx, const void *buffer, size_t length)
+{
+  return 100; /* ASSUAN_NOT_IMPLEMENTED */
+}  
+#endif /*!ENABLE_AGENT_SUPPORT*/
 
+\f
 /* Create a serialno/fpr string from the serial number and the secret
    key.  caller must free the returned string.  There is no error
    return. [Taken from 1.9's keyid.c]*/
@@ -82,7 +146,7 @@ serialno_and_fpr_from_sk (const unsigned char *sn, size_t snlen,
    buffers. The variable elements are pairs of (char *, size_t),
    terminated with a (NULL, 0). */
 void
-send_status_info (CTRL ctrl, const char *keyword, ...)
+send_status_info (ctrl_t ctrl, const char *keyword, ...)
 {
   va_list arg_ptr;
   const unsigned char *value;
@@ -122,14 +186,19 @@ send_status_info (CTRL ctrl, const char *keyword, ...)
         }
     }
   *p = 0;
-  ctrl->status_cb (ctrl->status_cb_arg, buf);
+  if (ctrl && ctrl->status_cb)
+    ctrl->status_cb (ctrl->status_cb_arg, buf);
 
   va_end (arg_ptr);
 }
 
 
-void gcry_md_hash_buffer (int algo, void *digest,
-                         const void *buffer, size_t length)
+/* Replacement function of the Libgcrypt onewhich is used in gnupg
+   1.9.  Thus function computes the digest of ALGO from the data in
+   BUFFER of LENGTH.  ALGO must be supported. */
+void 
+gcry_md_hash_buffer (int algo, void *digest,
+                     const void *buffer, size_t length)
 {
   MD_HANDLE h = md_open (algo, 0);
   if (!h)
@@ -197,7 +266,7 @@ card_set_reader_port (const char *portstr)
    no update time is available the returned value is 0.  Caller must
    free SERIAL unless the function returns an error. */
 int 
-app_get_serial_and_stamp (APP app, char **serial, time_t *stamp)
+app_get_serial_and_stamp (app_t app, char **serial, time_t *stamp)
 {
   unsigned char *buf, *p;
   int i;
@@ -220,13 +289,12 @@ app_get_serial_and_stamp (APP app, char **serial, time_t *stamp)
 
 
 
-
-
-
 /* Release the card info structure. */
 void 
 agent_release_card_info (struct agent_card_info_s *info)
 {
+  int i;
+
   if (!info)
     return;
 
@@ -237,22 +305,104 @@ agent_release_card_info (struct agent_card_info_s *info)
   xfree (info->login_data); info->login_data = NULL;
   info->fpr1valid = info->fpr2valid = info->fpr3valid = 0;
   info->cafpr1valid = info->cafpr2valid = info->cafpr3valid = 0;
+  for (i=0; i < 4; i++)
+    {
+      xfree (info->private_do[i]);
+      info->private_do[i] = NULL;
+    }
+}
+
+
+/* Print an error message for a failed assuan_transact and return a
+   gpg error code. No error is printed if RC is 0. */
+static gpg_error_t
+test_transact (int rc, const char *command)
+{
+  if (!rc)
+    return 0;
+  log_error ("sending command `%s' to agent failed: %s\n",
+             command, assuan_strerror (rc));
+  return gpg_error (GPG_ERR_CARD);
+}
+
+
+/* Try to open a card using an already running agent.  Prepare a
+   proper application context and return it. */
+static app_t
+open_card_via_agent (int *scd_available)
+{
+  assuan_context_t ctx;
+  app_t app;
+  struct agent_card_info_s info;
+  int rc;
+
+  *scd_available = 0;
+  ctx = agent_open (1, NULL);
+  if (!ctx)
+    return NULL;
+
+  /* Request the serialbnumber of the card.  If we get
+     NOT_SUPPORTED or NO_SCDAEMON back, the gpg-agent either has
+     disabled scdaemon or it can't be used.  We close the connection
+     in this case and use our own code.  This may happen if just the
+     gpg-agent has been installed for the sake of passphrase
+     caching. */
+  memset (&info, 0, sizeof info);
+  rc = assuan_transact (ctx, "SCD SERIALNO openpgp",
+                        NULL, NULL, NULL, NULL,
+                        learn_status_cb, &info);
+  if (rc)
+    {
+      if ((rc & 0xffff) == 60 || (rc & 0xffff) == 119)
+        ;  /* No scdaemon available to gpg-agent. */
+      else
+        {
+          write_status_text (STATUS_CARDCTRL, "4");
+          log_info ("selecting openpgp failed: %s\n", assuan_strerror (rc));
+          *scd_available = 1;
+        }
+      agent_release_card_info (&info);
+      agent_close (ctx);
+      return NULL;
+    }
+  
+  app = xcalloc (1, sizeof *app);
+  app->assuan_ctx = ctx;
+
+  return app;
 }
 
 
+
 /* Open the current card and select the openpgp application.  Return
    an APP context handle to be used for further procesing or NULL on
    error or if no OpenPGP application exists.*/
-static APP
+static app_t
 open_card (void)
 {
   int slot = -1;
   int rc;
-  APP app;
+  app_t app;
   int did_shutdown = 0;
+  int retry_count = 0;
 
-  card_close ();
+  /* First check whether we can contact a gpg-agent and divert all
+     operation to it. This is required because gpg as well as the
+     agent require exclusive access to the reader. */
+  if (opt.use_agent)
+    {
+      int scd_available;
 
+      app = open_card_via_agent (&scd_available);
+      if (app)
+        goto ready; /* Yes, there is a agent with a usable card, go that way. */
+      if (scd_available)
+        return NULL; /* agent avilabale but card problem. */
+    }
+
+
+  /* No agent or usable agent, thus we do it on our own. */
+  card_close ();
   
  retry:
   if (did_shutdown)
@@ -262,6 +412,7 @@ open_card (void)
       slot = apdu_open_reader (default_reader_port);
       if (slot == -1)
         {
+          write_status_text (STATUS_CARDCTRL, "5");
           log_error ("card reader not available\n");
           return NULL;
         }
@@ -270,7 +421,10 @@ open_card (void)
   app = xcalloc (1, sizeof *app);
   app->slot = slot;
   rc = app_select_openpgp (app);
-  if (rc && !opt.batch)
+  if (opt.limit_card_insert_tries 
+      && ++retry_count >= opt.limit_card_insert_tries)
+    ;
+  else if (rc && !opt.batch)
     {
       write_status_text (STATUS_CARDCTRL, "1");
       
@@ -288,12 +442,14 @@ open_card (void)
     }
   if (rc)
     {
+      write_status_text (STATUS_CARDCTRL, "4");
       log_info ("selecting openpgp failed: %s\n", gpg_strerror (rc));
       apdu_close_reader (slot);
       xfree (app);
       return NULL;
     }
 
+ ready:
   app->initialized = 1;
   current_app = app;
   if (is_status_enabled () )
@@ -312,34 +468,85 @@ open_card (void)
   return app;
 }
 
+
 void
 card_close (void)
 {
   if (current_app)
     {
-      APP app = current_app;
+      app_t app = current_app;
       current_app = NULL;
 
-      apdu_close_reader (app->slot);
+      if (app->assuan_ctx)
+        agent_close (app->assuan_ctx);
+      else
+        apdu_close_reader (app->slot);
       xfree (app);
     }
 }
 
 
+/* Format a cache ID from the serialnumber in SN and return it as an
+   allocated string.  In case of an error NULL is returned. */
+static char *
+format_cacheid (const char *sn)
+{
+  const char *s;
+  size_t snlen;
+  char *cacheid = NULL;
+
+  /* The serialnumber we use for a card is "CARDSN:serialno".  Where
+     serialno is the BCD string (i.e. hex string) with the full
+     number.  The serial number expect here constsis of hexdigits
+     followed by other characters, we cut off these other
+     characters. */
+  if (sn)
+    {
+      for (s=sn,snlen=0; hexdigitp (s); s++, snlen++)
+        ;
+      if (snlen == 32)
+        {
+          /* Yes, this looks indeed like an OpenPGP card S/N. */
+          cacheid = xtrymalloc (7+snlen+1);
+          if (cacheid)
+            {
+              memcpy (cacheid, "CARDSN:", 7);
+              memcpy (cacheid+7, sn, snlen);
+              cacheid[7+snlen] = 0;
+            }
+        }
+    }
+  return cacheid;
+}
+
+
+/* If RC is not 0, write an appropriate status message. */
+static void
+status_sc_op_failure (int rc)
+{
+  if (rc == G10ERR_CANCELED)
+    write_status_text (STATUS_SC_OP_FAILURE, "1");
+  else if (rc == G10ERR_BAD_PASS)
+    write_status_text (STATUS_SC_OP_FAILURE, "2");
+  else if (rc)
+    write_status (STATUS_SC_OP_FAILURE);
+}  
+
+
 /* Check that the serial number of the current card (as described by
    APP) matches SERIALNO.  If there is no match and we are not in
    batch mode, present a prompt to insert the desired card.  The
-   function return 0 is the present card is okay, -1 if the user
+   function returnd 0 if the present card is okay, -1 if the user
    selected to insert a new card or an error value.  Note that the
    card context will be closed in all cases except for 0 as return
    value and if it was possible to merely shutdown the reader. */
 static int
-check_card_serialno (APP app, const char *serialno)
+check_card_serialno (app_t app, const char *serialno)
 {
   const char *s;
   int ask = 0;
   int n;
-  
+
   for (s = serialno, n=0; *s != '/' && hexdigitp (s); s++, n++)
     ;
   if (n != 32)
@@ -361,17 +568,20 @@ check_card_serialno (APP app, const char *serialno)
         did_shutdown = 1;
       else
         card_close ();
-      tty_printf (_("Please remove the current card and "
-                    "insert the one with the serial number:\n"
-                    "   %.*s\n"), 32, serialno);
+
+      if (!opt.batch)
+        tty_printf (_("Please remove the current card and "
+                      "insert the one with serial number:\n"
+                      "   %.*s\n"), 32, serialno);
 
       sprintf (buf, "1 %.32s", serialno);
       write_status_text (STATUS_CARDCTRL, buf);
 
-      if ( cpr_get_answer_okay_cancel ("cardctrl.change_card.okay",
-                          _("Hit return when ready "
-                            "or enter 'c' to cancel: "),
-                                       1) )
+      if ( !opt.batch
+           && cpr_get_answer_okay_cancel ("cardctrl.change_card.okay",
+                                          _("Hit return when ready "
+                                            "or enter 'c' to cancel: "),
+                                          1) )
         {
           card_close ();
           return -1;
@@ -386,40 +596,6 @@ check_card_serialno (APP app, const char *serialno)
 }
 
 
-
-/* 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)
-{
-  char *buffer, *d;
-
-  buffer = d = xmalloc (strlen (s)+1);
-  while (*s)
-    {
-      if (*s == '%' && s[1] && s[2])
-        { 
-          s++;
-          *d = xtoi_2 (s);
-          if (!*d)
-            *d = '\xff';
-          d++;
-          s += 2;
-        }
-      else if (*s == '+')
-        {
-          *d++ = ' ';
-          s++;
-        }
-      else
-        *d++ = *s++;
-    }
-  *d = 0; 
-  return buffer;
-}
-
 /* Take a 20 byte hexencoded string and put it into the the provided
    20 byte buffer FPR in binary format. */
 static int
@@ -457,7 +633,7 @@ store_serialno (const char *line)
 
 
 
-static int
+static assuan_error_t
 learn_status_cb (void *opaque, const char *line)
 {
   struct agent_card_info_s *parm = opaque;
@@ -465,6 +641,7 @@ learn_status_cb (void *opaque, const char *line)
   int keywordlen;
   int i;
 
+/*   log_debug ("got status line `%s'\n", line); */
   for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
     ;
   while (spacep (line))
@@ -478,12 +655,12 @@ learn_status_cb (void *opaque, const char *line)
   else if (keywordlen == 9 && !memcmp (keyword, "DISP-NAME", keywordlen))
     {
       xfree (parm->disp_name);
-      parm->disp_name = unescape_status_string (line);
+      parm->disp_name = unescape_percent_string (line);
     }
   else if (keywordlen == 9 && !memcmp (keyword, "DISP-LANG", keywordlen))
     {
       xfree (parm->disp_lang);
-      parm->disp_lang = unescape_status_string (line);
+      parm->disp_lang = unescape_percent_string (line);
     }
   else if (keywordlen == 8 && !memcmp (keyword, "DISP-SEX", keywordlen))
     {
@@ -492,12 +669,12 @@ learn_status_cb (void *opaque, const char *line)
   else if (keywordlen == 10 && !memcmp (keyword, "PUBKEY-URL", keywordlen))
     {
       xfree (parm->pubkey_url);
-      parm->pubkey_url = unescape_status_string (line);
+      parm->pubkey_url = unescape_percent_string (line);
     }
   else if (keywordlen == 10 && !memcmp (keyword, "LOGIN-DATA", keywordlen))
     {
       xfree (parm->login_data);
-      parm->login_data = unescape_status_string (line);
+      parm->login_data = unescape_percent_string (line);
     }
   else if (keywordlen == 11 && !memcmp (keyword, "SIG-COUNTER", keywordlen))
     {
@@ -507,7 +684,7 @@ learn_status_cb (void *opaque, const char *line)
     {
       char *p, *buf;
 
-      buf = p = unescape_status_string (line);
+      buf = p = unescape_percent_string (line);
       if (buf)
         {
           while (spacep (p))
@@ -550,6 +727,20 @@ learn_status_cb (void *opaque, const char *line)
       else if (no == 3)
         parm->fpr3valid = unhexify_fpr (line, parm->fpr3);
     }
+  else if (keywordlen == 8 && !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 (keywordlen == 6 && !memcmp (keyword, "CA-FPR", keywordlen))
     {
       int no = atoi (line);
@@ -564,7 +755,15 @@ learn_status_cb (void *opaque, const char *line)
       else if (no == 3)
         parm->cafpr3valid = unhexify_fpr (line, parm->cafpr3);
     }
-
+  else if (keywordlen == 12 && !memcmp (keyword, "PRIVATE-DO-", 11)
+           && strchr ("1234", keyword[11]))
+    {
+      int no = keyword[11] - '1';
+      assert (no >= 0 && no <= 3);
+      xfree (parm->private_do[no]);
+      parm->private_do[no] = unescape_percent_string (line);
+    }
   return 0;
 }
 
@@ -573,7 +772,7 @@ learn_status_cb (void *opaque, const char *line)
 int 
 agent_learn (struct agent_card_info_s *info)
 {
-  APP app;
+  app_t app;
   int rc;
   struct ctrl_ctx_s ctrl;
   time_t stamp;
@@ -584,35 +783,68 @@ agent_learn (struct agent_card_info_s *info)
     return gpg_error (GPG_ERR_CARD);
 
   memset (info, 0, sizeof *info);
-  memset (&ctrl, 0, sizeof ctrl);
-  ctrl.status_cb = learn_status_cb;
-  ctrl.status_cb_arg = info;
 
-  rc = app_get_serial_and_stamp (app, &serial, &stamp);
-  if (!rc)
+  if (app->assuan_ctx)
     {
-      send_status_info (&ctrl, "SERIALNO", serial, strlen(serial), NULL, 0);
-      xfree (serial);
-      rc = app->fnc.learn_status (app, &ctrl);
+      rc = assuan_transact (app->assuan_ctx, "SCD LEARN --force",
+                            NULL, NULL, NULL, NULL,
+                            learn_status_cb, info);
+      rc = test_transact (rc, "SCD LEARN");
+    }
+  else
+    {
+      memset (&ctrl, 0, sizeof ctrl);
+      ctrl.status_cb = learn_status_cb;
+      ctrl.status_cb_arg = info;
+
+      rc = app_get_serial_and_stamp (app, &serial, &stamp);
+      if (!rc)
+        {
+          send_status_info (&ctrl, "SERIALNO",
+                            serial, strlen(serial), NULL, 0);
+          xfree (serial);
+          rc = app->fnc.learn_status (app, &ctrl);
+        }
     }
 
   return rc;
 }
 
-/* Get an attribite from the card. Make sure info is initialized. */
+
+/* Get an attribute from the card. Make sure info is initialized. */
 int 
 agent_scd_getattr (const char *name, struct agent_card_info_s *info)
 {
-  APP app;
+  int rc;
+  app_t app;
   struct ctrl_ctx_s ctrl;
 
   app = current_app? current_app : open_card ();
   if (!app)
     return gpg_error (GPG_ERR_CARD);
 
-  ctrl.status_cb = learn_status_cb;
-  ctrl.status_cb_arg = info;
-  return app->fnc.getattr (app, &ctrl, name);
+  if (app->assuan_ctx)
+    {
+      char line[ASSUAN_LINELENGTH];
+
+      /* We assume that NAME does not need escaping. */
+      if (12 + strlen (name) > DIM(line)-1)
+        return gpg_error (GPG_ERR_CARD);
+      stpcpy (stpcpy (line, "SCD GETATTR "), name); 
+
+      rc = test_transact (assuan_transact (app->assuan_ctx, line,
+                                           NULL, NULL, NULL, NULL,
+                                           learn_status_cb, info),
+                          "SCD GETATTR");
+    }
+  else
+    {
+      ctrl.status_cb = learn_status_cb;
+      ctrl.status_cb_arg = info;
+      rc = app->fnc.getattr (app, &ctrl, name);
+    }
+
+  return rc;
 }
 
 
@@ -620,24 +852,112 @@ agent_scd_getattr (const char *name, struct agent_card_info_s *info)
 static int 
 pin_cb (void *opaque, const char *info, char **retstr)
 {
+  struct pincb_parm_s *parm = opaque;
   char *value;
   int canceled;
-  int isadmin = (info && strstr (info, "dmin"));
-
+  int isadmin = 0;
+  int newpin = 0;
+  const char *again_text = NULL;
+  const char *ends, *s;
+  char *cacheid = NULL;
 
   *retstr = NULL;
-  log_debug ("asking for PIN '%s'\n", info);
+  /*   log_debug ("asking for PIN '%s'\n", info); */
+
+  /* We use a special prefix to check whether the Admin PIN has been
+     requested. */
+  if (info && *info =='|' && (ends=strchr (info+1, '|')))
+    {
+      for (s=info+1; s < ends; s++)
+        {
+          if (*s == 'A')
+            isadmin = 1;
+          else if (*s == 'N')
+            newpin = 1;
+        }
+      info = ends+1;
+    }
+  else if (info && *info == '|')
+    log_debug ("pin_cb called without proper PIN info hack\n");
+
+  /* If we are not requesting a new PIN and we are not requesting an
+     AdminPIN, compute a string to be used as the cacheID for
+     gpg-agent. */
+  if (!newpin && !isadmin && parm)
+    {
+      cacheid = format_cacheid (parm->sn);
+    }
+  else if (newpin && parm)
+    {
+      /* Make really sure that it is not cached anymore. */
+      agent_clear_pin_cache (parm->sn);
+    }
+
+
+ again:
+  if (is_status_enabled())
+    {
+      if (parm && parm->sn && *parm->sn)
+        {
+          char *buf = xmalloc ( 10 + strlen (parm->sn) + 1);
+          strcpy (stpcpy (buf, isadmin? "OPENPGP 3 ":"OPENPGP 1 "), parm->sn);
+          write_status_text (STATUS_NEED_PASSPHRASE_PIN, buf);
+          xfree (buf);
+        }
+      else  
+        write_status_text (STATUS_NEED_PASSPHRASE_PIN,
+                           isadmin? "OPENPGP 3" : "OPENPGP 1");
+    }
 
-  value = ask_passphrase (info, 
-                          isadmin? "passphrase.adminpin.ask"
-                                 : "passphrase.pin.ask", 
-                          isadmin?  _("Enter Admin PIN: ") : _("Enter PIN: "),
+  value = ask_passphrase (info, again_text,
+                          newpin && isadmin? "passphrase.adminpin.new.ask" :
+                          newpin?  "passphrase.pin.new.ask" :
+                          isadmin? "passphrase.adminpin.ask" :
+                                   "passphrase.pin.ask", 
+                          newpin && isadmin? _("Enter New Admin PIN: ") :
+                          newpin?  _("Enter New PIN: ") :
+                          isadmin? _("Enter Admin PIN: ")
+                                 : _("Enter PIN: "),
+                          cacheid,
                           &canceled);
+  xfree (cacheid);
+  cacheid = NULL;
+  again_text = NULL;
   if (!value && canceled)
-    return -1;
+    return G10ERR_CANCELED;
   else if (!value)
     return G10ERR_GENERAL;
 
+  if (newpin)
+    {
+      char *value2;
+
+      value2 = ask_passphrase (info, NULL,
+                               "passphrase.pin.repeat", 
+                               _("Repeat this PIN: "),
+                               NULL,
+                               &canceled);
+      if (!value2 && canceled)
+        {
+          xfree (value);
+          return G10ERR_CANCELED;
+        }
+      else if (!value2)
+        {
+          xfree (value);
+          return G10ERR_GENERAL;
+        }
+      if (strcmp (value, value2))
+        {
+          again_text = N_("PIN not correctly repeated; try again");
+          xfree (value2);
+          xfree (value);
+          value = NULL;
+          goto again;
+        }
+      xfree (value2);
+    }
+
   *retstr = value;
   return 0;
 }
@@ -647,26 +967,126 @@ pin_cb (void *opaque, const char *info, char **retstr)
 /* Send a SETATTR command to the SCdaemon. */
 int 
 agent_scd_setattr (const char *name,
-                   const unsigned char *value, size_t valuelen)
+                   const unsigned char *value, size_t valuelen,
+                   const char *serialno)
+{
+  app_t app;
+  int rc;
+  struct pincb_parm_s parm;
+
+  memset (&parm, 0, sizeof parm);
+  parm.sn = serialno;
+
+  app = current_app? current_app : open_card ();
+  if (!app)
+    return gpg_error (GPG_ERR_CARD);
+
+  if (app->assuan_ctx)
+    {
+      char line[ASSUAN_LINELENGTH];
+      char *p;
+
+      /* We assume that NAME does not need escaping. */
+      if (12 + strlen (name) > DIM(line)-1)
+        return gpg_error (GPG_ERR_CARD);
+      p = stpcpy (stpcpy (line, "SCD SETATTR "), name); 
+      *p++ = ' ';
+      for (; valuelen; value++, valuelen--)
+        {
+          if (p >= line + DIM(line)-5 )
+            return gpg_error (GPG_ERR_CARD);
+          if (*value < ' ' || *value == '+' || *value == '%')
+            {
+              sprintf (p, "%%%02X", *value);
+              p += 3;
+            }
+          else if (*value == ' ')
+            *p++ = '+';
+          else
+            *p++ = *value;
+        }
+      *p = 0;
+
+      rc = test_transact (assuan_transact (app->assuan_ctx, line,
+                                           NULL, NULL, NULL, NULL, NULL, NULL),
+                          "SCD SETATTR");
+    }
+  else
+    {
+      rc = app->fnc.setattr (app, name, pin_cb, &parm, value, valuelen);
+    }
+
+  status_sc_op_failure (rc);
+  return rc;
+}
+
+
+/* Handle a KEYDATA inquiry.  Note, we only send the data,
+   assuan_transact takes care of flushing and writing the end */
+static assuan_error_t
+inq_writekey_parms (void *opaque, const char *keyword)
 {
-  APP app;
+  struct writekey_parm_s *parm = opaque; 
+
+  return assuan_send_data (parm->ctx, parm->keydata, parm->keydatalen);
+}
+
+
+/* Send a WRITEKEY command to the SCdaemon. */
+int 
+agent_scd_writekey (int keyno, const char *serialno,
+                    const unsigned char *keydata, size_t keydatalen)
+{
+  app_t app;
+  int rc;
+  char line[ASSUAN_LINELENGTH];
+  struct pincb_parm_s parm;
+
+  memset (&parm, 0, sizeof parm);
+  parm.sn = serialno;
 
   app = current_app? current_app : open_card ();
   if (!app)
     return gpg_error (GPG_ERR_CARD);
 
-  return app->fnc.setattr (app, name, pin_cb, NULL, value, valuelen);
+  if (app->assuan_ctx)
+    {
+      struct writekey_parm_s parms;
+
+      snprintf (line, DIM(line)-1, "SCD WRITEKEY --force OPENPGP.%d", keyno);
+      line[DIM(line)-1] = 0;
+      parms.ctx = app->assuan_ctx;
+      parms.keydata = keydata;
+      parms.keydatalen = keydatalen;
+      rc = test_transact (assuan_transact (app->assuan_ctx, line,
+                                           NULL, NULL,
+                                           inq_writekey_parms, &parms,
+                                           NULL, NULL),
+                          "SCD WRITEKEY");
+    }
+  else
+    {
+      snprintf (line, DIM(line)-1, "OPENPGP.%d", keyno);
+      line[DIM(line)-1] = 0;
+      rc = app->fnc.writekey (app, NULL, line, 0x0001,
+                              pin_cb, &parm,
+                              keydata, keydatalen);
+    }
+
+  status_sc_op_failure (rc);
+  return rc;
 }
 
 
-static int
+
+static assuan_error_t
 genkey_status_cb (void *opaque, const char *line)
 {
   struct agent_card_genkey_s *parm = opaque;
   const char *keyword = line;
   int keywordlen;
 
-  log_debug ("got status line `%s'\n", line);
+/*   log_debug ("got status line `%s'\n", line); */
   for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
     ;
   while (spacep (line))
@@ -713,53 +1133,131 @@ genkey_status_cb (void *opaque, const char *line)
 
 /* Send a GENKEY command to the SCdaemon. */
 int 
-agent_scd_genkey (struct agent_card_genkey_s *info, int keyno, int force)
+agent_scd_genkey (struct agent_card_genkey_s *info, int keyno, int force,
+                  const char *serialno)
 {
-  APP app;
-  char keynostr[20];
+  app_t app;
+  char line[ASSUAN_LINELENGTH];
   struct ctrl_ctx_s ctrl;
+  int rc;
+  struct pincb_parm_s parm;
+
+  memset (&parm, 0, sizeof parm);
+  parm.sn = serialno;
 
   app = current_app? current_app : open_card ();
   if (!app)
     return gpg_error (GPG_ERR_CARD);
 
   memset (info, 0, sizeof *info);
-  sprintf (keynostr, "%d", keyno);
-  ctrl.status_cb = genkey_status_cb;
-  ctrl.status_cb_arg = info;
 
-  return app->fnc.genkey (app, &ctrl, keynostr,
-                           force? 1:0,
-                           pin_cb, NULL);
+  if (app->assuan_ctx)
+    {
+      snprintf (line, DIM(line)-1, "SCD GENKEY %s%d",
+                force? "--force ":"", keyno);
+      line[DIM(line)-1] = 0;
+      rc = test_transact (assuan_transact (app->assuan_ctx, line,
+                                           NULL, NULL, NULL, NULL,
+                                           genkey_status_cb, info),
+                          "SCD GENKEY");
+    }
+  else
+    {
+      snprintf (line, DIM(line)-1, "%d", keyno);
+      ctrl.status_cb = genkey_status_cb;
+      ctrl.status_cb_arg = info;
+      rc = app->fnc.genkey (app, &ctrl, line,
+                            force? 1:0,
+                            pin_cb, &parm);
+    }
+
+  status_sc_op_failure (rc);
+  return rc;
 }
 
+
+static assuan_error_t
+membuf_data_cb (void *opaque, const void *buffer, size_t length)
+{
+  membuf_t *data = opaque;
+
+  if (buffer)
+    put_membuf (data, buffer, length);
+  return 0;
+}
+  
+
 /* Send a PKSIGN command to the SCdaemon. */
 int 
 agent_scd_pksign (const char *serialno, int hashalgo,
                   const unsigned char *indata, size_t indatalen,
                   unsigned char **r_buf, size_t *r_buflen)
 {
-  APP app;
+  struct pincb_parm_s parm;
+  app_t app;
   int rc;
 
   *r_buf = NULL;
   *r_buflen = 0;
+  memset (&parm, 0, sizeof parm);
+  parm.sn = serialno;
  retry:
   app = current_app? current_app : open_card ();
   if (!app)
     return gpg_error (GPG_ERR_CARD);
 
-  /* Check that the card's serialnumber is as required.*/
-  rc = check_card_serialno (app, serialno);
-  if (rc == -1)
-    goto retry;
-  if (rc)
-    return rc;
+  if (app->assuan_ctx)
+    {
+      char *p, line[ASSUAN_LINELENGTH];
+      membuf_t data;
+      size_t len;
+      int i;
+
+      if (indatalen*2 + 50 > DIM(line))
+        return gpg_error (GPG_ERR_GENERAL);
+
+      p = stpcpy (line, "SCD SETDATA ");
+      for (i=0; i < indatalen ; i++, p += 2 )
+        sprintf (p, "%02X", indata[i]);
+      rc = test_transact (assuan_transact (app->assuan_ctx, line,
+                                           NULL, NULL, NULL, NULL, NULL, NULL),
+                          "SCD SETDATA");
+      if (!rc)
+        {
+          init_membuf (&data, 1024);
+          snprintf (line, DIM(line)-1, "SCD PKSIGN %s", serialno);
+          line[DIM(line)-1] = 0;
+          rc = test_transact (assuan_transact (app->assuan_ctx, line,
+                                               membuf_data_cb, &data,
+                                               NULL, NULL, NULL, NULL),
+                              "SCD PKSIGN");
+          if (rc)
+            xfree (get_membuf (&data, &len));
+          else
+            *r_buf = get_membuf (&data, r_buflen);
+        }
+    }
+  else
+    {
+      /* Check that the card's serialnumber is as required.*/
+      rc = check_card_serialno (app, serialno);
+      if (rc == -1)
+        goto retry;
+
+      if (!rc)
+        rc = app->fnc.sign (app, serialno, hashalgo,
+                            pin_cb, &parm,
+                            indata, indatalen,
+                            r_buf, r_buflen);
+    }
 
-  return app->fnc.sign (app, serialno, hashalgo,
-                        pin_cb, NULL,
-                        indata, indatalen,
-                        r_buf, r_buflen);
+  if (rc)
+    {
+      status_sc_op_failure (rc);
+      if (!app->assuan_ctx)
+        agent_clear_pin_cache (serialno);
+    }
+  return rc;
 }
 
 
@@ -769,36 +1267,86 @@ agent_scd_pkdecrypt (const char *serialno,
                      const unsigned char *indata, size_t indatalen,
                      unsigned char **r_buf, size_t *r_buflen)
 {
-  APP app;
+  struct pincb_parm_s parm;
+  app_t app;
   int rc;
 
   *r_buf = NULL;
   *r_buflen = 0;
+  memset (&parm, 0, sizeof parm);
+  parm.sn = serialno;
  retry:
   app = current_app? current_app : open_card ();
   if (!app)
     return gpg_error (GPG_ERR_CARD);
 
-  /* Check that the card's serialnumber is as required.*/
-  rc = check_card_serialno (app, serialno);
-  if (rc == -1)
-    goto retry;
-  if (rc)
-    return rc;
+  if (app->assuan_ctx)
+    {
+      char *p, line[ASSUAN_LINELENGTH];
+      membuf_t data;
+      size_t len;
+      int i;
 
-  return app->fnc.decipher (app, serialno, 
-                            pin_cb, NULL,
-                            indata, indatalen,
-                            r_buf, r_buflen);
+      if (indatalen*2 + 50 > DIM(line))
+        return gpg_error (GPG_ERR_GENERAL);
+
+      p = stpcpy (line, "SCD SETDATA ");
+      for (i=0; i < indatalen ; i++, p += 2 )
+        sprintf (p, "%02X", indata[i]);
+      rc = test_transact (assuan_transact (app->assuan_ctx, line,
+                                           NULL, NULL, NULL, NULL, NULL, NULL),
+                          "SCD SETDATA");
+      if (!rc)
+        {
+          init_membuf (&data, 1024);
+          snprintf (line, DIM(line)-1, "SCD PKDECRYPT %s", serialno);
+          line[DIM(line)-1] = 0;
+          rc = test_transact (assuan_transact (app->assuan_ctx, line,
+                                               membuf_data_cb, &data,
+                                               NULL, NULL, NULL, NULL),
+                              "SCD PKDECRYPT");
+          if (rc)
+            xfree (get_membuf (&data, &len));
+          else
+            *r_buf = get_membuf (&data, r_buflen);
+        }
+    }
+  else
+    {
+      /* Check that the card's serialnumber is as required.*/
+      rc = check_card_serialno (app, serialno);
+      if (rc == -1)
+        goto retry;
+      
+      if (!rc)
+        rc = app->fnc.decipher (app, serialno, 
+                                pin_cb, &parm,
+                                indata, indatalen,
+                                r_buf, r_buflen);
+    }
+
+  if (rc)
+    {
+      status_sc_op_failure (rc);
+      if (!app->assuan_ctx)
+        agent_clear_pin_cache (serialno);
+    }
+  return rc;
 }
 
-/* Change the PIN of an OpenPGP card or reset the retry counter. */
+/* Change the PIN of an OpenPGP card or reset the retry
+   counter. SERIALNO may be NULL or a hex string finally passed to the
+   passphrase callback. */
 int 
-agent_scd_change_pin (int chvno)
+agent_scd_change_pin (int chvno, const char *serialno)
 {
-  APP app;
-  char chvnostr[20];
+  app_t app;
   int reset = 0;
+  int rc;
+  struct pincb_parm_s parm;
+
+  memset (&parm, 0, sizeof parm);
+  parm.sn = serialno;
 
   reset = (chvno >= 100);
   chvno %= 100;
@@ -807,9 +1355,28 @@ agent_scd_change_pin (int chvno)
   if (!app)
     return gpg_error (GPG_ERR_CARD);
 
-  sprintf (chvnostr, "%d", chvno);
-  return app->fnc.change_pin (app, NULL, chvnostr, reset,
-                              pin_cb, NULL);
+  if (app->assuan_ctx)
+    {
+      char line[ASSUAN_LINELENGTH];
+
+      snprintf (line, DIM(line)-1, "SCD PASSWD%s %d",
+                reset? " --reset":"", chvno);
+      line[DIM(line)-1] = 0;
+      rc = test_transact (assuan_transact (app->assuan_ctx, line,
+                                           NULL, NULL, NULL, NULL, NULL, NULL),
+                          "SCD PASSWD");
+    }
+  else
+    {
+      char chvnostr[50];
+
+      sprintf (chvnostr, "%d", chvno);
+      rc = app->fnc.change_pin (app, NULL, chvnostr, reset,
+                                pin_cb, &parm);
+    }
+
+  status_sc_op_failure (rc);
+  return rc;
 }
 
 /* Perform a CHECKPIN operation.  SERIALNO should be the serial
@@ -818,12 +1385,46 @@ agent_scd_change_pin (int chvno)
 int
 agent_scd_checkpin (const char *serialnobuf)
 {
-  APP app;
+  app_t app;
+  int rc;
+  struct pincb_parm_s parm;
+
+  memset (&parm, 0, sizeof parm);
+  parm.sn = serialnobuf;
 
   app = current_app? current_app : open_card ();
   if (!app)
     return gpg_error (GPG_ERR_CARD);
 
-  return app->fnc.check_pin (app, serialnobuf, pin_cb, NULL);
+  if (app->assuan_ctx)
+    {
+      char line[ASSUAN_LINELENGTH];
+
+      if (15 + strlen (serialnobuf) > DIM(line)-1)
+        return gpg_error (GPG_ERR_CARD);
+      stpcpy (stpcpy (line, "SCD CHECKPIN "), serialnobuf); 
+      rc = test_transact (assuan_transact (app->assuan_ctx, line,
+                                           NULL, NULL, NULL, NULL, NULL, NULL),
+                          "SCD CHECKPIN");
+    }
+  else
+    {
+      rc = app->fnc.check_pin (app, serialnobuf, pin_cb, &parm);
+    }
+
+  status_sc_op_failure (rc);
+  return rc;
 }
 
+
+
+void
+agent_clear_pin_cache (const char *sn)
+{
+  char *cacheid = format_cacheid (sn);
+  if (cacheid)
+    {
+      passphrase_clear_cache (NULL, cacheid, 0);
+      xfree (cacheid);
+    }
+}