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