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 two spaces in the fingerprint with tabs, for
355 * nice alignment in the box.
357 p = skey->alg->fingerprint(skey->data);
358 listentry = dupprintf("%s\t%s", p, skey->comment);
359 fp_len = strlen(listentry);
362 p = strchr(listentry, ' ');
363 if (p && p < listentry + fp_len)
365 p = strchr(listentry, ' ');
366 if (p && p < listentry + fp_len)
369 SendDlgItemMessage(keylist, 100, LB_ADDSTRING, 0,
373 SendDlgItemMessage(keylist, 100, LB_SETCURSEL, (WPARAM) - 1, 0);
378 * This function loads a key from a file and adds it.
380 static void add_keyfile(Filename *filename)
383 struct RSAKey *rkey = NULL;
384 struct ssh2_userkey *skey = NULL;
389 const char *error = NULL;
393 type = key_type(filename);
394 if (type != SSH_KEYTYPE_SSH1 && type != SSH_KEYTYPE_SSH2) {
395 char *msg = dupprintf("Couldn't load this key (%s)",
396 key_type_to_str(type));
397 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
398 HELPCTXID(errors_cantloadkey));
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, keylistlen;
412 if (type == SSH_KEYTYPE_SSH1) {
413 if (!rsakey_pubblob(filename, &blob, &bloblen, NULL, &error)) {
414 char *msg = dupprintf("Couldn't load private key (%s)", error);
415 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
416 HELPCTXID(errors_cantloadkey));
420 keylist = get_keylist1(&keylistlen);
422 unsigned char *blob2;
423 blob = ssh2_userkey_loadpub(filename, NULL, &bloblen,
426 char *msg = dupprintf("Couldn't load private key (%s)", error);
427 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
428 HELPCTXID(errors_cantloadkey));
432 /* For our purposes we want the blob prefixed with its length */
433 blob2 = snewn(bloblen+4, unsigned char);
434 PUT_32BIT(blob2, bloblen);
435 memcpy(blob2 + 4, blob, bloblen);
439 keylist = get_keylist2(&keylistlen);
442 if (keylistlen < 4) {
443 MessageBox(NULL, "Received broken key list?!", APPNAME,
444 MB_OK | MB_ICONERROR);
447 nkeys = toint(GET_32BIT(keylist));
449 MessageBox(NULL, "Received broken key list?!", APPNAME,
450 MB_OK | MB_ICONERROR);
456 for (i = 0; i < nkeys; i++) {
457 if (!memcmp(blob, p, bloblen)) {
458 /* Key is already present; we can now leave. */
463 /* Now skip over public blob */
464 if (type == SSH_KEYTYPE_SSH1) {
465 int n = rsa_public_blob_len(p, keylistlen);
467 MessageBox(NULL, "Received broken key list?!", APPNAME,
468 MB_OK | MB_ICONERROR);
475 if (keylistlen < 4) {
476 MessageBox(NULL, "Received broken key list?!", APPNAME,
477 MB_OK | MB_ICONERROR);
480 n = toint(4 + GET_32BIT(p));
481 if (n < 0 || keylistlen < n) {
482 MessageBox(NULL, "Received broken key list?!", APPNAME,
483 MB_OK | MB_ICONERROR);
489 /* Now skip over comment field */
492 if (keylistlen < 4) {
493 MessageBox(NULL, "Received broken key list?!", APPNAME,
494 MB_OK | MB_ICONERROR);
497 n = toint(4 + GET_32BIT(p));
498 if (n < 0 || keylistlen < n) {
499 MessageBox(NULL, "Received broken key list?!", APPNAME,
500 MB_OK | MB_ICONERROR);
515 if (type == SSH_KEYTYPE_SSH1)
516 needs_pass = rsakey_encrypted(filename, &comment);
518 needs_pass = ssh2_userkey_encrypted(filename, &comment);
520 if (type == SSH_KEYTYPE_SSH1)
521 rkey = snew(struct RSAKey);
529 /* try all the remembered passphrases first */
530 char *pp = index234(passphrases, attempts);
532 passphrase = dupstr(pp);
535 struct PassphraseProcStruct pps;
537 pps.passphrase = &passphrase;
538 pps.comment = comment;
541 dlgret = DialogBoxParam(hinst, MAKEINTRESOURCE(210),
542 NULL, PassphraseProc, (LPARAM) &pps);
543 passphrase_box = NULL;
547 if (type == SSH_KEYTYPE_SSH1)
549 return; /* operation cancelled */
552 assert(passphrase != NULL);
555 passphrase = dupstr("");
557 if (type == SSH_KEYTYPE_SSH1)
558 ret = loadrsakey(filename, rkey, passphrase, &error);
560 skey = ssh2_load_userkey(filename, passphrase, &error);
561 if (skey == SSH2_WRONG_PASSPHRASE)
571 if(original_pass && ret) {
572 /* If they typed in an ok passphrase, remember it */
573 addpos234(passphrases, passphrase, 0);
575 /* Otherwise, destroy it */
583 char *msg = dupprintf("Couldn't load private key (%s)", error);
584 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
585 HELPCTXID(errors_cantloadkey));
587 if (type == SSH_KEYTYPE_SSH1)
591 if (type == SSH_KEYTYPE_SSH1) {
592 if (already_running) {
593 unsigned char *request, *response;
595 int reqlen, clen, resplen, ret;
597 clen = strlen(rkey->comment);
599 reqlen = 4 + 1 + /* length, message type */
601 ssh1_bignum_length(rkey->modulus) +
602 ssh1_bignum_length(rkey->exponent) +
603 ssh1_bignum_length(rkey->private_exponent) +
604 ssh1_bignum_length(rkey->iqmp) +
605 ssh1_bignum_length(rkey->p) +
606 ssh1_bignum_length(rkey->q) + 4 + clen /* comment */
609 request = snewn(reqlen, unsigned char);
611 request[4] = SSH1_AGENTC_ADD_RSA_IDENTITY;
613 PUT_32BIT(request + reqlen, bignum_bitcount(rkey->modulus));
615 reqlen += ssh1_write_bignum(request + reqlen, rkey->modulus);
616 reqlen += ssh1_write_bignum(request + reqlen, rkey->exponent);
618 ssh1_write_bignum(request + reqlen,
619 rkey->private_exponent);
620 reqlen += ssh1_write_bignum(request + reqlen, rkey->iqmp);
621 reqlen += ssh1_write_bignum(request + reqlen, rkey->p);
622 reqlen += ssh1_write_bignum(request + reqlen, rkey->q);
623 PUT_32BIT(request + reqlen, clen);
624 memcpy(request + reqlen + 4, rkey->comment, clen);
626 PUT_32BIT(request, reqlen - 4);
628 ret = agent_query(request, reqlen, &vresponse, &resplen,
631 response = vresponse;
632 if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
633 MessageBox(NULL, "The already running Pageant "
634 "refused to add the key.", APPNAME,
635 MB_OK | MB_ICONERROR);
640 if (add234(rsakeys, rkey) != rkey)
641 sfree(rkey); /* already present, don't waste RAM */
644 if (already_running) {
645 unsigned char *request, *response;
647 int reqlen, alglen, clen, keybloblen, resplen, ret;
648 alglen = strlen(skey->alg->name);
649 clen = strlen(skey->comment);
651 keybloblen = skey->alg->openssh_fmtkey(skey->data, NULL, 0);
653 reqlen = 4 + 1 + /* length, message type */
654 4 + alglen + /* algorithm name */
655 keybloblen + /* key data */
656 4 + clen /* comment */
659 request = snewn(reqlen, unsigned char);
661 request[4] = SSH2_AGENTC_ADD_IDENTITY;
663 PUT_32BIT(request + reqlen, alglen);
665 memcpy(request + reqlen, skey->alg->name, alglen);
667 reqlen += skey->alg->openssh_fmtkey(skey->data,
670 PUT_32BIT(request + reqlen, clen);
671 memcpy(request + reqlen + 4, skey->comment, clen);
673 PUT_32BIT(request, reqlen - 4);
675 ret = agent_query(request, reqlen, &vresponse, &resplen,
678 response = vresponse;
679 if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
680 MessageBox(NULL, "The already running Pageant "
681 "refused to add the key.", APPNAME,
682 MB_OK | MB_ICONERROR);
687 if (add234(ssh2keys, skey) != skey) {
688 skey->alg->freekey(skey->data);
689 sfree(skey); /* already present, don't waste RAM */
696 * Create an SSH-1 key list in a malloc'ed buffer; return its
699 static void *make_keylist1(int *length)
703 unsigned char *blob, *p, *ret;
707 * Count up the number and length of keys we hold.
711 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
713 blob = rsa_public_blob(key, &bloblen);
716 len += 4 + strlen(key->comment);
719 /* Allocate the buffer. */
720 p = ret = snewn(len, unsigned char);
721 if (length) *length = len;
725 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
726 blob = rsa_public_blob(key, &bloblen);
727 memcpy(p, blob, bloblen);
730 PUT_32BIT(p, strlen(key->comment));
731 memcpy(p + 4, key->comment, strlen(key->comment));
732 p += 4 + strlen(key->comment);
735 assert(p - ret == len);
740 * Create an SSH-2 key list in a malloc'ed buffer; return its
743 static void *make_keylist2(int *length)
745 struct ssh2_userkey *key;
747 unsigned char *blob, *p, *ret;
751 * Count up the number and length of keys we hold.
755 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
757 len += 4; /* length field */
758 blob = key->alg->public_blob(key->data, &bloblen);
761 len += 4 + strlen(key->comment);
764 /* Allocate the buffer. */
765 p = ret = snewn(len, unsigned char);
766 if (length) *length = len;
769 * Packet header is the obvious five bytes, plus four
770 * bytes for the key count.
774 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
775 blob = key->alg->public_blob(key->data, &bloblen);
776 PUT_32BIT(p, bloblen);
778 memcpy(p, blob, bloblen);
781 PUT_32BIT(p, strlen(key->comment));
782 memcpy(p + 4, key->comment, strlen(key->comment));
783 p += 4 + strlen(key->comment);
786 assert(p - ret == len);
791 * Acquire a keylist1 from the primary Pageant; this means either
792 * calling make_keylist1 (if that's us) or sending a message to the
793 * primary Pageant (if it's not).
795 static void *get_keylist1(int *length)
799 if (already_running) {
800 unsigned char request[5], *response;
803 request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
804 PUT_32BIT(request, 4);
806 retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
808 response = vresponse;
809 if (resplen < 5 || response[4] != SSH1_AGENT_RSA_IDENTITIES_ANSWER) {
814 ret = snewn(resplen-5, unsigned char);
815 memcpy(ret, response+5, resplen-5);
821 ret = make_keylist1(length);
827 * Acquire a keylist2 from the primary Pageant; this means either
828 * calling make_keylist2 (if that's us) or sending a message to the
829 * primary Pageant (if it's not).
831 static void *get_keylist2(int *length)
835 if (already_running) {
836 unsigned char request[5], *response;
840 request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
841 PUT_32BIT(request, 4);
843 retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
845 response = vresponse;
846 if (resplen < 5 || response[4] != SSH2_AGENT_IDENTITIES_ANSWER) {
851 ret = snewn(resplen-5, unsigned char);
852 memcpy(ret, response+5, resplen-5);
858 ret = make_keylist2(length);
864 * This is the main agent function that answers messages.
866 static void answer_msg(void *msg)
868 unsigned char *p = msg;
869 unsigned char *ret = msg;
870 unsigned char *msgend;
874 * Get the message length.
876 msgend = p + 4 + GET_32BIT(p);
879 * Get the message type.
887 case SSH1_AGENTC_REQUEST_RSA_IDENTITIES:
889 * Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER.
895 ret[4] = SSH1_AGENT_RSA_IDENTITIES_ANSWER;
896 keylist = make_keylist1(&len);
897 if (len + 5 > AGENT_MAX_MSGLEN) {
901 PUT_32BIT(ret, len + 1);
902 memcpy(ret + 5, keylist, len);
906 case SSH2_AGENTC_REQUEST_IDENTITIES:
908 * Reply with SSH2_AGENT_IDENTITIES_ANSWER.
914 ret[4] = SSH2_AGENT_IDENTITIES_ANSWER;
915 keylist = make_keylist2(&len);
916 if (len + 5 > AGENT_MAX_MSGLEN) {
920 PUT_32BIT(ret, len + 1);
921 memcpy(ret + 5, keylist, len);
925 case SSH1_AGENTC_RSA_CHALLENGE:
927 * Reply with either SSH1_AGENT_RSA_RESPONSE or
928 * SSH_AGENT_FAILURE, depending on whether we have that key
932 struct RSAKey reqkey, *key;
933 Bignum challenge, response;
934 unsigned char response_source[48], response_md5[16];
935 struct MD5Context md5c;
939 i = ssh1_read_bignum(p, msgend - p, &reqkey.exponent);
943 i = ssh1_read_bignum(p, msgend - p, &reqkey.modulus);
945 freebn(reqkey.exponent);
949 i = ssh1_read_bignum(p, msgend - p, &challenge);
951 freebn(reqkey.exponent);
952 freebn(reqkey.modulus);
957 freebn(reqkey.exponent);
958 freebn(reqkey.modulus);
962 memcpy(response_source + 32, p, 16);
966 (key = find234(rsakeys, &reqkey, NULL)) == NULL) {
967 freebn(reqkey.exponent);
968 freebn(reqkey.modulus);
972 response = rsadecrypt(challenge, key);
973 for (i = 0; i < 32; i++)
974 response_source[i] = bignum_byte(response, 31 - i);
977 MD5Update(&md5c, response_source, 48);
978 MD5Final(response_md5, &md5c);
979 smemclr(response_source, 48); /* burn the evidence */
980 freebn(response); /* and that evidence */
981 freebn(challenge); /* yes, and that evidence */
982 freebn(reqkey.exponent); /* and free some memory ... */
983 freebn(reqkey.modulus); /* ... while we're at it. */
986 * Packet is the obvious five byte header, plus sixteen
990 PUT_32BIT(ret, len - 4);
991 ret[4] = SSH1_AGENT_RSA_RESPONSE;
992 memcpy(ret + 5, response_md5, 16);
995 case SSH2_AGENTC_SIGN_REQUEST:
997 * Reply with either SSH2_AGENT_SIGN_RESPONSE or
998 * SSH_AGENT_FAILURE, depending on whether we have that key
1002 struct ssh2_userkey *key;
1004 unsigned char *data, *signature;
1005 int datalen, siglen, len;
1009 b.len = toint(GET_32BIT(p));
1010 if (b.len < 0 || b.len > msgend - (p+4))
1017 datalen = toint(GET_32BIT(p));
1019 if (datalen < 0 || datalen > msgend - p)
1022 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
1025 signature = key->alg->sign(key->data, data, datalen, &siglen);
1026 len = 5 + 4 + siglen;
1027 PUT_32BIT(ret, len - 4);
1028 ret[4] = SSH2_AGENT_SIGN_RESPONSE;
1029 PUT_32BIT(ret + 5, siglen);
1030 memcpy(ret + 5 + 4, signature, siglen);
1034 case SSH1_AGENTC_ADD_RSA_IDENTITY:
1036 * Add to the list and return SSH_AGENT_SUCCESS, or
1037 * SSH_AGENT_FAILURE if the key was malformed.
1044 key = snew(struct RSAKey);
1045 memset(key, 0, sizeof(struct RSAKey));
1047 n = makekey(p, msgend - p, key, NULL, 1);
1055 n = makeprivate(p, msgend - p, key);
1063 n = ssh1_read_bignum(p, msgend - p, &key->iqmp); /* p^-1 mod q */
1071 n = ssh1_read_bignum(p, msgend - p, &key->p); /* p */
1079 n = ssh1_read_bignum(p, msgend - p, &key->q); /* q */
1092 commentlen = toint(GET_32BIT(p));
1094 if (commentlen < 0 || commentlen > msgend - p) {
1100 comment = snewn(commentlen+1, char);
1102 memcpy(comment, p + 4, commentlen);
1103 comment[commentlen] = '\0';
1104 key->comment = comment;
1107 ret[4] = SSH_AGENT_FAILURE;
1108 if (add234(rsakeys, key) == key) {
1110 ret[4] = SSH_AGENT_SUCCESS;
1117 case SSH2_AGENTC_ADD_IDENTITY:
1119 * Add to the list and return SSH_AGENT_SUCCESS, or
1120 * SSH_AGENT_FAILURE if the key was malformed.
1123 struct ssh2_userkey *key;
1124 char *comment, *alg;
1125 int alglen, commlen;
1131 alglen = toint(GET_32BIT(p));
1133 if (alglen < 0 || alglen > msgend - p)
1138 key = snew(struct ssh2_userkey);
1139 /* Add further algorithm names here. */
1140 if (alglen == 7 && !memcmp(alg, "ssh-rsa", 7))
1141 key->alg = &ssh_rsa;
1142 else if (alglen == 7 && !memcmp(alg, "ssh-dss", 7))
1143 key->alg = &ssh_dss;
1144 else if (alglen == 19 && memcmp(alg, "ecdsa-sha2-nistp256", 19))
1145 key->alg = &ssh_ecdsa_nistp256;
1146 else if (alglen == 19 && memcmp(alg, "ecdsa-sha2-nistp384", 19))
1147 key->alg = &ssh_ecdsa_nistp384;
1148 else if (alglen == 19 && memcmp(alg, "ecdsa-sha2-nistp521", 19))
1149 key->alg = &ssh_ecdsa_nistp521;
1155 bloblen = msgend - p;
1156 key->data = key->alg->openssh_createkey(&p, &bloblen);
1163 * p has been advanced by openssh_createkey, but
1164 * certainly not _beyond_ the end of the buffer.
1166 assert(p <= msgend);
1169 key->alg->freekey(key->data);
1173 commlen = toint(GET_32BIT(p));
1176 if (commlen < 0 || commlen > msgend - p) {
1177 key->alg->freekey(key->data);
1181 comment = snewn(commlen + 1, char);
1183 memcpy(comment, p, commlen);
1184 comment[commlen] = '\0';
1186 key->comment = comment;
1189 ret[4] = SSH_AGENT_FAILURE;
1190 if (add234(ssh2keys, key) == key) {
1192 ret[4] = SSH_AGENT_SUCCESS;
1194 key->alg->freekey(key->data);
1195 sfree(key->comment);
1200 case SSH1_AGENTC_REMOVE_RSA_IDENTITY:
1202 * Remove from the list and return SSH_AGENT_SUCCESS, or
1203 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
1207 struct RSAKey reqkey, *key;
1210 n = makekey(p, msgend - p, &reqkey, NULL, 0);
1214 key = find234(rsakeys, &reqkey, NULL);
1215 freebn(reqkey.exponent);
1216 freebn(reqkey.modulus);
1218 ret[4] = SSH_AGENT_FAILURE;
1220 del234(rsakeys, key);
1224 ret[4] = SSH_AGENT_SUCCESS;
1228 case SSH2_AGENTC_REMOVE_IDENTITY:
1230 * Remove from the list and return SSH_AGENT_SUCCESS, or
1231 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
1235 struct ssh2_userkey *key;
1240 b.len = toint(GET_32BIT(p));
1243 if (b.len < 0 || b.len > msgend - p)
1248 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
1253 ret[4] = SSH_AGENT_FAILURE;
1255 del234(ssh2keys, key);
1257 key->alg->freekey(key->data);
1259 ret[4] = SSH_AGENT_SUCCESS;
1263 case SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES:
1265 * Remove all SSH-1 keys. Always returns success.
1268 struct RSAKey *rkey;
1270 while ((rkey = index234(rsakeys, 0)) != NULL) {
1271 del234(rsakeys, rkey);
1278 ret[4] = SSH_AGENT_SUCCESS;
1281 case SSH2_AGENTC_REMOVE_ALL_IDENTITIES:
1283 * Remove all SSH-2 keys. Always returns success.
1286 struct ssh2_userkey *skey;
1288 while ((skey = index234(ssh2keys, 0)) != NULL) {
1289 del234(ssh2keys, skey);
1290 skey->alg->freekey(skey->data);
1296 ret[4] = SSH_AGENT_SUCCESS;
1302 * Unrecognised message. Return SSH_AGENT_FAILURE.
1305 ret[4] = SSH_AGENT_FAILURE;
1311 * Key comparison function for the 2-3-4 tree of RSA keys.
1313 static int cmpkeys_rsa(void *av, void *bv)
1315 struct RSAKey *a = (struct RSAKey *) av;
1316 struct RSAKey *b = (struct RSAKey *) bv;
1323 * Compare by length of moduli.
1325 alen = bignum_bitcount(am);
1326 blen = bignum_bitcount(bm);
1329 else if (alen < blen)
1332 * Now compare by moduli themselves.
1334 alen = (alen + 7) / 8; /* byte count */
1335 while (alen-- > 0) {
1337 abyte = bignum_byte(am, alen);
1338 bbyte = bignum_byte(bm, alen);
1341 else if (abyte < bbyte)
1351 * Key comparison function for the 2-3-4 tree of SSH-2 keys.
1353 static int cmpkeys_ssh2(void *av, void *bv)
1355 struct ssh2_userkey *a = (struct ssh2_userkey *) av;
1356 struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
1359 unsigned char *ablob, *bblob;
1363 * Compare purely by public blob.
1365 ablob = a->alg->public_blob(a->data, &alen);
1366 bblob = b->alg->public_blob(b->data, &blen);
1369 for (i = 0; i < alen && i < blen; i++) {
1370 if (ablob[i] < bblob[i]) {
1373 } else if (ablob[i] > bblob[i]) {
1378 if (c == 0 && i < alen)
1379 c = +1; /* a is longer */
1380 if (c == 0 && i < blen)
1381 c = -1; /* a is longer */
1390 * Key comparison function for looking up a blob in the 2-3-4 tree
1393 static int cmpkeys_ssh2_asymm(void *av, void *bv)
1395 struct blob *a = (struct blob *) av;
1396 struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
1399 unsigned char *ablob, *bblob;
1403 * Compare purely by public blob.
1407 bblob = b->alg->public_blob(b->data, &blen);
1410 for (i = 0; i < alen && i < blen; i++) {
1411 if (ablob[i] < bblob[i]) {
1414 } else if (ablob[i] > bblob[i]) {
1419 if (c == 0 && i < alen)
1420 c = +1; /* a is longer */
1421 if (c == 0 && i < blen)
1422 c = -1; /* a is longer */
1430 * Prompt for a key file to add, and add it.
1432 static void prompt_add_keyfile(void)
1435 char *filelist = snewn(8192, char);
1437 if (!keypath) keypath = filereq_new();
1438 memset(&of, 0, sizeof(of));
1439 of.hwndOwner = hwnd;
1440 of.lpstrFilter = FILTER_KEY_FILES;
1441 of.lpstrCustomFilter = NULL;
1442 of.nFilterIndex = 1;
1443 of.lpstrFile = filelist;
1446 of.lpstrFileTitle = NULL;
1447 of.lpstrTitle = "Select Private Key File";
1448 of.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER;
1449 if (request_file(keypath, &of, TRUE, FALSE)) {
1450 if(strlen(filelist) > of.nFileOffset) {
1451 /* Only one filename returned? */
1452 Filename *fn = filename_from_str(filelist);
1456 /* we are returned a bunch of strings, end to
1457 * end. first string is the directory, the
1458 * rest the filenames. terminated with an
1461 char *dir = filelist;
1462 char *filewalker = filelist + strlen(dir) + 1;
1463 while (*filewalker != '\0') {
1464 char *filename = dupcat(dir, "\\", filewalker, NULL);
1465 Filename *fn = filename_from_str(filename);
1469 filewalker += strlen(filewalker) + 1;
1474 forget_passphrases();
1480 * Dialog-box function for the key list box.
1482 static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
1483 WPARAM wParam, LPARAM lParam)
1485 struct RSAKey *rkey;
1486 struct ssh2_userkey *skey;
1491 * Centre the window.
1493 { /* centre the window */
1497 hw = GetDesktopWindow();
1498 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
1500 (rs.right + rs.left + rd.left - rd.right) / 2,
1501 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
1502 rd.right - rd.left, rd.bottom - rd.top, TRUE);
1506 SetWindowLongPtr(hwnd, GWL_EXSTYLE,
1507 GetWindowLongPtr(hwnd, GWL_EXSTYLE) |
1510 HWND item = GetDlgItem(hwnd, 103); /* the Help button */
1512 DestroyWindow(item);
1517 static int tabs[] = { 35, 60, 210 };
1518 SendDlgItemMessage(hwnd, 100, LB_SETTABSTOPS,
1519 sizeof(tabs) / sizeof(*tabs),
1525 switch (LOWORD(wParam)) {
1529 DestroyWindow(hwnd);
1531 case 101: /* add key */
1532 if (HIWORD(wParam) == BN_CLICKED ||
1533 HIWORD(wParam) == BN_DOUBLECLICKED) {
1534 if (passphrase_box) {
1535 MessageBeep(MB_ICONERROR);
1536 SetForegroundWindow(passphrase_box);
1539 prompt_add_keyfile();
1542 case 102: /* remove key */
1543 if (HIWORD(wParam) == BN_CLICKED ||
1544 HIWORD(wParam) == BN_DOUBLECLICKED) {
1549 /* our counter within the array of selected items */
1552 /* get the number of items selected in the list */
1554 SendDlgItemMessage(hwnd, 100, LB_GETSELCOUNT, 0, 0);
1556 /* none selected? that was silly */
1557 if (numSelected == 0) {
1562 /* get item indices in an array */
1563 selectedArray = snewn(numSelected, int);
1564 SendDlgItemMessage(hwnd, 100, LB_GETSELITEMS,
1565 numSelected, (WPARAM)selectedArray);
1567 itemNum = numSelected - 1;
1568 rCount = count234(rsakeys);
1569 sCount = count234(ssh2keys);
1571 /* go through the non-rsakeys until we've covered them all,
1572 * and/or we're out of selected items to check. note that
1573 * we go *backwards*, to avoid complications from deleting
1574 * things hence altering the offset of subsequent items
1576 for (i = sCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1577 skey = index234(ssh2keys, i);
1579 if (selectedArray[itemNum] == rCount + i) {
1580 del234(ssh2keys, skey);
1581 skey->alg->freekey(skey->data);
1587 /* do the same for the rsa keys */
1588 for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1589 rkey = index234(rsakeys, i);
1591 if(selectedArray[itemNum] == i) {
1592 del234(rsakeys, rkey);
1599 sfree(selectedArray);
1603 case 103: /* help */
1604 if (HIWORD(wParam) == BN_CLICKED ||
1605 HIWORD(wParam) == BN_DOUBLECLICKED) {
1606 launch_help(hwnd, WINHELP_CTX_pageant_general);
1613 int id = ((LPHELPINFO)lParam)->iCtrlId;
1616 case 100: topic = WINHELP_CTX_pageant_keylist; break;
1617 case 101: topic = WINHELP_CTX_pageant_addkey; break;
1618 case 102: topic = WINHELP_CTX_pageant_remkey; break;
1621 launch_help(hwnd, topic);
1629 DestroyWindow(hwnd);
1635 /* Set up a system tray icon */
1636 static BOOL AddTrayIcon(HWND hwnd)
1639 NOTIFYICONDATA tnid;
1642 #ifdef NIM_SETVERSION
1644 res = Shell_NotifyIcon(NIM_SETVERSION, &tnid);
1647 tnid.cbSize = sizeof(NOTIFYICONDATA);
1649 tnid.uID = 1; /* unique within this systray use */
1650 tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
1651 tnid.uCallbackMessage = WM_SYSTRAY;
1652 tnid.hIcon = hicon = LoadIcon(hinst, MAKEINTRESOURCE(201));
1653 strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
1655 res = Shell_NotifyIcon(NIM_ADD, &tnid);
1657 if (hicon) DestroyIcon(hicon);
1662 /* Update the saved-sessions menu. */
1663 static void update_sessions(void)
1667 TCHAR buf[MAX_PATH + 1];
1670 int index_key, index_menu;
1675 if(ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, PUTTY_REGKEY, &hkey))
1678 for(num_entries = GetMenuItemCount(session_menu);
1679 num_entries > initial_menuitems_count;
1681 RemoveMenu(session_menu, 0, MF_BYPOSITION);
1686 while(ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) {
1687 TCHAR session_name[MAX_PATH + 1];
1688 unmungestr(buf, session_name, MAX_PATH);
1689 if(strcmp(buf, PUTTY_DEFAULT) != 0) {
1690 memset(&mii, 0, sizeof(mii));
1691 mii.cbSize = sizeof(mii);
1692 mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
1693 mii.fType = MFT_STRING;
1694 mii.fState = MFS_ENABLED;
1695 mii.wID = (index_menu * 16) + IDM_SESSIONS_BASE;
1696 mii.dwTypeData = session_name;
1697 InsertMenuItem(session_menu, index_menu, TRUE, &mii);
1705 if(index_menu == 0) {
1706 mii.cbSize = sizeof(mii);
1707 mii.fMask = MIIM_TYPE | MIIM_STATE;
1708 mii.fType = MFT_STRING;
1709 mii.fState = MFS_GRAYED;
1710 mii.dwTypeData = _T("(No sessions)");
1711 InsertMenuItem(session_menu, index_menu, TRUE, &mii);
1717 * Versions of Pageant prior to 0.61 expected this SID on incoming
1718 * communications. For backwards compatibility, and more particularly
1719 * for compatibility with derived works of PuTTY still using the old
1720 * Pageant client code, we accept it as an alternative to the one
1721 * returned from get_user_sid() in winpgntc.c.
1723 PSID get_default_sid(void)
1727 PSECURITY_DESCRIPTOR psd = NULL;
1728 PSID sid = NULL, copy = NULL, ret = NULL;
1730 if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE,
1731 GetCurrentProcessId())) == NULL)
1734 if (p_GetSecurityInfo(proc, SE_KERNEL_OBJECT, OWNER_SECURITY_INFORMATION,
1735 &sid, NULL, NULL, NULL, &psd) != ERROR_SUCCESS)
1738 sidlen = GetLengthSid(sid);
1740 copy = (PSID)smalloc(sidlen);
1742 if (!CopySid(sidlen, copy, sid))
1745 /* Success. Move sid into the return value slot, and null it out
1746 * to stop the cleanup code freeing it. */
1762 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1763 WPARAM wParam, LPARAM lParam)
1766 static int menuinprogress;
1767 static UINT msgTaskbarCreated = 0;
1771 msgTaskbarCreated = RegisterWindowMessage(_T("TaskbarCreated"));
1774 if (message==msgTaskbarCreated) {
1776 * Explorer has been restarted, so the tray icon will
1784 if (lParam == WM_RBUTTONUP) {
1786 GetCursorPos(&cursorpos);
1787 PostMessage(hwnd, WM_SYSTRAY2, cursorpos.x, cursorpos.y);
1788 } else if (lParam == WM_LBUTTONDBLCLK) {
1789 /* Run the default menu item. */
1790 UINT menuitem = GetMenuDefaultItem(systray_menu, FALSE, 0);
1792 PostMessage(hwnd, WM_COMMAND, menuitem, 0);
1796 if (!menuinprogress) {
1799 SetForegroundWindow(hwnd);
1800 ret = TrackPopupMenu(systray_menu,
1801 TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
1803 wParam, lParam, 0, hwnd, NULL);
1809 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1811 if((int)ShellExecute(hwnd, NULL, putty_path, _T(""), _T(""),
1813 MessageBox(NULL, "Unable to execute PuTTY!",
1814 "Error", MB_OK | MB_ICONERROR);
1819 SendMessage(passphrase_box, WM_CLOSE, 0, 0);
1820 SendMessage(hwnd, WM_CLOSE, 0, 0);
1824 keylist = CreateDialog(hinst, MAKEINTRESOURCE(211),
1826 ShowWindow(keylist, SW_SHOWNORMAL);
1829 * Sometimes the window comes up minimised / hidden for
1830 * no obvious reason. Prevent this. This also brings it
1831 * to the front if it's already present (the user
1832 * selected View Keys because they wanted to _see_ the
1835 SetForegroundWindow(keylist);
1836 SetWindowPos(keylist, HWND_TOP, 0, 0, 0, 0,
1837 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1840 if (passphrase_box) {
1841 MessageBeep(MB_ICONERROR);
1842 SetForegroundWindow(passphrase_box);
1845 prompt_add_keyfile();
1849 aboutbox = CreateDialog(hinst, MAKEINTRESOURCE(213),
1851 ShowWindow(aboutbox, SW_SHOWNORMAL);
1853 * Sometimes the window comes up minimised / hidden
1854 * for no obvious reason. Prevent this.
1856 SetForegroundWindow(aboutbox);
1857 SetWindowPos(aboutbox, HWND_TOP, 0, 0, 0, 0,
1858 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1862 launch_help(hwnd, WINHELP_CTX_pageant_general);
1866 if(wParam >= IDM_SESSIONS_BASE && wParam <= IDM_SESSIONS_MAX) {
1868 TCHAR buf[MAX_PATH + 1];
1869 TCHAR param[MAX_PATH + 1];
1870 memset(&mii, 0, sizeof(mii));
1871 mii.cbSize = sizeof(mii);
1872 mii.fMask = MIIM_TYPE;
1874 mii.dwTypeData = buf;
1875 GetMenuItemInfo(session_menu, wParam, FALSE, &mii);
1877 strcat(param, mii.dwTypeData);
1878 if((int)ShellExecute(hwnd, NULL, putty_path, param,
1879 _T(""), SW_SHOW) <= 32) {
1880 MessageBox(NULL, "Unable to execute PuTTY!", "Error",
1881 MB_OK | MB_ICONERROR);
1894 COPYDATASTRUCT *cds;
1899 PSID mapowner, ourself, ourself2;
1901 PSECURITY_DESCRIPTOR psd = NULL;
1904 cds = (COPYDATASTRUCT *) lParam;
1905 if (cds->dwData != AGENT_COPYDATA_ID)
1906 return 0; /* not our message, mate */
1907 mapname = (char *) cds->lpData;
1908 if (mapname[cds->cbData - 1] != '\0')
1909 return 0; /* failure to be ASCIZ! */
1911 debug(("mapname is :%s:\n", mapname));
1913 filemap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, mapname);
1915 debug(("filemap is %p\n", filemap));
1917 if (filemap != NULL && filemap != INVALID_HANDLE_VALUE) {
1921 if ((ourself = get_user_sid()) == NULL) {
1923 debug(("couldn't get user SID\n"));
1925 CloseHandle(filemap);
1929 if ((ourself2 = get_default_sid()) == NULL) {
1931 debug(("couldn't get default SID\n"));
1933 CloseHandle(filemap);
1938 if ((rc = p_GetSecurityInfo(filemap, SE_KERNEL_OBJECT,
1939 OWNER_SECURITY_INFORMATION,
1940 &mapowner, NULL, NULL, NULL,
1941 &psd) != ERROR_SUCCESS)) {
1943 debug(("couldn't get owner info for filemap: %d\n",
1946 CloseHandle(filemap);
1953 LPTSTR ours, ours2, theirs;
1954 ConvertSidToStringSid(mapowner, &theirs);
1955 ConvertSidToStringSid(ourself, &ours);
1956 ConvertSidToStringSid(ourself2, &ours2);
1957 debug(("got sids:\n oursnew=%s\n oursold=%s\n"
1958 " theirs=%s\n", ours, ours2, theirs));
1964 if (!EqualSid(mapowner, ourself) &&
1965 !EqualSid(mapowner, ourself2)) {
1966 CloseHandle(filemap);
1970 return 0; /* security ID mismatch! */
1973 debug(("security stuff matched\n"));
1980 debug(("security APIs not present\n"));
1984 p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
1986 debug(("p is %p\n", p));
1989 for (i = 0; i < 5; i++)
1990 debug(("p[%d]=%02x\n", i,
1991 ((unsigned char *) p)[i]));
1998 CloseHandle(filemap);
2003 return DefWindowProc(hwnd, message, wParam, lParam);
2007 * Fork and Exec the command in cmdline. [DBW]
2009 void spawn_cmd(char *cmdline, char * args, int show)
2011 if (ShellExecute(NULL, _T("open"), cmdline,
2012 args, NULL, show) <= (HINSTANCE) 32) {
2014 msg = dupprintf("Failed to run \"%.100s\", Error: %d", cmdline,
2015 (int)GetLastError());
2016 MessageBox(NULL, msg, APPNAME, MB_OK | MB_ICONEXCLAMATION);
2022 * This is a can't-happen stub, since Pageant never makes
2023 * asynchronous agent requests.
2025 void agent_schedule_callback(void (*callback)(void *, void *, int),
2026 void *callback_ctx, void *data, int len)
2028 assert(!"We shouldn't get here");
2031 void cleanup_exit(int code)
2037 int flags = FLAG_SYNCAGENT;
2039 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
2043 char *command = NULL;
2046 char **argv, **argstart;
2052 * Determine whether we're an NT system (should have security
2053 * APIs) or a non-NT system (don't do security).
2057 modalfatalbox("Windows refuses to report a version");
2059 if (osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT) {
2060 has_security = TRUE;
2062 has_security = FALSE;
2067 * Attempt to get the security API we need.
2069 if (!got_advapi()) {
2071 "Unable to access security APIs. Pageant will\n"
2072 "not run, in case it causes a security breach.",
2073 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
2078 "This program has been compiled for Win9X and will\n"
2079 "not run on NT, in case it causes a security breach.",
2080 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
2086 * See if we can find our Help file.
2091 * Look for the PuTTY binary (we will enable the saved session
2092 * submenu if we find it).
2095 char b[2048], *p, *q, *r;
2097 GetModuleFileName(NULL, b, sizeof(b) - 16);
2099 p = strrchr(b, '\\');
2100 if (p && p >= r) r = p+1;
2101 q = strrchr(b, ':');
2102 if (q && q >= r) r = q+1;
2103 strcpy(r, "putty.exe");
2104 if ( (fp = fopen(b, "r")) != NULL) {
2105 putty_path = dupstr(b);
2112 * Find out if Pageant is already running.
2114 already_running = agent_exists();
2117 * Initialise storage for RSA keys.
2119 if (!already_running) {
2120 rsakeys = newtree234(cmpkeys_rsa);
2121 ssh2keys = newtree234(cmpkeys_ssh2);
2125 * Initialise storage for short-term passphrase cache.
2127 passphrases = newtree234(NULL);
2130 * Process the command line and add keys as listed on it.
2132 split_into_argv(cmdline, &argc, &argv, &argstart);
2133 for (i = 0; i < argc; i++) {
2134 if (!strcmp(argv[i], "-pgpfp")) {
2137 } else if (!strcmp(argv[i], "-c")) {
2139 * If we see `-c', then the rest of the
2140 * command line should be treated as a
2141 * command to be spawned.
2144 command = argstart[i+1];
2149 Filename *fn = filename_from_str(argv[i]);
2157 * Forget any passphrase that we retained while going over
2158 * command line keyfiles.
2160 forget_passphrases();
2164 if (command[0] == '"')
2165 args = strchr(++command, '"');
2167 args = strchr(command, ' ');
2170 while(*args && isspace(*args)) args++;
2172 spawn_cmd(command, args, show);
2176 * If Pageant was already running, we leave now. If we haven't
2177 * even taken any auxiliary action (spawned a command or added
2180 if (already_running) {
2181 if (!command && !added_keys) {
2182 MessageBox(NULL, "Pageant is already running", "Pageant Error",
2183 MB_ICONERROR | MB_OK);
2190 wndclass.lpfnWndProc = WndProc;
2191 wndclass.cbClsExtra = 0;
2192 wndclass.cbWndExtra = 0;
2193 wndclass.hInstance = inst;
2194 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
2195 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
2196 wndclass.hbrBackground = GetStockObject(BLACK_BRUSH);
2197 wndclass.lpszMenuName = NULL;
2198 wndclass.lpszClassName = APPNAME;
2200 RegisterClass(&wndclass);
2205 hwnd = CreateWindow(APPNAME, APPNAME,
2206 WS_OVERLAPPEDWINDOW | WS_VSCROLL,
2207 CW_USEDEFAULT, CW_USEDEFAULT,
2208 100, 100, NULL, NULL, inst, NULL);
2210 /* Set up a system tray icon */
2213 /* Accelerators used: nsvkxa */
2214 systray_menu = CreatePopupMenu();
2216 session_menu = CreateMenu();
2217 AppendMenu(systray_menu, MF_ENABLED, IDM_PUTTY, "&New Session");
2218 AppendMenu(systray_menu, MF_POPUP | MF_ENABLED,
2219 (UINT) session_menu, "&Saved Sessions");
2220 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
2222 AppendMenu(systray_menu, MF_ENABLED, IDM_VIEWKEYS,
2224 AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key");
2225 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
2227 AppendMenu(systray_menu, MF_ENABLED, IDM_HELP, "&Help");
2228 AppendMenu(systray_menu, MF_ENABLED, IDM_ABOUT, "&About");
2229 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
2230 AppendMenu(systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit");
2231 initial_menuitems_count = GetMenuItemCount(session_menu);
2233 /* Set the default menu item. */
2234 SetMenuDefaultItem(systray_menu, IDM_VIEWKEYS, FALSE);
2236 ShowWindow(hwnd, SW_HIDE);
2239 * Main message loop.
2241 while (GetMessage(&msg, NULL, 0, 0) == 1) {
2242 if (!(IsWindow(keylist) && IsDialogMessage(keylist, &msg)) &&
2243 !(IsWindow(aboutbox) && IsDialogMessage(aboutbox, &msg))) {
2244 TranslateMessage(&msg);
2245 DispatchMessage(&msg);
2249 /* Clean up the system tray icon */
2251 NOTIFYICONDATA tnid;
2253 tnid.cbSize = sizeof(NOTIFYICONDATA);
2257 Shell_NotifyIcon(NIM_DELETE, &tnid);
2259 DestroyMenu(systray_menu);
2262 if (keypath) filereq_free(keypath);
2264 cleanup_exit(msg.wParam);
2265 return msg.wParam; /* just in case optimiser complains */