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