50d9cc69c30d67abab79540e9c6fb61a2c949ece
[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 struct sesslist sesslist; /* exported to window.c */
46
47 #define PRINTER_DISABLED_STRING "None (printing disabled)"
48
49 void force_normal(HWND hwnd)
50 {
51 static int recurse = 0;
52
53 WINDOWPLACEMENT wp;
54
55 if (recurse)
56 return;
57 recurse = 1;
58
59 wp.length = sizeof(wp);
60 if (GetWindowPlacement(hwnd, &wp) && wp.showCmd == SW_SHOWMAXIMIZED) {
61 wp.showCmd = SW_SHOWNORMAL;
62 SetWindowPlacement(hwnd, &wp);
63 }
64 recurse = 0;
65 }
66
67 static int CALLBACK LogProc(HWND hwnd, UINT msg,
68 WPARAM wParam, LPARAM lParam)
69 {
70 int i;
71
72 switch (msg) {
73 case WM_INITDIALOG:
74 {
75 char *str = dupprintf("%s Event Log", appname);
76 SetWindowText(hwnd, str);
77 sfree(str);
78 }
79 {
80 static int tabs[4] = { 78, 108 };
81 SendDlgItemMessage(hwnd, IDN_LIST, LB_SETTABSTOPS, 2,
82 (LPARAM) tabs);
83 }
84 for (i = 0; i < nevents; i++)
85 SendDlgItemMessage(hwnd, IDN_LIST, LB_ADDSTRING,
86 0, (LPARAM) events[i]);
87 return 1;
88 case WM_COMMAND:
89 switch (LOWORD(wParam)) {
90 case IDOK:
91 case IDCANCEL:
92 logbox = NULL;
93 SetActiveWindow(GetParent(hwnd));
94 DestroyWindow(hwnd);
95 return 0;
96 case IDN_COPY:
97 if (HIWORD(wParam) == BN_CLICKED ||
98 HIWORD(wParam) == BN_DOUBLECLICKED) {
99 int selcount;
100 int *selitems;
101 selcount = SendDlgItemMessage(hwnd, IDN_LIST,
102 LB_GETSELCOUNT, 0, 0);
103 if (selcount == 0) { /* don't even try to copy zero items */
104 MessageBeep(0);
105 break;
106 }
107
108 selitems = snewn(selcount, int);
109 if (selitems) {
110 int count = SendDlgItemMessage(hwnd, IDN_LIST,
111 LB_GETSELITEMS,
112 selcount,
113 (LPARAM) selitems);
114 int i;
115 int size;
116 char *clipdata;
117 static unsigned char sel_nl[] = SEL_NL;
118
119 if (count == 0) { /* can't copy zero stuff */
120 MessageBeep(0);
121 break;
122 }
123
124 size = 0;
125 for (i = 0; i < count; i++)
126 size +=
127 strlen(events[selitems[i]]) + sizeof(sel_nl);
128
129 clipdata = snewn(size, char);
130 if (clipdata) {
131 char *p = clipdata;
132 for (i = 0; i < count; i++) {
133 char *q = events[selitems[i]];
134 int qlen = strlen(q);
135 memcpy(p, q, qlen);
136 p += qlen;
137 memcpy(p, sel_nl, sizeof(sel_nl));
138 p += sizeof(sel_nl);
139 }
140 write_aclip(NULL, clipdata, size, TRUE);
141 sfree(clipdata);
142 }
143 sfree(selitems);
144
145 for (i = 0; i < nevents; i++)
146 SendDlgItemMessage(hwnd, IDN_LIST, LB_SETSEL,
147 FALSE, i);
148 }
149 }
150 return 0;
151 }
152 return 0;
153 case WM_CLOSE:
154 logbox = NULL;
155 SetActiveWindow(GetParent(hwnd));
156 DestroyWindow(hwnd);
157 return 0;
158 }
159 return 0;
160 }
161
162 static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
163 WPARAM wParam, LPARAM lParam)
164 {
165 switch (msg) {
166 case WM_INITDIALOG:
167 {
168 char *str = dupprintf("%s Licence", appname);
169 SetWindowText(hwnd, str);
170 sfree(str);
171 }
172 return 1;
173 case WM_COMMAND:
174 switch (LOWORD(wParam)) {
175 case IDOK:
176 case IDCANCEL:
177 EndDialog(hwnd, 1);
178 return 0;
179 }
180 return 0;
181 case WM_CLOSE:
182 EndDialog(hwnd, 1);
183 return 0;
184 }
185 return 0;
186 }
187
188 static int CALLBACK AboutProc(HWND hwnd, UINT msg,
189 WPARAM wParam, LPARAM lParam)
190 {
191 char *str;
192
193 switch (msg) {
194 case WM_INITDIALOG:
195 str = dupprintf("About %s", appname);
196 SetWindowText(hwnd, str);
197 sfree(str);
198 SetDlgItemText(hwnd, IDA_TEXT1, appname);
199 SetDlgItemText(hwnd, IDA_VERSION, ver);
200 return 1;
201 case WM_COMMAND:
202 switch (LOWORD(wParam)) {
203 case IDOK:
204 case IDCANCEL:
205 EndDialog(hwnd, TRUE);
206 return 0;
207 case IDA_LICENCE:
208 EnableWindow(hwnd, 0);
209 DialogBox(hinst, MAKEINTRESOURCE(IDD_LICENCEBOX),
210 hwnd, LicenceProc);
211 EnableWindow(hwnd, 1);
212 SetActiveWindow(hwnd);
213 return 0;
214
215 case IDA_WEB:
216 /* Load web browser */
217 ShellExecute(hwnd, "open",
218 "http://www.chiark.greenend.org.uk/~sgtatham/putty/",
219 0, 0, SW_SHOWDEFAULT);
220 return 0;
221 }
222 return 0;
223 case WM_CLOSE:
224 EndDialog(hwnd, TRUE);
225 return 0;
226 }
227 return 0;
228 }
229
230 static int SaneDialogBox(HINSTANCE hinst,
231 LPCTSTR tmpl,
232 HWND hwndparent,
233 DLGPROC lpDialogFunc)
234 {
235 WNDCLASS wc;
236 HWND hwnd;
237 MSG msg;
238 int flags;
239 int ret;
240 int gm;
241
242 wc.style = CS_DBLCLKS | CS_SAVEBITS | CS_BYTEALIGNWINDOW;
243 wc.lpfnWndProc = DefDlgProc;
244 wc.cbClsExtra = 0;
245 wc.cbWndExtra = DLGWINDOWEXTRA + 8;
246 wc.hInstance = hinst;
247 wc.hIcon = NULL;
248 wc.hCursor = LoadCursor(NULL, IDC_ARROW);
249 wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND +1);
250 wc.lpszMenuName = NULL;
251 wc.lpszClassName = "PuTTYConfigBox";
252 RegisterClass(&wc);
253
254 hwnd = CreateDialog(hinst, tmpl, hwndparent, lpDialogFunc);
255
256 SetWindowLong(hwnd, BOXFLAGS, 0); /* flags */
257 SetWindowLong(hwnd, BOXRESULT, 0); /* result from SaneEndDialog */
258
259 while ((gm=GetMessage(&msg, NULL, 0, 0)) > 0) {
260 flags=GetWindowLong(hwnd, BOXFLAGS);
261 if (!(flags & DF_END) && !IsDialogMessage(hwnd, &msg))
262 DispatchMessage(&msg);
263 if (flags & DF_END)
264 break;
265 }
266
267 if (gm == 0)
268 PostQuitMessage(msg.wParam); /* We got a WM_QUIT, pass it on */
269
270 ret=GetWindowLong(hwnd, BOXRESULT);
271 DestroyWindow(hwnd);
272 return ret;
273 }
274
275 static void SaneEndDialog(HWND hwnd, int ret)
276 {
277 SetWindowLong(hwnd, BOXRESULT, ret);
278 SetWindowLong(hwnd, BOXFLAGS, DF_END);
279 }
280
281 /*
282 * Null dialog procedure.
283 */
284 static int CALLBACK NullDlgProc(HWND hwnd, UINT msg,
285 WPARAM wParam, LPARAM lParam)
286 {
287 return 0;
288 }
289
290 enum {
291 IDCX_ABOUT = IDC_ABOUT,
292 IDCX_TVSTATIC,
293 IDCX_TREEVIEW,
294 IDCX_STDBASE,
295 IDCX_PANELBASE = IDCX_STDBASE + 32
296 };
297
298 struct treeview_faff {
299 HWND treeview;
300 HTREEITEM lastat[4];
301 };
302
303 static HTREEITEM treeview_insert(struct treeview_faff *faff,
304 int level, char *text, char *path)
305 {
306 TVINSERTSTRUCT ins;
307 int i;
308 HTREEITEM newitem;
309 ins.hParent = (level > 0 ? faff->lastat[level - 1] : TVI_ROOT);
310 ins.hInsertAfter = faff->lastat[level];
311 #if _WIN32_IE >= 0x0400 && defined NONAMELESSUNION
312 #define INSITEM DUMMYUNIONNAME.item
313 #else
314 #define INSITEM item
315 #endif
316 ins.INSITEM.mask = TVIF_TEXT | TVIF_PARAM;
317 ins.INSITEM.pszText = text;
318 ins.INSITEM.cchTextMax = strlen(text)+1;
319 ins.INSITEM.lParam = (LPARAM)path;
320 newitem = TreeView_InsertItem(faff->treeview, &ins);
321 if (level > 0)
322 TreeView_Expand(faff->treeview, faff->lastat[level - 1],
323 TVE_EXPAND);
324 faff->lastat[level] = newitem;
325 for (i = level + 1; i < 4; i++)
326 faff->lastat[i] = NULL;
327 return newitem;
328 }
329
330 /*
331 * Create the panelfuls of controls in the configuration box.
332 */
333 static void create_controls(HWND hwnd, char *path)
334 {
335 struct ctlpos cp;
336 int index;
337 int base_id;
338 struct winctrls *wc;
339
340 if (!path[0]) {
341 /*
342 * Here we must create the basic standard controls.
343 */
344 ctlposinit(&cp, hwnd, 3, 3, 235);
345 wc = &ctrls_base;
346 base_id = IDCX_STDBASE;
347 } else {
348 /*
349 * Otherwise, we're creating the controls for a particular
350 * panel.
351 */
352 ctlposinit(&cp, hwnd, 100, 3, 13);
353 wc = &ctrls_panel;
354 base_id = IDCX_PANELBASE;
355 }
356
357 for (index=-1; (index = ctrl_find_path(ctrlbox, path, index)) >= 0 ;) {
358 struct controlset *s = ctrlbox->ctrlsets[index];
359 winctrl_layout(&dp, wc, &cp, s, &base_id);
360 }
361 }
362
363 /*
364 * This function is the configuration box.
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, &sesslist, 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 get_sesslist(&sesslist, TRUE);
663 ret =
664 SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL,
665 GenericMainDlgProc);
666 get_sesslist(&sesslist, FALSE);
667
668 ctrl_free_box(ctrlbox);
669 winctrl_cleanup(&ctrls_panel);
670 winctrl_cleanup(&ctrls_base);
671 dp_cleanup(&dp);
672
673 return ret;
674 }
675
676 int do_reconfig(HWND hwnd, int protcfginfo)
677 {
678 Config backup_cfg;
679 int ret;
680
681 backup_cfg = cfg; /* structure copy */
682
683 ctrlbox = ctrl_new_box();
684 setup_config_box(ctrlbox, &sesslist, TRUE, cfg.protocol, protcfginfo);
685 win_setup_config_box(ctrlbox, &dp.hwnd, (help_path != NULL), TRUE);
686 dp_init(&dp);
687 winctrl_init(&ctrls_base);
688 winctrl_init(&ctrls_panel);
689 dp_add_tree(&dp, &ctrls_base);
690 dp_add_tree(&dp, &ctrls_panel);
691 dp.wintitle = dupprintf("%s Reconfiguration", appname);
692 dp.errtitle = dupprintf("%s Error", appname);
693 dp.data = &cfg;
694 dp.shortcuts['g'] = TRUE; /* the treeview: `Cate&gory' */
695
696 ret = SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL,
697 GenericMainDlgProc);
698
699 ctrl_free_box(ctrlbox);
700 winctrl_cleanup(&ctrls_base);
701 winctrl_cleanup(&ctrls_panel);
702 dp_cleanup(&dp);
703
704 if (!ret)
705 cfg = backup_cfg; /* structure copy */
706
707 return ret;
708 }
709
710 void logevent(void *frontend, const char *string)
711 {
712 char timebuf[40];
713 struct tm tm;
714
715 log_eventlog(logctx, string);
716
717 if (nevents >= negsize) {
718 negsize += 64;
719 events = sresize(events, negsize, char *);
720 }
721
722 tm=ltime();
723 strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm);
724
725 events[nevents] = snewn(strlen(timebuf) + strlen(string) + 1, char);
726 strcpy(events[nevents], timebuf);
727 strcat(events[nevents], string);
728 if (logbox) {
729 int count;
730 SendDlgItemMessage(logbox, IDN_LIST, LB_ADDSTRING,
731 0, (LPARAM) events[nevents]);
732 count = SendDlgItemMessage(logbox, IDN_LIST, LB_GETCOUNT, 0, 0);
733 SendDlgItemMessage(logbox, IDN_LIST, LB_SETTOPINDEX, count - 1, 0);
734 }
735 nevents++;
736 }
737
738 void showeventlog(HWND hwnd)
739 {
740 if (!logbox) {
741 logbox = CreateDialog(hinst, MAKEINTRESOURCE(IDD_LOGBOX),
742 hwnd, LogProc);
743 ShowWindow(logbox, SW_SHOWNORMAL);
744 }
745 SetActiveWindow(logbox);
746 }
747
748 void showabout(HWND hwnd)
749 {
750 DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc);
751 }
752
753 int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
754 char *keystr, char *fingerprint,
755 void (*callback)(void *ctx, int result), void *ctx)
756 {
757 int ret;
758
759 static const char absentmsg[] =
760 "The server's host key is not cached in the registry. You\n"
761 "have no guarantee that the server is the computer you\n"
762 "think it is.\n"
763 "The server's %s key fingerprint is:\n"
764 "%s\n"
765 "If you trust this host, hit Yes to add the key to\n"
766 "%s's cache and carry on connecting.\n"
767 "If you want to carry on connecting just once, without\n"
768 "adding the key to the cache, hit No.\n"
769 "If you do not trust this host, hit Cancel to abandon the\n"
770 "connection.\n";
771
772 static const char wrongmsg[] =
773 "WARNING - POTENTIAL SECURITY BREACH!\n"
774 "\n"
775 "The server's host key does not match the one %s has\n"
776 "cached in the registry. This means that either the\n"
777 "server administrator has changed the host key, or you\n"
778 "have actually connected to another computer pretending\n"
779 "to be the server.\n"
780 "The new %s key fingerprint is:\n"
781 "%s\n"
782 "If you were expecting this change and trust the new key,\n"
783 "hit Yes to update %s's cache and continue connecting.\n"
784 "If you want to carry on connecting but without updating\n"
785 "the cache, hit No.\n"
786 "If you want to abandon the connection completely, hit\n"
787 "Cancel. Hitting Cancel is the ONLY guaranteed safe\n" "choice.\n";
788
789 static const char mbtitle[] = "%s Security Alert";
790
791 /*
792 * Verify the key against the registry.
793 */
794 ret = verify_host_key(host, port, keytype, keystr);
795
796 if (ret == 0) /* success - key matched OK */
797 return 1;
798 if (ret == 2) { /* key was different */
799 int mbret;
800 char *text = dupprintf(wrongmsg, appname, keytype, fingerprint,
801 appname);
802 char *caption = dupprintf(mbtitle, appname);
803 mbret = message_box(text, caption,
804 MB_ICONWARNING | MB_YESNOCANCEL | MB_DEFBUTTON3,
805 HELPCTXID(errors_hostkey_changed));
806 assert(mbret==IDYES || mbret==IDNO || mbret==IDCANCEL);
807 sfree(text);
808 sfree(caption);
809 if (mbret == IDYES) {
810 store_host_key(host, port, keytype, keystr);
811 return 1;
812 } else if (mbret == IDNO)
813 return 1;
814 return 0;
815 }
816 if (ret == 1) { /* key was absent */
817 int mbret;
818 char *text = dupprintf(absentmsg, keytype, fingerprint, appname);
819 char *caption = dupprintf(mbtitle, appname);
820 mbret = message_box(text, caption,
821 MB_ICONWARNING | MB_YESNOCANCEL | MB_DEFBUTTON3,
822 HELPCTXID(errors_hostkey_absent));
823 assert(mbret==IDYES || mbret==IDNO || mbret==IDCANCEL);
824 sfree(text);
825 sfree(caption);
826 if (mbret == IDYES) {
827 store_host_key(host, port, keytype, keystr);
828 return 1;
829 } else if (mbret == IDNO)
830 return 1;
831 return 0;
832 }
833 }
834
835 /*
836 * Ask whether the selected algorithm is acceptable (since it was
837 * below the configured 'warn' threshold).
838 */
839 int askalg(void *frontend, const char *algtype, const char *algname,
840 void (*callback)(void *ctx, int result), void *ctx)
841 {
842 static const char mbtitle[] = "%s Security Alert";
843 static const char msg[] =
844 "The first %s supported by the server\n"
845 "is %.64s, which is below the configured\n"
846 "warning threshold.\n"
847 "Do you want to continue with this connection?\n";
848 char *message, *title;
849 int mbret;
850
851 message = dupprintf(msg, algtype, algname);
852 title = dupprintf(mbtitle, appname);
853 mbret = MessageBox(NULL, message, title,
854 MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2);
855 sfree(message);
856 sfree(title);
857 if (mbret == IDYES)
858 return 1;
859 else
860 return 0;
861 }
862
863 /*
864 * Ask whether to wipe a session log file before writing to it.
865 * Returns 2 for wipe, 1 for append, 0 for cancel (don't log).
866 */
867 int askappend(void *frontend, Filename filename,
868 void (*callback)(void *ctx, int result), void *ctx)
869 {
870 static const char msgtemplate[] =
871 "The session log file \"%.*s\" already exists.\n"
872 "You can overwrite it with a new session log,\n"
873 "append your session log to the end of it,\n"
874 "or disable session logging for this session.\n"
875 "Hit Yes to wipe the file, No to append to it,\n"
876 "or Cancel to disable logging.";
877 char *message;
878 char *mbtitle;
879 int mbret;
880
881 message = dupprintf(msgtemplate, FILENAME_MAX, filename.path);
882 mbtitle = dupprintf("%s Log to File", appname);
883
884 mbret = MessageBox(NULL, message, mbtitle,
885 MB_ICONQUESTION | MB_YESNOCANCEL | MB_DEFBUTTON3);
886
887 sfree(message);
888 sfree(mbtitle);
889
890 if (mbret == IDYES)
891 return 2;
892 else if (mbret == IDNO)
893 return 1;
894 else
895 return 0;
896 }
897
898 /*
899 * Warn about the obsolescent key file format.
900 *
901 * Uniquely among these functions, this one does _not_ expect a
902 * frontend handle. This means that if PuTTY is ported to a
903 * platform which requires frontend handles, this function will be
904 * an anomaly. Fortunately, the problem it addresses will not have
905 * been present on that platform, so it can plausibly be
906 * implemented as an empty function.
907 */
908 void old_keyfile_warning(void)
909 {
910 static const char mbtitle[] = "%s Key File Warning";
911 static const char message[] =
912 "You are loading an SSH 2 private key which has an\n"
913 "old version of the file format. This means your key\n"
914 "file is not fully tamperproof. Future versions of\n"
915 "%s may stop supporting this private key format,\n"
916 "so we recommend you convert your key to the new\n"
917 "format.\n"
918 "\n"
919 "You can perform this conversion by loading the key\n"
920 "into PuTTYgen and then saving it again.";
921
922 char *msg, *title;
923 msg = dupprintf(message, appname);
924 title = dupprintf(mbtitle, appname);
925
926 MessageBox(NULL, msg, title, MB_OK);
927
928 sfree(msg);
929 sfree(title);
930 }