]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - windows/winpgnt.c
Inaugural merge from 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                         sfree(ourself);
925                         return 0;
926                     }
927
928                     if ((rc = p_GetSecurityInfo(filemap, SE_KERNEL_OBJECT,
929                                                 OWNER_SECURITY_INFORMATION,
930                                                 &mapowner, NULL, NULL, NULL,
931                                                 &psd) != ERROR_SUCCESS)) {
932 #ifdef DEBUG_IPC
933                         debug(("couldn't get owner info for filemap: %d\n",
934                                rc));
935 #endif
936                         CloseHandle(filemap);
937                         sfree(ourself);
938                         sfree(ourself2);
939                         return 0;
940                     }
941 #ifdef DEBUG_IPC
942                     {
943                         LPTSTR ours, ours2, theirs;
944                         ConvertSidToStringSid(mapowner, &theirs);
945                         ConvertSidToStringSid(ourself, &ours);
946                         ConvertSidToStringSid(ourself2, &ours2);
947                         debug(("got sids:\n  oursnew=%s\n  oursold=%s\n"
948                                "  theirs=%s\n", ours, ours2, theirs));
949                         LocalFree(ours);
950                         LocalFree(ours2);
951                         LocalFree(theirs);
952                     }
953 #endif
954                     if (!EqualSid(mapowner, ourself) &&
955                         !EqualSid(mapowner, ourself2)) {
956                         CloseHandle(filemap);
957                         LocalFree(psd);
958                         sfree(ourself);
959                         sfree(ourself2);
960                         return 0;      /* security ID mismatch! */
961                     }
962 #ifdef DEBUG_IPC
963                     debug(("security stuff matched\n"));
964 #endif
965                     LocalFree(psd);
966                     sfree(ourself);
967                     sfree(ourself2);
968                 } else {
969 #ifdef DEBUG_IPC
970                     debug(("security APIs not present\n"));
971 #endif
972                 }
973 #endif
974                 p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
975 #ifdef DEBUG_IPC
976                 debug(("p is %p\n", p));
977                 {
978                     int i;
979                     for (i = 0; i < 5; i++)
980                         debug(("p[%d]=%02x\n", i,
981                                ((unsigned char *) p)[i]));
982                 }
983 #endif
984                 answer_msg(p);
985                 ret = 1;
986                 UnmapViewOfFile(p);
987             }
988             CloseHandle(filemap);
989             return ret;
990         }
991     }
992
993     return DefWindowProc(hwnd, message, wParam, lParam);
994 }
995
996 /*
997  * Fork and Exec the command in cmdline. [DBW]
998  */
999 void spawn_cmd(const char *cmdline, const char *args, int show)
1000 {
1001     if (ShellExecute(NULL, _T("open"), cmdline,
1002                      args, NULL, show) <= (HINSTANCE) 32) {
1003         char *msg;
1004         msg = dupprintf("Failed to run \"%.100s\", Error: %d", cmdline,
1005                         (int)GetLastError());
1006         MessageBox(NULL, msg, APPNAME, MB_OK | MB_ICONEXCLAMATION);
1007         sfree(msg);
1008     }
1009 }
1010
1011 /*
1012  * This is a can't-happen stub, since Pageant never makes
1013  * asynchronous agent requests.
1014  */
1015 void agent_schedule_callback(void (*callback)(void *, void *, int),
1016                              void *callback_ctx, void *data, int len)
1017 {
1018     assert(!"We shouldn't get here");
1019 }
1020
1021 void cleanup_exit(int code)
1022 {
1023     shutdown_help();
1024     exit(code);
1025 }
1026
1027 int flags = FLAG_SYNCAGENT;
1028
1029 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
1030 {
1031     WNDCLASS wndclass;
1032     MSG msg;
1033     const char *command = NULL;
1034     int added_keys = 0;
1035     int argc, i;
1036     char **argv, **argstart;
1037
1038     hinst = inst;
1039     hwnd = NULL;
1040
1041     /*
1042      * Determine whether we're an NT system (should have security
1043      * APIs) or a non-NT system (don't do security).
1044      */
1045     if (!init_winver())
1046     {
1047         modalfatalbox("Windows refuses to report a version");
1048     }
1049     if (osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT) {
1050         has_security = TRUE;
1051     } else
1052         has_security = FALSE;
1053
1054     if (has_security) {
1055 #ifndef NO_SECURITY
1056         /*
1057          * Attempt to get the security API we need.
1058          */
1059         if (!got_advapi()) {
1060             MessageBox(NULL,
1061                        "Unable to access security APIs. Pageant will\n"
1062                        "not run, in case it causes a security breach.",
1063                        "Pageant Fatal Error", MB_ICONERROR | MB_OK);
1064             return 1;
1065         }
1066 #else
1067         MessageBox(NULL,
1068                    "This program has been compiled for Win9X and will\n"
1069                    "not run on NT, in case it causes a security breach.",
1070                    "Pageant Fatal Error", MB_ICONERROR | MB_OK);
1071         return 1;
1072 #endif
1073     }
1074
1075     /*
1076      * See if we can find our Help file.
1077      */
1078     init_help();
1079
1080     /*
1081      * Look for the PuTTY binary (we will enable the saved session
1082      * submenu if we find it).
1083      */
1084     {
1085         char b[2048], *p, *q, *r;
1086         FILE *fp;
1087         GetModuleFileName(NULL, b, sizeof(b) - 16);
1088         r = b;
1089         p = strrchr(b, '\\');
1090         if (p && p >= r) r = p+1;
1091         q = strrchr(b, ':');
1092         if (q && q >= r) r = q+1;
1093         strcpy(r, "putty.exe");
1094         if ( (fp = fopen(b, "r")) != NULL) {
1095             putty_path = dupstr(b);
1096             fclose(fp);
1097         } else
1098             putty_path = NULL;
1099     }
1100
1101     /*
1102      * Find out if Pageant is already running.
1103      */
1104     already_running = agent_exists();
1105
1106     /*
1107      * Initialise the cross-platform Pageant code.
1108      */
1109     if (!already_running) {
1110         pageant_init();
1111     }
1112
1113     /*
1114      * Process the command line and add keys as listed on it.
1115      */
1116     split_into_argv(cmdline, &argc, &argv, &argstart);
1117     for (i = 0; i < argc; i++) {
1118         if (!strcmp(argv[i], "-pgpfp")) {
1119             pgp_fingerprints();
1120             return 1;
1121         } else if (!strcmp(argv[i], "-c")) {
1122             /*
1123              * If we see `-c', then the rest of the
1124              * command line should be treated as a
1125              * command to be spawned.
1126              */
1127             if (i < argc-1)
1128                 command = argstart[i+1];
1129             else
1130                 command = "";
1131             break;
1132         } else {
1133             Filename *fn = filename_from_str(argv[i]);
1134             win_add_keyfile(fn);
1135             filename_free(fn);
1136             added_keys = TRUE;
1137         }
1138     }
1139
1140     /*
1141      * Forget any passphrase that we retained while going over
1142      * command line keyfiles.
1143      */
1144     pageant_forget_passphrases();
1145
1146     if (command) {
1147         char *args;
1148         if (command[0] == '"')
1149             args = strchr(++command, '"');
1150         else
1151             args = strchr(command, ' ');
1152         if (args) {
1153             *args++ = 0;
1154             while(*args && isspace(*args)) args++;
1155         }
1156         spawn_cmd(command, args, show);
1157     }
1158
1159     /*
1160      * If Pageant was already running, we leave now. If we haven't
1161      * even taken any auxiliary action (spawned a command or added
1162      * keys), complain.
1163      */
1164     if (already_running) {
1165         if (!command && !added_keys) {
1166             MessageBox(NULL, "Pageant is already running", "Pageant Error",
1167                        MB_ICONERROR | MB_OK);
1168         }
1169         return 0;
1170     }
1171
1172     if (!prev) {
1173         wndclass.style = 0;
1174         wndclass.lpfnWndProc = WndProc;
1175         wndclass.cbClsExtra = 0;
1176         wndclass.cbWndExtra = 0;
1177         wndclass.hInstance = inst;
1178         wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
1179         wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
1180         wndclass.hbrBackground = GetStockObject(BLACK_BRUSH);
1181         wndclass.lpszMenuName = NULL;
1182         wndclass.lpszClassName = APPNAME;
1183
1184         RegisterClass(&wndclass);
1185     }
1186
1187     keylist = NULL;
1188
1189     hwnd = CreateWindow(APPNAME, APPNAME,
1190                         WS_OVERLAPPEDWINDOW | WS_VSCROLL,
1191                         CW_USEDEFAULT, CW_USEDEFAULT,
1192                         100, 100, NULL, NULL, inst, NULL);
1193
1194     /* Set up a system tray icon */
1195     AddTrayIcon(hwnd);
1196
1197     /* Accelerators used: nsvkxa */
1198     systray_menu = CreatePopupMenu();
1199     if (putty_path) {
1200         session_menu = CreateMenu();
1201         AppendMenu(systray_menu, MF_ENABLED, IDM_PUTTY, "&New Session");
1202         AppendMenu(systray_menu, MF_POPUP | MF_ENABLED,
1203                    (UINT_PTR) session_menu, "&Saved Sessions");
1204         AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
1205     }
1206     AppendMenu(systray_menu, MF_ENABLED, IDM_VIEWKEYS,
1207            "&View Keys");
1208     AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key");
1209     AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
1210     if (has_help())
1211         AppendMenu(systray_menu, MF_ENABLED, IDM_HELP, "&Help");
1212     AppendMenu(systray_menu, MF_ENABLED, IDM_ABOUT, "&About");
1213     AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
1214     AppendMenu(systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit");
1215     initial_menuitems_count = GetMenuItemCount(session_menu);
1216
1217     /* Set the default menu item. */
1218     SetMenuDefaultItem(systray_menu, IDM_VIEWKEYS, FALSE);
1219
1220     ShowWindow(hwnd, SW_HIDE);
1221
1222     /*
1223      * Main message loop.
1224      */
1225     while (GetMessage(&msg, NULL, 0, 0) == 1) {
1226         if (!(IsWindow(keylist) && IsDialogMessage(keylist, &msg)) &&
1227             !(IsWindow(aboutbox) && IsDialogMessage(aboutbox, &msg))) {
1228             TranslateMessage(&msg);
1229             DispatchMessage(&msg);
1230         }
1231     }
1232
1233     /* Clean up the system tray icon */
1234     {
1235         NOTIFYICONDATA tnid;
1236
1237         tnid.cbSize = sizeof(NOTIFYICONDATA);
1238         tnid.hWnd = hwnd;
1239         tnid.uID = 1;
1240
1241         Shell_NotifyIcon(NIM_DELETE, &tnid);
1242
1243         DestroyMenu(systray_menu);
1244     }
1245
1246     if (keypath) filereq_free(keypath);
1247
1248     cleanup_exit(msg.wParam);
1249     return msg.wParam;                 /* just in case optimiser complains */
1250 }