59c0112a2656dfdf7f8dd80aaf08624e8d39e661
[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 * (Being a dialog procedure, in general it returns 0 if the default
366 * dialog processing should be performed, and 1 if it should not.)
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 int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
756 char *keystr, char *fingerprint,
757 void (*callback)(void *ctx, int result), void *ctx)
758 {
759 int ret;
760
761 static const char absentmsg[] =
762 "The server's host key is not cached in the registry. You\n"
763 "have no guarantee that the server is the computer you\n"
764 "think it is.\n"
765 "The server's %s key fingerprint is:\n"
766 "%s\n"
767 "If you trust this host, hit Yes to add the key to\n"
768 "%s's cache and carry on connecting.\n"
769 "If you want to carry on connecting just once, without\n"
770 "adding the key to the cache, hit No.\n"
771 "If you do not trust this host, hit Cancel to abandon the\n"
772 "connection.\n";
773
774 static const char wrongmsg[] =
775 "WARNING - POTENTIAL SECURITY BREACH!\n"
776 "\n"
777 "The server's host key does not match the one %s has\n"
778 "cached in the registry. This means that either the\n"
779 "server administrator has changed the host key, or you\n"
780 "have actually connected to another computer pretending\n"
781 "to be the server.\n"
782 "The new %s key fingerprint is:\n"
783 "%s\n"
784 "If you were expecting this change and trust the new key,\n"
785 "hit Yes to update %s's cache and continue connecting.\n"
786 "If you want to carry on connecting but without updating\n"
787 "the cache, hit No.\n"
788 "If you want to abandon the connection completely, hit\n"
789 "Cancel. Hitting Cancel is the ONLY guaranteed safe\n" "choice.\n";
790
791 static const char mbtitle[] = "%s Security Alert";
792
793 /*
794 * Verify the key against the registry.
795 */
796 ret = verify_host_key(host, port, keytype, keystr);
797
798 if (ret == 0) /* success - key matched OK */
799 return 1;
800 if (ret == 2) { /* key was different */
801 int mbret;
802 char *text = dupprintf(wrongmsg, appname, keytype, fingerprint,
803 appname);
804 char *caption = dupprintf(mbtitle, appname);
805 mbret = message_box(text, caption,
806 MB_ICONWARNING | MB_YESNOCANCEL | MB_DEFBUTTON3,
807 HELPCTXID(errors_hostkey_changed));
808 assert(mbret==IDYES || mbret==IDNO || mbret==IDCANCEL);
809 sfree(text);
810 sfree(caption);
811 if (mbret == IDYES) {
812 store_host_key(host, port, keytype, keystr);
813 return 1;
814 } else if (mbret == IDNO)
815 return 1;
816 return 0;
817 }
818 if (ret == 1) { /* key was absent */
819 int mbret;
820 char *text = dupprintf(absentmsg, keytype, fingerprint, appname);
821 char *caption = dupprintf(mbtitle, appname);
822 mbret = message_box(text, caption,
823 MB_ICONWARNING | MB_YESNOCANCEL | MB_DEFBUTTON3,
824 HELPCTXID(errors_hostkey_absent));
825 assert(mbret==IDYES || mbret==IDNO || mbret==IDCANCEL);
826 sfree(text);
827 sfree(caption);
828 if (mbret == IDYES) {
829 store_host_key(host, port, keytype, keystr);
830 return 1;
831 } else if (mbret == IDNO)
832 return 1;
833 return 0;
834 }
835 }
836
837 /*
838 * Ask whether the selected algorithm is acceptable (since it was
839 * below the configured 'warn' threshold).
840 */
841 int askalg(void *frontend, const char *algtype, const char *algname,
842 void (*callback)(void *ctx, int result), void *ctx)
843 {
844 static const char mbtitle[] = "%s Security Alert";
845 static const char msg[] =
846 "The first %s supported by the server\n"
847 "is %.64s, which is below the configured\n"
848 "warning threshold.\n"
849 "Do you want to continue with this connection?\n";
850 char *message, *title;
851 int mbret;
852
853 message = dupprintf(msg, algtype, algname);
854 title = dupprintf(mbtitle, appname);
855 mbret = MessageBox(NULL, message, title,
856 MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2);
857 sfree(message);
858 sfree(title);
859 if (mbret == IDYES)
860 return 1;
861 else
862 return 0;
863 }
864
865 /*
866 * Ask whether to wipe a session log file before writing to it.
867 * Returns 2 for wipe, 1 for append, 0 for cancel (don't log).
868 */
869 int askappend(void *frontend, Filename filename,
870 void (*callback)(void *ctx, int result), void *ctx)
871 {
872 static const char msgtemplate[] =
873 "The session log file \"%.*s\" already exists.\n"
874 "You can overwrite it with a new session log,\n"
875 "append your session log to the end of it,\n"
876 "or disable session logging for this session.\n"
877 "Hit Yes to wipe the file, No to append to it,\n"
878 "or Cancel to disable logging.";
879 char *message;
880 char *mbtitle;
881 int mbret;
882
883 message = dupprintf(msgtemplate, FILENAME_MAX, filename.path);
884 mbtitle = dupprintf("%s Log to File", appname);
885
886 mbret = MessageBox(NULL, message, mbtitle,
887 MB_ICONQUESTION | MB_YESNOCANCEL | MB_DEFBUTTON3);
888
889 sfree(message);
890 sfree(mbtitle);
891
892 if (mbret == IDYES)
893 return 2;
894 else if (mbret == IDNO)
895 return 1;
896 else
897 return 0;
898 }
899
900 /*
901 * Warn about the obsolescent key file format.
902 *
903 * Uniquely among these functions, this one does _not_ expect a
904 * frontend handle. This means that if PuTTY is ported to a
905 * platform which requires frontend handles, this function will be
906 * an anomaly. Fortunately, the problem it addresses will not have
907 * been present on that platform, so it can plausibly be
908 * implemented as an empty function.
909 */
910 void old_keyfile_warning(void)
911 {
912 static const char mbtitle[] = "%s Key File Warning";
913 static const char message[] =
914 "You are loading an SSH-2 private key which has an\n"
915 "old version of the file format. This means your key\n"
916 "file is not fully tamperproof. Future versions of\n"
917 "%s may stop supporting this private key format,\n"
918 "so we recommend you convert your key to the new\n"
919 "format.\n"
920 "\n"
921 "You can perform this conversion by loading the key\n"
922 "into PuTTYgen and then saving it again.";
923
924 char *msg, *title;
925 msg = dupprintf(message, appname);
926 title = dupprintf(mbtitle, appname);
927
928 MessageBox(NULL, msg, title, MB_OK);
929
930 sfree(msg);
931 sfree(title);
932 }