(retrieve_key_material): Rewritten. Return a
authorWerner Koch <wk@gnupg.org>
Thu, 14 Apr 2005 17:25:43 +0000 (17:25 +0000)
committerWerner Koch <wk@gnupg.org>
Thu, 14 Apr 2005 17:25:43 +0000 (17:25 +0000)
proper error code.
(retrieve_next_token): Removed.
(retrieve_fpr_from_card): Rewritten to make use of DO caching and
to take the KEYNO as arg.
(get_public_key): Renamed variable for clarity.

scd/ChangeLog
scd/app-openpgp.c
scd/command.c

index f8f8043..9d246ff 100644 (file)
@@ -1,3 +1,31 @@
+2005-04-14  Werner Koch  <wk@g10code.com>
+
+       * app-openpgp.c (retrieve_key_material): Rewritten.  Return a
+       proper error code.
+       (retrieve_next_token): Removed.
+       (retrieve_fpr_from_card): Rewritten to make use of DO caching and
+       to take the KEYNO as arg.
+       (get_public_key): Renamed variable for clarity.
+
+2005-04-12  Werner Koch  <wk@g10code.com>
+
+       Basic support for several sessions.
+       
+       * command.c (scd_command_handler): Replace the primary_connection
+       stuff by a real connection list.  Release the local context on
+       exit.
+       (scd_update_reader_status_file): Update accordingly.  Send signal
+       to all connections who registered an event signal.
+       (cmd_lock, cmd_unlock, register_commands): New commands LOCK and
+       UNLOCK.
+       (cmd_setdata, cmd_pksign, cmd_pkauth, cmd_pkdecrypt, cmd_setattr) 
+       (cmd_genkey, cmd_passwd, cmd_checkpin): Return an error if reader
+       is locked.
+       (do_reset): Handle locking.
+       (open_card): Ditto.  Share the reader slot with other sessions.
+       (get_reader_slot): New.
+       (update_card_removed): New.  Use it in the TEST_CARD_REMOVAL macro.
+
 2005-04-07  Werner Koch  <wk@g10code.com>
 
        * app-openpgp.c (do_check_pin): Add hack to allow verification of
index 1ed0571..0d80c41 100644 (file)
@@ -784,266 +784,149 @@ do_getattr (app_t app, ctrl_t ctrl, const char *name)
 }
 
 /* Retrieve the fingerprint from the card inserted in SLOT and write
-   the according hex representation (40 hex digits plus NUL character)
-   to FPR.   */
+   the according hex representation to FPR.  Caller must have provide
+   a buffer at FPR of least 41 bytes.  Returns 0 on success or an
+   error code. */
+#if GNUPG_MAJOR_VERSION > 1
 static gpg_error_t
-retrieve_fpr_from_card (int slot, char *fpr)
+retrieve_fpr_from_card (app_t app, int keyno, char *fpr)
 {
-  const unsigned char *value;
-  unsigned char *data;
-  size_t data_n;
-  gpg_error_t err;
-  size_t value_n;
-  unsigned int i;
+  gpg_error_t err = 0;
+  void *relptr;
+  unsigned char *value;
+  size_t valuelen;
+  int i;
 
-  data = NULL;
+  assert (keyno >=0 && keyno <= 2);
 
-  err = iso7816_get_data (slot, 0x6E, &data, &data_n);
-  if (err)
-    /* FIXME */
-    goto out;
-
-  value = find_tlv (data, data_n, 0x00C5, &value_n);
-  if (! (value
-        && (! (value_n > (data_n - (value - data))))
-        && (value_n >= 60))) /* FIXME: Shouldn't this be "== 60"?  */
+  relptr = get_one_do (app, 0x00C5, &value, &valuelen, NULL);
+  if (relptr && valuelen >= 60)
     {
-      /* FIXME? */
-      err = gpg_error (GPG_ERR_CARD); /*  */
-      goto out;
+      for (i = 0; i < 20; i++)
+        sprintf (fpr + (i * 2), "%02X", value[(keyno*20)+i]);
     }
-
-  /* Copy out third key FPR.  */
-  for (i = 0; i < 20; i++)
-    sprintf (fpr + (i * 2), "%02X", (value + (2 * 20))[i]);
-
- out:
-
-  xfree (data);
-
+  else
+    err = gpg_error (GPG_ERR_NOT_FOUND);
+  xfree (relptr);
   return err;
 }
+#endif /*GNUPG_MAJOR_VERSION > 1*/
 
-/* Retrieve the next token from S, using ":" as delimiter.  */
-static char *
-retrieve_next_token (char *s)
-{
-  char *p;
-
-  p = strtok (s, ":");
-  if (! p)
-    log_error ("error while extracting token\n");
 
-  return p;
-}
-
-/* Retrieve the secret key material for the key, whose fingerprint is
-   FPR, from gpg output, which can be read through the stream FP.  The
-   RSA modulus will be stored in m/mlen, the secret exponent in
-   e/elen.  Return zero on success, one on failure.  */
-static int
-retrieve_key_material (FILE *fp, const char *fpr,
+/* Retrieve the public key material for the RSA key, whose fingerprint
+   is FPR, from gpg output, which can be read through the stream FP.
+   The RSA modulus will be stored at the address of M and MLEN, the
+   public exponent at E and ELEN.  Returns zero on success, an error
+   code on failure.  Caller must release the allocated buffers at M
+   and E if the function returns success.  */
+#if GNUPG_MAJOR_VERSION > 1
+static gpg_error_t
+retrieve_key_material (FILE *fp, const char *hexkeyid,
                       const unsigned char **m, size_t *mlen,
                       const unsigned char **e, size_t *elen)
 {
-  size_t line_size;
-  ssize_t line_ret;
-  char *line;
-  int ret;
-  int found_key;
-  char *token;
-  int pkd_n;
-  unsigned char *m_new;
-  unsigned char *e_new;
-  size_t m_new_n;
-  size_t e_new_n;
-  int is_rsa;
-  gcry_mpi_t mpi;
-  gcry_error_t err;
-  size_t max_length;
-
-  line_size = 0;
-  line = NULL;
-  found_key = 0;
-  pkd_n = 0;
-  m_new = NULL;
-  e_new = NULL;
-  mpi = NULL;
-  ret = 0;
-
-#warning This part should get rewritten for clarity
-  /* We should use an algorithm similar to the one used by gpgme.
-     This will reduce the size of the code at least by 50%.  [wk] */
-
-  while (1)
+  gcry_error_t err = 0;
+  char *line = NULL;    /* read_line() buffer. */
+  size_t line_size = 0; /* Helper for for read_line. */
+  int found_key = 0;    /* Helper to find a matching key. */
+  unsigned char *m_new = NULL;
+  unsigned char *e_new = NULL;
+  size_t m_new_n = 0;
+  size_t e_new_n = 0;
+
+  /* Loop over all records until we have found the subkey
+     corresponsing to the fingerprint. Inm general the first record
+     should be the pub record, but we don't rely on that.  Given that
+     we only need to look at one key, it is sufficient to compare the
+     keyid so that we don't need to look at "fpr" records. */
+  for (;;)
     {
-      /* FIXME?  */
-      max_length = 1024;
-      line_ret = read_line (fp, &line, &line_size, &max_length);
-      if (line_ret < 0)
-       {
-         ret = 1;
-         break;
-       }
-      if (! line_ret)
-       /* EOF.  */
-       /* FIXME?  */
-       break;
+      char *p;
+      char *fields[6];
+      int nfields;
+      size_t max_length;
+      gcry_mpi_t mpi;
+      int i;
 
-      token = retrieve_next_token (line);
-      if (! found_key)
-       {
-         /* Key not found yet, search for key entry.  */
-         if ((! strcmp (token, "pub")) || (! strcmp (token, "sub")))
-           {
-             /* Reached next key entry, parse it.  */
-
-             /* This is the trust level (right, FIXME?).  */
-             token = retrieve_next_token (NULL);
-             if (! token)
-               {
-                 ret = 1;
-                 break;
-               }
-
-             /* This is the size.  */
-             token = retrieve_next_token (NULL);
-             if (! token)
-               {
-                 ret = 1;
-                 break;
-               }
-
-             /* This is the algorithm (right, FIXME?).  */
-             token = retrieve_next_token (NULL);
-             if (! token)
-               {
-                 ret = 1;
-                 break;
-               }
-             is_rsa = ! strcmp (token, "1");
-
-             /* This is the fingerprint.  */
-             token = retrieve_next_token (NULL);
-             if (! token)
-               {
-                 ret = 1;
-                 break;
-               }
-
-             if (! strcmp (token, fpr))
-               {
-                 /* Found our key.  */
-                 if (! is_rsa)
-                   {
-                     /* FIXME.  */
-                     ret = 1;
-                     break;
-                   }
-                 found_key = 1;
-               }
-           }
-       }
-      else
+      max_length = 4096;
+      i = read_line (fp, &line, &line_size, &max_length);
+      if (!i)
+        break; /* EOF. */
+      if (i < 0)
        {
-         if (! strcmp (token, "sub"))
-           /* Next key entry, break.  */
-           break;
-
-         if (! strcmp (token, "pkd"))
-           {
-             if ((pkd_n == 0) || (pkd_n == 1))
-               {
-                 /* This is the pkd index.  */
-                 token = retrieve_next_token (NULL);
-                 if (! token)
-                   {
-                     /* FIXME.  */
-                     ret = 1;
-                     break;
-                   }
-
-                 /* This is the pkd size.  */
-                 token = retrieve_next_token (NULL);
-                 if (! token)
-                   {
-                     /* FIXME.  */
-                     ret = 1;
-                     break;
-                   }
-
-                 /* This is the pkd mpi.  */
-                 token = retrieve_next_token (NULL);
-                 if (! token)
-                   {
-                     /* FIXME.  */
-                     ret = 1;
-                     break;
-                   }
-
-                 err = gcry_mpi_scan (&mpi, GCRYMPI_FMT_HEX, token, 0, NULL);
-                 if (err)
-                   {
-                     log_error ("error while converting pkd %i from hex: %s\n",
-                                pkd_n, gcry_strerror (err));
-                     ret = 1;
-                     break;
-                   }
-
-                 if (pkd_n == 0)
-                   err = gcry_mpi_aprint (GCRYMPI_FMT_STD,
-                                          &m_new, &m_new_n, mpi);
-                 else
-                   err = gcry_mpi_aprint (GCRYMPI_FMT_STD,
-                                          &e_new, &e_new_n, mpi);
-                 if (err)
-                   {
-                     log_error ("error while converting pkd %i to std: %s\n",
-                                pkd_n, gcry_strerror (err));
-                     ret = 1;
-                     break;
-                   }
-                 gcry_mpi_release (mpi);
-                 mpi = NULL;
-                 pkd_n++;
-               }
-             else
-               {
-                 /* Too many pkd entries.  */
-                 /* FIXME */
-                 ret = 1;
-                 break;
-               }
-           }
+         err = gpg_error_from_errno (errno);
+         goto leave; /* Error. */
        }
-    }
-  if (ret)
-    goto out;
-
-  if (pkd_n < 2)
-    {
-      /* Not enough pkds retrieved.  */
-      ret = 1;
-      goto out;
-    }
-
-  *m = m_new;
-  *mlen = m_new_n;
-  *e = e_new;
-  *elen = e_new_n;
+      if (!max_length)
+        {
+          err = gpg_error (GPG_ERR_TRUNCATED);
+          goto leave;  /* Line truncated - we better stop processing.  */
+        }
 
- out:
+      /* Parse the line into fields. */
+      for (nfields=0, p=line; p && nfields < DIM (fields); nfields++)
+        {
+          fields[nfields] = p;
+          p = strchr (p, ':');
+          if (p)
+            *(p++) = 0;
+        }
+      if (!nfields)
+        continue; /* No fields at all - skip line.  */
 
-  if (ret)
+      if (!found_key)
+        {
+          if ( (!strcmp (fields[0], "sub") || !strcmp (fields[0], "pub") )
+               && nfields > 4 && !strcmp (fields[4], hexkeyid))
+            found_key = 1;
+          continue;
+       }
+      
+      if ( !strcmp (fields[0], "sub") || !strcmp (fields[0], "pub") )
+        break; /* Next key - stop.  */
+
+      if ( strcmp (fields[0], "pkd") )
+        continue; /* Not a key data record.  */
+      i = 0; /* Avoid erroneous compiler warning. */
+      if ( nfields < 4 || (i = atoi (fields[1])) < 0 || i > 1
+           || (!i && m_new) || (i && e_new))
+        {
+          err = gpg_error (GPG_ERR_GENERAL);
+          goto leave; /* Error: Invalid key data record or not an RSA key.  */
+        }
+      
+      err = gcry_mpi_scan (&mpi, GCRYMPI_FMT_HEX, fields[3], 0, NULL);
+      if (err)
+        mpi = NULL;
+      else if (!i)
+        err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &m_new, &m_new_n, mpi);
+      else
+        err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &e_new, &e_new_n, mpi);
+      gcry_mpi_release (mpi);
+      if (err)
+        goto leave;
+    }
+  
+  if (m_new && e_new)
     {
-      gcry_free (m_new);
-      gcry_free (e_new);
+      *m = m_new;
+      *mlen = m_new_n;
+      m_new = NULL;
+      *e = e_new;
+      *elen = e_new_n;
+      e_new = NULL;
     }
-  gcry_mpi_release (mpi);
-  gcry_free (line);
+  else
+    err = gpg_error (GPG_ERR_GENERAL);
 
-  return ret;
+ leave:
+  xfree (m_new);
+  xfree (e_new);
+  xfree (line);
+  return err;
 }
+#endif /*GNUPG_MAJOR_VERSION > 1*/
+
 
 /* Get the public key for KEYNO and store it as an S-expresion with
    the APP handle.  On error that field gets cleared.  If we already
@@ -1158,52 +1041,49 @@ get_public_key (app_t app, int keyno)
         The helper we use here is gpg itself, which should know about
         the key in any case.  */
 
-      char fpr_long[41];
-      char *fpr = fpr_long + 24;
-      char *command;
+      char fpr[41];
+      char *hexkeyid;
+      char *command = NULL;
       FILE *fp;
       int ret;
 
-      command = NULL;
+      buffer = NULL; /* We don't need buffer.  */
 
-      err = retrieve_fpr_from_card (app->slot, fpr_long);
+      err = retrieve_fpr_from_card (app, keyno, fpr);
       if (err)
        {
          log_error ("error while retrieving fpr from card: %s\n",
                     gpg_strerror (err));
          goto leave;
        }
+      hexkeyid = fpr + 24;
 
       ret = asprintf (&command,
                      "gpg --list-keys --with-colons --with-key-data '%s'",
-                     fpr_long);
+                     fpr);
       if (ret < 0)
        {
          err = gpg_error_from_errno (errno);
-         log_error ("error while creating pipe command "
-                    "for retrieving key: %s\n", gpg_strerror (err));
          goto leave;
        }
 
       fp = popen (command, "r");
-      if (! fp)
+      free (command);
+      if (!fp)
        {
          err = gpg_error_from_errno (errno);
-         log_error ("error while creating pipe: %s\n", gpg_strerror (err));
+         log_error ("running gpg failed: %s\n", gpg_strerror (err));
          goto leave;
        }
 
-      ret = retrieve_key_material (fp, fpr, &m, &mlen, &e, &elen);
+      err = retrieve_key_material (fp, hexkeyid, &m, &mlen, &e, &elen);
       fclose (fp);
-      if (ret)
+      if (err)
        {
-         /* FIXME?  */
-         err = gpg_error (GPG_ERR_INTERNAL);
-         log_error ("error while retrieving key material through pipe\n");
+         log_error ("error while retrieving key material through pipe: %s\n",
+                     gpg_strerror (err));
          goto leave;
        }
-
-      buffer = NULL;
     }
 
   /* Allocate a buffer to construct the S-expression.  */
@@ -1216,11 +1096,11 @@ get_public_key (app_t app, int keyno)
       goto leave;
     }
   
-  sprintf (keybuf, "(10:public-key(3:rsa(1:n%u", (unsigned int) mlen);
+  sprintf (keybuf, "(10:public-key(3:rsa(1:n%u:", (unsigned int) mlen);
   keybuf_p = keybuf + strlen (keybuf);
   memcpy (keybuf_p, m, mlen);
   keybuf_p += mlen;
-  sprintf (keybuf_p, ")(1:e%u", (unsigned int)elen);
+  sprintf (keybuf_p, ")(1:e%u:", (unsigned int)elen);
   keybuf_p += strlen (keybuf_p);
   memcpy (keybuf_p, e, elen);
   keybuf_p += elen;
index ea296b6..9881b1b 100644 (file)
@@ -26,6 +26,9 @@
 #include <ctype.h>
 #include <unistd.h>
 #include <signal.h>
+#ifdef USE_GNU_PTH
+# include <pth.h>
+#endif
 
 #include <assuan.h>
 
 #define MAXLEN_PIN 100
 
 
-/* We keep track of the primary client using scdaemon.  This one will
-   for example receive signal on card change. */
-static ctrl_t primary_connection;
-
-
 #define set_error(e,t) assuan_set_error (ctx, ASSUAN_ ## e, (t))
 
 
@@ -52,20 +50,65 @@ static ctrl_t primary_connection;
           int _r = (r);                                     \
           if (gpg_err_code (_r) == GPG_ERR_CARD_NOT_PRESENT \
               || gpg_err_code (_r) == GPG_ERR_CARD_REMOVED) \
-            (c)->server_local->card_removed = 1;            \
+            update_card_removed ((c)->reader_slot, 1);      \
        } while (0)
 
+#define IS_LOCKED(c)                                                     \
+     (locked_session && locked_session != (c)->server_local              \
+      && (c)->reader_slot != -1 && locked_session->ctrl_backlink         \
+      && (c)->reader_slot == locked_session->ctrl_backlink->reader_slot)
+
 
-/* Data used to associate an Assuan context with local server data */
+/* Data used to associate an Assuan context with local server data.
+   This object describes the local properties of one session.  */
 struct server_local_s {
+  /* We keep a list of all active sessions with the anchor at
+     SESSION_LIST (see below).  This field is used for linking. */
+  struct server_local_s *next_session; 
+
+  /* This object is usually assigned to a CTRL object (which is
+     globally visible).  While enumeratin all sessions we sometimes
+     need to access data of the CTRL object; thus we keep a
+     backpointer here. */
+  ctrl_t ctrl_backlink;
+
+  /* The Assuan context used by this session/server. */
   assuan_context_t assuan_ctx;
+
   int event_signal;        /* Or 0 if not used. */
-  int card_removed;        /* True if the card has been removed and a
-                              reset is required to continue
-                              operation. */
+
+  /* True if the card has been removed and a reset is required to
+     continue operation. */
+  int card_removed;        
 };
 
 
+/* To keep track of all running sessions, we link all active server
+   contexts and the anchor in this variable.  */
+static struct server_local_s *session_list;
+
+/* If a session has been locked we store a link to its server object
+   in this variable. */
+static struct server_local_s *locked_session;
+
+
+
+\f
+/* Update the CARD_REMOVED element of all sessions using the reader
+   given by SLOT to VALUE  */
+static void
+update_card_removed (int slot, int value)
+{
+  struct server_local_s *sl;
+
+  for (sl=session_list; sl; sl = sl->next_session)
+    if (sl->ctrl_backlink
+        && sl->ctrl_backlink->reader_slot == slot)
+      sl->card_removed = value;
+}
+
+
+
 /* Check whether the option NAME appears in LINE */
 static int
 has_option (const char *line, const char *name)
@@ -79,10 +122,13 @@ has_option (const char *line, const char *name)
 
 
 /* Reset the card and free the application context.  With DO_CLOSE set
-   to true, close the reader and don't do just a reset. */
+   to true and this is the last session with a reference to teh
+   reader, close the reader and don't do just a reset. */
 static void
 do_reset (ctrl_t ctrl, int do_close)
 {
+  int slot = ctrl->reader_slot;
+
   if (ctrl->card_ctx)
     {
       card_close (ctrl->card_ctx);
@@ -97,20 +143,61 @@ do_reset (ctrl_t ctrl, int do_close)
     }
   if (ctrl->reader_slot != -1)
     {
-      if (do_close || apdu_reset (ctrl->reader_slot))
+      struct server_local_s *sl;
+
+      /* If we are the only session with the reader open we may close
+         it.  If not, do a reset unless the a lock is held on the
+         reader.  */
+      for (sl=session_list; sl; sl = sl->next_session)
+        if (sl != ctrl->server_local
+            && sl->ctrl_backlink->reader_slot == ctrl->reader_slot)
+          break;
+      if (sl) /* There is another session with the reader open. */
+        {
+          if ( IS_LOCKED (ctrl) ) /* If it is locked, release it. */
+            ctrl->reader_slot = -1;
+          else
+            {
+              if (do_close) /* Always mark reader unused. */
+                ctrl->reader_slot = -1;
+              else if (apdu_reset (ctrl->reader_slot)) /* Reset only if
+                                                          not locked */
+                {
+                  /* The reset failed.  Mark the reader as closed. */
+                  ctrl->reader_slot = -1;
+                }
+
+              if (locked_session && ctrl->server_local == locked_session)
+                {
+                  locked_session = NULL;
+                  log_debug ("implicitly unlocking due to RESET\n");
+                }
+            }
+        }
+      else /* No other session has the reader open.  */
         {
-          apdu_close_reader (ctrl->reader_slot);
-          ctrl->reader_slot = -1;
+          if (do_close || apdu_reset (ctrl->reader_slot))
+            {
+              apdu_close_reader (ctrl->reader_slot);
+              ctrl->reader_slot = -1;
+            }
+          if ( IS_LOCKED (ctrl) )
+            {
+              log_debug ("WARNING: cleaning up stale session lock\n");
+              locked_session =  NULL;
+            }
         }
     }
-  ctrl->server_local->card_removed = 0;
+
+  /* Reset card removed flag for the current reader.  */
+  update_card_removed (slot, 0);
 }
 
 \f
 static void
 reset_notify (assuan_context_t ctx)
 {
-  CTRL ctrl = assuan_get_pointer (ctx); 
+  ctrl_t ctrl = assuan_get_pointer (ctx); 
 
   do_reset (ctrl, 0);
 }
@@ -134,6 +221,27 @@ option_handler (assuan_context_t ctx, const char *key, const char *value)
 }
 
 
+/* Return the slot of the current reader or open the reader if no
+   other sessions are using a reader.  Note, that we currently support
+   only one reader but most of the code (except for this function)
+   should be able to cope with several readers.  */
+static int
+get_reader_slot (void)
+{
+  struct server_local_s *sl;
+  int slot= -1;
+
+  for (sl=session_list; sl; sl = sl->next_session)
+    if (sl->ctrl_backlink
+        && (slot = sl->ctrl_backlink->reader_slot) != -1)
+      break;
+
+  if (slot == -1)
+    slot = apdu_open_reader (opt.reader_port);
+
+  return slot;
+}
+
 /* If the card has not yet been opened, do it.  Note that this
    function returns an Assuan error, so don't map the error a second
    time */
@@ -154,10 +262,13 @@ open_card (ctrl_t ctrl, const char *apptype)
   if (ctrl->card_ctx)
     return 0; /* Already initialized using a card context. */
 
+  if ( IS_LOCKED (ctrl) )
+    return gpg_error (GPG_ERR_EBUSY);
+
   if (ctrl->reader_slot != -1)
     slot = ctrl->reader_slot;
   else
-    slot = apdu_open_reader (opt.reader_port);
+    slot = get_reader_slot ();
   ctrl->reader_slot = slot;
   if (slot == -1)
     err = gpg_error (GPG_ERR_CARD);
@@ -177,9 +288,7 @@ open_card (ctrl_t ctrl, const char *apptype)
         err = card_open (&ctrl->card_ctx);
     }
 
-  if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT)
-    ctrl->server_local->card_removed = 1;
-
+  TEST_CARD_REMOVAL (ctrl, err);
   return map_to_assuan_status (err);
 }
 
@@ -248,12 +357,12 @@ cmd_serialno (assuan_context_t ctx, char *line)
   time_t stamp;
 
   /* Clear the remove flag so that the open_card is able to reread it.  */
-
-  /* FIXME: We can't do that if we are in a locked state.  Retrun an
-     appropriate erro r in that case.  IF the card has not been
-     removed we may very well continue.  */
   if (ctrl->server_local->card_removed)
-    do_reset (ctrl, 0);
+    {
+      if ( IS_LOCKED (ctrl) )
+        return gpg_error (GPG_ERR_EBUSY);
+      do_reset (ctrl, 0);
+    }
 
   if ((rc = open_card (ctrl, *line? line:NULL)))
     return rc;
@@ -342,7 +451,7 @@ cmd_serialno (assuan_context_t ctx, char *line)
 static int
 cmd_learn (assuan_context_t ctx, char *line)
 {
-  CTRL ctrl = assuan_get_pointer (ctx);
+  ctrl_t ctrl = assuan_get_pointer (ctx);
   int rc = 0;
   int idx;
 
@@ -491,7 +600,7 @@ cmd_learn (assuan_context_t ctx, char *line)
 static int
 cmd_readcert (assuan_context_t ctx, char *line)
 {
-  CTRL ctrl = assuan_get_pointer (ctx);
+  ctrl_t ctrl = assuan_get_pointer (ctx);
   int rc;
   unsigned char *cert;
   size_t ncert;
@@ -630,12 +739,13 @@ cmd_readkey (assuan_context_t ctx, char *line)
 static int
 cmd_setdata (assuan_context_t ctx, char *line)
 {
-  CTRL ctrl = assuan_get_pointer (ctx);
+  ctrl_t ctrl = assuan_get_pointer (ctx);
   int n;
   char *p;
   unsigned char *buf;
 
-  /* FIXME: If we are locked return an error.  */
+  if (locked_session && locked_session != ctrl->server_local)
+    return gpg_error (GPG_ERR_EBUSY);
 
   /* Parse the hexstring. */
   for (p=line,n=0; hexdigitp (p); p++, n++)
@@ -700,13 +810,14 @@ pin_cb (void *opaque, const char *info, char **retstr)
 static int
 cmd_pksign (assuan_context_t ctx, char *line)
 {
-  CTRL ctrl = assuan_get_pointer (ctx);
+  ctrl_t ctrl = assuan_get_pointer (ctx);
   int rc;
   unsigned char *outdata;
   size_t outdatalen;
   char *keyidstr;
 
-  /* FIXME: If we are locked return an error.  */
+  if ( IS_LOCKED (ctrl) )
+    return gpg_error (GPG_ERR_EBUSY);
 
   if ((rc = open_card (ctrl, NULL)))
     return rc;
@@ -753,13 +864,14 @@ cmd_pksign (assuan_context_t ctx, char *line)
 static int
 cmd_pkauth (assuan_context_t ctx, char *line)
 {
-  CTRL ctrl = assuan_get_pointer (ctx);
+  ctrl_t ctrl = assuan_get_pointer (ctx);
   int rc;
   unsigned char *outdata;
   size_t outdatalen;
   char *keyidstr;
 
-  /* FIXME: If we are locked return an error.  */
+  if ( IS_LOCKED (ctrl) )
+    return gpg_error (GPG_ERR_EBUSY);
 
   if ((rc = open_card (ctrl, NULL)))
     return rc;
@@ -802,13 +914,14 @@ cmd_pkauth (assuan_context_t ctx, char *line)
 static int
 cmd_pkdecrypt (assuan_context_t ctx, char *line)
 {
-  CTRL ctrl = assuan_get_pointer (ctx);
+  ctrl_t ctrl = assuan_get_pointer (ctx);
   int rc;
   unsigned char *outdata;
   size_t outdatalen;
   char *keyidstr;
 
-  /* FIXME: If we are locked return an error.  */
+  if ( IS_LOCKED (ctrl) )
+    return gpg_error (GPG_ERR_EBUSY);
 
   if ((rc = open_card (ctrl, NULL)))
     return rc;
@@ -861,7 +974,7 @@ cmd_pkdecrypt (assuan_context_t ctx, char *line)
 static int
 cmd_getattr (assuan_context_t ctx, char *line)
 {
-  CTRL ctrl = assuan_get_pointer (ctx);
+  ctrl_t ctrl = assuan_get_pointer (ctx);
   int rc;
   char *keyword;
 
@@ -900,14 +1013,15 @@ cmd_getattr (assuan_context_t ctx, char *line)
 static int
 cmd_setattr (assuan_context_t ctx, char *orig_line)
 {
-  CTRL ctrl = assuan_get_pointer (ctx);
+  ctrl_t ctrl = assuan_get_pointer (ctx);
   int rc;
   char *keyword;
   int keywordlen;
   size_t nbytes;
   char *line, *linebuf;
 
-  /* FIXME: If we are locked return an error.  */
+  if ( IS_LOCKED (ctrl) )
+    return gpg_error (GPG_ERR_EBUSY);
 
   if ((rc = open_card (ctrl, NULL)))
     return rc;
@@ -956,12 +1070,13 @@ cmd_setattr (assuan_context_t ctx, char *orig_line)
 static int
 cmd_genkey (assuan_context_t ctx, char *line)
 {
-  CTRL ctrl = assuan_get_pointer (ctx);
+  ctrl_t ctrl = assuan_get_pointer (ctx);
   int rc;
   char *keyno;
   int force = has_option (line, "--force");
 
-  /* FIXME: If we are locked return an error.  */
+  if ( IS_LOCKED (ctrl) )
+    return gpg_error (GPG_ERR_EBUSY);
 
   /* Skip over options. */
   while ( *line == '-' && line[1] == '-' )
@@ -1004,7 +1119,7 @@ cmd_genkey (assuan_context_t ctx, char *line)
 static int
 cmd_random (assuan_context_t ctx, char *line)
 {
-  CTRL ctrl = assuan_get_pointer (ctx);
+  ctrl_t ctrl = assuan_get_pointer (ctx);
   int rc;
   size_t nbytes;
   unsigned char *buffer;
@@ -1044,12 +1159,13 @@ cmd_random (assuan_context_t ctx, char *line)
 static int
 cmd_passwd (assuan_context_t ctx, char *line)
 {
-  CTRL ctrl = assuan_get_pointer (ctx);
+  ctrl_t ctrl = assuan_get_pointer (ctx);
   int rc;
   char *chvnostr;
   int reset_mode = has_option (line, "--reset");
 
-  /* FIXME: If we are locked return an error.  */
+  if ( IS_LOCKED (ctrl) )
+    return gpg_error (GPG_ERR_EBUSY);
 
   /* Skip over options. */
   while (*line == '-' && line[1] == '-')
@@ -1091,11 +1207,12 @@ cmd_passwd (assuan_context_t ctx, char *line)
 static int
 cmd_checkpin (assuan_context_t ctx, char *line)
 {
-  CTRL ctrl = assuan_get_pointer (ctx);
+  ctrl_t ctrl = assuan_get_pointer (ctx);
   int rc;
   char *keyidstr;
 
-  /* FIXME: If we are locked return an error.  */
+  if ( IS_LOCKED (ctrl) )
+    return gpg_error (GPG_ERR_EBUSY);
 
   if ((rc = open_card (ctrl, NULL)))
     return rc;
@@ -1122,15 +1239,83 @@ cmd_checkpin (assuan_context_t ctx, char *line)
 }
 
 
+/* LOCK [--wait]
+
+   Grant exclusive card access to this session.  Note that there is
+   no lock counter used and a second lock from the same session will
+   get ignore.  A single unlock (or RESET) unlocks the session.
+   Return GPG_ERR_EBUSY if another session has locked the reader.
+
+   If the option --wait is given the command will wait until a
+   lock has been released.
+ */
+static int
+cmd_lock (assuan_context_t ctx, char *line)
+{
+  ctrl_t ctrl = assuan_get_pointer (ctx);
+  int rc = 0;
+
+ retry:
+  if (locked_session)
+    {
+      if (locked_session != ctrl->server_local)
+        rc = gpg_error (GPG_ERR_EBUSY);
+    }
+  else
+    locked_session = ctrl->server_local;
+
+#ifdef USE_GNU_PTH
+  if (rc && has_option (line, "--wait"))
+    {
+      pth_sleep (1); /* Better implement an event mechanism. However,
+                        for card operations this should be
+                        sufficient. */
+      goto retry;
+    }
+#endif /*USE_GNU_PTH*/
+  
+  if (rc)
+    log_error ("cmd_lock failed: %s\n", gpg_strerror (rc));
+  return map_to_assuan_status (rc);
+}
+
+
+/* UNLOCK
+
+   Release exclusive card access.
+ */
+static int
+cmd_unlock (assuan_context_t ctx, char *line)
+{
+  ctrl_t ctrl = assuan_get_pointer (ctx);
+  int rc = 0;
+
+  if (locked_session)
+    {
+      if (locked_session != ctrl->server_local)
+        rc = gpg_error (GPG_ERR_EBUSY);
+      else
+        locked_session = NULL;
+    }
+  else
+    rc = gpg_error (GPG_ERR_NOT_LOCKED);
+
+  if (rc)
+    log_error ("cmd_unlock failed: %s\n", gpg_strerror (rc));
+  return map_to_assuan_status (rc);
+}
+
+
+
 
 \f
 /* Tell the assuan library about our commands */
 static int
-register_commands (ASSUAN_CONTEXT ctx)
+register_commands (assuan_context_t ctx)
 {
   static struct {
     const char *name;
-    int (*handler)(ASSUAN_CONTEXT, char *line);
+    int (*handler)(assuan_context_t, char *line);
   } table[] = {
     { "SERIALNO",     cmd_serialno },
     { "LEARN",        cmd_learn },
@@ -1148,6 +1333,8 @@ register_commands (ASSUAN_CONTEXT ctx)
     { "RANDOM",       cmd_random },
     { "PASSWD",       cmd_passwd },
     { "CHECKPIN",     cmd_checkpin },
+    { "LOCK",         cmd_lock },
+    { "UNLOCK",       cmd_unlock },
     { NULL }
   };
   int i, rc;
@@ -1172,7 +1359,7 @@ void
 scd_command_handler (int listen_fd)
 {
   int rc;
-  ASSUAN_CONTEXT ctx;
+  assuan_context_t ctx;
   struct server_control_s ctrl;
 
   memset (&ctrl, 0, sizeof ctrl);
@@ -1204,20 +1391,24 @@ scd_command_handler (int listen_fd)
       scd_exit (2);
     }
   assuan_set_pointer (ctx, &ctrl);
+
+  /* Allocate and initialize the server object.  Put it into the list
+     of active sessions. */
   ctrl.server_local = xcalloc (1, sizeof *ctrl.server_local);
+  ctrl.server_local->next_session = session_list;
+  session_list = ctrl.server_local;
+  ctrl.server_local->ctrl_backlink = &ctrl;
   ctrl.server_local->assuan_ctx = ctx;
 
   if (DBG_ASSUAN)
     assuan_set_log_stream (ctx, log_get_stream ());
 
-  /* Store the primary connection's assuan context. */
-  if (!primary_connection)
-    primary_connection = &ctrl;
-
   /* We open the reader right at startup so that the ticker is able to
      update the status file. */
   if (ctrl.reader_slot == -1)
-    ctrl.reader_slot = apdu_open_reader (opt.reader_port);
+    {
+      ctrl.reader_slot = get_reader_slot ();
+    }
 
   /* Command processing loop. */
   for (;;)
@@ -1241,13 +1432,26 @@ scd_command_handler (int listen_fd)
         }
     }
 
-  /* The next client will be the primary conenction if this one
-     terminates. */
-  if (primary_connection == &ctrl)
-    primary_connection = NULL;
+  /* Cleanup.  */
+  do_reset (&ctrl, 1); 
 
-  do_reset (&ctrl, 1); /* Cleanup. */
+  /* Release the server object.  */
+  if (session_list == ctrl.server_local)
+    session_list = ctrl.server_local->next_session;
+  else
+    {
+      struct server_local_s *sl;
+      
+      for (sl=session_list; sl->next_session; sl = sl->next_session)
+        if (sl->next_session == ctrl.server_local)
+          break;
+      if (!sl->next_session)
+          BUG ();
+      sl->next_session = ctrl.server_local->next_session;
+    }
+  xfree (ctrl.server_local);
 
+  /* Release the Assuan context.  */
   assuan_deinit_server (ctx);
 }
 
@@ -1256,14 +1460,14 @@ scd_command_handler (int listen_fd)
    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;
   size_t valuelen;
   char buf[950], *p;
   size_t n;
-  ASSUAN_CONTEXT ctx = ctrl->server_local->assuan_ctx;
+  assuan_context_t ctx = ctrl->server_local->assuan_ctx;
   
   va_start (arg_ptr, keyword);
 
@@ -1299,7 +1503,7 @@ send_status_info (CTRL ctrl, const char *keyword, ...)
 }
 
 
-/* This fucntion is called by the ticker thread to check for changes
+/* This function is called by the ticker thread to check for changes
    of the reader stati.  It updates the reader status files and if
    requested by the caller also send a signal to the caller.  */
 void
@@ -1328,6 +1532,7 @@ scd_update_reader_status_file (void)
             char *fname;
             char templ[50];
             FILE *fp;
+            struct server_local_s *sl;
 
             log_info ("updating status of slot %d to 0x%04X\n", slot, status);
             
@@ -1344,33 +1549,31 @@ scd_update_reader_status_file (void)
               }
             xfree (fname);
 
-            /* Set the card removed flag.  We will set this on any
-               card change because a reset or SERIALNO request must be
-               done in any case.  */
-            if (primary_connection && primary_connection->server_local
-                && last[slot].any )
-              primary_connection->server_local->card_removed = 1;
+            /* Set the card removed flag for all current sessions.  We
+               will set this on any card change because a reset or
+               SERIALNO request must be done in any case.  */
+            if (last[slot].any)
+              update_card_removed (slot, 1);
 
             last[slot].any = 1;
             last[slot].status = status;
             last[slot].changed = changed;
 
 
-            /* Send a signal to the primary client, if any.  */
-            if (primary_connection && primary_connection->server_local
-                && primary_connection->server_local->assuan_ctx)
-              {
-                pid_t pid = assuan_get_pid (primary_connection
-                                            ->server_local->assuan_ctx);
-                int signo = primary_connection->server_local->event_signal;
-
-                log_info ("client pid is %d, sending signal %d\n", pid, signo);
+            /* Send a signal to all clients who applied for it.  */
+            for (sl=session_list; sl; sl = sl->next_session)
+              if (sl->event_signal && sl->assuan_ctx)
+                {
+                  pid_t pid = assuan_get_pid (sl->assuan_ctx);
+                  int signo = sl->event_signal;
 
+                  log_info ("client pid is %d, sending signal %d\n",
+                            pid, signo);
 #ifndef HAVE_W32_SYSTEM
-                if (pid != (pid_t)(-1) && pid && signo > 0)
-                  kill (pid, signo);
+                  if (pid != (pid_t)(-1) && pid && signo > 0)
+                    kill (pid, signo);
 #endif
-              }
+                }
           }
       }
 }