2005-10-01 Marcus Brinkmann <marcus@g10code.de>
[gpgme.git] / assuan / assuan-pipe-connect.c
index f79884c..cbc131f 100644 (file)
 #include <errno.h>
 #include <fcntl.h>
 #include <sys/types.h>
+#ifndef HAVE_W32_SYSTEM
 #include <sys/wait.h>
+#else
+#include <windows.h>
+#endif
 
 #include "assuan-defs.h"
 
 #define MAX_OPEN_FDS 20
 #endif
 
-#define LOG(format, args...) \
-       fprintf (assuan_get_assuan_log_stream (), \
-                assuan_get_assuan_log_prefix (), \
-                "%s" format , ## args)
+#ifdef HAVE_W32_SYSTEM
+/* We assume that a HANDLE can be represented by an int which should
+   be true for all i386 systems (HANDLE is defined as void *) and
+   these are the only systems for which Windows is available.  Further
+   we assume that -1 denotes an invalid handle.  */
+#define fd_to_handle(a)  ((HANDLE)(a))
+#define handle_to_fd(a)  ((int)(a))
+#define pid_to_handle(a) ((HANDLE)(a))
+#define handle_to_pid(a) ((int)(a))
+#endif /*HAVE_W32_SYSTEM*/
+
+
+/* This should be called to make sure that SIGPIPE gets ignored.  */
+static void
+fix_signals (void)
+{
+#ifndef _ASSUAN_NO_FIXED_SIGNALS
+#ifndef HAVE_DOSISH_SYSTEM  /* No SIGPIPE for these systems.  */
+  static int fixed_signals;
 
+  if (!fixed_signals)
+    { 
+      struct sigaction act;
+        
+      sigaction (SIGPIPE, NULL, &act);
+      if (act.sa_handler == SIG_DFL)
+       {
+         act.sa_handler = SIG_IGN;
+         sigemptyset (&act.sa_mask);
+         act.sa_flags = 0;
+         sigaction (SIGPIPE, &act, NULL);
+        }
+      fixed_signals = 1;
+      /* FIXME: This is not MT safe */
+    }
+#endif /*HAVE_DOSISH_SYSTEM*/
+#endif /*!_ASSUAN_NO_FIXED_SIGNALS*/
+}
+
+
+#ifndef HAVE_W32_SYSTEM
 static int
 writen (int fd, const char *buffer, size_t length)
 {
@@ -63,75 +103,328 @@ writen (int fd, const char *buffer, size_t length)
     }
   return 0;  /* okay */
 }
-
+#endif
 
 static int
-do_finish (ASSUAN_CONTEXT ctx)
+do_finish (assuan_context_t ctx)
 {
   if (ctx->inbound.fd != -1)
     {
-      close (ctx->inbound.fd);
+      _assuan_close (ctx->inbound.fd);
       ctx->inbound.fd = -1;
     }
   if (ctx->outbound.fd != -1)
     {
-      close (ctx->outbound.fd);
+      _assuan_close (ctx->outbound.fd);
       ctx->outbound.fd = -1;
     }
-  if (ctx->pid != -1)
+  if (ctx->pid != -1 && ctx->pid)
     {
-#if 0
-      /* This is already done by the double fork.  */
-      waitpid (ctx->pid, NULL, 0);  /* FIXME Check return value.  */
+#ifndef HAVE_W32_SYSTEM
+#ifndef _ASSUAN_USE_DOUBLE_FORK
+      if (!ctx->flags.no_waitpid)
+        waitpid (ctx->pid, NULL, 0); 
       ctx->pid = -1;
 #endif
+#endif /*!HAVE_W32_SYSTEM*/
     }
   return 0;
 }
 
 static void
-do_deinit (ASSUAN_CONTEXT ctx)
+do_deinit (assuan_context_t ctx)
 {
   do_finish (ctx);
 }
 
 
+#ifdef HAVE_W32_SYSTEM
+/* Build a command line for use with W32's CreateProcess.  On success
+   CMDLINE gets the address of a newly allocated string.  */
+static int
+build_w32_commandline (char * const *argv, char **cmdline)
+{
+  int i, n;
+  const char *s;
+  char *buf, *p;
+
+  *cmdline = NULL;
+  n = 0;
+  for (i=0; (s=argv[i]); i++)
+    {
+      n += strlen (s) + 1 + 2;  /* (1 space, 2 quoting */
+      for (; *s; s++)
+        if (*s == '\"')
+          n++;  /* Need to double inner quotes.  */
+    }
+  n++;
+
+  buf = p = xtrymalloc (n);
+  if (!buf)
+    return -1;
+
+  for (i=0; argv[i]; i++) 
+    {
+      if (i)
+        p = stpcpy (p, " ");
+      if (!*argv[i]) /* Empty string. */
+        p = stpcpy (p, "\"\"");
+      else if (strpbrk (argv[i], " \t\n\v\f\""))
+        {
+          p = stpcpy (p, "\"");
+          for (s=argv[i]; *s; s++)
+            {
+              *p++ = *s;
+              if (*s == '\"')
+                *p++ = *s;
+            }
+          *p++ = '\"';
+          *p = 0;
+        }
+      else
+        p = stpcpy (p, argv[i]);
+    }
+
+  *cmdline= buf;
+  return 0;
+}
+#endif /*HAVE_W32_SYSTEM*/
+
+
+#ifdef HAVE_W32_SYSTEM
+/* Create pipe where one end end is inheritable.  */
+static int
+create_inheritable_pipe (int filedes[2], int for_write)
+{
+  HANDLE r, w, h;
+  SECURITY_ATTRIBUTES sec_attr;
+
+  memset (&sec_attr, 0, sizeof sec_attr );
+  sec_attr.nLength = sizeof sec_attr;
+  sec_attr.bInheritHandle = FALSE;
+    
+  if (!CreatePipe (&r, &w, &sec_attr, 0))
+    {
+      _assuan_log_printf ("CreatePipe failed: %s\n", w32_strerror (-1));
+      return -1;
+    }
+
+  if (!DuplicateHandle (GetCurrentProcess(), for_write? r : w,
+                        GetCurrentProcess(), &h, 0,
+                        TRUE, DUPLICATE_SAME_ACCESS ))
+    {
+      _assuan_log_printf ("DuplicateHandle failed: %s\n", w32_strerror (-1));
+      CloseHandle (r);
+      CloseHandle (w);
+      return -1;
+    }
+  if (for_write)
+    {
+      CloseHandle (r);
+      r = h;
+    }
+  else
+    {
+      CloseHandle (w);
+      w = h;
+    }
+
+  filedes[0] = handle_to_fd (r);
+  filedes[1] = handle_to_fd (w);
+  return 0;
+}
+#endif /*HAVE_W32_SYSTEM*/
+
 
 /* Connect to a server over a pipe, creating the assuan context and
    returning it in CTX.  The server filename is NAME, the argument
    vector in ARGV.  FD_CHILD_LIST is a -1 terminated list of file
-   descriptors not to close in the child.  */
-AssuanError
-assuan_pipe_connect (ASSUAN_CONTEXT *ctx, const char *name, char *const argv[],
-                    int *fd_child_list)
+   descriptors not to close in the child.  ATFORK is called in the
+   child right after the fork; ATFORKVALUE is passed as the first
+   argument and 0 is passed as the second argument. The ATFORK
+   function should only act if the second value is 0. */
+assuan_error_t
+assuan_pipe_connect2 (assuan_context_t *ctx,
+                      const char *name, char *const argv[],
+                      int *fd_child_list,
+                      void (*atfork) (void *opaque, int reserved),
+                      void *atforkvalue)
 {
-#ifndef _ASSUAN_IN_GPGME
-  static int fixed_signals = 0;
-#endif
-  AssuanError err;
+#ifdef HAVE_W32_SYSTEM
+  assuan_error_t err;
   int rp[2];
   int wp[2];
+  char mypidstr[50];
+  char *cmdline;
+  SECURITY_ATTRIBUTES sec_attr;
+  PROCESS_INFORMATION pi = 
+    {
+      NULL,      /* Returns process handle.  */
+      0,         /* Returns primary thread handle.  */
+      0,         /* Returns pid.  */
+      0          /* Returns tid.  */
+    };
+  STARTUPINFO si;
+  int fd, *fdp;
+  HANDLE nullfd = INVALID_HANDLE_VALUE;
 
   if (!ctx || !name || !argv || !argv[0])
     return ASSUAN_Invalid_Value;
 
-#ifndef _ASSUAN_IN_GPGME
-  if (!fixed_signals)
-    { 
-      struct sigaction act;
-        
-      sigaction (SIGPIPE, NULL, &act);
-      if (act.sa_handler == SIG_DFL)
-       {
-         act.sa_handler = SIG_IGN;
-         sigemptyset (&act.sa_mask);
-         act.sa_flags = 0;
-         sigaction (SIGPIPE, &act, NULL);
+  fix_signals ();
+
+  sprintf (mypidstr, "%lu", (unsigned long)getpid ());
+
+  /* Build the command line.  */
+  if (build_w32_commandline (argv, &cmdline))
+    return ASSUAN_Out_Of_Core;
+
+  /* Create thew two pipes. */
+  if (create_inheritable_pipe (rp, 0))
+    {
+      xfree (cmdline);
+      return ASSUAN_General_Error;
+    }
+  
+  if (create_inheritable_pipe (wp, 1))
+    {
+      CloseHandle (fd_to_handle (rp[0]));
+      CloseHandle (fd_to_handle (rp[1]));
+      xfree (cmdline);
+      return ASSUAN_General_Error;
+    }
+
+  
+  err = _assuan_new_context (ctx);
+  if (err)
+    {
+      CloseHandle (fd_to_handle (rp[0]));
+      CloseHandle (fd_to_handle (rp[1]));
+      CloseHandle (fd_to_handle (wp[0]));
+      CloseHandle (fd_to_handle (wp[1]));
+      xfree (cmdline);
+      return ASSUAN_General_Error;
+    }
+
+  (*ctx)->pipe_mode = 1;
+  (*ctx)->inbound.fd  = rp[0];  /* Our inbound is read end of read pipe. */
+  (*ctx)->outbound.fd = wp[1];  /* Our outbound is write end of write pipe. */
+  (*ctx)->deinit_handler = do_deinit;
+  (*ctx)->finish_handler = do_finish;
+
+
+  /* fixme: Actually we should set the "_assuan_pipe_connect_pid" env
+     variable.  However this requires us to write a full environment
+     handler, because the strings are expected in sorted order.  The
+     suggestion given in the MS Reference Library, to save the old
+     value, changeit, create proces and restore it, is not thread
+     safe.  */
+
+  /* Start the process.  */
+  memset (&sec_attr, 0, sizeof sec_attr );
+  sec_attr.nLength = sizeof sec_attr;
+  sec_attr.bInheritHandle = FALSE;
+  
+  memset (&si, 0, sizeof si);
+  si.cb = sizeof (si);
+  si.dwFlags = STARTF_USESTDHANDLES;
+  si.hStdInput  = fd_to_handle (wp[0]);
+  si.hStdOutput = fd_to_handle (rp[1]);
+
+  /* Dup stderr to /dev/null unless it is in the list of FDs to be
+     passed to the child. */
+  fd = fileno (stderr);
+  fdp = fd_child_list;
+  if (fdp)
+    {
+      for (; *fdp != -1 && *fdp != fd; fdp++)
+        ;
+    }
+  if (!fdp || *fdp == -1)
+    {
+      nullfd = CreateFile ("nul", GENERIC_WRITE,
+                           FILE_SHARE_READ | FILE_SHARE_WRITE,
+                           NULL, OPEN_EXISTING, 0, NULL);
+      if (nullfd == INVALID_HANDLE_VALUE)
+        {
+          _assuan_log_printf ("can't open `nul': %s\n", w32_strerror (-1));
+          CloseHandle (fd_to_handle (rp[0]));
+          CloseHandle (fd_to_handle (rp[1]));
+          CloseHandle (fd_to_handle (wp[0]));
+          CloseHandle (fd_to_handle (wp[1]));
+          xfree (cmdline);
+          _assuan_release_context (*ctx); 
+          return -1;
         }
-      fixed_signals = 1;
-      /* FIXME: This is not MT safe */
+      si.hStdError = nullfd;
     }
-#endif
+  else
+    si.hStdError = fd_to_handle (_get_osfhandle (fd));
+
+
+  /* Note: We inherit all handles flagged as inheritable.  This seems
+     to be a security flaw but there seems to be no way of selecting
+     handles to inherit. */
+  /*   _assuan_log_printf ("CreateProcess, path=`%s' cmdline=`%s'\n", */
+  /*                       name, cmdline); */
+  if (!CreateProcess (name,                 /* Program to start.  */
+                      cmdline,              /* Command line arguments.  */
+                      &sec_attr,            /* Process security attributes.  */
+                      &sec_attr,            /* Thread security attributes.  */
+                      TRUE,                 /* Inherit handles.  */
+                      (CREATE_DEFAULT_ERROR_MODE
+                       | GetPriorityClass (GetCurrentProcess ())
+                       | CREATE_SUSPENDED), /* Creation flags.  */
+                      NULL,                 /* Environment.  */
+                      NULL,                 /* Use current drive/directory.  */
+                      &si,                  /* Startup information. */
+                      &pi                   /* Returns process information.  */
+                      ))
+    {
+      _assuan_log_printf ("CreateProcess failed: %s\n", w32_strerror (-1));
+      CloseHandle (fd_to_handle (rp[0]));
+      CloseHandle (fd_to_handle (rp[1]));
+      CloseHandle (fd_to_handle (wp[0]));
+      CloseHandle (fd_to_handle (wp[1]));
+      if (nullfd != INVALID_HANDLE_VALUE)
+        CloseHandle (nullfd);
+      xfree (cmdline);
+      _assuan_release_context (*ctx); 
+      return ASSUAN_General_Error;
+    }
+  xfree (cmdline);
+  cmdline = NULL;
+  if (nullfd != INVALID_HANDLE_VALUE)
+    {
+      CloseHandle (nullfd);
+      nullfd = INVALID_HANDLE_VALUE;
+    }
+
+  CloseHandle (fd_to_handle (rp[1]));
+  CloseHandle (fd_to_handle (wp[0]));
+
+  /*   _assuan_log_printf ("CreateProcess ready: hProcess=%p hThread=%p" */
+  /*                       " dwProcessID=%d dwThreadId=%d\n", */
+  /*                       pi.hProcess, pi.hThread, */
+  /*                       (int) pi.dwProcessId, (int) pi.dwThreadId); */
+
+  ResumeThread (pi.hThread);
+  CloseHandle (pi.hThread); 
+  (*ctx)->pid = 0;  /* We don't use the PID. */
+  CloseHandle (pi.hProcess); /* We don't need to wait for the process. */
+
+#else /*!HAVE_W32_SYSTEM*/
+  assuan_error_t err;
+  int rp[2];
+  int wp[2];
+  char mypidstr[50];
+
+  if (!ctx || !name || !argv || !argv[0])
+    return ASSUAN_Invalid_Value;
+
+  fix_signals ();
+
+  sprintf (mypidstr, "%lu", (unsigned long)getpid ());
 
   if (pipe (rp) < 0)
     return ASSUAN_General_Error;
@@ -158,8 +451,8 @@ assuan_pipe_connect (ASSUAN_CONTEXT *ctx, const char *name, char *const argv[],
   (*ctx)->deinit_handler = do_deinit;
   (*ctx)->finish_handler = do_finish;
 
-  /* FIXME: Use _gpgme_io_spawn.  The PID stored here is actually
-     soon useless.  */
+  /* FIXME: For GPGME we should better use _gpgme_io_spawn.  The PID
+     stored here is actually soon useless.  */
   (*ctx)->pid = fork ();
   if ((*ctx)->pid < 0)
     {
@@ -173,111 +466,133 @@ assuan_pipe_connect (ASSUAN_CONTEXT *ctx, const char *name, char *const argv[],
 
   if ((*ctx)->pid == 0)
     {
-      /* Intermediate child to prevent zombie processes.  */
+#ifdef _ASSUAN_USE_DOUBLE_FORK      
       pid_t pid;
 
       if ((pid = fork ()) == 0)
+#endif
        {
-         /* Child.  */
-
-         int i, n;
-         char errbuf[512];
-         int *fdp;
-
-         /* Dup handles to stdin/stdout. */
-         if (rp[1] != STDOUT_FILENO)
-           {
-             if (dup2 (rp[1], STDOUT_FILENO) == -1)
-               {
-                 LOG ("dup2 failed in child: %s\n", strerror (errno));
-                 _exit (4);
-               }
-           }
-         if (wp[0] != STDIN_FILENO)
-           {
-             if (dup2 (wp[0], STDIN_FILENO) == -1)
-               {
-                 LOG ("dup2 failed in child: %s\n", strerror (errno));
-                 _exit (4);
-               }
-           }
-         
-         /* Dup stderr to /dev/null unless it is in the list of FDs to be
-            passed to the child. */
-         fdp = fd_child_list;
-         if (fdp)
-           {
-             for (; *fdp != -1 && *fdp != STDERR_FILENO; fdp++)
-               ;
-           }
-         if (!fdp || *fdp == -1)
-           {
-             int fd = open ("/dev/null", O_WRONLY);
-             if (fd == -1)
-               {
-                 LOG ("can't open `/dev/null': %s\n", strerror (errno));
-                 _exit (4);
-               }
-             if (dup2 (fd, STDERR_FILENO) == -1)
-               {
-                 LOG ("dup2(dev/null, 2) failed: %s\n", strerror (errno));
-                 _exit (4);
-               }
-           }
-
-
-         /* Close all files which will not be duped and are not in the
-            fd_child_list. */
-         n = sysconf (_SC_OPEN_MAX);
-         if (n < 0)
-           n = MAX_OPEN_FDS;
-         for (i=0; i < n; i++)
-           {
-             if ( i == STDIN_FILENO || i == STDOUT_FILENO || i == STDERR_FILENO)
-               continue;
-             fdp = fd_child_list;
-             if (fdp)
-               {
-                 while (*fdp != -1 && *fdp != i)
-                   fdp++;
-               }
-             
-             if (!(fdp && *fdp != -1))
-               close(i);
-           }
-
-         errno = 0;
-
-         execv (name, argv); 
-         /* oops - use the pipe to tell the parent about it */
-         snprintf (errbuf, sizeof(errbuf)-1, "ERR %d can't exec `%s': %.50s\n",
-                   ASSUAN_Problem_Starting_Server, name, strerror (errno));
-         errbuf[sizeof(errbuf)-1] = 0;
-         writen (1, errbuf, strlen (errbuf));
-         _exit (4);
-       } /* End child.  */
+          int i, n;
+          char errbuf[512];
+          int *fdp;
+          
+          if (atfork)
+            atfork (atforkvalue, 0);
+
+          /* Dup handles to stdin/stdout. */
+          if (rp[1] != STDOUT_FILENO)
+            {
+              if (dup2 (rp[1], STDOUT_FILENO) == -1)
+                {
+                  _assuan_log_printf ("dup2 failed in child: %s\n",
+                                      strerror (errno));
+                  _exit (4);
+                }
+            }
+          if (wp[0] != STDIN_FILENO)
+            {
+              if (dup2 (wp[0], STDIN_FILENO) == -1)
+                {
+                  _assuan_log_printf ("dup2 failed in child: %s\n",
+                                      strerror (errno));
+                  _exit (4);
+                }
+            }
+
+          /* Dup stderr to /dev/null unless it is in the list of FDs to be
+             passed to the child. */
+          fdp = fd_child_list;
+          if (fdp)
+            {
+              for (; *fdp != -1 && *fdp != STDERR_FILENO; fdp++)
+                ;
+            }
+          if (!fdp || *fdp == -1)
+            {
+              int fd = open ("/dev/null", O_WRONLY);
+              if (fd == -1)
+                {
+                  _assuan_log_printf ("can't open `/dev/null': %s\n",
+                                      strerror (errno));
+                  _exit (4);
+                }
+              if (dup2 (fd, STDERR_FILENO) == -1)
+                {
+                  _assuan_log_printf ("dup2(dev/null, 2) failed: %s\n",
+                                      strerror (errno));
+                  _exit (4);
+                }
+            }
+
+
+          /* Close all files which will not be duped and are not in the
+             fd_child_list. */
+          n = sysconf (_SC_OPEN_MAX);
+          if (n < 0)
+            n = MAX_OPEN_FDS;
+          for (i=0; i < n; i++)
+            {
+              if ( i == STDIN_FILENO || i == STDOUT_FILENO
+                   || i == STDERR_FILENO)
+                continue;
+              fdp = fd_child_list;
+              if (fdp)
+                {
+                  while (*fdp != -1 && *fdp != i)
+                    fdp++;
+                }
+
+              if (!(fdp && *fdp != -1))
+                close(i);
+            }
+          errno = 0;
+
+          /* We store our parents pid in the environment so that the
+             execed assuan server is able to read the actual pid of the
+             client.  The server can't use getppid becuase it might have
+             been double forked before the assuan server has been
+             initialized. */
+          setenv ("_assuan_pipe_connect_pid", mypidstr, 1);
+
+          execv (name, argv); 
+          /* oops - use the pipe to tell the parent about it */
+          snprintf (errbuf, sizeof(errbuf)-1,
+                    "ERR %d can't exec `%s': %.50s\n",
+                    ASSUAN_Problem_Starting_Server, name, strerror (errno));
+          errbuf[sizeof(errbuf)-1] = 0;
+          writen (1, errbuf, strlen (errbuf));
+          _exit (4);
+        }
+#ifdef _ASSUAN_USE_DOUBLE_FORK
       if (pid == -1)
        _exit (1);
       else
        _exit (0);
+#endif
     }
 
+#ifdef _ASSUAN_USE_DOUBLE_FORK
   waitpid ((*ctx)->pid, NULL, 0);
   (*ctx)->pid = -1;
+#endif
 
   close (rp[1]);
   close (wp[0]);
 
+#endif /*!HAVE_W32_SYSTEM*/
+
   /* initial handshake */
   {
     int okay, off;
 
     err = _assuan_read_from_server (*ctx, &okay, &off);
     if (err)
-      LOG ("can't connect server: %s\n", assuan_strerror (err));
+      _assuan_log_printf ("can't connect server: %s\n",
+                          assuan_strerror (err));
     else if (okay != 1)
       {
-       LOG ("can't connect server: `%s'\n", (*ctx)->inbound.line);
+       _assuan_log_printf ("can't connect server: `%s'\n",
+                            (*ctx)->inbound.line);
        err = ASSUAN_Connect_Failed;
       }
   }
@@ -292,15 +607,13 @@ assuan_pipe_connect (ASSUAN_CONTEXT *ctx, const char *name, char *const argv[],
 }
 
 
-
-
-
-
-
-
-
-
-
-
-
-
+/* Connect to a server over a pipe, creating the assuan context and
+   returning it in CTX.  The server filename is NAME, the argument
+   vector in ARGV.  FD_CHILD_LIST is a -1 terminated list of file
+   descriptors not to close in the child.  */
+assuan_error_t
+assuan_pipe_connect (assuan_context_t *ctx, const char *name, char *const argv[],
+                    int *fd_child_list)
+{
+  return assuan_pipe_connect2 (ctx, name, argv, fd_child_list, NULL, NULL);
+}