Log the text message in SSH_MSG_DISCONNECT (both protocols) so that
[u/mdw/putty] / pageant.c
CommitLineData
5c58ad2d 1/*
2 * Pageant: the PuTTY Authentication Agent.
3 */
4
5#include <windows.h>
123bc6ea 6#ifndef NO_SECURITY
d70f60ae 7#include <aclapi.h>
123bc6ea 8#endif
9b581c37 9#include <stdio.h>
49bad831 10#include <stdlib.h>
11#include <ctype.h>
02237972 12
5c58ad2d 13#include "ssh.h"
14#include "tree234.h"
15
16#define IDI_MAINICON 200
17#define IDI_TRAYICON 201
18
19#define WM_XUSER (WM_USER + 0x2000)
20#define WM_SYSTRAY (WM_XUSER + 6)
21#define WM_SYSTRAY2 (WM_XUSER + 7)
d70f60ae 22
23#define AGENT_COPYDATA_ID 0x804e50ba /* random goop */
24
25/*
26 * FIXME: maybe some day we can sort this out ...
27 */
28#define AGENT_MAX_MSGLEN 8192
5c58ad2d 29
30#define IDM_CLOSE 0x0010
31#define IDM_VIEWKEYS 0x0020
ab162329 32#define IDM_ADDKEY 0x0030
33#define IDM_ABOUT 0x0040
5c58ad2d 34
35#define APPNAME "Pageant"
36
5af3a3b6 37extern char ver[];
38
75cab814 39static HINSTANCE instance;
40static HWND hwnd;
41static HWND keylist;
42static HWND aboutbox;
43static HMENU systray_menu;
5c58ad2d 44
45cebe79 45static tree234 *rsakeys, *ssh2keys;
5c58ad2d 46
75cab814 47static int has_security;
123bc6ea 48#ifndef NO_SECURITY
016ef8ab 49typedef DWORD (WINAPI *gsi_fn_t)
50 (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION,
51 PSID *, PSID *, PACL *, PACL *,
52 PSECURITY_DESCRIPTOR *);
75cab814 53static gsi_fn_t getsecurityinfo;
123bc6ea 54#endif
016ef8ab 55
5c58ad2d 56/*
57 * We need this to link with the RSA code, because rsaencrypt()
45cebe79 58 * pads its data with random bytes. Since we only use rsadecrypt()
59 * and the signing functions, which are deterministic, this should
60 * never be called.
61 *
5c58ad2d 62 * If it _is_ called, there is a _serious_ problem, because it
63 * won't generate true random numbers. So we must scream, panic,
64 * and exit immediately if that should happen.
65 */
66int random_byte(void) {
67 MessageBox(hwnd, "Internal Error", APPNAME, MB_OK | MB_ICONERROR);
68 exit(0);
69}
70
71/*
45cebe79 72 * Blob structure for passing to the asymmetric SSH2 key compare
73 * function, prototyped here.
74 */
75struct blob {
76 unsigned char *blob;
77 int len;
78};
79static int cmpkeys_ssh2_asymm(void *av, void *bv);
80
81/*
5c58ad2d 82 * This function is needed to link with the DES code. We need not
83 * have it do anything at all.
84 */
85void logevent(char *msg) {
86}
87
88#define GET_32BIT(cp) \
89 (((unsigned long)(unsigned char)(cp)[0] << 24) | \
90 ((unsigned long)(unsigned char)(cp)[1] << 16) | \
91 ((unsigned long)(unsigned char)(cp)[2] << 8) | \
92 ((unsigned long)(unsigned char)(cp)[3]))
93
94#define PUT_32BIT(cp, value) { \
95 (cp)[0] = (unsigned char)((value) >> 24); \
96 (cp)[1] = (unsigned char)((value) >> 16); \
97 (cp)[2] = (unsigned char)((value) >> 8); \
98 (cp)[3] = (unsigned char)(value); }
99
100#define PASSPHRASE_MAXLEN 512
101
d4de2d2a 102struct PassphraseProcStruct {
103 char *passphrase;
104 char *comment;
105};
106
5c58ad2d 107/*
5af3a3b6 108 * Dialog-box function for the Licence box.
109 */
110static int CALLBACK LicenceProc (HWND hwnd, UINT msg,
111 WPARAM wParam, LPARAM lParam) {
112 switch (msg) {
113 case WM_INITDIALOG:
114 return 1;
115 case WM_COMMAND:
116 switch (LOWORD(wParam)) {
117 case IDOK:
118 EndDialog(hwnd, 1);
119 return 0;
120 }
121 return 0;
122 case WM_CLOSE:
123 EndDialog(hwnd, 1);
124 return 0;
125 }
126 return 0;
127}
128
129/*
130 * Dialog-box function for the About box.
131 */
132static int CALLBACK AboutProc (HWND hwnd, UINT msg,
133 WPARAM wParam, LPARAM lParam) {
134 switch (msg) {
135 case WM_INITDIALOG:
136 SetDlgItemText (hwnd, 100, ver);
137 return 1;
138 case WM_COMMAND:
139 switch (LOWORD(wParam)) {
140 case IDOK:
141 aboutbox = NULL;
142 DestroyWindow (hwnd);
143 return 0;
144 case 101:
145 EnableWindow(hwnd, 0);
146 DialogBox (instance, MAKEINTRESOURCE(214), NULL, LicenceProc);
147 EnableWindow(hwnd, 1);
148 SetActiveWindow(hwnd);
149 return 0;
150 }
151 return 0;
152 case WM_CLOSE:
153 aboutbox = NULL;
154 DestroyWindow (hwnd);
155 return 0;
156 }
157 return 0;
158}
159
160/*
5c58ad2d 161 * Dialog-box function for the passphrase box.
162 */
163static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
164 WPARAM wParam, LPARAM lParam) {
165 static char *passphrase;
d4de2d2a 166 struct PassphraseProcStruct *p;
5c58ad2d 167
168 switch (msg) {
169 case WM_INITDIALOG:
45cebe79 170 /*
171 * Centre the window.
172 */
173 { /* centre the window */
174 RECT rs, rd;
175 HWND hw;
176
177 hw = GetDesktopWindow();
178 if (GetWindowRect (hw, &rs) && GetWindowRect (hwnd, &rd))
179 MoveWindow (hwnd, (rs.right + rs.left + rd.left - rd.right)/2,
180 (rs.bottom + rs.top + rd.top - rd.bottom)/2,
181 rd.right-rd.left, rd.bottom-rd.top, TRUE);
182 }
183
ab162329 184 SetForegroundWindow(hwnd);
185 SetWindowPos (hwnd, HWND_TOP, 0, 0, 0, 0,
186 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
d4de2d2a 187 p = (struct PassphraseProcStruct *)lParam;
188 passphrase = p->passphrase;
189 if (p->comment)
190 SetDlgItemText(hwnd, 101, p->comment);
5c58ad2d 191 *passphrase = 0;
192 return 0;
193 case WM_COMMAND:
194 switch (LOWORD(wParam)) {
195 case IDOK:
196 if (*passphrase)
197 EndDialog (hwnd, 1);
198 else
199 MessageBeep (0);
200 return 0;
201 case IDCANCEL:
202 EndDialog (hwnd, 0);
203 return 0;
204 case 102: /* edit box */
205 if (HIWORD(wParam) == EN_CHANGE) {
206 GetDlgItemText (hwnd, 102, passphrase, PASSPHRASE_MAXLEN-1);
207 passphrase[PASSPHRASE_MAXLEN-1] = '\0';
208 }
209 return 0;
210 }
211 return 0;
212 case WM_CLOSE:
213 EndDialog (hwnd, 0);
214 return 0;
215 }
216 return 0;
217}
218
219/*
3c0b3d06 220 * Update the visible key list.
221 */
75cab814 222static void keylist_update(void) {
45cebe79 223 struct RSAKey *rkey;
224 struct ssh2_userkey *skey;
3c0b3d06 225 enum234 e;
226
227 if (keylist) {
228 SendDlgItemMessage(keylist, 100, LB_RESETCONTENT, 0, 0);
45cebe79 229 for (rkey = first234(rsakeys, &e); rkey; rkey = next234(&e)) {
230 char listentry[512], *p;
231 /*
232 * Replace two spaces in the fingerprint with tabs, for
233 * nice alignment in the box.
234 */
235 strcpy(listentry, "ssh1\t");
236 p = listentry+strlen(listentry);
237 rsa_fingerprint(p, sizeof(listentry)-(p-listentry), rkey);
238 p = strchr(listentry, ' '); if (p) *p = '\t';
239 p = strchr(listentry, ' '); if (p) *p = '\t';
240 SendDlgItemMessage (keylist, 100, LB_ADDSTRING,
241 0, (LPARAM)listentry);
242 }
243 for (skey = first234(ssh2keys, &e); skey; skey = next234(&e)) {
1c2a93c4 244 char listentry[512], *p;
45cebe79 245 int len;
1c2a93c4 246 /*
247 * Replace two spaces in the fingerprint with tabs, for
248 * nice alignment in the box.
249 */
45cebe79 250 p = skey->alg->fingerprint(skey->data);
251 strncpy(listentry, p, sizeof(listentry));
1c2a93c4 252 p = strchr(listentry, ' '); if (p) *p = '\t';
253 p = strchr(listentry, ' '); if (p) *p = '\t';
45cebe79 254 len = strlen(listentry);
255 if (len < sizeof(listentry)-2) {
256 listentry[len] = '\t';
257 strncpy(listentry+len+1, skey->comment, sizeof(listentry)-len-1);
258 }
3c0b3d06 259 SendDlgItemMessage (keylist, 100, LB_ADDSTRING,
1c2a93c4 260 0, (LPARAM)listentry);
3c0b3d06 261 }
262 SendDlgItemMessage (keylist, 100, LB_SETCURSEL, (WPARAM) -1, 0);
263 }
264}
265
266/*
5c58ad2d 267 * This function loads a key from a file and adds it.
268 */
75cab814 269static void add_keyfile(char *filename) {
5c58ad2d 270 char passphrase[PASSPHRASE_MAXLEN];
45cebe79 271 struct RSAKey *rkey;
272 struct ssh2_userkey *skey;
5c58ad2d 273 int needs_pass;
274 int ret;
275 int attempts;
d4de2d2a 276 char *comment;
277 struct PassphraseProcStruct pps;
45cebe79 278 int ver;
279
280 ver = keyfile_version(filename);
281 if (ver == 0) {
282 MessageBox(NULL, "Couldn't load private key.", APPNAME,
283 MB_OK | MB_ICONERROR);
284 return;
285 }
5c58ad2d 286
45cebe79 287 if (ver == 1)
288 needs_pass = rsakey_encrypted(filename, &comment);
289 else
290 needs_pass = ssh2_userkey_encrypted(filename, &comment);
5c58ad2d 291 attempts = 0;
45cebe79 292 if (ver == 1)
293 rkey = smalloc(sizeof(*rkey));
d4de2d2a 294 pps.passphrase = passphrase;
295 pps.comment = comment;
5c58ad2d 296 do {
297 if (needs_pass) {
298 int dlgret;
299 dlgret = DialogBoxParam(instance, MAKEINTRESOURCE(210),
300 NULL, PassphraseProc,
d4de2d2a 301 (LPARAM)&pps);
5c58ad2d 302 if (!dlgret) {
dcbde236 303 if (comment) sfree(comment);
45cebe79 304 if (ver == 1)
305 sfree(rkey);
5c58ad2d 306 return; /* operation cancelled */
307 }
308 } else
309 *passphrase = '\0';
45cebe79 310 if (ver == 1)
311 ret = loadrsakey(filename, rkey, passphrase);
312 else {
313 skey = ssh2_load_userkey(filename, passphrase);
314 if (skey == SSH2_WRONG_PASSPHRASE)
315 ret = -1;
316 else if (!skey)
317 ret = 0;
318 else
319 ret = 1;
320 }
5c58ad2d 321 attempts++;
322 } while (ret == -1);
dcbde236 323 if (comment) sfree(comment);
5c58ad2d 324 if (ret == 0) {
5af3a3b6 325 MessageBox(NULL, "Couldn't load private key.", APPNAME,
5c58ad2d 326 MB_OK | MB_ICONERROR);
45cebe79 327 if (ver == 1)
328 sfree(rkey);
5c58ad2d 329 return;
330 }
45cebe79 331 if (ver == 1) {
332 if (add234(rsakeys, rkey) != rkey)
333 sfree(rkey); /* already present, don't waste RAM */
334 } else {
335 if (add234(ssh2keys, skey) != skey) {
336 skey->alg->freekey(skey->data);
337 sfree(skey); /* already present, don't waste RAM */
338 }
339 }
5c58ad2d 340}
341
342/*
343 * This is the main agent function that answers messages.
344 */
75cab814 345static void answer_msg(void *msg) {
d70f60ae 346 unsigned char *p = msg;
347 unsigned char *ret = msg;
5c58ad2d 348 int type;
349
5c58ad2d 350 /*
351 * Get the message type.
352 */
353 type = p[4];
354
355 p += 5;
5c58ad2d 356 switch (type) {
45cebe79 357 case SSH1_AGENTC_REQUEST_RSA_IDENTITIES:
5c58ad2d 358 /*
45cebe79 359 * Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER.
5c58ad2d 360 */
361 {
362 enum234 e;
363 struct RSAKey *key;
364 int len, nkeys;
365
366 /*
367 * Count up the number and length of keys we hold.
368 */
369 len = nkeys = 0;
370 for (key = first234(rsakeys, &e); key; key = next234(&e)) {
371 nkeys++;
372 len += 4; /* length field */
373 len += ssh1_bignum_length(key->exponent);
374 len += ssh1_bignum_length(key->modulus);
375 len += 4 + strlen(key->comment);
376 }
377
378 /*
379 * Packet header is the obvious five bytes, plus four
380 * bytes for the key count.
381 */
382 len += 5 + 4;
d70f60ae 383 if (len > AGENT_MAX_MSGLEN)
384 goto failure; /* aaargh! too much stuff! */
385 PUT_32BIT(ret, len-4);
45cebe79 386 ret[4] = SSH1_AGENT_RSA_IDENTITIES_ANSWER;
d70f60ae 387 PUT_32BIT(ret+5, nkeys);
388 p = ret + 5 + 4;
389 for (key = first234(rsakeys, &e); key; key = next234(&e)) {
390 PUT_32BIT(p, ssh1_bignum_bitcount(key->modulus));
391 p += 4;
392 p += ssh1_write_bignum(p, key->exponent);
393 p += ssh1_write_bignum(p, key->modulus);
394 PUT_32BIT(p, strlen(key->comment));
395 memcpy(p+4, key->comment, strlen(key->comment));
396 p += 4 + strlen(key->comment);
5c58ad2d 397 }
398 }
399 break;
45cebe79 400 case SSH2_AGENTC_REQUEST_IDENTITIES:
5c58ad2d 401 /*
45cebe79 402 * Reply with SSH2_AGENT_IDENTITIES_ANSWER.
403 */
404 {
405 enum234 e;
406 struct ssh2_userkey *key;
407 int len, nkeys;
408 unsigned char *blob;
409 int bloblen;
410
411 /*
412 * Count up the number and length of keys we hold.
413 */
414 len = nkeys = 0;
415 for (key = first234(ssh2keys, &e); key; key = next234(&e)) {
416 nkeys++;
417 len += 4; /* length field */
418 blob = key->alg->public_blob(key->data, &bloblen);
419 len += bloblen;
420 sfree(blob);
421 len += 4 + strlen(key->comment);
422 }
423
424 /*
425 * Packet header is the obvious five bytes, plus four
426 * bytes for the key count.
427 */
428 len += 5 + 4;
429 if (len > AGENT_MAX_MSGLEN)
430 goto failure; /* aaargh! too much stuff! */
431 PUT_32BIT(ret, len-4);
432 ret[4] = SSH2_AGENT_IDENTITIES_ANSWER;
433 PUT_32BIT(ret+5, nkeys);
434 p = ret + 5 + 4;
435 for (key = first234(ssh2keys, &e); key; key = next234(&e)) {
436 blob = key->alg->public_blob(key->data, &bloblen);
437 PUT_32BIT(p, bloblen);
438 p += 4;
439 memcpy(p, blob, bloblen);
440 p += bloblen;
441 sfree(blob);
442 PUT_32BIT(p, strlen(key->comment));
443 memcpy(p+4, key->comment, strlen(key->comment));
444 p += 4 + strlen(key->comment);
445 }
446 }
447 break;
448 case SSH1_AGENTC_RSA_CHALLENGE:
449 /*
450 * Reply with either SSH1_AGENT_RSA_RESPONSE or
5c58ad2d 451 * SSH_AGENT_FAILURE, depending on whether we have that key
452 * or not.
453 */
454 {
455 struct RSAKey reqkey, *key;
456 Bignum challenge, response;
457 unsigned char response_source[48], response_md5[16];
458 struct MD5Context md5c;
459 int i, len;
460
461 p += 4;
462 p += ssh1_read_bignum(p, &reqkey.exponent);
463 p += ssh1_read_bignum(p, &reqkey.modulus);
464 p += ssh1_read_bignum(p, &challenge);
465 memcpy(response_source+32, p, 16); p += 16;
466 if (GET_32BIT(p) != 1 ||
467 (key = find234(rsakeys, &reqkey, NULL)) == NULL) {
468 freebn(reqkey.exponent);
469 freebn(reqkey.modulus);
470 freebn(challenge);
471 goto failure;
472 }
473 response = rsadecrypt(challenge, key);
474 for (i = 0; i < 32; i++)
475 response_source[i] = bignum_byte(response, 31-i);
476
477 MD5Init(&md5c);
478 MD5Update(&md5c, response_source, 48);
479 MD5Final(response_md5, &md5c);
480 memset(response_source, 0, 48); /* burn the evidence */
481 freebn(response); /* and that evidence */
482 freebn(challenge); /* yes, and that evidence */
483 freebn(reqkey.exponent); /* and free some memory ... */
484 freebn(reqkey.modulus); /* ... while we're at it. */
485
486 /*
487 * Packet is the obvious five byte header, plus sixteen
488 * bytes of MD5.
489 */
490 len = 5 + 16;
d70f60ae 491 PUT_32BIT(ret, len-4);
45cebe79 492 ret[4] = SSH1_AGENT_RSA_RESPONSE;
d70f60ae 493 memcpy(ret+5, response_md5, 16);
5c58ad2d 494 }
495 break;
45cebe79 496 case SSH2_AGENTC_SIGN_REQUEST:
497 /*
498 * Reply with either SSH2_AGENT_RSA_RESPONSE or
499 * SSH_AGENT_FAILURE, depending on whether we have that key
500 * or not.
501 */
502 {
503 struct ssh2_userkey *key;
504 struct blob b;
505 unsigned char *data, *signature;
506 int datalen, siglen, len;
507
508 b.len = GET_32BIT(p);
509 p += 4;
510 b.blob = p;
511 p += b.len;
512 datalen = GET_32BIT(p);
513 p += 4;
514 data = p;
515 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
516 if (!key)
517 goto failure;
518 signature = key->alg->sign(key->data, data, datalen, &siglen);
519 len = 5+4+siglen;
520 PUT_32BIT(ret, len-4);
521 ret[4] = SSH2_AGENT_SIGN_RESPONSE;
522 PUT_32BIT(ret+5, siglen);
523 memcpy(ret+5+4, signature, siglen);
524 sfree(signature);
525 }
526 break;
527 case SSH1_AGENTC_ADD_RSA_IDENTITY:
5c58ad2d 528 /*
529 * Add to the list and return SSH_AGENT_SUCCESS, or
530 * SSH_AGENT_FAILURE if the key was malformed.
531 */
3c0b3d06 532 {
533 struct RSAKey *key;
534 char *comment;
dcbde236 535 key = smalloc(sizeof(struct RSAKey));
3c0b3d06 536 memset(key, 0, sizeof(key));
537 p += makekey(p, key, NULL, 1);
538 p += makeprivate(p, key);
45cebe79 539 p += ssh1_read_bignum(p, key->iqmp); /* p^-1 mod q */
540 p += ssh1_read_bignum(p, key->p); /* p */
541 p += ssh1_read_bignum(p, key->q); /* q */
dcbde236 542 comment = smalloc(GET_32BIT(p));
3c0b3d06 543 if (comment) {
544 memcpy(comment, p+4, GET_32BIT(p));
545 key->comment = comment;
546 }
547 PUT_32BIT(ret, 1);
548 ret[4] = SSH_AGENT_FAILURE;
549 if (add234(rsakeys, key) == key) {
550 keylist_update();
551 ret[4] = SSH_AGENT_SUCCESS;
552 } else {
553 freersakey(key);
dcbde236 554 sfree(key);
3c0b3d06 555 }
556 }
5c58ad2d 557 break;
45cebe79 558 case SSH2_AGENTC_ADD_IDENTITY:
559 /*
560 * Add to the list and return SSH_AGENT_SUCCESS, or
561 * SSH_AGENT_FAILURE if the key was malformed.
562 */
563 {
564 struct ssh2_userkey *key;
565 char *comment, *alg;
566 int alglen, commlen;
567 int bloblen;
568
569 key = smalloc(sizeof(struct ssh2_userkey));
570
571 alglen = GET_32BIT(p); p += 4;
572 alg = p; p += alglen;
573 /* Add further algorithm names here. */
574 if (alglen == 7 && !memcmp(alg, "ssh-rsa", 7))
575 key->alg = &ssh_rsa;
576 else {
577 sfree(key);
578 goto failure;
579 }
580
581 bloblen = GET_32BIT((unsigned char *)msg) - (p-(unsigned char *)msg-4);
582 key->data = key->alg->openssh_createkey(&p, &bloblen);
583 if (!key->data) {
584 sfree(key);
585 goto failure;
586 }
587 commlen = GET_32BIT(p); p += 4;
588
589 comment = smalloc(commlen+1);
590 if (comment) {
591 memcpy(comment, p, commlen);
592 comment[commlen] = '\0';
593 }
594 key->comment = comment;
595
596 PUT_32BIT(ret, 1);
597 ret[4] = SSH_AGENT_FAILURE;
598 if (add234(ssh2keys, key) == key) {
599 keylist_update();
600 ret[4] = SSH_AGENT_SUCCESS;
601 } else {
602 key->alg->freekey(key->data);
603 sfree(key->comment);
604 sfree(key);
605 }
606 }
607 break;
608 case SSH1_AGENTC_REMOVE_RSA_IDENTITY:
5c58ad2d 609 /*
610 * Remove from the list and return SSH_AGENT_SUCCESS, or
611 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
612 * start with.
613 */
3c0b3d06 614 {
615 struct RSAKey reqkey, *key;
616
617 p += makekey(p, &reqkey, NULL, 0);
618 key = find234(rsakeys, &reqkey, NULL);
619 freebn(reqkey.exponent);
620 freebn(reqkey.modulus);
621 PUT_32BIT(ret, 1);
622 ret[4] = SSH_AGENT_FAILURE;
623 if (key) {
624 del234(rsakeys, key);
625 keylist_update();
d4de2d2a 626 freersakey(key);
45cebe79 627 sfree(key);
3c0b3d06 628 ret[4] = SSH_AGENT_SUCCESS;
629 }
630 }
5c58ad2d 631 break;
45cebe79 632 case SSH2_AGENTC_REMOVE_IDENTITY:
633 /*
634 * Remove from the list and return SSH_AGENT_SUCCESS, or
635 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
636 * start with.
637 */
638 {
639 struct ssh2_userkey *key;
640 struct blob b;
641
642 b.len = GET_32BIT(p);
643 p += 4;
644 b.blob = p;
645 p += b.len;
646 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
647 if (!key)
648 goto failure;
649
650 PUT_32BIT(ret, 1);
651 ret[4] = SSH_AGENT_FAILURE;
652 if (key) {
653 del234(ssh2keys, key);
654 keylist_update();
655 key->alg->freekey(key->data);
656 sfree(key);
657 ret[4] = SSH_AGENT_SUCCESS;
658 }
659 }
660 break;
661 case SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES:
662 /*
663 * Remove all SSH1 keys. Always returns success.
664 */
665 {
666 struct RSAKey *rkey;
667 enum234 e;
668
669 while ( (rkey = first234(rsakeys, &e)) != NULL ) {
670 del234(rsakeys, rkey);
671 freersakey(rkey);
672 sfree(rkey);
673 }
674 keylist_update();
675
676 PUT_32BIT(ret, 1);
677 ret[4] = SSH_AGENT_SUCCESS;
678 }
679 break;
680 case SSH2_AGENTC_REMOVE_ALL_IDENTITIES:
681 /*
682 * Remove all SSH2 keys. Always returns success.
683 */
684 {
685 struct ssh2_userkey *skey;
686 enum234 e;
687
688 while ( (skey = first234(ssh2keys, &e)) != NULL ) {
689 del234(ssh2keys, skey);
690 skey->alg->freekey(skey->data);
691 sfree(skey);
692 }
693 keylist_update();
694
695 PUT_32BIT(ret, 1);
696 ret[4] = SSH_AGENT_SUCCESS;
697 }
698 break;
5c58ad2d 699 default:
700 failure:
701 /*
702 * Unrecognised message. Return SSH_AGENT_FAILURE.
703 */
d70f60ae 704 PUT_32BIT(ret, 1);
705 ret[4] = SSH_AGENT_FAILURE;
5c58ad2d 706 break;
707 }
5c58ad2d 708}
709
710/*
711 * Key comparison function for the 2-3-4 tree of RSA keys.
712 */
45cebe79 713static int cmpkeys_rsa(void *av, void *bv) {
5c58ad2d 714 struct RSAKey *a = (struct RSAKey *)av;
715 struct RSAKey *b = (struct RSAKey *)bv;
716 Bignum am, bm;
717 int alen, blen;
718
719 am = a->modulus;
720 bm = b->modulus;
721 /*
722 * Compare by length of moduli.
723 */
724 alen = ssh1_bignum_bitcount(am);
725 blen = ssh1_bignum_bitcount(bm);
726 if (alen > blen) return +1; else if (alen < blen) return -1;
727 /*
728 * Now compare by moduli themselves.
729 */
730 alen = (alen + 7) / 8; /* byte count */
731 while (alen-- > 0) {
732 int abyte, bbyte;
733 abyte = bignum_byte(am, alen);
734 bbyte = bignum_byte(bm, alen);
735 if (abyte > bbyte) return +1; else if (abyte < bbyte) return -1;
736 }
737 /*
738 * Give up.
739 */
740 return 0;
741}
742
45cebe79 743/*
744 * Key comparison function for the 2-3-4 tree of SSH2 keys.
745 */
746static int cmpkeys_ssh2(void *av, void *bv) {
747 struct ssh2_userkey *a = (struct ssh2_userkey *)av;
748 struct ssh2_userkey *b = (struct ssh2_userkey *)bv;
749 int i;
750 int alen, blen;
751 unsigned char *ablob, *bblob;
752 int c;
753
754 /*
755 * Compare purely by public blob.
756 */
757 ablob = a->alg->public_blob(a->data, &alen);
758 bblob = b->alg->public_blob(b->data, &blen);
759
760 c = 0;
761 for (i = 0; i < alen && i < blen; i++) {
762 if (ablob[i] < bblob[i]) {
763 c = -1; break;
764 } else if (ablob[i] > bblob[i]) {
765 c = +1; break;
766 }
767 }
768 if (c == 0 && i < alen) c = +1; /* a is longer */
769 if (c == 0 && i < blen) c = -1; /* a is longer */
770
771 sfree(ablob);
772 sfree(bblob);
773
774 return c;
775}
776
777/*
778 * Key comparison function for looking up a blob in the 2-3-4 tree
779 * of SSH2 keys.
780 */
781static int cmpkeys_ssh2_asymm(void *av, void *bv) {
782 struct blob *a = (struct blob *)av;
783 struct ssh2_userkey *b = (struct ssh2_userkey *)bv;
784 int i;
785 int alen, blen;
786 unsigned char *ablob, *bblob;
787 int c;
788
789 /*
790 * Compare purely by public blob.
791 */
792 ablob = a->blob;
793 alen = a->len;
794 bblob = b->alg->public_blob(b->data, &blen);
795
796 c = 0;
797 for (i = 0; i < alen && i < blen; i++) {
798 if (ablob[i] < bblob[i]) {
799 c = -1; break;
800 } else if (ablob[i] > bblob[i]) {
801 c = +1; break;
802 }
803 }
804 if (c == 0 && i < alen) c = +1; /* a is longer */
805 if (c == 0 && i < blen) c = -1; /* a is longer */
806
807 sfree(bblob);
808
809 return c;
810}
811
5c58ad2d 812static void error(char *s) {
813 MessageBox(hwnd, s, APPNAME, MB_OK | MB_ICONERROR);
814}
815
816/*
ab162329 817 * Prompt for a key file to add, and add it.
818 */
819static void prompt_add_keyfile(void) {
820 OPENFILENAME of;
821 char filename[FILENAME_MAX];
822 memset(&of, 0, sizeof(of));
823#ifdef OPENFILENAME_SIZE_VERSION_400
824 of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
825#else
826 of.lStructSize = sizeof(of);
827#endif
828 of.hwndOwner = hwnd;
829 of.lpstrFilter = "All Files\0*\0\0\0";
830 of.lpstrCustomFilter = NULL;
831 of.nFilterIndex = 1;
832 of.lpstrFile = filename; *filename = '\0';
833 of.nMaxFile = sizeof(filename);
834 of.lpstrFileTitle = NULL;
835 of.lpstrInitialDir = NULL;
836 of.lpstrTitle = "Select Private Key File";
837 of.Flags = 0;
838 if (GetOpenFileName(&of)) {
839 add_keyfile(filename);
840 keylist_update();
841 }
842}
843
844/*
5c58ad2d 845 * Dialog-box function for the key list box.
846 */
847static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
848 WPARAM wParam, LPARAM lParam) {
849 enum234 e;
45cebe79 850 struct RSAKey *rkey;
851 struct ssh2_userkey *skey;
5c58ad2d 852
853 switch (msg) {
854 case WM_INITDIALOG:
45cebe79 855 /*
856 * Centre the window.
857 */
858 { /* centre the window */
859 RECT rs, rd;
860 HWND hw;
861
862 hw = GetDesktopWindow();
863 if (GetWindowRect (hw, &rs) && GetWindowRect (hwnd, &rd))
864 MoveWindow (hwnd, (rs.right + rs.left + rd.left - rd.right)/2,
865 (rs.bottom + rs.top + rd.top - rd.bottom)/2,
866 rd.right-rd.left, rd.bottom-rd.top, TRUE);
867 }
868
1c2a93c4 869 keylist = hwnd;
870 {
45cebe79 871 static int tabs[] = {35, 60, 210};
872 SendDlgItemMessage (hwnd, 100, LB_SETTABSTOPS,
873 sizeof(tabs)/sizeof(*tabs), (LPARAM) tabs);
1c2a93c4 874 }
875 keylist_update();
5c58ad2d 876 return 0;
877 case WM_COMMAND:
878 switch (LOWORD(wParam)) {
879 case IDOK:
880 case IDCANCEL:
881 keylist = NULL;
882 DestroyWindow(hwnd);
883 return 0;
884 case 101: /* add key */
885 if (HIWORD(wParam) == BN_CLICKED ||
886 HIWORD(wParam) == BN_DOUBLECLICKED) {
ab162329 887 prompt_add_keyfile();
5c58ad2d 888 }
889 return 0;
890 case 102: /* remove key */
891 if (HIWORD(wParam) == BN_CLICKED ||
892 HIWORD(wParam) == BN_DOUBLECLICKED) {
893 int n = SendDlgItemMessage (hwnd, 100, LB_GETCURSEL, 0, 0);
f70a606e 894 if (n == LB_ERR) {
5c58ad2d 895 MessageBeep(0);
896 break;
897 }
45cebe79 898 for (rkey = first234(rsakeys, &e); rkey; rkey = next234(&e))
5c58ad2d 899 if (n-- == 0)
900 break;
45cebe79 901 if (rkey) {
902 del234(rsakeys, rkey);
903 freersakey(rkey);
904 sfree(rkey);
905 } else {
906 for (skey = first234(ssh2keys, &e); skey; skey = next234(&e))
907 if (n-- == 0)
908 break;
909 if (skey) {
910 del234(ssh2keys, skey);
911 skey->alg->freekey(skey->data);
912 sfree(skey);
913 }
914 }
1c2a93c4 915 keylist_update();
5c58ad2d 916 }
917 return 0;
918 }
919 return 0;
920 case WM_CLOSE:
921 keylist = NULL;
922 DestroyWindow(hwnd);
923 return 0;
924 }
925 return 0;
926}
927
928static LRESULT CALLBACK WndProc (HWND hwnd, UINT message,
929 WPARAM wParam, LPARAM lParam) {
930 int ret;
931 static int menuinprogress;
932
933 switch (message) {
934 case WM_SYSTRAY:
935 if (lParam == WM_RBUTTONUP) {
936 POINT cursorpos;
937 GetCursorPos(&cursorpos);
938 PostMessage(hwnd, WM_SYSTRAY2, cursorpos.x, cursorpos.y);
905beba5 939 } else if (lParam == WM_LBUTTONDBLCLK) {
940 /* Equivalent to IDM_VIEWKEYS. */
941 PostMessage(hwnd, WM_COMMAND, IDM_VIEWKEYS, 0);
5c58ad2d 942 }
943 break;
944 case WM_SYSTRAY2:
945 if (!menuinprogress) {
946 menuinprogress = 1;
947 SetForegroundWindow(hwnd);
948 ret = TrackPopupMenu(systray_menu,
949 TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
950 TPM_RIGHTBUTTON,
951 wParam, lParam, 0, hwnd, NULL);
952 menuinprogress = 0;
953 }
954 break;
955 case WM_COMMAND:
956 case WM_SYSCOMMAND:
957 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
958 case IDM_CLOSE:
959 SendMessage(hwnd, WM_CLOSE, 0, 0);
960 break;
961 case IDM_VIEWKEYS:
962 if (!keylist) {
963 keylist = CreateDialog (instance, MAKEINTRESOURCE(211),
964 NULL, KeyListProc);
965 ShowWindow (keylist, SW_SHOWNORMAL);
905beba5 966 /*
967 * Sometimes the window comes up minimised / hidden
968 * for no obvious reason. Prevent this.
969 */
970 SetForegroundWindow(keylist);
971 SetWindowPos (keylist, HWND_TOP, 0, 0, 0, 0,
972 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
5c58ad2d 973 }
974 break;
ab162329 975 case IDM_ADDKEY:
976 prompt_add_keyfile();
977 break;
5af3a3b6 978 case IDM_ABOUT:
979 if (!aboutbox) {
980 aboutbox = CreateDialog (instance, MAKEINTRESOURCE(213),
981 NULL, AboutProc);
982 ShowWindow (aboutbox, SW_SHOWNORMAL);
983 /*
984 * Sometimes the window comes up minimised / hidden
985 * for no obvious reason. Prevent this.
986 */
987 SetForegroundWindow(aboutbox);
988 SetWindowPos (aboutbox, HWND_TOP, 0, 0, 0, 0,
989 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
990 }
991 break;
5c58ad2d 992 }
993 break;
994 case WM_DESTROY:
995 PostQuitMessage (0);
996 return 0;
997 case WM_COPYDATA:
998 {
999 COPYDATASTRUCT *cds;
d70f60ae 1000 char *mapname;
1001 void *p;
1002 HANDLE filemap, proc;
1003 PSID mapowner, procowner;
1004 PSECURITY_DESCRIPTOR psd1 = NULL, psd2 = NULL;
1005 int ret = 0;
5c58ad2d 1006
1007 cds = (COPYDATASTRUCT *)lParam;
d70f60ae 1008 if (cds->dwData != AGENT_COPYDATA_ID)
1009 return 0; /* not our message, mate */
1010 mapname = (char *)cds->lpData;
1011 if (mapname[cds->cbData - 1] != '\0')
1012 return 0; /* failure to be ASCIZ! */
1013#ifdef DEBUG_IPC
1014 debug(("mapname is :%s:\r\n", mapname));
1015#endif
1016 filemap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, mapname);
1017#ifdef DEBUG_IPC
1018 debug(("filemap is %p\r\n", filemap));
1019#endif
1020 if (filemap != NULL && filemap != INVALID_HANDLE_VALUE) {
1021 int rc;
123bc6ea 1022#ifndef NO_SECURITY
016ef8ab 1023 if (has_security) {
1024 if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE,
1025 GetCurrentProcessId())) == NULL) {
d70f60ae 1026#ifdef DEBUG_IPC
016ef8ab 1027 debug(("couldn't get handle for process\r\n"));
d70f60ae 1028#endif
016ef8ab 1029 return 0;
1030 }
1031 if (getsecurityinfo(proc, SE_KERNEL_OBJECT,
1032 OWNER_SECURITY_INFORMATION,
1033 &procowner, NULL, NULL, NULL,
1034 &psd2) != ERROR_SUCCESS) {
d70f60ae 1035#ifdef DEBUG_IPC
016ef8ab 1036 debug(("couldn't get owner info for process\r\n"));
d70f60ae 1037#endif
016ef8ab 1038 CloseHandle(proc);
1039 return 0; /* unable to get security info */
1040 }
d70f60ae 1041 CloseHandle(proc);
016ef8ab 1042 if ((rc = getsecurityinfo(filemap, SE_KERNEL_OBJECT,
1043 OWNER_SECURITY_INFORMATION,
1044 &mapowner, NULL, NULL, NULL,
1045 &psd1) != ERROR_SUCCESS)) {
d70f60ae 1046#ifdef DEBUG_IPC
016ef8ab 1047 debug(("couldn't get owner info for filemap: %d\r\n", rc));
d70f60ae 1048#endif
016ef8ab 1049 return 0;
1050 }
1051#ifdef DEBUG_IPC
1052 debug(("got security stuff\r\n"));
1053#endif
1054 if (!EqualSid(mapowner, procowner))
1055 return 0; /* security ID mismatch! */
d70f60ae 1056#ifdef DEBUG_IPC
016ef8ab 1057 debug(("security stuff matched\r\n"));
d70f60ae 1058#endif
016ef8ab 1059 LocalFree(psd1);
1060 LocalFree(psd2);
1061 } else {
d70f60ae 1062#ifdef DEBUG_IPC
016ef8ab 1063 debug(("security APIs not present\r\n"));
d70f60ae 1064#endif
016ef8ab 1065 }
123bc6ea 1066#endif
d70f60ae 1067 p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
1068#ifdef DEBUG_IPC
1069 debug(("p is %p\r\n", p));
1070 {int i; for(i=0;i<5;i++)debug(("p[%d]=%02x\r\n", i, ((unsigned char *)p)[i]));}
1071#endif
1072 answer_msg(p);
1073 ret = 1;
1074 UnmapViewOfFile(p);
1075 }
1076 CloseHandle(filemap);
1077 return ret;
5c58ad2d 1078 }
5c58ad2d 1079 }
1080
1081 return DefWindowProc (hwnd, message, wParam, lParam);
1082}
1083
1084int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) {
1085 WNDCLASS wndclass;
5c58ad2d 1086 MSG msg;
016ef8ab 1087 OSVERSIONINFO osi;
1088 HMODULE advapi;
1089
1090 /*
1091 * Determine whether we're an NT system (should have security
1092 * APIs) or a non-NT system (don't do security).
1093 */
1094 memset(&osi, 0, sizeof(OSVERSIONINFO));
1095 osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
1096 if (GetVersionEx(&osi) && osi.dwPlatformId==VER_PLATFORM_WIN32_NT) {
1097 has_security = TRUE;
1098 } else
1099 has_security = FALSE;
1100
1101 if (has_security) {
123bc6ea 1102#ifndef NO_SECURITY
016ef8ab 1103 /*
1104 * Attempt to ge the security API we need.
1105 */
1106 advapi = LoadLibrary("ADVAPI32.DLL");
1107 getsecurityinfo = (gsi_fn_t)GetProcAddress(advapi, "GetSecurityInfo");
1108 if (!getsecurityinfo) {
1109 MessageBox(NULL,
1110 "Unable to access security APIs. Pageant will\n"
1111 "not run, in case it causes a security breach.",
1112 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
1113 return 1;
1114 }
123bc6ea 1115#else
1116 MessageBox(NULL,
1117 "This program has been compiled for Win9X and will\n"
1118 "not run on NT, in case it causes a security breach.",
1119 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
1120 return 1;
1121#endif
016ef8ab 1122 } else
1123 advapi = NULL;
5c58ad2d 1124
2faac2e1 1125 /*
1126 * First bomb out totally if we are already running.
1127 */
1128 if (FindWindow("Pageant", "Pageant")) {
1129 MessageBox(NULL, "Pageant is already running", "Pageant Error",
1130 MB_ICONERROR | MB_OK);
016ef8ab 1131 if (advapi) FreeLibrary(advapi);
2faac2e1 1132 return 0;
1133 }
1134
5c58ad2d 1135 instance = inst;
1136
1137 if (!prev) {
1138 wndclass.style = 0;
1139 wndclass.lpfnWndProc = WndProc;
1140 wndclass.cbClsExtra = 0;
1141 wndclass.cbWndExtra = 0;
1142 wndclass.hInstance = inst;
1143 wndclass.hIcon = LoadIcon (inst,
1144 MAKEINTRESOURCE(IDI_MAINICON));
1145 wndclass.hCursor = LoadCursor (NULL, IDC_IBEAM);
1146 wndclass.hbrBackground = GetStockObject (BLACK_BRUSH);
1147 wndclass.lpszMenuName = NULL;
1148 wndclass.lpszClassName = APPNAME;
1149
1150 RegisterClass (&wndclass);
1151 }
1152
1153 hwnd = keylist = NULL;
1154
1155 hwnd = CreateWindow (APPNAME, APPNAME,
1156 WS_OVERLAPPEDWINDOW | WS_VSCROLL,
1157 CW_USEDEFAULT, CW_USEDEFAULT,
1158 100, 100, NULL, NULL, inst, NULL);
1159
1160 /* Set up a system tray icon */
1161 {
1162 BOOL res;
1163 NOTIFYICONDATA tnid;
1164 HICON hicon;
1165
1166#ifdef NIM_SETVERSION
1167 tnid.uVersion = 0;
1168 res = Shell_NotifyIcon(NIM_SETVERSION, &tnid);
1169#endif
1170
1171 tnid.cbSize = sizeof(NOTIFYICONDATA);
1172 tnid.hWnd = hwnd;
1173 tnid.uID = 1; /* unique within this systray use */
1174 tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
1175 tnid.uCallbackMessage = WM_SYSTRAY;
1176 tnid.hIcon = hicon = LoadIcon (instance, MAKEINTRESOURCE(201));
1177 strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
1178
1179 res = Shell_NotifyIcon(NIM_ADD, &tnid);
1180
1181 if (hicon)
1182 DestroyIcon(hicon);
1183
1184 systray_menu = CreatePopupMenu();
ab162329 1185 /* accelerators used: vkxa */
70acd63f 1186 AppendMenu (systray_menu, MF_ENABLED, IDM_VIEWKEYS, "&View Keys");
ab162329 1187 AppendMenu (systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key");
5af3a3b6 1188 AppendMenu (systray_menu, MF_ENABLED, IDM_ABOUT, "&About");
70acd63f 1189 AppendMenu (systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit");
5c58ad2d 1190 }
1191
1192 ShowWindow (hwnd, SW_HIDE);
1193
1194 /*
dacbd0e8 1195 * Initialise storage for RSA keys.
1196 */
45cebe79 1197 rsakeys = newtree234(cmpkeys_rsa);
1198 ssh2keys = newtree234(cmpkeys_ssh2);
dacbd0e8 1199
1200 /*
45cebe79 1201 * Process the command line and add keys as listed on it.
5c58ad2d 1202 */
1203 {
8c42f28c 1204 char *p;
1205 int inquotes = 0;
1206 p = cmdline;
dacbd0e8 1207 while (*p) {
1208 while (*p && isspace(*p)) p++;
1209 if (*p && !isspace(*p)) {
8c42f28c 1210 char *q = p, *pp = p;
1211 while (*p && (inquotes || !isspace(*p)))
1212 {
1213 if (*p == '"') {
1214 inquotes = !inquotes;
1215 p++;
1216 continue;
1217 }
1218 *pp++ = *p++;
1219 }
1220 if (*pp) {
1221 if (*p) p++;
1222 *pp++ = '\0';
1223 }
dacbd0e8 1224 add_keyfile(q);
1225 }
1226 }
5c58ad2d 1227 }
1228
1229 /*
dacbd0e8 1230 * Main message loop.
5c58ad2d 1231 */
5c58ad2d 1232 while (GetMessage(&msg, NULL, 0, 0) == 1) {
1233 TranslateMessage(&msg);
1234 DispatchMessage(&msg);
1235 }
1236
1237 /* Clean up the system tray icon */
1238 {
1239 NOTIFYICONDATA tnid;
1240
1241 tnid.cbSize = sizeof(NOTIFYICONDATA);
1242 tnid.hWnd = hwnd;
1243 tnid.uID = 1;
1244
1245 Shell_NotifyIcon(NIM_DELETE, &tnid);
1246
1247 DestroyMenu(systray_menu);
1248 }
1249
016ef8ab 1250 if (advapi) FreeLibrary(advapi);
5c58ad2d 1251 exit(msg.wParam);
1252}