/* 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 .
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#include
#include
#include
#include
#ifdef HAVE_SYS_TYPES_H
# include
#endif
#ifdef HAVE_SYS_STAT_H
# include
#endif
#include
#include
#include "priv-io.h"
/* #define DEBUG_TO_FILE 1 */
/* Name of this program. */
#define PGM "gpgme-w32spawn"
#ifdef DEBUG_TO_FILE
static FILE *mystderr;
#else
#define mystderr stderr
#endif
static wchar_t *
build_commandline (wchar_t **argv)
{
int i;
int n = 0;
wchar_t *buf;
wchar_t *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 == L'\\' || *p == L'"')
n++;
n++;
p++;
}
/* The trailing double-quote and the delimiter. */
n += 2;
}
/* And a trailing zero. */
n++;
buf = p = malloc (n * sizeof (wchar_t));
if (!buf)
return NULL;
for (i = 0; argv[i]; i++)
{
wchar_t *argvp = argv[i];
*(p++) = L'"';
while (*argvp)
{
if (*argvp == L'\\' || *argvp == L'"')
*(p++) = L'\\';
*(p++) = *(argvp++);
}
*(p++) = L'"';
*(p++) = L' ';
}
*(p++) = 0;
return buf;
}
int
my_spawn (wchar_t **argv, struct spawn_fd_item_s *fd_list, unsigned int flags)
{
SECURITY_ATTRIBUTES sec_attr;
PROCESS_INFORMATION pi =
{
NULL, /* returns process handle */
0, /* returns primary thread handle */
0, /* returns pid */
0 /* returns tid */
};
STARTUPINFOW si;
int cr_flags = CREATE_DEFAULT_ERROR_MODE
| GetPriorityClass (GetCurrentProcess ());
int i;
wchar_t *arg_string;
int duped_stdin = 0;
int duped_stdout = 0;
int duped_stderr = 0;
HANDLE hnul = INVALID_HANDLE_VALUE;
i = 0;
while (argv[i])
{
fprintf (mystderr, PGM": 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 = (flags & IOSPAWN_FLAG_SHOW_WINDOW) ? SW_SHOW : SW_HIDE;
si.hStdInput = GetStdHandle (STD_INPUT_HANDLE);
si.hStdOutput = GetStdHandle (STD_OUTPUT_HANDLE);
si.hStdError = GetStdHandle (STD_ERROR_HANDLE);
for (i = 0; fd_list[i].fd != -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 (mystderr, PGM": 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 (mystderr, PGM": 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 (mystderr, PGM":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 = CreateFileW (L"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;
if (!CreateProcessW (argv[0],
arg_string,
&sec_attr, /* process security attributes */
&sec_attr, /* thread security attributes */
TRUE, /* inherit handles */
cr_flags, /* creation flags */
NULL, /* environment */
NULL, /* use current drive/directory */
&si, /* startup information */
&pi)) /* returns process information */
{
free (arg_string);
fprintf (mystderr, PGM": spawn error: %d\n", (int)GetLastError ());
/* 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].fd != -1; i++)
CloseHandle ((HANDLE) fd_list[i].fd);
if (flags & IOSPAWN_FLAG_ALLOW_SET_FG)
{
static int initialized;
static BOOL (WINAPI * func)(DWORD);
void *handle;
if (!initialized)
{
/* Available since W2000; thus we dynload it. */
initialized = 1;
handle = LoadLibraryA ("user32.dll");
if (handle)
{
func = GetProcAddress (handle, "AllowSetForegroundWindow");
if (!func)
FreeLibrary (handle);
}
}
if (func)
{
int rc = func (pi.dwProcessId);
fprintf (mystderr, PGM": AllowSetForegroundWindow(%d): rc=%d\n",
(int)pi.dwProcessId, rc);
}
}
ResumeThread (pi.hThread);
CloseHandle (pi.hThread);
CloseHandle (pi.hProcess);
return 0;
}
#define MAX_TRANS 10
int
translate_get_from_file (const wchar_t *trans_file,
struct spawn_fd_item_s *fd_list,
unsigned int *r_flags)
{
/* 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 810
char line[BUFFER_MAX + 1];
char *linep;
int idx;
int res;
int fd;
*r_flags = 0;
fd = _wopen (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)
{
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;
if (!idx && *linep == '~')
{
/* Spawn flags have been passed. */
linep++;
*r_flags = strtoul (linep, &tail, 0);
if (tail == NULL || ! (*tail == '\0' || isspace (*tail)))
break;
linep = tail;
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].fd = from;
fd_list[idx].dup_to = dup_to;
fd_list[idx].peer_name = to;
fd_list[idx].arg_loc = loc;
}
fd_list[idx].fd = -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). */
wchar_t **
translate_handles (const wchar_t *trans_file, const wchar_t * const *argv,
struct spawn_fd_item_s *fd_list, unsigned int *r_flags)
{
int res;
int idx;
int n_args;
wchar_t **args;
res = translate_get_from_file (trans_file, fd_list, r_flags);
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] = wcsdup (argv[idx]);
if (!args[idx])
return NULL;
}
args[idx] = NULL;
n_args = idx;
for (idx = 0; fd_list[idx].fd != -1; idx++)
{
wchar_t buf[25];
int aidx;
aidx = fd_list[idx].arg_loc;
if (aidx == 0)
continue;
if (aidx >= n_args)
{
fprintf (mystderr, PGM": translation file does not match args\n");
return NULL;
}
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] == L'-' && argv[aidx][1] == L'&')
snwprintf (args[aidx], sizeof (buf), L"-&%d", fd_list[idx].peer_name);
else
snwprintf (args[aidx], sizeof (buf), L"%d", fd_list[idx].peer_name);
}
return args;
}
/* Since GPGME might be installed in a unicode directory it
must be callable with CreateProcessW which provides the
arguments in Unicode form.
So GPGME converts from its internal UTF-8 representation
to wchar, spawns gpgme-w32-spawn with CreateProcessW and then
we also forward this as wchar. */
int
wmain (int argc, const wchar_t * const *argv)
{
int rc = 0;
wchar_t **argv_spawn;
struct spawn_fd_item_s fd_list[MAX_TRANS + 1];
unsigned int flags;
if (argc < 3)
{
rc = 2;
goto leave;
}
#ifdef DEBUG_TO_FILE
mystderr = fopen ("h:/gpgme-w32spawn.log", "w");
#endif
argv_spawn = translate_handles (argv[1], &argv[2], fd_list, &flags);
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, flags);
if (rc < 0)
{
fprintf (mystderr, PGM": executing `%S' failed: %s\n",
argv[0], strerror (errno));
rc = 2;
goto leave;
}
leave:
if (rc)
fprintf (mystderr, PGM": internal error\n");
/* Always try to delete the temporary file. */
if (argc >= 2)
{
if (DeleteFileW (argv[1]) == 0)
fprintf (mystderr, PGM": failed to delete %S: ec=%ld\n",
argv[1], GetLastError ());
}
return rc;
}