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