]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - puttygen.c
Implemented export of OpenSSH keys.
[PuTTY.git] / puttygen.c
1 /*
2  * PuTTY key generation front end.
3  */
4
5 #include <windows.h>
6 #include <commctrl.h>
7 #include <time.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10
11 #define PUTTY_DO_GLOBALS
12
13 #include "putty.h"
14 #include "ssh.h"
15 #include "winstuff.h"
16
17 #define WM_DONEKEY (WM_XUSER + 1)
18
19 #define DEFAULT_KEYSIZE 1024
20
21 static int requested_help;
22
23 /* ----------------------------------------------------------------------
24  * Progress report code. This is really horrible :-)
25  */
26 #define PROGRESSRANGE 65535
27 #define MAXPHASE 5
28 struct progress {
29     int nphases;
30     struct {
31         int exponential;
32         unsigned startpoint, total;
33         unsigned param, current, n;    /* if exponential */
34         unsigned mult;                 /* if linear */
35     } phases[MAXPHASE];
36     unsigned total, divisor, range;
37     HWND progbar;
38 };
39
40 static void progress_update(void *param, int action, int phase, int iprogress)
41 {
42     struct progress *p = (struct progress *) param;
43     unsigned progress = iprogress;
44     int position;
45
46     if (action < PROGFN_READY && p->nphases < phase)
47         p->nphases = phase;
48     switch (action) {
49       case PROGFN_INITIALISE:
50         p->nphases = 0;
51         break;
52       case PROGFN_LIN_PHASE:
53         p->phases[phase-1].exponential = 0;
54         p->phases[phase-1].mult = p->phases[phase].total / progress;
55         break;
56       case PROGFN_EXP_PHASE:
57         p->phases[phase-1].exponential = 1;
58         p->phases[phase-1].param = 0x10000 + progress;
59         p->phases[phase-1].current = p->phases[phase-1].total;
60         p->phases[phase-1].n = 0;
61         break;
62       case PROGFN_PHASE_EXTENT:
63         p->phases[phase-1].total = progress;
64         break;
65       case PROGFN_READY:
66         {
67             unsigned total = 0;
68             int i;
69             for (i = 0; i < p->nphases; i++) {
70                 p->phases[i].startpoint = total;
71                 total += p->phases[i].total;
72             }
73             p->total = total;
74             p->divisor = ((p->total + PROGRESSRANGE - 1) / PROGRESSRANGE);
75             p->range = p->total / p->divisor;
76             SendMessage(p->progbar, PBM_SETRANGE, 0, MAKELPARAM(0, p->range));
77         }
78         break;
79       case PROGFN_PROGRESS:
80         if (p->phases[phase-1].exponential) {
81             while (p->phases[phase-1].n < progress) {
82                 p->phases[phase-1].n++;
83                 p->phases[phase-1].current *= p->phases[phase-1].param;
84                 p->phases[phase-1].current /= 0x10000;
85             }
86             position = (p->phases[phase-1].startpoint +
87                         p->phases[phase-1].total - p->phases[phase-1].current);
88         } else {
89             position = (p->phases[phase-1].startpoint +
90                         progress * p->phases[phase-1].mult);
91         }
92         SendMessage(p->progbar, PBM_SETPOS, position / p->divisor, 0);
93         break;
94     }
95 }
96
97 extern char ver[];
98
99 #define PASSPHRASE_MAXLEN 512
100
101 struct PassphraseProcStruct {
102     char *passphrase;
103     char *comment;
104 };
105
106 /*
107  * Dialog-box function for the passphrase box.
108  */
109 static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
110                                    WPARAM wParam, LPARAM lParam)
111 {
112     static char *passphrase = NULL;
113     struct PassphraseProcStruct *p;
114
115     switch (msg) {
116       case WM_INITDIALOG:
117         SetForegroundWindow(hwnd);
118         SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
119                      SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
120
121         /*
122          * Centre the window.
123          */
124         {                              /* centre the window */
125             RECT rs, rd;
126             HWND hw;
127
128             hw = GetDesktopWindow();
129             if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
130                 MoveWindow(hwnd,
131                            (rs.right + rs.left + rd.left - rd.right) / 2,
132                            (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
133                            rd.right - rd.left, rd.bottom - rd.top, TRUE);
134         }
135
136         p = (struct PassphraseProcStruct *) lParam;
137         passphrase = p->passphrase;
138         if (p->comment)
139             SetDlgItemText(hwnd, 101, p->comment);
140         *passphrase = 0;
141         SetDlgItemText(hwnd, 102, passphrase);
142         return 0;
143       case WM_COMMAND:
144         switch (LOWORD(wParam)) {
145           case IDOK:
146             if (*passphrase)
147                 EndDialog(hwnd, 1);
148             else
149                 MessageBeep(0);
150             return 0;
151           case IDCANCEL:
152             EndDialog(hwnd, 0);
153             return 0;
154           case 102:                    /* edit box */
155             if ((HIWORD(wParam) == EN_CHANGE) && passphrase) {
156                 GetDlgItemText(hwnd, 102, passphrase,
157                                PASSPHRASE_MAXLEN - 1);
158                 passphrase[PASSPHRASE_MAXLEN - 1] = '\0';
159             }
160             return 0;
161         }
162         return 0;
163       case WM_CLOSE:
164         EndDialog(hwnd, 0);
165         return 0;
166     }
167     return 0;
168 }
169
170 /*
171  * Prompt for a key file. Assumes the filename buffer is of size
172  * FILENAME_MAX.
173  */
174 static int prompt_keyfile(HWND hwnd, char *dlgtitle,
175                           char *filename, int save)
176 {
177     OPENFILENAME of;
178     memset(&of, 0, sizeof(of));
179 #ifdef OPENFILENAME_SIZE_VERSION_400
180     of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
181 #else
182     of.lStructSize = sizeof(of);
183 #endif
184     of.hwndOwner = hwnd;
185     of.lpstrFilter = "All Files\0*\0\0\0";
186     of.lpstrCustomFilter = NULL;
187     of.nFilterIndex = 1;
188     of.lpstrFile = filename;
189     *filename = '\0';
190     of.nMaxFile = FILENAME_MAX;
191     of.lpstrFileTitle = NULL;
192     of.lpstrInitialDir = NULL;
193     of.lpstrTitle = dlgtitle;
194     of.Flags = 0;
195     if (save)
196         return GetSaveFileName(&of);
197     else
198         return GetOpenFileName(&of);
199 }
200
201 /*
202  * This function is needed to link with the DES code. We need not
203  * have it do anything at all.
204  */
205 void logevent(char *msg)
206 {
207 }
208
209 /*
210  * Dialog-box function for the Licence box.
211  */
212 static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
213                                 WPARAM wParam, LPARAM lParam)
214 {
215     switch (msg) {
216       case WM_INITDIALOG:
217         /*
218          * Centre the window.
219          */
220         {                              /* centre the window */
221             RECT rs, rd;
222             HWND hw;
223
224             hw = GetDesktopWindow();
225             if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
226                 MoveWindow(hwnd,
227                            (rs.right + rs.left + rd.left - rd.right) / 2,
228                            (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
229                            rd.right - rd.left, rd.bottom - rd.top, TRUE);
230         }
231
232         return 1;
233       case WM_COMMAND:
234         switch (LOWORD(wParam)) {
235           case IDOK:
236             EndDialog(hwnd, 1);
237             return 0;
238         }
239         return 0;
240       case WM_CLOSE:
241         EndDialog(hwnd, 1);
242         return 0;
243     }
244     return 0;
245 }
246
247 /*
248  * Dialog-box function for the About box.
249  */
250 static int CALLBACK AboutProc(HWND hwnd, UINT msg,
251                               WPARAM wParam, LPARAM lParam)
252 {
253     switch (msg) {
254       case WM_INITDIALOG:
255         /*
256          * Centre the window.
257          */
258         {                              /* centre the window */
259             RECT rs, rd;
260             HWND hw;
261
262             hw = GetDesktopWindow();
263             if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
264                 MoveWindow(hwnd,
265                            (rs.right + rs.left + rd.left - rd.right) / 2,
266                            (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
267                            rd.right - rd.left, rd.bottom - rd.top, TRUE);
268         }
269
270         SetDlgItemText(hwnd, 100, ver);
271         return 1;
272       case WM_COMMAND:
273         switch (LOWORD(wParam)) {
274           case IDOK:
275             EndDialog(hwnd, 1);
276             return 0;
277           case 101:
278             EnableWindow(hwnd, 0);
279             DialogBox(hinst, MAKEINTRESOURCE(214), NULL, LicenceProc);
280             EnableWindow(hwnd, 1);
281             SetActiveWindow(hwnd);
282             return 0;
283         }
284         return 0;
285       case WM_CLOSE:
286         EndDialog(hwnd, 1);
287         return 0;
288     }
289     return 0;
290 }
291
292 /*
293  * Thread to generate a key.
294  */
295 struct rsa_key_thread_params {
296     HWND progressbar;                  /* notify this with progress */
297     HWND dialog;                       /* notify this on completion */
298     int keysize;                       /* bits in key */
299     int is_dsa;
300     struct RSAKey *key;
301     struct dss_key *dsskey;
302 };
303 static DWORD WINAPI generate_rsa_key_thread(void *param)
304 {
305     struct rsa_key_thread_params *params =
306         (struct rsa_key_thread_params *) param;
307     struct progress prog;
308     prog.progbar = params->progressbar;
309
310     progress_update(&prog, PROGFN_INITIALISE, 0, 0);
311
312     if (params->is_dsa)
313         dsa_generate(params->dsskey, params->keysize, progress_update, &prog);
314     else
315         rsa_generate(params->key, params->keysize, progress_update, &prog);
316
317     PostMessage(params->dialog, WM_DONEKEY, 0, 0);
318
319     sfree(params);
320     return 0;
321 }
322
323 struct MainDlgState {
324     int collecting_entropy;
325     int generation_thread_exists;
326     int key_exists;
327     int entropy_got, entropy_required, entropy_size;
328     int keysize;
329     int ssh2, is_dsa;
330     char **commentptr;                 /* points to key.comment or ssh2key.comment */
331     struct ssh2_userkey ssh2key;
332     unsigned *entropy;
333     struct RSAKey key;
334     struct dss_key dsskey;
335 };
336
337 static void hidemany(HWND hwnd, const int *ids, int hideit)
338 {
339     while (*ids) {
340         ShowWindow(GetDlgItem(hwnd, *ids++), (hideit ? SW_HIDE : SW_SHOW));
341     }
342 }
343
344 static void setupbigedit1(HWND hwnd, int id, int idstatic, struct RSAKey *key)
345 {
346     char *buffer;
347     char *dec1, *dec2;
348
349     dec1 = bignum_decimal(key->exponent);
350     dec2 = bignum_decimal(key->modulus);
351     buffer = smalloc(strlen(dec1) + strlen(dec2) +
352                      strlen(key->comment) + 30);
353     sprintf(buffer, "%d %s %s %s",
354             bignum_bitcount(key->modulus), dec1, dec2, key->comment);
355     SetDlgItemText(hwnd, id, buffer);
356     SetDlgItemText(hwnd, idstatic,
357                    "&Public key for pasting into authorized_keys file:");
358     sfree(dec1);
359     sfree(dec2);
360     sfree(buffer);
361 }
362
363 static void setupbigedit2(HWND hwnd, int id, int idstatic,
364                           struct ssh2_userkey *key)
365 {
366     unsigned char *pub_blob;
367     char *buffer, *p;
368     int pub_len;
369     int i;
370
371     pub_blob = key->alg->public_blob(key->data, &pub_len);
372     buffer = smalloc(strlen(key->alg->name) + 4 * ((pub_len + 2) / 3) +
373                      strlen(key->comment) + 3);
374     strcpy(buffer, key->alg->name);
375     p = buffer + strlen(buffer);
376     *p++ = ' ';
377     i = 0;
378     while (i < pub_len) {
379         int n = (pub_len - i < 3 ? pub_len - i : 3);
380         base64_encode_atom(pub_blob + i, n, p);
381         i += n;
382         p += 4;
383     }
384     *p++ = ' ';
385     strcpy(p, key->comment);
386     SetDlgItemText(hwnd, id, buffer);
387     SetDlgItemText(hwnd, idstatic, "&Public key for pasting into "
388                    "OpenSSH authorized_keys2 file:");
389     sfree(pub_blob);
390     sfree(buffer);
391 }
392
393 static int save_ssh1_pubkey(char *filename, struct RSAKey *key)
394 {
395     char *dec1, *dec2;
396     FILE *fp;
397
398     dec1 = bignum_decimal(key->exponent);
399     dec2 = bignum_decimal(key->modulus);
400     fp = fopen(filename, "wb");
401     if (!fp)
402         return 0;
403     fprintf(fp, "%d %s %s %s\n",
404             bignum_bitcount(key->modulus), dec1, dec2, key->comment);
405     fclose(fp);
406     sfree(dec1);
407     sfree(dec2);
408     return 1;
409 }
410
411 /*
412  * Warn about the obsolescent key file format.
413  */
414 void old_keyfile_warning(void)
415 {
416     static const char mbtitle[] = "PuTTY Key File Warning";
417     static const char message[] =
418         "You are loading an SSH 2 private key which has an\n"
419         "old version of the file format. This means your key\n"
420         "file is not fully tamperproof. Future versions of\n"
421         "PuTTY may stop supporting this private key format,\n"
422         "so we recommend you convert your key to the new\n"
423         "format.\n"
424         "\n"
425         "Once the key is loaded into PuTTYgen, you can perform\n"
426         "this conversion simply by saving it again.";
427
428     MessageBox(NULL, message, mbtitle, MB_OK);
429 }
430
431 static int save_ssh2_pubkey(char *filename, struct ssh2_userkey *key)
432 {
433     unsigned char *pub_blob;
434     char *p;
435     int pub_len;
436     int i, column;
437     FILE *fp;
438
439     pub_blob = key->alg->public_blob(key->data, &pub_len);
440
441     fp = fopen(filename, "wb");
442     if (!fp)
443         return 0;
444
445     fprintf(fp, "---- BEGIN SSH2 PUBLIC KEY ----\n");
446
447     fprintf(fp, "Comment: \"");
448     for (p = key->comment; *p; p++) {
449         if (*p == '\\' || *p == '\"')
450             fputc('\\', fp);
451         fputc(*p, fp);
452     }
453     fprintf(fp, "\"\n");
454
455     i = 0;
456     column = 0;
457     while (i < pub_len) {
458         char buf[5];
459         int n = (pub_len - i < 3 ? pub_len - i : 3);
460         base64_encode_atom(pub_blob + i, n, buf);
461         i += n;
462         buf[4] = '\0';
463         fputs(buf, fp);
464         if (++column >= 16) {
465             fputc('\n', fp);
466             column = 0;
467         }
468     }
469     if (column > 0)
470         fputc('\n', fp);
471     
472     fprintf(fp, "---- END SSH2 PUBLIC KEY ----\n");
473     fclose(fp);
474     sfree(pub_blob);
475     return 1;
476 }
477
478 /*
479  * Dialog-box function for the main PuTTYgen dialog box.
480  */
481 static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
482                                 WPARAM wParam, LPARAM lParam)
483 {
484     enum {
485         controlidstart = 100,
486         IDC_TITLE,
487         IDC_BOX_KEY,
488         IDC_NOKEY,
489         IDC_GENERATING,
490         IDC_PROGRESS,
491         IDC_PKSTATIC, IDC_KEYDISPLAY,
492         IDC_FPSTATIC, IDC_FINGERPRINT,
493         IDC_COMMENTSTATIC, IDC_COMMENTEDIT,
494         IDC_PASSPHRASE1STATIC, IDC_PASSPHRASE1EDIT,
495         IDC_PASSPHRASE2STATIC, IDC_PASSPHRASE2EDIT,
496         IDC_BOX_ACTIONS,
497         IDC_GENSTATIC, IDC_GENERATE,
498         IDC_LOADSTATIC, IDC_LOAD,
499         IDC_SAVESTATIC, IDC_SAVE, IDC_SAVEPUB,
500         IDC_BOX_PARAMS,
501         IDC_TYPESTATIC, IDC_KEYSSH1, IDC_KEYSSH2RSA, IDC_KEYSSH2DSA,
502         IDC_BITSSTATIC, IDC_BITS,
503         IDC_ABOUT,
504         IDC_GIVEHELP,
505         IDC_IMPORT, IDC_EXPORT_OPENSSH, IDC_EXPORT_SSHCOM
506     };
507     static const int nokey_ids[] = { IDC_NOKEY, 0 };
508     static const int generating_ids[] =
509         { IDC_GENERATING, IDC_PROGRESS, 0 };
510     static const int gotkey_ids[] = {
511         IDC_PKSTATIC, IDC_KEYDISPLAY,
512         IDC_FPSTATIC, IDC_FINGERPRINT,
513         IDC_COMMENTSTATIC, IDC_COMMENTEDIT,
514         IDC_PASSPHRASE1STATIC, IDC_PASSPHRASE1EDIT,
515         IDC_PASSPHRASE2STATIC, IDC_PASSPHRASE2EDIT, 0
516     };
517     static const char generating_msg[] =
518         "Please wait while a key is generated...";
519     static const char entropy_msg[] =
520         "Please generate some randomness by moving the mouse over the blank area.";
521     struct MainDlgState *state;
522
523     switch (msg) {
524       case WM_INITDIALOG:
525         if (help_path)
526             SetWindowLong(hwnd, GWL_EXSTYLE,
527                           GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_CONTEXTHELP);
528         else {
529             /*
530              * If we add a Help button, this is where we destroy it
531              * if the help file isn't present.
532              */
533         }
534         requested_help = FALSE;
535
536         {
537             HMENU menu, menu1;
538
539             menu = CreateMenu();
540
541             menu1 = CreateMenu();
542             AppendMenu(menu1, MF_ENABLED, IDC_GENERATE, "&Generate key pair");
543             AppendMenu(menu1, MF_ENABLED, IDC_LOAD, "&Load private key");
544             AppendMenu(menu1, MF_ENABLED, IDC_SAVEPUB, "Save p&ublic key");
545             AppendMenu(menu1, MF_ENABLED, IDC_SAVE, "&Save private key");
546
547             AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT) menu1, "&File");
548
549             menu1 = CreateMenu();
550             AppendMenu(menu1, MF_ENABLED, IDC_EXPORT_OPENSSH,
551                        "Export &OpenSSH key");
552 #if 0
553             AppendMenu(menu1, MF_ENABLED, IDC_EXPORT_SSHCOM,
554                        "Export &ssh.com key");
555 #endif
556
557             AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT) menu1,
558                        "&Export");
559
560             menu1 = CreateMenu();
561             AppendMenu(menu1, MF_ENABLED, IDC_ABOUT, "&About");
562             if (help_path)
563                 AppendMenu(menu1, MF_ENABLED, IDC_GIVEHELP, "&Help");
564
565             AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT) menu1, "&Help");
566
567             SetMenu(hwnd, menu);
568         }
569
570         /*
571          * Centre the window.
572          */
573         {                              /* centre the window */
574             RECT rs, rd;
575             HWND hw;
576
577             hw = GetDesktopWindow();
578             if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
579                 MoveWindow(hwnd,
580                            (rs.right + rs.left + rd.left - rd.right) / 2,
581                            (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
582                            rd.right - rd.left, rd.bottom - rd.top, TRUE);
583         }
584
585         state = smalloc(sizeof(*state));
586         state->generation_thread_exists = FALSE;
587         state->collecting_entropy = FALSE;
588         state->entropy = NULL;
589         state->key_exists = FALSE;
590         SetWindowLong(hwnd, GWL_USERDATA, (LONG) state);
591         {
592             struct ctlpos cp, cp2;
593
594             /* Accelerators used: acglops1rbd */
595
596             ctlposinit(&cp, hwnd, 4, 4, 4);
597             beginbox(&cp, "Key", IDC_BOX_KEY);
598             cp2 = cp;
599             statictext(&cp2, "No key.", 1, IDC_NOKEY);
600             cp2 = cp;
601             statictext(&cp2, "", 1, IDC_GENERATING);
602             progressbar(&cp2, IDC_PROGRESS);
603             bigeditctrl(&cp,
604                         "&Public key for pasting into authorized_keys file:",
605                         IDC_PKSTATIC, IDC_KEYDISPLAY, 5);
606             SendDlgItemMessage(hwnd, IDC_KEYDISPLAY, EM_SETREADONLY, 1, 0);
607             staticedit(&cp, "Key fingerprint:", IDC_FPSTATIC,
608                        IDC_FINGERPRINT, 75);
609             SendDlgItemMessage(hwnd, IDC_FINGERPRINT, EM_SETREADONLY, 1,
610                                0);
611             staticedit(&cp, "Key &comment:", IDC_COMMENTSTATIC,
612                        IDC_COMMENTEDIT, 75);
613             staticpassedit(&cp, "Key p&assphrase:", IDC_PASSPHRASE1STATIC,
614                            IDC_PASSPHRASE1EDIT, 75);
615             staticpassedit(&cp, "C&onfirm passphrase:",
616                            IDC_PASSPHRASE2STATIC, IDC_PASSPHRASE2EDIT, 75);
617             endbox(&cp);
618             beginbox(&cp, "Actions", IDC_BOX_ACTIONS);
619             staticbtn(&cp, "Generate a public/private key pair",
620                       IDC_GENSTATIC, "&Generate", IDC_GENERATE);
621             staticbtn(&cp, "Load an existing private key file",
622                       IDC_LOADSTATIC, "&Load", IDC_LOAD);
623             static2btn(&cp, "Save the generated key", IDC_SAVESTATIC,
624                        "Save p&ublic key", IDC_SAVEPUB,
625                        "&Save private key", IDC_SAVE);
626             endbox(&cp);
627             beginbox(&cp, "Parameters", IDC_BOX_PARAMS);
628             radioline(&cp, "Type of key to generate:", IDC_TYPESTATIC, 3,
629                       "SSH&1 (RSA)", IDC_KEYSSH1,
630                       "SSH2 &RSA", IDC_KEYSSH2RSA,
631                       "SSH2 &DSA", IDC_KEYSSH2DSA, NULL);
632             staticedit(&cp, "Number of &bits in a generated key:",
633                        IDC_BITSSTATIC, IDC_BITS, 20);
634             endbox(&cp);
635         }
636         CheckRadioButton(hwnd, IDC_KEYSSH1, IDC_KEYSSH2DSA, IDC_KEYSSH1);
637         SetDlgItemInt(hwnd, IDC_BITS, DEFAULT_KEYSIZE, FALSE);
638
639         /*
640          * Initially, hide the progress bar and the key display,
641          * and show the no-key display. Also disable the Save
642          * buttons, because with no key we obviously can't save
643          * anything.
644          */
645         hidemany(hwnd, nokey_ids, FALSE);
646         hidemany(hwnd, generating_ids, TRUE);
647         hidemany(hwnd, gotkey_ids, TRUE);
648         EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 0);
649         EnableWindow(GetDlgItem(hwnd, IDC_SAVEPUB), 0);
650
651         return 1;
652       case WM_MOUSEMOVE:
653         state = (struct MainDlgState *) GetWindowLong(hwnd, GWL_USERDATA);
654         if (state->collecting_entropy &&
655             state->entropy && state->entropy_got < state->entropy_required) {
656             state->entropy[state->entropy_got++] = lParam;
657             state->entropy[state->entropy_got++] = GetMessageTime();
658             SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS,
659                                state->entropy_got, 0);
660             if (state->entropy_got >= state->entropy_required) {
661                 struct rsa_key_thread_params *params;
662                 DWORD threadid;
663
664                 /*
665                  * Seed the entropy pool
666                  */
667                 random_add_heavynoise(state->entropy, state->entropy_size);
668                 memset(state->entropy, 0, state->entropy_size);
669                 sfree(state->entropy);
670                 state->collecting_entropy = FALSE;
671
672                 SetDlgItemText(hwnd, IDC_GENERATING, generating_msg);
673                 SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETRANGE, 0,
674                                    MAKELPARAM(0, PROGRESSRANGE));
675                 SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, 0, 0);
676
677                 params = smalloc(sizeof(*params));
678                 params->progressbar = GetDlgItem(hwnd, IDC_PROGRESS);
679                 params->dialog = hwnd;
680                 params->keysize = state->keysize;
681                 params->is_dsa = state->is_dsa;
682                 params->key = &state->key;
683                 params->dsskey = &state->dsskey;
684
685                 if (!CreateThread(NULL, 0, generate_rsa_key_thread,
686                                   params, 0, &threadid)) {
687                     MessageBox(hwnd, "Out of thread resources",
688                                "Key generation error",
689                                MB_OK | MB_ICONERROR);
690                     sfree(params);
691                 } else {
692                     state->generation_thread_exists = TRUE;
693                 }
694             }
695         }
696         break;
697       case WM_COMMAND:
698         switch (LOWORD(wParam)) {
699           case IDC_COMMENTEDIT:
700             if (HIWORD(wParam) == EN_CHANGE) {
701                 state = (struct MainDlgState *)
702                     GetWindowLong(hwnd, GWL_USERDATA);
703                 if (state->key_exists) {
704                     HWND editctl = GetDlgItem(hwnd, IDC_COMMENTEDIT);
705                     int len = GetWindowTextLength(editctl);
706                     if (*state->commentptr)
707                         sfree(*state->commentptr);
708                     *state->commentptr = smalloc(len + 1);
709                     GetWindowText(editctl, *state->commentptr, len + 1);
710                     if (state->ssh2) {
711                         setupbigedit2(hwnd, IDC_KEYDISPLAY, IDC_PKSTATIC,
712                                       &state->ssh2key);
713                     } else {
714                         setupbigedit1(hwnd, IDC_KEYDISPLAY, IDC_PKSTATIC,
715                                       &state->key);
716                     }
717                 }
718             }
719             break;
720           case IDC_ABOUT:
721             EnableWindow(hwnd, 0);
722             DialogBox(hinst, MAKEINTRESOURCE(213), NULL, AboutProc);
723             EnableWindow(hwnd, 1);
724             SetActiveWindow(hwnd);
725             return 0;
726           case IDC_GIVEHELP:
727             if (HIWORD(wParam) == BN_CLICKED ||
728                 HIWORD(wParam) == BN_DOUBLECLICKED) {
729                 if (help_path) {
730                     WinHelp(hwnd, help_path, HELP_COMMAND,
731                             (DWORD)"JI(`',`puttygen.general')");
732                     requested_help = TRUE;
733                 }
734             }
735             return 0;
736           case IDC_GENERATE:
737             state =
738                 (struct MainDlgState *) GetWindowLong(hwnd, GWL_USERDATA);
739             if (!state->generation_thread_exists) {
740                 BOOL ok;
741                 state->keysize = GetDlgItemInt(hwnd, IDC_BITS, &ok, FALSE);
742                 if (!ok)
743                     state->keysize = DEFAULT_KEYSIZE;
744                 /* If we ever introduce a new key type, check it here! */
745                 state->ssh2 = !IsDlgButtonChecked(hwnd, IDC_KEYSSH1);
746                 state->is_dsa = IsDlgButtonChecked(hwnd, IDC_KEYSSH2DSA);
747                 if (state->keysize < 256) {
748                     int ret = MessageBox(hwnd,
749                                          "PuTTYgen will not generate a key"
750                                          " smaller than 256 bits.\n"
751                                          "Key length reset to 256. Continue?",
752                                          "PuTTYgen Warning",
753                                          MB_ICONWARNING | MB_OKCANCEL);
754                     if (ret != IDOK)
755                         break;
756                     state->keysize = 256;
757                     SetDlgItemInt(hwnd, IDC_BITS, 256, FALSE);
758                 }
759                 hidemany(hwnd, nokey_ids, TRUE);
760                 hidemany(hwnd, generating_ids, FALSE);
761                 hidemany(hwnd, gotkey_ids, TRUE);
762                 EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 0);
763                 EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 0);
764                 EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 0);
765                 EnableWindow(GetDlgItem(hwnd, IDC_SAVEPUB), 0);
766                 EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH1), 0);
767                 EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2RSA), 0);
768                 EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2DSA), 0);
769                 EnableWindow(GetDlgItem(hwnd, IDC_BITS), 0);
770                 state->key_exists = FALSE;
771                 SetDlgItemText(hwnd, IDC_GENERATING, entropy_msg);
772                 state->collecting_entropy = TRUE;
773
774                 /*
775                  * My brief statistical tests on mouse movements
776                  * suggest that there are about 2.5 bits of
777                  * randomness in the x position, 2.5 in the y
778                  * position, and 1.7 in the message time, making
779                  * 5.7 bits of unpredictability per mouse movement.
780                  * However, other people have told me it's far less
781                  * than that, so I'm going to be stupidly cautious
782                  * and knock that down to a nice round 2. With this
783                  * method, we require two words per mouse movement,
784                  * so with 2 bits per mouse movement we expect 2
785                  * bits every 2 words.
786                  */
787                 state->entropy_required = (state->keysize / 2) * 2;
788                 state->entropy_got = 0;
789                 state->entropy_size = (state->entropy_required *
790                                        sizeof(*state->entropy));
791                 state->entropy = smalloc(state->entropy_size);
792
793                 SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETRANGE, 0,
794                                    MAKELPARAM(0, state->entropy_required));
795                 SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, 0, 0);
796             }
797             break;
798           case IDC_SAVE:
799           case IDC_EXPORT_OPENSSH:
800           case IDC_EXPORT_SSHCOM:
801             state =
802                 (struct MainDlgState *) GetWindowLong(hwnd, GWL_USERDATA);
803             if (state->key_exists) {
804                 char filename[FILENAME_MAX];
805                 char passphrase[PASSPHRASE_MAXLEN];
806                 char passphrase2[PASSPHRASE_MAXLEN];
807                 int type, realtype;
808
809                 if (state->ssh2)
810                     realtype = SSH_KEYTYPE_SSH2;
811                 else
812                     realtype = SSH_KEYTYPE_SSH1;
813
814                 if (LOWORD(wParam) == IDC_EXPORT_OPENSSH)
815                     type = SSH_KEYTYPE_OPENSSH;
816                 else if (LOWORD(wParam) == IDC_EXPORT_SSHCOM)
817                     type = SSH_KEYTYPE_SSHCOM;
818                 else
819                     type = realtype;
820
821                 if (type != realtype &&
822                     import_target_type(type) != realtype) {
823                     char msg[256];
824                     sprintf(msg, "Cannot export an SSH%d key in an SSH%d"
825                             " format", (state->ssh2 ? 2 : 1),
826                             (state->ssh2 ? 1 : 2));
827                     MessageBox(hwnd, msg,
828                                "PuTTYgen Error", MB_OK | MB_ICONERROR);
829                     break;
830                 }
831
832                 GetDlgItemText(hwnd, IDC_PASSPHRASE1EDIT,
833                                passphrase, sizeof(passphrase));
834                 GetDlgItemText(hwnd, IDC_PASSPHRASE2EDIT,
835                                passphrase2, sizeof(passphrase2));
836                 if (strcmp(passphrase, passphrase2)) {
837                     MessageBox(hwnd,
838                                "The two passphrases given do not match.",
839                                "PuTTYgen Error", MB_OK | MB_ICONERROR);
840                     break;
841                 }
842                 if (!*passphrase) {
843                     int ret;
844                     ret = MessageBox(hwnd,
845                                      "Are you sure you want to save this key\n"
846                                      "without a passphrase to protect it?",
847                                      "PuTTYgen Warning",
848                                      MB_YESNO | MB_ICONWARNING);
849                     if (ret != IDYES)
850                         break;
851                 }
852                 if (prompt_keyfile(hwnd, "Save private key as:",
853                                    filename, 1)) {
854                     int ret;
855                     FILE *fp = fopen(filename, "r");
856                     if (fp) {
857                         char buffer[FILENAME_MAX + 80];
858                         fclose(fp);
859                         sprintf(buffer, "Overwrite existing file\n%.*s?",
860                                 FILENAME_MAX, filename);
861                         ret = MessageBox(hwnd, buffer, "PuTTYgen Warning",
862                                          MB_YESNO | MB_ICONWARNING);
863                         if (ret != IDYES)
864                             break;
865                     }
866
867                     if (state->ssh2) {
868                         if (type != realtype)
869                             ret = export_ssh2(filename, type, &state->ssh2key,
870                                               *passphrase ? passphrase : NULL);
871                         else
872                             ret = ssh2_save_userkey(filename, &state->ssh2key,
873                                                     *passphrase ? passphrase :
874                                                     NULL);
875                     } else {
876                         if (type != realtype)
877                             ret = export_ssh1(filename, type, &state->key,
878                                               *passphrase ? passphrase : NULL);
879                         else
880                             ret = saversakey(filename, &state->key,
881                                              *passphrase ? passphrase : NULL);
882                     }
883                     if (ret <= 0) {
884                         MessageBox(hwnd, "Unable to save key file",
885                                    "PuTTYgen Error", MB_OK | MB_ICONERROR);
886                     }
887                 }
888             }
889             break;
890           case IDC_SAVEPUB:
891             state =
892                 (struct MainDlgState *) GetWindowLong(hwnd, GWL_USERDATA);
893             if (state->key_exists) {
894                 char filename[FILENAME_MAX];
895                 if (prompt_keyfile(hwnd, "Save public key as:",
896                                    filename, 1)) {
897                     int ret;
898                     FILE *fp = fopen(filename, "r");
899                     if (fp) {
900                         char buffer[FILENAME_MAX + 80];
901                         fclose(fp);
902                         sprintf(buffer, "Overwrite existing file\n%.*s?",
903                                 FILENAME_MAX, filename);
904                         ret = MessageBox(hwnd, buffer, "PuTTYgen Warning",
905                                          MB_YESNO | MB_ICONWARNING);
906                         if (ret != IDYES)
907                             break;
908                     }
909                     if (state->ssh2) {
910                         ret = save_ssh2_pubkey(filename, &state->ssh2key);
911                     } else {
912                         ret = save_ssh1_pubkey(filename, &state->key);
913                     }
914                     if (ret <= 0) {
915                         MessageBox(hwnd, "Unable to save key file",
916                                    "PuTTYgen Error", MB_OK | MB_ICONERROR);
917                     }
918                 }
919             }
920             break;
921           case IDC_LOAD:
922             state =
923                 (struct MainDlgState *) GetWindowLong(hwnd, GWL_USERDATA);
924             if (!state->generation_thread_exists) {
925                 char filename[FILENAME_MAX];
926                 if (prompt_keyfile(hwnd, "Load private key:", filename, 0)) {
927                     char passphrase[PASSPHRASE_MAXLEN];
928                     int needs_pass;
929                     int type, realtype;
930                     int ret;
931                     char *comment;
932                     struct PassphraseProcStruct pps;
933                     struct RSAKey newkey1;
934                     struct ssh2_userkey *newkey2 = NULL;
935
936                     type = realtype = key_type(filename);
937                     if (type != SSH_KEYTYPE_SSH1 &&
938                         type != SSH_KEYTYPE_SSH2 &&
939                         !import_possible(type)) {
940                         char msg[256];
941                         sprintf(msg, "Couldn't load private key (%s)",
942                                 key_type_to_str(type));
943                         MessageBox(NULL, msg,
944                                    "PuTTYgen Error", MB_OK | MB_ICONERROR);
945                         break;
946                     }
947
948                     if (type != SSH_KEYTYPE_SSH1 &&
949                         type != SSH_KEYTYPE_SSH2) {
950                         realtype = type;
951                         type = import_target_type(type);
952                     }
953
954                     comment = NULL;
955                     if (realtype == SSH_KEYTYPE_SSH1)
956                         needs_pass = rsakey_encrypted(filename, &comment);
957                     else if (realtype == SSH_KEYTYPE_SSH2)
958                         needs_pass =
959                             ssh2_userkey_encrypted(filename, &comment);
960                     else
961                         needs_pass = import_encrypted(filename, realtype,
962                                                       &comment);
963                     pps.passphrase = passphrase;
964                     pps.comment = comment;
965                     do {
966                         if (needs_pass) {
967                             int dlgret;
968                             dlgret = DialogBoxParam(hinst,
969                                                     MAKEINTRESOURCE(210),
970                                                     NULL, PassphraseProc,
971                                                     (LPARAM) & pps);
972                             if (!dlgret) {
973                                 ret = -2;
974                                 break;
975                             }
976                         } else
977                             *passphrase = '\0';
978                         if (type == SSH_KEYTYPE_SSH1) {
979                             if (realtype == type)
980                                 ret = loadrsakey(filename, &newkey1,
981                                                  passphrase);
982                             else
983                                 ret = import_ssh1(filename, realtype,
984                                                   &newkey1, passphrase);
985                         } else {
986                             if (realtype == type)
987                                 newkey2 = ssh2_load_userkey(filename,
988                                                             passphrase);
989                             else
990                                 newkey2 = import_ssh2(filename, realtype,
991                                                       passphrase);
992                             if (newkey2 == SSH2_WRONG_PASSPHRASE)
993                                 ret = -1;
994                             else if (!newkey2)
995                                 ret = 0;
996                             else
997                                 ret = 1;
998                         }
999                     } while (ret == -1);
1000                     if (comment)
1001                         sfree(comment);
1002                     if (ret == 0) {
1003                         MessageBox(NULL, "Couldn't load private key.",
1004                                    "PuTTYgen Error", MB_OK | MB_ICONERROR);
1005                     } else if (ret == 1) {
1006                         EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 1);
1007                         EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 1);
1008                         EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 1);
1009                         EnableWindow(GetDlgItem(hwnd, IDC_SAVEPUB), 1);
1010                         EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH1), 1);
1011                         EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2RSA), 1);
1012                         EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2DSA), 1);
1013                         EnableWindow(GetDlgItem(hwnd, IDC_BITS), 1);
1014                         /*
1015                          * Now update the key controls with all the
1016                          * key data.
1017                          */
1018                         {
1019                             SetDlgItemText(hwnd, IDC_PASSPHRASE1EDIT,
1020                                            passphrase);
1021                             SetDlgItemText(hwnd, IDC_PASSPHRASE2EDIT,
1022                                            passphrase);
1023                             if (type == SSH_KEYTYPE_SSH1) {
1024                                 char buf[128];
1025                                 char *savecomment;
1026
1027                                 state->ssh2 = FALSE;
1028                                 state->commentptr = &state->key.comment;
1029                                 state->key = newkey1;
1030
1031                                 /*
1032                                  * Set the key fingerprint.
1033                                  */
1034                                 savecomment = state->key.comment;
1035                                 state->key.comment = NULL;
1036                                 rsa_fingerprint(buf, sizeof(buf),
1037                                                 &state->key);
1038                                 state->key.comment = savecomment;
1039
1040                                 SetDlgItemText(hwnd, IDC_FINGERPRINT, buf);
1041                                 /*
1042                                  * Construct a decimal representation
1043                                  * of the key, for pasting into
1044                                  * .ssh/authorized_keys on a Unix box.
1045                                  */
1046                                 setupbigedit1(hwnd, IDC_KEYDISPLAY,
1047                                               IDC_PKSTATIC, &state->key);
1048                             } else {
1049                                 char *fp;
1050                                 char *savecomment;
1051
1052                                 state->ssh2 = TRUE;
1053                                 state->commentptr =
1054                                     &state->ssh2key.comment;
1055                                 state->ssh2key = *newkey2;      /* structure copy */
1056                                 sfree(newkey2);
1057
1058                                 savecomment = state->ssh2key.comment;
1059                                 state->ssh2key.comment = NULL;
1060                                 fp =
1061                                     state->ssh2key.alg->
1062                                     fingerprint(state->ssh2key.data);
1063                                 state->ssh2key.comment = savecomment;
1064
1065                                 SetDlgItemText(hwnd, IDC_FINGERPRINT, fp);
1066                                 sfree(fp);
1067
1068                                 setupbigedit2(hwnd, IDC_KEYDISPLAY,
1069                                               IDC_PKSTATIC, &state->ssh2key);
1070                             }
1071                             SetDlgItemText(hwnd, IDC_COMMENTEDIT,
1072                                            *state->commentptr);
1073                         }
1074                         /*
1075                          * Finally, hide the progress bar and show
1076                          * the key data.
1077                          */
1078                         hidemany(hwnd, nokey_ids, TRUE);
1079                         hidemany(hwnd, generating_ids, TRUE);
1080                         hidemany(hwnd, gotkey_ids, FALSE);
1081                         state->key_exists = TRUE;
1082                     }
1083                 }
1084             }
1085             break;
1086         }
1087         return 0;
1088       case WM_DONEKEY:
1089         state = (struct MainDlgState *) GetWindowLong(hwnd, GWL_USERDATA);
1090         state->generation_thread_exists = FALSE;
1091         state->key_exists = TRUE;
1092         SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETRANGE, 0,
1093                            MAKELPARAM(0, PROGRESSRANGE));
1094         SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, PROGRESSRANGE, 0);
1095         EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 1);
1096         EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 1);
1097         EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 1);
1098         EnableWindow(GetDlgItem(hwnd, IDC_SAVEPUB), 1);
1099         EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH1), 1);
1100         EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2RSA), 1);
1101         EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2DSA), 1);
1102         EnableWindow(GetDlgItem(hwnd, IDC_BITS), 1);
1103         if (state->ssh2) {
1104             if (state->is_dsa) {
1105                 state->ssh2key.data = &state->dsskey;
1106                 state->ssh2key.alg = &ssh_dss;
1107             } else {
1108                 state->ssh2key.data = &state->key;
1109                 state->ssh2key.alg = &ssh_rsa;
1110             }
1111             state->commentptr = &state->ssh2key.comment;
1112         } else {
1113             state->commentptr = &state->key.comment;
1114         }
1115         /*
1116          * Invent a comment for the key. We'll do this by including
1117          * the date in it. This will be so horrifyingly ugly that
1118          * the user will immediately want to change it, which is
1119          * what we want :-)
1120          */
1121         *state->commentptr = smalloc(30);
1122         {
1123             time_t t;
1124             struct tm *tm;
1125             time(&t);
1126             tm = localtime(&t);
1127             if (state->is_dsa)
1128                 strftime(*state->commentptr, 30, "dsa-key-%Y%m%d", tm);
1129             else
1130                 strftime(*state->commentptr, 30, "rsa-key-%Y%m%d", tm);
1131         }
1132
1133         /*
1134          * Now update the key controls with all the key data.
1135          */
1136         {
1137             char *savecomment;
1138             /*
1139              * Blank passphrase, initially. This isn't dangerous,
1140              * because we will warn (Are You Sure?) before allowing
1141              * the user to save an unprotected private key.
1142              */
1143             SetDlgItemText(hwnd, IDC_PASSPHRASE1EDIT, "");
1144             SetDlgItemText(hwnd, IDC_PASSPHRASE2EDIT, "");
1145             /*
1146              * Set the comment.
1147              */
1148             SetDlgItemText(hwnd, IDC_COMMENTEDIT, *state->commentptr);
1149             /*
1150              * Set the key fingerprint.
1151              */
1152             savecomment = *state->commentptr;
1153             *state->commentptr = NULL;
1154             if (state->ssh2) {
1155                 char *fp;
1156                 fp = state->ssh2key.alg->fingerprint(state->ssh2key.data);
1157                 SetDlgItemText(hwnd, IDC_FINGERPRINT, fp);
1158                 sfree(fp);
1159             } else {
1160                 char buf[128];
1161                 rsa_fingerprint(buf, sizeof(buf), &state->key);
1162                 SetDlgItemText(hwnd, IDC_FINGERPRINT, buf);
1163             }
1164             *state->commentptr = savecomment;
1165             /*
1166              * Construct a decimal representation of the key, for
1167              * pasting into .ssh/authorized_keys or
1168              * .ssh/authorized_keys2 on a Unix box.
1169              */
1170             if (state->ssh2) {
1171                 setupbigedit2(hwnd, IDC_KEYDISPLAY,
1172                               IDC_PKSTATIC, &state->ssh2key);
1173             } else {
1174                 setupbigedit1(hwnd, IDC_KEYDISPLAY,
1175                               IDC_PKSTATIC, &state->key);
1176             }
1177         }
1178         /*
1179          * Finally, hide the progress bar and show the key data.
1180          */
1181         hidemany(hwnd, nokey_ids, TRUE);
1182         hidemany(hwnd, generating_ids, TRUE);
1183         hidemany(hwnd, gotkey_ids, FALSE);
1184         break;
1185       case WM_HELP:
1186         if (help_path) {
1187             int id = ((LPHELPINFO)lParam)->iCtrlId;
1188             char *cmd = NULL;
1189             switch (id) {
1190               case IDC_GENERATING:
1191               case IDC_PROGRESS:
1192               case IDC_GENSTATIC:
1193               case IDC_GENERATE:
1194                 cmd = "JI(`',`puttygen.generate')"; break;
1195               case IDC_PKSTATIC:
1196               case IDC_KEYDISPLAY:
1197                 cmd = "JI(`',`puttygen.pastekey')"; break;
1198               case IDC_FPSTATIC:
1199               case IDC_FINGERPRINT:
1200                 cmd = "JI(`',`puttygen.fingerprint')"; break;
1201               case IDC_COMMENTSTATIC:
1202               case IDC_COMMENTEDIT:
1203                 cmd = "JI(`',`puttygen.comment')"; break;
1204               case IDC_PASSPHRASE1STATIC:
1205               case IDC_PASSPHRASE1EDIT:
1206               case IDC_PASSPHRASE2STATIC:
1207               case IDC_PASSPHRASE2EDIT:
1208                 cmd = "JI(`',`puttygen.passphrase')"; break;
1209               case IDC_LOADSTATIC:
1210               case IDC_LOAD:
1211                 cmd = "JI(`',`puttygen.load')"; break;
1212               case IDC_SAVESTATIC:
1213               case IDC_SAVE:
1214                 cmd = "JI(`',`puttygen.savepriv')"; break;
1215               case IDC_SAVEPUB:
1216                 cmd = "JI(`',`puttygen.savepub')"; break;
1217               case IDC_TYPESTATIC:
1218               case IDC_KEYSSH1:
1219               case IDC_KEYSSH2RSA:
1220               case IDC_KEYSSH2DSA:
1221                 cmd = "JI(`',`puttygen.keytype')"; break;
1222               case IDC_BITSSTATIC:
1223               case IDC_BITS:
1224                 cmd = "JI(`',`puttygen.bits')"; break;
1225             }
1226             if (cmd) {
1227                 WinHelp(hwnd, help_path, HELP_COMMAND, (DWORD)cmd);
1228                 requested_help = TRUE;
1229             } else {
1230                 MessageBeep(0);
1231             }
1232         }
1233         break;
1234       case WM_CLOSE:
1235         state = (struct MainDlgState *) GetWindowLong(hwnd, GWL_USERDATA);
1236         sfree(state);
1237         if (requested_help) {
1238             WinHelp(hwnd, help_path, HELP_QUIT, 0);
1239             requested_help = FALSE;
1240         }
1241         EndDialog(hwnd, 1);
1242         return 0;
1243     }
1244     return 0;
1245 }
1246
1247 void cleanup_exit(int code) { exit(code); }
1248
1249 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
1250 {
1251     InitCommonControls();
1252     hinst = inst;
1253
1254     /*
1255      * See if we can find our Help file.
1256      */
1257     {
1258         char b[2048], *p, *q, *r;
1259         FILE *fp;
1260         GetModuleFileName(NULL, b, sizeof(b) - 1);
1261         r = b;
1262         p = strrchr(b, '\\');
1263         if (p && p >= r) r = p+1;
1264         q = strrchr(b, ':');
1265         if (q && q >= r) r = q+1;
1266         strcpy(r, "putty.hlp");
1267         if ( (fp = fopen(b, "r")) != NULL) {
1268             help_path = dupstr(b);
1269             fclose(fp);
1270         } else
1271             help_path = NULL;
1272     }
1273
1274     random_init();
1275     return DialogBox(hinst, MAKEINTRESOURCE(201), NULL,
1276                      MainDlgProc) != IDOK;
1277 }