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