2 * Pageant: the PuTTY Authentication Agent.
18 #define IDI_MAINICON 200
19 #define IDI_TRAYICON 201
21 #define WM_XUSER (WM_USER + 0x2000)
22 #define WM_SYSTRAY (WM_XUSER + 6)
23 #define WM_SYSTRAY2 (WM_XUSER + 7)
25 #define AGENT_COPYDATA_ID 0x804e50ba /* random goop */
28 * FIXME: maybe some day we can sort this out ...
30 #define AGENT_MAX_MSGLEN 8192
32 #define IDM_CLOSE 0x0010
33 #define IDM_VIEWKEYS 0x0020
34 #define IDM_ADDKEY 0x0030
35 #define IDM_HELP 0x0040
36 #define IDM_ABOUT 0x0050
38 #define APPNAME "Pageant"
42 static HINSTANCE instance;
43 static HWND main_hwnd;
46 static HMENU systray_menu;
47 static int already_running;
48 static int requested_help;
50 static char *help_path;
52 static tree234 *rsakeys, *ssh2keys;
54 static int has_security;
56 typedef DWORD(WINAPI * gsi_fn_t)
57 (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION,
58 PSID *, PSID *, PACL *, PACL *, PSECURITY_DESCRIPTOR *);
59 static gsi_fn_t getsecurityinfo;
63 * Exports from pageantc.c
65 void agent_query(void *in, int inlen, void **out, int *outlen);
66 int agent_exists(void);
69 * We need this to link with the RSA code, because rsaencrypt()
70 * pads its data with random bytes. Since we only use rsadecrypt()
71 * and the signing functions, which are deterministic, this should
74 * If it _is_ called, there is a _serious_ problem, because it
75 * won't generate true random numbers. So we must scream, panic,
76 * and exit immediately if that should happen.
80 MessageBox(main_hwnd, "Internal Error", APPNAME, MB_OK | MB_ICONERROR);
82 /* this line can't be reached but it placates MSVC's warnings :-) */
87 * Blob structure for passing to the asymmetric SSH2 key compare
88 * function, prototyped here.
94 static int cmpkeys_ssh2_asymm(void *av, void *bv);
97 * This function is needed to link with the DES code. We need not
98 * have it do anything at all.
100 void logevent(char *msg)
104 #define GET_32BIT(cp) \
105 (((unsigned long)(unsigned char)(cp)[0] << 24) | \
106 ((unsigned long)(unsigned char)(cp)[1] << 16) | \
107 ((unsigned long)(unsigned char)(cp)[2] << 8) | \
108 ((unsigned long)(unsigned char)(cp)[3]))
110 #define PUT_32BIT(cp, value) { \
111 (cp)[0] = (unsigned char)((value) >> 24); \
112 (cp)[1] = (unsigned char)((value) >> 16); \
113 (cp)[2] = (unsigned char)((value) >> 8); \
114 (cp)[3] = (unsigned char)(value); }
116 #define PASSPHRASE_MAXLEN 512
118 struct PassphraseProcStruct {
123 static tree234 *passphrases = NULL;
126 * After processing a list of filenames, we want to forget the
129 static void forget_passphrases(void)
131 while (count234(passphrases) > 0) {
132 char *pp = index234(passphrases, 0);
133 memset(pp, 0, strlen(pp));
134 delpos234(passphrases, 0);
140 * Dialog-box function for the Licence box.
142 static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
143 WPARAM wParam, LPARAM lParam)
149 switch (LOWORD(wParam)) {
163 * Dialog-box function for the About box.
165 static int CALLBACK AboutProc(HWND hwnd, UINT msg,
166 WPARAM wParam, LPARAM lParam)
170 SetDlgItemText(hwnd, 100, ver);
173 switch (LOWORD(wParam)) {
179 EnableWindow(hwnd, 0);
180 DialogBox(instance, MAKEINTRESOURCE(214), NULL, LicenceProc);
181 EnableWindow(hwnd, 1);
182 SetActiveWindow(hwnd);
194 static HWND passphrase_box;
197 * Dialog-box function for the passphrase box.
199 static int 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);
231 SetDlgItemText(hwnd, 102, passphrase);
234 switch (LOWORD(wParam)) {
244 case 102: /* edit box */
245 if ((HIWORD(wParam) == EN_CHANGE) && passphrase) {
246 GetDlgItemText(hwnd, 102, passphrase,
247 PASSPHRASE_MAXLEN - 1);
248 passphrase[PASSPHRASE_MAXLEN - 1] = '\0';
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 static void keylist_update(void)
286 struct ssh2_userkey *skey;
290 SendDlgItemMessage(keylist, 100, LB_RESETCONTENT, 0, 0);
291 for (i = 0; NULL != (rkey = index234(rsakeys, 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 = index234(ssh2keys, i)); i++) {
310 char listentry[512], *p;
313 * Replace two spaces in the fingerprint with tabs, for
314 * nice alignment in the box.
316 p = skey->alg->fingerprint(skey->data);
317 strncpy(listentry, p, sizeof(listentry));
318 p = strchr(listentry, ' ');
321 p = strchr(listentry, ' ');
324 len = strlen(listentry);
325 if (len < sizeof(listentry) - 2) {
326 listentry[len] = '\t';
327 strncpy(listentry + len + 1, skey->comment,
328 sizeof(listentry) - len - 1);
330 SendDlgItemMessage(keylist, 100, LB_ADDSTRING, 0,
333 SendDlgItemMessage(keylist, 100, LB_SETCURSEL, (WPARAM) - 1, 0);
338 * This function loads a key from a file and adds it.
340 static void add_keyfile(char *filename)
342 char passphrase[PASSPHRASE_MAXLEN];
343 struct RSAKey *rkey = NULL;
344 struct ssh2_userkey *skey = NULL;
349 struct PassphraseProcStruct pps;
353 ver = keyfile_version(filename);
355 MessageBox(NULL, "Couldn't load private key.", APPNAME,
356 MB_OK | MB_ICONERROR);
361 needs_pass = rsakey_encrypted(filename, &comment);
363 needs_pass = ssh2_userkey_encrypted(filename, &comment);
366 rkey = smalloc(sizeof(*rkey));
367 pps.passphrase = passphrase;
368 pps.comment = comment;
372 /* try all the remembered passphrases first */
373 char *pp = index234(passphrases, attempts);
375 strcpy(passphrase, pp);
379 dlgret = DialogBoxParam(instance, MAKEINTRESOURCE(210),
380 NULL, PassphraseProc, (LPARAM) & pps);
381 passphrase_box = NULL;
387 return; /* operation cancelled */
393 ret = loadrsakey(filename, rkey, passphrase);
395 skey = ssh2_load_userkey(filename, passphrase);
396 if (skey == SSH2_WRONG_PASSPHRASE)
406 /* if they typed in an ok passphrase, remember it */
407 if(original_pass && ret) {
408 char *pp = dupstr(passphrase);
409 addpos234(passphrases, pp, 0);
415 MessageBox(NULL, "Couldn't load private key.", APPNAME,
416 MB_OK | MB_ICONERROR);
422 if (already_running) {
423 unsigned char *request, *response;
425 int reqlen, clen, resplen;
427 clen = strlen(rkey->comment);
429 reqlen = 4 + 1 + /* length, message type */
431 ssh1_bignum_length(rkey->modulus) +
432 ssh1_bignum_length(rkey->exponent) +
433 ssh1_bignum_length(rkey->private_exponent) +
434 ssh1_bignum_length(rkey->iqmp) +
435 ssh1_bignum_length(rkey->p) +
436 ssh1_bignum_length(rkey->q) + 4 + clen /* comment */
439 request = smalloc(reqlen);
441 request[4] = SSH1_AGENTC_ADD_RSA_IDENTITY;
443 PUT_32BIT(request + reqlen, bignum_bitcount(rkey->modulus));
445 reqlen += ssh1_write_bignum(request + reqlen, rkey->modulus);
446 reqlen += ssh1_write_bignum(request + reqlen, rkey->exponent);
448 ssh1_write_bignum(request + reqlen,
449 rkey->private_exponent);
450 reqlen += ssh1_write_bignum(request + reqlen, rkey->iqmp);
451 reqlen += ssh1_write_bignum(request + reqlen, rkey->p);
452 reqlen += ssh1_write_bignum(request + reqlen, rkey->q);
453 PUT_32BIT(request + reqlen, clen);
454 memcpy(request + reqlen + 4, rkey->comment, clen);
456 PUT_32BIT(request, reqlen - 4);
458 agent_query(request, reqlen, &vresponse, &resplen);
459 response = vresponse;
460 if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
461 MessageBox(NULL, "The already running Pageant "
462 "refused to add the key.", APPNAME,
463 MB_OK | MB_ICONERROR);
465 if (add234(rsakeys, rkey) != rkey)
466 sfree(rkey); /* already present, don't waste RAM */
469 if (already_running) {
470 unsigned char *request, *response;
472 int reqlen, alglen, clen, keybloblen, resplen;
473 alglen = strlen(skey->alg->name);
474 clen = strlen(skey->comment);
476 keybloblen = skey->alg->openssh_fmtkey(skey->data, NULL, 0);
478 reqlen = 4 + 1 + /* length, message type */
479 4 + alglen + /* algorithm name */
480 keybloblen + /* key data */
481 4 + clen /* comment */
484 request = smalloc(reqlen);
486 request[4] = SSH2_AGENTC_ADD_IDENTITY;
488 PUT_32BIT(request + reqlen, alglen);
490 memcpy(request + reqlen, skey->alg->name, alglen);
492 reqlen += skey->alg->openssh_fmtkey(skey->data,
495 PUT_32BIT(request + reqlen, clen);
496 memcpy(request + reqlen + 4, skey->comment, clen);
497 PUT_32BIT(request, reqlen - 4);
500 agent_query(request, reqlen, &vresponse, &resplen);
501 response = vresponse;
502 if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
503 MessageBox(NULL, "The already running Pageant"
504 "refused to add the key.", APPNAME,
505 MB_OK | MB_ICONERROR);
507 if (add234(ssh2keys, skey) != skey) {
508 skey->alg->freekey(skey->data);
509 sfree(skey); /* already present, don't waste RAM */
516 * This is the main agent function that answers messages.
518 static void answer_msg(void *msg)
520 unsigned char *p = msg;
521 unsigned char *ret = msg;
525 * Get the message type.
531 case SSH1_AGENTC_REQUEST_RSA_IDENTITIES:
533 * Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER.
541 * Count up the number and length of keys we hold.
544 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
546 len += 4; /* length field */
547 len += ssh1_bignum_length(key->exponent);
548 len += ssh1_bignum_length(key->modulus);
549 len += 4 + strlen(key->comment);
553 * Packet header is the obvious five bytes, plus four
554 * bytes for the key count.
557 if (len > AGENT_MAX_MSGLEN)
558 goto failure; /* aaargh! too much stuff! */
559 PUT_32BIT(ret, len - 4);
560 ret[4] = SSH1_AGENT_RSA_IDENTITIES_ANSWER;
561 PUT_32BIT(ret + 5, nkeys);
563 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
564 PUT_32BIT(p, bignum_bitcount(key->modulus));
566 p += ssh1_write_bignum(p, key->exponent);
567 p += ssh1_write_bignum(p, key->modulus);
568 PUT_32BIT(p, strlen(key->comment));
569 memcpy(p + 4, key->comment, strlen(key->comment));
570 p += 4 + strlen(key->comment);
574 case SSH2_AGENTC_REQUEST_IDENTITIES:
576 * Reply with SSH2_AGENT_IDENTITIES_ANSWER.
579 struct ssh2_userkey *key;
586 * Count up the number and length of keys we hold.
589 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
591 len += 4; /* length field */
592 blob = key->alg->public_blob(key->data, &bloblen);
595 len += 4 + strlen(key->comment);
599 * Packet header is the obvious five bytes, plus four
600 * bytes for the key count.
603 if (len > AGENT_MAX_MSGLEN)
604 goto failure; /* aaargh! too much stuff! */
605 PUT_32BIT(ret, len - 4);
606 ret[4] = SSH2_AGENT_IDENTITIES_ANSWER;
607 PUT_32BIT(ret + 5, nkeys);
609 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
610 blob = key->alg->public_blob(key->data, &bloblen);
611 PUT_32BIT(p, bloblen);
613 memcpy(p, blob, bloblen);
616 PUT_32BIT(p, strlen(key->comment));
617 memcpy(p + 4, key->comment, strlen(key->comment));
618 p += 4 + strlen(key->comment);
622 case SSH1_AGENTC_RSA_CHALLENGE:
624 * Reply with either SSH1_AGENT_RSA_RESPONSE or
625 * SSH_AGENT_FAILURE, depending on whether we have that key
629 struct RSAKey reqkey, *key;
630 Bignum challenge, response;
631 unsigned char response_source[48], response_md5[16];
632 struct MD5Context md5c;
636 p += ssh1_read_bignum(p, &reqkey.exponent);
637 p += ssh1_read_bignum(p, &reqkey.modulus);
638 p += ssh1_read_bignum(p, &challenge);
639 memcpy(response_source + 32, p, 16);
641 if (GET_32BIT(p) != 1 ||
642 (key = find234(rsakeys, &reqkey, NULL)) == NULL) {
643 freebn(reqkey.exponent);
644 freebn(reqkey.modulus);
648 response = rsadecrypt(challenge, key);
649 for (i = 0; i < 32; i++)
650 response_source[i] = bignum_byte(response, 31 - i);
653 MD5Update(&md5c, response_source, 48);
654 MD5Final(response_md5, &md5c);
655 memset(response_source, 0, 48); /* burn the evidence */
656 freebn(response); /* and that evidence */
657 freebn(challenge); /* yes, and that evidence */
658 freebn(reqkey.exponent); /* and free some memory ... */
659 freebn(reqkey.modulus); /* ... while we're at it. */
662 * Packet is the obvious five byte header, plus sixteen
666 PUT_32BIT(ret, len - 4);
667 ret[4] = SSH1_AGENT_RSA_RESPONSE;
668 memcpy(ret + 5, response_md5, 16);
671 case SSH2_AGENTC_SIGN_REQUEST:
673 * Reply with either SSH2_AGENT_SIGN_RESPONSE or
674 * SSH_AGENT_FAILURE, depending on whether we have that key
678 struct ssh2_userkey *key;
680 unsigned char *data, *signature;
681 int datalen, siglen, len;
683 b.len = GET_32BIT(p);
687 datalen = GET_32BIT(p);
690 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
693 signature = key->alg->sign(key->data, data, datalen, &siglen);
694 len = 5 + 4 + siglen;
695 PUT_32BIT(ret, len - 4);
696 ret[4] = SSH2_AGENT_SIGN_RESPONSE;
697 PUT_32BIT(ret + 5, siglen);
698 memcpy(ret + 5 + 4, signature, siglen);
702 case SSH1_AGENTC_ADD_RSA_IDENTITY:
704 * Add to the list and return SSH_AGENT_SUCCESS, or
705 * SSH_AGENT_FAILURE if the key was malformed.
711 key = smalloc(sizeof(struct RSAKey));
712 memset(key, 0, sizeof(struct RSAKey));
713 p += makekey(p, key, NULL, 1);
714 p += makeprivate(p, key);
715 p += ssh1_read_bignum(p, &key->iqmp); /* p^-1 mod q */
716 p += ssh1_read_bignum(p, &key->p); /* p */
717 p += ssh1_read_bignum(p, &key->q); /* q */
718 commentlen = GET_32BIT(p);
719 comment = smalloc(commentlen+1);
721 memcpy(comment, p + 4, commentlen);
722 comment[commentlen] = '\0';
723 key->comment = comment;
726 ret[4] = SSH_AGENT_FAILURE;
727 if (add234(rsakeys, key) == key) {
729 ret[4] = SSH_AGENT_SUCCESS;
736 case SSH2_AGENTC_ADD_IDENTITY:
738 * Add to the list and return SSH_AGENT_SUCCESS, or
739 * SSH_AGENT_FAILURE if the key was malformed.
742 struct ssh2_userkey *key;
747 key = smalloc(sizeof(struct ssh2_userkey));
749 alglen = GET_32BIT(p);
753 /* Add further algorithm names here. */
754 if (alglen == 7 && !memcmp(alg, "ssh-rsa", 7))
756 else if (alglen == 7 && !memcmp(alg, "ssh-dss", 7))
764 GET_32BIT((unsigned char *) msg) - (p -
765 (unsigned char *) msg -
767 key->data = key->alg->openssh_createkey(&p, &bloblen);
772 commlen = GET_32BIT(p);
775 comment = smalloc(commlen + 1);
777 memcpy(comment, p, commlen);
778 comment[commlen] = '\0';
780 key->comment = comment;
783 ret[4] = SSH_AGENT_FAILURE;
784 if (add234(ssh2keys, key) == key) {
786 ret[4] = SSH_AGENT_SUCCESS;
788 key->alg->freekey(key->data);
794 case SSH1_AGENTC_REMOVE_RSA_IDENTITY:
796 * Remove from the list and return SSH_AGENT_SUCCESS, or
797 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
801 struct RSAKey reqkey, *key;
803 p += makekey(p, &reqkey, NULL, 0);
804 key = find234(rsakeys, &reqkey, NULL);
805 freebn(reqkey.exponent);
806 freebn(reqkey.modulus);
808 ret[4] = SSH_AGENT_FAILURE;
810 del234(rsakeys, key);
814 ret[4] = SSH_AGENT_SUCCESS;
818 case SSH2_AGENTC_REMOVE_IDENTITY:
820 * Remove from the list and return SSH_AGENT_SUCCESS, or
821 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
825 struct ssh2_userkey *key;
828 b.len = GET_32BIT(p);
832 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
837 ret[4] = SSH_AGENT_FAILURE;
839 del234(ssh2keys, key);
841 key->alg->freekey(key->data);
843 ret[4] = SSH_AGENT_SUCCESS;
847 case SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES:
849 * Remove all SSH1 keys. Always returns success.
854 while ((rkey = index234(rsakeys, 0)) != NULL) {
855 del234(rsakeys, rkey);
862 ret[4] = SSH_AGENT_SUCCESS;
865 case SSH2_AGENTC_REMOVE_ALL_IDENTITIES:
867 * Remove all SSH2 keys. Always returns success.
870 struct ssh2_userkey *skey;
872 while ((skey = index234(ssh2keys, 0)) != NULL) {
873 del234(ssh2keys, skey);
874 skey->alg->freekey(skey->data);
880 ret[4] = SSH_AGENT_SUCCESS;
886 * Unrecognised message. Return SSH_AGENT_FAILURE.
889 ret[4] = SSH_AGENT_FAILURE;
895 * Key comparison function for the 2-3-4 tree of RSA keys.
897 static int cmpkeys_rsa(void *av, void *bv)
899 struct RSAKey *a = (struct RSAKey *) av;
900 struct RSAKey *b = (struct RSAKey *) bv;
907 * Compare by length of moduli.
909 alen = bignum_bitcount(am);
910 blen = bignum_bitcount(bm);
913 else if (alen < blen)
916 * Now compare by moduli themselves.
918 alen = (alen + 7) / 8; /* byte count */
921 abyte = bignum_byte(am, alen);
922 bbyte = bignum_byte(bm, alen);
925 else if (abyte < bbyte)
935 * Key comparison function for the 2-3-4 tree of SSH2 keys.
937 static int cmpkeys_ssh2(void *av, void *bv)
939 struct ssh2_userkey *a = (struct ssh2_userkey *) av;
940 struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
943 unsigned char *ablob, *bblob;
947 * Compare purely by public blob.
949 ablob = a->alg->public_blob(a->data, &alen);
950 bblob = b->alg->public_blob(b->data, &blen);
953 for (i = 0; i < alen && i < blen; i++) {
954 if (ablob[i] < bblob[i]) {
957 } else if (ablob[i] > bblob[i]) {
962 if (c == 0 && i < alen)
963 c = +1; /* a is longer */
964 if (c == 0 && i < blen)
965 c = -1; /* a is longer */
974 * Key comparison function for looking up a blob in the 2-3-4 tree
977 static int cmpkeys_ssh2_asymm(void *av, void *bv)
979 struct blob *a = (struct blob *) av;
980 struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
983 unsigned char *ablob, *bblob;
987 * Compare purely by public blob.
991 bblob = b->alg->public_blob(b->data, &blen);
994 for (i = 0; i < alen && i < blen; i++) {
995 if (ablob[i] < bblob[i]) {
998 } else if (ablob[i] > bblob[i]) {
1003 if (c == 0 && i < alen)
1004 c = +1; /* a is longer */
1005 if (c == 0 && i < blen)
1006 c = -1; /* a is longer */
1014 * Prompt for a key file to add, and add it.
1016 static void prompt_add_keyfile(void)
1019 char filename[FILENAME_MAX];
1020 char *filelist = smalloc(8192);
1024 memset(&of, 0, sizeof(of));
1025 #ifdef OPENFILENAME_SIZE_VERSION_400
1026 of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
1028 of.lStructSize = sizeof(of);
1030 of.hwndOwner = main_hwnd;
1031 of.lpstrFilter = "All Files\0*\0\0\0";
1032 of.lpstrCustomFilter = NULL;
1033 of.nFilterIndex = 1;
1034 of.lpstrFile = filelist;
1036 of.nMaxFile = FILENAME_MAX;
1037 of.lpstrFileTitle = NULL;
1038 of.lpstrInitialDir = NULL;
1039 of.lpstrTitle = "Select Private Key File";
1040 of.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER;
1041 if (GetOpenFileName(&of)) {
1042 if(strlen(filelist) > of.nFileOffset)
1043 /* Only one filename returned? */
1044 add_keyfile(filelist);
1046 /* we are returned a bunch of strings, end to
1047 * end. first string is the directory, the
1048 * rest the filenames. terminated with an
1051 filewalker = filelist;
1052 dirlen = strlen(filewalker);
1053 if(dirlen > FILENAME_MAX - 8) return;
1054 memcpy(filename, filewalker, dirlen);
1056 filewalker += dirlen + 1;
1057 filename[dirlen++] = '\\';
1059 /* then go over names one by one */
1061 n = strlen(filewalker) + 1;
1062 /* end of the list */
1065 /* too big, shouldn't happen */
1066 if(n + dirlen > FILENAME_MAX)
1069 memcpy(filename + dirlen, filewalker, n);
1072 add_keyfile(filename);
1077 forget_passphrases();
1083 * Dialog-box function for the key list box.
1085 static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
1086 WPARAM wParam, LPARAM lParam)
1088 struct RSAKey *rkey;
1089 struct ssh2_userkey *skey;
1094 * Centre the window.
1096 { /* centre the window */
1100 hw = GetDesktopWindow();
1101 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
1103 (rs.right + rs.left + rd.left - rd.right) / 2,
1104 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
1105 rd.right - rd.left, rd.bottom - rd.top, TRUE);
1109 SetWindowLong(hwnd, GWL_EXSTYLE,
1110 GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_CONTEXTHELP);
1112 HWND item = GetDlgItem(hwnd, 103); /* the Help button */
1114 DestroyWindow(item);
1116 requested_help = FALSE;
1120 static int tabs[] = { 35, 60, 210 };
1121 SendDlgItemMessage(hwnd, 100, LB_SETTABSTOPS,
1122 sizeof(tabs) / sizeof(*tabs),
1128 switch (LOWORD(wParam)) {
1132 DestroyWindow(hwnd);
1134 case 101: /* add key */
1135 if (HIWORD(wParam) == BN_CLICKED ||
1136 HIWORD(wParam) == BN_DOUBLECLICKED) {
1137 if (passphrase_box) {
1138 MessageBeep(MB_ICONERROR);
1139 SetForegroundWindow(passphrase_box);
1142 prompt_add_keyfile();
1145 case 102: /* remove key */
1146 if (HIWORD(wParam) == BN_CLICKED ||
1147 HIWORD(wParam) == BN_DOUBLECLICKED) {
1152 /* our counter within the array of selected items */
1155 /* get the number of items selected in the list */
1157 SendDlgItemMessage(hwnd, 100, LB_GETSELCOUNT, 0, 0);
1159 /* none selected? that was silly */
1160 if (numSelected == 0) {
1165 /* get item indices in an array */
1166 selectedArray = smalloc(numSelected * sizeof(int));
1167 SendDlgItemMessage(hwnd, 100, LB_GETSELITEMS,
1168 numSelected, (WPARAM)selectedArray);
1170 itemNum = numSelected - 1;
1171 rCount = count234(rsakeys);
1172 sCount = count234(ssh2keys);
1174 /* go through the non-rsakeys until we've covered them all,
1175 * and/or we're out of selected items to check. note that
1176 * we go *backwards*, to avoid complications from deleting
1177 * things hence altering the offset of subsequent items
1179 for (i = sCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1180 skey = index234(ssh2keys, i);
1182 if (selectedArray[itemNum] == rCount + i) {
1183 del234(ssh2keys, skey);
1184 skey->alg->freekey(skey->data);
1190 /* do the same for the rsa keys */
1191 for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1192 rkey = index234(rsakeys, i);
1194 if(selectedArray[itemNum] == i) {
1195 del234(rsakeys, rkey);
1202 sfree(selectedArray);
1206 case 103: /* help */
1207 if (HIWORD(wParam) == BN_CLICKED ||
1208 HIWORD(wParam) == BN_DOUBLECLICKED) {
1210 WinHelp(main_hwnd, help_path, HELP_COMMAND,
1211 (DWORD)"JI(`',`pageant.general')");
1212 requested_help = TRUE;
1220 int id = ((LPHELPINFO)lParam)->iCtrlId;
1223 case 100: cmd = "JI(`',`pageant.keylist')"; break;
1224 case 101: cmd = "JI(`',`pageant.addkey')"; break;
1225 case 102: cmd = "JI(`',`pageant.remkey')"; break;
1228 WinHelp(main_hwnd, help_path, HELP_COMMAND, (DWORD)cmd);
1229 requested_help = TRUE;
1237 DestroyWindow(hwnd);
1243 /* Set up a system tray icon */
1244 static BOOL AddTrayIcon(HWND hwnd)
1247 NOTIFYICONDATA tnid;
1250 #ifdef NIM_SETVERSION
1252 res = Shell_NotifyIcon(NIM_SETVERSION, &tnid);
1255 tnid.cbSize = sizeof(NOTIFYICONDATA);
1257 tnid.uID = 1; /* unique within this systray use */
1258 tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
1259 tnid.uCallbackMessage = WM_SYSTRAY;
1260 tnid.hIcon = hicon = LoadIcon(instance, MAKEINTRESOURCE(201));
1261 strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
1263 res = Shell_NotifyIcon(NIM_ADD, &tnid);
1265 if (hicon) DestroyIcon(hicon);
1270 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1271 WPARAM wParam, LPARAM lParam)
1274 static int menuinprogress;
1275 static UINT msgTaskbarCreated = 0;
1279 msgTaskbarCreated = RegisterWindowMessage(_T("TaskbarCreated"));
1282 if (message==msgTaskbarCreated) {
1284 * Explorer has been restarted, so the tray icon will
1292 if (lParam == WM_RBUTTONUP) {
1294 GetCursorPos(&cursorpos);
1295 PostMessage(hwnd, WM_SYSTRAY2, cursorpos.x, cursorpos.y);
1296 } else if (lParam == WM_LBUTTONDBLCLK) {
1297 /* Equivalent to IDM_VIEWKEYS. */
1298 PostMessage(hwnd, WM_COMMAND, IDM_VIEWKEYS, 0);
1302 if (!menuinprogress) {
1304 SetForegroundWindow(hwnd);
1305 ret = TrackPopupMenu(systray_menu,
1306 TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
1308 wParam, lParam, 0, hwnd, NULL);
1314 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1317 SendMessage(passphrase_box, WM_CLOSE, 0, 0);
1318 SendMessage(hwnd, WM_CLOSE, 0, 0);
1322 keylist = CreateDialog(instance, MAKEINTRESOURCE(211),
1324 ShowWindow(keylist, SW_SHOWNORMAL);
1326 * Sometimes the window comes up minimised / hidden
1327 * for no obvious reason. Prevent this.
1329 SetForegroundWindow(keylist);
1330 SetWindowPos(keylist, HWND_TOP, 0, 0, 0, 0,
1331 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1335 if (passphrase_box) {
1336 MessageBeep(MB_ICONERROR);
1337 SetForegroundWindow(passphrase_box);
1340 prompt_add_keyfile();
1344 aboutbox = CreateDialog(instance, MAKEINTRESOURCE(213),
1346 ShowWindow(aboutbox, SW_SHOWNORMAL);
1348 * Sometimes the window comes up minimised / hidden
1349 * for no obvious reason. Prevent this.
1351 SetForegroundWindow(aboutbox);
1352 SetWindowPos(aboutbox, HWND_TOP, 0, 0, 0, 0,
1353 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1358 WinHelp(main_hwnd, help_path, HELP_COMMAND,
1359 (DWORD)"JI(`',`pageant.general')");
1360 requested_help = TRUE;
1366 if (requested_help) {
1367 WinHelp(main_hwnd, help_path, HELP_QUIT, 0);
1368 requested_help = FALSE;
1374 COPYDATASTRUCT *cds;
1380 PSID mapowner, procowner;
1381 PSECURITY_DESCRIPTOR psd1 = NULL, psd2 = NULL;
1385 cds = (COPYDATASTRUCT *) lParam;
1386 if (cds->dwData != AGENT_COPYDATA_ID)
1387 return 0; /* not our message, mate */
1388 mapname = (char *) cds->lpData;
1389 if (mapname[cds->cbData - 1] != '\0')
1390 return 0; /* failure to be ASCIZ! */
1392 debug(("mapname is :%s:\n", mapname));
1394 filemap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, mapname);
1396 debug(("filemap is %p\n", filemap));
1398 if (filemap != NULL && filemap != INVALID_HANDLE_VALUE) {
1402 if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE,
1403 GetCurrentProcessId())) ==
1406 debug(("couldn't get handle for process\n"));
1410 if (getsecurityinfo(proc, SE_KERNEL_OBJECT,
1411 OWNER_SECURITY_INFORMATION,
1412 &procowner, NULL, NULL, NULL,
1413 &psd2) != ERROR_SUCCESS) {
1415 debug(("couldn't get owner info for process\n"));
1418 return 0; /* unable to get security info */
1421 if ((rc = getsecurityinfo(filemap, SE_KERNEL_OBJECT,
1422 OWNER_SECURITY_INFORMATION,
1423 &mapowner, NULL, NULL, NULL,
1424 &psd1) != ERROR_SUCCESS)) {
1427 ("couldn't get owner info for filemap: %d\n",
1433 debug(("got security stuff\n"));
1435 if (!EqualSid(mapowner, procowner))
1436 return 0; /* security ID mismatch! */
1438 debug(("security stuff matched\n"));
1444 debug(("security APIs not present\n"));
1448 p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
1450 debug(("p is %p\n", p));
1453 for (i = 0; i < 5; i++)
1456 ((unsigned char *) p)[i]));}
1462 CloseHandle(filemap);
1467 return DefWindowProc(hwnd, message, wParam, lParam);
1471 * Fork and Exec the command in cmdline. [DBW]
1473 void spawn_cmd(char *cmdline, char * args, int show)
1475 if (ShellExecute(NULL, _T("open"), cmdline,
1476 args, NULL, show) <= (HINSTANCE) 32) {
1478 sprintf(sMsg, _T("Failed to run \"%.100s\", Error: %d"), cmdline,
1479 (int)GetLastError());
1480 MessageBox(NULL, sMsg, APPNAME, MB_OK | MB_ICONEXCLAMATION);
1484 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
1490 char *command = NULL;
1494 * Determine whether we're an NT system (should have security
1495 * APIs) or a non-NT system (don't do security).
1497 memset(&osi, 0, sizeof(OSVERSIONINFO));
1498 osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
1499 if (GetVersionEx(&osi) && osi.dwPlatformId == VER_PLATFORM_WIN32_NT) {
1500 has_security = TRUE;
1502 has_security = FALSE;
1507 * Attempt to get the security API we need.
1509 advapi = LoadLibrary("ADVAPI32.DLL");
1511 (gsi_fn_t) GetProcAddress(advapi, "GetSecurityInfo");
1512 if (!getsecurityinfo) {
1514 "Unable to access security APIs. Pageant will\n"
1515 "not run, in case it causes a security breach.",
1516 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
1521 "This program has been compiled for Win9X and will\n"
1522 "not run on NT, in case it causes a security breach.",
1523 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
1532 * See if we can find our Help file.
1535 char b[2048], *p, *q, *r;
1537 GetModuleFileName(NULL, b, sizeof(b) - 1);
1539 p = strrchr(b, '\\');
1540 if (p && p >= r) r = p+1;
1541 q = strrchr(b, ':');
1542 if (q && q >= r) r = q+1;
1543 strcpy(r, "putty.hlp");
1544 if ( (fp = fopen(b, "r")) != NULL) {
1545 help_path = dupstr(b);
1552 * Find out if Pageant is already running.
1554 already_running = FALSE;
1556 already_running = TRUE;
1561 wndclass.lpfnWndProc = WndProc;
1562 wndclass.cbClsExtra = 0;
1563 wndclass.cbWndExtra = 0;
1564 wndclass.hInstance = inst;
1565 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
1566 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
1567 wndclass.hbrBackground = GetStockObject(BLACK_BRUSH);
1568 wndclass.lpszMenuName = NULL;
1569 wndclass.lpszClassName = APPNAME;
1571 RegisterClass(&wndclass);
1574 main_hwnd = keylist = NULL;
1576 main_hwnd = CreateWindow(APPNAME, APPNAME,
1577 WS_OVERLAPPEDWINDOW | WS_VSCROLL,
1578 CW_USEDEFAULT, CW_USEDEFAULT,
1579 100, 100, NULL, NULL, inst, NULL);
1581 /* Set up a system tray icon */
1582 AddTrayIcon(main_hwnd);
1584 systray_menu = CreatePopupMenu();
1585 /* accelerators used: vkxa */
1586 AppendMenu(systray_menu, MF_ENABLED, IDM_VIEWKEYS,
1588 AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key");
1589 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
1591 AppendMenu(systray_menu, MF_ENABLED, IDM_HELP, "&Help");
1592 AppendMenu(systray_menu, MF_ENABLED, IDM_ABOUT, "&About");
1593 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
1594 AppendMenu(systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit");
1596 ShowWindow(main_hwnd, SW_HIDE);
1599 * Initialise storage for RSA keys.
1601 rsakeys = newtree234(cmpkeys_rsa);
1602 ssh2keys = newtree234(cmpkeys_ssh2);
1607 * Initialise storage for short-term passphrase cache.
1609 passphrases = newtree234(NULL);
1612 * Process the command line and add keys as listed on it.
1613 * If we already determined that we need to spawn a program from above we
1614 * need to ignore the first two arguments. [DBW]
1621 while (*p && isspace(*p))
1623 if (*p && !isspace(*p)) {
1624 char *q = p, *pp = p;
1625 while (*p && (inquotes || !isspace(*p))) {
1627 inquotes = !inquotes;
1638 if (!strcmp(q, "-c")) {
1640 * If we see `-c', then the rest of the
1641 * command line should be treated as a
1642 * command to be spawned.
1644 while (*p && isspace(*p))
1657 * Forget any passphrase that we retained while going over
1658 * command line keyfiles.
1660 forget_passphrases();
1664 if (command[0] == '"')
1665 args = strchr(++command, '"');
1667 args = strchr(command, ' ');
1670 while(*args && isspace(*args)) args++;
1672 spawn_cmd(command, args, show);
1676 * If Pageant was already running, we leave now. If we haven't
1677 * even taken any auxiliary action (spawned a command or added
1680 if (already_running) {
1681 if (!command && !added_keys) {
1682 MessageBox(NULL, "Pageant is already running", "Pageant Error",
1683 MB_ICONERROR | MB_OK);
1686 FreeLibrary(advapi);
1691 * Main message loop.
1693 while (GetMessage(&msg, NULL, 0, 0) == 1) {
1694 TranslateMessage(&msg);
1695 DispatchMessage(&msg);
1698 /* Clean up the system tray icon */
1700 NOTIFYICONDATA tnid;
1702 tnid.cbSize = sizeof(NOTIFYICONDATA);
1703 tnid.hWnd = main_hwnd;
1706 Shell_NotifyIcon(NIM_DELETE, &tnid);
1708 DestroyMenu(systray_menu);
1712 FreeLibrary(advapi);