Rework keylist_update() to fix both a buffer-size limitation and a
[u/mdw/putty] / windows / winpgnt.c
1 /*
2 * Pageant: the PuTTY Authentication Agent.
3 */
4
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <ctype.h>
8 #include <assert.h>
9 #include <tchar.h>
10
11 #define PUTTY_DO_GLOBALS
12
13 #include "putty.h"
14 #include "ssh.h"
15 #include "misc.h"
16 #include "tree234.h"
17
18 #include <shellapi.h>
19
20 #ifndef NO_SECURITY
21 #include <aclapi.h>
22 #ifdef DEBUG_IPC
23 #define _WIN32_WINNT 0x0500 /* for ConvertSidToStringSid */
24 #include <sddl.h>
25 #endif
26 #endif
27
28 #define IDI_MAINICON 200
29 #define IDI_TRAYICON 201
30
31 #define WM_SYSTRAY (WM_APP + 6)
32 #define WM_SYSTRAY2 (WM_APP + 7)
33
34 #define AGENT_COPYDATA_ID 0x804e50ba /* random goop */
35
36 /*
37 * FIXME: maybe some day we can sort this out ...
38 */
39 #define AGENT_MAX_MSGLEN 8192
40
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. */
45
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
51
52 #define APPNAME "Pageant"
53
54 extern char ver[];
55
56 static HWND keylist;
57 static HWND aboutbox;
58 static HMENU systray_menu, session_menu;
59 static int already_running;
60
61 static char *putty_path;
62
63 /* CWD for "add key" file requester. */
64 static filereq *keypath = NULL;
65
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;
72
73 /*
74 * Print a modal (Really Bad) message box and perform a fatal exit.
75 */
76 void modalfatalbox(char *fmt, ...)
77 {
78 va_list ap;
79 char *buf;
80
81 va_start(ap, fmt);
82 buf = dupvprintf(fmt, ap);
83 va_end(ap);
84 MessageBox(hwnd, buf, "Pageant Fatal Error",
85 MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
86 sfree(buf);
87 exit(1);
88 }
89
90 /* Un-munge session names out of the registry. */
91 static void unmungestr(char *in, char *out, int outlen)
92 {
93 while (*in) {
94 if (*in == '%' && in[1] && in[2]) {
95 int i, j;
96
97 i = in[1] - '0';
98 i -= (i > 9 ? 7 : 0);
99 j = in[2] - '0';
100 j -= (j > 9 ? 7 : 0);
101
102 *out++ = (i << 4) + j;
103 if (!--outlen)
104 return;
105 in += 3;
106 } else {
107 *out++ = *in++;
108 if (!--outlen)
109 return;
110 }
111 }
112 *out = '\0';
113 return;
114 }
115
116 static tree234 *rsakeys, *ssh2keys;
117
118 static int has_security;
119 #ifndef NO_SECURITY
120 DECL_WINDOWS_FUNCTION(extern, DWORD, GetSecurityInfo,
121 (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION,
122 PSID *, PSID *, PACL *, PACL *,
123 PSECURITY_DESCRIPTOR *));
124 #endif
125
126 /*
127 * Forward references
128 */
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);
133
134 /*
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
138 * never be called.
139 *
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.
143 */
144 int random_byte(void)
145 {
146 MessageBox(hwnd, "Internal Error", APPNAME, MB_OK | MB_ICONERROR);
147 exit(0);
148 /* this line can't be reached but it placates MSVC's warnings :-) */
149 return 0;
150 }
151
152 /*
153 * Blob structure for passing to the asymmetric SSH-2 key compare
154 * function, prototyped here.
155 */
156 struct blob {
157 unsigned char *blob;
158 int len;
159 };
160 static int cmpkeys_ssh2_asymm(void *av, void *bv);
161
162 struct PassphraseProcStruct {
163 char **passphrase;
164 char *comment;
165 };
166
167 static tree234 *passphrases = NULL;
168
169 /*
170 * After processing a list of filenames, we want to forget the
171 * passphrases.
172 */
173 static void forget_passphrases(void)
174 {
175 while (count234(passphrases) > 0) {
176 char *pp = index234(passphrases, 0);
177 smemclr(pp, strlen(pp));
178 delpos234(passphrases, 0);
179 free(pp);
180 }
181 }
182
183 /*
184 * Dialog-box function for the Licence box.
185 */
186 static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
187 WPARAM wParam, LPARAM lParam)
188 {
189 switch (msg) {
190 case WM_INITDIALOG:
191 return 1;
192 case WM_COMMAND:
193 switch (LOWORD(wParam)) {
194 case IDOK:
195 case IDCANCEL:
196 EndDialog(hwnd, 1);
197 return 0;
198 }
199 return 0;
200 case WM_CLOSE:
201 EndDialog(hwnd, 1);
202 return 0;
203 }
204 return 0;
205 }
206
207 /*
208 * Dialog-box function for the About box.
209 */
210 static int CALLBACK AboutProc(HWND hwnd, UINT msg,
211 WPARAM wParam, LPARAM lParam)
212 {
213 switch (msg) {
214 case WM_INITDIALOG:
215 SetDlgItemText(hwnd, 100, ver);
216 return 1;
217 case WM_COMMAND:
218 switch (LOWORD(wParam)) {
219 case IDOK:
220 case IDCANCEL:
221 aboutbox = NULL;
222 DestroyWindow(hwnd);
223 return 0;
224 case 101:
225 EnableWindow(hwnd, 0);
226 DialogBox(hinst, MAKEINTRESOURCE(214), hwnd, LicenceProc);
227 EnableWindow(hwnd, 1);
228 SetActiveWindow(hwnd);
229 return 0;
230 }
231 return 0;
232 case WM_CLOSE:
233 aboutbox = NULL;
234 DestroyWindow(hwnd);
235 return 0;
236 }
237 return 0;
238 }
239
240 static HWND passphrase_box;
241
242 /*
243 * Dialog-box function for the passphrase box.
244 */
245 static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
246 WPARAM wParam, LPARAM lParam)
247 {
248 static char **passphrase = NULL;
249 struct PassphraseProcStruct *p;
250
251 switch (msg) {
252 case WM_INITDIALOG:
253 passphrase_box = hwnd;
254 /*
255 * Centre the window.
256 */
257 { /* centre the window */
258 RECT rs, rd;
259 HWND hw;
260
261 hw = GetDesktopWindow();
262 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
263 MoveWindow(hwnd,
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);
267 }
268
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;
274 if (p->comment)
275 SetDlgItemText(hwnd, 101, p->comment);
276 burnstr(*passphrase);
277 *passphrase = dupstr("");
278 SetDlgItemText(hwnd, 102, *passphrase);
279 return 0;
280 case WM_COMMAND:
281 switch (LOWORD(wParam)) {
282 case IDOK:
283 if (*passphrase)
284 EndDialog(hwnd, 1);
285 else
286 MessageBeep(0);
287 return 0;
288 case IDCANCEL:
289 EndDialog(hwnd, 0);
290 return 0;
291 case 102: /* edit box */
292 if ((HIWORD(wParam) == EN_CHANGE) && passphrase) {
293 burnstr(*passphrase);
294 *passphrase = GetDlgItemText_alloc(hwnd, 102);
295 }
296 return 0;
297 }
298 return 0;
299 case WM_CLOSE:
300 EndDialog(hwnd, 0);
301 return 0;
302 }
303 return 0;
304 }
305
306 /*
307 * Warn about the obsolescent key file format.
308 */
309 void old_keyfile_warning(void)
310 {
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"
318 "format.\n"
319 "\n"
320 "You can perform this conversion by loading the key\n"
321 "into PuTTYgen and then saving it again.";
322
323 MessageBox(NULL, message, mbtitle, MB_OK);
324 }
325
326 /*
327 * Update the visible key list.
328 */
329 static void keylist_update(void)
330 {
331 struct RSAKey *rkey;
332 struct ssh2_userkey *skey;
333 int i;
334
335 if (keylist) {
336 SendDlgItemMessage(keylist, 100, LB_RESETCONTENT, 0, 0);
337 for (i = 0; NULL != (rkey = index234(rsakeys, i)); i++) {
338 char listentry[512], *p;
339 /*
340 * Replace two spaces in the fingerprint with tabs, for
341 * nice alignment in the box.
342 */
343 strcpy(listentry, "ssh1\t");
344 p = listentry + strlen(listentry);
345 rsa_fingerprint(p, sizeof(listentry) - (p - listentry), rkey);
346 p = strchr(listentry, ' ');
347 if (p)
348 *p = '\t';
349 p = strchr(listentry, ' ');
350 if (p)
351 *p = '\t';
352 SendDlgItemMessage(keylist, 100, LB_ADDSTRING,
353 0, (LPARAM) listentry);
354 }
355 for (i = 0; NULL != (skey = index234(ssh2keys, i)); i++) {
356 char *listentry, *p;
357 int fp_len;
358 /*
359 * Replace two spaces in the fingerprint with tabs, for
360 * nice alignment in the box.
361 */
362 p = skey->alg->fingerprint(skey->data);
363 listentry = dupprintf("%s\t%s", p, skey->comment);
364 fp_len = strlen(listentry);
365 sfree(p);
366
367 p = strchr(listentry, ' ');
368 if (p && p < listentry + fp_len)
369 *p = '\t';
370 p = strchr(listentry, ' ');
371 if (p && p < listentry + fp_len)
372 *p = '\t';
373
374 SendDlgItemMessage(keylist, 100, LB_ADDSTRING, 0,
375 (LPARAM) listentry);
376 sfree(listentry);
377 }
378 SendDlgItemMessage(keylist, 100, LB_SETCURSEL, (WPARAM) - 1, 0);
379 }
380 }
381
382 /*
383 * This function loads a key from a file and adds it.
384 */
385 static void add_keyfile(Filename *filename)
386 {
387 char *passphrase;
388 struct RSAKey *rkey = NULL;
389 struct ssh2_userkey *skey = NULL;
390 int needs_pass;
391 int ret;
392 int attempts;
393 char *comment;
394 const char *error = NULL;
395 int type;
396 int original_pass;
397
398 type = key_type(filename);
399 if (type != SSH_KEYTYPE_SSH1 && type != SSH_KEYTYPE_SSH2) {
400 char *msg = dupprintf("Couldn't load this key (%s)",
401 key_type_to_str(type));
402 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
403 HELPCTXID(errors_cantloadkey));
404 sfree(msg);
405 return;
406 }
407
408 /*
409 * See if the key is already loaded (in the primary Pageant,
410 * which may or may not be us).
411 */
412 {
413 void *blob;
414 unsigned char *keylist, *p;
415 int i, nkeys, bloblen, keylistlen;
416
417 if (type == SSH_KEYTYPE_SSH1) {
418 if (!rsakey_pubblob(filename, &blob, &bloblen, NULL, &error)) {
419 char *msg = dupprintf("Couldn't load private key (%s)", error);
420 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
421 HELPCTXID(errors_cantloadkey));
422 sfree(msg);
423 return;
424 }
425 keylist = get_keylist1(&keylistlen);
426 } else {
427 unsigned char *blob2;
428 blob = ssh2_userkey_loadpub(filename, NULL, &bloblen,
429 NULL, &error);
430 if (!blob) {
431 char *msg = dupprintf("Couldn't load private key (%s)", error);
432 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
433 HELPCTXID(errors_cantloadkey));
434 sfree(msg);
435 return;
436 }
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);
441 sfree(blob);
442 blob = blob2;
443
444 keylist = get_keylist2(&keylistlen);
445 }
446 if (keylist) {
447 if (keylistlen < 4) {
448 MessageBox(NULL, "Received broken key list?!", APPNAME,
449 MB_OK | MB_ICONERROR);
450 return;
451 }
452 nkeys = toint(GET_32BIT(keylist));
453 if (nkeys < 0) {
454 MessageBox(NULL, "Received broken key list?!", APPNAME,
455 MB_OK | MB_ICONERROR);
456 return;
457 }
458 p = keylist + 4;
459 keylistlen -= 4;
460
461 for (i = 0; i < nkeys; i++) {
462 if (!memcmp(blob, p, bloblen)) {
463 /* Key is already present; we can now leave. */
464 sfree(keylist);
465 sfree(blob);
466 return;
467 }
468 /* Now skip over public blob */
469 if (type == SSH_KEYTYPE_SSH1) {
470 int n = rsa_public_blob_len(p, keylistlen);
471 if (n < 0) {
472 MessageBox(NULL, "Received broken key list?!", APPNAME,
473 MB_OK | MB_ICONERROR);
474 return;
475 }
476 p += n;
477 keylistlen -= n;
478 } else {
479 int n;
480 if (keylistlen < 4) {
481 MessageBox(NULL, "Received broken key list?!", APPNAME,
482 MB_OK | MB_ICONERROR);
483 return;
484 }
485 n = toint(4 + GET_32BIT(p));
486 if (n < 0 || keylistlen < n) {
487 MessageBox(NULL, "Received broken key list?!", APPNAME,
488 MB_OK | MB_ICONERROR);
489 return;
490 }
491 p += n;
492 keylistlen -= n;
493 }
494 /* Now skip over comment field */
495 {
496 int n;
497 if (keylistlen < 4) {
498 MessageBox(NULL, "Received broken key list?!", APPNAME,
499 MB_OK | MB_ICONERROR);
500 return;
501 }
502 n = toint(4 + GET_32BIT(p));
503 if (n < 0 || keylistlen < n) {
504 MessageBox(NULL, "Received broken key list?!", APPNAME,
505 MB_OK | MB_ICONERROR);
506 return;
507 }
508 p += n;
509 keylistlen -= n;
510 }
511 }
512
513 sfree(keylist);
514 }
515
516 sfree(blob);
517 }
518
519 error = NULL;
520 if (type == SSH_KEYTYPE_SSH1)
521 needs_pass = rsakey_encrypted(filename, &comment);
522 else
523 needs_pass = ssh2_userkey_encrypted(filename, &comment);
524 attempts = 0;
525 if (type == SSH_KEYTYPE_SSH1)
526 rkey = snew(struct RSAKey);
527 passphrase = NULL;
528 original_pass = 0;
529 do {
530 burnstr(passphrase);
531 passphrase = NULL;
532
533 if (needs_pass) {
534 /* try all the remembered passphrases first */
535 char *pp = index234(passphrases, attempts);
536 if(pp) {
537 passphrase = dupstr(pp);
538 } else {
539 int dlgret;
540 struct PassphraseProcStruct pps;
541
542 pps.passphrase = &passphrase;
543 pps.comment = comment;
544
545 original_pass = 1;
546 dlgret = DialogBoxParam(hinst, MAKEINTRESOURCE(210),
547 NULL, PassphraseProc, (LPARAM) &pps);
548 passphrase_box = NULL;
549 if (!dlgret) {
550 if (comment)
551 sfree(comment);
552 if (type == SSH_KEYTYPE_SSH1)
553 sfree(rkey);
554 return; /* operation cancelled */
555 }
556
557 assert(passphrase != NULL);
558 }
559 } else
560 passphrase = dupstr("");
561
562 if (type == SSH_KEYTYPE_SSH1)
563 ret = loadrsakey(filename, rkey, passphrase, &error);
564 else {
565 skey = ssh2_load_userkey(filename, passphrase, &error);
566 if (skey == SSH2_WRONG_PASSPHRASE)
567 ret = -1;
568 else if (!skey)
569 ret = 0;
570 else
571 ret = 1;
572 }
573 attempts++;
574 } while (ret == -1);
575
576 if(original_pass && ret) {
577 /* If they typed in an ok passphrase, remember it */
578 addpos234(passphrases, passphrase, 0);
579 } else {
580 /* Otherwise, destroy it */
581 burnstr(passphrase);
582 }
583 passphrase = NULL;
584
585 if (comment)
586 sfree(comment);
587 if (ret == 0) {
588 char *msg = dupprintf("Couldn't load private key (%s)", error);
589 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
590 HELPCTXID(errors_cantloadkey));
591 sfree(msg);
592 if (type == SSH_KEYTYPE_SSH1)
593 sfree(rkey);
594 return;
595 }
596 if (type == SSH_KEYTYPE_SSH1) {
597 if (already_running) {
598 unsigned char *request, *response;
599 void *vresponse;
600 int reqlen, clen, resplen, ret;
601
602 clen = strlen(rkey->comment);
603
604 reqlen = 4 + 1 + /* length, message type */
605 4 + /* bit count */
606 ssh1_bignum_length(rkey->modulus) +
607 ssh1_bignum_length(rkey->exponent) +
608 ssh1_bignum_length(rkey->private_exponent) +
609 ssh1_bignum_length(rkey->iqmp) +
610 ssh1_bignum_length(rkey->p) +
611 ssh1_bignum_length(rkey->q) + 4 + clen /* comment */
612 ;
613
614 request = snewn(reqlen, unsigned char);
615
616 request[4] = SSH1_AGENTC_ADD_RSA_IDENTITY;
617 reqlen = 5;
618 PUT_32BIT(request + reqlen, bignum_bitcount(rkey->modulus));
619 reqlen += 4;
620 reqlen += ssh1_write_bignum(request + reqlen, rkey->modulus);
621 reqlen += ssh1_write_bignum(request + reqlen, rkey->exponent);
622 reqlen +=
623 ssh1_write_bignum(request + reqlen,
624 rkey->private_exponent);
625 reqlen += ssh1_write_bignum(request + reqlen, rkey->iqmp);
626 reqlen += ssh1_write_bignum(request + reqlen, rkey->p);
627 reqlen += ssh1_write_bignum(request + reqlen, rkey->q);
628 PUT_32BIT(request + reqlen, clen);
629 memcpy(request + reqlen + 4, rkey->comment, clen);
630 reqlen += 4 + clen;
631 PUT_32BIT(request, reqlen - 4);
632
633 ret = agent_query(request, reqlen, &vresponse, &resplen,
634 NULL, NULL);
635 assert(ret == 1);
636 response = vresponse;
637 if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
638 MessageBox(NULL, "The already running Pageant "
639 "refused to add the key.", APPNAME,
640 MB_OK | MB_ICONERROR);
641
642 sfree(request);
643 sfree(response);
644 } else {
645 if (add234(rsakeys, rkey) != rkey)
646 sfree(rkey); /* already present, don't waste RAM */
647 }
648 } else {
649 if (already_running) {
650 unsigned char *request, *response;
651 void *vresponse;
652 int reqlen, alglen, clen, keybloblen, resplen, ret;
653 alglen = strlen(skey->alg->name);
654 clen = strlen(skey->comment);
655
656 keybloblen = skey->alg->openssh_fmtkey(skey->data, NULL, 0);
657
658 reqlen = 4 + 1 + /* length, message type */
659 4 + alglen + /* algorithm name */
660 keybloblen + /* key data */
661 4 + clen /* comment */
662 ;
663
664 request = snewn(reqlen, unsigned char);
665
666 request[4] = SSH2_AGENTC_ADD_IDENTITY;
667 reqlen = 5;
668 PUT_32BIT(request + reqlen, alglen);
669 reqlen += 4;
670 memcpy(request + reqlen, skey->alg->name, alglen);
671 reqlen += alglen;
672 reqlen += skey->alg->openssh_fmtkey(skey->data,
673 request + reqlen,
674 keybloblen);
675 PUT_32BIT(request + reqlen, clen);
676 memcpy(request + reqlen + 4, skey->comment, clen);
677 reqlen += clen + 4;
678 PUT_32BIT(request, reqlen - 4);
679
680 ret = agent_query(request, reqlen, &vresponse, &resplen,
681 NULL, NULL);
682 assert(ret == 1);
683 response = vresponse;
684 if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
685 MessageBox(NULL, "The already running Pageant "
686 "refused to add the key.", APPNAME,
687 MB_OK | MB_ICONERROR);
688
689 sfree(request);
690 sfree(response);
691 } else {
692 if (add234(ssh2keys, skey) != skey) {
693 skey->alg->freekey(skey->data);
694 sfree(skey); /* already present, don't waste RAM */
695 }
696 }
697 }
698 }
699
700 /*
701 * Create an SSH-1 key list in a malloc'ed buffer; return its
702 * length.
703 */
704 static void *make_keylist1(int *length)
705 {
706 int i, nkeys, len;
707 struct RSAKey *key;
708 unsigned char *blob, *p, *ret;
709 int bloblen;
710
711 /*
712 * Count up the number and length of keys we hold.
713 */
714 len = 4;
715 nkeys = 0;
716 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
717 nkeys++;
718 blob = rsa_public_blob(key, &bloblen);
719 len += bloblen;
720 sfree(blob);
721 len += 4 + strlen(key->comment);
722 }
723
724 /* Allocate the buffer. */
725 p = ret = snewn(len, unsigned char);
726 if (length) *length = len;
727
728 PUT_32BIT(p, nkeys);
729 p += 4;
730 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
731 blob = rsa_public_blob(key, &bloblen);
732 memcpy(p, blob, bloblen);
733 p += bloblen;
734 sfree(blob);
735 PUT_32BIT(p, strlen(key->comment));
736 memcpy(p + 4, key->comment, strlen(key->comment));
737 p += 4 + strlen(key->comment);
738 }
739
740 assert(p - ret == len);
741 return ret;
742 }
743
744 /*
745 * Create an SSH-2 key list in a malloc'ed buffer; return its
746 * length.
747 */
748 static void *make_keylist2(int *length)
749 {
750 struct ssh2_userkey *key;
751 int i, len, nkeys;
752 unsigned char *blob, *p, *ret;
753 int bloblen;
754
755 /*
756 * Count up the number and length of keys we hold.
757 */
758 len = 4;
759 nkeys = 0;
760 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
761 nkeys++;
762 len += 4; /* length field */
763 blob = key->alg->public_blob(key->data, &bloblen);
764 len += bloblen;
765 sfree(blob);
766 len += 4 + strlen(key->comment);
767 }
768
769 /* Allocate the buffer. */
770 p = ret = snewn(len, unsigned char);
771 if (length) *length = len;
772
773 /*
774 * Packet header is the obvious five bytes, plus four
775 * bytes for the key count.
776 */
777 PUT_32BIT(p, nkeys);
778 p += 4;
779 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
780 blob = key->alg->public_blob(key->data, &bloblen);
781 PUT_32BIT(p, bloblen);
782 p += 4;
783 memcpy(p, blob, bloblen);
784 p += bloblen;
785 sfree(blob);
786 PUT_32BIT(p, strlen(key->comment));
787 memcpy(p + 4, key->comment, strlen(key->comment));
788 p += 4 + strlen(key->comment);
789 }
790
791 assert(p - ret == len);
792 return ret;
793 }
794
795 /*
796 * Acquire a keylist1 from the primary Pageant; this means either
797 * calling make_keylist1 (if that's us) or sending a message to the
798 * primary Pageant (if it's not).
799 */
800 static void *get_keylist1(int *length)
801 {
802 void *ret;
803
804 if (already_running) {
805 unsigned char request[5], *response;
806 void *vresponse;
807 int resplen, retval;
808 request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
809 PUT_32BIT(request, 4);
810
811 retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
812 assert(retval == 1);
813 response = vresponse;
814 if (resplen < 5 || response[4] != SSH1_AGENT_RSA_IDENTITIES_ANSWER)
815 return NULL;
816
817 ret = snewn(resplen-5, unsigned char);
818 memcpy(ret, response+5, resplen-5);
819 sfree(response);
820
821 if (length)
822 *length = resplen-5;
823 } else {
824 ret = make_keylist1(length);
825 }
826 return ret;
827 }
828
829 /*
830 * Acquire a keylist2 from the primary Pageant; this means either
831 * calling make_keylist2 (if that's us) or sending a message to the
832 * primary Pageant (if it's not).
833 */
834 static void *get_keylist2(int *length)
835 {
836 void *ret;
837
838 if (already_running) {
839 unsigned char request[5], *response;
840 void *vresponse;
841 int resplen, retval;
842
843 request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
844 PUT_32BIT(request, 4);
845
846 retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
847 assert(retval == 1);
848 response = vresponse;
849 if (resplen < 5 || response[4] != SSH2_AGENT_IDENTITIES_ANSWER)
850 return NULL;
851
852 ret = snewn(resplen-5, unsigned char);
853 memcpy(ret, response+5, resplen-5);
854 sfree(response);
855
856 if (length)
857 *length = resplen-5;
858 } else {
859 ret = make_keylist2(length);
860 }
861 return ret;
862 }
863
864 /*
865 * This is the main agent function that answers messages.
866 */
867 static void answer_msg(void *msg)
868 {
869 unsigned char *p = msg;
870 unsigned char *ret = msg;
871 unsigned char *msgend;
872 int type;
873
874 /*
875 * Get the message length.
876 */
877 msgend = p + 4 + GET_32BIT(p);
878
879 /*
880 * Get the message type.
881 */
882 if (msgend < p+5)
883 goto failure;
884 type = p[4];
885
886 p += 5;
887 switch (type) {
888 case SSH1_AGENTC_REQUEST_RSA_IDENTITIES:
889 /*
890 * Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER.
891 */
892 {
893 int len;
894 void *keylist;
895
896 ret[4] = SSH1_AGENT_RSA_IDENTITIES_ANSWER;
897 keylist = make_keylist1(&len);
898 if (len + 5 > AGENT_MAX_MSGLEN) {
899 sfree(keylist);
900 goto failure;
901 }
902 PUT_32BIT(ret, len + 1);
903 memcpy(ret + 5, keylist, len);
904 sfree(keylist);
905 }
906 break;
907 case SSH2_AGENTC_REQUEST_IDENTITIES:
908 /*
909 * Reply with SSH2_AGENT_IDENTITIES_ANSWER.
910 */
911 {
912 int len;
913 void *keylist;
914
915 ret[4] = SSH2_AGENT_IDENTITIES_ANSWER;
916 keylist = make_keylist2(&len);
917 if (len + 5 > AGENT_MAX_MSGLEN) {
918 sfree(keylist);
919 goto failure;
920 }
921 PUT_32BIT(ret, len + 1);
922 memcpy(ret + 5, keylist, len);
923 sfree(keylist);
924 }
925 break;
926 case SSH1_AGENTC_RSA_CHALLENGE:
927 /*
928 * Reply with either SSH1_AGENT_RSA_RESPONSE or
929 * SSH_AGENT_FAILURE, depending on whether we have that key
930 * or not.
931 */
932 {
933 struct RSAKey reqkey, *key;
934 Bignum challenge, response;
935 unsigned char response_source[48], response_md5[16];
936 struct MD5Context md5c;
937 int i, len;
938
939 p += 4;
940 i = ssh1_read_bignum(p, msgend - p, &reqkey.exponent);
941 if (i < 0)
942 goto failure;
943 p += i;
944 i = ssh1_read_bignum(p, msgend - p, &reqkey.modulus);
945 if (i < 0)
946 goto failure;
947 p += i;
948 i = ssh1_read_bignum(p, msgend - p, &challenge);
949 if (i < 0)
950 goto failure;
951 p += i;
952 if (msgend < p+16) {
953 freebn(reqkey.exponent);
954 freebn(reqkey.modulus);
955 freebn(challenge);
956 goto failure;
957 }
958 memcpy(response_source + 32, p, 16);
959 p += 16;
960 if (msgend < p+4 ||
961 GET_32BIT(p) != 1 ||
962 (key = find234(rsakeys, &reqkey, NULL)) == NULL) {
963 freebn(reqkey.exponent);
964 freebn(reqkey.modulus);
965 freebn(challenge);
966 goto failure;
967 }
968 response = rsadecrypt(challenge, key);
969 for (i = 0; i < 32; i++)
970 response_source[i] = bignum_byte(response, 31 - i);
971
972 MD5Init(&md5c);
973 MD5Update(&md5c, response_source, 48);
974 MD5Final(response_md5, &md5c);
975 smemclr(response_source, 48); /* burn the evidence */
976 freebn(response); /* and that evidence */
977 freebn(challenge); /* yes, and that evidence */
978 freebn(reqkey.exponent); /* and free some memory ... */
979 freebn(reqkey.modulus); /* ... while we're at it. */
980
981 /*
982 * Packet is the obvious five byte header, plus sixteen
983 * bytes of MD5.
984 */
985 len = 5 + 16;
986 PUT_32BIT(ret, len - 4);
987 ret[4] = SSH1_AGENT_RSA_RESPONSE;
988 memcpy(ret + 5, response_md5, 16);
989 }
990 break;
991 case SSH2_AGENTC_SIGN_REQUEST:
992 /*
993 * Reply with either SSH2_AGENT_SIGN_RESPONSE or
994 * SSH_AGENT_FAILURE, depending on whether we have that key
995 * or not.
996 */
997 {
998 struct ssh2_userkey *key;
999 struct blob b;
1000 unsigned char *data, *signature;
1001 int datalen, siglen, len;
1002
1003 if (msgend < p+4)
1004 goto failure;
1005 b.len = toint(GET_32BIT(p));
1006 if (b.len < 0 || b.len > msgend - (p+4))
1007 goto failure;
1008 p += 4;
1009 b.blob = p;
1010 p += b.len;
1011 if (msgend < p+4)
1012 goto failure;
1013 datalen = toint(GET_32BIT(p));
1014 p += 4;
1015 if (datalen < 0 || datalen > msgend - p)
1016 goto failure;
1017 data = p;
1018 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
1019 if (!key)
1020 goto failure;
1021 signature = key->alg->sign(key->data, data, datalen, &siglen);
1022 len = 5 + 4 + siglen;
1023 PUT_32BIT(ret, len - 4);
1024 ret[4] = SSH2_AGENT_SIGN_RESPONSE;
1025 PUT_32BIT(ret + 5, siglen);
1026 memcpy(ret + 5 + 4, signature, siglen);
1027 sfree(signature);
1028 }
1029 break;
1030 case SSH1_AGENTC_ADD_RSA_IDENTITY:
1031 /*
1032 * Add to the list and return SSH_AGENT_SUCCESS, or
1033 * SSH_AGENT_FAILURE if the key was malformed.
1034 */
1035 {
1036 struct RSAKey *key;
1037 char *comment;
1038 int n, commentlen;
1039
1040 key = snew(struct RSAKey);
1041 memset(key, 0, sizeof(struct RSAKey));
1042
1043 n = makekey(p, msgend - p, key, NULL, 1);
1044 if (n < 0) {
1045 freersakey(key);
1046 sfree(key);
1047 goto failure;
1048 }
1049 p += n;
1050
1051 n = makeprivate(p, msgend - p, key);
1052 if (n < 0) {
1053 freersakey(key);
1054 sfree(key);
1055 goto failure;
1056 }
1057 p += n;
1058
1059 n = ssh1_read_bignum(p, msgend - p, &key->iqmp); /* p^-1 mod q */
1060 if (n < 0) {
1061 freersakey(key);
1062 sfree(key);
1063 goto failure;
1064 }
1065 p += n;
1066
1067 n = ssh1_read_bignum(p, msgend - p, &key->p); /* p */
1068 if (n < 0) {
1069 freersakey(key);
1070 sfree(key);
1071 goto failure;
1072 }
1073 p += n;
1074
1075 n = ssh1_read_bignum(p, msgend - p, &key->q); /* q */
1076 if (n < 0) {
1077 freersakey(key);
1078 sfree(key);
1079 goto failure;
1080 }
1081 p += n;
1082
1083 if (msgend < p+4) {
1084 freersakey(key);
1085 sfree(key);
1086 goto failure;
1087 }
1088 commentlen = toint(GET_32BIT(p));
1089
1090 if (commentlen < 0 || commentlen > msgend - p) {
1091 freersakey(key);
1092 sfree(key);
1093 goto failure;
1094 }
1095
1096 comment = snewn(commentlen+1, char);
1097 if (comment) {
1098 memcpy(comment, p + 4, commentlen);
1099 comment[commentlen] = '\0';
1100 key->comment = comment;
1101 }
1102 PUT_32BIT(ret, 1);
1103 ret[4] = SSH_AGENT_FAILURE;
1104 if (add234(rsakeys, key) == key) {
1105 keylist_update();
1106 ret[4] = SSH_AGENT_SUCCESS;
1107 } else {
1108 freersakey(key);
1109 sfree(key);
1110 }
1111 }
1112 break;
1113 case SSH2_AGENTC_ADD_IDENTITY:
1114 /*
1115 * Add to the list and return SSH_AGENT_SUCCESS, or
1116 * SSH_AGENT_FAILURE if the key was malformed.
1117 */
1118 {
1119 struct ssh2_userkey *key;
1120 char *comment, *alg;
1121 int alglen, commlen;
1122 int bloblen;
1123
1124
1125 if (msgend < p+4)
1126 goto failure;
1127 alglen = toint(GET_32BIT(p));
1128 p += 4;
1129 if (alglen < 0 || alglen > msgend - p)
1130 goto failure;
1131 alg = p;
1132 p += alglen;
1133
1134 key = snew(struct ssh2_userkey);
1135 /* Add further algorithm names here. */
1136 if (alglen == 7 && !memcmp(alg, "ssh-rsa", 7))
1137 key->alg = &ssh_rsa;
1138 else if (alglen == 7 && !memcmp(alg, "ssh-dss", 7))
1139 key->alg = &ssh_dss;
1140 else {
1141 sfree(key);
1142 goto failure;
1143 }
1144
1145 bloblen = msgend - p;
1146 key->data = key->alg->openssh_createkey(&p, &bloblen);
1147 if (!key->data) {
1148 sfree(key);
1149 goto failure;
1150 }
1151
1152 /*
1153 * p has been advanced by openssh_createkey, but
1154 * certainly not _beyond_ the end of the buffer.
1155 */
1156 assert(p <= msgend);
1157
1158 if (msgend < p+4) {
1159 key->alg->freekey(key->data);
1160 sfree(key);
1161 goto failure;
1162 }
1163 commlen = toint(GET_32BIT(p));
1164 p += 4;
1165
1166 if (commlen < 0 || commlen > msgend - p) {
1167 key->alg->freekey(key->data);
1168 sfree(key);
1169 goto failure;
1170 }
1171 comment = snewn(commlen + 1, char);
1172 if (comment) {
1173 memcpy(comment, p, commlen);
1174 comment[commlen] = '\0';
1175 }
1176 key->comment = comment;
1177
1178 PUT_32BIT(ret, 1);
1179 ret[4] = SSH_AGENT_FAILURE;
1180 if (add234(ssh2keys, key) == key) {
1181 keylist_update();
1182 ret[4] = SSH_AGENT_SUCCESS;
1183 } else {
1184 key->alg->freekey(key->data);
1185 sfree(key->comment);
1186 sfree(key);
1187 }
1188 }
1189 break;
1190 case SSH1_AGENTC_REMOVE_RSA_IDENTITY:
1191 /*
1192 * Remove from the list and return SSH_AGENT_SUCCESS, or
1193 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
1194 * start with.
1195 */
1196 {
1197 struct RSAKey reqkey, *key;
1198 int n;
1199
1200 n = makekey(p, msgend - p, &reqkey, NULL, 0);
1201 if (n < 0)
1202 goto failure;
1203
1204 key = find234(rsakeys, &reqkey, NULL);
1205 freebn(reqkey.exponent);
1206 freebn(reqkey.modulus);
1207 PUT_32BIT(ret, 1);
1208 ret[4] = SSH_AGENT_FAILURE;
1209 if (key) {
1210 del234(rsakeys, key);
1211 keylist_update();
1212 freersakey(key);
1213 sfree(key);
1214 ret[4] = SSH_AGENT_SUCCESS;
1215 }
1216 }
1217 break;
1218 case SSH2_AGENTC_REMOVE_IDENTITY:
1219 /*
1220 * Remove from the list and return SSH_AGENT_SUCCESS, or
1221 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
1222 * start with.
1223 */
1224 {
1225 struct ssh2_userkey *key;
1226 struct blob b;
1227
1228 if (msgend < p+4)
1229 goto failure;
1230 b.len = toint(GET_32BIT(p));
1231 p += 4;
1232
1233 if (b.len < 0 || b.len > msgend - p)
1234 goto failure;
1235 b.blob = p;
1236 p += b.len;
1237
1238 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
1239 if (!key)
1240 goto failure;
1241
1242 PUT_32BIT(ret, 1);
1243 ret[4] = SSH_AGENT_FAILURE;
1244 if (key) {
1245 del234(ssh2keys, key);
1246 keylist_update();
1247 key->alg->freekey(key->data);
1248 sfree(key);
1249 ret[4] = SSH_AGENT_SUCCESS;
1250 }
1251 }
1252 break;
1253 case SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES:
1254 /*
1255 * Remove all SSH-1 keys. Always returns success.
1256 */
1257 {
1258 struct RSAKey *rkey;
1259
1260 while ((rkey = index234(rsakeys, 0)) != NULL) {
1261 del234(rsakeys, rkey);
1262 freersakey(rkey);
1263 sfree(rkey);
1264 }
1265 keylist_update();
1266
1267 PUT_32BIT(ret, 1);
1268 ret[4] = SSH_AGENT_SUCCESS;
1269 }
1270 break;
1271 case SSH2_AGENTC_REMOVE_ALL_IDENTITIES:
1272 /*
1273 * Remove all SSH-2 keys. Always returns success.
1274 */
1275 {
1276 struct ssh2_userkey *skey;
1277
1278 while ((skey = index234(ssh2keys, 0)) != NULL) {
1279 del234(ssh2keys, skey);
1280 skey->alg->freekey(skey->data);
1281 sfree(skey);
1282 }
1283 keylist_update();
1284
1285 PUT_32BIT(ret, 1);
1286 ret[4] = SSH_AGENT_SUCCESS;
1287 }
1288 break;
1289 default:
1290 failure:
1291 /*
1292 * Unrecognised message. Return SSH_AGENT_FAILURE.
1293 */
1294 PUT_32BIT(ret, 1);
1295 ret[4] = SSH_AGENT_FAILURE;
1296 break;
1297 }
1298 }
1299
1300 /*
1301 * Key comparison function for the 2-3-4 tree of RSA keys.
1302 */
1303 static int cmpkeys_rsa(void *av, void *bv)
1304 {
1305 struct RSAKey *a = (struct RSAKey *) av;
1306 struct RSAKey *b = (struct RSAKey *) bv;
1307 Bignum am, bm;
1308 int alen, blen;
1309
1310 am = a->modulus;
1311 bm = b->modulus;
1312 /*
1313 * Compare by length of moduli.
1314 */
1315 alen = bignum_bitcount(am);
1316 blen = bignum_bitcount(bm);
1317 if (alen > blen)
1318 return +1;
1319 else if (alen < blen)
1320 return -1;
1321 /*
1322 * Now compare by moduli themselves.
1323 */
1324 alen = (alen + 7) / 8; /* byte count */
1325 while (alen-- > 0) {
1326 int abyte, bbyte;
1327 abyte = bignum_byte(am, alen);
1328 bbyte = bignum_byte(bm, alen);
1329 if (abyte > bbyte)
1330 return +1;
1331 else if (abyte < bbyte)
1332 return -1;
1333 }
1334 /*
1335 * Give up.
1336 */
1337 return 0;
1338 }
1339
1340 /*
1341 * Key comparison function for the 2-3-4 tree of SSH-2 keys.
1342 */
1343 static int cmpkeys_ssh2(void *av, void *bv)
1344 {
1345 struct ssh2_userkey *a = (struct ssh2_userkey *) av;
1346 struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
1347 int i;
1348 int alen, blen;
1349 unsigned char *ablob, *bblob;
1350 int c;
1351
1352 /*
1353 * Compare purely by public blob.
1354 */
1355 ablob = a->alg->public_blob(a->data, &alen);
1356 bblob = b->alg->public_blob(b->data, &blen);
1357
1358 c = 0;
1359 for (i = 0; i < alen && i < blen; i++) {
1360 if (ablob[i] < bblob[i]) {
1361 c = -1;
1362 break;
1363 } else if (ablob[i] > bblob[i]) {
1364 c = +1;
1365 break;
1366 }
1367 }
1368 if (c == 0 && i < alen)
1369 c = +1; /* a is longer */
1370 if (c == 0 && i < blen)
1371 c = -1; /* a is longer */
1372
1373 sfree(ablob);
1374 sfree(bblob);
1375
1376 return c;
1377 }
1378
1379 /*
1380 * Key comparison function for looking up a blob in the 2-3-4 tree
1381 * of SSH-2 keys.
1382 */
1383 static int cmpkeys_ssh2_asymm(void *av, void *bv)
1384 {
1385 struct blob *a = (struct blob *) av;
1386 struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
1387 int i;
1388 int alen, blen;
1389 unsigned char *ablob, *bblob;
1390 int c;
1391
1392 /*
1393 * Compare purely by public blob.
1394 */
1395 ablob = a->blob;
1396 alen = a->len;
1397 bblob = b->alg->public_blob(b->data, &blen);
1398
1399 c = 0;
1400 for (i = 0; i < alen && i < blen; i++) {
1401 if (ablob[i] < bblob[i]) {
1402 c = -1;
1403 break;
1404 } else if (ablob[i] > bblob[i]) {
1405 c = +1;
1406 break;
1407 }
1408 }
1409 if (c == 0 && i < alen)
1410 c = +1; /* a is longer */
1411 if (c == 0 && i < blen)
1412 c = -1; /* a is longer */
1413
1414 sfree(bblob);
1415
1416 return c;
1417 }
1418
1419 /*
1420 * Prompt for a key file to add, and add it.
1421 */
1422 static void prompt_add_keyfile(void)
1423 {
1424 OPENFILENAME of;
1425 char *filelist = snewn(8192, char);
1426
1427 if (!keypath) keypath = filereq_new();
1428 memset(&of, 0, sizeof(of));
1429 of.hwndOwner = hwnd;
1430 of.lpstrFilter = FILTER_KEY_FILES;
1431 of.lpstrCustomFilter = NULL;
1432 of.nFilterIndex = 1;
1433 of.lpstrFile = filelist;
1434 *filelist = '\0';
1435 of.nMaxFile = 8192;
1436 of.lpstrFileTitle = NULL;
1437 of.lpstrTitle = "Select Private Key File";
1438 of.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER;
1439 if (request_file(keypath, &of, TRUE, FALSE)) {
1440 if(strlen(filelist) > of.nFileOffset)
1441 /* Only one filename returned? */
1442 add_keyfile(filename_from_str(filelist));
1443 else {
1444 /* we are returned a bunch of strings, end to
1445 * end. first string is the directory, the
1446 * rest the filenames. terminated with an
1447 * empty string.
1448 */
1449 char *dir = filelist;
1450 char *filewalker = filelist + strlen(dir) + 1;
1451 while (*filewalker != '\0') {
1452 char *filename = dupcat(dir, "\\", filewalker, NULL);
1453 add_keyfile(filename_from_str(filename));
1454 sfree(filename);
1455 filewalker += strlen(filewalker) + 1;
1456 }
1457 }
1458
1459 keylist_update();
1460 forget_passphrases();
1461 }
1462 sfree(filelist);
1463 }
1464
1465 /*
1466 * Dialog-box function for the key list box.
1467 */
1468 static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
1469 WPARAM wParam, LPARAM lParam)
1470 {
1471 struct RSAKey *rkey;
1472 struct ssh2_userkey *skey;
1473
1474 switch (msg) {
1475 case WM_INITDIALOG:
1476 /*
1477 * Centre the window.
1478 */
1479 { /* centre the window */
1480 RECT rs, rd;
1481 HWND hw;
1482
1483 hw = GetDesktopWindow();
1484 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
1485 MoveWindow(hwnd,
1486 (rs.right + rs.left + rd.left - rd.right) / 2,
1487 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
1488 rd.right - rd.left, rd.bottom - rd.top, TRUE);
1489 }
1490
1491 if (has_help())
1492 SetWindowLongPtr(hwnd, GWL_EXSTYLE,
1493 GetWindowLongPtr(hwnd, GWL_EXSTYLE) |
1494 WS_EX_CONTEXTHELP);
1495 else {
1496 HWND item = GetDlgItem(hwnd, 103); /* the Help button */
1497 if (item)
1498 DestroyWindow(item);
1499 }
1500
1501 keylist = hwnd;
1502 {
1503 static int tabs[] = { 35, 60, 210 };
1504 SendDlgItemMessage(hwnd, 100, LB_SETTABSTOPS,
1505 sizeof(tabs) / sizeof(*tabs),
1506 (LPARAM) tabs);
1507 }
1508 keylist_update();
1509 return 0;
1510 case WM_COMMAND:
1511 switch (LOWORD(wParam)) {
1512 case IDOK:
1513 case IDCANCEL:
1514 keylist = NULL;
1515 DestroyWindow(hwnd);
1516 return 0;
1517 case 101: /* add key */
1518 if (HIWORD(wParam) == BN_CLICKED ||
1519 HIWORD(wParam) == BN_DOUBLECLICKED) {
1520 if (passphrase_box) {
1521 MessageBeep(MB_ICONERROR);
1522 SetForegroundWindow(passphrase_box);
1523 break;
1524 }
1525 prompt_add_keyfile();
1526 }
1527 return 0;
1528 case 102: /* remove key */
1529 if (HIWORD(wParam) == BN_CLICKED ||
1530 HIWORD(wParam) == BN_DOUBLECLICKED) {
1531 int i;
1532 int rCount, sCount;
1533 int *selectedArray;
1534
1535 /* our counter within the array of selected items */
1536 int itemNum;
1537
1538 /* get the number of items selected in the list */
1539 int numSelected =
1540 SendDlgItemMessage(hwnd, 100, LB_GETSELCOUNT, 0, 0);
1541
1542 /* none selected? that was silly */
1543 if (numSelected == 0) {
1544 MessageBeep(0);
1545 break;
1546 }
1547
1548 /* get item indices in an array */
1549 selectedArray = snewn(numSelected, int);
1550 SendDlgItemMessage(hwnd, 100, LB_GETSELITEMS,
1551 numSelected, (WPARAM)selectedArray);
1552
1553 itemNum = numSelected - 1;
1554 rCount = count234(rsakeys);
1555 sCount = count234(ssh2keys);
1556
1557 /* go through the non-rsakeys until we've covered them all,
1558 * and/or we're out of selected items to check. note that
1559 * we go *backwards*, to avoid complications from deleting
1560 * things hence altering the offset of subsequent items
1561 */
1562 for (i = sCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1563 skey = index234(ssh2keys, i);
1564
1565 if (selectedArray[itemNum] == rCount + i) {
1566 del234(ssh2keys, skey);
1567 skey->alg->freekey(skey->data);
1568 sfree(skey);
1569 itemNum--;
1570 }
1571 }
1572
1573 /* do the same for the rsa keys */
1574 for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1575 rkey = index234(rsakeys, i);
1576
1577 if(selectedArray[itemNum] == i) {
1578 del234(rsakeys, rkey);
1579 freersakey(rkey);
1580 sfree(rkey);
1581 itemNum--;
1582 }
1583 }
1584
1585 sfree(selectedArray);
1586 keylist_update();
1587 }
1588 return 0;
1589 case 103: /* help */
1590 if (HIWORD(wParam) == BN_CLICKED ||
1591 HIWORD(wParam) == BN_DOUBLECLICKED) {
1592 launch_help(hwnd, WINHELP_CTX_pageant_general);
1593 }
1594 return 0;
1595 }
1596 return 0;
1597 case WM_HELP:
1598 {
1599 int id = ((LPHELPINFO)lParam)->iCtrlId;
1600 char *topic = NULL;
1601 switch (id) {
1602 case 100: topic = WINHELP_CTX_pageant_keylist; break;
1603 case 101: topic = WINHELP_CTX_pageant_addkey; break;
1604 case 102: topic = WINHELP_CTX_pageant_remkey; break;
1605 }
1606 if (topic) {
1607 launch_help(hwnd, topic);
1608 } else {
1609 MessageBeep(0);
1610 }
1611 }
1612 break;
1613 case WM_CLOSE:
1614 keylist = NULL;
1615 DestroyWindow(hwnd);
1616 return 0;
1617 }
1618 return 0;
1619 }
1620
1621 /* Set up a system tray icon */
1622 static BOOL AddTrayIcon(HWND hwnd)
1623 {
1624 BOOL res;
1625 NOTIFYICONDATA tnid;
1626 HICON hicon;
1627
1628 #ifdef NIM_SETVERSION
1629 tnid.uVersion = 0;
1630 res = Shell_NotifyIcon(NIM_SETVERSION, &tnid);
1631 #endif
1632
1633 tnid.cbSize = sizeof(NOTIFYICONDATA);
1634 tnid.hWnd = hwnd;
1635 tnid.uID = 1; /* unique within this systray use */
1636 tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
1637 tnid.uCallbackMessage = WM_SYSTRAY;
1638 tnid.hIcon = hicon = LoadIcon(hinst, MAKEINTRESOURCE(201));
1639 strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
1640
1641 res = Shell_NotifyIcon(NIM_ADD, &tnid);
1642
1643 if (hicon) DestroyIcon(hicon);
1644
1645 return res;
1646 }
1647
1648 /* Update the saved-sessions menu. */
1649 static void update_sessions(void)
1650 {
1651 int num_entries;
1652 HKEY hkey;
1653 TCHAR buf[MAX_PATH + 1];
1654 MENUITEMINFO mii;
1655
1656 int index_key, index_menu;
1657
1658 if (!putty_path)
1659 return;
1660
1661 if(ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, PUTTY_REGKEY, &hkey))
1662 return;
1663
1664 for(num_entries = GetMenuItemCount(session_menu);
1665 num_entries > initial_menuitems_count;
1666 num_entries--)
1667 RemoveMenu(session_menu, 0, MF_BYPOSITION);
1668
1669 index_key = 0;
1670 index_menu = 0;
1671
1672 while(ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) {
1673 TCHAR session_name[MAX_PATH + 1];
1674 unmungestr(buf, session_name, MAX_PATH);
1675 if(strcmp(buf, PUTTY_DEFAULT) != 0) {
1676 memset(&mii, 0, sizeof(mii));
1677 mii.cbSize = sizeof(mii);
1678 mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
1679 mii.fType = MFT_STRING;
1680 mii.fState = MFS_ENABLED;
1681 mii.wID = (index_menu * 16) + IDM_SESSIONS_BASE;
1682 mii.dwTypeData = session_name;
1683 InsertMenuItem(session_menu, index_menu, TRUE, &mii);
1684 index_menu++;
1685 }
1686 index_key++;
1687 }
1688
1689 RegCloseKey(hkey);
1690
1691 if(index_menu == 0) {
1692 mii.cbSize = sizeof(mii);
1693 mii.fMask = MIIM_TYPE | MIIM_STATE;
1694 mii.fType = MFT_STRING;
1695 mii.fState = MFS_GRAYED;
1696 mii.dwTypeData = _T("(No sessions)");
1697 InsertMenuItem(session_menu, index_menu, TRUE, &mii);
1698 }
1699 }
1700
1701 #ifndef NO_SECURITY
1702 /*
1703 * Versions of Pageant prior to 0.61 expected this SID on incoming
1704 * communications. For backwards compatibility, and more particularly
1705 * for compatibility with derived works of PuTTY still using the old
1706 * Pageant client code, we accept it as an alternative to the one
1707 * returned from get_user_sid() in winpgntc.c.
1708 */
1709 PSID get_default_sid(void)
1710 {
1711 HANDLE proc = NULL;
1712 DWORD sidlen;
1713 PSECURITY_DESCRIPTOR psd = NULL;
1714 PSID sid = NULL, copy = NULL, ret = NULL;
1715
1716 if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE,
1717 GetCurrentProcessId())) == NULL)
1718 goto cleanup;
1719
1720 if (p_GetSecurityInfo(proc, SE_KERNEL_OBJECT, OWNER_SECURITY_INFORMATION,
1721 &sid, NULL, NULL, NULL, &psd) != ERROR_SUCCESS)
1722 goto cleanup;
1723
1724 sidlen = GetLengthSid(sid);
1725
1726 copy = (PSID)smalloc(sidlen);
1727
1728 if (!CopySid(sidlen, copy, sid))
1729 goto cleanup;
1730
1731 /* Success. Move sid into the return value slot, and null it out
1732 * to stop the cleanup code freeing it. */
1733 ret = copy;
1734 copy = NULL;
1735
1736 cleanup:
1737 if (proc != NULL)
1738 CloseHandle(proc);
1739 if (psd != NULL)
1740 LocalFree(psd);
1741 if (copy != NULL)
1742 sfree(copy);
1743
1744 return ret;
1745 }
1746 #endif
1747
1748 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1749 WPARAM wParam, LPARAM lParam)
1750 {
1751 int ret;
1752 static int menuinprogress;
1753 static UINT msgTaskbarCreated = 0;
1754
1755 switch (message) {
1756 case WM_CREATE:
1757 msgTaskbarCreated = RegisterWindowMessage(_T("TaskbarCreated"));
1758 break;
1759 default:
1760 if (message==msgTaskbarCreated) {
1761 /*
1762 * Explorer has been restarted, so the tray icon will
1763 * have been lost.
1764 */
1765 AddTrayIcon(hwnd);
1766 }
1767 break;
1768
1769 case WM_SYSTRAY:
1770 if (lParam == WM_RBUTTONUP) {
1771 POINT cursorpos;
1772 GetCursorPos(&cursorpos);
1773 PostMessage(hwnd, WM_SYSTRAY2, cursorpos.x, cursorpos.y);
1774 } else if (lParam == WM_LBUTTONDBLCLK) {
1775 /* Run the default menu item. */
1776 UINT menuitem = GetMenuDefaultItem(systray_menu, FALSE, 0);
1777 if (menuitem != -1)
1778 PostMessage(hwnd, WM_COMMAND, menuitem, 0);
1779 }
1780 break;
1781 case WM_SYSTRAY2:
1782 if (!menuinprogress) {
1783 menuinprogress = 1;
1784 update_sessions();
1785 SetForegroundWindow(hwnd);
1786 ret = TrackPopupMenu(systray_menu,
1787 TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
1788 TPM_RIGHTBUTTON,
1789 wParam, lParam, 0, hwnd, NULL);
1790 menuinprogress = 0;
1791 }
1792 break;
1793 case WM_COMMAND:
1794 case WM_SYSCOMMAND:
1795 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
1796 case IDM_PUTTY:
1797 if((int)ShellExecute(hwnd, NULL, putty_path, _T(""), _T(""),
1798 SW_SHOW) <= 32) {
1799 MessageBox(NULL, "Unable to execute PuTTY!",
1800 "Error", MB_OK | MB_ICONERROR);
1801 }
1802 break;
1803 case IDM_CLOSE:
1804 if (passphrase_box)
1805 SendMessage(passphrase_box, WM_CLOSE, 0, 0);
1806 SendMessage(hwnd, WM_CLOSE, 0, 0);
1807 break;
1808 case IDM_VIEWKEYS:
1809 if (!keylist) {
1810 keylist = CreateDialog(hinst, MAKEINTRESOURCE(211),
1811 NULL, KeyListProc);
1812 ShowWindow(keylist, SW_SHOWNORMAL);
1813 }
1814 /*
1815 * Sometimes the window comes up minimised / hidden for
1816 * no obvious reason. Prevent this. This also brings it
1817 * to the front if it's already present (the user
1818 * selected View Keys because they wanted to _see_ the
1819 * thing).
1820 */
1821 SetForegroundWindow(keylist);
1822 SetWindowPos(keylist, HWND_TOP, 0, 0, 0, 0,
1823 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1824 break;
1825 case IDM_ADDKEY:
1826 if (passphrase_box) {
1827 MessageBeep(MB_ICONERROR);
1828 SetForegroundWindow(passphrase_box);
1829 break;
1830 }
1831 prompt_add_keyfile();
1832 break;
1833 case IDM_ABOUT:
1834 if (!aboutbox) {
1835 aboutbox = CreateDialog(hinst, MAKEINTRESOURCE(213),
1836 NULL, AboutProc);
1837 ShowWindow(aboutbox, SW_SHOWNORMAL);
1838 /*
1839 * Sometimes the window comes up minimised / hidden
1840 * for no obvious reason. Prevent this.
1841 */
1842 SetForegroundWindow(aboutbox);
1843 SetWindowPos(aboutbox, HWND_TOP, 0, 0, 0, 0,
1844 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1845 }
1846 break;
1847 case IDM_HELP:
1848 launch_help(hwnd, WINHELP_CTX_pageant_general);
1849 break;
1850 default:
1851 {
1852 if(wParam >= IDM_SESSIONS_BASE && wParam <= IDM_SESSIONS_MAX) {
1853 MENUITEMINFO mii;
1854 TCHAR buf[MAX_PATH + 1];
1855 TCHAR param[MAX_PATH + 1];
1856 memset(&mii, 0, sizeof(mii));
1857 mii.cbSize = sizeof(mii);
1858 mii.fMask = MIIM_TYPE;
1859 mii.cch = MAX_PATH;
1860 mii.dwTypeData = buf;
1861 GetMenuItemInfo(session_menu, wParam, FALSE, &mii);
1862 strcpy(param, "@");
1863 strcat(param, mii.dwTypeData);
1864 if((int)ShellExecute(hwnd, NULL, putty_path, param,
1865 _T(""), SW_SHOW) <= 32) {
1866 MessageBox(NULL, "Unable to execute PuTTY!", "Error",
1867 MB_OK | MB_ICONERROR);
1868 }
1869 }
1870 }
1871 break;
1872 }
1873 break;
1874 case WM_DESTROY:
1875 quit_help(hwnd);
1876 PostQuitMessage(0);
1877 return 0;
1878 case WM_COPYDATA:
1879 {
1880 COPYDATASTRUCT *cds;
1881 char *mapname;
1882 void *p;
1883 HANDLE filemap;
1884 #ifndef NO_SECURITY
1885 PSID mapowner, ourself, ourself2;
1886 #endif
1887 PSECURITY_DESCRIPTOR psd = NULL;
1888 int ret = 0;
1889
1890 cds = (COPYDATASTRUCT *) lParam;
1891 if (cds->dwData != AGENT_COPYDATA_ID)
1892 return 0; /* not our message, mate */
1893 mapname = (char *) cds->lpData;
1894 if (mapname[cds->cbData - 1] != '\0')
1895 return 0; /* failure to be ASCIZ! */
1896 #ifdef DEBUG_IPC
1897 debug(("mapname is :%s:\n", mapname));
1898 #endif
1899 filemap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, mapname);
1900 #ifdef DEBUG_IPC
1901 debug(("filemap is %p\n", filemap));
1902 #endif
1903 if (filemap != NULL && filemap != INVALID_HANDLE_VALUE) {
1904 #ifndef NO_SECURITY
1905 int rc;
1906 if (has_security) {
1907 if ((ourself = get_user_sid()) == NULL) {
1908 #ifdef DEBUG_IPC
1909 debug(("couldn't get user SID\n"));
1910 #endif
1911 return 0;
1912 }
1913
1914 if ((ourself2 = get_default_sid()) == NULL) {
1915 #ifdef DEBUG_IPC
1916 debug(("couldn't get default SID\n"));
1917 #endif
1918 return 0;
1919 }
1920
1921 if ((rc = p_GetSecurityInfo(filemap, SE_KERNEL_OBJECT,
1922 OWNER_SECURITY_INFORMATION,
1923 &mapowner, NULL, NULL, NULL,
1924 &psd) != ERROR_SUCCESS)) {
1925 #ifdef DEBUG_IPC
1926 debug(("couldn't get owner info for filemap: %d\n",
1927 rc));
1928 #endif
1929 return 0;
1930 }
1931 #ifdef DEBUG_IPC
1932 {
1933 LPTSTR ours, ours2, theirs;
1934 ConvertSidToStringSid(mapowner, &theirs);
1935 ConvertSidToStringSid(ourself, &ours);
1936 ConvertSidToStringSid(ourself2, &ours2);
1937 debug(("got sids:\n oursnew=%s\n oursold=%s\n"
1938 " theirs=%s\n", ours, ours2, theirs));
1939 LocalFree(ours);
1940 LocalFree(ours2);
1941 LocalFree(theirs);
1942 }
1943 #endif
1944 if (!EqualSid(mapowner, ourself) &&
1945 !EqualSid(mapowner, ourself2)) {
1946 CloseHandle(filemap);
1947 return 0; /* security ID mismatch! */
1948 }
1949 #ifdef DEBUG_IPC
1950 debug(("security stuff matched\n"));
1951 #endif
1952 LocalFree(psd);
1953 sfree(ourself);
1954 sfree(ourself2);
1955 } else {
1956 #ifdef DEBUG_IPC
1957 debug(("security APIs not present\n"));
1958 #endif
1959 }
1960 #endif
1961 p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
1962 #ifdef DEBUG_IPC
1963 debug(("p is %p\n", p));
1964 {
1965 int i;
1966 for (i = 0; i < 5; i++)
1967 debug(("p[%d]=%02x\n", i,
1968 ((unsigned char *) p)[i]));
1969 }
1970 #endif
1971 answer_msg(p);
1972 ret = 1;
1973 UnmapViewOfFile(p);
1974 }
1975 CloseHandle(filemap);
1976 return ret;
1977 }
1978 }
1979
1980 return DefWindowProc(hwnd, message, wParam, lParam);
1981 }
1982
1983 /*
1984 * Fork and Exec the command in cmdline. [DBW]
1985 */
1986 void spawn_cmd(char *cmdline, char * args, int show)
1987 {
1988 if (ShellExecute(NULL, _T("open"), cmdline,
1989 args, NULL, show) <= (HINSTANCE) 32) {
1990 char *msg;
1991 msg = dupprintf("Failed to run \"%.100s\", Error: %d", cmdline,
1992 (int)GetLastError());
1993 MessageBox(NULL, msg, APPNAME, MB_OK | MB_ICONEXCLAMATION);
1994 sfree(msg);
1995 }
1996 }
1997
1998 /*
1999 * This is a can't-happen stub, since Pageant never makes
2000 * asynchronous agent requests.
2001 */
2002 void agent_schedule_callback(void (*callback)(void *, void *, int),
2003 void *callback_ctx, void *data, int len)
2004 {
2005 assert(!"We shouldn't get here");
2006 }
2007
2008 void cleanup_exit(int code)
2009 {
2010 shutdown_help();
2011 exit(code);
2012 }
2013
2014 int flags = FLAG_SYNCAGENT;
2015
2016 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
2017 {
2018 WNDCLASS wndclass;
2019 MSG msg;
2020 HMODULE advapi;
2021 char *command = NULL;
2022 int added_keys = 0;
2023 int argc, i;
2024 char **argv, **argstart;
2025
2026 hinst = inst;
2027 hwnd = NULL;
2028
2029 /*
2030 * Determine whether we're an NT system (should have security
2031 * APIs) or a non-NT system (don't do security).
2032 */
2033 if (!init_winver())
2034 {
2035 modalfatalbox("Windows refuses to report a version");
2036 }
2037 if (osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT) {
2038 has_security = TRUE;
2039 } else
2040 has_security = FALSE;
2041
2042 if (has_security) {
2043 #ifndef NO_SECURITY
2044 /*
2045 * Attempt to get the security API we need.
2046 */
2047 if (!init_advapi()) {
2048 MessageBox(NULL,
2049 "Unable to access security APIs. Pageant will\n"
2050 "not run, in case it causes a security breach.",
2051 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
2052 return 1;
2053 }
2054 #else
2055 MessageBox(NULL,
2056 "This program has been compiled for Win9X and will\n"
2057 "not run on NT, in case it causes a security breach.",
2058 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
2059 return 1;
2060 #endif
2061 } else
2062 advapi = NULL;
2063
2064 /*
2065 * See if we can find our Help file.
2066 */
2067 init_help();
2068
2069 /*
2070 * Look for the PuTTY binary (we will enable the saved session
2071 * submenu if we find it).
2072 */
2073 {
2074 char b[2048], *p, *q, *r;
2075 FILE *fp;
2076 GetModuleFileName(NULL, b, sizeof(b) - 16);
2077 r = b;
2078 p = strrchr(b, '\\');
2079 if (p && p >= r) r = p+1;
2080 q = strrchr(b, ':');
2081 if (q && q >= r) r = q+1;
2082 strcpy(r, "putty.exe");
2083 if ( (fp = fopen(b, "r")) != NULL) {
2084 putty_path = dupstr(b);
2085 fclose(fp);
2086 } else
2087 putty_path = NULL;
2088 }
2089
2090 /*
2091 * Find out if Pageant is already running.
2092 */
2093 already_running = agent_exists();
2094
2095 /*
2096 * Initialise storage for RSA keys.
2097 */
2098 if (!already_running) {
2099 rsakeys = newtree234(cmpkeys_rsa);
2100 ssh2keys = newtree234(cmpkeys_ssh2);
2101 }
2102
2103 /*
2104 * Initialise storage for short-term passphrase cache.
2105 */
2106 passphrases = newtree234(NULL);
2107
2108 /*
2109 * Process the command line and add keys as listed on it.
2110 */
2111 split_into_argv(cmdline, &argc, &argv, &argstart);
2112 for (i = 0; i < argc; i++) {
2113 if (!strcmp(argv[i], "-pgpfp")) {
2114 pgp_fingerprints();
2115 if (advapi)
2116 FreeLibrary(advapi);
2117 return 1;
2118 } else if (!strcmp(argv[i], "-c")) {
2119 /*
2120 * If we see `-c', then the rest of the
2121 * command line should be treated as a
2122 * command to be spawned.
2123 */
2124 if (i < argc-1)
2125 command = argstart[i+1];
2126 else
2127 command = "";
2128 break;
2129 } else {
2130 add_keyfile(filename_from_str(argv[i]));
2131 added_keys = TRUE;
2132 }
2133 }
2134
2135 /*
2136 * Forget any passphrase that we retained while going over
2137 * command line keyfiles.
2138 */
2139 forget_passphrases();
2140
2141 if (command) {
2142 char *args;
2143 if (command[0] == '"')
2144 args = strchr(++command, '"');
2145 else
2146 args = strchr(command, ' ');
2147 if (args) {
2148 *args++ = 0;
2149 while(*args && isspace(*args)) args++;
2150 }
2151 spawn_cmd(command, args, show);
2152 }
2153
2154 /*
2155 * If Pageant was already running, we leave now. If we haven't
2156 * even taken any auxiliary action (spawned a command or added
2157 * keys), complain.
2158 */
2159 if (already_running) {
2160 if (!command && !added_keys) {
2161 MessageBox(NULL, "Pageant is already running", "Pageant Error",
2162 MB_ICONERROR | MB_OK);
2163 }
2164 if (advapi)
2165 FreeLibrary(advapi);
2166 return 0;
2167 }
2168
2169 if (!prev) {
2170 wndclass.style = 0;
2171 wndclass.lpfnWndProc = WndProc;
2172 wndclass.cbClsExtra = 0;
2173 wndclass.cbWndExtra = 0;
2174 wndclass.hInstance = inst;
2175 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
2176 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
2177 wndclass.hbrBackground = GetStockObject(BLACK_BRUSH);
2178 wndclass.lpszMenuName = NULL;
2179 wndclass.lpszClassName = APPNAME;
2180
2181 RegisterClass(&wndclass);
2182 }
2183
2184 keylist = NULL;
2185
2186 hwnd = CreateWindow(APPNAME, APPNAME,
2187 WS_OVERLAPPEDWINDOW | WS_VSCROLL,
2188 CW_USEDEFAULT, CW_USEDEFAULT,
2189 100, 100, NULL, NULL, inst, NULL);
2190
2191 /* Set up a system tray icon */
2192 AddTrayIcon(hwnd);
2193
2194 /* Accelerators used: nsvkxa */
2195 systray_menu = CreatePopupMenu();
2196 if (putty_path) {
2197 session_menu = CreateMenu();
2198 AppendMenu(systray_menu, MF_ENABLED, IDM_PUTTY, "&New Session");
2199 AppendMenu(systray_menu, MF_POPUP | MF_ENABLED,
2200 (UINT) session_menu, "&Saved Sessions");
2201 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
2202 }
2203 AppendMenu(systray_menu, MF_ENABLED, IDM_VIEWKEYS,
2204 "&View Keys");
2205 AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key");
2206 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
2207 if (has_help())
2208 AppendMenu(systray_menu, MF_ENABLED, IDM_HELP, "&Help");
2209 AppendMenu(systray_menu, MF_ENABLED, IDM_ABOUT, "&About");
2210 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
2211 AppendMenu(systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit");
2212 initial_menuitems_count = GetMenuItemCount(session_menu);
2213
2214 /* Set the default menu item. */
2215 SetMenuDefaultItem(systray_menu, IDM_VIEWKEYS, FALSE);
2216
2217 ShowWindow(hwnd, SW_HIDE);
2218
2219 /*
2220 * Main message loop.
2221 */
2222 while (GetMessage(&msg, NULL, 0, 0) == 1) {
2223 if (!(IsWindow(keylist) && IsDialogMessage(keylist, &msg)) &&
2224 !(IsWindow(aboutbox) && IsDialogMessage(aboutbox, &msg))) {
2225 TranslateMessage(&msg);
2226 DispatchMessage(&msg);
2227 }
2228 }
2229
2230 /* Clean up the system tray icon */
2231 {
2232 NOTIFYICONDATA tnid;
2233
2234 tnid.cbSize = sizeof(NOTIFYICONDATA);
2235 tnid.hWnd = hwnd;
2236 tnid.uID = 1;
2237
2238 Shell_NotifyIcon(NIM_DELETE, &tnid);
2239
2240 DestroyMenu(systray_menu);
2241 }
2242
2243 if (keypath) filereq_free(keypath);
2244
2245 if (advapi)
2246 FreeLibrary(advapi);
2247
2248 cleanup_exit(msg.wParam);
2249 return msg.wParam; /* just in case optimiser complains */
2250 }