1 /*
2 * windlg.c - dialogs for PuTTY(tel), including the configuration dialog.
3 */
4
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <limits.h>
8 #include <assert.h>
9 #include <ctype.h>
10 #include <time.h>
11
12 #include "putty.h"
13 #include "ssh.h"
14 #include "win_res.h"
15 #include "winseat.h"
16 #include "storage.h"
17 #include "dialog.h"
18 #include "licence.h"
19
20 #include <commctrl.h>
21 #include <commdlg.h>
22 #include <shellapi.h>
23
24 #ifdef MSVC4
25 #define TVINSERTSTRUCT TV_INSERTSTRUCT
26 #define TVITEM TV_ITEM
27 #define ICON_BIG 1
28 #endif
29
30 /*
31 * These are the various bits of data required to handle the
32 * portable-dialog stuff in the config box. Having them at file
33 * scope in here isn't too bad a place to put them; if we were ever
34 * to need more than one config box per process we could always
35 * shift them to a per-config-box structure stored in GWL_USERDATA.
36 */
37 static struct controlbox *ctrlbox;
38 /*
39 * ctrls_base holds the OK and Cancel buttons: the controls which
40 * are present in all dialog panels. ctrls_panel holds the ones
41 * which change from panel to panel.
42 */
43 static struct winctrls ctrls_base, ctrls_panel;
44 static struct dlgparam dp;
45
46 #define LOGEVENT_INITIAL_MAX 128
47 #define LOGEVENT_CIRCULAR_MAX 128
48
49 static char *events_initial[LOGEVENT_INITIAL_MAX];
50 static char *events_circular[LOGEVENT_CIRCULAR_MAX];
51 static int ninitial = 0, ncircular = 0, circular_first = 0;
52
53 #define PRINTER_DISABLED_STRING "None (printing disabled)"
54
force_normal(HWND hwnd)55 void force_normal(HWND hwnd)
56 {
57 static bool recurse = false;
58
59 WINDOWPLACEMENT wp;
60
61 if (recurse)
62 return;
63 recurse = true;
64
65 wp.length = sizeof(wp);
66 if (GetWindowPlacement(hwnd, &wp) && wp.showCmd == SW_SHOWMAXIMIZED) {
67 wp.showCmd = SW_SHOWNORMAL;
68 SetWindowPlacement(hwnd, &wp);
69 }
70 recurse = false;
71 }
72
getevent(int i)73 static char *getevent(int i)
74 {
75 if (i < ninitial)
76 return events_initial[i];
77 if ((i -= ninitial) < ncircular)
78 return events_circular[(circular_first + i) % LOGEVENT_CIRCULAR_MAX];
79 return NULL;
80 }
81
82 static HWND logbox;
event_log_window(void)83 HWND event_log_window(void) { return logbox; }
84
LogProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)85 static INT_PTR CALLBACK LogProc(HWND hwnd, UINT msg,
86 WPARAM wParam, LPARAM lParam)
87 {
88 int i;
89
90 switch (msg) {
91 case WM_INITDIALOG: {
92 char *str = dupprintf("%s Event Log", appname);
93 SetWindowText(hwnd, str);
94 sfree(str);
95
96 static int tabs[4] = { 78, 108 };
97 SendDlgItemMessage(hwnd, IDN_LIST, LB_SETTABSTOPS, 2,
98 (LPARAM) tabs);
99
100 for (i = 0; i < ninitial; i++)
101 SendDlgItemMessage(hwnd, IDN_LIST, LB_ADDSTRING,
102 0, (LPARAM) events_initial[i]);
103 for (i = 0; i < ncircular; i++)
104 SendDlgItemMessage(hwnd, IDN_LIST, LB_ADDSTRING,
105 0, (LPARAM) events_circular[(circular_first + i) % LOGEVENT_CIRCULAR_MAX]);
106 return 1;
107 }
108 case WM_COMMAND:
109 switch (LOWORD(wParam)) {
110 case IDOK:
111 case IDCANCEL:
112 logbox = NULL;
113 SetActiveWindow(GetParent(hwnd));
114 DestroyWindow(hwnd);
115 return 0;
116 case IDN_COPY:
117 if (HIWORD(wParam) == BN_CLICKED ||
118 HIWORD(wParam) == BN_DOUBLECLICKED) {
119 int selcount;
120 int *selitems;
121 selcount = SendDlgItemMessage(hwnd, IDN_LIST,
122 LB_GETSELCOUNT, 0, 0);
123 if (selcount == 0) { /* don't even try to copy zero items */
124 MessageBeep(0);
125 break;
126 }
127
128 selitems = snewn(selcount, int);
129 if (selitems) {
130 int count = SendDlgItemMessage(hwnd, IDN_LIST,
131 LB_GETSELITEMS,
132 selcount,
133 (LPARAM) selitems);
134 int i;
135 int size;
136 char *clipdata;
137 static unsigned char sel_nl[] = SEL_NL;
138
139 if (count == 0) { /* can't copy zero stuff */
140 MessageBeep(0);
141 break;
142 }
143
144 size = 0;
145 for (i = 0; i < count; i++)
146 size +=
147 strlen(getevent(selitems[i])) + sizeof(sel_nl);
148
149 clipdata = snewn(size, char);
150 if (clipdata) {
151 char *p = clipdata;
152 for (i = 0; i < count; i++) {
153 char *q = getevent(selitems[i]);
154 int qlen = strlen(q);
155 memcpy(p, q, qlen);
156 p += qlen;
157 memcpy(p, sel_nl, sizeof(sel_nl));
158 p += sizeof(sel_nl);
159 }
160 write_aclip(CLIP_SYSTEM, clipdata, size, true);
161 sfree(clipdata);
162 }
163 sfree(selitems);
164
165 for (i = 0; i < (ninitial + ncircular); i++)
166 SendDlgItemMessage(hwnd, IDN_LIST, LB_SETSEL,
167 false, i);
168 }
169 }
170 return 0;
171 }
172 return 0;
173 case WM_CLOSE:
174 logbox = NULL;
175 SetActiveWindow(GetParent(hwnd));
176 DestroyWindow(hwnd);
177 return 0;
178 }
179 return 0;
180 }
181
LicenceProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)182 static INT_PTR CALLBACK LicenceProc(HWND hwnd, UINT msg,
183 WPARAM wParam, LPARAM lParam)
184 {
185 switch (msg) {
186 case WM_INITDIALOG: {
187 char *str = dupprintf("%s Licence", appname);
188 SetWindowText(hwnd, str);
189 sfree(str);
190 SetDlgItemText(hwnd, IDA_TEXT, LICENCE_TEXT("\r\n\r\n"));
191 return 1;
192 }
193 case WM_COMMAND:
194 switch (LOWORD(wParam)) {
195 case IDOK:
196 case IDCANCEL:
197 EndDialog(hwnd, 1);
198 return 0;
199 }
200 return 0;
201 case WM_CLOSE:
202 EndDialog(hwnd, 1);
203 return 0;
204 }
205 return 0;
206 }
207
AboutProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)208 static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg,
209 WPARAM wParam, LPARAM lParam)
210 {
211 char *str;
212
213 switch (msg) {
214 case WM_INITDIALOG: {
215 str = dupprintf("About %s", appname);
216 SetWindowText(hwnd, str);
217 sfree(str);
218 char *buildinfo_text = buildinfo("\r\n");
219 char *text = dupprintf
220 ("%s\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s",
221 appname, ver, buildinfo_text,
222 "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved.");
223 sfree(buildinfo_text);
224 SetDlgItemText(hwnd, IDA_TEXT, text);
225 MakeDlgItemBorderless(hwnd, IDA_TEXT);
226 sfree(text);
227 return 1;
228 }
229 case WM_COMMAND:
230 switch (LOWORD(wParam)) {
231 case IDOK:
232 case IDCANCEL:
233 EndDialog(hwnd, true);
234 return 0;
235 case IDA_LICENCE:
236 EnableWindow(hwnd, 0);
237 DialogBox(hinst, MAKEINTRESOURCE(IDD_LICENCEBOX),
238 hwnd, LicenceProc);
239 EnableWindow(hwnd, 1);
240 SetActiveWindow(hwnd);
241 return 0;
242
243 case IDA_WEB:
244 /* Load web browser */
245 ShellExecute(hwnd, "open",
246 "https://www.chiark.greenend.org.uk/~sgtatham/putty/",
247 0, 0, SW_SHOWDEFAULT);
248 return 0;
249 }
250 return 0;
251 case WM_CLOSE:
252 EndDialog(hwnd, true);
253 return 0;
254 }
255 return 0;
256 }
257
SaneDialogBox(HINSTANCE hinst,LPCTSTR tmpl,HWND hwndparent,DLGPROC lpDialogFunc)258 static int SaneDialogBox(HINSTANCE hinst,
259 LPCTSTR tmpl,
260 HWND hwndparent,
261 DLGPROC lpDialogFunc)
262 {
263 WNDCLASS wc;
264 HWND hwnd;
265 MSG msg;
266 int flags;
267 int ret;
268 int gm;
269
270 wc.style = CS_DBLCLKS | CS_SAVEBITS | CS_BYTEALIGNWINDOW;
271 wc.lpfnWndProc = DefDlgProc;
272 wc.cbClsExtra = 0;
273 wc.cbWndExtra = DLGWINDOWEXTRA + 2*sizeof(LONG_PTR);
274 wc.hInstance = hinst;
275 wc.hIcon = NULL;
276 wc.hCursor = LoadCursor(NULL, IDC_ARROW);
277 wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND +1);
278 wc.lpszMenuName = NULL;
279 wc.lpszClassName = "PuTTYConfigBox";
280 RegisterClass(&wc);
281
282 hwnd = CreateDialog(hinst, tmpl, hwndparent, lpDialogFunc);
283
284 SetWindowLongPtr(hwnd, BOXFLAGS, 0); /* flags */
285 SetWindowLongPtr(hwnd, BOXRESULT, 0); /* result from SaneEndDialog */
286
287 while ((gm=GetMessage(&msg, NULL, 0, 0)) > 0) {
288 flags=GetWindowLongPtr(hwnd, BOXFLAGS);
289 if (!(flags & DF_END) && !IsDialogMessage(hwnd, &msg))
290 DispatchMessage(&msg);
291 if (flags & DF_END)
292 break;
293 }
294
295 if (gm == 0)
296 PostQuitMessage(msg.wParam); /* We got a WM_QUIT, pass it on */
297
298 ret=GetWindowLongPtr(hwnd, BOXRESULT);
299 DestroyWindow(hwnd);
300 return ret;
301 }
302
SaneEndDialog(HWND hwnd,int ret)303 static void SaneEndDialog(HWND hwnd, int ret)
304 {
305 SetWindowLongPtr(hwnd, BOXRESULT, ret);
306 SetWindowLongPtr(hwnd, BOXFLAGS, DF_END);
307 }
308
309 /*
310 * Null dialog procedure.
311 */
NullDlgProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)312 static INT_PTR CALLBACK NullDlgProc(HWND hwnd, UINT msg,
313 WPARAM wParam, LPARAM lParam)
314 {
315 return 0;
316 }
317
318 enum {
319 IDCX_ABOUT = IDC_ABOUT,
320 IDCX_TVSTATIC,
321 IDCX_TREEVIEW,
322 IDCX_STDBASE,
323 IDCX_PANELBASE = IDCX_STDBASE + 32
324 };
325
326 struct treeview_faff {
327 HWND treeview;
328 HTREEITEM lastat[4];
329 };
330
treeview_insert(struct treeview_faff * faff,int level,char * text,char * path)331 static HTREEITEM treeview_insert(struct treeview_faff *faff,
332 int level, char *text, char *path)
333 {
334 TVINSERTSTRUCT ins;
335 int i;
336 HTREEITEM newitem;
337 ins.hParent = (level > 0 ? faff->lastat[level - 1] : TVI_ROOT);
338 ins.hInsertAfter = faff->lastat[level];
339 #if _WIN32_IE >= 0x0400 && defined NONAMELESSUNION
340 #define INSITEM DUMMYUNIONNAME.item
341 #else
342 #define INSITEM item
343 #endif
344 ins.INSITEM.mask = TVIF_TEXT | TVIF_PARAM;
345 ins.INSITEM.pszText = text;
346 ins.INSITEM.cchTextMax = strlen(text)+1;
347 ins.INSITEM.lParam = (LPARAM)path;
348 newitem = TreeView_InsertItem(faff->treeview, &ins);
349 if (level > 0)
350 TreeView_Expand(faff->treeview, faff->lastat[level - 1],
351 (level > 1 ? TVE_COLLAPSE : TVE_EXPAND));
352 faff->lastat[level] = newitem;
353 for (i = level + 1; i < 4; i++)
354 faff->lastat[i] = NULL;
355 return newitem;
356 }
357
358 /*
359 * Create the panelfuls of controls in the configuration box.
360 */
create_controls(HWND hwnd,char * path)361 static void create_controls(HWND hwnd, char *path)
362 {
363 struct ctlpos cp;
364 int index;
365 int base_id;
366 struct winctrls *wc;
367
368 if (!path[0]) {
369 /*
370 * Here we must create the basic standard controls.
371 */
372 ctlposinit(&cp, hwnd, 3, 3, 235);
373 wc = &ctrls_base;
374 base_id = IDCX_STDBASE;
375 } else {
376 /*
377 * Otherwise, we're creating the controls for a particular
378 * panel.
379 */
380 ctlposinit(&cp, hwnd, 100, 3, 13);
381 wc = &ctrls_panel;
382 base_id = IDCX_PANELBASE;
383 }
384
385 for (index=-1; (index = ctrl_find_path(ctrlbox, path, index)) >= 0 ;) {
386 struct controlset *s = ctrlbox->ctrlsets[index];
387 winctrl_layout(&dp, wc, &cp, s, &base_id);
388 }
389 }
390
391 /*
392 * This function is the configuration box.
393 * (Being a dialog procedure, in general it returns 0 if the default
394 * dialog processing should be performed, and 1 if it should not.)
395 */
GenericMainDlgProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)396 static INT_PTR CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg,
397 WPARAM wParam, LPARAM lParam)
398 {
399 HWND hw, treeview;
400 struct treeview_faff tvfaff;
401 int ret;
402
403 switch (msg) {
404 case WM_INITDIALOG:
405 dp.hwnd = hwnd;
406 create_controls(hwnd, ""); /* Open and Cancel buttons etc */
407 SetWindowText(hwnd, dp.wintitle);
408 SetWindowLongPtr(hwnd, GWLP_USERDATA, 0);
409 if (has_help())
410 SetWindowLongPtr(hwnd, GWL_EXSTYLE,
411 GetWindowLongPtr(hwnd, GWL_EXSTYLE) |
412 WS_EX_CONTEXTHELP);
413 else {
414 HWND item = GetDlgItem(hwnd, IDC_HELPBTN);
415 if (item)
416 DestroyWindow(item);
417 }
418 SendMessage(hwnd, WM_SETICON, (WPARAM) ICON_BIG,
419 (LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(IDI_CFGICON)));
420 /*
421 * Centre the window.
422 */
423 { /* centre the window */
424 RECT rs, rd;
425
426 hw = GetDesktopWindow();
427 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
428 MoveWindow(hwnd,
429 (rs.right + rs.left + rd.left - rd.right) / 2,
430 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
431 rd.right - rd.left, rd.bottom - rd.top, true);
432 }
433
434 /*
435 * Create the tree view.
436 */
437 {
438 RECT r;
439 WPARAM font;
440 HWND tvstatic;
441
442 r.left = 3;
443 r.right = r.left + 95;
444 r.top = 3;
445 r.bottom = r.top + 10;
446 MapDialogRect(hwnd, &r);
447 tvstatic = CreateWindowEx(0, "STATIC", "Cate&gory:",
448 WS_CHILD | WS_VISIBLE,
449 r.left, r.top,
450 r.right - r.left, r.bottom - r.top,
451 hwnd, (HMENU) IDCX_TVSTATIC, hinst,
452 NULL);
453 font = SendMessage(hwnd, WM_GETFONT, 0, 0);
454 SendMessage(tvstatic, WM_SETFONT, font, MAKELPARAM(true, 0));
455
456 r.left = 3;
457 r.right = r.left + 95;
458 r.top = 13;
459 r.bottom = r.top + 219;
460 MapDialogRect(hwnd, &r);
461 treeview = CreateWindowEx(WS_EX_CLIENTEDGE, WC_TREEVIEW, "",
462 WS_CHILD | WS_VISIBLE |
463 WS_TABSTOP | TVS_HASLINES |
464 TVS_DISABLEDRAGDROP | TVS_HASBUTTONS
465 | TVS_LINESATROOT |
466 TVS_SHOWSELALWAYS, r.left, r.top,
467 r.right - r.left, r.bottom - r.top,
468 hwnd, (HMENU) IDCX_TREEVIEW, hinst,
469 NULL);
470 font = SendMessage(hwnd, WM_GETFONT, 0, 0);
471 SendMessage(treeview, WM_SETFONT, font, MAKELPARAM(true, 0));
472 tvfaff.treeview = treeview;
473 memset(tvfaff.lastat, 0, sizeof(tvfaff.lastat));
474 }
475
476 /*
477 * Set up the tree view contents.
478 */
479 {
480 HTREEITEM hfirst = NULL;
481 int i;
482 char *path = NULL;
483 char *firstpath = NULL;
484
485 for (i = 0; i < ctrlbox->nctrlsets; i++) {
486 struct controlset *s = ctrlbox->ctrlsets[i];
487 HTREEITEM item;
488 int j;
489 char *c;
490
491 if (!s->pathname[0])
492 continue;
493 j = path ? ctrl_path_compare(s->pathname, path) : 0;
494 if (j == INT_MAX)
495 continue; /* same path, nothing to add to tree */
496
497 /*
498 * We expect never to find an implicit path
499 * component. For example, we expect never to see
500 * A/B/C followed by A/D/E, because that would
501 * _implicitly_ create A/D. All our path prefixes
502 * are expected to contain actual controls and be
503 * selectable in the treeview; so we would expect
504 * to see A/D _explicitly_ before encountering
505 * A/D/E.
506 */
507 assert(j == ctrl_path_elements(s->pathname) - 1);
508
509 c = strrchr(s->pathname, '/');
510 if (!c)
511 c = s->pathname;
512 else
513 c++;
514
515 item = treeview_insert(&tvfaff, j, c, s->pathname);
516 if (!hfirst) {
517 hfirst = item;
518 firstpath = s->pathname;
519 }
520
521 path = s->pathname;
522 }
523
524 /*
525 * Put the treeview selection on to the first panel in the
526 * ctrlbox.
527 */
528 TreeView_SelectItem(treeview, hfirst);
529
530 /*
531 * And create the actual control set for that panel, to
532 * match the initial treeview selection.
533 */
534 assert(firstpath); /* config.c must have given us _something_ */
535 create_controls(hwnd, firstpath);
536 dlg_refresh(NULL, &dp); /* and set up control values */
537 }
538
539 /*
540 * Set focus into the first available control.
541 */
542 {
543 int i;
544 struct winctrl *c;
545
546 for (i = 0; (c = winctrl_findbyindex(&ctrls_panel, i)) != NULL;
547 i++) {
548 if (c->ctrl) {
549 dlg_set_focus(c->ctrl, &dp);
550 break;
551 }
552 }
553 }
554
555 /*
556 * Now we've finished creating our initial set of controls,
557 * it's safe to actually show the window without risking setup
558 * flicker.
559 */
560 ShowWindow(hwnd, SW_SHOWNORMAL);
561
562 /*
563 * Set the flag that activates a couple of the other message
564 * handlers below, which were disabled until now to avoid
565 * spurious firing during the above setup procedure.
566 */
567 SetWindowLongPtr(hwnd, GWLP_USERDATA, 1);
568 return 0;
569 case WM_LBUTTONUP:
570 /*
571 * Button release should trigger WM_OK if there was a
572 * previous double click on the session list.
573 */
574 ReleaseCapture();
575 if (dp.ended)
576 SaneEndDialog(hwnd, dp.endresult ? 1 : 0);
577 break;
578 case WM_NOTIFY:
579 if (LOWORD(wParam) == IDCX_TREEVIEW &&
580 ((LPNMHDR) lParam)->code == TVN_SELCHANGED) {
581 /*
582 * Selection-change events on the treeview cause us to do
583 * a flurry of control deletion and creation - but only
584 * after WM_INITDIALOG has finished. The initial
585 * selection-change event(s) during treeview setup are
586 * ignored.
587 */
588 HTREEITEM i;
589 TVITEM item;
590 char buffer[64];
591
592 if (GetWindowLongPtr(hwnd, GWLP_USERDATA) != 1)
593 return 0;
594
595 i = TreeView_GetSelection(((LPNMHDR) lParam)->hwndFrom);
596
597 SendMessage (hwnd, WM_SETREDRAW, false, 0);
598
599 item.hItem = i;
600 item.pszText = buffer;
601 item.cchTextMax = sizeof(buffer);
602 item.mask = TVIF_TEXT | TVIF_PARAM;
603 TreeView_GetItem(((LPNMHDR) lParam)->hwndFrom, &item);
604 {
605 /* Destroy all controls in the currently visible panel. */
606 int k;
607 HWND item;
608 struct winctrl *c;
609
610 while ((c = winctrl_findbyindex(&ctrls_panel, 0)) != NULL) {
611 for (k = 0; k < c->num_ids; k++) {
612 item = GetDlgItem(hwnd, c->base_id + k);
613 if (item)
614 DestroyWindow(item);
615 }
616 winctrl_rem_shortcuts(&dp, c);
617 winctrl_remove(&ctrls_panel, c);
618 sfree(c->data);
619 sfree(c);
620 }
621 }
622 create_controls(hwnd, (char *)item.lParam);
623
624 dlg_refresh(NULL, &dp); /* set up control values */
625
626 SendMessage (hwnd, WM_SETREDRAW, true, 0);
627 InvalidateRect (hwnd, NULL, true);
628
629 SetFocus(((LPNMHDR) lParam)->hwndFrom); /* ensure focus stays */
630 return 0;
631 }
632 break;
633 case WM_COMMAND:
634 case WM_DRAWITEM:
635 default: /* also handle drag list msg here */
636 /*
637 * Only process WM_COMMAND once the dialog is fully formed.
638 */
639 if (GetWindowLongPtr(hwnd, GWLP_USERDATA) == 1) {
640 ret = winctrl_handle_command(&dp, msg, wParam, lParam);
641 if (dp.ended && GetCapture() != hwnd)
642 SaneEndDialog(hwnd, dp.endresult ? 1 : 0);
643 } else
644 ret = 0;
645 return ret;
646 case WM_HELP:
647 if (!winctrl_context_help(&dp, hwnd,
648 ((LPHELPINFO)lParam)->iCtrlId))
649 MessageBeep(0);
650 break;
651 case WM_CLOSE:
652 quit_help(hwnd);
653 SaneEndDialog(hwnd, 0);
654 return 0;
655
656 /* Grrr Explorer will maximize Dialogs! */
657 case WM_SIZE:
658 if (wParam == SIZE_MAXIMIZED)
659 force_normal(hwnd);
660 return 0;
661
662 }
663 return 0;
664 }
665
modal_about_box(HWND hwnd)666 void modal_about_box(HWND hwnd)
667 {
668 EnableWindow(hwnd, 0);
669 DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc);
670 EnableWindow(hwnd, 1);
671 SetActiveWindow(hwnd);
672 }
673
show_help(HWND hwnd)674 void show_help(HWND hwnd)
675 {
676 launch_help(hwnd, NULL);
677 }
678
defuse_showwindow(void)679 void defuse_showwindow(void)
680 {
681 /*
682 * Work around the fact that the app's first call to ShowWindow
683 * will ignore the default in favour of the shell-provided
684 * setting.
685 */
686 {
687 HWND hwnd;
688 hwnd = CreateDialog(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX),
689 NULL, NullDlgProc);
690 ShowWindow(hwnd, SW_HIDE);
691 SetActiveWindow(hwnd);
692 DestroyWindow(hwnd);
693 }
694 }
695
do_config(Conf * conf)696 bool do_config(Conf *conf)
697 {
698 bool ret;
699
700 ctrlbox = ctrl_new_box();
701 setup_config_box(ctrlbox, false, 0, 0);
702 win_setup_config_box(ctrlbox, &dp.hwnd, has_help(), false, 0);
703 dp_init(&dp);
704 winctrl_init(&ctrls_base);
705 winctrl_init(&ctrls_panel);
706 dp_add_tree(&dp, &ctrls_base);
707 dp_add_tree(&dp, &ctrls_panel);
708 dp.wintitle = dupprintf("%s Configuration", appname);
709 dp.errtitle = dupprintf("%s Error", appname);
710 dp.data = conf;
711 dlg_auto_set_fixed_pitch_flag(&dp);
712 dp.shortcuts['g'] = true; /* the treeview: `Cate&gory' */
713
714 ret =
715 SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL,
716 GenericMainDlgProc);
717
718 ctrl_free_box(ctrlbox);
719 winctrl_cleanup(&ctrls_panel);
720 winctrl_cleanup(&ctrls_base);
721 dp_cleanup(&dp);
722
723 return ret;
724 }
725
do_reconfig(HWND hwnd,Conf * conf,int protcfginfo)726 bool do_reconfig(HWND hwnd, Conf *conf, int protcfginfo)
727 {
728 Conf *backup_conf;
729 bool ret;
730 int protocol;
731
732 backup_conf = conf_copy(conf);
733
734 ctrlbox = ctrl_new_box();
735 protocol = conf_get_int(conf, CONF_protocol);
736 setup_config_box(ctrlbox, true, protocol, protcfginfo);
737 win_setup_config_box(ctrlbox, &dp.hwnd, has_help(), true, protocol);
738 dp_init(&dp);
739 winctrl_init(&ctrls_base);
740 winctrl_init(&ctrls_panel);
741 dp_add_tree(&dp, &ctrls_base);
742 dp_add_tree(&dp, &ctrls_panel);
743 dp.wintitle = dupprintf("%s Reconfiguration", appname);
744 dp.errtitle = dupprintf("%s Error", appname);
745 dp.data = conf;
746 dlg_auto_set_fixed_pitch_flag(&dp);
747 dp.shortcuts['g'] = true; /* the treeview: `Cate&gory' */
748
749 ret = SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL,
750 GenericMainDlgProc);
751
752 ctrl_free_box(ctrlbox);
753 winctrl_cleanup(&ctrls_base);
754 winctrl_cleanup(&ctrls_panel);
755 dp_cleanup(&dp);
756
757 if (!ret)
758 conf_copy_into(conf, backup_conf);
759
760 conf_free(backup_conf);
761
762 return ret;
763 }
764
win_gui_eventlog(LogPolicy * lp,const char * string)765 static void win_gui_eventlog(LogPolicy *lp, const char *string)
766 {
767 char timebuf[40];
768 char **location;
769 struct tm tm;
770
771 tm=ltime();
772 strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm);
773
774 if (ninitial < LOGEVENT_INITIAL_MAX)
775 location = &events_initial[ninitial];
776 else
777 location = &events_circular[(circular_first + ncircular) % LOGEVENT_CIRCULAR_MAX];
778
779 if (*location)
780 sfree(*location);
781 *location = dupcat(timebuf, string);
782 if (logbox) {
783 int count;
784 SendDlgItemMessage(logbox, IDN_LIST, LB_ADDSTRING,
785 0, (LPARAM) *location);
786 count = SendDlgItemMessage(logbox, IDN_LIST, LB_GETCOUNT, 0, 0);
787 SendDlgItemMessage(logbox, IDN_LIST, LB_SETTOPINDEX, count - 1, 0);
788 }
789 if (ninitial < LOGEVENT_INITIAL_MAX) {
790 ninitial++;
791 } else if (ncircular < LOGEVENT_CIRCULAR_MAX) {
792 ncircular++;
793 } else if (ncircular == LOGEVENT_CIRCULAR_MAX) {
794 circular_first = (circular_first + 1) % LOGEVENT_CIRCULAR_MAX;
795 sfree(events_circular[circular_first]);
796 events_circular[circular_first] = dupstr("..");
797 }
798 }
799
win_gui_logging_error(LogPolicy * lp,const char * event)800 static void win_gui_logging_error(LogPolicy *lp, const char *event)
801 {
802 WinGuiSeat *wgs = container_of(lp, WinGuiSeat, logpolicy);
803
804 /* Send 'can't open log file' errors to the terminal window.
805 * (Marked as stderr, although terminal.c won't care.) */
806 seat_stderr_pl(&wgs->seat, ptrlen_from_asciz(event));
807 seat_stderr_pl(&wgs->seat, PTRLEN_LITERAL("\r\n"));
808 }
809
showeventlog(HWND hwnd)810 void showeventlog(HWND hwnd)
811 {
812 if (!logbox) {
813 logbox = CreateDialog(hinst, MAKEINTRESOURCE(IDD_LOGBOX),
814 hwnd, LogProc);
815 ShowWindow(logbox, SW_SHOWNORMAL);
816 }
817 SetActiveWindow(logbox);
818 }
819
showabout(HWND hwnd)820 void showabout(HWND hwnd)
821 {
822 DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc);
823 }
824
825 struct hostkey_dialog_ctx {
826 const char *const *keywords;
827 const char *const *values;
828 FingerprintType fptype_default;
829 char **fingerprints;
830 const char *keydisp;
831 LPCTSTR iconid;
832 const char *helpctx;
833 };
834
HostKeyMoreInfoProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)835 static INT_PTR CALLBACK HostKeyMoreInfoProc(HWND hwnd, UINT msg,
836 WPARAM wParam, LPARAM lParam)
837 {
838 switch (msg) {
839 case WM_INITDIALOG: {
840 const struct hostkey_dialog_ctx *ctx =
841 (const struct hostkey_dialog_ctx *)lParam;
842 SetWindowLongPtr(hwnd, GWLP_USERDATA, (INT_PTR)ctx);
843
844 if (ctx->fingerprints[SSH_FPTYPE_SHA256])
845 SetDlgItemText(hwnd, IDC_HKI_SHA256,
846 ctx->fingerprints[SSH_FPTYPE_SHA256]);
847 if (ctx->fingerprints[SSH_FPTYPE_MD5])
848 SetDlgItemText(hwnd, IDC_HKI_MD5,
849 ctx->fingerprints[SSH_FPTYPE_MD5]);
850
851 SetDlgItemText(hwnd, IDA_TEXT, ctx->keydisp);
852
853 return 1;
854 }
855 case WM_COMMAND:
856 switch (LOWORD(wParam)) {
857 case IDOK:
858 EndDialog(hwnd, 0);
859 return 0;
860 }
861 return 0;
862 case WM_CLOSE:
863 EndDialog(hwnd, 0);
864 return 0;
865 }
866 return 0;
867 }
868
HostKeyDialogProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)869 static INT_PTR CALLBACK HostKeyDialogProc(HWND hwnd, UINT msg,
870 WPARAM wParam, LPARAM lParam)
871 {
872 switch (msg) {
873 case WM_INITDIALOG: {
874 strbuf *sb = strbuf_new();
875 const struct hostkey_dialog_ctx *ctx =
876 (const struct hostkey_dialog_ctx *)lParam;
877 SetWindowLongPtr(hwnd, GWLP_USERDATA, (INT_PTR)ctx);
878 for (int id = 100;; id++) {
879 char buf[256];
880
881 if (!GetDlgItemText(hwnd, id, buf, (int)lenof(buf)))
882 break;
883
884 strbuf_clear(sb);
885 for (const char *p = buf; *p ;) {
886 if (*p == '{') {
887 for (size_t i = 0; ctx->keywords[i]; i++) {
888 if (strstartswith(p, ctx->keywords[i])) {
889 p += strlen(ctx->keywords[i]);
890 put_datapl(sb, ptrlen_from_asciz(ctx->values[i]));
891 goto matched;
892 }
893 }
894 } else {
895 put_byte(sb, *p++);
896 }
897 matched:;
898 }
899
900 SetDlgItemText(hwnd, id, sb->s);
901 }
902 strbuf_free(sb);
903
904 SetDlgItemText(hwnd, IDC_HK_FINGERPRINT,
905 ctx->fingerprints[ctx->fptype_default]);
906 MakeDlgItemBorderless(hwnd, IDC_HK_FINGERPRINT);
907
908 HANDLE icon = LoadImage(
909 NULL, ctx->iconid, IMAGE_ICON,
910 GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON),
911 LR_SHARED);
912 SendDlgItemMessage(hwnd, IDC_HK_ICON, STM_SETICON, (WPARAM)icon, 0);
913
914 if (!has_help()) {
915 HWND item = GetDlgItem(hwnd, IDHELP);
916 if (item)
917 DestroyWindow(item);
918 }
919
920 return 1;
921 }
922 case WM_CTLCOLORSTATIC: {
923 HDC hdc = (HDC)wParam;
924 HWND control = (HWND)lParam;
925
926 if (GetWindowLongPtr(control, GWLP_ID) == IDC_HK_TITLE) {
927 SetBkMode(hdc, TRANSPARENT);
928 HFONT prev_font = (HFONT)SelectObject(
929 hdc, (HFONT)GetStockObject(SYSTEM_FONT));
930 LOGFONT lf;
931 if (GetObject(prev_font, sizeof(lf), &lf)) {
932 lf.lfWeight = FW_BOLD;
933 lf.lfHeight = lf.lfHeight * 3 / 2;
934 HFONT bold_font = CreateFontIndirect(&lf);
935 if (bold_font)
936 SelectObject(hdc, bold_font);
937 }
938 return (INT_PTR)GetSysColorBrush(COLOR_BTNFACE);
939 }
940 return 0;
941 }
942 case WM_COMMAND:
943 switch (LOWORD(wParam)) {
944 case IDC_HK_ACCEPT:
945 case IDC_HK_ONCE:
946 case IDCANCEL:
947 EndDialog(hwnd, LOWORD(wParam));
948 return 0;
949 case IDHELP: {
950 const struct hostkey_dialog_ctx *ctx =
951 (const struct hostkey_dialog_ctx *)
952 GetWindowLongPtr(hwnd, GWLP_USERDATA);
953 launch_help(hwnd, ctx->helpctx);
954 return 0;
955 }
956 case IDC_HK_MOREINFO: {
957 const struct hostkey_dialog_ctx *ctx =
958 (const struct hostkey_dialog_ctx *)
959 GetWindowLongPtr(hwnd, GWLP_USERDATA);
960 DialogBoxParam(hinst, MAKEINTRESOURCE(IDD_HK_MOREINFO),
961 hwnd, HostKeyMoreInfoProc, (LPARAM)ctx);
962 }
963 }
964 return 0;
965 case WM_CLOSE:
966 EndDialog(hwnd, IDCANCEL);
967 return 0;
968 }
969 return 0;
970 }
971
win_seat_verify_ssh_host_key(Seat * seat,const char * host,int port,const char * keytype,char * keystr,const char * keydisp,char ** fingerprints,void (* callback)(void * ctx,int result),void * ctx)972 int win_seat_verify_ssh_host_key(
973 Seat *seat, const char *host, int port, const char *keytype,
974 char *keystr, const char *keydisp, char **fingerprints,
975 void (*callback)(void *ctx, int result), void *ctx)
976 {
977 int ret;
978
979 WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat);
980
981 /*
982 * Verify the key against the registry.
983 */
984 ret = verify_host_key(host, port, keytype, keystr);
985
986 if (ret == 0) /* success - key matched OK */
987 return 1;
988 else {
989 static const char *const keywords[] =
990 { "{KEYTYPE}", "{APPNAME}", NULL };
991
992 const char *values[2];
993 values[0] = keytype;
994 values[1] = appname;
995
996 struct hostkey_dialog_ctx ctx[1];
997 ctx->keywords = keywords;
998 ctx->values = values;
999 ctx->fingerprints = fingerprints;
1000 ctx->fptype_default = ssh2_pick_default_fingerprint(fingerprints);
1001 ctx->keydisp = keydisp;
1002 ctx->iconid = (ret == 2 ? IDI_WARNING : IDI_QUESTION);
1003 ctx->helpctx = (ret == 2 ? WINHELP_CTX_errors_hostkey_changed :
1004 WINHELP_CTX_errors_hostkey_absent);
1005 int dlgid = (ret == 2 ? IDD_HK_WRONG : IDD_HK_ABSENT);
1006 int mbret = DialogBoxParam(
1007 hinst, MAKEINTRESOURCE(dlgid), wgs->term_hwnd,
1008 HostKeyDialogProc, (LPARAM)ctx);
1009 assert(mbret==IDC_HK_ACCEPT || mbret==IDC_HK_ONCE || mbret==IDCANCEL);
1010 if (mbret == IDC_HK_ACCEPT) {
1011 store_host_key(host, port, keytype, keystr);
1012 return 1;
1013 } else if (mbret == IDC_HK_ONCE)
1014 return 1;
1015 }
1016 return 0; /* abandon the connection */
1017 }
1018
1019 /*
1020 * Ask whether the selected algorithm is acceptable (since it was
1021 * below the configured 'warn' threshold).
1022 */
win_seat_confirm_weak_crypto_primitive(Seat * seat,const char * algtype,const char * algname,void (* callback)(void * ctx,int result),void * ctx)1023 int win_seat_confirm_weak_crypto_primitive(
1024 Seat *seat, const char *algtype, const char *algname,
1025 void (*callback)(void *ctx, int result), void *ctx)
1026 {
1027 static const char mbtitle[] = "%s Security Alert";
1028 static const char msg[] =
1029 "The first %s supported by the server\n"
1030 "is %s, which is below the configured\n"
1031 "warning threshold.\n"
1032 "Do you want to continue with this connection?\n";
1033 char *message, *title;
1034 int mbret;
1035
1036 message = dupprintf(msg, algtype, algname);
1037 title = dupprintf(mbtitle, appname);
1038 mbret = MessageBox(NULL, message, title,
1039 MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2);
1040 socket_reselect_all();
1041 sfree(message);
1042 sfree(title);
1043 if (mbret == IDYES)
1044 return 1;
1045 else
1046 return 0;
1047 }
1048
win_seat_confirm_weak_cached_hostkey(Seat * seat,const char * algname,const char * betteralgs,void (* callback)(void * ctx,int result),void * ctx)1049 int win_seat_confirm_weak_cached_hostkey(
1050 Seat *seat, const char *algname, const char *betteralgs,
1051 void (*callback)(void *ctx, int result), void *ctx)
1052 {
1053 static const char mbtitle[] = "%s Security Alert";
1054 static const char msg[] =
1055 "The first host key type we have stored for this server\n"
1056 "is %s, which is below the configured warning threshold.\n"
1057 "The server also provides the following types of host key\n"
1058 "above the threshold, which we do not have stored:\n"
1059 "%s\n"
1060 "Do you want to continue with this connection?\n";
1061 char *message, *title;
1062 int mbret;
1063
1064 message = dupprintf(msg, algname, betteralgs);
1065 title = dupprintf(mbtitle, appname);
1066 mbret = MessageBox(NULL, message, title,
1067 MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2);
1068 socket_reselect_all();
1069 sfree(message);
1070 sfree(title);
1071 if (mbret == IDYES)
1072 return 1;
1073 else
1074 return 0;
1075 }
1076
1077 /*
1078 * Ask whether to wipe a session log file before writing to it.
1079 * Returns 2 for wipe, 1 for append, 0 for cancel (don't log).
1080 */
win_gui_askappend(LogPolicy * lp,Filename * filename,void (* callback)(void * ctx,int result),void * ctx)1081 static int win_gui_askappend(LogPolicy *lp, Filename *filename,
1082 void (*callback)(void *ctx, int result),
1083 void *ctx)
1084 {
1085 static const char msgtemplate[] =
1086 "The session log file \"%.*s\" already exists.\n"
1087 "You can overwrite it with a new session log,\n"
1088 "append your session log to the end of it,\n"
1089 "or disable session logging for this session.\n"
1090 "Hit Yes to wipe the file, No to append to it,\n"
1091 "or Cancel to disable logging.";
1092 char *message;
1093 char *mbtitle;
1094 int mbret;
1095
1096 message = dupprintf(msgtemplate, FILENAME_MAX, filename->path);
1097 mbtitle = dupprintf("%s Log to File", appname);
1098
1099 mbret = MessageBox(NULL, message, mbtitle,
1100 MB_ICONQUESTION | MB_YESNOCANCEL | MB_DEFBUTTON3);
1101
1102 socket_reselect_all();
1103
1104 sfree(message);
1105 sfree(mbtitle);
1106
1107 if (mbret == IDYES)
1108 return 2;
1109 else if (mbret == IDNO)
1110 return 1;
1111 else
1112 return 0;
1113 }
1114
1115 const LogPolicyVtable win_gui_logpolicy_vt = {
1116 .eventlog = win_gui_eventlog,
1117 .askappend = win_gui_askappend,
1118 .logging_error = win_gui_logging_error,
1119 .verbose = null_lp_verbose_yes,
1120 };
1121
1122 /*
1123 * Warn about the obsolescent key file format.
1124 *
1125 * Uniquely among these functions, this one does _not_ expect a
1126 * frontend handle. This means that if PuTTY is ported to a
1127 * platform which requires frontend handles, this function will be
1128 * an anomaly. Fortunately, the problem it addresses will not have
1129 * been present on that platform, so it can plausibly be
1130 * implemented as an empty function.
1131 */
old_keyfile_warning(void)1132 void old_keyfile_warning(void)
1133 {
1134 static const char mbtitle[] = "%s Key File Warning";
1135 static const char message[] =
1136 "You are loading an SSH-2 private key which has an\n"
1137 "old version of the file format. This means your key\n"
1138 "file is not fully tamperproof. Future versions of\n"
1139 "%s may stop supporting this private key format,\n"
1140 "so we recommend you convert your key to the new\n"
1141 "format.\n"
1142 "\n"
1143 "You can perform this conversion by loading the key\n"
1144 "into PuTTYgen and then saving it again.";
1145
1146 char *msg, *title;
1147 msg = dupprintf(message, appname);
1148 title = dupprintf(mbtitle, appname);
1149
1150 MessageBox(NULL, msg, title, MB_OK);
1151
1152 socket_reselect_all();
1153
1154 sfree(msg);
1155 sfree(title);
1156 }
1157