Enhanced last patch.
[gnupg.git] / agent / gpg-agent.c
index 5ac951c..6cb0840 100644 (file)
@@ -1,6 +1,6 @@
 /* gpg-agent.c  -  The GnuPG Agent
  * Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005,
- *               2006, 2007 Free Software Foundation, Inc.
+ *               2006, 2007, 2009 Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
@@ -141,6 +141,9 @@ static ARGPARSE_OPTS opts[] = {
   { oDisableScdaemon, "disable-scdaemon", 0, N_("do not use the SCdaemon") },
   { oFakedSystemTime, "faked-system-time", 2, "@" }, /* (epoch time) */
 
+  { oBatch,      "batch",       0, "@" },
+  { oHomedir,    "homedir",     2, "@"},   
+
   { oDisplay,    "display",     2, "@" },
   { oTTYname,    "ttyname",     2, "@" },
   { oTTYtype,    "ttytype",     2, "@" },
@@ -194,9 +197,14 @@ static ARGPARSE_OPTS opts[] = {
 #define TIMERTICK_INTERVAL    (2)    /* Seconds.  */
 #endif
 
-/* flag to indicate that a shutdown was requested */
+/* Flag to indicate that a shutdown was requested.  */
 static int shutdown_pending;
 
+/* Counter for the currently running own socket checks.  */
+static int check_own_socket_running;
+
+/* True if we are listening on the standard socket.  */
+static int use_standard_socket;
 
 /* It is possible that we are currently running under setuid permissions */
 static int maybe_setuid = 1;
@@ -241,10 +249,8 @@ static pid_t parent_pid = (pid_t)(-1);
    Local prototypes. 
  */
 
-static char *create_socket_name (int use_standard_socket,
-                                 char *standard_name, char *template);
-static gnupg_fd_t create_server_socket (int is_standard_name, char *name, 
-                                        int is_ssh, 
+static char *create_socket_name (char *standard_name, char *template);
+static gnupg_fd_t create_server_socket (char *name, int is_ssh, 
                                         assuan_sock_nonce_t *nonce);
 static void create_directories (void);
 
@@ -253,6 +259,7 @@ static void agent_deinit_default_ctrl (ctrl_t ctrl);
 
 static void handle_connections (gnupg_fd_t listen_fd,
                                 gnupg_fd_t listen_fd_ssh);
+static void check_own_socket (void);
 static int check_for_running_agent (int silent, int mode);
 
 /* Pth wrapper function definitions. */
@@ -263,17 +270,43 @@ static int fixed_gcry_pth_init (void)
 }
 
 
+#ifndef PTH_HAVE_PTH_THREAD_ID
+static unsigned long pth_thread_id (void)
+{
+  return (unsigned long)pth_self ();
+}
+#endif
+
 
 \f
 /*
    Functions. 
  */
 
+static char *
+make_libversion (const char *libname, const char *(*getfnc)(const char*))
+{
+  const char *s;
+  char *result;
+  
+  if (maybe_setuid)
+    {
+      gcry_control (GCRYCTL_INIT_SECMEM, 0, 0);  /* Drop setuid. */
+      maybe_setuid = 0;
+    }
+  s = getfnc (NULL);
+  result = xmalloc (strlen (libname) + 1 + strlen (s) + 1);
+  strcpy (stpcpy (stpcpy (result, libname), " "), s);
+  return result;
+}
+
 
 static const char *
 my_strusage (int level)
 {
+  static char *ver_gcry;
   const char *p;
+
   switch (level)
     {
     case 11: p = "gpg-agent (GnuPG)";
@@ -282,6 +315,12 @@ my_strusage (int level)
     case 17: p = PRINTABLE_OS_NAME; break;
     case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n");
       break;
+    case 20:
+      if (!ver_gcry)
+        ver_gcry = make_libversion ("libgcrypt", gcry_check_version);
+      p = ver_gcry;
+      break;
+
     case 1:
     case 40: p =  _("Usage: gpg-agent [options] (-h for help)");
       break;
@@ -487,7 +526,6 @@ main (int argc, char **argv )
   char *logfile = NULL;
   int debug_wait = 0;
   int gpgconf_list = 0;
-  int use_standard_socket = 0;
   gpg_error_t err;
   const char *env_file_name = NULL;
 
@@ -500,9 +538,9 @@ main (int argc, char **argv )
   log_set_prefix ("gpg-agent", JNLIB_LOG_WITH_PREFIX|JNLIB_LOG_WITH_PID); 
 
   /* Make sure that our subsystems are ready.  */
+  i18n_init ();
   init_common_subsystems ();
 
-  i18n_init ();
 
   /* Libgcrypt requires us to register the threading model first.
      Note that this will also do the pth_init. */
@@ -840,6 +878,7 @@ main (int argc, char **argv )
                              |JNLIB_LOG_WITH_TIME
                              |JNLIB_LOG_WITH_PID));
       current_logfile = xstrdup (logfile);
+      assuan_set_assuan_log_stream (log_get_stream ());
     }
 
   /* Make sure that we have a default ttyname. */
@@ -887,19 +926,15 @@ main (int argc, char **argv )
 
 
       /* Create the sockets.  */
-      socket_name = create_socket_name (use_standard_socket,
-                                        "S.gpg-agent",
-                                        "/tmp/gpg-XXXXXX/S.gpg-agent");
+      socket_name = create_socket_name 
+        ("S.gpg-agent", "/tmp/gpg-XXXXXX/S.gpg-agent");
       if (opt.ssh_support)
-       socket_name_ssh = create_socket_name (use_standard_socket, 
-                                            "S.gpg-agent.ssh",
-                                            "/tmp/gpg-XXXXXX/S.gpg-agent.ssh");
+       socket_name_ssh = create_socket_name 
+          ("S.gpg-agent.ssh", "/tmp/gpg-XXXXXX/S.gpg-agent.ssh");
 
-      fd = create_server_socket (use_standard_socket, socket_name, 0,
-                                 &socket_nonce);
+      fd = create_server_socket (socket_name, 0, &socket_nonce);
       if (opt.ssh_support)
-       fd_ssh = create_server_socket (use_standard_socket, socket_name_ssh, 1,
-                                       &socket_nonce_ssh);
+       fd_ssh = create_server_socket (socket_name_ssh, 1, &socket_nonce_ssh);
       else
        fd_ssh = GNUPG_INVALID_FD;
 
@@ -1034,11 +1069,11 @@ main (int argc, char **argv )
                      printf ("%s; export SSH_AGENT_PID;\n", infostr_ssh_pid);
                    }
                 }
-              free (infostr); /* (Note that a vanilla free is here correct.) */
+              xfree (infostr); 
              if (opt.ssh_support)
                {
-                 free (infostr_ssh_sock);
-                 free (infostr_ssh_pid);
+                 xfree (infostr_ssh_sock);
+                 xfree (infostr_ssh_pid);
                }
               exit (0); 
             }
@@ -1132,31 +1167,33 @@ agent_init_default_ctrl (ctrl_t ctrl)
      and the request will fail anyway shortly after this
      initialization. */
   if (ctrl->display)
-    free (ctrl->display);
-  ctrl->display = default_display? strdup (default_display) : NULL;
+    xfree (ctrl->display);
+  ctrl->display = default_display? xtrystrdup (default_display) : NULL;
 
   if (ctrl->ttyname)
-    free (ctrl->ttyname);
-  ctrl->ttyname = default_ttyname? strdup (default_ttyname) : NULL;
+    xfree (ctrl->ttyname);
+  ctrl->ttyname = default_ttyname? xtrystrdup (default_ttyname) : NULL;
 
   if (ctrl->ttytype)
-    free (ctrl->ttytype);
-  ctrl->ttytype = default_ttytype? strdup (default_ttytype) : NULL;
+    xfree (ctrl->ttytype);
+  ctrl->ttytype = default_ttytype? xtrystrdup (default_ttytype) : NULL;
 
   if (ctrl->lc_ctype)
-    free (ctrl->lc_ctype);
-  ctrl->lc_ctype = default_lc_ctype? strdup (default_lc_ctype) : NULL;
+    xfree (ctrl->lc_ctype);
+  ctrl->lc_ctype = default_lc_ctype? xtrystrdup (default_lc_ctype) : NULL;
 
   if (ctrl->lc_messages)
-    free (ctrl->lc_messages);
-  ctrl->lc_messages = default_lc_messages? strdup (default_lc_messages) : NULL;
+    xfree (ctrl->lc_messages);
+  ctrl->lc_messages = default_lc_messages? xtrystrdup (default_lc_messages)
+                                    /**/ : NULL;
 
   if (ctrl->xauthority)
-    free (ctrl->xauthority);
-  ctrl->xauthority = default_xauthority? strdup (default_xauthority) : NULL;
+    xfree (ctrl->xauthority);
+  ctrl->xauthority = default_xauthority? xtrystrdup (default_xauthority)
+                                   /**/: NULL;
 
   if (ctrl->pinentry_user_data)
-    free (ctrl->pinentry_user_data);
+    xfree (ctrl->pinentry_user_data);
   ctrl->pinentry_user_data = NULL;
 }
 
@@ -1165,19 +1202,19 @@ static void
 agent_deinit_default_ctrl (ctrl_t ctrl)
 {
   if (ctrl->display)
-    free (ctrl->display);
+    xfree (ctrl->display);
   if (ctrl->ttyname)
-    free (ctrl->ttyname);
+    xfree (ctrl->ttyname);
   if (ctrl->ttytype)
-    free (ctrl->ttytype);
+    xfree (ctrl->ttytype);
   if (ctrl->lc_ctype)
-    free (ctrl->lc_ctype);
+    xfree (ctrl->lc_ctype);
   if (ctrl->lc_messages)
-    free (ctrl->lc_messages);
+    xfree (ctrl->lc_messages);
   if (ctrl->xauthority)
-    free (ctrl->xauthority);
+    xfree (ctrl->xauthority);
   if (ctrl->pinentry_user_data)
-    free (ctrl->pinentry_user_data);
+    xfree (ctrl->pinentry_user_data);
 }
 
 /* Reread parts of the configuration.  Note, that this function is
@@ -1258,12 +1295,34 @@ get_agent_scd_notify_event (void)
 
   if (!the_event)
     {
+      HANDLE h, h2;
       SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE};
 
-      the_event = CreateEvent ( &sa, FALSE, FALSE, NULL);
-      if (!the_event)
+      /* We need to use manual reset evet object due to the way our
+         w32-pth wait function works: If we would use an automatic
+         reset event we are not able to figure out which handle has
+         been signaled because at the time we single out the signaled
+         handles using WFSO the event has already been reset due to
+         the WFMO.  */
+      h = CreateEvent (&sa, TRUE, FALSE, NULL);
+      if (!h)
         log_error ("can't create scd notify event: %s\n", w32_strerror (-1) );
+      else if (!DuplicateHandle (GetCurrentProcess(), h,
+                                 GetCurrentProcess(), &h2,
+                                 EVENT_MODIFY_STATE|SYNCHRONIZE, TRUE, 0)) 
+        {
+          log_error ("setting syncronize for scd notify event failed: %s\n",
+                     w32_strerror (-1) );
+          CloseHandle (h);
+        }
+      else
+        {
+          CloseHandle (h);
+          the_event = h2;
+        }
     }
+
+  log_debug  ("returning notify handle %p\n", the_event);
   return the_event;
 }
 #endif /*HAVE_W32_SYSTEM*/
@@ -1279,8 +1338,7 @@ get_agent_scd_notify_event (void)
    terminates the process in case of an error.  Returns: Pointer to an
    allocated string with the absolute name of the socket used.  */
 static char *
-create_socket_name (int use_standard_socket,
-                   char *standard_name, char *template)
+create_socket_name (char *standard_name, char *template)
 {
   char *name, *p;
 
@@ -1317,14 +1375,12 @@ create_socket_name (int use_standard_socket,
 
 
 
-/* Create a Unix domain socket with NAME.  IS_STANDARD_NAME indicates
-   whether a non-random socket is used.  Returns the file descriptor
+/* Create a Unix domain socket with NAME.  Returns the file descriptor
    or terminates the process in case of an error.  Not that this
-   function needs to be used for the regular socket first and only then
-   for the ssh socket.  */
+   function needs to be used for the regular socket first and only
+   then for the ssh socket.  */
 static gnupg_fd_t
-create_server_socket (int is_standard_name, char *name, int is_ssh,
-                      assuan_sock_nonce_t *nonce)
+create_server_socket (char *name, int is_ssh, assuan_sock_nonce_t *nonce)
 {
   struct sockaddr_un *serv_addr;
   socklen_t len;
@@ -1351,7 +1407,7 @@ create_server_socket (int is_standard_name, char *name, int is_ssh,
         + strlen (serv_addr->sun_path) + 1);
 
   rc = assuan_sock_bind (fd, (struct sockaddr*) serv_addr, len);
-  if (is_standard_name && rc == -1 && errno == EADDRINUSE)
+  if (use_standard_socket && rc == -1 && errno == EADDRINUSE)
     {
       /* Check whether a gpg-agent is already running on the standard
          socket.  We do this test only if this is not the ssh socket.
@@ -1384,7 +1440,7 @@ create_server_socket (int is_standard_name, char *name, int is_ssh,
                  gpg_strerror (gpg_error_from_errno (errno)));
       
       assuan_sock_close (fd);
-      if (is_standard_name)
+      if (use_standard_socket)
         *name = 0; /* Inhibit removal of the socket by cleanup(). */
       agent_exit (2);
     }
@@ -1498,6 +1554,11 @@ create_directories (void)
 static void
 handle_tick (void)
 {
+  static time_t last_minute;
+
+  if (!last_minute)
+    last_minute = time (NULL);
+
   /* Check whether the scdaemon has died and cleanup in this case. */
   agent_scd_check_aliveness ();
 
@@ -1516,6 +1577,14 @@ handle_tick (void)
         }
     }
 #endif /*HAVE_W32_SYSTEM*/
+  
+  /* Code to be run every minute.  */
+  if (last_minute + 60 <= time (NULL))
+    {
+      check_own_socket ();
+      last_minute = time (NULL);
+    }
+
 }
 
 
@@ -1536,7 +1605,7 @@ static void
 agent_sigusr2_action (void)
 {
   if (opt.verbose)
-    log_info ("SIGUSR2 received - checking smartcard status\n");
+    log_info ("SIGUSR2 received - updating card event counter\n");
   /* Nothing to check right now.  We only increment a counter.  */
   bump_card_eventcounter ();
 }
@@ -1622,12 +1691,12 @@ start_connection_thread (void *arg)
   agent_init_default_ctrl (ctrl);
   if (opt.verbose)
     log_info (_("handler 0x%lx for fd %d started\n"), 
-              (long)pth_self (), FD2INT(ctrl->thread_startup.fd));
+              pth_thread_id (), FD2INT(ctrl->thread_startup.fd));
 
   start_command_handler (ctrl, GNUPG_INVALID_FD, ctrl->thread_startup.fd);
   if (opt.verbose)
     log_info (_("handler 0x%lx for fd %d terminated\n"), 
-              (long)pth_self (), FD2INT(ctrl->thread_startup.fd));
+              pth_thread_id (), FD2INT(ctrl->thread_startup.fd));
   
   agent_deinit_default_ctrl (ctrl);
   xfree (ctrl);
@@ -1647,12 +1716,12 @@ start_connection_thread_ssh (void *arg)
   agent_init_default_ctrl (ctrl);
   if (opt.verbose)
     log_info (_("ssh handler 0x%lx for fd %d started\n"),
-              (long)pth_self (), FD2INT(ctrl->thread_startup.fd));
+              pth_thread_id (), FD2INT(ctrl->thread_startup.fd));
 
   start_command_handler_ssh (ctrl, ctrl->thread_startup.fd);
   if (opt.verbose)
     log_info (_("ssh handler 0x%lx for fd %d terminated\n"),
-              (long)pth_self (), FD2INT(ctrl->thread_startup.fd));
+              pth_thread_id (), FD2INT(ctrl->thread_startup.fd));
   
   agent_deinit_default_ctrl (ctrl);
   xfree (ctrl);
@@ -1682,13 +1751,28 @@ handle_connections (gnupg_fd_t listen_fd, gnupg_fd_t listen_fd_ssh)
 
 #ifndef HAVE_W32_SYSTEM /* fixme */
   /* Make sure that the signals we are going to handle are not blocked
-     and create an event object for them. */
+     and create an event object for them.  We also set the default
+     action to ignore because we use an Pth event to get notified
+     about signals.  This avoids that the default action is taken in
+     case soemthing goes wrong within Pth.  The problem might also be
+     a Pth bug.  */
   sigemptyset (&sigs );
-  sigaddset (&sigs, SIGHUP);
-  sigaddset (&sigs, SIGUSR1);
-  sigaddset (&sigs, SIGUSR2);
-  sigaddset (&sigs, SIGINT);
-  sigaddset (&sigs, SIGTERM);
+  {
+    static const int mysigs[] = { SIGHUP, SIGUSR1, SIGUSR2, SIGINT, SIGTERM };
+    struct sigaction sa;
+    int i;
+
+    for (i=0; i < DIM (mysigs); i++)
+      {
+        sigemptyset (&sa.sa_mask);
+        sa.sa_handler = SIG_IGN;
+        sa.sa_flags = 0;
+        sigaction (mysigs[i], &sa, NULL);
+        
+        sigaddset (&sigs, mysigs[i]);
+      }
+  }
+
   pth_sigmask (SIG_UNBLOCK, &sigs, NULL);
   ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo);
 #else
@@ -1716,26 +1800,34 @@ handle_connections (gnupg_fd_t listen_fd, gnupg_fd_t listen_fd_ssh)
 
   for (;;)
     {
-      sigset_t oldsigs;
+      /* Make sure that our signals are not blocked.  */
+      pth_sigmask (SIG_UNBLOCK, &sigs, NULL);
 
+      /* Shutdown test.  */
       if (shutdown_pending)
         {
           if (pth_ctrl (PTH_CTRL_GETTHREADS) == 1)
             break; /* ready */
 
-          /* Do not accept anymore connections and wait for existing
-             connections to terminate */
-          signo = 0;
-          pth_wait (ev);
-          if (pth_event_occurred (ev) && signo)
-            handle_signal (signo);
-          continue;
+          /* Do not accept new connections but keep on running the
+             loop to cope with the timer events.  */
+          FD_ZERO (&fdset);
        }
 
-      /* Create a timeout event if needed. */
+      /* Create a timeout event if needed.  To help with power saving
+         we syncronize the ticks to the next full second.  */
       if (!time_ev)
-        time_ev = pth_event (PTH_EVENT_TIME, 
-                             pth_timeout (TIMERTICK_INTERVAL, 0));
+        {
+          pth_time_t nexttick;
+
+          nexttick = pth_timeout (TIMERTICK_INTERVAL, 0);
+          if (nexttick.tv_usec > 10)  /* Use a 10 usec threshhold.  */
+            {
+              nexttick.tv_sec++;
+              nexttick.tv_usec = 0;
+            }
+          time_ev = pth_event (PTH_EVENT_TIME, nexttick);
+        }
 
       /* POSIX says that fd_set should be implemented as a structure,
          thus a simple assignment is fine to copy the entire set.  */
@@ -1771,7 +1863,7 @@ handle_connections (gnupg_fd_t listen_fd, gnupg_fd_t listen_fd_ssh)
           log_error (_("pth_select failed: %s - waiting 1s\n"),
                      strerror (errno));
           pth_sleep (1);
-         continue;
+          continue;
        }
 
       if (pth_event_occurred (ev))
@@ -1790,13 +1882,13 @@ handle_connections (gnupg_fd_t listen_fd, gnupg_fd_t listen_fd_ssh)
           handle_tick ();
         }
 
-      
+
       /* We now might create new threads and because we don't want any
          signals (as we are handling them here) to be delivered to a
          new thread.  Thus we need to block those signals. */
-      pth_sigmask (SIG_BLOCK, &sigs, &oldsigs);
+      pth_sigmask (SIG_BLOCK, &sigs, NULL);
 
-      if (FD_ISSET (FD2INT (listen_fd), &read_fdset))
+      if (!shutdown_pending && FD_ISSET (FD2INT (listen_fd), &read_fdset))
        {
           ctrl_t ctrl;
 
@@ -1833,7 +1925,7 @@ handle_connections (gnupg_fd_t listen_fd, gnupg_fd_t listen_fd_ssh)
           fd = GNUPG_INVALID_FD;
        }
 
-      if (listen_fd_ssh != GNUPG_INVALID_FD 
+      if (!shutdown_pending && listen_fd_ssh != GNUPG_INVALID_FD 
           && FD_ISSET ( FD2INT (listen_fd_ssh), &read_fdset))
        {
           ctrl_t ctrl;
@@ -1871,10 +1963,6 @@ handle_connections (gnupg_fd_t listen_fd, gnupg_fd_t listen_fd_ssh)
             }
           fd = GNUPG_INVALID_FD;
        }
-
-      /* Restore the signal mask. */
-      pth_sigmask (SIG_SETMASK, &oldsigs, NULL);
-
     }
 
   pth_event_free (ev, PTH_FREE_ALL);
@@ -1885,6 +1973,111 @@ handle_connections (gnupg_fd_t listen_fd, gnupg_fd_t listen_fd_ssh)
 }
 
 
+
+/* Helper for check_own_socket.  */
+static int
+check_own_socket_pid_cb (void *opaque, const void *buffer, size_t length)
+{
+  membuf_t *mb = opaque;
+  put_membuf (mb, buffer, length);
+  return 0;
+}
+
+
+/* The thread running the actual check.  We need to run this in a
+   separate thread so that check_own_thread can be called from the
+   timer tick.  */
+static void *
+check_own_socket_thread (void *arg)
+{
+  int rc;
+  char *sockname = arg;
+  assuan_context_t ctx;
+  membuf_t mb;
+  char *buffer;
+
+  check_own_socket_running++;
+
+  rc = assuan_socket_connect (&ctx, sockname, (pid_t)(-1));
+  xfree (sockname);
+  if (rc)
+    {
+      log_error ("can't connect my own socket: %s\n", gpg_strerror (rc));
+      goto leave;
+    }
+  init_membuf (&mb, 100);
+  rc = assuan_transact (ctx, "GETINFO pid", check_own_socket_pid_cb, &mb,
+                        NULL, NULL, NULL, NULL);
+  put_membuf (&mb, "", 1);
+  buffer = get_membuf (&mb, NULL);
+  if (rc || !buffer)
+    {
+      log_error ("sending command \"%s\" to my own socket failed: %s\n", 
+                 "GETINFO pid", gpg_strerror (rc));
+      rc = 1;
+    }
+  else if ( (pid_t)strtoul (buffer, NULL, 10) != getpid ())
+    {
+      log_error ("socket is now serviced by another server\n");
+      rc = 1;
+    }
+  else if (opt.verbose > 1)
+    log_error ("socket is still served by this server\n");
+    
+  xfree (buffer);
+  assuan_disconnect (ctx);
+
+ leave:
+  if (rc)
+    {
+      /* We may not remove the socket as it is now in use by another
+         server.  Setting the name to empty does this.  */
+      if (socket_name)
+        *socket_name = 0;
+      if (socket_name_ssh)
+        *socket_name_ssh = 0;
+      shutdown_pending = 2;
+      log_info ("this process is useless - shutting down\n");
+    }
+  check_own_socket_running--;
+  return NULL;
+}
+
+
+/* Check whether we are still listening on our own socket.  In case
+   another gpg-agent process started after us has taken ownership of
+   our socket, we woul linger around without any real taks.  Thus we
+   better check once in a while whether we are really needed.  */
+static void
+check_own_socket (void)
+{
+  char *sockname;
+  pth_attr_t tattr;
+
+  if (!use_standard_socket)
+    return; /* This check makes only sense in standard socket mode.  */
+
+  if (check_own_socket_running || shutdown_pending)
+    return;  /* Still running or already shutting down.  */
+
+  sockname = make_filename (opt.homedir, "S.gpg-agent", NULL);
+  if (!sockname)
+    return; /* Out of memory.  */
+
+  tattr = pth_attr_new();
+  pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0);
+  pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 256*1024);
+  pth_attr_set (tattr, PTH_ATTR_NAME, "check-own-socket");
+
+  if (!pth_spawn (tattr, check_own_socket_thread, sockname))
+      log_error ("error spawning check_own_socket_thread: %s\n",
+                 strerror (errno) );
+  pth_attr_destroy (tattr);
+}
+
+
+
 /* Figure out whether an agent is available and running. Prints an
    error if not.  If SILENT is true, no messages are printed.  Usually
    started with MODE 0.  Returns 0 if the agent is running. */