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