Move SaneDialogBox()/SaneEndDialog() from winmisc.c to windlg.c, since they
[u/mdw/putty] / windows / windlg.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <limits.h>
4 #include <assert.h>
5 #include <ctype.h>
6 #include <time.h>
7
8 #include "putty.h"
9 #include "ssh.h"
10 #include "win_res.h"
11 #include "storage.h"
12 #include "dialog.h"
13
14 #include <commctrl.h>
15 #include <commdlg.h>
16 #include <shellapi.h>
17
18 #ifdef MSVC4
19 #define TVINSERTSTRUCT TV_INSERTSTRUCT
20 #define TVITEM TV_ITEM
21 #define ICON_BIG 1
22 #endif
23
24 /*
25 * These are the various bits of data required to handle the
26 * portable-dialog stuff in the config box. Having them at file
27 * scope in here isn't too bad a place to put them; if we were ever
28 * to need more than one config box per process we could always
29 * shift them to a per-config-box structure stored in GWL_USERDATA.
30 */
31 static struct controlbox *ctrlbox;
32 /*
33 * ctrls_base holds the OK and Cancel buttons: the controls which
34 * are present in all dialog panels. ctrls_panel holds the ones
35 * which change from panel to panel.
36 */
37 static struct winctrls ctrls_base, ctrls_panel;
38 static struct dlgparam dp;
39
40 static char **events = NULL;
41 static int nevents = 0, negsize = 0;
42
43 static int requested_help;
44
45 extern Config cfg; /* defined in window.c */
46
47 struct sesslist sesslist; /* exported to window.c */
48
49 #define PRINTER_DISABLED_STRING "None (printing disabled)"
50
51 void force_normal(HWND hwnd)
52 {
53 static int recurse = 0;
54
55 WINDOWPLACEMENT wp;
56
57 if (recurse)
58 return;
59 recurse = 1;
60
61 wp.length = sizeof(wp);
62 if (GetWindowPlacement(hwnd, &wp) && wp.showCmd == SW_SHOWMAXIMIZED) {
63 wp.showCmd = SW_SHOWNORMAL;
64 SetWindowPlacement(hwnd, &wp);
65 }
66 recurse = 0;
67 }
68
69 static int CALLBACK LogProc(HWND hwnd, UINT msg,
70 WPARAM wParam, LPARAM lParam)
71 {
72 int i;
73
74 switch (msg) {
75 case WM_INITDIALOG:
76 {
77 char *str = dupprintf("%s Event Log", appname);
78 SetWindowText(hwnd, str);
79 sfree(str);
80 }
81 {
82 static int tabs[4] = { 78, 108 };
83 SendDlgItemMessage(hwnd, IDN_LIST, LB_SETTABSTOPS, 2,
84 (LPARAM) tabs);
85 }
86 for (i = 0; i < nevents; i++)
87 SendDlgItemMessage(hwnd, IDN_LIST, LB_ADDSTRING,
88 0, (LPARAM) events[i]);
89 return 1;
90 case WM_COMMAND:
91 switch (LOWORD(wParam)) {
92 case IDOK:
93 case IDCANCEL:
94 logbox = NULL;
95 SetActiveWindow(GetParent(hwnd));
96 DestroyWindow(hwnd);
97 return 0;
98 case IDN_COPY:
99 if (HIWORD(wParam) == BN_CLICKED ||
100 HIWORD(wParam) == BN_DOUBLECLICKED) {
101 int selcount;
102 int *selitems;
103 selcount = SendDlgItemMessage(hwnd, IDN_LIST,
104 LB_GETSELCOUNT, 0, 0);
105 if (selcount == 0) { /* don't even try to copy zero items */
106 MessageBeep(0);
107 break;
108 }
109
110 selitems = snewn(selcount, int);
111 if (selitems) {
112 int count = SendDlgItemMessage(hwnd, IDN_LIST,
113 LB_GETSELITEMS,
114 selcount,
115 (LPARAM) selitems);
116 int i;
117 int size;
118 char *clipdata;
119 static unsigned char sel_nl[] = SEL_NL;
120
121 if (count == 0) { /* can't copy zero stuff */
122 MessageBeep(0);
123 break;
124 }
125
126 size = 0;
127 for (i = 0; i < count; i++)
128 size +=
129 strlen(events[selitems[i]]) + sizeof(sel_nl);
130
131 clipdata = snewn(size, char);
132 if (clipdata) {
133 char *p = clipdata;
134 for (i = 0; i < count; i++) {
135 char *q = events[selitems[i]];
136 int qlen = strlen(q);
137 memcpy(p, q, qlen);
138 p += qlen;
139 memcpy(p, sel_nl, sizeof(sel_nl));
140 p += sizeof(sel_nl);
141 }
142 write_aclip(NULL, clipdata, size, TRUE);
143 sfree(clipdata);
144 }
145 sfree(selitems);
146
147 for (i = 0; i < nevents; i++)
148 SendDlgItemMessage(hwnd, IDN_LIST, LB_SETSEL,
149 FALSE, i);
150 }
151 }
152 return 0;
153 }
154 return 0;
155 case WM_CLOSE:
156 logbox = NULL;
157 SetActiveWindow(GetParent(hwnd));
158 DestroyWindow(hwnd);
159 return 0;
160 }
161 return 0;
162 }
163
164 static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
165 WPARAM wParam, LPARAM lParam)
166 {
167 switch (msg) {
168 case WM_INITDIALOG:
169 {
170 char *str = dupprintf("%s Licence", appname);
171 SetWindowText(hwnd, str);
172 sfree(str);
173 }
174 return 1;
175 case WM_COMMAND:
176 switch (LOWORD(wParam)) {
177 case IDOK:
178 case IDCANCEL:
179 EndDialog(hwnd, 1);
180 return 0;
181 }
182 return 0;
183 case WM_CLOSE:
184 EndDialog(hwnd, 1);
185 return 0;
186 }
187 return 0;
188 }
189
190 static int CALLBACK AboutProc(HWND hwnd, UINT msg,
191 WPARAM wParam, LPARAM lParam)
192 {
193 char *str;
194
195 switch (msg) {
196 case WM_INITDIALOG:
197 str = dupprintf("About %s", appname);
198 SetWindowText(hwnd, str);
199 sfree(str);
200 SetDlgItemText(hwnd, IDA_TEXT1, appname);
201 SetDlgItemText(hwnd, IDA_VERSION, ver);
202 return 1;
203 case WM_COMMAND:
204 switch (LOWORD(wParam)) {
205 case IDOK:
206 case IDCANCEL:
207 EndDialog(hwnd, TRUE);
208 return 0;
209 case IDA_LICENCE:
210 EnableWindow(hwnd, 0);
211 DialogBox(hinst, MAKEINTRESOURCE(IDD_LICENCEBOX),
212 hwnd, LicenceProc);
213 EnableWindow(hwnd, 1);
214 SetActiveWindow(hwnd);
215 return 0;
216
217 case IDA_WEB:
218 /* Load web browser */
219 ShellExecute(hwnd, "open",
220 "http://www.chiark.greenend.org.uk/~sgtatham/putty/",
221 0, 0, SW_SHOWDEFAULT);
222 return 0;
223 }
224 return 0;
225 case WM_CLOSE:
226 EndDialog(hwnd, TRUE);
227 return 0;
228 }
229 return 0;
230 }
231
232 static int SaneDialogBox(HINSTANCE hinst,
233 LPCTSTR tmpl,
234 HWND hwndparent,
235 DLGPROC lpDialogFunc)
236 {
237 WNDCLASS wc;
238 HWND hwnd;
239 MSG msg;
240 int flags;
241 int ret;
242 int gm;
243
244 wc.style = CS_DBLCLKS | CS_SAVEBITS | CS_BYTEALIGNWINDOW;
245 wc.lpfnWndProc = DefDlgProc;
246 wc.cbClsExtra = 0;
247 wc.cbWndExtra = DLGWINDOWEXTRA + 8;
248 wc.hInstance = hinst;
249 wc.hIcon = NULL;
250 wc.hCursor = LoadCursor(NULL, IDC_ARROW);
251 wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND +1);
252 wc.lpszMenuName = NULL;
253 wc.lpszClassName = "PuTTYConfigBox";
254 RegisterClass(&wc);
255
256 hwnd = CreateDialog(hinst, tmpl, hwndparent, lpDialogFunc);
257
258 SetWindowLong(hwnd, BOXFLAGS, 0); /* flags */
259 SetWindowLong(hwnd, BOXRESULT, 0); /* result from SaneEndDialog */
260
261 while ((gm=GetMessage(&msg, NULL, 0, 0)) > 0) {
262 flags=GetWindowLong(hwnd, BOXFLAGS);
263 if (!(flags & DF_END) && !IsDialogMessage(hwnd, &msg))
264 DispatchMessage(&msg);
265 if (flags & DF_END)
266 break;
267 }
268
269 if (gm == 0)
270 PostQuitMessage(msg.wParam); /* We got a WM_QUIT, pass it on */
271
272 ret=GetWindowLong(hwnd, BOXRESULT);
273 DestroyWindow(hwnd);
274 return ret;
275 }
276
277 static void SaneEndDialog(HWND hwnd, int ret)
278 {
279 SetWindowLong(hwnd, BOXRESULT, ret);
280 SetWindowLong(hwnd, BOXFLAGS, DF_END);
281 }
282
283 /*
284 * Null dialog procedure.
285 */
286 static int CALLBACK NullDlgProc(HWND hwnd, UINT msg,
287 WPARAM wParam, LPARAM lParam)
288 {
289 return 0;
290 }
291
292 enum {
293 IDCX_ABOUT = IDC_ABOUT,
294 IDCX_TVSTATIC,
295 IDCX_TREEVIEW,
296 IDCX_STDBASE,
297 IDCX_PANELBASE = IDCX_STDBASE + 32
298 };
299
300 struct treeview_faff {
301 HWND treeview;
302 HTREEITEM lastat[4];
303 };
304
305 static HTREEITEM treeview_insert(struct treeview_faff *faff,
306 int level, char *text, char *path)
307 {
308 TVINSERTSTRUCT ins;
309 int i;
310 HTREEITEM newitem;
311 ins.hParent = (level > 0 ? faff->lastat[level - 1] : TVI_ROOT);
312 ins.hInsertAfter = faff->lastat[level];
313 #if _WIN32_IE >= 0x0400 && defined NONAMELESSUNION
314 #define INSITEM DUMMYUNIONNAME.item
315 #else
316 #define INSITEM item
317 #endif
318 ins.INSITEM.mask = TVIF_TEXT | TVIF_PARAM;
319 ins.INSITEM.pszText = text;
320 ins.INSITEM.cchTextMax = strlen(text)+1;
321 ins.INSITEM.lParam = (LPARAM)path;
322 newitem = TreeView_InsertItem(faff->treeview, &ins);
323 if (level > 0)
324 TreeView_Expand(faff->treeview, faff->lastat[level - 1],
325 TVE_EXPAND);
326 faff->lastat[level] = newitem;
327 for (i = level + 1; i < 4; i++)
328 faff->lastat[i] = NULL;
329 return newitem;
330 }
331
332 /*
333 * Create the panelfuls of controls in the configuration box.
334 */
335 static void create_controls(HWND hwnd, char *path)
336 {
337 struct ctlpos cp;
338 int index;
339 int base_id;
340 struct winctrls *wc;
341
342 if (!path[0]) {
343 /*
344 * Here we must create the basic standard controls.
345 */
346 ctlposinit(&cp, hwnd, 3, 3, 235);
347 wc = &ctrls_base;
348 base_id = IDCX_STDBASE;
349 } else {
350 /*
351 * Otherwise, we're creating the controls for a particular
352 * panel.
353 */
354 ctlposinit(&cp, hwnd, 100, 3, 13);
355 wc = &ctrls_panel;
356 base_id = IDCX_PANELBASE;
357 }
358
359 for (index=-1; (index = ctrl_find_path(ctrlbox, path, index)) >= 0 ;) {
360 struct controlset *s = ctrlbox->ctrlsets[index];
361 winctrl_layout(&dp, wc, &cp, s, &base_id);
362 }
363 }
364
365 /*
366 * This function is the configuration box.
367 */
368 static int CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg,
369 WPARAM wParam, LPARAM lParam)
370 {
371 HWND hw, treeview;
372 struct treeview_faff tvfaff;
373 int ret;
374
375 switch (msg) {
376 case WM_INITDIALOG:
377 dp.hwnd = hwnd;
378 create_controls(hwnd, ""); /* Open and Cancel buttons etc */
379 SetWindowText(hwnd, dp.wintitle);
380 SetWindowLong(hwnd, GWL_USERDATA, 0);
381 if (help_path)
382 SetWindowLong(hwnd, GWL_EXSTYLE,
383 GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_CONTEXTHELP);
384 else {
385 HWND item = GetDlgItem(hwnd, IDC_HELPBTN);
386 if (item)
387 DestroyWindow(item);
388 }
389 requested_help = FALSE;
390 SendMessage(hwnd, WM_SETICON, (WPARAM) ICON_BIG,
391 (LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(IDI_CFGICON)));
392 /*
393 * Centre the window.
394 */
395 { /* centre the window */
396 RECT rs, rd;
397
398 hw = GetDesktopWindow();
399 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
400 MoveWindow(hwnd,
401 (rs.right + rs.left + rd.left - rd.right) / 2,
402 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
403 rd.right - rd.left, rd.bottom - rd.top, TRUE);
404 }
405
406 /*
407 * Create the tree view.
408 */
409 {
410 RECT r;
411 WPARAM font;
412 HWND tvstatic;
413
414 r.left = 3;
415 r.right = r.left + 95;
416 r.top = 3;
417 r.bottom = r.top + 10;
418 MapDialogRect(hwnd, &r);
419 tvstatic = CreateWindowEx(0, "STATIC", "Cate&gory:",
420 WS_CHILD | WS_VISIBLE,
421 r.left, r.top,
422 r.right - r.left, r.bottom - r.top,
423 hwnd, (HMENU) IDCX_TVSTATIC, hinst,
424 NULL);
425 font = SendMessage(hwnd, WM_GETFONT, 0, 0);
426 SendMessage(tvstatic, WM_SETFONT, font, MAKELPARAM(TRUE, 0));
427
428 r.left = 3;
429 r.right = r.left + 95;
430 r.top = 13;
431 r.bottom = r.top + 219;
432 MapDialogRect(hwnd, &r);
433 treeview = CreateWindowEx(WS_EX_CLIENTEDGE, WC_TREEVIEW, "",
434 WS_CHILD | WS_VISIBLE |
435 WS_TABSTOP | TVS_HASLINES |
436 TVS_DISABLEDRAGDROP | TVS_HASBUTTONS
437 | TVS_LINESATROOT |
438 TVS_SHOWSELALWAYS, r.left, r.top,
439 r.right - r.left, r.bottom - r.top,
440 hwnd, (HMENU) IDCX_TREEVIEW, hinst,
441 NULL);
442 font = SendMessage(hwnd, WM_GETFONT, 0, 0);
443 SendMessage(treeview, WM_SETFONT, font, MAKELPARAM(TRUE, 0));
444 tvfaff.treeview = treeview;
445 memset(tvfaff.lastat, 0, sizeof(tvfaff.lastat));
446 }
447
448 /*
449 * Set up the tree view contents.
450 */
451 {
452 HTREEITEM hfirst = NULL;
453 int i;
454 char *path = NULL;
455
456 for (i = 0; i < ctrlbox->nctrlsets; i++) {
457 struct controlset *s = ctrlbox->ctrlsets[i];
458 HTREEITEM item;
459 int j;
460 char *c;
461
462 if (!s->pathname[0])
463 continue;
464 j = path ? ctrl_path_compare(s->pathname, path) : 0;
465 if (j == INT_MAX)
466 continue; /* same path, nothing to add to tree */
467
468 /*
469 * We expect never to find an implicit path
470 * component. For example, we expect never to see
471 * A/B/C followed by A/D/E, because that would
472 * _implicitly_ create A/D. All our path prefixes
473 * are expected to contain actual controls and be
474 * selectable in the treeview; so we would expect
475 * to see A/D _explicitly_ before encountering
476 * A/D/E.
477 */
478 assert(j == ctrl_path_elements(s->pathname) - 1);
479
480 c = strrchr(s->pathname, '/');
481 if (!c)
482 c = s->pathname;
483 else
484 c++;
485
486 item = treeview_insert(&tvfaff, j, c, s->pathname);
487 if (!hfirst)
488 hfirst = item;
489
490 path = s->pathname;
491 }
492
493 /*
494 * Put the treeview selection on to the Session panel.
495 * This should also cause creation of the relevant
496 * controls.
497 */
498 TreeView_SelectItem(treeview, hfirst);
499 }
500
501 /*
502 * Set focus into the first available control.
503 */
504 {
505 int i;
506 struct winctrl *c;
507
508 for (i = 0; (c = winctrl_findbyindex(&ctrls_panel, i)) != NULL;
509 i++) {
510 if (c->ctrl) {
511 dlg_set_focus(c->ctrl, &dp);
512 break;
513 }
514 }
515 }
516
517 SetWindowLong(hwnd, GWL_USERDATA, 1);
518 return 0;
519 case WM_LBUTTONUP:
520 /*
521 * Button release should trigger WM_OK if there was a
522 * previous double click on the session list.
523 */
524 ReleaseCapture();
525 if (dp.ended)
526 SaneEndDialog(hwnd, dp.endresult ? 1 : 0);
527 break;
528 case WM_NOTIFY:
529 if (LOWORD(wParam) == IDCX_TREEVIEW &&
530 ((LPNMHDR) lParam)->code == TVN_SELCHANGED) {
531 HTREEITEM i =
532 TreeView_GetSelection(((LPNMHDR) lParam)->hwndFrom);
533 TVITEM item;
534 char buffer[64];
535
536 SendMessage (hwnd, WM_SETREDRAW, FALSE, 0);
537
538 item.hItem = i;
539 item.pszText = buffer;
540 item.cchTextMax = sizeof(buffer);
541 item.mask = TVIF_TEXT | TVIF_PARAM;
542 TreeView_GetItem(((LPNMHDR) lParam)->hwndFrom, &item);
543 {
544 /* Destroy all controls in the currently visible panel. */
545 int k;
546 HWND item;
547 struct winctrl *c;
548
549 while ((c = winctrl_findbyindex(&ctrls_panel, 0)) != NULL) {
550 for (k = 0; k < c->num_ids; k++) {
551 item = GetDlgItem(hwnd, c->base_id + k);
552 if (item)
553 DestroyWindow(item);
554 }
555 winctrl_rem_shortcuts(&dp, c);
556 winctrl_remove(&ctrls_panel, c);
557 sfree(c->data);
558 sfree(c);
559 }
560 }
561 create_controls(hwnd, (char *)item.lParam);
562
563 dlg_refresh(NULL, &dp); /* set up control values */
564
565 SendMessage (hwnd, WM_SETREDRAW, TRUE, 0);
566 InvalidateRect (hwnd, NULL, TRUE);
567
568 SetFocus(((LPNMHDR) lParam)->hwndFrom); /* ensure focus stays */
569 return 0;
570 }
571 break;
572 case WM_COMMAND:
573 case WM_DRAWITEM:
574 default: /* also handle drag list msg here */
575 /*
576 * Only process WM_COMMAND once the dialog is fully formed.
577 */
578 if (GetWindowLong(hwnd, GWL_USERDATA) == 1) {
579 ret = winctrl_handle_command(&dp, msg, wParam, lParam);
580 if (dp.ended && GetCapture() != hwnd)
581 SaneEndDialog(hwnd, dp.endresult ? 1 : 0);
582 } else
583 ret = 0;
584 return ret;
585 case WM_HELP:
586 if (help_path) {
587 if (winctrl_context_help(&dp, hwnd,
588 ((LPHELPINFO)lParam)->iCtrlId))
589 requested_help = TRUE;
590 else
591 MessageBeep(0);
592 }
593 break;
594 case WM_CLOSE:
595 if (requested_help) {
596 WinHelp(hwnd, help_path, HELP_QUIT, 0);
597 requested_help = FALSE;
598 }
599 SaneEndDialog(hwnd, 0);
600 return 0;
601
602 /* Grrr Explorer will maximize Dialogs! */
603 case WM_SIZE:
604 if (wParam == SIZE_MAXIMIZED)
605 force_normal(hwnd);
606 return 0;
607
608 }
609 return 0;
610 }
611
612 void modal_about_box(HWND hwnd)
613 {
614 EnableWindow(hwnd, 0);
615 DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc);
616 EnableWindow(hwnd, 1);
617 SetActiveWindow(hwnd);
618 }
619
620 void show_help(HWND hwnd)
621 {
622 if (help_path) {
623 WinHelp(hwnd, help_path,
624 help_has_contents ? HELP_FINDER : HELP_CONTENTS,
625 0);
626 requested_help = TRUE;
627 }
628 }
629
630 void defuse_showwindow(void)
631 {
632 /*
633 * Work around the fact that the app's first call to ShowWindow
634 * will ignore the default in favour of the shell-provided
635 * setting.
636 */
637 {
638 HWND hwnd;
639 hwnd = CreateDialog(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX),
640 NULL, NullDlgProc);
641 ShowWindow(hwnd, SW_HIDE);
642 SetActiveWindow(hwnd);
643 DestroyWindow(hwnd);
644 }
645 }
646
647 int do_config(void)
648 {
649 int ret;
650
651 ctrlbox = ctrl_new_box();
652 setup_config_box(ctrlbox, &sesslist, FALSE, 0, 0);
653 win_setup_config_box(ctrlbox, &dp.hwnd, (help_path != NULL), FALSE);
654 dp_init(&dp);
655 winctrl_init(&ctrls_base);
656 winctrl_init(&ctrls_panel);
657 dp_add_tree(&dp, &ctrls_base);
658 dp_add_tree(&dp, &ctrls_panel);
659 dp.wintitle = dupprintf("%s Configuration", appname);
660 dp.errtitle = dupprintf("%s Error", appname);
661 dp.data = &cfg;
662 dp.shortcuts['g'] = TRUE; /* the treeview: `Cate&gory' */
663
664 get_sesslist(&sesslist, TRUE);
665 ret =
666 SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL,
667 GenericMainDlgProc);
668 get_sesslist(&sesslist, FALSE);
669
670 ctrl_free_box(ctrlbox);
671 winctrl_cleanup(&ctrls_panel);
672 winctrl_cleanup(&ctrls_base);
673 dp_cleanup(&dp);
674
675 return ret;
676 }
677
678 int do_reconfig(HWND hwnd, int protcfginfo)
679 {
680 Config backup_cfg;
681 int ret;
682
683 backup_cfg = cfg; /* structure copy */
684
685 ctrlbox = ctrl_new_box();
686 setup_config_box(ctrlbox, &sesslist, TRUE, cfg.protocol, protcfginfo);
687 win_setup_config_box(ctrlbox, &dp.hwnd, (help_path != NULL), TRUE);
688 dp_init(&dp);
689 winctrl_init(&ctrls_base);
690 winctrl_init(&ctrls_panel);
691 dp_add_tree(&dp, &ctrls_base);
692 dp_add_tree(&dp, &ctrls_panel);
693 dp.wintitle = dupprintf("%s Reconfiguration", appname);
694 dp.errtitle = dupprintf("%s Error", appname);
695 dp.data = &cfg;
696 dp.shortcuts['g'] = TRUE; /* the treeview: `Cate&gory' */
697
698 ret = SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL,
699 GenericMainDlgProc);
700
701 ctrl_free_box(ctrlbox);
702 winctrl_cleanup(&ctrls_base);
703 winctrl_cleanup(&ctrls_panel);
704 dp_cleanup(&dp);
705
706 if (!ret)
707 cfg = backup_cfg; /* structure copy */
708
709 return ret;
710 }
711
712 void logevent(void *frontend, const char *string)
713 {
714 char timebuf[40];
715 struct tm tm;
716
717 log_eventlog(logctx, string);
718
719 if (nevents >= negsize) {
720 negsize += 64;
721 events = sresize(events, negsize, char *);
722 }
723
724 tm=ltime();
725 strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm);
726
727 events[nevents] = snewn(strlen(timebuf) + strlen(string) + 1, char);
728 strcpy(events[nevents], timebuf);
729 strcat(events[nevents], string);
730 if (logbox) {
731 int count;
732 SendDlgItemMessage(logbox, IDN_LIST, LB_ADDSTRING,
733 0, (LPARAM) events[nevents]);
734 count = SendDlgItemMessage(logbox, IDN_LIST, LB_GETCOUNT, 0, 0);
735 SendDlgItemMessage(logbox, IDN_LIST, LB_SETTOPINDEX, count - 1, 0);
736 }
737 nevents++;
738 }
739
740 void showeventlog(HWND hwnd)
741 {
742 if (!logbox) {
743 logbox = CreateDialog(hinst, MAKEINTRESOURCE(IDD_LOGBOX),
744 hwnd, LogProc);
745 ShowWindow(logbox, SW_SHOWNORMAL);
746 }
747 SetActiveWindow(logbox);
748 }
749
750 void showabout(HWND hwnd)
751 {
752 DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc);
753 }
754
755 /* Helper function for verify_ssh_host_key(). */
756 static VOID CALLBACK verify_ssh_host_key_help(LPHELPINFO lpHelpInfo)
757 {
758 if (help_path) {
759 char *context = NULL;
760 #define CHECK_CTX(name) \
761 do { \
762 if (lpHelpInfo->dwContextId == WINHELP_CTXID_ ## name) \
763 context = WINHELP_CTX_ ## name; \
764 } while (0)
765 CHECK_CTX(errors_hostkey_absent);
766 CHECK_CTX(errors_hostkey_changed);
767 #undef CHECK_CTX
768 if (context) {
769 char *cmd = dupprintf("JI(`',`%s')", context);
770 WinHelp(hwnd, help_path, HELP_COMMAND, (DWORD)cmd);
771 sfree(cmd);
772 requested_help = TRUE;
773 }
774 }
775 }
776
777 int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
778 char *keystr, char *fingerprint,
779 void (*callback)(void *ctx, int result), void *ctx)
780 {
781 int ret;
782
783 static const char absentmsg[] =
784 "The server's host key is not cached in the registry. You\n"
785 "have no guarantee that the server is the computer you\n"
786 "think it is.\n"
787 "The server's %s key fingerprint is:\n"
788 "%s\n"
789 "If you trust this host, hit Yes to add the key to\n"
790 "%s's cache and carry on connecting.\n"
791 "If you want to carry on connecting just once, without\n"
792 "adding the key to the cache, hit No.\n"
793 "If you do not trust this host, hit Cancel to abandon the\n"
794 "connection.\n";
795
796 static const char wrongmsg[] =
797 "WARNING - POTENTIAL SECURITY BREACH!\n"
798 "\n"
799 "The server's host key does not match the one %s has\n"
800 "cached in the registry. This means that either the\n"
801 "server administrator has changed the host key, or you\n"
802 "have actually connected to another computer pretending\n"
803 "to be the server.\n"
804 "The new %s key fingerprint is:\n"
805 "%s\n"
806 "If you were expecting this change and trust the new key,\n"
807 "hit Yes to update %s's cache and continue connecting.\n"
808 "If you want to carry on connecting but without updating\n"
809 "the cache, hit No.\n"
810 "If you want to abandon the connection completely, hit\n"
811 "Cancel. Hitting Cancel is the ONLY guaranteed safe\n" "choice.\n";
812
813 static const char mbtitle[] = "%s Security Alert";
814
815 UINT help_button = 0;
816 MSGBOXPARAMS mbox;
817
818 /*
819 * We use MessageBoxIndirect() because it allows us to specify a
820 * callback function for the Help button.
821 */
822 mbox.cbSize = sizeof(mbox);
823 mbox.hInstance = hinst;
824 mbox.hwndOwner = hwnd;
825 mbox.lpfnMsgBoxCallback = &verify_ssh_host_key_help;
826 mbox.dwLanguageId = LANG_NEUTRAL;
827
828 /* Do we have a help file? */
829 if (help_path)
830 help_button = MB_HELP;
831
832 /*
833 * Verify the key against the registry.
834 */
835 ret = verify_host_key(host, port, keytype, keystr);
836
837 if (ret == 0) /* success - key matched OK */
838 return 1;
839 if (ret == 2) { /* key was different */
840 int mbret;
841 mbox.lpszText = dupprintf(wrongmsg, appname, keytype, fingerprint,
842 appname);
843 mbox.lpszCaption = dupprintf(mbtitle, appname);
844 mbox.dwContextHelpId = HELPCTXID(errors_hostkey_changed);
845 mbox.dwStyle = MB_ICONWARNING | MB_YESNOCANCEL | MB_DEFBUTTON3 |
846 help_button;
847 mbret = MessageBoxIndirect(&mbox);
848 assert(mbret==IDYES || mbret==IDNO || mbret==IDCANCEL);
849 sfree((void *)mbox.lpszText);
850 sfree((void *)mbox.lpszCaption);
851 if (mbret == IDYES) {
852 store_host_key(host, port, keytype, keystr);
853 return 1;
854 } else if (mbret == IDNO)
855 return 1;
856 return 0;
857 }
858 if (ret == 1) { /* key was absent */
859 int mbret;
860 mbox.lpszText = dupprintf(absentmsg, keytype, fingerprint, appname);
861 mbox.lpszCaption = dupprintf(mbtitle, appname);
862 mbox.dwContextHelpId = HELPCTXID(errors_hostkey_absent);
863 mbox.dwStyle = MB_ICONWARNING | MB_YESNOCANCEL | MB_DEFBUTTON3 |
864 help_button;
865 mbret = MessageBoxIndirect(&mbox);
866 assert(mbret==IDYES || mbret==IDNO || mbret==IDCANCEL);
867 sfree((void *)mbox.lpszText);
868 sfree((void *)mbox.lpszCaption);
869 if (mbret == IDYES)
870 store_host_key(host, port, keytype, keystr);
871 if (mbret == IDNO)
872 return 1;
873 return 0;
874 }
875 }
876
877 /*
878 * Ask whether the selected algorithm is acceptable (since it was
879 * below the configured 'warn' threshold).
880 */
881 int askalg(void *frontend, const char *algtype, const char *algname,
882 void (*callback)(void *ctx, int result), void *ctx)
883 {
884 static const char mbtitle[] = "%s Security Alert";
885 static const char msg[] =
886 "The first %s supported by the server\n"
887 "is %.64s, which is below the configured\n"
888 "warning threshold.\n"
889 "Do you want to continue with this connection?\n";
890 char *message, *title;
891 int mbret;
892
893 message = dupprintf(msg, algtype, algname);
894 title = dupprintf(mbtitle, appname);
895 mbret = MessageBox(NULL, message, title,
896 MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2);
897 sfree(message);
898 sfree(title);
899 if (mbret == IDYES)
900 return 1;
901 else
902 return 0;
903 }
904
905 /*
906 * Ask whether to wipe a session log file before writing to it.
907 * Returns 2 for wipe, 1 for append, 0 for cancel (don't log).
908 */
909 int askappend(void *frontend, Filename filename,
910 void (*callback)(void *ctx, int result), void *ctx)
911 {
912 static const char msgtemplate[] =
913 "The session log file \"%.*s\" already exists.\n"
914 "You can overwrite it with a new session log,\n"
915 "append your session log to the end of it,\n"
916 "or disable session logging for this session.\n"
917 "Hit Yes to wipe the file, No to append to it,\n"
918 "or Cancel to disable logging.";
919 char *message;
920 char *mbtitle;
921 int mbret;
922
923 message = dupprintf(msgtemplate, FILENAME_MAX, filename.path);
924 mbtitle = dupprintf("%s Log to File", appname);
925
926 mbret = MessageBox(NULL, message, mbtitle,
927 MB_ICONQUESTION | MB_YESNOCANCEL | MB_DEFBUTTON3);
928
929 sfree(message);
930 sfree(mbtitle);
931
932 if (mbret == IDYES)
933 return 2;
934 else if (mbret == IDNO)
935 return 1;
936 else
937 return 0;
938 }
939
940 /*
941 * Warn about the obsolescent key file format.
942 *
943 * Uniquely among these functions, this one does _not_ expect a
944 * frontend handle. This means that if PuTTY is ported to a
945 * platform which requires frontend handles, this function will be
946 * an anomaly. Fortunately, the problem it addresses will not have
947 * been present on that platform, so it can plausibly be
948 * implemented as an empty function.
949 */
950 void old_keyfile_warning(void)
951 {
952 static const char mbtitle[] = "%s Key File Warning";
953 static const char message[] =
954 "You are loading an SSH 2 private key which has an\n"
955 "old version of the file format. This means your key\n"
956 "file is not fully tamperproof. Future versions of\n"
957 "%s may stop supporting this private key format,\n"
958 "so we recommend you convert your key to the new\n"
959 "format.\n"
960 "\n"
961 "You can perform this conversion by loading the key\n"
962 "into PuTTYgen and then saving it again.";
963
964 char *msg, *title;
965 msg = dupprintf(message, appname);
966 title = dupprintf(mbtitle, appname);
967
968 MessageBox(NULL, msg, title, MB_OK);
969
970 sfree(msg);
971 sfree(title);
972 }