![Andre Heinecke](/assets/img/avatar_default.png)
* src/Makefile.am (gpgme_w32spawn_CFLAGS): Add -municode. * src/gpgme-w32-spawn.c (build_commandline, my_spawn) (translate_handles): Convert to wchar_t API. (main): Use wmain instead. -- Some time ago we introduced an inconsistency that w32-util called gpgme-w32-spawn through CreateProcessW but since gpgme-w32-spawn internally worked with 8 bit the chars were mangled and the arguments not passed correctly through the CreateProcessA of the child process. Since the GnuPG processes use GetCommandLineW this is the proper way to pass on Unicode command line arguments. Please note that we did not pass UTF-8 before this patch but rather some broken native encoding where Windows replaces unicode characters with question marks etc. GnuPG-Bug-Id: T6728
506 lines
12 KiB
C
506 lines
12 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 <https://gnu.org/licenses/>.
|
||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||
*/
|
||
|
||
|
||
#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>
|
||
#ifdef HAVE_SYS_TYPES_H
|
||
# include <sys/types.h>
|
||
#endif
|
||
#ifdef HAVE_SYS_STAT_H
|
||
# include <sys/stat.h>
|
||
#endif
|
||
#include <stdint.h>
|
||
#include <process.h>
|
||
|
||
#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;
|
||
}
|