2 * Pageant: the PuTTY Authentication Agent.
11 #define PUTTY_DO_GLOBALS
24 #define _WIN32_WINNT 0x0500 /* for ConvertSidToStringSid */
29 #define IDI_MAINICON 200
30 #define IDI_TRAYICON 201
32 #define WM_SYSTRAY (WM_APP + 6)
33 #define WM_SYSTRAY2 (WM_APP + 7)
35 #define AGENT_COPYDATA_ID 0x804e50ba /* random goop */
38 * FIXME: maybe some day we can sort this out ...
40 #define AGENT_MAX_MSGLEN 8192
42 /* From MSDN: In the WM_SYSCOMMAND message, the four low-order bits of
43 * wParam are used by Windows, and should be masked off, so we shouldn't
44 * attempt to store information in them. Hence all these identifiers have
45 * the low 4 bits clear. Also, identifiers should < 0xF000. */
47 #define IDM_CLOSE 0x0010
48 #define IDM_VIEWKEYS 0x0020
49 #define IDM_ADDKEY 0x0030
50 #define IDM_HELP 0x0040
51 #define IDM_ABOUT 0x0050
53 #define APPNAME "Pageant"
59 static HMENU systray_menu, session_menu;
60 static int already_running;
62 static char *putty_path;
64 /* CWD for "add key" file requester. */
65 static filereq *keypath = NULL;
67 #define IDM_PUTTY 0x0060
68 #define IDM_SESSIONS_BASE 0x1000
69 #define IDM_SESSIONS_MAX 0x2000
70 #define PUTTY_REGKEY "Software\\SimonTatham\\PuTTY\\Sessions"
71 #define PUTTY_DEFAULT "Default%20Settings"
72 static int initial_menuitems_count;
75 * Print a modal (Really Bad) message box and perform a fatal exit.
77 void modalfatalbox(char *fmt, ...)
83 buf = dupvprintf(fmt, ap);
85 MessageBox(hwnd, buf, "Pageant Fatal Error",
86 MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
91 /* Un-munge session names out of the registry. */
92 static void unmungestr(char *in, char *out, int outlen)
95 if (*in == '%' && in[1] && in[2]) {
101 j -= (j > 9 ? 7 : 0);
103 *out++ = (i << 4) + j;
117 static tree234 *rsakeys, *ssh2keys;
119 static int has_security;
124 static void *make_keylist1(int *length);
125 static void *make_keylist2(int *length);
126 static void *get_keylist1(int *length);
127 static void *get_keylist2(int *length);
130 * We need this to link with the RSA code, because rsaencrypt()
131 * pads its data with random bytes. Since we only use rsadecrypt()
132 * and the signing functions, which are deterministic, this should
135 * If it _is_ called, there is a _serious_ problem, because it
136 * won't generate true random numbers. So we must scream, panic,
137 * and exit immediately if that should happen.
139 int random_byte(void)
141 MessageBox(hwnd, "Internal Error", APPNAME, MB_OK | MB_ICONERROR);
143 /* this line can't be reached but it placates MSVC's warnings :-) */
148 * Blob structure for passing to the asymmetric SSH-2 key compare
149 * function, prototyped here.
155 static int cmpkeys_ssh2_asymm(void *av, void *bv);
157 struct PassphraseProcStruct {
162 static tree234 *passphrases = NULL;
165 * After processing a list of filenames, we want to forget the
168 static void forget_passphrases(void)
170 while (count234(passphrases) > 0) {
171 char *pp = index234(passphrases, 0);
172 smemclr(pp, strlen(pp));
173 delpos234(passphrases, 0);
179 * Dialog-box function for the Licence box.
181 static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
182 WPARAM wParam, LPARAM lParam)
188 switch (LOWORD(wParam)) {
203 * Dialog-box function for the About box.
205 static int CALLBACK AboutProc(HWND hwnd, UINT msg,
206 WPARAM wParam, LPARAM lParam)
210 SetDlgItemText(hwnd, 100, ver);
213 switch (LOWORD(wParam)) {
220 EnableWindow(hwnd, 0);
221 DialogBox(hinst, MAKEINTRESOURCE(214), hwnd, LicenceProc);
222 EnableWindow(hwnd, 1);
223 SetActiveWindow(hwnd);
235 static HWND passphrase_box;
238 * Dialog-box function for the passphrase box.
240 static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
241 WPARAM wParam, LPARAM lParam)
243 static char **passphrase = NULL;
244 struct PassphraseProcStruct *p;
248 passphrase_box = hwnd;
252 { /* centre the window */
256 hw = GetDesktopWindow();
257 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
259 (rs.right + rs.left + rd.left - rd.right) / 2,
260 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
261 rd.right - rd.left, rd.bottom - rd.top, TRUE);
264 SetForegroundWindow(hwnd);
265 SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
266 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
267 p = (struct PassphraseProcStruct *) lParam;
268 passphrase = p->passphrase;
270 SetDlgItemText(hwnd, 101, p->comment);
271 burnstr(*passphrase);
272 *passphrase = dupstr("");
273 SetDlgItemText(hwnd, 102, *passphrase);
276 switch (LOWORD(wParam)) {
286 case 102: /* edit box */
287 if ((HIWORD(wParam) == EN_CHANGE) && passphrase) {
288 burnstr(*passphrase);
289 *passphrase = GetDlgItemText_alloc(hwnd, 102);
302 * Warn about the obsolescent key file format.
304 void old_keyfile_warning(void)
306 static const char mbtitle[] = "PuTTY Key File Warning";
307 static const char message[] =
308 "You are loading an SSH-2 private key which has an\n"
309 "old version of the file format. This means your key\n"
310 "file is not fully tamperproof. Future versions of\n"
311 "PuTTY may stop supporting this private key format,\n"
312 "so we recommend you convert your key to the new\n"
315 "You can perform this conversion by loading the key\n"
316 "into PuTTYgen and then saving it again.";
318 MessageBox(NULL, message, mbtitle, MB_OK);
322 * Update the visible key list.
324 static void keylist_update(void)
327 struct ssh2_userkey *skey;
331 SendDlgItemMessage(keylist, 100, LB_RESETCONTENT, 0, 0);
332 for (i = 0; NULL != (rkey = index234(rsakeys, i)); i++) {
333 char listentry[512], *p;
335 * Replace two spaces in the fingerprint with tabs, for
336 * nice alignment in the box.
338 strcpy(listentry, "ssh1\t");
339 p = listentry + strlen(listentry);
340 rsa_fingerprint(p, sizeof(listentry) - (p - listentry), rkey);
341 p = strchr(listentry, ' ');
344 p = strchr(listentry, ' ');
347 SendDlgItemMessage(keylist, 100, LB_ADDSTRING,
348 0, (LPARAM) listentry);
350 for (i = 0; NULL != (skey = index234(ssh2keys, i)); i++) {
354 * Replace two spaces in the fingerprint with tabs, for
355 * nice alignment in the box.
357 p = skey->alg->fingerprint(skey->data);
358 listentry = dupprintf("%s\t%s", p, skey->comment);
359 fp_len = strlen(listentry);
362 p = strchr(listentry, ' ');
363 if (p && p < listentry + fp_len)
365 p = strchr(listentry, ' ');
366 if (p && p < listentry + fp_len)
369 SendDlgItemMessage(keylist, 100, LB_ADDSTRING, 0,
373 SendDlgItemMessage(keylist, 100, LB_SETCURSEL, (WPARAM) - 1, 0);
378 * This function loads a key from a file and adds it.
380 static void add_keyfile(Filename *filename)
383 struct RSAKey *rkey = NULL;
384 struct ssh2_userkey *skey = NULL;
389 const char *error = NULL;
393 type = key_type(filename);
394 if (type != SSH_KEYTYPE_SSH1 && type != SSH_KEYTYPE_SSH2) {
395 char *msg = dupprintf("Couldn't load this key (%s)",
396 key_type_to_str(type));
397 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
398 HELPCTXID(errors_cantloadkey));
404 * See if the key is already loaded (in the primary Pageant,
405 * which may or may not be us).
409 unsigned char *keylist, *p;
410 int i, nkeys, bloblen, keylistlen;
412 if (type == SSH_KEYTYPE_SSH1) {
413 if (!rsakey_pubblob(filename, &blob, &bloblen, NULL, &error)) {
414 char *msg = dupprintf("Couldn't load private key (%s)", error);
415 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
416 HELPCTXID(errors_cantloadkey));
420 keylist = get_keylist1(&keylistlen);
422 unsigned char *blob2;
423 blob = ssh2_userkey_loadpub(filename, NULL, &bloblen,
426 char *msg = dupprintf("Couldn't load private key (%s)", error);
427 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
428 HELPCTXID(errors_cantloadkey));
432 /* For our purposes we want the blob prefixed with its length */
433 blob2 = snewn(bloblen+4, unsigned char);
434 PUT_32BIT(blob2, bloblen);
435 memcpy(blob2 + 4, blob, bloblen);
439 keylist = get_keylist2(&keylistlen);
442 if (keylistlen < 4) {
443 MessageBox(NULL, "Received broken key list?!", APPNAME,
444 MB_OK | MB_ICONERROR);
447 nkeys = toint(GET_32BIT(keylist));
449 MessageBox(NULL, "Received broken key list?!", APPNAME,
450 MB_OK | MB_ICONERROR);
456 for (i = 0; i < nkeys; i++) {
457 if (!memcmp(blob, p, bloblen)) {
458 /* Key is already present; we can now leave. */
463 /* Now skip over public blob */
464 if (type == SSH_KEYTYPE_SSH1) {
465 int n = rsa_public_blob_len(p, keylistlen);
467 MessageBox(NULL, "Received broken key list?!", APPNAME,
468 MB_OK | MB_ICONERROR);
475 if (keylistlen < 4) {
476 MessageBox(NULL, "Received broken key list?!", APPNAME,
477 MB_OK | MB_ICONERROR);
480 n = toint(4 + GET_32BIT(p));
481 if (n < 0 || keylistlen < n) {
482 MessageBox(NULL, "Received broken key list?!", APPNAME,
483 MB_OK | MB_ICONERROR);
489 /* Now skip over comment field */
492 if (keylistlen < 4) {
493 MessageBox(NULL, "Received broken key list?!", APPNAME,
494 MB_OK | MB_ICONERROR);
497 n = toint(4 + GET_32BIT(p));
498 if (n < 0 || keylistlen < n) {
499 MessageBox(NULL, "Received broken key list?!", APPNAME,
500 MB_OK | MB_ICONERROR);
515 if (type == SSH_KEYTYPE_SSH1)
516 needs_pass = rsakey_encrypted(filename, &comment);
518 needs_pass = ssh2_userkey_encrypted(filename, &comment);
520 if (type == SSH_KEYTYPE_SSH1)
521 rkey = snew(struct RSAKey);
529 /* try all the remembered passphrases first */
530 char *pp = index234(passphrases, attempts);
532 passphrase = dupstr(pp);
535 struct PassphraseProcStruct pps;
537 pps.passphrase = &passphrase;
538 pps.comment = comment;
541 dlgret = DialogBoxParam(hinst, MAKEINTRESOURCE(210),
542 NULL, PassphraseProc, (LPARAM) &pps);
543 passphrase_box = NULL;
547 if (type == SSH_KEYTYPE_SSH1)
549 return; /* operation cancelled */
552 assert(passphrase != NULL);
555 passphrase = dupstr("");
557 if (type == SSH_KEYTYPE_SSH1)
558 ret = loadrsakey(filename, rkey, passphrase, &error);
560 skey = ssh2_load_userkey(filename, passphrase, &error);
561 if (skey == SSH2_WRONG_PASSPHRASE)
571 if(original_pass && ret) {
572 /* If they typed in an ok passphrase, remember it */
573 addpos234(passphrases, passphrase, 0);
575 /* Otherwise, destroy it */
583 char *msg = dupprintf("Couldn't load private key (%s)", error);
584 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
585 HELPCTXID(errors_cantloadkey));
587 if (type == SSH_KEYTYPE_SSH1)
591 if (type == SSH_KEYTYPE_SSH1) {
592 if (already_running) {
593 unsigned char *request, *response;
595 int reqlen, clen, resplen, ret;
597 clen = strlen(rkey->comment);
599 reqlen = 4 + 1 + /* length, message type */
601 ssh1_bignum_length(rkey->modulus) +
602 ssh1_bignum_length(rkey->exponent) +
603 ssh1_bignum_length(rkey->private_exponent) +
604 ssh1_bignum_length(rkey->iqmp) +
605 ssh1_bignum_length(rkey->p) +
606 ssh1_bignum_length(rkey->q) + 4 + clen /* comment */
609 request = snewn(reqlen, unsigned char);
611 request[4] = SSH1_AGENTC_ADD_RSA_IDENTITY;
613 PUT_32BIT(request + reqlen, bignum_bitcount(rkey->modulus));
615 reqlen += ssh1_write_bignum(request + reqlen, rkey->modulus);
616 reqlen += ssh1_write_bignum(request + reqlen, rkey->exponent);
618 ssh1_write_bignum(request + reqlen,
619 rkey->private_exponent);
620 reqlen += ssh1_write_bignum(request + reqlen, rkey->iqmp);
621 reqlen += ssh1_write_bignum(request + reqlen, rkey->p);
622 reqlen += ssh1_write_bignum(request + reqlen, rkey->q);
623 PUT_32BIT(request + reqlen, clen);
624 memcpy(request + reqlen + 4, rkey->comment, clen);
626 PUT_32BIT(request, reqlen - 4);
628 ret = agent_query(request, reqlen, &vresponse, &resplen,
631 response = vresponse;
632 if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
633 MessageBox(NULL, "The already running Pageant "
634 "refused to add the key.", APPNAME,
635 MB_OK | MB_ICONERROR);
640 if (add234(rsakeys, rkey) != rkey)
641 sfree(rkey); /* already present, don't waste RAM */
644 if (already_running) {
645 unsigned char *request, *response;
647 int reqlen, alglen, clen, keybloblen, resplen, ret;
648 alglen = strlen(skey->alg->name);
649 clen = strlen(skey->comment);
651 keybloblen = skey->alg->openssh_fmtkey(skey->data, NULL, 0);
653 reqlen = 4 + 1 + /* length, message type */
654 4 + alglen + /* algorithm name */
655 keybloblen + /* key data */
656 4 + clen /* comment */
659 request = snewn(reqlen, unsigned char);
661 request[4] = SSH2_AGENTC_ADD_IDENTITY;
663 PUT_32BIT(request + reqlen, alglen);
665 memcpy(request + reqlen, skey->alg->name, alglen);
667 reqlen += skey->alg->openssh_fmtkey(skey->data,
670 PUT_32BIT(request + reqlen, clen);
671 memcpy(request + reqlen + 4, skey->comment, clen);
673 PUT_32BIT(request, reqlen - 4);
675 ret = agent_query(request, reqlen, &vresponse, &resplen,
678 response = vresponse;
679 if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
680 MessageBox(NULL, "The already running Pageant "
681 "refused to add the key.", APPNAME,
682 MB_OK | MB_ICONERROR);
687 if (add234(ssh2keys, skey) != skey) {
688 skey->alg->freekey(skey->data);
689 sfree(skey); /* already present, don't waste RAM */
696 * Create an SSH-1 key list in a malloc'ed buffer; return its
699 static void *make_keylist1(int *length)
703 unsigned char *blob, *p, *ret;
707 * Count up the number and length of keys we hold.
711 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
713 blob = rsa_public_blob(key, &bloblen);
716 len += 4 + strlen(key->comment);
719 /* Allocate the buffer. */
720 p = ret = snewn(len, unsigned char);
721 if (length) *length = len;
725 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
726 blob = rsa_public_blob(key, &bloblen);
727 memcpy(p, blob, bloblen);
730 PUT_32BIT(p, strlen(key->comment));
731 memcpy(p + 4, key->comment, strlen(key->comment));
732 p += 4 + strlen(key->comment);
735 assert(p - ret == len);
740 * Create an SSH-2 key list in a malloc'ed buffer; return its
743 static void *make_keylist2(int *length)
745 struct ssh2_userkey *key;
747 unsigned char *blob, *p, *ret;
751 * Count up the number and length of keys we hold.
755 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
757 len += 4; /* length field */
758 blob = key->alg->public_blob(key->data, &bloblen);
761 len += 4 + strlen(key->comment);
764 /* Allocate the buffer. */
765 p = ret = snewn(len, unsigned char);
766 if (length) *length = len;
769 * Packet header is the obvious five bytes, plus four
770 * bytes for the key count.
774 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
775 blob = key->alg->public_blob(key->data, &bloblen);
776 PUT_32BIT(p, bloblen);
778 memcpy(p, blob, bloblen);
781 PUT_32BIT(p, strlen(key->comment));
782 memcpy(p + 4, key->comment, strlen(key->comment));
783 p += 4 + strlen(key->comment);
786 assert(p - ret == len);
791 * Acquire a keylist1 from the primary Pageant; this means either
792 * calling make_keylist1 (if that's us) or sending a message to the
793 * primary Pageant (if it's not).
795 static void *get_keylist1(int *length)
799 if (already_running) {
800 unsigned char request[5], *response;
803 request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
804 PUT_32BIT(request, 4);
806 retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
808 response = vresponse;
809 if (resplen < 5 || response[4] != SSH1_AGENT_RSA_IDENTITIES_ANSWER) {
814 ret = snewn(resplen-5, unsigned char);
815 memcpy(ret, response+5, resplen-5);
821 ret = make_keylist1(length);
827 * Acquire a keylist2 from the primary Pageant; this means either
828 * calling make_keylist2 (if that's us) or sending a message to the
829 * primary Pageant (if it's not).
831 static void *get_keylist2(int *length)
835 if (already_running) {
836 unsigned char request[5], *response;
840 request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
841 PUT_32BIT(request, 4);
843 retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
845 response = vresponse;
846 if (resplen < 5 || response[4] != SSH2_AGENT_IDENTITIES_ANSWER) {
851 ret = snewn(resplen-5, unsigned char);
852 memcpy(ret, response+5, resplen-5);
858 ret = make_keylist2(length);
864 * This is the main agent function that answers messages.
866 static void answer_msg(void *msg)
868 unsigned char *p = msg;
869 unsigned char *ret = msg;
870 unsigned char *msgend;
874 * Get the message length.
876 msgend = p + 4 + GET_32BIT(p);
879 * Get the message type.
887 case SSH1_AGENTC_REQUEST_RSA_IDENTITIES:
889 * Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER.
895 ret[4] = SSH1_AGENT_RSA_IDENTITIES_ANSWER;
896 keylist = make_keylist1(&len);
897 if (len + 5 > AGENT_MAX_MSGLEN) {
901 PUT_32BIT(ret, len + 1);
902 memcpy(ret + 5, keylist, len);
906 case SSH2_AGENTC_REQUEST_IDENTITIES:
908 * Reply with SSH2_AGENT_IDENTITIES_ANSWER.
914 ret[4] = SSH2_AGENT_IDENTITIES_ANSWER;
915 keylist = make_keylist2(&len);
916 if (len + 5 > AGENT_MAX_MSGLEN) {
920 PUT_32BIT(ret, len + 1);
921 memcpy(ret + 5, keylist, len);
925 case SSH1_AGENTC_RSA_CHALLENGE:
927 * Reply with either SSH1_AGENT_RSA_RESPONSE or
928 * SSH_AGENT_FAILURE, depending on whether we have that key
932 struct RSAKey reqkey, *key;
933 Bignum challenge, response;
934 unsigned char response_source[48], response_md5[16];
935 struct MD5Context md5c;
939 i = ssh1_read_bignum(p, msgend - p, &reqkey.exponent);
943 i = ssh1_read_bignum(p, msgend - p, &reqkey.modulus);
945 freebn(reqkey.exponent);
949 i = ssh1_read_bignum(p, msgend - p, &challenge);
951 freebn(reqkey.exponent);
952 freebn(reqkey.modulus);
957 freebn(reqkey.exponent);
958 freebn(reqkey.modulus);
962 memcpy(response_source + 32, p, 16);
966 (key = find234(rsakeys, &reqkey, NULL)) == NULL) {
967 freebn(reqkey.exponent);
968 freebn(reqkey.modulus);
972 response = rsadecrypt(challenge, key);
973 for (i = 0; i < 32; i++)
974 response_source[i] = bignum_byte(response, 31 - i);
977 MD5Update(&md5c, response_source, 48);
978 MD5Final(response_md5, &md5c);
979 smemclr(response_source, 48); /* burn the evidence */
980 freebn(response); /* and that evidence */
981 freebn(challenge); /* yes, and that evidence */
982 freebn(reqkey.exponent); /* and free some memory ... */
983 freebn(reqkey.modulus); /* ... while we're at it. */
986 * Packet is the obvious five byte header, plus sixteen
990 PUT_32BIT(ret, len - 4);
991 ret[4] = SSH1_AGENT_RSA_RESPONSE;
992 memcpy(ret + 5, response_md5, 16);
995 case SSH2_AGENTC_SIGN_REQUEST:
997 * Reply with either SSH2_AGENT_SIGN_RESPONSE or
998 * SSH_AGENT_FAILURE, depending on whether we have that key
1002 struct ssh2_userkey *key;
1004 unsigned char *data, *signature;
1005 int datalen, siglen, len;
1009 b.len = toint(GET_32BIT(p));
1010 if (b.len < 0 || b.len > msgend - (p+4))
1017 datalen = toint(GET_32BIT(p));
1019 if (datalen < 0 || datalen > msgend - p)
1022 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
1025 signature = key->alg->sign(key->data, data, datalen, &siglen);
1026 len = 5 + 4 + siglen;
1027 PUT_32BIT(ret, len - 4);
1028 ret[4] = SSH2_AGENT_SIGN_RESPONSE;
1029 PUT_32BIT(ret + 5, siglen);
1030 memcpy(ret + 5 + 4, signature, siglen);
1034 case SSH1_AGENTC_ADD_RSA_IDENTITY:
1036 * Add to the list and return SSH_AGENT_SUCCESS, or
1037 * SSH_AGENT_FAILURE if the key was malformed.
1044 key = snew(struct RSAKey);
1045 memset(key, 0, sizeof(struct RSAKey));
1047 n = makekey(p, msgend - p, key, NULL, 1);
1055 n = makeprivate(p, msgend - p, key);
1063 n = ssh1_read_bignum(p, msgend - p, &key->iqmp); /* p^-1 mod q */
1071 n = ssh1_read_bignum(p, msgend - p, &key->p); /* p */
1079 n = ssh1_read_bignum(p, msgend - p, &key->q); /* q */
1092 commentlen = toint(GET_32BIT(p));
1094 if (commentlen < 0 || commentlen > msgend - p) {
1100 comment = snewn(commentlen+1, char);
1102 memcpy(comment, p + 4, commentlen);
1103 comment[commentlen] = '\0';
1104 key->comment = comment;
1107 ret[4] = SSH_AGENT_FAILURE;
1108 if (add234(rsakeys, key) == key) {
1110 ret[4] = SSH_AGENT_SUCCESS;
1117 case SSH2_AGENTC_ADD_IDENTITY:
1119 * Add to the list and return SSH_AGENT_SUCCESS, or
1120 * SSH_AGENT_FAILURE if the key was malformed.
1123 struct ssh2_userkey *key;
1124 char *comment, *alg;
1125 int alglen, commlen;
1131 alglen = toint(GET_32BIT(p));
1133 if (alglen < 0 || alglen > msgend - p)
1138 key = snew(struct ssh2_userkey);
1139 /* Add further algorithm names here. */
1140 if (alglen == 7 && !memcmp(alg, "ssh-rsa", 7))
1141 key->alg = &ssh_rsa;
1142 else if (alglen == 7 && !memcmp(alg, "ssh-dss", 7))
1143 key->alg = &ssh_dss;
1149 bloblen = msgend - p;
1150 key->data = key->alg->openssh_createkey(&p, &bloblen);
1157 * p has been advanced by openssh_createkey, but
1158 * certainly not _beyond_ the end of the buffer.
1160 assert(p <= msgend);
1163 key->alg->freekey(key->data);
1167 commlen = toint(GET_32BIT(p));
1170 if (commlen < 0 || commlen > msgend - p) {
1171 key->alg->freekey(key->data);
1175 comment = snewn(commlen + 1, char);
1177 memcpy(comment, p, commlen);
1178 comment[commlen] = '\0';
1180 key->comment = comment;
1183 ret[4] = SSH_AGENT_FAILURE;
1184 if (add234(ssh2keys, key) == key) {
1186 ret[4] = SSH_AGENT_SUCCESS;
1188 key->alg->freekey(key->data);
1189 sfree(key->comment);
1194 case SSH1_AGENTC_REMOVE_RSA_IDENTITY:
1196 * Remove from the list and return SSH_AGENT_SUCCESS, or
1197 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
1201 struct RSAKey reqkey, *key;
1204 n = makekey(p, msgend - p, &reqkey, NULL, 0);
1208 key = find234(rsakeys, &reqkey, NULL);
1209 freebn(reqkey.exponent);
1210 freebn(reqkey.modulus);
1212 ret[4] = SSH_AGENT_FAILURE;
1214 del234(rsakeys, key);
1218 ret[4] = SSH_AGENT_SUCCESS;
1222 case SSH2_AGENTC_REMOVE_IDENTITY:
1224 * Remove from the list and return SSH_AGENT_SUCCESS, or
1225 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
1229 struct ssh2_userkey *key;
1234 b.len = toint(GET_32BIT(p));
1237 if (b.len < 0 || b.len > msgend - p)
1242 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
1247 ret[4] = SSH_AGENT_FAILURE;
1249 del234(ssh2keys, key);
1251 key->alg->freekey(key->data);
1253 ret[4] = SSH_AGENT_SUCCESS;
1257 case SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES:
1259 * Remove all SSH-1 keys. Always returns success.
1262 struct RSAKey *rkey;
1264 while ((rkey = index234(rsakeys, 0)) != NULL) {
1265 del234(rsakeys, rkey);
1272 ret[4] = SSH_AGENT_SUCCESS;
1275 case SSH2_AGENTC_REMOVE_ALL_IDENTITIES:
1277 * Remove all SSH-2 keys. Always returns success.
1280 struct ssh2_userkey *skey;
1282 while ((skey = index234(ssh2keys, 0)) != NULL) {
1283 del234(ssh2keys, skey);
1284 skey->alg->freekey(skey->data);
1290 ret[4] = SSH_AGENT_SUCCESS;
1296 * Unrecognised message. Return SSH_AGENT_FAILURE.
1299 ret[4] = SSH_AGENT_FAILURE;
1305 * Key comparison function for the 2-3-4 tree of RSA keys.
1307 static int cmpkeys_rsa(void *av, void *bv)
1309 struct RSAKey *a = (struct RSAKey *) av;
1310 struct RSAKey *b = (struct RSAKey *) bv;
1317 * Compare by length of moduli.
1319 alen = bignum_bitcount(am);
1320 blen = bignum_bitcount(bm);
1323 else if (alen < blen)
1326 * Now compare by moduli themselves.
1328 alen = (alen + 7) / 8; /* byte count */
1329 while (alen-- > 0) {
1331 abyte = bignum_byte(am, alen);
1332 bbyte = bignum_byte(bm, alen);
1335 else if (abyte < bbyte)
1345 * Key comparison function for the 2-3-4 tree of SSH-2 keys.
1347 static int cmpkeys_ssh2(void *av, void *bv)
1349 struct ssh2_userkey *a = (struct ssh2_userkey *) av;
1350 struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
1353 unsigned char *ablob, *bblob;
1357 * Compare purely by public blob.
1359 ablob = a->alg->public_blob(a->data, &alen);
1360 bblob = b->alg->public_blob(b->data, &blen);
1363 for (i = 0; i < alen && i < blen; i++) {
1364 if (ablob[i] < bblob[i]) {
1367 } else if (ablob[i] > bblob[i]) {
1372 if (c == 0 && i < alen)
1373 c = +1; /* a is longer */
1374 if (c == 0 && i < blen)
1375 c = -1; /* a is longer */
1384 * Key comparison function for looking up a blob in the 2-3-4 tree
1387 static int cmpkeys_ssh2_asymm(void *av, void *bv)
1389 struct blob *a = (struct blob *) av;
1390 struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
1393 unsigned char *ablob, *bblob;
1397 * Compare purely by public blob.
1401 bblob = b->alg->public_blob(b->data, &blen);
1404 for (i = 0; i < alen && i < blen; i++) {
1405 if (ablob[i] < bblob[i]) {
1408 } else if (ablob[i] > bblob[i]) {
1413 if (c == 0 && i < alen)
1414 c = +1; /* a is longer */
1415 if (c == 0 && i < blen)
1416 c = -1; /* a is longer */
1424 * Prompt for a key file to add, and add it.
1426 static void prompt_add_keyfile(void)
1429 char *filelist = snewn(8192, char);
1431 if (!keypath) keypath = filereq_new();
1432 memset(&of, 0, sizeof(of));
1433 of.hwndOwner = hwnd;
1434 of.lpstrFilter = FILTER_KEY_FILES;
1435 of.lpstrCustomFilter = NULL;
1436 of.nFilterIndex = 1;
1437 of.lpstrFile = filelist;
1440 of.lpstrFileTitle = NULL;
1441 of.lpstrTitle = "Select Private Key File";
1442 of.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER;
1443 if (request_file(keypath, &of, TRUE, FALSE)) {
1444 if(strlen(filelist) > of.nFileOffset) {
1445 /* Only one filename returned? */
1446 Filename *fn = filename_from_str(filelist);
1450 /* we are returned a bunch of strings, end to
1451 * end. first string is the directory, the
1452 * rest the filenames. terminated with an
1455 char *dir = filelist;
1456 char *filewalker = filelist + strlen(dir) + 1;
1457 while (*filewalker != '\0') {
1458 char *filename = dupcat(dir, "\\", filewalker, NULL);
1459 Filename *fn = filename_from_str(filename);
1463 filewalker += strlen(filewalker) + 1;
1468 forget_passphrases();
1474 * Dialog-box function for the key list box.
1476 static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
1477 WPARAM wParam, LPARAM lParam)
1479 struct RSAKey *rkey;
1480 struct ssh2_userkey *skey;
1485 * Centre the window.
1487 { /* centre the window */
1491 hw = GetDesktopWindow();
1492 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
1494 (rs.right + rs.left + rd.left - rd.right) / 2,
1495 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
1496 rd.right - rd.left, rd.bottom - rd.top, TRUE);
1500 SetWindowLongPtr(hwnd, GWL_EXSTYLE,
1501 GetWindowLongPtr(hwnd, GWL_EXSTYLE) |
1504 HWND item = GetDlgItem(hwnd, 103); /* the Help button */
1506 DestroyWindow(item);
1511 static int tabs[] = { 35, 60, 210 };
1512 SendDlgItemMessage(hwnd, 100, LB_SETTABSTOPS,
1513 sizeof(tabs) / sizeof(*tabs),
1519 switch (LOWORD(wParam)) {
1523 DestroyWindow(hwnd);
1525 case 101: /* add key */
1526 if (HIWORD(wParam) == BN_CLICKED ||
1527 HIWORD(wParam) == BN_DOUBLECLICKED) {
1528 if (passphrase_box) {
1529 MessageBeep(MB_ICONERROR);
1530 SetForegroundWindow(passphrase_box);
1533 prompt_add_keyfile();
1536 case 102: /* remove key */
1537 if (HIWORD(wParam) == BN_CLICKED ||
1538 HIWORD(wParam) == BN_DOUBLECLICKED) {
1543 /* our counter within the array of selected items */
1546 /* get the number of items selected in the list */
1548 SendDlgItemMessage(hwnd, 100, LB_GETSELCOUNT, 0, 0);
1550 /* none selected? that was silly */
1551 if (numSelected == 0) {
1556 /* get item indices in an array */
1557 selectedArray = snewn(numSelected, int);
1558 SendDlgItemMessage(hwnd, 100, LB_GETSELITEMS,
1559 numSelected, (WPARAM)selectedArray);
1561 itemNum = numSelected - 1;
1562 rCount = count234(rsakeys);
1563 sCount = count234(ssh2keys);
1565 /* go through the non-rsakeys until we've covered them all,
1566 * and/or we're out of selected items to check. note that
1567 * we go *backwards*, to avoid complications from deleting
1568 * things hence altering the offset of subsequent items
1570 for (i = sCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1571 skey = index234(ssh2keys, i);
1573 if (selectedArray[itemNum] == rCount + i) {
1574 del234(ssh2keys, skey);
1575 skey->alg->freekey(skey->data);
1581 /* do the same for the rsa keys */
1582 for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1583 rkey = index234(rsakeys, i);
1585 if(selectedArray[itemNum] == i) {
1586 del234(rsakeys, rkey);
1593 sfree(selectedArray);
1597 case 103: /* help */
1598 if (HIWORD(wParam) == BN_CLICKED ||
1599 HIWORD(wParam) == BN_DOUBLECLICKED) {
1600 launch_help(hwnd, WINHELP_CTX_pageant_general);
1607 int id = ((LPHELPINFO)lParam)->iCtrlId;
1610 case 100: topic = WINHELP_CTX_pageant_keylist; break;
1611 case 101: topic = WINHELP_CTX_pageant_addkey; break;
1612 case 102: topic = WINHELP_CTX_pageant_remkey; break;
1615 launch_help(hwnd, topic);
1623 DestroyWindow(hwnd);
1629 /* Set up a system tray icon */
1630 static BOOL AddTrayIcon(HWND hwnd)
1633 NOTIFYICONDATA tnid;
1636 #ifdef NIM_SETVERSION
1638 res = Shell_NotifyIcon(NIM_SETVERSION, &tnid);
1641 tnid.cbSize = sizeof(NOTIFYICONDATA);
1643 tnid.uID = 1; /* unique within this systray use */
1644 tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
1645 tnid.uCallbackMessage = WM_SYSTRAY;
1646 tnid.hIcon = hicon = LoadIcon(hinst, MAKEINTRESOURCE(201));
1647 strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
1649 res = Shell_NotifyIcon(NIM_ADD, &tnid);
1651 if (hicon) DestroyIcon(hicon);
1656 /* Update the saved-sessions menu. */
1657 static void update_sessions(void)
1661 TCHAR buf[MAX_PATH + 1];
1664 int index_key, index_menu;
1669 if(ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, PUTTY_REGKEY, &hkey))
1672 for(num_entries = GetMenuItemCount(session_menu);
1673 num_entries > initial_menuitems_count;
1675 RemoveMenu(session_menu, 0, MF_BYPOSITION);
1680 while(ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) {
1681 TCHAR session_name[MAX_PATH + 1];
1682 unmungestr(buf, session_name, MAX_PATH);
1683 if(strcmp(buf, PUTTY_DEFAULT) != 0) {
1684 memset(&mii, 0, sizeof(mii));
1685 mii.cbSize = sizeof(mii);
1686 mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
1687 mii.fType = MFT_STRING;
1688 mii.fState = MFS_ENABLED;
1689 mii.wID = (index_menu * 16) + IDM_SESSIONS_BASE;
1690 mii.dwTypeData = session_name;
1691 InsertMenuItem(session_menu, index_menu, TRUE, &mii);
1699 if(index_menu == 0) {
1700 mii.cbSize = sizeof(mii);
1701 mii.fMask = MIIM_TYPE | MIIM_STATE;
1702 mii.fType = MFT_STRING;
1703 mii.fState = MFS_GRAYED;
1704 mii.dwTypeData = _T("(No sessions)");
1705 InsertMenuItem(session_menu, index_menu, TRUE, &mii);
1711 * Versions of Pageant prior to 0.61 expected this SID on incoming
1712 * communications. For backwards compatibility, and more particularly
1713 * for compatibility with derived works of PuTTY still using the old
1714 * Pageant client code, we accept it as an alternative to the one
1715 * returned from get_user_sid() in winpgntc.c.
1717 PSID get_default_sid(void)
1721 PSECURITY_DESCRIPTOR psd = NULL;
1722 PSID sid = NULL, copy = NULL, ret = NULL;
1724 if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE,
1725 GetCurrentProcessId())) == NULL)
1728 if (p_GetSecurityInfo(proc, SE_KERNEL_OBJECT, OWNER_SECURITY_INFORMATION,
1729 &sid, NULL, NULL, NULL, &psd) != ERROR_SUCCESS)
1732 sidlen = GetLengthSid(sid);
1734 copy = (PSID)smalloc(sidlen);
1736 if (!CopySid(sidlen, copy, sid))
1739 /* Success. Move sid into the return value slot, and null it out
1740 * to stop the cleanup code freeing it. */
1756 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1757 WPARAM wParam, LPARAM lParam)
1760 static int menuinprogress;
1761 static UINT msgTaskbarCreated = 0;
1765 msgTaskbarCreated = RegisterWindowMessage(_T("TaskbarCreated"));
1768 if (message==msgTaskbarCreated) {
1770 * Explorer has been restarted, so the tray icon will
1778 if (lParam == WM_RBUTTONUP) {
1780 GetCursorPos(&cursorpos);
1781 PostMessage(hwnd, WM_SYSTRAY2, cursorpos.x, cursorpos.y);
1782 } else if (lParam == WM_LBUTTONDBLCLK) {
1783 /* Run the default menu item. */
1784 UINT menuitem = GetMenuDefaultItem(systray_menu, FALSE, 0);
1786 PostMessage(hwnd, WM_COMMAND, menuitem, 0);
1790 if (!menuinprogress) {
1793 SetForegroundWindow(hwnd);
1794 ret = TrackPopupMenu(systray_menu,
1795 TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
1797 wParam, lParam, 0, hwnd, NULL);
1803 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1805 if((int)ShellExecute(hwnd, NULL, putty_path, _T(""), _T(""),
1807 MessageBox(NULL, "Unable to execute PuTTY!",
1808 "Error", MB_OK | MB_ICONERROR);
1813 SendMessage(passphrase_box, WM_CLOSE, 0, 0);
1814 SendMessage(hwnd, WM_CLOSE, 0, 0);
1818 keylist = CreateDialog(hinst, MAKEINTRESOURCE(211),
1820 ShowWindow(keylist, SW_SHOWNORMAL);
1823 * Sometimes the window comes up minimised / hidden for
1824 * no obvious reason. Prevent this. This also brings it
1825 * to the front if it's already present (the user
1826 * selected View Keys because they wanted to _see_ the
1829 SetForegroundWindow(keylist);
1830 SetWindowPos(keylist, HWND_TOP, 0, 0, 0, 0,
1831 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1834 if (passphrase_box) {
1835 MessageBeep(MB_ICONERROR);
1836 SetForegroundWindow(passphrase_box);
1839 prompt_add_keyfile();
1843 aboutbox = CreateDialog(hinst, MAKEINTRESOURCE(213),
1845 ShowWindow(aboutbox, SW_SHOWNORMAL);
1847 * Sometimes the window comes up minimised / hidden
1848 * for no obvious reason. Prevent this.
1850 SetForegroundWindow(aboutbox);
1851 SetWindowPos(aboutbox, HWND_TOP, 0, 0, 0, 0,
1852 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1856 launch_help(hwnd, WINHELP_CTX_pageant_general);
1860 if(wParam >= IDM_SESSIONS_BASE && wParam <= IDM_SESSIONS_MAX) {
1862 TCHAR buf[MAX_PATH + 1];
1863 TCHAR param[MAX_PATH + 1];
1864 memset(&mii, 0, sizeof(mii));
1865 mii.cbSize = sizeof(mii);
1866 mii.fMask = MIIM_TYPE;
1868 mii.dwTypeData = buf;
1869 GetMenuItemInfo(session_menu, wParam, FALSE, &mii);
1871 strcat(param, mii.dwTypeData);
1872 if((int)ShellExecute(hwnd, NULL, putty_path, param,
1873 _T(""), SW_SHOW) <= 32) {
1874 MessageBox(NULL, "Unable to execute PuTTY!", "Error",
1875 MB_OK | MB_ICONERROR);
1888 COPYDATASTRUCT *cds;
1893 PSID mapowner, ourself, ourself2;
1895 PSECURITY_DESCRIPTOR psd = NULL;
1898 cds = (COPYDATASTRUCT *) lParam;
1899 if (cds->dwData != AGENT_COPYDATA_ID)
1900 return 0; /* not our message, mate */
1901 mapname = (char *) cds->lpData;
1902 if (mapname[cds->cbData - 1] != '\0')
1903 return 0; /* failure to be ASCIZ! */
1905 debug(("mapname is :%s:\n", mapname));
1907 filemap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, mapname);
1909 debug(("filemap is %p\n", filemap));
1911 if (filemap != NULL && filemap != INVALID_HANDLE_VALUE) {
1915 if ((ourself = get_user_sid()) == NULL) {
1917 debug(("couldn't get user SID\n"));
1919 CloseHandle(filemap);
1923 if ((ourself2 = get_default_sid()) == NULL) {
1925 debug(("couldn't get default SID\n"));
1927 CloseHandle(filemap);
1932 if ((rc = p_GetSecurityInfo(filemap, SE_KERNEL_OBJECT,
1933 OWNER_SECURITY_INFORMATION,
1934 &mapowner, NULL, NULL, NULL,
1935 &psd) != ERROR_SUCCESS)) {
1937 debug(("couldn't get owner info for filemap: %d\n",
1940 CloseHandle(filemap);
1947 LPTSTR ours, ours2, theirs;
1948 ConvertSidToStringSid(mapowner, &theirs);
1949 ConvertSidToStringSid(ourself, &ours);
1950 ConvertSidToStringSid(ourself2, &ours2);
1951 debug(("got sids:\n oursnew=%s\n oursold=%s\n"
1952 " theirs=%s\n", ours, ours2, theirs));
1958 if (!EqualSid(mapowner, ourself) &&
1959 !EqualSid(mapowner, ourself2)) {
1960 CloseHandle(filemap);
1964 return 0; /* security ID mismatch! */
1967 debug(("security stuff matched\n"));
1974 debug(("security APIs not present\n"));
1978 p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
1980 debug(("p is %p\n", p));
1983 for (i = 0; i < 5; i++)
1984 debug(("p[%d]=%02x\n", i,
1985 ((unsigned char *) p)[i]));
1992 CloseHandle(filemap);
1997 return DefWindowProc(hwnd, message, wParam, lParam);
2001 * Fork and Exec the command in cmdline. [DBW]
2003 void spawn_cmd(char *cmdline, char * args, int show)
2005 if (ShellExecute(NULL, _T("open"), cmdline,
2006 args, NULL, show) <= (HINSTANCE) 32) {
2008 msg = dupprintf("Failed to run \"%.100s\", Error: %d", cmdline,
2009 (int)GetLastError());
2010 MessageBox(NULL, msg, APPNAME, MB_OK | MB_ICONEXCLAMATION);
2016 * This is a can't-happen stub, since Pageant never makes
2017 * asynchronous agent requests.
2019 void agent_schedule_callback(void (*callback)(void *, void *, int),
2020 void *callback_ctx, void *data, int len)
2022 assert(!"We shouldn't get here");
2025 void cleanup_exit(int code)
2031 int flags = FLAG_SYNCAGENT;
2033 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
2037 char *command = NULL;
2040 char **argv, **argstart;
2046 * Determine whether we're an NT system (should have security
2047 * APIs) or a non-NT system (don't do security).
2051 modalfatalbox("Windows refuses to report a version");
2053 if (osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT) {
2054 has_security = TRUE;
2056 has_security = FALSE;
2061 * Attempt to get the security API we need.
2063 if (!got_advapi()) {
2065 "Unable to access security APIs. Pageant will\n"
2066 "not run, in case it causes a security breach.",
2067 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
2072 "This program has been compiled for Win9X and will\n"
2073 "not run on NT, in case it causes a security breach.",
2074 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
2080 * See if we can find our Help file.
2085 * Look for the PuTTY binary (we will enable the saved session
2086 * submenu if we find it).
2089 char b[2048], *p, *q, *r;
2091 GetModuleFileName(NULL, b, sizeof(b) - 16);
2093 p = strrchr(b, '\\');
2094 if (p && p >= r) r = p+1;
2095 q = strrchr(b, ':');
2096 if (q && q >= r) r = q+1;
2097 strcpy(r, "putty.exe");
2098 if ( (fp = fopen(b, "r")) != NULL) {
2099 putty_path = dupstr(b);
2106 * Find out if Pageant is already running.
2108 already_running = agent_exists();
2111 * Initialise storage for RSA keys.
2113 if (!already_running) {
2114 rsakeys = newtree234(cmpkeys_rsa);
2115 ssh2keys = newtree234(cmpkeys_ssh2);
2119 * Initialise storage for short-term passphrase cache.
2121 passphrases = newtree234(NULL);
2124 * Process the command line and add keys as listed on it.
2126 split_into_argv(cmdline, &argc, &argv, &argstart);
2127 for (i = 0; i < argc; i++) {
2128 if (!strcmp(argv[i], "-pgpfp")) {
2131 } else if (!strcmp(argv[i], "-c")) {
2133 * If we see `-c', then the rest of the
2134 * command line should be treated as a
2135 * command to be spawned.
2138 command = argstart[i+1];
2143 Filename *fn = filename_from_str(argv[i]);
2151 * Forget any passphrase that we retained while going over
2152 * command line keyfiles.
2154 forget_passphrases();
2158 if (command[0] == '"')
2159 args = strchr(++command, '"');
2161 args = strchr(command, ' ');
2164 while(*args && isspace(*args)) args++;
2166 spawn_cmd(command, args, show);
2170 * If Pageant was already running, we leave now. If we haven't
2171 * even taken any auxiliary action (spawned a command or added
2174 if (already_running) {
2175 if (!command && !added_keys) {
2176 MessageBox(NULL, "Pageant is already running", "Pageant Error",
2177 MB_ICONERROR | MB_OK);
2184 wndclass.lpfnWndProc = WndProc;
2185 wndclass.cbClsExtra = 0;
2186 wndclass.cbWndExtra = 0;
2187 wndclass.hInstance = inst;
2188 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
2189 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
2190 wndclass.hbrBackground = GetStockObject(BLACK_BRUSH);
2191 wndclass.lpszMenuName = NULL;
2192 wndclass.lpszClassName = APPNAME;
2194 RegisterClass(&wndclass);
2199 hwnd = CreateWindow(APPNAME, APPNAME,
2200 WS_OVERLAPPEDWINDOW | WS_VSCROLL,
2201 CW_USEDEFAULT, CW_USEDEFAULT,
2202 100, 100, NULL, NULL, inst, NULL);
2204 /* Set up a system tray icon */
2207 /* Accelerators used: nsvkxa */
2208 systray_menu = CreatePopupMenu();
2210 session_menu = CreateMenu();
2211 AppendMenu(systray_menu, MF_ENABLED, IDM_PUTTY, "&New Session");
2212 AppendMenu(systray_menu, MF_POPUP | MF_ENABLED,
2213 (UINT) session_menu, "&Saved Sessions");
2214 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
2216 AppendMenu(systray_menu, MF_ENABLED, IDM_VIEWKEYS,
2218 AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key");
2219 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
2221 AppendMenu(systray_menu, MF_ENABLED, IDM_HELP, "&Help");
2222 AppendMenu(systray_menu, MF_ENABLED, IDM_ABOUT, "&About");
2223 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
2224 AppendMenu(systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit");
2225 initial_menuitems_count = GetMenuItemCount(session_menu);
2227 /* Set the default menu item. */
2228 SetMenuDefaultItem(systray_menu, IDM_VIEWKEYS, FALSE);
2230 ShowWindow(hwnd, SW_HIDE);
2233 * Main message loop.
2235 while (GetMessage(&msg, NULL, 0, 0) == 1) {
2236 if (!(IsWindow(keylist) && IsDialogMessage(keylist, &msg)) &&
2237 !(IsWindow(aboutbox) && IsDialogMessage(aboutbox, &msg))) {
2238 TranslateMessage(&msg);
2239 DispatchMessage(&msg);
2243 /* Clean up the system tray icon */
2245 NOTIFYICONDATA tnid;
2247 tnid.cbSize = sizeof(NOTIFYICONDATA);
2251 Shell_NotifyIcon(NIM_DELETE, &tnid);
2253 DestroyMenu(systray_menu);
2256 if (keypath) filereq_free(keypath);
2258 cleanup_exit(msg.wParam);
2259 return msg.wParam; /* just in case optimiser complains */