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