scd: Fix regression tracking the connection count.
[gnupg.git] / scd / app.c
index a78d652..5b8da1c 100644 (file)
--- a/scd/app.c
+++ b/scd/app.c
@@ -58,14 +58,12 @@ print_progress_line (void *opaque, const char *what, int pc, int cur, int tot)
 static gpg_error_t
 lock_app (app_t app, ctrl_t ctrl)
 {
-  gpg_error_t err;
-
-  err = npth_mutex_lock (&app->lock);
-  if (err)
+  if (npth_mutex_lock (&app->lock))
     {
+      gpg_error_t err = gpg_error_from_syserror ();
       log_error ("failed to acquire APP lock for %p: %s\n",
-                 app, strerror (err));
-      return gpg_error_from_errno (err);
+                 app, gpg_strerror (err));
+      return err;
     }
 
   apdu_set_progress_cb (app->slot, print_progress_line, ctrl);
@@ -77,14 +75,14 @@ lock_app (app_t app, ctrl_t ctrl)
 static void
 unlock_app (app_t app)
 {
-  gpg_error_t err;
-
   apdu_set_progress_cb (app->slot, NULL, NULL);
 
-  err = npth_mutex_unlock (&app->lock);
-  if (err)
-    log_error ("failed to release APP lock for %p: %s\n",
-               app, strerror (err));
+  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));
+    }
 }
 
 
@@ -163,7 +161,7 @@ app_reset (app_t app, ctrl_t ctrl, int send_reset)
         err = gpg_error (GPG_ERR_CARD_RESET);
 
       /* Release the same application which is used by other sessions.  */
-      send_client_notifications (app);
+      send_client_notifications (app, 1);
     }
   else
     {
@@ -176,7 +174,8 @@ app_reset (app_t app, ctrl_t ctrl, int send_reset)
 }
 
 static gpg_error_t
-app_new_register (int slot, ctrl_t ctrl, const char *name)
+app_new_register (int slot, ctrl_t ctrl, const char *name,
+                  int periodical_check_needed)
 {
   gpg_error_t err = 0;
   app_t app = NULL;
@@ -194,11 +193,12 @@ app_new_register (int slot, ctrl_t ctrl, const char *name)
     }
 
   app->slot = slot;
-  err = npth_mutex_init (&app->lock, NULL);
-  if (err)
+  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", strerror (err));
+      log_error ("error initializing mutex: %s\n", gpg_strerror (err));
       xfree (app);
       return err;
     }
@@ -304,7 +304,7 @@ app_new_register (int slot, ctrl_t ctrl, const char *name)
       return err;
     }
 
-  app->require_get_status = 1;   /* For token, this can be 0.  */
+  app->periodical_check_needed = periodical_check_needed;
 
   npth_mutex_lock (&app_list_lock);
   app->next = app_top;
@@ -319,63 +319,86 @@ app_new_register (int slot, ctrl_t ctrl, const char *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)
+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;
-  app_t app;
-  int slot;
+  gpg_error_t err = 0;
+  app_t a;
 
   *r_app = NULL;
 
-  if (scan
-      /* FIXME: Here, we can change code to support multiple readers.
-         For now, we only open a single reader.
-      */
-      && !app_top)
+  if (scan || !app_top)
     {
-      slot = apdu_open_reader (opt.reader_port);
-      if (slot >= 0)
+      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 sw = apdu_connect (slot);
+          int slot;
+          int periodical_check_needed_this;
 
-          if (sw == SW_HOST_CARD_INACTIVE)
+          slot = apdu_open_reader (l);
+          if (slot < 0)
+            break;
+
+          periodical_check_needed_this = apdu_connect (slot);
+          if (periodical_check_needed_this < 0)
             {
-              /* Try again.  */
-              sw = apdu_reset (slot);
+              /* We close a reader with no card.  */
+              err = gpg_error (GPG_ERR_ENODEV);
             }
-
-          if (!sw || sw == SW_HOST_ALREADY_CONNECTED)
-            err = 0;
           else
-            err = gpg_error (GPG_ERR_ENODEV);
+            {
+              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);
         }
-      else
-        err = gpg_error (GPG_ERR_ENODEV);
 
-      if (!err)
-        err = app_new_register (slot, ctrl, name);
-      else
-        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 ();
     }
-  else
-    err = 0;
 
-  if (!err)
-    app = app_top;
-  else
-    app = NULL;
+  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 (app)
+  if (a)
     {
-      lock_app (app, ctrl);
-      err = check_conflict (app, name);
+      err = check_conflict (a, name);
       if (!err)
         {
-          app->ref_count++;
-          *r_app = app;
+          a->ref_count++;
+          *r_app = a;
         }
-      unlock_app (app);
+      unlock_app (a);
     }
+  else
+    err = gpg_error (GPG_ERR_ENODEV);
+
+  npth_mutex_unlock (&app_list_lock);
 
   return err;
 }
@@ -513,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;
-
-  if (!app || !serial)
-    return gpg_error (GPG_ERR_INV_VALUE);
+  char *serial;
 
-  *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;
 }
 
 
@@ -552,15 +565,12 @@ 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);
+    send_status_direct (ctrl, "APPTYPE", app->apptype);
   err = lock_app (app, ctrl);
   if (err)
     return err;
@@ -641,20 +651,18 @@ 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;
     }
@@ -1008,65 +1016,96 @@ report_change (int slot, int old_status, int cur_status)
   xfree (homestr);
 }
 
-void
+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 (a->require_get_status)
+
+      if (sw == SW_HOST_NO_READER)
+        {
+          /* Most likely the _reader_ has been unplugged.  */
+          status = 0;
+        }
+      else if (sw)
         {
-          int sw;
-          unsigned int status;
-          sw = apdu_get_status (a->slot, 0, &status);
+          /* Get status failed.  Ignore that.  */
+          if (a->periodical_check_needed)
+            periodical_check_needed = 1;
+          continue;
+        }
 
-          if (sw == SW_HOST_NO_READER)
-            {
-              /* Most likely the _reader_ has been unplugged.  */
-              status = 0;
-            }
-          else if (sw)
+      if (a->card_status != status)
+        {
+          report_change (a->slot, a->card_status, status);
+          send_client_notifications (a, status == 0);
+
+          if (status == 0)
             {
-              /* Get status failed.  Ignore that.  */
-              continue;
+              log_debug ("Removal of a card: %d\n", a->slot);
+              apdu_close_reader (a->slot);
+              deallocate_app (a);
             }
-
-          if (a->card_status != status)
+          else
             {
-              report_change (a->slot, a->card_status, status);
-              send_client_notifications (a);
-
-              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;
+              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. */
-void
+gpg_error_t
 initialize_module_command (void)
 {
-  static int initialized;
-  int err;
+  gpg_error_t err;
 
-  if (!initialized)
+  if (npth_mutex_init (&app_list_lock, NULL))
     {
-      err = npth_mutex_init (&app_list_lock, NULL);
-      if (!err)
-        initialized = 1;
+      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);
 }