summaryrefslogtreecommitdiff
path: root/src/gpgme-w32spawn.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/gpgme-w32spawn.c')
-rw-r--r--src/gpgme-w32spawn.c501
1 files changed, 501 insertions, 0 deletions
diff --git a/src/gpgme-w32spawn.c b/src/gpgme-w32spawn.c
new file mode 100644
index 0000000..ccf88a9
--- /dev/null
+++ b/src/gpgme-w32spawn.c
@@ -0,0 +1,501 @@
+/* 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>
+#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 <windows.h>
+
+/* Flag values as used by gpgme. */
+#define IOSPAWN_FLAG_ALLOW_SET_FG 1
+
+
+/* Name of this program. */
+#define PGM "gpgme-w32spawn"
+
+
+
+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, 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 */
+ };
+ 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, 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 = 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, PGM": 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, 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 (stderr, 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 (stderr, 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 = 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);
+
+ 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 = LoadLibrary ("user32.dll");
+ if (handle)
+ {
+ func = GetProcAddress (handle, "AllowSetForegroundWindow");
+ if (!func)
+ FreeLibrary (handle);
+ }
+ }
+
+ if (func)
+ {
+ int rc = func (pi.dwProcessId);
+ fprintf (stderr, 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 char *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 = 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)
+ {
+ 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].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, unsigned int *r_flags)
+{
+ int res;
+ int idx;
+ int n_args;
+ char **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] = strdup (argv[idx]);
+ if (!args[idx])
+ return NULL;
+ }
+ args[idx] = NULL;
+ n_args = idx;
+
+ for (idx = 0; fd_list[idx].handle != -1; idx++)
+ {
+ char buf[25];
+ int aidx;
+
+ aidx = fd_list[idx].arg_loc;
+ if (aidx == 0)
+ continue;
+
+ if (aidx >= n_args)
+ {
+ fprintf (stderr, 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] == '-' && 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];
+ unsigned int flags;
+
+ if (argc < 3)
+ {
+ rc = 2;
+ goto leave;
+ }
+
+ 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 (stderr, PGM": executing `%s' failed: %s\n",
+ argv[0], strerror (errno));
+ rc = 2;
+ goto leave;
+ }
+
+ leave:
+ if (rc)
+ fprintf (stderr, PGM": internal error\n");
+ /* Always try to delete the temporary file. */
+ if (argc >= 2)
+ {
+ if (DeleteFile (argv[1]) == 0)
+ fprintf (stderr, PGM": failed to delete %s: ec=%ld\n",
+ argv[1], GetLastError ());
+ }
+ return rc;
+}