scd: Only submit apdu_get_status when needed.
[gnupg.git] / scd / apdu.c
index 177cd8e..f88d970 100644 (file)
 #define CCID_DRIVER_INCLUDE_USB_IDS 1
 #include "ccid-driver.h"
 
+struct dev_list {
+  struct ccid_dev_table *ccid_table;
+  const char *portstr;
+  int idx;
+  int idx_max;
+};
+
 /* Due to conflicting use of threading libraries we usually can't link
    against libpcsclite if we are using Pth.  Instead we use a wrapper
    program.  Note that with nPth there is no need for a wrapper. */
@@ -144,7 +151,6 @@ struct reader_table_s {
                               not yet been read; i.e. the card is not
                               ready for use. */
 #ifdef USE_NPTH
-  int lock_initialized;
   npth_mutex_t lock;
 #endif
 };
@@ -153,6 +159,10 @@ typedef struct reader_table_s *reader_table_t;
 /* A global table to keep track of active readers. */
 static struct reader_table_s reader_table[MAX_READER];
 
+#ifdef USE_NPTH
+static npth_mutex_t reader_table_lock;
+#endif
+
 
 /* ct API function pointer. */
 static char (* DLSTDCALL CT_init) (unsigned short ctn, unsigned short Pn);
@@ -424,35 +434,27 @@ static int
 new_reader_slot (void)
 {
   int i, reader = -1;
-  int err;
 
   for (i=0; i < MAX_READER; i++)
-    {
-      if (!reader_table[i].used && reader == -1)
+    if (!reader_table[i].used)
+      {
         reader = i;
-    }
+        reader_table[reader].used = 1;
+        break;
+      }
+
   if (reader == -1)
     {
       log_error ("new_reader_slot: out of slots\n");
       return -1;
     }
-#ifdef USE_NPTH
-  if (!reader_table[reader].lock_initialized)
-    {
-      err = npth_mutex_init (&reader_table[reader].lock, NULL);
-      if (err)
-        {
-          log_error ("error initializing mutex: %s\n", strerror (err));
-          return -1;
-        }
-      reader_table[reader].lock_initialized = 1;
-    }
-#endif /*USE_NPTH*/
+
   if (lock_slot (reader))
     {
-      log_error ("error locking mutex: %s\n", strerror (errno));
+      reader_table[reader].used = 0;
       return -1;
     }
+
   reader_table[reader].connect_card = NULL;
   reader_table[reader].disconnect_card = NULL;
   reader_table[reader].close_reader = NULL;
@@ -465,7 +467,6 @@ new_reader_slot (void)
   reader_table[reader].pinpad_verify = pcsc_pinpad_verify;
   reader_table[reader].pinpad_modify = pcsc_pinpad_modify;
 
-  reader_table[reader].used = 1;
   reader_table[reader].is_t0 = 1;
   reader_table[reader].is_spr532 = 0;
   reader_table[reader].pinpad_varlen_supported = 0;
@@ -650,7 +651,6 @@ static int
 close_ct_reader (int slot)
 {
   CT_close (slot);
-  reader_table[slot].used = 0;
   return 0;
 }
 
@@ -1433,9 +1433,6 @@ static int
 close_pcsc_reader_direct (int slot)
 {
   pcsc_release_context (reader_table[slot].pcsc.context);
-  xfree (reader_table[slot].rdrname);
-  reader_table[slot].rdrname = NULL;
-  reader_table[slot].used = 0;
   return 0;
 }
 #endif /*!NEED_PCSC_WRAPPER*/
@@ -2438,8 +2435,6 @@ static int
 close_ccid_reader (int slot)
 {
   ccid_close_reader (reader_table[slot].ccid.handle);
-  reader_table[slot].rdrname = NULL;
-  reader_table[slot].used = 0;
   return 0;
 }
 
@@ -2573,7 +2568,7 @@ ccid_pinpad_operation (int slot, int class, int ins, int p0, int p1,
 
 /* Open the reader and try to read an ATR.  */
 static int
-open_ccid_reader (const char *portstr)
+open_ccid_reader (struct dev_list *dl)
 {
   int err;
   int slot;
@@ -2584,8 +2579,8 @@ open_ccid_reader (const char *portstr)
     return -1;
   slotp = reader_table + slot;
 
-  err = ccid_open_reader (&slotp->ccid.handle, portstr,
-                          (const char **)&slotp->rdrname);
+  err = ccid_open_reader (dl->portstr, dl->idx, dl->ccid_table,
+                          &slotp->ccid.handle, &slotp->rdrname);
   if (err)
     {
       slotp->used = 0;
@@ -2618,12 +2613,7 @@ open_ccid_reader (const char *portstr)
   unlock_slot (slot);
   return slot;
 }
-
-
-
 #endif /* HAVE_LIBUSB */
-
-
 \f
 #ifdef USE_G10CODE_RAPDU
 /*
@@ -2670,7 +2660,6 @@ static int
 close_rapdu_reader (int slot)
 {
   rapdu_release (reader_table[slot].rapdu.handle);
-  reader_table[slot].used = 0;
   return 0;
 }
 
@@ -2927,63 +2916,91 @@ open_rapdu_reader (int portno,
 /*
        Driver Access
  */
+gpg_error_t
+apdu_dev_list_start (const char *portstr, struct dev_list **l_p)
+{
+  struct dev_list *dl = xtrymalloc (sizeof (struct dev_list));
 
+  *l_p = NULL;
+  if (!dl)
+    return gpg_error_from_syserror ();
 
-/* Open the reader and return an internal slot number or -1 on
-   error. If PORTSTR is NULL we default to a suitable port (for ctAPI:
-   the first USB reader.  For PC/SC the first listed reader). */
-int
-apdu_open_reader (const char *portstr)
-{
-  static int pcsc_api_loaded, ct_api_loaded;
-  int slot;
+  dl->portstr = portstr;
+  dl->idx = 0;
 
-  if (DBG_READER)
-    log_debug ("enter: apdu_open_reader: portstr=%s\n", portstr);
+  npth_mutex_lock (&reader_table_lock);
 
 #ifdef HAVE_LIBUSB
-  if (!opt.disable_ccid)
+  if (opt.disable_ccid)
+    {
+      dl->ccid_table = NULL;
+      dl->idx_max = 1;
+    }
+  else
     {
-      static int once_available;
-      int i;
-      const char *s;
+      gpg_error_t err;
 
-      slot = open_ccid_reader (portstr);
-      if (slot != -1)
-        {
-          once_available = 1;
-          if (DBG_READER)
-            log_debug ("leave: apdu_open_reader => slot=%d [ccid]\n", slot);
-          return slot; /* got one */
-        }
+      err = ccid_dev_scan (&dl->idx_max, &dl->ccid_table);
+      if (err)
+        return err;
 
-      /* If we ever loaded successfully loaded a CCID reader we never
-         want to fallback to another driver.  This solves a problem
-         where ccid was used, the card unplugged and then scdaemon
-         tries to find a new reader and will eventually try PC/SC over
-         and over again.  To reset this flag "gpgconf --kill scdaemon"
-         can be used.  */
-      if (once_available)
+      if (dl->idx_max == 0)
         {
-          if (DBG_READER)
-            log_debug ("leave: apdu_open_reader => slot=-1 (once_avail)\n");
-          return -1;
-        }
-
-      /* If a CCID reader specification has been given, the user does
-         not want a fallback to other drivers. */
-      if (portstr)
-        for (s=portstr, i=0; *s; s++)
-          if (*s == ':' && (++i == 3))
+          /* If a CCID reader specification has been given, the user does
+             not want a fallback to other drivers. */
+          if (portstr && strlen (portstr) > 5 && portstr[4] == ':')
             {
               if (DBG_READER)
                 log_debug ("leave: apdu_open_reader => slot=-1 (no ccid)\n");
-              return -1;
+
+              xfree (dl);
+              npth_mutex_unlock (&reader_table_lock);
+              return gpg_error (GPG_ERR_ENODEV);
             }
+          else
+            dl->idx_max = 1;
+        }
     }
-
+#else
+  dl->ccid_table = NULL;
+  dl->idx_max = 1;
 #endif /* HAVE_LIBUSB */
 
+  *l_p = dl;
+  return 0;
+}
+
+int
+apdu_dev_list_finish (struct dev_list *dl)
+{
+  int all_have_intr_endp;
+
+#ifdef HAVE_LIBUSB
+  if (dl->ccid_table)
+    all_have_intr_endp = ccid_dev_scan_finish (dl->ccid_table, dl->idx_max);
+  else
+    all_have_intr_endp = 0;
+#else
+  all_have_intr_endp = 0;
+#endif
+  xfree (dl);
+  npth_mutex_unlock (&reader_table_lock);
+  return all_have_intr_endp;
+}
+
+
+/* Open the reader and return an internal slot number or -1 on
+   error. If PORTSTR is NULL we default to a suitable port (for ctAPI:
+   the first USB reader.  For PC/SC the first listed reader). */
+static int
+apdu_open_one_reader (const char *portstr)
+{
+  static int pcsc_api_loaded, ct_api_loaded;
+  int slot;
+
+  if (DBG_READER)
+    log_debug ("enter: apdu_open_reader: portstr=%s\n", portstr);
+
   if (opt.ctapi_driver && *opt.ctapi_driver)
     {
       int port = portstr? atoi (portstr) : 32768;
@@ -3013,7 +3030,6 @@ apdu_open_reader (const char *portstr)
       return open_ct_reader (port);
     }
 
-
   /* No ctAPI configured, so lets try the PC/SC API */
   if (!pcsc_api_loaded)
     {
@@ -3107,6 +3123,98 @@ apdu_open_reader (const char *portstr)
   return slot;
 }
 
+int
+apdu_open_reader (struct dev_list *dl)
+{
+  int slot;
+
+#ifdef HAVE_LIBUSB
+  if (dl->ccid_table)
+    { /* CCID readers.  */
+      int readerno;
+
+      /* See whether we want to use the reader ID string or a reader
+         number. A readerno of -1 indicates that the reader ID string is
+         to be used. */
+      if (dl->portstr && strchr (dl->portstr, ':'))
+        readerno = -1; /* We want to use the readerid.  */
+      else if (dl->portstr)
+        {
+          readerno = atoi (dl->portstr);
+          if (readerno < 0)
+            {
+              return -1;
+            }
+        }
+      else
+        readerno = 0;  /* Default. */
+
+      if (readerno > 0)
+        { /* Use single, the specific reader.  */
+          if (readerno >= dl->idx_max)
+            return -1;
+
+          dl->idx = readerno;
+          dl->portstr = NULL;
+          slot = open_ccid_reader (dl);
+          dl->idx = dl->idx_max;
+          if (slot >= 0)
+            return slot;
+          else
+            return -1;
+        }
+
+      while (dl->idx < dl->idx_max)
+        {
+          unsigned int bai = ccid_get_BAI (dl->idx, dl->ccid_table);
+
+          if (DBG_READER)
+            log_debug ("apdu_open_reader: BAI=%x\n", bai);
+
+          /* Check identity by BAI against already opened HANDLEs.  */
+          for (slot = 0; slot < MAX_READER; slot++)
+            if (reader_table[slot].used
+                && ccid_compare_BAI (reader_table[slot].ccid.handle, bai))
+              break;
+
+          if (slot == MAX_READER)
+            { /* Found a new device.  */
+              if (DBG_READER)
+                log_debug ("apdu_open_reader: new device=%x\n", bai);
+
+              slot = open_ccid_reader (dl);
+
+              dl->idx++;
+              if (slot >= 0)
+                return slot;
+              else
+                {
+                  /* Skip this reader.  */
+                  log_error ("ccid open error: skip\n");
+                  continue;
+                }
+            }
+          else
+            dl->idx++;
+        }
+
+      slot = -1;
+    }
+  else
+#endif
+    { /* PC/SC readers.  */
+      if (dl->idx == 0)
+        {
+          dl->idx++;
+          slot = apdu_open_one_reader (dl->portstr);
+        }
+      else
+        slot = -1;
+    }
+
+  return slot;
+}
+
 
 /* Open an remote reader and return an internal slot number or -1 on
    error. This function is an alternative to apdu_open_reader and used
@@ -3180,10 +3288,14 @@ apdu_close_reader (int slot)
   if (reader_table[slot].close_reader)
     {
       sw = reader_table[slot].close_reader (slot);
+      reader_table[slot].used = 0;
       if (DBG_READER)
         log_debug ("leave: apdu_close_reader => 0x%x (close_reader)\n", sw);
       return sw;
     }
+  xfree (reader_table[slot].rdrname);
+  reader_table[slot].rdrname = NULL;
+  reader_table[slot].used = 0;
   if (DBG_READER)
     log_debug ("leave: apdu_close_reader => SW_HOST_NOT_SUPPORTED\n");
   return SW_HOST_NOT_SUPPORTED;
@@ -3203,14 +3315,18 @@ apdu_prepare_exit (void)
   if (!sentinel)
     {
       sentinel = 1;
+      npth_mutex_lock (&reader_table_lock);
       for (slot = 0; slot < MAX_READER; slot++)
         if (reader_table[slot].used)
           {
             apdu_disconnect (slot);
             if (reader_table[slot].close_reader)
               reader_table[slot].close_reader (slot);
+            xfree (reader_table[slot].rdrname);
+            reader_table[slot].rdrname = NULL;
             reader_table[slot].used = 0;
           }
+      npth_mutex_unlock (&reader_table_lock);
       sentinel = 0;
     }
 }
@@ -4222,3 +4338,28 @@ apdu_get_reader_name (int slot)
 {
   return reader_table[slot].rdrname;
 }
+
+gpg_error_t
+apdu_init (void)
+{
+#ifdef USE_NPTH
+  gpg_error_t err;
+  int i;
+
+  if (npth_mutex_init (&reader_table_lock, NULL))
+    goto leave;
+
+  for (i = 0; i < MAX_READER; i++)
+    if (npth_mutex_init (&reader_table[i].lock, NULL))
+      goto leave;
+
+  /* All done well.  */
+  return 0;
+
+ leave:
+  err = gpg_error_from_syserror ();
+  log_error ("apdu: error initializing mutex: %s\n", gpg_strerror (err));
+  return err;
+#endif /*USE_NPTH*/
+  return 0;
+}