2 * Pageant: the PuTTY Authentication Agent.
19 #define IDI_MAINICON 200
20 #define IDI_TRAYICON 201
22 #define WM_XUSER (WM_USER + 0x2000)
23 #define WM_SYSTRAY (WM_XUSER + 6)
24 #define WM_SYSTRAY2 (WM_XUSER + 7)
26 #define AGENT_COPYDATA_ID 0x804e50ba /* random goop */
29 * FIXME: maybe some day we can sort this out ...
31 #define AGENT_MAX_MSGLEN 8192
33 #define IDM_CLOSE 0x0010
34 #define IDM_VIEWKEYS 0x0020
35 #define IDM_ADDKEY 0x0030
36 #define IDM_HELP 0x0040
37 #define IDM_ABOUT 0x0050
39 #define APPNAME "Pageant"
43 static HINSTANCE instance;
44 static HWND main_hwnd;
47 static HMENU systray_menu, session_menu;
48 static int already_running;
49 static int requested_help;
51 static char *help_path;
52 static char *putty_path;
54 #define IDM_PUTTY 0x0060
55 #define IDM_SESSIONS_BASE 0x1000
56 #define IDM_SESSIONS_MAX 0x2000
57 #define PUTTY_REGKEY "Software\\SimonTatham\\PuTTY\\Sessions"
58 #define PUTTY_DEFAULT "Default%20Settings"
59 static int initial_menuitems_count;
61 /* Un-munge session names out of the registry. */
62 static void unmungestr(char *in, char *out, int outlen)
65 if (*in == '%' && in[1] && in[2]) {
73 *out++ = (i << 4) + j;
87 static tree234 *rsakeys, *ssh2keys;
89 static int has_security;
91 typedef DWORD(WINAPI * gsi_fn_t)
92 (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION,
93 PSID *, PSID *, PACL *, PACL *, PSECURITY_DESCRIPTOR *);
94 static gsi_fn_t getsecurityinfo;
98 * Exports from pageantc.c
100 void agent_query(void *in, int inlen, void **out, int *outlen);
101 int agent_exists(void);
106 static void *make_keylist1(int *length);
107 static void *make_keylist2(int *length);
108 static void *get_keylist1(void);
109 static void *get_keylist2(void);
112 * We need this to link with the RSA code, because rsaencrypt()
113 * pads its data with random bytes. Since we only use rsadecrypt()
114 * and the signing functions, which are deterministic, this should
117 * If it _is_ called, there is a _serious_ problem, because it
118 * won't generate true random numbers. So we must scream, panic,
119 * and exit immediately if that should happen.
121 int random_byte(void)
123 MessageBox(main_hwnd, "Internal Error", APPNAME, MB_OK | MB_ICONERROR);
125 /* this line can't be reached but it placates MSVC's warnings :-) */
130 * Blob structure for passing to the asymmetric SSH2 key compare
131 * function, prototyped here.
137 static int cmpkeys_ssh2_asymm(void *av, void *bv);
140 * This function is needed to link with the DES code. We need not
141 * have it do anything at all.
143 void logevent(char *msg)
147 #define GET_32BIT(cp) \
148 (((unsigned long)(unsigned char)(cp)[0] << 24) | \
149 ((unsigned long)(unsigned char)(cp)[1] << 16) | \
150 ((unsigned long)(unsigned char)(cp)[2] << 8) | \
151 ((unsigned long)(unsigned char)(cp)[3]))
153 #define PUT_32BIT(cp, value) { \
154 (cp)[0] = (unsigned char)((value) >> 24); \
155 (cp)[1] = (unsigned char)((value) >> 16); \
156 (cp)[2] = (unsigned char)((value) >> 8); \
157 (cp)[3] = (unsigned char)(value); }
159 #define PASSPHRASE_MAXLEN 512
161 struct PassphraseProcStruct {
166 static tree234 *passphrases = NULL;
169 * After processing a list of filenames, we want to forget the
172 static void forget_passphrases(void)
174 while (count234(passphrases) > 0) {
175 char *pp = index234(passphrases, 0);
176 memset(pp, 0, strlen(pp));
177 delpos234(passphrases, 0);
183 * Dialog-box function for the Licence box.
185 static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
186 WPARAM wParam, LPARAM lParam)
192 switch (LOWORD(wParam)) {
206 * Dialog-box function for the About box.
208 static int CALLBACK AboutProc(HWND hwnd, UINT msg,
209 WPARAM wParam, LPARAM lParam)
213 SetDlgItemText(hwnd, 100, ver);
216 switch (LOWORD(wParam)) {
222 EnableWindow(hwnd, 0);
223 DialogBox(instance, MAKEINTRESOURCE(214), NULL, LicenceProc);
224 EnableWindow(hwnd, 1);
225 SetActiveWindow(hwnd);
237 static HWND passphrase_box;
240 * Dialog-box function for the passphrase box.
242 static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
243 WPARAM wParam, LPARAM lParam)
245 static char *passphrase = NULL;
246 struct PassphraseProcStruct *p;
250 passphrase_box = hwnd;
254 { /* centre the window */
258 hw = GetDesktopWindow();
259 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
261 (rs.right + rs.left + rd.left - rd.right) / 2,
262 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
263 rd.right - rd.left, rd.bottom - rd.top, TRUE);
266 SetForegroundWindow(hwnd);
267 SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
268 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
269 p = (struct PassphraseProcStruct *) lParam;
270 passphrase = p->passphrase;
272 SetDlgItemText(hwnd, 101, p->comment);
274 SetDlgItemText(hwnd, 102, passphrase);
277 switch (LOWORD(wParam)) {
287 case 102: /* edit box */
288 if ((HIWORD(wParam) == EN_CHANGE) && passphrase) {
289 GetDlgItemText(hwnd, 102, passphrase,
290 PASSPHRASE_MAXLEN - 1);
291 passphrase[PASSPHRASE_MAXLEN - 1] = '\0';
304 * Warn about the obsolescent key file format.
306 void old_keyfile_warning(void)
308 static const char mbtitle[] = "PuTTY Key File Warning";
309 static const char message[] =
310 "You are loading an SSH 2 private key which has an\n"
311 "old version of the file format. This means your key\n"
312 "file is not fully tamperproof. Future versions of\n"
313 "PuTTY may stop supporting this private key format,\n"
314 "so we recommend you convert your key to the new\n"
317 "You can perform this conversion by loading the key\n"
318 "into PuTTYgen and then saving it again.";
320 MessageBox(NULL, message, mbtitle, MB_OK);
324 * Update the visible key list.
326 static void keylist_update(void)
329 struct ssh2_userkey *skey;
333 SendDlgItemMessage(keylist, 100, LB_RESETCONTENT, 0, 0);
334 for (i = 0; NULL != (rkey = index234(rsakeys, i)); i++) {
335 char listentry[512], *p;
337 * Replace two spaces in the fingerprint with tabs, for
338 * nice alignment in the box.
340 strcpy(listentry, "ssh1\t");
341 p = listentry + strlen(listentry);
342 rsa_fingerprint(p, sizeof(listentry) - (p - listentry), rkey);
343 p = strchr(listentry, ' ');
346 p = strchr(listentry, ' ');
349 SendDlgItemMessage(keylist, 100, LB_ADDSTRING,
350 0, (LPARAM) listentry);
352 for (i = 0; NULL != (skey = index234(ssh2keys, i)); i++) {
353 char listentry[512], *p;
356 * Replace two spaces in the fingerprint with tabs, for
357 * nice alignment in the box.
359 p = skey->alg->fingerprint(skey->data);
360 strncpy(listentry, p, sizeof(listentry));
361 p = strchr(listentry, ' ');
364 p = strchr(listentry, ' ');
367 len = strlen(listentry);
368 if (len < sizeof(listentry) - 2) {
369 listentry[len] = '\t';
370 strncpy(listentry + len + 1, skey->comment,
371 sizeof(listentry) - len - 1);
373 SendDlgItemMessage(keylist, 100, LB_ADDSTRING, 0,
376 SendDlgItemMessage(keylist, 100, LB_SETCURSEL, (WPARAM) - 1, 0);
381 * This function loads a key from a file and adds it.
383 static void add_keyfile(char *filename)
385 char passphrase[PASSPHRASE_MAXLEN];
386 struct RSAKey *rkey = NULL;
387 struct ssh2_userkey *skey = NULL;
392 struct PassphraseProcStruct pps;
396 ver = keyfile_version(filename);
398 MessageBox(NULL, "Couldn't load private key.", APPNAME,
399 MB_OK | MB_ICONERROR);
404 * See if the key is already loaded (in the primary Pageant,
405 * which may or may not be us).
409 unsigned char *keylist, *p;
410 int i, nkeys, bloblen;
413 if (!rsakey_pubblob(filename, &blob, &bloblen)) {
414 MessageBox(NULL, "Couldn't load private key.", APPNAME,
415 MB_OK | MB_ICONERROR);
418 keylist = get_keylist1();
420 unsigned char *blob2;
421 blob = ssh2_userkey_loadpub(filename, NULL, &bloblen);
423 MessageBox(NULL, "Couldn't load private key.", APPNAME,
424 MB_OK | MB_ICONERROR);
427 /* For our purposes we want the blob prefixed with its length */
428 blob2 = smalloc(bloblen+4);
429 PUT_32BIT(blob2, bloblen);
430 memcpy(blob2 + 4, blob, bloblen);
434 keylist = get_keylist2();
437 nkeys = GET_32BIT(keylist);
440 for (i = 0; i < nkeys; i++) {
441 if (!memcmp(blob, p, bloblen)) {
442 /* Key is already present; we can now leave. */
447 /* Now skip over public blob */
449 p += rsa_public_blob_len(p);
451 p += 4 + GET_32BIT(p);
452 /* Now skip over comment field */
453 p += 4 + GET_32BIT(p);
463 needs_pass = rsakey_encrypted(filename, &comment);
465 needs_pass = ssh2_userkey_encrypted(filename, &comment);
468 rkey = smalloc(sizeof(*rkey));
469 pps.passphrase = passphrase;
470 pps.comment = comment;
474 /* try all the remembered passphrases first */
475 char *pp = index234(passphrases, attempts);
477 strcpy(passphrase, pp);
481 dlgret = DialogBoxParam(instance, MAKEINTRESOURCE(210),
482 NULL, PassphraseProc, (LPARAM) & pps);
483 passphrase_box = NULL;
489 return; /* operation cancelled */
495 ret = loadrsakey(filename, rkey, passphrase);
497 skey = ssh2_load_userkey(filename, passphrase);
498 if (skey == SSH2_WRONG_PASSPHRASE)
508 /* if they typed in an ok passphrase, remember it */
509 if(original_pass && ret) {
510 char *pp = dupstr(passphrase);
511 addpos234(passphrases, pp, 0);
517 MessageBox(NULL, "Couldn't load private key.", APPNAME,
518 MB_OK | MB_ICONERROR);
524 if (already_running) {
525 unsigned char *request, *response;
527 int reqlen, clen, resplen;
529 clen = strlen(rkey->comment);
531 reqlen = 4 + 1 + /* length, message type */
533 ssh1_bignum_length(rkey->modulus) +
534 ssh1_bignum_length(rkey->exponent) +
535 ssh1_bignum_length(rkey->private_exponent) +
536 ssh1_bignum_length(rkey->iqmp) +
537 ssh1_bignum_length(rkey->p) +
538 ssh1_bignum_length(rkey->q) + 4 + clen /* comment */
541 request = smalloc(reqlen);
543 request[4] = SSH1_AGENTC_ADD_RSA_IDENTITY;
545 PUT_32BIT(request + reqlen, bignum_bitcount(rkey->modulus));
547 reqlen += ssh1_write_bignum(request + reqlen, rkey->modulus);
548 reqlen += ssh1_write_bignum(request + reqlen, rkey->exponent);
550 ssh1_write_bignum(request + reqlen,
551 rkey->private_exponent);
552 reqlen += ssh1_write_bignum(request + reqlen, rkey->iqmp);
553 reqlen += ssh1_write_bignum(request + reqlen, rkey->p);
554 reqlen += ssh1_write_bignum(request + reqlen, rkey->q);
555 PUT_32BIT(request + reqlen, clen);
556 memcpy(request + reqlen + 4, rkey->comment, clen);
558 PUT_32BIT(request, reqlen - 4);
560 agent_query(request, reqlen, &vresponse, &resplen);
561 response = vresponse;
562 if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
563 MessageBox(NULL, "The already running Pageant "
564 "refused to add the key.", APPNAME,
565 MB_OK | MB_ICONERROR);
570 if (add234(rsakeys, rkey) != rkey)
571 sfree(rkey); /* already present, don't waste RAM */
574 if (already_running) {
575 unsigned char *request, *response;
577 int reqlen, alglen, clen, keybloblen, resplen;
578 alglen = strlen(skey->alg->name);
579 clen = strlen(skey->comment);
581 keybloblen = skey->alg->openssh_fmtkey(skey->data, NULL, 0);
583 reqlen = 4 + 1 + /* length, message type */
584 4 + alglen + /* algorithm name */
585 keybloblen + /* key data */
586 4 + clen /* comment */
589 request = smalloc(reqlen);
591 request[4] = SSH2_AGENTC_ADD_IDENTITY;
593 PUT_32BIT(request + reqlen, alglen);
595 memcpy(request + reqlen, skey->alg->name, alglen);
597 reqlen += skey->alg->openssh_fmtkey(skey->data,
600 PUT_32BIT(request + reqlen, clen);
601 memcpy(request + reqlen + 4, skey->comment, clen);
602 PUT_32BIT(request, reqlen - 4);
605 agent_query(request, reqlen, &vresponse, &resplen);
606 response = vresponse;
607 if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
608 MessageBox(NULL, "The already running Pageant"
609 "refused to add the key.", APPNAME,
610 MB_OK | MB_ICONERROR);
615 if (add234(ssh2keys, skey) != skey) {
616 skey->alg->freekey(skey->data);
617 sfree(skey); /* already present, don't waste RAM */
624 * Create an SSH1 key list in a malloc'ed buffer; return its
627 static void *make_keylist1(int *length)
631 unsigned char *blob, *p, *ret;
635 * Count up the number and length of keys we hold.
639 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
641 blob = rsa_public_blob(key, &bloblen);
644 len += 4 + strlen(key->comment);
647 /* Allocate the buffer. */
648 p = ret = smalloc(len);
649 if (length) *length = len;
653 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
654 blob = rsa_public_blob(key, &bloblen);
655 memcpy(p, blob, bloblen);
658 PUT_32BIT(p, strlen(key->comment));
659 memcpy(p + 4, key->comment, strlen(key->comment));
660 p += 4 + strlen(key->comment);
663 assert(p - ret == len);
668 * Create an SSH2 key list in a malloc'ed buffer; return its
671 static void *make_keylist2(int *length)
673 struct ssh2_userkey *key;
675 unsigned char *blob, *p, *ret;
679 * Count up the number and length of keys we hold.
683 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
685 len += 4; /* length field */
686 blob = key->alg->public_blob(key->data, &bloblen);
689 len += 4 + strlen(key->comment);
692 /* Allocate the buffer. */
693 p = ret = smalloc(len);
694 if (length) *length = len;
697 * Packet header is the obvious five bytes, plus four
698 * bytes for the key count.
702 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
703 blob = key->alg->public_blob(key->data, &bloblen);
704 PUT_32BIT(p, bloblen);
706 memcpy(p, blob, bloblen);
709 PUT_32BIT(p, strlen(key->comment));
710 memcpy(p + 4, key->comment, strlen(key->comment));
711 p += 4 + strlen(key->comment);
714 assert(p - ret == len);
719 * Acquire a keylist1 from the primary Pageant; this means either
720 * calling make_keylist1 (if that's us) or sending a message to the
721 * primary Pageant (if it's not).
723 static void *get_keylist1(void)
727 if (already_running) {
728 unsigned char request[5], *response;
731 request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
732 PUT_32BIT(request, 4);
734 agent_query(request, 5, &vresponse, &resplen);
735 response = vresponse;
736 if (resplen < 5 || response[4] != SSH1_AGENT_RSA_IDENTITIES_ANSWER)
739 ret = smalloc(resplen-5);
740 memcpy(ret, response+5, resplen-5);
743 ret = make_keylist1(NULL);
749 * Acquire a keylist2 from the primary Pageant; this means either
750 * calling make_keylist2 (if that's us) or sending a message to the
751 * primary Pageant (if it's not).
753 static void *get_keylist2(void)
757 if (already_running) {
758 unsigned char request[5], *response;
762 request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
763 PUT_32BIT(request, 4);
765 agent_query(request, 5, &vresponse, &resplen);
766 response = vresponse;
767 if (resplen < 5 || response[4] != SSH2_AGENT_IDENTITIES_ANSWER)
770 ret = smalloc(resplen-5);
771 memcpy(ret, response+5, resplen-5);
774 ret = make_keylist2(NULL);
780 * This is the main agent function that answers messages.
782 static void answer_msg(void *msg)
784 unsigned char *p = msg;
785 unsigned char *ret = msg;
789 * Get the message type.
795 case SSH1_AGENTC_REQUEST_RSA_IDENTITIES:
797 * Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER.
803 ret[4] = SSH1_AGENT_RSA_IDENTITIES_ANSWER;
804 keylist = make_keylist1(&len);
805 if (len + 5 > AGENT_MAX_MSGLEN) {
809 PUT_32BIT(ret, len + 1);
810 memcpy(ret + 5, keylist, len);
814 case SSH2_AGENTC_REQUEST_IDENTITIES:
816 * Reply with SSH2_AGENT_IDENTITIES_ANSWER.
822 ret[4] = SSH2_AGENT_IDENTITIES_ANSWER;
823 keylist = make_keylist2(&len);
824 if (len + 5 > AGENT_MAX_MSGLEN) {
828 PUT_32BIT(ret, len + 1);
829 memcpy(ret + 5, keylist, len);
833 case SSH1_AGENTC_RSA_CHALLENGE:
835 * Reply with either SSH1_AGENT_RSA_RESPONSE or
836 * SSH_AGENT_FAILURE, depending on whether we have that key
840 struct RSAKey reqkey, *key;
841 Bignum challenge, response;
842 unsigned char response_source[48], response_md5[16];
843 struct MD5Context md5c;
847 p += ssh1_read_bignum(p, &reqkey.exponent);
848 p += ssh1_read_bignum(p, &reqkey.modulus);
849 p += ssh1_read_bignum(p, &challenge);
850 memcpy(response_source + 32, p, 16);
852 if (GET_32BIT(p) != 1 ||
853 (key = find234(rsakeys, &reqkey, NULL)) == NULL) {
854 freebn(reqkey.exponent);
855 freebn(reqkey.modulus);
859 response = rsadecrypt(challenge, key);
860 for (i = 0; i < 32; i++)
861 response_source[i] = bignum_byte(response, 31 - i);
864 MD5Update(&md5c, response_source, 48);
865 MD5Final(response_md5, &md5c);
866 memset(response_source, 0, 48); /* burn the evidence */
867 freebn(response); /* and that evidence */
868 freebn(challenge); /* yes, and that evidence */
869 freebn(reqkey.exponent); /* and free some memory ... */
870 freebn(reqkey.modulus); /* ... while we're at it. */
873 * Packet is the obvious five byte header, plus sixteen
877 PUT_32BIT(ret, len - 4);
878 ret[4] = SSH1_AGENT_RSA_RESPONSE;
879 memcpy(ret + 5, response_md5, 16);
882 case SSH2_AGENTC_SIGN_REQUEST:
884 * Reply with either SSH2_AGENT_SIGN_RESPONSE or
885 * SSH_AGENT_FAILURE, depending on whether we have that key
889 struct ssh2_userkey *key;
891 unsigned char *data, *signature;
892 int datalen, siglen, len;
894 b.len = GET_32BIT(p);
898 datalen = GET_32BIT(p);
901 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
904 signature = key->alg->sign(key->data, data, datalen, &siglen);
905 len = 5 + 4 + siglen;
906 PUT_32BIT(ret, len - 4);
907 ret[4] = SSH2_AGENT_SIGN_RESPONSE;
908 PUT_32BIT(ret + 5, siglen);
909 memcpy(ret + 5 + 4, signature, siglen);
913 case SSH1_AGENTC_ADD_RSA_IDENTITY:
915 * Add to the list and return SSH_AGENT_SUCCESS, or
916 * SSH_AGENT_FAILURE if the key was malformed.
922 key = smalloc(sizeof(struct RSAKey));
923 memset(key, 0, sizeof(struct RSAKey));
924 p += makekey(p, key, NULL, 1);
925 p += makeprivate(p, key);
926 p += ssh1_read_bignum(p, &key->iqmp); /* p^-1 mod q */
927 p += ssh1_read_bignum(p, &key->p); /* p */
928 p += ssh1_read_bignum(p, &key->q); /* q */
929 commentlen = GET_32BIT(p);
930 comment = smalloc(commentlen+1);
932 memcpy(comment, p + 4, commentlen);
933 comment[commentlen] = '\0';
934 key->comment = comment;
937 ret[4] = SSH_AGENT_FAILURE;
938 if (add234(rsakeys, key) == key) {
940 ret[4] = SSH_AGENT_SUCCESS;
947 case SSH2_AGENTC_ADD_IDENTITY:
949 * Add to the list and return SSH_AGENT_SUCCESS, or
950 * SSH_AGENT_FAILURE if the key was malformed.
953 struct ssh2_userkey *key;
958 key = smalloc(sizeof(struct ssh2_userkey));
960 alglen = GET_32BIT(p);
964 /* Add further algorithm names here. */
965 if (alglen == 7 && !memcmp(alg, "ssh-rsa", 7))
967 else if (alglen == 7 && !memcmp(alg, "ssh-dss", 7))
975 GET_32BIT((unsigned char *) msg) - (p -
976 (unsigned char *) msg -
978 key->data = key->alg->openssh_createkey(&p, &bloblen);
983 commlen = GET_32BIT(p);
986 comment = smalloc(commlen + 1);
988 memcpy(comment, p, commlen);
989 comment[commlen] = '\0';
991 key->comment = comment;
994 ret[4] = SSH_AGENT_FAILURE;
995 if (add234(ssh2keys, key) == key) {
997 ret[4] = SSH_AGENT_SUCCESS;
999 key->alg->freekey(key->data);
1000 sfree(key->comment);
1005 case SSH1_AGENTC_REMOVE_RSA_IDENTITY:
1007 * Remove from the list and return SSH_AGENT_SUCCESS, or
1008 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
1012 struct RSAKey reqkey, *key;
1014 p += makekey(p, &reqkey, NULL, 0);
1015 key = find234(rsakeys, &reqkey, NULL);
1016 freebn(reqkey.exponent);
1017 freebn(reqkey.modulus);
1019 ret[4] = SSH_AGENT_FAILURE;
1021 del234(rsakeys, key);
1025 ret[4] = SSH_AGENT_SUCCESS;
1029 case SSH2_AGENTC_REMOVE_IDENTITY:
1031 * Remove from the list and return SSH_AGENT_SUCCESS, or
1032 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
1036 struct ssh2_userkey *key;
1039 b.len = GET_32BIT(p);
1043 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
1048 ret[4] = SSH_AGENT_FAILURE;
1050 del234(ssh2keys, key);
1052 key->alg->freekey(key->data);
1054 ret[4] = SSH_AGENT_SUCCESS;
1058 case SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES:
1060 * Remove all SSH1 keys. Always returns success.
1063 struct RSAKey *rkey;
1065 while ((rkey = index234(rsakeys, 0)) != NULL) {
1066 del234(rsakeys, rkey);
1073 ret[4] = SSH_AGENT_SUCCESS;
1076 case SSH2_AGENTC_REMOVE_ALL_IDENTITIES:
1078 * Remove all SSH2 keys. Always returns success.
1081 struct ssh2_userkey *skey;
1083 while ((skey = index234(ssh2keys, 0)) != NULL) {
1084 del234(ssh2keys, skey);
1085 skey->alg->freekey(skey->data);
1091 ret[4] = SSH_AGENT_SUCCESS;
1097 * Unrecognised message. Return SSH_AGENT_FAILURE.
1100 ret[4] = SSH_AGENT_FAILURE;
1106 * Key comparison function for the 2-3-4 tree of RSA keys.
1108 static int cmpkeys_rsa(void *av, void *bv)
1110 struct RSAKey *a = (struct RSAKey *) av;
1111 struct RSAKey *b = (struct RSAKey *) bv;
1118 * Compare by length of moduli.
1120 alen = bignum_bitcount(am);
1121 blen = bignum_bitcount(bm);
1124 else if (alen < blen)
1127 * Now compare by moduli themselves.
1129 alen = (alen + 7) / 8; /* byte count */
1130 while (alen-- > 0) {
1132 abyte = bignum_byte(am, alen);
1133 bbyte = bignum_byte(bm, alen);
1136 else if (abyte < bbyte)
1146 * Key comparison function for the 2-3-4 tree of SSH2 keys.
1148 static int cmpkeys_ssh2(void *av, void *bv)
1150 struct ssh2_userkey *a = (struct ssh2_userkey *) av;
1151 struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
1154 unsigned char *ablob, *bblob;
1158 * Compare purely by public blob.
1160 ablob = a->alg->public_blob(a->data, &alen);
1161 bblob = b->alg->public_blob(b->data, &blen);
1164 for (i = 0; i < alen && i < blen; i++) {
1165 if (ablob[i] < bblob[i]) {
1168 } else if (ablob[i] > bblob[i]) {
1173 if (c == 0 && i < alen)
1174 c = +1; /* a is longer */
1175 if (c == 0 && i < blen)
1176 c = -1; /* a is longer */
1185 * Key comparison function for looking up a blob in the 2-3-4 tree
1188 static int cmpkeys_ssh2_asymm(void *av, void *bv)
1190 struct blob *a = (struct blob *) av;
1191 struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
1194 unsigned char *ablob, *bblob;
1198 * Compare purely by public blob.
1202 bblob = b->alg->public_blob(b->data, &blen);
1205 for (i = 0; i < alen && i < blen; i++) {
1206 if (ablob[i] < bblob[i]) {
1209 } else if (ablob[i] > bblob[i]) {
1214 if (c == 0 && i < alen)
1215 c = +1; /* a is longer */
1216 if (c == 0 && i < blen)
1217 c = -1; /* a is longer */
1225 * Prompt for a key file to add, and add it.
1227 static void prompt_add_keyfile(void)
1230 char filename[FILENAME_MAX];
1231 char *filelist = smalloc(8192);
1235 memset(&of, 0, sizeof(of));
1236 #ifdef OPENFILENAME_SIZE_VERSION_400
1237 of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
1239 of.lStructSize = sizeof(of);
1241 of.hwndOwner = main_hwnd;
1242 of.lpstrFilter = "All Files\0*\0\0\0";
1243 of.lpstrCustomFilter = NULL;
1244 of.nFilterIndex = 1;
1245 of.lpstrFile = filelist;
1247 of.nMaxFile = FILENAME_MAX;
1248 of.lpstrFileTitle = NULL;
1249 of.lpstrInitialDir = NULL;
1250 of.lpstrTitle = "Select Private Key File";
1251 of.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER;
1252 if (GetOpenFileName(&of)) {
1253 if(strlen(filelist) > of.nFileOffset)
1254 /* Only one filename returned? */
1255 add_keyfile(filelist);
1257 /* we are returned a bunch of strings, end to
1258 * end. first string is the directory, the
1259 * rest the filenames. terminated with an
1262 filewalker = filelist;
1263 dirlen = strlen(filewalker);
1264 if(dirlen > FILENAME_MAX - 8) return;
1265 memcpy(filename, filewalker, dirlen);
1267 filewalker += dirlen + 1;
1268 filename[dirlen++] = '\\';
1270 /* then go over names one by one */
1272 n = strlen(filewalker) + 1;
1273 /* end of the list */
1276 /* too big, shouldn't happen */
1277 if(n + dirlen > FILENAME_MAX)
1280 memcpy(filename + dirlen, filewalker, n);
1283 add_keyfile(filename);
1288 forget_passphrases();
1294 * Dialog-box function for the key list box.
1296 static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
1297 WPARAM wParam, LPARAM lParam)
1299 struct RSAKey *rkey;
1300 struct ssh2_userkey *skey;
1305 * Centre the window.
1307 { /* centre the window */
1311 hw = GetDesktopWindow();
1312 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
1314 (rs.right + rs.left + rd.left - rd.right) / 2,
1315 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
1316 rd.right - rd.left, rd.bottom - rd.top, TRUE);
1320 SetWindowLong(hwnd, GWL_EXSTYLE,
1321 GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_CONTEXTHELP);
1323 HWND item = GetDlgItem(hwnd, 103); /* the Help button */
1325 DestroyWindow(item);
1327 requested_help = FALSE;
1331 static int tabs[] = { 35, 60, 210 };
1332 SendDlgItemMessage(hwnd, 100, LB_SETTABSTOPS,
1333 sizeof(tabs) / sizeof(*tabs),
1339 switch (LOWORD(wParam)) {
1343 DestroyWindow(hwnd);
1345 case 101: /* add key */
1346 if (HIWORD(wParam) == BN_CLICKED ||
1347 HIWORD(wParam) == BN_DOUBLECLICKED) {
1348 if (passphrase_box) {
1349 MessageBeep(MB_ICONERROR);
1350 SetForegroundWindow(passphrase_box);
1353 prompt_add_keyfile();
1356 case 102: /* remove key */
1357 if (HIWORD(wParam) == BN_CLICKED ||
1358 HIWORD(wParam) == BN_DOUBLECLICKED) {
1363 /* our counter within the array of selected items */
1366 /* get the number of items selected in the list */
1368 SendDlgItemMessage(hwnd, 100, LB_GETSELCOUNT, 0, 0);
1370 /* none selected? that was silly */
1371 if (numSelected == 0) {
1376 /* get item indices in an array */
1377 selectedArray = smalloc(numSelected * sizeof(int));
1378 SendDlgItemMessage(hwnd, 100, LB_GETSELITEMS,
1379 numSelected, (WPARAM)selectedArray);
1381 itemNum = numSelected - 1;
1382 rCount = count234(rsakeys);
1383 sCount = count234(ssh2keys);
1385 /* go through the non-rsakeys until we've covered them all,
1386 * and/or we're out of selected items to check. note that
1387 * we go *backwards*, to avoid complications from deleting
1388 * things hence altering the offset of subsequent items
1390 for (i = sCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1391 skey = index234(ssh2keys, i);
1393 if (selectedArray[itemNum] == rCount + i) {
1394 del234(ssh2keys, skey);
1395 skey->alg->freekey(skey->data);
1401 /* do the same for the rsa keys */
1402 for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1403 rkey = index234(rsakeys, i);
1405 if(selectedArray[itemNum] == i) {
1406 del234(rsakeys, rkey);
1413 sfree(selectedArray);
1417 case 103: /* help */
1418 if (HIWORD(wParam) == BN_CLICKED ||
1419 HIWORD(wParam) == BN_DOUBLECLICKED) {
1421 WinHelp(main_hwnd, help_path, HELP_COMMAND,
1422 (DWORD)"JI(`',`pageant.general')");
1423 requested_help = TRUE;
1431 int id = ((LPHELPINFO)lParam)->iCtrlId;
1434 case 100: cmd = "JI(`',`pageant.keylist')"; break;
1435 case 101: cmd = "JI(`',`pageant.addkey')"; break;
1436 case 102: cmd = "JI(`',`pageant.remkey')"; break;
1439 WinHelp(main_hwnd, help_path, HELP_COMMAND, (DWORD)cmd);
1440 requested_help = TRUE;
1448 DestroyWindow(hwnd);
1454 /* Set up a system tray icon */
1455 static BOOL AddTrayIcon(HWND hwnd)
1458 NOTIFYICONDATA tnid;
1461 #ifdef NIM_SETVERSION
1463 res = Shell_NotifyIcon(NIM_SETVERSION, &tnid);
1466 tnid.cbSize = sizeof(NOTIFYICONDATA);
1468 tnid.uID = 1; /* unique within this systray use */
1469 tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
1470 tnid.uCallbackMessage = WM_SYSTRAY;
1471 tnid.hIcon = hicon = LoadIcon(instance, MAKEINTRESOURCE(201));
1472 strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
1474 res = Shell_NotifyIcon(NIM_ADD, &tnid);
1476 if (hicon) DestroyIcon(hicon);
1481 /* Update the saved-sessions menu. */
1482 static void update_sessions(void)
1486 TCHAR buf[MAX_PATH + 1];
1489 int index_key, index_menu;
1494 if(ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, PUTTY_REGKEY, &hkey))
1497 for(num_entries = GetMenuItemCount(session_menu);
1498 num_entries > initial_menuitems_count;
1500 RemoveMenu(session_menu, 0, MF_BYPOSITION);
1505 while(ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) {
1506 TCHAR session_name[MAX_PATH + 1];
1507 unmungestr(buf, session_name, MAX_PATH);
1508 if(strcmp(buf, PUTTY_DEFAULT) != 0) {
1509 memset(&mii, 0, sizeof(mii));
1510 mii.cbSize = sizeof(mii);
1511 mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
1512 mii.fType = MFT_STRING;
1513 mii.fState = MFS_ENABLED;
1514 mii.wID = (index_menu * 16) + IDM_SESSIONS_BASE;
1515 mii.dwTypeData = session_name;
1516 InsertMenuItem(session_menu, index_menu, TRUE, &mii);
1524 if(index_menu == 0) {
1525 mii.cbSize = sizeof(mii);
1526 mii.fMask = MIIM_TYPE | MIIM_STATE;
1527 mii.fType = MFT_STRING;
1528 mii.fState = MFS_GRAYED;
1529 mii.dwTypeData = _T("(No sessions)");
1530 InsertMenuItem(session_menu, index_menu, TRUE, &mii);
1534 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1535 WPARAM wParam, LPARAM lParam)
1538 static int menuinprogress;
1539 static UINT msgTaskbarCreated = 0;
1543 msgTaskbarCreated = RegisterWindowMessage(_T("TaskbarCreated"));
1546 if (message==msgTaskbarCreated) {
1548 * Explorer has been restarted, so the tray icon will
1556 if (lParam == WM_RBUTTONUP) {
1558 GetCursorPos(&cursorpos);
1559 PostMessage(hwnd, WM_SYSTRAY2, cursorpos.x, cursorpos.y);
1560 } else if (lParam == WM_LBUTTONDBLCLK) {
1561 /* Equivalent to IDM_VIEWKEYS. */
1562 PostMessage(hwnd, WM_COMMAND, IDM_VIEWKEYS, 0);
1566 if (!menuinprogress) {
1569 SetForegroundWindow(hwnd);
1570 ret = TrackPopupMenu(systray_menu,
1571 TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
1573 wParam, lParam, 0, hwnd, NULL);
1579 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1581 if((int)ShellExecute(hwnd, NULL, putty_path, _T(""), _T(""),
1583 MessageBox(NULL, "Unable to execute PuTTY!",
1584 "Error", MB_OK | MB_ICONERROR);
1589 SendMessage(passphrase_box, WM_CLOSE, 0, 0);
1590 SendMessage(hwnd, WM_CLOSE, 0, 0);
1594 keylist = CreateDialog(instance, MAKEINTRESOURCE(211),
1596 ShowWindow(keylist, SW_SHOWNORMAL);
1598 * Sometimes the window comes up minimised / hidden
1599 * for no obvious reason. Prevent this.
1601 SetForegroundWindow(keylist);
1602 SetWindowPos(keylist, HWND_TOP, 0, 0, 0, 0,
1603 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1607 if (passphrase_box) {
1608 MessageBeep(MB_ICONERROR);
1609 SetForegroundWindow(passphrase_box);
1612 prompt_add_keyfile();
1616 aboutbox = CreateDialog(instance, MAKEINTRESOURCE(213),
1618 ShowWindow(aboutbox, SW_SHOWNORMAL);
1620 * Sometimes the window comes up minimised / hidden
1621 * for no obvious reason. Prevent this.
1623 SetForegroundWindow(aboutbox);
1624 SetWindowPos(aboutbox, HWND_TOP, 0, 0, 0, 0,
1625 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1630 WinHelp(main_hwnd, help_path, HELP_COMMAND,
1631 (DWORD)"JI(`',`pageant.general')");
1632 requested_help = TRUE;
1637 if(wParam >= IDM_SESSIONS_BASE && wParam <= IDM_SESSIONS_MAX) {
1639 TCHAR buf[MAX_PATH + 1];
1640 TCHAR param[MAX_PATH + 1];
1641 memset(&mii, 0, sizeof(mii));
1642 mii.cbSize = sizeof(mii);
1643 mii.fMask = MIIM_TYPE;
1645 mii.dwTypeData = buf;
1646 GetMenuItemInfo(session_menu, wParam, FALSE, &mii);
1648 strcat(param, mii.dwTypeData);
1649 if((int)ShellExecute(hwnd, NULL, putty_path, param,
1650 _T(""), SW_SHOW) <= 32) {
1651 MessageBox(NULL, "Unable to execute PuTTY!", "Error",
1652 MB_OK | MB_ICONERROR);
1660 if (requested_help) {
1661 WinHelp(main_hwnd, help_path, HELP_QUIT, 0);
1662 requested_help = FALSE;
1668 COPYDATASTRUCT *cds;
1674 PSID mapowner, procowner;
1675 PSECURITY_DESCRIPTOR psd1 = NULL, psd2 = NULL;
1679 cds = (COPYDATASTRUCT *) lParam;
1680 if (cds->dwData != AGENT_COPYDATA_ID)
1681 return 0; /* not our message, mate */
1682 mapname = (char *) cds->lpData;
1683 if (mapname[cds->cbData - 1] != '\0')
1684 return 0; /* failure to be ASCIZ! */
1686 debug(("mapname is :%s:\n", mapname));
1688 filemap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, mapname);
1690 debug(("filemap is %p\n", filemap));
1692 if (filemap != NULL && filemap != INVALID_HANDLE_VALUE) {
1696 if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE,
1697 GetCurrentProcessId())) ==
1700 debug(("couldn't get handle for process\n"));
1704 if (getsecurityinfo(proc, SE_KERNEL_OBJECT,
1705 OWNER_SECURITY_INFORMATION,
1706 &procowner, NULL, NULL, NULL,
1707 &psd2) != ERROR_SUCCESS) {
1709 debug(("couldn't get owner info for process\n"));
1712 return 0; /* unable to get security info */
1715 if ((rc = getsecurityinfo(filemap, SE_KERNEL_OBJECT,
1716 OWNER_SECURITY_INFORMATION,
1717 &mapowner, NULL, NULL, NULL,
1718 &psd1) != ERROR_SUCCESS)) {
1721 ("couldn't get owner info for filemap: %d\n",
1727 debug(("got security stuff\n"));
1729 if (!EqualSid(mapowner, procowner))
1730 return 0; /* security ID mismatch! */
1732 debug(("security stuff matched\n"));
1738 debug(("security APIs not present\n"));
1742 p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
1744 debug(("p is %p\n", p));
1747 for (i = 0; i < 5; i++)
1750 ((unsigned char *) p)[i]));}
1756 CloseHandle(filemap);
1761 return DefWindowProc(hwnd, message, wParam, lParam);
1765 * Fork and Exec the command in cmdline. [DBW]
1767 void spawn_cmd(char *cmdline, char * args, int show)
1769 if (ShellExecute(NULL, _T("open"), cmdline,
1770 args, NULL, show) <= (HINSTANCE) 32) {
1772 sprintf(sMsg, _T("Failed to run \"%.100s\", Error: %d"), cmdline,
1773 (int)GetLastError());
1774 MessageBox(NULL, sMsg, APPNAME, MB_OK | MB_ICONEXCLAMATION);
1778 void cleanup_exit(int code) { exit(code); }
1780 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
1786 char *command = NULL;
1790 * Determine whether we're an NT system (should have security
1791 * APIs) or a non-NT system (don't do security).
1793 memset(&osi, 0, sizeof(OSVERSIONINFO));
1794 osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
1795 if (GetVersionEx(&osi) && osi.dwPlatformId == VER_PLATFORM_WIN32_NT) {
1796 has_security = TRUE;
1798 has_security = FALSE;
1803 * Attempt to get the security API we need.
1805 advapi = LoadLibrary("ADVAPI32.DLL");
1807 (gsi_fn_t) GetProcAddress(advapi, "GetSecurityInfo");
1808 if (!getsecurityinfo) {
1810 "Unable to access security APIs. Pageant will\n"
1811 "not run, in case it causes a security breach.",
1812 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
1817 "This program has been compiled for Win9X and will\n"
1818 "not run on NT, in case it causes a security breach.",
1819 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
1828 * See if we can find our Help file.
1831 char b[2048], *p, *q, *r;
1833 GetModuleFileName(NULL, b, sizeof(b) - 1);
1835 p = strrchr(b, '\\');
1836 if (p && p >= r) r = p+1;
1837 q = strrchr(b, ':');
1838 if (q && q >= r) r = q+1;
1839 strcpy(r, "putty.hlp");
1840 if ( (fp = fopen(b, "r")) != NULL) {
1841 help_path = dupstr(b);
1848 * Look for the PuTTY binary (we will enable the saved session
1849 * submenu if we find it).
1852 char b[2048], *p, *q, *r;
1854 GetModuleFileName(NULL, b, sizeof(b) - 1);
1856 p = strrchr(b, '\\');
1857 if (p && p >= r) r = p+1;
1858 q = strrchr(b, ':');
1859 if (q && q >= r) r = q+1;
1860 strcpy(r, "putty.exe");
1861 if ( (fp = fopen(b, "r")) != NULL) {
1862 putty_path = dupstr(b);
1869 * Find out if Pageant is already running.
1871 already_running = FALSE;
1873 already_running = TRUE;
1878 wndclass.lpfnWndProc = WndProc;
1879 wndclass.cbClsExtra = 0;
1880 wndclass.cbWndExtra = 0;
1881 wndclass.hInstance = inst;
1882 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
1883 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
1884 wndclass.hbrBackground = GetStockObject(BLACK_BRUSH);
1885 wndclass.lpszMenuName = NULL;
1886 wndclass.lpszClassName = APPNAME;
1888 RegisterClass(&wndclass);
1891 main_hwnd = keylist = NULL;
1893 main_hwnd = CreateWindow(APPNAME, APPNAME,
1894 WS_OVERLAPPEDWINDOW | WS_VSCROLL,
1895 CW_USEDEFAULT, CW_USEDEFAULT,
1896 100, 100, NULL, NULL, inst, NULL);
1898 /* Set up a system tray icon */
1899 AddTrayIcon(main_hwnd);
1901 /* Accelerators used: nsvkxa */
1902 systray_menu = CreatePopupMenu();
1904 session_menu = CreateMenu();
1905 AppendMenu(systray_menu, MF_ENABLED, IDM_PUTTY, "&New Session");
1906 AppendMenu(systray_menu, MF_POPUP | MF_ENABLED,
1907 (UINT) session_menu, "&Saved Sessions");
1908 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
1910 AppendMenu(systray_menu, MF_ENABLED, IDM_VIEWKEYS,
1912 AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key");
1913 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
1915 AppendMenu(systray_menu, MF_ENABLED, IDM_HELP, "&Help");
1916 AppendMenu(systray_menu, MF_ENABLED, IDM_ABOUT, "&About");
1917 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
1918 AppendMenu(systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit");
1919 initial_menuitems_count = GetMenuItemCount(session_menu);
1921 ShowWindow(main_hwnd, SW_HIDE);
1924 * Initialise storage for RSA keys.
1926 rsakeys = newtree234(cmpkeys_rsa);
1927 ssh2keys = newtree234(cmpkeys_ssh2);
1932 * Initialise storage for short-term passphrase cache.
1934 passphrases = newtree234(NULL);
1937 * Process the command line and add keys as listed on it.
1938 * If we already determined that we need to spawn a program from above we
1939 * need to ignore the first two arguments. [DBW]
1946 while (*p && isspace(*p))
1948 if (*p && !isspace(*p)) {
1949 char *q = p, *pp = p;
1950 while (*p && (inquotes || !isspace(*p))) {
1952 inquotes = !inquotes;
1963 if (!strcmp(q, "-c")) {
1965 * If we see `-c', then the rest of the
1966 * command line should be treated as a
1967 * command to be spawned.
1969 while (*p && isspace(*p))
1982 * Forget any passphrase that we retained while going over
1983 * command line keyfiles.
1985 forget_passphrases();
1989 if (command[0] == '"')
1990 args = strchr(++command, '"');
1992 args = strchr(command, ' ');
1995 while(*args && isspace(*args)) args++;
1997 spawn_cmd(command, args, show);
2001 * If Pageant was already running, we leave now. If we haven't
2002 * even taken any auxiliary action (spawned a command or added
2005 if (already_running) {
2006 if (!command && !added_keys) {
2007 MessageBox(NULL, "Pageant is already running", "Pageant Error",
2008 MB_ICONERROR | MB_OK);
2011 FreeLibrary(advapi);
2016 * Main message loop.
2018 while (GetMessage(&msg, NULL, 0, 0) == 1) {
2019 TranslateMessage(&msg);
2020 DispatchMessage(&msg);
2023 /* Clean up the system tray icon */
2025 NOTIFYICONDATA tnid;
2027 tnid.cbSize = sizeof(NOTIFYICONDATA);
2028 tnid.hWnd = main_hwnd;
2031 Shell_NotifyIcon(NIM_DELETE, &tnid);
2033 DestroyMenu(systray_menu);
2037 FreeLibrary(advapi);