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