2 * Pageant: the PuTTY Authentication Agent.
11 #define PUTTY_DO_GLOBALS
24 #define IDI_MAINICON 200
25 #define IDI_TRAYICON 201
27 #define WM_SYSTRAY (WM_APP + 6)
28 #define WM_SYSTRAY2 (WM_APP + 7)
30 #define AGENT_COPYDATA_ID 0x804e50ba /* random goop */
33 * FIXME: maybe some day we can sort this out ...
35 #define AGENT_MAX_MSGLEN 8192
37 /* From MSDN: In the WM_SYSCOMMAND message, the four low-order bits of
38 * wParam are used by Windows, and should be masked off, so we shouldn't
39 * attempt to store information in them. Hence all these identifiers have
40 * the low 4 bits clear. Also, identifiers should < 0xF000. */
42 #define IDM_CLOSE 0x0010
43 #define IDM_VIEWKEYS 0x0020
44 #define IDM_ADDKEY 0x0030
45 #define IDM_HELP 0x0040
46 #define IDM_ABOUT 0x0050
48 #define APPNAME "Pageant"
54 static HMENU systray_menu, session_menu;
55 static int already_running;
57 static char *putty_path;
59 /* CWD for "add key" file requester. */
60 static filereq *keypath = NULL;
62 #define IDM_PUTTY 0x0060
63 #define IDM_SESSIONS_BASE 0x1000
64 #define IDM_SESSIONS_MAX 0x2000
65 #define PUTTY_REGKEY "Software\\SimonTatham\\PuTTY\\Sessions"
66 #define PUTTY_DEFAULT "Default%20Settings"
67 static int initial_menuitems_count;
70 * Print a modal (Really Bad) message box and perform a fatal exit.
72 void modalfatalbox(char *fmt, ...)
78 buf = dupvprintf(fmt, ap);
80 MessageBox(hwnd, buf, "Pageant Fatal Error",
81 MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
86 /* Un-munge session names out of the registry. */
87 static void unmungestr(char *in, char *out, int outlen)
90 if (*in == '%' && in[1] && in[2]) {
98 *out++ = (i << 4) + j;
112 static tree234 *rsakeys, *ssh2keys;
114 static int has_security;
116 typedef DWORD(WINAPI * gsi_fn_t)
117 (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION,
118 PSID *, PSID *, PACL *, PACL *, PSECURITY_DESCRIPTOR *);
119 static gsi_fn_t getsecurityinfo;
125 static void *make_keylist1(int *length);
126 static void *make_keylist2(int *length);
127 static void *get_keylist1(int *length);
128 static void *get_keylist2(int *length);
131 * We need this to link with the RSA code, because rsaencrypt()
132 * pads its data with random bytes. Since we only use rsadecrypt()
133 * and the signing functions, which are deterministic, this should
136 * If it _is_ called, there is a _serious_ problem, because it
137 * won't generate true random numbers. So we must scream, panic,
138 * and exit immediately if that should happen.
140 int random_byte(void)
142 MessageBox(hwnd, "Internal Error", APPNAME, MB_OK | MB_ICONERROR);
144 /* this line can't be reached but it placates MSVC's warnings :-) */
149 * Blob structure for passing to the asymmetric SSH-2 key compare
150 * function, prototyped here.
156 static int cmpkeys_ssh2_asymm(void *av, void *bv);
158 #define PASSPHRASE_MAXLEN 512
160 struct PassphraseProcStruct {
165 static tree234 *passphrases = NULL;
168 * After processing a list of filenames, we want to forget the
171 static void forget_passphrases(void)
173 while (count234(passphrases) > 0) {
174 char *pp = index234(passphrases, 0);
175 memset(pp, 0, strlen(pp));
176 delpos234(passphrases, 0);
182 * Dialog-box function for the Licence box.
184 static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
185 WPARAM wParam, LPARAM lParam)
191 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)) {
223 EnableWindow(hwnd, 0);
224 DialogBox(hinst, MAKEINTRESOURCE(214), hwnd, LicenceProc);
225 EnableWindow(hwnd, 1);
226 SetActiveWindow(hwnd);
238 static HWND passphrase_box;
241 * Dialog-box function for the passphrase box.
243 static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
244 WPARAM wParam, LPARAM lParam)
246 static char *passphrase = NULL;
247 struct PassphraseProcStruct *p;
251 passphrase_box = hwnd;
255 { /* centre the window */
259 hw = GetDesktopWindow();
260 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
262 (rs.right + rs.left + rd.left - rd.right) / 2,
263 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
264 rd.right - rd.left, rd.bottom - rd.top, TRUE);
267 SetForegroundWindow(hwnd);
268 SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
269 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
270 p = (struct PassphraseProcStruct *) lParam;
271 passphrase = p->passphrase;
273 SetDlgItemText(hwnd, 101, p->comment);
275 SetDlgItemText(hwnd, 102, passphrase);
278 switch (LOWORD(wParam)) {
288 case 102: /* edit box */
289 if ((HIWORD(wParam) == EN_CHANGE) && passphrase) {
290 GetDlgItemText(hwnd, 102, passphrase,
291 PASSPHRASE_MAXLEN - 1);
292 passphrase[PASSPHRASE_MAXLEN - 1] = '\0';
305 * Warn about the obsolescent key file format.
307 void old_keyfile_warning(void)
309 static const char mbtitle[] = "PuTTY Key File Warning";
310 static const char message[] =
311 "You are loading an SSH-2 private key which has an\n"
312 "old version of the file format. This means your key\n"
313 "file is not fully tamperproof. Future versions of\n"
314 "PuTTY may stop supporting this private key format,\n"
315 "so we recommend you convert your key to the new\n"
318 "You can perform this conversion by loading the key\n"
319 "into PuTTYgen and then saving it again.";
321 MessageBox(NULL, message, mbtitle, MB_OK);
325 * Update the visible key list.
327 static void keylist_update(void)
330 struct ssh2_userkey *skey;
334 SendDlgItemMessage(keylist, 100, LB_RESETCONTENT, 0, 0);
335 for (i = 0; NULL != (rkey = index234(rsakeys, i)); i++) {
336 char listentry[512], *p;
338 * Replace two spaces in the fingerprint with tabs, for
339 * nice alignment in the box.
341 strcpy(listentry, "ssh1\t");
342 p = listentry + strlen(listentry);
343 rsa_fingerprint(p, sizeof(listentry) - (p - listentry), rkey);
344 p = strchr(listentry, ' ');
347 p = strchr(listentry, ' ');
350 SendDlgItemMessage(keylist, 100, LB_ADDSTRING,
351 0, (LPARAM) listentry);
353 for (i = 0; NULL != (skey = index234(ssh2keys, i)); i++) {
354 char listentry[512], *p;
357 * Replace two spaces in the fingerprint with tabs, for
358 * nice alignment in the box.
360 p = skey->alg->fingerprint(skey->data);
361 strncpy(listentry, p, sizeof(listentry));
362 p = strchr(listentry, ' ');
365 p = strchr(listentry, ' ');
368 len = strlen(listentry);
369 if (len < sizeof(listentry) - 2) {
370 listentry[len] = '\t';
371 strncpy(listentry + len + 1, skey->comment,
372 sizeof(listentry) - len - 1);
374 SendDlgItemMessage(keylist, 100, LB_ADDSTRING, 0,
377 SendDlgItemMessage(keylist, 100, LB_SETCURSEL, (WPARAM) - 1, 0);
382 * This function loads a key from a file and adds it.
384 static void add_keyfile(Filename filename)
386 char passphrase[PASSPHRASE_MAXLEN];
387 struct RSAKey *rkey = NULL;
388 struct ssh2_userkey *skey = NULL;
393 const char *error = NULL;
394 struct PassphraseProcStruct pps;
398 type = key_type(&filename);
399 if (type != SSH_KEYTYPE_SSH1 && type != SSH_KEYTYPE_SSH2) {
400 char *msg = dupprintf("Couldn't load this key (%s)",
401 key_type_to_str(type));
402 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
403 HELPCTXID(errors_cantloadkey));
409 * See if the key is already loaded (in the primary Pageant,
410 * which may or may not be us).
414 unsigned char *keylist, *p;
415 int i, nkeys, bloblen, keylistlen;
417 if (type == SSH_KEYTYPE_SSH1) {
418 if (!rsakey_pubblob(&filename, &blob, &bloblen, &error)) {
419 char *msg = dupprintf("Couldn't load private key (%s)", error);
420 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
421 HELPCTXID(errors_cantloadkey));
425 keylist = get_keylist1(&keylistlen);
427 unsigned char *blob2;
428 blob = ssh2_userkey_loadpub(&filename, NULL, &bloblen, &error);
430 char *msg = dupprintf("Couldn't load private key (%s)", error);
431 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
432 HELPCTXID(errors_cantloadkey));
436 /* For our purposes we want the blob prefixed with its length */
437 blob2 = snewn(bloblen+4, unsigned char);
438 PUT_32BIT(blob2, bloblen);
439 memcpy(blob2 + 4, blob, bloblen);
443 keylist = get_keylist2(&keylistlen);
446 if (keylistlen < 4) {
447 MessageBox(NULL, "Received broken key list?!", APPNAME,
448 MB_OK | MB_ICONERROR);
451 nkeys = GET_32BIT(keylist);
455 for (i = 0; i < nkeys; i++) {
456 if (!memcmp(blob, p, bloblen)) {
457 /* Key is already present; we can now leave. */
462 /* Now skip over public blob */
463 if (type == SSH_KEYTYPE_SSH1) {
464 int n = rsa_public_blob_len(p, keylistlen);
466 MessageBox(NULL, "Received broken key list?!", APPNAME,
467 MB_OK | MB_ICONERROR);
474 if (keylistlen < 4) {
475 MessageBox(NULL, "Received broken key list?!", APPNAME,
476 MB_OK | MB_ICONERROR);
479 n = 4 + GET_32BIT(p);
480 if (keylistlen < n) {
481 MessageBox(NULL, "Received broken key list?!", APPNAME,
482 MB_OK | MB_ICONERROR);
488 /* Now skip over comment field */
491 if (keylistlen < 4) {
492 MessageBox(NULL, "Received broken key list?!", APPNAME,
493 MB_OK | MB_ICONERROR);
496 n = 4 + GET_32BIT(p);
497 if (keylistlen < n) {
498 MessageBox(NULL, "Received broken key list?!", APPNAME,
499 MB_OK | MB_ICONERROR);
514 if (type == SSH_KEYTYPE_SSH1)
515 needs_pass = rsakey_encrypted(&filename, &comment);
517 needs_pass = ssh2_userkey_encrypted(&filename, &comment);
519 if (type == SSH_KEYTYPE_SSH1)
520 rkey = snew(struct RSAKey);
521 pps.passphrase = passphrase;
522 pps.comment = comment;
526 /* try all the remembered passphrases first */
527 char *pp = index234(passphrases, attempts);
529 strcpy(passphrase, pp);
533 dlgret = DialogBoxParam(hinst, MAKEINTRESOURCE(210),
534 NULL, PassphraseProc, (LPARAM) &pps);
535 passphrase_box = NULL;
539 if (type == SSH_KEYTYPE_SSH1)
541 return; /* operation cancelled */
546 if (type == SSH_KEYTYPE_SSH1)
547 ret = loadrsakey(&filename, rkey, passphrase, &error);
549 skey = ssh2_load_userkey(&filename, passphrase, &error);
550 if (skey == SSH2_WRONG_PASSPHRASE)
560 /* if they typed in an ok passphrase, remember it */
561 if(original_pass && ret) {
562 char *pp = dupstr(passphrase);
563 addpos234(passphrases, pp, 0);
569 char *msg = dupprintf("Couldn't load private key (%s)", error);
570 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
571 HELPCTXID(errors_cantloadkey));
573 if (type == SSH_KEYTYPE_SSH1)
577 if (type == SSH_KEYTYPE_SSH1) {
578 if (already_running) {
579 unsigned char *request, *response;
581 int reqlen, clen, resplen, ret;
583 clen = strlen(rkey->comment);
585 reqlen = 4 + 1 + /* length, message type */
587 ssh1_bignum_length(rkey->modulus) +
588 ssh1_bignum_length(rkey->exponent) +
589 ssh1_bignum_length(rkey->private_exponent) +
590 ssh1_bignum_length(rkey->iqmp) +
591 ssh1_bignum_length(rkey->p) +
592 ssh1_bignum_length(rkey->q) + 4 + clen /* comment */
595 request = snewn(reqlen, unsigned char);
597 request[4] = SSH1_AGENTC_ADD_RSA_IDENTITY;
599 PUT_32BIT(request + reqlen, bignum_bitcount(rkey->modulus));
601 reqlen += ssh1_write_bignum(request + reqlen, rkey->modulus);
602 reqlen += ssh1_write_bignum(request + reqlen, rkey->exponent);
604 ssh1_write_bignum(request + reqlen,
605 rkey->private_exponent);
606 reqlen += ssh1_write_bignum(request + reqlen, rkey->iqmp);
607 reqlen += ssh1_write_bignum(request + reqlen, rkey->p);
608 reqlen += ssh1_write_bignum(request + reqlen, rkey->q);
609 PUT_32BIT(request + reqlen, clen);
610 memcpy(request + reqlen + 4, rkey->comment, clen);
612 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(rsakeys, rkey) != rkey)
627 sfree(rkey); /* already present, don't waste RAM */
630 if (already_running) {
631 unsigned char *request, *response;
633 int reqlen, alglen, clen, keybloblen, resplen, ret;
634 alglen = strlen(skey->alg->name);
635 clen = strlen(skey->comment);
637 keybloblen = skey->alg->openssh_fmtkey(skey->data, NULL, 0);
639 reqlen = 4 + 1 + /* length, message type */
640 4 + alglen + /* algorithm name */
641 keybloblen + /* key data */
642 4 + clen /* comment */
645 request = snewn(reqlen, unsigned char);
647 request[4] = SSH2_AGENTC_ADD_IDENTITY;
649 PUT_32BIT(request + reqlen, alglen);
651 memcpy(request + reqlen, skey->alg->name, alglen);
653 reqlen += skey->alg->openssh_fmtkey(skey->data,
656 PUT_32BIT(request + reqlen, clen);
657 memcpy(request + reqlen + 4, skey->comment, clen);
659 PUT_32BIT(request, reqlen - 4);
661 ret = agent_query(request, reqlen, &vresponse, &resplen,
664 response = vresponse;
665 if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
666 MessageBox(NULL, "The already running Pageant "
667 "refused to add the key.", APPNAME,
668 MB_OK | MB_ICONERROR);
673 if (add234(ssh2keys, skey) != skey) {
674 skey->alg->freekey(skey->data);
675 sfree(skey); /* already present, don't waste RAM */
682 * Create an SSH-1 key list in a malloc'ed buffer; return its
685 static void *make_keylist1(int *length)
689 unsigned char *blob, *p, *ret;
693 * Count up the number and length of keys we hold.
697 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
699 blob = rsa_public_blob(key, &bloblen);
702 len += 4 + strlen(key->comment);
705 /* Allocate the buffer. */
706 p = ret = snewn(len, unsigned char);
707 if (length) *length = len;
711 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
712 blob = rsa_public_blob(key, &bloblen);
713 memcpy(p, blob, bloblen);
716 PUT_32BIT(p, strlen(key->comment));
717 memcpy(p + 4, key->comment, strlen(key->comment));
718 p += 4 + strlen(key->comment);
721 assert(p - ret == len);
726 * Create an SSH-2 key list in a malloc'ed buffer; return its
729 static void *make_keylist2(int *length)
731 struct ssh2_userkey *key;
733 unsigned char *blob, *p, *ret;
737 * Count up the number and length of keys we hold.
741 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
743 len += 4; /* length field */
744 blob = key->alg->public_blob(key->data, &bloblen);
747 len += 4 + strlen(key->comment);
750 /* Allocate the buffer. */
751 p = ret = snewn(len, unsigned char);
752 if (length) *length = len;
755 * Packet header is the obvious five bytes, plus four
756 * bytes for the key count.
760 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
761 blob = key->alg->public_blob(key->data, &bloblen);
762 PUT_32BIT(p, bloblen);
764 memcpy(p, blob, bloblen);
767 PUT_32BIT(p, strlen(key->comment));
768 memcpy(p + 4, key->comment, strlen(key->comment));
769 p += 4 + strlen(key->comment);
772 assert(p - ret == len);
777 * Acquire a keylist1 from the primary Pageant; this means either
778 * calling make_keylist1 (if that's us) or sending a message to the
779 * primary Pageant (if it's not).
781 static void *get_keylist1(int *length)
785 if (already_running) {
786 unsigned char request[5], *response;
789 request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
790 PUT_32BIT(request, 4);
792 retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
794 response = vresponse;
795 if (resplen < 5 || response[4] != SSH1_AGENT_RSA_IDENTITIES_ANSWER)
798 ret = snewn(resplen-5, unsigned char);
799 memcpy(ret, response+5, resplen-5);
805 ret = make_keylist1(length);
811 * Acquire a keylist2 from the primary Pageant; this means either
812 * calling make_keylist2 (if that's us) or sending a message to the
813 * primary Pageant (if it's not).
815 static void *get_keylist2(int *length)
819 if (already_running) {
820 unsigned char request[5], *response;
824 request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
825 PUT_32BIT(request, 4);
827 retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
829 response = vresponse;
830 if (resplen < 5 || response[4] != SSH2_AGENT_IDENTITIES_ANSWER)
833 ret = snewn(resplen-5, unsigned char);
834 memcpy(ret, response+5, resplen-5);
840 ret = make_keylist2(length);
846 * This is the main agent function that answers messages.
848 static void answer_msg(void *msg)
850 unsigned char *p = msg;
851 unsigned char *ret = msg;
852 unsigned char *msgend;
856 * Get the message length.
858 msgend = p + 4 + GET_32BIT(p);
861 * Get the message type.
869 case SSH1_AGENTC_REQUEST_RSA_IDENTITIES:
871 * Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER.
877 ret[4] = SSH1_AGENT_RSA_IDENTITIES_ANSWER;
878 keylist = make_keylist1(&len);
879 if (len + 5 > AGENT_MAX_MSGLEN) {
883 PUT_32BIT(ret, len + 1);
884 memcpy(ret + 5, keylist, len);
888 case SSH2_AGENTC_REQUEST_IDENTITIES:
890 * Reply with SSH2_AGENT_IDENTITIES_ANSWER.
896 ret[4] = SSH2_AGENT_IDENTITIES_ANSWER;
897 keylist = make_keylist2(&len);
898 if (len + 5 > AGENT_MAX_MSGLEN) {
902 PUT_32BIT(ret, len + 1);
903 memcpy(ret + 5, keylist, len);
907 case SSH1_AGENTC_RSA_CHALLENGE:
909 * Reply with either SSH1_AGENT_RSA_RESPONSE or
910 * SSH_AGENT_FAILURE, depending on whether we have that key
914 struct RSAKey reqkey, *key;
915 Bignum challenge, response;
916 unsigned char response_source[48], response_md5[16];
917 struct MD5Context md5c;
921 i = ssh1_read_bignum(p, msgend - p, &reqkey.exponent);
925 i = ssh1_read_bignum(p, msgend - p, &reqkey.modulus);
929 i = ssh1_read_bignum(p, msgend - p, &challenge);
934 freebn(reqkey.exponent);
935 freebn(reqkey.modulus);
939 memcpy(response_source + 32, p, 16);
943 (key = find234(rsakeys, &reqkey, NULL)) == NULL) {
944 freebn(reqkey.exponent);
945 freebn(reqkey.modulus);
949 response = rsadecrypt(challenge, key);
950 for (i = 0; i < 32; i++)
951 response_source[i] = bignum_byte(response, 31 - i);
954 MD5Update(&md5c, response_source, 48);
955 MD5Final(response_md5, &md5c);
956 memset(response_source, 0, 48); /* burn the evidence */
957 freebn(response); /* and that evidence */
958 freebn(challenge); /* yes, and that evidence */
959 freebn(reqkey.exponent); /* and free some memory ... */
960 freebn(reqkey.modulus); /* ... while we're at it. */
963 * Packet is the obvious five byte header, plus sixteen
967 PUT_32BIT(ret, len - 4);
968 ret[4] = SSH1_AGENT_RSA_RESPONSE;
969 memcpy(ret + 5, response_md5, 16);
972 case SSH2_AGENTC_SIGN_REQUEST:
974 * Reply with either SSH2_AGENT_SIGN_RESPONSE or
975 * SSH_AGENT_FAILURE, depending on whether we have that key
979 struct ssh2_userkey *key;
981 unsigned char *data, *signature;
982 int datalen, siglen, len;
986 b.len = GET_32BIT(p);
988 if (msgend < p+b.len)
994 datalen = GET_32BIT(p);
996 if (msgend < p+datalen)
999 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
1002 signature = key->alg->sign(key->data, data, datalen, &siglen);
1003 len = 5 + 4 + siglen;
1004 PUT_32BIT(ret, len - 4);
1005 ret[4] = SSH2_AGENT_SIGN_RESPONSE;
1006 PUT_32BIT(ret + 5, siglen);
1007 memcpy(ret + 5 + 4, signature, siglen);
1011 case SSH1_AGENTC_ADD_RSA_IDENTITY:
1013 * Add to the list and return SSH_AGENT_SUCCESS, or
1014 * SSH_AGENT_FAILURE if the key was malformed.
1021 key = snew(struct RSAKey);
1022 memset(key, 0, sizeof(struct RSAKey));
1024 n = makekey(p, msgend - p, key, NULL, 1);
1032 n = makeprivate(p, msgend - p, key);
1040 n = ssh1_read_bignum(p, msgend - p, &key->iqmp); /* p^-1 mod q */
1048 n = ssh1_read_bignum(p, msgend - p, &key->p); /* p */
1056 n = ssh1_read_bignum(p, msgend - p, &key->q); /* q */
1069 commentlen = GET_32BIT(p);
1071 if (msgend < p+commentlen) {
1077 comment = snewn(commentlen+1, char);
1079 memcpy(comment, p + 4, commentlen);
1080 comment[commentlen] = '\0';
1081 key->comment = comment;
1084 ret[4] = SSH_AGENT_FAILURE;
1085 if (add234(rsakeys, key) == key) {
1087 ret[4] = SSH_AGENT_SUCCESS;
1094 case SSH2_AGENTC_ADD_IDENTITY:
1096 * Add to the list and return SSH_AGENT_SUCCESS, or
1097 * SSH_AGENT_FAILURE if the key was malformed.
1100 struct ssh2_userkey *key;
1101 char *comment, *alg;
1102 int alglen, commlen;
1108 alglen = GET_32BIT(p);
1110 if (msgend < p+alglen)
1115 key = snew(struct ssh2_userkey);
1116 /* Add further algorithm names here. */
1117 if (alglen == 7 && !memcmp(alg, "ssh-rsa", 7))
1118 key->alg = &ssh_rsa;
1119 else if (alglen == 7 && !memcmp(alg, "ssh-dss", 7))
1120 key->alg = &ssh_dss;
1126 bloblen = msgend - p;
1127 key->data = key->alg->openssh_createkey(&p, &bloblen);
1134 * p has been advanced by openssh_createkey, but
1135 * certainly not _beyond_ the end of the buffer.
1137 assert(p <= msgend);
1140 key->alg->freekey(key->data);
1144 commlen = GET_32BIT(p);
1147 if (msgend < p+commlen) {
1148 key->alg->freekey(key->data);
1152 comment = snewn(commlen + 1, char);
1154 memcpy(comment, p, commlen);
1155 comment[commlen] = '\0';
1157 key->comment = comment;
1160 ret[4] = SSH_AGENT_FAILURE;
1161 if (add234(ssh2keys, key) == key) {
1163 ret[4] = SSH_AGENT_SUCCESS;
1165 key->alg->freekey(key->data);
1166 sfree(key->comment);
1171 case SSH1_AGENTC_REMOVE_RSA_IDENTITY:
1173 * Remove from the list and return SSH_AGENT_SUCCESS, or
1174 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
1178 struct RSAKey reqkey, *key;
1181 n = makekey(p, msgend - p, &reqkey, NULL, 0);
1185 key = find234(rsakeys, &reqkey, NULL);
1186 freebn(reqkey.exponent);
1187 freebn(reqkey.modulus);
1189 ret[4] = SSH_AGENT_FAILURE;
1191 del234(rsakeys, key);
1195 ret[4] = SSH_AGENT_SUCCESS;
1199 case SSH2_AGENTC_REMOVE_IDENTITY:
1201 * Remove from the list and return SSH_AGENT_SUCCESS, or
1202 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
1206 struct ssh2_userkey *key;
1211 b.len = GET_32BIT(p);
1214 if (msgend < p+b.len)
1219 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
1224 ret[4] = SSH_AGENT_FAILURE;
1226 del234(ssh2keys, key);
1228 key->alg->freekey(key->data);
1230 ret[4] = SSH_AGENT_SUCCESS;
1234 case SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES:
1236 * Remove all SSH-1 keys. Always returns success.
1239 struct RSAKey *rkey;
1241 while ((rkey = index234(rsakeys, 0)) != NULL) {
1242 del234(rsakeys, rkey);
1249 ret[4] = SSH_AGENT_SUCCESS;
1252 case SSH2_AGENTC_REMOVE_ALL_IDENTITIES:
1254 * Remove all SSH-2 keys. Always returns success.
1257 struct ssh2_userkey *skey;
1259 while ((skey = index234(ssh2keys, 0)) != NULL) {
1260 del234(ssh2keys, skey);
1261 skey->alg->freekey(skey->data);
1267 ret[4] = SSH_AGENT_SUCCESS;
1273 * Unrecognised message. Return SSH_AGENT_FAILURE.
1276 ret[4] = SSH_AGENT_FAILURE;
1282 * Key comparison function for the 2-3-4 tree of RSA keys.
1284 static int cmpkeys_rsa(void *av, void *bv)
1286 struct RSAKey *a = (struct RSAKey *) av;
1287 struct RSAKey *b = (struct RSAKey *) bv;
1294 * Compare by length of moduli.
1296 alen = bignum_bitcount(am);
1297 blen = bignum_bitcount(bm);
1300 else if (alen < blen)
1303 * Now compare by moduli themselves.
1305 alen = (alen + 7) / 8; /* byte count */
1306 while (alen-- > 0) {
1308 abyte = bignum_byte(am, alen);
1309 bbyte = bignum_byte(bm, alen);
1312 else if (abyte < bbyte)
1322 * Key comparison function for the 2-3-4 tree of SSH-2 keys.
1324 static int cmpkeys_ssh2(void *av, void *bv)
1326 struct ssh2_userkey *a = (struct ssh2_userkey *) av;
1327 struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
1330 unsigned char *ablob, *bblob;
1334 * Compare purely by public blob.
1336 ablob = a->alg->public_blob(a->data, &alen);
1337 bblob = b->alg->public_blob(b->data, &blen);
1340 for (i = 0; i < alen && i < blen; i++) {
1341 if (ablob[i] < bblob[i]) {
1344 } else if (ablob[i] > bblob[i]) {
1349 if (c == 0 && i < alen)
1350 c = +1; /* a is longer */
1351 if (c == 0 && i < blen)
1352 c = -1; /* a is longer */
1361 * Key comparison function for looking up a blob in the 2-3-4 tree
1364 static int cmpkeys_ssh2_asymm(void *av, void *bv)
1366 struct blob *a = (struct blob *) av;
1367 struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
1370 unsigned char *ablob, *bblob;
1374 * Compare purely by public blob.
1378 bblob = b->alg->public_blob(b->data, &blen);
1381 for (i = 0; i < alen && i < blen; i++) {
1382 if (ablob[i] < bblob[i]) {
1385 } else if (ablob[i] > bblob[i]) {
1390 if (c == 0 && i < alen)
1391 c = +1; /* a is longer */
1392 if (c == 0 && i < blen)
1393 c = -1; /* a is longer */
1401 * Prompt for a key file to add, and add it.
1403 static void prompt_add_keyfile(void)
1406 char *filelist = snewn(8192, char);
1408 if (!keypath) keypath = filereq_new();
1409 memset(&of, 0, sizeof(of));
1410 of.hwndOwner = hwnd;
1411 of.lpstrFilter = FILTER_KEY_FILES;
1412 of.lpstrCustomFilter = NULL;
1413 of.nFilterIndex = 1;
1414 of.lpstrFile = filelist;
1417 of.lpstrFileTitle = NULL;
1418 of.lpstrTitle = "Select Private Key File";
1419 of.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER;
1420 if (request_file(keypath, &of, TRUE, FALSE)) {
1421 if(strlen(filelist) > of.nFileOffset)
1422 /* Only one filename returned? */
1423 add_keyfile(filename_from_str(filelist));
1425 /* we are returned a bunch of strings, end to
1426 * end. first string is the directory, the
1427 * rest the filenames. terminated with an
1430 char *dir = filelist;
1431 char *filewalker = filelist + strlen(dir) + 1;
1432 while (*filewalker != '\0') {
1433 char *filename = dupcat(dir, "\\", filewalker, NULL);
1434 add_keyfile(filename_from_str(filename));
1436 filewalker += strlen(filewalker) + 1;
1441 forget_passphrases();
1447 * Dialog-box function for the key list box.
1449 static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
1450 WPARAM wParam, LPARAM lParam)
1452 struct RSAKey *rkey;
1453 struct ssh2_userkey *skey;
1458 * Centre the window.
1460 { /* centre the window */
1464 hw = GetDesktopWindow();
1465 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
1467 (rs.right + rs.left + rd.left - rd.right) / 2,
1468 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
1469 rd.right - rd.left, rd.bottom - rd.top, TRUE);
1473 SetWindowLongPtr(hwnd, GWL_EXSTYLE,
1474 GetWindowLongPtr(hwnd, GWL_EXSTYLE) |
1477 HWND item = GetDlgItem(hwnd, 103); /* the Help button */
1479 DestroyWindow(item);
1481 requested_help = FALSE;
1485 static int tabs[] = { 35, 60, 210 };
1486 SendDlgItemMessage(hwnd, 100, LB_SETTABSTOPS,
1487 sizeof(tabs) / sizeof(*tabs),
1493 switch (LOWORD(wParam)) {
1497 DestroyWindow(hwnd);
1499 case 101: /* add key */
1500 if (HIWORD(wParam) == BN_CLICKED ||
1501 HIWORD(wParam) == BN_DOUBLECLICKED) {
1502 if (passphrase_box) {
1503 MessageBeep(MB_ICONERROR);
1504 SetForegroundWindow(passphrase_box);
1507 prompt_add_keyfile();
1510 case 102: /* remove key */
1511 if (HIWORD(wParam) == BN_CLICKED ||
1512 HIWORD(wParam) == BN_DOUBLECLICKED) {
1517 /* our counter within the array of selected items */
1520 /* get the number of items selected in the list */
1522 SendDlgItemMessage(hwnd, 100, LB_GETSELCOUNT, 0, 0);
1524 /* none selected? that was silly */
1525 if (numSelected == 0) {
1530 /* get item indices in an array */
1531 selectedArray = snewn(numSelected, int);
1532 SendDlgItemMessage(hwnd, 100, LB_GETSELITEMS,
1533 numSelected, (WPARAM)selectedArray);
1535 itemNum = numSelected - 1;
1536 rCount = count234(rsakeys);
1537 sCount = count234(ssh2keys);
1539 /* go through the non-rsakeys until we've covered them all,
1540 * and/or we're out of selected items to check. note that
1541 * we go *backwards*, to avoid complications from deleting
1542 * things hence altering the offset of subsequent items
1544 for (i = sCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1545 skey = index234(ssh2keys, i);
1547 if (selectedArray[itemNum] == rCount + i) {
1548 del234(ssh2keys, skey);
1549 skey->alg->freekey(skey->data);
1555 /* do the same for the rsa keys */
1556 for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1557 rkey = index234(rsakeys, i);
1559 if(selectedArray[itemNum] == i) {
1560 del234(rsakeys, rkey);
1567 sfree(selectedArray);
1571 case 103: /* help */
1572 if (HIWORD(wParam) == BN_CLICKED ||
1573 HIWORD(wParam) == BN_DOUBLECLICKED) {
1575 WinHelp(hwnd, help_path, HELP_COMMAND,
1576 (DWORD)"JI(`',`pageant.general')");
1577 requested_help = TRUE;
1585 int id = ((LPHELPINFO)lParam)->iCtrlId;
1588 case 100: topic = "pageant.keylist"; break;
1589 case 101: topic = "pageant.addkey"; break;
1590 case 102: topic = "pageant.remkey"; break;
1593 char *cmd = dupprintf("JI(`',`%s')", topic);
1594 WinHelp(hwnd, help_path, HELP_COMMAND, (DWORD)cmd);
1596 requested_help = TRUE;
1604 DestroyWindow(hwnd);
1610 /* Set up a system tray icon */
1611 static BOOL AddTrayIcon(HWND hwnd)
1614 NOTIFYICONDATA tnid;
1617 #ifdef NIM_SETVERSION
1619 res = Shell_NotifyIcon(NIM_SETVERSION, &tnid);
1622 tnid.cbSize = sizeof(NOTIFYICONDATA);
1624 tnid.uID = 1; /* unique within this systray use */
1625 tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
1626 tnid.uCallbackMessage = WM_SYSTRAY;
1627 tnid.hIcon = hicon = LoadIcon(hinst, MAKEINTRESOURCE(201));
1628 strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
1630 res = Shell_NotifyIcon(NIM_ADD, &tnid);
1632 if (hicon) DestroyIcon(hicon);
1637 /* Update the saved-sessions menu. */
1638 static void update_sessions(void)
1642 TCHAR buf[MAX_PATH + 1];
1645 int index_key, index_menu;
1650 if(ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, PUTTY_REGKEY, &hkey))
1653 for(num_entries = GetMenuItemCount(session_menu);
1654 num_entries > initial_menuitems_count;
1656 RemoveMenu(session_menu, 0, MF_BYPOSITION);
1661 while(ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) {
1662 TCHAR session_name[MAX_PATH + 1];
1663 unmungestr(buf, session_name, MAX_PATH);
1664 if(strcmp(buf, PUTTY_DEFAULT) != 0) {
1665 memset(&mii, 0, sizeof(mii));
1666 mii.cbSize = sizeof(mii);
1667 mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
1668 mii.fType = MFT_STRING;
1669 mii.fState = MFS_ENABLED;
1670 mii.wID = (index_menu * 16) + IDM_SESSIONS_BASE;
1671 mii.dwTypeData = session_name;
1672 InsertMenuItem(session_menu, index_menu, TRUE, &mii);
1680 if(index_menu == 0) {
1681 mii.cbSize = sizeof(mii);
1682 mii.fMask = MIIM_TYPE | MIIM_STATE;
1683 mii.fType = MFT_STRING;
1684 mii.fState = MFS_GRAYED;
1685 mii.dwTypeData = _T("(No sessions)");
1686 InsertMenuItem(session_menu, index_menu, TRUE, &mii);
1690 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1691 WPARAM wParam, LPARAM lParam)
1694 static int menuinprogress;
1695 static UINT msgTaskbarCreated = 0;
1699 msgTaskbarCreated = RegisterWindowMessage(_T("TaskbarCreated"));
1702 if (message==msgTaskbarCreated) {
1704 * Explorer has been restarted, so the tray icon will
1712 if (lParam == WM_RBUTTONUP) {
1714 GetCursorPos(&cursorpos);
1715 PostMessage(hwnd, WM_SYSTRAY2, cursorpos.x, cursorpos.y);
1716 } else if (lParam == WM_LBUTTONDBLCLK) {
1717 /* Run the default menu item. */
1718 UINT menuitem = GetMenuDefaultItem(systray_menu, FALSE, 0);
1720 PostMessage(hwnd, WM_COMMAND, menuitem, 0);
1724 if (!menuinprogress) {
1727 SetForegroundWindow(hwnd);
1728 ret = TrackPopupMenu(systray_menu,
1729 TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
1731 wParam, lParam, 0, hwnd, NULL);
1737 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1739 if((int)ShellExecute(hwnd, NULL, putty_path, _T(""), _T(""),
1741 MessageBox(NULL, "Unable to execute PuTTY!",
1742 "Error", MB_OK | MB_ICONERROR);
1747 SendMessage(passphrase_box, WM_CLOSE, 0, 0);
1748 SendMessage(hwnd, WM_CLOSE, 0, 0);
1752 keylist = CreateDialog(hinst, MAKEINTRESOURCE(211),
1754 ShowWindow(keylist, SW_SHOWNORMAL);
1757 * Sometimes the window comes up minimised / hidden for
1758 * no obvious reason. Prevent this. This also brings it
1759 * to the front if it's already present (the user
1760 * selected View Keys because they wanted to _see_ the
1763 SetForegroundWindow(keylist);
1764 SetWindowPos(keylist, HWND_TOP, 0, 0, 0, 0,
1765 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1768 if (passphrase_box) {
1769 MessageBeep(MB_ICONERROR);
1770 SetForegroundWindow(passphrase_box);
1773 prompt_add_keyfile();
1777 aboutbox = CreateDialog(hinst, MAKEINTRESOURCE(213),
1779 ShowWindow(aboutbox, SW_SHOWNORMAL);
1781 * Sometimes the window comes up minimised / hidden
1782 * for no obvious reason. Prevent this.
1784 SetForegroundWindow(aboutbox);
1785 SetWindowPos(aboutbox, HWND_TOP, 0, 0, 0, 0,
1786 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1791 WinHelp(hwnd, help_path, HELP_COMMAND,
1792 (DWORD)"JI(`',`pageant.general')");
1793 requested_help = TRUE;
1798 if(wParam >= IDM_SESSIONS_BASE && wParam <= IDM_SESSIONS_MAX) {
1800 TCHAR buf[MAX_PATH + 1];
1801 TCHAR param[MAX_PATH + 1];
1802 memset(&mii, 0, sizeof(mii));
1803 mii.cbSize = sizeof(mii);
1804 mii.fMask = MIIM_TYPE;
1806 mii.dwTypeData = buf;
1807 GetMenuItemInfo(session_menu, wParam, FALSE, &mii);
1809 strcat(param, mii.dwTypeData);
1810 if((int)ShellExecute(hwnd, NULL, putty_path, param,
1811 _T(""), SW_SHOW) <= 32) {
1812 MessageBox(NULL, "Unable to execute PuTTY!", "Error",
1813 MB_OK | MB_ICONERROR);
1821 if (requested_help) {
1822 WinHelp(hwnd, help_path, HELP_QUIT, 0);
1823 requested_help = FALSE;
1829 COPYDATASTRUCT *cds;
1835 PSID mapowner, procowner;
1836 PSECURITY_DESCRIPTOR psd1 = NULL, psd2 = NULL;
1840 cds = (COPYDATASTRUCT *) lParam;
1841 if (cds->dwData != AGENT_COPYDATA_ID)
1842 return 0; /* not our message, mate */
1843 mapname = (char *) cds->lpData;
1844 if (mapname[cds->cbData - 1] != '\0')
1845 return 0; /* failure to be ASCIZ! */
1847 debug(("mapname is :%s:\n", mapname));
1849 filemap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, mapname);
1851 debug(("filemap is %p\n", filemap));
1853 if (filemap != NULL && filemap != INVALID_HANDLE_VALUE) {
1857 if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE,
1858 GetCurrentProcessId())) ==
1861 debug(("couldn't get handle for process\n"));
1865 if (getsecurityinfo(proc, SE_KERNEL_OBJECT,
1866 OWNER_SECURITY_INFORMATION,
1867 &procowner, NULL, NULL, NULL,
1868 &psd2) != ERROR_SUCCESS) {
1870 debug(("couldn't get owner info for process\n"));
1873 return 0; /* unable to get security info */
1876 if ((rc = getsecurityinfo(filemap, SE_KERNEL_OBJECT,
1877 OWNER_SECURITY_INFORMATION,
1878 &mapowner, NULL, NULL, NULL,
1879 &psd1) != ERROR_SUCCESS)) {
1882 ("couldn't get owner info for filemap: %d\n",
1888 debug(("got security stuff\n"));
1890 if (!EqualSid(mapowner, procowner))
1891 return 0; /* security ID mismatch! */
1893 debug(("security stuff matched\n"));
1899 debug(("security APIs not present\n"));
1903 p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
1905 debug(("p is %p\n", p));
1908 for (i = 0; i < 5; i++)
1911 ((unsigned char *) p)[i]));}
1917 CloseHandle(filemap);
1922 return DefWindowProc(hwnd, message, wParam, lParam);
1926 * Fork and Exec the command in cmdline. [DBW]
1928 void spawn_cmd(char *cmdline, char * args, int show)
1930 if (ShellExecute(NULL, _T("open"), cmdline,
1931 args, NULL, show) <= (HINSTANCE) 32) {
1933 msg = dupprintf("Failed to run \"%.100s\", Error: %d", cmdline,
1934 (int)GetLastError());
1935 MessageBox(NULL, msg, APPNAME, MB_OK | MB_ICONEXCLAMATION);
1941 * This is a can't-happen stub, since Pageant never makes
1942 * asynchronous agent requests.
1944 void agent_schedule_callback(void (*callback)(void *, void *, int),
1945 void *callback_ctx, void *data, int len)
1947 assert(!"We shouldn't get here");
1950 void cleanup_exit(int code) { exit(code); }
1952 int flags = FLAG_SYNCAGENT;
1954 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
1959 char *command = NULL;
1962 char **argv, **argstart;
1968 * Determine whether we're an NT system (should have security
1969 * APIs) or a non-NT system (don't do security).
1973 modalfatalbox("Windows refuses to report a version");
1975 if (osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT) {
1976 has_security = TRUE;
1978 has_security = FALSE;
1983 * Attempt to get the security API we need.
1985 advapi = LoadLibrary("ADVAPI32.DLL");
1987 (gsi_fn_t) GetProcAddress(advapi, "GetSecurityInfo");
1988 if (!getsecurityinfo) {
1990 "Unable to access security APIs. Pageant will\n"
1991 "not run, in case it causes a security breach.",
1992 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
1997 "This program has been compiled for Win9X and will\n"
1998 "not run on NT, in case it causes a security breach.",
1999 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
2006 * See if we can find our Help file.
2009 char b[2048], *p, *q, *r;
2011 GetModuleFileName(NULL, b, sizeof(b) - 1);
2013 p = strrchr(b, '\\');
2014 if (p && p >= r) r = p+1;
2015 q = strrchr(b, ':');
2016 if (q && q >= r) r = q+1;
2017 strcpy(r, PUTTY_HELP_FILE);
2018 if ( (fp = fopen(b, "r")) != NULL) {
2019 help_path = dupstr(b);
2026 * Look for the PuTTY binary (we will enable the saved session
2027 * submenu if we find it).
2030 char b[2048], *p, *q, *r;
2032 GetModuleFileName(NULL, b, sizeof(b) - 1);
2034 p = strrchr(b, '\\');
2035 if (p && p >= r) r = p+1;
2036 q = strrchr(b, ':');
2037 if (q && q >= r) r = q+1;
2038 strcpy(r, "putty.exe");
2039 if ( (fp = fopen(b, "r")) != NULL) {
2040 putty_path = dupstr(b);
2047 * Find out if Pageant is already running.
2049 already_running = agent_exists();
2052 * Initialise storage for RSA keys.
2054 if (!already_running) {
2055 rsakeys = newtree234(cmpkeys_rsa);
2056 ssh2keys = newtree234(cmpkeys_ssh2);
2060 * Initialise storage for short-term passphrase cache.
2062 passphrases = newtree234(NULL);
2065 * Process the command line and add keys as listed on it.
2067 split_into_argv(cmdline, &argc, &argv, &argstart);
2068 for (i = 0; i < argc; i++) {
2069 if (!strcmp(argv[i], "-pgpfp")) {
2072 FreeLibrary(advapi);
2074 } else if (!strcmp(argv[i], "-c")) {
2076 * If we see `-c', then the rest of the
2077 * command line should be treated as a
2078 * command to be spawned.
2081 command = argstart[i+1];
2086 add_keyfile(filename_from_str(argv[i]));
2092 * Forget any passphrase that we retained while going over
2093 * command line keyfiles.
2095 forget_passphrases();
2099 if (command[0] == '"')
2100 args = strchr(++command, '"');
2102 args = strchr(command, ' ');
2105 while(*args && isspace(*args)) args++;
2107 spawn_cmd(command, args, show);
2111 * If Pageant was already running, we leave now. If we haven't
2112 * even taken any auxiliary action (spawned a command or added
2115 if (already_running) {
2116 if (!command && !added_keys) {
2117 MessageBox(NULL, "Pageant is already running", "Pageant Error",
2118 MB_ICONERROR | MB_OK);
2121 FreeLibrary(advapi);
2127 wndclass.lpfnWndProc = WndProc;
2128 wndclass.cbClsExtra = 0;
2129 wndclass.cbWndExtra = 0;
2130 wndclass.hInstance = inst;
2131 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
2132 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
2133 wndclass.hbrBackground = GetStockObject(BLACK_BRUSH);
2134 wndclass.lpszMenuName = NULL;
2135 wndclass.lpszClassName = APPNAME;
2137 RegisterClass(&wndclass);
2142 hwnd = CreateWindow(APPNAME, APPNAME,
2143 WS_OVERLAPPEDWINDOW | WS_VSCROLL,
2144 CW_USEDEFAULT, CW_USEDEFAULT,
2145 100, 100, NULL, NULL, inst, NULL);
2147 /* Set up a system tray icon */
2150 /* Accelerators used: nsvkxa */
2151 systray_menu = CreatePopupMenu();
2153 session_menu = CreateMenu();
2154 AppendMenu(systray_menu, MF_ENABLED, IDM_PUTTY, "&New Session");
2155 AppendMenu(systray_menu, MF_POPUP | MF_ENABLED,
2156 (UINT) session_menu, "&Saved Sessions");
2157 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
2159 AppendMenu(systray_menu, MF_ENABLED, IDM_VIEWKEYS,
2161 AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key");
2162 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
2164 AppendMenu(systray_menu, MF_ENABLED, IDM_HELP, "&Help");
2165 AppendMenu(systray_menu, MF_ENABLED, IDM_ABOUT, "&About");
2166 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
2167 AppendMenu(systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit");
2168 initial_menuitems_count = GetMenuItemCount(session_menu);
2170 /* Set the default menu item. */
2171 SetMenuDefaultItem(systray_menu, IDM_VIEWKEYS, FALSE);
2173 ShowWindow(hwnd, SW_HIDE);
2176 * Main message loop.
2178 while (GetMessage(&msg, NULL, 0, 0) == 1) {
2179 if (!(IsWindow(keylist) && IsDialogMessage(keylist, &msg)) &&
2180 !(IsWindow(aboutbox) && IsDialogMessage(aboutbox, &msg))) {
2181 TranslateMessage(&msg);
2182 DispatchMessage(&msg);
2186 /* Clean up the system tray icon */
2188 NOTIFYICONDATA tnid;
2190 tnid.cbSize = sizeof(NOTIFYICONDATA);
2194 Shell_NotifyIcon(NIM_DELETE, &tnid);
2196 DestroyMenu(systray_menu);
2199 if (keypath) filereq_free(keypath);
2202 FreeLibrary(advapi);