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