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