]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - windows/winpgnt.c
Ben Rudiak-Gould points out that we should be using WM_APP as the base for
[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_SYSTRAY   (WM_APP + 6)
28 #define WM_SYSTRAY2  (WM_APP + 7)
29
30 #define AGENT_COPYDATA_ID 0x804e50ba   /* random goop */
31
32 /*
33  * FIXME: maybe some day we can sort this out ...
34  */
35 #define AGENT_MAX_MSGLEN  8192
36
37 /* From MSDN: In the WM_SYSCOMMAND message, the four low-order bits of
38  * wParam are used by Windows, and should be masked off, so we shouldn't
39  * attempt to store information in them. Hence all these identifiers have
40  * the low 4 bits clear. Also, identifiers should < 0xF000. */
41
42 #define IDM_CLOSE    0x0010
43 #define IDM_VIEWKEYS 0x0020
44 #define IDM_ADDKEY   0x0030
45 #define IDM_HELP     0x0040
46 #define IDM_ABOUT    0x0050
47
48 #define APPNAME "Pageant"
49
50 extern char ver[];
51
52 static HWND keylist;
53 static HWND aboutbox;
54 static HMENU systray_menu, session_menu;
55 static int already_running;
56
57 static char *putty_path;
58
59 /* CWD for "add key" file requester. */
60 static filereq *keypath = NULL;
61
62 #define IDM_PUTTY         0x0060
63 #define IDM_SESSIONS_BASE 0x1000
64 #define IDM_SESSIONS_MAX  0x2000
65 #define PUTTY_REGKEY      "Software\\SimonTatham\\PuTTY\\Sessions"
66 #define PUTTY_DEFAULT     "Default%20Settings"
67 static int initial_menuitems_count;
68
69 /*
70  * Print a modal (Really Bad) message box and perform a fatal exit.
71  */
72 void modalfatalbox(char *fmt, ...)
73 {
74     va_list ap;
75     char *buf;
76
77     va_start(ap, fmt);
78     buf = dupvprintf(fmt, ap);
79     va_end(ap);
80     MessageBox(hwnd, buf, "Pageant Fatal Error",
81                MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
82     sfree(buf);
83     exit(1);
84 }
85
86 /* Un-munge session names out of the registry. */
87 static void unmungestr(char *in, char *out, int outlen)
88 {
89     while (*in) {
90         if (*in == '%' && in[1] && in[2]) {
91             int i, j;
92
93             i = in[1] - '0';
94             i -= (i > 9 ? 7 : 0);
95             j = in[2] - '0';
96             j -= (j > 9 ? 7 : 0);
97
98             *out++ = (i << 4) + j;
99             if (!--outlen)
100                 return;
101             in += 3;
102         } else {
103             *out++ = *in++;
104             if (!--outlen)
105                 return;
106         }
107     }
108     *out = '\0';
109     return;
110 }
111
112 static tree234 *rsakeys, *ssh2keys;
113
114 static int has_security;
115 #ifndef NO_SECURITY
116 typedef DWORD(WINAPI * gsi_fn_t)
117  (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION,
118   PSID *, PSID *, PACL *, PACL *, PSECURITY_DESCRIPTOR *);
119 static gsi_fn_t getsecurityinfo;
120 #endif
121
122 /*
123  * Forward references
124  */
125 static void *make_keylist1(int *length);
126 static void *make_keylist2(int *length);
127 static void *get_keylist1(int *length);
128 static void *get_keylist2(int *length);
129
130 /*
131  * We need this to link with the RSA code, because rsaencrypt()
132  * pads its data with random bytes. Since we only use rsadecrypt()
133  * and the signing functions, which are deterministic, this should
134  * never be called.
135  *
136  * If it _is_ called, there is a _serious_ problem, because it
137  * won't generate true random numbers. So we must scream, panic,
138  * and exit immediately if that should happen.
139  */
140 int random_byte(void)
141 {
142     MessageBox(hwnd, "Internal Error", APPNAME, MB_OK | MB_ICONERROR);
143     exit(0);
144     /* this line can't be reached but it placates MSVC's warnings :-) */
145     return 0;
146 }
147
148 /*
149  * Blob structure for passing to the asymmetric SSH-2 key compare
150  * function, prototyped here.
151  */
152 struct blob {
153     unsigned char *blob;
154     int len;
155 };
156 static int cmpkeys_ssh2_asymm(void *av, void *bv);
157
158 #define PASSPHRASE_MAXLEN 512
159
160 struct PassphraseProcStruct {
161     char *passphrase;
162     char *comment;
163 };
164
165 static tree234 *passphrases = NULL;
166
167 /* 
168  * After processing a list of filenames, we want to forget the
169  * passphrases.
170  */
171 static void forget_passphrases(void)
172 {
173     while (count234(passphrases) > 0) {
174         char *pp = index234(passphrases, 0);
175         memset(pp, 0, strlen(pp));
176         delpos234(passphrases, 0);
177         free(pp);
178     }
179 }
180
181 /*
182  * Dialog-box function for the Licence box.
183  */
184 static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
185                                 WPARAM wParam, LPARAM lParam)
186 {
187     switch (msg) {
188       case WM_INITDIALOG:
189         return 1;
190       case WM_COMMAND:
191         switch (LOWORD(wParam)) {
192           case IDOK:
193           case IDCANCEL:
194             EndDialog(hwnd, 1);
195             return 0;
196         }
197         return 0;
198       case WM_CLOSE:
199         EndDialog(hwnd, 1);
200         return 0;
201     }
202     return 0;
203 }
204
205 /*
206  * Dialog-box function for the About box.
207  */
208 static int CALLBACK AboutProc(HWND hwnd, UINT msg,
209                               WPARAM wParam, LPARAM lParam)
210 {
211     switch (msg) {
212       case WM_INITDIALOG:
213         SetDlgItemText(hwnd, 100, ver);
214         return 1;
215       case WM_COMMAND:
216         switch (LOWORD(wParam)) {
217           case IDOK:
218           case IDCANCEL:
219             aboutbox = NULL;
220             DestroyWindow(hwnd);
221             return 0;
222           case 101:
223             EnableWindow(hwnd, 0);
224             DialogBox(hinst, MAKEINTRESOURCE(214), hwnd, LicenceProc);
225             EnableWindow(hwnd, 1);
226             SetActiveWindow(hwnd);
227             return 0;
228         }
229         return 0;
230       case WM_CLOSE:
231         aboutbox = NULL;
232         DestroyWindow(hwnd);
233         return 0;
234     }
235     return 0;
236 }
237
238 static HWND passphrase_box;
239
240 /*
241  * Dialog-box function for the passphrase box.
242  */
243 static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
244                                    WPARAM wParam, LPARAM lParam)
245 {
246     static char *passphrase = NULL;
247     struct PassphraseProcStruct *p;
248
249     switch (msg) {
250       case WM_INITDIALOG:
251         passphrase_box = hwnd;
252         /*
253          * Centre the window.
254          */
255         {                              /* centre the window */
256             RECT rs, rd;
257             HWND hw;
258
259             hw = GetDesktopWindow();
260             if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
261                 MoveWindow(hwnd,
262                            (rs.right + rs.left + rd.left - rd.right) / 2,
263                            (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
264                            rd.right - rd.left, rd.bottom - rd.top, TRUE);
265         }
266
267         SetForegroundWindow(hwnd);
268         SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
269                      SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
270         p = (struct PassphraseProcStruct *) lParam;
271         passphrase = p->passphrase;
272         if (p->comment)
273             SetDlgItemText(hwnd, 101, p->comment);
274         *passphrase = 0;
275         SetDlgItemText(hwnd, 102, passphrase);
276         return 0;
277       case WM_COMMAND:
278         switch (LOWORD(wParam)) {
279           case IDOK:
280             if (*passphrase)
281                 EndDialog(hwnd, 1);
282             else
283                 MessageBeep(0);
284             return 0;
285           case IDCANCEL:
286             EndDialog(hwnd, 0);
287             return 0;
288           case 102:                    /* edit box */
289             if ((HIWORD(wParam) == EN_CHANGE) && passphrase) {
290                 GetDlgItemText(hwnd, 102, passphrase,
291                                PASSPHRASE_MAXLEN - 1);
292                 passphrase[PASSPHRASE_MAXLEN - 1] = '\0';
293             }
294             return 0;
295         }
296         return 0;
297       case WM_CLOSE:
298         EndDialog(hwnd, 0);
299         return 0;
300     }
301     return 0;
302 }
303
304 /*
305  * Warn about the obsolescent key file format.
306  */
307 void old_keyfile_warning(void)
308 {
309     static const char mbtitle[] = "PuTTY Key File Warning";
310     static const char message[] =
311         "You are loading an SSH-2 private key which has an\n"
312         "old version of the file format. This means your key\n"
313         "file is not fully tamperproof. Future versions of\n"
314         "PuTTY may stop supporting this private key format,\n"
315         "so we recommend you convert your key to the new\n"
316         "format.\n"
317         "\n"
318         "You can perform this conversion by loading the key\n"
319         "into PuTTYgen and then saving it again.";
320
321     MessageBox(NULL, message, mbtitle, MB_OK);
322 }
323
324 /*
325  * Update the visible key list.
326  */
327 static void keylist_update(void)
328 {
329     struct RSAKey *rkey;
330     struct ssh2_userkey *skey;
331     int i;
332
333     if (keylist) {
334         SendDlgItemMessage(keylist, 100, LB_RESETCONTENT, 0, 0);
335         for (i = 0; NULL != (rkey = index234(rsakeys, i)); i++) {
336             char listentry[512], *p;
337             /*
338              * Replace two spaces in the fingerprint with tabs, for
339              * nice alignment in the box.
340              */
341             strcpy(listentry, "ssh1\t");
342             p = listentry + strlen(listentry);
343             rsa_fingerprint(p, sizeof(listentry) - (p - listentry), rkey);
344             p = strchr(listentry, ' ');
345             if (p)
346                 *p = '\t';
347             p = strchr(listentry, ' ');
348             if (p)
349                 *p = '\t';
350             SendDlgItemMessage(keylist, 100, LB_ADDSTRING,
351                                0, (LPARAM) listentry);
352         }
353         for (i = 0; NULL != (skey = index234(ssh2keys, i)); i++) {
354             char listentry[512], *p;
355             int len;
356             /*
357              * Replace two spaces in the fingerprint with tabs, for
358              * nice alignment in the box.
359              */
360             p = skey->alg->fingerprint(skey->data);
361             strncpy(listentry, p, sizeof(listentry));
362             p = strchr(listentry, ' ');
363             if (p)
364                 *p = '\t';
365             p = strchr(listentry, ' ');
366             if (p)
367                 *p = '\t';
368             len = strlen(listentry);
369             if (len < sizeof(listentry) - 2) {
370                 listentry[len] = '\t';
371                 strncpy(listentry + len + 1, skey->comment,
372                         sizeof(listentry) - len - 1);
373             }
374             SendDlgItemMessage(keylist, 100, LB_ADDSTRING, 0,
375                                (LPARAM) listentry);
376         }
377         SendDlgItemMessage(keylist, 100, LB_SETCURSEL, (WPARAM) - 1, 0);
378     }
379 }
380
381 /*
382  * This function loads a key from a file and adds it.
383  */
384 static void add_keyfile(Filename filename)
385 {
386     char passphrase[PASSPHRASE_MAXLEN];
387     struct RSAKey *rkey = NULL;
388     struct ssh2_userkey *skey = NULL;
389     int needs_pass;
390     int ret;
391     int attempts;
392     char *comment;
393     const char *error = NULL;
394     struct PassphraseProcStruct pps;
395     int type;
396     int original_pass;
397         
398     type = key_type(&filename);
399     if (type != SSH_KEYTYPE_SSH1 && type != SSH_KEYTYPE_SSH2) {
400         char *msg = dupprintf("Couldn't load this key (%s)",
401                               key_type_to_str(type));
402         message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
403                     HELPCTXID(errors_cantloadkey));
404         sfree(msg);
405         return;
406     }
407
408     /*
409      * See if the key is already loaded (in the primary Pageant,
410      * which may or may not be us).
411      */
412     {
413         void *blob;
414         unsigned char *keylist, *p;
415         int i, nkeys, bloblen, keylistlen;
416
417         if (type == SSH_KEYTYPE_SSH1) {
418             if (!rsakey_pubblob(&filename, &blob, &bloblen, &error)) {
419                 char *msg = dupprintf("Couldn't load private key (%s)", error);
420                 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
421                             HELPCTXID(errors_cantloadkey));
422                 sfree(msg);
423                 return;
424             }
425             keylist = get_keylist1(&keylistlen);
426         } else {
427             unsigned char *blob2;
428             blob = ssh2_userkey_loadpub(&filename, NULL, &bloblen, &error);
429             if (!blob) {
430                 char *msg = dupprintf("Couldn't load private key (%s)", error);
431                 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
432                             HELPCTXID(errors_cantloadkey));
433                 sfree(msg);
434                 return;
435             }
436             /* For our purposes we want the blob prefixed with its length */
437             blob2 = snewn(bloblen+4, unsigned char);
438             PUT_32BIT(blob2, bloblen);
439             memcpy(blob2 + 4, blob, bloblen);
440             sfree(blob);
441             blob = blob2;
442
443             keylist = get_keylist2(&keylistlen);
444         }
445         if (keylist) {
446             if (keylistlen < 4) {
447                 MessageBox(NULL, "Received broken key list?!", APPNAME,
448                            MB_OK | MB_ICONERROR);
449                 return;
450             }
451             nkeys = GET_32BIT(keylist);
452             p = keylist + 4;
453             keylistlen -= 4;
454
455             for (i = 0; i < nkeys; i++) {
456                 if (!memcmp(blob, p, bloblen)) {
457                     /* Key is already present; we can now leave. */
458                     sfree(keylist);
459                     sfree(blob);
460                     return;
461                 }
462                 /* Now skip over public blob */
463                 if (type == SSH_KEYTYPE_SSH1) {
464                     int n = rsa_public_blob_len(p, keylistlen);
465                     if (n < 0) {
466                         MessageBox(NULL, "Received broken key list?!", APPNAME,
467                                    MB_OK | MB_ICONERROR);
468                         return;
469                     }
470                     p += n;
471                     keylistlen -= n;
472                 } else {
473                     int n;
474                     if (keylistlen < 4) {
475                         MessageBox(NULL, "Received broken key list?!", APPNAME,
476                                    MB_OK | MB_ICONERROR);
477                         return;
478                     }
479                     n = 4 + GET_32BIT(p);
480                     if (keylistlen < n) {
481                         MessageBox(NULL, "Received broken key list?!", APPNAME,
482                                    MB_OK | MB_ICONERROR);
483                         return;
484                     }
485                     p += n;
486                     keylistlen -= n;
487                 }
488                 /* Now skip over comment field */
489                 {
490                     int n;
491                     if (keylistlen < 4) {
492                         MessageBox(NULL, "Received broken key list?!", APPNAME,
493                                    MB_OK | MB_ICONERROR);
494                         return;
495                     }
496                     n = 4 + GET_32BIT(p);
497                     if (keylistlen < n) {
498                         MessageBox(NULL, "Received broken key list?!", APPNAME,
499                                    MB_OK | MB_ICONERROR);
500                         return;
501                     }
502                     p += n;
503                     keylistlen -= n;
504                 }
505             }
506
507             sfree(keylist);
508         }
509
510         sfree(blob);
511     }
512
513     error = NULL;
514     if (type == SSH_KEYTYPE_SSH1)
515         needs_pass = rsakey_encrypted(&filename, &comment);
516     else
517         needs_pass = ssh2_userkey_encrypted(&filename, &comment);
518     attempts = 0;
519     if (type == SSH_KEYTYPE_SSH1)
520         rkey = snew(struct RSAKey);
521     pps.passphrase = passphrase;
522     pps.comment = comment;
523     original_pass = 0;
524     do {
525         if (needs_pass) {
526             /* try all the remembered passphrases first */
527             char *pp = index234(passphrases, attempts);
528             if(pp) {
529                 strcpy(passphrase, pp);
530             } else {
531                 int dlgret;
532                 original_pass = 1;
533                 dlgret = DialogBoxParam(hinst, MAKEINTRESOURCE(210),
534                                         NULL, PassphraseProc, (LPARAM) &pps);
535                 passphrase_box = NULL;
536                 if (!dlgret) {
537                     if (comment)
538                         sfree(comment);
539                     if (type == SSH_KEYTYPE_SSH1)
540                         sfree(rkey);
541                     return;                    /* operation cancelled */
542                 }
543             }
544         } else
545             *passphrase = '\0';
546         if (type == SSH_KEYTYPE_SSH1)
547             ret = loadrsakey(&filename, rkey, passphrase, &error);
548         else {
549             skey = ssh2_load_userkey(&filename, passphrase, &error);
550             if (skey == SSH2_WRONG_PASSPHRASE)
551                 ret = -1;
552             else if (!skey)
553                 ret = 0;
554             else
555                 ret = 1;
556         }
557         attempts++;
558     } while (ret == -1);
559
560     /* if they typed in an ok passphrase, remember it */
561     if(original_pass && ret) {
562         char *pp = dupstr(passphrase);
563         addpos234(passphrases, pp, 0);
564     }
565
566     if (comment)
567         sfree(comment);
568     if (ret == 0) {
569         char *msg = dupprintf("Couldn't load private key (%s)", error);
570         message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
571                     HELPCTXID(errors_cantloadkey));
572         sfree(msg);
573         if (type == SSH_KEYTYPE_SSH1)
574             sfree(rkey);
575         return;
576     }
577     if (type == SSH_KEYTYPE_SSH1) {
578         if (already_running) {
579             unsigned char *request, *response;
580             void *vresponse;
581             int reqlen, clen, resplen, ret;
582
583             clen = strlen(rkey->comment);
584
585             reqlen = 4 + 1 +           /* length, message type */
586                 4 +                    /* bit count */
587                 ssh1_bignum_length(rkey->modulus) +
588                 ssh1_bignum_length(rkey->exponent) +
589                 ssh1_bignum_length(rkey->private_exponent) +
590                 ssh1_bignum_length(rkey->iqmp) +
591                 ssh1_bignum_length(rkey->p) +
592                 ssh1_bignum_length(rkey->q) + 4 + clen  /* comment */
593                 ;
594
595             request = snewn(reqlen, unsigned char);
596
597             request[4] = SSH1_AGENTC_ADD_RSA_IDENTITY;
598             reqlen = 5;
599             PUT_32BIT(request + reqlen, bignum_bitcount(rkey->modulus));
600             reqlen += 4;
601             reqlen += ssh1_write_bignum(request + reqlen, rkey->modulus);
602             reqlen += ssh1_write_bignum(request + reqlen, rkey->exponent);
603             reqlen +=
604                 ssh1_write_bignum(request + reqlen,
605                                   rkey->private_exponent);
606             reqlen += ssh1_write_bignum(request + reqlen, rkey->iqmp);
607             reqlen += ssh1_write_bignum(request + reqlen, rkey->p);
608             reqlen += ssh1_write_bignum(request + reqlen, rkey->q);
609             PUT_32BIT(request + reqlen, clen);
610             memcpy(request + reqlen + 4, rkey->comment, clen);
611             reqlen += 4 + clen;
612             PUT_32BIT(request, reqlen - 4);
613
614             ret = agent_query(request, reqlen, &vresponse, &resplen,
615                               NULL, NULL);
616             assert(ret == 1);
617             response = vresponse;
618             if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
619                 MessageBox(NULL, "The already running Pageant "
620                            "refused to add the key.", APPNAME,
621                            MB_OK | MB_ICONERROR);
622
623             sfree(request);
624             sfree(response);
625         } else {
626             if (add234(rsakeys, rkey) != rkey)
627                 sfree(rkey);           /* already present, don't waste RAM */
628         }
629     } else {
630         if (already_running) {
631             unsigned char *request, *response;
632             void *vresponse;
633             int reqlen, alglen, clen, keybloblen, resplen, ret;
634             alglen = strlen(skey->alg->name);
635             clen = strlen(skey->comment);
636
637             keybloblen = skey->alg->openssh_fmtkey(skey->data, NULL, 0);
638
639             reqlen = 4 + 1 +           /* length, message type */
640                 4 + alglen +           /* algorithm name */
641                 keybloblen +           /* key data */
642                 4 + clen               /* comment */
643                 ;
644
645             request = snewn(reqlen, unsigned char);
646
647             request[4] = SSH2_AGENTC_ADD_IDENTITY;
648             reqlen = 5;
649             PUT_32BIT(request + reqlen, alglen);
650             reqlen += 4;
651             memcpy(request + reqlen, skey->alg->name, alglen);
652             reqlen += alglen;
653             reqlen += skey->alg->openssh_fmtkey(skey->data,
654                                                 request + reqlen,
655                                                 keybloblen);
656             PUT_32BIT(request + reqlen, clen);
657             memcpy(request + reqlen + 4, skey->comment, clen);
658             reqlen += clen + 4;
659             PUT_32BIT(request, reqlen - 4);
660
661             ret = agent_query(request, reqlen, &vresponse, &resplen,
662                               NULL, NULL);
663             assert(ret == 1);
664             response = vresponse;
665             if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
666                 MessageBox(NULL, "The already running Pageant "
667                            "refused to add the key.", APPNAME,
668                            MB_OK | MB_ICONERROR);
669
670             sfree(request);
671             sfree(response);
672         } else {
673             if (add234(ssh2keys, skey) != skey) {
674                 skey->alg->freekey(skey->data);
675                 sfree(skey);           /* already present, don't waste RAM */
676             }
677         }
678     }
679 }
680
681 /*
682  * Create an SSH-1 key list in a malloc'ed buffer; return its
683  * length.
684  */
685 static void *make_keylist1(int *length)
686 {
687     int i, nkeys, len;
688     struct RSAKey *key;
689     unsigned char *blob, *p, *ret;
690     int bloblen;
691
692     /*
693      * Count up the number and length of keys we hold.
694      */
695     len = 4;
696     nkeys = 0;
697     for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
698         nkeys++;
699         blob = rsa_public_blob(key, &bloblen);
700         len += bloblen;
701         sfree(blob);
702         len += 4 + strlen(key->comment);
703     }
704
705     /* Allocate the buffer. */
706     p = ret = snewn(len, unsigned char);
707     if (length) *length = len;
708
709     PUT_32BIT(p, nkeys);
710     p += 4;
711     for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
712         blob = rsa_public_blob(key, &bloblen);
713         memcpy(p, blob, bloblen);
714         p += bloblen;
715         sfree(blob);
716         PUT_32BIT(p, strlen(key->comment));
717         memcpy(p + 4, key->comment, strlen(key->comment));
718         p += 4 + strlen(key->comment);
719     }
720
721     assert(p - ret == len);
722     return ret;
723 }
724
725 /*
726  * Create an SSH-2 key list in a malloc'ed buffer; return its
727  * length.
728  */
729 static void *make_keylist2(int *length)
730 {
731     struct ssh2_userkey *key;
732     int i, len, nkeys;
733     unsigned char *blob, *p, *ret;
734     int bloblen;
735
736     /*
737      * Count up the number and length of keys we hold.
738      */
739     len = 4;
740     nkeys = 0;
741     for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
742         nkeys++;
743         len += 4;              /* length field */
744         blob = key->alg->public_blob(key->data, &bloblen);
745         len += bloblen;
746         sfree(blob);
747         len += 4 + strlen(key->comment);
748     }
749
750     /* Allocate the buffer. */
751     p = ret = snewn(len, unsigned char);
752     if (length) *length = len;
753
754     /*
755      * Packet header is the obvious five bytes, plus four
756      * bytes for the key count.
757      */
758     PUT_32BIT(p, nkeys);
759     p += 4;
760     for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
761         blob = key->alg->public_blob(key->data, &bloblen);
762         PUT_32BIT(p, bloblen);
763         p += 4;
764         memcpy(p, blob, bloblen);
765         p += bloblen;
766         sfree(blob);
767         PUT_32BIT(p, strlen(key->comment));
768         memcpy(p + 4, key->comment, strlen(key->comment));
769         p += 4 + strlen(key->comment);
770     }
771
772     assert(p - ret == len);
773     return ret;
774 }
775
776 /*
777  * Acquire a keylist1 from the primary Pageant; this means either
778  * calling make_keylist1 (if that's us) or sending a message to the
779  * primary Pageant (if it's not).
780  */
781 static void *get_keylist1(int *length)
782 {
783     void *ret;
784
785     if (already_running) {
786         unsigned char request[5], *response;
787         void *vresponse;
788         int resplen, retval;
789         request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
790         PUT_32BIT(request, 4);
791
792         retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
793         assert(retval == 1);
794         response = vresponse;
795         if (resplen < 5 || response[4] != SSH1_AGENT_RSA_IDENTITIES_ANSWER)
796             return NULL;
797
798         ret = snewn(resplen-5, unsigned char);
799         memcpy(ret, response+5, resplen-5);
800         sfree(response);
801
802         if (length)
803             *length = resplen-5;
804     } else {
805         ret = make_keylist1(length);
806     }
807     return ret;
808 }
809
810 /*
811  * Acquire a keylist2 from the primary Pageant; this means either
812  * calling make_keylist2 (if that's us) or sending a message to the
813  * primary Pageant (if it's not).
814  */
815 static void *get_keylist2(int *length)
816 {
817     void *ret;
818
819     if (already_running) {
820         unsigned char request[5], *response;
821         void *vresponse;
822         int resplen, retval;
823
824         request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
825         PUT_32BIT(request, 4);
826
827         retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
828         assert(retval == 1);
829         response = vresponse;
830         if (resplen < 5 || response[4] != SSH2_AGENT_IDENTITIES_ANSWER)
831             return NULL;
832
833         ret = snewn(resplen-5, unsigned char);
834         memcpy(ret, response+5, resplen-5);
835         sfree(response);
836
837         if (length)
838             *length = resplen-5;
839     } else {
840         ret = make_keylist2(length);
841     }
842     return ret;
843 }
844
845 /*
846  * This is the main agent function that answers messages.
847  */
848 static void answer_msg(void *msg)
849 {
850     unsigned char *p = msg;
851     unsigned char *ret = msg;
852     unsigned char *msgend;
853     int type;
854
855     /*
856      * Get the message length.
857      */
858     msgend = p + 4 + GET_32BIT(p);
859
860     /*
861      * Get the message type.
862      */
863     if (msgend < p+5)
864         goto failure;
865     type = p[4];
866
867     p += 5;
868     switch (type) {
869       case SSH1_AGENTC_REQUEST_RSA_IDENTITIES:
870         /*
871          * Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER.
872          */
873         {
874             int len;
875             void *keylist;
876
877             ret[4] = SSH1_AGENT_RSA_IDENTITIES_ANSWER;
878             keylist = make_keylist1(&len);
879             if (len + 5 > AGENT_MAX_MSGLEN) {
880                 sfree(keylist);
881                 goto failure;
882             }
883             PUT_32BIT(ret, len + 1);
884             memcpy(ret + 5, keylist, len);
885             sfree(keylist);
886         }
887         break;
888       case SSH2_AGENTC_REQUEST_IDENTITIES:
889         /*
890          * Reply with SSH2_AGENT_IDENTITIES_ANSWER.
891          */
892         {
893             int len;
894             void *keylist;
895
896             ret[4] = SSH2_AGENT_IDENTITIES_ANSWER;
897             keylist = make_keylist2(&len);
898             if (len + 5 > AGENT_MAX_MSGLEN) {
899                 sfree(keylist);
900                 goto failure;
901             }
902             PUT_32BIT(ret, len + 1);
903             memcpy(ret + 5, keylist, len);
904             sfree(keylist);
905         }
906         break;
907       case SSH1_AGENTC_RSA_CHALLENGE:
908         /*
909          * Reply with either SSH1_AGENT_RSA_RESPONSE or
910          * SSH_AGENT_FAILURE, depending on whether we have that key
911          * or not.
912          */
913         {
914             struct RSAKey reqkey, *key;
915             Bignum challenge, response;
916             unsigned char response_source[48], response_md5[16];
917             struct MD5Context md5c;
918             int i, len;
919
920             p += 4;
921             i = ssh1_read_bignum(p, msgend - p, &reqkey.exponent);
922             if (i < 0)
923                 goto failure;
924             p += i;
925             i = ssh1_read_bignum(p, msgend - p, &reqkey.modulus);
926             if (i < 0)
927                 goto failure;
928             p += i;
929             i = ssh1_read_bignum(p, msgend - p, &challenge);
930             if (i < 0)
931                 goto failure;
932             p += i;
933             if (msgend < p+16) {
934                 freebn(reqkey.exponent);
935                 freebn(reqkey.modulus);
936                 freebn(challenge);
937                 goto failure;
938             }
939             memcpy(response_source + 32, p, 16);
940             p += 16;
941             if (msgend < p+4 ||
942                 GET_32BIT(p) != 1 ||
943                 (key = find234(rsakeys, &reqkey, NULL)) == NULL) {
944                 freebn(reqkey.exponent);
945                 freebn(reqkey.modulus);
946                 freebn(challenge);
947                 goto failure;
948             }
949             response = rsadecrypt(challenge, key);
950             for (i = 0; i < 32; i++)
951                 response_source[i] = bignum_byte(response, 31 - i);
952
953             MD5Init(&md5c);
954             MD5Update(&md5c, response_source, 48);
955             MD5Final(response_md5, &md5c);
956             memset(response_source, 0, 48);     /* burn the evidence */
957             freebn(response);          /* and that evidence */
958             freebn(challenge);         /* yes, and that evidence */
959             freebn(reqkey.exponent);   /* and free some memory ... */
960             freebn(reqkey.modulus);    /* ... while we're at it. */
961
962             /*
963              * Packet is the obvious five byte header, plus sixteen
964              * bytes of MD5.
965              */
966             len = 5 + 16;
967             PUT_32BIT(ret, len - 4);
968             ret[4] = SSH1_AGENT_RSA_RESPONSE;
969             memcpy(ret + 5, response_md5, 16);
970         }
971         break;
972       case SSH2_AGENTC_SIGN_REQUEST:
973         /*
974          * Reply with either SSH2_AGENT_SIGN_RESPONSE or
975          * SSH_AGENT_FAILURE, depending on whether we have that key
976          * or not.
977          */
978         {
979             struct ssh2_userkey *key;
980             struct blob b;
981             unsigned char *data, *signature;
982             int datalen, siglen, len;
983
984             if (msgend < p+4)
985                 goto failure;
986             b.len = GET_32BIT(p);
987             p += 4;
988             if (msgend < p+b.len)
989                 goto failure;
990             b.blob = p;
991             p += b.len;
992             if (msgend < p+4)
993                 goto failure;
994             datalen = GET_32BIT(p);
995             p += 4;
996             if (msgend < p+datalen)
997                 goto failure;
998             data = p;
999             key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
1000             if (!key)
1001                 goto failure;
1002             signature = key->alg->sign(key->data, data, datalen, &siglen);
1003             len = 5 + 4 + siglen;
1004             PUT_32BIT(ret, len - 4);
1005             ret[4] = SSH2_AGENT_SIGN_RESPONSE;
1006             PUT_32BIT(ret + 5, siglen);
1007             memcpy(ret + 5 + 4, signature, siglen);
1008             sfree(signature);
1009         }
1010         break;
1011       case SSH1_AGENTC_ADD_RSA_IDENTITY:
1012         /*
1013          * Add to the list and return SSH_AGENT_SUCCESS, or
1014          * SSH_AGENT_FAILURE if the key was malformed.
1015          */
1016         {
1017             struct RSAKey *key;
1018             char *comment;
1019             int n, commentlen;
1020
1021             key = snew(struct RSAKey);
1022             memset(key, 0, sizeof(struct RSAKey));
1023
1024             n = makekey(p, msgend - p, key, NULL, 1);
1025             if (n < 0) {
1026                 freersakey(key);
1027                 sfree(key);
1028                 goto failure;
1029             }
1030             p += n;
1031
1032             n = makeprivate(p, msgend - p, key);
1033             if (n < 0) {
1034                 freersakey(key);
1035                 sfree(key);
1036                 goto failure;
1037             }
1038             p += n;
1039
1040             n = ssh1_read_bignum(p, msgend - p, &key->iqmp);  /* p^-1 mod q */
1041             if (n < 0) {
1042                 freersakey(key);
1043                 sfree(key);
1044                 goto failure;
1045             }
1046             p += n;
1047
1048             n = ssh1_read_bignum(p, msgend - p, &key->p);  /* p */
1049             if (n < 0) {
1050                 freersakey(key);
1051                 sfree(key);
1052                 goto failure;
1053             }
1054             p += n;
1055
1056             n = ssh1_read_bignum(p, msgend - p, &key->q);  /* q */
1057             if (n < 0) {
1058                 freersakey(key);
1059                 sfree(key);
1060                 goto failure;
1061             }
1062             p += n;
1063
1064             if (msgend < p+4) {
1065                 freersakey(key);
1066                 sfree(key);
1067                 goto failure;
1068             }
1069             commentlen = GET_32BIT(p);
1070
1071             if (msgend < p+commentlen) {
1072                 freersakey(key);
1073                 sfree(key);
1074                 goto failure;
1075             }
1076
1077             comment = snewn(commentlen+1, char);
1078             if (comment) {
1079                 memcpy(comment, p + 4, commentlen);
1080                 comment[commentlen] = '\0';
1081                 key->comment = comment;
1082             }
1083             PUT_32BIT(ret, 1);
1084             ret[4] = SSH_AGENT_FAILURE;
1085             if (add234(rsakeys, key) == key) {
1086                 keylist_update();
1087                 ret[4] = SSH_AGENT_SUCCESS;
1088             } else {
1089                 freersakey(key);
1090                 sfree(key);
1091             }
1092         }
1093         break;
1094       case SSH2_AGENTC_ADD_IDENTITY:
1095         /*
1096          * Add to the list and return SSH_AGENT_SUCCESS, or
1097          * SSH_AGENT_FAILURE if the key was malformed.
1098          */
1099         {
1100             struct ssh2_userkey *key;
1101             char *comment, *alg;
1102             int alglen, commlen;
1103             int bloblen;
1104
1105
1106             if (msgend < p+4)
1107                 goto failure;
1108             alglen = GET_32BIT(p);
1109             p += 4;
1110             if (msgend < p+alglen)
1111                 goto failure;
1112             alg = p;
1113             p += alglen;
1114
1115             key = snew(struct ssh2_userkey);
1116             /* Add further algorithm names here. */
1117             if (alglen == 7 && !memcmp(alg, "ssh-rsa", 7))
1118                 key->alg = &ssh_rsa;
1119             else if (alglen == 7 && !memcmp(alg, "ssh-dss", 7))
1120                 key->alg = &ssh_dss;
1121             else {
1122                 sfree(key);
1123                 goto failure;
1124             }
1125
1126             bloblen = msgend - p;
1127             key->data = key->alg->openssh_createkey(&p, &bloblen);
1128             if (!key->data) {
1129                 sfree(key);
1130                 goto failure;
1131             }
1132
1133             /*
1134              * p has been advanced by openssh_createkey, but
1135              * certainly not _beyond_ the end of the buffer.
1136              */
1137             assert(p <= msgend);
1138
1139             if (msgend < p+4) {
1140                 key->alg->freekey(key->data);
1141                 sfree(key);
1142                 goto failure;
1143             }
1144             commlen = GET_32BIT(p);
1145             p += 4;
1146
1147             if (msgend < p+commlen) {
1148                 key->alg->freekey(key->data);
1149                 sfree(key);
1150                 goto failure;
1151             }
1152             comment = snewn(commlen + 1, char);
1153             if (comment) {
1154                 memcpy(comment, p, commlen);
1155                 comment[commlen] = '\0';
1156             }
1157             key->comment = comment;
1158
1159             PUT_32BIT(ret, 1);
1160             ret[4] = SSH_AGENT_FAILURE;
1161             if (add234(ssh2keys, key) == key) {
1162                 keylist_update();
1163                 ret[4] = SSH_AGENT_SUCCESS;
1164             } else {
1165                 key->alg->freekey(key->data);
1166                 sfree(key->comment);
1167                 sfree(key);
1168             }
1169         }
1170         break;
1171       case SSH1_AGENTC_REMOVE_RSA_IDENTITY:
1172         /*
1173          * Remove from the list and return SSH_AGENT_SUCCESS, or
1174          * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
1175          * start with.
1176          */
1177         {
1178             struct RSAKey reqkey, *key;
1179             int n;
1180
1181             n = makekey(p, msgend - p, &reqkey, NULL, 0);
1182             if (n < 0)
1183                 goto failure;
1184
1185             key = find234(rsakeys, &reqkey, NULL);
1186             freebn(reqkey.exponent);
1187             freebn(reqkey.modulus);
1188             PUT_32BIT(ret, 1);
1189             ret[4] = SSH_AGENT_FAILURE;
1190             if (key) {
1191                 del234(rsakeys, key);
1192                 keylist_update();
1193                 freersakey(key);
1194                 sfree(key);
1195                 ret[4] = SSH_AGENT_SUCCESS;
1196             }
1197         }
1198         break;
1199       case SSH2_AGENTC_REMOVE_IDENTITY:
1200         /*
1201          * Remove from the list and return SSH_AGENT_SUCCESS, or
1202          * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
1203          * start with.
1204          */
1205         {
1206             struct ssh2_userkey *key;
1207             struct blob b;
1208
1209             if (msgend < p+4)
1210                 goto failure;
1211             b.len = GET_32BIT(p);
1212             p += 4;
1213
1214             if (msgend < p+b.len)
1215                 goto failure;
1216             b.blob = p;
1217             p += b.len;
1218
1219             key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
1220             if (!key)
1221                 goto failure;
1222
1223             PUT_32BIT(ret, 1);
1224             ret[4] = SSH_AGENT_FAILURE;
1225             if (key) {
1226                 del234(ssh2keys, key);
1227                 keylist_update();
1228                 key->alg->freekey(key->data);
1229                 sfree(key);
1230                 ret[4] = SSH_AGENT_SUCCESS;
1231             }
1232         }
1233         break;
1234       case SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES:
1235         /*
1236          * Remove all SSH-1 keys. Always returns success.
1237          */
1238         {
1239             struct RSAKey *rkey;
1240
1241             while ((rkey = index234(rsakeys, 0)) != NULL) {
1242                 del234(rsakeys, rkey);
1243                 freersakey(rkey);
1244                 sfree(rkey);
1245             }
1246             keylist_update();
1247
1248             PUT_32BIT(ret, 1);
1249             ret[4] = SSH_AGENT_SUCCESS;
1250         }
1251         break;
1252       case SSH2_AGENTC_REMOVE_ALL_IDENTITIES:
1253         /*
1254          * Remove all SSH-2 keys. Always returns success.
1255          */
1256         {
1257             struct ssh2_userkey *skey;
1258
1259             while ((skey = index234(ssh2keys, 0)) != NULL) {
1260                 del234(ssh2keys, skey);
1261                 skey->alg->freekey(skey->data);
1262                 sfree(skey);
1263             }
1264             keylist_update();
1265
1266             PUT_32BIT(ret, 1);
1267             ret[4] = SSH_AGENT_SUCCESS;
1268         }
1269         break;
1270       default:
1271       failure:
1272         /*
1273          * Unrecognised message. Return SSH_AGENT_FAILURE.
1274          */
1275         PUT_32BIT(ret, 1);
1276         ret[4] = SSH_AGENT_FAILURE;
1277         break;
1278     }
1279 }
1280
1281 /*
1282  * Key comparison function for the 2-3-4 tree of RSA keys.
1283  */
1284 static int cmpkeys_rsa(void *av, void *bv)
1285 {
1286     struct RSAKey *a = (struct RSAKey *) av;
1287     struct RSAKey *b = (struct RSAKey *) bv;
1288     Bignum am, bm;
1289     int alen, blen;
1290
1291     am = a->modulus;
1292     bm = b->modulus;
1293     /*
1294      * Compare by length of moduli.
1295      */
1296     alen = bignum_bitcount(am);
1297     blen = bignum_bitcount(bm);
1298     if (alen > blen)
1299         return +1;
1300     else if (alen < blen)
1301         return -1;
1302     /*
1303      * Now compare by moduli themselves.
1304      */
1305     alen = (alen + 7) / 8;             /* byte count */
1306     while (alen-- > 0) {
1307         int abyte, bbyte;
1308         abyte = bignum_byte(am, alen);
1309         bbyte = bignum_byte(bm, alen);
1310         if (abyte > bbyte)
1311             return +1;
1312         else if (abyte < bbyte)
1313             return -1;
1314     }
1315     /*
1316      * Give up.
1317      */
1318     return 0;
1319 }
1320
1321 /*
1322  * Key comparison function for the 2-3-4 tree of SSH-2 keys.
1323  */
1324 static int cmpkeys_ssh2(void *av, void *bv)
1325 {
1326     struct ssh2_userkey *a = (struct ssh2_userkey *) av;
1327     struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
1328     int i;
1329     int alen, blen;
1330     unsigned char *ablob, *bblob;
1331     int c;
1332
1333     /*
1334      * Compare purely by public blob.
1335      */
1336     ablob = a->alg->public_blob(a->data, &alen);
1337     bblob = b->alg->public_blob(b->data, &blen);
1338
1339     c = 0;
1340     for (i = 0; i < alen && i < blen; i++) {
1341         if (ablob[i] < bblob[i]) {
1342             c = -1;
1343             break;
1344         } else if (ablob[i] > bblob[i]) {
1345             c = +1;
1346             break;
1347         }
1348     }
1349     if (c == 0 && i < alen)
1350         c = +1;                        /* a is longer */
1351     if (c == 0 && i < blen)
1352         c = -1;                        /* a is longer */
1353
1354     sfree(ablob);
1355     sfree(bblob);
1356
1357     return c;
1358 }
1359
1360 /*
1361  * Key comparison function for looking up a blob in the 2-3-4 tree
1362  * of SSH-2 keys.
1363  */
1364 static int cmpkeys_ssh2_asymm(void *av, void *bv)
1365 {
1366     struct blob *a = (struct blob *) av;
1367     struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
1368     int i;
1369     int alen, blen;
1370     unsigned char *ablob, *bblob;
1371     int c;
1372
1373     /*
1374      * Compare purely by public blob.
1375      */
1376     ablob = a->blob;
1377     alen = a->len;
1378     bblob = b->alg->public_blob(b->data, &blen);
1379
1380     c = 0;
1381     for (i = 0; i < alen && i < blen; i++) {
1382         if (ablob[i] < bblob[i]) {
1383             c = -1;
1384             break;
1385         } else if (ablob[i] > bblob[i]) {
1386             c = +1;
1387             break;
1388         }
1389     }
1390     if (c == 0 && i < alen)
1391         c = +1;                        /* a is longer */
1392     if (c == 0 && i < blen)
1393         c = -1;                        /* a is longer */
1394
1395     sfree(bblob);
1396
1397     return c;
1398 }
1399
1400 /*
1401  * Prompt for a key file to add, and add it.
1402  */
1403 static void prompt_add_keyfile(void)
1404 {
1405     OPENFILENAME of;
1406     char *filelist = snewn(8192, char);
1407         
1408     if (!keypath) keypath = filereq_new();
1409     memset(&of, 0, sizeof(of));
1410     of.hwndOwner = hwnd;
1411     of.lpstrFilter = FILTER_KEY_FILES;
1412     of.lpstrCustomFilter = NULL;
1413     of.nFilterIndex = 1;
1414     of.lpstrFile = filelist;
1415     *filelist = '\0';
1416     of.nMaxFile = 8192;
1417     of.lpstrFileTitle = NULL;
1418     of.lpstrTitle = "Select Private Key File";
1419     of.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER;
1420     if (request_file(keypath, &of, TRUE, FALSE)) {
1421         if(strlen(filelist) > of.nFileOffset)
1422             /* Only one filename returned? */
1423             add_keyfile(filename_from_str(filelist));
1424         else {
1425             /* we are returned a bunch of strings, end to
1426              * end. first string is the directory, the
1427              * rest the filenames. terminated with an
1428              * empty string.
1429              */
1430             char *dir = filelist;
1431             char *filewalker = filelist + strlen(dir) + 1;
1432             while (*filewalker != '\0') {
1433                 char *filename = dupcat(dir, "\\", filewalker, NULL);
1434                 add_keyfile(filename_from_str(filename));
1435                 sfree(filename);
1436                 filewalker += strlen(filewalker) + 1;
1437             }
1438         }
1439
1440         keylist_update();
1441         forget_passphrases();
1442     }
1443     sfree(filelist);
1444 }
1445
1446 /*
1447  * Dialog-box function for the key list box.
1448  */
1449 static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
1450                                 WPARAM wParam, LPARAM lParam)
1451 {
1452     struct RSAKey *rkey;
1453     struct ssh2_userkey *skey;
1454
1455     switch (msg) {
1456       case WM_INITDIALOG:
1457         /*
1458          * Centre the window.
1459          */
1460         {                              /* centre the window */
1461             RECT rs, rd;
1462             HWND hw;
1463
1464             hw = GetDesktopWindow();
1465             if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
1466                 MoveWindow(hwnd,
1467                            (rs.right + rs.left + rd.left - rd.right) / 2,
1468                            (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
1469                            rd.right - rd.left, rd.bottom - rd.top, TRUE);
1470         }
1471
1472         if (help_path)
1473             SetWindowLongPtr(hwnd, GWL_EXSTYLE,
1474                              GetWindowLongPtr(hwnd, GWL_EXSTYLE) |
1475                              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 }