2 * Pageant: the PuTTY Authentication Agent.
19 #define IDI_MAINICON 200
20 #define IDI_TRAYICON 201
22 #define WM_XUSER (WM_USER + 0x2000)
23 #define WM_SYSTRAY (WM_XUSER + 6)
24 #define WM_SYSTRAY2 (WM_XUSER + 7)
26 #define AGENT_COPYDATA_ID 0x804e50ba /* random goop */
29 * FIXME: maybe some day we can sort this out ...
31 #define AGENT_MAX_MSGLEN 8192
33 #define IDM_CLOSE 0x0010
34 #define IDM_VIEWKEYS 0x0020
35 #define IDM_ADDKEY 0x0030
36 #define IDM_HELP 0x0040
37 #define IDM_ABOUT 0x0050
39 #define APPNAME "Pageant"
43 static HINSTANCE instance;
44 static HWND main_hwnd;
47 static HMENU systray_menu, session_menu;
48 static int already_running;
49 static int requested_help;
51 static char *help_path;
52 static char *putty_path;
54 #define IDM_PUTTY 0x0060
55 #define IDM_SESSIONS_BASE 0x1000
56 #define IDM_SESSIONS_MAX 0x2000
57 #define PUTTY_REGKEY "Software\\SimonTatham\\PuTTY\\Sessions"
58 #define PUTTY_DEFAULT "Default%20Settings"
59 static int initial_menuitems_count;
61 /* Un-munge session names out of the registry. */
62 static void unmungestr(char *in, char *out, int outlen)
65 if (*in == '%' && in[1] && in[2]) {
73 *out++ = (i << 4) + j;
87 static tree234 *rsakeys, *ssh2keys;
89 static int has_security;
91 typedef DWORD(WINAPI * gsi_fn_t)
92 (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION,
93 PSID *, PSID *, PACL *, PACL *, PSECURITY_DESCRIPTOR *);
94 static gsi_fn_t getsecurityinfo;
98 * Exports from pageantc.c
100 void agent_query(void *in, int inlen, void **out, int *outlen);
101 int agent_exists(void);
106 static void *make_keylist1(int *length);
107 static void *make_keylist2(int *length);
108 static void *get_keylist1(void);
109 static void *get_keylist2(void);
112 * We need this to link with the RSA code, because rsaencrypt()
113 * pads its data with random bytes. Since we only use rsadecrypt()
114 * and the signing functions, which are deterministic, this should
117 * If it _is_ called, there is a _serious_ problem, because it
118 * won't generate true random numbers. So we must scream, panic,
119 * and exit immediately if that should happen.
121 int random_byte(void)
123 MessageBox(main_hwnd, "Internal Error", APPNAME, MB_OK | MB_ICONERROR);
125 /* this line can't be reached but it placates MSVC's warnings :-) */
130 * Blob structure for passing to the asymmetric SSH2 key compare
131 * function, prototyped here.
137 static int cmpkeys_ssh2_asymm(void *av, void *bv);
140 * This function is needed to link with the DES code. We need not
141 * have it do anything at all.
143 void logevent(char *msg)
147 #define GET_32BIT(cp) \
148 (((unsigned long)(unsigned char)(cp)[0] << 24) | \
149 ((unsigned long)(unsigned char)(cp)[1] << 16) | \
150 ((unsigned long)(unsigned char)(cp)[2] << 8) | \
151 ((unsigned long)(unsigned char)(cp)[3]))
153 #define PUT_32BIT(cp, value) { \
154 (cp)[0] = (unsigned char)((value) >> 24); \
155 (cp)[1] = (unsigned char)((value) >> 16); \
156 (cp)[2] = (unsigned char)((value) >> 8); \
157 (cp)[3] = (unsigned char)(value); }
159 #define PASSPHRASE_MAXLEN 512
161 struct PassphraseProcStruct {
166 static tree234 *passphrases = NULL;
169 * After processing a list of filenames, we want to forget the
172 static void forget_passphrases(void)
174 while (count234(passphrases) > 0) {
175 char *pp = index234(passphrases, 0);
176 memset(pp, 0, strlen(pp));
177 delpos234(passphrases, 0);
183 * Dialog-box function for the Licence box.
185 static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
186 WPARAM wParam, LPARAM lParam)
192 switch (LOWORD(wParam)) {
206 * Dialog-box function for the About box.
208 static int CALLBACK AboutProc(HWND hwnd, UINT msg,
209 WPARAM wParam, LPARAM lParam)
213 SetDlgItemText(hwnd, 100, ver);
216 switch (LOWORD(wParam)) {
222 EnableWindow(hwnd, 0);
223 DialogBox(instance, MAKEINTRESOURCE(214), NULL, LicenceProc);
224 EnableWindow(hwnd, 1);
225 SetActiveWindow(hwnd);
237 static HWND passphrase_box;
240 * Dialog-box function for the passphrase box.
242 static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
243 WPARAM wParam, LPARAM lParam)
245 static char *passphrase = NULL;
246 struct PassphraseProcStruct *p;
250 passphrase_box = hwnd;
254 { /* centre the window */
258 hw = GetDesktopWindow();
259 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
261 (rs.right + rs.left + rd.left - rd.right) / 2,
262 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
263 rd.right - rd.left, rd.bottom - rd.top, TRUE);
266 SetForegroundWindow(hwnd);
267 SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
268 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
269 p = (struct PassphraseProcStruct *) lParam;
270 passphrase = p->passphrase;
272 SetDlgItemText(hwnd, 101, p->comment);
274 SetDlgItemText(hwnd, 102, passphrase);
277 switch (LOWORD(wParam)) {
287 case 102: /* edit box */
288 if ((HIWORD(wParam) == EN_CHANGE) && passphrase) {
289 GetDlgItemText(hwnd, 102, passphrase,
290 PASSPHRASE_MAXLEN - 1);
291 passphrase[PASSPHRASE_MAXLEN - 1] = '\0';
304 * Warn about the obsolescent key file format.
306 void old_keyfile_warning(void)
308 static const char mbtitle[] = "PuTTY Key File Warning";
309 static const char message[] =
310 "You are loading an SSH 2 private key which has an\n"
311 "old version of the file format. This means your key\n"
312 "file is not fully tamperproof. Future versions of\n"
313 "PuTTY may stop supporting this private key format,\n"
314 "so we recommend you convert your key to the new\n"
317 "You can perform this conversion by loading the key\n"
318 "into PuTTYgen and then saving it again.";
320 MessageBox(NULL, message, mbtitle, MB_OK);
324 * Update the visible key list.
326 static void keylist_update(void)
329 struct ssh2_userkey *skey;
333 SendDlgItemMessage(keylist, 100, LB_RESETCONTENT, 0, 0);
334 for (i = 0; NULL != (rkey = index234(rsakeys, i)); i++) {
335 char listentry[512], *p;
337 * Replace two spaces in the fingerprint with tabs, for
338 * nice alignment in the box.
340 strcpy(listentry, "ssh1\t");
341 p = listentry + strlen(listentry);
342 rsa_fingerprint(p, sizeof(listentry) - (p - listentry), rkey);
343 p = strchr(listentry, ' ');
346 p = strchr(listentry, ' ');
349 SendDlgItemMessage(keylist, 100, LB_ADDSTRING,
350 0, (LPARAM) listentry);
352 for (i = 0; NULL != (skey = index234(ssh2keys, i)); i++) {
353 char listentry[512], *p;
356 * Replace two spaces in the fingerprint with tabs, for
357 * nice alignment in the box.
359 p = skey->alg->fingerprint(skey->data);
360 strncpy(listentry, p, sizeof(listentry));
361 p = strchr(listentry, ' ');
364 p = strchr(listentry, ' ');
367 len = strlen(listentry);
368 if (len < sizeof(listentry) - 2) {
369 listentry[len] = '\t';
370 strncpy(listentry + len + 1, skey->comment,
371 sizeof(listentry) - len - 1);
373 SendDlgItemMessage(keylist, 100, LB_ADDSTRING, 0,
376 SendDlgItemMessage(keylist, 100, LB_SETCURSEL, (WPARAM) - 1, 0);
381 * This function loads a key from a file and adds it.
383 static void add_keyfile(char *filename)
385 char passphrase[PASSPHRASE_MAXLEN];
386 struct RSAKey *rkey = NULL;
387 struct ssh2_userkey *skey = NULL;
392 struct PassphraseProcStruct pps;
396 type = key_type(filename);
397 if (type != SSH_KEYTYPE_SSH1 && type != SSH_KEYTYPE_SSH2) {
399 sprintf(msg, "Couldn't load this key (%s)", key_type_to_str(type));
400 MessageBox(NULL, msg, APPNAME, MB_OK | MB_ICONERROR);
405 * See if the key is already loaded (in the primary Pageant,
406 * which may or may not be us).
410 unsigned char *keylist, *p;
411 int i, nkeys, bloblen;
413 if (type == SSH_KEYTYPE_SSH1) {
414 if (!rsakey_pubblob(filename, &blob, &bloblen)) {
415 MessageBox(NULL, "Couldn't load private key.", APPNAME,
416 MB_OK | MB_ICONERROR);
419 keylist = get_keylist1();
421 unsigned char *blob2;
422 blob = ssh2_userkey_loadpub(filename, NULL, &bloblen);
424 MessageBox(NULL, "Couldn't load private key.", APPNAME,
425 MB_OK | MB_ICONERROR);
428 /* For our purposes we want the blob prefixed with its length */
429 blob2 = smalloc(bloblen+4);
430 PUT_32BIT(blob2, bloblen);
431 memcpy(blob2 + 4, blob, bloblen);
435 keylist = get_keylist2();
438 nkeys = GET_32BIT(keylist);
441 for (i = 0; i < nkeys; i++) {
442 if (!memcmp(blob, p, bloblen)) {
443 /* Key is already present; we can now leave. */
448 /* Now skip over public blob */
449 if (type == SSH_KEYTYPE_SSH1)
450 p += rsa_public_blob_len(p);
452 p += 4 + GET_32BIT(p);
453 /* Now skip over comment field */
454 p += 4 + GET_32BIT(p);
463 if (type == SSH_KEYTYPE_SSH1)
464 needs_pass = rsakey_encrypted(filename, &comment);
466 needs_pass = ssh2_userkey_encrypted(filename, &comment);
468 if (type == SSH_KEYTYPE_SSH1)
469 rkey = smalloc(sizeof(*rkey));
470 pps.passphrase = passphrase;
471 pps.comment = comment;
475 /* try all the remembered passphrases first */
476 char *pp = index234(passphrases, attempts);
478 strcpy(passphrase, pp);
482 dlgret = DialogBoxParam(instance, MAKEINTRESOURCE(210),
483 NULL, PassphraseProc, (LPARAM) & pps);
484 passphrase_box = NULL;
488 if (type == SSH_KEYTYPE_SSH1)
490 return; /* operation cancelled */
495 if (type == SSH_KEYTYPE_SSH1)
496 ret = loadrsakey(filename, rkey, passphrase);
498 skey = ssh2_load_userkey(filename, passphrase);
499 if (skey == SSH2_WRONG_PASSPHRASE)
509 /* if they typed in an ok passphrase, remember it */
510 if(original_pass && ret) {
511 char *pp = dupstr(passphrase);
512 addpos234(passphrases, pp, 0);
518 MessageBox(NULL, "Couldn't load private key.", APPNAME,
519 MB_OK | MB_ICONERROR);
520 if (type == SSH_KEYTYPE_SSH1)
524 if (type == SSH_KEYTYPE_SSH1) {
525 if (already_running) {
526 unsigned char *request, *response;
528 int reqlen, clen, resplen;
530 clen = strlen(rkey->comment);
532 reqlen = 4 + 1 + /* length, message type */
534 ssh1_bignum_length(rkey->modulus) +
535 ssh1_bignum_length(rkey->exponent) +
536 ssh1_bignum_length(rkey->private_exponent) +
537 ssh1_bignum_length(rkey->iqmp) +
538 ssh1_bignum_length(rkey->p) +
539 ssh1_bignum_length(rkey->q) + 4 + clen /* comment */
542 request = smalloc(reqlen);
544 request[4] = SSH1_AGENTC_ADD_RSA_IDENTITY;
546 PUT_32BIT(request + reqlen, bignum_bitcount(rkey->modulus));
548 reqlen += ssh1_write_bignum(request + reqlen, rkey->modulus);
549 reqlen += ssh1_write_bignum(request + reqlen, rkey->exponent);
551 ssh1_write_bignum(request + reqlen,
552 rkey->private_exponent);
553 reqlen += ssh1_write_bignum(request + reqlen, rkey->iqmp);
554 reqlen += ssh1_write_bignum(request + reqlen, rkey->p);
555 reqlen += ssh1_write_bignum(request + reqlen, rkey->q);
556 PUT_32BIT(request + reqlen, clen);
557 memcpy(request + reqlen + 4, rkey->comment, clen);
559 PUT_32BIT(request, reqlen - 4);
561 agent_query(request, reqlen, &vresponse, &resplen);
562 response = vresponse;
563 if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
564 MessageBox(NULL, "The already running Pageant "
565 "refused to add the key.", APPNAME,
566 MB_OK | MB_ICONERROR);
571 if (add234(rsakeys, rkey) != rkey)
572 sfree(rkey); /* already present, don't waste RAM */
575 if (already_running) {
576 unsigned char *request, *response;
578 int reqlen, alglen, clen, keybloblen, resplen;
579 alglen = strlen(skey->alg->name);
580 clen = strlen(skey->comment);
582 keybloblen = skey->alg->openssh_fmtkey(skey->data, NULL, 0);
584 reqlen = 4 + 1 + /* length, message type */
585 4 + alglen + /* algorithm name */
586 keybloblen + /* key data */
587 4 + clen /* comment */
590 request = smalloc(reqlen);
592 request[4] = SSH2_AGENTC_ADD_IDENTITY;
594 PUT_32BIT(request + reqlen, alglen);
596 memcpy(request + reqlen, skey->alg->name, alglen);
598 reqlen += skey->alg->openssh_fmtkey(skey->data,
601 PUT_32BIT(request + reqlen, clen);
602 memcpy(request + reqlen + 4, skey->comment, clen);
603 PUT_32BIT(request, reqlen - 4);
606 agent_query(request, reqlen, &vresponse, &resplen);
607 response = vresponse;
608 if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
609 MessageBox(NULL, "The already running Pageant"
610 "refused to add the key.", APPNAME,
611 MB_OK | MB_ICONERROR);
616 if (add234(ssh2keys, skey) != skey) {
617 skey->alg->freekey(skey->data);
618 sfree(skey); /* already present, don't waste RAM */
625 * Create an SSH1 key list in a malloc'ed buffer; return its
628 static void *make_keylist1(int *length)
632 unsigned char *blob, *p, *ret;
636 * Count up the number and length of keys we hold.
640 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
642 blob = rsa_public_blob(key, &bloblen);
645 len += 4 + strlen(key->comment);
648 /* Allocate the buffer. */
649 p = ret = smalloc(len);
650 if (length) *length = len;
654 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
655 blob = rsa_public_blob(key, &bloblen);
656 memcpy(p, blob, bloblen);
659 PUT_32BIT(p, strlen(key->comment));
660 memcpy(p + 4, key->comment, strlen(key->comment));
661 p += 4 + strlen(key->comment);
664 assert(p - ret == len);
669 * Create an SSH2 key list in a malloc'ed buffer; return its
672 static void *make_keylist2(int *length)
674 struct ssh2_userkey *key;
676 unsigned char *blob, *p, *ret;
680 * Count up the number and length of keys we hold.
684 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
686 len += 4; /* length field */
687 blob = key->alg->public_blob(key->data, &bloblen);
690 len += 4 + strlen(key->comment);
693 /* Allocate the buffer. */
694 p = ret = smalloc(len);
695 if (length) *length = len;
698 * Packet header is the obvious five bytes, plus four
699 * bytes for the key count.
703 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
704 blob = key->alg->public_blob(key->data, &bloblen);
705 PUT_32BIT(p, bloblen);
707 memcpy(p, blob, bloblen);
710 PUT_32BIT(p, strlen(key->comment));
711 memcpy(p + 4, key->comment, strlen(key->comment));
712 p += 4 + strlen(key->comment);
715 assert(p - ret == len);
720 * Acquire a keylist1 from the primary Pageant; this means either
721 * calling make_keylist1 (if that's us) or sending a message to the
722 * primary Pageant (if it's not).
724 static void *get_keylist1(void)
728 if (already_running) {
729 unsigned char request[5], *response;
732 request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
733 PUT_32BIT(request, 4);
735 agent_query(request, 5, &vresponse, &resplen);
736 response = vresponse;
737 if (resplen < 5 || response[4] != SSH1_AGENT_RSA_IDENTITIES_ANSWER)
740 ret = smalloc(resplen-5);
741 memcpy(ret, response+5, resplen-5);
744 ret = make_keylist1(NULL);
750 * Acquire a keylist2 from the primary Pageant; this means either
751 * calling make_keylist2 (if that's us) or sending a message to the
752 * primary Pageant (if it's not).
754 static void *get_keylist2(void)
758 if (already_running) {
759 unsigned char request[5], *response;
763 request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
764 PUT_32BIT(request, 4);
766 agent_query(request, 5, &vresponse, &resplen);
767 response = vresponse;
768 if (resplen < 5 || response[4] != SSH2_AGENT_IDENTITIES_ANSWER)
771 ret = smalloc(resplen-5);
772 memcpy(ret, response+5, resplen-5);
775 ret = make_keylist2(NULL);
781 * This is the main agent function that answers messages.
783 static void answer_msg(void *msg)
785 unsigned char *p = msg;
786 unsigned char *ret = msg;
790 * Get the message type.
796 case SSH1_AGENTC_REQUEST_RSA_IDENTITIES:
798 * Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER.
804 ret[4] = SSH1_AGENT_RSA_IDENTITIES_ANSWER;
805 keylist = make_keylist1(&len);
806 if (len + 5 > AGENT_MAX_MSGLEN) {
810 PUT_32BIT(ret, len + 1);
811 memcpy(ret + 5, keylist, len);
815 case SSH2_AGENTC_REQUEST_IDENTITIES:
817 * Reply with SSH2_AGENT_IDENTITIES_ANSWER.
823 ret[4] = SSH2_AGENT_IDENTITIES_ANSWER;
824 keylist = make_keylist2(&len);
825 if (len + 5 > AGENT_MAX_MSGLEN) {
829 PUT_32BIT(ret, len + 1);
830 memcpy(ret + 5, keylist, len);
834 case SSH1_AGENTC_RSA_CHALLENGE:
836 * Reply with either SSH1_AGENT_RSA_RESPONSE or
837 * SSH_AGENT_FAILURE, depending on whether we have that key
841 struct RSAKey reqkey, *key;
842 Bignum challenge, response;
843 unsigned char response_source[48], response_md5[16];
844 struct MD5Context md5c;
848 p += ssh1_read_bignum(p, &reqkey.exponent);
849 p += ssh1_read_bignum(p, &reqkey.modulus);
850 p += ssh1_read_bignum(p, &challenge);
851 memcpy(response_source + 32, p, 16);
853 if (GET_32BIT(p) != 1 ||
854 (key = find234(rsakeys, &reqkey, NULL)) == NULL) {
855 freebn(reqkey.exponent);
856 freebn(reqkey.modulus);
860 response = rsadecrypt(challenge, key);
861 for (i = 0; i < 32; i++)
862 response_source[i] = bignum_byte(response, 31 - i);
865 MD5Update(&md5c, response_source, 48);
866 MD5Final(response_md5, &md5c);
867 memset(response_source, 0, 48); /* burn the evidence */
868 freebn(response); /* and that evidence */
869 freebn(challenge); /* yes, and that evidence */
870 freebn(reqkey.exponent); /* and free some memory ... */
871 freebn(reqkey.modulus); /* ... while we're at it. */
874 * Packet is the obvious five byte header, plus sixteen
878 PUT_32BIT(ret, len - 4);
879 ret[4] = SSH1_AGENT_RSA_RESPONSE;
880 memcpy(ret + 5, response_md5, 16);
883 case SSH2_AGENTC_SIGN_REQUEST:
885 * Reply with either SSH2_AGENT_SIGN_RESPONSE or
886 * SSH_AGENT_FAILURE, depending on whether we have that key
890 struct ssh2_userkey *key;
892 unsigned char *data, *signature;
893 int datalen, siglen, len;
895 b.len = GET_32BIT(p);
899 datalen = GET_32BIT(p);
902 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
905 signature = key->alg->sign(key->data, data, datalen, &siglen);
906 len = 5 + 4 + siglen;
907 PUT_32BIT(ret, len - 4);
908 ret[4] = SSH2_AGENT_SIGN_RESPONSE;
909 PUT_32BIT(ret + 5, siglen);
910 memcpy(ret + 5 + 4, signature, siglen);
914 case SSH1_AGENTC_ADD_RSA_IDENTITY:
916 * Add to the list and return SSH_AGENT_SUCCESS, or
917 * SSH_AGENT_FAILURE if the key was malformed.
923 key = smalloc(sizeof(struct RSAKey));
924 memset(key, 0, sizeof(struct RSAKey));
925 p += makekey(p, key, NULL, 1);
926 p += makeprivate(p, key);
927 p += ssh1_read_bignum(p, &key->iqmp); /* p^-1 mod q */
928 p += ssh1_read_bignum(p, &key->p); /* p */
929 p += ssh1_read_bignum(p, &key->q); /* q */
930 commentlen = GET_32BIT(p);
931 comment = smalloc(commentlen+1);
933 memcpy(comment, p + 4, commentlen);
934 comment[commentlen] = '\0';
935 key->comment = comment;
938 ret[4] = SSH_AGENT_FAILURE;
939 if (add234(rsakeys, key) == key) {
941 ret[4] = SSH_AGENT_SUCCESS;
948 case SSH2_AGENTC_ADD_IDENTITY:
950 * Add to the list and return SSH_AGENT_SUCCESS, or
951 * SSH_AGENT_FAILURE if the key was malformed.
954 struct ssh2_userkey *key;
959 key = smalloc(sizeof(struct ssh2_userkey));
961 alglen = GET_32BIT(p);
965 /* Add further algorithm names here. */
966 if (alglen == 7 && !memcmp(alg, "ssh-rsa", 7))
968 else if (alglen == 7 && !memcmp(alg, "ssh-dss", 7))
976 GET_32BIT((unsigned char *) msg) - (p -
977 (unsigned char *) msg -
979 key->data = key->alg->openssh_createkey(&p, &bloblen);
984 commlen = GET_32BIT(p);
987 comment = smalloc(commlen + 1);
989 memcpy(comment, p, commlen);
990 comment[commlen] = '\0';
992 key->comment = comment;
995 ret[4] = SSH_AGENT_FAILURE;
996 if (add234(ssh2keys, key) == key) {
998 ret[4] = SSH_AGENT_SUCCESS;
1000 key->alg->freekey(key->data);
1001 sfree(key->comment);
1006 case SSH1_AGENTC_REMOVE_RSA_IDENTITY:
1008 * Remove from the list and return SSH_AGENT_SUCCESS, or
1009 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
1013 struct RSAKey reqkey, *key;
1015 p += makekey(p, &reqkey, NULL, 0);
1016 key = find234(rsakeys, &reqkey, NULL);
1017 freebn(reqkey.exponent);
1018 freebn(reqkey.modulus);
1020 ret[4] = SSH_AGENT_FAILURE;
1022 del234(rsakeys, key);
1026 ret[4] = SSH_AGENT_SUCCESS;
1030 case SSH2_AGENTC_REMOVE_IDENTITY:
1032 * Remove from the list and return SSH_AGENT_SUCCESS, or
1033 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
1037 struct ssh2_userkey *key;
1040 b.len = GET_32BIT(p);
1044 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
1049 ret[4] = SSH_AGENT_FAILURE;
1051 del234(ssh2keys, key);
1053 key->alg->freekey(key->data);
1055 ret[4] = SSH_AGENT_SUCCESS;
1059 case SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES:
1061 * Remove all SSH1 keys. Always returns success.
1064 struct RSAKey *rkey;
1066 while ((rkey = index234(rsakeys, 0)) != NULL) {
1067 del234(rsakeys, rkey);
1074 ret[4] = SSH_AGENT_SUCCESS;
1077 case SSH2_AGENTC_REMOVE_ALL_IDENTITIES:
1079 * Remove all SSH2 keys. Always returns success.
1082 struct ssh2_userkey *skey;
1084 while ((skey = index234(ssh2keys, 0)) != NULL) {
1085 del234(ssh2keys, skey);
1086 skey->alg->freekey(skey->data);
1092 ret[4] = SSH_AGENT_SUCCESS;
1098 * Unrecognised message. Return SSH_AGENT_FAILURE.
1101 ret[4] = SSH_AGENT_FAILURE;
1107 * Key comparison function for the 2-3-4 tree of RSA keys.
1109 static int cmpkeys_rsa(void *av, void *bv)
1111 struct RSAKey *a = (struct RSAKey *) av;
1112 struct RSAKey *b = (struct RSAKey *) bv;
1119 * Compare by length of moduli.
1121 alen = bignum_bitcount(am);
1122 blen = bignum_bitcount(bm);
1125 else if (alen < blen)
1128 * Now compare by moduli themselves.
1130 alen = (alen + 7) / 8; /* byte count */
1131 while (alen-- > 0) {
1133 abyte = bignum_byte(am, alen);
1134 bbyte = bignum_byte(bm, alen);
1137 else if (abyte < bbyte)
1147 * Key comparison function for the 2-3-4 tree of SSH2 keys.
1149 static int cmpkeys_ssh2(void *av, void *bv)
1151 struct ssh2_userkey *a = (struct ssh2_userkey *) av;
1152 struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
1155 unsigned char *ablob, *bblob;
1159 * Compare purely by public blob.
1161 ablob = a->alg->public_blob(a->data, &alen);
1162 bblob = b->alg->public_blob(b->data, &blen);
1165 for (i = 0; i < alen && i < blen; i++) {
1166 if (ablob[i] < bblob[i]) {
1169 } else if (ablob[i] > bblob[i]) {
1174 if (c == 0 && i < alen)
1175 c = +1; /* a is longer */
1176 if (c == 0 && i < blen)
1177 c = -1; /* a is longer */
1186 * Key comparison function for looking up a blob in the 2-3-4 tree
1189 static int cmpkeys_ssh2_asymm(void *av, void *bv)
1191 struct blob *a = (struct blob *) av;
1192 struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
1195 unsigned char *ablob, *bblob;
1199 * Compare purely by public blob.
1203 bblob = b->alg->public_blob(b->data, &blen);
1206 for (i = 0; i < alen && i < blen; i++) {
1207 if (ablob[i] < bblob[i]) {
1210 } else if (ablob[i] > bblob[i]) {
1215 if (c == 0 && i < alen)
1216 c = +1; /* a is longer */
1217 if (c == 0 && i < blen)
1218 c = -1; /* a is longer */
1226 * Prompt for a key file to add, and add it.
1228 static void prompt_add_keyfile(void)
1231 char filename[FILENAME_MAX];
1232 char *filelist = smalloc(8192);
1236 memset(&of, 0, sizeof(of));
1237 #ifdef OPENFILENAME_SIZE_VERSION_400
1238 of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
1240 of.lStructSize = sizeof(of);
1242 of.hwndOwner = main_hwnd;
1243 of.lpstrFilter = "All Files\0*\0\0\0";
1244 of.lpstrCustomFilter = NULL;
1245 of.nFilterIndex = 1;
1246 of.lpstrFile = filelist;
1248 of.nMaxFile = FILENAME_MAX;
1249 of.lpstrFileTitle = NULL;
1250 of.lpstrInitialDir = NULL;
1251 of.lpstrTitle = "Select Private Key File";
1252 of.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER;
1253 if (GetOpenFileName(&of)) {
1254 if(strlen(filelist) > of.nFileOffset)
1255 /* Only one filename returned? */
1256 add_keyfile(filelist);
1258 /* we are returned a bunch of strings, end to
1259 * end. first string is the directory, the
1260 * rest the filenames. terminated with an
1263 filewalker = filelist;
1264 dirlen = strlen(filewalker);
1265 if(dirlen > FILENAME_MAX - 8) return;
1266 memcpy(filename, filewalker, dirlen);
1268 filewalker += dirlen + 1;
1269 filename[dirlen++] = '\\';
1271 /* then go over names one by one */
1273 n = strlen(filewalker) + 1;
1274 /* end of the list */
1277 /* too big, shouldn't happen */
1278 if(n + dirlen > FILENAME_MAX)
1281 memcpy(filename + dirlen, filewalker, n);
1284 add_keyfile(filename);
1289 forget_passphrases();
1295 * Dialog-box function for the key list box.
1297 static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
1298 WPARAM wParam, LPARAM lParam)
1300 struct RSAKey *rkey;
1301 struct ssh2_userkey *skey;
1306 * Centre the window.
1308 { /* centre the window */
1312 hw = GetDesktopWindow();
1313 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
1315 (rs.right + rs.left + rd.left - rd.right) / 2,
1316 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
1317 rd.right - rd.left, rd.bottom - rd.top, TRUE);
1321 SetWindowLong(hwnd, GWL_EXSTYLE,
1322 GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_CONTEXTHELP);
1324 HWND item = GetDlgItem(hwnd, 103); /* the Help button */
1326 DestroyWindow(item);
1328 requested_help = FALSE;
1332 static int tabs[] = { 35, 60, 210 };
1333 SendDlgItemMessage(hwnd, 100, LB_SETTABSTOPS,
1334 sizeof(tabs) / sizeof(*tabs),
1340 switch (LOWORD(wParam)) {
1344 DestroyWindow(hwnd);
1346 case 101: /* add key */
1347 if (HIWORD(wParam) == BN_CLICKED ||
1348 HIWORD(wParam) == BN_DOUBLECLICKED) {
1349 if (passphrase_box) {
1350 MessageBeep(MB_ICONERROR);
1351 SetForegroundWindow(passphrase_box);
1354 prompt_add_keyfile();
1357 case 102: /* remove key */
1358 if (HIWORD(wParam) == BN_CLICKED ||
1359 HIWORD(wParam) == BN_DOUBLECLICKED) {
1364 /* our counter within the array of selected items */
1367 /* get the number of items selected in the list */
1369 SendDlgItemMessage(hwnd, 100, LB_GETSELCOUNT, 0, 0);
1371 /* none selected? that was silly */
1372 if (numSelected == 0) {
1377 /* get item indices in an array */
1378 selectedArray = smalloc(numSelected * sizeof(int));
1379 SendDlgItemMessage(hwnd, 100, LB_GETSELITEMS,
1380 numSelected, (WPARAM)selectedArray);
1382 itemNum = numSelected - 1;
1383 rCount = count234(rsakeys);
1384 sCount = count234(ssh2keys);
1386 /* go through the non-rsakeys until we've covered them all,
1387 * and/or we're out of selected items to check. note that
1388 * we go *backwards*, to avoid complications from deleting
1389 * things hence altering the offset of subsequent items
1391 for (i = sCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1392 skey = index234(ssh2keys, i);
1394 if (selectedArray[itemNum] == rCount + i) {
1395 del234(ssh2keys, skey);
1396 skey->alg->freekey(skey->data);
1402 /* do the same for the rsa keys */
1403 for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1404 rkey = index234(rsakeys, i);
1406 if(selectedArray[itemNum] == i) {
1407 del234(rsakeys, rkey);
1414 sfree(selectedArray);
1418 case 103: /* help */
1419 if (HIWORD(wParam) == BN_CLICKED ||
1420 HIWORD(wParam) == BN_DOUBLECLICKED) {
1422 WinHelp(main_hwnd, help_path, HELP_COMMAND,
1423 (DWORD)"JI(`',`pageant.general')");
1424 requested_help = TRUE;
1432 int id = ((LPHELPINFO)lParam)->iCtrlId;
1435 case 100: cmd = "JI(`',`pageant.keylist')"; break;
1436 case 101: cmd = "JI(`',`pageant.addkey')"; break;
1437 case 102: cmd = "JI(`',`pageant.remkey')"; break;
1440 WinHelp(main_hwnd, help_path, HELP_COMMAND, (DWORD)cmd);
1441 requested_help = TRUE;
1449 DestroyWindow(hwnd);
1455 /* Set up a system tray icon */
1456 static BOOL AddTrayIcon(HWND hwnd)
1459 NOTIFYICONDATA tnid;
1462 #ifdef NIM_SETVERSION
1464 res = Shell_NotifyIcon(NIM_SETVERSION, &tnid);
1467 tnid.cbSize = sizeof(NOTIFYICONDATA);
1469 tnid.uID = 1; /* unique within this systray use */
1470 tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
1471 tnid.uCallbackMessage = WM_SYSTRAY;
1472 tnid.hIcon = hicon = LoadIcon(instance, MAKEINTRESOURCE(201));
1473 strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
1475 res = Shell_NotifyIcon(NIM_ADD, &tnid);
1477 if (hicon) DestroyIcon(hicon);
1482 /* Update the saved-sessions menu. */
1483 static void update_sessions(void)
1487 TCHAR buf[MAX_PATH + 1];
1490 int index_key, index_menu;
1495 if(ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, PUTTY_REGKEY, &hkey))
1498 for(num_entries = GetMenuItemCount(session_menu);
1499 num_entries > initial_menuitems_count;
1501 RemoveMenu(session_menu, 0, MF_BYPOSITION);
1506 while(ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) {
1507 TCHAR session_name[MAX_PATH + 1];
1508 unmungestr(buf, session_name, MAX_PATH);
1509 if(strcmp(buf, PUTTY_DEFAULT) != 0) {
1510 memset(&mii, 0, sizeof(mii));
1511 mii.cbSize = sizeof(mii);
1512 mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
1513 mii.fType = MFT_STRING;
1514 mii.fState = MFS_ENABLED;
1515 mii.wID = (index_menu * 16) + IDM_SESSIONS_BASE;
1516 mii.dwTypeData = session_name;
1517 InsertMenuItem(session_menu, index_menu, TRUE, &mii);
1525 if(index_menu == 0) {
1526 mii.cbSize = sizeof(mii);
1527 mii.fMask = MIIM_TYPE | MIIM_STATE;
1528 mii.fType = MFT_STRING;
1529 mii.fState = MFS_GRAYED;
1530 mii.dwTypeData = _T("(No sessions)");
1531 InsertMenuItem(session_menu, index_menu, TRUE, &mii);
1535 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1536 WPARAM wParam, LPARAM lParam)
1539 static int menuinprogress;
1540 static UINT msgTaskbarCreated = 0;
1544 msgTaskbarCreated = RegisterWindowMessage(_T("TaskbarCreated"));
1547 if (message==msgTaskbarCreated) {
1549 * Explorer has been restarted, so the tray icon will
1557 if (lParam == WM_RBUTTONUP) {
1559 GetCursorPos(&cursorpos);
1560 PostMessage(hwnd, WM_SYSTRAY2, cursorpos.x, cursorpos.y);
1561 } else if (lParam == WM_LBUTTONDBLCLK) {
1562 /* Equivalent to IDM_VIEWKEYS. */
1563 PostMessage(hwnd, WM_COMMAND, IDM_VIEWKEYS, 0);
1567 if (!menuinprogress) {
1570 SetForegroundWindow(hwnd);
1571 ret = TrackPopupMenu(systray_menu,
1572 TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
1574 wParam, lParam, 0, hwnd, NULL);
1580 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1582 if((int)ShellExecute(hwnd, NULL, putty_path, _T(""), _T(""),
1584 MessageBox(NULL, "Unable to execute PuTTY!",
1585 "Error", MB_OK | MB_ICONERROR);
1590 SendMessage(passphrase_box, WM_CLOSE, 0, 0);
1591 SendMessage(hwnd, WM_CLOSE, 0, 0);
1595 keylist = CreateDialog(instance, MAKEINTRESOURCE(211),
1597 ShowWindow(keylist, SW_SHOWNORMAL);
1599 * Sometimes the window comes up minimised / hidden
1600 * for no obvious reason. Prevent this.
1602 SetForegroundWindow(keylist);
1603 SetWindowPos(keylist, HWND_TOP, 0, 0, 0, 0,
1604 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1608 if (passphrase_box) {
1609 MessageBeep(MB_ICONERROR);
1610 SetForegroundWindow(passphrase_box);
1613 prompt_add_keyfile();
1617 aboutbox = CreateDialog(instance, MAKEINTRESOURCE(213),
1619 ShowWindow(aboutbox, SW_SHOWNORMAL);
1621 * Sometimes the window comes up minimised / hidden
1622 * for no obvious reason. Prevent this.
1624 SetForegroundWindow(aboutbox);
1625 SetWindowPos(aboutbox, HWND_TOP, 0, 0, 0, 0,
1626 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1631 WinHelp(main_hwnd, help_path, HELP_COMMAND,
1632 (DWORD)"JI(`',`pageant.general')");
1633 requested_help = TRUE;
1638 if(wParam >= IDM_SESSIONS_BASE && wParam <= IDM_SESSIONS_MAX) {
1640 TCHAR buf[MAX_PATH + 1];
1641 TCHAR param[MAX_PATH + 1];
1642 memset(&mii, 0, sizeof(mii));
1643 mii.cbSize = sizeof(mii);
1644 mii.fMask = MIIM_TYPE;
1646 mii.dwTypeData = buf;
1647 GetMenuItemInfo(session_menu, wParam, FALSE, &mii);
1649 strcat(param, mii.dwTypeData);
1650 if((int)ShellExecute(hwnd, NULL, putty_path, param,
1651 _T(""), SW_SHOW) <= 32) {
1652 MessageBox(NULL, "Unable to execute PuTTY!", "Error",
1653 MB_OK | MB_ICONERROR);
1661 if (requested_help) {
1662 WinHelp(main_hwnd, help_path, HELP_QUIT, 0);
1663 requested_help = FALSE;
1669 COPYDATASTRUCT *cds;
1675 PSID mapowner, procowner;
1676 PSECURITY_DESCRIPTOR psd1 = NULL, psd2 = NULL;
1680 cds = (COPYDATASTRUCT *) lParam;
1681 if (cds->dwData != AGENT_COPYDATA_ID)
1682 return 0; /* not our message, mate */
1683 mapname = (char *) cds->lpData;
1684 if (mapname[cds->cbData - 1] != '\0')
1685 return 0; /* failure to be ASCIZ! */
1687 debug(("mapname is :%s:\n", mapname));
1689 filemap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, mapname);
1691 debug(("filemap is %p\n", filemap));
1693 if (filemap != NULL && filemap != INVALID_HANDLE_VALUE) {
1697 if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE,
1698 GetCurrentProcessId())) ==
1701 debug(("couldn't get handle for process\n"));
1705 if (getsecurityinfo(proc, SE_KERNEL_OBJECT,
1706 OWNER_SECURITY_INFORMATION,
1707 &procowner, NULL, NULL, NULL,
1708 &psd2) != ERROR_SUCCESS) {
1710 debug(("couldn't get owner info for process\n"));
1713 return 0; /* unable to get security info */
1716 if ((rc = getsecurityinfo(filemap, SE_KERNEL_OBJECT,
1717 OWNER_SECURITY_INFORMATION,
1718 &mapowner, NULL, NULL, NULL,
1719 &psd1) != ERROR_SUCCESS)) {
1722 ("couldn't get owner info for filemap: %d\n",
1728 debug(("got security stuff\n"));
1730 if (!EqualSid(mapowner, procowner))
1731 return 0; /* security ID mismatch! */
1733 debug(("security stuff matched\n"));
1739 debug(("security APIs not present\n"));
1743 p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
1745 debug(("p is %p\n", p));
1748 for (i = 0; i < 5; i++)
1751 ((unsigned char *) p)[i]));}
1757 CloseHandle(filemap);
1762 return DefWindowProc(hwnd, message, wParam, lParam);
1766 * Fork and Exec the command in cmdline. [DBW]
1768 void spawn_cmd(char *cmdline, char * args, int show)
1770 if (ShellExecute(NULL, _T("open"), cmdline,
1771 args, NULL, show) <= (HINSTANCE) 32) {
1773 sprintf(sMsg, _T("Failed to run \"%.100s\", Error: %d"), cmdline,
1774 (int)GetLastError());
1775 MessageBox(NULL, sMsg, APPNAME, MB_OK | MB_ICONEXCLAMATION);
1779 void cleanup_exit(int code) { exit(code); }
1781 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
1787 char *command = NULL;
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 storage for short-term passphrase cache.
1935 passphrases = newtree234(NULL);
1938 * Process the command line and add keys as listed on it.
1939 * If we already determined that we need to spawn a program from above we
1940 * need to ignore the first two arguments. [DBW]
1947 while (*p && isspace(*p))
1949 if (*p && !isspace(*p)) {
1950 char *q = p, *pp = p;
1951 while (*p && (inquotes || !isspace(*p))) {
1953 inquotes = !inquotes;
1964 if (!strcmp(q, "-c")) {
1966 * If we see `-c', then the rest of the
1967 * command line should be treated as a
1968 * command to be spawned.
1970 while (*p && isspace(*p))
1983 * Forget any passphrase that we retained while going over
1984 * command line keyfiles.
1986 forget_passphrases();
1990 if (command[0] == '"')
1991 args = strchr(++command, '"');
1993 args = strchr(command, ' ');
1996 while(*args && isspace(*args)) args++;
1998 spawn_cmd(command, args, show);
2002 * If Pageant was already running, we leave now. If we haven't
2003 * even taken any auxiliary action (spawned a command or added
2006 if (already_running) {
2007 if (!command && !added_keys) {
2008 MessageBox(NULL, "Pageant is already running", "Pageant Error",
2009 MB_ICONERROR | MB_OK);
2012 FreeLibrary(advapi);
2017 * Main message loop.
2019 while (GetMessage(&msg, NULL, 0, 0) == 1) {
2020 TranslateMessage(&msg);
2021 DispatchMessage(&msg);
2024 /* Clean up the system tray icon */
2026 NOTIFYICONDATA tnid;
2028 tnid.cbSize = sizeof(NOTIFYICONDATA);
2029 tnid.hWnd = main_hwnd;
2032 Shell_NotifyIcon(NIM_DELETE, &tnid);
2034 DestroyMenu(systray_menu);
2038 FreeLibrary(advapi);