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