diff options
Diffstat (limited to 'src/gpgme-w32spawn.c')
| -rw-r--r-- | src/gpgme-w32spawn.c | 434 | 
1 files changed, 434 insertions, 0 deletions
| diff --git a/src/gpgme-w32spawn.c b/src/gpgme-w32spawn.c new file mode 100644 index 00000000..f132a8fc --- /dev/null +++ b/src/gpgme-w32spawn.c @@ -0,0 +1,434 @@ +/* gpgme-w32spawn.c - Wrapper to spawn a process under Windows. +   Copyright (C) 2008 g10 Code GmbH + +   This file is part of GPGME. +  +   GPGME is free software; you can redistribute it and/or modify it +   under the terms of the GNU Lesser General Public License as +   published by the Free Software Foundation; either version 2.1 of +   the License, or (at your option) any later version. +    +   GPGME is distributed in the hope that it will be useful, but +   WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   Lesser General Public License for more details. +    +   You should have received a copy of the GNU Lesser General Public +   License along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <ctype.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <stdint.h> +#include <process.h> +#include <windows.h> + + + +struct spawn_fd_item_s +{ +  int handle; +  int dup_to; +  int peer_name; +  int arg_loc; +}; + + +static char * +build_commandline (char **argv) +{ +  int i; +  int n = 0; +  char *buf; +  char *p; +   +  /* We have to quote some things because under Windows the program +     parses the commandline and does some unquoting.  We enclose the +     whole argument in double-quotes, and escape literal double-quotes +     as well as backslashes with a backslash.  We end up with a +     trailing space at the end of the line, but that is harmless.  */ +  for (i = 0; argv[i]; i++) +    { +      p = argv[i]; +      /* The leading double-quote.  */ +      n++; +      while (*p) +	{ +	  /* An extra one for each literal that must be escaped.  */ +	  if (*p == '\\' || *p == '"') +	    n++; +	  n++; +	  p++; +	} +      /* The trailing double-quote and the delimiter.  */ +      n += 2; +    } +  /* And a trailing zero.  */ +  n++; + +  buf = p = malloc (n); +  if (!buf) +    return NULL; +  for (i = 0; argv[i]; i++) +    { +      char *argvp = argv[i]; + +      *(p++) = '"'; +      while (*argvp) +	{ +	  if (*argvp == '\\' || *argvp == '"') +	    *(p++) = '\\'; +	  *(p++) = *(argvp++); +	} +      *(p++) = '"'; +      *(p++) = ' '; +    } +  *(p++) = 0; + +  return buf; +} + + +int +my_spawn (char **argv, struct spawn_fd_item_s *fd_list) +{ +  SECURITY_ATTRIBUTES sec_attr; +  PROCESS_INFORMATION pi = +    { +      NULL,      /* returns process handle */ +      0,         /* returns primary thread handle */ +      0,         /* returns pid */ +      0          /* returns tid */ +    }; +  STARTUPINFO si; +  char *envblock = NULL; +  int cr_flags = CREATE_DEFAULT_ERROR_MODE +    | GetPriorityClass (GetCurrentProcess ()); +  int i; +  char *arg_string; +  int duped_stdin = 0; +  int duped_stdout = 0; +  int duped_stderr = 0; +  HANDLE hnul = INVALID_HANDLE_VALUE; +  /* FIXME.  */ +  int debug_me = 0; + +  i = 0; +  while (argv[i]) +    { +      fprintf (stderr, "argv[%2i] = %s\n", i, argv[i]); +      i++; +    } + +  memset (&sec_attr, 0, sizeof sec_attr); +  sec_attr.nLength = sizeof sec_attr; +  sec_attr.bInheritHandle = FALSE; +   +  arg_string = build_commandline (argv); +  if (!arg_string) +    return -1; + +  memset (&si, 0, sizeof si); +  si.cb = sizeof (si); +  si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; +  si.wShowWindow = debug_me ? SW_SHOW : SW_HIDE; +  si.hStdInput = GetStdHandle (STD_INPUT_HANDLE); +  si.hStdOutput = GetStdHandle (STD_OUTPUT_HANDLE); +  si.hStdError = GetStdHandle (STD_ERROR_HANDLE); + +  fprintf (stderr, "spawning: %s\n", arg_string); + +  for (i = 0; fd_list[i].handle != -1; i++) +    { +      /* The handle already is inheritable.  */ +      if (fd_list[i].dup_to == 0) +	{ +	  si.hStdInput = (HANDLE) fd_list[i].peer_name; +	  duped_stdin = 1; +	  fprintf (stderr, "dup 0x%x to stdin\n", fd_list[i].peer_name); +        } +      else if (fd_list[i].dup_to == 1) +	{ +	  si.hStdOutput = (HANDLE) fd_list[i].peer_name; +	  duped_stdout = 1; +	  fprintf (stderr, "dup 0x%x to stdout\n", fd_list[i].peer_name); +        } +      else if (fd_list[i].dup_to == 2) +	{ +	  si.hStdError = (HANDLE) fd_list[i].peer_name; +	  duped_stderr = 1; +	  fprintf (stderr, "dup 0x%x to stderr\n", fd_list[i].peer_name); +        } +    } +   +  if (!duped_stdin || !duped_stdout || !duped_stderr) +    { +      SECURITY_ATTRIBUTES sa; +       +      memset (&sa, 0, sizeof sa); +      sa.nLength = sizeof sa; +      sa.bInheritHandle = TRUE; +      hnul = CreateFile ("nul", +			 GENERIC_READ|GENERIC_WRITE, +			 FILE_SHARE_READ|FILE_SHARE_WRITE, +			 &sa, +			 OPEN_EXISTING, +			 FILE_ATTRIBUTE_NORMAL, +			 NULL); +      if (hnul == INVALID_HANDLE_VALUE) +	{ +	  free (arg_string); +	  /* FIXME: Should translate the error code.  */ +	  errno = EIO; +	  return -1; +        } +      /* Make sure that the process has a connected stdin.  */ +      if (!duped_stdin) +	si.hStdInput = hnul; +      /* Make sure that the process has a connected stdout.  */ +      if (!duped_stdout) +	si.hStdOutput = hnul; +      /* We normally don't want all the normal output.  */ +      if (!duped_stderr) +	si.hStdError = hnul; +    } +   +  cr_flags |= CREATE_SUSPENDED;  +  cr_flags |= DETACHED_PROCESS; +  if (!CreateProcessA (argv[0], +		       arg_string, +		       &sec_attr,     /* process security attributes */ +		       &sec_attr,     /* thread security attributes */ +		       TRUE,          /* inherit handles */ +		       cr_flags,      /* creation flags */ +		       envblock,      /* environment */ +		       NULL,          /* use current drive/directory */ +		       &si,           /* startup information */ +		       &pi))          /* returns process information */ +    { +      free (arg_string); +      /* FIXME: Should translate the error code.  */ +      errno = EIO; +      return -1; +    } + +  free (arg_string); + +  /* Close the /dev/nul handle if used.  */ +  if (hnul != INVALID_HANDLE_VALUE) +    CloseHandle (hnul); +   +  for (i = 0; fd_list[i].handle != -1; i++) +    CloseHandle ((HANDLE) fd_list[i].handle); +   +  ResumeThread (pi.hThread); +  CloseHandle (pi.hThread); +  CloseHandle (pi.hProcess); + +  return 0; +} + + +#define MAX_TRANS 10 + +int +translate_get_from_file (const char *trans_file,  +			 struct spawn_fd_item_s *fd_list) +{ +  /* Hold roughly MAX_TRANS triplets of 64 bit numbers in hex +     notation: "0xFEDCBA9876543210".  10*19*4 - 1 = 759.  This plans +     ahead for a time when a HANDLE is 64 bit.  */ +#define BUFFER_MAX 800 + +  char line[BUFFER_MAX + 1]; +  char *linep; +  int idx; +  int res; +  int fd; + +  fd = open (trans_file, O_RDONLY); +  if (fd < 0) +    return -1; + +  /* We always read one line from stdin.  */ +  res = read (fd, line, BUFFER_MAX); +  close (fd); +  if (res < 0) +    return -1; + +  line[BUFFER_MAX] = '\0'; +  linep = strchr (line, '\n'); +  if (linep > line && linep[-1] == '\r') +    linep--; +  *linep = '\0'; + +  linep = line; + +  /* Now start to read mapping pairs.  */ +  for (idx = 0; idx < MAX_TRANS; idx++) +    { +      unsigned long from; +      long dup_to; +      unsigned long to; +      unsigned long loc; +      char *tail; + +      /* FIXME: Maybe could use scanf.  */ +      while (isspace (*((unsigned char *)linep))) +	linep++; +      if (*linep == '\0') +	break; +      from = strtoul (linep, &tail, 0); +      if (tail == NULL || ! (*tail == '\0' || isspace (*tail))) +	break; +      linep = tail; + +      while (isspace (*linep)) +	linep++; +      if (*linep == '\0') +	break; +      dup_to = strtol (linep, &tail, 0); +      if (tail == NULL || ! (*tail == '\0' || isspace (*tail))) +	break; +      linep = tail; + +      while (isspace (*linep)) +	linep++; +      if (*linep == '\0') +	break; +      to = strtoul (linep, &tail, 0); +      if (tail == NULL || ! (*tail == '\0' || isspace (*tail))) +	break; +      linep = tail; + +      while (isspace (*linep)) +	linep++; +      if (*linep == '\0') +	break; +      loc = strtoul (linep, &tail, 0); +      if (tail == NULL || ! (*tail == '\0' || isspace (*tail))) +	break; +      linep = tail; + +      fd_list[idx].handle = from; +      fd_list[idx].dup_to = dup_to; +      fd_list[idx].peer_name = to; +      fd_list[idx].arg_loc = loc; +    } +  fd_list[idx].handle = -1; +  fd_list[idx].dup_to = -1; +  fd_list[idx].peer_name = -1; +  fd_list[idx].arg_loc = 0; +  return 0; +} + + +/* Read the translated handles from TRANS_FILE and do a substitution +   in ARGV.  Returns the new argv and the list of substitutions in +   FD_LIST (which must be MAX_TRANS+1 large).  */ +char ** +translate_handles (const char *trans_file, const char * const *argv, +		   struct spawn_fd_item_s *fd_list) +{ +  int res; +  int idx; +  char **args; + +  res = translate_get_from_file (trans_file, fd_list); +  if (res < 0) +    return NULL; + +  for (idx = 0; argv[idx]; idx++) +    ; +  args = malloc (sizeof (*args) * (idx + 1)); +  for (idx = 0; argv[idx]; idx++) +    { +      args[idx] = strdup (argv[idx]); +      if (!args[idx]) +	return NULL; +    } +  args[idx] = NULL; + +  for (idx = 0; fd_list[idx].handle != -1; idx++) +    { +      char buf[25]; +      int aidx; + +      aidx = fd_list[idx].arg_loc; +      if (aidx == 0) +	continue; + +      args[aidx] = malloc (sizeof (buf)); +      /* We currently disable translation for stdin/stdout/stderr.  We +	 assume that the spawned program handles 0/1/2 specially +	 already.  FIXME: Check if this is true.  */ +      if (!args[idx] || fd_list[idx].dup_to != -1) +	return NULL; + +      /* NOTE: Here is the part where application specific knowledge +	 comes in.  GPGME/GnuPG uses two forms of descriptor +	 specification, a plain number and a "-&" form.  */ +      if (argv[aidx][0] == '-' && argv[aidx][1] == '&') +	snprintf (args[aidx], sizeof (buf), "-&%d", fd_list[idx].peer_name); +      else +	snprintf (args[aidx], sizeof (buf), "%d", fd_list[idx].peer_name); +    } +  return args; +} + + +int +main (int argc, const char * const *argv) +{ +  int rc = 0; +  char **argv_spawn; +  struct spawn_fd_item_s fd_list[MAX_TRANS + 1]; + +  if (argc < 3) +    { +      rc = 2; +      goto leave; +    } + +  argv_spawn = translate_handles (argv[1], &argv[2], fd_list); +  if (!argv_spawn) +    { +      rc = 2; +      goto leave; +    } + +  /* Using execv does not replace the existing program image, but +     spawns a new one and daemonizes it, confusing the command line +     interpreter.  So we have to use spawnv.  */ +  rc = my_spawn (argv_spawn, fd_list); +  if (rc < 0) +    { +      fprintf (stderr, "gpgwrap: executing `%s' failed: %s\n", +	       argv[0], strerror (errno)); +      rc = 2; +      goto leave; +    } + + leave: +  if (rc) +    fprintf (stderr, "gpg-w32spawn: internal error\n"); +  /* Always try to delete the temporary file.  */ +  if (argc >= 2) +    { +      if (DeleteFile (argv[1]) == 0) +	fprintf (stderr, "Failed to delete %s: ec=%ld\n", +		 argv[1], GetLastError ()); +    } +  return rc; +} | 
