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