]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - windows/winpgnt.c
679116021673732d1c858a839b1e93400f6ad413
[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             sfree(response);
816             return NULL;
817         }
818
819         ret = snewn(resplen-5, unsigned char);
820         memcpy(ret, response+5, resplen-5);
821         sfree(response);
822
823         if (length)
824             *length = resplen-5;
825     } else {
826         ret = make_keylist1(length);
827     }
828     return ret;
829 }
830
831 /*
832  * Acquire a keylist2 from the primary Pageant; this means either
833  * calling make_keylist2 (if that's us) or sending a message to the
834  * primary Pageant (if it's not).
835  */
836 static void *get_keylist2(int *length)
837 {
838     void *ret;
839
840     if (already_running) {
841         unsigned char request[5], *response;
842         void *vresponse;
843         int resplen, retval;
844
845         request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
846         PUT_32BIT(request, 4);
847
848         retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
849         assert(retval == 1);
850         response = vresponse;
851         if (resplen < 5 || response[4] != SSH2_AGENT_IDENTITIES_ANSWER) {
852             sfree(response);
853             return NULL;
854         }
855
856         ret = snewn(resplen-5, unsigned char);
857         memcpy(ret, response+5, resplen-5);
858         sfree(response);
859
860         if (length)
861             *length = resplen-5;
862     } else {
863         ret = make_keylist2(length);
864     }
865     return ret;
866 }
867
868 /*
869  * This is the main agent function that answers messages.
870  */
871 static void answer_msg(void *msg)
872 {
873     unsigned char *p = msg;
874     unsigned char *ret = msg;
875     unsigned char *msgend;
876     int type;
877
878     /*
879      * Get the message length.
880      */
881     msgend = p + 4 + GET_32BIT(p);
882
883     /*
884      * Get the message type.
885      */
886     if (msgend < p+5)
887         goto failure;
888     type = p[4];
889
890     p += 5;
891     switch (type) {
892       case SSH1_AGENTC_REQUEST_RSA_IDENTITIES:
893         /*
894          * Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER.
895          */
896         {
897             int len;
898             void *keylist;
899
900             ret[4] = SSH1_AGENT_RSA_IDENTITIES_ANSWER;
901             keylist = make_keylist1(&len);
902             if (len + 5 > AGENT_MAX_MSGLEN) {
903                 sfree(keylist);
904                 goto failure;
905             }
906             PUT_32BIT(ret, len + 1);
907             memcpy(ret + 5, keylist, len);
908             sfree(keylist);
909         }
910         break;
911       case SSH2_AGENTC_REQUEST_IDENTITIES:
912         /*
913          * Reply with SSH2_AGENT_IDENTITIES_ANSWER.
914          */
915         {
916             int len;
917             void *keylist;
918
919             ret[4] = SSH2_AGENT_IDENTITIES_ANSWER;
920             keylist = make_keylist2(&len);
921             if (len + 5 > AGENT_MAX_MSGLEN) {
922                 sfree(keylist);
923                 goto failure;
924             }
925             PUT_32BIT(ret, len + 1);
926             memcpy(ret + 5, keylist, len);
927             sfree(keylist);
928         }
929         break;
930       case SSH1_AGENTC_RSA_CHALLENGE:
931         /*
932          * Reply with either SSH1_AGENT_RSA_RESPONSE or
933          * SSH_AGENT_FAILURE, depending on whether we have that key
934          * or not.
935          */
936         {
937             struct RSAKey reqkey, *key;
938             Bignum challenge, response;
939             unsigned char response_source[48], response_md5[16];
940             struct MD5Context md5c;
941             int i, len;
942
943             p += 4;
944             i = ssh1_read_bignum(p, msgend - p, &reqkey.exponent);
945             if (i < 0)
946                 goto failure;
947             p += i;
948             i = ssh1_read_bignum(p, msgend - p, &reqkey.modulus);
949             if (i < 0) {
950                 freebn(reqkey.exponent);
951                 goto failure;
952             }
953             p += i;
954             i = ssh1_read_bignum(p, msgend - p, &challenge);
955             if (i < 0) {
956                 freebn(reqkey.exponent);
957                 freebn(reqkey.modulus);
958                 goto failure;
959             }
960             p += i;
961             if (msgend < p+16) {
962                 freebn(reqkey.exponent);
963                 freebn(reqkey.modulus);
964                 freebn(challenge);
965                 goto failure;
966             }
967             memcpy(response_source + 32, p, 16);
968             p += 16;
969             if (msgend < p+4 ||
970                 GET_32BIT(p) != 1 ||
971                 (key = find234(rsakeys, &reqkey, NULL)) == NULL) {
972                 freebn(reqkey.exponent);
973                 freebn(reqkey.modulus);
974                 freebn(challenge);
975                 goto failure;
976             }
977             response = rsadecrypt(challenge, key);
978             for (i = 0; i < 32; i++)
979                 response_source[i] = bignum_byte(response, 31 - i);
980
981             MD5Init(&md5c);
982             MD5Update(&md5c, response_source, 48);
983             MD5Final(response_md5, &md5c);
984             smemclr(response_source, 48);       /* burn the evidence */
985             freebn(response);          /* and that evidence */
986             freebn(challenge);         /* yes, and that evidence */
987             freebn(reqkey.exponent);   /* and free some memory ... */
988             freebn(reqkey.modulus);    /* ... while we're at it. */
989
990             /*
991              * Packet is the obvious five byte header, plus sixteen
992              * bytes of MD5.
993              */
994             len = 5 + 16;
995             PUT_32BIT(ret, len - 4);
996             ret[4] = SSH1_AGENT_RSA_RESPONSE;
997             memcpy(ret + 5, response_md5, 16);
998         }
999         break;
1000       case SSH2_AGENTC_SIGN_REQUEST:
1001         /*
1002          * Reply with either SSH2_AGENT_SIGN_RESPONSE or
1003          * SSH_AGENT_FAILURE, depending on whether we have that key
1004          * or not.
1005          */
1006         {
1007             struct ssh2_userkey *key;
1008             struct blob b;
1009             unsigned char *data, *signature;
1010             int datalen, siglen, len;
1011
1012             if (msgend < p+4)
1013                 goto failure;
1014             b.len = toint(GET_32BIT(p));
1015             if (b.len < 0 || b.len > msgend - (p+4))
1016                 goto failure;
1017             p += 4;
1018             b.blob = p;
1019             p += b.len;
1020             if (msgend < p+4)
1021                 goto failure;
1022             datalen = toint(GET_32BIT(p));
1023             p += 4;
1024             if (datalen < 0 || datalen > msgend - p)
1025                 goto failure;
1026             data = p;
1027             key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
1028             if (!key)
1029                 goto failure;
1030             signature = key->alg->sign(key->data, data, datalen, &siglen);
1031             len = 5 + 4 + siglen;
1032             PUT_32BIT(ret, len - 4);
1033             ret[4] = SSH2_AGENT_SIGN_RESPONSE;
1034             PUT_32BIT(ret + 5, siglen);
1035             memcpy(ret + 5 + 4, signature, siglen);
1036             sfree(signature);
1037         }
1038         break;
1039       case SSH1_AGENTC_ADD_RSA_IDENTITY:
1040         /*
1041          * Add to the list and return SSH_AGENT_SUCCESS, or
1042          * SSH_AGENT_FAILURE if the key was malformed.
1043          */
1044         {
1045             struct RSAKey *key;
1046             char *comment;
1047             int n, commentlen;
1048
1049             key = snew(struct RSAKey);
1050             memset(key, 0, sizeof(struct RSAKey));
1051
1052             n = makekey(p, msgend - p, key, NULL, 1);
1053             if (n < 0) {
1054                 freersakey(key);
1055                 sfree(key);
1056                 goto failure;
1057             }
1058             p += n;
1059
1060             n = makeprivate(p, msgend - p, key);
1061             if (n < 0) {
1062                 freersakey(key);
1063                 sfree(key);
1064                 goto failure;
1065             }
1066             p += n;
1067
1068             n = ssh1_read_bignum(p, msgend - p, &key->iqmp);  /* p^-1 mod q */
1069             if (n < 0) {
1070                 freersakey(key);
1071                 sfree(key);
1072                 goto failure;
1073             }
1074             p += n;
1075
1076             n = ssh1_read_bignum(p, msgend - p, &key->p);  /* p */
1077             if (n < 0) {
1078                 freersakey(key);
1079                 sfree(key);
1080                 goto failure;
1081             }
1082             p += n;
1083
1084             n = ssh1_read_bignum(p, msgend - p, &key->q);  /* q */
1085             if (n < 0) {
1086                 freersakey(key);
1087                 sfree(key);
1088                 goto failure;
1089             }
1090             p += n;
1091
1092             if (msgend < p+4) {
1093                 freersakey(key);
1094                 sfree(key);
1095                 goto failure;
1096             }
1097             commentlen = toint(GET_32BIT(p));
1098
1099             if (commentlen < 0 || commentlen > msgend - p) {
1100                 freersakey(key);
1101                 sfree(key);
1102                 goto failure;
1103             }
1104
1105             comment = snewn(commentlen+1, char);
1106             if (comment) {
1107                 memcpy(comment, p + 4, commentlen);
1108                 comment[commentlen] = '\0';
1109                 key->comment = comment;
1110             }
1111             PUT_32BIT(ret, 1);
1112             ret[4] = SSH_AGENT_FAILURE;
1113             if (add234(rsakeys, key) == key) {
1114                 keylist_update();
1115                 ret[4] = SSH_AGENT_SUCCESS;
1116             } else {
1117                 freersakey(key);
1118                 sfree(key);
1119             }
1120         }
1121         break;
1122       case SSH2_AGENTC_ADD_IDENTITY:
1123         /*
1124          * Add to the list and return SSH_AGENT_SUCCESS, or
1125          * SSH_AGENT_FAILURE if the key was malformed.
1126          */
1127         {
1128             struct ssh2_userkey *key;
1129             char *comment, *alg;
1130             int alglen, commlen;
1131             int bloblen;
1132
1133
1134             if (msgend < p+4)
1135                 goto failure;
1136             alglen = toint(GET_32BIT(p));
1137             p += 4;
1138             if (alglen < 0 || alglen > msgend - p)
1139                 goto failure;
1140             alg = p;
1141             p += alglen;
1142
1143             key = snew(struct ssh2_userkey);
1144             /* Add further algorithm names here. */
1145             if (alglen == 7 && !memcmp(alg, "ssh-rsa", 7))
1146                 key->alg = &ssh_rsa;
1147             else if (alglen == 7 && !memcmp(alg, "ssh-dss", 7))
1148                 key->alg = &ssh_dss;
1149             else {
1150                 sfree(key);
1151                 goto failure;
1152             }
1153
1154             bloblen = msgend - p;
1155             key->data = key->alg->openssh_createkey(&p, &bloblen);
1156             if (!key->data) {
1157                 sfree(key);
1158                 goto failure;
1159             }
1160
1161             /*
1162              * p has been advanced by openssh_createkey, but
1163              * certainly not _beyond_ the end of the buffer.
1164              */
1165             assert(p <= msgend);
1166
1167             if (msgend < p+4) {
1168                 key->alg->freekey(key->data);
1169                 sfree(key);
1170                 goto failure;
1171             }
1172             commlen = toint(GET_32BIT(p));
1173             p += 4;
1174
1175             if (commlen < 0 || commlen > msgend - p) {
1176                 key->alg->freekey(key->data);
1177                 sfree(key);
1178                 goto failure;
1179             }
1180             comment = snewn(commlen + 1, char);
1181             if (comment) {
1182                 memcpy(comment, p, commlen);
1183                 comment[commlen] = '\0';
1184             }
1185             key->comment = comment;
1186
1187             PUT_32BIT(ret, 1);
1188             ret[4] = SSH_AGENT_FAILURE;
1189             if (add234(ssh2keys, key) == key) {
1190                 keylist_update();
1191                 ret[4] = SSH_AGENT_SUCCESS;
1192             } else {
1193                 key->alg->freekey(key->data);
1194                 sfree(key->comment);
1195                 sfree(key);
1196             }
1197         }
1198         break;
1199       case SSH1_AGENTC_REMOVE_RSA_IDENTITY:
1200         /*
1201          * Remove from the list and return SSH_AGENT_SUCCESS, or
1202          * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
1203          * start with.
1204          */
1205         {
1206             struct RSAKey reqkey, *key;
1207             int n;
1208
1209             n = makekey(p, msgend - p, &reqkey, NULL, 0);
1210             if (n < 0)
1211                 goto failure;
1212
1213             key = find234(rsakeys, &reqkey, NULL);
1214             freebn(reqkey.exponent);
1215             freebn(reqkey.modulus);
1216             PUT_32BIT(ret, 1);
1217             ret[4] = SSH_AGENT_FAILURE;
1218             if (key) {
1219                 del234(rsakeys, key);
1220                 keylist_update();
1221                 freersakey(key);
1222                 sfree(key);
1223                 ret[4] = SSH_AGENT_SUCCESS;
1224             }
1225         }
1226         break;
1227       case SSH2_AGENTC_REMOVE_IDENTITY:
1228         /*
1229          * Remove from the list and return SSH_AGENT_SUCCESS, or
1230          * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
1231          * start with.
1232          */
1233         {
1234             struct ssh2_userkey *key;
1235             struct blob b;
1236
1237             if (msgend < p+4)
1238                 goto failure;
1239             b.len = toint(GET_32BIT(p));
1240             p += 4;
1241
1242             if (b.len < 0 || b.len > msgend - p)
1243                 goto failure;
1244             b.blob = p;
1245             p += b.len;
1246
1247             key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
1248             if (!key)
1249                 goto failure;
1250
1251             PUT_32BIT(ret, 1);
1252             ret[4] = SSH_AGENT_FAILURE;
1253             if (key) {
1254                 del234(ssh2keys, key);
1255                 keylist_update();
1256                 key->alg->freekey(key->data);
1257                 sfree(key);
1258                 ret[4] = SSH_AGENT_SUCCESS;
1259             }
1260         }
1261         break;
1262       case SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES:
1263         /*
1264          * Remove all SSH-1 keys. Always returns success.
1265          */
1266         {
1267             struct RSAKey *rkey;
1268
1269             while ((rkey = index234(rsakeys, 0)) != NULL) {
1270                 del234(rsakeys, rkey);
1271                 freersakey(rkey);
1272                 sfree(rkey);
1273             }
1274             keylist_update();
1275
1276             PUT_32BIT(ret, 1);
1277             ret[4] = SSH_AGENT_SUCCESS;
1278         }
1279         break;
1280       case SSH2_AGENTC_REMOVE_ALL_IDENTITIES:
1281         /*
1282          * Remove all SSH-2 keys. Always returns success.
1283          */
1284         {
1285             struct ssh2_userkey *skey;
1286
1287             while ((skey = index234(ssh2keys, 0)) != NULL) {
1288                 del234(ssh2keys, skey);
1289                 skey->alg->freekey(skey->data);
1290                 sfree(skey);
1291             }
1292             keylist_update();
1293
1294             PUT_32BIT(ret, 1);
1295             ret[4] = SSH_AGENT_SUCCESS;
1296         }
1297         break;
1298       default:
1299       failure:
1300         /*
1301          * Unrecognised message. Return SSH_AGENT_FAILURE.
1302          */
1303         PUT_32BIT(ret, 1);
1304         ret[4] = SSH_AGENT_FAILURE;
1305         break;
1306     }
1307 }
1308
1309 /*
1310  * Key comparison function for the 2-3-4 tree of RSA keys.
1311  */
1312 static int cmpkeys_rsa(void *av, void *bv)
1313 {
1314     struct RSAKey *a = (struct RSAKey *) av;
1315     struct RSAKey *b = (struct RSAKey *) bv;
1316     Bignum am, bm;
1317     int alen, blen;
1318
1319     am = a->modulus;
1320     bm = b->modulus;
1321     /*
1322      * Compare by length of moduli.
1323      */
1324     alen = bignum_bitcount(am);
1325     blen = bignum_bitcount(bm);
1326     if (alen > blen)
1327         return +1;
1328     else if (alen < blen)
1329         return -1;
1330     /*
1331      * Now compare by moduli themselves.
1332      */
1333     alen = (alen + 7) / 8;             /* byte count */
1334     while (alen-- > 0) {
1335         int abyte, bbyte;
1336         abyte = bignum_byte(am, alen);
1337         bbyte = bignum_byte(bm, alen);
1338         if (abyte > bbyte)
1339             return +1;
1340         else if (abyte < bbyte)
1341             return -1;
1342     }
1343     /*
1344      * Give up.
1345      */
1346     return 0;
1347 }
1348
1349 /*
1350  * Key comparison function for the 2-3-4 tree of SSH-2 keys.
1351  */
1352 static int cmpkeys_ssh2(void *av, void *bv)
1353 {
1354     struct ssh2_userkey *a = (struct ssh2_userkey *) av;
1355     struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
1356     int i;
1357     int alen, blen;
1358     unsigned char *ablob, *bblob;
1359     int c;
1360
1361     /*
1362      * Compare purely by public blob.
1363      */
1364     ablob = a->alg->public_blob(a->data, &alen);
1365     bblob = b->alg->public_blob(b->data, &blen);
1366
1367     c = 0;
1368     for (i = 0; i < alen && i < blen; i++) {
1369         if (ablob[i] < bblob[i]) {
1370             c = -1;
1371             break;
1372         } else if (ablob[i] > bblob[i]) {
1373             c = +1;
1374             break;
1375         }
1376     }
1377     if (c == 0 && i < alen)
1378         c = +1;                        /* a is longer */
1379     if (c == 0 && i < blen)
1380         c = -1;                        /* a is longer */
1381
1382     sfree(ablob);
1383     sfree(bblob);
1384
1385     return c;
1386 }
1387
1388 /*
1389  * Key comparison function for looking up a blob in the 2-3-4 tree
1390  * of SSH-2 keys.
1391  */
1392 static int cmpkeys_ssh2_asymm(void *av, void *bv)
1393 {
1394     struct blob *a = (struct blob *) av;
1395     struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
1396     int i;
1397     int alen, blen;
1398     unsigned char *ablob, *bblob;
1399     int c;
1400
1401     /*
1402      * Compare purely by public blob.
1403      */
1404     ablob = a->blob;
1405     alen = a->len;
1406     bblob = b->alg->public_blob(b->data, &blen);
1407
1408     c = 0;
1409     for (i = 0; i < alen && i < blen; i++) {
1410         if (ablob[i] < bblob[i]) {
1411             c = -1;
1412             break;
1413         } else if (ablob[i] > bblob[i]) {
1414             c = +1;
1415             break;
1416         }
1417     }
1418     if (c == 0 && i < alen)
1419         c = +1;                        /* a is longer */
1420     if (c == 0 && i < blen)
1421         c = -1;                        /* a is longer */
1422
1423     sfree(bblob);
1424
1425     return c;
1426 }
1427
1428 /*
1429  * Prompt for a key file to add, and add it.
1430  */
1431 static void prompt_add_keyfile(void)
1432 {
1433     OPENFILENAME of;
1434     char *filelist = snewn(8192, char);
1435         
1436     if (!keypath) keypath = filereq_new();
1437     memset(&of, 0, sizeof(of));
1438     of.hwndOwner = hwnd;
1439     of.lpstrFilter = FILTER_KEY_FILES;
1440     of.lpstrCustomFilter = NULL;
1441     of.nFilterIndex = 1;
1442     of.lpstrFile = filelist;
1443     *filelist = '\0';
1444     of.nMaxFile = 8192;
1445     of.lpstrFileTitle = NULL;
1446     of.lpstrTitle = "Select Private Key File";
1447     of.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER;
1448     if (request_file(keypath, &of, TRUE, FALSE)) {
1449         if(strlen(filelist) > of.nFileOffset) {
1450             /* Only one filename returned? */
1451             Filename *fn = filename_from_str(filelist);
1452             add_keyfile(fn);
1453             filename_free(fn);
1454         } else {
1455             /* we are returned a bunch of strings, end to
1456              * end. first string is the directory, the
1457              * rest the filenames. terminated with an
1458              * empty string.
1459              */
1460             char *dir = filelist;
1461             char *filewalker = filelist + strlen(dir) + 1;
1462             while (*filewalker != '\0') {
1463                 char *filename = dupcat(dir, "\\", filewalker, NULL);
1464                 Filename *fn = filename_from_str(filename);
1465                 add_keyfile(fn);
1466                 filename_free(fn);
1467                 sfree(filename);
1468                 filewalker += strlen(filewalker) + 1;
1469             }
1470         }
1471
1472         keylist_update();
1473         forget_passphrases();
1474     }
1475     sfree(filelist);
1476 }
1477
1478 /*
1479  * Dialog-box function for the key list box.
1480  */
1481 static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
1482                                 WPARAM wParam, LPARAM lParam)
1483 {
1484     struct RSAKey *rkey;
1485     struct ssh2_userkey *skey;
1486
1487     switch (msg) {
1488       case WM_INITDIALOG:
1489         /*
1490          * Centre the window.
1491          */
1492         {                              /* centre the window */
1493             RECT rs, rd;
1494             HWND hw;
1495
1496             hw = GetDesktopWindow();
1497             if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
1498                 MoveWindow(hwnd,
1499                            (rs.right + rs.left + rd.left - rd.right) / 2,
1500                            (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
1501                            rd.right - rd.left, rd.bottom - rd.top, TRUE);
1502         }
1503
1504         if (has_help())
1505             SetWindowLongPtr(hwnd, GWL_EXSTYLE,
1506                              GetWindowLongPtr(hwnd, GWL_EXSTYLE) |
1507                              WS_EX_CONTEXTHELP);
1508         else {
1509             HWND item = GetDlgItem(hwnd, 103);   /* the Help button */
1510             if (item)
1511                 DestroyWindow(item);
1512         }
1513
1514         keylist = hwnd;
1515         {
1516             static int tabs[] = { 35, 60, 210 };
1517             SendDlgItemMessage(hwnd, 100, LB_SETTABSTOPS,
1518                                sizeof(tabs) / sizeof(*tabs),
1519                                (LPARAM) tabs);
1520         }
1521         keylist_update();
1522         return 0;
1523       case WM_COMMAND:
1524         switch (LOWORD(wParam)) {
1525           case IDOK:
1526           case IDCANCEL:
1527             keylist = NULL;
1528             DestroyWindow(hwnd);
1529             return 0;
1530           case 101:                    /* add key */
1531             if (HIWORD(wParam) == BN_CLICKED ||
1532                 HIWORD(wParam) == BN_DOUBLECLICKED) {
1533                 if (passphrase_box) {
1534                     MessageBeep(MB_ICONERROR);
1535                     SetForegroundWindow(passphrase_box);
1536                     break;
1537                 }
1538                 prompt_add_keyfile();
1539             }
1540             return 0;
1541           case 102:                    /* remove key */
1542             if (HIWORD(wParam) == BN_CLICKED ||
1543                 HIWORD(wParam) == BN_DOUBLECLICKED) {
1544                 int i;
1545                 int rCount, sCount;
1546                 int *selectedArray;
1547                 
1548                 /* our counter within the array of selected items */
1549                 int itemNum;
1550                 
1551                 /* get the number of items selected in the list */
1552                 int numSelected = 
1553                         SendDlgItemMessage(hwnd, 100, LB_GETSELCOUNT, 0, 0);
1554                 
1555                 /* none selected? that was silly */
1556                 if (numSelected == 0) {
1557                     MessageBeep(0);
1558                     break;
1559                 }
1560
1561                 /* get item indices in an array */
1562                 selectedArray = snewn(numSelected, int);
1563                 SendDlgItemMessage(hwnd, 100, LB_GETSELITEMS,
1564                                 numSelected, (WPARAM)selectedArray);
1565                 
1566                 itemNum = numSelected - 1;
1567                 rCount = count234(rsakeys);
1568                 sCount = count234(ssh2keys);
1569                 
1570                 /* go through the non-rsakeys until we've covered them all, 
1571                  * and/or we're out of selected items to check. note that
1572                  * we go *backwards*, to avoid complications from deleting
1573                  * things hence altering the offset of subsequent items
1574                  */
1575             for (i = sCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1576                         skey = index234(ssh2keys, i);
1577                         
1578                         if (selectedArray[itemNum] == rCount + i) {
1579                                 del234(ssh2keys, skey);
1580                                 skey->alg->freekey(skey->data);
1581                                 sfree(skey);
1582                                 itemNum--; 
1583                         }
1584                 }
1585                 
1586                 /* do the same for the rsa keys */
1587                 for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) {
1588                         rkey = index234(rsakeys, i);
1589
1590                         if(selectedArray[itemNum] == i) {
1591                                 del234(rsakeys, rkey);
1592                                 freersakey(rkey);
1593                                 sfree(rkey);
1594                                 itemNum--;
1595                         }
1596                 }
1597
1598                 sfree(selectedArray); 
1599                 keylist_update();
1600             }
1601             return 0;
1602           case 103:                    /* help */
1603             if (HIWORD(wParam) == BN_CLICKED ||
1604                 HIWORD(wParam) == BN_DOUBLECLICKED) {
1605                 launch_help(hwnd, WINHELP_CTX_pageant_general);
1606             }
1607             return 0;
1608         }
1609         return 0;
1610       case WM_HELP:
1611         {
1612             int id = ((LPHELPINFO)lParam)->iCtrlId;
1613             char *topic = NULL;
1614             switch (id) {
1615               case 100: topic = WINHELP_CTX_pageant_keylist; break;
1616               case 101: topic = WINHELP_CTX_pageant_addkey; break;
1617               case 102: topic = WINHELP_CTX_pageant_remkey; break;
1618             }
1619             if (topic) {
1620                 launch_help(hwnd, topic);
1621             } else {
1622                 MessageBeep(0);
1623             }
1624         }
1625         break;
1626       case WM_CLOSE:
1627         keylist = NULL;
1628         DestroyWindow(hwnd);
1629         return 0;
1630     }
1631     return 0;
1632 }
1633
1634 /* Set up a system tray icon */
1635 static BOOL AddTrayIcon(HWND hwnd)
1636 {
1637     BOOL res;
1638     NOTIFYICONDATA tnid;
1639     HICON hicon;
1640
1641 #ifdef NIM_SETVERSION
1642     tnid.uVersion = 0;
1643     res = Shell_NotifyIcon(NIM_SETVERSION, &tnid);
1644 #endif
1645
1646     tnid.cbSize = sizeof(NOTIFYICONDATA);
1647     tnid.hWnd = hwnd;
1648     tnid.uID = 1;              /* unique within this systray use */
1649     tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
1650     tnid.uCallbackMessage = WM_SYSTRAY;
1651     tnid.hIcon = hicon = LoadIcon(hinst, MAKEINTRESOURCE(201));
1652     strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
1653
1654     res = Shell_NotifyIcon(NIM_ADD, &tnid);
1655
1656     if (hicon) DestroyIcon(hicon);
1657     
1658     return res;
1659 }
1660
1661 /* Update the saved-sessions menu. */
1662 static void update_sessions(void)
1663 {
1664     int num_entries;
1665     HKEY hkey;
1666     TCHAR buf[MAX_PATH + 1];
1667     MENUITEMINFO mii;
1668
1669     int index_key, index_menu;
1670
1671     if (!putty_path)
1672         return;
1673
1674     if(ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, PUTTY_REGKEY, &hkey))
1675         return;
1676
1677     for(num_entries = GetMenuItemCount(session_menu);
1678         num_entries > initial_menuitems_count;
1679         num_entries--)
1680         RemoveMenu(session_menu, 0, MF_BYPOSITION);
1681
1682     index_key = 0;
1683     index_menu = 0;
1684
1685     while(ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) {
1686         TCHAR session_name[MAX_PATH + 1];
1687         unmungestr(buf, session_name, MAX_PATH);
1688         if(strcmp(buf, PUTTY_DEFAULT) != 0) {
1689             memset(&mii, 0, sizeof(mii));
1690             mii.cbSize = sizeof(mii);
1691             mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
1692             mii.fType = MFT_STRING;
1693             mii.fState = MFS_ENABLED;
1694             mii.wID = (index_menu * 16) + IDM_SESSIONS_BASE;
1695             mii.dwTypeData = session_name;
1696             InsertMenuItem(session_menu, index_menu, TRUE, &mii);
1697             index_menu++;
1698         }
1699         index_key++;
1700     }
1701
1702     RegCloseKey(hkey);
1703
1704     if(index_menu == 0) {
1705         mii.cbSize = sizeof(mii);
1706         mii.fMask = MIIM_TYPE | MIIM_STATE;
1707         mii.fType = MFT_STRING;
1708         mii.fState = MFS_GRAYED;
1709         mii.dwTypeData = _T("(No sessions)");
1710         InsertMenuItem(session_menu, index_menu, TRUE, &mii);
1711     }
1712 }
1713
1714 #ifndef NO_SECURITY
1715 /*
1716  * Versions of Pageant prior to 0.61 expected this SID on incoming
1717  * communications. For backwards compatibility, and more particularly
1718  * for compatibility with derived works of PuTTY still using the old
1719  * Pageant client code, we accept it as an alternative to the one
1720  * returned from get_user_sid() in winpgntc.c.
1721  */
1722 PSID get_default_sid(void)
1723 {
1724     HANDLE proc = NULL;
1725     DWORD sidlen;
1726     PSECURITY_DESCRIPTOR psd = NULL;
1727     PSID sid = NULL, copy = NULL, ret = NULL;
1728
1729     if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE,
1730                             GetCurrentProcessId())) == NULL)
1731         goto cleanup;
1732
1733     if (p_GetSecurityInfo(proc, SE_KERNEL_OBJECT, OWNER_SECURITY_INFORMATION,
1734                           &sid, NULL, NULL, NULL, &psd) != ERROR_SUCCESS)
1735         goto cleanup;
1736
1737     sidlen = GetLengthSid(sid);
1738
1739     copy = (PSID)smalloc(sidlen);
1740
1741     if (!CopySid(sidlen, copy, sid))
1742         goto cleanup;
1743
1744     /* Success. Move sid into the return value slot, and null it out
1745      * to stop the cleanup code freeing it. */
1746     ret = copy;
1747     copy = NULL;
1748
1749   cleanup:
1750     if (proc != NULL)
1751         CloseHandle(proc);
1752     if (psd != NULL)
1753         LocalFree(psd);
1754     if (copy != NULL)
1755         sfree(copy);
1756
1757     return ret;
1758 }
1759 #endif
1760
1761 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1762                                 WPARAM wParam, LPARAM lParam)
1763 {
1764     int ret;
1765     static int menuinprogress;
1766     static UINT msgTaskbarCreated = 0;
1767
1768     switch (message) {
1769       case WM_CREATE:
1770         msgTaskbarCreated = RegisterWindowMessage(_T("TaskbarCreated"));
1771         break;
1772       default:
1773         if (message==msgTaskbarCreated) {
1774             /*
1775              * Explorer has been restarted, so the tray icon will
1776              * have been lost.
1777              */
1778             AddTrayIcon(hwnd);
1779         }
1780         break;
1781         
1782       case WM_SYSTRAY:
1783         if (lParam == WM_RBUTTONUP) {
1784             POINT cursorpos;
1785             GetCursorPos(&cursorpos);
1786             PostMessage(hwnd, WM_SYSTRAY2, cursorpos.x, cursorpos.y);
1787         } else if (lParam == WM_LBUTTONDBLCLK) {
1788             /* Run the default menu item. */
1789             UINT menuitem = GetMenuDefaultItem(systray_menu, FALSE, 0);
1790             if (menuitem != -1)
1791                 PostMessage(hwnd, WM_COMMAND, menuitem, 0);
1792         }
1793         break;
1794       case WM_SYSTRAY2:
1795         if (!menuinprogress) {
1796             menuinprogress = 1;
1797             update_sessions();
1798             SetForegroundWindow(hwnd);
1799             ret = TrackPopupMenu(systray_menu,
1800                                  TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
1801                                  TPM_RIGHTBUTTON,
1802                                  wParam, lParam, 0, hwnd, NULL);
1803             menuinprogress = 0;
1804         }
1805         break;
1806       case WM_COMMAND:
1807       case WM_SYSCOMMAND:
1808         switch (wParam & ~0xF) {       /* low 4 bits reserved to Windows */
1809           case IDM_PUTTY:
1810             if((int)ShellExecute(hwnd, NULL, putty_path, _T(""), _T(""),
1811                                  SW_SHOW) <= 32) {
1812                 MessageBox(NULL, "Unable to execute PuTTY!",
1813                            "Error", MB_OK | MB_ICONERROR);
1814             }
1815             break;
1816           case IDM_CLOSE:
1817             if (passphrase_box)
1818                 SendMessage(passphrase_box, WM_CLOSE, 0, 0);
1819             SendMessage(hwnd, WM_CLOSE, 0, 0);
1820             break;
1821           case IDM_VIEWKEYS:
1822             if (!keylist) {
1823                 keylist = CreateDialog(hinst, MAKEINTRESOURCE(211),
1824                                        NULL, KeyListProc);
1825                 ShowWindow(keylist, SW_SHOWNORMAL);
1826             }
1827             /* 
1828              * Sometimes the window comes up minimised / hidden for
1829              * no obvious reason. Prevent this. This also brings it
1830              * to the front if it's already present (the user
1831              * selected View Keys because they wanted to _see_ the
1832              * thing).
1833              */
1834             SetForegroundWindow(keylist);
1835             SetWindowPos(keylist, HWND_TOP, 0, 0, 0, 0,
1836                          SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1837             break;
1838           case IDM_ADDKEY:
1839             if (passphrase_box) {
1840                 MessageBeep(MB_ICONERROR);
1841                 SetForegroundWindow(passphrase_box);
1842                 break;
1843             }
1844             prompt_add_keyfile();
1845             break;
1846           case IDM_ABOUT:
1847             if (!aboutbox) {
1848                 aboutbox = CreateDialog(hinst, MAKEINTRESOURCE(213),
1849                                         NULL, AboutProc);
1850                 ShowWindow(aboutbox, SW_SHOWNORMAL);
1851                 /* 
1852                  * Sometimes the window comes up minimised / hidden
1853                  * for no obvious reason. Prevent this.
1854                  */
1855                 SetForegroundWindow(aboutbox);
1856                 SetWindowPos(aboutbox, HWND_TOP, 0, 0, 0, 0,
1857                              SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1858             }
1859             break;
1860           case IDM_HELP:
1861             launch_help(hwnd, WINHELP_CTX_pageant_general);
1862             break;
1863           default:
1864             {
1865                 if(wParam >= IDM_SESSIONS_BASE && wParam <= IDM_SESSIONS_MAX) {
1866                     MENUITEMINFO mii;
1867                     TCHAR buf[MAX_PATH + 1];
1868                     TCHAR param[MAX_PATH + 1];
1869                     memset(&mii, 0, sizeof(mii));
1870                     mii.cbSize = sizeof(mii);
1871                     mii.fMask = MIIM_TYPE;
1872                     mii.cch = MAX_PATH;
1873                     mii.dwTypeData = buf;
1874                     GetMenuItemInfo(session_menu, wParam, FALSE, &mii);
1875                     strcpy(param, "@");
1876                     strcat(param, mii.dwTypeData);
1877                     if((int)ShellExecute(hwnd, NULL, putty_path, param,
1878                                          _T(""), SW_SHOW) <= 32) {
1879                         MessageBox(NULL, "Unable to execute PuTTY!", "Error",
1880                                    MB_OK | MB_ICONERROR);
1881                     }
1882                 }
1883             }
1884             break;
1885         }
1886         break;
1887       case WM_DESTROY:
1888         quit_help(hwnd);
1889         PostQuitMessage(0);
1890         return 0;
1891       case WM_COPYDATA:
1892         {
1893             COPYDATASTRUCT *cds;
1894             char *mapname;
1895             void *p;
1896             HANDLE filemap;
1897 #ifndef NO_SECURITY
1898             PSID mapowner, ourself, ourself2;
1899 #endif
1900             PSECURITY_DESCRIPTOR psd = NULL;
1901             int ret = 0;
1902
1903             cds = (COPYDATASTRUCT *) lParam;
1904             if (cds->dwData != AGENT_COPYDATA_ID)
1905                 return 0;              /* not our message, mate */
1906             mapname = (char *) cds->lpData;
1907             if (mapname[cds->cbData - 1] != '\0')
1908                 return 0;              /* failure to be ASCIZ! */
1909 #ifdef DEBUG_IPC
1910             debug(("mapname is :%s:\n", mapname));
1911 #endif
1912             filemap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, mapname);
1913 #ifdef DEBUG_IPC
1914             debug(("filemap is %p\n", filemap));
1915 #endif
1916             if (filemap != NULL && filemap != INVALID_HANDLE_VALUE) {
1917 #ifndef NO_SECURITY
1918                 int rc;
1919                 if (has_security) {
1920                     if ((ourself = get_user_sid()) == NULL) {
1921 #ifdef DEBUG_IPC
1922                         debug(("couldn't get user SID\n"));
1923 #endif
1924                         CloseHandle(filemap);
1925                         return 0;
1926                     }
1927
1928                     if ((ourself2 = get_default_sid()) == NULL) {
1929 #ifdef DEBUG_IPC
1930                         debug(("couldn't get default SID\n"));
1931 #endif
1932                         CloseHandle(filemap);
1933                         sfree(ourself);
1934                         return 0;
1935                     }
1936
1937                     if ((rc = p_GetSecurityInfo(filemap, SE_KERNEL_OBJECT,
1938                                                 OWNER_SECURITY_INFORMATION,
1939                                                 &mapowner, NULL, NULL, NULL,
1940                                                 &psd) != ERROR_SUCCESS)) {
1941 #ifdef DEBUG_IPC
1942                         debug(("couldn't get owner info for filemap: %d\n",
1943                                rc));
1944 #endif
1945                         CloseHandle(filemap);
1946                         sfree(ourself);
1947                         sfree(ourself2);
1948                         return 0;
1949                     }
1950 #ifdef DEBUG_IPC
1951                     {
1952                         LPTSTR ours, ours2, theirs;
1953                         ConvertSidToStringSid(mapowner, &theirs);
1954                         ConvertSidToStringSid(ourself, &ours);
1955                         ConvertSidToStringSid(ourself2, &ours2);
1956                         debug(("got sids:\n  oursnew=%s\n  oursold=%s\n"
1957                                "  theirs=%s\n", ours, ours2, theirs));
1958                         LocalFree(ours);
1959                         LocalFree(ours2);
1960                         LocalFree(theirs);
1961                     }
1962 #endif
1963                     if (!EqualSid(mapowner, ourself) &&
1964                         !EqualSid(mapowner, ourself2)) {
1965                         CloseHandle(filemap);
1966                         LocalFree(psd);
1967                         sfree(ourself);
1968                         sfree(ourself2);
1969                         return 0;      /* security ID mismatch! */
1970                     }
1971 #ifdef DEBUG_IPC
1972                     debug(("security stuff matched\n"));
1973 #endif
1974                     LocalFree(psd);
1975                     sfree(ourself);
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 (!init_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 }