2 * PuTTY key generation front end.
11 #define PUTTY_DO_GLOBALS
17 #define WM_DONEKEY (WM_XUSER + 1)
19 #define DEFAULT_KEYSIZE 1024
21 static int requested_help
;
23 /* ----------------------------------------------------------------------
24 * Progress report code. This is really horrible :-)
26 #define PROGRESSRANGE 65535
32 unsigned startpoint
, total
;
33 unsigned param
, current
, n
; /* if exponential */
34 unsigned mult
; /* if linear */
36 unsigned total
, divisor
, range
;
40 static void progress_update(void *param
, int action
, int phase
, int iprogress
)
42 struct progress
*p
= (struct progress
*) param
;
43 unsigned progress
= iprogress
;
46 if (action
< PROGFN_READY
&& p
->nphases
< phase
)
49 case PROGFN_INITIALISE
:
52 case PROGFN_LIN_PHASE
:
53 p
->phases
[phase
-1].exponential
= 0;
54 p
->phases
[phase
-1].mult
= p
->phases
[phase
].total
/ progress
;
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;
62 case PROGFN_PHASE_EXTENT
:
63 p
->phases
[phase
-1].total
= progress
;
69 for (i
= 0; i
< p
->nphases
; i
++) {
70 p
->phases
[i
].startpoint
= total
;
71 total
+= p
->phases
[i
].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
));
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;
86 position
= (p
->phases
[phase
-1].startpoint
+
87 p
->phases
[phase
-1].total
- p
->phases
[phase
-1].current
);
89 position
= (p
->phases
[phase
-1].startpoint
+
90 progress
* p
->phases
[phase
-1].mult
);
92 SendMessage(p
->progbar
, PBM_SETPOS
, position
/ p
->divisor
, 0);
99 #define PASSPHRASE_MAXLEN 512
101 struct PassphraseProcStruct
{
107 * Dialog-box function for the passphrase box.
109 static int CALLBACK
PassphraseProc(HWND hwnd
, UINT msg
,
110 WPARAM wParam
, LPARAM lParam
)
112 static char *passphrase
= NULL
;
113 struct PassphraseProcStruct
*p
;
117 SetForegroundWindow(hwnd
);
118 SetWindowPos(hwnd
, HWND_TOP
, 0, 0, 0, 0,
119 SWP_NOMOVE
| SWP_NOSIZE
| SWP_SHOWWINDOW
);
124 { /* centre the window */
128 hw
= GetDesktopWindow();
129 if (GetWindowRect(hw
, &rs
) && GetWindowRect(hwnd
, &rd
))
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
);
136 p
= (struct PassphraseProcStruct
*) lParam
;
137 passphrase
= p
->passphrase
;
139 SetDlgItemText(hwnd
, 101, p
->comment
);
141 SetDlgItemText(hwnd
, 102, passphrase
);
144 switch (LOWORD(wParam
)) {
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';
171 * Prompt for a key file. Assumes the filename buffer is of size
174 static int prompt_keyfile(HWND hwnd
, char *dlgtitle
,
175 char *filename
, int save
, int ppk
)
178 memset(&of
, 0, sizeof(of
));
179 #ifdef OPENFILENAME_SIZE_VERSION_400
180 of
.lStructSize
= OPENFILENAME_SIZE_VERSION_400
;
182 of
.lStructSize
= sizeof(of
);
186 of
.lpstrFilter
= "PuTTY Private Key Files\0*.PPK\0All Files\0*\0\0\0";
187 of
.lpstrDefExt
= ".ppk";
189 of
.lpstrFilter
= "All Files\0*\0\0\0";
191 of
.lpstrCustomFilter
= NULL
;
193 of
.lpstrFile
= filename
;
195 of
.nMaxFile
= FILENAME_MAX
;
196 of
.lpstrFileTitle
= NULL
;
197 of
.lpstrInitialDir
= NULL
;
198 of
.lpstrTitle
= dlgtitle
;
201 return GetSaveFileName(&of
);
203 return GetOpenFileName(&of
);
207 * This function is needed to link with the DES code. We need not
208 * have it do anything at all.
210 void logevent(char *msg
)
215 * Dialog-box function for the Licence box.
217 static int CALLBACK
LicenceProc(HWND hwnd
, UINT msg
,
218 WPARAM wParam
, LPARAM lParam
)
225 { /* centre the window */
229 hw
= GetDesktopWindow();
230 if (GetWindowRect(hw
, &rs
) && GetWindowRect(hwnd
, &rd
))
232 (rs
.right
+ rs
.left
+ rd
.left
- rd
.right
) / 2,
233 (rs
.bottom
+ rs
.top
+ rd
.top
- rd
.bottom
) / 2,
234 rd
.right
- rd
.left
, rd
.bottom
- rd
.top
, TRUE
);
239 switch (LOWORD(wParam
)) {
253 * Dialog-box function for the About box.
255 static int CALLBACK
AboutProc(HWND hwnd
, UINT msg
,
256 WPARAM wParam
, LPARAM lParam
)
263 { /* centre the window */
267 hw
= GetDesktopWindow();
268 if (GetWindowRect(hw
, &rs
) && GetWindowRect(hwnd
, &rd
))
270 (rs
.right
+ rs
.left
+ rd
.left
- rd
.right
) / 2,
271 (rs
.bottom
+ rs
.top
+ rd
.top
- rd
.bottom
) / 2,
272 rd
.right
- rd
.left
, rd
.bottom
- rd
.top
, TRUE
);
275 SetDlgItemText(hwnd
, 100, ver
);
278 switch (LOWORD(wParam
)) {
283 EnableWindow(hwnd
, 0);
284 DialogBox(hinst
, MAKEINTRESOURCE(214), NULL
, LicenceProc
);
285 EnableWindow(hwnd
, 1);
286 SetActiveWindow(hwnd
);
298 * Thread to generate a key.
300 struct rsa_key_thread_params
{
301 HWND progressbar
; /* notify this with progress */
302 HWND dialog
; /* notify this on completion */
303 int keysize
; /* bits in key */
306 struct dss_key
*dsskey
;
308 static DWORD WINAPI
generate_rsa_key_thread(void *param
)
310 struct rsa_key_thread_params
*params
=
311 (struct rsa_key_thread_params
*) param
;
312 struct progress prog
;
313 prog
.progbar
= params
->progressbar
;
315 progress_update(&prog
, PROGFN_INITIALISE
, 0, 0);
318 dsa_generate(params
->dsskey
, params
->keysize
, progress_update
, &prog
);
320 rsa_generate(params
->key
, params
->keysize
, progress_update
, &prog
);
322 PostMessage(params
->dialog
, WM_DONEKEY
, 0, 0);
328 struct MainDlgState
{
329 int collecting_entropy
;
330 int generation_thread_exists
;
332 int entropy_got
, entropy_required
, entropy_size
;
335 char **commentptr
; /* points to key.comment or ssh2key.comment */
336 struct ssh2_userkey ssh2key
;
339 struct dss_key dsskey
;
340 HMENU filemenu
, keymenu
, cvtmenu
;
343 static void hidemany(HWND hwnd
, const int *ids
, int hideit
)
346 ShowWindow(GetDlgItem(hwnd
, *ids
++), (hideit ? SW_HIDE
: SW_SHOW
));
350 static void setupbigedit1(HWND hwnd
, int id
, int idstatic
, struct RSAKey
*key
)
355 dec1
= bignum_decimal(key
->exponent
);
356 dec2
= bignum_decimal(key
->modulus
);
357 buffer
= smalloc(strlen(dec1
) + strlen(dec2
) +
358 strlen(key
->comment
) + 30);
359 sprintf(buffer
, "%d %s %s %s",
360 bignum_bitcount(key
->modulus
), dec1
, dec2
, key
->comment
);
361 SetDlgItemText(hwnd
, id
, buffer
);
362 SetDlgItemText(hwnd
, idstatic
,
363 "&Public key for pasting into authorized_keys file:");
369 static void setupbigedit2(HWND hwnd
, int id
, int idstatic
,
370 struct ssh2_userkey
*key
)
372 unsigned char *pub_blob
;
377 pub_blob
= key
->alg
->public_blob(key
->data
, &pub_len
);
378 buffer
= smalloc(strlen(key
->alg
->name
) + 4 * ((pub_len
+ 2) / 3) +
379 strlen(key
->comment
) + 3);
380 strcpy(buffer
, key
->alg
->name
);
381 p
= buffer
+ strlen(buffer
);
384 while (i
< pub_len
) {
385 int n
= (pub_len
- i
< 3 ? pub_len
- i
: 3);
386 base64_encode_atom(pub_blob
+ i
, n
, p
);
391 strcpy(p
, key
->comment
);
392 SetDlgItemText(hwnd
, id
, buffer
);
393 SetDlgItemText(hwnd
, idstatic
, "&Public key for pasting into "
394 "OpenSSH authorized_keys2 file:");
399 static int save_ssh1_pubkey(char *filename
, struct RSAKey
*key
)
404 dec1
= bignum_decimal(key
->exponent
);
405 dec2
= bignum_decimal(key
->modulus
);
406 fp
= fopen(filename
, "wb");
409 fprintf(fp
, "%d %s %s %s\n",
410 bignum_bitcount(key
->modulus
), dec1
, dec2
, key
->comment
);
418 * Warn about the obsolescent key file format.
420 void old_keyfile_warning(void)
422 static const char mbtitle
[] = "PuTTY Key File Warning";
423 static const char message
[] =
424 "You are loading an SSH 2 private key which has an\n"
425 "old version of the file format. This means your key\n"
426 "file is not fully tamperproof. Future versions of\n"
427 "PuTTY may stop supporting this private key format,\n"
428 "so we recommend you convert your key to the new\n"
431 "Once the key is loaded into PuTTYgen, you can perform\n"
432 "this conversion simply by saving it again.";
434 MessageBox(NULL
, message
, mbtitle
, MB_OK
);
437 static int save_ssh2_pubkey(char *filename
, struct ssh2_userkey
*key
)
439 unsigned char *pub_blob
;
445 pub_blob
= key
->alg
->public_blob(key
->data
, &pub_len
);
447 fp
= fopen(filename
, "wb");
451 fprintf(fp
, "---- BEGIN SSH2 PUBLIC KEY ----\n");
453 fprintf(fp
, "Comment: \"");
454 for (p
= key
->comment
; *p
; p
++) {
455 if (*p
== '\\' || *p
== '\"')
463 while (i
< pub_len
) {
465 int n
= (pub_len
- i
< 3 ? pub_len
- i
: 3);
466 base64_encode_atom(pub_blob
+ i
, n
, buf
);
470 if (++column
>= 16) {
478 fprintf(fp
, "---- END SSH2 PUBLIC KEY ----\n");
485 controlidstart
= 100,
492 IDC_PKSTATIC
, IDC_KEYDISPLAY
,
493 IDC_FPSTATIC
, IDC_FINGERPRINT
,
494 IDC_COMMENTSTATIC
, IDC_COMMENTEDIT
,
495 IDC_PASSPHRASE1STATIC
, IDC_PASSPHRASE1EDIT
,
496 IDC_PASSPHRASE2STATIC
, IDC_PASSPHRASE2EDIT
,
498 IDC_GENSTATIC
, IDC_GENERATE
,
499 IDC_LOADSTATIC
, IDC_LOAD
,
500 IDC_SAVESTATIC
, IDC_SAVE
, IDC_SAVEPUB
,
502 IDC_TYPESTATIC
, IDC_KEYSSH1
, IDC_KEYSSH2RSA
, IDC_KEYSSH2DSA
,
503 IDC_BITSSTATIC
, IDC_BITS
,
506 IDC_IMPORT
, IDC_EXPORT_OPENSSH
, IDC_EXPORT_SSHCOM
509 static const int nokey_ids
[] = { IDC_NOKEY
, 0 };
510 static const int generating_ids
[] = { IDC_GENERATING
, IDC_PROGRESS
, 0 };
511 static const int gotkey_ids
[] = {
512 IDC_PKSTATIC
, IDC_KEYDISPLAY
,
513 IDC_FPSTATIC
, IDC_FINGERPRINT
,
514 IDC_COMMENTSTATIC
, IDC_COMMENTEDIT
,
515 IDC_PASSPHRASE1STATIC
, IDC_PASSPHRASE1EDIT
,
516 IDC_PASSPHRASE2STATIC
, IDC_PASSPHRASE2EDIT
, 0
520 * Small UI helper function to switch the state of the main dialog
521 * by enabling and disabling controls and menu items.
523 void ui_set_state(HWND hwnd
, struct MainDlgState
*state
, int status
)
529 hidemany(hwnd
, nokey_ids
, FALSE
);
530 hidemany(hwnd
, generating_ids
, TRUE
);
531 hidemany(hwnd
, gotkey_ids
, TRUE
);
532 EnableWindow(GetDlgItem(hwnd
, IDC_GENERATE
), 1);
533 EnableWindow(GetDlgItem(hwnd
, IDC_LOAD
), 1);
534 EnableWindow(GetDlgItem(hwnd
, IDC_SAVE
), 0);
535 EnableWindow(GetDlgItem(hwnd
, IDC_SAVEPUB
), 0);
536 EnableWindow(GetDlgItem(hwnd
, IDC_KEYSSH1
), 1);
537 EnableWindow(GetDlgItem(hwnd
, IDC_KEYSSH2RSA
), 1);
538 EnableWindow(GetDlgItem(hwnd
, IDC_KEYSSH2DSA
), 1);
539 EnableWindow(GetDlgItem(hwnd
, IDC_BITS
), 1);
540 EnableMenuItem(state
->filemenu
, IDC_LOAD
, MF_ENABLED
|MF_BYCOMMAND
);
541 EnableMenuItem(state
->filemenu
, IDC_SAVE
, MF_GRAYED
|MF_BYCOMMAND
);
542 EnableMenuItem(state
->filemenu
, IDC_SAVEPUB
, MF_GRAYED
|MF_BYCOMMAND
);
543 EnableMenuItem(state
->keymenu
, IDC_GENERATE
, MF_ENABLED
|MF_BYCOMMAND
);
544 EnableMenuItem(state
->keymenu
, IDC_KEYSSH1
, MF_ENABLED
|MF_BYCOMMAND
);
545 EnableMenuItem(state
->keymenu
, IDC_KEYSSH2RSA
, MF_ENABLED
|MF_BYCOMMAND
);
546 EnableMenuItem(state
->keymenu
, IDC_KEYSSH2DSA
, MF_ENABLED
|MF_BYCOMMAND
);
547 EnableMenuItem(state
->cvtmenu
, IDC_IMPORT
, MF_ENABLED
|MF_BYCOMMAND
);
548 EnableMenuItem(state
->cvtmenu
, IDC_EXPORT_OPENSSH
,
549 MF_GRAYED
|MF_BYCOMMAND
);
550 EnableMenuItem(state
->cvtmenu
, IDC_EXPORT_SSHCOM
,
551 MF_GRAYED
|MF_BYCOMMAND
);
553 case 1: /* generating key */
554 hidemany(hwnd
, nokey_ids
, TRUE
);
555 hidemany(hwnd
, generating_ids
, FALSE
);
556 hidemany(hwnd
, gotkey_ids
, TRUE
);
557 EnableWindow(GetDlgItem(hwnd
, IDC_GENERATE
), 0);
558 EnableWindow(GetDlgItem(hwnd
, IDC_LOAD
), 0);
559 EnableWindow(GetDlgItem(hwnd
, IDC_SAVE
), 0);
560 EnableWindow(GetDlgItem(hwnd
, IDC_SAVEPUB
), 0);
561 EnableWindow(GetDlgItem(hwnd
, IDC_KEYSSH1
), 0);
562 EnableWindow(GetDlgItem(hwnd
, IDC_KEYSSH2RSA
), 0);
563 EnableWindow(GetDlgItem(hwnd
, IDC_KEYSSH2DSA
), 0);
564 EnableWindow(GetDlgItem(hwnd
, IDC_BITS
), 0);
565 EnableMenuItem(state
->filemenu
, IDC_LOAD
, MF_GRAYED
|MF_BYCOMMAND
);
566 EnableMenuItem(state
->filemenu
, IDC_SAVE
, MF_GRAYED
|MF_BYCOMMAND
);
567 EnableMenuItem(state
->filemenu
, IDC_SAVEPUB
, MF_GRAYED
|MF_BYCOMMAND
);
568 EnableMenuItem(state
->keymenu
, IDC_GENERATE
, MF_GRAYED
|MF_BYCOMMAND
);
569 EnableMenuItem(state
->keymenu
, IDC_KEYSSH1
, MF_GRAYED
|MF_BYCOMMAND
);
570 EnableMenuItem(state
->keymenu
, IDC_KEYSSH2RSA
, MF_GRAYED
|MF_BYCOMMAND
);
571 EnableMenuItem(state
->keymenu
, IDC_KEYSSH2DSA
, MF_GRAYED
|MF_BYCOMMAND
);
572 EnableMenuItem(state
->cvtmenu
, IDC_IMPORT
, MF_GRAYED
|MF_BYCOMMAND
);
573 EnableMenuItem(state
->cvtmenu
, IDC_EXPORT_OPENSSH
,
574 MF_GRAYED
|MF_BYCOMMAND
);
575 EnableMenuItem(state
->cvtmenu
, IDC_EXPORT_SSHCOM
,
576 MF_GRAYED
|MF_BYCOMMAND
);
579 hidemany(hwnd
, nokey_ids
, TRUE
);
580 hidemany(hwnd
, generating_ids
, TRUE
);
581 hidemany(hwnd
, gotkey_ids
, FALSE
);
582 EnableWindow(GetDlgItem(hwnd
, IDC_GENERATE
), 1);
583 EnableWindow(GetDlgItem(hwnd
, IDC_LOAD
), 1);
584 EnableWindow(GetDlgItem(hwnd
, IDC_SAVE
), 1);
585 EnableWindow(GetDlgItem(hwnd
, IDC_SAVEPUB
), 1);
586 EnableWindow(GetDlgItem(hwnd
, IDC_KEYSSH1
), 1);
587 EnableWindow(GetDlgItem(hwnd
, IDC_KEYSSH2RSA
), 1);
588 EnableWindow(GetDlgItem(hwnd
, IDC_KEYSSH2DSA
), 1);
589 EnableWindow(GetDlgItem(hwnd
, IDC_BITS
), 1);
590 EnableMenuItem(state
->filemenu
, IDC_LOAD
, MF_ENABLED
|MF_BYCOMMAND
);
591 EnableMenuItem(state
->filemenu
, IDC_SAVE
, MF_ENABLED
|MF_BYCOMMAND
);
592 EnableMenuItem(state
->filemenu
, IDC_SAVEPUB
, MF_ENABLED
|MF_BYCOMMAND
);
593 EnableMenuItem(state
->keymenu
, IDC_GENERATE
, MF_ENABLED
|MF_BYCOMMAND
);
594 EnableMenuItem(state
->keymenu
, IDC_KEYSSH1
, MF_ENABLED
|MF_BYCOMMAND
);
595 EnableMenuItem(state
->keymenu
, IDC_KEYSSH2RSA
,MF_ENABLED
|MF_BYCOMMAND
);
596 EnableMenuItem(state
->keymenu
, IDC_KEYSSH2DSA
,MF_ENABLED
|MF_BYCOMMAND
);
597 EnableMenuItem(state
->cvtmenu
, IDC_IMPORT
, MF_ENABLED
|MF_BYCOMMAND
);
599 * Enable export menu items if and only if the key type
600 * supports this kind of export.
602 type
= state
->ssh2 ? SSH_KEYTYPE_SSH2
: SSH_KEYTYPE_SSH1
;
603 #define do_export_menuitem(x,y) \
604 EnableMenuItem(state->cvtmenu, x, MF_BYCOMMAND | \
605 (import_target_type(y)==type?MF_ENABLED:MF_GRAYED))
606 do_export_menuitem(IDC_EXPORT_OPENSSH
, SSH_KEYTYPE_OPENSSH
);
607 do_export_menuitem(IDC_EXPORT_SSHCOM
, SSH_KEYTYPE_SSHCOM
);
608 #undef do_export_menuitem
614 * Dialog-box function for the main PuTTYgen dialog box.
616 static int CALLBACK
MainDlgProc(HWND hwnd
, UINT msg
,
617 WPARAM wParam
, LPARAM lParam
)
619 static const char generating_msg
[] =
620 "Please wait while a key is generated...";
621 static const char entropy_msg
[] =
622 "Please generate some randomness by moving the mouse over the blank area.";
623 struct MainDlgState
*state
;
628 SetWindowLong(hwnd
, GWL_EXSTYLE
,
629 GetWindowLong(hwnd
, GWL_EXSTYLE
) | WS_EX_CONTEXTHELP
);
632 * If we add a Help button, this is where we destroy it
633 * if the help file isn't present.
636 requested_help
= FALSE
;
638 state
= smalloc(sizeof(*state
));
639 state
->generation_thread_exists
= FALSE
;
640 state
->collecting_entropy
= FALSE
;
641 state
->entropy
= NULL
;
642 state
->key_exists
= FALSE
;
643 SetWindowLong(hwnd
, GWL_USERDATA
, (LONG
) state
);
649 menu1
= CreateMenu();
650 AppendMenu(menu1
, MF_ENABLED
, IDC_LOAD
, "&Load private key");
651 AppendMenu(menu1
, MF_ENABLED
, IDC_SAVEPUB
, "Save p&ublic key");
652 AppendMenu(menu1
, MF_ENABLED
, IDC_SAVE
, "&Save private key");
653 AppendMenu(menu1
, MF_SEPARATOR
, 0, 0);
654 AppendMenu(menu1
, MF_ENABLED
, IDC_QUIT
, "E&xit");
655 AppendMenu(menu
, MF_POPUP
| MF_ENABLED
, (UINT
) menu1
, "&File");
656 state
->filemenu
= menu1
;
658 menu1
= CreateMenu();
659 AppendMenu(menu1
, MF_ENABLED
, IDC_GENERATE
, "&Generate key pair");
660 AppendMenu(menu1
, MF_SEPARATOR
, 0, 0);
661 AppendMenu(menu1
, MF_ENABLED
, IDC_KEYSSH1
, "SSH&1 key (RSA)");
662 AppendMenu(menu1
, MF_ENABLED
, IDC_KEYSSH2RSA
, "SSH2 &RSA key");
663 AppendMenu(menu1
, MF_ENABLED
, IDC_KEYSSH2DSA
, "SSH2 &DSA key");
664 AppendMenu(menu
, MF_POPUP
| MF_ENABLED
, (UINT
) menu1
, "&Key");
665 state
->keymenu
= menu1
;
667 menu1
= CreateMenu();
668 AppendMenu(menu1
, MF_ENABLED
, IDC_IMPORT
, "&Import key");
669 AppendMenu(menu1
, MF_SEPARATOR
, 0, 0);
670 AppendMenu(menu1
, MF_ENABLED
, IDC_EXPORT_OPENSSH
,
671 "Export &OpenSSH key");
672 AppendMenu(menu1
, MF_ENABLED
, IDC_EXPORT_SSHCOM
,
673 "Export &ssh.com key");
674 AppendMenu(menu
, MF_POPUP
| MF_ENABLED
, (UINT
) menu1
,
676 state
->cvtmenu
= menu1
;
678 menu1
= CreateMenu();
679 AppendMenu(menu1
, MF_ENABLED
, IDC_ABOUT
, "&About");
681 AppendMenu(menu1
, MF_ENABLED
, IDC_GIVEHELP
, "&Help");
682 AppendMenu(menu
, MF_POPUP
| MF_ENABLED
, (UINT
) menu1
, "&Help");
690 { /* centre the window */
694 hw
= GetDesktopWindow();
695 if (GetWindowRect(hw
, &rs
) && GetWindowRect(hwnd
, &rd
))
697 (rs
.right
+ rs
.left
+ rd
.left
- rd
.right
) / 2,
698 (rs
.bottom
+ rs
.top
+ rd
.top
- rd
.bottom
) / 2,
699 rd
.right
- rd
.left
, rd
.bottom
- rd
.top
, TRUE
);
703 struct ctlpos cp
, cp2
;
705 /* Accelerators used: acglops1rbd */
707 ctlposinit(&cp
, hwnd
, 4, 4, 4);
708 beginbox(&cp
, "Key", IDC_BOX_KEY
);
710 statictext(&cp2
, "No key.", 1, IDC_NOKEY
);
712 statictext(&cp2
, "", 1, IDC_GENERATING
);
713 progressbar(&cp2
, IDC_PROGRESS
);
715 "&Public key for pasting into authorized_keys file:",
716 IDC_PKSTATIC
, IDC_KEYDISPLAY
, 5);
717 SendDlgItemMessage(hwnd
, IDC_KEYDISPLAY
, EM_SETREADONLY
, 1, 0);
718 staticedit(&cp
, "Key fingerprint:", IDC_FPSTATIC
,
719 IDC_FINGERPRINT
, 75);
720 SendDlgItemMessage(hwnd
, IDC_FINGERPRINT
, EM_SETREADONLY
, 1,
722 staticedit(&cp
, "Key &comment:", IDC_COMMENTSTATIC
,
723 IDC_COMMENTEDIT
, 75);
724 staticpassedit(&cp
, "Key p&assphrase:", IDC_PASSPHRASE1STATIC
,
725 IDC_PASSPHRASE1EDIT
, 75);
726 staticpassedit(&cp
, "C&onfirm passphrase:",
727 IDC_PASSPHRASE2STATIC
, IDC_PASSPHRASE2EDIT
, 75);
729 beginbox(&cp
, "Actions", IDC_BOX_ACTIONS
);
730 staticbtn(&cp
, "Generate a public/private key pair",
731 IDC_GENSTATIC
, "&Generate", IDC_GENERATE
);
732 staticbtn(&cp
, "Load an existing private key file",
733 IDC_LOADSTATIC
, "&Load", IDC_LOAD
);
734 static2btn(&cp
, "Save the generated key", IDC_SAVESTATIC
,
735 "Save p&ublic key", IDC_SAVEPUB
,
736 "&Save private key", IDC_SAVE
);
738 beginbox(&cp
, "Parameters", IDC_BOX_PARAMS
);
739 radioline(&cp
, "Type of key to generate:", IDC_TYPESTATIC
, 3,
740 "SSH&1 (RSA)", IDC_KEYSSH1
,
741 "SSH2 &RSA", IDC_KEYSSH2RSA
,
742 "SSH2 &DSA", IDC_KEYSSH2DSA
, NULL
);
743 staticedit(&cp
, "Number of &bits in a generated key:",
744 IDC_BITSSTATIC
, IDC_BITS
, 20);
747 CheckRadioButton(hwnd
, IDC_KEYSSH1
, IDC_KEYSSH2DSA
, IDC_KEYSSH1
);
748 CheckMenuRadioItem(state
->keymenu
, IDC_KEYSSH1
, IDC_KEYSSH2DSA
,
749 IDC_KEYSSH1
, MF_BYCOMMAND
);
750 SetDlgItemInt(hwnd
, IDC_BITS
, DEFAULT_KEYSIZE
, FALSE
);
753 * Initially, hide the progress bar and the key display,
754 * and show the no-key display. Also disable the Save
755 * buttons, because with no key we obviously can't save
758 ui_set_state(hwnd
, state
, 0);
762 state
= (struct MainDlgState
*) GetWindowLong(hwnd
, GWL_USERDATA
);
763 if (state
->collecting_entropy
&&
764 state
->entropy
&& state
->entropy_got
< state
->entropy_required
) {
765 state
->entropy
[state
->entropy_got
++] = lParam
;
766 state
->entropy
[state
->entropy_got
++] = GetMessageTime();
767 SendDlgItemMessage(hwnd
, IDC_PROGRESS
, PBM_SETPOS
,
768 state
->entropy_got
, 0);
769 if (state
->entropy_got
>= state
->entropy_required
) {
770 struct rsa_key_thread_params
*params
;
774 * Seed the entropy pool
776 random_add_heavynoise(state
->entropy
, state
->entropy_size
);
777 memset(state
->entropy
, 0, state
->entropy_size
);
778 sfree(state
->entropy
);
779 state
->collecting_entropy
= FALSE
;
781 SetDlgItemText(hwnd
, IDC_GENERATING
, generating_msg
);
782 SendDlgItemMessage(hwnd
, IDC_PROGRESS
, PBM_SETRANGE
, 0,
783 MAKELPARAM(0, PROGRESSRANGE
));
784 SendDlgItemMessage(hwnd
, IDC_PROGRESS
, PBM_SETPOS
, 0, 0);
786 params
= smalloc(sizeof(*params
));
787 params
->progressbar
= GetDlgItem(hwnd
, IDC_PROGRESS
);
788 params
->dialog
= hwnd
;
789 params
->keysize
= state
->keysize
;
790 params
->is_dsa
= state
->is_dsa
;
791 params
->key
= &state
->key
;
792 params
->dsskey
= &state
->dsskey
;
794 if (!CreateThread(NULL
, 0, generate_rsa_key_thread
,
795 params
, 0, &threadid
)) {
796 MessageBox(hwnd
, "Out of thread resources",
797 "Key generation error",
798 MB_OK
| MB_ICONERROR
);
801 state
->generation_thread_exists
= TRUE
;
807 switch (LOWORD(wParam
)) {
812 state
= (struct MainDlgState
*)
813 GetWindowLong(hwnd
, GWL_USERDATA
);
814 if (!IsDlgButtonChecked(hwnd
, LOWORD(wParam
)))
815 CheckRadioButton(hwnd
, IDC_KEYSSH1
, IDC_KEYSSH2DSA
,
817 CheckMenuRadioItem(state
->keymenu
, IDC_KEYSSH1
, IDC_KEYSSH2DSA
,
818 LOWORD(wParam
), MF_BYCOMMAND
);
822 PostMessage(hwnd
, WM_CLOSE
, 0, 0);
824 case IDC_COMMENTEDIT
:
825 if (HIWORD(wParam
) == EN_CHANGE
) {
826 state
= (struct MainDlgState
*)
827 GetWindowLong(hwnd
, GWL_USERDATA
);
828 if (state
->key_exists
) {
829 HWND editctl
= GetDlgItem(hwnd
, IDC_COMMENTEDIT
);
830 int len
= GetWindowTextLength(editctl
);
831 if (*state
->commentptr
)
832 sfree(*state
->commentptr
);
833 *state
->commentptr
= smalloc(len
+ 1);
834 GetWindowText(editctl
, *state
->commentptr
, len
+ 1);
836 setupbigedit2(hwnd
, IDC_KEYDISPLAY
, IDC_PKSTATIC
,
839 setupbigedit1(hwnd
, IDC_KEYDISPLAY
, IDC_PKSTATIC
,
846 EnableWindow(hwnd
, 0);
847 DialogBox(hinst
, MAKEINTRESOURCE(213), NULL
, AboutProc
);
848 EnableWindow(hwnd
, 1);
849 SetActiveWindow(hwnd
);
852 if (HIWORD(wParam
) == BN_CLICKED
||
853 HIWORD(wParam
) == BN_DOUBLECLICKED
) {
855 WinHelp(hwnd
, help_path
, HELP_COMMAND
,
856 (DWORD
)"JI(`',`puttygen.general')");
857 requested_help
= TRUE
;
863 (struct MainDlgState
*) GetWindowLong(hwnd
, GWL_USERDATA
);
864 if (!state
->generation_thread_exists
) {
866 state
->keysize
= GetDlgItemInt(hwnd
, IDC_BITS
, &ok
, FALSE
);
868 state
->keysize
= DEFAULT_KEYSIZE
;
869 /* If we ever introduce a new key type, check it here! */
870 state
->ssh2
= !IsDlgButtonChecked(hwnd
, IDC_KEYSSH1
);
871 state
->is_dsa
= IsDlgButtonChecked(hwnd
, IDC_KEYSSH2DSA
);
872 if (state
->keysize
< 256) {
873 int ret
= MessageBox(hwnd
,
874 "PuTTYgen will not generate a key"
875 " smaller than 256 bits.\n"
876 "Key length reset to 256. Continue?",
878 MB_ICONWARNING
| MB_OKCANCEL
);
881 state
->keysize
= 256;
882 SetDlgItemInt(hwnd
, IDC_BITS
, 256, FALSE
);
884 ui_set_state(hwnd
, state
, 1);
885 SetDlgItemText(hwnd
, IDC_GENERATING
, entropy_msg
);
886 state
->key_exists
= FALSE
;
887 state
->collecting_entropy
= TRUE
;
890 * My brief statistical tests on mouse movements
891 * suggest that there are about 2.5 bits of
892 * randomness in the x position, 2.5 in the y
893 * position, and 1.7 in the message time, making
894 * 5.7 bits of unpredictability per mouse movement.
895 * However, other people have told me it's far less
896 * than that, so I'm going to be stupidly cautious
897 * and knock that down to a nice round 2. With this
898 * method, we require two words per mouse movement,
899 * so with 2 bits per mouse movement we expect 2
900 * bits every 2 words.
902 state
->entropy_required
= (state
->keysize
/ 2) * 2;
903 state
->entropy_got
= 0;
904 state
->entropy_size
= (state
->entropy_required
*
905 sizeof(*state
->entropy
));
906 state
->entropy
= smalloc(state
->entropy_size
);
908 SendDlgItemMessage(hwnd
, IDC_PROGRESS
, PBM_SETRANGE
, 0,
909 MAKELPARAM(0, state
->entropy_required
));
910 SendDlgItemMessage(hwnd
, IDC_PROGRESS
, PBM_SETPOS
, 0, 0);
914 case IDC_EXPORT_OPENSSH
:
915 case IDC_EXPORT_SSHCOM
:
917 (struct MainDlgState
*) GetWindowLong(hwnd
, GWL_USERDATA
);
918 if (state
->key_exists
) {
919 char filename
[FILENAME_MAX
];
920 char passphrase
[PASSPHRASE_MAXLEN
];
921 char passphrase2
[PASSPHRASE_MAXLEN
];
925 realtype
= SSH_KEYTYPE_SSH2
;
927 realtype
= SSH_KEYTYPE_SSH1
;
929 if (LOWORD(wParam
) == IDC_EXPORT_OPENSSH
)
930 type
= SSH_KEYTYPE_OPENSSH
;
931 else if (LOWORD(wParam
) == IDC_EXPORT_SSHCOM
)
932 type
= SSH_KEYTYPE_SSHCOM
;
936 if (type
!= realtype
&&
937 import_target_type(type
) != realtype
) {
939 sprintf(msg
, "Cannot export an SSH%d key in an SSH%d"
940 " format", (state
->ssh2 ?
2 : 1),
941 (state
->ssh2 ?
1 : 2));
942 MessageBox(hwnd
, msg
,
943 "PuTTYgen Error", MB_OK
| MB_ICONERROR
);
947 GetDlgItemText(hwnd
, IDC_PASSPHRASE1EDIT
,
948 passphrase
, sizeof(passphrase
));
949 GetDlgItemText(hwnd
, IDC_PASSPHRASE2EDIT
,
950 passphrase2
, sizeof(passphrase2
));
951 if (strcmp(passphrase
, passphrase2
)) {
953 "The two passphrases given do not match.",
954 "PuTTYgen Error", MB_OK
| MB_ICONERROR
);
959 ret
= MessageBox(hwnd
,
960 "Are you sure you want to save this key\n"
961 "without a passphrase to protect it?",
963 MB_YESNO
| MB_ICONWARNING
);
967 if (prompt_keyfile(hwnd
, "Save private key as:",
968 filename
, 1, (type
== realtype
))) {
970 FILE *fp
= fopen(filename
, "r");
972 char buffer
[FILENAME_MAX
+ 80];
974 sprintf(buffer
, "Overwrite existing file\n%.*s?",
975 FILENAME_MAX
, filename
);
976 ret
= MessageBox(hwnd
, buffer
, "PuTTYgen Warning",
977 MB_YESNO
| MB_ICONWARNING
);
983 if (type
!= realtype
)
984 ret
= export_ssh2(filename
, type
, &state
->ssh2key
,
985 *passphrase ? passphrase
: NULL
);
987 ret
= ssh2_save_userkey(filename
, &state
->ssh2key
,
988 *passphrase ? passphrase
:
991 if (type
!= realtype
)
992 ret
= export_ssh1(filename
, type
, &state
->key
,
993 *passphrase ? passphrase
: NULL
);
995 ret
= saversakey(filename
, &state
->key
,
996 *passphrase ? passphrase
: NULL
);
999 MessageBox(hwnd
, "Unable to save key file",
1000 "PuTTYgen Error", MB_OK
| MB_ICONERROR
);
1007 (struct MainDlgState
*) GetWindowLong(hwnd
, GWL_USERDATA
);
1008 if (state
->key_exists
) {
1009 char filename
[FILENAME_MAX
];
1010 if (prompt_keyfile(hwnd
, "Save public key as:",
1013 FILE *fp
= fopen(filename
, "r");
1015 char buffer
[FILENAME_MAX
+ 80];
1017 sprintf(buffer
, "Overwrite existing file\n%.*s?",
1018 FILENAME_MAX
, filename
);
1019 ret
= MessageBox(hwnd
, buffer
, "PuTTYgen Warning",
1020 MB_YESNO
| MB_ICONWARNING
);
1025 ret
= save_ssh2_pubkey(filename
, &state
->ssh2key
);
1027 ret
= save_ssh1_pubkey(filename
, &state
->key
);
1030 MessageBox(hwnd
, "Unable to save key file",
1031 "PuTTYgen Error", MB_OK
| MB_ICONERROR
);
1039 (struct MainDlgState
*) GetWindowLong(hwnd
, GWL_USERDATA
);
1040 if (!state
->generation_thread_exists
) {
1041 char filename
[FILENAME_MAX
];
1042 if (prompt_keyfile(hwnd
, "Load private key:",
1043 filename
, 0, LOWORD(wParam
)==IDC_LOAD
)) {
1044 char passphrase
[PASSPHRASE_MAXLEN
];
1049 struct PassphraseProcStruct pps
;
1050 struct RSAKey newkey1
;
1051 struct ssh2_userkey
*newkey2
= NULL
;
1053 type
= realtype
= key_type(filename
);
1054 if (type
!= SSH_KEYTYPE_SSH1
&&
1055 type
!= SSH_KEYTYPE_SSH2
&&
1056 !import_possible(type
)) {
1058 sprintf(msg
, "Couldn't load private key (%s)",
1059 key_type_to_str(type
));
1060 MessageBox(NULL
, msg
,
1061 "PuTTYgen Error", MB_OK
| MB_ICONERROR
);
1065 if (type
!= SSH_KEYTYPE_SSH1
&&
1066 type
!= SSH_KEYTYPE_SSH2
) {
1068 type
= import_target_type(type
);
1072 if (realtype
== SSH_KEYTYPE_SSH1
)
1073 needs_pass
= rsakey_encrypted(filename
, &comment
);
1074 else if (realtype
== SSH_KEYTYPE_SSH2
)
1076 ssh2_userkey_encrypted(filename
, &comment
);
1078 needs_pass
= import_encrypted(filename
, realtype
,
1080 pps
.passphrase
= passphrase
;
1081 pps
.comment
= comment
;
1085 dlgret
= DialogBoxParam(hinst
,
1086 MAKEINTRESOURCE(210),
1087 NULL
, PassphraseProc
,
1095 if (type
== SSH_KEYTYPE_SSH1
) {
1096 if (realtype
== type
)
1097 ret
= loadrsakey(filename
, &newkey1
,
1100 ret
= import_ssh1(filename
, realtype
,
1101 &newkey1
, passphrase
);
1103 if (realtype
== type
)
1104 newkey2
= ssh2_load_userkey(filename
,
1107 newkey2
= import_ssh2(filename
, realtype
,
1109 if (newkey2
== SSH2_WRONG_PASSPHRASE
)
1116 } while (ret
== -1);
1120 MessageBox(NULL
, "Couldn't load private key.",
1121 "PuTTYgen Error", MB_OK
| MB_ICONERROR
);
1122 } else if (ret
== 1) {
1124 * Now update the key controls with all the
1128 SetDlgItemText(hwnd
, IDC_PASSPHRASE1EDIT
,
1130 SetDlgItemText(hwnd
, IDC_PASSPHRASE2EDIT
,
1132 if (type
== SSH_KEYTYPE_SSH1
) {
1136 state
->ssh2
= FALSE
;
1137 state
->commentptr
= &state
->key
.comment
;
1138 state
->key
= newkey1
;
1141 * Set the key fingerprint.
1143 savecomment
= state
->key
.comment
;
1144 state
->key
.comment
= NULL
;
1145 rsa_fingerprint(buf
, sizeof(buf
),
1147 state
->key
.comment
= savecomment
;
1149 SetDlgItemText(hwnd
, IDC_FINGERPRINT
, buf
);
1151 * Construct a decimal representation
1152 * of the key, for pasting into
1153 * .ssh/authorized_keys on a Unix box.
1155 setupbigedit1(hwnd
, IDC_KEYDISPLAY
,
1156 IDC_PKSTATIC
, &state
->key
);
1163 &state
->ssh2key
.comment
;
1164 state
->ssh2key
= *newkey2
; /* structure copy */
1167 savecomment
= state
->ssh2key
.comment
;
1168 state
->ssh2key
.comment
= NULL
;
1170 state
->ssh2key
.alg
->
1171 fingerprint(state
->ssh2key
.data
);
1172 state
->ssh2key
.comment
= savecomment
;
1174 SetDlgItemText(hwnd
, IDC_FINGERPRINT
, fp
);
1177 setupbigedit2(hwnd
, IDC_KEYDISPLAY
,
1178 IDC_PKSTATIC
, &state
->ssh2key
);
1180 SetDlgItemText(hwnd
, IDC_COMMENTEDIT
,
1181 *state
->commentptr
);
1184 * Finally, hide the progress bar and show
1187 ui_set_state(hwnd
, state
, 2);
1188 state
->key_exists
= TRUE
;
1191 * If the user has imported a foreign key
1192 * using the Load command, let them know.
1193 * If they've used the Import command, be
1196 if (realtype
!= type
&& LOWORD(wParam
) == IDC_LOAD
) {
1198 sprintf(msg
, "Successfully imported foreign key\n"
1200 "To use this key with PuTTY, you need to\n"
1201 "use the \"Save private key\" command to\n"
1202 "save it in PuTTY's own format.",
1203 key_type_to_str(realtype
));
1204 MessageBox(NULL
, msg
, "PuTTYgen Notice",
1205 MB_OK
| MB_ICONINFORMATION
);
1214 state
= (struct MainDlgState
*) GetWindowLong(hwnd
, GWL_USERDATA
);
1215 state
->generation_thread_exists
= FALSE
;
1216 state
->key_exists
= TRUE
;
1217 SendDlgItemMessage(hwnd
, IDC_PROGRESS
, PBM_SETRANGE
, 0,
1218 MAKELPARAM(0, PROGRESSRANGE
));
1219 SendDlgItemMessage(hwnd
, IDC_PROGRESS
, PBM_SETPOS
, PROGRESSRANGE
, 0);
1221 if (state
->is_dsa
) {
1222 state
->ssh2key
.data
= &state
->dsskey
;
1223 state
->ssh2key
.alg
= &ssh_dss
;
1225 state
->ssh2key
.data
= &state
->key
;
1226 state
->ssh2key
.alg
= &ssh_rsa
;
1228 state
->commentptr
= &state
->ssh2key
.comment
;
1230 state
->commentptr
= &state
->key
.comment
;
1233 * Invent a comment for the key. We'll do this by including
1234 * the date in it. This will be so horrifyingly ugly that
1235 * the user will immediately want to change it, which is
1238 *state
->commentptr
= smalloc(30);
1245 strftime(*state
->commentptr
, 30, "dsa-key-%Y%m%d", tm
);
1247 strftime(*state
->commentptr
, 30, "rsa-key-%Y%m%d", tm
);
1251 * Now update the key controls with all the key data.
1256 * Blank passphrase, initially. This isn't dangerous,
1257 * because we will warn (Are You Sure?) before allowing
1258 * the user to save an unprotected private key.
1260 SetDlgItemText(hwnd
, IDC_PASSPHRASE1EDIT
, "");
1261 SetDlgItemText(hwnd
, IDC_PASSPHRASE2EDIT
, "");
1265 SetDlgItemText(hwnd
, IDC_COMMENTEDIT
, *state
->commentptr
);
1267 * Set the key fingerprint.
1269 savecomment
= *state
->commentptr
;
1270 *state
->commentptr
= NULL
;
1273 fp
= state
->ssh2key
.alg
->fingerprint(state
->ssh2key
.data
);
1274 SetDlgItemText(hwnd
, IDC_FINGERPRINT
, fp
);
1278 rsa_fingerprint(buf
, sizeof(buf
), &state
->key
);
1279 SetDlgItemText(hwnd
, IDC_FINGERPRINT
, buf
);
1281 *state
->commentptr
= savecomment
;
1283 * Construct a decimal representation of the key, for
1284 * pasting into .ssh/authorized_keys or
1285 * .ssh/authorized_keys2 on a Unix box.
1288 setupbigedit2(hwnd
, IDC_KEYDISPLAY
,
1289 IDC_PKSTATIC
, &state
->ssh2key
);
1291 setupbigedit1(hwnd
, IDC_KEYDISPLAY
,
1292 IDC_PKSTATIC
, &state
->key
);
1296 * Finally, hide the progress bar and show the key data.
1298 ui_set_state(hwnd
, state
, 2);
1302 int id
= ((LPHELPINFO
)lParam
)->iCtrlId
;
1305 case IDC_GENERATING
:
1309 cmd
= "JI(`',`puttygen.generate')"; break;
1311 case IDC_KEYDISPLAY
:
1312 cmd
= "JI(`',`puttygen.pastekey')"; break;
1314 case IDC_FINGERPRINT
:
1315 cmd
= "JI(`',`puttygen.fingerprint')"; break;
1316 case IDC_COMMENTSTATIC
:
1317 case IDC_COMMENTEDIT
:
1318 cmd
= "JI(`',`puttygen.comment')"; break;
1319 case IDC_PASSPHRASE1STATIC
:
1320 case IDC_PASSPHRASE1EDIT
:
1321 case IDC_PASSPHRASE2STATIC
:
1322 case IDC_PASSPHRASE2EDIT
:
1323 cmd
= "JI(`',`puttygen.passphrase')"; break;
1324 case IDC_LOADSTATIC
:
1326 cmd
= "JI(`',`puttygen.load')"; break;
1327 case IDC_SAVESTATIC
:
1329 cmd
= "JI(`',`puttygen.savepriv')"; break;
1331 cmd
= "JI(`',`puttygen.savepub')"; break;
1332 case IDC_TYPESTATIC
:
1334 case IDC_KEYSSH2RSA
:
1335 case IDC_KEYSSH2DSA
:
1336 cmd
= "JI(`',`puttygen.keytype')"; break;
1337 case IDC_BITSSTATIC
:
1339 cmd
= "JI(`',`puttygen.bits')"; break;
1341 case IDC_EXPORT_OPENSSH
:
1342 case IDC_EXPORT_SSHCOM
:
1343 cmd
= "JI(`',`puttygen.conversions')"; break;
1346 WinHelp(hwnd
, help_path
, HELP_COMMAND
, (DWORD
)cmd
);
1347 requested_help
= TRUE
;
1354 state
= (struct MainDlgState
*) GetWindowLong(hwnd
, GWL_USERDATA
);
1356 if (requested_help
) {
1357 WinHelp(hwnd
, help_path
, HELP_QUIT
, 0);
1358 requested_help
= FALSE
;
1366 void cleanup_exit(int code
) { exit(code
); }
1368 int WINAPI
WinMain(HINSTANCE inst
, HINSTANCE prev
, LPSTR cmdline
, int show
)
1370 InitCommonControls();
1374 * See if we can find our Help file.
1377 char b
[2048], *p
, *q
, *r
;
1379 GetModuleFileName(NULL
, b
, sizeof(b
) - 1);
1381 p
= strrchr(b
, '\\');
1382 if (p
&& p
>= r
) r
= p
+1;
1383 q
= strrchr(b
, ':');
1384 if (q
&& q
>= r
) r
= q
+1;
1385 strcpy(r
, "putty.hlp");
1386 if ( (fp
= fopen(b
, "r")) != NULL
) {
1387 help_path
= dupstr(b
);
1394 return DialogBox(hinst
, MAKEINTRESOURCE(201), NULL
,
1395 MainDlgProc
) != IDOK
;