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;
124 static void *make_keylist1(int *length);
125 static void *make_keylist2(int *length);
126 static void *get_keylist1(int *length);
127 static void *get_keylist2(int *length);
130 * We need this to link with the RSA code, because rsaencrypt()
131 * pads its data with random bytes. Since we only use rsadecrypt()
132 * and the signing functions, which are deterministic, this should
135 * If it _is_ called, there is a _serious_ problem, because it
136 * won't generate true random numbers. So we must scream, panic,
137 * and exit immediately if that should happen.
139 int random_byte(void)
141 MessageBox(hwnd, "Internal Error", APPNAME, MB_OK | MB_ICONERROR);
143 /* this line can't be reached but it placates MSVC's warnings :-) */
148 * Blob structure for passing to the asymmetric SSH-2 key compare
149 * function, prototyped here.
155 static int cmpkeys_ssh2_asymm(void *av, void *bv);
157 struct PassphraseProcStruct {
162 static tree234 *passphrases = NULL;
165 * After processing a list of filenames, we want to forget the
168 static void forget_passphrases(void)
170 while (count234(passphrases) > 0) {
171 char *pp = index234(passphrases, 0);
172 smemclr(pp, strlen(pp));
173 delpos234(passphrases, 0);
179 * Dialog-box function for the Licence box.
181 static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
182 WPARAM wParam, LPARAM lParam)
188 switch (LOWORD(wParam)) {
203 * Dialog-box function for the About box.
205 static int CALLBACK AboutProc(HWND hwnd, UINT msg,
206 WPARAM wParam, LPARAM lParam)
210 SetDlgItemText(hwnd, 100, ver);
213 switch (LOWORD(wParam)) {
220 EnableWindow(hwnd, 0);
221 DialogBox(hinst, MAKEINTRESOURCE(214), hwnd, LicenceProc);
222 EnableWindow(hwnd, 1);
223 SetActiveWindow(hwnd);
235 static HWND passphrase_box;
238 * Dialog-box function for the passphrase box.
240 static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
241 WPARAM wParam, LPARAM lParam)
243 static char **passphrase = NULL;
244 struct PassphraseProcStruct *p;
248 passphrase_box = hwnd;
252 { /* centre the window */
256 hw = GetDesktopWindow();
257 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
259 (rs.right + rs.left + rd.left - rd.right) / 2,
260 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
261 rd.right - rd.left, rd.bottom - rd.top, TRUE);
264 SetForegroundWindow(hwnd);
265 SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
266 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
267 p = (struct PassphraseProcStruct *) lParam;
268 passphrase = p->passphrase;
270 SetDlgItemText(hwnd, 101, p->comment);
271 burnstr(*passphrase);
272 *passphrase = dupstr("");
273 SetDlgItemText(hwnd, 102, *passphrase);
276 switch (LOWORD(wParam)) {
286 case 102: /* edit box */
287 if ((HIWORD(wParam) == EN_CHANGE) && passphrase) {
288 burnstr(*passphrase);
289 *passphrase = GetDlgItemText_alloc(hwnd, 102);
302 * Warn about the obsolescent key file format.
304 void old_keyfile_warning(void)
306 static const char mbtitle[] = "PuTTY Key File Warning";
307 static const char message[] =
308 "You are loading an SSH-2 private key which has an\n"
309 "old version of the file format. This means your key\n"
310 "file is not fully tamperproof. Future versions of\n"
311 "PuTTY may stop supporting this private key format,\n"
312 "so we recommend you convert your key to the new\n"
315 "You can perform this conversion by loading the key\n"
316 "into PuTTYgen and then saving it again.";
318 MessageBox(NULL, message, mbtitle, MB_OK);
322 * Update the visible key list.
324 static void keylist_update(void)
327 struct ssh2_userkey *skey;
331 SendDlgItemMessage(keylist, 100, LB_RESETCONTENT, 0, 0);
332 for (i = 0; NULL != (rkey = index234(rsakeys, i)); i++) {
333 char listentry[512], *p;
335 * Replace two spaces in the fingerprint with tabs, for
336 * nice alignment in the box.
338 strcpy(listentry, "ssh1\t");
339 p = listentry + strlen(listentry);
340 rsa_fingerprint(p, sizeof(listentry) - (p - listentry), rkey);
341 p = strchr(listentry, ' ');
344 p = strchr(listentry, ' ');
347 SendDlgItemMessage(keylist, 100, LB_ADDSTRING,
348 0, (LPARAM) listentry);
350 for (i = 0; NULL != (skey = index234(ssh2keys, i)); i++) {
354 * Replace spaces with tabs in the fingerprint prefix, for
355 * nice alignment in the list box, until we encounter a :
356 * meaning we're into the fingerprint proper.
358 p = skey->alg->fingerprint(skey->data);
359 listentry = dupprintf("%s\t%s", p, skey->comment);
360 fp_len = strlen(listentry);
365 pos += strcspn(listentry + pos, " :");
366 if (listentry[pos] == ':')
368 listentry[pos++] = '\t';
371 SendDlgItemMessage(keylist, 100, LB_ADDSTRING, 0,
375 SendDlgItemMessage(keylist, 100, LB_SETCURSEL, (WPARAM) - 1, 0);
380 * This function loads a key from a file and adds it.
382 static void add_keyfile(Filename *filename)
385 struct RSAKey *rkey = NULL;
386 struct ssh2_userkey *skey = NULL;
391 const char *error = NULL;
395 type = key_type(filename);
396 if (type != SSH_KEYTYPE_SSH1 && type != SSH_KEYTYPE_SSH2) {
397 char *msg = dupprintf("Couldn't load this key (%s)",
398 key_type_to_str(type));
399 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
400 HELPCTXID(errors_cantloadkey));
406 * See if the key is already loaded (in the primary Pageant,
407 * which may or may not be us).
411 unsigned char *keylist, *p;
412 int i, nkeys, bloblen, keylistlen;
414 if (type == SSH_KEYTYPE_SSH1) {
415 if (!rsakey_pubblob(filename, &blob, &bloblen, NULL, &error)) {
416 char *msg = dupprintf("Couldn't load private key (%s)", error);
417 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
418 HELPCTXID(errors_cantloadkey));
422 keylist = get_keylist1(&keylistlen);
424 unsigned char *blob2;
425 blob = ssh2_userkey_loadpub(filename, NULL, &bloblen,
428 char *msg = dupprintf("Couldn't load private key (%s)", error);
429 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
430 HELPCTXID(errors_cantloadkey));
434 /* For our purposes we want the blob prefixed with its length */
435 blob2 = snewn(bloblen+4, unsigned char);
436 PUT_32BIT(blob2, bloblen);
437 memcpy(blob2 + 4, blob, bloblen);
441 keylist = get_keylist2(&keylistlen);
444 if (keylistlen < 4) {
445 MessageBox(NULL, "Received broken key list?!", APPNAME,
446 MB_OK | MB_ICONERROR);
449 nkeys = toint(GET_32BIT(keylist));
451 MessageBox(NULL, "Received broken key list?!", APPNAME,
452 MB_OK | MB_ICONERROR);
458 for (i = 0; i < nkeys; i++) {
459 if (!memcmp(blob, p, bloblen)) {
460 /* Key is already present; we can now leave. */
465 /* Now skip over public blob */
466 if (type == SSH_KEYTYPE_SSH1) {
467 int n = rsa_public_blob_len(p, keylistlen);
469 MessageBox(NULL, "Received broken key list?!", APPNAME,
470 MB_OK | MB_ICONERROR);
477 if (keylistlen < 4) {
478 MessageBox(NULL, "Received broken key list?!", APPNAME,
479 MB_OK | MB_ICONERROR);
482 n = toint(4 + GET_32BIT(p));
483 if (n < 0 || keylistlen < n) {
484 MessageBox(NULL, "Received broken key list?!", APPNAME,
485 MB_OK | MB_ICONERROR);
491 /* Now skip over comment field */
494 if (keylistlen < 4) {
495 MessageBox(NULL, "Received broken key list?!", APPNAME,
496 MB_OK | MB_ICONERROR);
499 n = toint(4 + GET_32BIT(p));
500 if (n < 0 || keylistlen < n) {
501 MessageBox(NULL, "Received broken key list?!", APPNAME,
502 MB_OK | MB_ICONERROR);
517 if (type == SSH_KEYTYPE_SSH1)
518 needs_pass = rsakey_encrypted(filename, &comment);
520 needs_pass = ssh2_userkey_encrypted(filename, &comment);
522 if (type == SSH_KEYTYPE_SSH1)
523 rkey = snew(struct RSAKey);
531 /* try all the remembered passphrases first */
532 char *pp = index234(passphrases, attempts);
534 passphrase = dupstr(pp);
537 struct PassphraseProcStruct pps;
539 pps.passphrase = &passphrase;
540 pps.comment = comment;
543 dlgret = DialogBoxParam(hinst, MAKEINTRESOURCE(210),
544 NULL, PassphraseProc, (LPARAM) &pps);
545 passphrase_box = NULL;
549 if (type == SSH_KEYTYPE_SSH1)
551 return; /* operation cancelled */
554 assert(passphrase != NULL);
557 passphrase = dupstr("");
559 if (type == SSH_KEYTYPE_SSH1)
560 ret = loadrsakey(filename, rkey, passphrase, &error);
562 skey = ssh2_load_userkey(filename, passphrase, &error);
563 if (skey == SSH2_WRONG_PASSPHRASE)
573 if(original_pass && ret) {
574 /* If they typed in an ok passphrase, remember it */
575 addpos234(passphrases, passphrase, 0);
577 /* Otherwise, destroy it */
585 char *msg = dupprintf("Couldn't load private key (%s)", error);
586 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
587 HELPCTXID(errors_cantloadkey));
589 if (type == SSH_KEYTYPE_SSH1)
593 if (type == SSH_KEYTYPE_SSH1) {
594 if (already_running) {
595 unsigned char *request, *response;
597 int reqlen, clen, resplen, ret;
599 clen = strlen(rkey->comment);
601 reqlen = 4 + 1 + /* length, message type */
603 ssh1_bignum_length(rkey->modulus) +
604 ssh1_bignum_length(rkey->exponent) +
605 ssh1_bignum_length(rkey->private_exponent) +
606 ssh1_bignum_length(rkey->iqmp) +
607 ssh1_bignum_length(rkey->p) +
608 ssh1_bignum_length(rkey->q) + 4 + clen /* comment */
611 request = snewn(reqlen, unsigned char);
613 request[4] = SSH1_AGENTC_ADD_RSA_IDENTITY;
615 PUT_32BIT(request + reqlen, bignum_bitcount(rkey->modulus));
617 reqlen += ssh1_write_bignum(request + reqlen, rkey->modulus);
618 reqlen += ssh1_write_bignum(request + reqlen, rkey->exponent);
620 ssh1_write_bignum(request + reqlen,
621 rkey->private_exponent);
622 reqlen += ssh1_write_bignum(request + reqlen, rkey->iqmp);
623 reqlen += ssh1_write_bignum(request + reqlen, rkey->p);
624 reqlen += ssh1_write_bignum(request + reqlen, rkey->q);
625 PUT_32BIT(request + reqlen, clen);
626 memcpy(request + reqlen + 4, rkey->comment, clen);
628 PUT_32BIT(request, reqlen - 4);
630 ret = agent_query(request, reqlen, &vresponse, &resplen,
633 response = vresponse;
634 if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
635 MessageBox(NULL, "The already running Pageant "
636 "refused to add the key.", APPNAME,
637 MB_OK | MB_ICONERROR);
642 if (add234(rsakeys, rkey) != rkey)
643 sfree(rkey); /* already present, don't waste RAM */
646 if (already_running) {
647 unsigned char *request, *response;
649 int reqlen, alglen, clen, keybloblen, resplen, ret;
650 alglen = strlen(skey->alg->name);
651 clen = strlen(skey->comment);
653 keybloblen = skey->alg->openssh_fmtkey(skey->data, NULL, 0);
655 reqlen = 4 + 1 + /* length, message type */
656 4 + alglen + /* algorithm name */
657 keybloblen + /* key data */
658 4 + clen /* comment */
661 request = snewn(reqlen, unsigned char);
663 request[4] = SSH2_AGENTC_ADD_IDENTITY;
665 PUT_32BIT(request + reqlen, alglen);
667 memcpy(request + reqlen, skey->alg->name, alglen);
669 reqlen += skey->alg->openssh_fmtkey(skey->data,
672 PUT_32BIT(request + reqlen, clen);
673 memcpy(request + reqlen + 4, skey->comment, clen);
675 PUT_32BIT(request, reqlen - 4);
677 ret = agent_query(request, reqlen, &vresponse, &resplen,
680 response = vresponse;
681 if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
682 MessageBox(NULL, "The already running Pageant "
683 "refused to add the key.", APPNAME,
684 MB_OK | MB_ICONERROR);
689 if (add234(ssh2keys, skey) != skey) {
690 skey->alg->freekey(skey->data);
691 sfree(skey); /* already present, don't waste RAM */
698 * Create an SSH-1 key list in a malloc'ed buffer; return its
701 static void *make_keylist1(int *length)
705 unsigned char *blob, *p, *ret;
709 * Count up the number and length of keys we hold.
713 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
715 blob = rsa_public_blob(key, &bloblen);
718 len += 4 + strlen(key->comment);
721 /* Allocate the buffer. */
722 p = ret = snewn(len, unsigned char);
723 if (length) *length = len;
727 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
728 blob = rsa_public_blob(key, &bloblen);
729 memcpy(p, blob, bloblen);
732 PUT_32BIT(p, strlen(key->comment));
733 memcpy(p + 4, key->comment, strlen(key->comment));
734 p += 4 + strlen(key->comment);
737 assert(p - ret == len);
742 * Create an SSH-2 key list in a malloc'ed buffer; return its
745 static void *make_keylist2(int *length)
747 struct ssh2_userkey *key;
749 unsigned char *blob, *p, *ret;
753 * Count up the number and length of keys we hold.
757 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
759 len += 4; /* length field */
760 blob = key->alg->public_blob(key->data, &bloblen);
763 len += 4 + strlen(key->comment);
766 /* Allocate the buffer. */
767 p = ret = snewn(len, unsigned char);
768 if (length) *length = len;
771 * Packet header is the obvious five bytes, plus four
772 * bytes for the key count.
776 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
777 blob = key->alg->public_blob(key->data, &bloblen);
778 PUT_32BIT(p, bloblen);
780 memcpy(p, blob, bloblen);
783 PUT_32BIT(p, strlen(key->comment));
784 memcpy(p + 4, key->comment, strlen(key->comment));
785 p += 4 + strlen(key->comment);
788 assert(p - ret == len);
793 * Acquire a keylist1 from the primary Pageant; this means either
794 * calling make_keylist1 (if that's us) or sending a message to the
795 * primary Pageant (if it's not).
797 static void *get_keylist1(int *length)
801 if (already_running) {
802 unsigned char request[5], *response;
805 request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
806 PUT_32BIT(request, 4);
808 retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
810 response = vresponse;
811 if (resplen < 5 || response[4] != SSH1_AGENT_RSA_IDENTITIES_ANSWER) {
816 ret = snewn(resplen-5, unsigned char);
817 memcpy(ret, response+5, resplen-5);
823 ret = make_keylist1(length);
829 * Acquire a keylist2 from the primary Pageant; this means either
830 * calling make_keylist2 (if that's us) or sending a message to the
831 * primary Pageant (if it's not).
833 static void *get_keylist2(int *length)
837 if (already_running) {
838 unsigned char request[5], *response;
842 request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
843 PUT_32BIT(request, 4);
845 retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
847 response = vresponse;
848 if (resplen < 5 || response[4] != SSH2_AGENT_IDENTITIES_ANSWER) {
853 ret = snewn(resplen-5, unsigned char);
854 memcpy(ret, response+5, resplen-5);
860 ret = make_keylist2(length);
866 * This is the main agent function that answers messages.
868 static void answer_msg(void *msg)
870 unsigned char *p = msg;
871 unsigned char *ret = msg;
872 unsigned char *msgend;
876 * Get the message length.
878 msgend = p + 4 + GET_32BIT(p);
881 * Get the message type.
889 case SSH1_AGENTC_REQUEST_RSA_IDENTITIES:
891 * Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER.
897 ret[4] = SSH1_AGENT_RSA_IDENTITIES_ANSWER;
898 keylist = make_keylist1(&len);
899 if (len + 5 > AGENT_MAX_MSGLEN) {
903 PUT_32BIT(ret, len + 1);
904 memcpy(ret + 5, keylist, len);
908 case SSH2_AGENTC_REQUEST_IDENTITIES:
910 * Reply with SSH2_AGENT_IDENTITIES_ANSWER.
916 ret[4] = SSH2_AGENT_IDENTITIES_ANSWER;
917 keylist = make_keylist2(&len);
918 if (len + 5 > AGENT_MAX_MSGLEN) {
922 PUT_32BIT(ret, len + 1);
923 memcpy(ret + 5, keylist, len);
927 case SSH1_AGENTC_RSA_CHALLENGE:
929 * Reply with either SSH1_AGENT_RSA_RESPONSE or
930 * SSH_AGENT_FAILURE, depending on whether we have that key
934 struct RSAKey reqkey, *key;
935 Bignum challenge, response;
936 unsigned char response_source[48], response_md5[16];
937 struct MD5Context md5c;
941 i = ssh1_read_bignum(p, msgend - p, &reqkey.exponent);
945 i = ssh1_read_bignum(p, msgend - p, &reqkey.modulus);
947 freebn(reqkey.exponent);
951 i = ssh1_read_bignum(p, msgend - p, &challenge);
953 freebn(reqkey.exponent);
954 freebn(reqkey.modulus);
959 freebn(reqkey.exponent);
960 freebn(reqkey.modulus);
964 memcpy(response_source + 32, p, 16);
968 (key = find234(rsakeys, &reqkey, NULL)) == NULL) {
969 freebn(reqkey.exponent);
970 freebn(reqkey.modulus);
974 response = rsadecrypt(challenge, key);
975 for (i = 0; i < 32; i++)
976 response_source[i] = bignum_byte(response, 31 - i);
979 MD5Update(&md5c, response_source, 48);
980 MD5Final(response_md5, &md5c);
981 smemclr(response_source, 48); /* burn the evidence */
982 freebn(response); /* and that evidence */
983 freebn(challenge); /* yes, and that evidence */
984 freebn(reqkey.exponent); /* and free some memory ... */
985 freebn(reqkey.modulus); /* ... while we're at it. */
988 * Packet is the obvious five byte header, plus sixteen
992 PUT_32BIT(ret, len - 4);
993 ret[4] = SSH1_AGENT_RSA_RESPONSE;
994 memcpy(ret + 5, response_md5, 16);
997 case SSH2_AGENTC_SIGN_REQUEST:
999 * Reply with either SSH2_AGENT_SIGN_RESPONSE or
1000 * SSH_AGENT_FAILURE, depending on whether we have that key
1004 struct ssh2_userkey *key;
1006 unsigned char *data, *signature;
1007 int datalen, siglen, len;
1011 b.len = toint(GET_32BIT(p));
1012 if (b.len < 0 || b.len > msgend - (p+4))
1019 datalen = toint(GET_32BIT(p));
1021 if (datalen < 0 || datalen > msgend - p)
1024 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
1027 signature = key->alg->sign(key->data, data, datalen, &siglen);
1028 len = 5 + 4 + siglen;
1029 PUT_32BIT(ret, len - 4);
1030 ret[4] = SSH2_AGENT_SIGN_RESPONSE;
1031 PUT_32BIT(ret + 5, siglen);
1032 memcpy(ret + 5 + 4, signature, siglen);
1036 case SSH1_AGENTC_ADD_RSA_IDENTITY:
1038 * Add to the list and return SSH_AGENT_SUCCESS, or
1039 * SSH_AGENT_FAILURE if the key was malformed.
1046 key = snew(struct RSAKey);
1047 memset(key, 0, sizeof(struct RSAKey));
1049 n = makekey(p, msgend - p, key, NULL, 1);
1057 n = makeprivate(p, msgend - p, key);
1065 n = ssh1_read_bignum(p, msgend - p, &key->iqmp); /* p^-1 mod q */
1073 n = ssh1_read_bignum(p, msgend - p, &key->p); /* p */
1081 n = ssh1_read_bignum(p, msgend - p, &key->q); /* q */
1094 commentlen = toint(GET_32BIT(p));
1096 if (commentlen < 0 || commentlen > msgend - p) {
1102 comment = snewn(commentlen+1, char);
1104 memcpy(comment, p + 4, commentlen);
1105 comment[commentlen] = '\0';
1106 key->comment = comment;
1109 ret[4] = SSH_AGENT_FAILURE;
1110 if (add234(rsakeys, key) == key) {
1112 ret[4] = SSH_AGENT_SUCCESS;
1119 case SSH2_AGENTC_ADD_IDENTITY:
1121 * Add to the list and return SSH_AGENT_SUCCESS, or
1122 * SSH_AGENT_FAILURE if the key was malformed.
1125 struct ssh2_userkey *key;
1126 char *comment, *alg;
1127 int alglen, commlen;
1133 alglen = toint(GET_32BIT(p));
1135 if (alglen < 0 || alglen > msgend - p)
1140 key = snew(struct ssh2_userkey);
1141 /* Add further algorithm names here. */
1142 if (alglen == 7 && !memcmp(alg, "ssh-rsa", 7))
1143 key->alg = &ssh_rsa;
1144 else if (alglen == 7 && !memcmp(alg, "ssh-dss", 7))
1145 key->alg = &ssh_dss;
1146 else if (alglen == 19 && memcmp(alg, "ecdsa-sha2-nistp256", 19))
1147 key->alg = &ssh_ecdsa_nistp256;
1148 else if (alglen == 19 && memcmp(alg, "ecdsa-sha2-nistp384", 19))
1149 key->alg = &ssh_ecdsa_nistp384;
1150 else if (alglen == 19 && memcmp(alg, "ecdsa-sha2-nistp521", 19))
1151 key->alg = &ssh_ecdsa_nistp521;
1157 bloblen = msgend - p;
1158 key->data = key->alg->openssh_createkey(&p, &bloblen);
1165 * p has been advanced by openssh_createkey, but
1166 * certainly not _beyond_ the end of the buffer.
1168 assert(p <= msgend);
1171 key->alg->freekey(key->data);
1175 commlen = toint(GET_32BIT(p));
1178 if (commlen < 0 || commlen > msgend - p) {
1179 key->alg->freekey(key->data);
1183 comment = snewn(commlen + 1, char);
1185 memcpy(comment, p, commlen);
1186 comment[commlen] = '\0';
1188 key->comment = comment;
1191 ret[4] = SSH_AGENT_FAILURE;
1192 if (add234(ssh2keys, key) == key) {
1194 ret[4] = SSH_AGENT_SUCCESS;
1196 key->alg->freekey(key->data);
1197 sfree(key->comment);
1202 case SSH1_AGENTC_REMOVE_RSA_IDENTITY:
1204 * Remove from the list and return SSH_AGENT_SUCCESS, or
1205 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
1209 struct RSAKey reqkey, *key;
1212 n = makekey(p, msgend - p, &reqkey, NULL, 0);
1216 key = find234(rsakeys, &reqkey, NULL);
1217 freebn(reqkey.exponent);
1218 freebn(reqkey.modulus);
1220 ret[4] = SSH_AGENT_FAILURE;
1222 del234(rsakeys, key);
1226 ret[4] = SSH_AGENT_SUCCESS;
1230 case SSH2_AGENTC_REMOVE_IDENTITY:
1232 * Remove from the list and return SSH_AGENT_SUCCESS, or
1233 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
1237 struct ssh2_userkey *key;
1242 b.len = toint(GET_32BIT(p));
1245 if (b.len < 0 || b.len > msgend - p)
1250 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
1255 ret[4] = SSH_AGENT_FAILURE;
1257 del234(ssh2keys, key);
1259 key->alg->freekey(key->data);
1261 ret[4] = SSH_AGENT_SUCCESS;
1265 case SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES:
1267 * Remove all SSH-1 keys. Always returns success.
1270 struct RSAKey *rkey;
1272 while ((rkey = index234(rsakeys, 0)) != NULL) {
1273 del234(rsakeys, rkey);
1280 ret[4] = SSH_AGENT_SUCCESS;
1283 case SSH2_AGENTC_REMOVE_ALL_IDENTITIES:
1285 * Remove all SSH-2 keys. Always returns success.
1288 struct ssh2_userkey *skey;
1290 while ((skey = index234(ssh2keys, 0)) != NULL) {
1291 del234(ssh2keys, skey);
1292 skey->alg->freekey(skey->data);
1298 ret[4] = SSH_AGENT_SUCCESS;
1304 * Unrecognised message. Return SSH_AGENT_FAILURE.
1307 ret[4] = SSH_AGENT_FAILURE;
1313 * Key comparison function for the 2-3-4 tree of RSA keys.
1315 static int cmpkeys_rsa(void *av, void *bv)
1317 struct RSAKey *a = (struct RSAKey *) av;
1318 struct RSAKey *b = (struct RSAKey *) bv;
1325 * Compare by length of moduli.
1327 alen = bignum_bitcount(am);
1328 blen = bignum_bitcount(bm);
1331 else if (alen < blen)
1334 * Now compare by moduli themselves.
1336 alen = (alen + 7) / 8; /* byte count */
1337 while (alen-- > 0) {
1339 abyte = bignum_byte(am, alen);
1340 bbyte = bignum_byte(bm, alen);
1343 else if (abyte < bbyte)
1353 * Key comparison function for the 2-3-4 tree of SSH-2 keys.
1355 static int cmpkeys_ssh2(void *av, void *bv)
1357 struct ssh2_userkey *a = (struct ssh2_userkey *) av;
1358 struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
1361 unsigned char *ablob, *bblob;
1365 * Compare purely by public blob.
1367 ablob = a->alg->public_blob(a->data, &alen);
1368 bblob = b->alg->public_blob(b->data, &blen);
1371 for (i = 0; i < alen && i < blen; i++) {
1372 if (ablob[i] < bblob[i]) {
1375 } else if (ablob[i] > bblob[i]) {
1380 if (c == 0 && i < alen)
1381 c = +1; /* a is longer */
1382 if (c == 0 && i < blen)
1383 c = -1; /* a is longer */
1392 * Key comparison function for looking up a blob in the 2-3-4 tree
1395 static int cmpkeys_ssh2_asymm(void *av, void *bv)
1397 struct blob *a = (struct blob *) av;
1398 struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
1401 unsigned char *ablob, *bblob;
1405 * Compare purely by public blob.
1409 bblob = b->alg->public_blob(b->data, &blen);
1412 for (i = 0; i < alen && i < blen; i++) {
1413 if (ablob[i] < bblob[i]) {
1416 } else if (ablob[i] > bblob[i]) {
1421 if (c == 0 && i < alen)
1422 c = +1; /* a is longer */
1423 if (c == 0 && i < blen)
1424 c = -1; /* a is longer */
1432 * Prompt for a key file to add, and add it.
1434 static void prompt_add_keyfile(void)
1437 char *filelist = snewn(8192, char);
1439 if (!keypath) keypath = filereq_new();
1440 memset(&of, 0, sizeof(of));
1441 of.hwndOwner = hwnd;
1442 of.lpstrFilter = FILTER_KEY_FILES;
1443 of.lpstrCustomFilter = NULL;
1444 of.nFilterIndex = 1;
1445 of.lpstrFile = filelist;
1448 of.lpstrFileTitle = NULL;
1449 of.lpstrTitle = "Select Private Key File";
1450 of.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER;
1451 if (request_file(keypath, &of, TRUE, FALSE)) {
1452 if(strlen(filelist) > of.nFileOffset) {
1453 /* Only one filename returned? */
1454 Filename *fn = filename_from_str(filelist);
1458 /* we are returned a bunch of strings, end to
1459 * end. first string is the directory, the
1460 * rest the filenames. terminated with an
1463 char *dir = filelist;
1464 char *filewalker = filelist + strlen(dir) + 1;
1465 while (*filewalker != '\0') {
1466 char *filename = dupcat(dir, "\\", filewalker, NULL);
1467 Filename *fn = filename_from_str(filename);
1471 filewalker += strlen(filewalker) + 1;
1476 forget_passphrases();
1482 * Dialog-box function for the key list box.
1484 static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
1485 WPARAM wParam, LPARAM lParam)
1487 struct RSAKey *rkey;
1488 struct ssh2_userkey *skey;
1493 * Centre the window.
1495 { /* centre the window */
1499 hw = GetDesktopWindow();
1500 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
1502 (rs.right + rs.left + rd.left - rd.right) / 2,
1503 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
1504 rd.right - rd.left, rd.bottom - rd.top, TRUE);
1508 SetWindowLongPtr(hwnd, GWL_EXSTYLE,
1509 GetWindowLongPtr(hwnd, GWL_EXSTYLE) |
1512 HWND item = GetDlgItem(hwnd, 103); /* the Help button */
1514 DestroyWindow(item);
1519 static int tabs[] = { 35, 75, 250 };
1520 SendDlgItemMessage(hwnd, 100, LB_SETTABSTOPS,
1521 sizeof(tabs) / sizeof(*tabs),
1527 switch (LOWORD(wParam)) {
1531 DestroyWindow(hwnd);
1533 case 101: /* add key */
1534 if (HIWORD(wParam) == BN_CLICKED ||
1535 HIWORD(wParam) == BN_DOUBLECLICKED) {
1536 if (passphrase_box) {
1537 MessageBeep(MB_ICONERROR);
1538 SetForegroundWindow(passphrase_box);
1541 prompt_add_keyfile();
1544 case 102: /* remove key */
1545 if (HIWORD(wParam) == BN_CLICKED ||
1546 HIWORD(wParam) == BN_DOUBLECLICKED) {
1551 /* our counter within the array of selected items */
1554 /* get the number of items selected in the list */
1556 SendDlgItemMessage(hwnd, 100, LB_GETSELCOUNT, 0, 0);
1558 /* none selected? that was silly */
1559 if (numSelected == 0) {
1564 /* get item indices in an array */
1565 selectedArray = snewn(numSelected, int);
1566 SendDlgItemMessage(hwnd, 100, LB_GETSELITEMS,
1567 numSelected, (WPARAM)selectedArray);
1569 itemNum = numSelected - 1;
1570 rCount = count234(rsakeys);
1571 sCount = count234(ssh2keys);
1573 /* go through the non-rsakeys until we've covered them all,
1574 * and/or we're out of selected items to check. note that
1575 * we go *backwards*, to avoid complications from deleting
1576 * things hence altering the offset of subsequent items
1578 for (i = sCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1579 skey = index234(ssh2keys, i);
1581 if (selectedArray[itemNum] == rCount + i) {
1582 del234(ssh2keys, skey);
1583 skey->alg->freekey(skey->data);
1589 /* do the same for the rsa keys */
1590 for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1591 rkey = index234(rsakeys, i);
1593 if(selectedArray[itemNum] == i) {
1594 del234(rsakeys, rkey);
1601 sfree(selectedArray);
1605 case 103: /* help */
1606 if (HIWORD(wParam) == BN_CLICKED ||
1607 HIWORD(wParam) == BN_DOUBLECLICKED) {
1608 launch_help(hwnd, WINHELP_CTX_pageant_general);
1615 int id = ((LPHELPINFO)lParam)->iCtrlId;
1618 case 100: topic = WINHELP_CTX_pageant_keylist; break;
1619 case 101: topic = WINHELP_CTX_pageant_addkey; break;
1620 case 102: topic = WINHELP_CTX_pageant_remkey; break;
1623 launch_help(hwnd, topic);
1631 DestroyWindow(hwnd);
1637 /* Set up a system tray icon */
1638 static BOOL AddTrayIcon(HWND hwnd)
1641 NOTIFYICONDATA tnid;
1644 #ifdef NIM_SETVERSION
1646 res = Shell_NotifyIcon(NIM_SETVERSION, &tnid);
1649 tnid.cbSize = sizeof(NOTIFYICONDATA);
1651 tnid.uID = 1; /* unique within this systray use */
1652 tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
1653 tnid.uCallbackMessage = WM_SYSTRAY;
1654 tnid.hIcon = hicon = LoadIcon(hinst, MAKEINTRESOURCE(201));
1655 strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
1657 res = Shell_NotifyIcon(NIM_ADD, &tnid);
1659 if (hicon) DestroyIcon(hicon);
1664 /* Update the saved-sessions menu. */
1665 static void update_sessions(void)
1669 TCHAR buf[MAX_PATH + 1];
1672 int index_key, index_menu;
1677 if(ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, PUTTY_REGKEY, &hkey))
1680 for(num_entries = GetMenuItemCount(session_menu);
1681 num_entries > initial_menuitems_count;
1683 RemoveMenu(session_menu, 0, MF_BYPOSITION);
1688 while(ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) {
1689 TCHAR session_name[MAX_PATH + 1];
1690 unmungestr(buf, session_name, MAX_PATH);
1691 if(strcmp(buf, PUTTY_DEFAULT) != 0) {
1692 memset(&mii, 0, sizeof(mii));
1693 mii.cbSize = sizeof(mii);
1694 mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
1695 mii.fType = MFT_STRING;
1696 mii.fState = MFS_ENABLED;
1697 mii.wID = (index_menu * 16) + IDM_SESSIONS_BASE;
1698 mii.dwTypeData = session_name;
1699 InsertMenuItem(session_menu, index_menu, TRUE, &mii);
1707 if(index_menu == 0) {
1708 mii.cbSize = sizeof(mii);
1709 mii.fMask = MIIM_TYPE | MIIM_STATE;
1710 mii.fType = MFT_STRING;
1711 mii.fState = MFS_GRAYED;
1712 mii.dwTypeData = _T("(No sessions)");
1713 InsertMenuItem(session_menu, index_menu, TRUE, &mii);
1719 * Versions of Pageant prior to 0.61 expected this SID on incoming
1720 * communications. For backwards compatibility, and more particularly
1721 * for compatibility with derived works of PuTTY still using the old
1722 * Pageant client code, we accept it as an alternative to the one
1723 * returned from get_user_sid() in winpgntc.c.
1725 PSID get_default_sid(void)
1729 PSECURITY_DESCRIPTOR psd = NULL;
1730 PSID sid = NULL, copy = NULL, ret = NULL;
1732 if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE,
1733 GetCurrentProcessId())) == NULL)
1736 if (p_GetSecurityInfo(proc, SE_KERNEL_OBJECT, OWNER_SECURITY_INFORMATION,
1737 &sid, NULL, NULL, NULL, &psd) != ERROR_SUCCESS)
1740 sidlen = GetLengthSid(sid);
1742 copy = (PSID)smalloc(sidlen);
1744 if (!CopySid(sidlen, copy, sid))
1747 /* Success. Move sid into the return value slot, and null it out
1748 * to stop the cleanup code freeing it. */
1764 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1765 WPARAM wParam, LPARAM lParam)
1768 static int menuinprogress;
1769 static UINT msgTaskbarCreated = 0;
1773 msgTaskbarCreated = RegisterWindowMessage(_T("TaskbarCreated"));
1776 if (message==msgTaskbarCreated) {
1778 * Explorer has been restarted, so the tray icon will
1786 if (lParam == WM_RBUTTONUP) {
1788 GetCursorPos(&cursorpos);
1789 PostMessage(hwnd, WM_SYSTRAY2, cursorpos.x, cursorpos.y);
1790 } else if (lParam == WM_LBUTTONDBLCLK) {
1791 /* Run the default menu item. */
1792 UINT menuitem = GetMenuDefaultItem(systray_menu, FALSE, 0);
1794 PostMessage(hwnd, WM_COMMAND, menuitem, 0);
1798 if (!menuinprogress) {
1801 SetForegroundWindow(hwnd);
1802 ret = TrackPopupMenu(systray_menu,
1803 TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
1805 wParam, lParam, 0, hwnd, NULL);
1811 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1813 if((int)ShellExecute(hwnd, NULL, putty_path, _T(""), _T(""),
1815 MessageBox(NULL, "Unable to execute PuTTY!",
1816 "Error", MB_OK | MB_ICONERROR);
1821 SendMessage(passphrase_box, WM_CLOSE, 0, 0);
1822 SendMessage(hwnd, WM_CLOSE, 0, 0);
1826 keylist = CreateDialog(hinst, MAKEINTRESOURCE(211),
1828 ShowWindow(keylist, SW_SHOWNORMAL);
1831 * Sometimes the window comes up minimised / hidden for
1832 * no obvious reason. Prevent this. This also brings it
1833 * to the front if it's already present (the user
1834 * selected View Keys because they wanted to _see_ the
1837 SetForegroundWindow(keylist);
1838 SetWindowPos(keylist, HWND_TOP, 0, 0, 0, 0,
1839 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1842 if (passphrase_box) {
1843 MessageBeep(MB_ICONERROR);
1844 SetForegroundWindow(passphrase_box);
1847 prompt_add_keyfile();
1851 aboutbox = CreateDialog(hinst, MAKEINTRESOURCE(213),
1853 ShowWindow(aboutbox, SW_SHOWNORMAL);
1855 * Sometimes the window comes up minimised / hidden
1856 * for no obvious reason. Prevent this.
1858 SetForegroundWindow(aboutbox);
1859 SetWindowPos(aboutbox, HWND_TOP, 0, 0, 0, 0,
1860 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1864 launch_help(hwnd, WINHELP_CTX_pageant_general);
1868 if(wParam >= IDM_SESSIONS_BASE && wParam <= IDM_SESSIONS_MAX) {
1870 TCHAR buf[MAX_PATH + 1];
1871 TCHAR param[MAX_PATH + 1];
1872 memset(&mii, 0, sizeof(mii));
1873 mii.cbSize = sizeof(mii);
1874 mii.fMask = MIIM_TYPE;
1876 mii.dwTypeData = buf;
1877 GetMenuItemInfo(session_menu, wParam, FALSE, &mii);
1879 strcat(param, mii.dwTypeData);
1880 if((int)ShellExecute(hwnd, NULL, putty_path, param,
1881 _T(""), SW_SHOW) <= 32) {
1882 MessageBox(NULL, "Unable to execute PuTTY!", "Error",
1883 MB_OK | MB_ICONERROR);
1896 COPYDATASTRUCT *cds;
1901 PSID mapowner, ourself, ourself2;
1903 PSECURITY_DESCRIPTOR psd = NULL;
1906 cds = (COPYDATASTRUCT *) lParam;
1907 if (cds->dwData != AGENT_COPYDATA_ID)
1908 return 0; /* not our message, mate */
1909 mapname = (char *) cds->lpData;
1910 if (mapname[cds->cbData - 1] != '\0')
1911 return 0; /* failure to be ASCIZ! */
1913 debug(("mapname is :%s:\n", mapname));
1915 filemap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, mapname);
1917 debug(("filemap is %p\n", filemap));
1919 if (filemap != NULL && filemap != INVALID_HANDLE_VALUE) {
1923 if ((ourself = get_user_sid()) == NULL) {
1925 debug(("couldn't get user SID\n"));
1927 CloseHandle(filemap);
1931 if ((ourself2 = get_default_sid()) == NULL) {
1933 debug(("couldn't get default SID\n"));
1935 CloseHandle(filemap);
1940 if ((rc = p_GetSecurityInfo(filemap, SE_KERNEL_OBJECT,
1941 OWNER_SECURITY_INFORMATION,
1942 &mapowner, NULL, NULL, NULL,
1943 &psd) != ERROR_SUCCESS)) {
1945 debug(("couldn't get owner info for filemap: %d\n",
1948 CloseHandle(filemap);
1955 LPTSTR ours, ours2, theirs;
1956 ConvertSidToStringSid(mapowner, &theirs);
1957 ConvertSidToStringSid(ourself, &ours);
1958 ConvertSidToStringSid(ourself2, &ours2);
1959 debug(("got sids:\n oursnew=%s\n oursold=%s\n"
1960 " theirs=%s\n", ours, ours2, theirs));
1966 if (!EqualSid(mapowner, ourself) &&
1967 !EqualSid(mapowner, ourself2)) {
1968 CloseHandle(filemap);
1972 return 0; /* security ID mismatch! */
1975 debug(("security stuff matched\n"));
1982 debug(("security APIs not present\n"));
1986 p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
1988 debug(("p is %p\n", p));
1991 for (i = 0; i < 5; i++)
1992 debug(("p[%d]=%02x\n", i,
1993 ((unsigned char *) p)[i]));
2000 CloseHandle(filemap);
2005 return DefWindowProc(hwnd, message, wParam, lParam);
2009 * Fork and Exec the command in cmdline. [DBW]
2011 void spawn_cmd(char *cmdline, char * args, int show)
2013 if (ShellExecute(NULL, _T("open"), cmdline,
2014 args, NULL, show) <= (HINSTANCE) 32) {
2016 msg = dupprintf("Failed to run \"%.100s\", Error: %d", cmdline,
2017 (int)GetLastError());
2018 MessageBox(NULL, msg, APPNAME, MB_OK | MB_ICONEXCLAMATION);
2024 * This is a can't-happen stub, since Pageant never makes
2025 * asynchronous agent requests.
2027 void agent_schedule_callback(void (*callback)(void *, void *, int),
2028 void *callback_ctx, void *data, int len)
2030 assert(!"We shouldn't get here");
2033 void cleanup_exit(int code)
2039 int flags = FLAG_SYNCAGENT;
2041 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
2045 char *command = NULL;
2048 char **argv, **argstart;
2054 * Determine whether we're an NT system (should have security
2055 * APIs) or a non-NT system (don't do security).
2059 modalfatalbox("Windows refuses to report a version");
2061 if (osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT) {
2062 has_security = TRUE;
2064 has_security = FALSE;
2069 * Attempt to get the security API we need.
2071 if (!got_advapi()) {
2073 "Unable to access security APIs. Pageant will\n"
2074 "not run, in case it causes a security breach.",
2075 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
2080 "This program has been compiled for Win9X and will\n"
2081 "not run on NT, in case it causes a security breach.",
2082 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
2088 * See if we can find our Help file.
2093 * Look for the PuTTY binary (we will enable the saved session
2094 * submenu if we find it).
2097 char b[2048], *p, *q, *r;
2099 GetModuleFileName(NULL, b, sizeof(b) - 16);
2101 p = strrchr(b, '\\');
2102 if (p && p >= r) r = p+1;
2103 q = strrchr(b, ':');
2104 if (q && q >= r) r = q+1;
2105 strcpy(r, "putty.exe");
2106 if ( (fp = fopen(b, "r")) != NULL) {
2107 putty_path = dupstr(b);
2114 * Find out if Pageant is already running.
2116 already_running = agent_exists();
2119 * Initialise storage for RSA keys.
2121 if (!already_running) {
2122 rsakeys = newtree234(cmpkeys_rsa);
2123 ssh2keys = newtree234(cmpkeys_ssh2);
2127 * Initialise storage for short-term passphrase cache.
2129 passphrases = newtree234(NULL);
2132 * Process the command line and add keys as listed on it.
2134 split_into_argv(cmdline, &argc, &argv, &argstart);
2135 for (i = 0; i < argc; i++) {
2136 if (!strcmp(argv[i], "-pgpfp")) {
2139 } else if (!strcmp(argv[i], "-c")) {
2141 * If we see `-c', then the rest of the
2142 * command line should be treated as a
2143 * command to be spawned.
2146 command = argstart[i+1];
2151 Filename *fn = filename_from_str(argv[i]);
2159 * Forget any passphrase that we retained while going over
2160 * command line keyfiles.
2162 forget_passphrases();
2166 if (command[0] == '"')
2167 args = strchr(++command, '"');
2169 args = strchr(command, ' ');
2172 while(*args && isspace(*args)) args++;
2174 spawn_cmd(command, args, show);
2178 * If Pageant was already running, we leave now. If we haven't
2179 * even taken any auxiliary action (spawned a command or added
2182 if (already_running) {
2183 if (!command && !added_keys) {
2184 MessageBox(NULL, "Pageant is already running", "Pageant Error",
2185 MB_ICONERROR | MB_OK);
2192 wndclass.lpfnWndProc = WndProc;
2193 wndclass.cbClsExtra = 0;
2194 wndclass.cbWndExtra = 0;
2195 wndclass.hInstance = inst;
2196 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
2197 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
2198 wndclass.hbrBackground = GetStockObject(BLACK_BRUSH);
2199 wndclass.lpszMenuName = NULL;
2200 wndclass.lpszClassName = APPNAME;
2202 RegisterClass(&wndclass);
2207 hwnd = CreateWindow(APPNAME, APPNAME,
2208 WS_OVERLAPPEDWINDOW | WS_VSCROLL,
2209 CW_USEDEFAULT, CW_USEDEFAULT,
2210 100, 100, NULL, NULL, inst, NULL);
2212 /* Set up a system tray icon */
2215 /* Accelerators used: nsvkxa */
2216 systray_menu = CreatePopupMenu();
2218 session_menu = CreateMenu();
2219 AppendMenu(systray_menu, MF_ENABLED, IDM_PUTTY, "&New Session");
2220 AppendMenu(systray_menu, MF_POPUP | MF_ENABLED,
2221 (UINT) session_menu, "&Saved Sessions");
2222 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
2224 AppendMenu(systray_menu, MF_ENABLED, IDM_VIEWKEYS,
2226 AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key");
2227 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
2229 AppendMenu(systray_menu, MF_ENABLED, IDM_HELP, "&Help");
2230 AppendMenu(systray_menu, MF_ENABLED, IDM_ABOUT, "&About");
2231 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
2232 AppendMenu(systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit");
2233 initial_menuitems_count = GetMenuItemCount(session_menu);
2235 /* Set the default menu item. */
2236 SetMenuDefaultItem(systray_menu, IDM_VIEWKEYS, FALSE);
2238 ShowWindow(hwnd, SW_HIDE);
2241 * Main message loop.
2243 while (GetMessage(&msg, NULL, 0, 0) == 1) {
2244 if (!(IsWindow(keylist) && IsDialogMessage(keylist, &msg)) &&
2245 !(IsWindow(aboutbox) && IsDialogMessage(aboutbox, &msg))) {
2246 TranslateMessage(&msg);
2247 DispatchMessage(&msg);
2251 /* Clean up the system tray icon */
2253 NOTIFYICONDATA tnid;
2255 tnid.cbSize = sizeof(NOTIFYICONDATA);
2259 Shell_NotifyIcon(NIM_DELETE, &tnid);
2261 DestroyMenu(systray_menu);
2264 if (keypath) filereq_free(keypath);
2266 cleanup_exit(msg.wParam);
2267 return msg.wParam; /* just in case optimiser complains */