2 * Pageant: the PuTTY Authentication Agent.
21 #define IDI_MAINICON 200
22 #define IDI_TRAYICON 201
24 #define WM_XUSER (WM_USER + 0x2000)
25 #define WM_SYSTRAY (WM_XUSER + 6)
26 #define WM_SYSTRAY2 (WM_XUSER + 7)
28 #define AGENT_COPYDATA_ID 0x804e50ba /* random goop */
31 * FIXME: maybe some day we can sort this out ...
33 #define AGENT_MAX_MSGLEN 8192
35 #define IDM_CLOSE 0x0010
36 #define IDM_VIEWKEYS 0x0020
37 #define IDM_ADDKEY 0x0030
38 #define IDM_HELP 0x0040
39 #define IDM_ABOUT 0x0050
41 #define APPNAME "Pageant"
45 static HINSTANCE instance;
46 static HWND main_hwnd;
49 static HMENU systray_menu, session_menu;
50 static int already_running;
51 static int requested_help;
53 static char *help_path;
54 static char *putty_path;
56 #define IDM_PUTTY 0x0060
57 #define IDM_SESSIONS_BASE 0x1000
58 #define IDM_SESSIONS_MAX 0x2000
59 #define PUTTY_REGKEY "Software\\SimonTatham\\PuTTY\\Sessions"
60 #define PUTTY_DEFAULT "Default%20Settings"
61 static int initial_menuitems_count;
64 * Print a modal (Really Bad) message box and perform a fatal exit.
66 void modalfatalbox(char *fmt, ...)
72 buf = dupvprintf(fmt, ap);
74 MessageBox(main_hwnd, buf, "Pageant Fatal Error",
75 MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
80 /* Un-munge session names out of the registry. */
81 static void unmungestr(char *in, char *out, int outlen)
84 if (*in == '%' && in[1] && in[2]) {
92 *out++ = (i << 4) + j;
106 static tree234 *rsakeys, *ssh2keys;
108 static int has_security;
110 typedef DWORD(WINAPI * gsi_fn_t)
111 (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION,
112 PSID *, PSID *, PACL *, PACL *, PSECURITY_DESCRIPTOR *);
113 static gsi_fn_t getsecurityinfo;
117 * Exports from pageantc.c
119 void agent_query(void *in, int inlen, void **out, int *outlen);
120 int agent_exists(void);
125 static void *make_keylist1(int *length);
126 static void *make_keylist2(int *length);
127 static void *get_keylist1(void);
128 static void *get_keylist2(void);
131 * Blob structure for passing to the asymmetric SSH2 key compare
132 * function, prototyped here.
138 static int cmpkeys_ssh2_asymm(void *av, void *bv);
140 #define GET_32BIT(cp) \
141 (((unsigned long)(unsigned char)(cp)[0] << 24) | \
142 ((unsigned long)(unsigned char)(cp)[1] << 16) | \
143 ((unsigned long)(unsigned char)(cp)[2] << 8) | \
144 ((unsigned long)(unsigned char)(cp)[3]))
146 #define PUT_32BIT(cp, value) { \
147 (cp)[0] = (unsigned char)((value) >> 24); \
148 (cp)[1] = (unsigned char)((value) >> 16); \
149 (cp)[2] = (unsigned char)((value) >> 8); \
150 (cp)[3] = (unsigned char)(value); }
152 #define PASSPHRASE_MAXLEN 512
154 struct PassphraseProcStruct {
159 static tree234 *passphrases = NULL;
162 * After processing a list of filenames, we want to forget the
165 static void forget_passphrases(void)
167 while (count234(passphrases) > 0) {
168 char *pp = index234(passphrases, 0);
169 memset(pp, 0, strlen(pp));
170 delpos234(passphrases, 0);
176 * Dialog-box function for the Licence box.
178 static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
179 WPARAM wParam, LPARAM lParam)
185 switch (LOWORD(wParam)) {
199 * Dialog-box function for the About box.
201 static int CALLBACK AboutProc(HWND hwnd, UINT msg,
202 WPARAM wParam, LPARAM lParam)
206 SetDlgItemText(hwnd, 100, ver);
209 switch (LOWORD(wParam)) {
215 EnableWindow(hwnd, 0);
216 DialogBox(instance, MAKEINTRESOURCE(214), hwnd, LicenceProc);
217 EnableWindow(hwnd, 1);
218 SetActiveWindow(hwnd);
230 static HWND passphrase_box;
233 * Dialog-box function for the passphrase box.
235 static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
236 WPARAM wParam, LPARAM lParam)
238 static char *passphrase = NULL;
239 struct PassphraseProcStruct *p;
243 passphrase_box = hwnd;
247 { /* centre the window */
251 hw = GetDesktopWindow();
252 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
254 (rs.right + rs.left + rd.left - rd.right) / 2,
255 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
256 rd.right - rd.left, rd.bottom - rd.top, TRUE);
259 SetForegroundWindow(hwnd);
260 SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
261 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
262 p = (struct PassphraseProcStruct *) lParam;
263 passphrase = p->passphrase;
265 SetDlgItemText(hwnd, 101, p->comment);
267 SetDlgItemText(hwnd, 102, passphrase);
270 switch (LOWORD(wParam)) {
280 case 102: /* edit box */
281 if ((HIWORD(wParam) == EN_CHANGE) && passphrase) {
282 GetDlgItemText(hwnd, 102, passphrase,
283 PASSPHRASE_MAXLEN - 1);
284 passphrase[PASSPHRASE_MAXLEN - 1] = '\0';
297 * Warn about the obsolescent key file format.
299 void old_keyfile_warning(void)
301 static const char mbtitle[] = "PuTTY Key File Warning";
302 static const char message[] =
303 "You are loading an SSH 2 private key which has an\n"
304 "old version of the file format. This means your key\n"
305 "file is not fully tamperproof. Future versions of\n"
306 "PuTTY may stop supporting this private key format,\n"
307 "so we recommend you convert your key to the new\n"
310 "You can perform this conversion by loading the key\n"
311 "into PuTTYgen and then saving it again.";
313 MessageBox(NULL, message, mbtitle, MB_OK);
317 * Update the visible key list.
319 static void keylist_update(void)
322 struct ssh2_userkey *skey;
326 SendDlgItemMessage(keylist, 100, LB_RESETCONTENT, 0, 0);
327 for (i = 0; NULL != (rkey = index234(rsakeys, i)); i++) {
328 char listentry[512], *p;
330 * Replace two spaces in the fingerprint with tabs, for
331 * nice alignment in the box.
333 strcpy(listentry, "ssh1\t");
334 p = listentry + strlen(listentry);
335 rsa_fingerprint(p, sizeof(listentry) - (p - listentry), rkey);
336 p = strchr(listentry, ' ');
339 p = strchr(listentry, ' ');
342 SendDlgItemMessage(keylist, 100, LB_ADDSTRING,
343 0, (LPARAM) listentry);
345 for (i = 0; NULL != (skey = index234(ssh2keys, i)); i++) {
346 char listentry[512], *p;
349 * Replace two spaces in the fingerprint with tabs, for
350 * nice alignment in the box.
352 p = skey->alg->fingerprint(skey->data);
353 strncpy(listentry, p, sizeof(listentry));
354 p = strchr(listentry, ' ');
357 p = strchr(listentry, ' ');
360 len = strlen(listentry);
361 if (len < sizeof(listentry) - 2) {
362 listentry[len] = '\t';
363 strncpy(listentry + len + 1, skey->comment,
364 sizeof(listentry) - len - 1);
366 SendDlgItemMessage(keylist, 100, LB_ADDSTRING, 0,
369 SendDlgItemMessage(keylist, 100, LB_SETCURSEL, (WPARAM) - 1, 0);
374 * This function loads a key from a file and adds it.
376 static void add_keyfile(Filename filename)
378 char passphrase[PASSPHRASE_MAXLEN];
379 struct RSAKey *rkey = NULL;
380 struct ssh2_userkey *skey = NULL;
385 struct PassphraseProcStruct pps;
389 type = key_type(&filename);
390 if (type != SSH_KEYTYPE_SSH1 && type != SSH_KEYTYPE_SSH2) {
392 sprintf(msg, "Couldn't load this key (%s)", key_type_to_str(type));
393 MessageBox(NULL, msg, APPNAME, MB_OK | MB_ICONERROR);
398 * See if the key is already loaded (in the primary Pageant,
399 * which may or may not be us).
403 unsigned char *keylist, *p;
404 int i, nkeys, bloblen;
406 if (type == SSH_KEYTYPE_SSH1) {
407 if (!rsakey_pubblob(&filename, &blob, &bloblen)) {
408 MessageBox(NULL, "Couldn't load private key.", APPNAME,
409 MB_OK | MB_ICONERROR);
412 keylist = get_keylist1();
414 unsigned char *blob2;
415 blob = ssh2_userkey_loadpub(&filename, NULL, &bloblen);
417 MessageBox(NULL, "Couldn't load private key.", APPNAME,
418 MB_OK | MB_ICONERROR);
421 /* For our purposes we want the blob prefixed with its length */
422 blob2 = smalloc(bloblen+4);
423 PUT_32BIT(blob2, bloblen);
424 memcpy(blob2 + 4, blob, bloblen);
428 keylist = get_keylist2();
431 nkeys = GET_32BIT(keylist);
434 for (i = 0; i < nkeys; i++) {
435 if (!memcmp(blob, p, bloblen)) {
436 /* Key is already present; we can now leave. */
441 /* Now skip over public blob */
442 if (type == SSH_KEYTYPE_SSH1)
443 p += rsa_public_blob_len(p);
445 p += 4 + GET_32BIT(p);
446 /* Now skip over comment field */
447 p += 4 + GET_32BIT(p);
456 if (type == SSH_KEYTYPE_SSH1)
457 needs_pass = rsakey_encrypted(&filename, &comment);
459 needs_pass = ssh2_userkey_encrypted(&filename, &comment);
461 if (type == SSH_KEYTYPE_SSH1)
462 rkey = smalloc(sizeof(*rkey));
463 pps.passphrase = passphrase;
464 pps.comment = comment;
468 /* try all the remembered passphrases first */
469 char *pp = index234(passphrases, attempts);
471 strcpy(passphrase, pp);
475 dlgret = DialogBoxParam(instance, MAKEINTRESOURCE(210),
476 NULL, PassphraseProc, (LPARAM) & pps);
477 passphrase_box = NULL;
481 if (type == SSH_KEYTYPE_SSH1)
483 return; /* operation cancelled */
488 if (type == SSH_KEYTYPE_SSH1)
489 ret = loadrsakey(&filename, rkey, passphrase);
491 skey = ssh2_load_userkey(&filename, passphrase);
492 if (skey == SSH2_WRONG_PASSPHRASE)
502 /* if they typed in an ok passphrase, remember it */
503 if(original_pass && ret) {
504 char *pp = dupstr(passphrase);
505 addpos234(passphrases, pp, 0);
511 MessageBox(NULL, "Couldn't load private key.", APPNAME,
512 MB_OK | MB_ICONERROR);
513 if (type == SSH_KEYTYPE_SSH1)
517 if (type == SSH_KEYTYPE_SSH1) {
518 if (already_running) {
519 unsigned char *request, *response;
521 int reqlen, clen, resplen;
523 clen = strlen(rkey->comment);
525 reqlen = 4 + 1 + /* length, message type */
527 ssh1_bignum_length(rkey->modulus) +
528 ssh1_bignum_length(rkey->exponent) +
529 ssh1_bignum_length(rkey->private_exponent) +
530 ssh1_bignum_length(rkey->iqmp) +
531 ssh1_bignum_length(rkey->p) +
532 ssh1_bignum_length(rkey->q) + 4 + clen /* comment */
535 request = smalloc(reqlen);
537 request[4] = SSH1_AGENTC_ADD_RSA_IDENTITY;
539 PUT_32BIT(request + reqlen, bignum_bitcount(rkey->modulus));
541 reqlen += ssh1_write_bignum(request + reqlen, rkey->modulus);
542 reqlen += ssh1_write_bignum(request + reqlen, rkey->exponent);
544 ssh1_write_bignum(request + reqlen,
545 rkey->private_exponent);
546 reqlen += ssh1_write_bignum(request + reqlen, rkey->iqmp);
547 reqlen += ssh1_write_bignum(request + reqlen, rkey->p);
548 reqlen += ssh1_write_bignum(request + reqlen, rkey->q);
549 PUT_32BIT(request + reqlen, clen);
550 memcpy(request + reqlen + 4, rkey->comment, clen);
552 PUT_32BIT(request, reqlen - 4);
554 agent_query(request, reqlen, &vresponse, &resplen);
555 response = vresponse;
556 if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
557 MessageBox(NULL, "The already running Pageant "
558 "refused to add the key.", APPNAME,
559 MB_OK | MB_ICONERROR);
564 if (add234(rsakeys, rkey) != rkey)
565 sfree(rkey); /* already present, don't waste RAM */
568 if (already_running) {
569 unsigned char *request, *response;
571 int reqlen, alglen, clen, keybloblen, resplen;
572 alglen = strlen(skey->alg->name);
573 clen = strlen(skey->comment);
575 keybloblen = skey->alg->openssh_fmtkey(skey->data, NULL, 0);
577 reqlen = 4 + 1 + /* length, message type */
578 4 + alglen + /* algorithm name */
579 keybloblen + /* key data */
580 4 + clen /* comment */
583 request = smalloc(reqlen);
585 request[4] = SSH2_AGENTC_ADD_IDENTITY;
587 PUT_32BIT(request + reqlen, alglen);
589 memcpy(request + reqlen, skey->alg->name, alglen);
591 reqlen += skey->alg->openssh_fmtkey(skey->data,
594 PUT_32BIT(request + reqlen, clen);
595 memcpy(request + reqlen + 4, skey->comment, clen);
596 PUT_32BIT(request, reqlen - 4);
599 agent_query(request, reqlen, &vresponse, &resplen);
600 response = vresponse;
601 if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
602 MessageBox(NULL, "The already running Pageant "
603 "refused to add the key.", APPNAME,
604 MB_OK | MB_ICONERROR);
609 if (add234(ssh2keys, skey) != skey) {
610 skey->alg->freekey(skey->data);
611 sfree(skey); /* already present, don't waste RAM */
618 * Create an SSH1 key list in a malloc'ed buffer; return its
621 static void *make_keylist1(int *length)
625 unsigned char *blob, *p, *ret;
629 * Count up the number and length of keys we hold.
633 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
635 blob = rsa_public_blob(key, &bloblen);
638 len += 4 + strlen(key->comment);
641 /* Allocate the buffer. */
642 p = ret = smalloc(len);
643 if (length) *length = len;
647 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
648 blob = rsa_public_blob(key, &bloblen);
649 memcpy(p, blob, bloblen);
652 PUT_32BIT(p, strlen(key->comment));
653 memcpy(p + 4, key->comment, strlen(key->comment));
654 p += 4 + strlen(key->comment);
657 assert(p - ret == len);
662 * Create an SSH2 key list in a malloc'ed buffer; return its
665 static void *make_keylist2(int *length)
667 struct ssh2_userkey *key;
669 unsigned char *blob, *p, *ret;
673 * Count up the number and length of keys we hold.
677 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
679 len += 4; /* length field */
680 blob = key->alg->public_blob(key->data, &bloblen);
683 len += 4 + strlen(key->comment);
686 /* Allocate the buffer. */
687 p = ret = smalloc(len);
688 if (length) *length = len;
691 * Packet header is the obvious five bytes, plus four
692 * bytes for the key count.
696 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
697 blob = key->alg->public_blob(key->data, &bloblen);
698 PUT_32BIT(p, bloblen);
700 memcpy(p, blob, bloblen);
703 PUT_32BIT(p, strlen(key->comment));
704 memcpy(p + 4, key->comment, strlen(key->comment));
705 p += 4 + strlen(key->comment);
708 assert(p - ret == len);
713 * Acquire a keylist1 from the primary Pageant; this means either
714 * calling make_keylist1 (if that's us) or sending a message to the
715 * primary Pageant (if it's not).
717 static void *get_keylist1(void)
721 if (already_running) {
722 unsigned char request[5], *response;
725 request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
726 PUT_32BIT(request, 4);
728 agent_query(request, 5, &vresponse, &resplen);
729 response = vresponse;
730 if (resplen < 5 || response[4] != SSH1_AGENT_RSA_IDENTITIES_ANSWER)
733 ret = smalloc(resplen-5);
734 memcpy(ret, response+5, resplen-5);
737 ret = make_keylist1(NULL);
743 * Acquire a keylist2 from the primary Pageant; this means either
744 * calling make_keylist2 (if that's us) or sending a message to the
745 * primary Pageant (if it's not).
747 static void *get_keylist2(void)
751 if (already_running) {
752 unsigned char request[5], *response;
756 request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
757 PUT_32BIT(request, 4);
759 agent_query(request, 5, &vresponse, &resplen);
760 response = vresponse;
761 if (resplen < 5 || response[4] != SSH2_AGENT_IDENTITIES_ANSWER)
764 ret = smalloc(resplen-5);
765 memcpy(ret, response+5, resplen-5);
768 ret = make_keylist2(NULL);
774 * This is the main agent function that answers messages.
776 static void answer_msg(void *msg)
778 unsigned char *p = msg;
779 unsigned char *ret = msg;
783 * Get the message type.
789 case SSH1_AGENTC_REQUEST_RSA_IDENTITIES:
791 * Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER.
797 ret[4] = SSH1_AGENT_RSA_IDENTITIES_ANSWER;
798 keylist = make_keylist1(&len);
799 if (len + 5 > AGENT_MAX_MSGLEN) {
803 PUT_32BIT(ret, len + 1);
804 memcpy(ret + 5, keylist, len);
808 case SSH2_AGENTC_REQUEST_IDENTITIES:
810 * Reply with SSH2_AGENT_IDENTITIES_ANSWER.
816 ret[4] = SSH2_AGENT_IDENTITIES_ANSWER;
817 keylist = make_keylist2(&len);
818 if (len + 5 > AGENT_MAX_MSGLEN) {
822 PUT_32BIT(ret, len + 1);
823 memcpy(ret + 5, keylist, len);
827 case SSH1_AGENTC_RSA_CHALLENGE:
829 * Reply with either SSH1_AGENT_RSA_RESPONSE or
830 * SSH_AGENT_FAILURE, depending on whether we have that key
834 struct RSAKey reqkey, *key;
835 Bignum challenge, response;
836 unsigned char response_source[48], response_md5[16];
837 struct MD5Context md5c;
841 p += ssh1_read_bignum(p, &reqkey.exponent);
842 p += ssh1_read_bignum(p, &reqkey.modulus);
843 p += ssh1_read_bignum(p, &challenge);
844 memcpy(response_source + 32, p, 16);
846 if (GET_32BIT(p) != 1 ||
847 (key = find234(rsakeys, &reqkey, NULL)) == NULL) {
848 freebn(reqkey.exponent);
849 freebn(reqkey.modulus);
853 response = rsadecrypt(challenge, key);
854 for (i = 0; i < 32; i++)
855 response_source[i] = bignum_byte(response, 31 - i);
858 MD5Update(&md5c, response_source, 48);
859 MD5Final(response_md5, &md5c);
860 memset(response_source, 0, 48); /* burn the evidence */
861 freebn(response); /* and that evidence */
862 freebn(challenge); /* yes, and that evidence */
863 freebn(reqkey.exponent); /* and free some memory ... */
864 freebn(reqkey.modulus); /* ... while we're at it. */
867 * Packet is the obvious five byte header, plus sixteen
871 PUT_32BIT(ret, len - 4);
872 ret[4] = SSH1_AGENT_RSA_RESPONSE;
873 memcpy(ret + 5, response_md5, 16);
876 case SSH2_AGENTC_SIGN_REQUEST:
878 * Reply with either SSH2_AGENT_SIGN_RESPONSE or
879 * SSH_AGENT_FAILURE, depending on whether we have that key
883 struct ssh2_userkey *key;
885 unsigned char *data, *signature;
886 int datalen, siglen, len;
888 b.len = GET_32BIT(p);
892 datalen = GET_32BIT(p);
895 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
898 signature = key->alg->sign(key->data, data, datalen, &siglen);
899 len = 5 + 4 + siglen;
900 PUT_32BIT(ret, len - 4);
901 ret[4] = SSH2_AGENT_SIGN_RESPONSE;
902 PUT_32BIT(ret + 5, siglen);
903 memcpy(ret + 5 + 4, signature, siglen);
907 case SSH1_AGENTC_ADD_RSA_IDENTITY:
909 * Add to the list and return SSH_AGENT_SUCCESS, or
910 * SSH_AGENT_FAILURE if the key was malformed.
916 key = smalloc(sizeof(struct RSAKey));
917 memset(key, 0, sizeof(struct RSAKey));
918 p += makekey(p, key, NULL, 1);
919 p += makeprivate(p, key);
920 p += ssh1_read_bignum(p, &key->iqmp); /* p^-1 mod q */
921 p += ssh1_read_bignum(p, &key->p); /* p */
922 p += ssh1_read_bignum(p, &key->q); /* q */
923 commentlen = GET_32BIT(p);
924 comment = smalloc(commentlen+1);
926 memcpy(comment, p + 4, commentlen);
927 comment[commentlen] = '\0';
928 key->comment = comment;
931 ret[4] = SSH_AGENT_FAILURE;
932 if (add234(rsakeys, key) == key) {
934 ret[4] = SSH_AGENT_SUCCESS;
941 case SSH2_AGENTC_ADD_IDENTITY:
943 * Add to the list and return SSH_AGENT_SUCCESS, or
944 * SSH_AGENT_FAILURE if the key was malformed.
947 struct ssh2_userkey *key;
952 key = smalloc(sizeof(struct ssh2_userkey));
954 alglen = GET_32BIT(p);
958 /* Add further algorithm names here. */
959 if (alglen == 7 && !memcmp(alg, "ssh-rsa", 7))
961 else if (alglen == 7 && !memcmp(alg, "ssh-dss", 7))
969 GET_32BIT((unsigned char *) msg) - (p -
970 (unsigned char *) msg -
972 key->data = key->alg->openssh_createkey(&p, &bloblen);
977 commlen = GET_32BIT(p);
980 comment = smalloc(commlen + 1);
982 memcpy(comment, p, commlen);
983 comment[commlen] = '\0';
985 key->comment = comment;
988 ret[4] = SSH_AGENT_FAILURE;
989 if (add234(ssh2keys, key) == key) {
991 ret[4] = SSH_AGENT_SUCCESS;
993 key->alg->freekey(key->data);
999 case SSH1_AGENTC_REMOVE_RSA_IDENTITY:
1001 * Remove from the list and return SSH_AGENT_SUCCESS, or
1002 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
1006 struct RSAKey reqkey, *key;
1008 p += makekey(p, &reqkey, NULL, 0);
1009 key = find234(rsakeys, &reqkey, NULL);
1010 freebn(reqkey.exponent);
1011 freebn(reqkey.modulus);
1013 ret[4] = SSH_AGENT_FAILURE;
1015 del234(rsakeys, key);
1019 ret[4] = SSH_AGENT_SUCCESS;
1023 case SSH2_AGENTC_REMOVE_IDENTITY:
1025 * Remove from the list and return SSH_AGENT_SUCCESS, or
1026 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
1030 struct ssh2_userkey *key;
1033 b.len = GET_32BIT(p);
1037 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
1042 ret[4] = SSH_AGENT_FAILURE;
1044 del234(ssh2keys, key);
1046 key->alg->freekey(key->data);
1048 ret[4] = SSH_AGENT_SUCCESS;
1052 case SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES:
1054 * Remove all SSH1 keys. Always returns success.
1057 struct RSAKey *rkey;
1059 while ((rkey = index234(rsakeys, 0)) != NULL) {
1060 del234(rsakeys, rkey);
1067 ret[4] = SSH_AGENT_SUCCESS;
1070 case SSH2_AGENTC_REMOVE_ALL_IDENTITIES:
1072 * Remove all SSH2 keys. Always returns success.
1075 struct ssh2_userkey *skey;
1077 while ((skey = index234(ssh2keys, 0)) != NULL) {
1078 del234(ssh2keys, skey);
1079 skey->alg->freekey(skey->data);
1085 ret[4] = SSH_AGENT_SUCCESS;
1091 * Unrecognised message. Return SSH_AGENT_FAILURE.
1094 ret[4] = SSH_AGENT_FAILURE;
1100 * Key comparison function for the 2-3-4 tree of RSA keys.
1102 static int cmpkeys_rsa(void *av, void *bv)
1104 struct RSAKey *a = (struct RSAKey *) av;
1105 struct RSAKey *b = (struct RSAKey *) bv;
1112 * Compare by length of moduli.
1114 alen = bignum_bitcount(am);
1115 blen = bignum_bitcount(bm);
1118 else if (alen < blen)
1121 * Now compare by moduli themselves.
1123 alen = (alen + 7) / 8; /* byte count */
1124 while (alen-- > 0) {
1126 abyte = bignum_byte(am, alen);
1127 bbyte = bignum_byte(bm, alen);
1130 else if (abyte < bbyte)
1140 * Key comparison function for the 2-3-4 tree of SSH2 keys.
1142 static int cmpkeys_ssh2(void *av, void *bv)
1144 struct ssh2_userkey *a = (struct ssh2_userkey *) av;
1145 struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
1148 unsigned char *ablob, *bblob;
1152 * Compare purely by public blob.
1154 ablob = a->alg->public_blob(a->data, &alen);
1155 bblob = b->alg->public_blob(b->data, &blen);
1158 for (i = 0; i < alen && i < blen; i++) {
1159 if (ablob[i] < bblob[i]) {
1162 } else if (ablob[i] > bblob[i]) {
1167 if (c == 0 && i < alen)
1168 c = +1; /* a is longer */
1169 if (c == 0 && i < blen)
1170 c = -1; /* a is longer */
1179 * Key comparison function for looking up a blob in the 2-3-4 tree
1182 static int cmpkeys_ssh2_asymm(void *av, void *bv)
1184 struct blob *a = (struct blob *) av;
1185 struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
1188 unsigned char *ablob, *bblob;
1192 * Compare purely by public blob.
1196 bblob = b->alg->public_blob(b->data, &blen);
1199 for (i = 0; i < alen && i < blen; i++) {
1200 if (ablob[i] < bblob[i]) {
1203 } else if (ablob[i] > bblob[i]) {
1208 if (c == 0 && i < alen)
1209 c = +1; /* a is longer */
1210 if (c == 0 && i < blen)
1211 c = -1; /* a is longer */
1219 * Prompt for a key file to add, and add it.
1221 static void prompt_add_keyfile(void)
1224 char filename[FILENAME_MAX];
1225 char *filelist = smalloc(8192);
1229 memset(&of, 0, sizeof(of));
1230 #ifdef OPENFILENAME_SIZE_VERSION_400
1231 of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
1233 of.lStructSize = sizeof(of);
1235 of.hwndOwner = main_hwnd;
1236 of.lpstrFilter = "PuTTY Private Key Files (*.ppk)\0*.ppk\0"
1237 "All Files (*.*)\0*\0\0\0";
1238 of.lpstrCustomFilter = NULL;
1239 of.nFilterIndex = 1;
1240 of.lpstrFile = filelist;
1242 of.nMaxFile = FILENAME_MAX;
1243 of.lpstrFileTitle = NULL;
1244 of.lpstrInitialDir = NULL;
1245 of.lpstrTitle = "Select Private Key File";
1246 of.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER;
1247 if (GetOpenFileName(&of)) {
1248 if(strlen(filelist) > of.nFileOffset)
1249 /* Only one filename returned? */
1250 add_keyfile(filename_from_str(filelist));
1252 /* we are returned a bunch of strings, end to
1253 * end. first string is the directory, the
1254 * rest the filenames. terminated with an
1257 filewalker = filelist;
1258 dirlen = strlen(filewalker);
1259 if(dirlen > FILENAME_MAX - 8) return;
1260 memcpy(filename, filewalker, dirlen);
1262 filewalker += dirlen + 1;
1263 filename[dirlen++] = '\\';
1265 /* then go over names one by one */
1267 n = strlen(filewalker) + 1;
1268 /* end of the list */
1271 /* too big, shouldn't happen */
1272 if(n + dirlen > FILENAME_MAX)
1275 memcpy(filename + dirlen, filewalker, n);
1278 add_keyfile(filename_from_str(filename));
1283 forget_passphrases();
1289 * Dialog-box function for the key list box.
1291 static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
1292 WPARAM wParam, LPARAM lParam)
1294 struct RSAKey *rkey;
1295 struct ssh2_userkey *skey;
1300 * Centre the window.
1302 { /* centre the window */
1306 hw = GetDesktopWindow();
1307 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
1309 (rs.right + rs.left + rd.left - rd.right) / 2,
1310 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
1311 rd.right - rd.left, rd.bottom - rd.top, TRUE);
1315 SetWindowLong(hwnd, GWL_EXSTYLE,
1316 GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_CONTEXTHELP);
1318 HWND item = GetDlgItem(hwnd, 103); /* the Help button */
1320 DestroyWindow(item);
1322 requested_help = FALSE;
1326 static int tabs[] = { 35, 60, 210 };
1327 SendDlgItemMessage(hwnd, 100, LB_SETTABSTOPS,
1328 sizeof(tabs) / sizeof(*tabs),
1334 switch (LOWORD(wParam)) {
1338 DestroyWindow(hwnd);
1340 case 101: /* add key */
1341 if (HIWORD(wParam) == BN_CLICKED ||
1342 HIWORD(wParam) == BN_DOUBLECLICKED) {
1343 if (passphrase_box) {
1344 MessageBeep(MB_ICONERROR);
1345 SetForegroundWindow(passphrase_box);
1348 prompt_add_keyfile();
1351 case 102: /* remove key */
1352 if (HIWORD(wParam) == BN_CLICKED ||
1353 HIWORD(wParam) == BN_DOUBLECLICKED) {
1358 /* our counter within the array of selected items */
1361 /* get the number of items selected in the list */
1363 SendDlgItemMessage(hwnd, 100, LB_GETSELCOUNT, 0, 0);
1365 /* none selected? that was silly */
1366 if (numSelected == 0) {
1371 /* get item indices in an array */
1372 selectedArray = smalloc(numSelected * sizeof(int));
1373 SendDlgItemMessage(hwnd, 100, LB_GETSELITEMS,
1374 numSelected, (WPARAM)selectedArray);
1376 itemNum = numSelected - 1;
1377 rCount = count234(rsakeys);
1378 sCount = count234(ssh2keys);
1380 /* go through the non-rsakeys until we've covered them all,
1381 * and/or we're out of selected items to check. note that
1382 * we go *backwards*, to avoid complications from deleting
1383 * things hence altering the offset of subsequent items
1385 for (i = sCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1386 skey = index234(ssh2keys, i);
1388 if (selectedArray[itemNum] == rCount + i) {
1389 del234(ssh2keys, skey);
1390 skey->alg->freekey(skey->data);
1396 /* do the same for the rsa keys */
1397 for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1398 rkey = index234(rsakeys, i);
1400 if(selectedArray[itemNum] == i) {
1401 del234(rsakeys, rkey);
1408 sfree(selectedArray);
1412 case 103: /* help */
1413 if (HIWORD(wParam) == BN_CLICKED ||
1414 HIWORD(wParam) == BN_DOUBLECLICKED) {
1416 WinHelp(main_hwnd, help_path, HELP_COMMAND,
1417 (DWORD)"JI(`',`pageant.general')");
1418 requested_help = TRUE;
1426 int id = ((LPHELPINFO)lParam)->iCtrlId;
1429 case 100: cmd = "JI(`',`pageant.keylist')"; break;
1430 case 101: cmd = "JI(`',`pageant.addkey')"; break;
1431 case 102: cmd = "JI(`',`pageant.remkey')"; break;
1434 WinHelp(main_hwnd, help_path, HELP_COMMAND, (DWORD)cmd);
1435 requested_help = TRUE;
1443 DestroyWindow(hwnd);
1449 /* Set up a system tray icon */
1450 static BOOL AddTrayIcon(HWND hwnd)
1453 NOTIFYICONDATA tnid;
1456 #ifdef NIM_SETVERSION
1458 res = Shell_NotifyIcon(NIM_SETVERSION, &tnid);
1461 tnid.cbSize = sizeof(NOTIFYICONDATA);
1463 tnid.uID = 1; /* unique within this systray use */
1464 tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
1465 tnid.uCallbackMessage = WM_SYSTRAY;
1466 tnid.hIcon = hicon = LoadIcon(instance, MAKEINTRESOURCE(201));
1467 strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
1469 res = Shell_NotifyIcon(NIM_ADD, &tnid);
1471 if (hicon) DestroyIcon(hicon);
1476 /* Update the saved-sessions menu. */
1477 static void update_sessions(void)
1481 TCHAR buf[MAX_PATH + 1];
1484 int index_key, index_menu;
1489 if(ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, PUTTY_REGKEY, &hkey))
1492 for(num_entries = GetMenuItemCount(session_menu);
1493 num_entries > initial_menuitems_count;
1495 RemoveMenu(session_menu, 0, MF_BYPOSITION);
1500 while(ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) {
1501 TCHAR session_name[MAX_PATH + 1];
1502 unmungestr(buf, session_name, MAX_PATH);
1503 if(strcmp(buf, PUTTY_DEFAULT) != 0) {
1504 memset(&mii, 0, sizeof(mii));
1505 mii.cbSize = sizeof(mii);
1506 mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
1507 mii.fType = MFT_STRING;
1508 mii.fState = MFS_ENABLED;
1509 mii.wID = (index_menu * 16) + IDM_SESSIONS_BASE;
1510 mii.dwTypeData = session_name;
1511 InsertMenuItem(session_menu, index_menu, TRUE, &mii);
1519 if(index_menu == 0) {
1520 mii.cbSize = sizeof(mii);
1521 mii.fMask = MIIM_TYPE | MIIM_STATE;
1522 mii.fType = MFT_STRING;
1523 mii.fState = MFS_GRAYED;
1524 mii.dwTypeData = _T("(No sessions)");
1525 InsertMenuItem(session_menu, index_menu, TRUE, &mii);
1529 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1530 WPARAM wParam, LPARAM lParam)
1533 static int menuinprogress;
1534 static UINT msgTaskbarCreated = 0;
1538 msgTaskbarCreated = RegisterWindowMessage(_T("TaskbarCreated"));
1541 if (message==msgTaskbarCreated) {
1543 * Explorer has been restarted, so the tray icon will
1551 if (lParam == WM_RBUTTONUP) {
1553 GetCursorPos(&cursorpos);
1554 PostMessage(hwnd, WM_SYSTRAY2, cursorpos.x, cursorpos.y);
1555 } else if (lParam == WM_LBUTTONDBLCLK) {
1556 /* Equivalent to IDM_VIEWKEYS. */
1557 PostMessage(hwnd, WM_COMMAND, IDM_VIEWKEYS, 0);
1561 if (!menuinprogress) {
1564 SetForegroundWindow(hwnd);
1565 ret = TrackPopupMenu(systray_menu,
1566 TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
1568 wParam, lParam, 0, hwnd, NULL);
1574 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1576 if((int)ShellExecute(hwnd, NULL, putty_path, _T(""), _T(""),
1578 MessageBox(NULL, "Unable to execute PuTTY!",
1579 "Error", MB_OK | MB_ICONERROR);
1584 SendMessage(passphrase_box, WM_CLOSE, 0, 0);
1585 SendMessage(hwnd, WM_CLOSE, 0, 0);
1589 keylist = CreateDialog(instance, MAKEINTRESOURCE(211),
1591 ShowWindow(keylist, SW_SHOWNORMAL);
1594 * Sometimes the window comes up minimised / hidden for
1595 * no obvious reason. Prevent this. This also brings it
1596 * to the front if it's already present (the user
1597 * selected View Keys because they wanted to _see_ the
1600 SetForegroundWindow(keylist);
1601 SetWindowPos(keylist, HWND_TOP, 0, 0, 0, 0,
1602 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1605 if (passphrase_box) {
1606 MessageBeep(MB_ICONERROR);
1607 SetForegroundWindow(passphrase_box);
1610 prompt_add_keyfile();
1614 aboutbox = CreateDialog(instance, MAKEINTRESOURCE(213),
1616 ShowWindow(aboutbox, SW_SHOWNORMAL);
1618 * Sometimes the window comes up minimised / hidden
1619 * for no obvious reason. Prevent this.
1621 SetForegroundWindow(aboutbox);
1622 SetWindowPos(aboutbox, HWND_TOP, 0, 0, 0, 0,
1623 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1628 WinHelp(main_hwnd, help_path, HELP_COMMAND,
1629 (DWORD)"JI(`',`pageant.general')");
1630 requested_help = TRUE;
1635 if(wParam >= IDM_SESSIONS_BASE && wParam <= IDM_SESSIONS_MAX) {
1637 TCHAR buf[MAX_PATH + 1];
1638 TCHAR param[MAX_PATH + 1];
1639 memset(&mii, 0, sizeof(mii));
1640 mii.cbSize = sizeof(mii);
1641 mii.fMask = MIIM_TYPE;
1643 mii.dwTypeData = buf;
1644 GetMenuItemInfo(session_menu, wParam, FALSE, &mii);
1646 strcat(param, mii.dwTypeData);
1647 if((int)ShellExecute(hwnd, NULL, putty_path, param,
1648 _T(""), SW_SHOW) <= 32) {
1649 MessageBox(NULL, "Unable to execute PuTTY!", "Error",
1650 MB_OK | MB_ICONERROR);
1658 if (requested_help) {
1659 WinHelp(main_hwnd, help_path, HELP_QUIT, 0);
1660 requested_help = FALSE;
1666 COPYDATASTRUCT *cds;
1672 PSID mapowner, procowner;
1673 PSECURITY_DESCRIPTOR psd1 = NULL, psd2 = NULL;
1677 cds = (COPYDATASTRUCT *) lParam;
1678 if (cds->dwData != AGENT_COPYDATA_ID)
1679 return 0; /* not our message, mate */
1680 mapname = (char *) cds->lpData;
1681 if (mapname[cds->cbData - 1] != '\0')
1682 return 0; /* failure to be ASCIZ! */
1684 debug(("mapname is :%s:\n", mapname));
1686 filemap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, mapname);
1688 debug(("filemap is %p\n", filemap));
1690 if (filemap != NULL && filemap != INVALID_HANDLE_VALUE) {
1694 if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE,
1695 GetCurrentProcessId())) ==
1698 debug(("couldn't get handle for process\n"));
1702 if (getsecurityinfo(proc, SE_KERNEL_OBJECT,
1703 OWNER_SECURITY_INFORMATION,
1704 &procowner, NULL, NULL, NULL,
1705 &psd2) != ERROR_SUCCESS) {
1707 debug(("couldn't get owner info for process\n"));
1710 return 0; /* unable to get security info */
1713 if ((rc = getsecurityinfo(filemap, SE_KERNEL_OBJECT,
1714 OWNER_SECURITY_INFORMATION,
1715 &mapowner, NULL, NULL, NULL,
1716 &psd1) != ERROR_SUCCESS)) {
1719 ("couldn't get owner info for filemap: %d\n",
1725 debug(("got security stuff\n"));
1727 if (!EqualSid(mapowner, procowner))
1728 return 0; /* security ID mismatch! */
1730 debug(("security stuff matched\n"));
1736 debug(("security APIs not present\n"));
1740 p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
1742 debug(("p is %p\n", p));
1745 for (i = 0; i < 5; i++)
1748 ((unsigned char *) p)[i]));}
1754 CloseHandle(filemap);
1759 return DefWindowProc(hwnd, message, wParam, lParam);
1763 * Fork and Exec the command in cmdline. [DBW]
1765 void spawn_cmd(char *cmdline, char * args, int show)
1767 if (ShellExecute(NULL, _T("open"), cmdline,
1768 args, NULL, show) <= (HINSTANCE) 32) {
1770 msg = dupprintf("Failed to run \"%.100s\", Error: %d", cmdline,
1771 (int)GetLastError());
1772 MessageBox(NULL, msg, APPNAME, MB_OK | MB_ICONEXCLAMATION);
1777 void cleanup_exit(int code) { exit(code); }
1779 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
1785 char *command = NULL;
1788 char **argv, **argstart;
1791 * Determine whether we're an NT system (should have security
1792 * APIs) or a non-NT system (don't do security).
1794 memset(&osi, 0, sizeof(OSVERSIONINFO));
1795 osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
1796 if (GetVersionEx(&osi) && osi.dwPlatformId == VER_PLATFORM_WIN32_NT) {
1797 has_security = TRUE;
1799 has_security = FALSE;
1804 * Attempt to get the security API we need.
1806 advapi = LoadLibrary("ADVAPI32.DLL");
1808 (gsi_fn_t) GetProcAddress(advapi, "GetSecurityInfo");
1809 if (!getsecurityinfo) {
1811 "Unable to access security APIs. Pageant will\n"
1812 "not run, in case it causes a security breach.",
1813 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
1818 "This program has been compiled for Win9X and will\n"
1819 "not run on NT, in case it causes a security breach.",
1820 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
1829 * See if we can find our Help file.
1832 char b[2048], *p, *q, *r;
1834 GetModuleFileName(NULL, b, sizeof(b) - 1);
1836 p = strrchr(b, '\\');
1837 if (p && p >= r) r = p+1;
1838 q = strrchr(b, ':');
1839 if (q && q >= r) r = q+1;
1840 strcpy(r, "putty.hlp");
1841 if ( (fp = fopen(b, "r")) != NULL) {
1842 help_path = dupstr(b);
1849 * Look for the PuTTY binary (we will enable the saved session
1850 * submenu if we find it).
1853 char b[2048], *p, *q, *r;
1855 GetModuleFileName(NULL, b, sizeof(b) - 1);
1857 p = strrchr(b, '\\');
1858 if (p && p >= r) r = p+1;
1859 q = strrchr(b, ':');
1860 if (q && q >= r) r = q+1;
1861 strcpy(r, "putty.exe");
1862 if ( (fp = fopen(b, "r")) != NULL) {
1863 putty_path = dupstr(b);
1870 * Find out if Pageant is already running.
1872 already_running = FALSE;
1874 already_running = TRUE;
1879 wndclass.lpfnWndProc = WndProc;
1880 wndclass.cbClsExtra = 0;
1881 wndclass.cbWndExtra = 0;
1882 wndclass.hInstance = inst;
1883 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
1884 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
1885 wndclass.hbrBackground = GetStockObject(BLACK_BRUSH);
1886 wndclass.lpszMenuName = NULL;
1887 wndclass.lpszClassName = APPNAME;
1889 RegisterClass(&wndclass);
1892 main_hwnd = keylist = NULL;
1894 main_hwnd = CreateWindow(APPNAME, APPNAME,
1895 WS_OVERLAPPEDWINDOW | WS_VSCROLL,
1896 CW_USEDEFAULT, CW_USEDEFAULT,
1897 100, 100, NULL, NULL, inst, NULL);
1899 /* Set up a system tray icon */
1900 AddTrayIcon(main_hwnd);
1902 /* Accelerators used: nsvkxa */
1903 systray_menu = CreatePopupMenu();
1905 session_menu = CreateMenu();
1906 AppendMenu(systray_menu, MF_ENABLED, IDM_PUTTY, "&New Session");
1907 AppendMenu(systray_menu, MF_POPUP | MF_ENABLED,
1908 (UINT) session_menu, "&Saved Sessions");
1909 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
1911 AppendMenu(systray_menu, MF_ENABLED, IDM_VIEWKEYS,
1913 AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key");
1914 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
1916 AppendMenu(systray_menu, MF_ENABLED, IDM_HELP, "&Help");
1917 AppendMenu(systray_menu, MF_ENABLED, IDM_ABOUT, "&About");
1918 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
1919 AppendMenu(systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit");
1920 initial_menuitems_count = GetMenuItemCount(session_menu);
1922 ShowWindow(main_hwnd, SW_HIDE);
1925 * Initialise storage for RSA keys.
1927 rsakeys = newtree234(cmpkeys_rsa);
1928 ssh2keys = newtree234(cmpkeys_ssh2);
1933 * Initialise the random number generator.
1938 * Initialise storage for short-term passphrase cache.
1940 passphrases = newtree234(NULL);
1943 * Process the command line and add keys as listed on it.
1945 split_into_argv(cmdline, &argc, &argv, &argstart);
1946 for (i = 0; i < argc; i++) {
1947 if (!strcmp(argv[i], "-c")) {
1949 * If we see `-c', then the rest of the
1950 * command line should be treated as a
1951 * command to be spawned.
1954 command = argstart[i+1];
1959 add_keyfile(filename_from_str(argv[i]));
1965 * Forget any passphrase that we retained while going over
1966 * command line keyfiles.
1968 forget_passphrases();
1972 if (command[0] == '"')
1973 args = strchr(++command, '"');
1975 args = strchr(command, ' ');
1978 while(*args && isspace(*args)) args++;
1980 spawn_cmd(command, args, show);
1984 * If Pageant was already running, we leave now. If we haven't
1985 * even taken any auxiliary action (spawned a command or added
1988 if (already_running) {
1989 if (!command && !added_keys) {
1990 MessageBox(NULL, "Pageant is already running", "Pageant Error",
1991 MB_ICONERROR | MB_OK);
1994 FreeLibrary(advapi);
1999 * Main message loop.
2001 while (GetMessage(&msg, NULL, 0, 0) == 1) {
2002 if (!(IsWindow(keylist) && IsDialogMessage(keylist, &msg)) &&
2003 !(IsWindow(aboutbox) && IsDialogMessage(aboutbox, &msg))) {
2004 TranslateMessage(&msg);
2005 DispatchMessage(&msg);
2009 /* Clean up the system tray icon */
2011 NOTIFYICONDATA tnid;
2013 tnid.cbSize = sizeof(NOTIFYICONDATA);
2014 tnid.hWnd = main_hwnd;
2017 Shell_NotifyIcon(NIM_DELETE, &tnid);
2019 DestroyMenu(systray_menu);
2023 FreeLibrary(advapi);