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"
52 extern const char ver[];
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 *buildinfo_text = buildinfo("\r\n");
156 char *text = dupprintf
157 ("Pageant\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s",
159 "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved.");
160 sfree(buildinfo_text);
161 SetDlgItemText(hwnd, 1000, text);
166 switch (LOWORD(wParam)) {
173 EnableWindow(hwnd, 0);
174 DialogBox(hinst, MAKEINTRESOURCE(214), hwnd, LicenceProc);
175 EnableWindow(hwnd, 1);
176 SetActiveWindow(hwnd);
179 /* Load web browser */
180 ShellExecute(hwnd, "open",
181 "http://www.chiark.greenend.org.uk/~sgtatham/putty/",
182 0, 0, SW_SHOWDEFAULT);
194 static HWND passphrase_box;
197 * Dialog-box function for the passphrase box.
199 static INT_PTR CALLBACK PassphraseProc(HWND hwnd, UINT msg,
200 WPARAM wParam, LPARAM lParam)
202 static char **passphrase = NULL;
203 struct PassphraseProcStruct *p;
207 passphrase_box = hwnd;
211 { /* centre the window */
215 hw = GetDesktopWindow();
216 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
218 (rs.right + rs.left + rd.left - rd.right) / 2,
219 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
220 rd.right - rd.left, rd.bottom - rd.top, TRUE);
223 SetForegroundWindow(hwnd);
224 SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
225 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
226 p = (struct PassphraseProcStruct *) lParam;
227 passphrase = p->passphrase;
229 SetDlgItemText(hwnd, 101, p->comment);
230 burnstr(*passphrase);
231 *passphrase = dupstr("");
232 SetDlgItemText(hwnd, 102, *passphrase);
235 switch (LOWORD(wParam)) {
245 case 102: /* edit box */
246 if ((HIWORD(wParam) == EN_CHANGE) && passphrase) {
247 burnstr(*passphrase);
248 *passphrase = GetDlgItemText_alloc(hwnd, 102);
261 * Warn about the obsolescent key file format.
263 void old_keyfile_warning(void)
265 static const char mbtitle[] = "PuTTY Key File Warning";
266 static const char message[] =
267 "You are loading an SSH-2 private key which has an\n"
268 "old version of the file format. This means your key\n"
269 "file is not fully tamperproof. Future versions of\n"
270 "PuTTY may stop supporting this private key format,\n"
271 "so we recommend you convert your key to the new\n"
274 "You can perform this conversion by loading the key\n"
275 "into PuTTYgen and then saving it again.";
277 MessageBox(NULL, message, mbtitle, MB_OK);
281 * Update the visible key list.
283 void keylist_update(void)
286 struct ssh2_userkey *skey;
290 SendDlgItemMessage(keylist, 100, LB_RESETCONTENT, 0, 0);
291 for (i = 0; NULL != (rkey = pageant_nth_ssh1_key(i)); i++) {
292 char listentry[512], *p;
294 * Replace two spaces in the fingerprint with tabs, for
295 * nice alignment in the box.
297 strcpy(listentry, "ssh1\t");
298 p = listentry + strlen(listentry);
299 rsa_fingerprint(p, sizeof(listentry) - (p - listentry), rkey);
300 p = strchr(listentry, ' ');
303 p = strchr(listentry, ' ');
306 SendDlgItemMessage(keylist, 100, LB_ADDSTRING,
307 0, (LPARAM) listentry);
309 for (i = 0; NULL != (skey = pageant_nth_ssh2_key(i)); i++) {
314 * For nice alignment in the list box, we would ideally
315 * want every entry to align to the tab stop settings, and
316 * have a column for algorithm name, one for bit count,
317 * one for hex fingerprint, and one for key comment.
319 * Unfortunately, some of the algorithm names are so long
320 * that they overflow into the bit-count field.
321 * Fortunately, at the moment, those are _precisely_ the
322 * algorithm names that don't need a bit count displayed
323 * anyway (because for NIST-style ECDSA the bit count is
324 * mentioned in the algorithm name, and for ssh-ed25519
325 * there is only one possible value anyway). So we fudge
326 * this by simply omitting the bit count field in that
329 * This is fragile not only in the face of further key
330 * types that don't follow this pattern, but also in the
331 * face of font metrics changes - the Windows semantics
332 * for list box tab stops is that \t aligns to the next
333 * one you haven't already exceeded, so I have to guess
334 * when the key type will overflow past the bit-count tab
335 * stop and leave out a tab character. Urgh.
338 p = ssh2_fingerprint(skey->alg, skey->data);
339 listentry = dupprintf("%s\t%s", p, skey->comment);
344 pos += strcspn(listentry + pos, " :");
345 if (listentry[pos] == ':' || !listentry[pos])
347 listentry[pos++] = '\t';
349 if (skey->alg != &ssh_dss && skey->alg != &ssh_rsa) {
351 * Remove the bit-count field, which is between the
352 * first and second \t.
356 while (listentry[pos] && listentry[pos] != '\t')
360 while (listentry[pos] && listentry[pos] != '\t')
363 if ((listentry[outpos] = listentry[pos]) == '\0')
370 SendDlgItemMessage(keylist, 100, LB_ADDSTRING, 0,
374 SendDlgItemMessage(keylist, 100, LB_SETCURSEL, (WPARAM) - 1, 0);
378 static void answer_msg(void *msgv)
380 unsigned char *msg = (unsigned char *)msgv;
385 msglen = GET_32BIT(msg);
386 if (msglen > AGENT_MAX_MSGLEN) {
387 reply = pageant_failure_msg(&replylen);
389 reply = pageant_handle_msg(msg + 4, msglen, &replylen, NULL, NULL);
390 if (replylen > AGENT_MAX_MSGLEN) {
391 smemclr(reply, replylen);
393 reply = pageant_failure_msg(&replylen);
398 * Windows Pageant answers messages in place, by overwriting the
399 * input message buffer.
401 memcpy(msg, reply, replylen);
402 smemclr(reply, replylen);
406 static void win_add_keyfile(Filename *filename)
410 char *passphrase = NULL;
413 * Try loading the key without a passphrase. (Or rather, without a
414 * _new_ passphrase; pageant_add_keyfile will take care of trying
415 * all the passphrases we've already stored.)
417 ret = pageant_add_keyfile(filename, NULL, &err);
418 if (ret == PAGEANT_ACTION_OK) {
420 } else if (ret == PAGEANT_ACTION_FAILURE) {
425 * OK, a passphrase is needed, and we've been given the key
426 * comment to use in the passphrase prompt.
430 struct PassphraseProcStruct pps;
432 pps.passphrase = &passphrase;
434 dlgret = DialogBoxParam(hinst, MAKEINTRESOURCE(210),
435 NULL, PassphraseProc, (LPARAM) &pps);
436 passphrase_box = NULL;
439 goto done; /* operation cancelled */
443 assert(passphrase != NULL);
445 ret = pageant_add_keyfile(filename, passphrase, &err);
446 if (ret == PAGEANT_ACTION_OK) {
448 } else if (ret == PAGEANT_ACTION_FAILURE) {
452 smemclr(passphrase, strlen(passphrase));
458 message_box(err, APPNAME, MB_OK | MB_ICONERROR,
459 HELPCTXID(errors_cantloadkey));
462 smemclr(passphrase, strlen(passphrase));
470 * Prompt for a key file to add, and add it.
472 static void prompt_add_keyfile(void)
475 char *filelist = snewn(8192, char);
477 if (!keypath) keypath = filereq_new();
478 memset(&of, 0, sizeof(of));
480 of.lpstrFilter = FILTER_KEY_FILES;
481 of.lpstrCustomFilter = NULL;
483 of.lpstrFile = filelist;
486 of.lpstrFileTitle = NULL;
487 of.lpstrTitle = "Select Private Key File";
488 of.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER;
489 if (request_file(keypath, &of, TRUE, FALSE)) {
490 if(strlen(filelist) > of.nFileOffset) {
491 /* Only one filename returned? */
492 Filename *fn = filename_from_str(filelist);
496 /* we are returned a bunch of strings, end to
497 * end. first string is the directory, the
498 * rest the filenames. terminated with an
501 char *dir = filelist;
502 char *filewalker = filelist + strlen(dir) + 1;
503 while (*filewalker != '\0') {
504 char *filename = dupcat(dir, "\\", filewalker, NULL);
505 Filename *fn = filename_from_str(filename);
509 filewalker += strlen(filewalker) + 1;
514 pageant_forget_passphrases();
520 * Dialog-box function for the key list box.
522 static INT_PTR CALLBACK KeyListProc(HWND hwnd, UINT msg,
523 WPARAM wParam, LPARAM lParam)
526 struct ssh2_userkey *skey;
533 { /* centre the window */
537 hw = GetDesktopWindow();
538 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
540 (rs.right + rs.left + rd.left - rd.right) / 2,
541 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
542 rd.right - rd.left, rd.bottom - rd.top, TRUE);
546 SetWindowLongPtr(hwnd, GWL_EXSTYLE,
547 GetWindowLongPtr(hwnd, GWL_EXSTYLE) |
550 HWND item = GetDlgItem(hwnd, 103); /* the Help button */
557 static int tabs[] = { 35, 75, 250 };
558 SendDlgItemMessage(hwnd, 100, LB_SETTABSTOPS,
559 sizeof(tabs) / sizeof(*tabs),
565 switch (LOWORD(wParam)) {
571 case 101: /* add key */
572 if (HIWORD(wParam) == BN_CLICKED ||
573 HIWORD(wParam) == BN_DOUBLECLICKED) {
574 if (passphrase_box) {
575 MessageBeep(MB_ICONERROR);
576 SetForegroundWindow(passphrase_box);
579 prompt_add_keyfile();
582 case 102: /* remove key */
583 if (HIWORD(wParam) == BN_CLICKED ||
584 HIWORD(wParam) == BN_DOUBLECLICKED) {
589 /* our counter within the array of selected items */
592 /* get the number of items selected in the list */
594 SendDlgItemMessage(hwnd, 100, LB_GETSELCOUNT, 0, 0);
596 /* none selected? that was silly */
597 if (numSelected == 0) {
602 /* get item indices in an array */
603 selectedArray = snewn(numSelected, int);
604 SendDlgItemMessage(hwnd, 100, LB_GETSELITEMS,
605 numSelected, (WPARAM)selectedArray);
607 itemNum = numSelected - 1;
608 rCount = pageant_count_ssh1_keys();
609 sCount = pageant_count_ssh2_keys();
611 /* go through the non-rsakeys until we've covered them all,
612 * and/or we're out of selected items to check. note that
613 * we go *backwards*, to avoid complications from deleting
614 * things hence altering the offset of subsequent items
616 for (i = sCount - 1; (itemNum >= 0) && (i >= 0); i--) {
617 skey = pageant_nth_ssh2_key(i);
619 if (selectedArray[itemNum] == rCount + i) {
620 pageant_delete_ssh2_key(skey);
621 skey->alg->freekey(skey->data);
627 /* do the same for the rsa keys */
628 for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) {
629 rkey = pageant_nth_ssh1_key(i);
631 if(selectedArray[itemNum] == i) {
632 pageant_delete_ssh1_key(rkey);
639 sfree(selectedArray);
644 if (HIWORD(wParam) == BN_CLICKED ||
645 HIWORD(wParam) == BN_DOUBLECLICKED) {
646 launch_help(hwnd, WINHELP_CTX_pageant_general);
653 int id = ((LPHELPINFO)lParam)->iCtrlId;
654 const char *topic = NULL;
656 case 100: topic = WINHELP_CTX_pageant_keylist; break;
657 case 101: topic = WINHELP_CTX_pageant_addkey; break;
658 case 102: topic = WINHELP_CTX_pageant_remkey; break;
661 launch_help(hwnd, topic);
675 /* Set up a system tray icon */
676 static BOOL AddTrayIcon(HWND hwnd)
682 #ifdef NIM_SETVERSION
684 res = Shell_NotifyIcon(NIM_SETVERSION, &tnid);
687 tnid.cbSize = sizeof(NOTIFYICONDATA);
689 tnid.uID = 1; /* unique within this systray use */
690 tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
691 tnid.uCallbackMessage = WM_SYSTRAY;
692 tnid.hIcon = hicon = LoadIcon(hinst, MAKEINTRESOURCE(201));
693 strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
695 res = Shell_NotifyIcon(NIM_ADD, &tnid);
697 if (hicon) DestroyIcon(hicon);
702 /* Update the saved-sessions menu. */
703 static void update_sessions(void)
707 TCHAR buf[MAX_PATH + 1];
710 int index_key, index_menu;
715 if(ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, PUTTY_REGKEY, &hkey))
718 for(num_entries = GetMenuItemCount(session_menu);
719 num_entries > initial_menuitems_count;
721 RemoveMenu(session_menu, 0, MF_BYPOSITION);
726 while(ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) {
727 TCHAR session_name[MAX_PATH + 1];
728 unmungestr(buf, session_name, MAX_PATH);
729 if(strcmp(buf, PUTTY_DEFAULT) != 0) {
730 memset(&mii, 0, sizeof(mii));
731 mii.cbSize = sizeof(mii);
732 mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
733 mii.fType = MFT_STRING;
734 mii.fState = MFS_ENABLED;
735 mii.wID = (index_menu * 16) + IDM_SESSIONS_BASE;
736 mii.dwTypeData = session_name;
737 InsertMenuItem(session_menu, index_menu, TRUE, &mii);
745 if(index_menu == 0) {
746 mii.cbSize = sizeof(mii);
747 mii.fMask = MIIM_TYPE | MIIM_STATE;
748 mii.fType = MFT_STRING;
749 mii.fState = MFS_GRAYED;
750 mii.dwTypeData = _T("(No sessions)");
751 InsertMenuItem(session_menu, index_menu, TRUE, &mii);
757 * Versions of Pageant prior to 0.61 expected this SID on incoming
758 * communications. For backwards compatibility, and more particularly
759 * for compatibility with derived works of PuTTY still using the old
760 * Pageant client code, we accept it as an alternative to the one
761 * returned from get_user_sid() in winpgntc.c.
763 PSID get_default_sid(void)
767 PSECURITY_DESCRIPTOR psd = NULL;
768 PSID sid = NULL, copy = NULL, ret = NULL;
770 if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE,
771 GetCurrentProcessId())) == NULL)
774 if (p_GetSecurityInfo(proc, SE_KERNEL_OBJECT, OWNER_SECURITY_INFORMATION,
775 &sid, NULL, NULL, NULL, &psd) != ERROR_SUCCESS)
778 sidlen = GetLengthSid(sid);
780 copy = (PSID)smalloc(sidlen);
782 if (!CopySid(sidlen, copy, sid))
785 /* Success. Move sid into the return value slot, and null it out
786 * to stop the cleanup code freeing it. */
802 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
803 WPARAM wParam, LPARAM lParam)
805 static int menuinprogress;
806 static UINT msgTaskbarCreated = 0;
810 msgTaskbarCreated = RegisterWindowMessage(_T("TaskbarCreated"));
813 if (message==msgTaskbarCreated) {
815 * Explorer has been restarted, so the tray icon will
823 if (lParam == WM_RBUTTONUP) {
825 GetCursorPos(&cursorpos);
826 PostMessage(hwnd, WM_SYSTRAY2, cursorpos.x, cursorpos.y);
827 } else if (lParam == WM_LBUTTONDBLCLK) {
828 /* Run the default menu item. */
829 UINT menuitem = GetMenuDefaultItem(systray_menu, FALSE, 0);
831 PostMessage(hwnd, WM_COMMAND, menuitem, 0);
835 if (!menuinprogress) {
838 SetForegroundWindow(hwnd);
839 TrackPopupMenu(systray_menu,
840 TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
842 wParam, lParam, 0, hwnd, NULL);
848 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
850 if((INT_PTR)ShellExecute(hwnd, NULL, putty_path, _T(""), _T(""),
852 MessageBox(NULL, "Unable to execute PuTTY!",
853 "Error", MB_OK | MB_ICONERROR);
858 SendMessage(passphrase_box, WM_CLOSE, 0, 0);
859 SendMessage(hwnd, WM_CLOSE, 0, 0);
863 keylist = CreateDialog(hinst, MAKEINTRESOURCE(211),
865 ShowWindow(keylist, SW_SHOWNORMAL);
868 * Sometimes the window comes up minimised / hidden for
869 * no obvious reason. Prevent this. This also brings it
870 * to the front if it's already present (the user
871 * selected View Keys because they wanted to _see_ the
874 SetForegroundWindow(keylist);
875 SetWindowPos(keylist, HWND_TOP, 0, 0, 0, 0,
876 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
879 if (passphrase_box) {
880 MessageBeep(MB_ICONERROR);
881 SetForegroundWindow(passphrase_box);
884 prompt_add_keyfile();
888 aboutbox = CreateDialog(hinst, MAKEINTRESOURCE(213),
890 ShowWindow(aboutbox, SW_SHOWNORMAL);
892 * Sometimes the window comes up minimised / hidden
893 * for no obvious reason. Prevent this.
895 SetForegroundWindow(aboutbox);
896 SetWindowPos(aboutbox, HWND_TOP, 0, 0, 0, 0,
897 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
901 launch_help(hwnd, WINHELP_CTX_pageant_general);
905 if(wParam >= IDM_SESSIONS_BASE && wParam <= IDM_SESSIONS_MAX) {
907 TCHAR buf[MAX_PATH + 1];
908 TCHAR param[MAX_PATH + 1];
909 memset(&mii, 0, sizeof(mii));
910 mii.cbSize = sizeof(mii);
911 mii.fMask = MIIM_TYPE;
913 mii.dwTypeData = buf;
914 GetMenuItemInfo(session_menu, wParam, FALSE, &mii);
916 strcat(param, mii.dwTypeData);
917 if((INT_PTR)ShellExecute(hwnd, NULL, putty_path, param,
918 _T(""), SW_SHOW) <= 32) {
919 MessageBox(NULL, "Unable to execute PuTTY!", "Error",
920 MB_OK | MB_ICONERROR);
938 PSID mapowner, ourself, ourself2;
940 PSECURITY_DESCRIPTOR psd = NULL;
943 cds = (COPYDATASTRUCT *) lParam;
944 if (cds->dwData != AGENT_COPYDATA_ID)
945 return 0; /* not our message, mate */
946 mapname = (char *) cds->lpData;
947 if (mapname[cds->cbData - 1] != '\0')
948 return 0; /* failure to be ASCIZ! */
950 debug(("mapname is :%s:\n", mapname));
952 filemap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, mapname);
954 debug(("filemap is %p\n", filemap));
956 if (filemap != NULL && filemap != INVALID_HANDLE_VALUE) {
960 if ((ourself = get_user_sid()) == NULL) {
962 debug(("couldn't get user SID\n"));
964 CloseHandle(filemap);
968 if ((ourself2 = get_default_sid()) == NULL) {
970 debug(("couldn't get default SID\n"));
972 CloseHandle(filemap);
976 if ((rc = p_GetSecurityInfo(filemap, SE_KERNEL_OBJECT,
977 OWNER_SECURITY_INFORMATION,
978 &mapowner, NULL, NULL, NULL,
979 &psd) != ERROR_SUCCESS)) {
981 debug(("couldn't get owner info for filemap: %d\n",
984 CloseHandle(filemap);
990 LPTSTR ours, ours2, theirs;
991 ConvertSidToStringSid(mapowner, &theirs);
992 ConvertSidToStringSid(ourself, &ours);
993 ConvertSidToStringSid(ourself2, &ours2);
994 debug(("got sids:\n oursnew=%s\n oursold=%s\n"
995 " theirs=%s\n", ours, ours2, theirs));
1001 if (!EqualSid(mapowner, ourself) &&
1002 !EqualSid(mapowner, ourself2)) {
1003 CloseHandle(filemap);
1006 return 0; /* security ID mismatch! */
1009 debug(("security stuff matched\n"));
1015 debug(("security APIs not present\n"));
1019 p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
1021 debug(("p is %p\n", p));
1024 for (i = 0; i < 5; i++)
1025 debug(("p[%d]=%02x\n", i,
1026 ((unsigned char *) p)[i]));
1033 CloseHandle(filemap);
1038 return DefWindowProc(hwnd, message, wParam, lParam);
1042 * Fork and Exec the command in cmdline. [DBW]
1044 void spawn_cmd(const char *cmdline, const char *args, int show)
1046 if (ShellExecute(NULL, _T("open"), cmdline,
1047 args, NULL, show) <= (HINSTANCE) 32) {
1049 msg = dupprintf("Failed to run \"%.100s\", Error: %d", cmdline,
1050 (int)GetLastError());
1051 MessageBox(NULL, msg, APPNAME, MB_OK | MB_ICONEXCLAMATION);
1057 * This is a can't-happen stub, since Pageant never makes
1058 * asynchronous agent requests.
1060 void agent_schedule_callback(void (*callback)(void *, void *, int),
1061 void *callback_ctx, void *data, int len)
1063 assert(!"We shouldn't get here");
1066 void cleanup_exit(int code)
1072 int flags = FLAG_SYNCAGENT;
1074 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
1078 const char *command = NULL;
1081 char **argv, **argstart;
1083 dll_hijacking_protection();
1089 * Determine whether we're an NT system (should have security
1090 * APIs) or a non-NT system (don't do security).
1094 modalfatalbox("Windows refuses to report a version");
1096 if (osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT) {
1097 has_security = TRUE;
1099 has_security = FALSE;
1104 * Attempt to get the security API we need.
1106 if (!got_advapi()) {
1108 "Unable to access security APIs. Pageant will\n"
1109 "not run, in case it causes a security breach.",
1110 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
1115 "This program has been compiled for Win9X and will\n"
1116 "not run on NT, in case it causes a security breach.",
1117 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
1123 * See if we can find our Help file.
1128 * Look for the PuTTY binary (we will enable the saved session
1129 * submenu if we find it).
1132 char b[2048], *p, *q, *r;
1134 GetModuleFileName(NULL, b, sizeof(b) - 16);
1136 p = strrchr(b, '\\');
1137 if (p && p >= r) r = p+1;
1138 q = strrchr(b, ':');
1139 if (q && q >= r) r = q+1;
1140 strcpy(r, "putty.exe");
1141 if ( (fp = fopen(b, "r")) != NULL) {
1142 putty_path = dupstr(b);
1149 * Find out if Pageant is already running.
1151 already_running = agent_exists();
1154 * Initialise the cross-platform Pageant code.
1156 if (!already_running) {
1161 * Process the command line and add keys as listed on it.
1163 split_into_argv(cmdline, &argc, &argv, &argstart);
1164 for (i = 0; i < argc; i++) {
1165 if (!strcmp(argv[i], "-pgpfp")) {
1168 } else if (!strcmp(argv[i], "-restrict-acl") ||
1169 !strcmp(argv[i], "-restrict_acl") ||
1170 !strcmp(argv[i], "-restrictacl")) {
1171 restrict_process_acl();
1172 } else if (!strcmp(argv[i], "-c")) {
1174 * If we see `-c', then the rest of the
1175 * command line should be treated as a
1176 * command to be spawned.
1179 command = argstart[i+1];
1184 Filename *fn = filename_from_str(argv[i]);
1185 win_add_keyfile(fn);
1192 * Forget any passphrase that we retained while going over
1193 * command line keyfiles.
1195 pageant_forget_passphrases();
1199 if (command[0] == '"')
1200 args = strchr(++command, '"');
1202 args = strchr(command, ' ');
1205 while(*args && isspace(*args)) args++;
1207 spawn_cmd(command, args, show);
1211 * If Pageant was already running, we leave now. If we haven't
1212 * even taken any auxiliary action (spawned a command or added
1215 if (already_running) {
1216 if (!command && !added_keys) {
1217 MessageBox(NULL, "Pageant is already running", "Pageant Error",
1218 MB_ICONERROR | MB_OK);
1225 wndclass.lpfnWndProc = WndProc;
1226 wndclass.cbClsExtra = 0;
1227 wndclass.cbWndExtra = 0;
1228 wndclass.hInstance = inst;
1229 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
1230 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
1231 wndclass.hbrBackground = GetStockObject(BLACK_BRUSH);
1232 wndclass.lpszMenuName = NULL;
1233 wndclass.lpszClassName = APPNAME;
1235 RegisterClass(&wndclass);
1240 hwnd = CreateWindow(APPNAME, APPNAME,
1241 WS_OVERLAPPEDWINDOW | WS_VSCROLL,
1242 CW_USEDEFAULT, CW_USEDEFAULT,
1243 100, 100, NULL, NULL, inst, NULL);
1245 /* Set up a system tray icon */
1248 /* Accelerators used: nsvkxa */
1249 systray_menu = CreatePopupMenu();
1251 session_menu = CreateMenu();
1252 AppendMenu(systray_menu, MF_ENABLED, IDM_PUTTY, "&New Session");
1253 AppendMenu(systray_menu, MF_POPUP | MF_ENABLED,
1254 (UINT_PTR) session_menu, "&Saved Sessions");
1255 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
1257 AppendMenu(systray_menu, MF_ENABLED, IDM_VIEWKEYS,
1259 AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key");
1260 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
1262 AppendMenu(systray_menu, MF_ENABLED, IDM_HELP, "&Help");
1263 AppendMenu(systray_menu, MF_ENABLED, IDM_ABOUT, "&About");
1264 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
1265 AppendMenu(systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit");
1266 initial_menuitems_count = GetMenuItemCount(session_menu);
1268 /* Set the default menu item. */
1269 SetMenuDefaultItem(systray_menu, IDM_VIEWKEYS, FALSE);
1271 ShowWindow(hwnd, SW_HIDE);
1274 * Main message loop.
1276 while (GetMessage(&msg, NULL, 0, 0) == 1) {
1277 if (!(IsWindow(keylist) && IsDialogMessage(keylist, &msg)) &&
1278 !(IsWindow(aboutbox) && IsDialogMessage(aboutbox, &msg))) {
1279 TranslateMessage(&msg);
1280 DispatchMessage(&msg);
1284 /* Clean up the system tray icon */
1286 NOTIFYICONDATA tnid;
1288 tnid.cbSize = sizeof(NOTIFYICONDATA);
1292 Shell_NotifyIcon(NIM_DELETE, &tnid);
1294 DestroyMenu(systray_menu);
1297 if (keypath) filereq_free(keypath);
1299 cleanup_exit(msg.wParam);
1300 return msg.wParam; /* just in case optimiser complains */