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