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