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