2 * Pageant: the PuTTY Authentication Agent.
11 #define PUTTY_DO_GLOBALS
23 #define _WIN32_WINNT 0x0500 /* for ConvertSidToStringSid */
28 #define IDI_MAINICON 200
29 #define IDI_TRAYICON 201
31 #define WM_SYSTRAY (WM_APP + 6)
32 #define WM_SYSTRAY2 (WM_APP + 7)
34 #define AGENT_COPYDATA_ID 0x804e50ba /* random goop */
37 * FIXME: maybe some day we can sort this out ...
39 #define AGENT_MAX_MSGLEN 8192
41 /* From MSDN: In the WM_SYSCOMMAND message, the four low-order bits of
42 * wParam are used by Windows, and should be masked off, so we shouldn't
43 * attempt to store information in them. Hence all these identifiers have
44 * the low 4 bits clear. Also, identifiers should < 0xF000. */
46 #define IDM_CLOSE 0x0010
47 #define IDM_VIEWKEYS 0x0020
48 #define IDM_ADDKEY 0x0030
49 #define IDM_HELP 0x0040
50 #define IDM_ABOUT 0x0050
52 #define APPNAME "Pageant"
58 static HMENU systray_menu, session_menu;
59 static int already_running;
61 static char *putty_path;
63 /* CWD for "add key" file requester. */
64 static filereq *keypath = NULL;
66 #define IDM_PUTTY 0x0060
67 #define IDM_SESSIONS_BASE 0x1000
68 #define IDM_SESSIONS_MAX 0x2000
69 #define PUTTY_REGKEY "Software\\SimonTatham\\PuTTY\\Sessions"
70 #define PUTTY_DEFAULT "Default%20Settings"
71 static int initial_menuitems_count;
74 * Print a modal (Really Bad) message box and perform a fatal exit.
76 void modalfatalbox(char *fmt, ...)
82 buf = dupvprintf(fmt, ap);
84 MessageBox(hwnd, buf, "Pageant Fatal Error",
85 MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
90 /* Un-munge session names out of the registry. */
91 static void unmungestr(char *in, char *out, int outlen)
94 if (*in == '%' && in[1] && in[2]) {
100 j -= (j > 9 ? 7 : 0);
102 *out++ = (i << 4) + j;
116 static tree234 *rsakeys, *ssh2keys;
118 static int has_security;
120 DECL_WINDOWS_FUNCTION(extern, DWORD, GetSecurityInfo,
121 (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION,
122 PSID *, PSID *, PACL *, PACL *,
123 PSECURITY_DESCRIPTOR *));
129 static void *make_keylist1(int *length);
130 static void *make_keylist2(int *length);
131 static void *get_keylist1(int *length);
132 static void *get_keylist2(int *length);
135 * We need this to link with the RSA code, because rsaencrypt()
136 * pads its data with random bytes. Since we only use rsadecrypt()
137 * and the signing functions, which are deterministic, this should
140 * If it _is_ called, there is a _serious_ problem, because it
141 * won't generate true random numbers. So we must scream, panic,
142 * and exit immediately if that should happen.
144 int random_byte(void)
146 MessageBox(hwnd, "Internal Error", APPNAME, MB_OK | MB_ICONERROR);
148 /* this line can't be reached but it placates MSVC's warnings :-) */
153 * Blob structure for passing to the asymmetric SSH-2 key compare
154 * function, prototyped here.
160 static int cmpkeys_ssh2_asymm(void *av, void *bv);
162 struct PassphraseProcStruct {
167 static tree234 *passphrases = NULL;
170 * After processing a list of filenames, we want to forget the
173 static void forget_passphrases(void)
175 while (count234(passphrases) > 0) {
176 char *pp = index234(passphrases, 0);
177 smemclr(pp, strlen(pp));
178 delpos234(passphrases, 0);
184 * Dialog-box function for the Licence box.
186 static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
187 WPARAM wParam, LPARAM lParam)
193 switch (LOWORD(wParam)) {
208 * Dialog-box function for the About box.
210 static int CALLBACK AboutProc(HWND hwnd, UINT msg,
211 WPARAM wParam, LPARAM lParam)
215 SetDlgItemText(hwnd, 100, ver);
218 switch (LOWORD(wParam)) {
225 EnableWindow(hwnd, 0);
226 DialogBox(hinst, MAKEINTRESOURCE(214), hwnd, LicenceProc);
227 EnableWindow(hwnd, 1);
228 SetActiveWindow(hwnd);
240 static HWND passphrase_box;
243 * Dialog-box function for the passphrase box.
245 static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
246 WPARAM wParam, LPARAM lParam)
248 static char **passphrase = NULL;
249 struct PassphraseProcStruct *p;
253 passphrase_box = hwnd;
257 { /* centre the window */
261 hw = GetDesktopWindow();
262 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
264 (rs.right + rs.left + rd.left - rd.right) / 2,
265 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
266 rd.right - rd.left, rd.bottom - rd.top, TRUE);
269 SetForegroundWindow(hwnd);
270 SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
271 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
272 p = (struct PassphraseProcStruct *) lParam;
273 passphrase = p->passphrase;
275 SetDlgItemText(hwnd, 101, p->comment);
276 burnstr(*passphrase);
277 *passphrase = dupstr("");
278 SetDlgItemText(hwnd, 102, *passphrase);
281 switch (LOWORD(wParam)) {
291 case 102: /* edit box */
292 if ((HIWORD(wParam) == EN_CHANGE) && passphrase) {
293 burnstr(*passphrase);
294 *passphrase = GetDlgItemText_alloc(hwnd, 102);
307 * Warn about the obsolescent key file format.
309 void old_keyfile_warning(void)
311 static const char mbtitle[] = "PuTTY Key File Warning";
312 static const char message[] =
313 "You are loading an SSH-2 private key which has an\n"
314 "old version of the file format. This means your key\n"
315 "file is not fully tamperproof. Future versions of\n"
316 "PuTTY may stop supporting this private key format,\n"
317 "so we recommend you convert your key to the new\n"
320 "You can perform this conversion by loading the key\n"
321 "into PuTTYgen and then saving it again.";
323 MessageBox(NULL, message, mbtitle, MB_OK);
327 * Update the visible key list.
329 static void keylist_update(void)
332 struct ssh2_userkey *skey;
336 SendDlgItemMessage(keylist, 100, LB_RESETCONTENT, 0, 0);
337 for (i = 0; NULL != (rkey = index234(rsakeys, i)); i++) {
338 char listentry[512], *p;
340 * Replace two spaces in the fingerprint with tabs, for
341 * nice alignment in the box.
343 strcpy(listentry, "ssh1\t");
344 p = listentry + strlen(listentry);
345 rsa_fingerprint(p, sizeof(listentry) - (p - listentry), rkey);
346 p = strchr(listentry, ' ');
349 p = strchr(listentry, ' ');
352 SendDlgItemMessage(keylist, 100, LB_ADDSTRING,
353 0, (LPARAM) listentry);
355 for (i = 0; NULL != (skey = index234(ssh2keys, i)); i++) {
356 char listentry[512], *p;
359 * Replace two spaces in the fingerprint with tabs, for
360 * nice alignment in the box.
362 p = skey->alg->fingerprint(skey->data);
363 strncpy(listentry, p, sizeof(listentry));
364 p = strchr(listentry, ' ');
367 p = strchr(listentry, ' ');
370 len = strlen(listentry);
371 if (len < sizeof(listentry) - 2) {
372 listentry[len] = '\t';
373 strncpy(listentry + len + 1, skey->comment,
374 sizeof(listentry) - len - 1);
376 SendDlgItemMessage(keylist, 100, LB_ADDSTRING, 0,
379 SendDlgItemMessage(keylist, 100, LB_SETCURSEL, (WPARAM) - 1, 0);
384 * This function loads a key from a file and adds it.
386 static void add_keyfile(Filename *filename)
389 struct RSAKey *rkey = NULL;
390 struct ssh2_userkey *skey = NULL;
395 const char *error = NULL;
399 type = key_type(filename);
400 if (type != SSH_KEYTYPE_SSH1 && type != SSH_KEYTYPE_SSH2) {
401 char *msg = dupprintf("Couldn't load this key (%s)",
402 key_type_to_str(type));
403 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
404 HELPCTXID(errors_cantloadkey));
410 * See if the key is already loaded (in the primary Pageant,
411 * which may or may not be us).
415 unsigned char *keylist, *p;
416 int i, nkeys, bloblen, keylistlen;
418 if (type == SSH_KEYTYPE_SSH1) {
419 if (!rsakey_pubblob(filename, &blob, &bloblen, NULL, &error)) {
420 char *msg = dupprintf("Couldn't load private key (%s)", error);
421 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
422 HELPCTXID(errors_cantloadkey));
426 keylist = get_keylist1(&keylistlen);
428 unsigned char *blob2;
429 blob = ssh2_userkey_loadpub(filename, NULL, &bloblen,
432 char *msg = dupprintf("Couldn't load private key (%s)", error);
433 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
434 HELPCTXID(errors_cantloadkey));
438 /* For our purposes we want the blob prefixed with its length */
439 blob2 = snewn(bloblen+4, unsigned char);
440 PUT_32BIT(blob2, bloblen);
441 memcpy(blob2 + 4, blob, bloblen);
445 keylist = get_keylist2(&keylistlen);
448 if (keylistlen < 4) {
449 MessageBox(NULL, "Received broken key list?!", APPNAME,
450 MB_OK | MB_ICONERROR);
453 nkeys = GET_32BIT(keylist);
457 for (i = 0; i < nkeys; i++) {
458 if (!memcmp(blob, p, bloblen)) {
459 /* Key is already present; we can now leave. */
464 /* Now skip over public blob */
465 if (type == SSH_KEYTYPE_SSH1) {
466 int n = rsa_public_blob_len(p, keylistlen);
468 MessageBox(NULL, "Received broken key list?!", APPNAME,
469 MB_OK | MB_ICONERROR);
476 if (keylistlen < 4) {
477 MessageBox(NULL, "Received broken key list?!", APPNAME,
478 MB_OK | MB_ICONERROR);
481 n = 4 + GET_32BIT(p);
482 if (keylistlen < n) {
483 MessageBox(NULL, "Received broken key list?!", APPNAME,
484 MB_OK | MB_ICONERROR);
490 /* Now skip over comment field */
493 if (keylistlen < 4) {
494 MessageBox(NULL, "Received broken key list?!", APPNAME,
495 MB_OK | MB_ICONERROR);
498 n = 4 + GET_32BIT(p);
499 if (keylistlen < n) {
500 MessageBox(NULL, "Received broken key list?!", APPNAME,
501 MB_OK | MB_ICONERROR);
516 if (type == SSH_KEYTYPE_SSH1)
517 needs_pass = rsakey_encrypted(filename, &comment);
519 needs_pass = ssh2_userkey_encrypted(filename, &comment);
521 if (type == SSH_KEYTYPE_SSH1)
522 rkey = snew(struct RSAKey);
530 /* try all the remembered passphrases first */
531 char *pp = index234(passphrases, attempts);
533 passphrase = dupstr(pp);
536 struct PassphraseProcStruct pps;
538 pps.passphrase = &passphrase;
539 pps.comment = comment;
542 dlgret = DialogBoxParam(hinst, MAKEINTRESOURCE(210),
543 NULL, PassphraseProc, (LPARAM) &pps);
544 passphrase_box = NULL;
548 if (type == SSH_KEYTYPE_SSH1)
550 return; /* operation cancelled */
553 assert(passphrase != NULL);
556 passphrase = dupstr("");
558 if (type == SSH_KEYTYPE_SSH1)
559 ret = loadrsakey(filename, rkey, passphrase, &error);
561 skey = ssh2_load_userkey(filename, passphrase, &error);
562 if (skey == SSH2_WRONG_PASSPHRASE)
572 if(original_pass && ret) {
573 /* If they typed in an ok passphrase, remember it */
574 addpos234(passphrases, passphrase, 0);
576 /* Otherwise, destroy it */
584 char *msg = dupprintf("Couldn't load private key (%s)", error);
585 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
586 HELPCTXID(errors_cantloadkey));
588 if (type == SSH_KEYTYPE_SSH1)
592 if (type == SSH_KEYTYPE_SSH1) {
593 if (already_running) {
594 unsigned char *request, *response;
596 int reqlen, clen, resplen, ret;
598 clen = strlen(rkey->comment);
600 reqlen = 4 + 1 + /* length, message type */
602 ssh1_bignum_length(rkey->modulus) +
603 ssh1_bignum_length(rkey->exponent) +
604 ssh1_bignum_length(rkey->private_exponent) +
605 ssh1_bignum_length(rkey->iqmp) +
606 ssh1_bignum_length(rkey->p) +
607 ssh1_bignum_length(rkey->q) + 4 + clen /* comment */
610 request = snewn(reqlen, unsigned char);
612 request[4] = SSH1_AGENTC_ADD_RSA_IDENTITY;
614 PUT_32BIT(request + reqlen, bignum_bitcount(rkey->modulus));
616 reqlen += ssh1_write_bignum(request + reqlen, rkey->modulus);
617 reqlen += ssh1_write_bignum(request + reqlen, rkey->exponent);
619 ssh1_write_bignum(request + reqlen,
620 rkey->private_exponent);
621 reqlen += ssh1_write_bignum(request + reqlen, rkey->iqmp);
622 reqlen += ssh1_write_bignum(request + reqlen, rkey->p);
623 reqlen += ssh1_write_bignum(request + reqlen, rkey->q);
624 PUT_32BIT(request + reqlen, clen);
625 memcpy(request + reqlen + 4, rkey->comment, clen);
627 PUT_32BIT(request, reqlen - 4);
629 ret = agent_query(request, reqlen, &vresponse, &resplen,
632 response = vresponse;
633 if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
634 MessageBox(NULL, "The already running Pageant "
635 "refused to add the key.", APPNAME,
636 MB_OK | MB_ICONERROR);
641 if (add234(rsakeys, rkey) != rkey)
642 sfree(rkey); /* already present, don't waste RAM */
645 if (already_running) {
646 unsigned char *request, *response;
648 int reqlen, alglen, clen, keybloblen, resplen, ret;
649 alglen = strlen(skey->alg->name);
650 clen = strlen(skey->comment);
652 keybloblen = skey->alg->openssh_fmtkey(skey->data, NULL, 0);
654 reqlen = 4 + 1 + /* length, message type */
655 4 + alglen + /* algorithm name */
656 keybloblen + /* key data */
657 4 + clen /* comment */
660 request = snewn(reqlen, unsigned char);
662 request[4] = SSH2_AGENTC_ADD_IDENTITY;
664 PUT_32BIT(request + reqlen, alglen);
666 memcpy(request + reqlen, skey->alg->name, alglen);
668 reqlen += skey->alg->openssh_fmtkey(skey->data,
671 PUT_32BIT(request + reqlen, clen);
672 memcpy(request + reqlen + 4, skey->comment, clen);
674 PUT_32BIT(request, reqlen - 4);
676 ret = agent_query(request, reqlen, &vresponse, &resplen,
679 response = vresponse;
680 if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
681 MessageBox(NULL, "The already running Pageant "
682 "refused to add the key.", APPNAME,
683 MB_OK | MB_ICONERROR);
688 if (add234(ssh2keys, skey) != skey) {
689 skey->alg->freekey(skey->data);
690 sfree(skey); /* already present, don't waste RAM */
697 * Create an SSH-1 key list in a malloc'ed buffer; return its
700 static void *make_keylist1(int *length)
704 unsigned char *blob, *p, *ret;
708 * Count up the number and length of keys we hold.
712 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
714 blob = rsa_public_blob(key, &bloblen);
717 len += 4 + strlen(key->comment);
720 /* Allocate the buffer. */
721 p = ret = snewn(len, unsigned char);
722 if (length) *length = len;
726 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
727 blob = rsa_public_blob(key, &bloblen);
728 memcpy(p, blob, bloblen);
731 PUT_32BIT(p, strlen(key->comment));
732 memcpy(p + 4, key->comment, strlen(key->comment));
733 p += 4 + strlen(key->comment);
736 assert(p - ret == len);
741 * Create an SSH-2 key list in a malloc'ed buffer; return its
744 static void *make_keylist2(int *length)
746 struct ssh2_userkey *key;
748 unsigned char *blob, *p, *ret;
752 * Count up the number and length of keys we hold.
756 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
758 len += 4; /* length field */
759 blob = key->alg->public_blob(key->data, &bloblen);
762 len += 4 + strlen(key->comment);
765 /* Allocate the buffer. */
766 p = ret = snewn(len, unsigned char);
767 if (length) *length = len;
770 * Packet header is the obvious five bytes, plus four
771 * bytes for the key count.
775 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
776 blob = key->alg->public_blob(key->data, &bloblen);
777 PUT_32BIT(p, bloblen);
779 memcpy(p, blob, bloblen);
782 PUT_32BIT(p, strlen(key->comment));
783 memcpy(p + 4, key->comment, strlen(key->comment));
784 p += 4 + strlen(key->comment);
787 assert(p - ret == len);
792 * Acquire a keylist1 from the primary Pageant; this means either
793 * calling make_keylist1 (if that's us) or sending a message to the
794 * primary Pageant (if it's not).
796 static void *get_keylist1(int *length)
800 if (already_running) {
801 unsigned char request[5], *response;
804 request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
805 PUT_32BIT(request, 4);
807 retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
809 response = vresponse;
810 if (resplen < 5 || response[4] != SSH1_AGENT_RSA_IDENTITIES_ANSWER)
813 ret = snewn(resplen-5, unsigned char);
814 memcpy(ret, response+5, resplen-5);
820 ret = make_keylist1(length);
826 * Acquire a keylist2 from the primary Pageant; this means either
827 * calling make_keylist2 (if that's us) or sending a message to the
828 * primary Pageant (if it's not).
830 static void *get_keylist2(int *length)
834 if (already_running) {
835 unsigned char request[5], *response;
839 request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
840 PUT_32BIT(request, 4);
842 retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
844 response = vresponse;
845 if (resplen < 5 || response[4] != SSH2_AGENT_IDENTITIES_ANSWER)
848 ret = snewn(resplen-5, unsigned char);
849 memcpy(ret, response+5, resplen-5);
855 ret = make_keylist2(length);
861 * This is the main agent function that answers messages.
863 static void answer_msg(void *msg)
865 unsigned char *p = msg;
866 unsigned char *ret = msg;
867 unsigned char *msgend;
871 * Get the message length.
873 msgend = p + 4 + GET_32BIT(p);
876 * Get the message type.
884 case SSH1_AGENTC_REQUEST_RSA_IDENTITIES:
886 * Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER.
892 ret[4] = SSH1_AGENT_RSA_IDENTITIES_ANSWER;
893 keylist = make_keylist1(&len);
894 if (len + 5 > AGENT_MAX_MSGLEN) {
898 PUT_32BIT(ret, len + 1);
899 memcpy(ret + 5, keylist, len);
903 case SSH2_AGENTC_REQUEST_IDENTITIES:
905 * Reply with SSH2_AGENT_IDENTITIES_ANSWER.
911 ret[4] = SSH2_AGENT_IDENTITIES_ANSWER;
912 keylist = make_keylist2(&len);
913 if (len + 5 > AGENT_MAX_MSGLEN) {
917 PUT_32BIT(ret, len + 1);
918 memcpy(ret + 5, keylist, len);
922 case SSH1_AGENTC_RSA_CHALLENGE:
924 * Reply with either SSH1_AGENT_RSA_RESPONSE or
925 * SSH_AGENT_FAILURE, depending on whether we have that key
929 struct RSAKey reqkey, *key;
930 Bignum challenge, response;
931 unsigned char response_source[48], response_md5[16];
932 struct MD5Context md5c;
936 i = ssh1_read_bignum(p, msgend - p, &reqkey.exponent);
940 i = ssh1_read_bignum(p, msgend - p, &reqkey.modulus);
944 i = ssh1_read_bignum(p, msgend - p, &challenge);
949 freebn(reqkey.exponent);
950 freebn(reqkey.modulus);
954 memcpy(response_source + 32, p, 16);
958 (key = find234(rsakeys, &reqkey, NULL)) == NULL) {
959 freebn(reqkey.exponent);
960 freebn(reqkey.modulus);
964 response = rsadecrypt(challenge, key);
965 for (i = 0; i < 32; i++)
966 response_source[i] = bignum_byte(response, 31 - i);
969 MD5Update(&md5c, response_source, 48);
970 MD5Final(response_md5, &md5c);
971 smemclr(response_source, 48); /* burn the evidence */
972 freebn(response); /* and that evidence */
973 freebn(challenge); /* yes, and that evidence */
974 freebn(reqkey.exponent); /* and free some memory ... */
975 freebn(reqkey.modulus); /* ... while we're at it. */
978 * Packet is the obvious five byte header, plus sixteen
982 PUT_32BIT(ret, len - 4);
983 ret[4] = SSH1_AGENT_RSA_RESPONSE;
984 memcpy(ret + 5, response_md5, 16);
987 case SSH2_AGENTC_SIGN_REQUEST:
989 * Reply with either SSH2_AGENT_SIGN_RESPONSE or
990 * SSH_AGENT_FAILURE, depending on whether we have that key
994 struct ssh2_userkey *key;
996 unsigned char *data, *signature;
997 int datalen, siglen, len;
1001 b.len = GET_32BIT(p);
1003 if (msgend < p+b.len)
1009 datalen = GET_32BIT(p);
1011 if (msgend < p+datalen)
1014 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
1017 signature = key->alg->sign(key->data, data, datalen, &siglen);
1018 len = 5 + 4 + siglen;
1019 PUT_32BIT(ret, len - 4);
1020 ret[4] = SSH2_AGENT_SIGN_RESPONSE;
1021 PUT_32BIT(ret + 5, siglen);
1022 memcpy(ret + 5 + 4, signature, siglen);
1026 case SSH1_AGENTC_ADD_RSA_IDENTITY:
1028 * Add to the list and return SSH_AGENT_SUCCESS, or
1029 * SSH_AGENT_FAILURE if the key was malformed.
1036 key = snew(struct RSAKey);
1037 memset(key, 0, sizeof(struct RSAKey));
1039 n = makekey(p, msgend - p, key, NULL, 1);
1047 n = makeprivate(p, msgend - p, key);
1055 n = ssh1_read_bignum(p, msgend - p, &key->iqmp); /* p^-1 mod q */
1063 n = ssh1_read_bignum(p, msgend - p, &key->p); /* p */
1071 n = ssh1_read_bignum(p, msgend - p, &key->q); /* q */
1084 commentlen = GET_32BIT(p);
1086 if (msgend < p+commentlen) {
1092 comment = snewn(commentlen+1, char);
1094 memcpy(comment, p + 4, commentlen);
1095 comment[commentlen] = '\0';
1096 key->comment = comment;
1099 ret[4] = SSH_AGENT_FAILURE;
1100 if (add234(rsakeys, key) == key) {
1102 ret[4] = SSH_AGENT_SUCCESS;
1109 case SSH2_AGENTC_ADD_IDENTITY:
1111 * Add to the list and return SSH_AGENT_SUCCESS, or
1112 * SSH_AGENT_FAILURE if the key was malformed.
1115 struct ssh2_userkey *key;
1116 char *comment, *alg;
1117 int alglen, commlen;
1123 alglen = GET_32BIT(p);
1125 if (msgend < p+alglen)
1130 key = snew(struct ssh2_userkey);
1131 /* Add further algorithm names here. */
1132 if (alglen == 7 && !memcmp(alg, "ssh-rsa", 7))
1133 key->alg = &ssh_rsa;
1134 else if (alglen == 7 && !memcmp(alg, "ssh-dss", 7))
1135 key->alg = &ssh_dss;
1141 bloblen = msgend - p;
1142 key->data = key->alg->openssh_createkey(&p, &bloblen);
1149 * p has been advanced by openssh_createkey, but
1150 * certainly not _beyond_ the end of the buffer.
1152 assert(p <= msgend);
1155 key->alg->freekey(key->data);
1159 commlen = GET_32BIT(p);
1162 if (msgend < p+commlen) {
1163 key->alg->freekey(key->data);
1167 comment = snewn(commlen + 1, char);
1169 memcpy(comment, p, commlen);
1170 comment[commlen] = '\0';
1172 key->comment = comment;
1175 ret[4] = SSH_AGENT_FAILURE;
1176 if (add234(ssh2keys, key) == key) {
1178 ret[4] = SSH_AGENT_SUCCESS;
1180 key->alg->freekey(key->data);
1181 sfree(key->comment);
1186 case SSH1_AGENTC_REMOVE_RSA_IDENTITY:
1188 * Remove from the list and return SSH_AGENT_SUCCESS, or
1189 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
1193 struct RSAKey reqkey, *key;
1196 n = makekey(p, msgend - p, &reqkey, NULL, 0);
1200 key = find234(rsakeys, &reqkey, NULL);
1201 freebn(reqkey.exponent);
1202 freebn(reqkey.modulus);
1204 ret[4] = SSH_AGENT_FAILURE;
1206 del234(rsakeys, key);
1210 ret[4] = SSH_AGENT_SUCCESS;
1214 case SSH2_AGENTC_REMOVE_IDENTITY:
1216 * Remove from the list and return SSH_AGENT_SUCCESS, or
1217 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
1221 struct ssh2_userkey *key;
1226 b.len = GET_32BIT(p);
1229 if (msgend < p+b.len)
1234 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
1239 ret[4] = SSH_AGENT_FAILURE;
1241 del234(ssh2keys, key);
1243 key->alg->freekey(key->data);
1245 ret[4] = SSH_AGENT_SUCCESS;
1249 case SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES:
1251 * Remove all SSH-1 keys. Always returns success.
1254 struct RSAKey *rkey;
1256 while ((rkey = index234(rsakeys, 0)) != NULL) {
1257 del234(rsakeys, rkey);
1264 ret[4] = SSH_AGENT_SUCCESS;
1267 case SSH2_AGENTC_REMOVE_ALL_IDENTITIES:
1269 * Remove all SSH-2 keys. Always returns success.
1272 struct ssh2_userkey *skey;
1274 while ((skey = index234(ssh2keys, 0)) != NULL) {
1275 del234(ssh2keys, skey);
1276 skey->alg->freekey(skey->data);
1282 ret[4] = SSH_AGENT_SUCCESS;
1288 * Unrecognised message. Return SSH_AGENT_FAILURE.
1291 ret[4] = SSH_AGENT_FAILURE;
1297 * Key comparison function for the 2-3-4 tree of RSA keys.
1299 static int cmpkeys_rsa(void *av, void *bv)
1301 struct RSAKey *a = (struct RSAKey *) av;
1302 struct RSAKey *b = (struct RSAKey *) bv;
1309 * Compare by length of moduli.
1311 alen = bignum_bitcount(am);
1312 blen = bignum_bitcount(bm);
1315 else if (alen < blen)
1318 * Now compare by moduli themselves.
1320 alen = (alen + 7) / 8; /* byte count */
1321 while (alen-- > 0) {
1323 abyte = bignum_byte(am, alen);
1324 bbyte = bignum_byte(bm, alen);
1327 else if (abyte < bbyte)
1337 * Key comparison function for the 2-3-4 tree of SSH-2 keys.
1339 static int cmpkeys_ssh2(void *av, void *bv)
1341 struct ssh2_userkey *a = (struct ssh2_userkey *) av;
1342 struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
1345 unsigned char *ablob, *bblob;
1349 * Compare purely by public blob.
1351 ablob = a->alg->public_blob(a->data, &alen);
1352 bblob = b->alg->public_blob(b->data, &blen);
1355 for (i = 0; i < alen && i < blen; i++) {
1356 if (ablob[i] < bblob[i]) {
1359 } else if (ablob[i] > bblob[i]) {
1364 if (c == 0 && i < alen)
1365 c = +1; /* a is longer */
1366 if (c == 0 && i < blen)
1367 c = -1; /* a is longer */
1376 * Key comparison function for looking up a blob in the 2-3-4 tree
1379 static int cmpkeys_ssh2_asymm(void *av, void *bv)
1381 struct blob *a = (struct blob *) av;
1382 struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
1385 unsigned char *ablob, *bblob;
1389 * Compare purely by public blob.
1393 bblob = b->alg->public_blob(b->data, &blen);
1396 for (i = 0; i < alen && i < blen; i++) {
1397 if (ablob[i] < bblob[i]) {
1400 } else if (ablob[i] > bblob[i]) {
1405 if (c == 0 && i < alen)
1406 c = +1; /* a is longer */
1407 if (c == 0 && i < blen)
1408 c = -1; /* a is longer */
1416 * Prompt for a key file to add, and add it.
1418 static void prompt_add_keyfile(void)
1421 char *filelist = snewn(8192, char);
1423 if (!keypath) keypath = filereq_new();
1424 memset(&of, 0, sizeof(of));
1425 of.hwndOwner = hwnd;
1426 of.lpstrFilter = FILTER_KEY_FILES;
1427 of.lpstrCustomFilter = NULL;
1428 of.nFilterIndex = 1;
1429 of.lpstrFile = filelist;
1432 of.lpstrFileTitle = NULL;
1433 of.lpstrTitle = "Select Private Key File";
1434 of.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER;
1435 if (request_file(keypath, &of, TRUE, FALSE)) {
1436 if(strlen(filelist) > of.nFileOffset)
1437 /* Only one filename returned? */
1438 add_keyfile(filename_from_str(filelist));
1440 /* we are returned a bunch of strings, end to
1441 * end. first string is the directory, the
1442 * rest the filenames. terminated with an
1445 char *dir = filelist;
1446 char *filewalker = filelist + strlen(dir) + 1;
1447 while (*filewalker != '\0') {
1448 char *filename = dupcat(dir, "\\", filewalker, NULL);
1449 add_keyfile(filename_from_str(filename));
1451 filewalker += strlen(filewalker) + 1;
1456 forget_passphrases();
1462 * Dialog-box function for the key list box.
1464 static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
1465 WPARAM wParam, LPARAM lParam)
1467 struct RSAKey *rkey;
1468 struct ssh2_userkey *skey;
1473 * Centre the window.
1475 { /* centre the window */
1479 hw = GetDesktopWindow();
1480 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
1482 (rs.right + rs.left + rd.left - rd.right) / 2,
1483 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
1484 rd.right - rd.left, rd.bottom - rd.top, TRUE);
1488 SetWindowLongPtr(hwnd, GWL_EXSTYLE,
1489 GetWindowLongPtr(hwnd, GWL_EXSTYLE) |
1492 HWND item = GetDlgItem(hwnd, 103); /* the Help button */
1494 DestroyWindow(item);
1499 static int tabs[] = { 35, 60, 210 };
1500 SendDlgItemMessage(hwnd, 100, LB_SETTABSTOPS,
1501 sizeof(tabs) / sizeof(*tabs),
1507 switch (LOWORD(wParam)) {
1511 DestroyWindow(hwnd);
1513 case 101: /* add key */
1514 if (HIWORD(wParam) == BN_CLICKED ||
1515 HIWORD(wParam) == BN_DOUBLECLICKED) {
1516 if (passphrase_box) {
1517 MessageBeep(MB_ICONERROR);
1518 SetForegroundWindow(passphrase_box);
1521 prompt_add_keyfile();
1524 case 102: /* remove key */
1525 if (HIWORD(wParam) == BN_CLICKED ||
1526 HIWORD(wParam) == BN_DOUBLECLICKED) {
1531 /* our counter within the array of selected items */
1534 /* get the number of items selected in the list */
1536 SendDlgItemMessage(hwnd, 100, LB_GETSELCOUNT, 0, 0);
1538 /* none selected? that was silly */
1539 if (numSelected == 0) {
1544 /* get item indices in an array */
1545 selectedArray = snewn(numSelected, int);
1546 SendDlgItemMessage(hwnd, 100, LB_GETSELITEMS,
1547 numSelected, (WPARAM)selectedArray);
1549 itemNum = numSelected - 1;
1550 rCount = count234(rsakeys);
1551 sCount = count234(ssh2keys);
1553 /* go through the non-rsakeys until we've covered them all,
1554 * and/or we're out of selected items to check. note that
1555 * we go *backwards*, to avoid complications from deleting
1556 * things hence altering the offset of subsequent items
1558 for (i = sCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1559 skey = index234(ssh2keys, i);
1561 if (selectedArray[itemNum] == rCount + i) {
1562 del234(ssh2keys, skey);
1563 skey->alg->freekey(skey->data);
1569 /* do the same for the rsa keys */
1570 for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1571 rkey = index234(rsakeys, i);
1573 if(selectedArray[itemNum] == i) {
1574 del234(rsakeys, rkey);
1581 sfree(selectedArray);
1585 case 103: /* help */
1586 if (HIWORD(wParam) == BN_CLICKED ||
1587 HIWORD(wParam) == BN_DOUBLECLICKED) {
1588 launch_help(hwnd, WINHELP_CTX_pageant_general);
1595 int id = ((LPHELPINFO)lParam)->iCtrlId;
1598 case 100: topic = WINHELP_CTX_pageant_keylist; break;
1599 case 101: topic = WINHELP_CTX_pageant_addkey; break;
1600 case 102: topic = WINHELP_CTX_pageant_remkey; break;
1603 launch_help(hwnd, topic);
1611 DestroyWindow(hwnd);
1617 /* Set up a system tray icon */
1618 static BOOL AddTrayIcon(HWND hwnd)
1621 NOTIFYICONDATA tnid;
1624 #ifdef NIM_SETVERSION
1626 res = Shell_NotifyIcon(NIM_SETVERSION, &tnid);
1629 tnid.cbSize = sizeof(NOTIFYICONDATA);
1631 tnid.uID = 1; /* unique within this systray use */
1632 tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
1633 tnid.uCallbackMessage = WM_SYSTRAY;
1634 tnid.hIcon = hicon = LoadIcon(hinst, MAKEINTRESOURCE(201));
1635 strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
1637 res = Shell_NotifyIcon(NIM_ADD, &tnid);
1639 if (hicon) DestroyIcon(hicon);
1644 /* Update the saved-sessions menu. */
1645 static void update_sessions(void)
1649 TCHAR buf[MAX_PATH + 1];
1652 int index_key, index_menu;
1657 if(ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, PUTTY_REGKEY, &hkey))
1660 for(num_entries = GetMenuItemCount(session_menu);
1661 num_entries > initial_menuitems_count;
1663 RemoveMenu(session_menu, 0, MF_BYPOSITION);
1668 while(ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) {
1669 TCHAR session_name[MAX_PATH + 1];
1670 unmungestr(buf, session_name, MAX_PATH);
1671 if(strcmp(buf, PUTTY_DEFAULT) != 0) {
1672 memset(&mii, 0, sizeof(mii));
1673 mii.cbSize = sizeof(mii);
1674 mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
1675 mii.fType = MFT_STRING;
1676 mii.fState = MFS_ENABLED;
1677 mii.wID = (index_menu * 16) + IDM_SESSIONS_BASE;
1678 mii.dwTypeData = session_name;
1679 InsertMenuItem(session_menu, index_menu, TRUE, &mii);
1687 if(index_menu == 0) {
1688 mii.cbSize = sizeof(mii);
1689 mii.fMask = MIIM_TYPE | MIIM_STATE;
1690 mii.fType = MFT_STRING;
1691 mii.fState = MFS_GRAYED;
1692 mii.dwTypeData = _T("(No sessions)");
1693 InsertMenuItem(session_menu, index_menu, TRUE, &mii);
1699 * Versions of Pageant prior to 0.61 expected this SID on incoming
1700 * communications. For backwards compatibility, and more particularly
1701 * for compatibility with derived works of PuTTY still using the old
1702 * Pageant client code, we accept it as an alternative to the one
1703 * returned from get_user_sid() in winpgntc.c.
1705 PSID get_default_sid(void)
1709 PSECURITY_DESCRIPTOR psd = NULL;
1710 PSID sid = NULL, copy = NULL, ret = NULL;
1712 if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE,
1713 GetCurrentProcessId())) == NULL)
1716 if (p_GetSecurityInfo(proc, SE_KERNEL_OBJECT, OWNER_SECURITY_INFORMATION,
1717 &sid, NULL, NULL, NULL, &psd) != ERROR_SUCCESS)
1720 sidlen = GetLengthSid(sid);
1722 copy = (PSID)smalloc(sidlen);
1724 if (!CopySid(sidlen, copy, sid))
1727 /* Success. Move sid into the return value slot, and null it out
1728 * to stop the cleanup code freeing it. */
1744 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1745 WPARAM wParam, LPARAM lParam)
1748 static int menuinprogress;
1749 static UINT msgTaskbarCreated = 0;
1753 msgTaskbarCreated = RegisterWindowMessage(_T("TaskbarCreated"));
1756 if (message==msgTaskbarCreated) {
1758 * Explorer has been restarted, so the tray icon will
1766 if (lParam == WM_RBUTTONUP) {
1768 GetCursorPos(&cursorpos);
1769 PostMessage(hwnd, WM_SYSTRAY2, cursorpos.x, cursorpos.y);
1770 } else if (lParam == WM_LBUTTONDBLCLK) {
1771 /* Run the default menu item. */
1772 UINT menuitem = GetMenuDefaultItem(systray_menu, FALSE, 0);
1774 PostMessage(hwnd, WM_COMMAND, menuitem, 0);
1778 if (!menuinprogress) {
1781 SetForegroundWindow(hwnd);
1782 ret = TrackPopupMenu(systray_menu,
1783 TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
1785 wParam, lParam, 0, hwnd, NULL);
1791 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1793 if((int)ShellExecute(hwnd, NULL, putty_path, _T(""), _T(""),
1795 MessageBox(NULL, "Unable to execute PuTTY!",
1796 "Error", MB_OK | MB_ICONERROR);
1801 SendMessage(passphrase_box, WM_CLOSE, 0, 0);
1802 SendMessage(hwnd, WM_CLOSE, 0, 0);
1806 keylist = CreateDialog(hinst, MAKEINTRESOURCE(211),
1808 ShowWindow(keylist, SW_SHOWNORMAL);
1811 * Sometimes the window comes up minimised / hidden for
1812 * no obvious reason. Prevent this. This also brings it
1813 * to the front if it's already present (the user
1814 * selected View Keys because they wanted to _see_ the
1817 SetForegroundWindow(keylist);
1818 SetWindowPos(keylist, HWND_TOP, 0, 0, 0, 0,
1819 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1822 if (passphrase_box) {
1823 MessageBeep(MB_ICONERROR);
1824 SetForegroundWindow(passphrase_box);
1827 prompt_add_keyfile();
1831 aboutbox = CreateDialog(hinst, MAKEINTRESOURCE(213),
1833 ShowWindow(aboutbox, SW_SHOWNORMAL);
1835 * Sometimes the window comes up minimised / hidden
1836 * for no obvious reason. Prevent this.
1838 SetForegroundWindow(aboutbox);
1839 SetWindowPos(aboutbox, HWND_TOP, 0, 0, 0, 0,
1840 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1844 launch_help(hwnd, WINHELP_CTX_pageant_general);
1848 if(wParam >= IDM_SESSIONS_BASE && wParam <= IDM_SESSIONS_MAX) {
1850 TCHAR buf[MAX_PATH + 1];
1851 TCHAR param[MAX_PATH + 1];
1852 memset(&mii, 0, sizeof(mii));
1853 mii.cbSize = sizeof(mii);
1854 mii.fMask = MIIM_TYPE;
1856 mii.dwTypeData = buf;
1857 GetMenuItemInfo(session_menu, wParam, FALSE, &mii);
1859 strcat(param, mii.dwTypeData);
1860 if((int)ShellExecute(hwnd, NULL, putty_path, param,
1861 _T(""), SW_SHOW) <= 32) {
1862 MessageBox(NULL, "Unable to execute PuTTY!", "Error",
1863 MB_OK | MB_ICONERROR);
1876 COPYDATASTRUCT *cds;
1881 PSID mapowner, ourself, ourself2;
1883 PSECURITY_DESCRIPTOR psd = NULL;
1886 cds = (COPYDATASTRUCT *) lParam;
1887 if (cds->dwData != AGENT_COPYDATA_ID)
1888 return 0; /* not our message, mate */
1889 mapname = (char *) cds->lpData;
1890 if (mapname[cds->cbData - 1] != '\0')
1891 return 0; /* failure to be ASCIZ! */
1893 debug(("mapname is :%s:\n", mapname));
1895 filemap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, mapname);
1897 debug(("filemap is %p\n", filemap));
1899 if (filemap != NULL && filemap != INVALID_HANDLE_VALUE) {
1903 if ((ourself = get_user_sid()) == NULL) {
1905 debug(("couldn't get user SID\n"));
1910 if ((ourself2 = get_default_sid()) == NULL) {
1912 debug(("couldn't get default SID\n"));
1917 if ((rc = p_GetSecurityInfo(filemap, SE_KERNEL_OBJECT,
1918 OWNER_SECURITY_INFORMATION,
1919 &mapowner, NULL, NULL, NULL,
1920 &psd) != ERROR_SUCCESS)) {
1922 debug(("couldn't get owner info for filemap: %d\n",
1929 LPTSTR ours, ours2, theirs;
1930 ConvertSidToStringSid(mapowner, &theirs);
1931 ConvertSidToStringSid(ourself, &ours);
1932 ConvertSidToStringSid(ourself2, &ours2);
1933 debug(("got sids:\n oursnew=%s\n oursold=%s\n"
1934 " theirs=%s\n", ours, ours2, theirs));
1940 if (!EqualSid(mapowner, ourself) &&
1941 !EqualSid(mapowner, ourself2)) {
1942 CloseHandle(filemap);
1943 return 0; /* security ID mismatch! */
1946 debug(("security stuff matched\n"));
1953 debug(("security APIs not present\n"));
1957 p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
1959 debug(("p is %p\n", p));
1962 for (i = 0; i < 5; i++)
1963 debug(("p[%d]=%02x\n", i,
1964 ((unsigned char *) p)[i]));
1971 CloseHandle(filemap);
1976 return DefWindowProc(hwnd, message, wParam, lParam);
1980 * Fork and Exec the command in cmdline. [DBW]
1982 void spawn_cmd(char *cmdline, char * args, int show)
1984 if (ShellExecute(NULL, _T("open"), cmdline,
1985 args, NULL, show) <= (HINSTANCE) 32) {
1987 msg = dupprintf("Failed to run \"%.100s\", Error: %d", cmdline,
1988 (int)GetLastError());
1989 MessageBox(NULL, msg, APPNAME, MB_OK | MB_ICONEXCLAMATION);
1995 * This is a can't-happen stub, since Pageant never makes
1996 * asynchronous agent requests.
1998 void agent_schedule_callback(void (*callback)(void *, void *, int),
1999 void *callback_ctx, void *data, int len)
2001 assert(!"We shouldn't get here");
2004 void cleanup_exit(int code)
2010 int flags = FLAG_SYNCAGENT;
2012 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
2017 char *command = NULL;
2020 char **argv, **argstart;
2026 * Determine whether we're an NT system (should have security
2027 * APIs) or a non-NT system (don't do security).
2031 modalfatalbox("Windows refuses to report a version");
2033 if (osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT) {
2034 has_security = TRUE;
2036 has_security = FALSE;
2041 * Attempt to get the security API we need.
2043 if (!init_advapi()) {
2045 "Unable to access security APIs. Pageant will\n"
2046 "not run, in case it causes a security breach.",
2047 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
2052 "This program has been compiled for Win9X and will\n"
2053 "not run on NT, in case it causes a security breach.",
2054 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
2061 * See if we can find our Help file.
2066 * Look for the PuTTY binary (we will enable the saved session
2067 * submenu if we find it).
2070 char b[2048], *p, *q, *r;
2072 GetModuleFileName(NULL, b, sizeof(b) - 16);
2074 p = strrchr(b, '\\');
2075 if (p && p >= r) r = p+1;
2076 q = strrchr(b, ':');
2077 if (q && q >= r) r = q+1;
2078 strcpy(r, "putty.exe");
2079 if ( (fp = fopen(b, "r")) != NULL) {
2080 putty_path = dupstr(b);
2087 * Find out if Pageant is already running.
2089 already_running = agent_exists();
2092 * Initialise storage for RSA keys.
2094 if (!already_running) {
2095 rsakeys = newtree234(cmpkeys_rsa);
2096 ssh2keys = newtree234(cmpkeys_ssh2);
2100 * Initialise storage for short-term passphrase cache.
2102 passphrases = newtree234(NULL);
2105 * Process the command line and add keys as listed on it.
2107 split_into_argv(cmdline, &argc, &argv, &argstart);
2108 for (i = 0; i < argc; i++) {
2109 if (!strcmp(argv[i], "-pgpfp")) {
2112 FreeLibrary(advapi);
2114 } else if (!strcmp(argv[i], "-c")) {
2116 * If we see `-c', then the rest of the
2117 * command line should be treated as a
2118 * command to be spawned.
2121 command = argstart[i+1];
2126 add_keyfile(filename_from_str(argv[i]));
2132 * Forget any passphrase that we retained while going over
2133 * command line keyfiles.
2135 forget_passphrases();
2139 if (command[0] == '"')
2140 args = strchr(++command, '"');
2142 args = strchr(command, ' ');
2145 while(*args && isspace(*args)) args++;
2147 spawn_cmd(command, args, show);
2151 * If Pageant was already running, we leave now. If we haven't
2152 * even taken any auxiliary action (spawned a command or added
2155 if (already_running) {
2156 if (!command && !added_keys) {
2157 MessageBox(NULL, "Pageant is already running", "Pageant Error",
2158 MB_ICONERROR | MB_OK);
2161 FreeLibrary(advapi);
2167 wndclass.lpfnWndProc = WndProc;
2168 wndclass.cbClsExtra = 0;
2169 wndclass.cbWndExtra = 0;
2170 wndclass.hInstance = inst;
2171 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
2172 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
2173 wndclass.hbrBackground = GetStockObject(BLACK_BRUSH);
2174 wndclass.lpszMenuName = NULL;
2175 wndclass.lpszClassName = APPNAME;
2177 RegisterClass(&wndclass);
2182 hwnd = CreateWindow(APPNAME, APPNAME,
2183 WS_OVERLAPPEDWINDOW | WS_VSCROLL,
2184 CW_USEDEFAULT, CW_USEDEFAULT,
2185 100, 100, NULL, NULL, inst, NULL);
2187 /* Set up a system tray icon */
2190 /* Accelerators used: nsvkxa */
2191 systray_menu = CreatePopupMenu();
2193 session_menu = CreateMenu();
2194 AppendMenu(systray_menu, MF_ENABLED, IDM_PUTTY, "&New Session");
2195 AppendMenu(systray_menu, MF_POPUP | MF_ENABLED,
2196 (UINT) session_menu, "&Saved Sessions");
2197 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
2199 AppendMenu(systray_menu, MF_ENABLED, IDM_VIEWKEYS,
2201 AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key");
2202 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
2204 AppendMenu(systray_menu, MF_ENABLED, IDM_HELP, "&Help");
2205 AppendMenu(systray_menu, MF_ENABLED, IDM_ABOUT, "&About");
2206 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
2207 AppendMenu(systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit");
2208 initial_menuitems_count = GetMenuItemCount(session_menu);
2210 /* Set the default menu item. */
2211 SetMenuDefaultItem(systray_menu, IDM_VIEWKEYS, FALSE);
2213 ShowWindow(hwnd, SW_HIDE);
2216 * Main message loop.
2218 while (GetMessage(&msg, NULL, 0, 0) == 1) {
2219 if (!(IsWindow(keylist) && IsDialogMessage(keylist, &msg)) &&
2220 !(IsWindow(aboutbox) && IsDialogMessage(aboutbox, &msg))) {
2221 TranslateMessage(&msg);
2222 DispatchMessage(&msg);
2226 /* Clean up the system tray icon */
2228 NOTIFYICONDATA tnid;
2230 tnid.cbSize = sizeof(NOTIFYICONDATA);
2234 Shell_NotifyIcon(NIM_DELETE, &tnid);
2236 DestroyMenu(systray_menu);
2239 if (keypath) filereq_free(keypath);
2242 FreeLibrary(advapi);
2244 cleanup_exit(msg.wParam);
2245 return msg.wParam; /* just in case optimiser complains */