* trustlist.c (read_trustfiles): Take a missing trustlist as an
[gnupg.git] / agent / call-scd.c
index fc81e2f..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;
     }
 
 
@@ -185,26 +233,15 @@ start_scd (ctrl_t ctrl)
     }
   ctrl->scd_local->locked++;
 
-  /* If we already have a context, we better do a sanity check now to
-     see whether it has accidently died.  This avoids annoying
-     timeouts and hung connections. */
   if (ctrl->scd_local->ctx)
-    {
-      pid_t pid;
-#ifndef HAVE_W32_SYSTEM 
-      pid = assuan_get_pid (ctrl->scd_local->ctx);
-      if (pid != (pid_t)(-1) && pid
-          && ((rc=waitpid (pid, NULL, WNOHANG))==-1 || (rc == pid)) )
-        {
-          assuan_disconnect (ctrl->scd_local->ctx);
-          ctrl->scd_local->ctx = NULL;
-        }
-      else
-#endif
-        return 0; /* Okay, the context is fine. */
-    }
+    return 0; /* Okay, the context is fine.  We used to test for an
+                 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. */
+
 
-  /* We need to protect the lowwing code. */
+  /* We need to protect the following code. */
   if (!pth_mutex_acquire (&start_scd_lock, 0, NULL))
     {
       log_error ("failed to acquire the start_scd lock: %s\n",
@@ -230,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;
         }
@@ -279,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;
     }
@@ -350,8 +387,80 @@ start_scd (ctrl_t ctrl)
 }
 
 
+/* Check whether the Scdaemon is still alive and clean it up if not. */
+void
+agent_scd_check_aliveness (void)
+{
+  pth_event_t evt;
+  pid_t pid;
+  int rc;
+
+  if (!primary_scd_ctx)
+    return; /* No scdaemon running. */
+
+  /* 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))
+    {
+      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)
+    {
+      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. 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;
+        }
+    }
+
+  if (!pth_mutex_release (&start_scd_lock))
+    log_error ("failed to release the start_scd lock while"
+               " doing the aliveness check: %s\n", strerror (errno));
+}
+
+
 
-/* 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)
 {
@@ -359,18 +468,46 @@ agent_reset_scd (ctrl_t ctrl)
     {
       if (ctrl->scd_local->ctx)
         {
-          /* We can't disconnect the primary context becuase libassuan
+          /* We can't disconnect the primary context because libassuan
              does a waitpid on it and thus the system would hang.
              Instead we send a reset and keep that connection for
              reuse. */
           if (ctrl->scd_local->ctx == primary_scd_ctx)
             {
-              if (!assuan_transact (primary_scd_ctx, "RESET",
-                                    NULL, NULL, NULL, NULL, NULL, NULL))
-                primary_scd_ctx_reusable = 1;
+              /* 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;
@@ -390,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)
@@ -418,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;
@@ -474,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;
@@ -497,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;
     }
@@ -530,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);
@@ -539,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;
@@ -550,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;
@@ -558,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;
 }
@@ -589,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];
@@ -614,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;
@@ -630,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);
@@ -687,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;
@@ -702,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)
@@ -738,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)
@@ -774,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)
@@ -818,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;
     }
@@ -856,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);
   
@@ -876,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;
 
@@ -896,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;
@@ -931,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);