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