435 lines
10 KiB
C
435 lines
10 KiB
C
|
/* 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;
|
|||
|
}
|