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