2 * Pageant: the PuTTY Authentication Agent.
11 #define PUTTY_DO_GLOBALS
24 #define _WIN32_WINNT 0x0500 /* for ConvertSidToStringSid */
29 #define IDI_MAINICON 200
30 #define IDI_TRAYICON 201
32 #define WM_SYSTRAY (WM_APP + 6)
33 #define WM_SYSTRAY2 (WM_APP + 7)
35 #define AGENT_COPYDATA_ID 0x804e50ba /* random goop */
38 * FIXME: maybe some day we can sort this out ...
40 #define AGENT_MAX_MSGLEN 8192
42 /* From MSDN: In the WM_SYSCOMMAND message, the four low-order bits of
43 * wParam are used by Windows, and should be masked off, so we shouldn't
44 * attempt to store information in them. Hence all these identifiers have
45 * the low 4 bits clear. Also, identifiers should < 0xF000. */
47 #define IDM_CLOSE 0x0010
48 #define IDM_VIEWKEYS 0x0020
49 #define IDM_ADDKEY 0x0030
50 #define IDM_HELP 0x0040
51 #define IDM_ABOUT 0x0050
53 #define APPNAME "Pageant"
59 static HMENU systray_menu, session_menu;
60 static int already_running;
62 static char *putty_path;
64 /* CWD for "add key" file requester. */
65 static filereq *keypath = NULL;
67 #define IDM_PUTTY 0x0060
68 #define IDM_SESSIONS_BASE 0x1000
69 #define IDM_SESSIONS_MAX 0x2000
70 #define PUTTY_REGKEY "Software\\SimonTatham\\PuTTY\\Sessions"
71 #define PUTTY_DEFAULT "Default%20Settings"
72 static int initial_menuitems_count;
75 * Print a modal (Really Bad) message box and perform a fatal exit.
77 void modalfatalbox(char *fmt, ...)
83 buf = dupvprintf(fmt, ap);
85 MessageBox(hwnd, buf, "Pageant Fatal Error",
86 MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
91 /* Un-munge session names out of the registry. */
92 static void unmungestr(char *in, char *out, int outlen)
95 if (*in == '%' && in[1] && in[2]) {
101 j -= (j > 9 ? 7 : 0);
103 *out++ = (i << 4) + j;
117 static tree234 *rsakeys, *ssh2keys;
119 static int has_security;
121 DECL_WINDOWS_FUNCTION(extern, DWORD, GetSecurityInfo,
122 (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION,
123 PSID *, PSID *, PACL *, PACL *,
124 PSECURITY_DESCRIPTOR *));
130 static void *make_keylist1(int *length);
131 static void *make_keylist2(int *length);
132 static void *get_keylist1(int *length);
133 static void *get_keylist2(int *length);
136 * We need this to link with the RSA code, because rsaencrypt()
137 * pads its data with random bytes. Since we only use rsadecrypt()
138 * and the signing functions, which are deterministic, this should
141 * If it _is_ called, there is a _serious_ problem, because it
142 * won't generate true random numbers. So we must scream, panic,
143 * and exit immediately if that should happen.
145 int random_byte(void)
147 MessageBox(hwnd, "Internal Error", APPNAME, MB_OK | MB_ICONERROR);
149 /* this line can't be reached but it placates MSVC's warnings :-) */
154 * Blob structure for passing to the asymmetric SSH-2 key compare
155 * function, prototyped here.
161 static int cmpkeys_ssh2_asymm(void *av, void *bv);
163 struct PassphraseProcStruct {
168 static tree234 *passphrases = NULL;
171 * After processing a list of filenames, we want to forget the
174 static void forget_passphrases(void)
176 while (count234(passphrases) > 0) {
177 char *pp = index234(passphrases, 0);
178 smemclr(pp, strlen(pp));
179 delpos234(passphrases, 0);
185 * Dialog-box function for the Licence box.
187 static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
188 WPARAM wParam, LPARAM lParam)
194 switch (LOWORD(wParam)) {
209 * Dialog-box function for the About box.
211 static int CALLBACK AboutProc(HWND hwnd, UINT msg,
212 WPARAM wParam, LPARAM lParam)
216 SetDlgItemText(hwnd, 100, ver);
219 switch (LOWORD(wParam)) {
226 EnableWindow(hwnd, 0);
227 DialogBox(hinst, MAKEINTRESOURCE(214), hwnd, LicenceProc);
228 EnableWindow(hwnd, 1);
229 SetActiveWindow(hwnd);
241 static HWND passphrase_box;
244 * Dialog-box function for the passphrase box.
246 static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
247 WPARAM wParam, LPARAM lParam)
249 static char **passphrase = NULL;
250 struct PassphraseProcStruct *p;
254 passphrase_box = hwnd;
258 { /* centre the window */
262 hw = GetDesktopWindow();
263 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
265 (rs.right + rs.left + rd.left - rd.right) / 2,
266 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
267 rd.right - rd.left, rd.bottom - rd.top, TRUE);
270 SetForegroundWindow(hwnd);
271 SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
272 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
273 p = (struct PassphraseProcStruct *) lParam;
274 passphrase = p->passphrase;
276 SetDlgItemText(hwnd, 101, p->comment);
277 burnstr(*passphrase);
278 *passphrase = dupstr("");
279 SetDlgItemText(hwnd, 102, *passphrase);
282 switch (LOWORD(wParam)) {
292 case 102: /* edit box */
293 if ((HIWORD(wParam) == EN_CHANGE) && passphrase) {
294 burnstr(*passphrase);
295 *passphrase = GetDlgItemText_alloc(hwnd, 102);
308 * Warn about the obsolescent key file format.
310 void old_keyfile_warning(void)
312 static const char mbtitle[] = "PuTTY Key File Warning";
313 static const char message[] =
314 "You are loading an SSH-2 private key which has an\n"
315 "old version of the file format. This means your key\n"
316 "file is not fully tamperproof. Future versions of\n"
317 "PuTTY may stop supporting this private key format,\n"
318 "so we recommend you convert your key to the new\n"
321 "You can perform this conversion by loading the key\n"
322 "into PuTTYgen and then saving it again.";
324 MessageBox(NULL, message, mbtitle, MB_OK);
328 * Update the visible key list.
330 static void keylist_update(void)
333 struct ssh2_userkey *skey;
337 SendDlgItemMessage(keylist, 100, LB_RESETCONTENT, 0, 0);
338 for (i = 0; NULL != (rkey = index234(rsakeys, i)); i++) {
339 char listentry[512], *p;
341 * Replace two spaces in the fingerprint with tabs, for
342 * nice alignment in the box.
344 strcpy(listentry, "ssh1\t");
345 p = listentry + strlen(listentry);
346 rsa_fingerprint(p, sizeof(listentry) - (p - listentry), rkey);
347 p = strchr(listentry, ' ');
350 p = strchr(listentry, ' ');
353 SendDlgItemMessage(keylist, 100, LB_ADDSTRING,
354 0, (LPARAM) listentry);
356 for (i = 0; NULL != (skey = index234(ssh2keys, i)); i++) {
360 * Replace two spaces in the fingerprint with tabs, for
361 * nice alignment in the box.
363 p = skey->alg->fingerprint(skey->data);
364 listentry = dupprintf("%s\t%s", p, skey->comment);
365 fp_len = strlen(listentry);
368 p = strchr(listentry, ' ');
369 if (p && p < listentry + fp_len)
371 p = strchr(listentry, ' ');
372 if (p && p < listentry + fp_len)
375 SendDlgItemMessage(keylist, 100, LB_ADDSTRING, 0,
379 SendDlgItemMessage(keylist, 100, LB_SETCURSEL, (WPARAM) - 1, 0);
384 * This function loads a key from a file and adds it.
386 static void add_keyfile(Filename *filename)
389 struct RSAKey *rkey = NULL;
390 struct ssh2_userkey *skey = NULL;
395 const char *error = NULL;
399 type = key_type(filename);
400 if (type != SSH_KEYTYPE_SSH1 && type != SSH_KEYTYPE_SSH2) {
401 char *msg = dupprintf("Couldn't load this key (%s)",
402 key_type_to_str(type));
403 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
404 HELPCTXID(errors_cantloadkey));
410 * See if the key is already loaded (in the primary Pageant,
411 * which may or may not be us).
415 unsigned char *keylist, *p;
416 int i, nkeys, bloblen, keylistlen;
418 if (type == SSH_KEYTYPE_SSH1) {
419 if (!rsakey_pubblob(filename, &blob, &bloblen, NULL, &error)) {
420 char *msg = dupprintf("Couldn't load private key (%s)", error);
421 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
422 HELPCTXID(errors_cantloadkey));
426 keylist = get_keylist1(&keylistlen);
428 unsigned char *blob2;
429 blob = ssh2_userkey_loadpub(filename, NULL, &bloblen,
432 char *msg = dupprintf("Couldn't load private key (%s)", error);
433 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
434 HELPCTXID(errors_cantloadkey));
438 /* For our purposes we want the blob prefixed with its length */
439 blob2 = snewn(bloblen+4, unsigned char);
440 PUT_32BIT(blob2, bloblen);
441 memcpy(blob2 + 4, blob, bloblen);
445 keylist = get_keylist2(&keylistlen);
448 if (keylistlen < 4) {
449 MessageBox(NULL, "Received broken key list?!", APPNAME,
450 MB_OK | MB_ICONERROR);
453 nkeys = toint(GET_32BIT(keylist));
455 MessageBox(NULL, "Received broken key list?!", APPNAME,
456 MB_OK | MB_ICONERROR);
462 for (i = 0; i < nkeys; i++) {
463 if (!memcmp(blob, p, bloblen)) {
464 /* Key is already present; we can now leave. */
469 /* Now skip over public blob */
470 if (type == SSH_KEYTYPE_SSH1) {
471 int n = rsa_public_blob_len(p, keylistlen);
473 MessageBox(NULL, "Received broken key list?!", APPNAME,
474 MB_OK | MB_ICONERROR);
481 if (keylistlen < 4) {
482 MessageBox(NULL, "Received broken key list?!", APPNAME,
483 MB_OK | MB_ICONERROR);
486 n = toint(4 + GET_32BIT(p));
487 if (n < 0 || keylistlen < n) {
488 MessageBox(NULL, "Received broken key list?!", APPNAME,
489 MB_OK | MB_ICONERROR);
495 /* Now skip over comment field */
498 if (keylistlen < 4) {
499 MessageBox(NULL, "Received broken key list?!", APPNAME,
500 MB_OK | MB_ICONERROR);
503 n = toint(4 + GET_32BIT(p));
504 if (n < 0 || keylistlen < n) {
505 MessageBox(NULL, "Received broken key list?!", APPNAME,
506 MB_OK | MB_ICONERROR);
521 if (type == SSH_KEYTYPE_SSH1)
522 needs_pass = rsakey_encrypted(filename, &comment);
524 needs_pass = ssh2_userkey_encrypted(filename, &comment);
526 if (type == SSH_KEYTYPE_SSH1)
527 rkey = snew(struct RSAKey);
535 /* try all the remembered passphrases first */
536 char *pp = index234(passphrases, attempts);
538 passphrase = dupstr(pp);
541 struct PassphraseProcStruct pps;
543 pps.passphrase = &passphrase;
544 pps.comment = comment;
547 dlgret = DialogBoxParam(hinst, MAKEINTRESOURCE(210),
548 NULL, PassphraseProc, (LPARAM) &pps);
549 passphrase_box = NULL;
553 if (type == SSH_KEYTYPE_SSH1)
555 return; /* operation cancelled */
558 assert(passphrase != NULL);
561 passphrase = dupstr("");
563 if (type == SSH_KEYTYPE_SSH1)
564 ret = loadrsakey(filename, rkey, passphrase, &error);
566 skey = ssh2_load_userkey(filename, passphrase, &error);
567 if (skey == SSH2_WRONG_PASSPHRASE)
577 if(original_pass && ret) {
578 /* If they typed in an ok passphrase, remember it */
579 addpos234(passphrases, passphrase, 0);
581 /* Otherwise, destroy it */
589 char *msg = dupprintf("Couldn't load private key (%s)", error);
590 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
591 HELPCTXID(errors_cantloadkey));
593 if (type == SSH_KEYTYPE_SSH1)
597 if (type == SSH_KEYTYPE_SSH1) {
598 if (already_running) {
599 unsigned char *request, *response;
601 int reqlen, clen, resplen, ret;
603 clen = strlen(rkey->comment);
605 reqlen = 4 + 1 + /* length, message type */
607 ssh1_bignum_length(rkey->modulus) +
608 ssh1_bignum_length(rkey->exponent) +
609 ssh1_bignum_length(rkey->private_exponent) +
610 ssh1_bignum_length(rkey->iqmp) +
611 ssh1_bignum_length(rkey->p) +
612 ssh1_bignum_length(rkey->q) + 4 + clen /* comment */
615 request = snewn(reqlen, unsigned char);
617 request[4] = SSH1_AGENTC_ADD_RSA_IDENTITY;
619 PUT_32BIT(request + reqlen, bignum_bitcount(rkey->modulus));
621 reqlen += ssh1_write_bignum(request + reqlen, rkey->modulus);
622 reqlen += ssh1_write_bignum(request + reqlen, rkey->exponent);
624 ssh1_write_bignum(request + reqlen,
625 rkey->private_exponent);
626 reqlen += ssh1_write_bignum(request + reqlen, rkey->iqmp);
627 reqlen += ssh1_write_bignum(request + reqlen, rkey->p);
628 reqlen += ssh1_write_bignum(request + reqlen, rkey->q);
629 PUT_32BIT(request + reqlen, clen);
630 memcpy(request + reqlen + 4, rkey->comment, clen);
632 PUT_32BIT(request, reqlen - 4);
634 ret = agent_query(request, reqlen, &vresponse, &resplen,
637 response = vresponse;
638 if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
639 MessageBox(NULL, "The already running Pageant "
640 "refused to add the key.", APPNAME,
641 MB_OK | MB_ICONERROR);
646 if (add234(rsakeys, rkey) != rkey)
647 sfree(rkey); /* already present, don't waste RAM */
650 if (already_running) {
651 unsigned char *request, *response;
653 int reqlen, alglen, clen, keybloblen, resplen, ret;
654 alglen = strlen(skey->alg->name);
655 clen = strlen(skey->comment);
657 keybloblen = skey->alg->openssh_fmtkey(skey->data, NULL, 0);
659 reqlen = 4 + 1 + /* length, message type */
660 4 + alglen + /* algorithm name */
661 keybloblen + /* key data */
662 4 + clen /* comment */
665 request = snewn(reqlen, unsigned char);
667 request[4] = SSH2_AGENTC_ADD_IDENTITY;
669 PUT_32BIT(request + reqlen, alglen);
671 memcpy(request + reqlen, skey->alg->name, alglen);
673 reqlen += skey->alg->openssh_fmtkey(skey->data,
676 PUT_32BIT(request + reqlen, clen);
677 memcpy(request + reqlen + 4, skey->comment, clen);
679 PUT_32BIT(request, reqlen - 4);
681 ret = agent_query(request, reqlen, &vresponse, &resplen,
684 response = vresponse;
685 if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
686 MessageBox(NULL, "The already running Pageant "
687 "refused to add the key.", APPNAME,
688 MB_OK | MB_ICONERROR);
693 if (add234(ssh2keys, skey) != skey) {
694 skey->alg->freekey(skey->data);
695 sfree(skey); /* already present, don't waste RAM */
702 * Create an SSH-1 key list in a malloc'ed buffer; return its
705 static void *make_keylist1(int *length)
709 unsigned char *blob, *p, *ret;
713 * Count up the number and length of keys we hold.
717 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
719 blob = rsa_public_blob(key, &bloblen);
722 len += 4 + strlen(key->comment);
725 /* Allocate the buffer. */
726 p = ret = snewn(len, unsigned char);
727 if (length) *length = len;
731 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
732 blob = rsa_public_blob(key, &bloblen);
733 memcpy(p, blob, bloblen);
736 PUT_32BIT(p, strlen(key->comment));
737 memcpy(p + 4, key->comment, strlen(key->comment));
738 p += 4 + strlen(key->comment);
741 assert(p - ret == len);
746 * Create an SSH-2 key list in a malloc'ed buffer; return its
749 static void *make_keylist2(int *length)
751 struct ssh2_userkey *key;
753 unsigned char *blob, *p, *ret;
757 * Count up the number and length of keys we hold.
761 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
763 len += 4; /* length field */
764 blob = key->alg->public_blob(key->data, &bloblen);
767 len += 4 + strlen(key->comment);
770 /* Allocate the buffer. */
771 p = ret = snewn(len, unsigned char);
772 if (length) *length = len;
775 * Packet header is the obvious five bytes, plus four
776 * bytes for the key count.
780 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
781 blob = key->alg->public_blob(key->data, &bloblen);
782 PUT_32BIT(p, bloblen);
784 memcpy(p, blob, bloblen);
787 PUT_32BIT(p, strlen(key->comment));
788 memcpy(p + 4, key->comment, strlen(key->comment));
789 p += 4 + strlen(key->comment);
792 assert(p - ret == len);
797 * Acquire a keylist1 from the primary Pageant; this means either
798 * calling make_keylist1 (if that's us) or sending a message to the
799 * primary Pageant (if it's not).
801 static void *get_keylist1(int *length)
805 if (already_running) {
806 unsigned char request[5], *response;
809 request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
810 PUT_32BIT(request, 4);
812 retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
814 response = vresponse;
815 if (resplen < 5 || response[4] != SSH1_AGENT_RSA_IDENTITIES_ANSWER) {
820 ret = snewn(resplen-5, unsigned char);
821 memcpy(ret, response+5, resplen-5);
827 ret = make_keylist1(length);
833 * Acquire a keylist2 from the primary Pageant; this means either
834 * calling make_keylist2 (if that's us) or sending a message to the
835 * primary Pageant (if it's not).
837 static void *get_keylist2(int *length)
841 if (already_running) {
842 unsigned char request[5], *response;
846 request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
847 PUT_32BIT(request, 4);
849 retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
851 response = vresponse;
852 if (resplen < 5 || response[4] != SSH2_AGENT_IDENTITIES_ANSWER) {
857 ret = snewn(resplen-5, unsigned char);
858 memcpy(ret, response+5, resplen-5);
864 ret = make_keylist2(length);
870 * This is the main agent function that answers messages.
872 static void answer_msg(void *msg)
874 unsigned char *p = msg;
875 unsigned char *ret = msg;
876 unsigned char *msgend;
880 * Get the message length.
882 msgend = p + 4 + GET_32BIT(p);
885 * Get the message type.
893 case SSH1_AGENTC_REQUEST_RSA_IDENTITIES:
895 * Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER.
901 ret[4] = SSH1_AGENT_RSA_IDENTITIES_ANSWER;
902 keylist = make_keylist1(&len);
903 if (len + 5 > AGENT_MAX_MSGLEN) {
907 PUT_32BIT(ret, len + 1);
908 memcpy(ret + 5, keylist, len);
912 case SSH2_AGENTC_REQUEST_IDENTITIES:
914 * Reply with SSH2_AGENT_IDENTITIES_ANSWER.
920 ret[4] = SSH2_AGENT_IDENTITIES_ANSWER;
921 keylist = make_keylist2(&len);
922 if (len + 5 > AGENT_MAX_MSGLEN) {
926 PUT_32BIT(ret, len + 1);
927 memcpy(ret + 5, keylist, len);
931 case SSH1_AGENTC_RSA_CHALLENGE:
933 * Reply with either SSH1_AGENT_RSA_RESPONSE or
934 * SSH_AGENT_FAILURE, depending on whether we have that key
938 struct RSAKey reqkey, *key;
939 Bignum challenge, response;
940 unsigned char response_source[48], response_md5[16];
941 struct MD5Context md5c;
945 i = ssh1_read_bignum(p, msgend - p, &reqkey.exponent);
949 i = ssh1_read_bignum(p, msgend - p, &reqkey.modulus);
951 freebn(reqkey.exponent);
955 i = ssh1_read_bignum(p, msgend - p, &challenge);
957 freebn(reqkey.exponent);
958 freebn(reqkey.modulus);
963 freebn(reqkey.exponent);
964 freebn(reqkey.modulus);
968 memcpy(response_source + 32, p, 16);
972 (key = find234(rsakeys, &reqkey, NULL)) == NULL) {
973 freebn(reqkey.exponent);
974 freebn(reqkey.modulus);
978 response = rsadecrypt(challenge, key);
979 for (i = 0; i < 32; i++)
980 response_source[i] = bignum_byte(response, 31 - i);
983 MD5Update(&md5c, response_source, 48);
984 MD5Final(response_md5, &md5c);
985 smemclr(response_source, 48); /* burn the evidence */
986 freebn(response); /* and that evidence */
987 freebn(challenge); /* yes, and that evidence */
988 freebn(reqkey.exponent); /* and free some memory ... */
989 freebn(reqkey.modulus); /* ... while we're at it. */
992 * Packet is the obvious five byte header, plus sixteen
996 PUT_32BIT(ret, len - 4);
997 ret[4] = SSH1_AGENT_RSA_RESPONSE;
998 memcpy(ret + 5, response_md5, 16);
1001 case SSH2_AGENTC_SIGN_REQUEST:
1003 * Reply with either SSH2_AGENT_SIGN_RESPONSE or
1004 * SSH_AGENT_FAILURE, depending on whether we have that key
1008 struct ssh2_userkey *key;
1010 unsigned char *data, *signature;
1011 int datalen, siglen, len;
1015 b.len = toint(GET_32BIT(p));
1016 if (b.len < 0 || b.len > msgend - (p+4))
1023 datalen = toint(GET_32BIT(p));
1025 if (datalen < 0 || datalen > msgend - p)
1028 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
1031 signature = key->alg->sign(key->data, data, datalen, &siglen);
1032 len = 5 + 4 + siglen;
1033 PUT_32BIT(ret, len - 4);
1034 ret[4] = SSH2_AGENT_SIGN_RESPONSE;
1035 PUT_32BIT(ret + 5, siglen);
1036 memcpy(ret + 5 + 4, signature, siglen);
1040 case SSH1_AGENTC_ADD_RSA_IDENTITY:
1042 * Add to the list and return SSH_AGENT_SUCCESS, or
1043 * SSH_AGENT_FAILURE if the key was malformed.
1050 key = snew(struct RSAKey);
1051 memset(key, 0, sizeof(struct RSAKey));
1053 n = makekey(p, msgend - p, key, NULL, 1);
1061 n = makeprivate(p, msgend - p, key);
1069 n = ssh1_read_bignum(p, msgend - p, &key->iqmp); /* p^-1 mod q */
1077 n = ssh1_read_bignum(p, msgend - p, &key->p); /* p */
1085 n = ssh1_read_bignum(p, msgend - p, &key->q); /* q */
1098 commentlen = toint(GET_32BIT(p));
1100 if (commentlen < 0 || commentlen > msgend - p) {
1106 comment = snewn(commentlen+1, char);
1108 memcpy(comment, p + 4, commentlen);
1109 comment[commentlen] = '\0';
1110 key->comment = comment;
1113 ret[4] = SSH_AGENT_FAILURE;
1114 if (add234(rsakeys, key) == key) {
1116 ret[4] = SSH_AGENT_SUCCESS;
1123 case SSH2_AGENTC_ADD_IDENTITY:
1125 * Add to the list and return SSH_AGENT_SUCCESS, or
1126 * SSH_AGENT_FAILURE if the key was malformed.
1129 struct ssh2_userkey *key;
1130 char *comment, *alg;
1131 int alglen, commlen;
1137 alglen = toint(GET_32BIT(p));
1139 if (alglen < 0 || alglen > msgend - p)
1144 key = snew(struct ssh2_userkey);
1145 /* Add further algorithm names here. */
1146 if (alglen == 7 && !memcmp(alg, "ssh-rsa", 7))
1147 key->alg = &ssh_rsa;
1148 else if (alglen == 7 && !memcmp(alg, "ssh-dss", 7))
1149 key->alg = &ssh_dss;
1155 bloblen = msgend - p;
1156 key->data = key->alg->openssh_createkey(&p, &bloblen);
1163 * p has been advanced by openssh_createkey, but
1164 * certainly not _beyond_ the end of the buffer.
1166 assert(p <= msgend);
1169 key->alg->freekey(key->data);
1173 commlen = toint(GET_32BIT(p));
1176 if (commlen < 0 || commlen > msgend - p) {
1177 key->alg->freekey(key->data);
1181 comment = snewn(commlen + 1, char);
1183 memcpy(comment, p, commlen);
1184 comment[commlen] = '\0';
1186 key->comment = comment;
1189 ret[4] = SSH_AGENT_FAILURE;
1190 if (add234(ssh2keys, key) == key) {
1192 ret[4] = SSH_AGENT_SUCCESS;
1194 key->alg->freekey(key->data);
1195 sfree(key->comment);
1200 case SSH1_AGENTC_REMOVE_RSA_IDENTITY:
1202 * Remove from the list and return SSH_AGENT_SUCCESS, or
1203 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
1207 struct RSAKey reqkey, *key;
1210 n = makekey(p, msgend - p, &reqkey, NULL, 0);
1214 key = find234(rsakeys, &reqkey, NULL);
1215 freebn(reqkey.exponent);
1216 freebn(reqkey.modulus);
1218 ret[4] = SSH_AGENT_FAILURE;
1220 del234(rsakeys, key);
1224 ret[4] = SSH_AGENT_SUCCESS;
1228 case SSH2_AGENTC_REMOVE_IDENTITY:
1230 * Remove from the list and return SSH_AGENT_SUCCESS, or
1231 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
1235 struct ssh2_userkey *key;
1240 b.len = toint(GET_32BIT(p));
1243 if (b.len < 0 || b.len > msgend - p)
1248 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
1253 ret[4] = SSH_AGENT_FAILURE;
1255 del234(ssh2keys, key);
1257 key->alg->freekey(key->data);
1259 ret[4] = SSH_AGENT_SUCCESS;
1263 case SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES:
1265 * Remove all SSH-1 keys. Always returns success.
1268 struct RSAKey *rkey;
1270 while ((rkey = index234(rsakeys, 0)) != NULL) {
1271 del234(rsakeys, rkey);
1278 ret[4] = SSH_AGENT_SUCCESS;
1281 case SSH2_AGENTC_REMOVE_ALL_IDENTITIES:
1283 * Remove all SSH-2 keys. Always returns success.
1286 struct ssh2_userkey *skey;
1288 while ((skey = index234(ssh2keys, 0)) != NULL) {
1289 del234(ssh2keys, skey);
1290 skey->alg->freekey(skey->data);
1296 ret[4] = SSH_AGENT_SUCCESS;
1302 * Unrecognised message. Return SSH_AGENT_FAILURE.
1305 ret[4] = SSH_AGENT_FAILURE;
1311 * Key comparison function for the 2-3-4 tree of RSA keys.
1313 static int cmpkeys_rsa(void *av, void *bv)
1315 struct RSAKey *a = (struct RSAKey *) av;
1316 struct RSAKey *b = (struct RSAKey *) bv;
1323 * Compare by length of moduli.
1325 alen = bignum_bitcount(am);
1326 blen = bignum_bitcount(bm);
1329 else if (alen < blen)
1332 * Now compare by moduli themselves.
1334 alen = (alen + 7) / 8; /* byte count */
1335 while (alen-- > 0) {
1337 abyte = bignum_byte(am, alen);
1338 bbyte = bignum_byte(bm, alen);
1341 else if (abyte < bbyte)
1351 * Key comparison function for the 2-3-4 tree of SSH-2 keys.
1353 static int cmpkeys_ssh2(void *av, void *bv)
1355 struct ssh2_userkey *a = (struct ssh2_userkey *) av;
1356 struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
1359 unsigned char *ablob, *bblob;
1363 * Compare purely by public blob.
1365 ablob = a->alg->public_blob(a->data, &alen);
1366 bblob = b->alg->public_blob(b->data, &blen);
1369 for (i = 0; i < alen && i < blen; i++) {
1370 if (ablob[i] < bblob[i]) {
1373 } else if (ablob[i] > bblob[i]) {
1378 if (c == 0 && i < alen)
1379 c = +1; /* a is longer */
1380 if (c == 0 && i < blen)
1381 c = -1; /* a is longer */
1390 * Key comparison function for looking up a blob in the 2-3-4 tree
1393 static int cmpkeys_ssh2_asymm(void *av, void *bv)
1395 struct blob *a = (struct blob *) av;
1396 struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
1399 unsigned char *ablob, *bblob;
1403 * Compare purely by public blob.
1407 bblob = b->alg->public_blob(b->data, &blen);
1410 for (i = 0; i < alen && i < blen; i++) {
1411 if (ablob[i] < bblob[i]) {
1414 } else if (ablob[i] > bblob[i]) {
1419 if (c == 0 && i < alen)
1420 c = +1; /* a is longer */
1421 if (c == 0 && i < blen)
1422 c = -1; /* a is longer */
1430 * Prompt for a key file to add, and add it.
1432 static void prompt_add_keyfile(void)
1435 char *filelist = snewn(8192, char);
1437 if (!keypath) keypath = filereq_new();
1438 memset(&of, 0, sizeof(of));
1439 of.hwndOwner = hwnd;
1440 of.lpstrFilter = FILTER_KEY_FILES;
1441 of.lpstrCustomFilter = NULL;
1442 of.nFilterIndex = 1;
1443 of.lpstrFile = filelist;
1446 of.lpstrFileTitle = NULL;
1447 of.lpstrTitle = "Select Private Key File";
1448 of.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER;
1449 if (request_file(keypath, &of, TRUE, FALSE)) {
1450 if(strlen(filelist) > of.nFileOffset) {
1451 /* Only one filename returned? */
1452 Filename *fn = filename_from_str(filelist);
1456 /* we are returned a bunch of strings, end to
1457 * end. first string is the directory, the
1458 * rest the filenames. terminated with an
1461 char *dir = filelist;
1462 char *filewalker = filelist + strlen(dir) + 1;
1463 while (*filewalker != '\0') {
1464 char *filename = dupcat(dir, "\\", filewalker, NULL);
1465 Filename *fn = filename_from_str(filename);
1469 filewalker += strlen(filewalker) + 1;
1474 forget_passphrases();
1480 * Dialog-box function for the key list box.
1482 static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
1483 WPARAM wParam, LPARAM lParam)
1485 struct RSAKey *rkey;
1486 struct ssh2_userkey *skey;
1491 * Centre the window.
1493 { /* centre the window */
1497 hw = GetDesktopWindow();
1498 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
1500 (rs.right + rs.left + rd.left - rd.right) / 2,
1501 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
1502 rd.right - rd.left, rd.bottom - rd.top, TRUE);
1506 SetWindowLongPtr(hwnd, GWL_EXSTYLE,
1507 GetWindowLongPtr(hwnd, GWL_EXSTYLE) |
1510 HWND item = GetDlgItem(hwnd, 103); /* the Help button */
1512 DestroyWindow(item);
1517 static int tabs[] = { 35, 60, 210 };
1518 SendDlgItemMessage(hwnd, 100, LB_SETTABSTOPS,
1519 sizeof(tabs) / sizeof(*tabs),
1525 switch (LOWORD(wParam)) {
1529 DestroyWindow(hwnd);
1531 case 101: /* add key */
1532 if (HIWORD(wParam) == BN_CLICKED ||
1533 HIWORD(wParam) == BN_DOUBLECLICKED) {
1534 if (passphrase_box) {
1535 MessageBeep(MB_ICONERROR);
1536 SetForegroundWindow(passphrase_box);
1539 prompt_add_keyfile();
1542 case 102: /* remove key */
1543 if (HIWORD(wParam) == BN_CLICKED ||
1544 HIWORD(wParam) == BN_DOUBLECLICKED) {
1549 /* our counter within the array of selected items */
1552 /* get the number of items selected in the list */
1554 SendDlgItemMessage(hwnd, 100, LB_GETSELCOUNT, 0, 0);
1556 /* none selected? that was silly */
1557 if (numSelected == 0) {
1562 /* get item indices in an array */
1563 selectedArray = snewn(numSelected, int);
1564 SendDlgItemMessage(hwnd, 100, LB_GETSELITEMS,
1565 numSelected, (WPARAM)selectedArray);
1567 itemNum = numSelected - 1;
1568 rCount = count234(rsakeys);
1569 sCount = count234(ssh2keys);
1571 /* go through the non-rsakeys until we've covered them all,
1572 * and/or we're out of selected items to check. note that
1573 * we go *backwards*, to avoid complications from deleting
1574 * things hence altering the offset of subsequent items
1576 for (i = sCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1577 skey = index234(ssh2keys, i);
1579 if (selectedArray[itemNum] == rCount + i) {
1580 del234(ssh2keys, skey);
1581 skey->alg->freekey(skey->data);
1587 /* do the same for the rsa keys */
1588 for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1589 rkey = index234(rsakeys, i);
1591 if(selectedArray[itemNum] == i) {
1592 del234(rsakeys, rkey);
1599 sfree(selectedArray);
1603 case 103: /* help */
1604 if (HIWORD(wParam) == BN_CLICKED ||
1605 HIWORD(wParam) == BN_DOUBLECLICKED) {
1606 launch_help(hwnd, WINHELP_CTX_pageant_general);
1613 int id = ((LPHELPINFO)lParam)->iCtrlId;
1616 case 100: topic = WINHELP_CTX_pageant_keylist; break;
1617 case 101: topic = WINHELP_CTX_pageant_addkey; break;
1618 case 102: topic = WINHELP_CTX_pageant_remkey; break;
1621 launch_help(hwnd, topic);
1629 DestroyWindow(hwnd);
1635 /* Set up a system tray icon */
1636 static BOOL AddTrayIcon(HWND hwnd)
1639 NOTIFYICONDATA tnid;
1642 #ifdef NIM_SETVERSION
1644 res = Shell_NotifyIcon(NIM_SETVERSION, &tnid);
1647 tnid.cbSize = sizeof(NOTIFYICONDATA);
1649 tnid.uID = 1; /* unique within this systray use */
1650 tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
1651 tnid.uCallbackMessage = WM_SYSTRAY;
1652 tnid.hIcon = hicon = LoadIcon(hinst, MAKEINTRESOURCE(201));
1653 strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
1655 res = Shell_NotifyIcon(NIM_ADD, &tnid);
1657 if (hicon) DestroyIcon(hicon);
1662 /* Update the saved-sessions menu. */
1663 static void update_sessions(void)
1667 TCHAR buf[MAX_PATH + 1];
1670 int index_key, index_menu;
1675 if(ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, PUTTY_REGKEY, &hkey))
1678 for(num_entries = GetMenuItemCount(session_menu);
1679 num_entries > initial_menuitems_count;
1681 RemoveMenu(session_menu, 0, MF_BYPOSITION);
1686 while(ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) {
1687 TCHAR session_name[MAX_PATH + 1];
1688 unmungestr(buf, session_name, MAX_PATH);
1689 if(strcmp(buf, PUTTY_DEFAULT) != 0) {
1690 memset(&mii, 0, sizeof(mii));
1691 mii.cbSize = sizeof(mii);
1692 mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
1693 mii.fType = MFT_STRING;
1694 mii.fState = MFS_ENABLED;
1695 mii.wID = (index_menu * 16) + IDM_SESSIONS_BASE;
1696 mii.dwTypeData = session_name;
1697 InsertMenuItem(session_menu, index_menu, TRUE, &mii);
1705 if(index_menu == 0) {
1706 mii.cbSize = sizeof(mii);
1707 mii.fMask = MIIM_TYPE | MIIM_STATE;
1708 mii.fType = MFT_STRING;
1709 mii.fState = MFS_GRAYED;
1710 mii.dwTypeData = _T("(No sessions)");
1711 InsertMenuItem(session_menu, index_menu, TRUE, &mii);
1717 * Versions of Pageant prior to 0.61 expected this SID on incoming
1718 * communications. For backwards compatibility, and more particularly
1719 * for compatibility with derived works of PuTTY still using the old
1720 * Pageant client code, we accept it as an alternative to the one
1721 * returned from get_user_sid() in winpgntc.c.
1723 PSID get_default_sid(void)
1727 PSECURITY_DESCRIPTOR psd = NULL;
1728 PSID sid = NULL, copy = NULL, ret = NULL;
1730 if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE,
1731 GetCurrentProcessId())) == NULL)
1734 if (p_GetSecurityInfo(proc, SE_KERNEL_OBJECT, OWNER_SECURITY_INFORMATION,
1735 &sid, NULL, NULL, NULL, &psd) != ERROR_SUCCESS)
1738 sidlen = GetLengthSid(sid);
1740 copy = (PSID)smalloc(sidlen);
1742 if (!CopySid(sidlen, copy, sid))
1745 /* Success. Move sid into the return value slot, and null it out
1746 * to stop the cleanup code freeing it. */
1762 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1763 WPARAM wParam, LPARAM lParam)
1766 static int menuinprogress;
1767 static UINT msgTaskbarCreated = 0;
1771 msgTaskbarCreated = RegisterWindowMessage(_T("TaskbarCreated"));
1774 if (message==msgTaskbarCreated) {
1776 * Explorer has been restarted, so the tray icon will
1784 if (lParam == WM_RBUTTONUP) {
1786 GetCursorPos(&cursorpos);
1787 PostMessage(hwnd, WM_SYSTRAY2, cursorpos.x, cursorpos.y);
1788 } else if (lParam == WM_LBUTTONDBLCLK) {
1789 /* Run the default menu item. */
1790 UINT menuitem = GetMenuDefaultItem(systray_menu, FALSE, 0);
1792 PostMessage(hwnd, WM_COMMAND, menuitem, 0);
1796 if (!menuinprogress) {
1799 SetForegroundWindow(hwnd);
1800 ret = TrackPopupMenu(systray_menu,
1801 TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
1803 wParam, lParam, 0, hwnd, NULL);
1809 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1811 if((int)ShellExecute(hwnd, NULL, putty_path, _T(""), _T(""),
1813 MessageBox(NULL, "Unable to execute PuTTY!",
1814 "Error", MB_OK | MB_ICONERROR);
1819 SendMessage(passphrase_box, WM_CLOSE, 0, 0);
1820 SendMessage(hwnd, WM_CLOSE, 0, 0);
1824 keylist = CreateDialog(hinst, MAKEINTRESOURCE(211),
1826 ShowWindow(keylist, SW_SHOWNORMAL);
1829 * Sometimes the window comes up minimised / hidden for
1830 * no obvious reason. Prevent this. This also brings it
1831 * to the front if it's already present (the user
1832 * selected View Keys because they wanted to _see_ the
1835 SetForegroundWindow(keylist);
1836 SetWindowPos(keylist, HWND_TOP, 0, 0, 0, 0,
1837 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1840 if (passphrase_box) {
1841 MessageBeep(MB_ICONERROR);
1842 SetForegroundWindow(passphrase_box);
1845 prompt_add_keyfile();
1849 aboutbox = CreateDialog(hinst, MAKEINTRESOURCE(213),
1851 ShowWindow(aboutbox, SW_SHOWNORMAL);
1853 * Sometimes the window comes up minimised / hidden
1854 * for no obvious reason. Prevent this.
1856 SetForegroundWindow(aboutbox);
1857 SetWindowPos(aboutbox, HWND_TOP, 0, 0, 0, 0,
1858 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1862 launch_help(hwnd, WINHELP_CTX_pageant_general);
1866 if(wParam >= IDM_SESSIONS_BASE && wParam <= IDM_SESSIONS_MAX) {
1868 TCHAR buf[MAX_PATH + 1];
1869 TCHAR param[MAX_PATH + 1];
1870 memset(&mii, 0, sizeof(mii));
1871 mii.cbSize = sizeof(mii);
1872 mii.fMask = MIIM_TYPE;
1874 mii.dwTypeData = buf;
1875 GetMenuItemInfo(session_menu, wParam, FALSE, &mii);
1877 strcat(param, mii.dwTypeData);
1878 if((int)ShellExecute(hwnd, NULL, putty_path, param,
1879 _T(""), SW_SHOW) <= 32) {
1880 MessageBox(NULL, "Unable to execute PuTTY!", "Error",
1881 MB_OK | MB_ICONERROR);
1894 COPYDATASTRUCT *cds;
1899 PSID mapowner, ourself, ourself2;
1901 PSECURITY_DESCRIPTOR psd = NULL;
1904 cds = (COPYDATASTRUCT *) lParam;
1905 if (cds->dwData != AGENT_COPYDATA_ID)
1906 return 0; /* not our message, mate */
1907 mapname = (char *) cds->lpData;
1908 if (mapname[cds->cbData - 1] != '\0')
1909 return 0; /* failure to be ASCIZ! */
1911 debug(("mapname is :%s:\n", mapname));
1913 filemap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, mapname);
1915 debug(("filemap is %p\n", filemap));
1917 if (filemap != NULL && filemap != INVALID_HANDLE_VALUE) {
1921 if ((ourself = get_user_sid()) == NULL) {
1923 debug(("couldn't get user SID\n"));
1925 CloseHandle(filemap);
1929 if ((ourself2 = get_default_sid()) == NULL) {
1931 debug(("couldn't get default SID\n"));
1933 CloseHandle(filemap);
1938 if ((rc = p_GetSecurityInfo(filemap, SE_KERNEL_OBJECT,
1939 OWNER_SECURITY_INFORMATION,
1940 &mapowner, NULL, NULL, NULL,
1941 &psd) != ERROR_SUCCESS)) {
1943 debug(("couldn't get owner info for filemap: %d\n",
1946 CloseHandle(filemap);
1953 LPTSTR ours, ours2, theirs;
1954 ConvertSidToStringSid(mapowner, &theirs);
1955 ConvertSidToStringSid(ourself, &ours);
1956 ConvertSidToStringSid(ourself2, &ours2);
1957 debug(("got sids:\n oursnew=%s\n oursold=%s\n"
1958 " theirs=%s\n", ours, ours2, theirs));
1964 if (!EqualSid(mapowner, ourself) &&
1965 !EqualSid(mapowner, ourself2)) {
1966 CloseHandle(filemap);
1970 return 0; /* security ID mismatch! */
1973 debug(("security stuff matched\n"));
1980 debug(("security APIs not present\n"));
1984 p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
1986 debug(("p is %p\n", p));
1989 for (i = 0; i < 5; i++)
1990 debug(("p[%d]=%02x\n", i,
1991 ((unsigned char *) p)[i]));
1998 CloseHandle(filemap);
2003 return DefWindowProc(hwnd, message, wParam, lParam);
2007 * Fork and Exec the command in cmdline. [DBW]
2009 void spawn_cmd(char *cmdline, char * args, int show)
2011 if (ShellExecute(NULL, _T("open"), cmdline,
2012 args, NULL, show) <= (HINSTANCE) 32) {
2014 msg = dupprintf("Failed to run \"%.100s\", Error: %d", cmdline,
2015 (int)GetLastError());
2016 MessageBox(NULL, msg, APPNAME, MB_OK | MB_ICONEXCLAMATION);
2022 * This is a can't-happen stub, since Pageant never makes
2023 * asynchronous agent requests.
2025 void agent_schedule_callback(void (*callback)(void *, void *, int),
2026 void *callback_ctx, void *data, int len)
2028 assert(!"We shouldn't get here");
2031 void cleanup_exit(int code)
2037 int flags = FLAG_SYNCAGENT;
2039 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
2043 char *command = NULL;
2046 char **argv, **argstart;
2052 * Determine whether we're an NT system (should have security
2053 * APIs) or a non-NT system (don't do security).
2057 modalfatalbox("Windows refuses to report a version");
2059 if (osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT) {
2060 has_security = TRUE;
2062 has_security = FALSE;
2067 * Attempt to get the security API we need.
2069 if (!got_advapi()) {
2071 "Unable to access security APIs. Pageant will\n"
2072 "not run, in case it causes a security breach.",
2073 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
2078 "This program has been compiled for Win9X and will\n"
2079 "not run on NT, in case it causes a security breach.",
2080 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
2086 * See if we can find our Help file.
2091 * Look for the PuTTY binary (we will enable the saved session
2092 * submenu if we find it).
2095 char b[2048], *p, *q, *r;
2097 GetModuleFileName(NULL, b, sizeof(b) - 16);
2099 p = strrchr(b, '\\');
2100 if (p && p >= r) r = p+1;
2101 q = strrchr(b, ':');
2102 if (q && q >= r) r = q+1;
2103 strcpy(r, "putty.exe");
2104 if ( (fp = fopen(b, "r")) != NULL) {
2105 putty_path = dupstr(b);
2112 * Find out if Pageant is already running.
2114 already_running = agent_exists();
2117 * Initialise storage for RSA keys.
2119 if (!already_running) {
2120 rsakeys = newtree234(cmpkeys_rsa);
2121 ssh2keys = newtree234(cmpkeys_ssh2);
2125 * Initialise storage for short-term passphrase cache.
2127 passphrases = newtree234(NULL);
2130 * Process the command line and add keys as listed on it.
2132 split_into_argv(cmdline, &argc, &argv, &argstart);
2133 for (i = 0; i < argc; i++) {
2134 if (!strcmp(argv[i], "-pgpfp")) {
2137 } else if (!strcmp(argv[i], "-c")) {
2139 * If we see `-c', then the rest of the
2140 * command line should be treated as a
2141 * command to be spawned.
2144 command = argstart[i+1];
2149 Filename *fn = filename_from_str(argv[i]);
2157 * Forget any passphrase that we retained while going over
2158 * command line keyfiles.
2160 forget_passphrases();
2164 if (command[0] == '"')
2165 args = strchr(++command, '"');
2167 args = strchr(command, ' ');
2170 while(*args && isspace(*args)) args++;
2172 spawn_cmd(command, args, show);
2176 * If Pageant was already running, we leave now. If we haven't
2177 * even taken any auxiliary action (spawned a command or added
2180 if (already_running) {
2181 if (!command && !added_keys) {
2182 MessageBox(NULL, "Pageant is already running", "Pageant Error",
2183 MB_ICONERROR | MB_OK);
2190 wndclass.lpfnWndProc = WndProc;
2191 wndclass.cbClsExtra = 0;
2192 wndclass.cbWndExtra = 0;
2193 wndclass.hInstance = inst;
2194 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
2195 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
2196 wndclass.hbrBackground = GetStockObject(BLACK_BRUSH);
2197 wndclass.lpszMenuName = NULL;
2198 wndclass.lpszClassName = APPNAME;
2200 RegisterClass(&wndclass);
2205 hwnd = CreateWindow(APPNAME, APPNAME,
2206 WS_OVERLAPPEDWINDOW | WS_VSCROLL,
2207 CW_USEDEFAULT, CW_USEDEFAULT,
2208 100, 100, NULL, NULL, inst, NULL);
2210 /* Set up a system tray icon */
2213 /* Accelerators used: nsvkxa */
2214 systray_menu = CreatePopupMenu();
2216 session_menu = CreateMenu();
2217 AppendMenu(systray_menu, MF_ENABLED, IDM_PUTTY, "&New Session");
2218 AppendMenu(systray_menu, MF_POPUP | MF_ENABLED,
2219 (UINT) session_menu, "&Saved Sessions");
2220 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
2222 AppendMenu(systray_menu, MF_ENABLED, IDM_VIEWKEYS,
2224 AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key");
2225 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
2227 AppendMenu(systray_menu, MF_ENABLED, IDM_HELP, "&Help");
2228 AppendMenu(systray_menu, MF_ENABLED, IDM_ABOUT, "&About");
2229 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
2230 AppendMenu(systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit");
2231 initial_menuitems_count = GetMenuItemCount(session_menu);
2233 /* Set the default menu item. */
2234 SetMenuDefaultItem(systray_menu, IDM_VIEWKEYS, FALSE);
2236 ShowWindow(hwnd, SW_HIDE);
2239 * Main message loop.
2241 while (GetMessage(&msg, NULL, 0, 0) == 1) {
2242 if (!(IsWindow(keylist) && IsDialogMessage(keylist, &msg)) &&
2243 !(IsWindow(aboutbox) && IsDialogMessage(aboutbox, &msg))) {
2244 TranslateMessage(&msg);
2245 DispatchMessage(&msg);
2249 /* Clean up the system tray icon */
2251 NOTIFYICONDATA tnid;
2253 tnid.cbSize = sizeof(NOTIFYICONDATA);
2257 Shell_NotifyIcon(NIM_DELETE, &tnid);
2259 DestroyMenu(systray_menu);
2262 if (keypath) filereq_free(keypath);
2264 cleanup_exit(msg.wParam);
2265 return msg.wParam; /* just in case optimiser complains */