]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - windows/windlg.c
db1ef2f3df1d1e7d2284da511907e954136910b2
[PuTTY.git] / 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 (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 = &cfg;
652     dp.shortcuts['g'] = TRUE;          /* the treeview: `Cate&gory' */
653
654     ret =
655         SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL,
656                   GenericMainDlgProc);
657
658     ctrl_free_box(ctrlbox);
659     winctrl_cleanup(&ctrls_panel);
660     winctrl_cleanup(&ctrls_base);
661     dp_cleanup(&dp);
662
663     return ret;
664 }
665
666 int do_reconfig(HWND hwnd, int protcfginfo)
667 {
668     Config backup_cfg;
669     int ret;
670
671     backup_cfg = cfg;                  /* structure copy */
672
673     ctrlbox = ctrl_new_box();
674     setup_config_box(ctrlbox, TRUE, cfg.protocol, protcfginfo);
675     win_setup_config_box(ctrlbox, &dp.hwnd, has_help(), TRUE,
676                          cfg.protocol);
677     dp_init(&dp);
678     winctrl_init(&ctrls_base);
679     winctrl_init(&ctrls_panel);
680     dp_add_tree(&dp, &ctrls_base);
681     dp_add_tree(&dp, &ctrls_panel);
682     dp.wintitle = dupprintf("%s Reconfiguration", appname);
683     dp.errtitle = dupprintf("%s Error", appname);
684     dp.data = &cfg;
685     dp.shortcuts['g'] = TRUE;          /* the treeview: `Cate&gory' */
686
687     ret = SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL,
688                   GenericMainDlgProc);
689
690     ctrl_free_box(ctrlbox);
691     winctrl_cleanup(&ctrls_base);
692     winctrl_cleanup(&ctrls_panel);
693     dp_cleanup(&dp);
694
695     if (!ret)
696         cfg = backup_cfg;              /* structure copy */
697
698     return ret;
699 }
700
701 void logevent(void *frontend, const char *string)
702 {
703     char timebuf[40];
704     struct tm tm;
705
706     log_eventlog(logctx, string);
707
708     if (nevents >= negsize) {
709         negsize += 64;
710         events = sresize(events, negsize, char *);
711     }
712
713     tm=ltime();
714     strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm);
715
716     events[nevents] = snewn(strlen(timebuf) + strlen(string) + 1, char);
717     strcpy(events[nevents], timebuf);
718     strcat(events[nevents], string);
719     if (logbox) {
720         int count;
721         SendDlgItemMessage(logbox, IDN_LIST, LB_ADDSTRING,
722                            0, (LPARAM) events[nevents]);
723         count = SendDlgItemMessage(logbox, IDN_LIST, LB_GETCOUNT, 0, 0);
724         SendDlgItemMessage(logbox, IDN_LIST, LB_SETTOPINDEX, count - 1, 0);
725     }
726     nevents++;
727 }
728
729 void showeventlog(HWND hwnd)
730 {
731     if (!logbox) {
732         logbox = CreateDialog(hinst, MAKEINTRESOURCE(IDD_LOGBOX),
733                               hwnd, LogProc);
734         ShowWindow(logbox, SW_SHOWNORMAL);
735     }
736     SetActiveWindow(logbox);
737 }
738
739 void showabout(HWND hwnd)
740 {
741     DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc);
742 }
743
744 int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
745                         char *keystr, char *fingerprint,
746                         void (*callback)(void *ctx, int result), void *ctx)
747 {
748     int ret;
749
750     static const char absentmsg[] =
751         "The server's host key is not cached in the registry. You\n"
752         "have no guarantee that the server is the computer you\n"
753         "think it is.\n"
754         "The server's %s key fingerprint is:\n"
755         "%s\n"
756         "If you trust this host, hit Yes to add the key to\n"
757         "%s's cache and carry on connecting.\n"
758         "If you want to carry on connecting just once, without\n"
759         "adding the key to the cache, hit No.\n"
760         "If you do not trust this host, hit Cancel to abandon the\n"
761         "connection.\n";
762
763     static const char wrongmsg[] =
764         "WARNING - POTENTIAL SECURITY BREACH!\n"
765         "\n"
766         "The server's host key does not match the one %s has\n"
767         "cached in the registry. This means that either the\n"
768         "server administrator has changed the host key, or you\n"
769         "have actually connected to another computer pretending\n"
770         "to be the server.\n"
771         "The new %s key fingerprint is:\n"
772         "%s\n"
773         "If you were expecting this change and trust the new key,\n"
774         "hit Yes to update %s's cache and continue connecting.\n"
775         "If you want to carry on connecting but without updating\n"
776         "the cache, hit No.\n"
777         "If you want to abandon the connection completely, hit\n"
778         "Cancel. Hitting Cancel is the ONLY guaranteed safe\n" "choice.\n";
779
780     static const char mbtitle[] = "%s Security Alert";
781
782     /*
783      * Verify the key against the registry.
784      */
785     ret = verify_host_key(host, port, keytype, keystr);
786
787     if (ret == 0)                      /* success - key matched OK */
788         return 1;
789     if (ret == 2) {                    /* key was different */
790         int mbret;
791         char *text = dupprintf(wrongmsg, appname, keytype, fingerprint,
792                                appname);
793         char *caption = dupprintf(mbtitle, appname);
794         mbret = message_box(text, caption,
795                             MB_ICONWARNING | MB_YESNOCANCEL | MB_DEFBUTTON3,
796                             HELPCTXID(errors_hostkey_changed));
797         assert(mbret==IDYES || mbret==IDNO || mbret==IDCANCEL);
798         sfree(text);
799         sfree(caption);
800         if (mbret == IDYES) {
801             store_host_key(host, port, keytype, keystr);
802             return 1;
803         } else if (mbret == IDNO)
804             return 1;
805         return 0;
806     }
807     if (ret == 1) {                    /* key was absent */
808         int mbret;
809         char *text = dupprintf(absentmsg, keytype, fingerprint, appname);
810         char *caption = dupprintf(mbtitle, appname);
811         mbret = message_box(text, caption,
812                             MB_ICONWARNING | MB_YESNOCANCEL | MB_DEFBUTTON3,
813                             HELPCTXID(errors_hostkey_absent));
814         assert(mbret==IDYES || mbret==IDNO || mbret==IDCANCEL);
815         sfree(text);
816         sfree(caption);
817         if (mbret == IDYES) {
818             store_host_key(host, port, keytype, keystr);
819             return 1;
820         } else if (mbret == IDNO)
821             return 1;
822         return 0;
823     }
824 }
825
826 /*
827  * Ask whether the selected algorithm is acceptable (since it was
828  * below the configured 'warn' threshold).
829  */
830 int askalg(void *frontend, const char *algtype, const char *algname,
831            void (*callback)(void *ctx, int result), void *ctx)
832 {
833     static const char mbtitle[] = "%s Security Alert";
834     static const char msg[] =
835         "The first %s supported by the server\n"
836         "is %.64s, which is below the configured\n"
837         "warning threshold.\n"
838         "Do you want to continue with this connection?\n";
839     char *message, *title;
840     int mbret;
841
842     message = dupprintf(msg, algtype, algname);
843     title = dupprintf(mbtitle, appname);
844     mbret = MessageBox(NULL, message, title,
845                        MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2);
846     sfree(message);
847     sfree(title);
848     if (mbret == IDYES)
849         return 1;
850     else
851         return 0;
852 }
853
854 /*
855  * Ask whether to wipe a session log file before writing to it.
856  * Returns 2 for wipe, 1 for append, 0 for cancel (don't log).
857  */
858 int askappend(void *frontend, Filename filename,
859               void (*callback)(void *ctx, int result), void *ctx)
860 {
861     static const char msgtemplate[] =
862         "The session log file \"%.*s\" already exists.\n"
863         "You can overwrite it with a new session log,\n"
864         "append your session log to the end of it,\n"
865         "or disable session logging for this session.\n"
866         "Hit Yes to wipe the file, No to append to it,\n"
867         "or Cancel to disable logging.";
868     char *message;
869     char *mbtitle;
870     int mbret;
871
872     message = dupprintf(msgtemplate, FILENAME_MAX, filename.path);
873     mbtitle = dupprintf("%s Log to File", appname);
874
875     mbret = MessageBox(NULL, message, mbtitle,
876                        MB_ICONQUESTION | MB_YESNOCANCEL | MB_DEFBUTTON3);
877
878     sfree(message);
879     sfree(mbtitle);
880
881     if (mbret == IDYES)
882         return 2;
883     else if (mbret == IDNO)
884         return 1;
885     else
886         return 0;
887 }
888
889 /*
890  * Warn about the obsolescent key file format.
891  * 
892  * Uniquely among these functions, this one does _not_ expect a
893  * frontend handle. This means that if PuTTY is ported to a
894  * platform which requires frontend handles, this function will be
895  * an anomaly. Fortunately, the problem it addresses will not have
896  * been present on that platform, so it can plausibly be
897  * implemented as an empty function.
898  */
899 void old_keyfile_warning(void)
900 {
901     static const char mbtitle[] = "%s Key File Warning";
902     static const char message[] =
903         "You are loading an SSH-2 private key which has an\n"
904         "old version of the file format. This means your key\n"
905         "file is not fully tamperproof. Future versions of\n"
906         "%s may stop supporting this private key format,\n"
907         "so we recommend you convert your key to the new\n"
908         "format.\n"
909         "\n"
910         "You can perform this conversion by loading the key\n"
911         "into PuTTYgen and then saving it again.";
912
913     char *msg, *title;
914     msg = dupprintf(message, appname);
915     title = dupprintf(mbtitle, appname);
916
917     MessageBox(NULL, msg, title, MB_OK);
918
919     sfree(msg);
920     sfree(title);
921 }