If a new session was saved from Change Settings, a side-effect on Windows was
[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 + 8;
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 SetWindowLong(hwnd, BOXFLAGS, 0); /* flags */
255 SetWindowLong(hwnd, BOXRESULT, 0); /* result from SaneEndDialog */
256
257 while ((gm=GetMessage(&msg, NULL, 0, 0)) > 0) {
258 flags=GetWindowLong(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=GetWindowLong(hwnd, BOXRESULT);
269 DestroyWindow(hwnd);
270 return ret;
271 }
272
273 static void SaneEndDialog(HWND hwnd, int ret)
274 {
275 SetWindowLong(hwnd, BOXRESULT, ret);
276 SetWindowLong(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 SetWindowLong(hwnd, GWL_USERDATA, 0);
379 if (help_path)
380 SetWindowLong(hwnd, GWL_EXSTYLE,
381 GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_CONTEXTHELP);
382 else {
383 HWND item = GetDlgItem(hwnd, IDC_HELPBTN);
384 if (item)
385 DestroyWindow(item);
386 }
387 requested_help = FALSE;
388 SendMessage(hwnd, WM_SETICON, (WPARAM) ICON_BIG,
389 (LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(IDI_CFGICON)));
390 /*
391 * Centre the window.
392 */
393 { /* centre the window */
394 RECT rs, rd;
395
396 hw = GetDesktopWindow();
397 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
398 MoveWindow(hwnd,
399 (rs.right + rs.left + rd.left - rd.right) / 2,
400 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
401 rd.right - rd.left, rd.bottom - rd.top, TRUE);
402 }
403
404 /*
405 * Create the tree view.
406 */
407 {
408 RECT r;
409 WPARAM font;
410 HWND tvstatic;
411
412 r.left = 3;
413 r.right = r.left + 95;
414 r.top = 3;
415 r.bottom = r.top + 10;
416 MapDialogRect(hwnd, &r);
417 tvstatic = CreateWindowEx(0, "STATIC", "Cate&gory:",
418 WS_CHILD | WS_VISIBLE,
419 r.left, r.top,
420 r.right - r.left, r.bottom - r.top,
421 hwnd, (HMENU) IDCX_TVSTATIC, hinst,
422 NULL);
423 font = SendMessage(hwnd, WM_GETFONT, 0, 0);
424 SendMessage(tvstatic, WM_SETFONT, font, MAKELPARAM(TRUE, 0));
425
426 r.left = 3;
427 r.right = r.left + 95;
428 r.top = 13;
429 r.bottom = r.top + 219;
430 MapDialogRect(hwnd, &r);
431 treeview = CreateWindowEx(WS_EX_CLIENTEDGE, WC_TREEVIEW, "",
432 WS_CHILD | WS_VISIBLE |
433 WS_TABSTOP | TVS_HASLINES |
434 TVS_DISABLEDRAGDROP | TVS_HASBUTTONS
435 | TVS_LINESATROOT |
436 TVS_SHOWSELALWAYS, r.left, r.top,
437 r.right - r.left, r.bottom - r.top,
438 hwnd, (HMENU) IDCX_TREEVIEW, hinst,
439 NULL);
440 font = SendMessage(hwnd, WM_GETFONT, 0, 0);
441 SendMessage(treeview, WM_SETFONT, font, MAKELPARAM(TRUE, 0));
442 tvfaff.treeview = treeview;
443 memset(tvfaff.lastat, 0, sizeof(tvfaff.lastat));
444 }
445
446 /*
447 * Set up the tree view contents.
448 */
449 {
450 HTREEITEM hfirst = NULL;
451 int i;
452 char *path = NULL;
453
454 for (i = 0; i < ctrlbox->nctrlsets; i++) {
455 struct controlset *s = ctrlbox->ctrlsets[i];
456 HTREEITEM item;
457 int j;
458 char *c;
459
460 if (!s->pathname[0])
461 continue;
462 j = path ? ctrl_path_compare(s->pathname, path) : 0;
463 if (j == INT_MAX)
464 continue; /* same path, nothing to add to tree */
465
466 /*
467 * We expect never to find an implicit path
468 * component. For example, we expect never to see
469 * A/B/C followed by A/D/E, because that would
470 * _implicitly_ create A/D. All our path prefixes
471 * are expected to contain actual controls and be
472 * selectable in the treeview; so we would expect
473 * to see A/D _explicitly_ before encountering
474 * A/D/E.
475 */
476 assert(j == ctrl_path_elements(s->pathname) - 1);
477
478 c = strrchr(s->pathname, '/');
479 if (!c)
480 c = s->pathname;
481 else
482 c++;
483
484 item = treeview_insert(&tvfaff, j, c, s->pathname);
485 if (!hfirst)
486 hfirst = item;
487
488 path = s->pathname;
489 }
490
491 /*
492 * Put the treeview selection on to the Session panel.
493 * This should also cause creation of the relevant
494 * controls.
495 */
496 TreeView_SelectItem(treeview, hfirst);
497 }
498
499 /*
500 * Set focus into the first available control.
501 */
502 {
503 int i;
504 struct winctrl *c;
505
506 for (i = 0; (c = winctrl_findbyindex(&ctrls_panel, i)) != NULL;
507 i++) {
508 if (c->ctrl) {
509 dlg_set_focus(c->ctrl, &dp);
510 break;
511 }
512 }
513 }
514
515 SetWindowLong(hwnd, GWL_USERDATA, 1);
516 return 0;
517 case WM_LBUTTONUP:
518 /*
519 * Button release should trigger WM_OK if there was a
520 * previous double click on the session list.
521 */
522 ReleaseCapture();
523 if (dp.ended)
524 SaneEndDialog(hwnd, dp.endresult ? 1 : 0);
525 break;
526 case WM_NOTIFY:
527 if (LOWORD(wParam) == IDCX_TREEVIEW &&
528 ((LPNMHDR) lParam)->code == TVN_SELCHANGED) {
529 HTREEITEM i =
530 TreeView_GetSelection(((LPNMHDR) lParam)->hwndFrom);
531 TVITEM item;
532 char buffer[64];
533
534 SendMessage (hwnd, WM_SETREDRAW, FALSE, 0);
535
536 item.hItem = i;
537 item.pszText = buffer;
538 item.cchTextMax = sizeof(buffer);
539 item.mask = TVIF_TEXT | TVIF_PARAM;
540 TreeView_GetItem(((LPNMHDR) lParam)->hwndFrom, &item);
541 {
542 /* Destroy all controls in the currently visible panel. */
543 int k;
544 HWND item;
545 struct winctrl *c;
546
547 while ((c = winctrl_findbyindex(&ctrls_panel, 0)) != NULL) {
548 for (k = 0; k < c->num_ids; k++) {
549 item = GetDlgItem(hwnd, c->base_id + k);
550 if (item)
551 DestroyWindow(item);
552 }
553 winctrl_rem_shortcuts(&dp, c);
554 winctrl_remove(&ctrls_panel, c);
555 sfree(c->data);
556 sfree(c);
557 }
558 }
559 create_controls(hwnd, (char *)item.lParam);
560
561 dlg_refresh(NULL, &dp); /* set up control values */
562
563 SendMessage (hwnd, WM_SETREDRAW, TRUE, 0);
564 InvalidateRect (hwnd, NULL, TRUE);
565
566 SetFocus(((LPNMHDR) lParam)->hwndFrom); /* ensure focus stays */
567 return 0;
568 }
569 break;
570 case WM_COMMAND:
571 case WM_DRAWITEM:
572 default: /* also handle drag list msg here */
573 /*
574 * Only process WM_COMMAND once the dialog is fully formed.
575 */
576 if (GetWindowLong(hwnd, GWL_USERDATA) == 1) {
577 ret = winctrl_handle_command(&dp, msg, wParam, lParam);
578 if (dp.ended && GetCapture() != hwnd)
579 SaneEndDialog(hwnd, dp.endresult ? 1 : 0);
580 } else
581 ret = 0;
582 return ret;
583 case WM_HELP:
584 if (help_path) {
585 if (winctrl_context_help(&dp, hwnd,
586 ((LPHELPINFO)lParam)->iCtrlId))
587 requested_help = TRUE;
588 else
589 MessageBeep(0);
590 }
591 break;
592 case WM_CLOSE:
593 if (requested_help) {
594 WinHelp(hwnd, help_path, HELP_QUIT, 0);
595 requested_help = FALSE;
596 }
597 SaneEndDialog(hwnd, 0);
598 return 0;
599
600 /* Grrr Explorer will maximize Dialogs! */
601 case WM_SIZE:
602 if (wParam == SIZE_MAXIMIZED)
603 force_normal(hwnd);
604 return 0;
605
606 }
607 return 0;
608 }
609
610 void modal_about_box(HWND hwnd)
611 {
612 EnableWindow(hwnd, 0);
613 DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc);
614 EnableWindow(hwnd, 1);
615 SetActiveWindow(hwnd);
616 }
617
618 void show_help(HWND hwnd)
619 {
620 if (help_path) {
621 WinHelp(hwnd, help_path,
622 help_has_contents ? HELP_FINDER : HELP_CONTENTS,
623 0);
624 requested_help = TRUE;
625 }
626 }
627
628 void defuse_showwindow(void)
629 {
630 /*
631 * Work around the fact that the app's first call to ShowWindow
632 * will ignore the default in favour of the shell-provided
633 * setting.
634 */
635 {
636 HWND hwnd;
637 hwnd = CreateDialog(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX),
638 NULL, NullDlgProc);
639 ShowWindow(hwnd, SW_HIDE);
640 SetActiveWindow(hwnd);
641 DestroyWindow(hwnd);
642 }
643 }
644
645 int do_config(void)
646 {
647 int ret;
648
649 ctrlbox = ctrl_new_box();
650 setup_config_box(ctrlbox, FALSE, 0, 0);
651 win_setup_config_box(ctrlbox, &dp.hwnd, (help_path != NULL), FALSE);
652 dp_init(&dp);
653 winctrl_init(&ctrls_base);
654 winctrl_init(&ctrls_panel);
655 dp_add_tree(&dp, &ctrls_base);
656 dp_add_tree(&dp, &ctrls_panel);
657 dp.wintitle = dupprintf("%s Configuration", appname);
658 dp.errtitle = dupprintf("%s Error", appname);
659 dp.data = &cfg;
660 dp.shortcuts['g'] = TRUE; /* the treeview: `Cate&gory' */
661
662 ret =
663 SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL,
664 GenericMainDlgProc);
665
666 ctrl_free_box(ctrlbox);
667 winctrl_cleanup(&ctrls_panel);
668 winctrl_cleanup(&ctrls_base);
669 dp_cleanup(&dp);
670
671 return ret;
672 }
673
674 int do_reconfig(HWND hwnd, int protcfginfo)
675 {
676 Config backup_cfg;
677 int ret;
678
679 backup_cfg = cfg; /* structure copy */
680
681 ctrlbox = ctrl_new_box();
682 setup_config_box(ctrlbox, TRUE, cfg.protocol, protcfginfo);
683 win_setup_config_box(ctrlbox, &dp.hwnd, (help_path != NULL), TRUE);
684 dp_init(&dp);
685 winctrl_init(&ctrls_base);
686 winctrl_init(&ctrls_panel);
687 dp_add_tree(&dp, &ctrls_base);
688 dp_add_tree(&dp, &ctrls_panel);
689 dp.wintitle = dupprintf("%s Reconfiguration", appname);
690 dp.errtitle = dupprintf("%s Error", appname);
691 dp.data = &cfg;
692 dp.shortcuts['g'] = TRUE; /* the treeview: `Cate&gory' */
693
694 ret = SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL,
695 GenericMainDlgProc);
696
697 ctrl_free_box(ctrlbox);
698 winctrl_cleanup(&ctrls_base);
699 winctrl_cleanup(&ctrls_panel);
700 dp_cleanup(&dp);
701
702 if (!ret)
703 cfg = backup_cfg; /* structure copy */
704
705 return ret;
706 }
707
708 void logevent(void *frontend, const char *string)
709 {
710 char timebuf[40];
711 struct tm tm;
712
713 log_eventlog(logctx, string);
714
715 if (nevents >= negsize) {
716 negsize += 64;
717 events = sresize(events, negsize, char *);
718 }
719
720 tm=ltime();
721 strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm);
722
723 events[nevents] = snewn(strlen(timebuf) + strlen(string) + 1, char);
724 strcpy(events[nevents], timebuf);
725 strcat(events[nevents], string);
726 if (logbox) {
727 int count;
728 SendDlgItemMessage(logbox, IDN_LIST, LB_ADDSTRING,
729 0, (LPARAM) events[nevents]);
730 count = SendDlgItemMessage(logbox, IDN_LIST, LB_GETCOUNT, 0, 0);
731 SendDlgItemMessage(logbox, IDN_LIST, LB_SETTOPINDEX, count - 1, 0);
732 }
733 nevents++;
734 }
735
736 void showeventlog(HWND hwnd)
737 {
738 if (!logbox) {
739 logbox = CreateDialog(hinst, MAKEINTRESOURCE(IDD_LOGBOX),
740 hwnd, LogProc);
741 ShowWindow(logbox, SW_SHOWNORMAL);
742 }
743 SetActiveWindow(logbox);
744 }
745
746 void showabout(HWND hwnd)
747 {
748 DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc);
749 }
750
751 int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
752 char *keystr, char *fingerprint,
753 void (*callback)(void *ctx, int result), void *ctx)
754 {
755 int ret;
756
757 static const char absentmsg[] =
758 "The server's host key is not cached in the registry. You\n"
759 "have no guarantee that the server is the computer you\n"
760 "think it is.\n"
761 "The server's %s key fingerprint is:\n"
762 "%s\n"
763 "If you trust this host, hit Yes to add the key to\n"
764 "%s's cache and carry on connecting.\n"
765 "If you want to carry on connecting just once, without\n"
766 "adding the key to the cache, hit No.\n"
767 "If you do not trust this host, hit Cancel to abandon the\n"
768 "connection.\n";
769
770 static const char wrongmsg[] =
771 "WARNING - POTENTIAL SECURITY BREACH!\n"
772 "\n"
773 "The server's host key does not match the one %s has\n"
774 "cached in the registry. This means that either the\n"
775 "server administrator has changed the host key, or you\n"
776 "have actually connected to another computer pretending\n"
777 "to be the server.\n"
778 "The new %s key fingerprint is:\n"
779 "%s\n"
780 "If you were expecting this change and trust the new key,\n"
781 "hit Yes to update %s's cache and continue connecting.\n"
782 "If you want to carry on connecting but without updating\n"
783 "the cache, hit No.\n"
784 "If you want to abandon the connection completely, hit\n"
785 "Cancel. Hitting Cancel is the ONLY guaranteed safe\n" "choice.\n";
786
787 static const char mbtitle[] = "%s Security Alert";
788
789 /*
790 * Verify the key against the registry.
791 */
792 ret = verify_host_key(host, port, keytype, keystr);
793
794 if (ret == 0) /* success - key matched OK */
795 return 1;
796 if (ret == 2) { /* key was different */
797 int mbret;
798 char *text = dupprintf(wrongmsg, appname, keytype, fingerprint,
799 appname);
800 char *caption = dupprintf(mbtitle, appname);
801 mbret = message_box(text, caption,
802 MB_ICONWARNING | MB_YESNOCANCEL | MB_DEFBUTTON3,
803 HELPCTXID(errors_hostkey_changed));
804 assert(mbret==IDYES || mbret==IDNO || mbret==IDCANCEL);
805 sfree(text);
806 sfree(caption);
807 if (mbret == IDYES) {
808 store_host_key(host, port, keytype, keystr);
809 return 1;
810 } else if (mbret == IDNO)
811 return 1;
812 return 0;
813 }
814 if (ret == 1) { /* key was absent */
815 int mbret;
816 char *text = dupprintf(absentmsg, keytype, fingerprint, appname);
817 char *caption = dupprintf(mbtitle, appname);
818 mbret = message_box(text, caption,
819 MB_ICONWARNING | MB_YESNOCANCEL | MB_DEFBUTTON3,
820 HELPCTXID(errors_hostkey_absent));
821 assert(mbret==IDYES || mbret==IDNO || mbret==IDCANCEL);
822 sfree(text);
823 sfree(caption);
824 if (mbret == IDYES) {
825 store_host_key(host, port, keytype, keystr);
826 return 1;
827 } else if (mbret == IDNO)
828 return 1;
829 return 0;
830 }
831 }
832
833 /*
834 * Ask whether the selected algorithm is acceptable (since it was
835 * below the configured 'warn' threshold).
836 */
837 int askalg(void *frontend, const char *algtype, const char *algname,
838 void (*callback)(void *ctx, int result), void *ctx)
839 {
840 static const char mbtitle[] = "%s Security Alert";
841 static const char msg[] =
842 "The first %s supported by the server\n"
843 "is %.64s, which is below the configured\n"
844 "warning threshold.\n"
845 "Do you want to continue with this connection?\n";
846 char *message, *title;
847 int mbret;
848
849 message = dupprintf(msg, algtype, algname);
850 title = dupprintf(mbtitle, appname);
851 mbret = MessageBox(NULL, message, title,
852 MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2);
853 sfree(message);
854 sfree(title);
855 if (mbret == IDYES)
856 return 1;
857 else
858 return 0;
859 }
860
861 /*
862 * Ask whether to wipe a session log file before writing to it.
863 * Returns 2 for wipe, 1 for append, 0 for cancel (don't log).
864 */
865 int askappend(void *frontend, Filename filename,
866 void (*callback)(void *ctx, int result), void *ctx)
867 {
868 static const char msgtemplate[] =
869 "The session log file \"%.*s\" already exists.\n"
870 "You can overwrite it with a new session log,\n"
871 "append your session log to the end of it,\n"
872 "or disable session logging for this session.\n"
873 "Hit Yes to wipe the file, No to append to it,\n"
874 "or Cancel to disable logging.";
875 char *message;
876 char *mbtitle;
877 int mbret;
878
879 message = dupprintf(msgtemplate, FILENAME_MAX, filename.path);
880 mbtitle = dupprintf("%s Log to File", appname);
881
882 mbret = MessageBox(NULL, message, mbtitle,
883 MB_ICONQUESTION | MB_YESNOCANCEL | MB_DEFBUTTON3);
884
885 sfree(message);
886 sfree(mbtitle);
887
888 if (mbret == IDYES)
889 return 2;
890 else if (mbret == IDNO)
891 return 1;
892 else
893 return 0;
894 }
895
896 /*
897 * Warn about the obsolescent key file format.
898 *
899 * Uniquely among these functions, this one does _not_ expect a
900 * frontend handle. This means that if PuTTY is ported to a
901 * platform which requires frontend handles, this function will be
902 * an anomaly. Fortunately, the problem it addresses will not have
903 * been present on that platform, so it can plausibly be
904 * implemented as an empty function.
905 */
906 void old_keyfile_warning(void)
907 {
908 static const char mbtitle[] = "%s Key File Warning";
909 static const char message[] =
910 "You are loading an SSH-2 private key which has an\n"
911 "old version of the file format. This means your key\n"
912 "file is not fully tamperproof. Future versions of\n"
913 "%s may stop supporting this private key format,\n"
914 "so we recommend you convert your key to the new\n"
915 "format.\n"
916 "\n"
917 "You can perform this conversion by loading the key\n"
918 "into PuTTYgen and then saving it again.";
919
920 char *msg, *title;
921 msg = dupprintf(message, appname);
922 title = dupprintf(mbtitle, appname);
923
924 MessageBox(NULL, msg, title, MB_OK);
925
926 sfree(msg);
927 sfree(title);
928 }