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