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