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