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