Oops - the help for the new Telnet NL option wasn't quite there.
[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>
ddecd643 12#include <tchar.h>
13
5c58ad2d 14#include "ssh.h"
ecea795f 15#include "misc.h"
5c58ad2d 16#include "tree234.h"
17
18#define IDI_MAINICON 200
19#define IDI_TRAYICON 201
20
21#define WM_XUSER (WM_USER + 0x2000)
22#define WM_SYSTRAY (WM_XUSER + 6)
23#define WM_SYSTRAY2 (WM_XUSER + 7)
d70f60ae 24
25#define AGENT_COPYDATA_ID 0x804e50ba /* random goop */
26
27/*
28 * FIXME: maybe some day we can sort this out ...
29 */
30#define AGENT_MAX_MSGLEN 8192
5c58ad2d 31
32#define IDM_CLOSE 0x0010
33#define IDM_VIEWKEYS 0x0020
ab162329 34#define IDM_ADDKEY 0x0030
ecea795f 35#define IDM_HELP 0x0040
36#define IDM_ABOUT 0x0050
5c58ad2d 37
38#define APPNAME "Pageant"
39
5af3a3b6 40extern char ver[];
41
75cab814 42static HINSTANCE instance;
ecea795f 43static HWND main_hwnd;
75cab814 44static HWND keylist;
45static HWND aboutbox;
46static HMENU systray_menu;
ddecd643 47static int already_running;
ecea795f 48static int requested_help;
49
50static char *help_path;
5c58ad2d 51
45cebe79 52static tree234 *rsakeys, *ssh2keys;
5c58ad2d 53
75cab814 54static int has_security;
123bc6ea 55#ifndef NO_SECURITY
32874aea 56typedef DWORD(WINAPI * gsi_fn_t)
57 (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION,
58 PSID *, PSID *, PACL *, PACL *, PSECURITY_DESCRIPTOR *);
75cab814 59static gsi_fn_t getsecurityinfo;
123bc6ea 60#endif
016ef8ab 61
5c58ad2d 62/*
260f3dec 63 * Exports from pageantc.c
64 */
65void agent_query(void *in, int inlen, void **out, int *outlen);
66int agent_exists(void);
67
68/*
5c58ad2d 69 * We need this to link with the RSA code, because rsaencrypt()
45cebe79 70 * pads its data with random bytes. Since we only use rsadecrypt()
71 * and the signing functions, which are deterministic, this should
72 * never be called.
5c72ca61 73 *
5c58ad2d 74 * If it _is_ called, there is a _serious_ problem, because it
75 * won't generate true random numbers. So we must scream, panic,
76 * and exit immediately if that should happen.
77 */
32874aea 78int random_byte(void)
79{
ecea795f 80 MessageBox(main_hwnd, "Internal Error", APPNAME, MB_OK | MB_ICONERROR);
5c58ad2d 81 exit(0);
cdcbdf3b 82 /* this line can't be reached but it placates MSVC's warnings :-) */
83 return 0;
5c58ad2d 84}
85
86/*
45cebe79 87 * Blob structure for passing to the asymmetric SSH2 key compare
88 * function, prototyped here.
89 */
90struct blob {
91 unsigned char *blob;
92 int len;
93};
94static int cmpkeys_ssh2_asymm(void *av, void *bv);
95
96/*
5c58ad2d 97 * This function is needed to link with the DES code. We need not
98 * have it do anything at all.
99 */
32874aea 100void logevent(char *msg)
101{
5c58ad2d 102}
103
104#define GET_32BIT(cp) \
105 (((unsigned long)(unsigned char)(cp)[0] << 24) | \
106 ((unsigned long)(unsigned char)(cp)[1] << 16) | \
107 ((unsigned long)(unsigned char)(cp)[2] << 8) | \
108 ((unsigned long)(unsigned char)(cp)[3]))
109
110#define PUT_32BIT(cp, value) { \
111 (cp)[0] = (unsigned char)((value) >> 24); \
112 (cp)[1] = (unsigned char)((value) >> 16); \
113 (cp)[2] = (unsigned char)((value) >> 8); \
114 (cp)[3] = (unsigned char)(value); }
115
116#define PASSPHRASE_MAXLEN 512
117
d4de2d2a 118struct PassphraseProcStruct {
119 char *passphrase;
120 char *comment;
121};
122
0959acf7 123static tree234 *passphrases = NULL;
124
125/*
126 * After processing a list of filenames, we want to forget the
127 * passphrases.
128 */
129static void forget_passphrases(void)
130{
0959acf7 131 while (count234(passphrases) > 0) {
132 char *pp = index234(passphrases, 0);
133 memset(pp, 0, strlen(pp));
134 delpos234(passphrases, 0);
135 free(pp);
136 }
137}
138
5c58ad2d 139/*
5af3a3b6 140 * Dialog-box function for the Licence box.
141 */
32874aea 142static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
143 WPARAM wParam, LPARAM lParam)
144{
5af3a3b6 145 switch (msg) {
146 case WM_INITDIALOG:
147 return 1;
148 case WM_COMMAND:
149 switch (LOWORD(wParam)) {
150 case IDOK:
32874aea 151 EndDialog(hwnd, 1);
5af3a3b6 152 return 0;
153 }
154 return 0;
155 case WM_CLOSE:
156 EndDialog(hwnd, 1);
157 return 0;
158 }
159 return 0;
160}
161
162/*
163 * Dialog-box function for the About box.
164 */
32874aea 165static int CALLBACK AboutProc(HWND hwnd, UINT msg,
166 WPARAM wParam, LPARAM lParam)
167{
5af3a3b6 168 switch (msg) {
169 case WM_INITDIALOG:
32874aea 170 SetDlgItemText(hwnd, 100, ver);
5af3a3b6 171 return 1;
172 case WM_COMMAND:
173 switch (LOWORD(wParam)) {
174 case IDOK:
175 aboutbox = NULL;
32874aea 176 DestroyWindow(hwnd);
5af3a3b6 177 return 0;
178 case 101:
179 EnableWindow(hwnd, 0);
32874aea 180 DialogBox(instance, MAKEINTRESOURCE(214), NULL, LicenceProc);
5af3a3b6 181 EnableWindow(hwnd, 1);
32874aea 182 SetActiveWindow(hwnd);
5af3a3b6 183 return 0;
184 }
185 return 0;
186 case WM_CLOSE:
187 aboutbox = NULL;
32874aea 188 DestroyWindow(hwnd);
5af3a3b6 189 return 0;
190 }
191 return 0;
192}
193
cb5ca813 194static HWND passphrase_box;
195
5af3a3b6 196/*
5c58ad2d 197 * Dialog-box function for the passphrase box.
198 */
199static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
32874aea 200 WPARAM wParam, LPARAM lParam)
201{
8e1feb27 202 static char *passphrase = NULL;
d4de2d2a 203 struct PassphraseProcStruct *p;
5c58ad2d 204
205 switch (msg) {
206 case WM_INITDIALOG:
cb5ca813 207 passphrase_box = hwnd;
45cebe79 208 /*
209 * Centre the window.
210 */
211 { /* centre the window */
212 RECT rs, rd;
213 HWND hw;
214
215 hw = GetDesktopWindow();
32874aea 216 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
217 MoveWindow(hwnd,
218 (rs.right + rs.left + rd.left - rd.right) / 2,
219 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
220 rd.right - rd.left, rd.bottom - rd.top, TRUE);
45cebe79 221 }
222
32874aea 223 SetForegroundWindow(hwnd);
224 SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
225 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
226 p = (struct PassphraseProcStruct *) lParam;
227 passphrase = p->passphrase;
228 if (p->comment)
229 SetDlgItemText(hwnd, 101, p->comment);
230 *passphrase = 0;
231 SetDlgItemText(hwnd, 102, passphrase);
232 return 0;
5c58ad2d 233 case WM_COMMAND:
234 switch (LOWORD(wParam)) {
235 case IDOK:
236 if (*passphrase)
32874aea 237 EndDialog(hwnd, 1);
5c58ad2d 238 else
32874aea 239 MessageBeep(0);
5c58ad2d 240 return 0;
241 case IDCANCEL:
32874aea 242 EndDialog(hwnd, 0);
5c58ad2d 243 return 0;
32874aea 244 case 102: /* edit box */
8e1feb27 245 if ((HIWORD(wParam) == EN_CHANGE) && passphrase) {
32874aea 246 GetDlgItemText(hwnd, 102, passphrase,
247 PASSPHRASE_MAXLEN - 1);
248 passphrase[PASSPHRASE_MAXLEN - 1] = '\0';
249 }
250 return 0;
5c58ad2d 251 }
252 return 0;
253 case WM_CLOSE:
32874aea 254 EndDialog(hwnd, 0);
5c58ad2d 255 return 0;
256 }
257 return 0;
258}
259
260/*
7bedb13c 261 * Warn about the obsolescent key file format.
262 */
263void old_keyfile_warning(void)
264{
265 static const char mbtitle[] = "PuTTY Key File Warning";
266 static const char message[] =
267 "You are loading an SSH 2 private key which has an\n"
268 "old version of the file format. This means your key\n"
269 "file is not fully tamperproof. Future versions of\n"
270 "PuTTY may stop supporting this private key format,\n"
271 "so we recommend you convert your key to the new\n"
272 "format.\n"
273 "\n"
274 "You can perform this conversion by loading the key\n"
275 "into PuTTYgen and then saving it again.";
276
277 MessageBox(NULL, message, mbtitle, MB_OK);
278}
279
280/*
3c0b3d06 281 * Update the visible key list.
282 */
32874aea 283static void keylist_update(void)
284{
45cebe79 285 struct RSAKey *rkey;
286 struct ssh2_userkey *skey;
d2371c81 287 int i;
3c0b3d06 288
289 if (keylist) {
32874aea 290 SendDlgItemMessage(keylist, 100, LB_RESETCONTENT, 0, 0);
291 for (i = 0; NULL != (rkey = index234(rsakeys, i)); i++) {
292 char listentry[512], *p;
293 /*
294 * Replace two spaces in the fingerprint with tabs, for
295 * nice alignment in the box.
296 */
45cebe79 297 strcpy(listentry, "ssh1\t");
32874aea 298 p = listentry + strlen(listentry);
299 rsa_fingerprint(p, sizeof(listentry) - (p - listentry), rkey);
300 p = strchr(listentry, ' ');
301 if (p)
302 *p = '\t';
303 p = strchr(listentry, ' ');
304 if (p)
305 *p = '\t';
306 SendDlgItemMessage(keylist, 100, LB_ADDSTRING,
307 0, (LPARAM) listentry);
308 }
309 for (i = 0; NULL != (skey = index234(ssh2keys, i)); i++) {
310 char listentry[512], *p;
45cebe79 311 int len;
32874aea 312 /*
313 * Replace two spaces in the fingerprint with tabs, for
314 * nice alignment in the box.
315 */
45cebe79 316 p = skey->alg->fingerprint(skey->data);
317 strncpy(listentry, p, sizeof(listentry));
32874aea 318 p = strchr(listentry, ' ');
319 if (p)
320 *p = '\t';
321 p = strchr(listentry, ' ');
322 if (p)
323 *p = '\t';
45cebe79 324 len = strlen(listentry);
32874aea 325 if (len < sizeof(listentry) - 2) {
45cebe79 326 listentry[len] = '\t';
32874aea 327 strncpy(listentry + len + 1, skey->comment,
328 sizeof(listentry) - len - 1);
45cebe79 329 }
32874aea 330 SendDlgItemMessage(keylist, 100, LB_ADDSTRING, 0,
331 (LPARAM) listentry);
332 }
333 SendDlgItemMessage(keylist, 100, LB_SETCURSEL, (WPARAM) - 1, 0);
3c0b3d06 334 }
335}
336
337/*
5c58ad2d 338 * This function loads a key from a file and adds it.
339 */
32874aea 340static void add_keyfile(char *filename)
341{
5c58ad2d 342 char passphrase[PASSPHRASE_MAXLEN];
2d466ffd 343 struct RSAKey *rkey = NULL;
344 struct ssh2_userkey *skey = NULL;
5c58ad2d 345 int needs_pass;
346 int ret;
347 int attempts;
d4de2d2a 348 char *comment;
349 struct PassphraseProcStruct pps;
45cebe79 350 int ver;
0959acf7 351 int original_pass;
352
45cebe79 353 ver = keyfile_version(filename);
354 if (ver == 0) {
32874aea 355 MessageBox(NULL, "Couldn't load private key.", APPNAME,
356 MB_OK | MB_ICONERROR);
357 return;
45cebe79 358 }
5c58ad2d 359
45cebe79 360 if (ver == 1)
361 needs_pass = rsakey_encrypted(filename, &comment);
362 else
363 needs_pass = ssh2_userkey_encrypted(filename, &comment);
5c58ad2d 364 attempts = 0;
45cebe79 365 if (ver == 1)
366 rkey = smalloc(sizeof(*rkey));
d4de2d2a 367 pps.passphrase = passphrase;
368 pps.comment = comment;
0959acf7 369 original_pass = 0;
5c58ad2d 370 do {
32874aea 371 if (needs_pass) {
0959acf7 372 /* try all the remembered passphrases first */
373 char *pp = index234(passphrases, attempts);
374 if(pp) {
375 strcpy(passphrase, pp);
376 } else {
377 int dlgret;
378 original_pass = 1;
379 dlgret = DialogBoxParam(instance, MAKEINTRESOURCE(210),
380 NULL, PassphraseProc, (LPARAM) & pps);
381 passphrase_box = NULL;
382 if (!dlgret) {
383 if (comment)
384 sfree(comment);
385 if (ver == 1)
386 sfree(rkey);
387 return; /* operation cancelled */
388 }
32874aea 389 }
390 } else
391 *passphrase = '\0';
45cebe79 392 if (ver == 1)
393 ret = loadrsakey(filename, rkey, passphrase);
394 else {
395 skey = ssh2_load_userkey(filename, passphrase);
396 if (skey == SSH2_WRONG_PASSPHRASE)
397 ret = -1;
398 else if (!skey)
399 ret = 0;
400 else
401 ret = 1;
402 }
32874aea 403 attempts++;
5c58ad2d 404 } while (ret == -1);
0959acf7 405
406 /* if they typed in an ok passphrase, remember it */
407 if(original_pass && ret) {
408 char *pp = dupstr(passphrase);
409 addpos234(passphrases, pp, 0);
410 }
411
32874aea 412 if (comment)
413 sfree(comment);
5c58ad2d 414 if (ret == 0) {
32874aea 415 MessageBox(NULL, "Couldn't load private key.", APPNAME,
416 MB_OK | MB_ICONERROR);
417 if (ver == 1)
45cebe79 418 sfree(rkey);
32874aea 419 return;
5c58ad2d 420 }
45cebe79 421 if (ver == 1) {
ddecd643 422 if (already_running) {
423 unsigned char *request, *response;
2d466ffd 424 void *vresponse;
ddecd643 425 int reqlen, clen, resplen;
426
ddecd643 427 clen = strlen(rkey->comment);
ddecd643 428
429 reqlen = 4 + 1 + /* length, message type */
430 4 + /* bit count */
431 ssh1_bignum_length(rkey->modulus) +
432 ssh1_bignum_length(rkey->exponent) +
433 ssh1_bignum_length(rkey->private_exponent) +
434 ssh1_bignum_length(rkey->iqmp) +
435 ssh1_bignum_length(rkey->p) +
32874aea 436 ssh1_bignum_length(rkey->q) + 4 + clen /* comment */
ddecd643 437 ;
ddecd643 438
439 request = smalloc(reqlen);
ddecd643 440
ddecd643 441 request[4] = SSH1_AGENTC_ADD_RSA_IDENTITY;
ddecd643 442 reqlen = 5;
32874aea 443 PUT_32BIT(request + reqlen, bignum_bitcount(rkey->modulus));
ddecd643 444 reqlen += 4;
32874aea 445 reqlen += ssh1_write_bignum(request + reqlen, rkey->modulus);
446 reqlen += ssh1_write_bignum(request + reqlen, rkey->exponent);
447 reqlen +=
448 ssh1_write_bignum(request + reqlen,
449 rkey->private_exponent);
450 reqlen += ssh1_write_bignum(request + reqlen, rkey->iqmp);
451 reqlen += ssh1_write_bignum(request + reqlen, rkey->p);
452 reqlen += ssh1_write_bignum(request + reqlen, rkey->q);
453 PUT_32BIT(request + reqlen, clen);
454 memcpy(request + reqlen + 4, rkey->comment, clen);
455 reqlen += 4 + clen;
456 PUT_32BIT(request, reqlen - 4);
ddecd643 457
2d466ffd 458 agent_query(request, reqlen, &vresponse, &resplen);
459 response = vresponse;
ddecd643 460 if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
461 MessageBox(NULL, "The already running Pageant "
462 "refused to add the key.", APPNAME,
463 MB_OK | MB_ICONERROR);
464 } else {
465 if (add234(rsakeys, rkey) != rkey)
466 sfree(rkey); /* already present, don't waste RAM */
467 }
45cebe79 468 } else {
ddecd643 469 if (already_running) {
470 unsigned char *request, *response;
2d466ffd 471 void *vresponse;
ddecd643 472 int reqlen, alglen, clen, keybloblen, resplen;
ddecd643 473 alglen = strlen(skey->alg->name);
ddecd643 474 clen = strlen(skey->comment);
ddecd643 475
ddecd643 476 keybloblen = skey->alg->openssh_fmtkey(skey->data, NULL, 0);
ddecd643 477
ddecd643 478 reqlen = 4 + 1 + /* length, message type */
479 4 + alglen + /* algorithm name */
480 keybloblen + /* key data */
481 4 + clen /* comment */
482 ;
ddecd643 483
ddecd643 484 request = smalloc(reqlen);
ddecd643 485
486 request[4] = SSH2_AGENTC_ADD_IDENTITY;
ddecd643 487 reqlen = 5;
32874aea 488 PUT_32BIT(request + reqlen, alglen);
ddecd643 489 reqlen += 4;
32874aea 490 memcpy(request + reqlen, skey->alg->name, alglen);
ddecd643 491 reqlen += alglen;
ddecd643 492 reqlen += skey->alg->openssh_fmtkey(skey->data,
32874aea 493 request + reqlen,
494 keybloblen);
495 PUT_32BIT(request + reqlen, clen);
496 memcpy(request + reqlen + 4, skey->comment, clen);
497 PUT_32BIT(request, reqlen - 4);
498 reqlen += clen + 4;
ddecd643 499
2d466ffd 500 agent_query(request, reqlen, &vresponse, &resplen);
501 response = vresponse;
ddecd643 502 if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
503 MessageBox(NULL, "The already running Pageant"
504 "refused to add the key.", APPNAME,
505 MB_OK | MB_ICONERROR);
ddecd643 506 } else {
507 if (add234(ssh2keys, skey) != skey) {
508 skey->alg->freekey(skey->data);
509 sfree(skey); /* already present, don't waste RAM */
510 }
45cebe79 511 }
512 }
5c58ad2d 513}
514
515/*
516 * This is the main agent function that answers messages.
517 */
32874aea 518static void answer_msg(void *msg)
519{
d70f60ae 520 unsigned char *p = msg;
521 unsigned char *ret = msg;
5c58ad2d 522 int type;
523
5c58ad2d 524 /*
525 * Get the message type.
526 */
527 type = p[4];
528
529 p += 5;
5c58ad2d 530 switch (type) {
45cebe79 531 case SSH1_AGENTC_REQUEST_RSA_IDENTITIES:
32874aea 532 /*
533 * Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER.
534 */
535 {
536 struct RSAKey *key;
537 int len, nkeys;
d2371c81 538 int i;
5c58ad2d 539
32874aea 540 /*
541 * Count up the number and length of keys we hold.
542 */
543 len = nkeys = 0;
544 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
545 nkeys++;
546 len += 4; /* length field */
547 len += ssh1_bignum_length(key->exponent);
548 len += ssh1_bignum_length(key->modulus);
549 len += 4 + strlen(key->comment);
550 }
551
552 /*
553 * Packet header is the obvious five bytes, plus four
554 * bytes for the key count.
555 */
556 len += 5 + 4;
557 if (len > AGENT_MAX_MSGLEN)
558 goto failure; /* aaargh! too much stuff! */
559 PUT_32BIT(ret, len - 4);
560 ret[4] = SSH1_AGENT_RSA_IDENTITIES_ANSWER;
561 PUT_32BIT(ret + 5, nkeys);
562 p = ret + 5 + 4;
563 for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
564 PUT_32BIT(p, bignum_bitcount(key->modulus));
565 p += 4;
566 p += ssh1_write_bignum(p, key->exponent);
567 p += ssh1_write_bignum(p, key->modulus);
568 PUT_32BIT(p, strlen(key->comment));
569 memcpy(p + 4, key->comment, strlen(key->comment));
570 p += 4 + strlen(key->comment);
571 }
572 }
573 break;
45cebe79 574 case SSH2_AGENTC_REQUEST_IDENTITIES:
32874aea 575 /*
576 * Reply with SSH2_AGENT_IDENTITIES_ANSWER.
577 */
578 {
579 struct ssh2_userkey *key;
580 int len, nkeys;
45cebe79 581 unsigned char *blob;
582 int bloblen;
d2371c81 583 int i;
45cebe79 584
32874aea 585 /*
586 * Count up the number and length of keys we hold.
587 */
588 len = nkeys = 0;
589 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
590 nkeys++;
591 len += 4; /* length field */
45cebe79 592 blob = key->alg->public_blob(key->data, &bloblen);
593 len += bloblen;
594 sfree(blob);
32874aea 595 len += 4 + strlen(key->comment);
596 }
597
598 /*
599 * Packet header is the obvious five bytes, plus four
600 * bytes for the key count.
601 */
602 len += 5 + 4;
603 if (len > AGENT_MAX_MSGLEN)
604 goto failure; /* aaargh! too much stuff! */
605 PUT_32BIT(ret, len - 4);
606 ret[4] = SSH2_AGENT_IDENTITIES_ANSWER;
607 PUT_32BIT(ret + 5, nkeys);
608 p = ret + 5 + 4;
609 for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
45cebe79 610 blob = key->alg->public_blob(key->data, &bloblen);
32874aea 611 PUT_32BIT(p, bloblen);
612 p += 4;
45cebe79 613 memcpy(p, blob, bloblen);
614 p += bloblen;
615 sfree(blob);
32874aea 616 PUT_32BIT(p, strlen(key->comment));
617 memcpy(p + 4, key->comment, strlen(key->comment));
618 p += 4 + strlen(key->comment);
619 }
620 }
621 break;
45cebe79 622 case SSH1_AGENTC_RSA_CHALLENGE:
32874aea 623 /*
624 * Reply with either SSH1_AGENT_RSA_RESPONSE or
625 * SSH_AGENT_FAILURE, depending on whether we have that key
626 * or not.
627 */
628 {
629 struct RSAKey reqkey, *key;
630 Bignum challenge, response;
631 unsigned char response_source[48], response_md5[16];
632 struct MD5Context md5c;
633 int i, len;
634
635 p += 4;
636 p += ssh1_read_bignum(p, &reqkey.exponent);
637 p += ssh1_read_bignum(p, &reqkey.modulus);
638 p += ssh1_read_bignum(p, &challenge);
639 memcpy(response_source + 32, p, 16);
640 p += 16;
641 if (GET_32BIT(p) != 1 ||
642 (key = find234(rsakeys, &reqkey, NULL)) == NULL) {
643 freebn(reqkey.exponent);
644 freebn(reqkey.modulus);
645 freebn(challenge);
646 goto failure;
647 }
648 response = rsadecrypt(challenge, key);
649 for (i = 0; i < 32; i++)
650 response_source[i] = bignum_byte(response, 31 - i);
651
652 MD5Init(&md5c);
653 MD5Update(&md5c, response_source, 48);
654 MD5Final(response_md5, &md5c);
655 memset(response_source, 0, 48); /* burn the evidence */
656 freebn(response); /* and that evidence */
657 freebn(challenge); /* yes, and that evidence */
658 freebn(reqkey.exponent); /* and free some memory ... */
659 freebn(reqkey.modulus); /* ... while we're at it. */
660
661 /*
662 * Packet is the obvious five byte header, plus sixteen
663 * bytes of MD5.
664 */
665 len = 5 + 16;
666 PUT_32BIT(ret, len - 4);
667 ret[4] = SSH1_AGENT_RSA_RESPONSE;
668 memcpy(ret + 5, response_md5, 16);
669 }
670 break;
45cebe79 671 case SSH2_AGENTC_SIGN_REQUEST:
32874aea 672 /*
5c72ca61 673 * Reply with either SSH2_AGENT_SIGN_RESPONSE or
32874aea 674 * SSH_AGENT_FAILURE, depending on whether we have that key
675 * or not.
676 */
45cebe79 677 {
678 struct ssh2_userkey *key;
679 struct blob b;
680 unsigned char *data, *signature;
681 int datalen, siglen, len;
682
683 b.len = GET_32BIT(p);
684 p += 4;
685 b.blob = p;
686 p += b.len;
687 datalen = GET_32BIT(p);
688 p += 4;
689 data = p;
690 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
691 if (!key)
692 goto failure;
693 signature = key->alg->sign(key->data, data, datalen, &siglen);
32874aea 694 len = 5 + 4 + siglen;
695 PUT_32BIT(ret, len - 4);
696 ret[4] = SSH2_AGENT_SIGN_RESPONSE;
697 PUT_32BIT(ret + 5, siglen);
698 memcpy(ret + 5 + 4, signature, siglen);
45cebe79 699 sfree(signature);
700 }
701 break;
702 case SSH1_AGENTC_ADD_RSA_IDENTITY:
32874aea 703 /*
704 * Add to the list and return SSH_AGENT_SUCCESS, or
705 * SSH_AGENT_FAILURE if the key was malformed.
706 */
707 {
708 struct RSAKey *key;
709 char *comment;
080954b8 710 int commentlen;
32874aea 711 key = smalloc(sizeof(struct RSAKey));
28edeffe 712 memset(key, 0, sizeof(struct RSAKey));
32874aea 713 p += makekey(p, key, NULL, 1);
714 p += makeprivate(p, key);
080954b8 715 p += ssh1_read_bignum(p, &key->iqmp); /* p^-1 mod q */
716 p += ssh1_read_bignum(p, &key->p); /* p */
717 p += ssh1_read_bignum(p, &key->q); /* q */
718 commentlen = GET_32BIT(p);
719 comment = smalloc(commentlen+1);
32874aea 720 if (comment) {
080954b8 721 memcpy(comment, p + 4, commentlen);
722 comment[commentlen] = '\0';
32874aea 723 key->comment = comment;
724 }
725 PUT_32BIT(ret, 1);
726 ret[4] = SSH_AGENT_FAILURE;
727 if (add234(rsakeys, key) == key) {
728 keylist_update();
729 ret[4] = SSH_AGENT_SUCCESS;
730 } else {
731 freersakey(key);
732 sfree(key);
733 }
734 }
735 break;
45cebe79 736 case SSH2_AGENTC_ADD_IDENTITY:
32874aea 737 /*
738 * Add to the list and return SSH_AGENT_SUCCESS, or
739 * SSH_AGENT_FAILURE if the key was malformed.
740 */
741 {
742 struct ssh2_userkey *key;
743 char *comment, *alg;
45cebe79 744 int alglen, commlen;
745 int bloblen;
746
747 key = smalloc(sizeof(struct ssh2_userkey));
748
32874aea 749 alglen = GET_32BIT(p);
750 p += 4;
751 alg = p;
752 p += alglen;
45cebe79 753 /* Add further algorithm names here. */
754 if (alglen == 7 && !memcmp(alg, "ssh-rsa", 7))
755 key->alg = &ssh_rsa;
5c72ca61 756 else if (alglen == 7 && !memcmp(alg, "ssh-dss", 7))
757 key->alg = &ssh_dss;
45cebe79 758 else {
759 sfree(key);
760 goto failure;
761 }
762
32874aea 763 bloblen =
764 GET_32BIT((unsigned char *) msg) - (p -
765 (unsigned char *) msg -
766 4);
45cebe79 767 key->data = key->alg->openssh_createkey(&p, &bloblen);
768 if (!key->data) {
769 sfree(key);
770 goto failure;
771 }
32874aea 772 commlen = GET_32BIT(p);
773 p += 4;
45cebe79 774
32874aea 775 comment = smalloc(commlen + 1);
776 if (comment) {
777 memcpy(comment, p, commlen);
45cebe79 778 comment[commlen] = '\0';
32874aea 779 }
45cebe79 780 key->comment = comment;
781
32874aea 782 PUT_32BIT(ret, 1);
783 ret[4] = SSH_AGENT_FAILURE;
784 if (add234(ssh2keys, key) == key) {
785 keylist_update();
786 ret[4] = SSH_AGENT_SUCCESS;
787 } else {
45cebe79 788 key->alg->freekey(key->data);
789 sfree(key->comment);
32874aea 790 sfree(key);
791 }
792 }
793 break;
45cebe79 794 case SSH1_AGENTC_REMOVE_RSA_IDENTITY:
32874aea 795 /*
796 * Remove from the list and return SSH_AGENT_SUCCESS, or
797 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
798 * start with.
799 */
800 {
801 struct RSAKey reqkey, *key;
802
803 p += makekey(p, &reqkey, NULL, 0);
804 key = find234(rsakeys, &reqkey, NULL);
805 freebn(reqkey.exponent);
806 freebn(reqkey.modulus);
807 PUT_32BIT(ret, 1);
808 ret[4] = SSH_AGENT_FAILURE;
809 if (key) {
810 del234(rsakeys, key);
811 keylist_update();
812 freersakey(key);
45cebe79 813 sfree(key);
32874aea 814 ret[4] = SSH_AGENT_SUCCESS;
815 }
816 }
817 break;
45cebe79 818 case SSH2_AGENTC_REMOVE_IDENTITY:
32874aea 819 /*
820 * Remove from the list and return SSH_AGENT_SUCCESS, or
821 * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
822 * start with.
823 */
824 {
825 struct ssh2_userkey *key;
45cebe79 826 struct blob b;
827
828 b.len = GET_32BIT(p);
829 p += 4;
830 b.blob = p;
831 p += b.len;
832 key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
833 if (!key)
834 goto failure;
835
32874aea 836 PUT_32BIT(ret, 1);
837 ret[4] = SSH_AGENT_FAILURE;
838 if (key) {
839 del234(ssh2keys, key);
840 keylist_update();
45cebe79 841 key->alg->freekey(key->data);
842 sfree(key);
32874aea 843 ret[4] = SSH_AGENT_SUCCESS;
844 }
845 }
846 break;
45cebe79 847 case SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES:
32874aea 848 /*
849 * Remove all SSH1 keys. Always returns success.
850 */
851 {
45cebe79 852 struct RSAKey *rkey;
45cebe79 853
32874aea 854 while ((rkey = index234(rsakeys, 0)) != NULL) {
855 del234(rsakeys, rkey);
45cebe79 856 freersakey(rkey);
857 sfree(rkey);
32874aea 858 }
45cebe79 859 keylist_update();
860
861 PUT_32BIT(ret, 1);
862 ret[4] = SSH_AGENT_SUCCESS;
32874aea 863 }
864 break;
45cebe79 865 case SSH2_AGENTC_REMOVE_ALL_IDENTITIES:
32874aea 866 /*
867 * Remove all SSH2 keys. Always returns success.
868 */
869 {
870 struct ssh2_userkey *skey;
871
872 while ((skey = index234(ssh2keys, 0)) != NULL) {
873 del234(ssh2keys, skey);
45cebe79 874 skey->alg->freekey(skey->data);
875 sfree(skey);
32874aea 876 }
45cebe79 877 keylist_update();
878
879 PUT_32BIT(ret, 1);
880 ret[4] = SSH_AGENT_SUCCESS;
32874aea 881 }
882 break;
5c58ad2d 883 default:
32874aea 884 failure:
885 /*
886 * Unrecognised message. Return SSH_AGENT_FAILURE.
887 */
888 PUT_32BIT(ret, 1);
889 ret[4] = SSH_AGENT_FAILURE;
890 break;
5c58ad2d 891 }
5c58ad2d 892}
893
894/*
895 * Key comparison function for the 2-3-4 tree of RSA keys.
896 */
32874aea 897static int cmpkeys_rsa(void *av, void *bv)
898{
899 struct RSAKey *a = (struct RSAKey *) av;
900 struct RSAKey *b = (struct RSAKey *) bv;
5c58ad2d 901 Bignum am, bm;
902 int alen, blen;
903
904 am = a->modulus;
905 bm = b->modulus;
906 /*
907 * Compare by length of moduli.
908 */
ddecd643 909 alen = bignum_bitcount(am);
910 blen = bignum_bitcount(bm);
32874aea 911 if (alen > blen)
912 return +1;
913 else if (alen < blen)
914 return -1;
5c58ad2d 915 /*
916 * Now compare by moduli themselves.
917 */
32874aea 918 alen = (alen + 7) / 8; /* byte count */
5c58ad2d 919 while (alen-- > 0) {
32874aea 920 int abyte, bbyte;
921 abyte = bignum_byte(am, alen);
922 bbyte = bignum_byte(bm, alen);
923 if (abyte > bbyte)
924 return +1;
925 else if (abyte < bbyte)
926 return -1;
5c58ad2d 927 }
928 /*
929 * Give up.
930 */
931 return 0;
932}
933
45cebe79 934/*
935 * Key comparison function for the 2-3-4 tree of SSH2 keys.
936 */
32874aea 937static int cmpkeys_ssh2(void *av, void *bv)
938{
939 struct ssh2_userkey *a = (struct ssh2_userkey *) av;
940 struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
45cebe79 941 int i;
942 int alen, blen;
943 unsigned char *ablob, *bblob;
944 int c;
32874aea 945
45cebe79 946 /*
947 * Compare purely by public blob.
948 */
949 ablob = a->alg->public_blob(a->data, &alen);
950 bblob = b->alg->public_blob(b->data, &blen);
951
952 c = 0;
953 for (i = 0; i < alen && i < blen; i++) {
954 if (ablob[i] < bblob[i]) {
32874aea 955 c = -1;
956 break;
45cebe79 957 } else if (ablob[i] > bblob[i]) {
32874aea 958 c = +1;
959 break;
45cebe79 960 }
961 }
32874aea 962 if (c == 0 && i < alen)
963 c = +1; /* a is longer */
964 if (c == 0 && i < blen)
965 c = -1; /* a is longer */
45cebe79 966
967 sfree(ablob);
968 sfree(bblob);
969
970 return c;
971}
972
973/*
974 * Key comparison function for looking up a blob in the 2-3-4 tree
975 * of SSH2 keys.
976 */
32874aea 977static int cmpkeys_ssh2_asymm(void *av, void *bv)
978{
979 struct blob *a = (struct blob *) av;
980 struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
45cebe79 981 int i;
982 int alen, blen;
983 unsigned char *ablob, *bblob;
984 int c;
32874aea 985
45cebe79 986 /*
987 * Compare purely by public blob.
988 */
989 ablob = a->blob;
990 alen = a->len;
991 bblob = b->alg->public_blob(b->data, &blen);
992
993 c = 0;
994 for (i = 0; i < alen && i < blen; i++) {
995 if (ablob[i] < bblob[i]) {
32874aea 996 c = -1;
997 break;
45cebe79 998 } else if (ablob[i] > bblob[i]) {
32874aea 999 c = +1;
1000 break;
45cebe79 1001 }
1002 }
32874aea 1003 if (c == 0 && i < alen)
1004 c = +1; /* a is longer */
1005 if (c == 0 && i < blen)
1006 c = -1; /* a is longer */
45cebe79 1007
1008 sfree(bblob);
1009
1010 return c;
1011}
1012
5c58ad2d 1013/*
ab162329 1014 * Prompt for a key file to add, and add it.
1015 */
32874aea 1016static void prompt_add_keyfile(void)
1017{
ab162329 1018 OPENFILENAME of;
1019 char filename[FILENAME_MAX];
0959acf7 1020 char *filelist = smalloc(8192);
1021 char *filewalker;
1022 int n, dirlen;
1023
ab162329 1024 memset(&of, 0, sizeof(of));
1025#ifdef OPENFILENAME_SIZE_VERSION_400
1026 of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
1027#else
1028 of.lStructSize = sizeof(of);
1029#endif
ecea795f 1030 of.hwndOwner = main_hwnd;
ab162329 1031 of.lpstrFilter = "All Files\0*\0\0\0";
1032 of.lpstrCustomFilter = NULL;
1033 of.nFilterIndex = 1;
0959acf7 1034 of.lpstrFile = filelist;
1035 *filelist = '\0';
1036 of.nMaxFile = FILENAME_MAX;
ab162329 1037 of.lpstrFileTitle = NULL;
1038 of.lpstrInitialDir = NULL;
1039 of.lpstrTitle = "Select Private Key File";
0959acf7 1040 of.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER;
ab162329 1041 if (GetOpenFileName(&of)) {
0959acf7 1042 if(strlen(filelist) > of.nFileOffset)
1043 /* Only one filename returned? */
1044 add_keyfile(filelist);
1045 else {
1046 /* we are returned a bunch of strings, end to
1047 * end. first string is the directory, the
1048 * rest the filenames. terminated with an
1049 * empty string.
1050 */
1051 filewalker = filelist;
1052 dirlen = strlen(filewalker);
1053 if(dirlen > FILENAME_MAX - 8) return;
1054 memcpy(filename, filewalker, dirlen);
1055
1056 filewalker += dirlen + 1;
1057 filename[dirlen++] = '\\';
1058
1059 /* then go over names one by one */
1060 for(;;) {
1061 n = strlen(filewalker) + 1;
1062 /* end of the list */
1063 if(n == 1)
1064 break;
1065 /* too big, shouldn't happen */
1066 if(n + dirlen > FILENAME_MAX)
1067 break;
1068
1069 memcpy(filename + dirlen, filewalker, n);
1070 filewalker += n;
1071
1072 add_keyfile(filename);
1073 }
1074 }
1075
32874aea 1076 keylist_update();
0959acf7 1077 forget_passphrases();
ab162329 1078 }
0959acf7 1079 sfree(filelist);
ab162329 1080}
1081
1082/*
5c58ad2d 1083 * Dialog-box function for the key list box.
1084 */
1085static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
32874aea 1086 WPARAM wParam, LPARAM lParam)
1087{
45cebe79 1088 struct RSAKey *rkey;
1089 struct ssh2_userkey *skey;
5c58ad2d 1090
1091 switch (msg) {
1092 case WM_INITDIALOG:
45cebe79 1093 /*
1094 * Centre the window.
1095 */
1096 { /* centre the window */
1097 RECT rs, rd;
1098 HWND hw;
1099
1100 hw = GetDesktopWindow();
32874aea 1101 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
1102 MoveWindow(hwnd,
1103 (rs.right + rs.left + rd.left - rd.right) / 2,
1104 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
1105 rd.right - rd.left, rd.bottom - rd.top, TRUE);
45cebe79 1106 }
1107
ecea795f 1108 if (help_path)
1109 SetWindowLong(hwnd, GWL_EXSTYLE,
1110 GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_CONTEXTHELP);
1111 else {
1112 HWND item = GetDlgItem(hwnd, 103); /* the Help button */
1113 if (item)
1114 DestroyWindow(item);
1115 }
1116 requested_help = FALSE;
1117
32874aea 1118 keylist = hwnd;
1c2a93c4 1119 {
32874aea 1120 static int tabs[] = { 35, 60, 210 };
1121 SendDlgItemMessage(hwnd, 100, LB_SETTABSTOPS,
1122 sizeof(tabs) / sizeof(*tabs),
1123 (LPARAM) tabs);
1c2a93c4 1124 }
32874aea 1125 keylist_update();
1126 return 0;
5c58ad2d 1127 case WM_COMMAND:
1128 switch (LOWORD(wParam)) {
1129 case IDOK:
1130 case IDCANCEL:
32874aea 1131 keylist = NULL;
1132 DestroyWindow(hwnd);
1133 return 0;
1134 case 101: /* add key */
5c58ad2d 1135 if (HIWORD(wParam) == BN_CLICKED ||
1136 HIWORD(wParam) == BN_DOUBLECLICKED) {
cb5ca813 1137 if (passphrase_box) {
1138 MessageBeep(MB_ICONERROR);
1139 SetForegroundWindow(passphrase_box);
1140 break;
1141 }
32874aea 1142 prompt_add_keyfile();
1143 }
1144 return 0;
1145 case 102: /* remove key */
5c58ad2d 1146 if (HIWORD(wParam) == BN_CLICKED ||
1147 HIWORD(wParam) == BN_DOUBLECLICKED) {
d2371c81 1148 int i;
0959acf7 1149 int rCount, sCount;
1150 int *selectedArray;
1151
1152 /* our counter within the array of selected items */
1153 int itemNum;
1154
1155 /* get the number of items selected in the list */
1156 int numSelected =
1157 SendDlgItemMessage(hwnd, 100, LB_GETSELCOUNT, 0, 0);
1158
1159 /* none selected? that was silly */
1160 if (numSelected == 0) {
5c58ad2d 1161 MessageBeep(0);
1162 break;
1163 }
0959acf7 1164
1165 /* get item indices in an array */
1166 selectedArray = smalloc(numSelected * sizeof(int));
1167 SendDlgItemMessage(hwnd, 100, LB_GETSELITEMS,
1168 numSelected, (WPARAM)selectedArray);
1169
1170 itemNum = numSelected - 1;
1171 rCount = count234(rsakeys);
1172 sCount = count234(ssh2keys);
1173
1174 /* go through the non-rsakeys until we've covered them all,
1175 * and/or we're out of selected items to check. note that
1176 * we go *backwards*, to avoid complications from deleting
1177 * things hence altering the offset of subsequent items
1178 */
1179 for (i = sCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1180 skey = index234(ssh2keys, i);
1181
1182 if (selectedArray[itemNum] == rCount + i) {
1183 del234(ssh2keys, skey);
1184 skey->alg->freekey(skey->data);
1185 sfree(skey);
1186 itemNum--;
1187 }
45cebe79 1188 }
0959acf7 1189
1190 /* do the same for the rsa keys */
1191 for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1192 rkey = index234(rsakeys, i);
1193
1194 if(selectedArray[itemNum] == i) {
1195 del234(rsakeys, rkey);
1196 freersakey(rkey);
1197 sfree(rkey);
1198 itemNum--;
1199 }
1200 }
1201
1202 sfree(selectedArray);
32874aea 1203 keylist_update();
1204 }
1205 return 0;
ecea795f 1206 case 103: /* help */
1207 if (HIWORD(wParam) == BN_CLICKED ||
1208 HIWORD(wParam) == BN_DOUBLECLICKED) {
1209 if (help_path) {
1210 WinHelp(main_hwnd, help_path, HELP_COMMAND,
1211 (DWORD)"JI(`',`pageant.general')");
1212 requested_help = TRUE;
1213 }
1214 }
1215 return 0;
5c58ad2d 1216 }
1217 return 0;
ecea795f 1218 case WM_HELP:
1219 if (help_path) {
1220 int id = ((LPHELPINFO)lParam)->iCtrlId;
1221 char *cmd = NULL;
1222 switch (id) {
1223 case 100: cmd = "JI(`',`pageant.keylist')"; break;
1224 case 101: cmd = "JI(`',`pageant.addkey')"; break;
1225 case 102: cmd = "JI(`',`pageant.remkey')"; break;
1226 }
1227 if (cmd) {
1228 WinHelp(main_hwnd, help_path, HELP_COMMAND, (DWORD)cmd);
1229 requested_help = TRUE;
1230 } else {
1231 MessageBeep(0);
1232 }
1233 }
1234 break;
5c58ad2d 1235 case WM_CLOSE:
32874aea 1236 keylist = NULL;
1237 DestroyWindow(hwnd);
5c58ad2d 1238 return 0;
1239 }
1240 return 0;
1241}
1242
15ad2e8a 1243/* Set up a system tray icon */
1244static BOOL AddTrayIcon(HWND hwnd)
1245{
1246 BOOL res;
1247 NOTIFYICONDATA tnid;
1248 HICON hicon;
1249
1250#ifdef NIM_SETVERSION
1251 tnid.uVersion = 0;
1252 res = Shell_NotifyIcon(NIM_SETVERSION, &tnid);
1253#endif
1254
1255 tnid.cbSize = sizeof(NOTIFYICONDATA);
1256 tnid.hWnd = hwnd;
1257 tnid.uID = 1; /* unique within this systray use */
1258 tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
1259 tnid.uCallbackMessage = WM_SYSTRAY;
1260 tnid.hIcon = hicon = LoadIcon(instance, MAKEINTRESOURCE(201));
1261 strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
1262
1263 res = Shell_NotifyIcon(NIM_ADD, &tnid);
1264
1265 if (hicon) DestroyIcon(hicon);
1266
1267 return res;
1268}
1269
32874aea 1270static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1271 WPARAM wParam, LPARAM lParam)
1272{
5c58ad2d 1273 int ret;
1274 static int menuinprogress;
15ad2e8a 1275 static UINT msgTaskbarCreated = 0;
5c58ad2d 1276
1277 switch (message) {
15ad2e8a 1278 case WM_CREATE:
1279 msgTaskbarCreated = RegisterWindowMessage(_T("TaskbarCreated"));
1280 break;
1281 default:
1282 if (message==msgTaskbarCreated) {
8e3a513c 1283 /*
1284 * Explorer has been restarted, so the tray icon will
1285 * have been lost.
1286 */
1287 AddTrayIcon(hwnd);
15ad2e8a 1288 }
1289 break;
1290
5c58ad2d 1291 case WM_SYSTRAY:
32874aea 1292 if (lParam == WM_RBUTTONUP) {
1293 POINT cursorpos;
1294 GetCursorPos(&cursorpos);
1295 PostMessage(hwnd, WM_SYSTRAY2, cursorpos.x, cursorpos.y);
1296 } else if (lParam == WM_LBUTTONDBLCLK) {
1297 /* Equivalent to IDM_VIEWKEYS. */
1298 PostMessage(hwnd, WM_COMMAND, IDM_VIEWKEYS, 0);
1299 }
1300 break;
5c58ad2d 1301 case WM_SYSTRAY2:
32874aea 1302 if (!menuinprogress) {
1303 menuinprogress = 1;
1304 SetForegroundWindow(hwnd);
1305 ret = TrackPopupMenu(systray_menu,
1306 TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
1307 TPM_RIGHTBUTTON,
1308 wParam, lParam, 0, hwnd, NULL);
1309 menuinprogress = 0;
1310 }
1311 break;
5c58ad2d 1312 case WM_COMMAND:
1313 case WM_SYSCOMMAND:
1314 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
32874aea 1315 case IDM_CLOSE:
cb5ca813 1316 if (passphrase_box)
1317 SendMessage(passphrase_box, WM_CLOSE, 0, 0);
32874aea 1318 SendMessage(hwnd, WM_CLOSE, 0, 0);
1319 break;
1320 case IDM_VIEWKEYS:
1321 if (!keylist) {
1322 keylist = CreateDialog(instance, MAKEINTRESOURCE(211),
1323 NULL, KeyListProc);
1324 ShowWindow(keylist, SW_SHOWNORMAL);
1325 /*
1326 * Sometimes the window comes up minimised / hidden
1327 * for no obvious reason. Prevent this.
1328 */
1329 SetForegroundWindow(keylist);
1330 SetWindowPos(keylist, HWND_TOP, 0, 0, 0, 0,
1331 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1332 }
1333 break;
1334 case IDM_ADDKEY:
cb5ca813 1335 if (passphrase_box) {
1336 MessageBeep(MB_ICONERROR);
1337 SetForegroundWindow(passphrase_box);
1338 break;
1339 }
32874aea 1340 prompt_add_keyfile();
1341 break;
1342 case IDM_ABOUT:
1343 if (!aboutbox) {
1344 aboutbox = CreateDialog(instance, MAKEINTRESOURCE(213),
1345 NULL, AboutProc);
1346 ShowWindow(aboutbox, SW_SHOWNORMAL);
1347 /*
1348 * Sometimes the window comes up minimised / hidden
1349 * for no obvious reason. Prevent this.
1350 */
1351 SetForegroundWindow(aboutbox);
1352 SetWindowPos(aboutbox, HWND_TOP, 0, 0, 0, 0,
1353 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1354 }
1355 break;
ecea795f 1356 case IDM_HELP:
1357 if (help_path) {
1358 WinHelp(main_hwnd, help_path, HELP_COMMAND,
1359 (DWORD)"JI(`',`pageant.general')");
1360 requested_help = TRUE;
1361 }
1362 break;
32874aea 1363 }
1364 break;
5c58ad2d 1365 case WM_DESTROY:
ecea795f 1366 if (requested_help) {
1367 WinHelp(main_hwnd, help_path, HELP_QUIT, 0);
1368 requested_help = FALSE;
1369 }
32874aea 1370 PostQuitMessage(0);
5c58ad2d 1371 return 0;
1372 case WM_COPYDATA:
32874aea 1373 {
1374 COPYDATASTRUCT *cds;
1375 char *mapname;
1376 void *p;
2d466ffd 1377 HANDLE filemap;
1378#ifndef NO_SECURITY
1379 HANDLE proc;
32874aea 1380 PSID mapowner, procowner;
1381 PSECURITY_DESCRIPTOR psd1 = NULL, psd2 = NULL;
2d466ffd 1382#endif
32874aea 1383 int ret = 0;
1384
1385 cds = (COPYDATASTRUCT *) lParam;
1386 if (cds->dwData != AGENT_COPYDATA_ID)
1387 return 0; /* not our message, mate */
1388 mapname = (char *) cds->lpData;
1389 if (mapname[cds->cbData - 1] != '\0')
1390 return 0; /* failure to be ASCIZ! */
d70f60ae 1391#ifdef DEBUG_IPC
32874aea 1392 debug(("mapname is :%s:\n", mapname));
d70f60ae 1393#endif
32874aea 1394 filemap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, mapname);
d70f60ae 1395#ifdef DEBUG_IPC
32874aea 1396 debug(("filemap is %p\n", filemap));
d70f60ae 1397#endif
32874aea 1398 if (filemap != NULL && filemap != INVALID_HANDLE_VALUE) {
123bc6ea 1399#ifndef NO_SECURITY
2d466ffd 1400 int rc;
32874aea 1401 if (has_security) {
1402 if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE,
1403 GetCurrentProcessId())) ==
1404 NULL) {
d70f60ae 1405#ifdef DEBUG_IPC
32874aea 1406 debug(("couldn't get handle for process\n"));
d70f60ae 1407#endif
32874aea 1408 return 0;
1409 }
1410 if (getsecurityinfo(proc, SE_KERNEL_OBJECT,
1411 OWNER_SECURITY_INFORMATION,
1412 &procowner, NULL, NULL, NULL,
1413 &psd2) != ERROR_SUCCESS) {
d70f60ae 1414#ifdef DEBUG_IPC
32874aea 1415 debug(("couldn't get owner info for process\n"));
d70f60ae 1416#endif
32874aea 1417 CloseHandle(proc);
1418 return 0; /* unable to get security info */
1419 }
1420 CloseHandle(proc);
1421 if ((rc = getsecurityinfo(filemap, SE_KERNEL_OBJECT,
1422 OWNER_SECURITY_INFORMATION,
1423 &mapowner, NULL, NULL, NULL,
1424 &psd1) != ERROR_SUCCESS)) {
d70f60ae 1425#ifdef DEBUG_IPC
32874aea 1426 debug(
1427 ("couldn't get owner info for filemap: %d\n",
1428 rc));
d70f60ae 1429#endif
32874aea 1430 return 0;
1431 }
016ef8ab 1432#ifdef DEBUG_IPC
32874aea 1433 debug(("got security stuff\n"));
016ef8ab 1434#endif
32874aea 1435 if (!EqualSid(mapowner, procowner))
1436 return 0; /* security ID mismatch! */
d70f60ae 1437#ifdef DEBUG_IPC
32874aea 1438 debug(("security stuff matched\n"));
d70f60ae 1439#endif
32874aea 1440 LocalFree(psd1);
1441 LocalFree(psd2);
1442 } else {
d70f60ae 1443#ifdef DEBUG_IPC
32874aea 1444 debug(("security APIs not present\n"));
d70f60ae 1445#endif
32874aea 1446 }
123bc6ea 1447#endif
32874aea 1448 p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
d70f60ae 1449#ifdef DEBUG_IPC
32874aea 1450 debug(("p is %p\n", p));
1451 {
1452 int i;
1453 for (i = 0; i < 5; i++)
1454 debug(
1455 ("p[%d]=%02x\n", i,
1456 ((unsigned char *) p)[i]));}
d70f60ae 1457#endif
32874aea 1458 answer_msg(p);
1459 ret = 1;
1460 UnmapViewOfFile(p);
1461 }
1462 CloseHandle(filemap);
1463 return ret;
1464 }
5c58ad2d 1465 }
1466
32874aea 1467 return DefWindowProc(hwnd, message, wParam, lParam);
5c58ad2d 1468}
1469
ddecd643 1470/*
1471 * Fork and Exec the command in cmdline. [DBW]
1472 */
2c975497 1473void spawn_cmd(char *cmdline, char * args, int show)
32874aea 1474{
ddecd643 1475 if (ShellExecute(NULL, _T("open"), cmdline,
2c975497 1476 args, NULL, show) <= (HINSTANCE) 32) {
32874aea 1477 TCHAR sMsg[140];
1478 sprintf(sMsg, _T("Failed to run \"%.100s\", Error: %d"), cmdline,
2d466ffd 1479 (int)GetLastError());
32874aea 1480 MessageBox(NULL, sMsg, APPNAME, MB_OK | MB_ICONEXCLAMATION);
ddecd643 1481 }
1482}
1483
32874aea 1484int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
1485{
5c58ad2d 1486 WNDCLASS wndclass;
5c58ad2d 1487 MSG msg;
016ef8ab 1488 OSVERSIONINFO osi;
1489 HMODULE advapi;
ddecd643 1490 char *command = NULL;
1491 int added_keys = 0;
016ef8ab 1492
1493 /*
1494 * Determine whether we're an NT system (should have security
1495 * APIs) or a non-NT system (don't do security).
1496 */
1497 memset(&osi, 0, sizeof(OSVERSIONINFO));
1498 osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
32874aea 1499 if (GetVersionEx(&osi) && osi.dwPlatformId == VER_PLATFORM_WIN32_NT) {
1500 has_security = TRUE;
016ef8ab 1501 } else
32874aea 1502 has_security = FALSE;
016ef8ab 1503
1504 if (has_security) {
123bc6ea 1505#ifndef NO_SECURITY
32874aea 1506 /*
1507 * Attempt to get the security API we need.
1508 */
1509 advapi = LoadLibrary("ADVAPI32.DLL");
1510 getsecurityinfo =
1511 (gsi_fn_t) GetProcAddress(advapi, "GetSecurityInfo");
1512 if (!getsecurityinfo) {
1513 MessageBox(NULL,
1514 "Unable to access security APIs. Pageant will\n"
1515 "not run, in case it causes a security breach.",
1516 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
1517 return 1;
1518 }
123bc6ea 1519#else
1520 MessageBox(NULL,
1521 "This program has been compiled for Win9X and will\n"
1522 "not run on NT, in case it causes a security breach.",
1523 "Pageant Fatal Error", MB_ICONERROR | MB_OK);
1524 return 1;
1525#endif
016ef8ab 1526 } else
32874aea 1527 advapi = NULL;
5c58ad2d 1528
1529 instance = inst;
1530
ddecd643 1531 /*
ecea795f 1532 * See if we can find our Help file.
1533 */
1534 {
1535 char b[2048], *p, *q, *r;
1536 FILE *fp;
1537 GetModuleFileName(NULL, b, sizeof(b) - 1);
1538 r = b;
1539 p = strrchr(b, '\\');
1540 if (p && p >= r) r = p+1;
1541 q = strrchr(b, ':');
1542 if (q && q >= r) r = q+1;
1543 strcpy(r, "putty.hlp");
1544 if ( (fp = fopen(b, "r")) != NULL) {
1545 help_path = dupstr(b);
1546 fclose(fp);
1547 } else
1548 help_path = NULL;
1549 }
1550
1551 /*
ddecd643 1552 * Find out if Pageant is already running.
1553 */
1554 already_running = FALSE;
260f3dec 1555 if (agent_exists())
ddecd643 1556 already_running = TRUE;
1557 else {
1558
1559 if (!prev) {
32874aea 1560 wndclass.style = 0;
1561 wndclass.lpfnWndProc = WndProc;
1562 wndclass.cbClsExtra = 0;
1563 wndclass.cbWndExtra = 0;
1564 wndclass.hInstance = inst;
1565 wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
1566 wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
1567 wndclass.hbrBackground = GetStockObject(BLACK_BRUSH);
1568 wndclass.lpszMenuName = NULL;
ddecd643 1569 wndclass.lpszClassName = APPNAME;
1570
32874aea 1571 RegisterClass(&wndclass);
ddecd643 1572 }
5c58ad2d 1573
ecea795f 1574 main_hwnd = keylist = NULL;
5c58ad2d 1575
ecea795f 1576 main_hwnd = CreateWindow(APPNAME, APPNAME,
1577 WS_OVERLAPPEDWINDOW | WS_VSCROLL,
1578 CW_USEDEFAULT, CW_USEDEFAULT,
1579 100, 100, NULL, NULL, inst, NULL);
5c58ad2d 1580
ddecd643 1581 /* Set up a system tray icon */
ecea795f 1582 AddTrayIcon(main_hwnd);
15ad2e8a 1583
1584 systray_menu = CreatePopupMenu();
1585 /* accelerators used: vkxa */
1586 AppendMenu(systray_menu, MF_ENABLED, IDM_VIEWKEYS,
1587 "&View Keys");
1588 AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key");
ecea795f 1589 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
1590 if (help_path)
1591 AppendMenu(systray_menu, MF_ENABLED, IDM_HELP, "&Help");
15ad2e8a 1592 AppendMenu(systray_menu, MF_ENABLED, IDM_ABOUT, "&About");
ecea795f 1593 AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
15ad2e8a 1594 AppendMenu(systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit");
5c58ad2d 1595
ecea795f 1596 ShowWindow(main_hwnd, SW_HIDE);
5c58ad2d 1597
ddecd643 1598 /*
1599 * Initialise storage for RSA keys.
1600 */
1601 rsakeys = newtree234(cmpkeys_rsa);
1602 ssh2keys = newtree234(cmpkeys_ssh2);
1603
1604 }
dacbd0e8 1605
1606 /*
0959acf7 1607 * Initialise storage for short-term passphrase cache.
1608 */
1609 passphrases = newtree234(NULL);
1610
1611 /*
45cebe79 1612 * Process the command line and add keys as listed on it.
ddecd643 1613 * If we already determined that we need to spawn a program from above we
1614 * need to ignore the first two arguments. [DBW]
5c58ad2d 1615 */
1616 {
32874aea 1617 char *p;
1618 int inquotes = 0;
32874aea 1619 p = cmdline;
1620 while (*p) {
1621 while (*p && isspace(*p))
1622 p++;
0959acf7 1623 if (*p && !isspace(*p)) {
32874aea 1624 char *q = p, *pp = p;
1625 while (*p && (inquotes || !isspace(*p))) {
0959acf7 1626 if (*p == '"') {
32874aea 1627 inquotes = !inquotes;
1628 p++;
1629 continue;
1630 }
1631 *pp++ = *p++;
1632 }
1633 if (*pp) {
1634 if (*p)
1635 p++;
1636 *pp++ = '\0';
1637 }
ddecd643 1638 if (!strcmp(q, "-c")) {
1639 /*
1640 * If we see `-c', then the rest of the
1641 * command line should be treated as a
1642 * command to be spawned.
1643 */
32874aea 1644 while (*p && isspace(*p))
1645 p++;
ddecd643 1646 command = p;
1647 break;
1648 } else {
1649 add_keyfile(q);
1650 added_keys = TRUE;
1651 }
32874aea 1652 }
1653 }
5c58ad2d 1654 }
1655
0959acf7 1656 /*
1657 * Forget any passphrase that we retained while going over
1658 * command line keyfiles.
1659 */
1660 forget_passphrases();
1661
2c975497 1662 if (command) {
1663 char *args;
1664 if (command[0] == '"')
1665 args = strchr(++command, '"');
1666 else
1667 args = strchr(command, ' ');
1668 if (args) {
1669 *args++ = 0;
1670 while(*args && isspace(*args)) args++;
1671 }
1672 spawn_cmd(command, args, show);
1673 }
ddecd643 1674
1675 /*
1676 * If Pageant was already running, we leave now. If we haven't
1677 * even taken any auxiliary action (spawned a command or added
1678 * keys), complain.
1679 */
1680 if (already_running) {
1681 if (!command && !added_keys) {
1682 MessageBox(NULL, "Pageant is already running", "Pageant Error",
1683 MB_ICONERROR | MB_OK);
1684 }
32874aea 1685 if (advapi)
1686 FreeLibrary(advapi);
1687 return 0;
ddecd643 1688 }
1689
5c58ad2d 1690 /*
dacbd0e8 1691 * Main message loop.
5c58ad2d 1692 */
5c58ad2d 1693 while (GetMessage(&msg, NULL, 0, 0) == 1) {
32874aea 1694 TranslateMessage(&msg);
1695 DispatchMessage(&msg);
5c58ad2d 1696 }
1697
1698 /* Clean up the system tray icon */
1699 {
32874aea 1700 NOTIFYICONDATA tnid;
5c58ad2d 1701
32874aea 1702 tnid.cbSize = sizeof(NOTIFYICONDATA);
ecea795f 1703 tnid.hWnd = main_hwnd;
32874aea 1704 tnid.uID = 1;
5c58ad2d 1705
32874aea 1706 Shell_NotifyIcon(NIM_DELETE, &tnid);
5c58ad2d 1707
32874aea 1708 DestroyMenu(systray_menu);
5c58ad2d 1709 }
1710
32874aea 1711 if (advapi)
1712 FreeLibrary(advapi);
5c58ad2d 1713 exit(msg.wParam);
1714}