Merge branch 'master' of ssh+git://playfair.gnupg.org/git/gpgme
[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://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
125   i = 0;
126   while (argv[i])
127     {
128       fprintf (stderr, PGM": argv[%2i] = %s\n", i, argv[i]);
129       i++;
130     }
131
132   memset (&sec_attr, 0, sizeof sec_attr);
133   sec_attr.nLength = sizeof sec_attr;
134   sec_attr.bInheritHandle = FALSE;
135
136   arg_string = build_commandline (argv);
137   if (!arg_string)
138     return -1;
139
140   memset (&si, 0, sizeof si);
141   si.cb = sizeof (si);
142   si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
143   si.wShowWindow = (flags & IOSPAWN_FLAG_SHOW_WINDOW) ? SW_SHOW : SW_HIDE;
144   si.hStdInput = GetStdHandle (STD_INPUT_HANDLE);
145   si.hStdOutput = GetStdHandle (STD_OUTPUT_HANDLE);
146   si.hStdError = GetStdHandle (STD_ERROR_HANDLE);
147
148   fprintf (stderr, PGM": spawning: %s\n", arg_string);
149
150   for (i = 0; fd_list[i].fd != -1; i++)
151     {
152       /* The handle already is inheritable.  */
153       if (fd_list[i].dup_to == 0)
154         {
155           si.hStdInput = (HANDLE) fd_list[i].peer_name;
156           duped_stdin = 1;
157           fprintf (stderr, PGM": dup 0x%x to stdin\n", fd_list[i].peer_name);
158         }
159       else if (fd_list[i].dup_to == 1)
160         {
161           si.hStdOutput = (HANDLE) fd_list[i].peer_name;
162           duped_stdout = 1;
163           fprintf (stderr, PGM": dup 0x%x to stdout\n", fd_list[i].peer_name);
164         }
165       else if (fd_list[i].dup_to == 2)
166         {
167           si.hStdError = (HANDLE) fd_list[i].peer_name;
168           duped_stderr = 1;
169           fprintf (stderr, PGM":dup 0x%x to stderr\n", fd_list[i].peer_name);
170         }
171     }
172
173   if (!duped_stdin || !duped_stdout || !duped_stderr)
174     {
175       SECURITY_ATTRIBUTES sa;
176
177       memset (&sa, 0, sizeof sa);
178       sa.nLength = sizeof sa;
179       sa.bInheritHandle = TRUE;
180       hnul = CreateFile ("nul",
181                          GENERIC_READ|GENERIC_WRITE,
182                          FILE_SHARE_READ|FILE_SHARE_WRITE,
183                          &sa,
184                          OPEN_EXISTING,
185                          FILE_ATTRIBUTE_NORMAL,
186                          NULL);
187       if (hnul == INVALID_HANDLE_VALUE)
188         {
189           free (arg_string);
190           /* FIXME: Should translate the error code.  */
191           errno = EIO;
192           return -1;
193         }
194       /* Make sure that the process has a connected stdin.  */
195       if (!duped_stdin)
196         si.hStdInput = hnul;
197       /* Make sure that the process has a connected stdout.  */
198       if (!duped_stdout)
199         si.hStdOutput = hnul;
200       /* We normally don't want all the normal output.  */
201       if (!duped_stderr)
202         si.hStdError = hnul;
203     }
204
205   cr_flags |= CREATE_SUSPENDED;
206   if (!CreateProcessA (argv[0],
207                        arg_string,
208                        &sec_attr,     /* process security attributes */
209                        &sec_attr,     /* thread security attributes */
210                        TRUE,          /* inherit handles */
211                        cr_flags,      /* creation flags */
212                        envblock,      /* environment */
213                        NULL,          /* use current drive/directory */
214                        &si,           /* startup information */
215                        &pi))          /* returns process information */
216     {
217       free (arg_string);
218       /* FIXME: Should translate the error code.  */
219       errno = EIO;
220       return -1;
221     }
222
223   free (arg_string);
224
225   /* Close the /dev/nul handle if used.  */
226   if (hnul != INVALID_HANDLE_VALUE)
227     CloseHandle (hnul);
228
229   for (i = 0; fd_list[i].fd != -1; i++)
230     CloseHandle ((HANDLE) fd_list[i].fd);
231
232   if (flags & IOSPAWN_FLAG_ALLOW_SET_FG)
233     {
234       static int initialized;
235       static BOOL (WINAPI * func)(DWORD);
236       void *handle;
237
238       if (!initialized)
239         {
240           /* Available since W2000; thus we dynload it.  */
241           initialized = 1;
242           handle = LoadLibrary ("user32.dll");
243           if (handle)
244             {
245               func = GetProcAddress (handle, "AllowSetForegroundWindow");
246               if (!func)
247                 FreeLibrary (handle);
248             }
249         }
250
251       if (func)
252         {
253           int rc = func (pi.dwProcessId);
254           fprintf (stderr, PGM": AllowSetForegroundWindow(%d): rc=%d\n",
255                    (int)pi.dwProcessId, rc);
256         }
257     }
258
259   ResumeThread (pi.hThread);
260   CloseHandle (pi.hThread);
261   CloseHandle (pi.hProcess);
262
263   return 0;
264 }
265
266 \f
267 #define MAX_TRANS 10
268
269 int
270 translate_get_from_file (const char *trans_file,
271                          struct spawn_fd_item_s *fd_list,
272                          unsigned int *r_flags)
273 {
274   /* Hold roughly MAX_TRANS triplets of 64 bit numbers in hex
275      notation: "0xFEDCBA9876543210".  10*19*4 - 1 = 759.  This plans
276      ahead for a time when a HANDLE is 64 bit.  */
277 #define BUFFER_MAX 810
278
279   char line[BUFFER_MAX + 1];
280   char *linep;
281   int idx;
282   int res;
283   int fd;
284
285   *r_flags = 0;
286
287   fd = open (trans_file, O_RDONLY);
288   if (fd < 0)
289     return -1;
290
291   /* We always read one line from stdin.  */
292   res = read (fd, line, BUFFER_MAX);
293   close (fd);
294   if (res < 0)
295     return -1;
296
297   line[BUFFER_MAX] = '\0';
298   linep = strchr (line, '\n');
299   if (linep)
300     {
301       if (linep > line && linep[-1] == '\r')
302         linep--;
303       *linep = '\0';
304     }
305   linep = line;
306
307   /* Now start to read mapping pairs.  */
308   for (idx = 0; idx < MAX_TRANS; idx++)
309     {
310       unsigned long from;
311       long dup_to;
312       unsigned long to;
313       unsigned long loc;
314       char *tail;
315
316       /* FIXME: Maybe could use scanf.  */
317       while (isspace (*((unsigned char *)linep)))
318         linep++;
319       if (*linep == '\0')
320         break;
321       if (!idx && *linep == '~')
322         {
323           /* Spawn flags have been passed.  */
324           linep++;
325           *r_flags = strtoul (linep, &tail, 0);
326           if (tail == NULL || ! (*tail == '\0' || isspace (*tail)))
327             break;
328           linep = tail;
329
330           while (isspace (*((unsigned char *)linep)))
331             linep++;
332           if (*linep == '\0')
333             break;
334         }
335
336       from = strtoul (linep, &tail, 0);
337       if (tail == NULL || ! (*tail == '\0' || isspace (*tail)))
338         break;
339       linep = tail;
340
341       while (isspace (*linep))
342         linep++;
343       if (*linep == '\0')
344         break;
345       dup_to = strtol (linep, &tail, 0);
346       if (tail == NULL || ! (*tail == '\0' || isspace (*tail)))
347         break;
348       linep = tail;
349
350       while (isspace (*linep))
351         linep++;
352       if (*linep == '\0')
353         break;
354       to = strtoul (linep, &tail, 0);
355       if (tail == NULL || ! (*tail == '\0' || isspace (*tail)))
356         break;
357       linep = tail;
358
359       while (isspace (*linep))
360         linep++;
361       if (*linep == '\0')
362         break;
363       loc = strtoul (linep, &tail, 0);
364       if (tail == NULL || ! (*tail == '\0' || isspace (*tail)))
365         break;
366       linep = tail;
367
368       fd_list[idx].fd = from;
369       fd_list[idx].dup_to = dup_to;
370       fd_list[idx].peer_name = to;
371       fd_list[idx].arg_loc = loc;
372     }
373   fd_list[idx].fd = -1;
374   fd_list[idx].dup_to = -1;
375   fd_list[idx].peer_name = -1;
376   fd_list[idx].arg_loc = 0;
377   return 0;
378 }
379
380
381 /* Read the translated handles from TRANS_FILE and do a substitution
382    in ARGV.  Returns the new argv and the list of substitutions in
383    FD_LIST (which must be MAX_TRANS+1 large).  */
384 char **
385 translate_handles (const char *trans_file, const char * const *argv,
386                    struct spawn_fd_item_s *fd_list, unsigned int *r_flags)
387 {
388   int res;
389   int idx;
390   int n_args;
391   char **args;
392
393   res = translate_get_from_file (trans_file, fd_list, r_flags);
394   if (res < 0)
395     return NULL;
396
397   for (idx = 0; argv[idx]; idx++)
398     ;
399   args = malloc (sizeof (*args) * (idx + 1));
400   for (idx = 0; argv[idx]; idx++)
401     {
402       args[idx] = strdup (argv[idx]);
403       if (!args[idx])
404         return NULL;
405     }
406   args[idx] = NULL;
407   n_args = idx;
408
409   for (idx = 0; fd_list[idx].fd != -1; idx++)
410     {
411       char buf[25];
412       int aidx;
413
414       aidx = fd_list[idx].arg_loc;
415       if (aidx == 0)
416         continue;
417
418       if (aidx >= n_args)
419         {
420           fprintf (stderr, PGM": translation file does not match args\n");
421           return NULL;
422         }
423
424       args[aidx] = malloc (sizeof (buf));
425       /* We currently disable translation for stdin/stdout/stderr.  We
426          assume that the spawned program handles 0/1/2 specially
427          already.  FIXME: Check if this is true.  */
428       if (!args[idx] || fd_list[idx].dup_to != -1)
429         return NULL;
430
431       /* NOTE: Here is the part where application specific knowledge
432          comes in.  GPGME/GnuPG uses two forms of descriptor
433          specification, a plain number and a "-&" form.  */
434       if (argv[aidx][0] == '-' && argv[aidx][1] == '&')
435         snprintf (args[aidx], sizeof (buf), "-&%d", fd_list[idx].peer_name);
436       else
437         snprintf (args[aidx], sizeof (buf), "%d", fd_list[idx].peer_name);
438     }
439   return args;
440 }
441
442
443 int
444 main (int argc, const char * const *argv)
445 {
446   int rc = 0;
447   char **argv_spawn;
448   struct spawn_fd_item_s fd_list[MAX_TRANS + 1];
449   unsigned int flags;
450
451   if (argc < 3)
452     {
453       rc = 2;
454       goto leave;
455     }
456
457   argv_spawn = translate_handles (argv[1], &argv[2], fd_list, &flags);
458   if (!argv_spawn)
459     {
460       rc = 2;
461       goto leave;
462     }
463
464   /* Using execv does not replace the existing program image, but
465      spawns a new one and daemonizes it, confusing the command line
466      interpreter.  So we have to use spawnv.  */
467   rc = my_spawn (argv_spawn, fd_list, flags);
468   if (rc < 0)
469     {
470       fprintf (stderr, PGM": executing `%s' failed: %s\n",
471                argv[0], strerror (errno));
472       rc = 2;
473       goto leave;
474     }
475
476  leave:
477   if (rc)
478     fprintf (stderr, PGM": internal error\n");
479   /* Always try to delete the temporary file.  */
480   if (argc >= 2)
481     {
482       if (DeleteFile (argv[1]) == 0)
483         fprintf (stderr, PGM": failed to delete %s: ec=%ld\n",
484                  argv[1], GetLastError ());
485     }
486   return rc;
487 }