2 * Pageant: the PuTTY Authentication Agent.
11 #define PUTTY_DO_GLOBALS
26 #define _WIN32_WINNT 0x0500 /* for ConvertSidToStringSid */
31 #define IDI_MAINICON 200
32 #define IDI_TRAYICON 201
34 #define WM_SYSTRAY (WM_APP + 6)
35 #define WM_SYSTRAY2 (WM_APP + 7)
37 #define AGENT_COPYDATA_ID 0x804e50ba /* random goop */
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. */
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
50 #define APPNAME "Pageant"
56 static HMENU systray_menu, session_menu;
57 static int already_running;
59 static char *putty_path;
61 /* CWD for "add key" file requester. */
62 static filereq *keypath = NULL;
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;
72 * Print a modal (Really Bad) message box and perform a fatal exit.
74 void modalfatalbox(const char *fmt, ...)
80 buf = dupvprintf(fmt, ap);
82 MessageBox(hwnd, buf, "Pageant Fatal Error",
83 MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
88 /* Un-munge session names out of the registry. */
89 static void unmungestr(char *in, char *out, int outlen)
92 if (*in == '%' && in[1] && in[2]) {
100 *out++ = (i << 4) + j;
114 static int has_security;
116 struct PassphraseProcStruct {
122 * Dialog-box function for the Licence box.
124 static INT_PTR CALLBACK LicenceProc(HWND hwnd, UINT msg,
125 WPARAM wParam, LPARAM lParam)
129 SetDlgItemText(hwnd, 1000, LICENCE_TEXT("\r\n\r\n"));
132 switch (LOWORD(wParam)) {
147 * Dialog-box function for the About box.
149 static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg,
150 WPARAM wParam, LPARAM lParam)
155 char *text = dupprintf
156 ("Pageant\r\n\r\n%s\r\n\r\n%s",
158 "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved.");
159 SetDlgItemText(hwnd, 1000, text);
164 switch (LOWORD(wParam)) {
171 EnableWindow(hwnd, 0);
172 DialogBox(hinst, MAKEINTRESOURCE(214), hwnd, LicenceProc);
173 EnableWindow(hwnd, 1);
174 SetActiveWindow(hwnd);
186 static HWND passphrase_box;
189 * Dialog-box function for the passphrase box.
191 static INT_PTR CALLBACK PassphraseProc(HWND hwnd, UINT msg,
192 WPARAM wParam, LPARAM lParam)
194 static char **passphrase = NULL;
195 struct PassphraseProcStruct *p;
199 passphrase_box = hwnd;
203 { /* centre the window */
207 hw = GetDesktopWindow();
208 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
210 (rs.right + rs.left + rd.left - rd.right) / 2,
211 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
212 rd.right - rd.left, rd.bottom - rd.top, TRUE);
215 SetForegroundWindow(hwnd);
216 SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
217 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
218 p = (struct PassphraseProcStruct *) lParam;
219 passphrase = p->passphrase;
221 SetDlgItemText(hwnd, 101, p->comment);
222 burnstr(*passphrase);
223 *passphrase = dupstr("");
224 SetDlgItemText(hwnd, 102, *passphrase);
227 switch (LOWORD(wParam)) {
237 case 102: /* edit box */
238 if ((HIWORD(wParam) == EN_CHANGE) && passphrase) {
239 burnstr(*passphrase);
240 *passphrase = GetDlgItemText_alloc(hwnd, 102);
253 * Warn about the obsolescent key file format.
255 void old_keyfile_warning(void)
257 static const char mbtitle[] = "PuTTY Key File Warning";
258 static const char message[] =
259 "You are loading an SSH-2 private key which has an\n"
260 "old version of the file format. This means your key\n"
261 "file is not fully tamperproof. Future versions of\n"
262 "PuTTY may stop supporting this private key format,\n"
263 "so we recommend you convert your key to the new\n"
266 "You can perform this conversion by loading the key\n"
267 "into PuTTYgen and then saving it again.";
269 MessageBox(NULL, message, mbtitle, MB_OK);
273 * Update the visible key list.
275 void keylist_update(void)
278 struct ssh2_userkey *skey;
282 SendDlgItemMessage(keylist, 100, LB_RESETCONTENT, 0, 0);
283 for (i = 0; NULL != (rkey = pageant_nth_ssh1_key(i)); i++) {
284 char listentry[512], *p;
286 * Replace two spaces in the fingerprint with tabs, for
287 * nice alignment in the box.
289 strcpy(listentry, "ssh1\t");
290 p = listentry + strlen(listentry);
291 rsa_fingerprint(p, sizeof(listentry) - (p - listentry), rkey);
292 p = strchr(listentry, ' ');
295 p = strchr(listentry, ' ');
298 SendDlgItemMessage(keylist, 100, LB_ADDSTRING,
299 0, (LPARAM) listentry);
301 for (i = 0; NULL != (skey = pageant_nth_ssh2_key(i)); i++) {
306 * For nice alignment in the list box, we would ideally
307 * want every entry to align to the tab stop settings, and
308 * have a column for algorithm name, one for bit count,
309 * one for hex fingerprint, and one for key comment.
311 * Unfortunately, some of the algorithm names are so long
312 * that they overflow into the bit-count field.
313 * Fortunately, at the moment, those are _precisely_ the
314 * algorithm names that don't need a bit count displayed
315 * anyway (because for NIST-style ECDSA the bit count is
316 * mentioned in the algorithm name, and for ssh-ed25519
317 * there is only one possible value anyway). So we fudge
318 * this by simply omitting the bit count field in that
321 * This is fragile not only in the face of further key
322 * types that don't follow this pattern, but also in the
323 * face of font metrics changes - the Windows semantics
324 * for list box tab stops is that \t aligns to the next
325 * one you haven't already exceeded, so I have to guess
326 * when the key type will overflow past the bit-count tab
327 * stop and leave out a tab character. Urgh.
330 p = ssh2_fingerprint(skey->alg, skey->data);
331 listentry = dupprintf("%s\t%s", p, skey->comment);
336 pos += strcspn(listentry + pos, " :");
337 if (listentry[pos] == ':' || !listentry[pos])
339 listentry[pos++] = '\t';
341 if (skey->alg != &ssh_dss && skey->alg != &ssh_rsa) {
343 * Remove the bit-count field, which is between the
344 * first and second \t.
348 while (listentry[pos] && listentry[pos] != '\t')
352 while (listentry[pos] && listentry[pos] != '\t')
355 if ((listentry[outpos] = listentry[pos]) == '\0')
362 SendDlgItemMessage(keylist, 100, LB_ADDSTRING, 0,
366 SendDlgItemMessage(keylist, 100, LB_SETCURSEL, (WPARAM) - 1, 0);
370 static void answer_msg(void *msgv)
372 unsigned char *msg = (unsigned char *)msgv;
377 msglen = GET_32BIT(msg);
378 if (msglen > AGENT_MAX_MSGLEN) {
379 reply = pageant_failure_msg(&replylen);
381 reply = pageant_handle_msg(msg + 4, msglen, &replylen, NULL, NULL);
382 if (replylen > AGENT_MAX_MSGLEN) {
383 smemclr(reply, replylen);
385 reply = pageant_failure_msg(&replylen);
390 * Windows Pageant answers messages in place, by overwriting the
391 * input message buffer.
393 memcpy(msg, reply, replylen);
394 smemclr(reply, replylen);
398 static void win_add_keyfile(Filename *filename)
402 char *passphrase = NULL;
405 * Try loading the key without a passphrase. (Or rather, without a
406 * _new_ passphrase; pageant_add_keyfile will take care of trying
407 * all the passphrases we've already stored.)
409 ret = pageant_add_keyfile(filename, NULL, &err);
410 if (ret == PAGEANT_ACTION_OK) {
412 } else if (ret == PAGEANT_ACTION_FAILURE) {
417 * OK, a passphrase is needed, and we've been given the key
418 * comment to use in the passphrase prompt.
422 struct PassphraseProcStruct pps;
424 pps.passphrase = &passphrase;
426 dlgret = DialogBoxParam(hinst, MAKEINTRESOURCE(210),
427 NULL, PassphraseProc, (LPARAM) &pps);
428 passphrase_box = NULL;
431 goto done; /* operation cancelled */
435 assert(passphrase != NULL);
437 ret = pageant_add_keyfile(filename, passphrase, &err);
438 if (ret == PAGEANT_ACTION_OK) {
440 } else if (ret == PAGEANT_ACTION_FAILURE) {
444 smemclr(passphrase, strlen(passphrase));
450 message_box(err, APPNAME, MB_OK | MB_ICONERROR,
451 HELPCTXID(errors_cantloadkey));
454 smemclr(passphrase, strlen(passphrase));
462 * Prompt for a key file to add, and add it.
464 static void prompt_add_keyfile(void)
467 char *filelist = snewn(8192, char);
469 if (!keypath) keypath = filereq_new();
470 memset(&of, 0, sizeof(of));
472 of.lpstrFilter = FILTER_KEY_FILES;
473 of.lpstrCustomFilter = NULL;
475 of.lpstrFile = filelist;
478 of.lpstrFileTitle = NULL;
479 of.lpstrTitle = "Select Private Key File";
480 of.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER;
481 if (request_file(keypath, &of, TRUE, FALSE)) {
482 if(strlen(filelist) > of.nFileOffset) {
483 /* Only one filename returned? */
484 Filename *fn = filename_from_str(filelist);
488 /* we are returned a bunch of strings, end to
489 * end. first string is the directory, the
490 * rest the filenames. terminated with an
493 char *dir = filelist;
494 char *filewalker = filelist + strlen(dir) + 1;
495 while (*filewalker != '\0') {
496 char *filename = dupcat(dir, "\\", filewalker, NULL);
497 Filename *fn = filename_from_str(filename);
501 filewalker += strlen(filewalker) + 1;
506 pageant_forget_passphrases();
512 * Dialog-box function for the key list box.
514 static INT_PTR CALLBACK KeyListProc(HWND hwnd, UINT msg,
515 WPARAM wParam, LPARAM lParam)
518 struct ssh2_userkey *skey;
525 { /* centre the window */
529 hw = GetDesktopWindow();
530 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
532 (rs.right + rs.left + rd.left - rd.right) / 2,
533 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
534 rd.right - rd.left, rd.bottom - rd.top, TRUE);
538 SetWindowLongPtr(hwnd, GWL_EXSTYLE,
539 GetWindowLongPtr(hwnd, GWL_EXSTYLE) |
542 HWND item = GetDlgItem(hwnd, 103); /* the Help button */
549 static int tabs[] = { 35, 75, 250 };
550 SendDlgItemMessage(hwnd, 100, LB_SETTABSTOPS,
551 sizeof(tabs) / sizeof(*tabs),
557 switch (LOWORD(wParam)) {
563 case 101: /* add key */
564 if (HIWORD(wParam) == BN_CLICKED ||
565 HIWORD(wParam) == BN_DOUBLECLICKED) {
566 if (passphrase_box) {
567 MessageBeep(MB_ICONERROR);
568 SetForegroundWindow(passphrase_box);
571 prompt_add_keyfile();
574 case 102: /* remove key */
575 if (HIWORD(wParam) == BN_CLICKED ||
576 HIWORD(wParam) == BN_DOUBLECLICKED) {
581 /* our counter within the array of selected items */
584 /* get the number of items selected in the list */
586 SendDlgItemMessage(hwnd, 100, LB_GETSELCOUNT, 0, 0);
588 /* none selected? that was silly */
589 if (numSelected == 0) {
594 /* get item indices in an array */
595 selectedArray = snewn(numSelected, int);
596 SendDlgItemMessage(hwnd, 100, LB_GETSELITEMS,
597 numSelected, (WPARAM)selectedArray);
599 itemNum = numSelected - 1;
600 rCount = pageant_count_ssh1_keys();
601 sCount = pageant_count_ssh2_keys();
603 /* go through the non-rsakeys until we've covered them all,
604 * and/or we're out of selected items to check. note that
605 * we go *backwards*, to avoid complications from deleting
606 * things hence altering the offset of subsequent items
608 for (i = sCount - 1; (itemNum >= 0) && (i >= 0); i--) {
609 skey = pageant_nth_ssh2_key(i);
611 if (selectedArray[itemNum] == rCount + i) {
612 pageant_delete_ssh2_key(skey);
613 skey->alg->freekey(skey->data);
619 /* do the same for the rsa keys */
620 for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) {
621 rkey = pageant_nth_ssh1_key(i);
623 if(selectedArray[itemNum] == i) {
624 pageant_delete_ssh1_key(rkey);
631 sfree(selectedArray);
636 if (HIWORD(wParam) == BN_CLICKED ||
637 HIWORD(wParam) == BN_DOUBLECLICKED) {
638 launch_help(hwnd, WINHELP_CTX_pageant_general);
645 int id = ((LPHELPINFO)lParam)->iCtrlId;
646 const char *topic = NULL;
648 case 100: topic = WINHELP_CTX_pageant_keylist; break;
649 case 101: topic = WINHELP_CTX_pageant_addkey; break;
650 case 102: topic = WINHELP_CTX_pageant_remkey; break;
653 launch_help(hwnd, topic);
667 /* Set up a system tray icon */
668 static BOOL AddTrayIcon(HWND hwnd)
674 #ifdef NIM_SETVERSION
676 res = Shell_NotifyIcon(NIM_SETVERSION, &tnid);
679 tnid.cbSize = sizeof(NOTIFYICONDATA);
681 tnid.uID = 1; /* unique within this systray use */
682 tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
683 tnid.uCallbackMessage = WM_SYSTRAY;
684 tnid.hIcon = hicon = LoadIcon(hinst, MAKEINTRESOURCE(201));
685 strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
687 res = Shell_NotifyIcon(NIM_ADD, &tnid);
689 if (hicon) DestroyIcon(hicon);
694 /* Update the saved-sessions menu. */
695 static void update_sessions(void)
699 TCHAR buf[MAX_PATH + 1];
702 int index_key, index_menu;
707 if(ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, PUTTY_REGKEY, &hkey))
710 for(num_entries = GetMenuItemCount(session_menu);
711 num_entries > initial_menuitems_count;
713 RemoveMenu(session_menu, 0, MF_BYPOSITION);
718 while(ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) {
719 TCHAR session_name[MAX_PATH + 1];
720 unmungestr(buf, session_name, MAX_PATH);
721 if(strcmp(buf, PUTTY_DEFAULT) != 0) {
722 memset(&mii, 0, sizeof(mii));
723 mii.cbSize = sizeof(mii);
724 mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
725 mii.fType = MFT_STRING;
726 mii.fState = MFS_ENABLED;
727 mii.wID = (index_menu * 16) + IDM_SESSIONS_BASE;
728 mii.dwTypeData = session_name;
729 InsertMenuItem(session_menu, index_menu, TRUE, &mii);
737 if(index_menu == 0) {
738 mii.cbSize = sizeof(mii);
739 mii.fMask = MIIM_TYPE | MIIM_STATE;
740 mii.fType = MFT_STRING;
741 mii.fState = MFS_GRAYED;
742 mii.dwTypeData = _T("(No sessions)");
743 InsertMenuItem(session_menu, index_menu, TRUE, &mii);
749 * Versions of Pageant prior to 0.61 expected this SID on incoming
750 * communications. For backwards compatibility, and more particularly
751 * for compatibility with derived works of PuTTY still using the old
752 * Pageant client code, we accept it as an alternative to the one
753 * returned from get_user_sid() in winpgntc.c.
755 PSID get_default_sid(void)
759 PSECURITY_DESCRIPTOR psd = NULL;
760 PSID sid = NULL, copy = NULL, ret = NULL;
762 if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE,
763 GetCurrentProcessId())) == NULL)
766 if (p_GetSecurityInfo(proc, SE_KERNEL_OBJECT, OWNER_SECURITY_INFORMATION,
767 &sid, NULL, NULL, NULL, &psd) != ERROR_SUCCESS)
770 sidlen = GetLengthSid(sid);
772 copy = (PSID)smalloc(sidlen);
774 if (!CopySid(sidlen, copy, sid))
777 /* Success. Move sid into the return value slot, and null it out
778 * to stop the cleanup code freeing it. */
794 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
795 WPARAM wParam, LPARAM lParam)
797 static int menuinprogress;
798 static UINT msgTaskbarCreated = 0;
802 msgTaskbarCreated = RegisterWindowMessage(_T("TaskbarCreated"));
805 if (message==msgTaskbarCreated) {
807 * Explorer has been restarted, so the tray icon will
815 if (lParam == WM_RBUTTONUP) {
817 GetCursorPos(&cursorpos);
818 PostMessage(hwnd, WM_SYSTRAY2, cursorpos.x, cursorpos.y);
819 } else if (lParam == WM_LBUTTONDBLCLK) {
820 /* Run the default menu item. */
821 UINT menuitem = GetMenuDefaultItem(systray_menu, FALSE, 0);
823 PostMessage(hwnd, WM_COMMAND, menuitem, 0);
827 if (!menuinprogress) {
830 SetForegroundWindow(hwnd);
831 TrackPopupMenu(systray_menu,
832 TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
834 wParam, lParam, 0, hwnd, NULL);
840 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
842 if((INT_PTR)ShellExecute(hwnd, NULL, putty_path, _T(""), _T(""),
844 MessageBox(NULL, "Unable to execute PuTTY!",
845 "Error", MB_OK | MB_ICONERROR);
850 SendMessage(passphrase_box, WM_CLOSE, 0, 0);
851 SendMessage(hwnd, WM_CLOSE, 0, 0);
855 keylist = CreateDialog(hinst, MAKEINTRESOURCE(211),
857 ShowWindow(keylist, SW_SHOWNORMAL);
860 * Sometimes the window comes up minimised / hidden for
861 * no obvious reason. Prevent this. This also brings it
862 * to the front if it's already present (the user
863 * selected View Keys because they wanted to _see_ the
866 SetForegroundWindow(keylist);
867 SetWindowPos(keylist, HWND_TOP, 0, 0, 0, 0,
868 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
871 if (passphrase_box) {
872 MessageBeep(MB_ICONERROR);
873 SetForegroundWindow(passphrase_box);
876 prompt_add_keyfile();
880 aboutbox = CreateDialog(hinst, MAKEINTRESOURCE(213),
882 ShowWindow(aboutbox, SW_SHOWNORMAL);
884 * Sometimes the window comes up minimised / hidden
885 * for no obvious reason. Prevent this.
887 SetForegroundWindow(aboutbox);
888 SetWindowPos(aboutbox, HWND_TOP, 0, 0, 0, 0,
889 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
893 launch_help(hwnd, WINHELP_CTX_pageant_general);
897 if(wParam >= IDM_SESSIONS_BASE && wParam <= IDM_SESSIONS_MAX) {
899 TCHAR buf[MAX_PATH + 1];
900 TCHAR param[MAX_PATH + 1];
901 memset(&mii, 0, sizeof(mii));
902 mii.cbSize = sizeof(mii);
903 mii.fMask = MIIM_TYPE;
905 mii.dwTypeData = buf;
906 GetMenuItemInfo(session_menu, wParam, FALSE, &mii);
908 strcat(param, mii.dwTypeData);
909 if((INT_PTR)ShellExecute(hwnd, NULL, putty_path, param,
910 _T(""), SW_SHOW) <= 32) {
911 MessageBox(NULL, "Unable to execute PuTTY!", "Error",
912 MB_OK | MB_ICONERROR);
930 PSID mapowner, ourself, ourself2;
932 PSECURITY_DESCRIPTOR psd = NULL;
935 cds = (COPYDATASTRUCT *) lParam;
936 if (cds->dwData != AGENT_COPYDATA_ID)
937 return 0; /* not our message, mate */
938 mapname = (char *) cds->lpData;
939 if (mapname[cds->cbData - 1] != '\0')
940 return 0; /* failure to be ASCIZ! */
942 debug(("mapname is :%s:\n", mapname));
944 filemap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, mapname);
946 debug(("filemap is %p\n", filemap));
948 if (filemap != NULL && filemap != INVALID_HANDLE_VALUE) {
952 if ((ourself = get_user_sid()) == NULL) {
954 debug(("couldn't get user SID\n"));
956 CloseHandle(filemap);
960 if ((ourself2 = get_default_sid()) == NULL) {
962 debug(("couldn't get default SID\n"));
964 CloseHandle(filemap);
968 if ((rc = p_GetSecurityInfo(filemap, SE_KERNEL_OBJECT,
969 OWNER_SECURITY_INFORMATION,
970 &mapowner, NULL, NULL, NULL,
971 &psd) != ERROR_SUCCESS)) {
973 debug(("couldn't get owner info for filemap: %d\n",
976 CloseHandle(filemap);
982 LPTSTR ours, ours2, theirs;
983 ConvertSidToStringSid(mapowner, &theirs);
984 ConvertSidToStringSid(ourself, &ours);
985 ConvertSidToStringSid(ourself2, &ours2);
986 debug(("got sids:\n oursnew=%s\n oursold=%s\n"
987 " theirs=%s\n", ours, ours2, theirs));
993 if (!EqualSid(mapowner, ourself) &&
994 !EqualSid(mapowner, ourself2)) {
995 CloseHandle(filemap);
998 return 0; /* security ID mismatch! */
1001 debug(("security stuff matched\n"));
1007 debug(("security APIs not present\n"));
1011 p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
1013 debug(("p is %p\n", p));
1016 for (i = 0; i < 5; i++)
1017 debug(("p[%d]=%02x\n", i,
1018 ((unsigned char *) p)[i]));
1025 CloseHandle(filemap);
1030 return DefWindowProc(hwnd, message, wParam, lParam);
1034 * Fork and Exec the command in cmdline. [DBW]
1036 void spawn_cmd(const char *cmdline, const char *args, int show)
1038 if (ShellExecute(NULL, _T("open"), cmdline,
1039 args, NULL, show) <= (HINSTANCE) 32) {
1041 msg = dupprintf("Failed to run \"%.100s\", Error: %d", cmdline,
1042 (int)GetLastError());
1043 MessageBox(NULL, msg, APPNAME, MB_OK | MB_ICONEXCLAMATION);
1049 * This is a can't-happen stub, since Pageant never makes
1050 * asynchronous agent requests.
1052 void agent_schedule_callback(void (*callback)(void *, void *, int),
1053 void *callback_ctx, void *data, int len)
1055 assert(!"We shouldn't get here");
1058 void cleanup_exit(int code)
1064 int flags = FLAG_SYNCAGENT;
1066 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
1070 const char *command = NULL;
1073 char **argv, **argstart;
1079 * Determine whether we're an NT system (should have security
1080 * APIs) or a non-NT system (don't do security).
1084 modalfatalbox("Windows refuses to report a version");
1086 if (osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT) {
1087 has_security = TRUE;
1089 has_security = FALSE;
1094 * Attempt to get the security API we need.
1096 if (!got_advapi()) {
1098 "Unable to access security APIs. Pageant will\n"
1099 "not run, in case it causes a security breach.",
1100 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
1105 "This program has been compiled for Win9X and will\n"
1106 "not run on NT, in case it causes a security breach.",
1107 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
1113 * See if we can find our Help file.
1118 * Look for the PuTTY binary (we will enable the saved session
1119 * submenu if we find it).
1122 char b[2048], *p, *q, *r;
1124 GetModuleFileName(NULL, b, sizeof(b) - 16);
1126 p = strrchr(b, '\\');
1127 if (p && p >= r) r = p+1;
1128 q = strrchr(b, ':');
1129 if (q && q >= r) r = q+1;
1130 strcpy(r, "putty.exe");
1131 if ( (fp = fopen(b, "r")) != NULL) {
1132 putty_path = dupstr(b);
1139 * Find out if Pageant is already running.
1141 already_running = agent_exists();
1144 * Initialise the cross-platform Pageant code.
1146 if (!already_running) {
1151 * Process the command line and add keys as listed on it.
1153 split_into_argv(cmdline, &argc, &argv, &argstart);
1154 for (i = 0; i < argc; i++) {
1155 if (!strcmp(argv[i], "-pgpfp")) {
1158 } else if (!strcmp(argv[i], "-c")) {
1160 * If we see `-c', then the rest of the
1161 * command line should be treated as a
1162 * command to be spawned.
1165 command = argstart[i+1];
1170 Filename *fn = filename_from_str(argv[i]);
1171 win_add_keyfile(fn);
1178 * Forget any passphrase that we retained while going over
1179 * command line keyfiles.
1181 pageant_forget_passphrases();
1185 if (command[0] == '"')
1186 args = strchr(++command, '"');
1188 args = strchr(command, ' ');
1191 while(*args && isspace(*args)) args++;
1193 spawn_cmd(command, args, show);
1197 * If Pageant was already running, we leave now. If we haven't
1198 * even taken any auxiliary action (spawned a command or added
1201 if (already_running) {
1202 if (!command && !added_keys) {
1203 MessageBox(NULL, "Pageant is already running", "Pageant Error",
1204 MB_ICONERROR | MB_OK);
1211 wndclass.lpfnWndProc = WndProc;
1212 wndclass.cbClsExtra = 0;
1213 wndclass.cbWndExtra = 0;
1214 wndclass.hInstance = inst;
1215 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
1216 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
1217 wndclass.hbrBackground = GetStockObject(BLACK_BRUSH);
1218 wndclass.lpszMenuName = NULL;
1219 wndclass.lpszClassName = APPNAME;
1221 RegisterClass(&wndclass);
1226 hwnd = CreateWindow(APPNAME, APPNAME,
1227 WS_OVERLAPPEDWINDOW | WS_VSCROLL,
1228 CW_USEDEFAULT, CW_USEDEFAULT,
1229 100, 100, NULL, NULL, inst, NULL);
1231 /* Set up a system tray icon */
1234 /* Accelerators used: nsvkxa */
1235 systray_menu = CreatePopupMenu();
1237 session_menu = CreateMenu();
1238 AppendMenu(systray_menu, MF_ENABLED, IDM_PUTTY, "&New Session");
1239 AppendMenu(systray_menu, MF_POPUP | MF_ENABLED,
1240 (UINT_PTR) session_menu, "&Saved Sessions");
1241 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
1243 AppendMenu(systray_menu, MF_ENABLED, IDM_VIEWKEYS,
1245 AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key");
1246 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
1248 AppendMenu(systray_menu, MF_ENABLED, IDM_HELP, "&Help");
1249 AppendMenu(systray_menu, MF_ENABLED, IDM_ABOUT, "&About");
1250 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
1251 AppendMenu(systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit");
1252 initial_menuitems_count = GetMenuItemCount(session_menu);
1254 /* Set the default menu item. */
1255 SetMenuDefaultItem(systray_menu, IDM_VIEWKEYS, FALSE);
1257 ShowWindow(hwnd, SW_HIDE);
1260 * Main message loop.
1262 while (GetMessage(&msg, NULL, 0, 0) == 1) {
1263 if (!(IsWindow(keylist) && IsDialogMessage(keylist, &msg)) &&
1264 !(IsWindow(aboutbox) && IsDialogMessage(aboutbox, &msg))) {
1265 TranslateMessage(&msg);
1266 DispatchMessage(&msg);
1270 /* Clean up the system tray icon */
1272 NOTIFYICONDATA tnid;
1274 tnid.cbSize = sizeof(NOTIFYICONDATA);
1278 Shell_NotifyIcon(NIM_DELETE, &tnid);
1280 DestroyMenu(systray_menu);
1283 if (keypath) filereq_free(keypath);
1285 cleanup_exit(msg.wParam);
1286 return msg.wParam; /* just in case optimiser complains */