]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - windows/winpgnt.c
Use readonly edit controls in some Windows dialogs.
[PuTTY.git] / windows / winpgnt.c
1 /*
2  * Pageant: the PuTTY Authentication Agent.
3  */
4
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <ctype.h>
8 #include <assert.h>
9 #include <tchar.h>
10
11 #define PUTTY_DO_GLOBALS
12
13 #include "putty.h"
14 #include "ssh.h"
15 #include "misc.h"
16 #include "tree234.h"
17 #include "winsecur.h"
18 #include "pageant.h"
19
20 #include <shellapi.h>
21
22 #ifndef NO_SECURITY
23 #include <aclapi.h>
24 #ifdef DEBUG_IPC
25 #define _WIN32_WINNT 0x0500            /* for ConvertSidToStringSid */
26 #include <sddl.h>
27 #endif
28 #endif
29
30 #define IDI_MAINICON 200
31 #define IDI_TRAYICON 201
32
33 #define WM_SYSTRAY   (WM_APP + 6)
34 #define WM_SYSTRAY2  (WM_APP + 7)
35
36 #define AGENT_COPYDATA_ID 0x804e50ba   /* random goop */
37
38 /* From MSDN: In the WM_SYSCOMMAND message, the four low-order bits of
39  * wParam are used by Windows, and should be masked off, so we shouldn't
40  * attempt to store information in them. Hence all these identifiers have
41  * the low 4 bits clear. Also, identifiers should < 0xF000. */
42
43 #define IDM_CLOSE    0x0010
44 #define IDM_VIEWKEYS 0x0020
45 #define IDM_ADDKEY   0x0030
46 #define IDM_HELP     0x0040
47 #define IDM_ABOUT    0x0050
48
49 #define APPNAME "Pageant"
50
51 extern char ver[];
52
53 static HWND keylist;
54 static HWND aboutbox;
55 static HMENU systray_menu, session_menu;
56 static int already_running;
57
58 static char *putty_path;
59
60 /* CWD for "add key" file requester. */
61 static filereq *keypath = NULL;
62
63 #define IDM_PUTTY         0x0060
64 #define IDM_SESSIONS_BASE 0x1000
65 #define IDM_SESSIONS_MAX  0x2000
66 #define PUTTY_REGKEY      "Software\\SimonTatham\\PuTTY\\Sessions"
67 #define PUTTY_DEFAULT     "Default%20Settings"
68 static int initial_menuitems_count;
69
70 /*
71  * Print a modal (Really Bad) message box and perform a fatal exit.
72  */
73 void modalfatalbox(const char *fmt, ...)
74 {
75     va_list ap;
76     char *buf;
77
78     va_start(ap, fmt);
79     buf = dupvprintf(fmt, ap);
80     va_end(ap);
81     MessageBox(hwnd, buf, "Pageant Fatal Error",
82                MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
83     sfree(buf);
84     exit(1);
85 }
86
87 /* Un-munge session names out of the registry. */
88 static void unmungestr(char *in, char *out, int outlen)
89 {
90     while (*in) {
91         if (*in == '%' && in[1] && in[2]) {
92             int i, j;
93
94             i = in[1] - '0';
95             i -= (i > 9 ? 7 : 0);
96             j = in[2] - '0';
97             j -= (j > 9 ? 7 : 0);
98
99             *out++ = (i << 4) + j;
100             if (!--outlen)
101                 return;
102             in += 3;
103         } else {
104             *out++ = *in++;
105             if (!--outlen)
106                 return;
107         }
108     }
109     *out = '\0';
110     return;
111 }
112
113 static int has_security;
114
115 struct PassphraseProcStruct {
116     char **passphrase;
117     char *comment;
118 };
119
120 /*
121  * Dialog-box function for the Licence box.
122  */
123 static INT_PTR CALLBACK LicenceProc(HWND hwnd, UINT msg,
124                                 WPARAM wParam, LPARAM lParam)
125 {
126     switch (msg) {
127       case WM_INITDIALOG:
128             SetDlgItemText(hwnd, 1000,
129         "Copyright 1997-2015 Simon Tatham.\r\n\r\n"
130
131         "Portions copyright Robert de Bath, Joris van Rantwijk, Delian "
132         "Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas "
133         "Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, "
134         "Markus Kuhn, Colin Watson, Christopher Staite, and CORE SDI S.A.\r\n\r\n"
135
136         "Permission is hereby granted, free of charge, to any person "
137         "obtaining a copy of this software and associated documentation "
138         "files (the ""Software""), to deal in the Software without restriction, "
139         "including without limitation the rights to use, copy, modify, merge, "
140         "publish, distribute, sublicense, and/or sell copies of the Software, "
141         "and to permit persons to whom the Software is furnished to do so, "
142         "subject to the following conditions:\r\n\r\n"
143
144         "The above copyright notice and this permission notice shall be "
145         "included in all copies or substantial portions of the Software.\r\n\r\n"
146
147         "THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT "
148         "WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, "
149         "INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF "
150         "MERCHANTABILITY, FITNESS FOR A PARTICULAR "
151         "PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE "
152         "COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES "
153         "OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, "
154         "TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN "
155         "CONNECTION WITH THE SOFTWARE OR THE USE OR "
156         "OTHER DEALINGS IN THE SOFTWARE."
157 );
158         return 1;
159       case WM_COMMAND:
160         switch (LOWORD(wParam)) {
161           case IDOK:
162           case IDCANCEL:
163             EndDialog(hwnd, 1);
164             return 0;
165         }
166         return 0;
167       case WM_CLOSE:
168         EndDialog(hwnd, 1);
169         return 0;
170     }
171     return 0;
172 }
173
174 /*
175  * Dialog-box function for the About box.
176  */
177 static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg,
178                               WPARAM wParam, LPARAM lParam)
179 {
180     switch (msg) {
181       case WM_INITDIALOG:
182         {
183             char *text = dupprintf
184                 ("Pageant\r\n\r\n%s\r\n\r\n%s",
185                  ver,
186                  "\251 1997-2015 Simon Tatham. All rights reserved.");
187             SetDlgItemText(hwnd, 1000, text);
188             sfree(text);
189         }
190         return 1;
191       case WM_COMMAND:
192         switch (LOWORD(wParam)) {
193           case IDOK:
194           case IDCANCEL:
195             aboutbox = NULL;
196             DestroyWindow(hwnd);
197             return 0;
198           case 101:
199             EnableWindow(hwnd, 0);
200             DialogBox(hinst, MAKEINTRESOURCE(214), hwnd, LicenceProc);
201             EnableWindow(hwnd, 1);
202             SetActiveWindow(hwnd);
203             return 0;
204         }
205         return 0;
206       case WM_CLOSE:
207         aboutbox = NULL;
208         DestroyWindow(hwnd);
209         return 0;
210     }
211     return 0;
212 }
213
214 static HWND passphrase_box;
215
216 /*
217  * Dialog-box function for the passphrase box.
218  */
219 static INT_PTR CALLBACK PassphraseProc(HWND hwnd, UINT msg,
220                                    WPARAM wParam, LPARAM lParam)
221 {
222     static char **passphrase = NULL;
223     struct PassphraseProcStruct *p;
224
225     switch (msg) {
226       case WM_INITDIALOG:
227         passphrase_box = hwnd;
228         /*
229          * Centre the window.
230          */
231         {                              /* centre the window */
232             RECT rs, rd;
233             HWND hw;
234
235             hw = GetDesktopWindow();
236             if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
237                 MoveWindow(hwnd,
238                            (rs.right + rs.left + rd.left - rd.right) / 2,
239                            (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
240                            rd.right - rd.left, rd.bottom - rd.top, TRUE);
241         }
242
243         SetForegroundWindow(hwnd);
244         SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
245                      SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
246         p = (struct PassphraseProcStruct *) lParam;
247         passphrase = p->passphrase;
248         if (p->comment)
249             SetDlgItemText(hwnd, 101, p->comment);
250         burnstr(*passphrase);
251         *passphrase = dupstr("");
252         SetDlgItemText(hwnd, 102, *passphrase);
253         return 0;
254       case WM_COMMAND:
255         switch (LOWORD(wParam)) {
256           case IDOK:
257             if (*passphrase)
258                 EndDialog(hwnd, 1);
259             else
260                 MessageBeep(0);
261             return 0;
262           case IDCANCEL:
263             EndDialog(hwnd, 0);
264             return 0;
265           case 102:                    /* edit box */
266             if ((HIWORD(wParam) == EN_CHANGE) && passphrase) {
267                 burnstr(*passphrase);
268                 *passphrase = GetDlgItemText_alloc(hwnd, 102);
269             }
270             return 0;
271         }
272         return 0;
273       case WM_CLOSE:
274         EndDialog(hwnd, 0);
275         return 0;
276     }
277     return 0;
278 }
279
280 /*
281  * Warn about the obsolescent key file format.
282  */
283 void old_keyfile_warning(void)
284 {
285     static const char mbtitle[] = "PuTTY Key File Warning";
286     static const char message[] =
287         "You are loading an SSH-2 private key which has an\n"
288         "old version of the file format. This means your key\n"
289         "file is not fully tamperproof. Future versions of\n"
290         "PuTTY may stop supporting this private key format,\n"
291         "so we recommend you convert your key to the new\n"
292         "format.\n"
293         "\n"
294         "You can perform this conversion by loading the key\n"
295         "into PuTTYgen and then saving it again.";
296
297     MessageBox(NULL, message, mbtitle, MB_OK);
298 }
299
300 /*
301  * Update the visible key list.
302  */
303 void keylist_update(void)
304 {
305     struct RSAKey *rkey;
306     struct ssh2_userkey *skey;
307     int i;
308
309     if (keylist) {
310         SendDlgItemMessage(keylist, 100, LB_RESETCONTENT, 0, 0);
311         for (i = 0; NULL != (rkey = pageant_nth_ssh1_key(i)); i++) {
312             char listentry[512], *p;
313             /*
314              * Replace two spaces in the fingerprint with tabs, for
315              * nice alignment in the box.
316              */
317             strcpy(listentry, "ssh1\t");
318             p = listentry + strlen(listentry);
319             rsa_fingerprint(p, sizeof(listentry) - (p - listentry), rkey);
320             p = strchr(listentry, ' ');
321             if (p)
322                 *p = '\t';
323             p = strchr(listentry, ' ');
324             if (p)
325                 *p = '\t';
326             SendDlgItemMessage(keylist, 100, LB_ADDSTRING,
327                                0, (LPARAM) listentry);
328         }
329         for (i = 0; NULL != (skey = pageant_nth_ssh2_key(i)); i++) {
330             char *listentry, *p;
331             int pos;
332             /*
333              * Replace spaces with tabs in the fingerprint prefix, for
334              * nice alignment in the list box, until we encounter a :
335              * meaning we're into the fingerprint proper.
336              */
337             p = ssh2_fingerprint(skey->alg, skey->data);
338             listentry = dupprintf("%s\t%s", p, skey->comment);
339             sfree(p);
340
341             pos = 0;
342             while (1) {
343                 pos += strcspn(listentry + pos, " :");
344                 if (listentry[pos] == ':' || !listentry[pos])
345                     break;
346                 listentry[pos++] = '\t';
347             }
348
349             SendDlgItemMessage(keylist, 100, LB_ADDSTRING, 0,
350                                (LPARAM) listentry);
351             sfree(listentry);
352         }
353         SendDlgItemMessage(keylist, 100, LB_SETCURSEL, (WPARAM) - 1, 0);
354     }
355 }
356
357 static void answer_msg(void *msgv)
358 {
359     unsigned char *msg = (unsigned char *)msgv;
360     unsigned msglen;
361     void *reply;
362     int replylen;
363
364     msglen = GET_32BIT(msg);
365     if (msglen > AGENT_MAX_MSGLEN) {
366         reply = pageant_failure_msg(&replylen);
367     } else {
368         reply = pageant_handle_msg(msg + 4, msglen, &replylen, NULL, NULL);
369         if (replylen > AGENT_MAX_MSGLEN) {
370             smemclr(reply, replylen);
371             sfree(reply);
372             reply = pageant_failure_msg(&replylen);
373         }
374     }
375
376     /*
377      * Windows Pageant answers messages in place, by overwriting the
378      * input message buffer.
379      */
380     memcpy(msg, reply, replylen);
381     smemclr(reply, replylen);
382     sfree(reply);
383 }
384
385 static void win_add_keyfile(Filename *filename)
386 {
387     char *err;
388     int ret;
389     char *passphrase = NULL;
390
391     /*
392      * Try loading the key without a passphrase. (Or rather, without a
393      * _new_ passphrase; pageant_add_keyfile will take care of trying
394      * all the passphrases we've already stored.)
395      */
396     ret = pageant_add_keyfile(filename, NULL, &err);
397     if (ret == PAGEANT_ACTION_OK) {
398         goto done;
399     } else if (ret == PAGEANT_ACTION_FAILURE) {
400         goto error;
401     }
402
403     /*
404      * OK, a passphrase is needed, and we've been given the key
405      * comment to use in the passphrase prompt.
406      */
407     while (1) {
408         INT_PTR dlgret;
409         struct PassphraseProcStruct pps;
410
411         pps.passphrase = &passphrase;
412         pps.comment = err;
413         dlgret = DialogBoxParam(hinst, MAKEINTRESOURCE(210),
414                                 NULL, PassphraseProc, (LPARAM) &pps);
415         passphrase_box = NULL;
416
417         if (!dlgret)
418             goto done;                 /* operation cancelled */
419
420         sfree(err);
421
422         assert(passphrase != NULL);
423
424         ret = pageant_add_keyfile(filename, passphrase, &err);
425         if (ret == PAGEANT_ACTION_OK) {
426             goto done;
427         } else if (ret == PAGEANT_ACTION_FAILURE) {
428             goto error;
429         }
430
431         smemclr(passphrase, strlen(passphrase));
432         sfree(passphrase);
433         passphrase = NULL;
434     }
435
436   error:
437     message_box(err, APPNAME, MB_OK | MB_ICONERROR,
438                 HELPCTXID(errors_cantloadkey));
439   done:
440     if (passphrase) {
441         smemclr(passphrase, strlen(passphrase));
442         sfree(passphrase);
443     }
444     sfree(err);
445     return;
446 }
447
448 /*
449  * Prompt for a key file to add, and add it.
450  */
451 static void prompt_add_keyfile(void)
452 {
453     OPENFILENAME of;
454     char *filelist = snewn(8192, char);
455         
456     if (!keypath) keypath = filereq_new();
457     memset(&of, 0, sizeof(of));
458     of.hwndOwner = hwnd;
459     of.lpstrFilter = FILTER_KEY_FILES;
460     of.lpstrCustomFilter = NULL;
461     of.nFilterIndex = 1;
462     of.lpstrFile = filelist;
463     *filelist = '\0';
464     of.nMaxFile = 8192;
465     of.lpstrFileTitle = NULL;
466     of.lpstrTitle = "Select Private Key File";
467     of.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER;
468     if (request_file(keypath, &of, TRUE, FALSE)) {
469         if(strlen(filelist) > of.nFileOffset) {
470             /* Only one filename returned? */
471             Filename *fn = filename_from_str(filelist);
472             win_add_keyfile(fn);
473             filename_free(fn);
474         } else {
475             /* we are returned a bunch of strings, end to
476              * end. first string is the directory, the
477              * rest the filenames. terminated with an
478              * empty string.
479              */
480             char *dir = filelist;
481             char *filewalker = filelist + strlen(dir) + 1;
482             while (*filewalker != '\0') {
483                 char *filename = dupcat(dir, "\\", filewalker, NULL);
484                 Filename *fn = filename_from_str(filename);
485                 win_add_keyfile(fn);
486                 filename_free(fn);
487                 sfree(filename);
488                 filewalker += strlen(filewalker) + 1;
489             }
490         }
491
492         keylist_update();
493         pageant_forget_passphrases();
494     }
495     sfree(filelist);
496 }
497
498 /*
499  * Dialog-box function for the key list box.
500  */
501 static INT_PTR CALLBACK KeyListProc(HWND hwnd, UINT msg,
502                                 WPARAM wParam, LPARAM lParam)
503 {
504     struct RSAKey *rkey;
505     struct ssh2_userkey *skey;
506
507     switch (msg) {
508       case WM_INITDIALOG:
509         /*
510          * Centre the window.
511          */
512         {                              /* centre the window */
513             RECT rs, rd;
514             HWND hw;
515
516             hw = GetDesktopWindow();
517             if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
518                 MoveWindow(hwnd,
519                            (rs.right + rs.left + rd.left - rd.right) / 2,
520                            (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
521                            rd.right - rd.left, rd.bottom - rd.top, TRUE);
522         }
523
524         if (has_help())
525             SetWindowLongPtr(hwnd, GWL_EXSTYLE,
526                              GetWindowLongPtr(hwnd, GWL_EXSTYLE) |
527                              WS_EX_CONTEXTHELP);
528         else {
529             HWND item = GetDlgItem(hwnd, 103);   /* the Help button */
530             if (item)
531                 DestroyWindow(item);
532         }
533
534         keylist = hwnd;
535         {
536             static int tabs[] = { 35, 75, 250 };
537             SendDlgItemMessage(hwnd, 100, LB_SETTABSTOPS,
538                                sizeof(tabs) / sizeof(*tabs),
539                                (LPARAM) tabs);
540         }
541         keylist_update();
542         return 0;
543       case WM_COMMAND:
544         switch (LOWORD(wParam)) {
545           case IDOK:
546           case IDCANCEL:
547             keylist = NULL;
548             DestroyWindow(hwnd);
549             return 0;
550           case 101:                    /* add key */
551             if (HIWORD(wParam) == BN_CLICKED ||
552                 HIWORD(wParam) == BN_DOUBLECLICKED) {
553                 if (passphrase_box) {
554                     MessageBeep(MB_ICONERROR);
555                     SetForegroundWindow(passphrase_box);
556                     break;
557                 }
558                 prompt_add_keyfile();
559             }
560             return 0;
561           case 102:                    /* remove key */
562             if (HIWORD(wParam) == BN_CLICKED ||
563                 HIWORD(wParam) == BN_DOUBLECLICKED) {
564                 int i;
565                 int rCount, sCount;
566                 int *selectedArray;
567                 
568                 /* our counter within the array of selected items */
569                 int itemNum;
570                 
571                 /* get the number of items selected in the list */
572                 int numSelected = 
573                         SendDlgItemMessage(hwnd, 100, LB_GETSELCOUNT, 0, 0);
574                 
575                 /* none selected? that was silly */
576                 if (numSelected == 0) {
577                     MessageBeep(0);
578                     break;
579                 }
580
581                 /* get item indices in an array */
582                 selectedArray = snewn(numSelected, int);
583                 SendDlgItemMessage(hwnd, 100, LB_GETSELITEMS,
584                                 numSelected, (WPARAM)selectedArray);
585                 
586                 itemNum = numSelected - 1;
587                 rCount = pageant_count_ssh1_keys();
588                 sCount = pageant_count_ssh2_keys();
589                 
590                 /* go through the non-rsakeys until we've covered them all, 
591                  * and/or we're out of selected items to check. note that
592                  * we go *backwards*, to avoid complications from deleting
593                  * things hence altering the offset of subsequent items
594                  */
595                 for (i = sCount - 1; (itemNum >= 0) && (i >= 0); i--) {
596                     skey = pageant_nth_ssh2_key(i);
597                         
598                     if (selectedArray[itemNum] == rCount + i) {
599                         pageant_delete_ssh2_key(skey);
600                         skey->alg->freekey(skey->data);
601                         sfree(skey);
602                         itemNum--;
603                     }
604                 }
605                 
606                 /* do the same for the rsa keys */
607                 for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) {
608                     rkey = pageant_nth_ssh1_key(i);
609
610                     if(selectedArray[itemNum] == i) {
611                         pageant_delete_ssh1_key(rkey);
612                         freersakey(rkey);
613                         sfree(rkey);
614                         itemNum--;
615                     }
616                 }
617
618                 sfree(selectedArray); 
619                 keylist_update();
620             }
621             return 0;
622           case 103:                    /* help */
623             if (HIWORD(wParam) == BN_CLICKED ||
624                 HIWORD(wParam) == BN_DOUBLECLICKED) {
625                 launch_help(hwnd, WINHELP_CTX_pageant_general);
626             }
627             return 0;
628         }
629         return 0;
630       case WM_HELP:
631         {
632             int id = ((LPHELPINFO)lParam)->iCtrlId;
633             const char *topic = NULL;
634             switch (id) {
635               case 100: topic = WINHELP_CTX_pageant_keylist; break;
636               case 101: topic = WINHELP_CTX_pageant_addkey; break;
637               case 102: topic = WINHELP_CTX_pageant_remkey; break;
638             }
639             if (topic) {
640                 launch_help(hwnd, topic);
641             } else {
642                 MessageBeep(0);
643             }
644         }
645         break;
646       case WM_CLOSE:
647         keylist = NULL;
648         DestroyWindow(hwnd);
649         return 0;
650     }
651     return 0;
652 }
653
654 /* Set up a system tray icon */
655 static BOOL AddTrayIcon(HWND hwnd)
656 {
657     BOOL res;
658     NOTIFYICONDATA tnid;
659     HICON hicon;
660
661 #ifdef NIM_SETVERSION
662     tnid.uVersion = 0;
663     res = Shell_NotifyIcon(NIM_SETVERSION, &tnid);
664 #endif
665
666     tnid.cbSize = sizeof(NOTIFYICONDATA);
667     tnid.hWnd = hwnd;
668     tnid.uID = 1;              /* unique within this systray use */
669     tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
670     tnid.uCallbackMessage = WM_SYSTRAY;
671     tnid.hIcon = hicon = LoadIcon(hinst, MAKEINTRESOURCE(201));
672     strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
673
674     res = Shell_NotifyIcon(NIM_ADD, &tnid);
675
676     if (hicon) DestroyIcon(hicon);
677     
678     return res;
679 }
680
681 /* Update the saved-sessions menu. */
682 static void update_sessions(void)
683 {
684     int num_entries;
685     HKEY hkey;
686     TCHAR buf[MAX_PATH + 1];
687     MENUITEMINFO mii;
688
689     int index_key, index_menu;
690
691     if (!putty_path)
692         return;
693
694     if(ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, PUTTY_REGKEY, &hkey))
695         return;
696
697     for(num_entries = GetMenuItemCount(session_menu);
698         num_entries > initial_menuitems_count;
699         num_entries--)
700         RemoveMenu(session_menu, 0, MF_BYPOSITION);
701
702     index_key = 0;
703     index_menu = 0;
704
705     while(ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) {
706         TCHAR session_name[MAX_PATH + 1];
707         unmungestr(buf, session_name, MAX_PATH);
708         if(strcmp(buf, PUTTY_DEFAULT) != 0) {
709             memset(&mii, 0, sizeof(mii));
710             mii.cbSize = sizeof(mii);
711             mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
712             mii.fType = MFT_STRING;
713             mii.fState = MFS_ENABLED;
714             mii.wID = (index_menu * 16) + IDM_SESSIONS_BASE;
715             mii.dwTypeData = session_name;
716             InsertMenuItem(session_menu, index_menu, TRUE, &mii);
717             index_menu++;
718         }
719         index_key++;
720     }
721
722     RegCloseKey(hkey);
723
724     if(index_menu == 0) {
725         mii.cbSize = sizeof(mii);
726         mii.fMask = MIIM_TYPE | MIIM_STATE;
727         mii.fType = MFT_STRING;
728         mii.fState = MFS_GRAYED;
729         mii.dwTypeData = _T("(No sessions)");
730         InsertMenuItem(session_menu, index_menu, TRUE, &mii);
731     }
732 }
733
734 #ifndef NO_SECURITY
735 /*
736  * Versions of Pageant prior to 0.61 expected this SID on incoming
737  * communications. For backwards compatibility, and more particularly
738  * for compatibility with derived works of PuTTY still using the old
739  * Pageant client code, we accept it as an alternative to the one
740  * returned from get_user_sid() in winpgntc.c.
741  */
742 PSID get_default_sid(void)
743 {
744     HANDLE proc = NULL;
745     DWORD sidlen;
746     PSECURITY_DESCRIPTOR psd = NULL;
747     PSID sid = NULL, copy = NULL, ret = NULL;
748
749     if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE,
750                             GetCurrentProcessId())) == NULL)
751         goto cleanup;
752
753     if (p_GetSecurityInfo(proc, SE_KERNEL_OBJECT, OWNER_SECURITY_INFORMATION,
754                           &sid, NULL, NULL, NULL, &psd) != ERROR_SUCCESS)
755         goto cleanup;
756
757     sidlen = GetLengthSid(sid);
758
759     copy = (PSID)smalloc(sidlen);
760
761     if (!CopySid(sidlen, copy, sid))
762         goto cleanup;
763
764     /* Success. Move sid into the return value slot, and null it out
765      * to stop the cleanup code freeing it. */
766     ret = copy;
767     copy = NULL;
768
769   cleanup:
770     if (proc != NULL)
771         CloseHandle(proc);
772     if (psd != NULL)
773         LocalFree(psd);
774     if (copy != NULL)
775         sfree(copy);
776
777     return ret;
778 }
779 #endif
780
781 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
782                                 WPARAM wParam, LPARAM lParam)
783 {
784     static int menuinprogress;
785     static UINT msgTaskbarCreated = 0;
786
787     switch (message) {
788       case WM_CREATE:
789         msgTaskbarCreated = RegisterWindowMessage(_T("TaskbarCreated"));
790         break;
791       default:
792         if (message==msgTaskbarCreated) {
793             /*
794              * Explorer has been restarted, so the tray icon will
795              * have been lost.
796              */
797             AddTrayIcon(hwnd);
798         }
799         break;
800         
801       case WM_SYSTRAY:
802         if (lParam == WM_RBUTTONUP) {
803             POINT cursorpos;
804             GetCursorPos(&cursorpos);
805             PostMessage(hwnd, WM_SYSTRAY2, cursorpos.x, cursorpos.y);
806         } else if (lParam == WM_LBUTTONDBLCLK) {
807             /* Run the default menu item. */
808             UINT menuitem = GetMenuDefaultItem(systray_menu, FALSE, 0);
809             if (menuitem != -1)
810                 PostMessage(hwnd, WM_COMMAND, menuitem, 0);
811         }
812         break;
813       case WM_SYSTRAY2:
814         if (!menuinprogress) {
815             menuinprogress = 1;
816             update_sessions();
817             SetForegroundWindow(hwnd);
818             TrackPopupMenu(systray_menu,
819                            TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
820                            TPM_RIGHTBUTTON,
821                            wParam, lParam, 0, hwnd, NULL);
822             menuinprogress = 0;
823         }
824         break;
825       case WM_COMMAND:
826       case WM_SYSCOMMAND:
827         switch (wParam & ~0xF) {       /* low 4 bits reserved to Windows */
828           case IDM_PUTTY:
829             if((INT_PTR)ShellExecute(hwnd, NULL, putty_path, _T(""), _T(""),
830                                  SW_SHOW) <= 32) {
831                 MessageBox(NULL, "Unable to execute PuTTY!",
832                            "Error", MB_OK | MB_ICONERROR);
833             }
834             break;
835           case IDM_CLOSE:
836             if (passphrase_box)
837                 SendMessage(passphrase_box, WM_CLOSE, 0, 0);
838             SendMessage(hwnd, WM_CLOSE, 0, 0);
839             break;
840           case IDM_VIEWKEYS:
841             if (!keylist) {
842                 keylist = CreateDialog(hinst, MAKEINTRESOURCE(211),
843                                        NULL, KeyListProc);
844                 ShowWindow(keylist, SW_SHOWNORMAL);
845             }
846             /* 
847              * Sometimes the window comes up minimised / hidden for
848              * no obvious reason. Prevent this. This also brings it
849              * to the front if it's already present (the user
850              * selected View Keys because they wanted to _see_ the
851              * thing).
852              */
853             SetForegroundWindow(keylist);
854             SetWindowPos(keylist, HWND_TOP, 0, 0, 0, 0,
855                          SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
856             break;
857           case IDM_ADDKEY:
858             if (passphrase_box) {
859                 MessageBeep(MB_ICONERROR);
860                 SetForegroundWindow(passphrase_box);
861                 break;
862             }
863             prompt_add_keyfile();
864             break;
865           case IDM_ABOUT:
866             if (!aboutbox) {
867                 aboutbox = CreateDialog(hinst, MAKEINTRESOURCE(213),
868                                         NULL, AboutProc);
869                 ShowWindow(aboutbox, SW_SHOWNORMAL);
870                 /* 
871                  * Sometimes the window comes up minimised / hidden
872                  * for no obvious reason. Prevent this.
873                  */
874                 SetForegroundWindow(aboutbox);
875                 SetWindowPos(aboutbox, HWND_TOP, 0, 0, 0, 0,
876                              SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
877             }
878             break;
879           case IDM_HELP:
880             launch_help(hwnd, WINHELP_CTX_pageant_general);
881             break;
882           default:
883             {
884                 if(wParam >= IDM_SESSIONS_BASE && wParam <= IDM_SESSIONS_MAX) {
885                     MENUITEMINFO mii;
886                     TCHAR buf[MAX_PATH + 1];
887                     TCHAR param[MAX_PATH + 1];
888                     memset(&mii, 0, sizeof(mii));
889                     mii.cbSize = sizeof(mii);
890                     mii.fMask = MIIM_TYPE;
891                     mii.cch = MAX_PATH;
892                     mii.dwTypeData = buf;
893                     GetMenuItemInfo(session_menu, wParam, FALSE, &mii);
894                     strcpy(param, "@");
895                     strcat(param, mii.dwTypeData);
896                     if((INT_PTR)ShellExecute(hwnd, NULL, putty_path, param,
897                                          _T(""), SW_SHOW) <= 32) {
898                         MessageBox(NULL, "Unable to execute PuTTY!", "Error",
899                                    MB_OK | MB_ICONERROR);
900                     }
901                 }
902             }
903             break;
904         }
905         break;
906       case WM_DESTROY:
907         quit_help(hwnd);
908         PostQuitMessage(0);
909         return 0;
910       case WM_COPYDATA:
911         {
912             COPYDATASTRUCT *cds;
913             char *mapname;
914             void *p;
915             HANDLE filemap;
916 #ifndef NO_SECURITY
917             PSID mapowner, ourself, ourself2;
918 #endif
919             PSECURITY_DESCRIPTOR psd = NULL;
920             int ret = 0;
921
922             cds = (COPYDATASTRUCT *) lParam;
923             if (cds->dwData != AGENT_COPYDATA_ID)
924                 return 0;              /* not our message, mate */
925             mapname = (char *) cds->lpData;
926             if (mapname[cds->cbData - 1] != '\0')
927                 return 0;              /* failure to be ASCIZ! */
928 #ifdef DEBUG_IPC
929             debug(("mapname is :%s:\n", mapname));
930 #endif
931             filemap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, mapname);
932 #ifdef DEBUG_IPC
933             debug(("filemap is %p\n", filemap));
934 #endif
935             if (filemap != NULL && filemap != INVALID_HANDLE_VALUE) {
936 #ifndef NO_SECURITY
937                 int rc;
938                 if (has_security) {
939                     if ((ourself = get_user_sid()) == NULL) {
940 #ifdef DEBUG_IPC
941                         debug(("couldn't get user SID\n"));
942 #endif
943                         CloseHandle(filemap);
944                         return 0;
945                     }
946
947                     if ((ourself2 = get_default_sid()) == NULL) {
948 #ifdef DEBUG_IPC
949                         debug(("couldn't get default SID\n"));
950 #endif
951                         CloseHandle(filemap);
952                         sfree(ourself);
953                         return 0;
954                     }
955
956                     if ((rc = p_GetSecurityInfo(filemap, SE_KERNEL_OBJECT,
957                                                 OWNER_SECURITY_INFORMATION,
958                                                 &mapowner, NULL, NULL, NULL,
959                                                 &psd) != ERROR_SUCCESS)) {
960 #ifdef DEBUG_IPC
961                         debug(("couldn't get owner info for filemap: %d\n",
962                                rc));
963 #endif
964                         CloseHandle(filemap);
965                         sfree(ourself);
966                         sfree(ourself2);
967                         return 0;
968                     }
969 #ifdef DEBUG_IPC
970                     {
971                         LPTSTR ours, ours2, theirs;
972                         ConvertSidToStringSid(mapowner, &theirs);
973                         ConvertSidToStringSid(ourself, &ours);
974                         ConvertSidToStringSid(ourself2, &ours2);
975                         debug(("got sids:\n  oursnew=%s\n  oursold=%s\n"
976                                "  theirs=%s\n", ours, ours2, theirs));
977                         LocalFree(ours);
978                         LocalFree(ours2);
979                         LocalFree(theirs);
980                     }
981 #endif
982                     if (!EqualSid(mapowner, ourself) &&
983                         !EqualSid(mapowner, ourself2)) {
984                         CloseHandle(filemap);
985                         LocalFree(psd);
986                         sfree(ourself);
987                         sfree(ourself2);
988                         return 0;      /* security ID mismatch! */
989                     }
990 #ifdef DEBUG_IPC
991                     debug(("security stuff matched\n"));
992 #endif
993                     LocalFree(psd);
994                     sfree(ourself);
995                     sfree(ourself2);
996                 } else {
997 #ifdef DEBUG_IPC
998                     debug(("security APIs not present\n"));
999 #endif
1000                 }
1001 #endif
1002                 p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
1003 #ifdef DEBUG_IPC
1004                 debug(("p is %p\n", p));
1005                 {
1006                     int i;
1007                     for (i = 0; i < 5; i++)
1008                         debug(("p[%d]=%02x\n", i,
1009                                ((unsigned char *) p)[i]));
1010                 }
1011 #endif
1012                 answer_msg(p);
1013                 ret = 1;
1014                 UnmapViewOfFile(p);
1015             }
1016             CloseHandle(filemap);
1017             return ret;
1018         }
1019     }
1020
1021     return DefWindowProc(hwnd, message, wParam, lParam);
1022 }
1023
1024 /*
1025  * Fork and Exec the command in cmdline. [DBW]
1026  */
1027 void spawn_cmd(const char *cmdline, const char *args, int show)
1028 {
1029     if (ShellExecute(NULL, _T("open"), cmdline,
1030                      args, NULL, show) <= (HINSTANCE) 32) {
1031         char *msg;
1032         msg = dupprintf("Failed to run \"%.100s\", Error: %d", cmdline,
1033                         (int)GetLastError());
1034         MessageBox(NULL, msg, APPNAME, MB_OK | MB_ICONEXCLAMATION);
1035         sfree(msg);
1036     }
1037 }
1038
1039 /*
1040  * This is a can't-happen stub, since Pageant never makes
1041  * asynchronous agent requests.
1042  */
1043 void agent_schedule_callback(void (*callback)(void *, void *, int),
1044                              void *callback_ctx, void *data, int len)
1045 {
1046     assert(!"We shouldn't get here");
1047 }
1048
1049 void cleanup_exit(int code)
1050 {
1051     shutdown_help();
1052     exit(code);
1053 }
1054
1055 int flags = FLAG_SYNCAGENT;
1056
1057 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
1058 {
1059     WNDCLASS wndclass;
1060     MSG msg;
1061     const char *command = NULL;
1062     int added_keys = 0;
1063     int argc, i;
1064     char **argv, **argstart;
1065
1066     hinst = inst;
1067     hwnd = NULL;
1068
1069     /*
1070      * Determine whether we're an NT system (should have security
1071      * APIs) or a non-NT system (don't do security).
1072      */
1073     if (!init_winver())
1074     {
1075         modalfatalbox("Windows refuses to report a version");
1076     }
1077     if (osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT) {
1078         has_security = TRUE;
1079     } else
1080         has_security = FALSE;
1081
1082     if (has_security) {
1083 #ifndef NO_SECURITY
1084         /*
1085          * Attempt to get the security API we need.
1086          */
1087         if (!got_advapi()) {
1088             MessageBox(NULL,
1089                        "Unable to access security APIs. Pageant will\n"
1090                        "not run, in case it causes a security breach.",
1091                        "Pageant Fatal Error", MB_ICONERROR | MB_OK);
1092             return 1;
1093         }
1094 #else
1095         MessageBox(NULL,
1096                    "This program has been compiled for Win9X and will\n"
1097                    "not run on NT, in case it causes a security breach.",
1098                    "Pageant Fatal Error", MB_ICONERROR | MB_OK);
1099         return 1;
1100 #endif
1101     }
1102
1103     /*
1104      * See if we can find our Help file.
1105      */
1106     init_help();
1107
1108     /*
1109      * Look for the PuTTY binary (we will enable the saved session
1110      * submenu if we find it).
1111      */
1112     {
1113         char b[2048], *p, *q, *r;
1114         FILE *fp;
1115         GetModuleFileName(NULL, b, sizeof(b) - 16);
1116         r = b;
1117         p = strrchr(b, '\\');
1118         if (p && p >= r) r = p+1;
1119         q = strrchr(b, ':');
1120         if (q && q >= r) r = q+1;
1121         strcpy(r, "putty.exe");
1122         if ( (fp = fopen(b, "r")) != NULL) {
1123             putty_path = dupstr(b);
1124             fclose(fp);
1125         } else
1126             putty_path = NULL;
1127     }
1128
1129     /*
1130      * Find out if Pageant is already running.
1131      */
1132     already_running = agent_exists();
1133
1134     /*
1135      * Initialise the cross-platform Pageant code.
1136      */
1137     if (!already_running) {
1138         pageant_init();
1139     }
1140
1141     /*
1142      * Process the command line and add keys as listed on it.
1143      */
1144     split_into_argv(cmdline, &argc, &argv, &argstart);
1145     for (i = 0; i < argc; i++) {
1146         if (!strcmp(argv[i], "-pgpfp")) {
1147             pgp_fingerprints();
1148             return 1;
1149         } else if (!strcmp(argv[i], "-c")) {
1150             /*
1151              * If we see `-c', then the rest of the
1152              * command line should be treated as a
1153              * command to be spawned.
1154              */
1155             if (i < argc-1)
1156                 command = argstart[i+1];
1157             else
1158                 command = "";
1159             break;
1160         } else {
1161             Filename *fn = filename_from_str(argv[i]);
1162             win_add_keyfile(fn);
1163             filename_free(fn);
1164             added_keys = TRUE;
1165         }
1166     }
1167
1168     /*
1169      * Forget any passphrase that we retained while going over
1170      * command line keyfiles.
1171      */
1172     pageant_forget_passphrases();
1173
1174     if (command) {
1175         char *args;
1176         if (command[0] == '"')
1177             args = strchr(++command, '"');
1178         else
1179             args = strchr(command, ' ');
1180         if (args) {
1181             *args++ = 0;
1182             while(*args && isspace(*args)) args++;
1183         }
1184         spawn_cmd(command, args, show);
1185     }
1186
1187     /*
1188      * If Pageant was already running, we leave now. If we haven't
1189      * even taken any auxiliary action (spawned a command or added
1190      * keys), complain.
1191      */
1192     if (already_running) {
1193         if (!command && !added_keys) {
1194             MessageBox(NULL, "Pageant is already running", "Pageant Error",
1195                        MB_ICONERROR | MB_OK);
1196         }
1197         return 0;
1198     }
1199
1200     if (!prev) {
1201         wndclass.style = 0;
1202         wndclass.lpfnWndProc = WndProc;
1203         wndclass.cbClsExtra = 0;
1204         wndclass.cbWndExtra = 0;
1205         wndclass.hInstance = inst;
1206         wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
1207         wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
1208         wndclass.hbrBackground = GetStockObject(BLACK_BRUSH);
1209         wndclass.lpszMenuName = NULL;
1210         wndclass.lpszClassName = APPNAME;
1211
1212         RegisterClass(&wndclass);
1213     }
1214
1215     keylist = NULL;
1216
1217     hwnd = CreateWindow(APPNAME, APPNAME,
1218                         WS_OVERLAPPEDWINDOW | WS_VSCROLL,
1219                         CW_USEDEFAULT, CW_USEDEFAULT,
1220                         100, 100, NULL, NULL, inst, NULL);
1221
1222     /* Set up a system tray icon */
1223     AddTrayIcon(hwnd);
1224
1225     /* Accelerators used: nsvkxa */
1226     systray_menu = CreatePopupMenu();
1227     if (putty_path) {
1228         session_menu = CreateMenu();
1229         AppendMenu(systray_menu, MF_ENABLED, IDM_PUTTY, "&New Session");
1230         AppendMenu(systray_menu, MF_POPUP | MF_ENABLED,
1231                    (UINT_PTR) session_menu, "&Saved Sessions");
1232         AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
1233     }
1234     AppendMenu(systray_menu, MF_ENABLED, IDM_VIEWKEYS,
1235            "&View Keys");
1236     AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key");
1237     AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
1238     if (has_help())
1239         AppendMenu(systray_menu, MF_ENABLED, IDM_HELP, "&Help");
1240     AppendMenu(systray_menu, MF_ENABLED, IDM_ABOUT, "&About");
1241     AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
1242     AppendMenu(systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit");
1243     initial_menuitems_count = GetMenuItemCount(session_menu);
1244
1245     /* Set the default menu item. */
1246     SetMenuDefaultItem(systray_menu, IDM_VIEWKEYS, FALSE);
1247
1248     ShowWindow(hwnd, SW_HIDE);
1249
1250     /*
1251      * Main message loop.
1252      */
1253     while (GetMessage(&msg, NULL, 0, 0) == 1) {
1254         if (!(IsWindow(keylist) && IsDialogMessage(keylist, &msg)) &&
1255             !(IsWindow(aboutbox) && IsDialogMessage(aboutbox, &msg))) {
1256             TranslateMessage(&msg);
1257             DispatchMessage(&msg);
1258         }
1259     }
1260
1261     /* Clean up the system tray icon */
1262     {
1263         NOTIFYICONDATA tnid;
1264
1265         tnid.cbSize = sizeof(NOTIFYICONDATA);
1266         tnid.hWnd = hwnd;
1267         tnid.uID = 1;
1268
1269         Shell_NotifyIcon(NIM_DELETE, &tnid);
1270
1271         DestroyMenu(systray_menu);
1272     }
1273
1274     if (keypath) filereq_free(keypath);
1275
1276     cleanup_exit(msg.wParam);
1277     return msg.wParam;                 /* just in case optimiser complains */
1278 }