scd: Fix regression tracking the connection count.
[gnupg.git] / scd / app.c
index dfb5991..5b8da1c 100644 (file)
--- a/scd/app.c
+++ b/scd/app.c
@@ -1,5 +1,5 @@
 /* app.c - Application selection.
- *     Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc.
+ * Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
@@ -14,7 +14,7 @@
  * 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 <http://www.gnu.org/licenses/>.
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
  */
 
 #include <config.h>
 #include <npth.h>
 
 #include "scdaemon.h"
+#include "exechelp.h"
 #include "app-common.h"
-#include "apdu.h"
 #include "iso7816.h"
+#include "apdu.h"
 #include "tlv.h"
 
-/* This table is used to keep track of locks on a per reader base.
-   The index into the table is the slot number of the reader.  The
-   mutex will be initialized on demand (one of the advantages of a
-   userland threading system). */
-static struct
-{
-  int initialized;
-  npth_mutex_t lock;
-  app_t app;        /* Application context in use or NULL. */
-  app_t last_app;   /* Last application object used as this slot or NULL. */
-} lock_table[10];
-
-
-
-static void deallocate_app (app_t app);
-
-
+static npth_mutex_t app_list_lock;
+static app_t app_top;
 \f
 static void
 print_progress_line (void *opaque, const char *what, int pc, int cur, int tot)
@@ -70,55 +56,33 @@ print_progress_line (void *opaque, const char *what, int pc, int cur, int tot)
    success; only then the unlock_reader function must be called after
    returning from the handler. */
 static gpg_error_t
-lock_reader (int slot, ctrl_t ctrl)
+lock_app (app_t app, ctrl_t ctrl)
 {
-  int res;
-
-  if (slot < 0 || slot >= DIM (lock_table))
-    return gpg_error (slot<0? GPG_ERR_INV_VALUE : GPG_ERR_RESOURCE_LIMIT);
-
-  if (!lock_table[slot].initialized)
-    {
-      res = npth_mutex_init (&lock_table[slot].lock, NULL);
-      if (res)
-        {
-          log_error ("error initializing mutex: %s\n", strerror (res));
-          return gpg_error_from_errno (res);
-        }
-      lock_table[slot].initialized = 1;
-      lock_table[slot].app = NULL;
-      lock_table[slot].last_app = NULL;
-    }
-
-  res = npth_mutex_lock (&lock_table[slot].lock);
-  if (res)
+  if (npth_mutex_lock (&app->lock))
     {
-      log_error ("failed to acquire APP lock for slot %d: %s\n",
-                 slot, strerror (res));
-      return gpg_error_from_errno (res);
+      gpg_error_t err = gpg_error_from_syserror ();
+      log_error ("failed to acquire APP lock for %p: %s\n",
+                 app, gpg_strerror (err));
+      return err;
     }
 
-  apdu_set_progress_cb (slot, print_progress_line, ctrl);
+  apdu_set_progress_cb (app->slot, print_progress_line, ctrl);
 
   return 0;
 }
 
 /* Release a lock on the reader.  See lock_reader(). */
 static void
-unlock_reader (int slot)
+unlock_app (app_t app)
 {
-  int res;
-
-  if (slot < 0 || slot >= DIM (lock_table)
-      || !lock_table[slot].initialized)
-    log_bug ("unlock_reader called for invalid slot %d\n", slot);
+  apdu_set_progress_cb (app->slot, NULL, NULL);
 
-  apdu_set_progress_cb (slot, NULL, NULL);
-
-  res = npth_mutex_unlock (&lock_table[slot].lock);
-  if (res)
-    log_error ("failed to release APP lock for slot %d: %s\n",
-               slot, strerror (res));
+  if (npth_mutex_unlock (&app->lock))
+    {
+      gpg_error_t err = gpg_error_from_syserror ();
+      log_error ("failed to release APP lock for %p: %s\n",
+                 app, gpg_strerror (err));
+    }
 }
 
 
@@ -127,26 +91,12 @@ unlock_reader (int slot)
 void
 app_dump_state (void)
 {
-  int slot;
+  app_t a;
 
-  for (slot=0; slot < DIM (lock_table); slot++)
-    if (lock_table[slot].initialized)
-      {
-        log_info ("app_dump_state: slot=%d", slot);
-        if (lock_table[slot].app)
-          {
-            log_printf (" app=%p", lock_table[slot].app);
-            if (lock_table[slot].app->apptype)
-              log_printf (" type='%s'", lock_table[slot].app->apptype);
-          }
-        if (lock_table[slot].last_app)
-          {
-            log_printf (" lastapp=%p", lock_table[slot].last_app);
-            if (lock_table[slot].last_app->apptype)
-              log_printf (" type='%s'", lock_table[slot].last_app->apptype);
-          }
-        log_printf ("\n");
-      }
+  npth_mutex_lock (&app_list_lock);
+  for (a = app_top; a; a = a->next)
+    log_info ("app_dump_state: app=%p type='%s'\n", a, a->apptype);
+  npth_mutex_unlock (&app_list_lock);
 }
 
 /* Check wether the application NAME is allowed.  This does not mean
@@ -163,156 +113,104 @@ is_app_allowed (const char *name)
 }
 
 
-/* This may be called to tell this module about a removed or resetted card. */
-void
-application_notify_card_reset (int slot)
+static gpg_error_t
+check_conflict (app_t app, const char *name)
 {
-  app_t app;
-
-  if (slot < 0 || slot >= DIM (lock_table))
-    return;
+  if (!app || !name || (app->apptype && !ascii_strcasecmp (app->apptype, name)))
+    return 0;
 
-  /* FIXME: We are ignoring any error value here.  */
-  lock_reader (slot, NULL);
+  log_info ("application '%s' in use - can't switch\n",
+            app->apptype? app->apptype : "<null>");
 
-  /* Mark application as non-reusable.  */
-  if (lock_table[slot].app)
-    lock_table[slot].app->no_reuse = 1;
-
-  /* Deallocate a saved application for that slot, so that we won't
-     try to reuse it.  If there is no saved application, set a flag so
-     that we won't save the current state. */
-  app = lock_table[slot].last_app;
-
-  if (app)
-    {
-      lock_table[slot].last_app = NULL;
-      deallocate_app (app);
-    }
-  unlock_reader (slot);
+  return gpg_error (GPG_ERR_CONFLICT);
 }
 
-
 /* This function is used by the serialno command to check for an
    application conflict which may appear if the serialno command is
    used to request a specific application and the connection has
    already done a select_application. */
 gpg_error_t
-check_application_conflict (ctrl_t ctrl, int slot, const char *name)
+check_application_conflict (const char *name, app_t app)
 {
-  app_t app;
+  return check_conflict (app, name);
+}
 
-  (void)ctrl;
 
-  if (slot < 0 || slot >= DIM (lock_table))
-    return gpg_error (GPG_ERR_INV_VALUE);
+static void
+release_application_internal (app_t app)
+{
+  if (!app->ref_count)
+    log_bug ("trying to release an already released context\n");
 
-  app = lock_table[slot].initialized ? lock_table[slot].app : NULL;
-  if (app && app->apptype && name)
-    if ( ascii_strcasecmp (app->apptype, name))
-      return gpg_error (GPG_ERR_CONFLICT);
-  return 0;
+  --app->ref_count;
 }
 
-
-/* If called with NAME as NULL, select the best fitting application
-   and return a context; otherwise select the application with NAME
-   and return a context.  SLOT identifies the reader device. Returns
-   an error code and stores NULL at R_APP if no application was found
-   or no card is present. */
 gpg_error_t
-select_application (ctrl_t ctrl, int slot, const char *name, app_t *r_app)
+app_reset (app_t app, ctrl_t ctrl, int send_reset)
 {
   gpg_error_t err;
-  app_t app = NULL;
-  unsigned char *result = NULL;
-  size_t resultlen;
-  int want_undefined;
 
-  (void)ctrl;
-
-  *r_app = NULL;
-
-  want_undefined = (name && !strcmp (name, "undefined"));
-
-  err = lock_reader (slot, ctrl);
+  err = lock_app (app, ctrl);
   if (err)
     return err;
 
-  /* First check whether we already have an application to share. */
-  app = lock_table[slot].initialized ? lock_table[slot].app : NULL;
-  if (app && name)
-    if (!app->apptype || ascii_strcasecmp (app->apptype, name))
-      {
-        unlock_reader (slot);
-        if (app->apptype)
-          log_info ("application '%s' in use by reader %d - can't switch\n",
-                    app->apptype, slot);
-        return gpg_error (GPG_ERR_CONFLICT);
-      }
-
-  /* Don't use a non-reusable marked application.  */
-  if (app && app->no_reuse)
+  if (send_reset)
     {
-      unlock_reader (slot);
-      log_info ("lingering application '%s' in use by reader %d"
-                " - can't switch\n",
-                app->apptype? app->apptype:"?", slot);
-      return gpg_error (GPG_ERR_CONFLICT);
-    }
+      int sw = apdu_reset (app->slot);
+      if (sw)
+        err = gpg_error (GPG_ERR_CARD_RESET);
 
-  /* If we don't have an app, check whether we have a saved
-     application for that slot.  This is useful so that a card does
-     not get reset even if only one session is using the card - this
-     way the PIN cache and other cached data are preserved.  */
-  if (!app && lock_table[slot].initialized && lock_table[slot].last_app)
-    {
-      app = lock_table[slot].last_app;
-      if (!name || (app->apptype && !ascii_strcasecmp (app->apptype, name)) )
-        {
-          /* Yes, we can reuse this application - either the caller
-             requested an unspecific one or the requested one matches
-             the saved one. */
-          lock_table[slot].app = app;
-          lock_table[slot].last_app = NULL;
-        }
-      else
-        {
-          /* No, this saved application can't be used - deallocate it. */
-          lock_table[slot].last_app = NULL;
-          deallocate_app (app);
-          app = NULL;
-        }
+      /* Release the same application which is used by other sessions.  */
+      send_client_notifications (app, 1);
     }
-
-  /* If we can reuse an application, bump the reference count and
-     return it.  */
-  if (app)
+  else
     {
-      if (app->slot != slot)
-        log_bug ("slot mismatch %d/%d\n", app->slot, slot);
-      app->slot = slot;
-
-      app->ref_count++;
-      *r_app = app;
-      unlock_reader (slot);
-      return 0; /* Okay: We share that one. */
+      ctrl->app_ctx = NULL;
+      release_application_internal (app);
     }
 
+  unlock_app (app);
+  return err;
+}
+
+static gpg_error_t
+app_new_register (int slot, ctrl_t ctrl, const char *name,
+                  int periodical_check_needed)
+{
+  gpg_error_t err = 0;
+  app_t app = NULL;
+  unsigned char *result = NULL;
+  size_t resultlen;
+  int want_undefined;
+
   /* Need to allocate a new one.  */
   app = xtrycalloc (1, sizeof *app);
   if (!app)
     {
       err = gpg_error_from_syserror ();
       log_info ("error allocating context: %s\n", gpg_strerror (err));
-      unlock_reader (slot);
       return err;
     }
+
   app->slot = slot;
+  app->card_status = (unsigned int)-1;
 
+  if (npth_mutex_init (&app->lock, NULL))
+    {
+      err = gpg_error_from_syserror ();
+      log_error ("error initializing mutex: %s\n", gpg_strerror (err));
+      xfree (app);
+      return err;
+    }
 
-  /* Fixme: We should now first check whether a card is at all
-     present. */
+  err = lock_app (app, ctrl);
+  if (err)
+    {
+      xfree (app);
+      return err;
+    }
+
+  want_undefined = (name && !strcmp (name, "undefined"));
 
   /* Try to read the GDO file first to get a default serial number.
      We skip this if the undefined application has been requested. */
@@ -387,7 +285,9 @@ select_application (ctrl_t ctrl, int slot, const char *name, app_t *r_app)
     err = app_select_geldkarte (app);
   if (err && is_app_allowed ("dinsig") && (!name || !strcmp (name, "dinsig")))
     err = app_select_dinsig (app);
-  if (err && name)
+  if (err && is_app_allowed ("sc-hsm") && (!name || !strcmp (name, "sc-hsm")))
+    err = app_select_sc_hsm (app);
+  if (err && name && gpg_err_code (err) != GPG_ERR_OBJ_TERM_STATE)
     err = gpg_error (GPG_ERR_NOT_SUPPORTED);
 
  leave:
@@ -399,19 +299,110 @@ select_application (ctrl_t ctrl, int slot, const char *name, app_t *r_app)
       else
         log_info ("no supported card application found: %s\n",
                   gpg_strerror (err));
+      unlock_app (app);
       xfree (app);
-      unlock_reader (slot);
       return err;
     }
 
-  app->ref_count = 1;
+  app->periodical_check_needed = periodical_check_needed;
 
-  lock_table[slot].app = app;
-  *r_app = app;
-  unlock_reader (slot);
+  npth_mutex_lock (&app_list_lock);
+  app->next = app_top;
+  app_top = app;
+  npth_mutex_unlock (&app_list_lock);
+  unlock_app (app);
   return 0;
 }
 
+/* If called with NAME as NULL, select the best fitting application
+   and return a context; otherwise select the application with NAME
+   and return a context.  Returns an error code and stores NULL at
+   R_APP if no application was found or no card is present. */
+gpg_error_t
+select_application (ctrl_t ctrl, const char *name, app_t *r_app,
+                    int scan, const unsigned char *serialno_bin,
+                    size_t serialno_bin_len)
+{
+  gpg_error_t err = 0;
+  app_t a;
+
+  *r_app = NULL;
+
+  if (scan || !app_top)
+    {
+      struct dev_list *l;
+      int periodical_check_needed = 0;
+
+      /* Scan the devices to find new device(s).  */
+      err = apdu_dev_list_start (opt.reader_port, &l);
+      if (err)
+        return err;
+
+      while (1)
+        {
+          int slot;
+          int periodical_check_needed_this;
+
+          slot = apdu_open_reader (l);
+          if (slot < 0)
+            break;
+
+          periodical_check_needed_this = apdu_connect (slot);
+          if (periodical_check_needed_this < 0)
+            {
+              /* We close a reader with no card.  */
+              err = gpg_error (GPG_ERR_ENODEV);
+            }
+          else
+            {
+              err = app_new_register (slot, ctrl, name,
+                                      periodical_check_needed_this);
+              if (periodical_check_needed_this)
+                periodical_check_needed = 1;
+            }
+
+          if (err)
+            apdu_close_reader (slot);
+        }
+
+      apdu_dev_list_finish (l);
+
+      /* If periodical check is needed for new device(s), kick the
+       scdaemon loop.  */
+      if (periodical_check_needed)
+        scd_kick_the_loop ();
+    }
+
+  npth_mutex_lock (&app_list_lock);
+  for (a = app_top; a; a = a->next)
+    {
+      lock_app (a, ctrl);
+      if (serialno_bin == NULL)
+        break;
+      if (a->serialnolen == serialno_bin_len
+          && !memcmp (a->serialno, serialno_bin, a->serialnolen))
+        break;
+      unlock_app (a);
+    }
+
+  if (a)
+    {
+      err = check_conflict (a, name);
+      if (!err)
+        {
+          a->ref_count++;
+          *r_app = a;
+        }
+      unlock_app (a);
+    }
+  else
+    err = gpg_error (GPG_ERR_ENODEV);
+
+  npth_mutex_unlock (&app_list_lock);
+
+  return err;
+}
+
 
 char *
 get_supported_applications (void)
@@ -422,6 +413,7 @@ get_supported_applications (void)
     "p15",
     "geldkarte",
     "dinsig",
+    "sc-hsm",
     /* Note: "undefined" is not listed here because it needs special
        treatment by the client.  */
     NULL
@@ -446,10 +438,27 @@ get_supported_applications (void)
 }
 
 
-/* Deallocate the application. */
+/* Deallocate the application.  */
 static void
 deallocate_app (app_t app)
 {
+  app_t a, a_prev = NULL;
+
+  for (a = app_top; a; a = a->next)
+    if (a == app)
+      {
+        if (a_prev == NULL)
+          app_top = a->next;
+        else
+          a_prev->next = a->next;
+        break;
+      }
+    else
+      a_prev = a;
+
+  if (app->ref_count)
+    log_error ("trying to release context used yet (%d)\n", app->ref_count);
+
   if (app->fnc.deinit)
     {
       app->fnc.deinit (app);
@@ -468,41 +477,17 @@ deallocate_app (app_t app)
 void
 release_application (app_t app)
 {
-  int slot;
-
   if (!app)
     return;
 
-  if (!app->ref_count)
-    log_bug ("trying to release an already released context\n");
-  if (--app->ref_count)
-    return;
-
-  /* Move the reference to the application in the lock table. */
-  slot = app->slot;
-  /* FIXME: We are ignoring any error value.  */
-  lock_reader (slot, NULL);
-  if (lock_table[slot].app != app)
-    {
-      unlock_reader (slot);
-      log_bug ("app mismatch %p/%p\n", app, lock_table[slot].app);
-      deallocate_app (app);
-      return;
-    }
+  /* We don't deallocate app here.  Instead, we keep it.  This is
+     useful so that a card does not get reset even if only one session
+     is using the card - this way the PIN cache and other cached data
+     are preserved.  */
 
-  if (lock_table[slot].last_app)
-    deallocate_app (lock_table[slot].last_app);
-  if (app->no_reuse)
-    {
-      /* If we shall not re-use the application we can't save it for
-         later use. */
-      deallocate_app (app);
-      lock_table[slot].last_app = NULL;
-    }
-  else
-    lock_table[slot].last_app = lock_table[slot].app;
-  lock_table[slot].app = NULL;
-  unlock_reader (slot);
+  lock_app (app, NULL);
+  release_application_internal (app);
+  unlock_app (app);
 }
 
 
@@ -551,33 +536,23 @@ app_munge_serialno (app_t app)
 
 
 
-/* Retrieve the serial number and the time of the last update of the
-   card.  The serial number is returned as a malloced string (hex
-   encoded) in SERIAL and the time of update is returned in STAMP.  If
-   no update time is available the returned value is 0.  Caller must
-   free SERIAL unless the function returns an error.  If STAMP is not
-   of interest, NULL may be passed. */
-gpg_error_t
-app_get_serial_and_stamp (app_t app, char **serial, time_t *stamp)
+/* Retrieve the serial number of the card.  The serial number is
+   returned as a malloced string (hex encoded) in SERIAL.  Caller must
+   free SERIAL unless the function returns an error.  */
+char *
+app_get_serialno (app_t app)
 {
-  char *buf;
+  char *serial;
 
-  if (!app || !serial)
-    return gpg_error (GPG_ERR_INV_VALUE);
-
-  *serial = NULL;
-  if (stamp)
-    *stamp = 0; /* not available */
+  if (!app)
+    return NULL;
 
   if (!app->serialnolen)
-    buf = xtrystrdup ("FF7F00");
+    serial = xtrystrdup ("FF7F00");
   else
-    buf = bin2hex (app->serialno, app->serialnolen, NULL);
-  if (!buf)
-    return gpg_error_from_syserror ();
+    serial = bin2hex (app->serialno, app->serialnolen, NULL);
 
-  *serial = buf;
-  return 0;
+  return serial;
 }
 
 
@@ -590,20 +565,17 @@ app_write_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
 
   if (!app)
     return gpg_error (GPG_ERR_INV_VALUE);
-  if (!app->ref_count)
-    return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
   if (!app->fnc.learn_status)
     return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
 
   /* We do not send APPTYPE if only keypairinfo is requested.  */
   if (app->apptype && !(flags & 1))
-    send_status_info (ctrl, "APPTYPE",
-                      app->apptype, strlen (app->apptype), NULL, 0);
-  err = lock_reader (app->slot, ctrl);
+    send_status_direct (ctrl, "APPTYPE", app->apptype);
+  err = lock_app (app, ctrl);
   if (err)
     return err;
   err = app->fnc.learn_status (app, ctrl, flags);
-  unlock_reader (app->slot);
+  unlock_app (app);
   return err;
 }
 
@@ -613,7 +585,7 @@ app_write_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
    buffer put into CERT and the length of the certificate put into
    CERTLEN. */
 gpg_error_t
-app_readcert (app_t app, const char *certid,
+app_readcert (app_t app, ctrl_t ctrl, const char *certid,
               unsigned char **cert, size_t *certlen)
 {
   gpg_error_t err;
@@ -624,11 +596,11 @@ app_readcert (app_t app, const char *certid,
     return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
   if (!app->fnc.readcert)
     return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
-  err = lock_reader (app->slot, NULL/* FIXME*/);
+  err = lock_app (app, ctrl);
   if (err)
     return err;
   err = app->fnc.readcert (app, certid, cert, certlen);
-  unlock_reader (app->slot);
+  unlock_app (app);
   return err;
 }
 
@@ -641,7 +613,8 @@ app_readcert (app_t app, const char *certid,
 
    This function might not be supported by all applications.  */
 gpg_error_t
-app_readkey (app_t app, const char *keyid, unsigned char **pk, size_t *pklen)
+app_readkey (app_t app, ctrl_t ctrl, int advanced, const char *keyid,
+             unsigned char **pk, size_t *pklen)
 {
   gpg_error_t err;
 
@@ -656,11 +629,11 @@ app_readkey (app_t app, const char *keyid, unsigned char **pk, size_t *pklen)
     return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
   if (!app->fnc.readkey)
     return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
-  err = lock_reader (app->slot, NULL /*FIXME*/);
+  err = lock_app (app, ctrl);
   if (err)
     return err;
-  err= app->fnc.readkey (app, keyid, pk, pklen);
-  unlock_reader (app->slot);
+  err= app->fnc.readkey (app, advanced, keyid, pk, pklen);
+  unlock_app (app);
   return err;
 }
 
@@ -678,37 +651,35 @@ app_getattr (app_t app, ctrl_t ctrl, const char *name)
 
   if (app->apptype && name && !strcmp (name, "APPTYPE"))
     {
-      send_status_info (ctrl, "APPTYPE",
-                        app->apptype, strlen (app->apptype), NULL, 0);
+      send_status_direct (ctrl, "APPTYPE", app->apptype);
       return 0;
     }
   if (name && !strcmp (name, "SERIALNO"))
     {
       char *serial;
-      time_t stamp;
-      int rc;
 
-      rc = app_get_serial_and_stamp (app, &serial, &stamp);
-      if (rc)
-        return rc;
-      send_status_info (ctrl, "SERIALNO", serial, strlen (serial), NULL, 0);
+      serial = app_get_serialno (app);
+      if (!serial)
+        return gpg_error (GPG_ERR_INV_VALUE);
+
+      send_status_direct (ctrl, "SERIALNO", serial);
       xfree (serial);
       return 0;
     }
 
   if (!app->fnc.getattr)
     return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
-  err = lock_reader (app->slot, ctrl);
+  err = lock_app (app, ctrl);
   if (err)
     return err;
   err =  app->fnc.getattr (app, ctrl, name);
-  unlock_reader (app->slot);
+  unlock_app (app);
   return err;
 }
 
 /* Perform a SETATTR operation.  */
 gpg_error_t
-app_setattr (app_t app, const char *name,
+app_setattr (app_t app, ctrl_t ctrl, const char *name,
              gpg_error_t (*pincb)(void*, const char *, char **),
              void *pincb_arg,
              const unsigned char *value, size_t valuelen)
@@ -721,11 +692,11 @@ app_setattr (app_t app, const char *name,
     return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
   if (!app->fnc.setattr)
     return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
-  err = lock_reader (app->slot, NULL /*FIXME*/);
+  err = lock_app (app, ctrl);
   if (err)
     return err;
   err = app->fnc.setattr (app, name, pincb, pincb_arg, value, valuelen);
-  unlock_reader (app->slot);
+  unlock_app (app);
   return err;
 }
 
@@ -733,7 +704,7 @@ app_setattr (app_t app, const char *name,
    If a PIN is required the PINCB will be used to ask for the PIN; it
    should return the PIN in an allocated buffer and put it into PIN.  */
 gpg_error_t
-app_sign (app_t app, const char *keyidstr, int hashalgo,
+app_sign (app_t app, ctrl_t ctrl, const char *keyidstr, int hashalgo,
           gpg_error_t (*pincb)(void*, const char *, char **),
           void *pincb_arg,
           const void *indata, size_t indatalen,
@@ -747,14 +718,14 @@ app_sign (app_t app, const char *keyidstr, int hashalgo,
     return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
   if (!app->fnc.sign)
     return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
-  err = lock_reader (app->slot, NULL /*FIXME*/);
+  err = lock_app (app, ctrl);
   if (err)
     return err;
   err = app->fnc.sign (app, keyidstr, hashalgo,
                        pincb, pincb_arg,
                        indata, indatalen,
                        outdata, outdatalen);
-  unlock_reader (app->slot);
+  unlock_app (app);
   if (opt.verbose)
     log_info ("operation sign result: %s\n", gpg_strerror (err));
   return err;
@@ -765,7 +736,7 @@ app_sign (app_t app, const char *keyidstr, int hashalgo,
    PINCB will be used to ask for the PIN; it should return the PIN in
    an allocated buffer and put it into PIN.  */
 gpg_error_t
-app_auth (app_t app, const char *keyidstr,
+app_auth (app_t app, ctrl_t ctrl, const char *keyidstr,
           gpg_error_t (*pincb)(void*, const char *, char **),
           void *pincb_arg,
           const void *indata, size_t indatalen,
@@ -779,14 +750,14 @@ app_auth (app_t app, const char *keyidstr,
     return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
   if (!app->fnc.auth)
     return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
-  err = lock_reader (app->slot, NULL /*FIXME*/);
+  err = lock_app (app, ctrl);
   if (err)
     return err;
   err = app->fnc.auth (app, keyidstr,
                        pincb, pincb_arg,
                        indata, indatalen,
                        outdata, outdatalen);
-  unlock_reader (app->slot);
+  unlock_app (app);
   if (opt.verbose)
     log_info ("operation auth result: %s\n", gpg_strerror (err));
   return err;
@@ -797,28 +768,32 @@ app_auth (app_t app, const char *keyidstr,
    If a PIN is required the PINCB will be used to ask for the PIN; it
    should return the PIN in an allocated buffer and put it into PIN.  */
 gpg_error_t
-app_decipher (app_t app, const char *keyidstr,
+app_decipher (app_t app, ctrl_t ctrl, const char *keyidstr,
               gpg_error_t (*pincb)(void*, const char *, char **),
               void *pincb_arg,
               const void *indata, size_t indatalen,
-              unsigned char **outdata, size_t *outdatalen )
+              unsigned char **outdata, size_t *outdatalen,
+              unsigned int *r_info)
 {
   gpg_error_t err;
 
+  *r_info = 0;
+
   if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb)
     return gpg_error (GPG_ERR_INV_VALUE);
   if (!app->ref_count)
     return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
   if (!app->fnc.decipher)
     return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
-  err = lock_reader (app->slot, NULL /*FIXME*/);
+  err = lock_app (app, ctrl);
   if (err)
     return err;
   err = app->fnc.decipher (app, keyidstr,
                            pincb, pincb_arg,
                            indata, indatalen,
-                           outdata, outdatalen);
-  unlock_reader (app->slot);
+                           outdata, outdatalen,
+                           r_info);
+  unlock_app (app);
   if (opt.verbose)
     log_info ("operation decipher result: %s\n", gpg_strerror (err));
   return err;
@@ -841,12 +816,12 @@ app_writecert (app_t app, ctrl_t ctrl,
     return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
   if (!app->fnc.writecert)
     return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
-  err = lock_reader (app->slot, ctrl);
+  err = lock_app (app, ctrl);
   if (err)
     return err;
   err = app->fnc.writecert (app, ctrl, certidstr,
                             pincb, pincb_arg, data, datalen);
-  unlock_reader (app->slot);
+  unlock_app (app);
   if (opt.verbose)
     log_info ("operation writecert result: %s\n", gpg_strerror (err));
   return err;
@@ -869,12 +844,12 @@ app_writekey (app_t app, ctrl_t ctrl,
     return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
   if (!app->fnc.writekey)
     return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
-  err = lock_reader (app->slot, ctrl);
+  err = lock_app (app, ctrl);
   if (err)
     return err;
   err = app->fnc.writekey (app, ctrl, keyidstr, flags,
                            pincb, pincb_arg, keydata, keydatalen);
-  unlock_reader (app->slot);
+  unlock_app (app);
   if (opt.verbose)
     log_info ("operation writekey result: %s\n", gpg_strerror (err));
   return err;
@@ -896,23 +871,23 @@ app_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags,
     return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
   if (!app->fnc.genkey)
     return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
-  err = lock_reader (app->slot, ctrl);
+  err = lock_app (app, ctrl);
   if (err)
     return err;
   err = app->fnc.genkey (app, ctrl, keynostr, flags,
                          createtime, pincb, pincb_arg);
-  unlock_reader (app->slot);
+  unlock_app (app);
   if (opt.verbose)
     log_info ("operation genkey result: %s\n", gpg_strerror (err));
   return err;
 }
 
 
-/* Perform a GET CHALLENGE operation.  This fucntion is special as it
+/* Perform a GET CHALLENGE operation.  This function is special as it
    directly accesses the card without any application specific
    wrapper. */
 gpg_error_t
-app_get_challenge (app_t app, size_t nbytes, unsigned char *buffer)
+app_get_challenge (app_t app, ctrl_t ctrl, size_t nbytes, unsigned char *buffer)
 {
   gpg_error_t err;
 
@@ -920,11 +895,11 @@ app_get_challenge (app_t app, size_t nbytes, unsigned char *buffer)
     return gpg_error (GPG_ERR_INV_VALUE);
   if (!app->ref_count)
     return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
-  err = lock_reader (app->slot, NULL /*FIXME*/);
+  err = lock_app (app, ctrl);
   if (err)
     return err;
   err = iso7816_get_challenge (app->slot, nbytes, buffer);
-  unlock_reader (app->slot);
+  unlock_app (app);
   return err;
 }
 
@@ -944,12 +919,12 @@ app_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, int reset_mode,
     return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
   if (!app->fnc.change_pin)
     return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
-  err = lock_reader (app->slot, ctrl);
+  err = lock_app (app, ctrl);
   if (err)
     return err;
   err = app->fnc.change_pin (app, ctrl, chvnostr, reset_mode,
                              pincb, pincb_arg);
-  unlock_reader (app->slot);
+  unlock_app (app);
   if (opt.verbose)
     log_info ("operation change_pin result: %s\n", gpg_strerror (err));
   return err;
@@ -960,7 +935,7 @@ app_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, int reset_mode,
    be used to initialze a the PIN cache for long lasting other
    operations.  Its use is highly application dependent. */
 gpg_error_t
-app_check_pin (app_t app, const char *keyidstr,
+app_check_pin (app_t app, ctrl_t ctrl, const char *keyidstr,
                gpg_error_t (*pincb)(void*, const char *, char **),
                void *pincb_arg)
 {
@@ -972,12 +947,165 @@ app_check_pin (app_t app, const char *keyidstr,
     return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
   if (!app->fnc.check_pin)
     return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
-  err = lock_reader (app->slot, NULL /*FIXME*/);
+  err = lock_app (app, ctrl);
   if (err)
     return err;
   err = app->fnc.check_pin (app, keyidstr, pincb, pincb_arg);
-  unlock_reader (app->slot);
+  unlock_app (app);
   if (opt.verbose)
     log_info ("operation check_pin result: %s\n", gpg_strerror (err));
   return err;
 }
+
+static void
+report_change (int slot, int old_status, int cur_status)
+{
+  char *homestr, *envstr;
+  char *fname;
+  char templ[50];
+  FILE *fp;
+
+  snprintf (templ, sizeof templ, "reader_%d.status", slot);
+  fname = make_filename (gnupg_homedir (), templ, NULL );
+  fp = fopen (fname, "w");
+  if (fp)
+    {
+      fprintf (fp, "%s\n",
+               (cur_status & 1)? "USABLE":
+               (cur_status & 4)? "ACTIVE":
+               (cur_status & 2)? "PRESENT": "NOCARD");
+      fclose (fp);
+    }
+  xfree (fname);
+
+  homestr = make_filename (gnupg_homedir (), NULL);
+  if (gpgrt_asprintf (&envstr, "GNUPGHOME=%s", homestr) < 0)
+    log_error ("out of core while building environment\n");
+  else
+    {
+      gpg_error_t err;
+      const char *args[9], *envs[2];
+      char numbuf1[30], numbuf2[30], numbuf3[30];
+
+      envs[0] = envstr;
+      envs[1] = NULL;
+
+      sprintf (numbuf1, "%d", slot);
+      sprintf (numbuf2, "0x%04X", old_status);
+      sprintf (numbuf3, "0x%04X", cur_status);
+      args[0] = "--reader-port";
+      args[1] = numbuf1;
+      args[2] = "--old-code";
+      args[3] = numbuf2;
+      args[4] = "--new-code";
+      args[5] = numbuf3;
+      args[6] = "--status";
+      args[7] = ((cur_status & 1)? "USABLE":
+                 (cur_status & 4)? "ACTIVE":
+                 (cur_status & 2)? "PRESENT": "NOCARD");
+      args[8] = NULL;
+
+      fname = make_filename (gnupg_homedir (), "scd-event", NULL);
+      err = gnupg_spawn_process_detached (fname, args, envs);
+      if (err && gpg_err_code (err) != GPG_ERR_ENOENT)
+        log_error ("failed to run event handler '%s': %s\n",
+                   fname, gpg_strerror (err));
+      xfree (fname);
+      xfree (envstr);
+    }
+  xfree (homestr);
+}
+
+int
+scd_update_reader_status_file (void)
+{
+  app_t a, app_next;
+  int periodical_check_needed = 0;
+
+  npth_mutex_lock (&app_list_lock);
+  for (a = app_top; a; a = app_next)
+    {
+      int sw;
+      unsigned int status;
+
+      sw = apdu_get_status (a->slot, 0, &status);
+      app_next = a->next;
+
+      if (sw == SW_HOST_NO_READER)
+        {
+          /* Most likely the _reader_ has been unplugged.  */
+          status = 0;
+        }
+      else if (sw)
+        {
+          /* Get status failed.  Ignore that.  */
+          if (a->periodical_check_needed)
+            periodical_check_needed = 1;
+          continue;
+        }
+
+      if (a->card_status != status)
+        {
+          report_change (a->slot, a->card_status, status);
+          send_client_notifications (a, status == 0);
+
+          if (status == 0)
+            {
+              log_debug ("Removal of a card: %d\n", a->slot);
+              apdu_close_reader (a->slot);
+              deallocate_app (a);
+            }
+          else
+            {
+              a->card_status = status;
+              if (a->periodical_check_needed)
+                periodical_check_needed = 1;
+            }
+        }
+      else
+        {
+          if (a->periodical_check_needed)
+            periodical_check_needed = 1;
+        }
+    }
+  npth_mutex_unlock (&app_list_lock);
+
+  return periodical_check_needed;
+}
+
+/* This function must be called once to initialize this module.  This
+   has to be done before a second thread is spawned.  We can't do the
+   static initialization because Pth emulation code might not be able
+   to do a static init; in particular, it is not possible for W32. */
+gpg_error_t
+initialize_module_command (void)
+{
+  gpg_error_t err;
+
+  if (npth_mutex_init (&app_list_lock, NULL))
+    {
+      err = gpg_error_from_syserror ();
+      log_error ("app: error initializing mutex: %s\n", gpg_strerror (err));
+      return err;
+    }
+
+  return apdu_init ();
+}
+
+void
+app_send_card_list (ctrl_t ctrl)
+{
+  app_t a;
+  char buf[65];
+
+  npth_mutex_lock (&app_list_lock);
+  for (a = app_top; a; a = a->next)
+    {
+      if (DIM (buf) < 2 * a->serialnolen + 1)
+        continue;
+
+      bin2hex (a->serialno, a->serialnolen, buf);
+      send_status_direct (ctrl, "SERIALNO", buf);
+    }
+  npth_mutex_unlock (&app_list_lock);
+}