85bf2b14aa9ba7954cab46e02cbc65ec6468be19
[pinentry.git] / w32 / main.c
1 /* main.c - Secure W32 dialog for PIN entry.
2    Copyright (C) 2004, 2007 g10 Code GmbH
3
4    This program is free software; you can redistribute it and/or
5    modify it under the terms of the GNU General Public License as
6    published by the Free Software Foundation; either version 2 of the
7    License, or (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful, but
10    WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software
16    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
17    02111-1307, USA  */
18
19 #include <config.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #define WINVER 0x0403  /* Required for SendInput.  */
23 #include <windows.h>
24 #ifdef HAVE_W32CE_SYSTEM
25 # include <winioctl.h>
26 # include <sipapi.h>
27 #endif
28
29 #include "pinentry.h"
30 #include "memory.h"
31
32 #include "resource.h"
33 /* #include "msgcodes.h" */
34
35 #define PGMNAME "pinentry-w32"
36
37 #ifndef LSFW_LOCK
38 # define LSFW_LOCK 1
39 # define LSFW_UNLOCK 2
40 #endif
41
42 #ifndef debugfp
43 #define debugfp stderr
44 #endif
45
46
47 /* This function pointer gets initialized in main.  */
48 #ifndef HAVE_W32CE_SYSTEM
49 static BOOL WINAPI (*lock_set_foreground_window)(UINT);
50 #endif
51
52 static int w32_cmd_handler (pinentry_t pe);
53 static void ok_button_clicked (HWND dlg, pinentry_t pe);
54
55
56 /* We use global variables for the state, because there should never
57    ever be a second instance.  */
58 static HWND dialog_handle;
59 static int confirm_mode;
60 static int passphrase_ok;
61 static int confirm_yes;
62
63 /* The file descriptors for the loop.  */
64 static int w32_infd;
65 static int w32_outfd;
66
67
68 /* Connect this module to the pinentry framework.  */
69 pinentry_cmd_handler_t pinentry_cmd_handler = w32_cmd_handler;
70
71
72
73 const char *
74 w32_strerror (int ec)
75 {
76   static char strerr[256];
77
78   if (ec == -1)
79     ec = (int)GetLastError ();
80 #ifdef HAVE_W32CE_SYSTEM
81   /* There is only a wchar_t FormatMessage.  It does not make much
82      sense to play the conversion game; we print only the code.  */
83   snprintf (strerr, sizeof strerr, "ec=%d", ec);
84 #else
85   FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, ec,
86                  MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
87                  strerr, sizeof strerr - 1, NULL);
88 #endif
89   return strerr;
90 }
91
92
93
94 #ifdef HAVE_W32CE_SYSTEM
95 /* Create a pipe.  WRITE_END shall have the opposite value of the one
96    pssed to _assuan_w32ce_prepare_pipe; see there for more
97    details.  */
98 #define GPGCEDEV_IOCTL_MAKE_PIPE                                        \
99   CTL_CODE (FILE_DEVICE_STREAMS, 2049, METHOD_BUFFERED, FILE_ANY_ACCESS)
100 static HANDLE
101 w32ce_finish_pipe (int rvid, int write_end)
102 {
103   HANDLE hd;
104
105   hd = CreateFile (L"GPG1:", write_end? GENERIC_WRITE : GENERIC_READ,
106                    FILE_SHARE_READ | FILE_SHARE_WRITE,
107                    NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL,NULL);
108   if (hd != INVALID_HANDLE_VALUE)
109     {
110       if (!DeviceIoControl (hd, GPGCEDEV_IOCTL_MAKE_PIPE,
111                             &rvid, sizeof rvid, NULL, 0, NULL, NULL))
112         {
113           DWORD lastrc = GetLastError ();
114           CloseHandle (hd);
115           hd = INVALID_HANDLE_VALUE;
116           SetLastError (lastrc);
117         }
118     }
119
120   return hd;
121 }
122 #endif /*HAVE_W32CE_SYSTEM*/
123
124
125 /* static HWND */
126 /* show_window_hierarchy (HWND parent, int level) */
127 /* { */
128 /*   HWND child; */
129
130 /*   child = GetWindow (parent, GW_CHILD); */
131 /*   while (child) */
132 /*     { */
133 /*       char buf[1024+1]; */
134 /*       char name[200]; */
135 /*       int nname; */
136 /*       char *pname; */
137
138 /*       memset (buf, 0, sizeof (buf)); */
139 /*       GetWindowText (child, buf, sizeof (buf)-1); */
140 /*       nname = GetClassName (child, name, sizeof (name)-1); */
141 /*       if (nname) */
142 /*         pname = name; */
143 /*       else */
144 /*         pname = NULL; */
145 /*       fprintf (debugfp, "### %*shwnd=%p (%s) `%s'\n", level*2, "", child, */
146 /*                pname? pname:"", buf); */
147 /*       show_window_hierarchy (child, level+1); */
148 /*       child = GetNextWindow (child, GW_HWNDNEXT);     */
149 /*     } */
150
151 /*   return NULL; */
152 /* } */
153
154
155
156 /* Convert a wchar to UTF8.  Caller needs to release the string.
157    Returns NULL on error. */
158 static char *
159 wchar_to_utf8 (const wchar_t *string, size_t len, int secure)
160 {
161   int n;
162   char *result;
163
164   /* Note, that CP_UTF8 is not defined in Windows versions earlier
165      than NT.  */
166   n = WideCharToMultiByte (CP_UTF8, 0, string, len, NULL, 0, NULL, NULL);
167   if (n < 0)
168     return NULL;
169
170   result = secure? secmem_malloc (n+1) : malloc (n+1);
171   if (!result)
172     return NULL;
173   n = WideCharToMultiByte (CP_UTF8, 0, string, len, result, n, NULL, NULL);
174   if (n < 0)
175     {
176       if (secure)
177         secmem_free (result);
178       else
179         free (result);
180       return NULL;
181     }
182   return result;
183 }
184
185
186 /* Convert a UTF8 string to wchar.  Returns NULL on error. Caller
187    needs to free the returned value.  */
188 wchar_t *
189 utf8_to_wchar (const char *string)
190 {
191   int n;
192   wchar_t *result;
193   size_t len = strlen (string);
194
195   n = MultiByteToWideChar (CP_UTF8, 0, string, len, NULL, 0);
196   if (n < 0)
197     return NULL;
198
199   result = calloc ((n+1), sizeof *result);
200   if (!result)
201     return NULL;
202   n = MultiByteToWideChar (CP_UTF8, 0, string, len, result, n);
203   if (n < 0)
204     {
205       free (result);
206       return NULL;
207     }
208   result[n] = 0;
209   return result;
210 }
211
212
213 /* Raise the software input panel.  */
214 static void
215 raise_sip (HWND dlg)
216 {
217 #ifdef HAVE_W32CE_SYSTEM
218   SIPINFO si;
219
220   SetForegroundWindow (dlg);
221
222   memset (&si, 0, sizeof si);
223   si.cbSize = sizeof si;
224
225   if (SipGetInfo (&si))
226     {
227       si.fdwFlags |= SIPF_ON;
228       SipSetInfo (&si);
229     }
230 #else
231   (void)dlg;
232 #endif
233 }
234
235 /* Center the window CHILDWND with the desktop as its parent
236    window.  STYLE is passed as second arg to SetWindowPos.*/
237 static void
238 center_window (HWND childwnd, HWND style)
239 {
240 #ifndef HAVE_W32CE_SYSTEM
241   HWND parwnd;
242   RECT rchild, rparent;
243   HDC hdc;
244   int wchild, hchild, wparent, hparent;
245   int wscreen, hscreen, xnew, ynew;
246   int flags = SWP_NOSIZE | SWP_NOZORDER;
247
248   parwnd = GetDesktopWindow ();
249   GetWindowRect (childwnd, &rchild);
250   wchild = rchild.right - rchild.left;
251   hchild = rchild.bottom - rchild.top;
252
253   GetWindowRect (parwnd, &rparent);
254   wparent = rparent.right - rparent.left;
255   hparent = rparent.bottom - rparent.top;
256
257   hdc = GetDC (childwnd);
258   wscreen = GetDeviceCaps (hdc, HORZRES);
259   hscreen = GetDeviceCaps (hdc, VERTRES);
260   ReleaseDC (childwnd, hdc);
261   xnew = rparent.left + ((wparent - wchild) / 2);
262   if (xnew < 0)
263     xnew = 0;
264   else if ((xnew+wchild) > wscreen)
265     xnew = wscreen - wchild;
266   ynew = rparent.top  + ((hparent - hchild) / 2);
267   if (ynew < 0)
268     ynew = 0;
269   else if ((ynew+hchild) > hscreen)
270     ynew = hscreen - hchild;
271   if (style == HWND_TOPMOST || style == HWND_NOTOPMOST)
272     flags = SWP_NOMOVE | SWP_NOSIZE;
273   SetWindowPos (childwnd, style? style : NULL, xnew, ynew, 0, 0, flags);
274 #endif
275 }
276
277
278
279 static void
280 move_mouse_and_click (HWND hwnd)
281 {
282 #ifndef HAVE_W32CE_SYSTEM
283   RECT rect;
284   HDC hdc;
285   int wscreen, hscreen, x, y, normx, normy;
286   INPUT inp[3];
287   int idx;
288
289   hdc = GetDC (hwnd);
290   wscreen = GetDeviceCaps (hdc, HORZRES);
291   hscreen = GetDeviceCaps (hdc, VERTRES);
292   ReleaseDC (hwnd, hdc);
293   if (wscreen < 10 || hscreen < 10)
294     return;
295
296   GetWindowRect (hwnd, &rect);
297   x = rect.left;
298   y = rect.bottom;
299
300   normx = x * (65535 / wscreen);
301   if (normx < 0 || normx > 65535)
302     return;
303   normy = y * (65535 / hscreen);
304   if (normy < 0 || normy > 65535)
305     return;
306
307   for (idx=0; idx < 3; idx++)
308     memset (&inp[idx], 0, sizeof inp[idx]);
309
310   idx=0;
311   inp[idx].type = INPUT_MOUSE;
312   inp[idx].mi.dx = normx;
313   inp[idx].mi.dy = normy;
314   inp[idx].mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
315   idx++;
316
317   inp[idx].type = INPUT_MOUSE;
318   inp[idx].mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
319   idx++;
320
321   inp[idx].type = INPUT_MOUSE;
322   inp[idx].mi.dwFlags = MOUSEEVENTF_LEFTUP;
323   idx++;
324
325   if ( (SendInput (idx, inp, sizeof (INPUT)) != idx) && debugfp)
326     fprintf (debugfp, "SendInput failed: %s\n", w32_strerror (-1));
327 #endif
328 }
329
330
331
332 /* Resize the button so that STRING fits into it.   */
333 static void
334 resize_button (HWND hwnd, const char *string)
335 {
336   if (!hwnd)
337     return;
338
339   /* FIXME: Need to figure out how to convert dialog coorddnates to
340      screen coordinates and how buttons should be placed.  */
341 /*   SetWindowPos (hbutton, NULL, */
342 /*                 10, 180,  */
343 /*                 strlen (string+2), 14, */
344 /*                 (SWP_NOZORDER)); */
345 }
346
347
348
349
350 \f
351 /* Call SetDlgItemTextW with an UTF8 string.  */
352 static void
353 set_dlg_item_text (HWND dlg, int item, const char *string)
354 {
355   if (!string || !*string)
356     SetDlgItemTextW (dlg, item, L"");
357   else
358     {
359       wchar_t *wbuf;
360
361       wbuf = utf8_to_wchar (string);
362       if (!wbuf)
363         SetDlgItemTextW (dlg, item, L"[out of core]");
364       else
365         {
366           SetDlgItemTextW (dlg, item, wbuf);
367           free (wbuf);
368         }
369     }
370 }
371
372
373 /* Dialog processing loop.  */
374 static BOOL CALLBACK
375 dlg_proc (HWND dlg, UINT msg, WPARAM wparam, LPARAM lparam)
376 {
377   static pinentry_t pe;
378   static int item;
379
380
381 /*   { */
382 /*     int idx; */
383
384 /*     for (idx=0; msgcodes[idx].string; idx++) */
385 /*       if (msg == msgcodes[idx].msg) */
386 /*         break; */
387 /*     if (msgcodes[idx].string) */
388 /*       fprintf (debugfp, "received %s\n", msgcodes[idx].string); */
389 /*     else */
390 /*       fprintf (debugfp, "received WM_%u\n", msg); */
391 /*   } */
392
393   switch (msg)
394     {
395     case WM_INITDIALOG:
396       dialog_handle = dlg;
397       pe = (pinentry_t)lparam;
398       if (!pe)
399         abort ();
400       set_dlg_item_text (dlg, IDC_PINENT_PROMPT, pe->prompt);
401       set_dlg_item_text (dlg, IDC_PINENT_DESC, pe->description);
402       set_dlg_item_text (dlg, IDC_PINENT_TEXT, "");
403       if (pe->ok)
404         {
405           set_dlg_item_text (dlg, IDOK, pe->ok);
406           resize_button (GetDlgItem (dlg, IDOK), pe->ok);
407         }
408       if (pe->cancel)
409         {
410           set_dlg_item_text (dlg, IDCANCEL, pe->cancel);
411           resize_button (GetDlgItem (dlg, IDCANCEL), pe->cancel);
412         }
413       if (pe->error)
414         set_dlg_item_text (dlg, IDC_PINENT_ERR, pe->error);
415
416       if (confirm_mode)
417         {
418           EnableWindow (GetDlgItem (dlg, IDC_PINENT_TEXT), FALSE);
419           SetWindowPos (GetDlgItem (dlg, IDC_PINENT_TEXT), NULL, 0, 0, 0, 0,
420                         (SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_HIDEWINDOW));
421
422           item = IDOK;
423         }
424       else
425         item = IDC_PINENT_TEXT;
426
427       center_window (dlg, HWND_TOP);
428
429       /* Unfortunately we can't use SetForegroundWindow because there
430          is no easy eay to have all the calling processes do an
431          AllowSetForegroundWindow.  What we do instead is to bad hack
432          by simulating a click to the Window. */
433 /*       if (SetForegroundWindow (dlg) && lock_set_foreground_window) */
434 /*         { */
435 /*           lock_set_foreground_window (LSFW_LOCK); */
436 /*         } */
437
438 /*       show_window_hierarchy (GetDesktopWindow (), 0); */
439
440       ShowWindow (dlg, SW_SHOW);
441       move_mouse_and_click ( GetDlgItem (dlg, IDC_PINENT_PROMPT) );
442       raise_sip (dlg);
443       break;
444
445     case WM_COMMAND:
446       switch (LOWORD (wparam))
447         {
448         case IDOK:
449           if (confirm_mode)
450             confirm_yes = 1;
451           else
452             ok_button_clicked (dlg, pe);
453           EndDialog (dlg, TRUE);
454           break;
455
456         case IDCANCEL:
457           pe->result = -1;
458           EndDialog (dlg, FALSE);
459           break;
460         }
461       break;
462
463      case WM_KEYDOWN:
464        if (wparam == VK_RETURN)
465          {
466            if (confirm_mode)
467              confirm_yes = 1;
468            else
469              ok_button_clicked (dlg, pe);
470            EndDialog (dlg, TRUE);
471          }
472        break;
473
474     }
475   return FALSE;
476 }
477
478
479 /* The okay button has been clicked or the enter enter key in the text
480    field.  */
481 static void
482 ok_button_clicked (HWND dlg, pinentry_t pe)
483 {
484   char *s_utf8;
485   wchar_t *w_buffer;
486   size_t w_buffer_size = 255;
487   unsigned int nchar;
488
489   pe->locale_err = 1;
490   w_buffer = secmem_malloc ((w_buffer_size + 1) * sizeof *w_buffer);
491   if (!w_buffer)
492     return;
493
494   nchar = GetDlgItemTextW (dlg, IDC_PINENT_TEXT, w_buffer, w_buffer_size);
495   s_utf8 = wchar_to_utf8 (w_buffer, nchar, 1);
496   secmem_free (w_buffer);
497   if (s_utf8)
498     {
499       passphrase_ok = 1;
500       pinentry_setbufferlen (pe, strlen (s_utf8) + 1);
501       if (pe->pin)
502         strcpy (pe->pin, s_utf8);
503       secmem_free (s_utf8);
504       pe->locale_err = 0;
505       pe->result = pe->pin? strlen (pe->pin) : 0;
506     }
507 }
508
509
510 static int
511 w32_cmd_handler (pinentry_t pe)
512 {
513 /*   HWND lastwindow = GetForegroundWindow (); */
514
515   confirm_mode = !pe->pin;
516
517   passphrase_ok = confirm_yes = 0;
518
519   dialog_handle = NULL;
520   DialogBoxParam (GetModuleHandle (NULL), MAKEINTRESOURCE (IDD_PINENT),
521                   GetDesktopWindow (), dlg_proc, (LPARAM)pe);
522   if (dialog_handle)
523     {
524 /*       if (lock_set_foreground_window) */
525 /*         lock_set_foreground_window (LSFW_UNLOCK); */
526 /*       if (lastwindow) */
527 /*         SetForegroundWindow (lastwindow); */
528     }
529   else
530     return -1;
531
532   if (confirm_mode)
533     return confirm_yes;
534   else if (passphrase_ok && pe->pin)
535     return strlen (pe->pin);
536   else
537     return -1;
538 }
539
540
541 /* WindowsCE uses a very strange way of handling the standard streams.
542    There is a function SetStdioPath to associate a standard stream
543    with a file or a device but what we really want is to use pipes as
544    standard streams.  Despite that we implement pipes using a device,
545    we would have some limitations on the number of open pipes due to
546    the 3 character limit of device file name.  Thus we don't take this
547    path.  Another option would be to install a file system driver with
548    support for pipes; this would allow us to get rid of the device
549    name length limitation.  However, with GnuPG we can get away be
550    redefining the standard streams and passing the handles to be used
551    on the command line.  This has also the advantage that it makes
552    creating a process much easier and does not require the
553    SetStdioPath set and restore game.  The caller needs to pass the
554    rendezvous ids using up to three options:
555
556      -&S0=<rvid> -&S1=<rvid> -&S2=<rvid>
557
558    They are all optional but they must be the first arguments on the
559    command line.  Parsing stops as soon as an invalid option is found.
560    These rendezvous ids are then used to finish the pipe creation.*/
561 #ifdef HAVE_W32CE_SYSTEM
562 static void
563 parse_std_file_handles (int *argcp, char ***argvp)
564 {
565   int argc = *argcp;
566   char **argv = *argvp;
567   const char *s;
568   int fd;
569   int i;
570   int fixup = 0;
571
572   if (!argc)
573     return;
574
575   for (argc--, argv++; argc; argc--, argv++)
576     {
577       s = *argv;
578       if (*s == '-' && s[1] == '&' && s[2] == 'S'
579           && (s[3] == '0' || s[3] == '1' || s[3] == '2')
580           && s[4] == '='
581           && (strchr ("-01234567890", s[5]) || !strcmp (s+5, "null")))
582         {
583           if (s[5] == 'n')
584             fd = (int)(-1);
585           else
586             fd = (int)w32ce_finish_pipe (atoi (s+5), s[3] != '0');
587           if (s[3] == '0' && fd != -1)
588             w32_infd = fd;
589           else if (s[3] == '1' && fd != -1)
590             w32_outfd = fd;
591           fixup++;
592         }
593       else
594         break;
595     }
596
597   if (fixup)
598     {
599       argc = *argcp;
600       argc -= fixup;
601       *argcp = argc;
602
603       argv = *argvp;
604       for (i=1; i < argc; i++)
605         argv[i] = argv[i + fixup];
606       for (; i < argc + fixup; i++)
607         argv[i] = NULL;
608     }
609
610
611 }
612 #endif /*HAVE_W32CE_SYSTEM*/
613
614
615 int
616 main (int argc, char **argv)
617 {
618 #ifndef HAVE_W32CE_SYSTEM
619   void *handle;
620 #endif
621
622   w32_infd = STDIN_FILENO;
623   w32_outfd = STDOUT_FILENO;
624
625 #ifdef HAVE_W32CE_SYSTEM
626   parse_std_file_handles (&argc, &argv);
627 #endif
628
629   pinentry_init (PGMNAME);
630
631   /* Consumes all arguments.  */
632   if (pinentry_parse_opts (argc, argv))
633     exit (EXIT_SUCCESS);
634
635 /*   debugfp = fopen ("pinentry.log", "w"); */
636 /*   if (!debugfp) */
637 /*     debugfp = stderr; */
638
639   /* We need to load a function because that one is only available
640      since W2000 but not in older NTs.  */
641 #ifndef HAVE_W32CE_SYSTEM
642   handle = LoadLibrary ("user32.dll");
643   if (handle)
644     {
645       void *foo;
646       foo = GetProcAddress (handle, "LockSetForegroundWindow");
647       if (foo)
648         lock_set_foreground_window = foo;
649       else
650         CloseHandle (handle);
651     }
652 #endif
653
654   if (pinentry_loop2 (w32_infd, w32_outfd))
655     return 1;
656
657 #ifdef HAVE_W32CE_SYSTEM
658   Sleep (400);
659 #endif
660   return 0;
661 }