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