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