gpgme/src/gpgme-w32spawn.c
Andre Heinecke a0a4cd411c
Change gpgme-w32-spawn to unicode
* 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
2023-10-25 17:42:27 +02:00

506 lines
12 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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;
}