gpgme/src/gpgme-w32spawn.c

435 lines
10 KiB
C
Raw Normal View History

assuan/ 2008-06-25 Marcus Brinkmann <marcus@g10code.de> * assuan-pipe-connect.c (struct spawn_fd_item_s): Add new members. (HANDLE_TRANSLATION): New macro. (pipe_connect_gpgme): Adjust caller of _gpgme_io_spawn. [HANDLE_TRANSLATION]: Return translated handles. gpgme/ 2008-06-25 Marcus Brinkmann <marcus@g10code.de> * gpgme-w32spawn.c: New file. * Makefile.am (libexec_PROGRAMS) [HAVE_W32_SYSTEM]: New variable with gpgme-w32spawn. * engine-gpgsm.c (gpgsm_new): Use server translated handles. (gpgsm_set_locale): Return early if locale value is NULL. * util.h (_gpgme_mkstemp) (_gpgme_get_w32spawn_path) [HAVE_W32_SYSTEM]: New function prototypes. * w32-util.c: Include <stdint.h>, <sys/stat.h> and <unistd.h>. (letters, mkstemp, _gpgme_mkstemp, _gpgme_get_w32spawn_path): New functions. * rungpg.c (gpg_decrypt, gpg_encrypt, gpg_encrypt_sign) (gpg_genkey, gpg_import, gpg_verify, gpg_sign): Pass data over special filename FD rather than stdin. (struct arg_and_data_s): Add member ARG_LOCP. (struct fd_data_map_s): Add member ARG_LOC. (struct engine_gpg): Add member ARG_LOC to status and colon. (_add_arg, add_arg_with_locp): New function. (add_arg_ext): Reimplement in terms of _add_arg. (gpg_new): Remember argument location for status FD. (build_argv): Set argument location if requested. Also set argument location of fd_data_map for data items. (start): Adjust caller of _gpgme_io_spawn. * priv-io.h (struct spawn_fd_item_s): Add members peer_name and arg_loc. (_gpgme_io_spawn): Remove parent fd list argument. * posix-io.c (get_max_fds): New function. (_gpgme_io_dup): Add tracing. (_gpgme_io_spawn): Remove parent fd list. Change meaning of child fd list to contain all child fds that should be inherited. Close all other file descriptors after fork. * w32-io.c, w32-glib-io.c, w32-qt-io.c(_gpgme_io_spawn): Remove parent fd list. Change meaning of child fd list to contain all child fds that should be inherited. Do not inherit any file descriptors, but DuplicateHandle them. Spawn process through wrapper process. Provide wrapper process with a temporary file containing handle translation data. Return translated handle names. * w32-io.c (reader): Add more tracing output. (_gpgme_io_read): Likewise. * engine-gpgconf.c (gpgconf_read): Adjust caller of _gpgme_io_spawn. * version.c (_gpgme_get_program_version): Likewise.
2008-06-25 16:52:31 +00:00
/* 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;
}