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