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