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