2 * PuTTY key generation front end (Windows).
10 #define PUTTY_DO_GLOBALS
21 #define WM_DONEKEY (WM_APP + 1)
23 #define DEFAULT_KEYSIZE 2048
25 static char *cmdline_keyfile = NULL;
28 * Print a modal (Really Bad) message box and perform a fatal exit.
30 void modalfatalbox(char *fmt, ...)
36 stuff = dupvprintf(fmt, ap);
38 MessageBox(NULL, stuff, "PuTTYgen Fatal Error",
39 MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
45 * Print a non-fatal message box and do not exit.
47 void nonfatal(char *fmt, ...)
53 stuff = dupvprintf(fmt, ap);
55 MessageBox(NULL, stuff, "PuTTYgen Error",
56 MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
60 /* ----------------------------------------------------------------------
61 * Progress report code. This is really horrible :-)
63 #define PROGRESSRANGE 65535
69 unsigned startpoint, total;
70 unsigned param, current, n; /* if exponential */
71 unsigned mult; /* if linear */
73 unsigned total, divisor, range;
77 static void progress_update(void *param, int action, int phase, int iprogress)
79 struct progress *p = (struct progress *) param;
80 unsigned progress = iprogress;
83 if (action < PROGFN_READY && p->nphases < phase)
86 case PROGFN_INITIALISE:
89 case PROGFN_LIN_PHASE:
90 p->phases[phase-1].exponential = 0;
91 p->phases[phase-1].mult = p->phases[phase].total / progress;
93 case PROGFN_EXP_PHASE:
94 p->phases[phase-1].exponential = 1;
95 p->phases[phase-1].param = 0x10000 + progress;
96 p->phases[phase-1].current = p->phases[phase-1].total;
97 p->phases[phase-1].n = 0;
99 case PROGFN_PHASE_EXTENT:
100 p->phases[phase-1].total = progress;
106 for (i = 0; i < p->nphases; i++) {
107 p->phases[i].startpoint = total;
108 total += p->phases[i].total;
111 p->divisor = ((p->total + PROGRESSRANGE - 1) / PROGRESSRANGE);
112 p->range = p->total / p->divisor;
113 SendMessage(p->progbar, PBM_SETRANGE, 0, MAKELPARAM(0, p->range));
116 case PROGFN_PROGRESS:
117 if (p->phases[phase-1].exponential) {
118 while (p->phases[phase-1].n < progress) {
119 p->phases[phase-1].n++;
120 p->phases[phase-1].current *= p->phases[phase-1].param;
121 p->phases[phase-1].current /= 0x10000;
123 position = (p->phases[phase-1].startpoint +
124 p->phases[phase-1].total - p->phases[phase-1].current);
126 position = (p->phases[phase-1].startpoint +
127 progress * p->phases[phase-1].mult);
129 SendMessage(p->progbar, PBM_SETPOS, position / p->divisor, 0);
136 struct PassphraseProcStruct {
142 * Dialog-box function for the passphrase box.
144 static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
145 WPARAM wParam, LPARAM lParam)
147 static char **passphrase = NULL;
148 struct PassphraseProcStruct *p;
152 SetForegroundWindow(hwnd);
153 SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
154 SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
159 { /* centre the window */
163 hw = GetDesktopWindow();
164 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
166 (rs.right + rs.left + rd.left - rd.right) / 2,
167 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
168 rd.right - rd.left, rd.bottom - rd.top, TRUE);
171 p = (struct PassphraseProcStruct *) lParam;
172 passphrase = p->passphrase;
174 SetDlgItemText(hwnd, 101, p->comment);
175 burnstr(*passphrase);
176 *passphrase = dupstr("");
177 SetDlgItemText(hwnd, 102, *passphrase);
180 switch (LOWORD(wParam)) {
190 case 102: /* edit box */
191 if ((HIWORD(wParam) == EN_CHANGE) && passphrase) {
192 burnstr(*passphrase);
193 *passphrase = GetDlgItemText_alloc(hwnd, 102);
206 * Prompt for a key file. Assumes the filename buffer is of size
209 static int prompt_keyfile(HWND hwnd, char *dlgtitle,
210 char *filename, int save, int ppk)
213 memset(&of, 0, sizeof(of));
216 of.lpstrFilter = "PuTTY Private Key Files (*.ppk)\0*.ppk\0"
217 "All Files (*.*)\0*\0\0\0";
218 of.lpstrDefExt = ".ppk";
220 of.lpstrFilter = "All Files (*.*)\0*\0\0\0";
222 of.lpstrCustomFilter = NULL;
224 of.lpstrFile = filename;
226 of.nMaxFile = FILENAME_MAX;
227 of.lpstrFileTitle = NULL;
228 of.lpstrTitle = dlgtitle;
230 return request_file(NULL, &of, FALSE, save);
234 * Dialog-box function for the Licence box.
236 static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
237 WPARAM wParam, LPARAM lParam)
244 { /* centre the window */
248 hw = GetDesktopWindow();
249 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
251 (rs.right + rs.left + rd.left - rd.right) / 2,
252 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
253 rd.right - rd.left, rd.bottom - rd.top, TRUE);
258 switch (LOWORD(wParam)) {
273 * Dialog-box function for the About box.
275 static int CALLBACK AboutProc(HWND hwnd, UINT msg,
276 WPARAM wParam, LPARAM lParam)
283 { /* centre the window */
287 hw = GetDesktopWindow();
288 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
290 (rs.right + rs.left + rd.left - rd.right) / 2,
291 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
292 rd.right - rd.left, rd.bottom - rd.top, TRUE);
295 SetDlgItemText(hwnd, 100, ver);
298 switch (LOWORD(wParam)) {
304 EnableWindow(hwnd, 0);
305 DialogBox(hinst, MAKEINTRESOURCE(214), hwnd, LicenceProc);
306 EnableWindow(hwnd, 1);
307 SetActiveWindow(hwnd);
318 typedef enum {RSA, DSA, ECDSA} keytype;
321 * Thread to generate a key.
323 struct rsa_key_thread_params {
324 HWND progressbar; /* notify this with progress */
325 HWND dialog; /* notify this on completion */
326 int keysize; /* bits in key */
330 struct dss_key *dsskey;
331 struct ec_key *eckey;
334 static DWORD WINAPI generate_rsa_key_thread(void *param)
336 struct rsa_key_thread_params *params =
337 (struct rsa_key_thread_params *) param;
338 struct progress prog;
339 prog.progbar = params->progressbar;
341 progress_update(&prog, PROGFN_INITIALISE, 0, 0);
343 if (params->keytype == DSA)
344 dsa_generate(params->dsskey, params->keysize, progress_update, &prog);
345 else if (params->keytype == ECDSA)
346 ec_generate(params->eckey, params->keysize, progress_update, &prog);
348 rsa_generate(params->key, params->keysize, progress_update, &prog);
350 PostMessage(params->dialog, WM_DONEKEY, 0, 0);
356 struct MainDlgState {
357 int collecting_entropy;
358 int generation_thread_exists;
360 int entropy_got, entropy_required, entropy_size;
364 char **commentptr; /* points to key.comment or ssh2key.comment */
365 struct ssh2_userkey ssh2key;
369 struct dss_key dsskey;
372 HMENU filemenu, keymenu, cvtmenu;
375 static void hidemany(HWND hwnd, const int *ids, int hideit)
378 ShowWindow(GetDlgItem(hwnd, *ids++), (hideit ? SW_HIDE : SW_SHOW));
382 static void setupbigedit1(HWND hwnd, int id, int idstatic, struct RSAKey *key)
387 dec1 = bignum_decimal(key->exponent);
388 dec2 = bignum_decimal(key->modulus);
389 buffer = dupprintf("%d %s %s %s", bignum_bitcount(key->modulus),
390 dec1, dec2, key->comment);
391 SetDlgItemText(hwnd, id, buffer);
392 SetDlgItemText(hwnd, idstatic,
393 "&Public key for pasting into authorized_keys file:");
399 static void setupbigedit2(HWND hwnd, int id, int idstatic,
400 struct ssh2_userkey *key)
402 unsigned char *pub_blob;
407 pub_blob = key->alg->public_blob(key->data, &pub_len);
408 buffer = snewn(strlen(key->alg->name) + 4 * ((pub_len + 2) / 3) +
409 strlen(key->comment) + 3, char);
410 strcpy(buffer, key->alg->name);
411 p = buffer + strlen(buffer);
414 while (i < pub_len) {
415 int n = (pub_len - i < 3 ? pub_len - i : 3);
416 base64_encode_atom(pub_blob + i, n, p);
421 strcpy(p, key->comment);
422 SetDlgItemText(hwnd, id, buffer);
423 SetDlgItemText(hwnd, idstatic, "&Public key for pasting into "
424 "OpenSSH authorized_keys file:");
429 static int save_ssh1_pubkey(char *filename, struct RSAKey *key)
434 fp = fopen(filename, "wb");
437 dec1 = bignum_decimal(key->exponent);
438 dec2 = bignum_decimal(key->modulus);
439 fprintf(fp, "%d %s %s %s\n",
440 bignum_bitcount(key->modulus), dec1, dec2, key->comment);
448 * Warn about the obsolescent key file format.
450 void old_keyfile_warning(void)
452 static const char mbtitle[] = "PuTTY Key File Warning";
453 static const char message[] =
454 "You are loading an SSH-2 private key which has an\n"
455 "old version of the file format. This means your key\n"
456 "file is not fully tamperproof. Future versions of\n"
457 "PuTTY may stop supporting this private key format,\n"
458 "so we recommend you convert your key to the new\n"
461 "Once the key is loaded into PuTTYgen, you can perform\n"
462 "this conversion simply by saving it again.";
464 MessageBox(NULL, message, mbtitle, MB_OK);
467 static int save_ssh2_pubkey(char *filename, struct ssh2_userkey *key)
469 unsigned char *pub_blob;
475 pub_blob = key->alg->public_blob(key->data, &pub_len);
477 fp = fopen(filename, "wb");
481 fprintf(fp, "---- BEGIN SSH2 PUBLIC KEY ----\n");
483 fprintf(fp, "Comment: \"");
484 for (p = key->comment; *p; p++) {
485 if (*p == '\\' || *p == '\"')
493 while (i < pub_len) {
495 int n = (pub_len - i < 3 ? pub_len - i : 3);
496 base64_encode_atom(pub_blob + i, n, buf);
500 if (++column >= 16) {
508 fprintf(fp, "---- END SSH2 PUBLIC KEY ----\n");
515 controlidstart = 100,
522 IDC_PKSTATIC, IDC_KEYDISPLAY,
523 IDC_FPSTATIC, IDC_FINGERPRINT,
524 IDC_COMMENTSTATIC, IDC_COMMENTEDIT,
525 IDC_PASSPHRASE1STATIC, IDC_PASSPHRASE1EDIT,
526 IDC_PASSPHRASE2STATIC, IDC_PASSPHRASE2EDIT,
528 IDC_GENSTATIC, IDC_GENERATE,
529 IDC_LOADSTATIC, IDC_LOAD,
530 IDC_SAVESTATIC, IDC_SAVE, IDC_SAVEPUB,
532 IDC_TYPESTATIC, IDC_KEYSSH1, IDC_KEYSSH2RSA, IDC_KEYSSH2DSA,
534 IDC_BITSSTATIC, IDC_BITS,
537 IDC_IMPORT, IDC_EXPORT_OPENSSH, IDC_EXPORT_SSHCOM
540 static const int nokey_ids[] = { IDC_NOKEY, 0 };
541 static const int generating_ids[] = { IDC_GENERATING, IDC_PROGRESS, 0 };
542 static const int gotkey_ids[] = {
543 IDC_PKSTATIC, IDC_KEYDISPLAY,
544 IDC_FPSTATIC, IDC_FINGERPRINT,
545 IDC_COMMENTSTATIC, IDC_COMMENTEDIT,
546 IDC_PASSPHRASE1STATIC, IDC_PASSPHRASE1EDIT,
547 IDC_PASSPHRASE2STATIC, IDC_PASSPHRASE2EDIT, 0
551 * Small UI helper function to switch the state of the main dialog
552 * by enabling and disabling controls and menu items.
554 void ui_set_state(HWND hwnd, struct MainDlgState *state, int status)
560 hidemany(hwnd, nokey_ids, FALSE);
561 hidemany(hwnd, generating_ids, TRUE);
562 hidemany(hwnd, gotkey_ids, TRUE);
563 EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 1);
564 EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 1);
565 EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 0);
566 EnableWindow(GetDlgItem(hwnd, IDC_SAVEPUB), 0);
567 EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH1), 1);
568 EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2RSA), 1);
569 EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2DSA), 1);
570 EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2ECDSA), 1);
571 EnableWindow(GetDlgItem(hwnd, IDC_BITS), 1);
572 EnableMenuItem(state->filemenu, IDC_LOAD, MF_ENABLED|MF_BYCOMMAND);
573 EnableMenuItem(state->filemenu, IDC_SAVE, MF_GRAYED|MF_BYCOMMAND);
574 EnableMenuItem(state->filemenu, IDC_SAVEPUB, MF_GRAYED|MF_BYCOMMAND);
575 EnableMenuItem(state->keymenu, IDC_GENERATE, MF_ENABLED|MF_BYCOMMAND);
576 EnableMenuItem(state->keymenu, IDC_KEYSSH1, MF_ENABLED|MF_BYCOMMAND);
577 EnableMenuItem(state->keymenu, IDC_KEYSSH2RSA, MF_ENABLED|MF_BYCOMMAND);
578 EnableMenuItem(state->keymenu, IDC_KEYSSH2DSA, MF_ENABLED|MF_BYCOMMAND);
579 EnableMenuItem(state->keymenu, IDC_KEYSSH2ECDSA,
580 MF_ENABLED|MF_BYCOMMAND);
581 EnableMenuItem(state->cvtmenu, IDC_IMPORT, MF_ENABLED|MF_BYCOMMAND);
582 EnableMenuItem(state->cvtmenu, IDC_EXPORT_OPENSSH,
583 MF_GRAYED|MF_BYCOMMAND);
584 EnableMenuItem(state->cvtmenu, IDC_EXPORT_SSHCOM,
585 MF_GRAYED|MF_BYCOMMAND);
587 case 1: /* generating key */
588 hidemany(hwnd, nokey_ids, TRUE);
589 hidemany(hwnd, generating_ids, FALSE);
590 hidemany(hwnd, gotkey_ids, TRUE);
591 EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 0);
592 EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 0);
593 EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 0);
594 EnableWindow(GetDlgItem(hwnd, IDC_SAVEPUB), 0);
595 EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH1), 0);
596 EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2RSA), 0);
597 EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2DSA), 0);
598 EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2ECDSA), 0);
599 EnableWindow(GetDlgItem(hwnd, IDC_BITS), 0);
600 EnableMenuItem(state->filemenu, IDC_LOAD, MF_GRAYED|MF_BYCOMMAND);
601 EnableMenuItem(state->filemenu, IDC_SAVE, MF_GRAYED|MF_BYCOMMAND);
602 EnableMenuItem(state->filemenu, IDC_SAVEPUB, MF_GRAYED|MF_BYCOMMAND);
603 EnableMenuItem(state->keymenu, IDC_GENERATE, MF_GRAYED|MF_BYCOMMAND);
604 EnableMenuItem(state->keymenu, IDC_KEYSSH1, MF_GRAYED|MF_BYCOMMAND);
605 EnableMenuItem(state->keymenu, IDC_KEYSSH2RSA, MF_GRAYED|MF_BYCOMMAND);
606 EnableMenuItem(state->keymenu, IDC_KEYSSH2DSA, MF_GRAYED|MF_BYCOMMAND);
607 EnableMenuItem(state->keymenu, IDC_KEYSSH2ECDSA,
608 MF_GRAYED|MF_BYCOMMAND);
609 EnableMenuItem(state->cvtmenu, IDC_IMPORT, MF_GRAYED|MF_BYCOMMAND);
610 EnableMenuItem(state->cvtmenu, IDC_EXPORT_OPENSSH,
611 MF_GRAYED|MF_BYCOMMAND);
612 EnableMenuItem(state->cvtmenu, IDC_EXPORT_SSHCOM,
613 MF_GRAYED|MF_BYCOMMAND);
616 hidemany(hwnd, nokey_ids, TRUE);
617 hidemany(hwnd, generating_ids, TRUE);
618 hidemany(hwnd, gotkey_ids, FALSE);
619 EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 1);
620 EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 1);
621 EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 1);
622 EnableWindow(GetDlgItem(hwnd, IDC_SAVEPUB), 1);
623 EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH1), 1);
624 EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2RSA), 1);
625 EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2DSA), 1);
626 EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2ECDSA), 1);
627 EnableWindow(GetDlgItem(hwnd, IDC_BITS), 1);
628 EnableMenuItem(state->filemenu, IDC_LOAD, MF_ENABLED|MF_BYCOMMAND);
629 EnableMenuItem(state->filemenu, IDC_SAVE, MF_ENABLED|MF_BYCOMMAND);
630 EnableMenuItem(state->filemenu, IDC_SAVEPUB, MF_ENABLED|MF_BYCOMMAND);
631 EnableMenuItem(state->keymenu, IDC_GENERATE, MF_ENABLED|MF_BYCOMMAND);
632 EnableMenuItem(state->keymenu, IDC_KEYSSH1, MF_ENABLED|MF_BYCOMMAND);
633 EnableMenuItem(state->keymenu, IDC_KEYSSH2RSA,MF_ENABLED|MF_BYCOMMAND);
634 EnableMenuItem(state->keymenu, IDC_KEYSSH2DSA,MF_ENABLED|MF_BYCOMMAND);
635 EnableMenuItem(state->keymenu, IDC_KEYSSH2ECDSA,
636 MF_ENABLED|MF_BYCOMMAND);
637 EnableMenuItem(state->cvtmenu, IDC_IMPORT, MF_ENABLED|MF_BYCOMMAND);
639 * Enable export menu items if and only if the key type
640 * supports this kind of export.
642 type = state->ssh2 ? SSH_KEYTYPE_SSH2 : SSH_KEYTYPE_SSH1;
643 #define do_export_menuitem(x,y) \
644 EnableMenuItem(state->cvtmenu, x, MF_BYCOMMAND | \
645 (import_target_type(y)==type?MF_ENABLED:MF_GRAYED))
646 do_export_menuitem(IDC_EXPORT_OPENSSH, SSH_KEYTYPE_OPENSSH);
647 do_export_menuitem(IDC_EXPORT_SSHCOM, SSH_KEYTYPE_SSHCOM);
648 #undef do_export_menuitem
653 void load_key_file(HWND hwnd, struct MainDlgState *state,
654 Filename *filename, int was_import_cmd)
660 const char *errmsg = NULL;
662 struct RSAKey newkey1;
663 struct ssh2_userkey *newkey2 = NULL;
665 type = realtype = key_type(filename);
666 if (type != SSH_KEYTYPE_SSH1 &&
667 type != SSH_KEYTYPE_SSH2 &&
668 !import_possible(type)) {
669 char *msg = dupprintf("Couldn't load private key (%s)",
670 key_type_to_str(type));
671 message_box(msg, "PuTTYgen Error", MB_OK | MB_ICONERROR,
672 HELPCTXID(errors_cantloadkey));
677 if (type != SSH_KEYTYPE_SSH1 &&
678 type != SSH_KEYTYPE_SSH2) {
680 type = import_target_type(type);
685 if (realtype == SSH_KEYTYPE_SSH1)
686 needs_pass = rsakey_encrypted(filename, &comment);
687 else if (realtype == SSH_KEYTYPE_SSH2)
688 needs_pass = ssh2_userkey_encrypted(filename, &comment);
690 needs_pass = import_encrypted(filename, realtype, &comment);
697 struct PassphraseProcStruct pps;
698 pps.passphrase = &passphrase;
699 pps.comment = comment;
700 dlgret = DialogBoxParam(hinst,
701 MAKEINTRESOURCE(210),
702 NULL, PassphraseProc,
708 assert(passphrase != NULL);
710 passphrase = dupstr("");
711 if (type == SSH_KEYTYPE_SSH1) {
712 if (realtype == type)
713 ret = loadrsakey(filename, &newkey1, passphrase, &errmsg);
715 ret = import_ssh1(filename, realtype, &newkey1,
716 passphrase, &errmsg);
718 if (realtype == type)
719 newkey2 = ssh2_load_userkey(filename, passphrase, &errmsg);
721 newkey2 = import_ssh2(filename, realtype, passphrase, &errmsg);
722 if (newkey2 == SSH2_WRONG_PASSPHRASE)
733 char *msg = dupprintf("Couldn't load private key (%s)", errmsg);
734 message_box(msg, "PuTTYgen Error", MB_OK | MB_ICONERROR,
735 HELPCTXID(errors_cantloadkey));
737 } else if (ret == 1) {
739 * Now update the key controls with all the
743 SetDlgItemText(hwnd, IDC_PASSPHRASE1EDIT,
745 SetDlgItemText(hwnd, IDC_PASSPHRASE2EDIT,
747 if (type == SSH_KEYTYPE_SSH1) {
752 state->commentptr = &state->key.comment;
753 state->key = newkey1;
756 * Set the key fingerprint.
758 savecomment = state->key.comment;
759 state->key.comment = NULL;
760 rsa_fingerprint(buf, sizeof(buf),
762 state->key.comment = savecomment;
764 SetDlgItemText(hwnd, IDC_FINGERPRINT, buf);
766 * Construct a decimal representation
767 * of the key, for pasting into
768 * .ssh/authorized_keys on a Unix box.
770 setupbigedit1(hwnd, IDC_KEYDISPLAY,
771 IDC_PKSTATIC, &state->key);
778 &state->ssh2key.comment;
779 state->ssh2key = *newkey2; /* structure copy */
782 savecomment = state->ssh2key.comment;
783 state->ssh2key.comment = NULL;
786 fingerprint(state->ssh2key.data);
787 state->ssh2key.comment = savecomment;
789 SetDlgItemText(hwnd, IDC_FINGERPRINT, fp);
792 setupbigedit2(hwnd, IDC_KEYDISPLAY,
793 IDC_PKSTATIC, &state->ssh2key);
795 SetDlgItemText(hwnd, IDC_COMMENTEDIT,
799 * Finally, hide the progress bar and show
802 ui_set_state(hwnd, state, 2);
803 state->key_exists = TRUE;
806 * If the user has imported a foreign key
807 * using the Load command, let them know.
808 * If they've used the Import command, be
811 if (realtype != type && !was_import_cmd) {
813 sprintf(msg, "Successfully imported foreign key\n"
815 "To use this key with PuTTY, you need to\n"
816 "use the \"Save private key\" command to\n"
817 "save it in PuTTY's own format.",
818 key_type_to_str(realtype));
819 MessageBox(NULL, msg, "PuTTYgen Notice",
820 MB_OK | MB_ICONINFORMATION);
827 * Dialog-box function for the main PuTTYgen dialog box.
829 static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
830 WPARAM wParam, LPARAM lParam)
832 static const char generating_msg[] =
833 "Please wait while a key is generated...";
834 static const char entropy_msg[] =
835 "Please generate some randomness by moving the mouse over the blank area.";
836 struct MainDlgState *state;
841 SetWindowLongPtr(hwnd, GWL_EXSTYLE,
842 GetWindowLongPtr(hwnd, GWL_EXSTYLE) |
846 * If we add a Help button, this is where we destroy it
847 * if the help file isn't present.
850 SendMessage(hwnd, WM_SETICON, (WPARAM) ICON_BIG,
851 (LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(200)));
853 state = snew(struct MainDlgState);
854 state->generation_thread_exists = FALSE;
855 state->collecting_entropy = FALSE;
856 state->entropy = NULL;
857 state->key_exists = FALSE;
858 SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) state);
864 menu1 = CreateMenu();
865 AppendMenu(menu1, MF_ENABLED, IDC_LOAD, "&Load private key");
866 AppendMenu(menu1, MF_ENABLED, IDC_SAVEPUB, "Save p&ublic key");
867 AppendMenu(menu1, MF_ENABLED, IDC_SAVE, "&Save private key");
868 AppendMenu(menu1, MF_SEPARATOR, 0, 0);
869 AppendMenu(menu1, MF_ENABLED, IDC_QUIT, "E&xit");
870 AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT) menu1, "&File");
871 state->filemenu = menu1;
873 menu1 = CreateMenu();
874 AppendMenu(menu1, MF_ENABLED, IDC_GENERATE, "&Generate key pair");
875 AppendMenu(menu1, MF_SEPARATOR, 0, 0);
876 AppendMenu(menu1, MF_ENABLED, IDC_KEYSSH1, "SSH-&1 key (RSA)");
877 AppendMenu(menu1, MF_ENABLED, IDC_KEYSSH2RSA, "SSH-2 &RSA key");
878 AppendMenu(menu1, MF_ENABLED, IDC_KEYSSH2DSA, "SSH-2 &DSA key");
879 AppendMenu(menu1, MF_ENABLED, IDC_KEYSSH2ECDSA, "SSH-2 &ECDSA key");
880 AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT) menu1, "&Key");
881 state->keymenu = menu1;
883 menu1 = CreateMenu();
884 AppendMenu(menu1, MF_ENABLED, IDC_IMPORT, "&Import key");
885 AppendMenu(menu1, MF_SEPARATOR, 0, 0);
886 AppendMenu(menu1, MF_ENABLED, IDC_EXPORT_OPENSSH,
887 "Export &OpenSSH key");
888 AppendMenu(menu1, MF_ENABLED, IDC_EXPORT_SSHCOM,
889 "Export &ssh.com key");
890 AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT) menu1,
892 state->cvtmenu = menu1;
894 menu1 = CreateMenu();
895 AppendMenu(menu1, MF_ENABLED, IDC_ABOUT, "&About");
897 AppendMenu(menu1, MF_ENABLED, IDC_GIVEHELP, "&Help");
898 AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT) menu1, "&Help");
906 { /* centre the window */
910 hw = GetDesktopWindow();
911 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
913 (rs.right + rs.left + rd.left - rd.right) / 2,
914 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
915 rd.right - rd.left, rd.bottom - rd.top, TRUE);
919 struct ctlpos cp, cp2;
921 /* Accelerators used: acglops1rbd */
923 ctlposinit(&cp, hwnd, 4, 4, 4);
924 beginbox(&cp, "Key", IDC_BOX_KEY);
926 statictext(&cp2, "No key.", 1, IDC_NOKEY);
928 statictext(&cp2, "", 1, IDC_GENERATING);
929 progressbar(&cp2, IDC_PROGRESS);
931 "&Public key for pasting into authorized_keys file:",
932 IDC_PKSTATIC, IDC_KEYDISPLAY, 5);
933 SendDlgItemMessage(hwnd, IDC_KEYDISPLAY, EM_SETREADONLY, 1, 0);
934 staticedit(&cp, "Key f&ingerprint:", IDC_FPSTATIC,
935 IDC_FINGERPRINT, 75);
936 SendDlgItemMessage(hwnd, IDC_FINGERPRINT, EM_SETREADONLY, 1,
938 staticedit(&cp, "Key &comment:", IDC_COMMENTSTATIC,
939 IDC_COMMENTEDIT, 75);
940 staticpassedit(&cp, "Key p&assphrase:", IDC_PASSPHRASE1STATIC,
941 IDC_PASSPHRASE1EDIT, 75);
942 staticpassedit(&cp, "C&onfirm passphrase:",
943 IDC_PASSPHRASE2STATIC, IDC_PASSPHRASE2EDIT, 75);
945 beginbox(&cp, "Actions", IDC_BOX_ACTIONS);
946 staticbtn(&cp, "Generate a public/private key pair",
947 IDC_GENSTATIC, "&Generate", IDC_GENERATE);
948 staticbtn(&cp, "Load an existing private key file",
949 IDC_LOADSTATIC, "&Load", IDC_LOAD);
950 static2btn(&cp, "Save the generated key", IDC_SAVESTATIC,
951 "Save p&ublic key", IDC_SAVEPUB,
952 "&Save private key", IDC_SAVE);
954 beginbox(&cp, "Parameters", IDC_BOX_PARAMS);
955 radioline(&cp, "Type of key to generate:", IDC_TYPESTATIC, 3,
956 "SSH-&1 (RSA)", IDC_KEYSSH1,
957 "SSH-2 &RSA", IDC_KEYSSH2RSA,
958 "SSH-2 &DSA", IDC_KEYSSH2DSA,
959 "SSH-2 &ECDSA", IDC_KEYSSH2ECDSA, NULL);
960 staticedit(&cp, "Number of &bits in a generated key:",
961 IDC_BITSSTATIC, IDC_BITS, 20);
964 CheckRadioButton(hwnd, IDC_KEYSSH1, IDC_KEYSSH2ECDSA, IDC_KEYSSH2RSA);
965 CheckMenuRadioItem(state->keymenu, IDC_KEYSSH1, IDC_KEYSSH2ECDSA,
966 IDC_KEYSSH2RSA, MF_BYCOMMAND);
967 SetDlgItemInt(hwnd, IDC_BITS, DEFAULT_KEYSIZE, FALSE);
970 * Initially, hide the progress bar and the key display,
971 * and show the no-key display. Also disable the Save
972 * buttons, because with no key we obviously can't save
975 ui_set_state(hwnd, state, 0);
978 * Load a key file if one was provided on the command line.
980 if (cmdline_keyfile) {
981 Filename *fn = filename_from_str(cmdline_keyfile);
982 load_key_file(hwnd, state, fn, 0);
988 state = (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
989 if (state->collecting_entropy &&
990 state->entropy && state->entropy_got < state->entropy_required) {
991 state->entropy[state->entropy_got++] = lParam;
992 state->entropy[state->entropy_got++] = GetMessageTime();
993 SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS,
994 state->entropy_got, 0);
995 if (state->entropy_got >= state->entropy_required) {
996 struct rsa_key_thread_params *params;
1000 * Seed the entropy pool
1002 random_add_heavynoise(state->entropy, state->entropy_size);
1003 smemclr(state->entropy, state->entropy_size);
1004 sfree(state->entropy);
1005 state->collecting_entropy = FALSE;
1007 SetDlgItemText(hwnd, IDC_GENERATING, generating_msg);
1008 SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETRANGE, 0,
1009 MAKELPARAM(0, PROGRESSRANGE));
1010 SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, 0, 0);
1012 params = snew(struct rsa_key_thread_params);
1013 params->progressbar = GetDlgItem(hwnd, IDC_PROGRESS);
1014 params->dialog = hwnd;
1015 params->keysize = state->keysize;
1016 params->keytype = state->keytype;
1017 params->key = &state->key;
1018 params->dsskey = &state->dsskey;
1020 if (!CreateThread(NULL, 0, generate_rsa_key_thread,
1021 params, 0, &threadid)) {
1022 MessageBox(hwnd, "Out of thread resources",
1023 "Key generation error",
1024 MB_OK | MB_ICONERROR);
1027 state->generation_thread_exists = TRUE;
1033 switch (LOWORD(wParam)) {
1035 case IDC_KEYSSH2RSA:
1036 case IDC_KEYSSH2DSA:
1037 case IDC_KEYSSH2ECDSA:
1039 state = (struct MainDlgState *)
1040 GetWindowLongPtr(hwnd, GWLP_USERDATA);
1041 if (!IsDlgButtonChecked(hwnd, LOWORD(wParam)))
1042 CheckRadioButton(hwnd, IDC_KEYSSH1, IDC_KEYSSH2DSA,
1044 CheckMenuRadioItem(state->keymenu, IDC_KEYSSH1, IDC_KEYSSH2DSA,
1045 LOWORD(wParam), MF_BYCOMMAND);
1046 CheckRadioButton(hwnd, IDC_KEYSSH1, IDC_KEYSSH2ECDSA,
1048 CheckMenuRadioItem(state->keymenu, IDC_KEYSSH1,
1050 LOWORD(wParam), MF_BYCOMMAND);
1054 PostMessage(hwnd, WM_CLOSE, 0, 0);
1056 case IDC_COMMENTEDIT:
1057 if (HIWORD(wParam) == EN_CHANGE) {
1058 state = (struct MainDlgState *)
1059 GetWindowLongPtr(hwnd, GWLP_USERDATA);
1060 if (state->key_exists) {
1061 HWND editctl = GetDlgItem(hwnd, IDC_COMMENTEDIT);
1062 int len = GetWindowTextLength(editctl);
1063 if (*state->commentptr)
1064 sfree(*state->commentptr);
1065 *state->commentptr = snewn(len + 1, char);
1066 GetWindowText(editctl, *state->commentptr, len + 1);
1068 setupbigedit2(hwnd, IDC_KEYDISPLAY, IDC_PKSTATIC,
1071 setupbigedit1(hwnd, IDC_KEYDISPLAY, IDC_PKSTATIC,
1078 EnableWindow(hwnd, 0);
1079 DialogBox(hinst, MAKEINTRESOURCE(213), hwnd, AboutProc);
1080 EnableWindow(hwnd, 1);
1081 SetActiveWindow(hwnd);
1084 if (HIWORD(wParam) == BN_CLICKED ||
1085 HIWORD(wParam) == BN_DOUBLECLICKED) {
1086 launch_help(hwnd, WINHELP_CTX_puttygen_general);
1090 if (HIWORD(wParam) != BN_CLICKED &&
1091 HIWORD(wParam) != BN_DOUBLECLICKED)
1094 (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
1095 if (!state->generation_thread_exists) {
1097 state->keysize = GetDlgItemInt(hwnd, IDC_BITS, &ok, FALSE);
1099 state->keysize = DEFAULT_KEYSIZE;
1100 /* If we ever introduce a new key type, check it here! */
1101 state->ssh2 = !IsDlgButtonChecked(hwnd, IDC_KEYSSH1);
1102 state->keytype = RSA;
1103 if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2DSA)) {
1104 state->keytype = DSA;
1105 } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2ECDSA)) {
1106 state->keytype = ECDSA;
1108 if (state->keysize < 256) {
1109 int ret = MessageBox(hwnd,
1110 "PuTTYgen will not generate a key"
1111 " smaller than 256 bits.\n"
1112 "Key length reset to 256. Continue?",
1114 MB_ICONWARNING | MB_OKCANCEL);
1117 state->keysize = 256;
1118 SetDlgItemInt(hwnd, IDC_BITS, 256, FALSE);
1120 if (state->keytype == ECDSA && !(state->keysize == 256 ||
1121 state->keysize == 384 ||
1122 state->keysize == 521)) {
1123 int ret = MessageBox(hwnd,
1124 "Only 256, 384 and 521 bit elliptic"
1125 " curves are supported.\n"
1126 "Key length reset to 256. Continue?",
1128 MB_ICONWARNING | MB_OKCANCEL);
1131 state->keysize = 256;
1132 SetDlgItemInt(hwnd, IDC_BITS, 256, FALSE);
1134 ui_set_state(hwnd, state, 1);
1135 SetDlgItemText(hwnd, IDC_GENERATING, entropy_msg);
1136 state->key_exists = FALSE;
1137 state->collecting_entropy = TRUE;
1140 * My brief statistical tests on mouse movements
1141 * suggest that there are about 2.5 bits of
1142 * randomness in the x position, 2.5 in the y
1143 * position, and 1.7 in the message time, making
1144 * 5.7 bits of unpredictability per mouse movement.
1145 * However, other people have told me it's far less
1146 * than that, so I'm going to be stupidly cautious
1147 * and knock that down to a nice round 2. With this
1148 * method, we require two words per mouse movement,
1149 * so with 2 bits per mouse movement we expect 2
1150 * bits every 2 words.
1152 state->entropy_required = (state->keysize / 2) * 2;
1153 state->entropy_got = 0;
1154 state->entropy_size = (state->entropy_required *
1156 state->entropy = snewn(state->entropy_required, unsigned);
1158 SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETRANGE, 0,
1159 MAKELPARAM(0, state->entropy_required));
1160 SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, 0, 0);
1164 case IDC_EXPORT_OPENSSH:
1165 case IDC_EXPORT_SSHCOM:
1166 if (HIWORD(wParam) != BN_CLICKED)
1169 (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
1170 if (state->key_exists) {
1171 char filename[FILENAME_MAX];
1172 char *passphrase, *passphrase2;
1176 realtype = SSH_KEYTYPE_SSH2;
1178 realtype = SSH_KEYTYPE_SSH1;
1180 if (LOWORD(wParam) == IDC_EXPORT_OPENSSH)
1181 type = SSH_KEYTYPE_OPENSSH;
1182 else if (LOWORD(wParam) == IDC_EXPORT_SSHCOM)
1183 type = SSH_KEYTYPE_SSHCOM;
1187 if (type != realtype &&
1188 import_target_type(type) != realtype) {
1190 sprintf(msg, "Cannot export an SSH-%d key in an SSH-%d"
1191 " format", (state->ssh2 ? 2 : 1),
1192 (state->ssh2 ? 1 : 2));
1193 MessageBox(hwnd, msg,
1194 "PuTTYgen Error", MB_OK | MB_ICONERROR);
1198 passphrase = GetDlgItemText_alloc(hwnd, IDC_PASSPHRASE1EDIT);
1199 passphrase2 = GetDlgItemText_alloc(hwnd, IDC_PASSPHRASE2EDIT);
1200 if (strcmp(passphrase, passphrase2)) {
1202 "The two passphrases given do not match.",
1203 "PuTTYgen Error", MB_OK | MB_ICONERROR);
1204 burnstr(passphrase);
1205 burnstr(passphrase2);
1208 burnstr(passphrase2);
1211 ret = MessageBox(hwnd,
1212 "Are you sure you want to save this key\n"
1213 "without a passphrase to protect it?",
1215 MB_YESNO | MB_ICONWARNING);
1217 burnstr(passphrase);
1221 if (prompt_keyfile(hwnd, "Save private key as:",
1222 filename, 1, (type == realtype))) {
1224 FILE *fp = fopen(filename, "r");
1228 buffer = dupprintf("Overwrite existing file\n%s?",
1230 ret = MessageBox(hwnd, buffer, "PuTTYgen Warning",
1231 MB_YESNO | MB_ICONWARNING);
1234 burnstr(passphrase);
1240 Filename *fn = filename_from_str(filename);
1241 if (type != realtype)
1242 ret = export_ssh2(fn, type, &state->ssh2key,
1243 *passphrase ? passphrase : NULL);
1245 ret = ssh2_save_userkey(fn, &state->ssh2key,
1246 *passphrase ? passphrase :
1250 Filename *fn = filename_from_str(filename);
1251 if (type != realtype)
1252 ret = export_ssh1(fn, type, &state->key,
1253 *passphrase ? passphrase : NULL);
1255 ret = saversakey(fn, &state->key,
1256 *passphrase ? passphrase : NULL);
1260 MessageBox(hwnd, "Unable to save key file",
1261 "PuTTYgen Error", MB_OK | MB_ICONERROR);
1264 burnstr(passphrase);
1268 if (HIWORD(wParam) != BN_CLICKED)
1271 (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
1272 if (state->key_exists) {
1273 char filename[FILENAME_MAX];
1274 if (prompt_keyfile(hwnd, "Save public key as:",
1277 FILE *fp = fopen(filename, "r");
1281 buffer = dupprintf("Overwrite existing file\n%s?",
1283 ret = MessageBox(hwnd, buffer, "PuTTYgen Warning",
1284 MB_YESNO | MB_ICONWARNING);
1290 ret = save_ssh2_pubkey(filename, &state->ssh2key);
1292 ret = save_ssh1_pubkey(filename, &state->key);
1295 MessageBox(hwnd, "Unable to save key file",
1296 "PuTTYgen Error", MB_OK | MB_ICONERROR);
1303 if (HIWORD(wParam) != BN_CLICKED)
1306 (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
1307 if (!state->generation_thread_exists) {
1308 char filename[FILENAME_MAX];
1309 if (prompt_keyfile(hwnd, "Load private key:",
1310 filename, 0, LOWORD(wParam)==IDC_LOAD)) {
1311 Filename *fn = filename_from_str(filename);
1312 load_key_file(hwnd, state, fn, LOWORD(wParam) != IDC_LOAD);
1320 state = (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
1321 state->generation_thread_exists = FALSE;
1322 state->key_exists = TRUE;
1323 SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETRANGE, 0,
1324 MAKELPARAM(0, PROGRESSRANGE));
1325 SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, PROGRESSRANGE, 0);
1327 if (state->keytype == DSA) {
1328 state->ssh2key.data = &state->dsskey;
1329 state->ssh2key.alg = &ssh_dss;
1330 } else if (state->keytype == ECDSA) {
1331 state->ssh2key.data = &state->eckey;
1332 if (state->eckey.publicKey.curve->fieldBits == 256)
1333 state->ssh2key.alg = &ssh_ecdsa_nistp256;
1334 else if (state->eckey.publicKey.curve->fieldBits == 384)
1335 state->ssh2key.alg = &ssh_ecdsa_nistp384;
1337 state->ssh2key.alg = &ssh_ecdsa_nistp521;
1339 state->ssh2key.data = &state->key;
1340 state->ssh2key.alg = &ssh_rsa;
1342 state->commentptr = &state->ssh2key.comment;
1344 state->commentptr = &state->key.comment;
1347 * Invent a comment for the key. We'll do this by including
1348 * the date in it. This will be so horrifyingly ugly that
1349 * the user will immediately want to change it, which is
1352 *state->commentptr = snewn(30, char);
1356 if (state->keytype == DSA)
1357 strftime(*state->commentptr, 30, "dsa-key-%Y%m%d", &tm);
1358 else if (state->keytype == ECDSA)
1359 strftime(*state->commentptr, 30, "ecdsa-key-%Y%m%d", &tm);
1361 strftime(*state->commentptr, 30, "rsa-key-%Y%m%d", &tm);
1365 * Now update the key controls with all the key data.
1370 * Blank passphrase, initially. This isn't dangerous,
1371 * because we will warn (Are You Sure?) before allowing
1372 * the user to save an unprotected private key.
1374 SetDlgItemText(hwnd, IDC_PASSPHRASE1EDIT, "");
1375 SetDlgItemText(hwnd, IDC_PASSPHRASE2EDIT, "");
1379 SetDlgItemText(hwnd, IDC_COMMENTEDIT, *state->commentptr);
1381 * Set the key fingerprint.
1383 savecomment = *state->commentptr;
1384 *state->commentptr = NULL;
1387 fp = state->ssh2key.alg->fingerprint(state->ssh2key.data);
1388 SetDlgItemText(hwnd, IDC_FINGERPRINT, fp);
1392 rsa_fingerprint(buf, sizeof(buf), &state->key);
1393 SetDlgItemText(hwnd, IDC_FINGERPRINT, buf);
1395 *state->commentptr = savecomment;
1397 * Construct a decimal representation of the key, for
1398 * pasting into .ssh/authorized_keys or
1399 * .ssh/authorized_keys2 on a Unix box.
1402 setupbigedit2(hwnd, IDC_KEYDISPLAY,
1403 IDC_PKSTATIC, &state->ssh2key);
1405 setupbigedit1(hwnd, IDC_KEYDISPLAY,
1406 IDC_PKSTATIC, &state->key);
1410 * Finally, hide the progress bar and show the key data.
1412 ui_set_state(hwnd, state, 2);
1416 int id = ((LPHELPINFO)lParam)->iCtrlId;
1419 case IDC_GENERATING:
1423 topic = WINHELP_CTX_puttygen_generate; break;
1425 case IDC_KEYDISPLAY:
1426 topic = WINHELP_CTX_puttygen_pastekey; break;
1428 case IDC_FINGERPRINT:
1429 topic = WINHELP_CTX_puttygen_fingerprint; break;
1430 case IDC_COMMENTSTATIC:
1431 case IDC_COMMENTEDIT:
1432 topic = WINHELP_CTX_puttygen_comment; break;
1433 case IDC_PASSPHRASE1STATIC:
1434 case IDC_PASSPHRASE1EDIT:
1435 case IDC_PASSPHRASE2STATIC:
1436 case IDC_PASSPHRASE2EDIT:
1437 topic = WINHELP_CTX_puttygen_passphrase; break;
1438 case IDC_LOADSTATIC:
1440 topic = WINHELP_CTX_puttygen_load; break;
1441 case IDC_SAVESTATIC:
1443 topic = WINHELP_CTX_puttygen_savepriv; break;
1445 topic = WINHELP_CTX_puttygen_savepub; break;
1446 case IDC_TYPESTATIC:
1448 case IDC_KEYSSH2RSA:
1449 case IDC_KEYSSH2DSA:
1450 case IDC_KEYSSH2ECDSA:
1451 topic = WINHELP_CTX_puttygen_keytype; break;
1452 case IDC_BITSSTATIC:
1454 topic = WINHELP_CTX_puttygen_bits; break;
1456 case IDC_EXPORT_OPENSSH:
1457 case IDC_EXPORT_SSHCOM:
1458 topic = WINHELP_CTX_puttygen_conversions; break;
1461 launch_help(hwnd, topic);
1468 state = (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
1477 void cleanup_exit(int code)
1483 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
1489 InitCommonControls();
1494 * See if we can find our Help file.
1498 split_into_argv(cmdline, &argc, &argv, NULL);
1501 if (!strcmp(argv[0], "-pgpfp")) {
1506 * Assume the first argument to be a private key file, and
1507 * attempt to load it.
1509 cmdline_keyfile = argv[0];
1514 ret = DialogBox(hinst, MAKEINTRESOURCE(201), NULL, MainDlgProc) != IDOK;
1517 return ret; /* just in case optimiser complains */