Implemented export of OpenSSH keys.
[u/mdw/putty] / 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 }