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