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