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