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