2 * Pageant: the PuTTY Authentication Agent.
22 #define IDI_MAINICON 200
23 #define IDI_TRAYICON 201
25 #define WM_XUSER (WM_USER + 0x2000)
26 #define WM_SYSTRAY (WM_XUSER + 6)
27 #define WM_SYSTRAY2 (WM_XUSER + 7)
29 #define AGENT_COPYDATA_ID 0x804e50ba /* random goop */
32 * FIXME: maybe some day we can sort this out ...
34 #define AGENT_MAX_MSGLEN 8192
36 #define IDM_CLOSE 0x0010
37 #define IDM_VIEWKEYS 0x0020
38 #define IDM_ADDKEY 0x0030
39 #define IDM_HELP 0x0040
40 #define IDM_ABOUT 0x0050
42 #define APPNAME "Pageant"
46 static HINSTANCE instance;
47 static HWND main_hwnd;
50 static HMENU systray_menu, session_menu;
51 static int already_running;
52 static int requested_help;
55 static char *putty_path;
57 #define IDM_PUTTY 0x0060
58 #define IDM_SESSIONS_BASE 0x1000
59 #define IDM_SESSIONS_MAX 0x2000
60 #define PUTTY_REGKEY "Software\\SimonTatham\\PuTTY\\Sessions"
61 #define PUTTY_DEFAULT "Default%20Settings"
62 static int initial_menuitems_count;
65 * Print a modal (Really Bad) message box and perform a fatal exit.
67 void modalfatalbox(char *fmt, ...)
73 buf = dupvprintf(fmt, ap);
75 MessageBox(main_hwnd, buf, "Pageant Fatal Error",
76 MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
81 /* Un-munge session names out of the registry. */
82 static void unmungestr(char *in, char *out, int outlen)
85 if (*in == '%' && in[1] && in[2]) {
93 *out++ = (i << 4) + j;
107 static tree234 *rsakeys, *ssh2keys;
109 static int has_security;
111 typedef DWORD(WINAPI * gsi_fn_t)
112 (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION,
113 PSID *, PSID *, PACL *, PACL *, PSECURITY_DESCRIPTOR *);
114 static gsi_fn_t getsecurityinfo;
120 static void *make_keylist1(int *length);
121 static void *make_keylist2(int *length);
122 static void *get_keylist1(void);
123 static void *get_keylist2(void);
126 * We need this to link with the RSA code, because rsaencrypt()
127 * pads its data with random bytes. Since we only use rsadecrypt()
128 * and the signing functions, which are deterministic, this should
131 * If it _is_ called, there is a _serious_ problem, because it
132 * won't generate true random numbers. So we must scream, panic,
133 * and exit immediately if that should happen.
135 int random_byte(void)
137 MessageBox(main_hwnd, "Internal Error", APPNAME, MB_OK | MB_ICONERROR);
139 /* this line can't be reached but it placates MSVC's warnings :-) */
144 * Blob structure for passing to the asymmetric SSH2 key compare
145 * function, prototyped here.
151 static int cmpkeys_ssh2_asymm(void *av, void *bv);
153 #define GET_32BIT(cp) \
154 (((unsigned long)(unsigned char)(cp)[0] << 24) | \
155 ((unsigned long)(unsigned char)(cp)[1] << 16) | \
156 ((unsigned long)(unsigned char)(cp)[2] << 8) | \
157 ((unsigned long)(unsigned char)(cp)[3]))
159 #define PUT_32BIT(cp, value) { \
160 (cp)[0] = (unsigned char)((value) >> 24); \
161 (cp)[1] = (unsigned char)((value) >> 16); \
162 (cp)[2] = (unsigned char)((value) >> 8); \
163 (cp)[3] = (unsigned char)(value); }
165 #define PASSPHRASE_MAXLEN 512
167 struct PassphraseProcStruct {
172 static tree234 *passphrases = NULL;
175 * After processing a list of filenames, we want to forget the
178 static void forget_passphrases(void)
180 while (count234(passphrases) > 0) {
181 char *pp = index234(passphrases, 0);
182 memset(pp, 0, strlen(pp));
183 delpos234(passphrases, 0);
189 * Dialog-box function for the Licence box.
191 static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
192 WPARAM wParam, LPARAM lParam)
198 switch (LOWORD(wParam)) {
212 * Dialog-box function for the About box.
214 static int CALLBACK AboutProc(HWND hwnd, UINT msg,
215 WPARAM wParam, LPARAM lParam)
219 SetDlgItemText(hwnd, 100, ver);
222 switch (LOWORD(wParam)) {
228 EnableWindow(hwnd, 0);
229 DialogBox(instance, MAKEINTRESOURCE(214), hwnd, LicenceProc);
230 EnableWindow(hwnd, 1);
231 SetActiveWindow(hwnd);
243 static HWND passphrase_box;
246 * Dialog-box function for the passphrase box.
248 static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
249 WPARAM wParam, LPARAM lParam)
251 static char *passphrase = NULL;
252 struct PassphraseProcStruct *p;
256 passphrase_box = hwnd;
260 { /* centre the window */
264 hw = GetDesktopWindow();
265 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
267 (rs.right + rs.left + rd.left - rd.right) / 2,
268 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
269 rd.right - rd.left, rd.bottom - rd.top, TRUE);
272 SetForegroundWindow(hwnd);
273 SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
274 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
275 p = (struct PassphraseProcStruct *) lParam;
276 passphrase = p->passphrase;
278 SetDlgItemText(hwnd, 101, p->comment);
280 SetDlgItemText(hwnd, 102, passphrase);
283 switch (LOWORD(wParam)) {
293 case 102: /* edit box */
294 if ((HIWORD(wParam) == EN_CHANGE) && passphrase) {
295 GetDlgItemText(hwnd, 102, passphrase,
296 PASSPHRASE_MAXLEN - 1);
297 passphrase[PASSPHRASE_MAXLEN - 1] = '\0';
310 * Warn about the obsolescent key file format.
312 void old_keyfile_warning(void)
314 static const char mbtitle[] = "PuTTY Key File Warning";
315 static const char message[] =
316 "You are loading an SSH 2 private key which has an\n"
317 "old version of the file format. This means your key\n"
318 "file is not fully tamperproof. Future versions of\n"
319 "PuTTY may stop supporting this private key format,\n"
320 "so we recommend you convert your key to the new\n"
323 "You can perform this conversion by loading the key\n"
324 "into PuTTYgen and then saving it again.";
326 MessageBox(NULL, message, mbtitle, MB_OK);
330 * Update the visible key list.
332 static void keylist_update(void)
335 struct ssh2_userkey *skey;
339 SendDlgItemMessage(keylist, 100, LB_RESETCONTENT, 0, 0);
340 for (i = 0; NULL != (rkey = index234(rsakeys, i)); i++) {
341 char listentry[512], *p;
343 * Replace two spaces in the fingerprint with tabs, for
344 * nice alignment in the box.
346 strcpy(listentry, "ssh1\t");
347 p = listentry + strlen(listentry);
348 rsa_fingerprint(p, sizeof(listentry) - (p - listentry), rkey);
349 p = strchr(listentry, ' ');
352 p = strchr(listentry, ' ');
355 SendDlgItemMessage(keylist, 100, LB_ADDSTRING,
356 0, (LPARAM) listentry);
358 for (i = 0; NULL != (skey = index234(ssh2keys, i)); i++) {
359 char listentry[512], *p;
362 * Replace two spaces in the fingerprint with tabs, for
363 * nice alignment in the box.
365 p = skey->alg->fingerprint(skey->data);
366 strncpy(listentry, p, sizeof(listentry));
367 p = strchr(listentry, ' ');
370 p = strchr(listentry, ' ');
373 len = strlen(listentry);
374 if (len < sizeof(listentry) - 2) {
375 listentry[len] = '\t';
376 strncpy(listentry + len + 1, skey->comment,
377 sizeof(listentry) - len - 1);
379 SendDlgItemMessage(keylist, 100, LB_ADDSTRING, 0,
382 SendDlgItemMessage(keylist, 100, LB_SETCURSEL, (WPARAM) - 1, 0);
387 * This function loads a key from a file and adds it.
389 static void add_keyfile(Filename filename)
391 char passphrase[PASSPHRASE_MAXLEN];
392 struct RSAKey *rkey = NULL;
393 struct ssh2_userkey *skey = NULL;
398 struct PassphraseProcStruct pps;
402 type = key_type(&filename);
403 if (type != SSH_KEYTYPE_SSH1 && type != SSH_KEYTYPE_SSH2) {
405 sprintf(msg, "Couldn't load this key (%s)", key_type_to_str(type));
406 MessageBox(NULL, msg, APPNAME, MB_OK | MB_ICONERROR);
411 * See if the key is already loaded (in the primary Pageant,
412 * which may or may not be us).
416 unsigned char *keylist, *p;
417 int i, nkeys, bloblen;
419 if (type == SSH_KEYTYPE_SSH1) {
420 if (!rsakey_pubblob(&filename, &blob, &bloblen, NULL)) {
421 MessageBox(NULL, "Couldn't load private key.", APPNAME,
422 MB_OK | MB_ICONERROR);
425 keylist = get_keylist1();
427 unsigned char *blob2;
428 blob = ssh2_userkey_loadpub(&filename, NULL, &bloblen, NULL);
430 MessageBox(NULL, "Couldn't load private key.", APPNAME,
431 MB_OK | MB_ICONERROR);
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();
444 nkeys = GET_32BIT(keylist);
447 for (i = 0; i < nkeys; i++) {
448 if (!memcmp(blob, p, bloblen)) {
449 /* Key is already present; we can now leave. */
454 /* Now skip over public blob */
455 if (type == SSH_KEYTYPE_SSH1)
456 p += rsa_public_blob_len(p);
458 p += 4 + GET_32BIT(p);
459 /* Now skip over comment field */
460 p += 4 + GET_32BIT(p);
469 if (type == SSH_KEYTYPE_SSH1)
470 needs_pass = rsakey_encrypted(&filename, &comment);
472 needs_pass = ssh2_userkey_encrypted(&filename, &comment);
474 if (type == SSH_KEYTYPE_SSH1)
475 rkey = snew(struct RSAKey);
476 pps.passphrase = passphrase;
477 pps.comment = comment;
481 /* try all the remembered passphrases first */
482 char *pp = index234(passphrases, attempts);
484 strcpy(passphrase, pp);
488 dlgret = DialogBoxParam(instance, MAKEINTRESOURCE(210),
489 NULL, PassphraseProc, (LPARAM) & pps);
490 passphrase_box = NULL;
494 if (type == SSH_KEYTYPE_SSH1)
496 return; /* operation cancelled */
501 if (type == SSH_KEYTYPE_SSH1)
502 ret = loadrsakey(&filename, rkey, passphrase, NULL);
504 skey = ssh2_load_userkey(&filename, passphrase, NULL);
505 if (skey == SSH2_WRONG_PASSPHRASE)
515 /* if they typed in an ok passphrase, remember it */
516 if(original_pass && ret) {
517 char *pp = dupstr(passphrase);
518 addpos234(passphrases, pp, 0);
524 MessageBox(NULL, "Couldn't load private key.", APPNAME,
525 MB_OK | MB_ICONERROR);
526 if (type == SSH_KEYTYPE_SSH1)
530 if (type == SSH_KEYTYPE_SSH1) {
531 if (already_running) {
532 unsigned char *request, *response;
534 int reqlen, clen, resplen, ret;
536 clen = strlen(rkey->comment);
538 reqlen = 4 + 1 + /* length, message type */
540 ssh1_bignum_length(rkey->modulus) +
541 ssh1_bignum_length(rkey->exponent) +
542 ssh1_bignum_length(rkey->private_exponent) +
543 ssh1_bignum_length(rkey->iqmp) +
544 ssh1_bignum_length(rkey->p) +
545 ssh1_bignum_length(rkey->q) + 4 + clen /* comment */
548 request = snewn(reqlen, unsigned char);
550 request[4] = SSH1_AGENTC_ADD_RSA_IDENTITY;
552 PUT_32BIT(request + reqlen, bignum_bitcount(rkey->modulus));
554 reqlen += ssh1_write_bignum(request + reqlen, rkey->modulus);
555 reqlen += ssh1_write_bignum(request + reqlen, rkey->exponent);
557 ssh1_write_bignum(request + reqlen,
558 rkey->private_exponent);
559 reqlen += ssh1_write_bignum(request + reqlen, rkey->iqmp);
560 reqlen += ssh1_write_bignum(request + reqlen, rkey->p);
561 reqlen += ssh1_write_bignum(request + reqlen, rkey->q);
562 PUT_32BIT(request + reqlen, clen);
563 memcpy(request + reqlen + 4, rkey->comment, clen);
565 PUT_32BIT(request, reqlen - 4);
567 ret = agent_query(request, reqlen, &vresponse, &resplen,
570 response = vresponse;
571 if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
572 MessageBox(NULL, "The already running Pageant "
573 "refused to add the key.", APPNAME,
574 MB_OK | MB_ICONERROR);
579 if (add234(rsakeys, rkey) != rkey)
580 sfree(rkey); /* already present, don't waste RAM */
583 if (already_running) {
584 unsigned char *request, *response;
586 int reqlen, alglen, clen, keybloblen, resplen, ret;
587 alglen = strlen(skey->alg->name);
588 clen = strlen(skey->comment);
590 keybloblen = skey->alg->openssh_fmtkey(skey->data, NULL, 0);
592 reqlen = 4 + 1 + /* length, message type */
593 4 + alglen + /* algorithm name */
594 keybloblen + /* key data */
595 4 + clen /* comment */
598 request = snewn(reqlen, unsigned char);
600 request[4] = SSH2_AGENTC_ADD_IDENTITY;
602 PUT_32BIT(request + reqlen, alglen);
604 memcpy(request + reqlen, skey->alg->name, alglen);
606 reqlen += skey->alg->openssh_fmtkey(skey->data,
609 PUT_32BIT(request + reqlen, clen);
610 memcpy(request + reqlen + 4, skey->comment, clen);
611 PUT_32BIT(request, reqlen - 4);
614 ret = agent_query(request, reqlen, &vresponse, &resplen,
617 response = vresponse;
618 if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
619 MessageBox(NULL, "The already running Pageant "
620 "refused to add the key.", APPNAME,
621 MB_OK | MB_ICONERROR);
626 if (add234(ssh2keys, skey) != skey) {
627 skey->alg->freekey(skey->data);
628 sfree(skey); /* already present, don't waste RAM */
635 * Create an SSH1 key list in a malloc'ed buffer; return its
638 static void *make_keylist1(int *length)
642 unsigned char *blob, *p, *ret;
646 * Count up the number and length of keys we hold.
650 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
652 blob = rsa_public_blob(key, &bloblen);
655 len += 4 + strlen(key->comment);
658 /* Allocate the buffer. */
659 p = ret = snewn(len, unsigned char);
660 if (length) *length = len;
664 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
665 blob = rsa_public_blob(key, &bloblen);
666 memcpy(p, blob, bloblen);
669 PUT_32BIT(p, strlen(key->comment));
670 memcpy(p + 4, key->comment, strlen(key->comment));
671 p += 4 + strlen(key->comment);
674 assert(p - ret == len);
679 * Create an SSH2 key list in a malloc'ed buffer; return its
682 static void *make_keylist2(int *length)
684 struct ssh2_userkey *key;
686 unsigned char *blob, *p, *ret;
690 * Count up the number and length of keys we hold.
694 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
696 len += 4; /* length field */
697 blob = key->alg->public_blob(key->data, &bloblen);
700 len += 4 + strlen(key->comment);
703 /* Allocate the buffer. */
704 p = ret = snewn(len, unsigned char);
705 if (length) *length = len;
708 * Packet header is the obvious five bytes, plus four
709 * bytes for the key count.
713 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
714 blob = key->alg->public_blob(key->data, &bloblen);
715 PUT_32BIT(p, bloblen);
717 memcpy(p, blob, bloblen);
720 PUT_32BIT(p, strlen(key->comment));
721 memcpy(p + 4, key->comment, strlen(key->comment));
722 p += 4 + strlen(key->comment);
725 assert(p - ret == len);
730 * Acquire a keylist1 from the primary Pageant; this means either
731 * calling make_keylist1 (if that's us) or sending a message to the
732 * primary Pageant (if it's not).
734 static void *get_keylist1(void)
738 if (already_running) {
739 unsigned char request[5], *response;
742 request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
743 PUT_32BIT(request, 4);
745 retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
747 response = vresponse;
748 if (resplen < 5 || response[4] != SSH1_AGENT_RSA_IDENTITIES_ANSWER)
751 ret = snewn(resplen-5, unsigned char);
752 memcpy(ret, response+5, resplen-5);
755 ret = make_keylist1(NULL);
761 * Acquire a keylist2 from the primary Pageant; this means either
762 * calling make_keylist2 (if that's us) or sending a message to the
763 * primary Pageant (if it's not).
765 static void *get_keylist2(void)
769 if (already_running) {
770 unsigned char request[5], *response;
774 request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
775 PUT_32BIT(request, 4);
777 retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
779 response = vresponse;
780 if (resplen < 5 || response[4] != SSH2_AGENT_IDENTITIES_ANSWER)
783 ret = snewn(resplen-5, unsigned char);
784 memcpy(ret, response+5, resplen-5);
787 ret = make_keylist2(NULL);
793 * This is the main agent function that answers messages.
795 static void answer_msg(void *msg)
797 unsigned char *p = msg;
798 unsigned char *ret = msg;
802 * Get the message type.
808 case SSH1_AGENTC_REQUEST_RSA_IDENTITIES:
810 * Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER.
816 ret[4] = SSH1_AGENT_RSA_IDENTITIES_ANSWER;
817 keylist = make_keylist1(&len);
818 if (len + 5 > AGENT_MAX_MSGLEN) {
822 PUT_32BIT(ret, len + 1);
823 memcpy(ret + 5, keylist, len);
827 case SSH2_AGENTC_REQUEST_IDENTITIES:
829 * Reply with SSH2_AGENT_IDENTITIES_ANSWER.
835 ret[4] = SSH2_AGENT_IDENTITIES_ANSWER;
836 keylist = make_keylist2(&len);
837 if (len + 5 > AGENT_MAX_MSGLEN) {
841 PUT_32BIT(ret, len + 1);
842 memcpy(ret + 5, keylist, len);
846 case SSH1_AGENTC_RSA_CHALLENGE:
848 * Reply with either SSH1_AGENT_RSA_RESPONSE or
849 * SSH_AGENT_FAILURE, depending on whether we have that key
853 struct RSAKey reqkey, *key;
854 Bignum challenge, response;
855 unsigned char response_source[48], response_md5[16];
856 struct MD5Context md5c;
860 p += ssh1_read_bignum(p, &reqkey.exponent);
861 p += ssh1_read_bignum(p, &reqkey.modulus);
862 p += ssh1_read_bignum(p, &challenge);
863 memcpy(response_source + 32, p, 16);
865 if (GET_32BIT(p) != 1 ||
866 (key = find234(rsakeys, &reqkey, NULL)) == NULL) {
867 freebn(reqkey.exponent);
868 freebn(reqkey.modulus);
872 response = rsadecrypt(challenge, key);
873 for (i = 0; i < 32; i++)
874 response_source[i] = bignum_byte(response, 31 - i);
877 MD5Update(&md5c, response_source, 48);
878 MD5Final(response_md5, &md5c);
879 memset(response_source, 0, 48); /* burn the evidence */
880 freebn(response); /* and that evidence */
881 freebn(challenge); /* yes, and that evidence */
882 freebn(reqkey.exponent); /* and free some memory ... */
883 freebn(reqkey.modulus); /* ... while we're at it. */
886 * Packet is the obvious five byte header, plus sixteen
890 PUT_32BIT(ret, len - 4);
891 ret[4] = SSH1_AGENT_RSA_RESPONSE;
892 memcpy(ret + 5, response_md5, 16);
895 case SSH2_AGENTC_SIGN_REQUEST:
897 * Reply with either SSH2_AGENT_SIGN_RESPONSE or
898 * SSH_AGENT_FAILURE, depending on whether we have that key
902 struct ssh2_userkey *key;
904 unsigned char *data, *signature;
905 int datalen, siglen, len;
907 b.len = GET_32BIT(p);
911 datalen = GET_32BIT(p);
914 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
917 signature = key->alg->sign(key->data, data, datalen, &siglen);
918 len = 5 + 4 + siglen;
919 PUT_32BIT(ret, len - 4);
920 ret[4] = SSH2_AGENT_SIGN_RESPONSE;
921 PUT_32BIT(ret + 5, siglen);
922 memcpy(ret + 5 + 4, signature, siglen);
926 case SSH1_AGENTC_ADD_RSA_IDENTITY:
928 * Add to the list and return SSH_AGENT_SUCCESS, or
929 * SSH_AGENT_FAILURE if the key was malformed.
935 key = snew(struct RSAKey);
936 memset(key, 0, sizeof(struct RSAKey));
937 p += makekey(p, key, NULL, 1);
938 p += makeprivate(p, key);
939 p += ssh1_read_bignum(p, &key->iqmp); /* p^-1 mod q */
940 p += ssh1_read_bignum(p, &key->p); /* p */
941 p += ssh1_read_bignum(p, &key->q); /* q */
942 commentlen = GET_32BIT(p);
943 comment = snewn(commentlen+1, char);
945 memcpy(comment, p + 4, commentlen);
946 comment[commentlen] = '\0';
947 key->comment = comment;
950 ret[4] = SSH_AGENT_FAILURE;
951 if (add234(rsakeys, key) == key) {
953 ret[4] = SSH_AGENT_SUCCESS;
960 case SSH2_AGENTC_ADD_IDENTITY:
962 * Add to the list and return SSH_AGENT_SUCCESS, or
963 * SSH_AGENT_FAILURE if the key was malformed.
966 struct ssh2_userkey *key;
971 key = snew(struct ssh2_userkey);
973 alglen = GET_32BIT(p);
977 /* Add further algorithm names here. */
978 if (alglen == 7 && !memcmp(alg, "ssh-rsa", 7))
980 else if (alglen == 7 && !memcmp(alg, "ssh-dss", 7))
988 GET_32BIT((unsigned char *) msg) - (p -
989 (unsigned char *) msg -
991 key->data = key->alg->openssh_createkey(&p, &bloblen);
996 commlen = GET_32BIT(p);
999 comment = snewn(commlen + 1, char);
1001 memcpy(comment, p, commlen);
1002 comment[commlen] = '\0';
1004 key->comment = comment;
1007 ret[4] = SSH_AGENT_FAILURE;
1008 if (add234(ssh2keys, key) == key) {
1010 ret[4] = SSH_AGENT_SUCCESS;
1012 key->alg->freekey(key->data);
1013 sfree(key->comment);
1018 case SSH1_AGENTC_REMOVE_RSA_IDENTITY:
1020 * Remove from the list and return SSH_AGENT_SUCCESS, or
1021 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
1025 struct RSAKey reqkey, *key;
1027 p += makekey(p, &reqkey, NULL, 0);
1028 key = find234(rsakeys, &reqkey, NULL);
1029 freebn(reqkey.exponent);
1030 freebn(reqkey.modulus);
1032 ret[4] = SSH_AGENT_FAILURE;
1034 del234(rsakeys, key);
1038 ret[4] = SSH_AGENT_SUCCESS;
1042 case SSH2_AGENTC_REMOVE_IDENTITY:
1044 * Remove from the list and return SSH_AGENT_SUCCESS, or
1045 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
1049 struct ssh2_userkey *key;
1052 b.len = GET_32BIT(p);
1056 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
1061 ret[4] = SSH_AGENT_FAILURE;
1063 del234(ssh2keys, key);
1065 key->alg->freekey(key->data);
1067 ret[4] = SSH_AGENT_SUCCESS;
1071 case SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES:
1073 * Remove all SSH1 keys. Always returns success.
1076 struct RSAKey *rkey;
1078 while ((rkey = index234(rsakeys, 0)) != NULL) {
1079 del234(rsakeys, rkey);
1086 ret[4] = SSH_AGENT_SUCCESS;
1089 case SSH2_AGENTC_REMOVE_ALL_IDENTITIES:
1091 * Remove all SSH2 keys. Always returns success.
1094 struct ssh2_userkey *skey;
1096 while ((skey = index234(ssh2keys, 0)) != NULL) {
1097 del234(ssh2keys, skey);
1098 skey->alg->freekey(skey->data);
1104 ret[4] = SSH_AGENT_SUCCESS;
1110 * Unrecognised message. Return SSH_AGENT_FAILURE.
1113 ret[4] = SSH_AGENT_FAILURE;
1119 * Key comparison function for the 2-3-4 tree of RSA keys.
1121 static int cmpkeys_rsa(void *av, void *bv)
1123 struct RSAKey *a = (struct RSAKey *) av;
1124 struct RSAKey *b = (struct RSAKey *) bv;
1131 * Compare by length of moduli.
1133 alen = bignum_bitcount(am);
1134 blen = bignum_bitcount(bm);
1137 else if (alen < blen)
1140 * Now compare by moduli themselves.
1142 alen = (alen + 7) / 8; /* byte count */
1143 while (alen-- > 0) {
1145 abyte = bignum_byte(am, alen);
1146 bbyte = bignum_byte(bm, alen);
1149 else if (abyte < bbyte)
1159 * Key comparison function for the 2-3-4 tree of SSH2 keys.
1161 static int cmpkeys_ssh2(void *av, void *bv)
1163 struct ssh2_userkey *a = (struct ssh2_userkey *) av;
1164 struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
1167 unsigned char *ablob, *bblob;
1171 * Compare purely by public blob.
1173 ablob = a->alg->public_blob(a->data, &alen);
1174 bblob = b->alg->public_blob(b->data, &blen);
1177 for (i = 0; i < alen && i < blen; i++) {
1178 if (ablob[i] < bblob[i]) {
1181 } else if (ablob[i] > bblob[i]) {
1186 if (c == 0 && i < alen)
1187 c = +1; /* a is longer */
1188 if (c == 0 && i < blen)
1189 c = -1; /* a is longer */
1198 * Key comparison function for looking up a blob in the 2-3-4 tree
1201 static int cmpkeys_ssh2_asymm(void *av, void *bv)
1203 struct blob *a = (struct blob *) av;
1204 struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
1207 unsigned char *ablob, *bblob;
1211 * Compare purely by public blob.
1215 bblob = b->alg->public_blob(b->data, &blen);
1218 for (i = 0; i < alen && i < blen; i++) {
1219 if (ablob[i] < bblob[i]) {
1222 } else if (ablob[i] > bblob[i]) {
1227 if (c == 0 && i < alen)
1228 c = +1; /* a is longer */
1229 if (c == 0 && i < blen)
1230 c = -1; /* a is longer */
1238 * Prompt for a key file to add, and add it.
1240 static void prompt_add_keyfile(void)
1243 char filename[FILENAME_MAX];
1244 char *filelist = snewn(8192, char);
1248 memset(&of, 0, sizeof(of));
1249 #ifdef OPENFILENAME_SIZE_VERSION_400
1250 of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
1252 of.lStructSize = sizeof(of);
1254 of.hwndOwner = main_hwnd;
1255 of.lpstrFilter = "PuTTY Private Key Files (*.ppk)\0*.ppk\0"
1256 "All Files (*.*)\0*\0\0\0";
1257 of.lpstrCustomFilter = NULL;
1258 of.nFilterIndex = 1;
1259 of.lpstrFile = filelist;
1261 of.nMaxFile = FILENAME_MAX;
1262 of.lpstrFileTitle = NULL;
1263 of.lpstrInitialDir = NULL;
1264 of.lpstrTitle = "Select Private Key File";
1265 of.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER;
1266 if (GetOpenFileName(&of)) {
1267 if(strlen(filelist) > of.nFileOffset)
1268 /* Only one filename returned? */
1269 add_keyfile(filename_from_str(filelist));
1271 /* we are returned a bunch of strings, end to
1272 * end. first string is the directory, the
1273 * rest the filenames. terminated with an
1276 filewalker = filelist;
1277 dirlen = strlen(filewalker);
1278 if(dirlen > FILENAME_MAX - 8) return;
1279 memcpy(filename, filewalker, dirlen);
1281 filewalker += dirlen + 1;
1282 filename[dirlen++] = '\\';
1284 /* then go over names one by one */
1286 n = strlen(filewalker) + 1;
1287 /* end of the list */
1290 /* too big, shouldn't happen */
1291 if(n + dirlen > FILENAME_MAX)
1294 memcpy(filename + dirlen, filewalker, n);
1297 add_keyfile(filename_from_str(filename));
1302 forget_passphrases();
1308 * Dialog-box function for the key list box.
1310 static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
1311 WPARAM wParam, LPARAM lParam)
1313 struct RSAKey *rkey;
1314 struct ssh2_userkey *skey;
1319 * Centre the window.
1321 { /* centre the window */
1325 hw = GetDesktopWindow();
1326 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
1328 (rs.right + rs.left + rd.left - rd.right) / 2,
1329 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
1330 rd.right - rd.left, rd.bottom - rd.top, TRUE);
1334 SetWindowLong(hwnd, GWL_EXSTYLE,
1335 GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_CONTEXTHELP);
1337 HWND item = GetDlgItem(hwnd, 103); /* the Help button */
1339 DestroyWindow(item);
1341 requested_help = FALSE;
1345 static int tabs[] = { 35, 60, 210 };
1346 SendDlgItemMessage(hwnd, 100, LB_SETTABSTOPS,
1347 sizeof(tabs) / sizeof(*tabs),
1353 switch (LOWORD(wParam)) {
1357 DestroyWindow(hwnd);
1359 case 101: /* add key */
1360 if (HIWORD(wParam) == BN_CLICKED ||
1361 HIWORD(wParam) == BN_DOUBLECLICKED) {
1362 if (passphrase_box) {
1363 MessageBeep(MB_ICONERROR);
1364 SetForegroundWindow(passphrase_box);
1367 prompt_add_keyfile();
1370 case 102: /* remove key */
1371 if (HIWORD(wParam) == BN_CLICKED ||
1372 HIWORD(wParam) == BN_DOUBLECLICKED) {
1377 /* our counter within the array of selected items */
1380 /* get the number of items selected in the list */
1382 SendDlgItemMessage(hwnd, 100, LB_GETSELCOUNT, 0, 0);
1384 /* none selected? that was silly */
1385 if (numSelected == 0) {
1390 /* get item indices in an array */
1391 selectedArray = snewn(numSelected, int);
1392 SendDlgItemMessage(hwnd, 100, LB_GETSELITEMS,
1393 numSelected, (WPARAM)selectedArray);
1395 itemNum = numSelected - 1;
1396 rCount = count234(rsakeys);
1397 sCount = count234(ssh2keys);
1399 /* go through the non-rsakeys until we've covered them all,
1400 * and/or we're out of selected items to check. note that
1401 * we go *backwards*, to avoid complications from deleting
1402 * things hence altering the offset of subsequent items
1404 for (i = sCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1405 skey = index234(ssh2keys, i);
1407 if (selectedArray[itemNum] == rCount + i) {
1408 del234(ssh2keys, skey);
1409 skey->alg->freekey(skey->data);
1415 /* do the same for the rsa keys */
1416 for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1417 rkey = index234(rsakeys, i);
1419 if(selectedArray[itemNum] == i) {
1420 del234(rsakeys, rkey);
1427 sfree(selectedArray);
1431 case 103: /* help */
1432 if (HIWORD(wParam) == BN_CLICKED ||
1433 HIWORD(wParam) == BN_DOUBLECLICKED) {
1435 WinHelp(main_hwnd, help_path, HELP_COMMAND,
1436 (DWORD)"JI(`',`pageant.general')");
1437 requested_help = TRUE;
1445 int id = ((LPHELPINFO)lParam)->iCtrlId;
1448 case 100: cmd = "JI(`',`pageant.keylist')"; break;
1449 case 101: cmd = "JI(`',`pageant.addkey')"; break;
1450 case 102: cmd = "JI(`',`pageant.remkey')"; break;
1453 WinHelp(main_hwnd, help_path, HELP_COMMAND, (DWORD)cmd);
1454 requested_help = TRUE;
1462 DestroyWindow(hwnd);
1468 /* Set up a system tray icon */
1469 static BOOL AddTrayIcon(HWND hwnd)
1472 NOTIFYICONDATA tnid;
1475 #ifdef NIM_SETVERSION
1477 res = Shell_NotifyIcon(NIM_SETVERSION, &tnid);
1480 tnid.cbSize = sizeof(NOTIFYICONDATA);
1482 tnid.uID = 1; /* unique within this systray use */
1483 tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
1484 tnid.uCallbackMessage = WM_SYSTRAY;
1485 tnid.hIcon = hicon = LoadIcon(instance, MAKEINTRESOURCE(201));
1486 strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
1488 res = Shell_NotifyIcon(NIM_ADD, &tnid);
1490 if (hicon) DestroyIcon(hicon);
1495 /* Update the saved-sessions menu. */
1496 static void update_sessions(void)
1500 TCHAR buf[MAX_PATH + 1];
1503 int index_key, index_menu;
1508 if(ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, PUTTY_REGKEY, &hkey))
1511 for(num_entries = GetMenuItemCount(session_menu);
1512 num_entries > initial_menuitems_count;
1514 RemoveMenu(session_menu, 0, MF_BYPOSITION);
1519 while(ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) {
1520 TCHAR session_name[MAX_PATH + 1];
1521 unmungestr(buf, session_name, MAX_PATH);
1522 if(strcmp(buf, PUTTY_DEFAULT) != 0) {
1523 memset(&mii, 0, sizeof(mii));
1524 mii.cbSize = sizeof(mii);
1525 mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
1526 mii.fType = MFT_STRING;
1527 mii.fState = MFS_ENABLED;
1528 mii.wID = (index_menu * 16) + IDM_SESSIONS_BASE;
1529 mii.dwTypeData = session_name;
1530 InsertMenuItem(session_menu, index_menu, TRUE, &mii);
1538 if(index_menu == 0) {
1539 mii.cbSize = sizeof(mii);
1540 mii.fMask = MIIM_TYPE | MIIM_STATE;
1541 mii.fType = MFT_STRING;
1542 mii.fState = MFS_GRAYED;
1543 mii.dwTypeData = _T("(No sessions)");
1544 InsertMenuItem(session_menu, index_menu, TRUE, &mii);
1548 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1549 WPARAM wParam, LPARAM lParam)
1552 static int menuinprogress;
1553 static UINT msgTaskbarCreated = 0;
1557 msgTaskbarCreated = RegisterWindowMessage(_T("TaskbarCreated"));
1560 if (message==msgTaskbarCreated) {
1562 * Explorer has been restarted, so the tray icon will
1570 if (lParam == WM_RBUTTONUP) {
1572 GetCursorPos(&cursorpos);
1573 PostMessage(hwnd, WM_SYSTRAY2, cursorpos.x, cursorpos.y);
1574 } else if (lParam == WM_LBUTTONDBLCLK) {
1575 /* Equivalent to IDM_VIEWKEYS. */
1576 PostMessage(hwnd, WM_COMMAND, IDM_VIEWKEYS, 0);
1580 if (!menuinprogress) {
1583 SetForegroundWindow(hwnd);
1584 ret = TrackPopupMenu(systray_menu,
1585 TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
1587 wParam, lParam, 0, hwnd, NULL);
1593 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1595 if((int)ShellExecute(hwnd, NULL, putty_path, _T(""), _T(""),
1597 MessageBox(NULL, "Unable to execute PuTTY!",
1598 "Error", MB_OK | MB_ICONERROR);
1603 SendMessage(passphrase_box, WM_CLOSE, 0, 0);
1604 SendMessage(hwnd, WM_CLOSE, 0, 0);
1608 keylist = CreateDialog(instance, MAKEINTRESOURCE(211),
1610 ShowWindow(keylist, SW_SHOWNORMAL);
1613 * Sometimes the window comes up minimised / hidden for
1614 * no obvious reason. Prevent this. This also brings it
1615 * to the front if it's already present (the user
1616 * selected View Keys because they wanted to _see_ the
1619 SetForegroundWindow(keylist);
1620 SetWindowPos(keylist, HWND_TOP, 0, 0, 0, 0,
1621 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1624 if (passphrase_box) {
1625 MessageBeep(MB_ICONERROR);
1626 SetForegroundWindow(passphrase_box);
1629 prompt_add_keyfile();
1633 aboutbox = CreateDialog(instance, MAKEINTRESOURCE(213),
1635 ShowWindow(aboutbox, SW_SHOWNORMAL);
1637 * Sometimes the window comes up minimised / hidden
1638 * for no obvious reason. Prevent this.
1640 SetForegroundWindow(aboutbox);
1641 SetWindowPos(aboutbox, HWND_TOP, 0, 0, 0, 0,
1642 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1647 WinHelp(main_hwnd, help_path, HELP_COMMAND,
1648 (DWORD)"JI(`',`pageant.general')");
1649 requested_help = TRUE;
1654 if(wParam >= IDM_SESSIONS_BASE && wParam <= IDM_SESSIONS_MAX) {
1656 TCHAR buf[MAX_PATH + 1];
1657 TCHAR param[MAX_PATH + 1];
1658 memset(&mii, 0, sizeof(mii));
1659 mii.cbSize = sizeof(mii);
1660 mii.fMask = MIIM_TYPE;
1662 mii.dwTypeData = buf;
1663 GetMenuItemInfo(session_menu, wParam, FALSE, &mii);
1665 strcat(param, mii.dwTypeData);
1666 if((int)ShellExecute(hwnd, NULL, putty_path, param,
1667 _T(""), SW_SHOW) <= 32) {
1668 MessageBox(NULL, "Unable to execute PuTTY!", "Error",
1669 MB_OK | MB_ICONERROR);
1677 if (requested_help) {
1678 WinHelp(main_hwnd, help_path, HELP_QUIT, 0);
1679 requested_help = FALSE;
1685 COPYDATASTRUCT *cds;
1691 PSID mapowner, procowner;
1692 PSECURITY_DESCRIPTOR psd1 = NULL, psd2 = NULL;
1696 cds = (COPYDATASTRUCT *) lParam;
1697 if (cds->dwData != AGENT_COPYDATA_ID)
1698 return 0; /* not our message, mate */
1699 mapname = (char *) cds->lpData;
1700 if (mapname[cds->cbData - 1] != '\0')
1701 return 0; /* failure to be ASCIZ! */
1703 debug(("mapname is :%s:\n", mapname));
1705 filemap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, mapname);
1707 debug(("filemap is %p\n", filemap));
1709 if (filemap != NULL && filemap != INVALID_HANDLE_VALUE) {
1713 if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE,
1714 GetCurrentProcessId())) ==
1717 debug(("couldn't get handle for process\n"));
1721 if (getsecurityinfo(proc, SE_KERNEL_OBJECT,
1722 OWNER_SECURITY_INFORMATION,
1723 &procowner, NULL, NULL, NULL,
1724 &psd2) != ERROR_SUCCESS) {
1726 debug(("couldn't get owner info for process\n"));
1729 return 0; /* unable to get security info */
1732 if ((rc = getsecurityinfo(filemap, SE_KERNEL_OBJECT,
1733 OWNER_SECURITY_INFORMATION,
1734 &mapowner, NULL, NULL, NULL,
1735 &psd1) != ERROR_SUCCESS)) {
1738 ("couldn't get owner info for filemap: %d\n",
1744 debug(("got security stuff\n"));
1746 if (!EqualSid(mapowner, procowner))
1747 return 0; /* security ID mismatch! */
1749 debug(("security stuff matched\n"));
1755 debug(("security APIs not present\n"));
1759 p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
1761 debug(("p is %p\n", p));
1764 for (i = 0; i < 5; i++)
1767 ((unsigned char *) p)[i]));}
1773 CloseHandle(filemap);
1778 return DefWindowProc(hwnd, message, wParam, lParam);
1782 * Fork and Exec the command in cmdline. [DBW]
1784 void spawn_cmd(char *cmdline, char * args, int show)
1786 if (ShellExecute(NULL, _T("open"), cmdline,
1787 args, NULL, show) <= (HINSTANCE) 32) {
1789 msg = dupprintf("Failed to run \"%.100s\", Error: %d", cmdline,
1790 (int)GetLastError());
1791 MessageBox(NULL, msg, APPNAME, MB_OK | MB_ICONEXCLAMATION);
1797 * This is a can't-happen stub, since Pageant never makes
1798 * asynchronous agent requests.
1800 void agent_schedule_callback(void (*callback)(void *, void *, int),
1801 void *callback_ctx, void *data, int len)
1803 assert(!"We shouldn't get here");
1806 void cleanup_exit(int code) { exit(code); }
1808 int flags = FLAG_SYNCAGENT;
1810 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
1815 char *command = NULL;
1818 char **argv, **argstart;
1821 * Determine whether we're an NT system (should have security
1822 * APIs) or a non-NT system (don't do security).
1826 modalfatalbox("Windows refuses to report a version");
1828 if (osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT) {
1829 has_security = TRUE;
1831 has_security = FALSE;
1836 * Attempt to get the security API we need.
1838 advapi = LoadLibrary("ADVAPI32.DLL");
1840 (gsi_fn_t) GetProcAddress(advapi, "GetSecurityInfo");
1841 if (!getsecurityinfo) {
1843 "Unable to access security APIs. Pageant will\n"
1844 "not run, in case it causes a security breach.",
1845 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
1850 "This program has been compiled for Win9X and will\n"
1851 "not run on NT, in case it causes a security breach.",
1852 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
1861 * See if we can find our Help file.
1864 char b[2048], *p, *q, *r;
1866 GetModuleFileName(NULL, b, sizeof(b) - 1);
1868 p = strrchr(b, '\\');
1869 if (p && p >= r) r = p+1;
1870 q = strrchr(b, ':');
1871 if (q && q >= r) r = q+1;
1872 strcpy(r, "putty.hlp");
1873 if ( (fp = fopen(b, "r")) != NULL) {
1874 help_path = dupstr(b);
1881 * Look for the PuTTY binary (we will enable the saved session
1882 * submenu if we find it).
1885 char b[2048], *p, *q, *r;
1887 GetModuleFileName(NULL, b, sizeof(b) - 1);
1889 p = strrchr(b, '\\');
1890 if (p && p >= r) r = p+1;
1891 q = strrchr(b, ':');
1892 if (q && q >= r) r = q+1;
1893 strcpy(r, "putty.exe");
1894 if ( (fp = fopen(b, "r")) != NULL) {
1895 putty_path = dupstr(b);
1902 * Find out if Pageant is already running.
1904 already_running = FALSE;
1906 already_running = TRUE;
1911 wndclass.lpfnWndProc = WndProc;
1912 wndclass.cbClsExtra = 0;
1913 wndclass.cbWndExtra = 0;
1914 wndclass.hInstance = inst;
1915 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
1916 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
1917 wndclass.hbrBackground = GetStockObject(BLACK_BRUSH);
1918 wndclass.lpszMenuName = NULL;
1919 wndclass.lpszClassName = APPNAME;
1921 RegisterClass(&wndclass);
1924 main_hwnd = keylist = NULL;
1926 main_hwnd = CreateWindow(APPNAME, APPNAME,
1927 WS_OVERLAPPEDWINDOW | WS_VSCROLL,
1928 CW_USEDEFAULT, CW_USEDEFAULT,
1929 100, 100, NULL, NULL, inst, NULL);
1931 /* Set up a system tray icon */
1932 AddTrayIcon(main_hwnd);
1934 /* Accelerators used: nsvkxa */
1935 systray_menu = CreatePopupMenu();
1937 session_menu = CreateMenu();
1938 AppendMenu(systray_menu, MF_ENABLED, IDM_PUTTY, "&New Session");
1939 AppendMenu(systray_menu, MF_POPUP | MF_ENABLED,
1940 (UINT) session_menu, "&Saved Sessions");
1941 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
1943 AppendMenu(systray_menu, MF_ENABLED, IDM_VIEWKEYS,
1945 AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key");
1946 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
1948 AppendMenu(systray_menu, MF_ENABLED, IDM_HELP, "&Help");
1949 AppendMenu(systray_menu, MF_ENABLED, IDM_ABOUT, "&About");
1950 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
1951 AppendMenu(systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit");
1952 initial_menuitems_count = GetMenuItemCount(session_menu);
1954 ShowWindow(main_hwnd, SW_HIDE);
1957 * Initialise storage for RSA keys.
1959 rsakeys = newtree234(cmpkeys_rsa);
1960 ssh2keys = newtree234(cmpkeys_ssh2);
1965 * Initialise storage for short-term passphrase cache.
1967 passphrases = newtree234(NULL);
1970 * Process the command line and add keys as listed on it.
1972 split_into_argv(cmdline, &argc, &argv, &argstart);
1973 for (i = 0; i < argc; i++) {
1974 if (!strcmp(argv[i], "-c")) {
1976 * If we see `-c', then the rest of the
1977 * command line should be treated as a
1978 * command to be spawned.
1981 command = argstart[i+1];
1986 add_keyfile(filename_from_str(argv[i]));
1992 * Forget any passphrase that we retained while going over
1993 * command line keyfiles.
1995 forget_passphrases();
1999 if (command[0] == '"')
2000 args = strchr(++command, '"');
2002 args = strchr(command, ' ');
2005 while(*args && isspace(*args)) args++;
2007 spawn_cmd(command, args, show);
2011 * If Pageant was already running, we leave now. If we haven't
2012 * even taken any auxiliary action (spawned a command or added
2015 if (already_running) {
2016 if (!command && !added_keys) {
2017 MessageBox(NULL, "Pageant is already running", "Pageant Error",
2018 MB_ICONERROR | MB_OK);
2021 FreeLibrary(advapi);
2026 * Main message loop.
2028 while (GetMessage(&msg, NULL, 0, 0) == 1) {
2029 if (!(IsWindow(keylist) && IsDialogMessage(keylist, &msg)) &&
2030 !(IsWindow(aboutbox) && IsDialogMessage(aboutbox, &msg))) {
2031 TranslateMessage(&msg);
2032 DispatchMessage(&msg);
2036 /* Clean up the system tray icon */
2038 NOTIFYICONDATA tnid;
2040 tnid.cbSize = sizeof(NOTIFYICONDATA);
2041 tnid.hWnd = main_hwnd;
2044 Shell_NotifyIcon(NIM_DELETE, &tnid);
2046 DestroyMenu(systray_menu);
2050 FreeLibrary(advapi);