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