]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - windows/winpgnt.c
Put proper logging into Pageant.
[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 #include "pageant.h"
19
20 #include <shellapi.h>
21
22 #ifndef NO_SECURITY
23 #include <aclapi.h>
24 #ifdef DEBUG_IPC
25 #define _WIN32_WINNT 0x0500            /* for ConvertSidToStringSid */
26 #include <sddl.h>
27 #endif
28 #endif
29
30 #define IDI_MAINICON 200
31 #define IDI_TRAYICON 201
32
33 #define WM_SYSTRAY   (WM_APP + 6)
34 #define WM_SYSTRAY2  (WM_APP + 7)
35
36 #define AGENT_COPYDATA_ID 0x804e50ba   /* random goop */
37
38 /* From MSDN: In the WM_SYSCOMMAND message, the four low-order bits of
39  * wParam are used by Windows, and should be masked off, so we shouldn't
40  * attempt to store information in them. Hence all these identifiers have
41  * the low 4 bits clear. Also, identifiers should < 0xF000. */
42
43 #define IDM_CLOSE    0x0010
44 #define IDM_VIEWKEYS 0x0020
45 #define IDM_ADDKEY   0x0030
46 #define IDM_HELP     0x0040
47 #define IDM_ABOUT    0x0050
48
49 #define APPNAME "Pageant"
50
51 extern char ver[];
52
53 static HWND keylist;
54 static HWND aboutbox;
55 static HMENU systray_menu, session_menu;
56 static int already_running;
57
58 static char *putty_path;
59
60 /* CWD for "add key" file requester. */
61 static filereq *keypath = NULL;
62
63 #define IDM_PUTTY         0x0060
64 #define IDM_SESSIONS_BASE 0x1000
65 #define IDM_SESSIONS_MAX  0x2000
66 #define PUTTY_REGKEY      "Software\\SimonTatham\\PuTTY\\Sessions"
67 #define PUTTY_DEFAULT     "Default%20Settings"
68 static int initial_menuitems_count;
69
70 /*
71  * Print a modal (Really Bad) message box and perform a fatal exit.
72  */
73 void modalfatalbox(char *fmt, ...)
74 {
75     va_list ap;
76     char *buf;
77
78     va_start(ap, fmt);
79     buf = dupvprintf(fmt, ap);
80     va_end(ap);
81     MessageBox(hwnd, buf, "Pageant Fatal Error",
82                MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
83     sfree(buf);
84     exit(1);
85 }
86
87 /* Un-munge session names out of the registry. */
88 static void unmungestr(char *in, char *out, int outlen)
89 {
90     while (*in) {
91         if (*in == '%' && in[1] && in[2]) {
92             int i, j;
93
94             i = in[1] - '0';
95             i -= (i > 9 ? 7 : 0);
96             j = in[2] - '0';
97             j -= (j > 9 ? 7 : 0);
98
99             *out++ = (i << 4) + j;
100             if (!--outlen)
101                 return;
102             in += 3;
103         } else {
104             *out++ = *in++;
105             if (!--outlen)
106                 return;
107         }
108     }
109     *out = '\0';
110     return;
111 }
112
113 static int has_security;
114
115 /*
116  * Forward references
117  */
118 static void *get_keylist1(int *length);
119 static void *get_keylist2(int *length);
120
121 struct PassphraseProcStruct {
122     char **passphrase;
123     char *comment;
124 };
125
126 static tree234 *passphrases = NULL;
127
128 /* 
129  * After processing a list of filenames, we want to forget the
130  * passphrases.
131  */
132 static void forget_passphrases(void)
133 {
134     while (count234(passphrases) > 0) {
135         char *pp = index234(passphrases, 0);
136         smemclr(pp, strlen(pp));
137         delpos234(passphrases, 0);
138         free(pp);
139     }
140 }
141
142 /*
143  * Dialog-box function for the Licence box.
144  */
145 static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
146                                 WPARAM wParam, LPARAM lParam)
147 {
148     switch (msg) {
149       case WM_INITDIALOG:
150         return 1;
151       case WM_COMMAND:
152         switch (LOWORD(wParam)) {
153           case IDOK:
154           case IDCANCEL:
155             EndDialog(hwnd, 1);
156             return 0;
157         }
158         return 0;
159       case WM_CLOSE:
160         EndDialog(hwnd, 1);
161         return 0;
162     }
163     return 0;
164 }
165
166 /*
167  * Dialog-box function for the About box.
168  */
169 static int CALLBACK AboutProc(HWND hwnd, UINT msg,
170                               WPARAM wParam, LPARAM lParam)
171 {
172     switch (msg) {
173       case WM_INITDIALOG:
174         SetDlgItemText(hwnd, 100, ver);
175         return 1;
176       case WM_COMMAND:
177         switch (LOWORD(wParam)) {
178           case IDOK:
179           case IDCANCEL:
180             aboutbox = NULL;
181             DestroyWindow(hwnd);
182             return 0;
183           case 101:
184             EnableWindow(hwnd, 0);
185             DialogBox(hinst, MAKEINTRESOURCE(214), hwnd, LicenceProc);
186             EnableWindow(hwnd, 1);
187             SetActiveWindow(hwnd);
188             return 0;
189         }
190         return 0;
191       case WM_CLOSE:
192         aboutbox = NULL;
193         DestroyWindow(hwnd);
194         return 0;
195     }
196     return 0;
197 }
198
199 static HWND passphrase_box;
200
201 /*
202  * Dialog-box function for the passphrase box.
203  */
204 static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
205                                    WPARAM wParam, LPARAM lParam)
206 {
207     static char **passphrase = NULL;
208     struct PassphraseProcStruct *p;
209
210     switch (msg) {
211       case WM_INITDIALOG:
212         passphrase_box = hwnd;
213         /*
214          * Centre the window.
215          */
216         {                              /* centre the window */
217             RECT rs, rd;
218             HWND hw;
219
220             hw = GetDesktopWindow();
221             if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
222                 MoveWindow(hwnd,
223                            (rs.right + rs.left + rd.left - rd.right) / 2,
224                            (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
225                            rd.right - rd.left, rd.bottom - rd.top, TRUE);
226         }
227
228         SetForegroundWindow(hwnd);
229         SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
230                      SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
231         p = (struct PassphraseProcStruct *) lParam;
232         passphrase = p->passphrase;
233         if (p->comment)
234             SetDlgItemText(hwnd, 101, p->comment);
235         burnstr(*passphrase);
236         *passphrase = dupstr("");
237         SetDlgItemText(hwnd, 102, *passphrase);
238         return 0;
239       case WM_COMMAND:
240         switch (LOWORD(wParam)) {
241           case IDOK:
242             if (*passphrase)
243                 EndDialog(hwnd, 1);
244             else
245                 MessageBeep(0);
246             return 0;
247           case IDCANCEL:
248             EndDialog(hwnd, 0);
249             return 0;
250           case 102:                    /* edit box */
251             if ((HIWORD(wParam) == EN_CHANGE) && passphrase) {
252                 burnstr(*passphrase);
253                 *passphrase = GetDlgItemText_alloc(hwnd, 102);
254             }
255             return 0;
256         }
257         return 0;
258       case WM_CLOSE:
259         EndDialog(hwnd, 0);
260         return 0;
261     }
262     return 0;
263 }
264
265 /*
266  * Warn about the obsolescent key file format.
267  */
268 void old_keyfile_warning(void)
269 {
270     static const char mbtitle[] = "PuTTY Key File Warning";
271     static const char message[] =
272         "You are loading an SSH-2 private key which has an\n"
273         "old version of the file format. This means your key\n"
274         "file is not fully tamperproof. Future versions of\n"
275         "PuTTY may stop supporting this private key format,\n"
276         "so we recommend you convert your key to the new\n"
277         "format.\n"
278         "\n"
279         "You can perform this conversion by loading the key\n"
280         "into PuTTYgen and then saving it again.";
281
282     MessageBox(NULL, message, mbtitle, MB_OK);
283 }
284
285 /*
286  * Update the visible key list.
287  */
288 void keylist_update(void)
289 {
290     struct RSAKey *rkey;
291     struct ssh2_userkey *skey;
292     int i;
293
294     if (keylist) {
295         SendDlgItemMessage(keylist, 100, LB_RESETCONTENT, 0, 0);
296         for (i = 0; NULL != (rkey = pageant_nth_ssh1_key(i)); i++) {
297             char listentry[512], *p;
298             /*
299              * Replace two spaces in the fingerprint with tabs, for
300              * nice alignment in the box.
301              */
302             strcpy(listentry, "ssh1\t");
303             p = listentry + strlen(listentry);
304             rsa_fingerprint(p, sizeof(listentry) - (p - listentry), rkey);
305             p = strchr(listentry, ' ');
306             if (p)
307                 *p = '\t';
308             p = strchr(listentry, ' ');
309             if (p)
310                 *p = '\t';
311             SendDlgItemMessage(keylist, 100, LB_ADDSTRING,
312                                0, (LPARAM) listentry);
313         }
314         for (i = 0; NULL != (skey = pageant_nth_ssh2_key(i)); i++) {
315             char *listentry, *p;
316             int pos, fp_len;
317             /*
318              * Replace spaces with tabs in the fingerprint prefix, for
319              * nice alignment in the list box, until we encounter a :
320              * meaning we're into the fingerprint proper.
321              */
322             p = skey->alg->fingerprint(skey->data);
323             listentry = dupprintf("%s\t%s", p, skey->comment);
324             fp_len = strlen(listentry);
325             sfree(p);
326
327             pos = 0;
328             while (1) {
329                 pos += strcspn(listentry + pos, " :");
330                 if (listentry[pos] == ':')
331                     break;
332                 listentry[pos++] = '\t';
333             }
334
335             SendDlgItemMessage(keylist, 100, LB_ADDSTRING, 0,
336                                (LPARAM) listentry);
337             sfree(listentry);
338         }
339         SendDlgItemMessage(keylist, 100, LB_SETCURSEL, (WPARAM) - 1, 0);
340     }
341 }
342
343 /*
344  * This function loads a key from a file and adds it.
345  */
346 static void add_keyfile(Filename *filename)
347 {
348     char *passphrase;
349     struct RSAKey *rkey = NULL;
350     struct ssh2_userkey *skey = NULL;
351     int needs_pass;
352     int ret;
353     int attempts;
354     char *comment;
355     const char *error = NULL;
356     int type;
357     int original_pass;
358         
359     type = key_type(filename);
360     if (type != SSH_KEYTYPE_SSH1 && type != SSH_KEYTYPE_SSH2) {
361         char *msg = dupprintf("Couldn't load this key (%s)",
362                               key_type_to_str(type));
363         message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
364                     HELPCTXID(errors_cantloadkey));
365         sfree(msg);
366         return;
367     }
368
369     /*
370      * See if the key is already loaded (in the primary Pageant,
371      * which may or may not be us).
372      */
373     {
374         void *blob;
375         unsigned char *keylist, *p;
376         int i, nkeys, bloblen, keylistlen;
377
378         if (type == SSH_KEYTYPE_SSH1) {
379             if (!rsakey_pubblob(filename, &blob, &bloblen, NULL, &error)) {
380                 char *msg = dupprintf("Couldn't load private key (%s)", error);
381                 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
382                             HELPCTXID(errors_cantloadkey));
383                 sfree(msg);
384                 return;
385             }
386             keylist = get_keylist1(&keylistlen);
387         } else {
388             unsigned char *blob2;
389             blob = ssh2_userkey_loadpub(filename, NULL, &bloblen,
390                                         NULL, &error);
391             if (!blob) {
392                 char *msg = dupprintf("Couldn't load private key (%s)", error);
393                 message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
394                             HELPCTXID(errors_cantloadkey));
395                 sfree(msg);
396                 return;
397             }
398             /* For our purposes we want the blob prefixed with its length */
399             blob2 = snewn(bloblen+4, unsigned char);
400             PUT_32BIT(blob2, bloblen);
401             memcpy(blob2 + 4, blob, bloblen);
402             sfree(blob);
403             blob = blob2;
404
405             keylist = get_keylist2(&keylistlen);
406         }
407         if (keylist) {
408             if (keylistlen < 4) {
409                 MessageBox(NULL, "Received broken key list?!", APPNAME,
410                            MB_OK | MB_ICONERROR);
411                 return;
412             }
413             nkeys = toint(GET_32BIT(keylist));
414             if (nkeys < 0) {
415                 MessageBox(NULL, "Received broken key list?!", APPNAME,
416                            MB_OK | MB_ICONERROR);
417                 return;
418             }
419             p = keylist + 4;
420             keylistlen -= 4;
421
422             for (i = 0; i < nkeys; i++) {
423                 if (!memcmp(blob, p, bloblen)) {
424                     /* Key is already present; we can now leave. */
425                     sfree(keylist);
426                     sfree(blob);
427                     return;
428                 }
429                 /* Now skip over public blob */
430                 if (type == SSH_KEYTYPE_SSH1) {
431                     int n = rsa_public_blob_len(p, keylistlen);
432                     if (n < 0) {
433                         MessageBox(NULL, "Received broken key list?!", APPNAME,
434                                    MB_OK | MB_ICONERROR);
435                         return;
436                     }
437                     p += n;
438                     keylistlen -= n;
439                 } else {
440                     int n;
441                     if (keylistlen < 4) {
442                         MessageBox(NULL, "Received broken key list?!", APPNAME,
443                                    MB_OK | MB_ICONERROR);
444                         return;
445                     }
446                     n = toint(4 + GET_32BIT(p));
447                     if (n < 0 || keylistlen < n) {
448                         MessageBox(NULL, "Received broken key list?!", APPNAME,
449                                    MB_OK | MB_ICONERROR);
450                         return;
451                     }
452                     p += n;
453                     keylistlen -= n;
454                 }
455                 /* Now skip over comment field */
456                 {
457                     int n;
458                     if (keylistlen < 4) {
459                         MessageBox(NULL, "Received broken key list?!", APPNAME,
460                                    MB_OK | MB_ICONERROR);
461                         return;
462                     }
463                     n = toint(4 + GET_32BIT(p));
464                     if (n < 0 || keylistlen < n) {
465                         MessageBox(NULL, "Received broken key list?!", APPNAME,
466                                    MB_OK | MB_ICONERROR);
467                         return;
468                     }
469                     p += n;
470                     keylistlen -= n;
471                 }
472             }
473
474             sfree(keylist);
475         }
476
477         sfree(blob);
478     }
479
480     error = NULL;
481     if (type == SSH_KEYTYPE_SSH1)
482         needs_pass = rsakey_encrypted(filename, &comment);
483     else
484         needs_pass = ssh2_userkey_encrypted(filename, &comment);
485     attempts = 0;
486     if (type == SSH_KEYTYPE_SSH1)
487         rkey = snew(struct RSAKey);
488     passphrase = NULL;
489     original_pass = 0;
490     do {
491         burnstr(passphrase);
492         passphrase = NULL;
493
494         if (needs_pass) {
495             /* try all the remembered passphrases first */
496             char *pp = index234(passphrases, attempts);
497             if(pp) {
498                 passphrase = dupstr(pp);
499             } else {
500                 int dlgret;
501                 struct PassphraseProcStruct pps;
502
503                 pps.passphrase = &passphrase;
504                 pps.comment = comment;
505
506                 original_pass = 1;
507                 dlgret = DialogBoxParam(hinst, MAKEINTRESOURCE(210),
508                                         NULL, PassphraseProc, (LPARAM) &pps);
509                 passphrase_box = NULL;
510                 if (!dlgret) {
511                     if (comment)
512                         sfree(comment);
513                     if (type == SSH_KEYTYPE_SSH1)
514                         sfree(rkey);
515                     return;                    /* operation cancelled */
516                 }
517
518                 assert(passphrase != NULL);
519             }
520         } else
521             passphrase = dupstr("");
522
523         if (type == SSH_KEYTYPE_SSH1)
524             ret = loadrsakey(filename, rkey, passphrase, &error);
525         else {
526             skey = ssh2_load_userkey(filename, passphrase, &error);
527             if (skey == SSH2_WRONG_PASSPHRASE)
528                 ret = -1;
529             else if (!skey)
530                 ret = 0;
531             else
532                 ret = 1;
533         }
534         attempts++;
535     } while (ret == -1);
536
537     if(original_pass && ret) {
538         /* If they typed in an ok passphrase, remember it */
539         addpos234(passphrases, passphrase, 0);
540     } else {
541         /* Otherwise, destroy it */
542         burnstr(passphrase);
543     }
544     passphrase = NULL;
545
546     if (comment)
547         sfree(comment);
548     if (ret == 0) {
549         char *msg = dupprintf("Couldn't load private key (%s)", error);
550         message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
551                     HELPCTXID(errors_cantloadkey));
552         sfree(msg);
553         if (type == SSH_KEYTYPE_SSH1)
554             sfree(rkey);
555         return;
556     }
557     if (type == SSH_KEYTYPE_SSH1) {
558         if (already_running) {
559             unsigned char *request, *response;
560             void *vresponse;
561             int reqlen, clen, resplen, ret;
562
563             clen = strlen(rkey->comment);
564
565             reqlen = 4 + 1 +           /* length, message type */
566                 4 +                    /* bit count */
567                 ssh1_bignum_length(rkey->modulus) +
568                 ssh1_bignum_length(rkey->exponent) +
569                 ssh1_bignum_length(rkey->private_exponent) +
570                 ssh1_bignum_length(rkey->iqmp) +
571                 ssh1_bignum_length(rkey->p) +
572                 ssh1_bignum_length(rkey->q) + 4 + clen  /* comment */
573                 ;
574
575             request = snewn(reqlen, unsigned char);
576
577             request[4] = SSH1_AGENTC_ADD_RSA_IDENTITY;
578             reqlen = 5;
579             PUT_32BIT(request + reqlen, bignum_bitcount(rkey->modulus));
580             reqlen += 4;
581             reqlen += ssh1_write_bignum(request + reqlen, rkey->modulus);
582             reqlen += ssh1_write_bignum(request + reqlen, rkey->exponent);
583             reqlen +=
584                 ssh1_write_bignum(request + reqlen,
585                                   rkey->private_exponent);
586             reqlen += ssh1_write_bignum(request + reqlen, rkey->iqmp);
587             reqlen += ssh1_write_bignum(request + reqlen, rkey->p);
588             reqlen += ssh1_write_bignum(request + reqlen, rkey->q);
589             PUT_32BIT(request + reqlen, clen);
590             memcpy(request + reqlen + 4, rkey->comment, clen);
591             reqlen += 4 + clen;
592             PUT_32BIT(request, reqlen - 4);
593
594             ret = agent_query(request, reqlen, &vresponse, &resplen,
595                               NULL, NULL);
596             assert(ret == 1);
597             response = vresponse;
598             if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
599                 MessageBox(NULL, "The already running Pageant "
600                            "refused to add the key.", APPNAME,
601                            MB_OK | MB_ICONERROR);
602
603             sfree(request);
604             sfree(response);
605         } else {
606             if (!pageant_add_ssh1_key(rkey))
607                 sfree(rkey);           /* already present, don't waste RAM */
608         }
609     } else {
610         if (already_running) {
611             unsigned char *request, *response;
612             void *vresponse;
613             int reqlen, alglen, clen, keybloblen, resplen, ret;
614             alglen = strlen(skey->alg->name);
615             clen = strlen(skey->comment);
616
617             keybloblen = skey->alg->openssh_fmtkey(skey->data, NULL, 0);
618
619             reqlen = 4 + 1 +           /* length, message type */
620                 4 + alglen +           /* algorithm name */
621                 keybloblen +           /* key data */
622                 4 + clen               /* comment */
623                 ;
624
625             request = snewn(reqlen, unsigned char);
626
627             request[4] = SSH2_AGENTC_ADD_IDENTITY;
628             reqlen = 5;
629             PUT_32BIT(request + reqlen, alglen);
630             reqlen += 4;
631             memcpy(request + reqlen, skey->alg->name, alglen);
632             reqlen += alglen;
633             reqlen += skey->alg->openssh_fmtkey(skey->data,
634                                                 request + reqlen,
635                                                 keybloblen);
636             PUT_32BIT(request + reqlen, clen);
637             memcpy(request + reqlen + 4, skey->comment, clen);
638             reqlen += clen + 4;
639             PUT_32BIT(request, reqlen - 4);
640
641             ret = agent_query(request, reqlen, &vresponse, &resplen,
642                               NULL, NULL);
643             assert(ret == 1);
644             response = vresponse;
645             if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
646                 MessageBox(NULL, "The already running Pageant "
647                            "refused to add the key.", APPNAME,
648                            MB_OK | MB_ICONERROR);
649
650             sfree(request);
651             sfree(response);
652         } else {
653             if (!pageant_add_ssh2_key(skey)) {
654                 skey->alg->freekey(skey->data);
655                 sfree(skey);           /* already present, don't waste RAM */
656             }
657         }
658     }
659 }
660
661 /*
662  * Acquire a keylist1 from the primary Pageant; this means either
663  * calling pageant_make_keylist1 (if that's us) or sending a message
664  * to the primary Pageant (if it's not).
665  */
666 static void *get_keylist1(int *length)
667 {
668     void *ret;
669
670     if (already_running) {
671         unsigned char request[5], *response;
672         void *vresponse;
673         int resplen, retval;
674         request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
675         PUT_32BIT(request, 4);
676
677         retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
678         assert(retval == 1);
679         response = vresponse;
680         if (resplen < 5 || response[4] != SSH1_AGENT_RSA_IDENTITIES_ANSWER) {
681             sfree(response);
682             return NULL;
683         }
684
685         ret = snewn(resplen-5, unsigned char);
686         memcpy(ret, response+5, resplen-5);
687         sfree(response);
688
689         if (length)
690             *length = resplen-5;
691     } else {
692         ret = pageant_make_keylist1(length);
693     }
694     return ret;
695 }
696
697 /*
698  * Acquire a keylist2 from the primary Pageant; this means either
699  * calling pageant_make_keylist2 (if that's us) or sending a message
700  * to the primary Pageant (if it's not).
701  */
702 static void *get_keylist2(int *length)
703 {
704     void *ret;
705
706     if (already_running) {
707         unsigned char request[5], *response;
708         void *vresponse;
709         int resplen, retval;
710
711         request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
712         PUT_32BIT(request, 4);
713
714         retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
715         assert(retval == 1);
716         response = vresponse;
717         if (resplen < 5 || response[4] != SSH2_AGENT_IDENTITIES_ANSWER) {
718             sfree(response);
719             return NULL;
720         }
721
722         ret = snewn(resplen-5, unsigned char);
723         memcpy(ret, response+5, resplen-5);
724         sfree(response);
725
726         if (length)
727             *length = resplen-5;
728     } else {
729         ret = pageant_make_keylist2(length);
730     }
731     return ret;
732 }
733
734 static void answer_msg(void *msgv)
735 {
736     unsigned char *msg = (unsigned char *)msgv;
737     unsigned msglen;
738     void *reply;
739     int replylen;
740
741     msglen = GET_32BIT(msg);
742     if (msglen > AGENT_MAX_MSGLEN) {
743         reply = pageant_failure_msg(&replylen);
744     } else {
745         reply = pageant_handle_msg(msg + 4, msglen, &replylen, NULL, NULL);
746         if (replylen > AGENT_MAX_MSGLEN) {
747             smemclr(reply, replylen);
748             sfree(reply);
749             reply = pageant_failure_msg(&replylen);
750         }
751     }
752
753     /*
754      * Windows Pageant answers messages in place, by overwriting the
755      * input message buffer.
756      */
757     memcpy(msg, reply, replylen);
758     smemclr(reply, replylen);
759     sfree(reply);
760 }
761
762 /*
763  * Prompt for a key file to add, and add it.
764  */
765 static void prompt_add_keyfile(void)
766 {
767     OPENFILENAME of;
768     char *filelist = snewn(8192, char);
769         
770     if (!keypath) keypath = filereq_new();
771     memset(&of, 0, sizeof(of));
772     of.hwndOwner = hwnd;
773     of.lpstrFilter = FILTER_KEY_FILES;
774     of.lpstrCustomFilter = NULL;
775     of.nFilterIndex = 1;
776     of.lpstrFile = filelist;
777     *filelist = '\0';
778     of.nMaxFile = 8192;
779     of.lpstrFileTitle = NULL;
780     of.lpstrTitle = "Select Private Key File";
781     of.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER;
782     if (request_file(keypath, &of, TRUE, FALSE)) {
783         if(strlen(filelist) > of.nFileOffset) {
784             /* Only one filename returned? */
785             Filename *fn = filename_from_str(filelist);
786             add_keyfile(fn);
787             filename_free(fn);
788         } else {
789             /* we are returned a bunch of strings, end to
790              * end. first string is the directory, the
791              * rest the filenames. terminated with an
792              * empty string.
793              */
794             char *dir = filelist;
795             char *filewalker = filelist + strlen(dir) + 1;
796             while (*filewalker != '\0') {
797                 char *filename = dupcat(dir, "\\", filewalker, NULL);
798                 Filename *fn = filename_from_str(filename);
799                 add_keyfile(fn);
800                 filename_free(fn);
801                 sfree(filename);
802                 filewalker += strlen(filewalker) + 1;
803             }
804         }
805
806         keylist_update();
807         forget_passphrases();
808     }
809     sfree(filelist);
810 }
811
812 /*
813  * Dialog-box function for the key list box.
814  */
815 static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
816                                 WPARAM wParam, LPARAM lParam)
817 {
818     struct RSAKey *rkey;
819     struct ssh2_userkey *skey;
820
821     switch (msg) {
822       case WM_INITDIALOG:
823         /*
824          * Centre the window.
825          */
826         {                              /* centre the window */
827             RECT rs, rd;
828             HWND hw;
829
830             hw = GetDesktopWindow();
831             if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
832                 MoveWindow(hwnd,
833                            (rs.right + rs.left + rd.left - rd.right) / 2,
834                            (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
835                            rd.right - rd.left, rd.bottom - rd.top, TRUE);
836         }
837
838         if (has_help())
839             SetWindowLongPtr(hwnd, GWL_EXSTYLE,
840                              GetWindowLongPtr(hwnd, GWL_EXSTYLE) |
841                              WS_EX_CONTEXTHELP);
842         else {
843             HWND item = GetDlgItem(hwnd, 103);   /* the Help button */
844             if (item)
845                 DestroyWindow(item);
846         }
847
848         keylist = hwnd;
849         {
850             static int tabs[] = { 35, 75, 250 };
851             SendDlgItemMessage(hwnd, 100, LB_SETTABSTOPS,
852                                sizeof(tabs) / sizeof(*tabs),
853                                (LPARAM) tabs);
854         }
855         keylist_update();
856         return 0;
857       case WM_COMMAND:
858         switch (LOWORD(wParam)) {
859           case IDOK:
860           case IDCANCEL:
861             keylist = NULL;
862             DestroyWindow(hwnd);
863             return 0;
864           case 101:                    /* add key */
865             if (HIWORD(wParam) == BN_CLICKED ||
866                 HIWORD(wParam) == BN_DOUBLECLICKED) {
867                 if (passphrase_box) {
868                     MessageBeep(MB_ICONERROR);
869                     SetForegroundWindow(passphrase_box);
870                     break;
871                 }
872                 prompt_add_keyfile();
873             }
874             return 0;
875           case 102:                    /* remove key */
876             if (HIWORD(wParam) == BN_CLICKED ||
877                 HIWORD(wParam) == BN_DOUBLECLICKED) {
878                 int i;
879                 int rCount, sCount;
880                 int *selectedArray;
881                 
882                 /* our counter within the array of selected items */
883                 int itemNum;
884                 
885                 /* get the number of items selected in the list */
886                 int numSelected = 
887                         SendDlgItemMessage(hwnd, 100, LB_GETSELCOUNT, 0, 0);
888                 
889                 /* none selected? that was silly */
890                 if (numSelected == 0) {
891                     MessageBeep(0);
892                     break;
893                 }
894
895                 /* get item indices in an array */
896                 selectedArray = snewn(numSelected, int);
897                 SendDlgItemMessage(hwnd, 100, LB_GETSELITEMS,
898                                 numSelected, (WPARAM)selectedArray);
899                 
900                 itemNum = numSelected - 1;
901                 rCount = pageant_count_ssh1_keys();
902                 sCount = pageant_count_ssh2_keys();
903                 
904                 /* go through the non-rsakeys until we've covered them all, 
905                  * and/or we're out of selected items to check. note that
906                  * we go *backwards*, to avoid complications from deleting
907                  * things hence altering the offset of subsequent items
908                  */
909                 for (i = sCount - 1; (itemNum >= 0) && (i >= 0); i--) {
910                     skey = pageant_nth_ssh2_key(i);
911                         
912                     if (selectedArray[itemNum] == rCount + i) {
913                         pageant_delete_ssh2_key(skey);
914                         skey->alg->freekey(skey->data);
915                         sfree(skey);
916                         itemNum--;
917                     }
918                 }
919                 
920                 /* do the same for the rsa keys */
921                 for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) {
922                     rkey = pageant_nth_ssh1_key(i);
923
924                     if(selectedArray[itemNum] == i) {
925                         pageant_delete_ssh1_key(rkey);
926                         freersakey(rkey);
927                         sfree(rkey);
928                         itemNum--;
929                     }
930                 }
931
932                 sfree(selectedArray); 
933                 keylist_update();
934             }
935             return 0;
936           case 103:                    /* help */
937             if (HIWORD(wParam) == BN_CLICKED ||
938                 HIWORD(wParam) == BN_DOUBLECLICKED) {
939                 launch_help(hwnd, WINHELP_CTX_pageant_general);
940             }
941             return 0;
942         }
943         return 0;
944       case WM_HELP:
945         {
946             int id = ((LPHELPINFO)lParam)->iCtrlId;
947             char *topic = NULL;
948             switch (id) {
949               case 100: topic = WINHELP_CTX_pageant_keylist; break;
950               case 101: topic = WINHELP_CTX_pageant_addkey; break;
951               case 102: topic = WINHELP_CTX_pageant_remkey; break;
952             }
953             if (topic) {
954                 launch_help(hwnd, topic);
955             } else {
956                 MessageBeep(0);
957             }
958         }
959         break;
960       case WM_CLOSE:
961         keylist = NULL;
962         DestroyWindow(hwnd);
963         return 0;
964     }
965     return 0;
966 }
967
968 /* Set up a system tray icon */
969 static BOOL AddTrayIcon(HWND hwnd)
970 {
971     BOOL res;
972     NOTIFYICONDATA tnid;
973     HICON hicon;
974
975 #ifdef NIM_SETVERSION
976     tnid.uVersion = 0;
977     res = Shell_NotifyIcon(NIM_SETVERSION, &tnid);
978 #endif
979
980     tnid.cbSize = sizeof(NOTIFYICONDATA);
981     tnid.hWnd = hwnd;
982     tnid.uID = 1;              /* unique within this systray use */
983     tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
984     tnid.uCallbackMessage = WM_SYSTRAY;
985     tnid.hIcon = hicon = LoadIcon(hinst, MAKEINTRESOURCE(201));
986     strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
987
988     res = Shell_NotifyIcon(NIM_ADD, &tnid);
989
990     if (hicon) DestroyIcon(hicon);
991     
992     return res;
993 }
994
995 /* Update the saved-sessions menu. */
996 static void update_sessions(void)
997 {
998     int num_entries;
999     HKEY hkey;
1000     TCHAR buf[MAX_PATH + 1];
1001     MENUITEMINFO mii;
1002
1003     int index_key, index_menu;
1004
1005     if (!putty_path)
1006         return;
1007
1008     if(ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, PUTTY_REGKEY, &hkey))
1009         return;
1010
1011     for(num_entries = GetMenuItemCount(session_menu);
1012         num_entries > initial_menuitems_count;
1013         num_entries--)
1014         RemoveMenu(session_menu, 0, MF_BYPOSITION);
1015
1016     index_key = 0;
1017     index_menu = 0;
1018
1019     while(ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) {
1020         TCHAR session_name[MAX_PATH + 1];
1021         unmungestr(buf, session_name, MAX_PATH);
1022         if(strcmp(buf, PUTTY_DEFAULT) != 0) {
1023             memset(&mii, 0, sizeof(mii));
1024             mii.cbSize = sizeof(mii);
1025             mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
1026             mii.fType = MFT_STRING;
1027             mii.fState = MFS_ENABLED;
1028             mii.wID = (index_menu * 16) + IDM_SESSIONS_BASE;
1029             mii.dwTypeData = session_name;
1030             InsertMenuItem(session_menu, index_menu, TRUE, &mii);
1031             index_menu++;
1032         }
1033         index_key++;
1034     }
1035
1036     RegCloseKey(hkey);
1037
1038     if(index_menu == 0) {
1039         mii.cbSize = sizeof(mii);
1040         mii.fMask = MIIM_TYPE | MIIM_STATE;
1041         mii.fType = MFT_STRING;
1042         mii.fState = MFS_GRAYED;
1043         mii.dwTypeData = _T("(No sessions)");
1044         InsertMenuItem(session_menu, index_menu, TRUE, &mii);
1045     }
1046 }
1047
1048 #ifndef NO_SECURITY
1049 /*
1050  * Versions of Pageant prior to 0.61 expected this SID on incoming
1051  * communications. For backwards compatibility, and more particularly
1052  * for compatibility with derived works of PuTTY still using the old
1053  * Pageant client code, we accept it as an alternative to the one
1054  * returned from get_user_sid() in winpgntc.c.
1055  */
1056 PSID get_default_sid(void)
1057 {
1058     HANDLE proc = NULL;
1059     DWORD sidlen;
1060     PSECURITY_DESCRIPTOR psd = NULL;
1061     PSID sid = NULL, copy = NULL, ret = NULL;
1062
1063     if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE,
1064                             GetCurrentProcessId())) == NULL)
1065         goto cleanup;
1066
1067     if (p_GetSecurityInfo(proc, SE_KERNEL_OBJECT, OWNER_SECURITY_INFORMATION,
1068                           &sid, NULL, NULL, NULL, &psd) != ERROR_SUCCESS)
1069         goto cleanup;
1070
1071     sidlen = GetLengthSid(sid);
1072
1073     copy = (PSID)smalloc(sidlen);
1074
1075     if (!CopySid(sidlen, copy, sid))
1076         goto cleanup;
1077
1078     /* Success. Move sid into the return value slot, and null it out
1079      * to stop the cleanup code freeing it. */
1080     ret = copy;
1081     copy = NULL;
1082
1083   cleanup:
1084     if (proc != NULL)
1085         CloseHandle(proc);
1086     if (psd != NULL)
1087         LocalFree(psd);
1088     if (copy != NULL)
1089         sfree(copy);
1090
1091     return ret;
1092 }
1093 #endif
1094
1095 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1096                                 WPARAM wParam, LPARAM lParam)
1097 {
1098     int ret;
1099     static int menuinprogress;
1100     static UINT msgTaskbarCreated = 0;
1101
1102     switch (message) {
1103       case WM_CREATE:
1104         msgTaskbarCreated = RegisterWindowMessage(_T("TaskbarCreated"));
1105         break;
1106       default:
1107         if (message==msgTaskbarCreated) {
1108             /*
1109              * Explorer has been restarted, so the tray icon will
1110              * have been lost.
1111              */
1112             AddTrayIcon(hwnd);
1113         }
1114         break;
1115         
1116       case WM_SYSTRAY:
1117         if (lParam == WM_RBUTTONUP) {
1118             POINT cursorpos;
1119             GetCursorPos(&cursorpos);
1120             PostMessage(hwnd, WM_SYSTRAY2, cursorpos.x, cursorpos.y);
1121         } else if (lParam == WM_LBUTTONDBLCLK) {
1122             /* Run the default menu item. */
1123             UINT menuitem = GetMenuDefaultItem(systray_menu, FALSE, 0);
1124             if (menuitem != -1)
1125                 PostMessage(hwnd, WM_COMMAND, menuitem, 0);
1126         }
1127         break;
1128       case WM_SYSTRAY2:
1129         if (!menuinprogress) {
1130             menuinprogress = 1;
1131             update_sessions();
1132             SetForegroundWindow(hwnd);
1133             ret = TrackPopupMenu(systray_menu,
1134                                  TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
1135                                  TPM_RIGHTBUTTON,
1136                                  wParam, lParam, 0, hwnd, NULL);
1137             menuinprogress = 0;
1138         }
1139         break;
1140       case WM_COMMAND:
1141       case WM_SYSCOMMAND:
1142         switch (wParam & ~0xF) {       /* low 4 bits reserved to Windows */
1143           case IDM_PUTTY:
1144             if((int)ShellExecute(hwnd, NULL, putty_path, _T(""), _T(""),
1145                                  SW_SHOW) <= 32) {
1146                 MessageBox(NULL, "Unable to execute PuTTY!",
1147                            "Error", MB_OK | MB_ICONERROR);
1148             }
1149             break;
1150           case IDM_CLOSE:
1151             if (passphrase_box)
1152                 SendMessage(passphrase_box, WM_CLOSE, 0, 0);
1153             SendMessage(hwnd, WM_CLOSE, 0, 0);
1154             break;
1155           case IDM_VIEWKEYS:
1156             if (!keylist) {
1157                 keylist = CreateDialog(hinst, MAKEINTRESOURCE(211),
1158                                        NULL, KeyListProc);
1159                 ShowWindow(keylist, SW_SHOWNORMAL);
1160             }
1161             /* 
1162              * Sometimes the window comes up minimised / hidden for
1163              * no obvious reason. Prevent this. This also brings it
1164              * to the front if it's already present (the user
1165              * selected View Keys because they wanted to _see_ the
1166              * thing).
1167              */
1168             SetForegroundWindow(keylist);
1169             SetWindowPos(keylist, HWND_TOP, 0, 0, 0, 0,
1170                          SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1171             break;
1172           case IDM_ADDKEY:
1173             if (passphrase_box) {
1174                 MessageBeep(MB_ICONERROR);
1175                 SetForegroundWindow(passphrase_box);
1176                 break;
1177             }
1178             prompt_add_keyfile();
1179             break;
1180           case IDM_ABOUT:
1181             if (!aboutbox) {
1182                 aboutbox = CreateDialog(hinst, MAKEINTRESOURCE(213),
1183                                         NULL, AboutProc);
1184                 ShowWindow(aboutbox, SW_SHOWNORMAL);
1185                 /* 
1186                  * Sometimes the window comes up minimised / hidden
1187                  * for no obvious reason. Prevent this.
1188                  */
1189                 SetForegroundWindow(aboutbox);
1190                 SetWindowPos(aboutbox, HWND_TOP, 0, 0, 0, 0,
1191                              SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
1192             }
1193             break;
1194           case IDM_HELP:
1195             launch_help(hwnd, WINHELP_CTX_pageant_general);
1196             break;
1197           default:
1198             {
1199                 if(wParam >= IDM_SESSIONS_BASE && wParam <= IDM_SESSIONS_MAX) {
1200                     MENUITEMINFO mii;
1201                     TCHAR buf[MAX_PATH + 1];
1202                     TCHAR param[MAX_PATH + 1];
1203                     memset(&mii, 0, sizeof(mii));
1204                     mii.cbSize = sizeof(mii);
1205                     mii.fMask = MIIM_TYPE;
1206                     mii.cch = MAX_PATH;
1207                     mii.dwTypeData = buf;
1208                     GetMenuItemInfo(session_menu, wParam, FALSE, &mii);
1209                     strcpy(param, "@");
1210                     strcat(param, mii.dwTypeData);
1211                     if((int)ShellExecute(hwnd, NULL, putty_path, param,
1212                                          _T(""), SW_SHOW) <= 32) {
1213                         MessageBox(NULL, "Unable to execute PuTTY!", "Error",
1214                                    MB_OK | MB_ICONERROR);
1215                     }
1216                 }
1217             }
1218             break;
1219         }
1220         break;
1221       case WM_DESTROY:
1222         quit_help(hwnd);
1223         PostQuitMessage(0);
1224         return 0;
1225       case WM_COPYDATA:
1226         {
1227             COPYDATASTRUCT *cds;
1228             char *mapname;
1229             void *p;
1230             HANDLE filemap;
1231 #ifndef NO_SECURITY
1232             PSID mapowner, ourself, ourself2;
1233 #endif
1234             PSECURITY_DESCRIPTOR psd = NULL;
1235             int ret = 0;
1236
1237             cds = (COPYDATASTRUCT *) lParam;
1238             if (cds->dwData != AGENT_COPYDATA_ID)
1239                 return 0;              /* not our message, mate */
1240             mapname = (char *) cds->lpData;
1241             if (mapname[cds->cbData - 1] != '\0')
1242                 return 0;              /* failure to be ASCIZ! */
1243 #ifdef DEBUG_IPC
1244             debug(("mapname is :%s:\n", mapname));
1245 #endif
1246             filemap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, mapname);
1247 #ifdef DEBUG_IPC
1248             debug(("filemap is %p\n", filemap));
1249 #endif
1250             if (filemap != NULL && filemap != INVALID_HANDLE_VALUE) {
1251 #ifndef NO_SECURITY
1252                 int rc;
1253                 if (has_security) {
1254                     if ((ourself = get_user_sid()) == NULL) {
1255 #ifdef DEBUG_IPC
1256                         debug(("couldn't get user SID\n"));
1257 #endif
1258                         CloseHandle(filemap);
1259                         return 0;
1260                     }
1261
1262                     if ((ourself2 = get_default_sid()) == NULL) {
1263 #ifdef DEBUG_IPC
1264                         debug(("couldn't get default SID\n"));
1265 #endif
1266                         CloseHandle(filemap);
1267                         sfree(ourself);
1268                         return 0;
1269                     }
1270
1271                     if ((rc = p_GetSecurityInfo(filemap, SE_KERNEL_OBJECT,
1272                                                 OWNER_SECURITY_INFORMATION,
1273                                                 &mapowner, NULL, NULL, NULL,
1274                                                 &psd) != ERROR_SUCCESS)) {
1275 #ifdef DEBUG_IPC
1276                         debug(("couldn't get owner info for filemap: %d\n",
1277                                rc));
1278 #endif
1279                         CloseHandle(filemap);
1280                         sfree(ourself);
1281                         sfree(ourself2);
1282                         return 0;
1283                     }
1284 #ifdef DEBUG_IPC
1285                     {
1286                         LPTSTR ours, ours2, theirs;
1287                         ConvertSidToStringSid(mapowner, &theirs);
1288                         ConvertSidToStringSid(ourself, &ours);
1289                         ConvertSidToStringSid(ourself2, &ours2);
1290                         debug(("got sids:\n  oursnew=%s\n  oursold=%s\n"
1291                                "  theirs=%s\n", ours, ours2, theirs));
1292                         LocalFree(ours);
1293                         LocalFree(ours2);
1294                         LocalFree(theirs);
1295                     }
1296 #endif
1297                     if (!EqualSid(mapowner, ourself) &&
1298                         !EqualSid(mapowner, ourself2)) {
1299                         CloseHandle(filemap);
1300                         LocalFree(psd);
1301                         sfree(ourself);
1302                         sfree(ourself2);
1303                         return 0;      /* security ID mismatch! */
1304                     }
1305 #ifdef DEBUG_IPC
1306                     debug(("security stuff matched\n"));
1307 #endif
1308                     LocalFree(psd);
1309                     sfree(ourself);
1310                     sfree(ourself2);
1311                 } else {
1312 #ifdef DEBUG_IPC
1313                     debug(("security APIs not present\n"));
1314 #endif
1315                 }
1316 #endif
1317                 p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
1318 #ifdef DEBUG_IPC
1319                 debug(("p is %p\n", p));
1320                 {
1321                     int i;
1322                     for (i = 0; i < 5; i++)
1323                         debug(("p[%d]=%02x\n", i,
1324                                ((unsigned char *) p)[i]));
1325                 }
1326 #endif
1327                 answer_msg(p);
1328                 ret = 1;
1329                 UnmapViewOfFile(p);
1330             }
1331             CloseHandle(filemap);
1332             return ret;
1333         }
1334     }
1335
1336     return DefWindowProc(hwnd, message, wParam, lParam);
1337 }
1338
1339 /*
1340  * Fork and Exec the command in cmdline. [DBW]
1341  */
1342 void spawn_cmd(char *cmdline, char * args, int show)
1343 {
1344     if (ShellExecute(NULL, _T("open"), cmdline,
1345                      args, NULL, show) <= (HINSTANCE) 32) {
1346         char *msg;
1347         msg = dupprintf("Failed to run \"%.100s\", Error: %d", cmdline,
1348                         (int)GetLastError());
1349         MessageBox(NULL, msg, APPNAME, MB_OK | MB_ICONEXCLAMATION);
1350         sfree(msg);
1351     }
1352 }
1353
1354 /*
1355  * This is a can't-happen stub, since Pageant never makes
1356  * asynchronous agent requests.
1357  */
1358 void agent_schedule_callback(void (*callback)(void *, void *, int),
1359                              void *callback_ctx, void *data, int len)
1360 {
1361     assert(!"We shouldn't get here");
1362 }
1363
1364 void cleanup_exit(int code)
1365 {
1366     shutdown_help();
1367     exit(code);
1368 }
1369
1370 int flags = FLAG_SYNCAGENT;
1371
1372 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
1373 {
1374     WNDCLASS wndclass;
1375     MSG msg;
1376     char *command = NULL;
1377     int added_keys = 0;
1378     int argc, i;
1379     char **argv, **argstart;
1380
1381     hinst = inst;
1382     hwnd = NULL;
1383
1384     /*
1385      * Determine whether we're an NT system (should have security
1386      * APIs) or a non-NT system (don't do security).
1387      */
1388     if (!init_winver())
1389     {
1390         modalfatalbox("Windows refuses to report a version");
1391     }
1392     if (osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT) {
1393         has_security = TRUE;
1394     } else
1395         has_security = FALSE;
1396
1397     if (has_security) {
1398 #ifndef NO_SECURITY
1399         /*
1400          * Attempt to get the security API we need.
1401          */
1402         if (!got_advapi()) {
1403             MessageBox(NULL,
1404                        "Unable to access security APIs. Pageant will\n"
1405                        "not run, in case it causes a security breach.",
1406                        "Pageant Fatal Error", MB_ICONERROR | MB_OK);
1407             return 1;
1408         }
1409 #else
1410         MessageBox(NULL,
1411                    "This program has been compiled for Win9X and will\n"
1412                    "not run on NT, in case it causes a security breach.",
1413                    "Pageant Fatal Error", MB_ICONERROR | MB_OK);
1414         return 1;
1415 #endif
1416     }
1417
1418     /*
1419      * See if we can find our Help file.
1420      */
1421     init_help();
1422
1423     /*
1424      * Look for the PuTTY binary (we will enable the saved session
1425      * submenu if we find it).
1426      */
1427     {
1428         char b[2048], *p, *q, *r;
1429         FILE *fp;
1430         GetModuleFileName(NULL, b, sizeof(b) - 16);
1431         r = b;
1432         p = strrchr(b, '\\');
1433         if (p && p >= r) r = p+1;
1434         q = strrchr(b, ':');
1435         if (q && q >= r) r = q+1;
1436         strcpy(r, "putty.exe");
1437         if ( (fp = fopen(b, "r")) != NULL) {
1438             putty_path = dupstr(b);
1439             fclose(fp);
1440         } else
1441             putty_path = NULL;
1442     }
1443
1444     /*
1445      * Find out if Pageant is already running.
1446      */
1447     already_running = agent_exists();
1448
1449     /*
1450      * Initialise the cross-platform Pageant code.
1451      */
1452     if (!already_running) {
1453         pageant_init();
1454     }
1455
1456     /*
1457      * Initialise storage for short-term passphrase cache.
1458      */
1459     passphrases = newtree234(NULL);
1460
1461     /*
1462      * Process the command line and add keys as listed on it.
1463      */
1464     split_into_argv(cmdline, &argc, &argv, &argstart);
1465     for (i = 0; i < argc; i++) {
1466         if (!strcmp(argv[i], "-pgpfp")) {
1467             pgp_fingerprints();
1468             return 1;
1469         } else if (!strcmp(argv[i], "-c")) {
1470             /*
1471              * If we see `-c', then the rest of the
1472              * command line should be treated as a
1473              * command to be spawned.
1474              */
1475             if (i < argc-1)
1476                 command = argstart[i+1];
1477             else
1478                 command = "";
1479             break;
1480         } else {
1481             Filename *fn = filename_from_str(argv[i]);
1482             add_keyfile(fn);
1483             filename_free(fn);
1484             added_keys = TRUE;
1485         }
1486     }
1487
1488     /*
1489      * Forget any passphrase that we retained while going over
1490      * command line keyfiles.
1491      */
1492     forget_passphrases();
1493
1494     if (command) {
1495         char *args;
1496         if (command[0] == '"')
1497             args = strchr(++command, '"');
1498         else
1499             args = strchr(command, ' ');
1500         if (args) {
1501             *args++ = 0;
1502             while(*args && isspace(*args)) args++;
1503         }
1504         spawn_cmd(command, args, show);
1505     }
1506
1507     /*
1508      * If Pageant was already running, we leave now. If we haven't
1509      * even taken any auxiliary action (spawned a command or added
1510      * keys), complain.
1511      */
1512     if (already_running) {
1513         if (!command && !added_keys) {
1514             MessageBox(NULL, "Pageant is already running", "Pageant Error",
1515                        MB_ICONERROR | MB_OK);
1516         }
1517         return 0;
1518     }
1519
1520     if (!prev) {
1521         wndclass.style = 0;
1522         wndclass.lpfnWndProc = WndProc;
1523         wndclass.cbClsExtra = 0;
1524         wndclass.cbWndExtra = 0;
1525         wndclass.hInstance = inst;
1526         wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
1527         wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
1528         wndclass.hbrBackground = GetStockObject(BLACK_BRUSH);
1529         wndclass.lpszMenuName = NULL;
1530         wndclass.lpszClassName = APPNAME;
1531
1532         RegisterClass(&wndclass);
1533     }
1534
1535     keylist = NULL;
1536
1537     hwnd = CreateWindow(APPNAME, APPNAME,
1538                         WS_OVERLAPPEDWINDOW | WS_VSCROLL,
1539                         CW_USEDEFAULT, CW_USEDEFAULT,
1540                         100, 100, NULL, NULL, inst, NULL);
1541
1542     /* Set up a system tray icon */
1543     AddTrayIcon(hwnd);
1544
1545     /* Accelerators used: nsvkxa */
1546     systray_menu = CreatePopupMenu();
1547     if (putty_path) {
1548         session_menu = CreateMenu();
1549         AppendMenu(systray_menu, MF_ENABLED, IDM_PUTTY, "&New Session");
1550         AppendMenu(systray_menu, MF_POPUP | MF_ENABLED,
1551                    (UINT) session_menu, "&Saved Sessions");
1552         AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
1553     }
1554     AppendMenu(systray_menu, MF_ENABLED, IDM_VIEWKEYS,
1555            "&View Keys");
1556     AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key");
1557     AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
1558     if (has_help())
1559         AppendMenu(systray_menu, MF_ENABLED, IDM_HELP, "&Help");
1560     AppendMenu(systray_menu, MF_ENABLED, IDM_ABOUT, "&About");
1561     AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
1562     AppendMenu(systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit");
1563     initial_menuitems_count = GetMenuItemCount(session_menu);
1564
1565     /* Set the default menu item. */
1566     SetMenuDefaultItem(systray_menu, IDM_VIEWKEYS, FALSE);
1567
1568     ShowWindow(hwnd, SW_HIDE);
1569
1570     /*
1571      * Main message loop.
1572      */
1573     while (GetMessage(&msg, NULL, 0, 0) == 1) {
1574         if (!(IsWindow(keylist) && IsDialogMessage(keylist, &msg)) &&
1575             !(IsWindow(aboutbox) && IsDialogMessage(aboutbox, &msg))) {
1576             TranslateMessage(&msg);
1577             DispatchMessage(&msg);
1578         }
1579     }
1580
1581     /* Clean up the system tray icon */
1582     {
1583         NOTIFYICONDATA tnid;
1584
1585         tnid.cbSize = sizeof(NOTIFYICONDATA);
1586         tnid.hWnd = hwnd;
1587         tnid.uID = 1;
1588
1589         Shell_NotifyIcon(NIM_DELETE, &tnid);
1590
1591         DestroyMenu(systray_menu);
1592     }
1593
1594     if (keypath) filereq_free(keypath);
1595
1596     cleanup_exit(msg.wParam);
1597     return msg.wParam;                 /* just in case optimiser complains */
1598 }