2 * Pageant: the PuTTY Authentication Agent.
11 #define PUTTY_DO_GLOBALS
24 #define IDI_MAINICON 200
25 #define IDI_TRAYICON 201
27 #define WM_XUSER (WM_USER + 0x2000)
28 #define WM_SYSTRAY (WM_XUSER + 6)
29 #define WM_SYSTRAY2 (WM_XUSER + 7)
31 #define AGENT_COPYDATA_ID 0x804e50ba /* random goop */
34 * FIXME: maybe some day we can sort this out ...
36 #define AGENT_MAX_MSGLEN 8192
38 /* From MSDN: In the WM_SYSCOMMAND message, the four low-order bits of
39 * wParam are used by Windows, and should be masked off, so we shouldn't
40 * attempt to store information in them. Hence all these identifiers have
41 * the low 4 bits clear. Also, identifiers should < 0xF000. */
43 #define IDM_CLOSE 0x0010
44 #define IDM_VIEWKEYS 0x0020
45 #define IDM_ADDKEY 0x0030
46 #define IDM_HELP 0x0040
47 #define IDM_ABOUT 0x0050
49 #define APPNAME "Pageant"
55 static HMENU systray_menu, session_menu;
56 static int already_running;
58 static char *putty_path;
60 /* CWD for "add key" file requester. */
61 static filereq *keypath = NULL;
63 #define IDM_PUTTY 0x0060
64 #define IDM_SESSIONS_BASE 0x1000
65 #define IDM_SESSIONS_MAX 0x2000
66 #define PUTTY_REGKEY "Software\\SimonTatham\\PuTTY\\Sessions"
67 #define PUTTY_DEFAULT "Default%20Settings"
68 static int initial_menuitems_count;
71 * Print a modal (Really Bad) message box and perform a fatal exit.
73 void modalfatalbox(char *fmt, ...)
79 buf = dupvprintf(fmt, ap);
81 MessageBox(hwnd, buf, "Pageant Fatal Error",
82 MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
87 /* Un-munge session names out of the registry. */
88 static void unmungestr(char *in, char *out, int outlen)
91 if (*in == '%' && in[1] && in[2]) {
99 *out++ = (i << 4) + j;
113 static tree234 *rsakeys, *ssh2keys;
115 static int has_security;
117 typedef DWORD(WINAPI * gsi_fn_t)
118 (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION,
119 PSID *, PSID *, PACL *, PACL *, PSECURITY_DESCRIPTOR *);
120 static gsi_fn_t getsecurityinfo;
126 static void *make_keylist1(int *length);
127 static void *make_keylist2(int *length);
128 static void *get_keylist1(int *length);
129 static void *get_keylist2(int *length);
132 * We need this to link with the RSA code, because rsaencrypt()
133 * pads its data with random bytes. Since we only use rsadecrypt()
134 * and the signing functions, which are deterministic, this should
137 * If it _is_ called, there is a _serious_ problem, because it
138 * won't generate true random numbers. So we must scream, panic,
139 * and exit immediately if that should happen.
141 int random_byte(void)
143 MessageBox(hwnd, "Internal Error", APPNAME, MB_OK | MB_ICONERROR);
145 /* this line can't be reached but it placates MSVC's warnings :-) */
150 * Blob structure for passing to the asymmetric SSH-2 key compare
151 * function, prototyped here.
157 static int cmpkeys_ssh2_asymm(void *av, void *bv);
159 #define PASSPHRASE_MAXLEN 512
161 struct PassphraseProcStruct {
166 static tree234 *passphrases = NULL;
169 * After processing a list of filenames, we want to forget the
172 static void forget_passphrases(void)
174 while (count234(passphrases) > 0) {
175 char *pp = index234(passphrases, 0);
176 memset(pp, 0, strlen(pp));
177 delpos234(passphrases, 0);
183 * Dialog-box function for the Licence box.
185 static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
186 WPARAM wParam, LPARAM lParam)
192 switch (LOWORD(wParam)) {
207 * Dialog-box function for the About box.
209 static int CALLBACK AboutProc(HWND hwnd, UINT msg,
210 WPARAM wParam, LPARAM lParam)
214 SetDlgItemText(hwnd, 100, ver);
217 switch (LOWORD(wParam)) {
224 EnableWindow(hwnd, 0);
225 DialogBox(hinst, MAKEINTRESOURCE(214), hwnd, LicenceProc);
226 EnableWindow(hwnd, 1);
227 SetActiveWindow(hwnd);
239 static HWND passphrase_box;
242 * Dialog-box function for the passphrase box.
244 static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
245 WPARAM wParam, LPARAM lParam)
247 static char *passphrase = NULL;
248 struct PassphraseProcStruct *p;
252 passphrase_box = hwnd;
256 { /* centre the window */
260 hw = GetDesktopWindow();
261 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
263 (rs.right + rs.left + rd.left - rd.right) / 2,
264 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
265 rd.right - rd.left, rd.bottom - rd.top, TRUE);
268 SetForegroundWindow(hwnd);
269 SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
270 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
271 p = (struct PassphraseProcStruct *) lParam;
272 passphrase = p->passphrase;
274 SetDlgItemText(hwnd, 101, p->comment);
276 SetDlgItemText(hwnd, 102, passphrase);
279 switch (LOWORD(wParam)) {
289 case 102: /* edit box */
290 if ((HIWORD(wParam) == EN_CHANGE) && passphrase) {
291 GetDlgItemText(hwnd, 102, passphrase,
292 PASSPHRASE_MAXLEN - 1);
293 passphrase[PASSPHRASE_MAXLEN - 1] = '\0';
306 * Warn about the obsolescent key file format.
308 void old_keyfile_warning(void)
310 static const char mbtitle[] = "PuTTY Key File Warning";
311 static const char message[] =
312 "You are loading an SSH-2 private key which has an\n"
313 "old version of the file format. This means your key\n"
314 "file is not fully tamperproof. Future versions of\n"
315 "PuTTY may stop supporting this private key format,\n"
316 "so we recommend you convert your key to the new\n"
319 "You can perform this conversion by loading the key\n"
320 "into PuTTYgen and then saving it again.";
322 MessageBox(NULL, message, mbtitle, MB_OK);
326 * Update the visible key list.
328 static void keylist_update(void)
331 struct ssh2_userkey *skey;
335 SendDlgItemMessage(keylist, 100, LB_RESETCONTENT, 0, 0);
336 for (i = 0; NULL != (rkey = index234(rsakeys, i)); i++) {
337 char listentry[512], *p;
339 * Replace two spaces in the fingerprint with tabs, for
340 * nice alignment in the box.
342 strcpy(listentry, "ssh1\t");
343 p = listentry + strlen(listentry);
344 rsa_fingerprint(p, sizeof(listentry) - (p - listentry), rkey);
345 p = strchr(listentry, ' ');
348 p = strchr(listentry, ' ');
351 SendDlgItemMessage(keylist, 100, LB_ADDSTRING,
352 0, (LPARAM) listentry);
354 for (i = 0; NULL != (skey = index234(ssh2keys, i)); i++) {
355 char listentry[512], *p;
358 * Replace two spaces in the fingerprint with tabs, for
359 * nice alignment in the box.
361 p = skey->alg->fingerprint(skey->data);
362 strncpy(listentry, p, sizeof(listentry));
363 p = strchr(listentry, ' ');
366 p = strchr(listentry, ' ');
369 len = strlen(listentry);
370 if (len < sizeof(listentry) - 2) {
371 listentry[len] = '\t';
372 strncpy(listentry + len + 1, skey->comment,
373 sizeof(listentry) - len - 1);
375 SendDlgItemMessage(keylist, 100, LB_ADDSTRING, 0,
378 SendDlgItemMessage(keylist, 100, LB_SETCURSEL, (WPARAM) - 1, 0);
383 * This function loads a key from a file and adds it.
385 static void add_keyfile(Filename filename)
387 char passphrase[PASSPHRASE_MAXLEN];
388 struct RSAKey *rkey = NULL;
389 struct ssh2_userkey *skey = NULL;
394 const char *error = NULL;
395 struct PassphraseProcStruct pps;
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, &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, &error);
431 char *msg = dupprintf("Couldn't load private key (%s)", error);
432 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
433 HELPCTXID(errors_cantloadkey));
437 /* For our purposes we want the blob prefixed with its length */
438 blob2 = snewn(bloblen+4, unsigned char);
439 PUT_32BIT(blob2, bloblen);
440 memcpy(blob2 + 4, blob, bloblen);
444 keylist = get_keylist2(&keylistlen);
447 if (keylistlen < 4) {
448 MessageBox(NULL, "Received broken key list?!", APPNAME,
449 MB_OK | MB_ICONERROR);
452 nkeys = GET_32BIT(keylist);
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 = 4 + GET_32BIT(p);
481 if (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 = 4 + GET_32BIT(p);
498 if (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);
522 pps.passphrase = passphrase;
523 pps.comment = comment;
527 /* try all the remembered passphrases first */
528 char *pp = index234(passphrases, attempts);
530 strcpy(passphrase, pp);
534 dlgret = DialogBoxParam(hinst, MAKEINTRESOURCE(210),
535 NULL, PassphraseProc, (LPARAM) &pps);
536 passphrase_box = NULL;
540 if (type == SSH_KEYTYPE_SSH1)
542 return; /* operation cancelled */
547 if (type == SSH_KEYTYPE_SSH1)
548 ret = loadrsakey(&filename, rkey, passphrase, &error);
550 skey = ssh2_load_userkey(&filename, passphrase, &error);
551 if (skey == SSH2_WRONG_PASSPHRASE)
561 /* if they typed in an ok passphrase, remember it */
562 if(original_pass && ret) {
563 char *pp = dupstr(passphrase);
564 addpos234(passphrases, pp, 0);
570 char *msg = dupprintf("Couldn't load private key (%s)", error);
571 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
572 HELPCTXID(errors_cantloadkey));
574 if (type == SSH_KEYTYPE_SSH1)
578 if (type == SSH_KEYTYPE_SSH1) {
579 if (already_running) {
580 unsigned char *request, *response;
582 int reqlen, clen, resplen, ret;
584 clen = strlen(rkey->comment);
586 reqlen = 4 + 1 + /* length, message type */
588 ssh1_bignum_length(rkey->modulus) +
589 ssh1_bignum_length(rkey->exponent) +
590 ssh1_bignum_length(rkey->private_exponent) +
591 ssh1_bignum_length(rkey->iqmp) +
592 ssh1_bignum_length(rkey->p) +
593 ssh1_bignum_length(rkey->q) + 4 + clen /* comment */
596 request = snewn(reqlen, unsigned char);
598 request[4] = SSH1_AGENTC_ADD_RSA_IDENTITY;
600 PUT_32BIT(request + reqlen, bignum_bitcount(rkey->modulus));
602 reqlen += ssh1_write_bignum(request + reqlen, rkey->modulus);
603 reqlen += ssh1_write_bignum(request + reqlen, rkey->exponent);
605 ssh1_write_bignum(request + reqlen,
606 rkey->private_exponent);
607 reqlen += ssh1_write_bignum(request + reqlen, rkey->iqmp);
608 reqlen += ssh1_write_bignum(request + reqlen, rkey->p);
609 reqlen += ssh1_write_bignum(request + reqlen, rkey->q);
610 PUT_32BIT(request + reqlen, clen);
611 memcpy(request + reqlen + 4, rkey->comment, clen);
613 PUT_32BIT(request, reqlen - 4);
615 ret = agent_query(request, reqlen, &vresponse, &resplen,
618 response = vresponse;
619 if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
620 MessageBox(NULL, "The already running Pageant "
621 "refused to add the key.", APPNAME,
622 MB_OK | MB_ICONERROR);
627 if (add234(rsakeys, rkey) != rkey)
628 sfree(rkey); /* already present, don't waste RAM */
631 if (already_running) {
632 unsigned char *request, *response;
634 int reqlen, alglen, clen, keybloblen, resplen, ret;
635 alglen = strlen(skey->alg->name);
636 clen = strlen(skey->comment);
638 keybloblen = skey->alg->openssh_fmtkey(skey->data, NULL, 0);
640 reqlen = 4 + 1 + /* length, message type */
641 4 + alglen + /* algorithm name */
642 keybloblen + /* key data */
643 4 + clen /* comment */
646 request = snewn(reqlen, unsigned char);
648 request[4] = SSH2_AGENTC_ADD_IDENTITY;
650 PUT_32BIT(request + reqlen, alglen);
652 memcpy(request + reqlen, skey->alg->name, alglen);
654 reqlen += skey->alg->openssh_fmtkey(skey->data,
657 PUT_32BIT(request + reqlen, clen);
658 memcpy(request + reqlen + 4, skey->comment, clen);
660 PUT_32BIT(request, reqlen - 4);
662 ret = agent_query(request, reqlen, &vresponse, &resplen,
665 response = vresponse;
666 if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
667 MessageBox(NULL, "The already running Pageant "
668 "refused to add the key.", APPNAME,
669 MB_OK | MB_ICONERROR);
674 if (add234(ssh2keys, skey) != skey) {
675 skey->alg->freekey(skey->data);
676 sfree(skey); /* already present, don't waste RAM */
683 * Create an SSH-1 key list in a malloc'ed buffer; return its
686 static void *make_keylist1(int *length)
690 unsigned char *blob, *p, *ret;
694 * Count up the number and length of keys we hold.
698 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
700 blob = rsa_public_blob(key, &bloblen);
703 len += 4 + strlen(key->comment);
706 /* Allocate the buffer. */
707 p = ret = snewn(len, unsigned char);
708 if (length) *length = len;
712 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
713 blob = rsa_public_blob(key, &bloblen);
714 memcpy(p, blob, bloblen);
717 PUT_32BIT(p, strlen(key->comment));
718 memcpy(p + 4, key->comment, strlen(key->comment));
719 p += 4 + strlen(key->comment);
722 assert(p - ret == len);
727 * Create an SSH-2 key list in a malloc'ed buffer; return its
730 static void *make_keylist2(int *length)
732 struct ssh2_userkey *key;
734 unsigned char *blob, *p, *ret;
738 * Count up the number and length of keys we hold.
742 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
744 len += 4; /* length field */
745 blob = key->alg->public_blob(key->data, &bloblen);
748 len += 4 + strlen(key->comment);
751 /* Allocate the buffer. */
752 p = ret = snewn(len, unsigned char);
753 if (length) *length = len;
756 * Packet header is the obvious five bytes, plus four
757 * bytes for the key count.
761 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
762 blob = key->alg->public_blob(key->data, &bloblen);
763 PUT_32BIT(p, bloblen);
765 memcpy(p, blob, bloblen);
768 PUT_32BIT(p, strlen(key->comment));
769 memcpy(p + 4, key->comment, strlen(key->comment));
770 p += 4 + strlen(key->comment);
773 assert(p - ret == len);
778 * Acquire a keylist1 from the primary Pageant; this means either
779 * calling make_keylist1 (if that's us) or sending a message to the
780 * primary Pageant (if it's not).
782 static void *get_keylist1(int *length)
786 if (already_running) {
787 unsigned char request[5], *response;
790 request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
791 PUT_32BIT(request, 4);
793 retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
795 response = vresponse;
796 if (resplen < 5 || response[4] != SSH1_AGENT_RSA_IDENTITIES_ANSWER)
799 ret = snewn(resplen-5, unsigned char);
800 memcpy(ret, response+5, resplen-5);
806 ret = make_keylist1(length);
812 * Acquire a keylist2 from the primary Pageant; this means either
813 * calling make_keylist2 (if that's us) or sending a message to the
814 * primary Pageant (if it's not).
816 static void *get_keylist2(int *length)
820 if (already_running) {
821 unsigned char request[5], *response;
825 request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
826 PUT_32BIT(request, 4);
828 retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
830 response = vresponse;
831 if (resplen < 5 || response[4] != SSH2_AGENT_IDENTITIES_ANSWER)
834 ret = snewn(resplen-5, unsigned char);
835 memcpy(ret, response+5, resplen-5);
841 ret = make_keylist2(length);
847 * This is the main agent function that answers messages.
849 static void answer_msg(void *msg)
851 unsigned char *p = msg;
852 unsigned char *ret = msg;
853 unsigned char *msgend;
857 * Get the message length.
859 msgend = p + 4 + GET_32BIT(p);
862 * Get the message type.
870 case SSH1_AGENTC_REQUEST_RSA_IDENTITIES:
872 * Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER.
878 ret[4] = SSH1_AGENT_RSA_IDENTITIES_ANSWER;
879 keylist = make_keylist1(&len);
880 if (len + 5 > AGENT_MAX_MSGLEN) {
884 PUT_32BIT(ret, len + 1);
885 memcpy(ret + 5, keylist, len);
889 case SSH2_AGENTC_REQUEST_IDENTITIES:
891 * Reply with SSH2_AGENT_IDENTITIES_ANSWER.
897 ret[4] = SSH2_AGENT_IDENTITIES_ANSWER;
898 keylist = make_keylist2(&len);
899 if (len + 5 > AGENT_MAX_MSGLEN) {
903 PUT_32BIT(ret, len + 1);
904 memcpy(ret + 5, keylist, len);
908 case SSH1_AGENTC_RSA_CHALLENGE:
910 * Reply with either SSH1_AGENT_RSA_RESPONSE or
911 * SSH_AGENT_FAILURE, depending on whether we have that key
915 struct RSAKey reqkey, *key;
916 Bignum challenge, response;
917 unsigned char response_source[48], response_md5[16];
918 struct MD5Context md5c;
922 i = ssh1_read_bignum(p, msgend - p, &reqkey.exponent);
926 i = ssh1_read_bignum(p, msgend - p, &reqkey.modulus);
930 i = ssh1_read_bignum(p, msgend - p, &challenge);
935 freebn(reqkey.exponent);
936 freebn(reqkey.modulus);
940 memcpy(response_source + 32, p, 16);
944 (key = find234(rsakeys, &reqkey, NULL)) == NULL) {
945 freebn(reqkey.exponent);
946 freebn(reqkey.modulus);
950 response = rsadecrypt(challenge, key);
951 for (i = 0; i < 32; i++)
952 response_source[i] = bignum_byte(response, 31 - i);
955 MD5Update(&md5c, response_source, 48);
956 MD5Final(response_md5, &md5c);
957 memset(response_source, 0, 48); /* burn the evidence */
958 freebn(response); /* and that evidence */
959 freebn(challenge); /* yes, and that evidence */
960 freebn(reqkey.exponent); /* and free some memory ... */
961 freebn(reqkey.modulus); /* ... while we're at it. */
964 * Packet is the obvious five byte header, plus sixteen
968 PUT_32BIT(ret, len - 4);
969 ret[4] = SSH1_AGENT_RSA_RESPONSE;
970 memcpy(ret + 5, response_md5, 16);
973 case SSH2_AGENTC_SIGN_REQUEST:
975 * Reply with either SSH2_AGENT_SIGN_RESPONSE or
976 * SSH_AGENT_FAILURE, depending on whether we have that key
980 struct ssh2_userkey *key;
982 unsigned char *data, *signature;
983 int datalen, siglen, len;
987 b.len = GET_32BIT(p);
989 if (msgend < p+b.len)
995 datalen = GET_32BIT(p);
997 if (msgend < p+datalen)
1000 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
1003 signature = key->alg->sign(key->data, data, datalen, &siglen);
1004 len = 5 + 4 + siglen;
1005 PUT_32BIT(ret, len - 4);
1006 ret[4] = SSH2_AGENT_SIGN_RESPONSE;
1007 PUT_32BIT(ret + 5, siglen);
1008 memcpy(ret + 5 + 4, signature, siglen);
1012 case SSH1_AGENTC_ADD_RSA_IDENTITY:
1014 * Add to the list and return SSH_AGENT_SUCCESS, or
1015 * SSH_AGENT_FAILURE if the key was malformed.
1022 key = snew(struct RSAKey);
1023 memset(key, 0, sizeof(struct RSAKey));
1025 n = makekey(p, msgend - p, key, NULL, 1);
1033 n = makeprivate(p, msgend - p, key);
1041 n = ssh1_read_bignum(p, msgend - p, &key->iqmp); /* p^-1 mod q */
1049 n = ssh1_read_bignum(p, msgend - p, &key->p); /* p */
1057 n = ssh1_read_bignum(p, msgend - p, &key->q); /* q */
1070 commentlen = GET_32BIT(p);
1072 if (msgend < p+commentlen) {
1078 comment = snewn(commentlen+1, char);
1080 memcpy(comment, p + 4, commentlen);
1081 comment[commentlen] = '\0';
1082 key->comment = comment;
1085 ret[4] = SSH_AGENT_FAILURE;
1086 if (add234(rsakeys, key) == key) {
1088 ret[4] = SSH_AGENT_SUCCESS;
1095 case SSH2_AGENTC_ADD_IDENTITY:
1097 * Add to the list and return SSH_AGENT_SUCCESS, or
1098 * SSH_AGENT_FAILURE if the key was malformed.
1101 struct ssh2_userkey *key;
1102 char *comment, *alg;
1103 int alglen, commlen;
1109 alglen = GET_32BIT(p);
1111 if (msgend < p+alglen)
1116 key = snew(struct ssh2_userkey);
1117 /* Add further algorithm names here. */
1118 if (alglen == 7 && !memcmp(alg, "ssh-rsa", 7))
1119 key->alg = &ssh_rsa;
1120 else if (alglen == 7 && !memcmp(alg, "ssh-dss", 7))
1121 key->alg = &ssh_dss;
1127 bloblen = msgend - p;
1128 key->data = key->alg->openssh_createkey(&p, &bloblen);
1135 * p has been advanced by openssh_createkey, but
1136 * certainly not _beyond_ the end of the buffer.
1138 assert(p <= msgend);
1141 key->alg->freekey(key->data);
1145 commlen = GET_32BIT(p);
1148 if (msgend < p+commlen) {
1149 key->alg->freekey(key->data);
1153 comment = snewn(commlen + 1, char);
1155 memcpy(comment, p, commlen);
1156 comment[commlen] = '\0';
1158 key->comment = comment;
1161 ret[4] = SSH_AGENT_FAILURE;
1162 if (add234(ssh2keys, key) == key) {
1164 ret[4] = SSH_AGENT_SUCCESS;
1166 key->alg->freekey(key->data);
1167 sfree(key->comment);
1172 case SSH1_AGENTC_REMOVE_RSA_IDENTITY:
1174 * Remove from the list and return SSH_AGENT_SUCCESS, or
1175 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
1179 struct RSAKey reqkey, *key;
1182 n = makekey(p, msgend - p, &reqkey, NULL, 0);
1186 key = find234(rsakeys, &reqkey, NULL);
1187 freebn(reqkey.exponent);
1188 freebn(reqkey.modulus);
1190 ret[4] = SSH_AGENT_FAILURE;
1192 del234(rsakeys, key);
1196 ret[4] = SSH_AGENT_SUCCESS;
1200 case SSH2_AGENTC_REMOVE_IDENTITY:
1202 * Remove from the list and return SSH_AGENT_SUCCESS, or
1203 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
1207 struct ssh2_userkey *key;
1212 b.len = GET_32BIT(p);
1215 if (msgend < p+b.len)
1220 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
1225 ret[4] = SSH_AGENT_FAILURE;
1227 del234(ssh2keys, key);
1229 key->alg->freekey(key->data);
1231 ret[4] = SSH_AGENT_SUCCESS;
1235 case SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES:
1237 * Remove all SSH-1 keys. Always returns success.
1240 struct RSAKey *rkey;
1242 while ((rkey = index234(rsakeys, 0)) != NULL) {
1243 del234(rsakeys, rkey);
1250 ret[4] = SSH_AGENT_SUCCESS;
1253 case SSH2_AGENTC_REMOVE_ALL_IDENTITIES:
1255 * Remove all SSH-2 keys. Always returns success.
1258 struct ssh2_userkey *skey;
1260 while ((skey = index234(ssh2keys, 0)) != NULL) {
1261 del234(ssh2keys, skey);
1262 skey->alg->freekey(skey->data);
1268 ret[4] = SSH_AGENT_SUCCESS;
1274 * Unrecognised message. Return SSH_AGENT_FAILURE.
1277 ret[4] = SSH_AGENT_FAILURE;
1283 * Key comparison function for the 2-3-4 tree of RSA keys.
1285 static int cmpkeys_rsa(void *av, void *bv)
1287 struct RSAKey *a = (struct RSAKey *) av;
1288 struct RSAKey *b = (struct RSAKey *) bv;
1295 * Compare by length of moduli.
1297 alen = bignum_bitcount(am);
1298 blen = bignum_bitcount(bm);
1301 else if (alen < blen)
1304 * Now compare by moduli themselves.
1306 alen = (alen + 7) / 8; /* byte count */
1307 while (alen-- > 0) {
1309 abyte = bignum_byte(am, alen);
1310 bbyte = bignum_byte(bm, alen);
1313 else if (abyte < bbyte)
1323 * Key comparison function for the 2-3-4 tree of SSH-2 keys.
1325 static int cmpkeys_ssh2(void *av, void *bv)
1327 struct ssh2_userkey *a = (struct ssh2_userkey *) av;
1328 struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
1331 unsigned char *ablob, *bblob;
1335 * Compare purely by public blob.
1337 ablob = a->alg->public_blob(a->data, &alen);
1338 bblob = b->alg->public_blob(b->data, &blen);
1341 for (i = 0; i < alen && i < blen; i++) {
1342 if (ablob[i] < bblob[i]) {
1345 } else if (ablob[i] > bblob[i]) {
1350 if (c == 0 && i < alen)
1351 c = +1; /* a is longer */
1352 if (c == 0 && i < blen)
1353 c = -1; /* a is longer */
1362 * Key comparison function for looking up a blob in the 2-3-4 tree
1365 static int cmpkeys_ssh2_asymm(void *av, void *bv)
1367 struct blob *a = (struct blob *) av;
1368 struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
1371 unsigned char *ablob, *bblob;
1375 * Compare purely by public blob.
1379 bblob = b->alg->public_blob(b->data, &blen);
1382 for (i = 0; i < alen && i < blen; i++) {
1383 if (ablob[i] < bblob[i]) {
1386 } else if (ablob[i] > bblob[i]) {
1391 if (c == 0 && i < alen)
1392 c = +1; /* a is longer */
1393 if (c == 0 && i < blen)
1394 c = -1; /* a is longer */
1402 * Prompt for a key file to add, and add it.
1404 static void prompt_add_keyfile(void)
1407 char *filelist = snewn(8192, char);
1409 if (!keypath) keypath = filereq_new();
1410 memset(&of, 0, sizeof(of));
1411 of.hwndOwner = hwnd;
1412 of.lpstrFilter = FILTER_KEY_FILES;
1413 of.lpstrCustomFilter = NULL;
1414 of.nFilterIndex = 1;
1415 of.lpstrFile = filelist;
1418 of.lpstrFileTitle = NULL;
1419 of.lpstrTitle = "Select Private Key File";
1420 of.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER;
1421 if (request_file(keypath, &of, TRUE, FALSE)) {
1422 if(strlen(filelist) > of.nFileOffset)
1423 /* Only one filename returned? */
1424 add_keyfile(filename_from_str(filelist));
1426 /* we are returned a bunch of strings, end to
1427 * end. first string is the directory, the
1428 * rest the filenames. terminated with an
1431 char *dir = filelist;
1432 char *filewalker = filelist + strlen(dir) + 1;
1433 while (*filewalker != '\0') {
1434 char *filename = dupcat(dir, "\\", filewalker, NULL);
1435 add_keyfile(filename_from_str(filename));
1437 filewalker += strlen(filewalker) + 1;
1442 forget_passphrases();
1448 * Dialog-box function for the key list box.
1450 static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
1451 WPARAM wParam, LPARAM lParam)
1453 struct RSAKey *rkey;
1454 struct ssh2_userkey *skey;
1459 * Centre the window.
1461 { /* centre the window */
1465 hw = GetDesktopWindow();
1466 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
1468 (rs.right + rs.left + rd.left - rd.right) / 2,
1469 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
1470 rd.right - rd.left, rd.bottom - rd.top, TRUE);
1474 SetWindowLongPtr(hwnd, GWL_EXSTYLE,
1475 GetWindowLongPtr(hwnd, GWL_EXSTYLE) |
1478 HWND item = GetDlgItem(hwnd, 103); /* the Help button */
1480 DestroyWindow(item);
1482 requested_help = FALSE;
1486 static int tabs[] = { 35, 60, 210 };
1487 SendDlgItemMessage(hwnd, 100, LB_SETTABSTOPS,
1488 sizeof(tabs) / sizeof(*tabs),
1494 switch (LOWORD(wParam)) {
1498 DestroyWindow(hwnd);
1500 case 101: /* add key */
1501 if (HIWORD(wParam) == BN_CLICKED ||
1502 HIWORD(wParam) == BN_DOUBLECLICKED) {
1503 if (passphrase_box) {
1504 MessageBeep(MB_ICONERROR);
1505 SetForegroundWindow(passphrase_box);
1508 prompt_add_keyfile();
1511 case 102: /* remove key */
1512 if (HIWORD(wParam) == BN_CLICKED ||
1513 HIWORD(wParam) == BN_DOUBLECLICKED) {
1518 /* our counter within the array of selected items */
1521 /* get the number of items selected in the list */
1523 SendDlgItemMessage(hwnd, 100, LB_GETSELCOUNT, 0, 0);
1525 /* none selected? that was silly */
1526 if (numSelected == 0) {
1531 /* get item indices in an array */
1532 selectedArray = snewn(numSelected, int);
1533 SendDlgItemMessage(hwnd, 100, LB_GETSELITEMS,
1534 numSelected, (WPARAM)selectedArray);
1536 itemNum = numSelected - 1;
1537 rCount = count234(rsakeys);
1538 sCount = count234(ssh2keys);
1540 /* go through the non-rsakeys until we've covered them all,
1541 * and/or we're out of selected items to check. note that
1542 * we go *backwards*, to avoid complications from deleting
1543 * things hence altering the offset of subsequent items
1545 for (i = sCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1546 skey = index234(ssh2keys, i);
1548 if (selectedArray[itemNum] == rCount + i) {
1549 del234(ssh2keys, skey);
1550 skey->alg->freekey(skey->data);
1556 /* do the same for the rsa keys */
1557 for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1558 rkey = index234(rsakeys, i);
1560 if(selectedArray[itemNum] == i) {
1561 del234(rsakeys, rkey);
1568 sfree(selectedArray);
1572 case 103: /* help */
1573 if (HIWORD(wParam) == BN_CLICKED ||
1574 HIWORD(wParam) == BN_DOUBLECLICKED) {
1576 WinHelp(hwnd, help_path, HELP_COMMAND,
1577 (DWORD)"JI(`',`pageant.general')");
1578 requested_help = TRUE;
1586 int id = ((LPHELPINFO)lParam)->iCtrlId;
1589 case 100: topic = "pageant.keylist"; break;
1590 case 101: topic = "pageant.addkey"; break;
1591 case 102: topic = "pageant.remkey"; break;
1594 char *cmd = dupprintf("JI(`',`%s')", topic);
1595 WinHelp(hwnd, help_path, HELP_COMMAND, (DWORD)cmd);
1597 requested_help = TRUE;
1605 DestroyWindow(hwnd);
1611 /* Set up a system tray icon */
1612 static BOOL AddTrayIcon(HWND hwnd)
1615 NOTIFYICONDATA tnid;
1618 #ifdef NIM_SETVERSION
1620 res = Shell_NotifyIcon(NIM_SETVERSION, &tnid);
1623 tnid.cbSize = sizeof(NOTIFYICONDATA);
1625 tnid.uID = 1; /* unique within this systray use */
1626 tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
1627 tnid.uCallbackMessage = WM_SYSTRAY;
1628 tnid.hIcon = hicon = LoadIcon(hinst, MAKEINTRESOURCE(201));
1629 strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
1631 res = Shell_NotifyIcon(NIM_ADD, &tnid);
1633 if (hicon) DestroyIcon(hicon);
1638 /* Update the saved-sessions menu. */
1639 static void update_sessions(void)
1643 TCHAR buf[MAX_PATH + 1];
1646 int index_key, index_menu;
1651 if(ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, PUTTY_REGKEY, &hkey))
1654 for(num_entries = GetMenuItemCount(session_menu);
1655 num_entries > initial_menuitems_count;
1657 RemoveMenu(session_menu, 0, MF_BYPOSITION);
1662 while(ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) {
1663 TCHAR session_name[MAX_PATH + 1];
1664 unmungestr(buf, session_name, MAX_PATH);
1665 if(strcmp(buf, PUTTY_DEFAULT) != 0) {
1666 memset(&mii, 0, sizeof(mii));
1667 mii.cbSize = sizeof(mii);
1668 mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
1669 mii.fType = MFT_STRING;
1670 mii.fState = MFS_ENABLED;
1671 mii.wID = (index_menu * 16) + IDM_SESSIONS_BASE;
1672 mii.dwTypeData = session_name;
1673 InsertMenuItem(session_menu, index_menu, TRUE, &mii);
1681 if(index_menu == 0) {
1682 mii.cbSize = sizeof(mii);
1683 mii.fMask = MIIM_TYPE | MIIM_STATE;
1684 mii.fType = MFT_STRING;
1685 mii.fState = MFS_GRAYED;
1686 mii.dwTypeData = _T("(No sessions)");
1687 InsertMenuItem(session_menu, index_menu, TRUE, &mii);
1691 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1692 WPARAM wParam, LPARAM lParam)
1695 static int menuinprogress;
1696 static UINT msgTaskbarCreated = 0;
1700 msgTaskbarCreated = RegisterWindowMessage(_T("TaskbarCreated"));
1703 if (message==msgTaskbarCreated) {
1705 * Explorer has been restarted, so the tray icon will
1713 if (lParam == WM_RBUTTONUP) {
1715 GetCursorPos(&cursorpos);
1716 PostMessage(hwnd, WM_SYSTRAY2, cursorpos.x, cursorpos.y);
1717 } else if (lParam == WM_LBUTTONDBLCLK) {
1718 /* Run the default menu item. */
1719 UINT menuitem = GetMenuDefaultItem(systray_menu, FALSE, 0);
1721 PostMessage(hwnd, WM_COMMAND, menuitem, 0);
1725 if (!menuinprogress) {
1728 SetForegroundWindow(hwnd);
1729 ret = TrackPopupMenu(systray_menu,
1730 TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
1732 wParam, lParam, 0, hwnd, NULL);
1738 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1740 if((int)ShellExecute(hwnd, NULL, putty_path, _T(""), _T(""),
1742 MessageBox(NULL, "Unable to execute PuTTY!",
1743 "Error", MB_OK | MB_ICONERROR);
1748 SendMessage(passphrase_box, WM_CLOSE, 0, 0);
1749 SendMessage(hwnd, WM_CLOSE, 0, 0);
1753 keylist = CreateDialog(hinst, MAKEINTRESOURCE(211),
1755 ShowWindow(keylist, SW_SHOWNORMAL);
1758 * Sometimes the window comes up minimised / hidden for
1759 * no obvious reason. Prevent this. This also brings it
1760 * to the front if it's already present (the user
1761 * selected View Keys because they wanted to _see_ the
1764 SetForegroundWindow(keylist);
1765 SetWindowPos(keylist, HWND_TOP, 0, 0, 0, 0,
1766 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1769 if (passphrase_box) {
1770 MessageBeep(MB_ICONERROR);
1771 SetForegroundWindow(passphrase_box);
1774 prompt_add_keyfile();
1778 aboutbox = CreateDialog(hinst, MAKEINTRESOURCE(213),
1780 ShowWindow(aboutbox, SW_SHOWNORMAL);
1782 * Sometimes the window comes up minimised / hidden
1783 * for no obvious reason. Prevent this.
1785 SetForegroundWindow(aboutbox);
1786 SetWindowPos(aboutbox, HWND_TOP, 0, 0, 0, 0,
1787 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1792 WinHelp(hwnd, help_path, HELP_COMMAND,
1793 (DWORD)"JI(`',`pageant.general')");
1794 requested_help = TRUE;
1799 if(wParam >= IDM_SESSIONS_BASE && wParam <= IDM_SESSIONS_MAX) {
1801 TCHAR buf[MAX_PATH + 1];
1802 TCHAR param[MAX_PATH + 1];
1803 memset(&mii, 0, sizeof(mii));
1804 mii.cbSize = sizeof(mii);
1805 mii.fMask = MIIM_TYPE;
1807 mii.dwTypeData = buf;
1808 GetMenuItemInfo(session_menu, wParam, FALSE, &mii);
1810 strcat(param, mii.dwTypeData);
1811 if((int)ShellExecute(hwnd, NULL, putty_path, param,
1812 _T(""), SW_SHOW) <= 32) {
1813 MessageBox(NULL, "Unable to execute PuTTY!", "Error",
1814 MB_OK | MB_ICONERROR);
1822 if (requested_help) {
1823 WinHelp(hwnd, help_path, HELP_QUIT, 0);
1824 requested_help = FALSE;
1830 COPYDATASTRUCT *cds;
1836 PSID mapowner, procowner;
1837 PSECURITY_DESCRIPTOR psd1 = NULL, psd2 = NULL;
1841 cds = (COPYDATASTRUCT *) lParam;
1842 if (cds->dwData != AGENT_COPYDATA_ID)
1843 return 0; /* not our message, mate */
1844 mapname = (char *) cds->lpData;
1845 if (mapname[cds->cbData - 1] != '\0')
1846 return 0; /* failure to be ASCIZ! */
1848 debug(("mapname is :%s:\n", mapname));
1850 filemap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, mapname);
1852 debug(("filemap is %p\n", filemap));
1854 if (filemap != NULL && filemap != INVALID_HANDLE_VALUE) {
1858 if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE,
1859 GetCurrentProcessId())) ==
1862 debug(("couldn't get handle for process\n"));
1866 if (getsecurityinfo(proc, SE_KERNEL_OBJECT,
1867 OWNER_SECURITY_INFORMATION,
1868 &procowner, NULL, NULL, NULL,
1869 &psd2) != ERROR_SUCCESS) {
1871 debug(("couldn't get owner info for process\n"));
1874 return 0; /* unable to get security info */
1877 if ((rc = getsecurityinfo(filemap, SE_KERNEL_OBJECT,
1878 OWNER_SECURITY_INFORMATION,
1879 &mapowner, NULL, NULL, NULL,
1880 &psd1) != ERROR_SUCCESS)) {
1883 ("couldn't get owner info for filemap: %d\n",
1889 debug(("got security stuff\n"));
1891 if (!EqualSid(mapowner, procowner))
1892 return 0; /* security ID mismatch! */
1894 debug(("security stuff matched\n"));
1900 debug(("security APIs not present\n"));
1904 p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
1906 debug(("p is %p\n", p));
1909 for (i = 0; i < 5; i++)
1912 ((unsigned char *) p)[i]));}
1918 CloseHandle(filemap);
1923 return DefWindowProc(hwnd, message, wParam, lParam);
1927 * Fork and Exec the command in cmdline. [DBW]
1929 void spawn_cmd(char *cmdline, char * args, int show)
1931 if (ShellExecute(NULL, _T("open"), cmdline,
1932 args, NULL, show) <= (HINSTANCE) 32) {
1934 msg = dupprintf("Failed to run \"%.100s\", Error: %d", cmdline,
1935 (int)GetLastError());
1936 MessageBox(NULL, msg, APPNAME, MB_OK | MB_ICONEXCLAMATION);
1942 * This is a can't-happen stub, since Pageant never makes
1943 * asynchronous agent requests.
1945 void agent_schedule_callback(void (*callback)(void *, void *, int),
1946 void *callback_ctx, void *data, int len)
1948 assert(!"We shouldn't get here");
1951 void cleanup_exit(int code) { exit(code); }
1953 int flags = FLAG_SYNCAGENT;
1955 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
1960 char *command = NULL;
1963 char **argv, **argstart;
1969 * Determine whether we're an NT system (should have security
1970 * APIs) or a non-NT system (don't do security).
1974 modalfatalbox("Windows refuses to report a version");
1976 if (osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT) {
1977 has_security = TRUE;
1979 has_security = FALSE;
1984 * Attempt to get the security API we need.
1986 advapi = LoadLibrary("ADVAPI32.DLL");
1988 (gsi_fn_t) GetProcAddress(advapi, "GetSecurityInfo");
1989 if (!getsecurityinfo) {
1991 "Unable to access security APIs. Pageant will\n"
1992 "not run, in case it causes a security breach.",
1993 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
1998 "This program has been compiled for Win9X and will\n"
1999 "not run on NT, in case it causes a security breach.",
2000 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
2007 * See if we can find our Help file.
2010 char b[2048], *p, *q, *r;
2012 GetModuleFileName(NULL, b, sizeof(b) - 1);
2014 p = strrchr(b, '\\');
2015 if (p && p >= r) r = p+1;
2016 q = strrchr(b, ':');
2017 if (q && q >= r) r = q+1;
2018 strcpy(r, PUTTY_HELP_FILE);
2019 if ( (fp = fopen(b, "r")) != NULL) {
2020 help_path = dupstr(b);
2027 * Look for the PuTTY binary (we will enable the saved session
2028 * submenu if we find it).
2031 char b[2048], *p, *q, *r;
2033 GetModuleFileName(NULL, b, sizeof(b) - 1);
2035 p = strrchr(b, '\\');
2036 if (p && p >= r) r = p+1;
2037 q = strrchr(b, ':');
2038 if (q && q >= r) r = q+1;
2039 strcpy(r, "putty.exe");
2040 if ( (fp = fopen(b, "r")) != NULL) {
2041 putty_path = dupstr(b);
2048 * Find out if Pageant is already running.
2050 already_running = agent_exists();
2053 * Initialise storage for RSA keys.
2055 if (!already_running) {
2056 rsakeys = newtree234(cmpkeys_rsa);
2057 ssh2keys = newtree234(cmpkeys_ssh2);
2061 * Initialise storage for short-term passphrase cache.
2063 passphrases = newtree234(NULL);
2066 * Process the command line and add keys as listed on it.
2068 split_into_argv(cmdline, &argc, &argv, &argstart);
2069 for (i = 0; i < argc; i++) {
2070 if (!strcmp(argv[i], "-pgpfp")) {
2073 FreeLibrary(advapi);
2075 } else if (!strcmp(argv[i], "-c")) {
2077 * If we see `-c', then the rest of the
2078 * command line should be treated as a
2079 * command to be spawned.
2082 command = argstart[i+1];
2087 add_keyfile(filename_from_str(argv[i]));
2093 * Forget any passphrase that we retained while going over
2094 * command line keyfiles.
2096 forget_passphrases();
2100 if (command[0] == '"')
2101 args = strchr(++command, '"');
2103 args = strchr(command, ' ');
2106 while(*args && isspace(*args)) args++;
2108 spawn_cmd(command, args, show);
2112 * If Pageant was already running, we leave now. If we haven't
2113 * even taken any auxiliary action (spawned a command or added
2116 if (already_running) {
2117 if (!command && !added_keys) {
2118 MessageBox(NULL, "Pageant is already running", "Pageant Error",
2119 MB_ICONERROR | MB_OK);
2122 FreeLibrary(advapi);
2128 wndclass.lpfnWndProc = WndProc;
2129 wndclass.cbClsExtra = 0;
2130 wndclass.cbWndExtra = 0;
2131 wndclass.hInstance = inst;
2132 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
2133 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
2134 wndclass.hbrBackground = GetStockObject(BLACK_BRUSH);
2135 wndclass.lpszMenuName = NULL;
2136 wndclass.lpszClassName = APPNAME;
2138 RegisterClass(&wndclass);
2143 hwnd = CreateWindow(APPNAME, APPNAME,
2144 WS_OVERLAPPEDWINDOW | WS_VSCROLL,
2145 CW_USEDEFAULT, CW_USEDEFAULT,
2146 100, 100, NULL, NULL, inst, NULL);
2148 /* Set up a system tray icon */
2151 /* Accelerators used: nsvkxa */
2152 systray_menu = CreatePopupMenu();
2154 session_menu = CreateMenu();
2155 AppendMenu(systray_menu, MF_ENABLED, IDM_PUTTY, "&New Session");
2156 AppendMenu(systray_menu, MF_POPUP | MF_ENABLED,
2157 (UINT) session_menu, "&Saved Sessions");
2158 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
2160 AppendMenu(systray_menu, MF_ENABLED, IDM_VIEWKEYS,
2162 AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key");
2163 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
2165 AppendMenu(systray_menu, MF_ENABLED, IDM_HELP, "&Help");
2166 AppendMenu(systray_menu, MF_ENABLED, IDM_ABOUT, "&About");
2167 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
2168 AppendMenu(systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit");
2169 initial_menuitems_count = GetMenuItemCount(session_menu);
2171 /* Set the default menu item. */
2172 SetMenuDefaultItem(systray_menu, IDM_VIEWKEYS, FALSE);
2174 ShowWindow(hwnd, SW_HIDE);
2177 * Main message loop.
2179 while (GetMessage(&msg, NULL, 0, 0) == 1) {
2180 if (!(IsWindow(keylist) && IsDialogMessage(keylist, &msg)) &&
2181 !(IsWindow(aboutbox) && IsDialogMessage(aboutbox, &msg))) {
2182 TranslateMessage(&msg);
2183 DispatchMessage(&msg);
2187 /* Clean up the system tray icon */
2189 NOTIFYICONDATA tnid;
2191 tnid.cbSize = sizeof(NOTIFYICONDATA);
2195 Shell_NotifyIcon(NIM_DELETE, &tnid);
2197 DestroyMenu(systray_menu);
2200 if (keypath) filereq_free(keypath);
2203 FreeLibrary(advapi);