json: Fix t-decrypt-verify.out for GnuPG >= 2.3.
[gpgme.git] / src / gpgme-w32spawn.c
1 /* gpgme-w32spawn.c - Wrapper to spawn a process under Windows.
2  * Copyright (C) 2008 g10 Code GmbH
3  *
4  * This file is part of GPGME.
5  *
6  * GPGME is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as
8  * published by the Free Software Foundation; either version 2.1 of
9  * the License, or (at your option) any later version.
10  *
11  * GPGME is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this program; if not, see <https://gnu.org/licenses/>.
18  * SPDX-License-Identifier: LGPL-2.1-or-later
19  */
20
21
22 #ifdef HAVE_CONFIG_H
23 # include <config.h>
24 #endif
25
26 #include <errno.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <fcntl.h>
30 #include <ctype.h>
31 #include <string.h>
32 #ifdef HAVE_SYS_TYPES_H
33 # include <sys/types.h>
34 #endif
35 #ifdef HAVE_SYS_STAT_H
36 # include <sys/stat.h>
37 #endif
38 #include <stdint.h>
39 #include <process.h>
40
41 #include "priv-io.h"
42
43
44 /* Name of this program.  */
45 #define PGM "gpgme-w32spawn"
46
47
48 \f
49 static char *
50 build_commandline (char **argv)
51 {
52   int i;
53   int n = 0;
54   char *buf;
55   char *p;
56
57   /* We have to quote some things because under Windows the program
58      parses the commandline and does some unquoting.  We enclose the
59      whole argument in double-quotes, and escape literal double-quotes
60      as well as backslashes with a backslash.  We end up with a
61      trailing space at the end of the line, but that is harmless.  */
62   for (i = 0; argv[i]; i++)
63     {
64       p = argv[i];
65       /* The leading double-quote.  */
66       n++;
67       while (*p)
68         {
69           /* An extra one for each literal that must be escaped.  */
70           if (*p == '\\' || *p == '"')
71             n++;
72           n++;
73           p++;
74         }
75       /* The trailing double-quote and the delimiter.  */
76       n += 2;
77     }
78   /* And a trailing zero.  */
79   n++;
80
81   buf = p = malloc (n);
82   if (!buf)
83     return NULL;
84   for (i = 0; argv[i]; i++)
85     {
86       char *argvp = argv[i];
87
88       *(p++) = '"';
89       while (*argvp)
90         {
91           if (*argvp == '\\' || *argvp == '"')
92             *(p++) = '\\';
93           *(p++) = *(argvp++);
94         }
95       *(p++) = '"';
96       *(p++) = ' ';
97     }
98   *(p++) = 0;
99
100   return buf;
101 }
102
103
104 int
105 my_spawn (char **argv, struct spawn_fd_item_s *fd_list, unsigned int flags)
106 {
107   SECURITY_ATTRIBUTES sec_attr;
108   PROCESS_INFORMATION pi =
109     {
110       NULL,      /* returns process handle */
111       0,         /* returns primary thread handle */
112       0,         /* returns pid */
113       0          /* returns tid */
114     };
115   STARTUPINFO si;
116   char *envblock = NULL;
117   int cr_flags = CREATE_DEFAULT_ERROR_MODE
118     | GetPriorityClass (GetCurrentProcess ());
119   int i;
120   char *arg_string;
121   int duped_stdin = 0;
122   int duped_stdout = 0;
123   int duped_stderr = 0;
124   HANDLE hnul = INVALID_HANDLE_VALUE;
125
126   i = 0;
127   while (argv[i])
128     {
129       fprintf (stderr, PGM": argv[%2i] = %s\n", i, argv[i]);
130       i++;
131     }
132
133   memset (&sec_attr, 0, sizeof sec_attr);
134   sec_attr.nLength = sizeof sec_attr;
135   sec_attr.bInheritHandle = FALSE;
136
137   arg_string = build_commandline (argv);
138   if (!arg_string)
139     return -1;
140
141   memset (&si, 0, sizeof si);
142   si.cb = sizeof (si);
143   si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
144   si.wShowWindow = (flags & IOSPAWN_FLAG_SHOW_WINDOW) ? SW_SHOW : SW_HIDE;
145   si.hStdInput = GetStdHandle (STD_INPUT_HANDLE);
146   si.hStdOutput = GetStdHandle (STD_OUTPUT_HANDLE);
147   si.hStdError = GetStdHandle (STD_ERROR_HANDLE);
148
149   fprintf (stderr, PGM": spawning: %s\n", arg_string);
150
151   for (i = 0; fd_list[i].fd != -1; i++)
152     {
153       /* The handle already is inheritable.  */
154       if (fd_list[i].dup_to == 0)
155         {
156           si.hStdInput = (HANDLE) fd_list[i].peer_name;
157           duped_stdin = 1;
158           fprintf (stderr, PGM": dup 0x%x to stdin\n", fd_list[i].peer_name);
159         }
160       else if (fd_list[i].dup_to == 1)
161         {
162           si.hStdOutput = (HANDLE) fd_list[i].peer_name;
163           duped_stdout = 1;
164           fprintf (stderr, PGM": dup 0x%x to stdout\n", fd_list[i].peer_name);
165         }
166       else if (fd_list[i].dup_to == 2)
167         {
168           si.hStdError = (HANDLE) fd_list[i].peer_name;
169           duped_stderr = 1;
170           fprintf (stderr, PGM":dup 0x%x to stderr\n", fd_list[i].peer_name);
171         }
172     }
173
174   if (!duped_stdin || !duped_stdout || !duped_stderr)
175     {
176       SECURITY_ATTRIBUTES sa;
177
178       memset (&sa, 0, sizeof sa);
179       sa.nLength = sizeof sa;
180       sa.bInheritHandle = TRUE;
181       hnul = CreateFile ("nul",
182                          GENERIC_READ|GENERIC_WRITE,
183                          FILE_SHARE_READ|FILE_SHARE_WRITE,
184                          &sa,
185                          OPEN_EXISTING,
186                          FILE_ATTRIBUTE_NORMAL,
187                          NULL);
188       if (hnul == INVALID_HANDLE_VALUE)
189         {
190           free (arg_string);
191           /* FIXME: Should translate the error code.  */
192           errno = EIO;
193           return -1;
194         }
195       /* Make sure that the process has a connected stdin.  */
196       if (!duped_stdin)
197         si.hStdInput = hnul;
198       /* Make sure that the process has a connected stdout.  */
199       if (!duped_stdout)
200         si.hStdOutput = hnul;
201       /* We normally don't want all the normal output.  */
202       if (!duped_stderr)
203         si.hStdError = hnul;
204     }
205
206   cr_flags |= CREATE_SUSPENDED;
207   if (!CreateProcessA (argv[0],
208                        arg_string,
209                        &sec_attr,     /* process security attributes */
210                        &sec_attr,     /* thread security attributes */
211                        TRUE,          /* inherit handles */
212                        cr_flags,      /* creation flags */
213                        envblock,      /* environment */
214                        NULL,          /* use current drive/directory */
215                        &si,           /* startup information */
216                        &pi))          /* returns process information */
217     {
218       free (arg_string);
219       /* FIXME: Should translate the error code.  */
220       errno = EIO;
221       return -1;
222     }
223
224   free (arg_string);
225
226   /* Close the /dev/nul handle if used.  */
227   if (hnul != INVALID_HANDLE_VALUE)
228     CloseHandle (hnul);
229
230   for (i = 0; fd_list[i].fd != -1; i++)
231     CloseHandle ((HANDLE) fd_list[i].fd);
232
233   if (flags & IOSPAWN_FLAG_ALLOW_SET_FG)
234     {
235       static int initialized;
236       static BOOL (WINAPI * func)(DWORD);
237       void *handle;
238
239       if (!initialized)
240         {
241           /* Available since W2000; thus we dynload it.  */
242           initialized = 1;
243           handle = LoadLibrary ("user32.dll");
244           if (handle)
245             {
246               func = GetProcAddress (handle, "AllowSetForegroundWindow");
247               if (!func)
248                 FreeLibrary (handle);
249             }
250         }
251
252       if (func)
253         {
254           int rc = func (pi.dwProcessId);
255           fprintf (stderr, PGM": AllowSetForegroundWindow(%d): rc=%d\n",
256                    (int)pi.dwProcessId, rc);
257         }
258     }
259
260   ResumeThread (pi.hThread);
261   CloseHandle (pi.hThread);
262   CloseHandle (pi.hProcess);
263
264   return 0;
265 }
266
267 \f
268 #define MAX_TRANS 10
269
270 int
271 translate_get_from_file (const char *trans_file,
272                          struct spawn_fd_item_s *fd_list,
273                          unsigned int *r_flags)
274 {
275   /* Hold roughly MAX_TRANS triplets of 64 bit numbers in hex
276      notation: "0xFEDCBA9876543210".  10*19*4 - 1 = 759.  This plans
277      ahead for a time when a HANDLE is 64 bit.  */
278 #define BUFFER_MAX 810
279
280   char line[BUFFER_MAX + 1];
281   char *linep;
282   int idx;
283   int res;
284   int fd;
285
286   *r_flags = 0;
287
288   fd = open (trans_file, O_RDONLY);
289   if (fd < 0)
290     return -1;
291
292   /* We always read one line from stdin.  */
293   res = read (fd, line, BUFFER_MAX);
294   close (fd);
295   if (res < 0)
296     return -1;
297
298   line[BUFFER_MAX] = '\0';
299   linep = strchr (line, '\n');
300   if (linep)
301     {
302       if (linep > line && linep[-1] == '\r')
303         linep--;
304       *linep = '\0';
305     }
306   linep = line;
307
308   /* Now start to read mapping pairs.  */
309   for (idx = 0; idx < MAX_TRANS; idx++)
310     {
311       unsigned long from;
312       long dup_to;
313       unsigned long to;
314       unsigned long loc;
315       char *tail;
316
317       /* FIXME: Maybe could use scanf.  */
318       while (isspace (*((unsigned char *)linep)))
319         linep++;
320       if (*linep == '\0')
321         break;
322       if (!idx && *linep == '~')
323         {
324           /* Spawn flags have been passed.  */
325           linep++;
326           *r_flags = strtoul (linep, &tail, 0);
327           if (tail == NULL || ! (*tail == '\0' || isspace (*tail)))
328             break;
329           linep = tail;
330
331           while (isspace (*((unsigned char *)linep)))
332             linep++;
333           if (*linep == '\0')
334             break;
335         }
336
337       from = strtoul (linep, &tail, 0);
338       if (tail == NULL || ! (*tail == '\0' || isspace (*tail)))
339         break;
340       linep = tail;
341
342       while (isspace (*linep))
343         linep++;
344       if (*linep == '\0')
345         break;
346       dup_to = strtol (linep, &tail, 0);
347       if (tail == NULL || ! (*tail == '\0' || isspace (*tail)))
348         break;
349       linep = tail;
350
351       while (isspace (*linep))
352         linep++;
353       if (*linep == '\0')
354         break;
355       to = strtoul (linep, &tail, 0);
356       if (tail == NULL || ! (*tail == '\0' || isspace (*tail)))
357         break;
358       linep = tail;
359
360       while (isspace (*linep))
361         linep++;
362       if (*linep == '\0')
363         break;
364       loc = strtoul (linep, &tail, 0);
365       if (tail == NULL || ! (*tail == '\0' || isspace (*tail)))
366         break;
367       linep = tail;
368
369       fd_list[idx].fd = from;
370       fd_list[idx].dup_to = dup_to;
371       fd_list[idx].peer_name = to;
372       fd_list[idx].arg_loc = loc;
373     }
374   fd_list[idx].fd = -1;
375   fd_list[idx].dup_to = -1;
376   fd_list[idx].peer_name = -1;
377   fd_list[idx].arg_loc = 0;
378   return 0;
379 }
380
381
382 /* Read the translated handles from TRANS_FILE and do a substitution
383    in ARGV.  Returns the new argv and the list of substitutions in
384    FD_LIST (which must be MAX_TRANS+1 large).  */
385 char **
386 translate_handles (const char *trans_file, const char * const *argv,
387                    struct spawn_fd_item_s *fd_list, unsigned int *r_flags)
388 {
389   int res;
390   int idx;
391   int n_args;
392   char **args;
393
394   res = translate_get_from_file (trans_file, fd_list, r_flags);
395   if (res < 0)
396     return NULL;
397
398   for (idx = 0; argv[idx]; idx++)
399     ;
400   args = malloc (sizeof (*args) * (idx + 1));
401   for (idx = 0; argv[idx]; idx++)
402     {
403       args[idx] = strdup (argv[idx]);
404       if (!args[idx])
405         return NULL;
406     }
407   args[idx] = NULL;
408   n_args = idx;
409
410   for (idx = 0; fd_list[idx].fd != -1; idx++)
411     {
412       char buf[25];
413       int aidx;
414
415       aidx = fd_list[idx].arg_loc;
416       if (aidx == 0)
417         continue;
418
419       if (aidx >= n_args)
420         {
421           fprintf (stderr, PGM": translation file does not match args\n");
422           return NULL;
423         }
424
425       args[aidx] = malloc (sizeof (buf));
426       /* We currently disable translation for stdin/stdout/stderr.  We
427          assume that the spawned program handles 0/1/2 specially
428          already.  FIXME: Check if this is true.  */
429       if (!args[idx] || fd_list[idx].dup_to != -1)
430         return NULL;
431
432       /* NOTE: Here is the part where application specific knowledge
433          comes in.  GPGME/GnuPG uses two forms of descriptor
434          specification, a plain number and a "-&" form.  */
435       if (argv[aidx][0] == '-' && argv[aidx][1] == '&')
436         snprintf (args[aidx], sizeof (buf), "-&%d", fd_list[idx].peer_name);
437       else
438         snprintf (args[aidx], sizeof (buf), "%d", fd_list[idx].peer_name);
439     }
440   return args;
441 }
442
443
444 int
445 main (int argc, const char * const *argv)
446 {
447   int rc = 0;
448   char **argv_spawn;
449   struct spawn_fd_item_s fd_list[MAX_TRANS + 1];
450   unsigned int flags;
451
452   if (argc < 3)
453     {
454       rc = 2;
455       goto leave;
456     }
457
458   argv_spawn = translate_handles (argv[1], &argv[2], fd_list, &flags);
459   if (!argv_spawn)
460     {
461       rc = 2;
462       goto leave;
463     }
464
465   /* Using execv does not replace the existing program image, but
466      spawns a new one and daemonizes it, confusing the command line
467      interpreter.  So we have to use spawnv.  */
468   rc = my_spawn (argv_spawn, fd_list, flags);
469   if (rc < 0)
470     {
471       fprintf (stderr, PGM": executing `%s' failed: %s\n",
472                argv[0], strerror (errno));
473       rc = 2;
474       goto leave;
475     }
476
477  leave:
478   if (rc)
479     fprintf (stderr, PGM": internal error\n");
480   /* Always try to delete the temporary file.  */
481   if (argc >= 2)
482     {
483       if (DeleteFile (argv[1]) == 0)
484         fprintf (stderr, PGM": failed to delete %s: ec=%ld\n",
485                  argv[1], GetLastError ());
486     }
487   return rc;
488 }