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