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