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