* trustlist.c (read_trustfiles): Take a missing trustlist as an
[gnupg.git] / agent / call-scd.c
index 617ef0d..53e4ced 100644 (file)
@@ -15,7 +15,8 @@
  *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
  */
 
 #include <config.h>
@@ -26,6 +27,7 @@
 #include <ctype.h>
 #include <assert.h>
 #include <unistd.h>
+#include <signal.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #ifndef HAVE_W32_SYSTEM
 /* Definition of module local data of the CTRL structure.  */
 struct scd_local_s
 {
+  /* We keep a list of all allocated context with a an achnor at
+     SCD_LOCAL_LIST (see below). */
+  struct scd_local_s *next_local;
+
+  /* We need to get back to the ctrl object actually referencing this
+     structure.  This is really an awkward way of enumerint the lcoal
+     contects.  A much cleaner way would be to keep a global list of
+     ctrl objects to enumerate them.  */
+  ctrl_t ctrl_backlink;
+
   assuan_context_t ctx; /* NULL or session context for the SCdaemon
                            used with this connection. */
   int locked;           /* This flag is used to assert proper use of
@@ -72,6 +84,10 @@ struct inq_needpin_s
 };
 
 
+/* To keep track of all active SCD contexts, we keep a linked list
+   anchored at this variable. */
+static struct scd_local_s *scd_local_list;
+
 /* A Mutex used inside the start_scd function. */
 static pth_mutex_t start_scd_lock;
 
@@ -116,6 +132,35 @@ initialize_module_call_scd (void)
 }
 
 
+static void
+dump_mutex_state (pth_mutex_t *m)
+{
+  if (!(m->mx_state & PTH_MUTEX_INITIALIZED))
+    log_printf ("not_initialized");
+  else if (!(m->mx_state & PTH_MUTEX_LOCKED))
+    log_printf ("not_locked");
+  else
+    log_printf ("locked tid=0x%lx count=%lu", (long)m->mx_owner, m->mx_count);
+}
+
+
+/* This function may be called to print infromation pertaining to the
+   current state of this module to the log. */
+void
+agent_scd_dump_state (void)
+{
+  log_info ("agent_scd_dump_state: scd_lock=");
+  dump_mutex_state (&start_scd_lock);
+  log_printf ("\n");
+  log_info ("agent_scd_dump_state: primary_scd_ctx=%p pid=%ld reusable=%d\n",
+            primary_scd_ctx, 
+            (long)assuan_get_pid (primary_scd_ctx),
+            primary_scd_ctx_reusable);
+  if (socket_name)
+    log_info ("agent_scd_dump_state: socket=`%s'\n", socket_name);
+}
+
+
 /* The unlock_scd function shall be called after having accessed the
    SCD.  It is currently not very useful but gives an opportunity to
    keep track of connections currently calling SCD.  Note that the
@@ -149,7 +194,7 @@ atfork_cb (void *opaque, int where)
 
 /* Fork off the SCdaemon if this has not already been done.  Lock the
    daemon and make sure that a proper context has been setup in CTRL.
-   Thsi fucntion might also lock the daemon, which means that the
+   This function might also lock the daemon, which means that the
    caller must call unlock_scd after this fucntion has returned
    success and the actual Assuan transaction been done. */
 static int
@@ -172,7 +217,10 @@ start_scd (ctrl_t ctrl)
     {
       ctrl->scd_local = xtrycalloc (1, sizeof *ctrl->scd_local);
       if (!ctrl->scd_local)
-        return gpg_error_from_errno (errno);
+        return gpg_error_from_syserror ();
+      ctrl->scd_local->ctrl_backlink = ctrl;
+      ctrl->scd_local->next_local = scd_local_list;
+      scd_local_list = ctrl->scd_local;
     }
 
 
@@ -187,7 +235,7 @@ start_scd (ctrl_t ctrl)
 
   if (ctrl->scd_local->ctx)
     return 0; /* Okay, the context is fine.  We used to test for an
-                 alive context here and do an disconnect.  How that we
+                 alive context here and do an disconnect.  Now that we
                  have a ticker function to check for it, it is easier
                  not to check here but to let the connection run on an
                  error instead. */
@@ -219,7 +267,7 @@ start_scd (ctrl_t ctrl)
       if (rc)
         {
           log_error ("can't connect to socket `%s': %s\n",
-                     socket_name, assuan_strerror (rc));
+                     socket_name, gpg_strerror (rc));
           err = gpg_error (GPG_ERR_NO_SCDAEMON);
           goto leave;
         }
@@ -268,12 +316,12 @@ start_scd (ctrl_t ctrl)
   no_close_list[i] = -1;
 
   /* Connect to the pinentry and perform initial handshaking */
-  rc = assuan_pipe_connect2 (&ctx, opt.scdaemon_program, (char**)argv,
-                             no_close_list, atfork_cb, NULL);
+  rc = assuan_pipe_connect_ext (&ctx, opt.scdaemon_program, argv,
+                                no_close_list, atfork_cb, NULL, 0);
   if (rc)
     {
       log_error ("can't connect to the SCdaemon: %s\n",
-                 assuan_strerror (rc));
+                 gpg_strerror (rc));
       err = gpg_error (GPG_ERR_NO_SCDAEMON);
       goto leave;
     }
@@ -343,36 +391,62 @@ start_scd (ctrl_t ctrl)
 void
 agent_scd_check_aliveness (void)
 {
+  pth_event_t evt;
   pid_t pid;
   int rc;
 
-  /* We can do so only if there is no more active primary connection.
-     With an active primary connection, this is all no problem because
-     with the end of gpg-agent's session a disconnect is send and the
-     this function will be used at a later time. */
-  if (!primary_scd_ctx || !primary_scd_ctx_reusable)
-    return;
+  if (!primary_scd_ctx)
+    return; /* No scdaemon running. */
 
-  if (!pth_mutex_acquire (&start_scd_lock, 0, NULL))
+  /* This is not a critical function so we use a short timeout while
+     acquiring the lock.  */
+  evt = pth_event (PTH_EVENT_TIME, pth_timeout (1, 0));
+  if (!pth_mutex_acquire (&start_scd_lock, 0, evt))
     {
-      log_error ("failed to acquire the start_scd lock while"
-                 " doing an aliveness check: %s\n",
-                 strerror (errno));
+      if (pth_event_occurred (evt))
+        {
+          if (opt.verbose > 1)
+            log_info ("failed to acquire the start_scd lock while"
+                      " doing an aliveness check: %s\n", "timeout");
+        }
+      else
+        log_error ("failed to acquire the start_scd lock while"
+                   " doing an aliveness check: %s\n", strerror (errno));
+      pth_event_free (evt, PTH_FREE_THIS);
       return;
     }
+  pth_event_free (evt, PTH_FREE_THIS);
 
-  if (primary_scd_ctx && primary_scd_ctx_reusable)
+  if (primary_scd_ctx)
     {
       pid = assuan_get_pid (primary_scd_ctx);
       if (pid != (pid_t)(-1) && pid
           && ((rc=waitpid (pid, NULL, WNOHANG))==-1 || (rc == pid)) )
         {
-          /* Okay, scdaemon died.  Disconnect the primary connection now
-             but take care that it won't do another wait. */
+          /* Okay, scdaemon died.  Disconnect the primary connection
+             now but take care that it won't do another wait. Also
+             cleanup all other connections and release their
+             resources.  The next use will start a new daemon then.
+             Due to the use of the START_SCD_LOCAL we are sure that
+             none of these context are actually in use. */
+          struct scd_local_s *sl;
+
           assuan_set_flag (primary_scd_ctx, ASSUAN_NO_WAITPID, 1);
           assuan_disconnect (primary_scd_ctx);
+
+          for (sl=scd_local_list; sl; sl = sl->next_local)
+            {
+              if (sl->ctx)
+                {
+                  if (sl->ctx != primary_scd_ctx)
+                    assuan_disconnect (sl->ctx);
+                  sl->ctx = NULL;
+                }
+            }
+          
           primary_scd_ctx = NULL;
           primary_scd_ctx_reusable = 0;
+
           xfree (socket_name);
           socket_name = NULL;
         }
@@ -384,7 +458,9 @@ agent_scd_check_aliveness (void)
 }
 
 
-/* Reset the SCD if it has been used. */
+
+/* Reset the SCD if it has been used.  Actually it is not a reset but
+   a cleanup of resources used by the current connection. */
 int
 agent_reset_scd (ctrl_t ctrl)
 {
@@ -398,16 +474,40 @@ agent_reset_scd (ctrl_t ctrl)
              reuse. */
           if (ctrl->scd_local->ctx == primary_scd_ctx)
             {
-              /* The RESET may fail for example if the scdaemon has
-                 already been terminated.  We need to set the reusable
-                 flag anyway to make sure that the aliveness check can
-                 clean it up. */
-              assuan_transact (primary_scd_ctx, "RESET",
+              /* Send a RESTART to the SCD.  This is required for the
+                 primary connection as a kind of virtual EOF; we don't
+                 have another way to tell it that the next command
+                 should be viewed as if a new connection has been
+                 made.  For the non-primary connections this is not
+                 needed as we simply close the socket.  We don't check
+                 for an error here because the RESTART may fail for
+                 example if the scdaemon has already been terminated.
+                 Anyway, we need to set the reusable flag to make sure
+                 that the aliveness check can clean it up. */
+              assuan_transact (primary_scd_ctx, "RESTART",
                                NULL, NULL, NULL, NULL, NULL, NULL);
               primary_scd_ctx_reusable = 1;
             }
           else
             assuan_disconnect (ctrl->scd_local->ctx);
+          ctrl->scd_local->ctx = NULL;
+        }
+      
+      /* Remove the local context from our list and release it. */
+      if (!scd_local_list)
+        BUG ();
+      else if (scd_local_list == ctrl->scd_local)
+        scd_local_list = ctrl->scd_local->next_local;
+      else
+        {
+          struct scd_local_s *sl;
+      
+          for (sl=scd_local_list; sl->next_local; sl = sl->next_local)
+            if (sl->next_local == ctrl->scd_local)
+              break;
+          if (!sl->next_local)
+            BUG ();
+          sl->next_local = ctrl->scd_local->next_local;
         }
       xfree (ctrl->scd_local);
       ctrl->scd_local = NULL;
@@ -427,7 +527,7 @@ unescape_status_string (const unsigned char *s)
 {
   char *buffer, *d;
 
-  buffer = d = xtrymalloc (strlen (s)+1);
+  buffer = d = xtrymalloc (strlen ((const char*)s)+1);
   if (!buffer)
     return NULL;
   while (*s)
@@ -455,7 +555,7 @@ unescape_status_string (const unsigned char *s)
 
 
 \f
-static AssuanError
+static int
 learn_status_cb (void *opaque, const char *line)
 {
   struct learn_parm_s *parm = opaque;
@@ -511,14 +611,14 @@ agent_card_learn (ctrl_t ctrl,
                         NULL, NULL, NULL, NULL,
                         learn_status_cb, &parm);
   if (rc)
-    return unlock_scd (ctrl, map_assuan_err (rc));
+    return unlock_scd (ctrl, rc);
 
   return unlock_scd (ctrl, 0);
 }
 
 
 \f
-static AssuanError
+static int
 get_serialno_cb (void *opaque, const char *line)
 {
   char **serialno = opaque;
@@ -534,14 +634,14 @@ get_serialno_cb (void *opaque, const char *line)
   if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
     {
       if (*serialno)
-        return ASSUAN_Unexpected_Status;
+        return gpg_error (GPG_ERR_CONFLICT); /* Unexpected status line. */
       for (n=0,s=line; hexdigitp (s); s++, n++)
         ;
       if (!n || (n&1)|| !(spacep (s) || !*s) )
-        return ASSUAN_Invalid_Status;
+        return gpg_error (GPG_ERR_ASS_PARAMETER);
       *serialno = xtrymalloc (n+1);
       if (!*serialno)
-        return ASSUAN_Out_Of_Core;
+        return out_of_core ();
       memcpy (*serialno, line, n);
       (*serialno)[n] = 0;
     }
@@ -567,7 +667,7 @@ agent_card_serialno (ctrl_t ctrl, char **r_serialno)
   if (rc)
     {
       xfree (serialno);
-      return unlock_scd (ctrl, map_assuan_err (rc));
+      return unlock_scd (ctrl, rc);
     }
   *r_serialno = serialno;
   return unlock_scd (ctrl, 0);
@@ -576,7 +676,7 @@ agent_card_serialno (ctrl_t ctrl, char **r_serialno)
 
 
 \f
-static AssuanError
+static int
 membuf_data_cb (void *opaque, const void *buffer, size_t length)
 {
   membuf_t *data = opaque;
@@ -587,7 +687,7 @@ membuf_data_cb (void *opaque, const void *buffer, size_t length)
 }
   
 /* Handle the NEEDPIN inquiry. */
-static AssuanError
+static int
 inq_needpin (void *opaque, const char *line)
 {
   struct inq_needpin_s *parm = opaque;
@@ -595,24 +695,41 @@ inq_needpin (void *opaque, const char *line)
   size_t pinlen;
   int rc;
 
-  if (!(!strncmp (line, "NEEDPIN", 7) && (line[7] == ' ' || !line[7])))
+  if (!strncmp (line, "NEEDPIN", 7) && (line[7] == ' ' || !line[7]))
+    {
+      line += 7;
+      while (*line == ' ')
+        line++;
+      
+      pinlen = 90;
+      pin = gcry_malloc_secure (pinlen);
+      if (!pin)
+        return out_of_core ();
+
+      rc = parm->getpin_cb (parm->getpin_cb_arg, line, pin, pinlen);
+      if (!rc)
+        rc = assuan_send_data (parm->ctx, pin, pinlen);
+      xfree (pin);
+    }
+  else if (!strncmp (line, "POPUPKEYPADPROMPT", 17)
+           && (line[17] == ' ' || !line[17]))
+    {
+      line += 17;
+      while (*line == ' ')
+        line++;
+      
+      rc = parm->getpin_cb (parm->getpin_cb_arg, line, NULL, 1);
+    }
+  else if (!strncmp (line, "DISMISSKEYPADPROMPT", 19)
+           && (line[19] == ' ' || !line[19]))
+    {
+      rc = parm->getpin_cb (parm->getpin_cb_arg, "", NULL, 0);
+    }
+  else
     {
       log_error ("unsupported inquiry `%s'\n", line);
-      return ASSUAN_Inquire_Unknown;
+      rc = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE);
     }
-  line += 7;
-
-  pinlen = 90;
-  pin = gcry_malloc_secure (pinlen);
-  if (!pin)
-    return ASSUAN_Out_Of_Core;
-
-  rc = parm->getpin_cb (parm->getpin_cb_arg, line, pin, pinlen);
-  if (rc)
-    rc = ASSUAN_Canceled;
-  if (!rc)
-    rc = assuan_send_data (parm->ctx, pin, pinlen);
-  xfree (pin);
 
   return rc;
 }
@@ -626,7 +743,7 @@ agent_card_pksign (ctrl_t ctrl,
                    int (*getpin_cb)(void *, const char *, char*, size_t),
                    void *getpin_cb_arg,
                    const unsigned char *indata, size_t indatalen,
-                   char **r_buf, size_t *r_buflen)
+                   unsigned char **r_buf, size_t *r_buflen)
 {
   int rc, i;
   char *p, line[ASSUAN_LINELENGTH];
@@ -651,7 +768,7 @@ agent_card_pksign (ctrl_t ctrl,
   rc = assuan_transact (ctrl->scd_local->ctx, line,
                         NULL, NULL, NULL, NULL, NULL, NULL);
   if (rc)
-    return unlock_scd (ctrl, map_assuan_err (rc));
+    return unlock_scd (ctrl, rc);
 
   init_membuf (&data, 1024);
   inqparm.ctx = ctrl->scd_local->ctx;
@@ -667,21 +784,18 @@ agent_card_pksign (ctrl_t ctrl,
   if (rc)
     {
       xfree (get_membuf (&data, &len));
-      return unlock_scd (ctrl, map_assuan_err (rc));
+      return unlock_scd (ctrl, rc);
     }
   sigbuf = get_membuf (&data, &sigbuflen);
 
   /* Create an S-expression from it which is formatted like this:
      "(7:sig-val(3:rsa(1:sSIGBUFLEN:SIGBUF)))" */
   *r_buflen = 21 + 11 + sigbuflen + 4;
-  *r_buf = xtrymalloc (*r_buflen);
-  if (!*r_buf)
-    {
-      gpg_error_t tmperr = out_of_core ();
-      xfree (*r_buf);
-      return unlock_scd (ctrl, tmperr);
-    }
-  p = stpcpy (*r_buf, "(7:sig-val(3:rsa(1:s" );
+  p = xtrymalloc (*r_buflen);
+  *r_buf = (unsigned char*)p;
+  if (!p)
+    return unlock_scd (ctrl, out_of_core ());
+  p = stpcpy (p, "(7:sig-val(3:rsa(1:s" );
   sprintf (p, "%u:", (unsigned int)sigbuflen);
   p += strlen (p);
   memcpy (p, sigbuf, sigbuflen);
@@ -724,7 +838,7 @@ agent_card_pkdecrypt (ctrl_t ctrl,
   rc = assuan_transact (ctrl->scd_local->ctx, line,
                         NULL, NULL, NULL, NULL, NULL, NULL);
   if (rc)
-    return unlock_scd (ctrl, map_assuan_err (rc));
+    return unlock_scd (ctrl, rc);
 
   init_membuf (&data, 1024);
   inqparm.ctx = ctrl->scd_local->ctx;
@@ -739,7 +853,7 @@ agent_card_pkdecrypt (ctrl_t ctrl,
   if (rc)
     {
       xfree (get_membuf (&data, &len));
-      return unlock_scd (ctrl, map_assuan_err (rc));
+      return unlock_scd (ctrl, rc);
     }
   *r_buf = get_membuf (&data, r_buflen);
   if (!*r_buf)
@@ -775,7 +889,7 @@ agent_card_readcert (ctrl_t ctrl,
   if (rc)
     {
       xfree (get_membuf (&data, &len));
-      return unlock_scd (ctrl, map_assuan_err (rc));
+      return unlock_scd (ctrl, rc);
     }
   *r_buf = get_membuf (&data, r_buflen);
   if (!*r_buf)
@@ -811,7 +925,7 @@ agent_card_readkey (ctrl_t ctrl, const char *id, unsigned char **r_buf)
   if (rc)
     {
       xfree (get_membuf (&data, &len));
-      return unlock_scd (ctrl, map_assuan_err (rc));
+      return unlock_scd (ctrl, rc);
     }
   *r_buf = get_membuf (&data, &buflen);
   if (!*r_buf)
@@ -855,7 +969,7 @@ card_getattr_cb (void *opaque, const char *line)
   if (keywordlen == parm->keywordlen
       && !memcmp (keyword, parm->keyword, keywordlen))
     {
-      parm->data = unescape_status_string (line);
+      parm->data = unescape_status_string ((const unsigned char*)line);
       if (!parm->data)
         parm->error = errno;
     }
@@ -893,9 +1007,9 @@ agent_card_getattr (ctrl_t ctrl, const char *name, char **result)
   if (err)
     return err;
 
-  err = map_assuan_err (assuan_transact (ctrl->scd_local->ctx, line,
-                                         NULL, NULL, NULL, NULL,
-                                         card_getattr_cb, &parm));
+  err = assuan_transact (ctrl->scd_local->ctx, line,
+                         NULL, NULL, NULL, NULL,
+                         card_getattr_cb, &parm);
   if (!err && parm.error)
     err = gpg_error_from_errno (parm.error);
   
@@ -913,10 +1027,10 @@ agent_card_getattr (ctrl_t ctrl, const char *name, char **result)
 
 
 \f
-static AssuanError
+static int
 pass_status_thru (void *opaque, const char *line)
 {
-  ASSUAN_CONTEXT ctx = opaque;
+  assuan_context_t ctx = opaque;
   char keyword[200];
   int i;
 
@@ -933,10 +1047,10 @@ pass_status_thru (void *opaque, const char *line)
   return 0;
 }
 
-static AssuanError
+static int
 pass_data_thru (void *opaque, const void *buffer, size_t length)
 {
-  ASSUAN_CONTEXT ctx = opaque;
+  assuan_context_t ctx = opaque;
 
   assuan_send_data (ctx, buffer, length);
   return 0;
@@ -968,7 +1082,7 @@ agent_card_scd (ctrl_t ctrl, const char *cmdline,
                         pass_status_thru, assuan_context);
   if (rc)
     {
-      return unlock_scd (ctrl, map_assuan_err (rc));
+      return unlock_scd (ctrl, rc);
     }
 
   return unlock_scd (ctrl, 0);