]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - windows/winpgnt.c
Unify GET_32BIT()/PUT_32BIT() et al from numerous source files into misc.h.
[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             SetWindowLong(hwnd, GWL_EXSTYLE,
1475                           GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_CONTEXTHELP);
1476         else {
1477             HWND item = GetDlgItem(hwnd, 103);   /* the Help button */
1478             if (item)
1479                 DestroyWindow(item);
1480         }
1481         requested_help = FALSE;
1482
1483         keylist = hwnd;
1484         {
1485             static int tabs[] = { 35, 60, 210 };
1486             SendDlgItemMessage(hwnd, 100, LB_SETTABSTOPS,
1487                                sizeof(tabs) / sizeof(*tabs),
1488                                (LPARAM) tabs);
1489         }
1490         keylist_update();
1491         return 0;
1492       case WM_COMMAND:
1493         switch (LOWORD(wParam)) {
1494           case IDOK:
1495           case IDCANCEL:
1496             keylist = NULL;
1497             DestroyWindow(hwnd);
1498             return 0;
1499           case 101:                    /* add key */
1500             if (HIWORD(wParam) == BN_CLICKED ||
1501                 HIWORD(wParam) == BN_DOUBLECLICKED) {
1502                 if (passphrase_box) {
1503                     MessageBeep(MB_ICONERROR);
1504                     SetForegroundWindow(passphrase_box);
1505                     break;
1506                 }
1507                 prompt_add_keyfile();
1508             }
1509             return 0;
1510           case 102:                    /* remove key */
1511             if (HIWORD(wParam) == BN_CLICKED ||
1512                 HIWORD(wParam) == BN_DOUBLECLICKED) {
1513                 int i;
1514                 int rCount, sCount;
1515                 int *selectedArray;
1516                 
1517                 /* our counter within the array of selected items */
1518                 int itemNum;
1519                 
1520                 /* get the number of items selected in the list */
1521                 int numSelected = 
1522                         SendDlgItemMessage(hwnd, 100, LB_GETSELCOUNT, 0, 0);
1523                 
1524                 /* none selected? that was silly */
1525                 if (numSelected == 0) {
1526                     MessageBeep(0);
1527                     break;
1528                 }
1529
1530                 /* get item indices in an array */
1531                 selectedArray = snewn(numSelected, int);
1532                 SendDlgItemMessage(hwnd, 100, LB_GETSELITEMS,
1533                                 numSelected, (WPARAM)selectedArray);
1534                 
1535                 itemNum = numSelected - 1;
1536                 rCount = count234(rsakeys);
1537                 sCount = count234(ssh2keys);
1538                 
1539                 /* go through the non-rsakeys until we've covered them all, 
1540                  * and/or we're out of selected items to check. note that
1541                  * we go *backwards*, to avoid complications from deleting
1542                  * things hence altering the offset of subsequent items
1543                  */
1544             for (i = sCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1545                         skey = index234(ssh2keys, i);
1546                         
1547                         if (selectedArray[itemNum] == rCount + i) {
1548                                 del234(ssh2keys, skey);
1549                                 skey->alg->freekey(skey->data);
1550                                 sfree(skey);
1551                                 itemNum--; 
1552                         }
1553                 }
1554                 
1555                 /* do the same for the rsa keys */
1556                 for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1557                         rkey = index234(rsakeys, i);
1558
1559                         if(selectedArray[itemNum] == i) {
1560                                 del234(rsakeys, rkey);
1561                                 freersakey(rkey);
1562                                 sfree(rkey);
1563                                 itemNum--;
1564                         }
1565                 }
1566
1567                 sfree(selectedArray); 
1568                 keylist_update();
1569             }
1570             return 0;
1571           case 103:                    /* help */
1572             if (HIWORD(wParam) == BN_CLICKED ||
1573                 HIWORD(wParam) == BN_DOUBLECLICKED) {
1574                 if (help_path) {
1575                     WinHelp(hwnd, help_path, HELP_COMMAND,
1576                             (DWORD)"JI(`',`pageant.general')");
1577                     requested_help = TRUE;
1578                 }
1579             }
1580             return 0;
1581         }
1582         return 0;
1583       case WM_HELP:
1584         if (help_path) {
1585             int id = ((LPHELPINFO)lParam)->iCtrlId;
1586             char *topic = NULL;
1587             switch (id) {
1588               case 100: topic = "pageant.keylist"; break;
1589               case 101: topic = "pageant.addkey"; break;
1590               case 102: topic = "pageant.remkey"; break;
1591             }
1592             if (topic) {
1593                 char *cmd = dupprintf("JI(`',`%s')", topic);
1594                 WinHelp(hwnd, help_path, HELP_COMMAND, (DWORD)cmd);
1595                 sfree(cmd);
1596                 requested_help = TRUE;
1597             } else {
1598                 MessageBeep(0);
1599             }
1600         }
1601         break;
1602       case WM_CLOSE:
1603         keylist = NULL;
1604         DestroyWindow(hwnd);
1605         return 0;
1606     }
1607     return 0;
1608 }
1609
1610 /* Set up a system tray icon */
1611 static BOOL AddTrayIcon(HWND hwnd)
1612 {
1613     BOOL res;
1614     NOTIFYICONDATA tnid;
1615     HICON hicon;
1616
1617 #ifdef NIM_SETVERSION
1618     tnid.uVersion = 0;
1619     res = Shell_NotifyIcon(NIM_SETVERSION, &tnid);
1620 #endif
1621
1622     tnid.cbSize = sizeof(NOTIFYICONDATA);
1623     tnid.hWnd = hwnd;
1624     tnid.uID = 1;              /* unique within this systray use */
1625     tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
1626     tnid.uCallbackMessage = WM_SYSTRAY;
1627     tnid.hIcon = hicon = LoadIcon(hinst, MAKEINTRESOURCE(201));
1628     strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
1629
1630     res = Shell_NotifyIcon(NIM_ADD, &tnid);
1631
1632     if (hicon) DestroyIcon(hicon);
1633     
1634     return res;
1635 }
1636
1637 /* Update the saved-sessions menu. */
1638 static void update_sessions(void)
1639 {
1640     int num_entries;
1641     HKEY hkey;
1642     TCHAR buf[MAX_PATH + 1];
1643     MENUITEMINFO mii;
1644
1645     int index_key, index_menu;
1646
1647     if (!putty_path)
1648         return;
1649
1650     if(ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, PUTTY_REGKEY, &hkey))
1651         return;
1652
1653     for(num_entries = GetMenuItemCount(session_menu);
1654         num_entries > initial_menuitems_count;
1655         num_entries--)
1656         RemoveMenu(session_menu, 0, MF_BYPOSITION);
1657
1658     index_key = 0;
1659     index_menu = 0;
1660
1661     while(ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) {
1662         TCHAR session_name[MAX_PATH + 1];
1663         unmungestr(buf, session_name, MAX_PATH);
1664         if(strcmp(buf, PUTTY_DEFAULT) != 0) {
1665             memset(&mii, 0, sizeof(mii));
1666             mii.cbSize = sizeof(mii);
1667             mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
1668             mii.fType = MFT_STRING;
1669             mii.fState = MFS_ENABLED;
1670             mii.wID = (index_menu * 16) + IDM_SESSIONS_BASE;
1671             mii.dwTypeData = session_name;
1672             InsertMenuItem(session_menu, index_menu, TRUE, &mii);
1673             index_menu++;
1674         }
1675         index_key++;
1676     }
1677
1678     RegCloseKey(hkey);
1679
1680     if(index_menu == 0) {
1681         mii.cbSize = sizeof(mii);
1682         mii.fMask = MIIM_TYPE | MIIM_STATE;
1683         mii.fType = MFT_STRING;
1684         mii.fState = MFS_GRAYED;
1685         mii.dwTypeData = _T("(No sessions)");
1686         InsertMenuItem(session_menu, index_menu, TRUE, &mii);
1687     }
1688 }
1689
1690 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1691                                 WPARAM wParam, LPARAM lParam)
1692 {
1693     int ret;
1694     static int menuinprogress;
1695     static UINT msgTaskbarCreated = 0;
1696
1697     switch (message) {
1698       case WM_CREATE:
1699         msgTaskbarCreated = RegisterWindowMessage(_T("TaskbarCreated"));
1700         break;
1701       default:
1702         if (message==msgTaskbarCreated) {
1703             /*
1704              * Explorer has been restarted, so the tray icon will
1705              * have been lost.
1706              */
1707             AddTrayIcon(hwnd);
1708         }
1709         break;
1710         
1711       case WM_SYSTRAY:
1712         if (lParam == WM_RBUTTONUP) {
1713             POINT cursorpos;
1714             GetCursorPos(&cursorpos);
1715             PostMessage(hwnd, WM_SYSTRAY2, cursorpos.x, cursorpos.y);
1716         } else if (lParam == WM_LBUTTONDBLCLK) {
1717             /* Run the default menu item. */
1718             UINT menuitem = GetMenuDefaultItem(systray_menu, FALSE, 0);
1719             if (menuitem != -1)
1720                 PostMessage(hwnd, WM_COMMAND, menuitem, 0);
1721         }
1722         break;
1723       case WM_SYSTRAY2:
1724         if (!menuinprogress) {
1725             menuinprogress = 1;
1726             update_sessions();
1727             SetForegroundWindow(hwnd);
1728             ret = TrackPopupMenu(systray_menu,
1729                                  TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
1730                                  TPM_RIGHTBUTTON,
1731                                  wParam, lParam, 0, hwnd, NULL);
1732             menuinprogress = 0;
1733         }
1734         break;
1735       case WM_COMMAND:
1736       case WM_SYSCOMMAND:
1737         switch (wParam & ~0xF) {       /* low 4 bits reserved to Windows */
1738           case IDM_PUTTY:
1739             if((int)ShellExecute(hwnd, NULL, putty_path, _T(""), _T(""),
1740                                  SW_SHOW) <= 32) {
1741                 MessageBox(NULL, "Unable to execute PuTTY!",
1742                            "Error", MB_OK | MB_ICONERROR);
1743             }
1744             break;
1745           case IDM_CLOSE:
1746             if (passphrase_box)
1747                 SendMessage(passphrase_box, WM_CLOSE, 0, 0);
1748             SendMessage(hwnd, WM_CLOSE, 0, 0);
1749             break;
1750           case IDM_VIEWKEYS:
1751             if (!keylist) {
1752                 keylist = CreateDialog(hinst, MAKEINTRESOURCE(211),
1753                                        NULL, KeyListProc);
1754                 ShowWindow(keylist, SW_SHOWNORMAL);
1755             }
1756             /* 
1757              * Sometimes the window comes up minimised / hidden for
1758              * no obvious reason. Prevent this. This also brings it
1759              * to the front if it's already present (the user
1760              * selected View Keys because they wanted to _see_ the
1761              * thing).
1762              */
1763             SetForegroundWindow(keylist);
1764             SetWindowPos(keylist, HWND_TOP, 0, 0, 0, 0,
1765                          SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1766             break;
1767           case IDM_ADDKEY:
1768             if (passphrase_box) {
1769                 MessageBeep(MB_ICONERROR);
1770                 SetForegroundWindow(passphrase_box);
1771                 break;
1772             }
1773             prompt_add_keyfile();
1774             break;
1775           case IDM_ABOUT:
1776             if (!aboutbox) {
1777                 aboutbox = CreateDialog(hinst, MAKEINTRESOURCE(213),
1778                                         NULL, AboutProc);
1779                 ShowWindow(aboutbox, SW_SHOWNORMAL);
1780                 /* 
1781                  * Sometimes the window comes up minimised / hidden
1782                  * for no obvious reason. Prevent this.
1783                  */
1784                 SetForegroundWindow(aboutbox);
1785                 SetWindowPos(aboutbox, HWND_TOP, 0, 0, 0, 0,
1786                              SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1787             }
1788             break;
1789           case IDM_HELP:
1790             if (help_path) {
1791                 WinHelp(hwnd, help_path, HELP_COMMAND,
1792                         (DWORD)"JI(`',`pageant.general')");
1793                 requested_help = TRUE;
1794             }
1795             break;
1796           default:
1797             {
1798                 if(wParam >= IDM_SESSIONS_BASE && wParam <= IDM_SESSIONS_MAX) {
1799                     MENUITEMINFO mii;
1800                     TCHAR buf[MAX_PATH + 1];
1801                     TCHAR param[MAX_PATH + 1];
1802                     memset(&mii, 0, sizeof(mii));
1803                     mii.cbSize = sizeof(mii);
1804                     mii.fMask = MIIM_TYPE;
1805                     mii.cch = MAX_PATH;
1806                     mii.dwTypeData = buf;
1807                     GetMenuItemInfo(session_menu, wParam, FALSE, &mii);
1808                     strcpy(param, "@");
1809                     strcat(param, mii.dwTypeData);
1810                     if((int)ShellExecute(hwnd, NULL, putty_path, param,
1811                                          _T(""), SW_SHOW) <= 32) {
1812                         MessageBox(NULL, "Unable to execute PuTTY!", "Error",
1813                                    MB_OK | MB_ICONERROR);
1814                     }
1815                 }
1816             }
1817             break;
1818         }
1819         break;
1820       case WM_DESTROY:
1821         if (requested_help) {
1822             WinHelp(hwnd, help_path, HELP_QUIT, 0);
1823             requested_help = FALSE;
1824         }
1825         PostQuitMessage(0);
1826         return 0;
1827       case WM_COPYDATA:
1828         {
1829             COPYDATASTRUCT *cds;
1830             char *mapname;
1831             void *p;
1832             HANDLE filemap;
1833 #ifndef NO_SECURITY
1834             HANDLE proc;
1835             PSID mapowner, procowner;
1836             PSECURITY_DESCRIPTOR psd1 = NULL, psd2 = NULL;
1837 #endif
1838             int ret = 0;
1839
1840             cds = (COPYDATASTRUCT *) lParam;
1841             if (cds->dwData != AGENT_COPYDATA_ID)
1842                 return 0;              /* not our message, mate */
1843             mapname = (char *) cds->lpData;
1844             if (mapname[cds->cbData - 1] != '\0')
1845                 return 0;              /* failure to be ASCIZ! */
1846 #ifdef DEBUG_IPC
1847             debug(("mapname is :%s:\n", mapname));
1848 #endif
1849             filemap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, mapname);
1850 #ifdef DEBUG_IPC
1851             debug(("filemap is %p\n", filemap));
1852 #endif
1853             if (filemap != NULL && filemap != INVALID_HANDLE_VALUE) {
1854 #ifndef NO_SECURITY
1855                 int rc;
1856                 if (has_security) {
1857                     if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE,
1858                                             GetCurrentProcessId())) ==
1859                         NULL) {
1860 #ifdef DEBUG_IPC
1861                         debug(("couldn't get handle for process\n"));
1862 #endif
1863                         return 0;
1864                     }
1865                     if (getsecurityinfo(proc, SE_KERNEL_OBJECT,
1866                                         OWNER_SECURITY_INFORMATION,
1867                                         &procowner, NULL, NULL, NULL,
1868                                         &psd2) != ERROR_SUCCESS) {
1869 #ifdef DEBUG_IPC
1870                         debug(("couldn't get owner info for process\n"));
1871 #endif
1872                         CloseHandle(proc);
1873                         return 0;      /* unable to get security info */
1874                     }
1875                     CloseHandle(proc);
1876                     if ((rc = getsecurityinfo(filemap, SE_KERNEL_OBJECT,
1877                                               OWNER_SECURITY_INFORMATION,
1878                                               &mapowner, NULL, NULL, NULL,
1879                                               &psd1) != ERROR_SUCCESS)) {
1880 #ifdef DEBUG_IPC
1881                         debug(
1882                               ("couldn't get owner info for filemap: %d\n",
1883                                rc));
1884 #endif
1885                         return 0;
1886                     }
1887 #ifdef DEBUG_IPC
1888                     debug(("got security stuff\n"));
1889 #endif
1890                     if (!EqualSid(mapowner, procowner))
1891                         return 0;      /* security ID mismatch! */
1892 #ifdef DEBUG_IPC
1893                     debug(("security stuff matched\n"));
1894 #endif
1895                     LocalFree(psd1);
1896                     LocalFree(psd2);
1897                 } else {
1898 #ifdef DEBUG_IPC
1899                     debug(("security APIs not present\n"));
1900 #endif
1901                 }
1902 #endif
1903                 p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
1904 #ifdef DEBUG_IPC
1905                 debug(("p is %p\n", p));
1906                 {
1907                     int i;
1908                     for (i = 0; i < 5; i++)
1909                         debug(
1910                               ("p[%d]=%02x\n", i,
1911                                ((unsigned char *) p)[i]));}
1912 #endif
1913                 answer_msg(p);
1914                 ret = 1;
1915                 UnmapViewOfFile(p);
1916             }
1917             CloseHandle(filemap);
1918             return ret;
1919         }
1920     }
1921
1922     return DefWindowProc(hwnd, message, wParam, lParam);
1923 }
1924
1925 /*
1926  * Fork and Exec the command in cmdline. [DBW]
1927  */
1928 void spawn_cmd(char *cmdline, char * args, int show)
1929 {
1930     if (ShellExecute(NULL, _T("open"), cmdline,
1931                      args, NULL, show) <= (HINSTANCE) 32) {
1932         char *msg;
1933         msg = dupprintf("Failed to run \"%.100s\", Error: %d", cmdline,
1934                         (int)GetLastError());
1935         MessageBox(NULL, msg, APPNAME, MB_OK | MB_ICONEXCLAMATION);
1936         sfree(msg);
1937     }
1938 }
1939
1940 /*
1941  * This is a can't-happen stub, since Pageant never makes
1942  * asynchronous agent requests.
1943  */
1944 void agent_schedule_callback(void (*callback)(void *, void *, int),
1945                              void *callback_ctx, void *data, int len)
1946 {
1947     assert(!"We shouldn't get here");
1948 }
1949
1950 void cleanup_exit(int code) { exit(code); }
1951
1952 int flags = FLAG_SYNCAGENT;
1953
1954 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
1955 {
1956     WNDCLASS wndclass;
1957     MSG msg;
1958     HMODULE advapi;
1959     char *command = NULL;
1960     int added_keys = 0;
1961     int argc, i;
1962     char **argv, **argstart;
1963
1964     hinst = inst;
1965     hwnd = NULL;
1966
1967     /*
1968      * Determine whether we're an NT system (should have security
1969      * APIs) or a non-NT system (don't do security).
1970      */
1971     if (!init_winver())
1972     {
1973         modalfatalbox("Windows refuses to report a version");
1974     }
1975     if (osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT) {
1976         has_security = TRUE;
1977     } else
1978         has_security = FALSE;
1979
1980     if (has_security) {
1981 #ifndef NO_SECURITY
1982         /*
1983          * Attempt to get the security API we need.
1984          */
1985         advapi = LoadLibrary("ADVAPI32.DLL");
1986         getsecurityinfo =
1987             (gsi_fn_t) GetProcAddress(advapi, "GetSecurityInfo");
1988         if (!getsecurityinfo) {
1989             MessageBox(NULL,
1990                        "Unable to access security APIs. Pageant will\n"
1991                        "not run, in case it causes a security breach.",
1992                        "Pageant Fatal Error", MB_ICONERROR | MB_OK);
1993             return 1;
1994         }
1995 #else
1996         MessageBox(NULL,
1997                    "This program has been compiled for Win9X and will\n"
1998                    "not run on NT, in case it causes a security breach.",
1999                    "Pageant Fatal Error", MB_ICONERROR | MB_OK);
2000         return 1;
2001 #endif
2002     } else
2003         advapi = NULL;
2004
2005     /*
2006      * See if we can find our Help file.
2007      */
2008     {
2009         char b[2048], *p, *q, *r;
2010         FILE *fp;
2011         GetModuleFileName(NULL, b, sizeof(b) - 1);
2012         r = b;
2013         p = strrchr(b, '\\');
2014         if (p && p >= r) r = p+1;
2015         q = strrchr(b, ':');
2016         if (q && q >= r) r = q+1;
2017         strcpy(r, PUTTY_HELP_FILE);
2018         if ( (fp = fopen(b, "r")) != NULL) {
2019             help_path = dupstr(b);
2020             fclose(fp);
2021         } else
2022             help_path = NULL;
2023     }
2024
2025     /*
2026      * Look for the PuTTY binary (we will enable the saved session
2027      * submenu if we find it).
2028      */
2029     {
2030         char b[2048], *p, *q, *r;
2031         FILE *fp;
2032         GetModuleFileName(NULL, b, sizeof(b) - 1);
2033         r = b;
2034         p = strrchr(b, '\\');
2035         if (p && p >= r) r = p+1;
2036         q = strrchr(b, ':');
2037         if (q && q >= r) r = q+1;
2038         strcpy(r, "putty.exe");
2039         if ( (fp = fopen(b, "r")) != NULL) {
2040             putty_path = dupstr(b);
2041             fclose(fp);
2042         } else
2043             putty_path = NULL;
2044     }
2045
2046     /*
2047      * Find out if Pageant is already running.
2048      */
2049     already_running = agent_exists();
2050
2051     /*
2052      * Initialise storage for RSA keys.
2053      */
2054     if (!already_running) {
2055         rsakeys = newtree234(cmpkeys_rsa);
2056         ssh2keys = newtree234(cmpkeys_ssh2);
2057     }
2058
2059     /*
2060      * Initialise storage for short-term passphrase cache.
2061      */
2062     passphrases = newtree234(NULL);
2063
2064     /*
2065      * Process the command line and add keys as listed on it.
2066      */
2067     split_into_argv(cmdline, &argc, &argv, &argstart);
2068     for (i = 0; i < argc; i++) {
2069         if (!strcmp(argv[i], "-pgpfp")) {
2070             pgp_fingerprints();
2071             if (advapi)
2072                 FreeLibrary(advapi);
2073             return 1;
2074         } else if (!strcmp(argv[i], "-c")) {
2075             /*
2076              * If we see `-c', then the rest of the
2077              * command line should be treated as a
2078              * command to be spawned.
2079              */
2080             if (i < argc-1)
2081                 command = argstart[i+1];
2082             else
2083                 command = "";
2084             break;
2085         } else {
2086             add_keyfile(filename_from_str(argv[i]));
2087             added_keys = TRUE;
2088         }
2089     }
2090
2091     /*
2092      * Forget any passphrase that we retained while going over
2093      * command line keyfiles.
2094      */
2095     forget_passphrases();
2096
2097     if (command) {
2098         char *args;
2099         if (command[0] == '"')
2100             args = strchr(++command, '"');
2101         else
2102             args = strchr(command, ' ');
2103         if (args) {
2104             *args++ = 0;
2105             while(*args && isspace(*args)) args++;
2106         }
2107         spawn_cmd(command, args, show);
2108     }
2109
2110     /*
2111      * If Pageant was already running, we leave now. If we haven't
2112      * even taken any auxiliary action (spawned a command or added
2113      * keys), complain.
2114      */
2115     if (already_running) {
2116         if (!command && !added_keys) {
2117             MessageBox(NULL, "Pageant is already running", "Pageant Error",
2118                        MB_ICONERROR | MB_OK);
2119         }
2120         if (advapi)
2121             FreeLibrary(advapi);
2122         return 0;
2123     }
2124
2125     if (!prev) {
2126         wndclass.style = 0;
2127         wndclass.lpfnWndProc = WndProc;
2128         wndclass.cbClsExtra = 0;
2129         wndclass.cbWndExtra = 0;
2130         wndclass.hInstance = inst;
2131         wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
2132         wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
2133         wndclass.hbrBackground = GetStockObject(BLACK_BRUSH);
2134         wndclass.lpszMenuName = NULL;
2135         wndclass.lpszClassName = APPNAME;
2136
2137         RegisterClass(&wndclass);
2138     }
2139
2140     keylist = NULL;
2141
2142     hwnd = CreateWindow(APPNAME, APPNAME,
2143                         WS_OVERLAPPEDWINDOW | WS_VSCROLL,
2144                         CW_USEDEFAULT, CW_USEDEFAULT,
2145                         100, 100, NULL, NULL, inst, NULL);
2146
2147     /* Set up a system tray icon */
2148     AddTrayIcon(hwnd);
2149
2150     /* Accelerators used: nsvkxa */
2151     systray_menu = CreatePopupMenu();
2152     if (putty_path) {
2153         session_menu = CreateMenu();
2154         AppendMenu(systray_menu, MF_ENABLED, IDM_PUTTY, "&New Session");
2155         AppendMenu(systray_menu, MF_POPUP | MF_ENABLED,
2156                    (UINT) session_menu, "&Saved Sessions");
2157         AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
2158     }
2159     AppendMenu(systray_menu, MF_ENABLED, IDM_VIEWKEYS,
2160            "&View Keys");
2161     AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key");
2162     AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
2163     if (help_path)
2164         AppendMenu(systray_menu, MF_ENABLED, IDM_HELP, "&Help");
2165     AppendMenu(systray_menu, MF_ENABLED, IDM_ABOUT, "&About");
2166     AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
2167     AppendMenu(systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit");
2168     initial_menuitems_count = GetMenuItemCount(session_menu);
2169
2170     /* Set the default menu item. */
2171     SetMenuDefaultItem(systray_menu, IDM_VIEWKEYS, FALSE);
2172
2173     ShowWindow(hwnd, SW_HIDE);
2174
2175     /*
2176      * Main message loop.
2177      */
2178     while (GetMessage(&msg, NULL, 0, 0) == 1) {
2179         if (!(IsWindow(keylist) && IsDialogMessage(keylist, &msg)) &&
2180             !(IsWindow(aboutbox) && IsDialogMessage(aboutbox, &msg))) {
2181             TranslateMessage(&msg);
2182             DispatchMessage(&msg);
2183         }
2184     }
2185
2186     /* Clean up the system tray icon */
2187     {
2188         NOTIFYICONDATA tnid;
2189
2190         tnid.cbSize = sizeof(NOTIFYICONDATA);
2191         tnid.hWnd = hwnd;
2192         tnid.uID = 1;
2193
2194         Shell_NotifyIcon(NIM_DELETE, &tnid);
2195
2196         DestroyMenu(systray_menu);
2197     }
2198
2199     if (keypath) filereq_free(keypath);
2200
2201     if (advapi)
2202         FreeLibrary(advapi);
2203     return msg.wParam;
2204 }