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