diff options
author | Dimitrij <kvarkas@gmail.com> | 2022-10-31 00:45:23 +0300 |
---|---|---|
committer | Dimitrij <kvarkas@gmail.com> | 2022-10-31 00:45:23 +0300 |
commit | 302fb2e8ddea1c993552c9a30c02f41d01ca54a9 (patch) | |
tree | d6cf1b32664296ef2cecda33caeafbe39e6695c1 /windows | |
parent | 59105d9b26363e47f00676bd365b2ac8d4cb536a (diff) | |
parent | 4ff82ab29a22936b78510c68f544a99e677efed3 (diff) |
Diffstat (limited to 'windows')
-rw-r--r-- | windows/CMakeLists.txt | 186 | ||||
-rw-r--r-- | windows/agent-client.c (renamed from windows/winpgntc.c) | 63 | ||||
-rw-r--r-- | windows/cliloop.c (renamed from windows/wincliloop.c) | 22 | ||||
-rw-r--r-- | windows/config.c (renamed from windows/wincfg.c) | 75 | ||||
-rw-r--r-- | windows/conpty.c | 433 | ||||
-rw-r--r-- | windows/console.c (renamed from windows/wincons.c) | 186 | ||||
-rw-r--r-- | windows/controls.c (renamed from windows/winctrls.c) | 458 | ||||
-rw-r--r-- | windows/cryptoapi.h (renamed from windows/wincapi.h) | 6 | ||||
-rw-r--r-- | windows/dialog.c (renamed from windows/windlg.c) | 906 | ||||
-rw-r--r-- | windows/gss.c (renamed from windows/wingss.c) | 19 | ||||
-rw-r--r-- | windows/handle-io.c (renamed from windows/winhandl.c) | 250 | ||||
-rw-r--r-- | windows/handle-socket.c (renamed from windows/winhsock.c) | 233 | ||||
-rw-r--r-- | windows/handle-wait.c | 143 | ||||
-rw-r--r-- | windows/help.c (renamed from windows/winhelp.c) | 6 | ||||
-rw-r--r-- | windows/help.h (renamed from windows/winhelp.h) | 15 | ||||
-rw-r--r-- | windows/help.rc2 | 8 | ||||
-rw-r--r-- | windows/installer.wxs | 15 | ||||
-rw-r--r-- | windows/jump-list.c (renamed from windows/winjump.c) | 48 | ||||
-rw-r--r-- | windows/local-proxy.c (renamed from windows/winproxy.c) | 89 | ||||
-rw-r--r-- | windows/named-pipe-client.c (renamed from windows/winnpc.c) | 21 | ||||
-rw-r--r-- | windows/named-pipe-server.c (renamed from windows/winnps.c) | 57 | ||||
-rw-r--r-- | windows/network.c (renamed from windows/winnet.c) | 568 | ||||
-rw-r--r-- | windows/no-jump-list.c (renamed from windows/winnojmp.c) | 6 | ||||
-rw-r--r-- | windows/nohelp.c (renamed from windows/winnohlp.c) | 2 | ||||
-rw-r--r-- | windows/noise.c (renamed from windows/winnoise.c) | 0 | ||||
-rw-r--r-- | windows/pageant.c (renamed from windows/winpgnt.c) | 795 | ||||
-rw-r--r-- | windows/pageant.rc | 8 | ||||
-rw-r--r-- | windows/platform.h (renamed from windows/winstuff.h) | 306 | ||||
-rw-r--r-- | windows/plink.c (renamed from windows/winplink.c) | 51 | ||||
-rw-r--r-- | windows/printing.c (renamed from windows/winprint.c) | 16 | ||||
-rw-r--r-- | windows/psocks.c (renamed from windows/winsocks.c) | 0 | ||||
-rw-r--r-- | windows/pterm.c | 65 | ||||
-rw-r--r-- | windows/pterm.ico | bin | 0 -> 4078 bytes | |||
-rw-r--r-- | windows/pterm.rc | 15 | ||||
-rw-r--r-- | windows/ptermcfg.ico | bin | 0 -> 4078 bytes | |||
-rw-r--r-- | windows/putty-common.rc2 | 98 | ||||
-rw-r--r-- | windows/putty-rc.h (renamed from windows/win_res.h) | 12 | ||||
-rw-r--r-- | windows/putty.c | 203 | ||||
-rw-r--r-- | windows/putty.rc | 8 | ||||
-rw-r--r-- | windows/puttygen.c (renamed from windows/winpgen.c) | 936 | ||||
-rw-r--r-- | windows/puttygen.rc | 12 | ||||
-rw-r--r-- | windows/puttytel.rc | 8 | ||||
-rw-r--r-- | windows/rcstuff.h | 15 | ||||
-rw-r--r-- | windows/security-api.h (renamed from windows/winsecur.h) | 8 | ||||
-rw-r--r-- | windows/select-cli.c (renamed from windows/winselcli.c) | 2 | ||||
-rw-r--r-- | windows/select-gui.c | 65 | ||||
-rw-r--r-- | windows/serial.c (renamed from windows/winser.c) | 13 | ||||
-rw-r--r-- | windows/sftp.c (renamed from windows/winsftp.c) | 30 | ||||
-rw-r--r-- | windows/sharing.c (renamed from windows/winshare.c) | 56 | ||||
-rw-r--r-- | windows/storage.c (renamed from windows/winstore.c) | 564 | ||||
-rw-r--r-- | windows/test_screenshot.c | 45 | ||||
-rw-r--r-- | windows/unicode.c (renamed from windows/winucs.c) | 279 | ||||
-rw-r--r-- | windows/utils/agent_mutex_name.c | 14 | ||||
-rw-r--r-- | windows/utils/agent_named_pipe_name.c | 17 | ||||
-rw-r--r-- | windows/utils/arm_arch_queries.c | 45 | ||||
-rw-r--r-- | windows/utils/aux_match_opt.c | 117 | ||||
-rw-r--r-- | windows/utils/centre_window.c | 20 | ||||
-rw-r--r-- | windows/utils/cryptoapi.c (renamed from windows/wincapi.c) | 8 | ||||
-rw-r--r-- | windows/utils/defaults.c (renamed from windows/windefs.c) | 2 | ||||
-rw-r--r-- | windows/utils/dll_hijacking_protection.c | 43 | ||||
-rw-r--r-- | windows/utils/dputs.c | 39 | ||||
-rw-r--r-- | windows/utils/escape_registry_key.c | 48 | ||||
-rw-r--r-- | windows/utils/filename.c | 54 | ||||
-rw-r--r-- | windows/utils/fontspec.c | 43 | ||||
-rw-r--r-- | windows/utils/get_system_dir.c | 21 | ||||
-rw-r--r-- | windows/utils/get_username.c | 77 | ||||
-rw-r--r-- | windows/utils/getdlgitemtext_alloc.c | 20 | ||||
-rw-r--r-- | windows/utils/interprocess_mutex.c | 48 | ||||
-rw-r--r-- | windows/utils/is_console_handle.c | 13 | ||||
-rw-r--r-- | windows/utils/load_system32_dll.c | 18 | ||||
-rw-r--r-- | windows/utils/ltime.c (renamed from windows/wintime.c) | 3 | ||||
-rw-r--r-- | windows/utils/make_spr_sw_abort_winerror.c | 22 | ||||
-rw-r--r-- | windows/utils/makedlgitemborderless.c | 19 | ||||
-rw-r--r-- | windows/utils/message_box.c | 49 | ||||
-rw-r--r-- | windows/utils/minefield.c (renamed from windows/winmiscs.c) | 77 | ||||
-rw-r--r-- | windows/utils/open_for_write_would_lose_data.c | 76 | ||||
-rw-r--r-- | windows/utils/pgp_fingerprints_msgbox.c | 25 | ||||
-rw-r--r-- | windows/utils/platform_get_x_display.c | 13 | ||||
-rw-r--r-- | windows/utils/registry.c | 184 | ||||
-rw-r--r-- | windows/utils/request_file.c | 71 | ||||
-rw-r--r-- | windows/utils/screenshot.c | 126 | ||||
-rw-r--r-- | windows/utils/security.c (renamed from windows/winsecur.c) | 38 | ||||
-rw-r--r-- | windows/utils/shinydialogbox.c | 111 | ||||
-rw-r--r-- | windows/utils/split_into_argv.c (renamed from windows/winutils.c) | 413 | ||||
-rw-r--r-- | windows/utils/strtoumax.c | 12 | ||||
-rw-r--r-- | windows/utils/version.c | 45 | ||||
-rw-r--r-- | windows/utils/win_strerror.c | 72 | ||||
-rw-r--r-- | windows/win-gui-seat.h (renamed from windows/winseat.h) | 4 | ||||
-rw-r--r-- | windows/win_res.rc2 | 137 | ||||
-rw-r--r-- | windows/window.c | 404 | ||||
-rw-r--r-- | windows/winhelp.rc2 | 8 | ||||
-rw-r--r-- | windows/winmisc.c | 467 | ||||
-rw-r--r-- | windows/winselgui.c | 38 | ||||
-rw-r--r-- | windows/x11.c (renamed from windows/winx11.c) | 2 |
94 files changed, 7157 insertions, 3331 deletions
diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt new file mode 100644 index 00000000..d34b4106 --- /dev/null +++ b/windows/CMakeLists.txt @@ -0,0 +1,186 @@ +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) + +add_sources_from_current_dir(utils + utils/agent_mutex_name.c + utils/agent_named_pipe_name.c + utils/arm_arch_queries.c + utils/aux_match_opt.c + utils/centre_window.c + utils/cryptoapi.c + utils/defaults.c + utils/dll_hijacking_protection.c + utils/dputs.c + utils/escape_registry_key.c + utils/filename.c + utils/fontspec.c + utils/getdlgitemtext_alloc.c + utils/get_system_dir.c + utils/get_username.c + utils/interprocess_mutex.c + utils/is_console_handle.c + utils/load_system32_dll.c + utils/ltime.c + utils/makedlgitemborderless.c + utils/make_spr_sw_abort_winerror.c + utils/message_box.c + utils/minefield.c + utils/open_for_write_would_lose_data.c + utils/pgp_fingerprints_msgbox.c + utils/platform_get_x_display.c + utils/registry.c + utils/request_file.c + utils/screenshot.c + utils/security.c + utils/shinydialogbox.c + utils/split_into_argv.c + utils/version.c + utils/win_strerror.c + unicode.c) +if(NOT HAVE_STRTOUMAX) + add_sources_from_current_dir(utils utils/strtoumax.c) +endif() +add_sources_from_current_dir(eventloop + cliloop.c handle-wait.c) +add_sources_from_current_dir(console + select-cli.c nohelp.c console.c) +add_sources_from_current_dir(settings + storage.c) +add_sources_from_current_dir(network + network.c handle-socket.c named-pipe-client.c named-pipe-server.c local-proxy.c x11.c) +add_sources_from_current_dir(sshcommon + noise.c) +add_sources_from_current_dir(sshclient + agent-client.c gss.c sharing.c) +add_sources_from_current_dir(sftpclient + sftp.c) +add_sources_from_current_dir(otherbackends + serial.c) +add_sources_from_current_dir(agent + agent-client.c) +add_sources_from_current_dir(guiterminal + dialog.c controls.c config.c printing.c jump-list.c sizetip.c) +add_dependencies(guiterminal generated_licence_h) # dialog.c uses licence.h + +# This object awkwardly needs to live in the network library as well +# as the eventloop library, in case it didn't get pulled in from the +# latter before handle-socket.c needed it. +add_library(handle-io OBJECT + handle-io.c) +target_sources(eventloop PRIVATE $<TARGET_OBJECTS:handle-io>) +target_sources(network PRIVATE $<TARGET_OBJECTS:handle-io>) + +add_library(guimisc STATIC + select-gui.c) + +add_executable(pageant + pageant.c + help.c + pageant.rc) +add_dependencies(pageant generated_licence_h) +target_link_libraries(pageant + guimisc eventloop agent network crypto utils + ${platform_libraries}) +set_target_properties(pageant PROPERTIES + WIN32_EXECUTABLE ON + LINK_FLAGS "${LFLAG_MANIFEST_NO}") +installed_program(pageant) + +add_sources_from_current_dir(plink no-jump-list.c nohelp.c plink.rc) +add_dependencies(plink generated_licence_h) + +add_sources_from_current_dir(pscp no-jump-list.c nohelp.c pscp.rc) +add_dependencies(pscp generated_licence_h) + +add_sources_from_current_dir(psftp no-jump-list.c nohelp.c psftp.rc) +add_dependencies(psftp generated_licence_h) + +add_sources_from_current_dir(psocks nohelp.c) + +add_executable(putty + window.c + putty.c + help.c + putty.rc) +be_list(putty PuTTY SSH SERIAL OTHERBACKENDS) +add_dependencies(putty generated_licence_h) +target_link_libraries(putty + guiterminal guimisc eventloop sshclient otherbackends settings network crypto + utils + ${platform_libraries}) +set_target_properties(putty PROPERTIES + WIN32_EXECUTABLE ON + LINK_FLAGS "${LFLAG_MANIFEST_NO}") +installed_program(putty) + +add_executable(puttytel + window.c + putty.c + help.c + ${CMAKE_SOURCE_DIR}/stubs/no-gss.c + ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c + ${CMAKE_SOURCE_DIR}/stubs/no-rand.c + ${CMAKE_SOURCE_DIR}/proxy/nocproxy.c + ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c + puttytel.rc) +be_list(puttytel PuTTYtel SERIAL OTHERBACKENDS) +add_dependencies(puttytel generated_licence_h) +target_link_libraries(puttytel + guiterminal guimisc eventloop otherbackends settings network utils + ${platform_libraries}) +set_target_properties(puttytel PROPERTIES + WIN32_EXECUTABLE ON + LINK_FLAGS "${LFLAG_MANIFEST_NO}") +installed_program(puttytel) + +add_executable(puttygen + puttygen.c + ${CMAKE_SOURCE_DIR}/stubs/no-timing.c + noise.c + no-jump-list.c + storage.c + help.c + ${CMAKE_SOURCE_DIR}/sshpubk.c + ${CMAKE_SOURCE_DIR}/sshrand.c + controls.c + puttygen.rc) +add_dependencies(puttygen generated_licence_h) +target_link_libraries(puttygen + keygen guimisc crypto utils + ${platform_libraries}) +set_target_properties(puttygen PROPERTIES + WIN32_EXECUTABLE ON + LINK_FLAGS "${LFLAG_MANIFEST_NO}") +installed_program(puttygen) + +if(HAVE_CONPTY) + add_executable(pterm + window.c + pterm.c + help.c + conpty.c + ${CMAKE_SOURCE_DIR}/stubs/no-gss.c + ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c + ${CMAKE_SOURCE_DIR}/stubs/no-rand.c + ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c + pterm.rc) + be_list(pterm pterm) + add_dependencies(pterm generated_licence_h) + target_link_libraries(pterm + guiterminal guimisc eventloop settings network utils + ${platform_libraries}) + set_target_properties(pterm PROPERTIES + WIN32_EXECUTABLE ON + LINK_FLAGS "${LFLAG_MANIFEST_NO}") + installed_program(pterm) +else() + message("ConPTY not available; cannot build Windows pterm") +endif() + +add_executable(test_split_into_argv + utils/split_into_argv.c) +target_compile_definitions(test_split_into_argv PRIVATE TEST) +target_link_libraries(test_split_into_argv utils ${platform_libraries}) + +add_executable(test_screenshot + test_screenshot.c) +target_link_libraries(test_screenshot utils ${platform_libraries}) diff --git a/windows/winpgntc.c b/windows/agent-client.c index 557dc532..168465e5 100644 --- a/windows/winpgntc.c +++ b/windows/agent-client.c @@ -9,12 +9,8 @@ #include "putty.h" #include "pageant.h" /* for AGENT_MAX_MSGLEN */ -#ifndef NO_SECURITY -#include "winsecur.h" -#include "wincapi.h" -#endif - -#define AGENT_COPYDATA_ID 0x804e50ba /* random goop */ +#include "security-api.h" +#include "cryptoapi.h" static bool wm_copydata_agent_exists(void) { @@ -50,7 +46,6 @@ static void wm_copydata_agent_query(strbuf *query, void **out, int *outlen) mapname = dupprintf("PageantRequest%08x", (unsigned)GetCurrentThreadId()); psa = NULL; -#ifndef NO_SECURITY if (got_advapi()) { /* * Make the file mapping we create for communication with @@ -67,8 +62,8 @@ static void wm_copydata_agent_query(strbuf *query, void **out, int *outlen) psd = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); if (psd) { - if (p_InitializeSecurityDescriptor - (psd, SECURITY_DESCRIPTOR_REVISION) && + if (p_InitializeSecurityDescriptor( + psd, SECURITY_DESCRIPTOR_REVISION) && p_SetSecurityDescriptorOwner(psd, usersid, false)) { sa.nLength = sizeof(sa); sa.bInheritHandle = true; @@ -81,7 +76,6 @@ static void wm_copydata_agent_query(strbuf *query, void **out, int *outlen) } } } -#endif /* NO_SECURITY */ filemap = CreateFileMapping(INVALID_HANDLE_VALUE, psa, PAGE_READWRITE, 0, AGENT_MAX_MSGLEN, mapname); @@ -129,19 +123,6 @@ static void wm_copydata_agent_query(strbuf *query, void **out, int *outlen) LocalFree(psd); } -#ifndef NO_SECURITY - -char *agent_named_pipe_name(void) -{ - char *username, *suffix, *pipename; - username = get_username(); - suffix = capi_obfuscate_string("Pageant"); - pipename = dupprintf("\\\\.\\pipe\\pageant.%s.%s", username, suffix); - sfree(username); - sfree(suffix); - return pipename; -} - Socket *agent_connect(Plug *plug) { char *pipename = agent_named_pipe_name(); @@ -310,39 +291,3 @@ agent_pending_query *agent_query( wm_copydata_agent_query(query, out, outlen); return NULL; } - -#else /* NO_SECURITY */ - -Socket *agent_connect(void *vctx, Plug *plug) -{ - unreachable("no agent_connect_ctx can be constructed on this platform"); -} - -agent_connect_ctx *agent_get_connect_ctx(void) -{ - return NULL; -} - -void agent_free_connect_ctx(agent_connect_ctx *ctx) -{ -} - -bool agent_exists(void) -{ - return wm_copydata_agent_exists(); -} - -agent_pending_query *agent_query( - strbuf *query, void **out, int *outlen, - void (*callback)(void *, void *, int), void *callback_ctx) -{ - wm_copydata_agent_query(query, out, outlen); - return NULL; -} - -void agent_cancel_query(agent_pending_query *q) -{ - unreachable("Windows agent queries are never asynchronous!"); -} - -#endif /* NO_SECURITY */ diff --git a/windows/wincliloop.c b/windows/cliloop.c index 26a4d3aa..eced54ca 100644 --- a/windows/wincliloop.c +++ b/windows/cliloop.c @@ -8,8 +8,6 @@ void cli_main_loop(cliloop_pre_t pre, cliloop_post_t post, void *ctx) now = GETTICKCOUNT(); while (true) { - int nhandles; - HANDLE *handles; DWORD n; DWORD ticks; @@ -34,25 +32,25 @@ void cli_main_loop(cliloop_pre_t pre, cliloop_post_t post, void *ctx) * get WAIT_TIMEOUT */ } - handles = handle_get_events(&nhandles); + HandleWaitList *hwl = get_handle_wait_list(); size_t winselcli_index = -(size_t)1; - size_t extra_base = nhandles; + size_t extra_base = hwl->nhandles; if (winselcli_event != INVALID_HANDLE_VALUE) { + assert(extra_base < MAXIMUM_WAIT_OBJECTS); winselcli_index = extra_base++; - handles = sresize(handles, extra_base, HANDLE); - handles[winselcli_index] = winselcli_event; + hwl->handles[winselcli_index] = winselcli_event; } size_t total_handles = extra_base + n_extra_handles; - handles = sresize(handles, total_handles, HANDLE); + assert(total_handles < MAXIMUM_WAIT_OBJECTS); for (size_t i = 0; i < n_extra_handles; i++) - handles[extra_base + i] = extra_handles[i]; + hwl->handles[extra_base + i] = extra_handles[i]; - n = WaitForMultipleObjects(total_handles, handles, false, ticks); + n = WaitForMultipleObjects(total_handles, hwl->handles, false, ticks); size_t extra_handle_index = n_extra_handles; - if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) { - handle_got_event(handles[n - WAIT_OBJECT_0]); + if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)hwl->nhandles) { + handle_wait_activate(hwl, n - WAIT_OBJECT_0); } else if (winselcli_event != INVALID_HANDLE_VALUE && n == WAIT_OBJECT_0 + winselcli_index) { WSANETWORKEVENTS things; @@ -122,7 +120,7 @@ void cli_main_loop(cliloop_pre_t pre, cliloop_post_t post, void *ctx) now = GETTICKCOUNT(); } - sfree(handles); + handle_wait_list_free(hwl); if (!post(ctx, extra_handle_index)) break; diff --git a/windows/wincfg.c b/windows/config.c index fab3240f..fc9070bf 100644 --- a/windows/wincfg.c +++ b/windows/config.c @@ -1,5 +1,5 @@ /* - * wincfg.c - the Windows-specific parts of the PuTTY configuration + * config.c - the Windows-specific parts of the PuTTY configuration * box. */ @@ -10,27 +10,27 @@ #include "dialog.h" #include "storage.h" -static void about_handler(union control *ctrl, dlgparam *dlg, +static void about_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { - HWND *hwndp = (HWND *)ctrl->generic.context.p; + HWND *hwndp = (HWND *)ctrl->context.p; if (event == EVENT_ACTION) { modal_about_box(*hwndp); } } -static void help_handler(union control *ctrl, dlgparam *dlg, +static void help_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { - HWND *hwndp = (HWND *)ctrl->generic.context.p; + HWND *hwndp = (HWND *)ctrl->context.p; if (event == EVENT_ACTION) { show_help(*hwndp); } } -static void variable_pitch_handler(union control *ctrl, dlgparam *dlg, +static void variable_pitch_handler(dlgcontrol *ctrl, dlgparam *dlg, void *data, int event) { if (event == EVENT_REFRESH) { @@ -46,7 +46,7 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, const struct BackendVtable *backvt; bool resize_forbidden = false; struct controlset *s; - union control *c; + dlgcontrol *c; char *str; if (!midsession) { @@ -56,11 +56,11 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, s = ctrl_getset(b, "", "", ""); c = ctrl_pushbutton(s, "About", 'a', HELPCTX(no_help), about_handler, P(hwndp)); - c->generic.column = 0; + c->column = 0; if (has_help) { c = ctrl_pushbutton(s, "Help", 'h', HELPCTX(no_help), help_handler, P(hwndp)); - c->generic.column = 1; + c->column = 1; } } @@ -82,8 +82,8 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, int i; for (i = 0; i < s->ncontrols; i++) { c = s->ctrls[i]; - if (c->generic.type == CTRL_CHECKBOX && - c->generic.context.i == CONF_scrollbar) { + if (c->type == CTRL_CHECKBOX && + c->context.i == CONF_scrollbar) { /* * Control i is the scrollbar checkbox. * Control s->ncontrols-1 is the scrollbar-in-FS one. @@ -91,7 +91,7 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, if (i < s->ncontrols-2) { c = s->ctrls[s->ncontrols-1]; memmove(s->ctrls+i+2, s->ctrls+i+1, - (s->ncontrols-i-2)*sizeof(union control *)); + (s->ncontrols-i-2)*sizeof(dlgcontrol *)); s->ctrls[i+1] = c; } break; @@ -134,9 +134,9 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, int i; for (i = 0; i < s->ncontrols; i++) { c = s->ctrls[i]; - if (c->generic.type == CTRL_RADIO && - c->generic.context.i == CONF_beep) { - assert(c->generic.handler == conf_radiobutton_handler); + if (c->type == CTRL_RADIO && + c->context.i == CONF_beep) { + assert(c->handler == conf_radiobutton_handler); c->radio.nbuttons += 2; c->radio.buttons = sresize(c->radio.buttons, c->radio.nbuttons, char *); @@ -173,7 +173,7 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, I(CONF_beep_ind), "Disabled", I(B_IND_DISABLED), "Flashing", I(B_IND_FLASH), - "Steady", I(B_IND_STEADY), NULL); + "Steady", I(B_IND_STEADY)); /* * The sunken-edge border is a Windows GUI feature. @@ -198,7 +198,7 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, "Antialiased", I(FQ_ANTIALIASED), "Non-Antialiased", I(FQ_NONANTIALIASED), "ClearType", I(FQ_CLEARTYPE), - "Default", I(FQ_DEFAULT), NULL); + "Default", I(FQ_DEFAULT)); /* * Cyrillic Lock is a horrid misfeature even on Windows, and @@ -233,9 +233,9 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, int i; for (i = 0; i < s->ncontrols; i++) { c = s->ctrls[i]; - if (c->generic.type == CTRL_RADIO && - c->generic.context.i == CONF_vtmode) { - assert(c->generic.handler == conf_radiobutton_handler); + if (c->type == CTRL_RADIO && + c->context.i == CONF_vtmode) { + assert(c->handler == conf_radiobutton_handler); c->radio.nbuttons += 3; c->radio.buttons = sresize(c->radio.buttons, c->radio.nbuttons, char *); @@ -289,14 +289,14 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, I(CONF_mouse_is_xterm), "Windows (Middle extends, Right brings up menu)", I(2), "Compromise (Middle extends, Right pastes)", I(0), - "xterm (Right extends, Middle pastes)", I(1), NULL); + "xterm (Right extends, Middle pastes)", I(1)); /* * This really ought to go at the _top_ of its box, not the * bottom, so we'll just do some shuffling now we've set it * up... */ c = s->ctrls[s->ncontrols-1]; /* this should be the new control */ - memmove(s->ctrls+1, s->ctrls, (s->ncontrols-1)*sizeof(union control *)); + memmove(s->ctrls+1, s->ctrls, (s->ncontrols-1)*sizeof(dlgcontrol *)); s->ctrls[0] = c; /* @@ -328,7 +328,7 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, "Change the number of rows and columns", I(RESIZE_TERM), "Change the size of the font", I(RESIZE_FONT), "Change font size only when maximised", I(RESIZE_EITHER), - "Forbid resizing completely", I(RESIZE_DISABLED), NULL); + "Forbid resizing completely", I(RESIZE_DISABLED)); } /* @@ -355,37 +355,16 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, I(CONF_fullscreenonaltenter)); /* - * Windows supports a local-command proxy. This also means we - * must adjust the text on the `Telnet command' control. + * Windows supports a local-command proxy. */ if (!midsession) { int i; s = ctrl_getset(b, "Connection/Proxy", "basics", NULL); for (i = 0; i < s->ncontrols; i++) { c = s->ctrls[i]; - if (c->generic.type == CTRL_RADIO && - c->generic.context.i == CONF_proxy_type) { - assert(c->generic.handler == conf_radiobutton_handler); - c->radio.nbuttons++; - c->radio.buttons = - sresize(c->radio.buttons, c->radio.nbuttons, char *); - c->radio.buttons[c->radio.nbuttons-1] = - dupstr("Local"); - c->radio.buttondata = - sresize(c->radio.buttondata, c->radio.nbuttons, intorptr); - c->radio.buttondata[c->radio.nbuttons-1] = I(PROXY_CMD); - break; - } - } - - for (i = 0; i < s->ncontrols; i++) { - c = s->ctrls[i]; - if (c->generic.type == CTRL_EDITBOX && - c->generic.context.i == CONF_proxy_telnet_command) { - assert(c->generic.handler == conf_editbox_handler); - sfree(c->generic.label); - c->generic.label = dupstr("Telnet command, or local" - " proxy command"); + if (c->type == CTRL_LISTBOX && + c->handler == proxy_type_handler) { + c->context.i |= PROXY_UI_FLAG_LOCAL; break; } } diff --git a/windows/conpty.c b/windows/conpty.c new file mode 100644 index 00000000..c23c81e1 --- /dev/null +++ b/windows/conpty.c @@ -0,0 +1,433 @@ +/* + * Backend to run a Windows console session using ConPTY. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> + +#include "putty.h" + +#include <windows.h> +#include <consoleapi.h> + +typedef struct ConPTY ConPTY; +struct ConPTY { + HPCON pseudoconsole; + HANDLE outpipe, inpipe, hprocess; + struct handle *out, *in; + HandleWait *subprocess; + bool exited; + DWORD exitstatus; + Seat *seat; + LogContext *logctx; + int bufsize; + Backend backend; +}; + +DECL_WINDOWS_FUNCTION(static, HRESULT, CreatePseudoConsole, + (COORD, HANDLE, HANDLE, DWORD, HPCON *)); +DECL_WINDOWS_FUNCTION(static, void, ClosePseudoConsole, (HPCON)); +DECL_WINDOWS_FUNCTION(static, HRESULT, ResizePseudoConsole, (HPCON, COORD)); + +static bool init_conpty_api(void) +{ + static bool tried = false; + if (!tried) { + tried = true; + HMODULE kernel32_module = load_system32_dll("kernel32.dll"); + GET_WINDOWS_FUNCTION(kernel32_module, CreatePseudoConsole); + GET_WINDOWS_FUNCTION(kernel32_module, ClosePseudoConsole); + GET_WINDOWS_FUNCTION(kernel32_module, ResizePseudoConsole); + } + + return (p_CreatePseudoConsole != NULL && + p_ClosePseudoConsole != NULL && + p_ResizePseudoConsole != NULL); +} + +static void conpty_terminate(ConPTY *conpty) +{ + if (conpty->out) { + handle_free(conpty->out); + conpty->out = NULL; + } + if (conpty->outpipe != INVALID_HANDLE_VALUE) { + CloseHandle(conpty->outpipe); + conpty->outpipe = INVALID_HANDLE_VALUE; + } + if (conpty->in) { + handle_free(conpty->in); + conpty->in = NULL; + } + if (conpty->inpipe != INVALID_HANDLE_VALUE) { + CloseHandle(conpty->inpipe); + conpty->inpipe = INVALID_HANDLE_VALUE; + } + if (conpty->subprocess) { + delete_handle_wait(conpty->subprocess); + conpty->subprocess = NULL; + conpty->hprocess = INVALID_HANDLE_VALUE; + } + if (conpty->pseudoconsole != INVALID_HANDLE_VALUE) { + p_ClosePseudoConsole(conpty->pseudoconsole); + conpty->pseudoconsole = INVALID_HANDLE_VALUE; + } +} + +static void conpty_process_wait_callback(void *vctx) +{ + ConPTY *conpty = (ConPTY *)vctx; + + if (!GetExitCodeProcess(conpty->hprocess, &conpty->exitstatus)) + return; + conpty->exited = true; + + /* + * We can stop waiting for the process now. + */ + if (conpty->subprocess) { + delete_handle_wait(conpty->subprocess); + conpty->subprocess = NULL; + conpty->hprocess = INVALID_HANDLE_VALUE; + } + + /* + * Once the contained process exits, close the pseudo-console as + * well. But don't close the pipes yet, since apparently + * ClosePseudoConsole can trigger a final bout of terminal output + * as things clean themselves up. + */ + if (conpty->pseudoconsole != INVALID_HANDLE_VALUE) { + p_ClosePseudoConsole(conpty->pseudoconsole); + conpty->pseudoconsole = INVALID_HANDLE_VALUE; + } +} + +static size_t conpty_gotdata( + struct handle *h, const void *data, size_t len, int err) +{ + ConPTY *conpty = (ConPTY *)handle_get_privdata(h); + if (err || len == 0) { + char *error_msg; + + conpty_terminate(conpty); + + seat_notify_remote_exit(conpty->seat); + + if (!err && conpty->exited) { + /* + * The clean-exit case: our subprocess terminated, we + * deleted the PseudoConsole ourself, and now we got the + * expected EOF on the pipe. + */ + return 0; + } + + if (err) + error_msg = dupprintf("Error reading from console pty: %s", + win_strerror(err)); + else + error_msg = dupprintf( + "Unexpected end of file reading from console pty"); + + logevent(conpty->logctx, error_msg); + seat_connection_fatal(conpty->seat, "%s", error_msg); + sfree(error_msg); + + return 0; + } else { + return seat_stdout(conpty->seat, data, len); + } +} + +static void conpty_sentdata(struct handle *h, size_t new_backlog, int err, + bool close) +{ + ConPTY *conpty = (ConPTY *)handle_get_privdata(h); + if (err) { + const char *error_msg = "Error writing to conpty device"; + + conpty_terminate(conpty); + + seat_notify_remote_exit(conpty->seat); + + logevent(conpty->logctx, error_msg); + + seat_connection_fatal(conpty->seat, "%s", error_msg); + } else { + conpty->bufsize = new_backlog; + } +} + +static char *conpty_init(const BackendVtable *vt, Seat *seat, + Backend **backend_handle, LogContext *logctx, + Conf *conf, const char *host, int port, + char **realhost, bool nodelay, bool keepalive) +{ + ConPTY *conpty; + char *err = NULL; + + HANDLE in_r = INVALID_HANDLE_VALUE; + HANDLE in_w = INVALID_HANDLE_VALUE; + HANDLE out_r = INVALID_HANDLE_VALUE; + HANDLE out_w = INVALID_HANDLE_VALUE; + + HPCON pcon; + bool pcon_needs_cleanup = false; + + STARTUPINFOEX si; + memset(&si, 0, sizeof(si)); + + if (!init_conpty_api()) { + err = dupprintf("Pseudo-console API is not available on this " + "Windows system"); + goto out; + } + + if (!CreatePipe(&in_r, &in_w, NULL, 0)) { + err = dupprintf("CreatePipe: %s", win_strerror(GetLastError())); + goto out; + } + if (!CreatePipe(&out_r, &out_w, NULL, 0)) { + err = dupprintf("CreatePipe: %s", win_strerror(GetLastError())); + goto out; + } + + COORD size; + size.X = conf_get_int(conf, CONF_width); + size.Y = conf_get_int(conf, CONF_height); + + HRESULT result = p_CreatePseudoConsole(size, in_r, out_w, 0, &pcon); + if (FAILED(result)) { + if (HRESULT_FACILITY(result) == FACILITY_WIN32) + err = dupprintf("CreatePseudoConsole: %s", + win_strerror(HRESULT_CODE(result))); + else + err = dupprintf("CreatePseudoConsole failed: HRESULT=0x%08x", + (unsigned)result); + goto out; + } + pcon_needs_cleanup = true; + + CloseHandle(in_r); + in_r = INVALID_HANDLE_VALUE; + CloseHandle(out_w); + out_w = INVALID_HANDLE_VALUE; + + si.StartupInfo.cb = sizeof(si); + + SIZE_T attrsize = 0; + InitializeProcThreadAttributeList(NULL, 1, 0, &attrsize); + si.lpAttributeList = smalloc(attrsize); + if (!InitializeProcThreadAttributeList( + si.lpAttributeList, 1, 0, &attrsize)) { + err = dupprintf("InitializeProcThreadAttributeList: %s", + win_strerror(GetLastError())); + goto out; + } + if (!UpdateProcThreadAttribute( + si.lpAttributeList, + 0, PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, + pcon, sizeof(pcon), NULL, NULL)) { + err = dupprintf("UpdateProcThreadAttribute: %s", + win_strerror(GetLastError())); + goto out; + } + + PROCESS_INFORMATION pi; + memset(&pi, 0, sizeof(pi)); + + char *command; + const char *conf_cmd = conf_get_str(conf, CONF_remote_cmd); + if (*conf_cmd) { + command = dupstr(conf_cmd); + } else { + command = dupcat(get_system_dir(), "\\cmd.exe"); + } + bool created_ok = CreateProcess(NULL, command, NULL, NULL, + false, EXTENDED_STARTUPINFO_PRESENT, + NULL, NULL, &si.StartupInfo, &pi); + sfree(command); + if (!created_ok) { + err = dupprintf("CreateProcess: %s", + win_strerror(GetLastError())); + goto out; + } + + /* No local authentication phase in this protocol */ + seat_set_trust_status(seat, false); + + conpty = snew(ConPTY); + memset(conpty, 0, sizeof(ConPTY)); + conpty->pseudoconsole = pcon; + pcon_needs_cleanup = false; + conpty->outpipe = in_w; + conpty->out = handle_output_new(in_w, conpty_sentdata, conpty, 0); + in_w = INVALID_HANDLE_VALUE; + conpty->inpipe = out_r; + conpty->in = handle_input_new(out_r, conpty_gotdata, conpty, 0); + out_r = INVALID_HANDLE_VALUE; + conpty->subprocess = add_handle_wait( + pi.hProcess, conpty_process_wait_callback, conpty); + conpty->hprocess = pi.hProcess; + CloseHandle(pi.hThread); + conpty->exited = false; + conpty->exitstatus = 0; + conpty->bufsize = 0; + conpty->backend.vt = vt; + *backend_handle = &conpty->backend; + + conpty->seat = seat; + conpty->logctx = logctx; + + *realhost = dupstr(""); + + /* + * Specials are always available. + */ + seat_update_specials_menu(conpty->seat); + + out: + if (in_r != INVALID_HANDLE_VALUE) + CloseHandle(in_r); + if (in_w != INVALID_HANDLE_VALUE) + CloseHandle(in_w); + if (out_r != INVALID_HANDLE_VALUE) + CloseHandle(out_r); + if (out_w != INVALID_HANDLE_VALUE) + CloseHandle(out_w); + if (pcon_needs_cleanup) + p_ClosePseudoConsole(pcon); + sfree(si.lpAttributeList); + return err; +} + +static void conpty_free(Backend *be) +{ + ConPTY *conpty = container_of(be, ConPTY, backend); + + conpty_terminate(conpty); + expire_timer_context(conpty); + sfree(conpty); +} + +static void conpty_reconfig(Backend *be, Conf *conf) +{ +} + +static void conpty_send(Backend *be, const char *buf, size_t len) +{ + ConPTY *conpty = container_of(be, ConPTY, backend); + + if (conpty->out == NULL) + return; + + conpty->bufsize = handle_write(conpty->out, buf, len); +} + +static size_t conpty_sendbuffer(Backend *be) +{ + ConPTY *conpty = container_of(be, ConPTY, backend); + return conpty->bufsize; +} + +static void conpty_size(Backend *be, int width, int height) +{ + ConPTY *conpty = container_of(be, ConPTY, backend); + COORD size; + size.X = width; + size.Y = height; + p_ResizePseudoConsole(conpty->pseudoconsole, size); +} + +static void conpty_special(Backend *be, SessionSpecialCode code, int arg) +{ +} + +static const SessionSpecial *conpty_get_specials(Backend *be) +{ + static const SessionSpecial specials[] = { + {NULL, SS_EXITMENU} + }; + return specials; +} + +static bool conpty_connected(Backend *be) +{ + return true; /* always connected */ +} + +static bool conpty_sendok(Backend *be) +{ + return true; +} + +static void conpty_unthrottle(Backend *be, size_t backlog) +{ + ConPTY *conpty = container_of(be, ConPTY, backend); + if (conpty->in) + handle_unthrottle(conpty->in, backlog); +} + +static bool conpty_ldisc(Backend *be, int option) +{ + return false; +} + +static void conpty_provide_ldisc(Backend *be, Ldisc *ldisc) +{ +} + +static int conpty_exitcode(Backend *be) +{ + ConPTY *conpty = container_of(be, ConPTY, backend); + + if (conpty->exited) { + /* + * PuTTY's representation of exit statuses expects them to be + * non-negative 'int' values. But Windows exit statuses can + * include all those exception codes like 0xC000001D which + * convert to negative 32-bit ints. + * + * I don't think there's a great deal of use for returning + * those in full detail, right now. (Though if we ever + * connected this system up to a Windows version of psusan or + * Uppity, perhaps there might be?) + * + * So we clip them at INT_MAX-1, since INT_MAX is reserved for + * 'exit so unclean as to inhibit Close On Clean Exit'. + */ + return (0 <= conpty->exitstatus && conpty->exitstatus < INT_MAX) ? + conpty->exitstatus : INT_MAX-1; + } else { + return -1; + } +} + +static int conpty_cfg_info(Backend *be) +{ + return 0; +} + +const BackendVtable conpty_backend = { + .init = conpty_init, + .free = conpty_free, + .reconfig = conpty_reconfig, + .send = conpty_send, + .sendbuffer = conpty_sendbuffer, + .size = conpty_size, + .special = conpty_special, + .get_specials = conpty_get_specials, + .connected = conpty_connected, + .exitcode = conpty_exitcode, + .sendok = conpty_sendok, + .ldisc_option_state = conpty_ldisc, + .provide_ldisc = conpty_provide_ldisc, + .unthrottle = conpty_unthrottle, + .cfg_info = conpty_cfg_info, + .id = "conpty", + .displayname_tc = "ConPTY", + .displayname_lc = "ConPTY", /* proper name, so capitalise it anyway */ + .protocol = -1, +}; diff --git a/windows/wincons.c b/windows/console.c index 414167b4..2fd572b4 100644 --- a/windows/wincons.c +++ b/windows/console.c @@ -1,5 +1,5 @@ /* - * wincons.c - various interactive-prompt routines shared between + * console.c - various interactive-prompt routines shared between * the Windows console PuTTY tools */ @@ -32,50 +32,55 @@ void console_print_error_msg(const char *prefix, const char *msg) fflush(stderr); } -int console_verify_ssh_host_key( +SeatPromptResult console_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **fingerprints, - void (*callback)(void *ctx, int result), void *ctx) + char *keystr, SeatDialogText *text, HelpCtx helpctx, + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { - int ret; HANDLE hin; DWORD savemode, i; - const char *common_fmt, *intro, *prompt; + const char *prompt = NULL; - char line[32]; - - /* - * Verify the key against the registry. - */ - ret = verify_host_key(host, port, keytype, keystr); + stdio_sink errsink[1]; + stdio_sink_init(errsink, stderr); - if (ret == 0) /* success - key matched OK */ - return 1; - - if (ret == 2) { /* key was different */ - common_fmt = hk_wrongmsg_common_fmt; - intro = hk_wrongmsg_interactive_intro; - prompt = hk_wrongmsg_interactive_prompt; - } else { /* key was absent */ - common_fmt = hk_absentmsg_common_fmt; - intro = hk_absentmsg_interactive_intro; - prompt = hk_absentmsg_interactive_prompt; - } - - FingerprintType fptype_default = - ssh2_pick_default_fingerprint(fingerprints); + char line[32]; - fprintf(stderr, common_fmt, keytype, fingerprints[fptype_default]); - if (console_batch_mode) { - fputs(console_abandoned_msg, stderr); - return 0; + for (SeatDialogTextItem *item = text->items, + *end = item+text->nitems; item < end; item++) { + switch (item->type) { + case SDT_PARA: + wordwrap(BinarySink_UPCAST(errsink), + ptrlen_from_asciz(item->text), 60); + fputc('\n', stderr); + break; + case SDT_DISPLAY: + fprintf(stderr, " %s\n", item->text); + break; + case SDT_SCARY_HEADING: + /* Can't change font size or weight in this context */ + fprintf(stderr, "%s\n", item->text); + break; + case SDT_BATCH_ABORT: + if (console_batch_mode) { + fprintf(stderr, "%s\n", item->text); + fflush(stderr); + return SPR_SW_ABORT("Cannot confirm a host key in batch mode"); + } + break; + case SDT_PROMPT: + prompt = item->text; + break; + default: + break; + } } - - fputs(intro, stderr); - fflush(stderr); + assert(prompt); /* something in the SeatDialogText should have set this */ while (true) { - fputs(prompt, stderr); + fprintf(stderr, + "%s (y/n, Return cancels connection, i for more info) ", + prompt); fflush(stderr); line[0] = '\0'; /* fail safe if ReadFile returns no data */ @@ -88,13 +93,22 @@ int console_verify_ssh_host_key( SetConsoleMode(hin, savemode); if (line[0] == 'i' || line[0] == 'I') { - fprintf(stderr, "Full public key:\n%s\n", keydisp); - if (fingerprints[SSH_FPTYPE_SHA256]) - fprintf(stderr, "SHA256 key fingerprint:\n%s\n", - fingerprints[SSH_FPTYPE_SHA256]); - if (fingerprints[SSH_FPTYPE_MD5]) - fprintf(stderr, "MD5 key fingerprint:\n%s\n", - fingerprints[SSH_FPTYPE_MD5]); + for (SeatDialogTextItem *item = text->items, + *end = item+text->nitems; item < end; item++) { + switch (item->type) { + case SDT_MORE_INFO_KEY: + fprintf(stderr, "%s", item->text); + break; + case SDT_MORE_INFO_VALUE_SHORT: + fprintf(stderr, ": %s\n", item->text); + break; + case SDT_MORE_INFO_VALUE_BLOB: + fprintf(stderr, ":\n%s\n", item->text); + break; + default: + break; + } + } } else { break; } @@ -106,16 +120,16 @@ int console_verify_ssh_host_key( line[0] != 'q' && line[0] != 'Q') { if (line[0] == 'y' || line[0] == 'Y') store_host_key(host, port, keytype, keystr); - return 1; + return SPR_OK; } else { fputs(console_abandoned_msg, stderr); - return 0; + return SPR_USER_ABORT; } } -int console_confirm_weak_crypto_primitive( +SeatPromptResult console_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, - void (*callback)(void *ctx, int result), void *ctx) + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { HANDLE hin; DWORD savemode, i; @@ -126,7 +140,8 @@ int console_confirm_weak_crypto_primitive( if (console_batch_mode) { fputs(console_abandoned_msg, stderr); - return 0; + return SPR_SW_ABORT("Cannot confirm a weak crypto primitive " + "in batch mode"); } fputs(console_continue_prompt, stderr); @@ -140,16 +155,16 @@ int console_confirm_weak_crypto_primitive( SetConsoleMode(hin, savemode); if (line[0] == 'y' || line[0] == 'Y') { - return 1; + return SPR_OK; } else { fputs(console_abandoned_msg, stderr); - return 0; + return SPR_USER_ABORT; } } -int console_confirm_weak_cached_hostkey( +SeatPromptResult console_confirm_weak_cached_hostkey( Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, int result), void *ctx) + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { HANDLE hin; DWORD savemode, i; @@ -160,7 +175,8 @@ int console_confirm_weak_cached_hostkey( if (console_batch_mode) { fputs(console_abandoned_msg, stderr); - return 0; + return SPR_SW_ABORT("Cannot confirm a weak cached host key " + "in batch mode"); } fputs(console_continue_prompt, stderr); @@ -174,10 +190,10 @@ int console_confirm_weak_cached_hostkey( SetConsoleMode(hin, savemode); if (line[0] == 'y' || line[0] == 'Y') { - return 1; + return SPR_OK; } else { fputs(console_abandoned_msg, stderr); - return 0; + return SPR_USER_ABORT; } } @@ -187,21 +203,22 @@ bool is_interactive(void) } bool console_antispoof_prompt = true; -bool console_set_trust_status(Seat *seat, bool trusted) + +void console_set_trust_status(Seat *seat, bool trusted) { - if (console_batch_mode || !is_interactive() || !console_antispoof_prompt) { + /* Do nothing in response to a change of trust status, because + * there's nothing we can do in a console environment. However, + * the query function below will make a fiddly decision about + * whether to tell the backend to enable fallback handling. */ +} + +bool console_can_set_trust_status(Seat *seat) +{ + if (console_batch_mode) { /* * In batch mode, we don't need to worry about the server * mimicking our interactive authentication, because the user * already knows not to expect any. - * - * If standard input isn't connected to a terminal, likewise, - * because even if the server did send a spoof authentication - * prompt, the user couldn't respond to it via the terminal - * anyway. - * - * We also vacuously return success if the user has purposely - * disabled the antispoof prompt. */ return true; } @@ -209,6 +226,23 @@ bool console_set_trust_status(Seat *seat, bool trusted) return false; } +bool console_has_mixed_input_stream(Seat *seat) +{ + if (!is_interactive() || !console_antispoof_prompt) { + /* + * If standard input isn't connected to a terminal, then even + * if the server did send a spoof authentication prompt, the + * user couldn't respond to it via the terminal anyway. + * + * We also pretend this is true if the user has purposely + * disabled the antispoof prompt. + */ + return false; + } + + return true; +} + /* * Ask whether to wipe a session log file before writing to it. * Returns 2 for wipe, 1 for append, 0 for cancel (don't log). @@ -329,7 +363,7 @@ static void console_write(HANDLE hout, ptrlen data) WriteFile(hout, data.ptr, data.len, &dummy, NULL); } -int console_get_userpass_input(prompts_t *p) +SeatPromptResult console_get_userpass_input(prompts_t *p) { HANDLE hin = INVALID_HANDLE_VALUE, hout = INVALID_HANDLE_VALUE; size_t curr_prompt; @@ -351,7 +385,8 @@ int console_get_userpass_input(prompts_t *p) */ if (p->n_prompts) { if (console_batch_mode) - return 0; + return SPR_SW_ABORT("Cannot answer interactive prompts " + "in batch mode"); hin = GetStdHandle(STD_INPUT_HANDLE); if (hin == INVALID_HANDLE_VALUE) { fprintf(stderr, "Cannot get standard input handle\n"); @@ -404,6 +439,7 @@ int console_get_userpass_input(prompts_t *p) console_write(hout, ptrlen_from_asciz(pr->prompt)); bool failed = false; + SeatPromptResult spr; while (1) { /* * Amount of data to try to read from the console in one @@ -426,8 +462,17 @@ int console_get_userpass_input(prompts_t *p) void *ptr = strbuf_append(pr->result, toread); DWORD ret = 0; - if (!ReadFile(hin, ptr, toread, &ret, NULL) || ret == 0) { + if (!ReadFile(hin, ptr, toread, &ret, NULL)) { + /* An OS error when reading from the console is treated as an + * unexpected error and reported to the user. */ failed = true; + spr = make_spr_sw_abort_winerror( + "Error reading from console", GetLastError()); + break; + } else if (ret == 0) { + /* Regard EOF on the terminal as a deliberate user-abort */ + failed = true; + spr = SPR_USER_ABORT; break; } @@ -443,10 +488,9 @@ int console_get_userpass_input(prompts_t *p) if (!pr->echo) console_write(hout, PTRLEN_LITERAL("\r\n")); - if (failed) { - return 0; /* failure due to read error */ - } + if (failed) + return spr; } - return 1; /* success */ + return SPR_OK; } diff --git a/windows/winctrls.c b/windows/controls.c index 59129eab..ce3638e4 100644 --- a/windows/winctrls.c +++ b/windows/controls.c @@ -1,5 +1,5 @@ /* - * winctrls.c: routines to self-manage the controls in a dialog + * controls.c: routines to self-manage the controls in a dialog * box. */ @@ -71,8 +71,8 @@ void ctlposinit(struct ctlpos *cp, HWND hwnd, cp->width -= leftborder + rightborder; } -HWND doctl(struct ctlpos *cp, RECT r, - char *wclass, int wstyle, int exstyle, char *wtext, int wid) +HWND doctl(struct ctlpos *cp, RECT r, const char *wclass, int wstyle, + int exstyle, const char *wtext, int wid) { HWND ctl; /* @@ -115,7 +115,7 @@ HWND doctl(struct ctlpos *cp, RECT r, /* * A title bar across the top of a sub-dialog. */ -void bartitle(struct ctlpos *cp, char *name, int id) +void bartitle(struct ctlpos *cp, const char *name, int id) { RECT r; @@ -130,7 +130,7 @@ void bartitle(struct ctlpos *cp, char *name, int id) /* * Begin a grouping box, with or without a group title. */ -void beginbox(struct ctlpos *cp, char *name, int idbox) +void beginbox(struct ctlpos *cp, const char *name, int idbox) { cp->boxystart = cp->ypos; if (!name) @@ -165,8 +165,8 @@ void endbox(struct ctlpos *cp) /* * A static line, followed by a full-width edit box. */ -void editboxfw(struct ctlpos *cp, bool password, char *text, - int staticid, int editid) +void editboxfw(struct ctlpos *cp, bool password, bool readonly, + const char *text, int staticid, int editid) { RECT r; @@ -183,7 +183,8 @@ void editboxfw(struct ctlpos *cp, bool password, char *text, r.bottom = EDITHEIGHT; doctl(cp, r, "EDIT", WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | - (password ? ES_PASSWORD : 0), + (password ? ES_PASSWORD : 0) | + (readonly ? ES_READONLY : 0), WS_EX_CLIENTEDGE, "", editid); cp->ypos += EDITHEIGHT + GAPBETWEEN; } @@ -191,7 +192,7 @@ void editboxfw(struct ctlpos *cp, bool password, char *text, /* * A static line, followed by a full-width combo box. */ -void combobox(struct ctlpos *cp, char *text, int staticid, int listid) +void combobox(struct ctlpos *cp, const char *text, int staticid, int listid) { RECT r; @@ -212,9 +213,9 @@ void combobox(struct ctlpos *cp, char *text, int staticid, int listid) cp->ypos += COMBOHEIGHT + GAPBETWEEN; } -struct radio { char *text; int id; }; +struct radio { const char *text; int id; }; -static void radioline_common(struct ctlpos *cp, char *text, int id, +static void radioline_common(struct ctlpos *cp, const char *text, int id, int nacross, struct radio *buttons, int nbuttons) { RECT r; @@ -236,7 +237,7 @@ static void radioline_common(struct ctlpos *cp, char *text, int id, group = WS_GROUP; i = 0; for (j = 0; j < nbuttons; j++) { - char *btext = buttons[j].text; + const char *btext = buttons[j].text; int bid = buttons[j].id; if (i == nacross) { @@ -274,7 +275,7 @@ static void radioline_common(struct ctlpos *cp, char *text, int id, * * (*) Button1 (*) Button2 (*) ButtonWithReallyLongTitle */ -void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...) +void radioline(struct ctlpos *cp, const char *text, int id, int nacross, ...) { va_list ap; struct radio *buttons; @@ -283,7 +284,7 @@ void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...) va_start(ap, nacross); nbuttons = 0; while (1) { - char *btext = va_arg(ap, char *); + const char *btext = va_arg(ap, const char *); if (!btext) break; (void) va_arg(ap, int); /* id */ @@ -293,7 +294,7 @@ void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...) buttons = snewn(nbuttons, struct radio); va_start(ap, nacross); for (i = 0; i < nbuttons; i++) { - buttons[i].text = va_arg(ap, char *); + buttons[i].text = va_arg(ap, const char *); buttons[i].id = va_arg(ap, int); } va_end(ap); @@ -314,7 +315,7 @@ void bareradioline(struct ctlpos *cp, int nacross, ...) va_start(ap, nacross); nbuttons = 0; while (1) { - char *btext = va_arg(ap, char *); + const char *btext = va_arg(ap, const char *); if (!btext) break; (void) va_arg(ap, int); /* id */ @@ -324,7 +325,7 @@ void bareradioline(struct ctlpos *cp, int nacross, ...) buttons = snewn(nbuttons, struct radio); va_start(ap, nacross); for (i = 0; i < nbuttons; i++) { - buttons[i].text = va_arg(ap, char *); + buttons[i].text = va_arg(ap, const char *); buttons[i].id = va_arg(ap, int); } va_end(ap); @@ -336,7 +337,7 @@ void bareradioline(struct ctlpos *cp, int nacross, ...) * A set of radio buttons on multiple lines, with a static above * them. */ -void radiobig(struct ctlpos *cp, char *text, int id, ...) +void radiobig(struct ctlpos *cp, const char *text, int id, ...) { va_list ap; struct radio *buttons; @@ -345,7 +346,7 @@ void radiobig(struct ctlpos *cp, char *text, int id, ...) va_start(ap, id); nbuttons = 0; while (1) { - char *btext = va_arg(ap, char *); + const char *btext = va_arg(ap, const char *); if (!btext) break; (void) va_arg(ap, int); /* id */ @@ -355,7 +356,7 @@ void radiobig(struct ctlpos *cp, char *text, int id, ...) buttons = snewn(nbuttons, struct radio); va_start(ap, id); for (i = 0; i < nbuttons; i++) { - buttons[i].text = va_arg(ap, char *); + buttons[i].text = va_arg(ap, const char *); buttons[i].id = va_arg(ap, int); } va_end(ap); @@ -366,7 +367,7 @@ void radiobig(struct ctlpos *cp, char *text, int id, ...) /* * A single standalone checkbox. */ -void checkbox(struct ctlpos *cp, char *text, int id) +void checkbox(struct ctlpos *cp, const char *text, int id) { RECT r; @@ -385,13 +386,14 @@ void checkbox(struct ctlpos *cp, char *text, int id) * wrapped text (a malloc'ed string containing \ns), and also * returns the number of lines required. */ -char *staticwrap(struct ctlpos *cp, HWND hwnd, char *text, int *lines) +char *staticwrap(struct ctlpos *cp, HWND hwnd, const char *text, int *lines) { HDC hdc = GetDC(hwnd); int width, nlines, j; INT *pwidths, nfit; SIZE size; - char *ret, *p, *q; + const char *p; + char *ret, *q; RECT r; HFONT oldfont, newfont; @@ -471,7 +473,7 @@ char *staticwrap(struct ctlpos *cp, HWND hwnd, char *text, int *lines) /* * A single standalone static text control. */ -void statictext(struct ctlpos *cp, char *text, int lines, int id) +void statictext(struct ctlpos *cp, const char *text, int lines, int id) { RECT r; @@ -504,8 +506,8 @@ void paneltitle(struct ctlpos *cp, int id) /* * A button on the right hand side, with a static to its left. */ -void staticbtn(struct ctlpos *cp, char *stext, int sid, - char *btext, int bid) +void staticbtn(struct ctlpos *cp, const char *stext, int sid, + const char *btext, int bid) { const int height = (PUSHBTNHEIGHT > STATICHEIGHT ? PUSHBTNHEIGHT : STATICHEIGHT); @@ -536,7 +538,7 @@ void staticbtn(struct ctlpos *cp, char *stext, int sid, /* * A simple push button. */ -void button(struct ctlpos *cp, char *btext, int bid, bool defbtn) +void button(struct ctlpos *cp, const char *btext, int bid, bool defbtn) { RECT r; @@ -561,8 +563,8 @@ void button(struct ctlpos *cp, char *btext, int bid, bool defbtn) /* * Like staticbtn, but two buttons. */ -void static2btn(struct ctlpos *cp, char *stext, int sid, - char *btext1, int bid1, char *btext2, int bid2) +void static2btn(struct ctlpos *cp, const char *stext, int sid, + const char *btext1, int bid1, const char *btext2, int bid2) { const int height = (PUSHBTNHEIGHT > STATICHEIGHT ? PUSHBTNHEIGHT : STATICHEIGHT); @@ -603,7 +605,7 @@ void static2btn(struct ctlpos *cp, char *stext, int sid, /* * An edit control on the right hand side, with a static to its left. */ -static void staticedit_internal(struct ctlpos *cp, char *stext, +static void staticedit_internal(struct ctlpos *cp, const char *stext, int sid, int eid, int percentedit, int style) { @@ -634,13 +636,13 @@ static void staticedit_internal(struct ctlpos *cp, char *stext, cp->ypos += height + GAPBETWEEN; } -void staticedit(struct ctlpos *cp, char *stext, +void staticedit(struct ctlpos *cp, const char *stext, int sid, int eid, int percentedit) { staticedit_internal(cp, stext, sid, eid, percentedit, 0); } -void staticpassedit(struct ctlpos *cp, char *stext, +void staticpassedit(struct ctlpos *cp, const char *stext, int sid, int eid, int percentedit) { staticedit_internal(cp, stext, sid, eid, percentedit, ES_PASSWORD); @@ -650,7 +652,7 @@ void staticpassedit(struct ctlpos *cp, char *stext, * A drop-down list box on the right hand side, with a static to * its left. */ -void staticddl(struct ctlpos *cp, char *stext, +void staticddl(struct ctlpos *cp, const char *stext, int sid, int lid, int percentlist) { const int height = (COMBOHEIGHT > STATICHEIGHT ? @@ -683,7 +685,7 @@ void staticddl(struct ctlpos *cp, char *stext, /* * A combo box on the right hand side, with a static to its left. */ -void staticcombo(struct ctlpos *cp, char *stext, +void staticcombo(struct ctlpos *cp, const char *stext, int sid, int lid, int percentlist) { const int height = (COMBOHEIGHT > STATICHEIGHT ? @@ -716,7 +718,7 @@ void staticcombo(struct ctlpos *cp, char *stext, /* * A static, with a full-width drop-down list box below it. */ -void staticddlbig(struct ctlpos *cp, char *stext, +void staticddlbig(struct ctlpos *cp, const char *stext, int sid, int lid) { RECT r; @@ -743,7 +745,7 @@ void staticddlbig(struct ctlpos *cp, char *stext, /* * A big multiline edit control with a static labelling it. */ -void bigeditctrl(struct ctlpos *cp, char *stext, +void bigeditctrl(struct ctlpos *cp, const char *stext, int sid, int eid, int lines) { RECT r; @@ -770,7 +772,7 @@ void bigeditctrl(struct ctlpos *cp, char *stext, /* * A list box with a static labelling it. */ -void listbox(struct ctlpos *cp, char *stext, +void listbox(struct ctlpos *cp, const char *stext, int sid, int lid, int lines, bool multi) { RECT r; @@ -799,7 +801,8 @@ void listbox(struct ctlpos *cp, char *stext, /* * A tab-control substitute when a real tab control is unavailable. */ -void ersatztab(struct ctlpos *cp, char *stext, int sid, int lid, int s2id) +void ersatztab(struct ctlpos *cp, const char *stext, int sid, int lid, + int s2id) { const int height = (COMBOHEIGHT > STATICHEIGHT ? COMBOHEIGHT : STATICHEIGHT); @@ -842,8 +845,8 @@ void ersatztab(struct ctlpos *cp, char *stext, int sid, int lid, int s2id) * A static line, followed by an edit control on the left hand side * and a button on the right. */ -void editbutton(struct ctlpos *cp, char *stext, int sid, - int eid, char *btext, int bid) +void editbutton(struct ctlpos *cp, const char *stext, int sid, + int eid, const char *btext, int bid) { const int height = (EDITHEIGHT > PUSHBTNHEIGHT ? EDITHEIGHT : PUSHBTNHEIGHT); @@ -886,7 +889,7 @@ void editbutton(struct ctlpos *cp, char *stext, int sid, * XXX: this is a rough hack and could be improved. */ void prefslist(struct prefslist *hdl, struct ctlpos *cp, int lines, - char *stext, int sid, int listid, int upbid, int dnbid) + const char *stext, int sid, int listid, int upbid, int dnbid) { const static int percents[] = { 5, 75, 20 }; RECT r; @@ -1250,7 +1253,7 @@ static int winctrl_cmp_byid(void *av, void *bv) } static int winctrl_cmp_byctrl_find(void *av, void *bv) { - union control *a = (union control *)av; + dlgcontrol *a = (dlgcontrol *)av; struct winctrl *b = (struct winctrl *)bv; if (a < b->ctrl) return -1; @@ -1310,7 +1313,7 @@ void winctrl_remove(struct winctrls *wc, struct winctrl *c) assert(ret == c); } -struct winctrl *winctrl_findbyctrl(struct winctrls *wc, union control *ctrl) +struct winctrl *winctrl_findbyctrl(struct winctrls *wc, dlgcontrol *ctrl) { return find234(wc->byctrl, ctrl, winctrl_cmp_byctrl_find); } @@ -1354,7 +1357,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, int ncols, colstart, colspan; struct ctlpos tabdelays[16]; - union control *tabdelayed[16]; + dlgcontrol *tabdelayed[16]; int ntabdelays; struct ctlpos pos; @@ -1367,6 +1370,8 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, base_id = *id; + ctrlset_normalise_aligns(s); + /* Start a containing box, if we have a boxname. */ if (s->boxname && *s->boxname) { struct winctrl *c = snew(struct winctrl); @@ -1402,7 +1407,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, /* Loop over each control in the controlset. */ for (i = 0; i < s->ncontrols; i++) { - union control *ctrl = s->ctrls[i]; + dlgcontrol *ctrl = s->ctrls[i]; /* * Generic processing that pertains to all control types. @@ -1415,7 +1420,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, * CTRL_COLUMNS and doesn't require any control creation at * all. */ - if (ctrl->generic.type == CTRL_COLUMNS) { + if (ctrl->type == CTRL_COLUMNS) { assert((ctrl->columns.ncols == 1) ^ (ncols == 1)); if (ncols == 1) { @@ -1455,10 +1460,10 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, } continue; - } else if (ctrl->generic.type == CTRL_TABDELAY) { + } else if (ctrl->type == CTRL_TABDELAY) { int i; - assert(!ctrl->generic.tabdelay); + assert(!ctrl->delay_taborder); ctrl = ctrl->tabdelay.ctrl; for (i = 0; i < ntabdelays; i++) @@ -1478,8 +1483,8 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, */ int col; - colstart = COLUMN_START(ctrl->generic.column); - colspan = COLUMN_SPAN(ctrl->generic.column); + colstart = COLUMN_START(ctrl->column); + colspan = COLUMN_SPAN(ctrl->column); pos = columns[colstart]; /* structure copy */ pos.width = columns[colstart+colspan-1].width + @@ -1494,7 +1499,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, * tabdelay list, and unset pos.hwnd to inhibit actual * control creation. */ - if (ctrl->generic.tabdelay) { + if (ctrl->delay_taborder) { assert(ntabdelays < lenof(tabdelays)); tabdelays[ntabdelays] = pos; /* structure copy */ tabdelayed[ntabdelays] = ctrl; @@ -1522,22 +1527,28 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, * Now we're ready to actually create the control, by * switching on its type. */ - switch (ctrl->generic.type) { - case CTRL_TEXT: { - char *wrapped, *escaped; - int lines; - num_ids = 1; - wrapped = staticwrap(&pos, cp->hwnd, - ctrl->generic.label, &lines); - escaped = shortcut_escape(wrapped, NO_SHORTCUT); - statictext(&pos, escaped, lines, base_id); - sfree(escaped); - sfree(wrapped); + switch (ctrl->type) { + case CTRL_TEXT: + if (ctrl->text.wrap) { + char *wrapped, *escaped; + int lines; + num_ids = 1; + wrapped = staticwrap(&pos, cp->hwnd, + ctrl->label, &lines); + escaped = shortcut_escape(wrapped, NO_SHORTCUT); + statictext(&pos, escaped, lines, base_id); + sfree(escaped); + sfree(wrapped); + } else { + num_ids = 1; + editboxfw(&pos, false, true, NULL, 0, base_id); + SetDlgItemText(pos.hwnd, base_id, ctrl->label); + MakeDlgItemBorderless(pos.hwnd, base_id); + } break; - } case CTRL_EDITBOX: num_ids = 2; /* static, edit */ - escaped = shortcut_escape(ctrl->editbox.label, + escaped = shortcut_escape(ctrl->label, ctrl->editbox.shortcut); shortcuts[nshortcuts++] = ctrl->editbox.shortcut; if (ctrl->editbox.percentwidth == 100) { @@ -1545,7 +1556,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, combobox(&pos, escaped, base_id, base_id+1); else - editboxfw(&pos, ctrl->editbox.password, escaped, + editboxfw(&pos, ctrl->editbox.password, false, escaped, base_id, base_id+1); } else { if (ctrl->editbox.has_list) { @@ -1564,23 +1575,23 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, struct radio *buttons; int i; - escaped = shortcut_escape(ctrl->radio.label, + escaped = shortcut_escape(ctrl->label, ctrl->radio.shortcut); shortcuts[nshortcuts++] = ctrl->radio.shortcut; buttons = snewn(ctrl->radio.nbuttons, struct radio); for (i = 0; i < ctrl->radio.nbuttons; i++) { - buttons[i].text = - shortcut_escape(ctrl->radio.buttons[i], - (char)(ctrl->radio.shortcuts ? - ctrl->radio.shortcuts[i] : - NO_SHORTCUT)); - buttons[i].id = base_id + 1 + i; - if (ctrl->radio.shortcuts) { - assert(nshortcuts < MAX_SHORTCUTS_PER_CTRL); - shortcuts[nshortcuts++] = ctrl->radio.shortcuts[i]; - } + buttons[i].text = + shortcut_escape(ctrl->radio.buttons[i], + (char)(ctrl->radio.shortcuts ? + ctrl->radio.shortcuts[i] : + NO_SHORTCUT)); + buttons[i].id = base_id + 1 + i; + if (ctrl->radio.shortcuts) { + assert(nshortcuts < MAX_SHORTCUTS_PER_CTRL); + shortcuts[nshortcuts++] = ctrl->radio.shortcuts[i]; + } } radioline_common(&pos, escaped, base_id, @@ -1588,7 +1599,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, buttons, ctrl->radio.nbuttons); for (i = 0; i < ctrl->radio.nbuttons; i++) { - sfree(buttons[i].text); + sfree((char *)buttons[i].text); } sfree(buttons); sfree(escaped); @@ -1596,14 +1607,14 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, } case CTRL_CHECKBOX: num_ids = 1; - escaped = shortcut_escape(ctrl->checkbox.label, + escaped = shortcut_escape(ctrl->label, ctrl->checkbox.shortcut); shortcuts[nshortcuts++] = ctrl->checkbox.shortcut; checkbox(&pos, escaped, base_id); sfree(escaped); break; case CTRL_BUTTON: - escaped = shortcut_escape(ctrl->button.label, + escaped = shortcut_escape(ctrl->label, ctrl->button.shortcut); shortcuts[nshortcuts++] = ctrl->button.shortcut; if (ctrl->button.iscancel) @@ -1614,7 +1625,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, break; case CTRL_LISTBOX: num_ids = 2; - escaped = shortcut_escape(ctrl->listbox.label, + escaped = shortcut_escape(ctrl->label, ctrl->listbox.shortcut); shortcuts[nshortcuts++] = ctrl->listbox.shortcut; if (ctrl->listbox.draglist) { @@ -1663,18 +1674,20 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, sfree(escaped); break; case CTRL_FILESELECT: - num_ids = 3; - escaped = shortcut_escape(ctrl->fileselect.label, - ctrl->fileselect.shortcut); + escaped = shortcut_escape(ctrl->label, ctrl->fileselect.shortcut); shortcuts[nshortcuts++] = ctrl->fileselect.shortcut; - editbutton(&pos, escaped, base_id, base_id+1, - "Bro&wse...", base_id+2); - shortcuts[nshortcuts++] = 'w'; + num_ids = 3; + if (!ctrl->fileselect.just_button) { + editbutton(&pos, escaped, base_id, base_id+1, + "Browse...", base_id+2); + } else { + button(&pos, escaped, base_id+2, false); + } sfree(escaped); break; case CTRL_FONTSELECT: num_ids = 3; - escaped = shortcut_escape(ctrl->fontselect.label, + escaped = shortcut_escape(ctrl->label, ctrl->fontselect.shortcut); shortcuts[nshortcuts++] = ctrl->fontselect.shortcut; statictext(&pos, escaped, 1, base_id); @@ -1709,28 +1722,43 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, if (actual_base_id == base_id) base_id += num_ids; - if (ctrl->generic.align_next_to) { + if (ctrl->align_next_to) { /* * Implement align_next_to by looking at the y extents * of the two controls now that both are created, and * moving one or the other downwards so that they're * centred on a common horizontal line. */ - struct winctrl *c2 = winctrl_findbyctrl( - wc, ctrl->generic.align_next_to); - HWND win1 = GetDlgItem(pos.hwnd, c->align_id); - HWND win2 = GetDlgItem(pos.hwnd, c2->align_id); - RECT rect1, rect2; - if (win1 && win2 && - GetWindowRect(win1, &rect1) && - GetWindowRect(win2, &rect2)) { - LONG top = (rect1.top < rect2.top ? rect1.top : rect2.top); - LONG bottom = (rect1.bottom > rect2.bottom ? - rect1.bottom : rect2.bottom); - move_windows(pos.hwnd, c->base_id, c->num_ids, - (top + bottom - rect1.top - rect1.bottom)/2); - move_windows(pos.hwnd, c2->base_id, c2->num_ids, - (top + bottom - rect2.top - rect2.bottom)/2); + LONG mid2 = 0; + for (dlgcontrol *thisctrl = ctrl; thisctrl; + thisctrl = thisctrl->align_next_to) { + struct winctrl *thisc = winctrl_findbyctrl(wc, thisctrl); + assert(thisc); + + HWND win = GetDlgItem(pos.hwnd, thisc->align_id); + assert(win); + + RECT rect; + if (!GetWindowRect(win, &rect)) + continue; + if (mid2 < rect.top + rect.bottom) + mid2 = rect.top + rect.bottom; + } + + for (dlgcontrol *thisctrl = ctrl; thisctrl; + thisctrl = thisctrl->align_next_to) { + struct winctrl *thisc = winctrl_findbyctrl(wc, thisctrl); + assert(thisc); + + HWND win = GetDlgItem(pos.hwnd, thisc->align_id); + assert(win); + + RECT rect; + if (!GetWindowRect(win, &rect)) + continue; + + LONG dy = (mid2 - (rect.top + rect.bottom)) / 2; + move_windows(pos.hwnd, thisc->base_id, thisc->num_ids, dy); } } } else { @@ -1762,7 +1790,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, endbox(cp); } -static void winctrl_set_focus(union control *ctrl, struct dlgparam *dp, +static void winctrl_set_focus(dlgcontrol *ctrl, struct dlgparam *dp, bool has_focus) { if (has_focus) { @@ -1775,7 +1803,7 @@ static void winctrl_set_focus(union control *ctrl, struct dlgparam *dp, } } -union control *dlg_last_focused(union control *ctrl, dlgparam *dp) +dlgcontrol *dlg_last_focused(dlgcontrol *ctrl, dlgparam *dp) { return dp->focused == ctrl ? dp->lastfocused : dp->focused; } @@ -1788,7 +1816,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, WPARAM wParam, LPARAM lParam) { struct winctrl *c; - union control *ctrl; + dlgcontrol *ctrl; int i, id; bool ret; static UINT draglistmsg = WM_NULL; @@ -1827,7 +1855,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, SetMapMode(hdc, MM_TEXT); /* ensure logical units == pixels */ GetTextExtentPoint32(hdc, (char *)c->data, - strlen((char *)c->data), &s); + strlen((char *)c->data), &s); DrawEdge(hdc, &r, EDGE_ETCHED, BF_ADJUST | BF_RECT); TextOut(hdc, r.left + (r.right-r.left-s.cx)/2, @@ -1840,7 +1868,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, ctrl = c->ctrl; id = LOWORD(wParam) - c->base_id; - if (!ctrl || !ctrl->generic.handler) + if (!ctrl || !ctrl->handler) return false; /* nothing we can do here */ /* @@ -1856,7 +1884,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, /* * Now switch on the control type and the message. */ - switch (ctrl->generic.type) { + switch (ctrl->type) { case CTRL_EDITBOX: if (msg == WM_COMMAND && !ctrl->editbox.has_list && (HIWORD(wParam) == EN_SETFOCUS || HIWORD(wParam) == EN_KILLFOCUS)) @@ -1867,7 +1895,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, if (msg == WM_COMMAND && !ctrl->editbox.has_list && HIWORD(wParam) == EN_CHANGE) - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); if (msg == WM_COMMAND && ctrl->editbox.has_list) { if (HIWORD(wParam) == CBN_SELCHANGE) { @@ -1883,11 +1911,11 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, index, (LPARAM)text); SetDlgItemText(dp->hwnd, c->base_id+1, text); sfree(text); - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); } else if (HIWORD(wParam) == CBN_EDITCHANGE) { - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); } else if (HIWORD(wParam) == CBN_KILLFOCUS) { - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_REFRESH); + ctrl->handler(ctrl, dp, dp->data, EVENT_REFRESH); } } @@ -1907,7 +1935,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, (HIWORD(wParam) == BN_CLICKED || HIWORD(wParam) == BN_DOUBLECLICKED) && IsDlgButtonChecked(dp->hwnd, LOWORD(wParam))) { - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); } break; case CTRL_CHECKBOX: @@ -1917,7 +1945,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, if (msg == WM_COMMAND && (HIWORD(wParam) == BN_CLICKED || HIWORD(wParam) == BN_DOUBLECLICKED)) { - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); } break; case CTRL_BUTTON: @@ -1927,7 +1955,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, if (msg == WM_COMMAND && (HIWORD(wParam) == BN_CLICKED || HIWORD(wParam) == BN_DOUBLECLICKED)) { - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_ACTION); + ctrl->handler(ctrl, dp, dp->data, EVENT_ACTION); } break; case CTRL_LISTBOX: @@ -1945,14 +1973,14 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, pret = handle_prefslist(c->data, NULL, 0, (msg != WM_COMMAND), dp->hwnd, wParam, lParam); if (pret & 2) - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); ret = pret & 1; } else { if (msg == WM_COMMAND && HIWORD(wParam) == LBN_DBLCLK) { SetCapture(dp->hwnd); - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_ACTION); + ctrl->handler(ctrl, dp, dp->data, EVENT_ACTION); } else if (msg == WM_COMMAND && HIWORD(wParam) == LBN_SELCHANGE) { - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_SELCHANGE); + ctrl->handler(ctrl, dp, dp->data, EVENT_SELCHANGE); } } break; @@ -1964,7 +1992,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS)) winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS); if (msg == WM_COMMAND && id == 1 && HIWORD(wParam) == EN_CHANGE) - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); if (id == 2 && (msg == WM_COMMAND && (HIWORD(wParam) == BN_CLICKED || @@ -1981,15 +2009,27 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, of.lpstrCustomFilter = NULL; of.nFilterIndex = 1; of.lpstrFile = filename; - GetDlgItemText(dp->hwnd, c->base_id+1, filename, lenof(filename)); - filename[lenof(filename)-1] = '\0'; + if (!ctrl->fileselect.just_button) { + GetDlgItemText(dp->hwnd, c->base_id+1, + filename, lenof(filename)); + filename[lenof(filename)-1] = '\0'; + } else { + *filename = '\0'; + } of.nMaxFile = lenof(filename); of.lpstrFileTitle = NULL; of.lpstrTitle = ctrl->fileselect.title; of.Flags = 0; if (request_file(NULL, &of, false, ctrl->fileselect.for_writing)) { - SetDlgItemText(dp->hwnd, c->base_id + 1, filename); - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + if (!ctrl->fileselect.just_button) { + SetDlgItemText(dp->hwnd, c->base_id + 1, filename); + ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + } else { + assert(!c->data); + c->data = filename; + ctrl->handler(ctrl, dp, dp->data, EVENT_ACTION); + c->data = NULL; + } } } break; @@ -2034,7 +2074,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, dlg_fontsel_set(ctrl, dp, fs); fontspec_free(fs); - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); } } break; @@ -2065,7 +2105,7 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, dp->coloursel_result.ok = true; } else dp->coloursel_result.ok = false; - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_CALLBACK); + ctrl->handler(ctrl, dp, dp->data, EVENT_CALLBACK); } return ret; @@ -2094,12 +2134,12 @@ bool winctrl_context_help(struct dlgparam *dp, HWND hwnd, int id) /* * This is the Windows front end, so we're allowed to assume - * `helpctx.p' is a context string. + * `helpctx' is a context string. */ - if (!c->ctrl || !c->ctrl->generic.helpctx.p) + if (!c->ctrl || !c->ctrl->helpctx) return false; /* no help available for this ctrl */ - launch_help(hwnd, c->ctrl->generic.helpctx.p); + launch_help(hwnd, c->ctrl->helpctx); return true; } @@ -2108,7 +2148,7 @@ bool winctrl_context_help(struct dlgparam *dp, HWND hwnd, int id) * mechanism can call to access the dialog box entries. */ -static struct winctrl *dlg_findbyctrl(struct dlgparam *dp, union control *ctrl) +static struct winctrl *dlg_findbyctrl(struct dlgparam *dp, dlgcontrol *ctrl) { int i; @@ -2120,7 +2160,7 @@ static struct winctrl *dlg_findbyctrl(struct dlgparam *dp, union control *ctrl) return NULL; } -bool dlg_is_visible(union control *ctrl, dlgparam *dp) +bool dlg_is_visible(dlgcontrol *ctrl, dlgparam *dp) { /* * In this implementation of the dialog box, we physically @@ -2131,91 +2171,99 @@ bool dlg_is_visible(union control *ctrl, dlgparam *dp) return dlg_findbyctrl(dp, ctrl) != NULL; } -void dlg_radiobutton_set(union control *ctrl, dlgparam *dp, int whichbutton) +void dlg_radiobutton_set(dlgcontrol *ctrl, dlgparam *dp, int whichbutton) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_RADIO); + assert(c && c->ctrl->type == CTRL_RADIO); CheckRadioButton(dp->hwnd, c->base_id + 1, c->base_id + c->ctrl->radio.nbuttons, c->base_id + 1 + whichbutton); } -int dlg_radiobutton_get(union control *ctrl, dlgparam *dp) +int dlg_radiobutton_get(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); int i; - assert(c && c->ctrl->generic.type == CTRL_RADIO); + assert(c && c->ctrl->type == CTRL_RADIO); for (i = 0; i < c->ctrl->radio.nbuttons; i++) if (IsDlgButtonChecked(dp->hwnd, c->base_id + 1 + i)) return i; unreachable("no radio button was checked"); } -void dlg_checkbox_set(union control *ctrl, dlgparam *dp, bool checked) +void dlg_checkbox_set(dlgcontrol *ctrl, dlgparam *dp, bool checked) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_CHECKBOX); + assert(c && c->ctrl->type == CTRL_CHECKBOX); CheckDlgButton(dp->hwnd, c->base_id, checked); } -bool dlg_checkbox_get(union control *ctrl, dlgparam *dp) +bool dlg_checkbox_get(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_CHECKBOX); + assert(c && c->ctrl->type == CTRL_CHECKBOX); return 0 != IsDlgButtonChecked(dp->hwnd, c->base_id); } -void dlg_editbox_set(union control *ctrl, dlgparam *dp, char const *text) +void dlg_editbox_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_EDITBOX); + assert(c && c->ctrl->type == CTRL_EDITBOX); SetDlgItemText(dp->hwnd, c->base_id+1, text); } -char *dlg_editbox_get(union control *ctrl, dlgparam *dp) +char *dlg_editbox_get(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_EDITBOX); + assert(c && c->ctrl->type == CTRL_EDITBOX); return GetDlgItemText_alloc(dp->hwnd, c->base_id+1); } +void dlg_editbox_select_range(dlgcontrol *ctrl, dlgparam *dp, + size_t start, size_t len) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + assert(c && c->ctrl->type == CTRL_EDITBOX); + SendDlgItemMessage(dp->hwnd, c->base_id+1, EM_SETSEL, start, start+len); +} + /* The `listbox' functions can also apply to combo boxes. */ -void dlg_listbox_clear(union control *ctrl, dlgparam *dp) +void dlg_listbox_clear(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); int msg; assert(c && - (c->ctrl->generic.type == CTRL_LISTBOX || - (c->ctrl->generic.type == CTRL_EDITBOX && + (c->ctrl->type == CTRL_LISTBOX || + (c->ctrl->type == CTRL_EDITBOX && c->ctrl->editbox.has_list))); - msg = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? + msg = (c->ctrl->type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? LB_RESETCONTENT : CB_RESETCONTENT); SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, 0); } -void dlg_listbox_del(union control *ctrl, dlgparam *dp, int index) +void dlg_listbox_del(dlgcontrol *ctrl, dlgparam *dp, int index) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); int msg; assert(c && - (c->ctrl->generic.type == CTRL_LISTBOX || - (c->ctrl->generic.type == CTRL_EDITBOX && + (c->ctrl->type == CTRL_LISTBOX || + (c->ctrl->type == CTRL_EDITBOX && c->ctrl->editbox.has_list))); - msg = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? + msg = (c->ctrl->type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? LB_DELETESTRING : CB_DELETESTRING); SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, index, 0); } -void dlg_listbox_add(union control *ctrl, dlgparam *dp, char const *text) +void dlg_listbox_add(dlgcontrol *ctrl, dlgparam *dp, char const *text) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); int msg; assert(c && - (c->ctrl->generic.type == CTRL_LISTBOX || - (c->ctrl->generic.type == CTRL_EDITBOX && + (c->ctrl->type == CTRL_LISTBOX || + (c->ctrl->type == CTRL_EDITBOX && c->ctrl->editbox.has_list))); - msg = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? + msg = (c->ctrl->type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? LB_ADDSTRING : CB_ADDSTRING); SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, (LPARAM)text); } @@ -2227,39 +2275,39 @@ void dlg_listbox_add(union control *ctrl, dlgparam *dp, char const *text) * strings in any listbox then you MUST not assign them different * IDs and expect to get meaningful results back. */ -void dlg_listbox_addwithid(union control *ctrl, dlgparam *dp, +void dlg_listbox_addwithid(dlgcontrol *ctrl, dlgparam *dp, char const *text, int id) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); int msg, msg2, index; assert(c && - (c->ctrl->generic.type == CTRL_LISTBOX || - (c->ctrl->generic.type == CTRL_EDITBOX && + (c->ctrl->type == CTRL_LISTBOX || + (c->ctrl->type == CTRL_EDITBOX && c->ctrl->editbox.has_list))); - msg = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? + msg = (c->ctrl->type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? LB_ADDSTRING : CB_ADDSTRING); - msg2 = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? - LB_SETITEMDATA : CB_SETITEMDATA); + msg2 = (c->ctrl->type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? + LB_SETITEMDATA : CB_SETITEMDATA); index = SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, (LPARAM)text); SendDlgItemMessage(dp->hwnd, c->base_id+1, msg2, index, (LPARAM)id); } -int dlg_listbox_getid(union control *ctrl, dlgparam *dp, int index) +int dlg_listbox_getid(dlgcontrol *ctrl, dlgparam *dp, int index) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); int msg; - assert(c && c->ctrl->generic.type == CTRL_LISTBOX); + assert(c && c->ctrl->type == CTRL_LISTBOX); msg = (c->ctrl->listbox.height != 0 ? LB_GETITEMDATA : CB_GETITEMDATA); return SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, index, 0); } /* dlg_listbox_index returns <0 if no single element is selected. */ -int dlg_listbox_index(union control *ctrl, dlgparam *dp) +int dlg_listbox_index(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); int msg, ret; - assert(c && c->ctrl->generic.type == CTRL_LISTBOX); + assert(c && c->ctrl->type == CTRL_LISTBOX); if (c->ctrl->listbox.multisel) { assert(c->ctrl->listbox.height != 0); /* not combo box */ ret = SendDlgItemMessage(dp->hwnd, c->base_id+1, LB_GETSELCOUNT, 0, 0); @@ -2274,41 +2322,41 @@ int dlg_listbox_index(union control *ctrl, dlgparam *dp) return ret; } -bool dlg_listbox_issel(union control *ctrl, dlgparam *dp, int index) +bool dlg_listbox_issel(dlgcontrol *ctrl, dlgparam *dp, int index) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_LISTBOX && + assert(c && c->ctrl->type == CTRL_LISTBOX && c->ctrl->listbox.multisel && c->ctrl->listbox.height != 0); return SendDlgItemMessage(dp->hwnd, c->base_id+1, LB_GETSEL, index, 0); } -void dlg_listbox_select(union control *ctrl, dlgparam *dp, int index) +void dlg_listbox_select(dlgcontrol *ctrl, dlgparam *dp, int index) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); int msg; - assert(c && c->ctrl->generic.type == CTRL_LISTBOX && + assert(c && c->ctrl->type == CTRL_LISTBOX && !c->ctrl->listbox.multisel); msg = (c->ctrl->listbox.height != 0 ? LB_SETCURSEL : CB_SETCURSEL); SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, index, 0); } -void dlg_text_set(union control *ctrl, dlgparam *dp, char const *text) +void dlg_text_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_TEXT); + assert(c && c->ctrl->type == CTRL_TEXT); SetDlgItemText(dp->hwnd, c->base_id, text); } -void dlg_label_change(union control *ctrl, dlgparam *dp, char const *text) +void dlg_label_change(dlgcontrol *ctrl, dlgparam *dp, char const *text) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); char *escaped = NULL; int id = -1; assert(c); - switch (c->ctrl->generic.type) { + switch (c->ctrl->type) { case CTRL_EDITBOX: escaped = shortcut_escape(text, c->ctrl->editbox.shortcut); id = c->base_id; @@ -2331,7 +2379,10 @@ void dlg_label_change(union control *ctrl, dlgparam *dp, char const *text) break; case CTRL_FILESELECT: escaped = shortcut_escape(text, ctrl->fileselect.shortcut); - id = c->base_id; + if (ctrl->fileselect.just_button) + id = c->base_id + 2; /* the button */ + else + id = c->base_id; /* the label */ break; case CTRL_FONTSELECT: escaped = shortcut_escape(text, ctrl->fontselect.shortcut); @@ -2346,30 +2397,37 @@ void dlg_label_change(union control *ctrl, dlgparam *dp, char const *text) } } -void dlg_filesel_set(union control *ctrl, dlgparam *dp, Filename *fn) +void dlg_filesel_set(dlgcontrol *ctrl, dlgparam *dp, Filename *fn) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_FILESELECT); + assert(c); + assert(c->ctrl->type == CTRL_FILESELECT); + assert(!c->ctrl->fileselect.just_button); SetDlgItemText(dp->hwnd, c->base_id+1, fn->path); } -Filename *dlg_filesel_get(union control *ctrl, dlgparam *dp) +Filename *dlg_filesel_get(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); char *tmp; Filename *ret; - assert(c && c->ctrl->generic.type == CTRL_FILESELECT); - tmp = GetDlgItemText_alloc(dp->hwnd, c->base_id+1); - ret = filename_from_str(tmp); - sfree(tmp); - return ret; + assert(c); + assert(c->ctrl->type == CTRL_FILESELECT); + if (!c->ctrl->fileselect.just_button) { + tmp = GetDlgItemText_alloc(dp->hwnd, c->base_id+1); + ret = filename_from_str(tmp); + sfree(tmp); + return ret; + } else { + return filename_from_str(c->data); + } } -void dlg_fontsel_set(union control *ctrl, dlgparam *dp, FontSpec *fs) +void dlg_fontsel_set(dlgcontrol *ctrl, dlgparam *dp, FontSpec *fs) { char *buf, *boldstr; struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_FONTSELECT); + assert(c && c->ctrl->type == CTRL_FONTSELECT); fontspec_free((FontSpec *)c->data); c->data = fontspec_copy(fs); @@ -2387,10 +2445,10 @@ void dlg_fontsel_set(union control *ctrl, dlgparam *dp, FontSpec *fs) dlg_auto_set_fixed_pitch_flag(dp); } -FontSpec *dlg_fontsel_get(union control *ctrl, dlgparam *dp) +FontSpec *dlg_fontsel_get(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->generic.type == CTRL_FONTSELECT); + assert(c && c->ctrl->type == CTRL_FONTSELECT); return fontspec_copy((FontSpec *)c->data); } @@ -2399,32 +2457,32 @@ FontSpec *dlg_fontsel_get(union control *ctrl, dlgparam *dp) * cause the front end (if possible) to delay updating the screen * until it's all complete, thus avoiding flicker. */ -void dlg_update_start(union control *ctrl, dlgparam *dp) +void dlg_update_start(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - if (c && c->ctrl->generic.type == CTRL_LISTBOX) { + if (c && c->ctrl->type == CTRL_LISTBOX) { SendDlgItemMessage(dp->hwnd, c->base_id+1, WM_SETREDRAW, false, 0); } } -void dlg_update_done(union control *ctrl, dlgparam *dp) +void dlg_update_done(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - if (c && c->ctrl->generic.type == CTRL_LISTBOX) { + if (c && c->ctrl->type == CTRL_LISTBOX) { HWND hw = GetDlgItem(dp->hwnd, c->base_id+1); SendMessage(hw, WM_SETREDRAW, true, 0); InvalidateRect(hw, NULL, true); } } -void dlg_set_focus(union control *ctrl, dlgparam *dp) +void dlg_set_focus(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); int id; HWND ctl; if (!c) return; - switch (ctrl->generic.type) { + switch (ctrl->type) { case CTRL_EDITBOX: id = c->base_id + 1; break; case CTRL_RADIO: for (id = c->base_id + ctrl->radio.nbuttons; id > 1; id--) @@ -2475,7 +2533,7 @@ void dlg_end(dlgparam *dp, int value) dp->endresult = value; } -void dlg_refresh(union control *ctrl, dlgparam *dp) +void dlg_refresh(dlgcontrol *ctrl, dlgparam *dp) { int i, j; struct winctrl *c; @@ -2488,21 +2546,21 @@ void dlg_refresh(union control *ctrl, dlgparam *dp) for (i = 0; (c = winctrl_findbyindex(dp->controltrees[j], i)) != NULL; i++) { - if (c->ctrl && c->ctrl->generic.handler != NULL) - c->ctrl->generic.handler(c->ctrl, dp, - dp->data, EVENT_REFRESH); + if (c->ctrl && c->ctrl->handler != NULL) + c->ctrl->handler(c->ctrl, dp, + dp->data, EVENT_REFRESH); } } } else { /* * Send EVENT_REFRESH to a specific control. */ - if (ctrl->generic.handler != NULL) - ctrl->generic.handler(ctrl, dp, dp->data, EVENT_REFRESH); + if (ctrl->handler != NULL) + ctrl->handler(ctrl, dp, dp->data, EVENT_REFRESH); } } -void dlg_coloursel_start(union control *ctrl, dlgparam *dp, int r, int g, int b) +void dlg_coloursel_start(dlgcontrol *ctrl, dlgparam *dp, int r, int g, int b) { dp->coloursel_wanted = true; dp->coloursel_result.r = r; @@ -2510,7 +2568,7 @@ void dlg_coloursel_start(union control *ctrl, dlgparam *dp, int r, int g, int b) dp->coloursel_result.b = b; } -bool dlg_coloursel_results(union control *ctrl, dlgparam *dp, +bool dlg_coloursel_results(dlgcontrol *ctrl, dlgparam *dp, int *r, int *g, int *b) { if (dp->coloursel_result.ok) { diff --git a/windows/wincapi.h b/windows/cryptoapi.h index 732412e2..4ea7fe49 100644 --- a/windows/wincapi.h +++ b/windows/cryptoapi.h @@ -1,12 +1,10 @@ /* - * wincapi.h: Windows Crypto API functions defined in wincapi.c that + * cryptoapi.h: Windows Crypto API functions defined in PuTTY that * use the crypt32 library. Also centralises the machinery for * dynamically loading that library, and our own functions using that * in turn. */ -#if !defined NO_SECURITY - DECL_WINDOWS_FUNCTION(extern, BOOL, CryptProtectMemory, (LPVOID,DWORD,DWORD)); bool got_crypt(void); @@ -27,5 +25,3 @@ bool got_crypt(void); * The returned string is dynamically allocated. */ char *capi_obfuscate_string(const char *realname); - -#endif diff --git a/windows/windlg.c b/windows/dialog.c index 9c5fdb76..5e49cca9 100644 --- a/windows/windlg.c +++ b/windows/dialog.c @@ -1,5 +1,5 @@ /* - * windlg.c - dialogs for PuTTY(tel), including the configuration dialog. + * dialog.c - dialogs for PuTTY(tel), including the configuration dialog. */ #include <stdio.h> @@ -11,8 +11,8 @@ #include "putty.h" #include "ssh.h" -#include "win_res.h" -#include "winseat.h" +#include "putty-rc.h" +#include "win-gui-seat.h" #include "storage.h" #include "dialog.h" #include "licence.h" @@ -27,21 +27,190 @@ #define ICON_BIG 1 #endif +typedef struct PortableDialogStuff { + /* + * These are the various bits of data required to handle a dialog + * box that's been built up from the cross-platform dialog.c + * system. + */ + + /* The 'controlbox' that was returned from the portable setup function */ + struct controlbox *ctrlbox; + + /* The dlgparam that's passed to all the runtime dlg_* functions. + * Declared as an array of 1 so it's convenient to pass it as a pointer. */ + struct dlgparam dp[1]; + + /* + * Collections of instantiated controls. There can be more than + * one of these, because sometimes you want to destroy and + * recreate a subset of them - e.g. when switching panes in the + * main PuTTY config box, you delete and recreate _most_ of the + * controls, but not the OK and Cancel buttons at the bottom. + */ + size_t nctrltrees; + struct winctrls *ctrltrees; + + /* + * Flag indicating whether the dialog box has been initialised. + * Used to suppresss spurious firing of message handlers during + * setup. + */ + bool initialised; +} PortableDialogStuff; + /* - * These are the various bits of data required to handle the - * portable-dialog stuff in the config box. Having them at file - * scope in here isn't too bad a place to put them; if we were ever - * to need more than one config box per process we could always - * shift them to a per-config-box structure stored in GWL_USERDATA. + * Initialise a PortableDialogStuff, before launching the dialog box. */ -static struct controlbox *ctrlbox; +static PortableDialogStuff *pds_new(size_t nctrltrees) +{ + PortableDialogStuff *pds = snew(PortableDialogStuff); + memset(pds, 0, sizeof(*pds)); + + pds->ctrlbox = ctrl_new_box(); + + dp_init(pds->dp); + + pds->nctrltrees = nctrltrees; + pds->ctrltrees = snewn(pds->nctrltrees, struct winctrls); + for (size_t i = 0; i < pds->nctrltrees; i++) { + winctrl_init(&pds->ctrltrees[i]); + dp_add_tree(pds->dp, &pds->ctrltrees[i]); + } + + pds->dp->errtitle = dupprintf("%s Error", appname); + + pds->initialised = false; + + return pds; +} + +static void pds_free(PortableDialogStuff *pds) +{ + ctrl_free_box(pds->ctrlbox); + + dp_cleanup(pds->dp); + + for (size_t i = 0; i < pds->nctrltrees; i++) + winctrl_cleanup(&pds->ctrltrees[i]); + sfree(pds->ctrltrees); + + sfree(pds); +} + +static INT_PTR pds_default_dlgproc(PortableDialogStuff *pds, HWND hwnd, + UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_LBUTTONUP: + /* + * Button release should trigger WM_OK if there was a + * previous double click on the host CA list. + */ + ReleaseCapture(); + if (pds->dp->ended) + ShinyEndDialog(hwnd, pds->dp->endresult ? 1 : 0); + break; + case WM_COMMAND: + case WM_DRAWITEM: + default: { /* also handle drag list msg here */ + /* + * Only process WM_COMMAND once the dialog is fully formed. + */ + int ret; + if (pds->initialised) { + ret = winctrl_handle_command(pds->dp, msg, wParam, lParam); + if (pds->dp->ended && GetCapture() != hwnd) + ShinyEndDialog(hwnd, pds->dp->endresult ? 1 : 0); + } else + ret = 0; + return ret; + } + case WM_HELP: + if (!winctrl_context_help(pds->dp, + hwnd, ((LPHELPINFO)lParam)->iCtrlId)) + MessageBeep(0); + break; + case WM_CLOSE: + quit_help(hwnd); + ShinyEndDialog(hwnd, 0); + return 0; + + /* Grrr Explorer will maximize Dialogs! */ + case WM_SIZE: + if (wParam == SIZE_MAXIMIZED) + force_normal(hwnd); + return 0; + + } + return 0; +} + +static void pds_initdialog_start(PortableDialogStuff *pds, HWND hwnd) +{ + pds->dp->hwnd = hwnd; + + if (pds->dp->wintitle) /* apply override title, if provided */ + SetWindowText(hwnd, pds->dp->wintitle); + + /* The portable dialog system generally includes the ability to + * handle context help for particular controls. Enable the + * relevant window styles if we have a help file available. */ + if (has_help()) { + LONG_PTR style = GetWindowLongPtr(hwnd, GWL_EXSTYLE); + SetWindowLongPtr(hwnd, GWL_EXSTYLE, style | WS_EX_CONTEXTHELP); + } else { + /* If not, and if the dialog template provided a top-level + * Help button, delete it */ + HWND item = GetDlgItem(hwnd, IDC_HELPBTN); + if (item) + DestroyWindow(item); + } +} + /* - * ctrls_base holds the OK and Cancel buttons: the controls which - * are present in all dialog panels. ctrls_panel holds the ones - * which change from panel to panel. + * Create the panelfuls of controls in the configuration box. */ -static struct winctrls ctrls_base, ctrls_panel; -static struct dlgparam dp; +static void pds_create_controls( + PortableDialogStuff *pds, size_t which_tree, int base_id, + int left, int right, int top, char *path) +{ + struct ctlpos cp; + + ctlposinit(&cp, pds->dp->hwnd, left, right, top); + + for (int index = -1; (index = ctrl_find_path( + pds->ctrlbox, path, index)) >= 0 ;) { + struct controlset *s = pds->ctrlbox->ctrlsets[index]; + winctrl_layout(pds->dp, &pds->ctrltrees[which_tree], &cp, s, &base_id); + } +} + +static void pds_initdialog_finish(PortableDialogStuff *pds) +{ + /* + * Set focus into the first available control in ctrltree #0, + * which the caller was expected to set up to be the one + * containing the dialog controls likely to be used first. + */ + struct winctrl *c; + for (int i = 0; (c = winctrl_findbyindex(&pds->ctrltrees[0], i)) != NULL; + i++) { + if (c->ctrl) { + dlg_set_focus(c->ctrl, pds->dp); + break; + } + } + + /* + * Now we've finished creating our initial set of controls, + * it's safe to actually show the window without risking setup + * flicker. + */ + ShowWindow(pds->dp->hwnd, SW_SHOWNORMAL); + + pds->initialised = true; +} #define LOGEVENT_INITIAL_MAX 128 #define LOGEVENT_CIRCULAR_MAX 128 @@ -216,10 +385,10 @@ static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg, SetWindowText(hwnd, str); sfree(str); char *buildinfo_text = buildinfo("\r\n"); - char *text = dupprintf - ("%s\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s", - appname, ver, buildinfo_text, - "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved."); + char *text = dupprintf( + "%s\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s", + appname, ver, buildinfo_text, + "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved."); sfree(buildinfo_text); SetDlgItemText(hwnd, IDA_TEXT, text); MakeDlgItemBorderless(hwnd, IDA_TEXT); @@ -255,57 +424,6 @@ static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg, return 0; } -static int SaneDialogBox(HINSTANCE hinst, - LPCTSTR tmpl, - HWND hwndparent, - DLGPROC lpDialogFunc) -{ - WNDCLASS wc; - HWND hwnd; - MSG msg; - int flags; - int ret; - int gm; - - wc.style = CS_DBLCLKS | CS_SAVEBITS | CS_BYTEALIGNWINDOW; - wc.lpfnWndProc = DefDlgProc; - wc.cbClsExtra = 0; - wc.cbWndExtra = DLGWINDOWEXTRA + 2*sizeof(LONG_PTR); - wc.hInstance = hinst; - wc.hIcon = NULL; - wc.hCursor = LoadCursor(NULL, IDC_ARROW); - wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND +1); - wc.lpszMenuName = NULL; - wc.lpszClassName = "PuTTYConfigBox"; - RegisterClass(&wc); - - hwnd = CreateDialog(hinst, tmpl, hwndparent, lpDialogFunc); - - SetWindowLongPtr(hwnd, BOXFLAGS, 0); /* flags */ - SetWindowLongPtr(hwnd, BOXRESULT, 0); /* result from SaneEndDialog */ - - while ((gm=GetMessage(&msg, NULL, 0, 0)) > 0) { - flags=GetWindowLongPtr(hwnd, BOXFLAGS); - if (!(flags & DF_END) && !IsDialogMessage(hwnd, &msg)) - DispatchMessage(&msg); - if (flags & DF_END) - break; - } - - if (gm == 0) - PostQuitMessage(msg.wParam); /* We got a WM_QUIT, pass it on */ - - ret=GetWindowLongPtr(hwnd, BOXRESULT); - DestroyWindow(hwnd); - return ret; -} - -static void SaneEndDialog(HWND hwnd, int ret) -{ - SetWindowLongPtr(hwnd, BOXRESULT, ret); - SetWindowLongPtr(hwnd, BOXFLAGS, DF_END); -} - /* * Null dialog procedure. */ @@ -355,81 +473,37 @@ static HTREEITEM treeview_insert(struct treeview_faff *faff, return newitem; } -/* - * Create the panelfuls of controls in the configuration box. - */ -static void create_controls(HWND hwnd, char *path) -{ - struct ctlpos cp; - int index; - int base_id; - struct winctrls *wc; - - if (!path[0]) { - /* - * Here we must create the basic standard controls. - */ - ctlposinit(&cp, hwnd, 3, 3, 235); - wc = &ctrls_base; - base_id = IDCX_STDBASE; - } else { - /* - * Otherwise, we're creating the controls for a particular - * panel. - */ - ctlposinit(&cp, hwnd, 100, 3, 13); - wc = &ctrls_panel; - base_id = IDCX_PANELBASE; - } +const char *dialog_box_demo_screenshot_filename = NULL; - for (index=-1; (index = ctrl_find_path(ctrlbox, path, index)) >= 0 ;) { - struct controlset *s = ctrlbox->ctrlsets[index]; - winctrl_layout(&dp, wc, &cp, s, &base_id); - } -} +/* ctrltrees indices for the main dialog box */ +enum { + TREE_PANEL, /* things we swap out every time treeview selects a new pane */ + TREE_BASE, /* fixed things at the bottom like OK and Cancel buttons */ +}; /* * This function is the configuration box. * (Being a dialog procedure, in general it returns 0 if the default * dialog processing should be performed, and 1 if it should not.) */ -static INT_PTR CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) +static INT_PTR GenericMainDlgProc(HWND hwnd, UINT msg, WPARAM wParam, + LPARAM lParam, void *ctx) { - HWND hw, treeview; + PortableDialogStuff *pds = (PortableDialogStuff *)ctx; + const int DEMO_SCREENSHOT_TIMER_ID = 1230; + HWND treeview; struct treeview_faff tvfaff; - int ret; switch (msg) { case WM_INITDIALOG: - dp.hwnd = hwnd; - create_controls(hwnd, ""); /* Open and Cancel buttons etc */ - SetWindowText(hwnd, dp.wintitle); - SetWindowLongPtr(hwnd, GWLP_USERDATA, 0); - if (has_help()) - SetWindowLongPtr(hwnd, GWL_EXSTYLE, - GetWindowLongPtr(hwnd, GWL_EXSTYLE) | - WS_EX_CONTEXTHELP); - else { - HWND item = GetDlgItem(hwnd, IDC_HELPBTN); - if (item) - DestroyWindow(item); - } + pds_initdialog_start(pds, hwnd); + + pds_create_controls(pds, TREE_BASE, IDCX_STDBASE, 3, 3, 235, ""); + SendMessage(hwnd, WM_SETICON, (WPARAM) ICON_BIG, (LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(IDI_CFGICON))); - /* - * Centre the window. - */ - { /* centre the window */ - RECT rs, rd; - - hw = GetDesktopWindow(); - if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd)) - MoveWindow(hwnd, - (rs.right + rs.left + rd.left - rd.right) / 2, - (rs.bottom + rs.top + rd.top - rd.bottom) / 2, - rd.right - rd.left, rd.bottom - rd.top, true); - } + + centre_window(hwnd); /* * Create the tree view. @@ -482,8 +556,8 @@ static INT_PTR CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg, char *path = NULL; char *firstpath = NULL; - for (i = 0; i < ctrlbox->nctrlsets; i++) { - struct controlset *s = ctrlbox->ctrlsets[i]; + for (i = 0; i < pds->ctrlbox->nctrlsets; i++) { + struct controlset *s = pds->ctrlbox->ctrlsets[i]; HTREEITEM item; int j; char *c; @@ -508,9 +582,9 @@ static INT_PTR CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg, c = strrchr(s->pathname, '/'); if (!c) - c = s->pathname; + c = s->pathname; else - c++; + c++; item = treeview_insert(&tvfaff, j, c, s->pathname); if (!hfirst) { @@ -532,49 +606,32 @@ static INT_PTR CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg, * match the initial treeview selection. */ assert(firstpath); /* config.c must have given us _something_ */ - create_controls(hwnd, firstpath); - dlg_refresh(NULL, &dp); /* and set up control values */ + pds_create_controls(pds, TREE_PANEL, IDCX_PANELBASE, + 100, 3, 13, firstpath); + dlg_refresh(NULL, pds->dp); /* and set up control values */ } - /* - * Set focus into the first available control. - */ - { - int i; - struct winctrl *c; + if (dialog_box_demo_screenshot_filename) + SetTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID, TICKSPERSEC, NULL); - for (i = 0; (c = winctrl_findbyindex(&ctrls_panel, i)) != NULL; - i++) { - if (c->ctrl) { - dlg_set_focus(c->ctrl, &dp); - break; - } + pds_initdialog_finish(pds); + return 0; + + case WM_TIMER: + if (dialog_box_demo_screenshot_filename && + (UINT_PTR)wParam == DEMO_SCREENSHOT_TIMER_ID) { + KillTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID); + char *err = save_screenshot( + hwnd, dialog_box_demo_screenshot_filename); + if (err) { + MessageBox(hwnd, err, "Demo screenshot failure", + MB_OK | MB_ICONERROR); + sfree(err); } + ShinyEndDialog(hwnd, 0); } - - /* - * Now we've finished creating our initial set of controls, - * it's safe to actually show the window without risking setup - * flicker. - */ - ShowWindow(hwnd, SW_SHOWNORMAL); - - /* - * Set the flag that activates a couple of the other message - * handlers below, which were disabled until now to avoid - * spurious firing during the above setup procedure. - */ - SetWindowLongPtr(hwnd, GWLP_USERDATA, 1); return 0; - case WM_LBUTTONUP: - /* - * Button release should trigger WM_OK if there was a - * previous double click on the session list. - */ - ReleaseCapture(); - if (dp.ended) - SaneEndDialog(hwnd, dp.endresult ? 1 : 0); - break; + case WM_NOTIFY: if (LOWORD(wParam) == IDCX_TREEVIEW && ((LPNMHDR) lParam)->code == TVN_SELCHANGED) { @@ -589,7 +646,7 @@ static INT_PTR CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg, TVITEM item; char buffer[64]; - if (GetWindowLongPtr(hwnd, GWLP_USERDATA) != 1) + if (!pds->initialised) return 0; i = TreeView_GetSelection(((LPNMHDR) lParam)->hwndFrom); @@ -607,60 +664,34 @@ static INT_PTR CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg, HWND item; struct winctrl *c; - while ((c = winctrl_findbyindex(&ctrls_panel, 0)) != NULL) { + while ((c = winctrl_findbyindex( + &pds->ctrltrees[TREE_PANEL], 0)) != NULL) { for (k = 0; k < c->num_ids; k++) { item = GetDlgItem(hwnd, c->base_id + k); if (item) DestroyWindow(item); } - winctrl_rem_shortcuts(&dp, c); - winctrl_remove(&ctrls_panel, c); + winctrl_rem_shortcuts(pds->dp, c); + winctrl_remove(&pds->ctrltrees[TREE_PANEL], c); sfree(c->data); sfree(c); } } - create_controls(hwnd, (char *)item.lParam); + pds_create_controls(pds, TREE_PANEL, IDCX_PANELBASE, + 100, 3, 13, (char *)item.lParam); - dlg_refresh(NULL, &dp); /* set up control values */ + dlg_refresh(NULL, pds->dp); /* set up control values */ SendMessage (hwnd, WM_SETREDRAW, true, 0); InvalidateRect (hwnd, NULL, true); SetFocus(((LPNMHDR) lParam)->hwndFrom); /* ensure focus stays */ - return 0; } - break; - case WM_COMMAND: - case WM_DRAWITEM: - default: /* also handle drag list msg here */ - /* - * Only process WM_COMMAND once the dialog is fully formed. - */ - if (GetWindowLongPtr(hwnd, GWLP_USERDATA) == 1) { - ret = winctrl_handle_command(&dp, msg, wParam, lParam); - if (dp.ended && GetCapture() != hwnd) - SaneEndDialog(hwnd, dp.endresult ? 1 : 0); - } else - ret = 0; - return ret; - case WM_HELP: - if (!winctrl_context_help(&dp, hwnd, - ((LPHELPINFO)lParam)->iCtrlId)) - MessageBeep(0); - break; - case WM_CLOSE: - quit_help(hwnd); - SaneEndDialog(hwnd, 0); - return 0; - - /* Grrr Explorer will maximize Dialogs! */ - case WM_SIZE: - if (wParam == SIZE_MAXIMIZED) - force_normal(hwnd); return 0; + default: + return pds_default_dlgproc(pds, hwnd, msg, wParam, lParam); } - return 0; } void modal_about_box(HWND hwnd) @@ -696,29 +727,22 @@ void defuse_showwindow(void) bool do_config(Conf *conf) { bool ret; + PortableDialogStuff *pds = pds_new(2); + + setup_config_box(pds->ctrlbox, false, 0, 0); + win_setup_config_box(pds->ctrlbox, &pds->dp->hwnd, has_help(), false, 0); + + pds->dp->wintitle = dupprintf("%s Configuration", appname); + pds->dp->data = conf; - ctrlbox = ctrl_new_box(); - setup_config_box(ctrlbox, false, 0, 0); - win_setup_config_box(ctrlbox, &dp.hwnd, has_help(), false, 0); - dp_init(&dp); - winctrl_init(&ctrls_base); - winctrl_init(&ctrls_panel); - dp_add_tree(&dp, &ctrls_base); - dp_add_tree(&dp, &ctrls_panel); - dp.wintitle = dupprintf("%s Configuration", appname); - dp.errtitle = dupprintf("%s Error", appname); - dp.data = conf; - dlg_auto_set_fixed_pitch_flag(&dp); - dp.shortcuts['g'] = true; /* the treeview: `Cate&gory' */ - - ret = - SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL, - GenericMainDlgProc); - - ctrl_free_box(ctrlbox); - winctrl_cleanup(&ctrls_panel); - winctrl_cleanup(&ctrls_base); - dp_cleanup(&dp); + dlg_auto_set_fixed_pitch_flag(pds->dp); + + pds->dp->shortcuts['g'] = true; /* the treeview: `Cate&gory' */ + + ret = ShinyDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), "PuTTYConfigBox", + NULL, GenericMainDlgProc, pds); + + pds_free(pds); return ret; } @@ -728,31 +752,26 @@ bool do_reconfig(HWND hwnd, Conf *conf, int protcfginfo) Conf *backup_conf; bool ret; int protocol; + PortableDialogStuff *pds = pds_new(2); backup_conf = conf_copy(conf); - ctrlbox = ctrl_new_box(); protocol = conf_get_int(conf, CONF_protocol); - setup_config_box(ctrlbox, true, protocol, protcfginfo); - win_setup_config_box(ctrlbox, &dp.hwnd, has_help(), true, protocol); - dp_init(&dp); - winctrl_init(&ctrls_base); - winctrl_init(&ctrls_panel); - dp_add_tree(&dp, &ctrls_base); - dp_add_tree(&dp, &ctrls_panel); - dp.wintitle = dupprintf("%s Reconfiguration", appname); - dp.errtitle = dupprintf("%s Error", appname); - dp.data = conf; - dlg_auto_set_fixed_pitch_flag(&dp); - dp.shortcuts['g'] = true; /* the treeview: `Cate&gory' */ - - ret = SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL, - GenericMainDlgProc); - - ctrl_free_box(ctrlbox); - winctrl_cleanup(&ctrls_base); - winctrl_cleanup(&ctrls_panel); - dp_cleanup(&dp); + setup_config_box(pds->ctrlbox, true, protocol, protcfginfo); + win_setup_config_box(pds->ctrlbox, &pds->dp->hwnd, has_help(), + true, protocol); + + pds->dp->wintitle = dupprintf("%s Reconfiguration", appname); + pds->dp->data = conf; + + dlg_auto_set_fixed_pitch_flag(pds->dp); + + pds->dp->shortcuts['g'] = true; /* the treeview: `Cate&gory' */ + + ret = ShinyDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), "PuTTYConfigBox", + NULL, GenericMainDlgProc, pds); + + pds_free(pds); if (!ret) conf_copy_into(conf, backup_conf); @@ -823,90 +842,232 @@ void showabout(HWND hwnd) } struct hostkey_dialog_ctx { - const char *const *keywords; - const char *const *values; - FingerprintType fptype_default; - char **fingerprints; - const char *keydisp; - LPCTSTR iconid; + SeatDialogText *text; + bool has_title; const char *helpctx; }; -static INT_PTR CALLBACK HostKeyMoreInfoProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) +static INT_PTR HostKeyMoreInfoProc(HWND hwnd, UINT msg, WPARAM wParam, + LPARAM lParam, void *vctx) { + struct hostkey_dialog_ctx *ctx = (struct hostkey_dialog_ctx *)vctx; + switch (msg) { case WM_INITDIALOG: { - const struct hostkey_dialog_ctx *ctx = - (const struct hostkey_dialog_ctx *)lParam; - SetWindowLongPtr(hwnd, GWLP_USERDATA, (INT_PTR)ctx); + int index = 100, y = 12; + + WPARAM font = SendMessage(hwnd, WM_GETFONT, 0, 0); - if (ctx->fingerprints[SSH_FPTYPE_SHA256]) - SetDlgItemText(hwnd, IDC_HKI_SHA256, - ctx->fingerprints[SSH_FPTYPE_SHA256]); - if (ctx->fingerprints[SSH_FPTYPE_MD5]) - SetDlgItemText(hwnd, IDC_HKI_MD5, - ctx->fingerprints[SSH_FPTYPE_MD5]); + const char *key = NULL; + for (SeatDialogTextItem *item = ctx->text->items, + *end = item + ctx->text->nitems; item < end; item++) { + switch (item->type) { + case SDT_MORE_INFO_KEY: + key = item->text; + break; + case SDT_MORE_INFO_VALUE_SHORT: + case SDT_MORE_INFO_VALUE_BLOB: { + RECT rk, rv; + DWORD editstyle = WS_CHILD | WS_VISIBLE | WS_TABSTOP | + ES_AUTOHSCROLL | ES_READONLY; + if (item->type == SDT_MORE_INFO_VALUE_BLOB) { + rk.left = 12; + rk.right = 376; + rk.top = y; + rk.bottom = 8; + y += 10; + + editstyle |= ES_MULTILINE; + rv.left = 12; + rv.right = 376; + rv.top = y; + rv.bottom = 64; + y += 68; + } else { + rk.left = 12; + rk.right = 80; + rk.top = y+2; + rk.bottom = 8; + + rv.left = 100; + rv.right = 288; + rv.top = y; + rv.bottom = 12; + + y += 16; + } + + MapDialogRect(hwnd, &rk); + HWND ctl = CreateWindowEx( + 0, "STATIC", key, WS_CHILD | WS_VISIBLE, + rk.left, rk.top, rk.right, rk.bottom, + hwnd, (HMENU)(ULONG_PTR)index++, hinst, NULL); + SendMessage(ctl, WM_SETFONT, font, MAKELPARAM(true, 0)); + + MapDialogRect(hwnd, &rv); + ctl = CreateWindowEx( + WS_EX_CLIENTEDGE, "EDIT", item->text, editstyle, + rv.left, rv.top, rv.right, rv.bottom, + hwnd, (HMENU)(ULONG_PTR)index++, hinst, NULL); + SendMessage(ctl, WM_SETFONT, font, MAKELPARAM(true, 0)); + break; + } + default: + break; + } + } - SetDlgItemText(hwnd, IDA_TEXT, ctx->keydisp); + /* + * Now resize the overall window, and move the Close button at + * the bottom. + */ + RECT r; + r.left = 176; + r.top = y + 10; + r.right = r.bottom = 0; + MapDialogRect(hwnd, &r); + HWND ctl = GetDlgItem(hwnd, IDOK); + SetWindowPos(ctl, NULL, r.left, r.top, 0, 0, + SWP_NOSIZE | SWP_NOREDRAW | SWP_NOZORDER); + + r.left = r.top = r.right = 0; + r.bottom = 300; + MapDialogRect(hwnd, &r); + int oldheight = r.bottom; + + r.left = r.top = r.right = 0; + r.bottom = y + 30; + MapDialogRect(hwnd, &r); + int newheight = r.bottom; + + GetWindowRect(hwnd, &r); + + SetWindowPos(hwnd, NULL, 0, 0, r.right - r.left, + r.bottom - r.top + newheight - oldheight, + SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER); + ShowWindow(hwnd, SW_SHOWNORMAL); return 1; } case WM_COMMAND: switch (LOWORD(wParam)) { case IDOK: - EndDialog(hwnd, 0); + ShinyEndDialog(hwnd, 0); return 0; } return 0; case WM_CLOSE: - EndDialog(hwnd, 0); + ShinyEndDialog(hwnd, 0); return 0; } return 0; } -static INT_PTR CALLBACK HostKeyDialogProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) +static INT_PTR HostKeyDialogProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam, void *vctx) { + struct hostkey_dialog_ctx *ctx = (struct hostkey_dialog_ctx *)vctx; + switch (msg) { case WM_INITDIALOG: { - strbuf *sb = strbuf_new(); - const struct hostkey_dialog_ctx *ctx = - (const struct hostkey_dialog_ctx *)lParam; - SetWindowLongPtr(hwnd, GWLP_USERDATA, (INT_PTR)ctx); - for (int id = 100;; id++) { - char buf[256]; - - if (!GetDlgItemText(hwnd, id, buf, (int)lenof(buf))) + strbuf *dlg_text = strbuf_new(); + const char *dlg_title = ""; + ctx->has_title = false; + LPCTSTR iconid = IDI_QUESTION; + + for (SeatDialogTextItem *item = ctx->text->items, + *end = item + ctx->text->nitems; item < end; item++) { + switch (item->type) { + case SDT_PARA: + put_fmt(dlg_text, "%s\r\n\r\n", item->text); + break; + case SDT_DISPLAY: + put_fmt(dlg_text, "%s\r\n\r\n", item->text); + break; + case SDT_SCARY_HEADING: + SetDlgItemText(hwnd, IDC_HK_TITLE, item->text); + iconid = IDI_WARNING; + ctx->has_title = true; + break; + case SDT_TITLE: + dlg_title = item->text; + break; + default: break; - - strbuf_clear(sb); - for (const char *p = buf; *p ;) { - if (*p == '{') { - for (size_t i = 0; ctx->keywords[i]; i++) { - if (strstartswith(p, ctx->keywords[i])) { - p += strlen(ctx->keywords[i]); - put_datapl(sb, ptrlen_from_asciz(ctx->values[i])); - goto matched; - } - } - } else { - put_byte(sb, *p++); - } - matched:; } + } + while (strbuf_chomp(dlg_text, '\r') || strbuf_chomp(dlg_text, '\n')); + + SetDlgItemText(hwnd, IDC_HK_TEXT, dlg_text->s); + MakeDlgItemBorderless(hwnd, IDC_HK_TEXT); + strbuf_free(dlg_text); - SetDlgItemText(hwnd, id, sb->s); + SetWindowText(hwnd, dlg_title); + + if (!ctx->has_title) { + HWND item = GetDlgItem(hwnd, IDC_HK_TITLE); + if (item) + DestroyWindow(item); + } + + /* + * Find out how tall the text in the edit control really ended + * up (after line wrapping), and adjust the height of the + * whole box to match it. + */ + int height = SendDlgItemMessage(hwnd, IDC_HK_TEXT, + EM_GETLINECOUNT, 0, 0); + height *= 8; /* height of a text line, by definition of dialog units */ + + int edittop = ctx->has_title ? 40 : 20; + + RECT r; + r.left = 40; + r.top = edittop; + r.right = 290; + r.bottom = height; + MapDialogRect(hwnd, &r); + SetWindowPos(GetDlgItem(hwnd, IDC_HK_TEXT), NULL, + r.left, r.top, r.right, r.bottom, + SWP_NOREDRAW | SWP_NOZORDER); + + static const struct { + int id, x; + } buttons[] = { + { IDCANCEL, 288 }, + { IDC_HK_ACCEPT, 168 }, + { IDC_HK_ONCE, 216 }, + { IDC_HK_MOREINFO, 60 }, + { IDHELP, 12 }, + }; + for (size_t i = 0; i < lenof(buttons); i++) { + HWND ctl = GetDlgItem(hwnd, buttons[i].id); + r.left = buttons[i].x; + r.top = edittop + height + 20; + r.right = r.bottom = 0; + MapDialogRect(hwnd, &r); + SetWindowPos(ctl, NULL, r.left, r.top, 0, 0, + SWP_NOSIZE | SWP_NOREDRAW | SWP_NOZORDER); } - strbuf_free(sb); - SetDlgItemText(hwnd, IDC_HK_FINGERPRINT, - ctx->fingerprints[ctx->fptype_default]); - MakeDlgItemBorderless(hwnd, IDC_HK_FINGERPRINT); + r.left = r.top = r.right = 0; + r.bottom = 240; + MapDialogRect(hwnd, &r); + int oldheight = r.bottom; + + r.left = r.top = r.right = 0; + r.bottom = edittop + height + 40; + MapDialogRect(hwnd, &r); + int newheight = r.bottom; + + GetWindowRect(hwnd, &r); + + SetWindowPos(hwnd, NULL, 0, 0, r.right - r.left, + r.bottom - r.top + newheight - oldheight, + SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER); HANDLE icon = LoadImage( - NULL, ctx->iconid, IMAGE_ICON, + NULL, iconid, IMAGE_ICON, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), LR_SHARED); SendDlgItemMessage(hwnd, IDC_HK_ICON, STM_SETICON, (WPARAM)icon, 0); @@ -917,13 +1078,16 @@ static INT_PTR CALLBACK HostKeyDialogProc(HWND hwnd, UINT msg, DestroyWindow(item); } + ShowWindow(hwnd, SW_SHOWNORMAL); + return 1; } case WM_CTLCOLORSTATIC: { HDC hdc = (HDC)wParam; HWND control = (HWND)lParam; - if (GetWindowLongPtr(control, GWLP_ID) == IDC_HK_TITLE) { + if (GetWindowLongPtr(control, GWLP_ID) == IDC_HK_TITLE && + ctx->has_title) { SetBkMode(hdc, TRANSPARENT); HFONT prev_font = (HFONT)SelectObject( hdc, (HFONT)GetStockObject(SYSTEM_FONT)); @@ -944,85 +1108,69 @@ static INT_PTR CALLBACK HostKeyDialogProc(HWND hwnd, UINT msg, case IDC_HK_ACCEPT: case IDC_HK_ONCE: case IDCANCEL: - EndDialog(hwnd, LOWORD(wParam)); + ShinyEndDialog(hwnd, LOWORD(wParam)); return 0; case IDHELP: { - const struct hostkey_dialog_ctx *ctx = - (const struct hostkey_dialog_ctx *) - GetWindowLongPtr(hwnd, GWLP_USERDATA); launch_help(hwnd, ctx->helpctx); return 0; } case IDC_HK_MOREINFO: { - const struct hostkey_dialog_ctx *ctx = - (const struct hostkey_dialog_ctx *) - GetWindowLongPtr(hwnd, GWLP_USERDATA); - DialogBoxParam(hinst, MAKEINTRESOURCE(IDD_HK_MOREINFO), - hwnd, HostKeyMoreInfoProc, (LPARAM)ctx); + ShinyDialogBox(hinst, MAKEINTRESOURCE(IDD_HK_MOREINFO), + "PuTTYHostKeyMoreInfo", hwnd, + HostKeyMoreInfoProc, ctx); } } return 0; case WM_CLOSE: - EndDialog(hwnd, IDCANCEL); + ShinyEndDialog(hwnd, IDCANCEL); return 0; } return 0; } -int win_seat_verify_ssh_host_key( - Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **fingerprints, - void (*callback)(void *ctx, int result), void *ctx) +const SeatDialogPromptDescriptions *win_seat_prompt_descriptions(Seat *seat) { - int ret; + static const SeatDialogPromptDescriptions descs = { + .hk_accept_action = "press \"Accept\"", + .hk_connect_once_action = "press \"Connect Once\"", + .hk_cancel_action = "press \"Cancel\"", + .hk_cancel_action_Participle = "Pressing \"Cancel\"", + }; + return &descs; +} +SeatPromptResult win_seat_confirm_ssh_host_key( + Seat *seat, const char *host, int port, const char *keytype, + char *keystr, SeatDialogText *text, HelpCtx helpctx, + void (*callback)(void *ctx, SeatPromptResult result), void *cbctx) +{ WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); - /* - * Verify the key against the registry. - */ - ret = verify_host_key(host, port, keytype, keystr); - - if (ret == 0) /* success - key matched OK */ - return 1; - else { - static const char *const keywords[] = - { "{KEYTYPE}", "{APPNAME}", NULL }; - - const char *values[2]; - values[0] = keytype; - values[1] = appname; - - struct hostkey_dialog_ctx ctx[1]; - ctx->keywords = keywords; - ctx->values = values; - ctx->fingerprints = fingerprints; - ctx->fptype_default = ssh2_pick_default_fingerprint(fingerprints); - ctx->keydisp = keydisp; - ctx->iconid = (ret == 2 ? IDI_WARNING : IDI_QUESTION); - ctx->helpctx = (ret == 2 ? WINHELP_CTX_errors_hostkey_changed : - WINHELP_CTX_errors_hostkey_absent); - int dlgid = (ret == 2 ? IDD_HK_WRONG : IDD_HK_ABSENT); - int mbret = DialogBoxParam( - hinst, MAKEINTRESOURCE(dlgid), wgs->term_hwnd, - HostKeyDialogProc, (LPARAM)ctx); - assert(mbret==IDC_HK_ACCEPT || mbret==IDC_HK_ONCE || mbret==IDCANCEL); - if (mbret == IDC_HK_ACCEPT) { - store_host_key(host, port, keytype, keystr); - return 1; - } else if (mbret == IDC_HK_ONCE) - return 1; + struct hostkey_dialog_ctx ctx[1]; + ctx->text = text; + ctx->helpctx = helpctx; + + int mbret = ShinyDialogBox( + hinst, MAKEINTRESOURCE(IDD_HOSTKEY), "PuTTYHostKeyDialog", + wgs->term_hwnd, HostKeyDialogProc, ctx); + assert(mbret==IDC_HK_ACCEPT || mbret==IDC_HK_ONCE || mbret==IDCANCEL); + if (mbret == IDC_HK_ACCEPT) { + store_host_key(host, port, keytype, keystr); + return SPR_OK; + } else if (mbret == IDC_HK_ONCE) { + return SPR_OK; } - return 0; /* abandon the connection */ + + return SPR_USER_ABORT; } /* * Ask whether the selected algorithm is acceptable (since it was * below the configured 'warn' threshold). */ -int win_seat_confirm_weak_crypto_primitive( +SeatPromptResult win_seat_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, - void (*callback)(void *ctx, int result), void *ctx) + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { static const char mbtitle[] = "%s Security Alert"; static const char msg[] = @@ -1041,14 +1189,14 @@ int win_seat_confirm_weak_crypto_primitive( sfree(message); sfree(title); if (mbret == IDYES) - return 1; + return SPR_OK; else - return 0; + return SPR_USER_ABORT; } -int win_seat_confirm_weak_cached_hostkey( +SeatPromptResult win_seat_confirm_weak_cached_hostkey( Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, int result), void *ctx) + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { static const char mbtitle[] = "%s Security Alert"; static const char msg[] = @@ -1069,9 +1217,9 @@ int win_seat_confirm_weak_cached_hostkey( sfree(message); sfree(title); if (mbret == IDYES) - return 1; + return SPR_OK; else - return 0; + return SPR_USER_ABORT; } /* @@ -1154,3 +1302,41 @@ void old_keyfile_warning(void) sfree(msg); sfree(title); } + +static INT_PTR CAConfigProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, + void *ctx) +{ + PortableDialogStuff *pds = (PortableDialogStuff *)ctx; + + switch (msg) { + case WM_INITDIALOG: + pds_initdialog_start(pds, hwnd); + + SendMessage(hwnd, WM_SETICON, (WPARAM) ICON_BIG, + (LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(IDI_CFGICON))); + + centre_window(hwnd); + + pds_create_controls(pds, 0, IDCX_PANELBASE, 3, 3, 3, "Main"); + pds_create_controls(pds, 0, IDCX_STDBASE, 3, 3, 243, ""); + dlg_refresh(NULL, pds->dp); /* and set up control values */ + + pds_initdialog_finish(pds); + return 0; + + default: + return pds_default_dlgproc(pds, hwnd, msg, wParam, lParam); + } +} + +void show_ca_config_box(dlgparam *dp) +{ + PortableDialogStuff *pds = pds_new(1); + + setup_ca_config_box(pds->ctrlbox); + + ShinyDialogBox(hinst, MAKEINTRESOURCE(IDD_CA_CONFIG), "PuTTYConfigBox", + dp ? dp->hwnd : NULL, CAConfigProc, pds); + + pds_free(pds); +} diff --git a/windows/wingss.c b/windows/gss.c index 79c4921c..724eeec1 100644 --- a/windows/wingss.c +++ b/windows/gss.c @@ -6,9 +6,9 @@ #define SECURITY_WIN32 #include <security.h> -#include "pgssapi.h" -#include "sshgss.h" -#include "sshgssc.h" +#include "ssh/pgssapi.h" +#include "ssh/gss.h" +#include "ssh/gssc.h" #include "misc.h" @@ -91,8 +91,6 @@ typedef struct winSsh_gss_ctx { const Ssh_gss_buf gss_mech_krb5={9,"\x2A\x86\x48\x86\xF7\x12\x01\x02\x02"}; -const char *gsslogmsg = NULL; - static void ssh_sspi_bind_fns(struct ssh_gss_library *lib); static tree234 *libraries_to_never_unload; @@ -127,7 +125,7 @@ struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) if (!kernel32_module) { kernel32_module = load_system32_dll("kernel32.dll"); } -#if defined _MSC_VER && _MSC_VER < 1900 +#if !HAVE_ADDDLLDIRECTORY /* Omit the type-check because older MSVCs don't have this function */ GET_WINDOWS_FUNCTION_NO_TYPECHECK(kernel32_module, AddDllDirectory); #else @@ -227,7 +225,8 @@ struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) lib->gsslogmsg = "Using SSPI from SECUR32.DLL"; lib->handle = (void *)module; - GET_WINDOWS_FUNCTION(module, AcquireCredentialsHandleA); + /* No typecheck because Winelib thinks one PVOID is a PLUID */ + GET_WINDOWS_FUNCTION_NO_TYPECHECK(module, AcquireCredentialsHandleA); GET_WINDOWS_FUNCTION(module, InitializeSecurityContextA); GET_WINDOWS_FUNCTION(module, FreeContextBuffer); GET_WINDOWS_FUNCTION(module, FreeCredentialsHandle); @@ -465,7 +464,7 @@ static Ssh_gss_stat ssh_sspi_init_sec_context(struct ssh_gss_library *lib, SecBufferDesc input_desc ={SECBUFFER_VERSION,1,&wrecv_tok}; unsigned long flags=ISC_REQ_MUTUAL_AUTH|ISC_REQ_REPLAY_DETECT| ISC_REQ_CONFIDENTIALITY|ISC_REQ_ALLOCATE_MEMORY; - unsigned long ret_flags=0; + ULONG ret_flags=0; TimeStamp localexp; /* check if we have to delegate ... */ @@ -664,8 +663,8 @@ static Ssh_gss_stat ssh_sspi_verify_mic(struct ssh_gss_library *lib, InputSecurityToken[1].pvBuffer = mic->value; winctx->maj_stat = p_VerifySignature(&winctx->context, - &InputBufferDescriptor, - 0, &qop); + &InputBufferDescriptor, + 0, &qop); return winctx->maj_stat; } diff --git a/windows/winhandl.c b/windows/handle-io.c index 82d2aded..0f5b7e94 100644 --- a/windows/winhandl.c +++ b/windows/handle-io.c @@ -1,5 +1,5 @@ /* - * winhandl.c: Module to give Windows front ends the general + * handle-io.c: Module to give Windows front ends the general * ability to deal with consoles, pipes, serial ports, or any other * type of data stream accessed through a Windows API HANDLE rather * than a WinSock SOCKET. @@ -37,6 +37,12 @@ * Generic definitions. */ +typedef struct handle_list_node handle_list_node; +struct handle_list_node { + handle_list_node *next, *prev; +}; +static void add_to_ready_list(handle_list_node *node); + /* * Maximum amount of backlog we will allow to build up on an input * handle before we stop reading from it. @@ -56,7 +62,7 @@ struct handle_generic { * thread. */ HANDLE h; /* the handle itself */ - HANDLE ev_to_main; /* event used to signal main thread */ + handle_list_node ready_node; /* for linking on to the ready list */ HANDLE ev_from_main; /* event used to signal back to us */ bool moribund; /* are we going to kill this soon? */ bool done; /* request subthread to terminate */ @@ -65,7 +71,7 @@ struct handle_generic { void *privdata; /* for client to remember who they are */ }; -typedef enum { HT_INPUT, HT_OUTPUT, HT_FOREIGN } HandleType; +typedef enum { HT_INPUT, HT_OUTPUT } HandleType; /* ---------------------------------------------------------------------- * Input threads. @@ -79,7 +85,7 @@ struct handle_input { * Copy of the handle_generic structure. */ HANDLE h; /* the handle itself */ - HANDLE ev_to_main; /* event used to signal main thread */ + handle_list_node ready_node; /* for linking on to the ready list */ HANDLE ev_from_main; /* event used to signal back to us */ bool moribund; /* are we going to kill this soon? */ bool done; /* request subthread to terminate */ @@ -93,7 +99,7 @@ struct handle_input { int flags; /* - * Data set by the input thread before signalling ev_to_main, + * Data set by the input thread before marking the handle ready, * and read by the main thread after receiving that signal. */ char buffer[4096]; /* the data read from the handle */ @@ -176,7 +182,7 @@ static DWORD WINAPI handle_input_threadfunc(void *param) */ finished = (ctx->len == 0); - SetEvent(ctx->ev_to_main); + add_to_ready_list(&ctx->ready_node); if (finished) break; @@ -189,7 +195,7 @@ static DWORD WINAPI handle_input_threadfunc(void *param) * not touch ctx at all, because the main thread might * have freed it. */ - SetEvent(ctx->ev_to_main); + add_to_ready_list(&ctx->ready_node); break; } } @@ -240,7 +246,7 @@ struct handle_output { * Copy of the handle_generic structure. */ HANDLE h; /* the handle itself */ - HANDLE ev_to_main; /* event used to signal main thread */ + handle_list_node ready_node; /* for linking on to the ready list */ HANDLE ev_from_main; /* event used to signal back to us */ bool moribund; /* are we going to kill this soon? */ bool done; /* request subthread to terminate */ @@ -261,8 +267,8 @@ struct handle_output { DWORD len; /* how much data there is */ /* - * Data set by the input thread before signalling ev_to_main, - * and read by the main thread after receiving that signal. + * Data set by the input thread before marking this handle as + * ready, and read by the main thread after receiving that signal. */ DWORD lenwritten; /* how much data we actually wrote */ int writeerr; /* return value from WriteFile */ @@ -278,6 +284,7 @@ struct handle_output { * drops. */ handle_outputfn_t sentdata; + struct handle *sentdata_param; }; static DWORD WINAPI handle_output_threadfunc(void *param) @@ -303,7 +310,7 @@ static DWORD WINAPI handle_output_threadfunc(void *param) * not touch ctx at all, because the main thread might * have freed it. */ - SetEvent(ctx->ev_to_main); + add_to_ready_list(&ctx->ready_node); break; } if (povl) { @@ -326,7 +333,7 @@ static DWORD WINAPI handle_output_threadfunc(void *param) ctx->writeerr = 0; } - SetEvent(ctx->ev_to_main); + add_to_ready_list(&ctx->ready_node); if (!writeret) { /* * The write operation has suffered an error. Telling that @@ -355,40 +362,13 @@ static void handle_try_output(struct handle_output *ctx) ctx->busy = true; } else if (!ctx->busy && bufchain_size(&ctx->queued_data) == 0 && ctx->outgoingeof == EOF_PENDING) { - CloseHandle(ctx->h); + ctx->sentdata(ctx->sentdata_param, 0, 0, true); ctx->h = INVALID_HANDLE_VALUE; ctx->outgoingeof = EOF_SENT; } } /* ---------------------------------------------------------------------- - * 'Foreign events'. These are handle structures which just contain a - * single event object passed to us by another module such as - * winnps.c, so that they can make use of our handle_get_events / - * handle_got_event mechanism for communicating with application main - * loops. - */ -struct handle_foreign { - /* - * Copy of the handle_generic structure. - */ - HANDLE h; /* the handle itself */ - HANDLE ev_to_main; /* event used to signal main thread */ - HANDLE ev_from_main; /* event used to signal back to us */ - bool moribund; /* are we going to kill this soon? */ - bool done; /* request subthread to terminate */ - bool defunct; /* has the subthread already gone? */ - bool busy; /* operation currently in progress? */ - void *privdata; /* for client to remember who they are */ - - /* - * Our own data, just consisting of knowledge of who to call back. - */ - void (*callback)(void *); - void *ctx; -}; - -/* ---------------------------------------------------------------------- * Unified code handling both input and output threads. */ @@ -398,36 +378,91 @@ struct handle { struct handle_generic g; struct handle_input i; struct handle_output o; - struct handle_foreign f; } u; }; -static tree234 *handles_by_evtomain; +/* + * Linked list storing the current list of handles ready to have + * something done to them by the main thread. + */ +static handle_list_node ready_head[1]; +static CRITICAL_SECTION ready_critsec[1]; -static int handle_cmp_evtomain(void *av, void *bv) +/* + * Event object used by all subthreads to signal that they've just put + * something on the ready list, i.e. that the ready list is non-empty. + */ +static HANDLE ready_event = INVALID_HANDLE_VALUE; + +static void add_to_ready_list(handle_list_node *node) { - struct handle *a = (struct handle *)av; - struct handle *b = (struct handle *)bv; + /* + * Called from subthreads, when their handle has done something + * that they need the main thread to respond to. We append the + * given list node to the end of the ready list, and set + * ready_event to signal to the main thread that the ready list is + * now non-empty. + */ + EnterCriticalSection(ready_critsec); + node->next = ready_head; + node->prev = ready_head->prev; + node->next->prev = node->prev->next = node; + SetEvent(ready_event); + LeaveCriticalSection(ready_critsec); +} - if ((uintptr_t)a->u.g.ev_to_main < (uintptr_t)b->u.g.ev_to_main) - return -1; - else if ((uintptr_t)a->u.g.ev_to_main > (uintptr_t)b->u.g.ev_to_main) - return +1; - else - return 0; +static void remove_from_ready_list(handle_list_node *node) +{ + /* + * Called from the main thread, just before destroying a 'struct + * handle' completely: as a precaution, we make absolutely sure + * it's not linked on the ready list, just in case somehow it + * still was. + */ + EnterCriticalSection(ready_critsec); + node->next->prev = node->prev; + node->prev->next = node->next; + node->next = node->prev = node; + LeaveCriticalSection(ready_critsec); } -static int handle_find_evtomain(void *av, void *bv) +static void handle_ready(struct handle *h); /* process one handle (below) */ + +static void handle_ready_callback(void *vctx) { - HANDLE *a = (HANDLE *)av; - struct handle *b = (struct handle *)bv; + /* + * Called when the main thread detects ready_event, indicating + * that at least one handle is on the ready list. We empty the + * whole list and process the handles one by one. + * + * It's possible that other handles may be destroyed, and hence + * taken _off_ the ready list, during this processing. That + * shouldn't cause a deadlock, because according to the API docs, + * it's safe to call EnterCriticalSection twice in the same thread + * - the second call will return immediately because that thread + * already owns the critsec. (And then it takes two calls to + * LeaveCriticalSection to release it again, which is just what we + * want here.) + */ + EnterCriticalSection(ready_critsec); + while (ready_head->next != ready_head) { + handle_list_node *node = ready_head->next; + node->prev->next = node->next; + node->next->prev = node->prev; + node->next = node->prev = node; + handle_ready(container_of(node, struct handle, u.g.ready_node)); + } + LeaveCriticalSection(ready_critsec); +} - if ((uintptr_t)*a < (uintptr_t)b->u.g.ev_to_main) - return -1; - else if ((uintptr_t)*a > (uintptr_t)b->u.g.ev_to_main) - return +1; - else - return 0; +static inline void ensure_ready_event_setup(void) +{ + if (ready_event == INVALID_HANDLE_VALUE) { + ready_head->prev = ready_head->next = ready_head; + InitializeCriticalSection(ready_critsec); + ready_event = CreateEvent(NULL, false, false, NULL); + add_handle_wait(ready_event, handle_ready_callback, NULL); + } } struct handle *handle_input_new(HANDLE handle, handle_inputfn_t gotdata, @@ -438,7 +473,6 @@ struct handle *handle_input_new(HANDLE handle, handle_inputfn_t gotdata, h->type = HT_INPUT; h->u.i.h = handle; - h->u.i.ev_to_main = CreateEvent(NULL, false, false, NULL); h->u.i.ev_from_main = CreateEvent(NULL, false, false, NULL); h->u.i.gotdata = gotdata; h->u.i.defunct = false; @@ -447,10 +481,7 @@ struct handle *handle_input_new(HANDLE handle, handle_inputfn_t gotdata, h->u.i.privdata = privdata; h->u.i.flags = flags; - if (!handles_by_evtomain) - handles_by_evtomain = newtree234(handle_cmp_evtomain); - add234(handles_by_evtomain, h); - + ensure_ready_event_setup(); HANDLE hThread = CreateThread(NULL, 0, handle_input_threadfunc, &h->u.i, 0, &in_threadid); if (hThread) @@ -468,7 +499,6 @@ struct handle *handle_output_new(HANDLE handle, handle_outputfn_t sentdata, h->type = HT_OUTPUT; h->u.o.h = handle; - h->u.o.ev_to_main = CreateEvent(NULL, false, false, NULL); h->u.o.ev_from_main = CreateEvent(NULL, false, false, NULL); h->u.o.busy = false; h->u.o.defunct = false; @@ -478,12 +508,10 @@ struct handle *handle_output_new(HANDLE handle, handle_outputfn_t sentdata, bufchain_init(&h->u.o.queued_data); h->u.o.outgoingeof = EOF_NO; h->u.o.sentdata = sentdata; + h->u.o.sentdata_param = h; h->u.o.flags = flags; - if (!handles_by_evtomain) - handles_by_evtomain = newtree234(handle_cmp_evtomain); - add234(handles_by_evtomain, h); - + ensure_ready_event_setup(); HANDLE hThread = CreateThread(NULL, 0, handle_output_threadfunc, &h->u.o, 0, &out_threadid); if (hThread) @@ -492,30 +520,6 @@ struct handle *handle_output_new(HANDLE handle, handle_outputfn_t sentdata, return h; } -struct handle *handle_add_foreign_event(HANDLE event, - void (*callback)(void *), void *ctx) -{ - struct handle *h = snew(struct handle); - - h->type = HT_FOREIGN; - h->u.f.h = INVALID_HANDLE_VALUE; - h->u.f.ev_to_main = event; - h->u.f.ev_from_main = INVALID_HANDLE_VALUE; - h->u.f.defunct = true; /* we have no thread in the first place */ - h->u.f.moribund = false; - h->u.f.done = false; - h->u.f.privdata = NULL; - h->u.f.callback = callback; - h->u.f.ctx = ctx; - h->u.f.busy = true; - - if (!handles_by_evtomain) - handles_by_evtomain = newtree234(handle_cmp_evtomain); - add234(handles_by_evtomain, h); - - return h; -} - size_t handle_write(struct handle *h, const void *data, size_t len) { assert(h->type == HT_OUTPUT); @@ -541,46 +545,19 @@ void handle_write_eof(struct handle *h) } } -HANDLE *handle_get_events(int *nevents) -{ - HANDLE *ret; - struct handle *h; - int i; - size_t n, size; - - /* - * Go through our tree counting the handle objects currently - * engaged in useful activity. - */ - ret = NULL; - n = size = 0; - if (handles_by_evtomain) { - for (i = 0; (h = index234(handles_by_evtomain, i)) != NULL; i++) { - if (h->u.g.busy) { - sgrowarray(ret, size, n); - ret[n++] = h->u.g.ev_to_main; - } - } - } - - *nevents = n; - return ret; -} - static void handle_destroy(struct handle *h) { if (h->type == HT_OUTPUT) bufchain_clear(&h->u.o.queued_data); CloseHandle(h->u.g.ev_from_main); - CloseHandle(h->u.g.ev_to_main); - del234(handles_by_evtomain, h); + remove_from_ready_list(&h->u.g.ready_node); sfree(h); } void handle_free(struct handle *h) { assert(h && !h->u.g.moribund); - if (h->u.g.busy && h->type != HT_FOREIGN) { + if (h->u.g.busy) { /* * If the handle is currently busy, we cannot immediately free * it, because its subthread is in the middle of something. @@ -612,24 +589,8 @@ void handle_free(struct handle *h) } } -void handle_got_event(HANDLE event) +static void handle_ready(struct handle *h) { - struct handle *h; - - assert(handles_by_evtomain); - h = find234(handles_by_evtomain, &event, handle_find_evtomain); - if (!h) { - /* - * This isn't an error condition. If two or more event - * objects were signalled during the same select operation, - * and processing of the first caused the second handle to - * be closed, then it will sometimes happen that we receive - * an event notification here for a handle which is already - * deceased. In that situation we simply do nothing. - */ - return; - } - if (h->u.g.moribund) { /* * A moribund handle is one which we have either already @@ -685,19 +646,14 @@ void handle_got_event(HANDLE event) * thread is terminating by now). */ h->u.o.defunct = true; - h->u.o.sentdata(h, 0, h->u.o.writeerr); + h->u.o.sentdata(h, 0, h->u.o.writeerr, false); } else { bufchain_consume(&h->u.o.queued_data, h->u.o.lenwritten); noise_ultralight(NOISE_SOURCE_IOLEN, h->u.o.lenwritten); - h->u.o.sentdata(h, bufchain_size(&h->u.o.queued_data), 0); + h->u.o.sentdata(h, bufchain_size(&h->u.o.queued_data), 0, false); handle_try_output(&h->u.o); } break; - - case HT_FOREIGN: - /* Just call the callback. */ - h->u.f.callback(h->u.f.ctx); - break; } } diff --git a/windows/winhsock.c b/windows/handle-socket.c index 543b77b6..2820975c 100644 --- a/windows/winhsock.c +++ b/windows/handle-socket.c @@ -11,35 +11,56 @@ #include "putty.h" #include "network.h" +/* + * Freezing one of these sockets is a slightly fiddly business, + * because the reads from the handle are happening in a separate + * thread as blocking system calls and so once one is in progress it + * can't sensibly be interrupted. Hence, after the user tries to + * freeze one of these sockets, it's unavoidable that we may receive + * one more load of data before we manage to get handle-io.c to stop + * reading. + */ +typedef enum HandleSocketFreezeState { + UNFROZEN, /* reading as normal */ + FREEZING, /* have been set to frozen but winhandl is still reading */ + FROZEN, /* really frozen - winhandl has been throttled */ + THAWING /* we're gradually releasing our remaining data */ +} HandleSocketFreezeState; + typedef struct HandleSocket { - HANDLE send_H, recv_H, stderr_H; - struct handle *send_h, *recv_h, *stderr_h; + union { + struct { + HANDLE send_H, recv_H, stderr_H; + struct handle *send_h, *recv_h, *stderr_h; - /* - * Freezing one of these sockets is a slightly fiddly business, - * because the reads from the handle are happening in a separate - * thread as blocking system calls and so once one is in progress - * it can't sensibly be interrupted. Hence, after the user tries - * to freeze one of these sockets, it's unavoidable that we may - * receive one more load of data before we manage to get - * winhandl.c to stop reading. - */ - enum { - UNFROZEN, /* reading as normal */ - FREEZING, /* have been set to frozen but winhandl is still reading */ - FROZEN, /* really frozen - winhandl has been throttled */ - THAWING /* we're gradually releasing our remaining data */ - } frozen; - /* We buffer data here if we receive it from winhandl while frozen. */ - bufchain inputdata; + HandleSocketFreezeState frozen; + /* We buffer data here if we receive it from winhandl + * while frozen. */ + bufchain inputdata; - /* Handle logging proxy error messages from stderr_H, if we have one. */ - ProxyStderrBuf psb; + /* Handle logging proxy error messages from stderr_H, if + * we have one */ + ProxyStderrBuf psb; - bool defer_close, deferred_close; /* in case of re-entrance */ + bool defer_close, deferred_close; /* in case of re-entrance */ + }; + struct { + DeferredSocketOpener *opener; + + /* We buffer data here if we receive it via sk_write + * before the socket is opened. */ + bufchain outputdata; + + bool output_eof_pending; + + bool start_frozen; + }; + }; char *error; + SockAddr *addr; + int port; Plug *plug; Socket sock; @@ -51,17 +72,17 @@ static size_t handle_gotdata( HandleSocket *hs = (HandleSocket *)handle_get_privdata(h); if (err) { - plug_closing(hs->plug, "Read error from handle", 0, 0); + plug_closing_error(hs->plug, "Read error from handle"); return 0; } else if (len == 0) { - plug_closing(hs->plug, NULL, 0, 0); + plug_closing_normal(hs->plug); return 0; } else { assert(hs->frozen != FROZEN && hs->frozen != THAWING); if (hs->frozen == FREEZING) { /* * If we've received data while this socket is supposed to - * be frozen (because the read winhandl.c started before + * be frozen (because the read handle-io.c started before * sk_set_frozen was called has now returned) then buffer * the data for when we unfreeze. */ @@ -91,12 +112,21 @@ static size_t handle_stderr( return 0; } -static void handle_sentdata(struct handle *h, size_t new_backlog, int err) +static void handle_sentdata(struct handle *h, size_t new_backlog, int err, + bool close) { HandleSocket *hs = (HandleSocket *)handle_get_privdata(h); + if (close) { + if (hs->send_H != INVALID_HANDLE_VALUE) + CloseHandle(hs->send_H); + if (hs->recv_H != INVALID_HANDLE_VALUE && hs->recv_H != hs->send_H) + CloseHandle(hs->recv_H); + hs->send_H = hs->recv_H = INVALID_HANDLE_VALUE; + } + if (err) { - plug_closing(hs->plug, win_strerror(err), err, 0); + plug_closing_system_error(hs->plug, err); return; } @@ -123,11 +153,15 @@ static void sk_handle_close(Socket *s) handle_free(hs->send_h); handle_free(hs->recv_h); - CloseHandle(hs->send_H); - if (hs->recv_H != hs->send_H) + if (hs->send_H != INVALID_HANDLE_VALUE) + CloseHandle(hs->send_H); + if (hs->recv_H != INVALID_HANDLE_VALUE && hs->recv_H != hs->send_H) CloseHandle(hs->recv_H); bufchain_clear(&hs->inputdata); + if (hs->addr) + sk_addr_free(hs->addr); + delete_callbacks_for_context(hs); sfree(hs); @@ -214,7 +248,7 @@ static void sk_handle_set_frozen(Socket *s, bool is_frozen) case THAWING: /* * We were in the middle of emptying our bufchain, and got - * frozen again. In that case, winhandl.c is already + * frozen again. In that case, handle-io.c is already * throttled, so just return to FROZEN state. The toplevel * callback will notice and disable itself. */ @@ -272,7 +306,7 @@ static SocketPeerInfo *sk_handle_peer_info(Socket *s) if (!kernel32_module) { kernel32_module = load_system32_dll("kernel32.dll"); -#if (defined _MSC_VER && _MSC_VER < 1900) || defined __MINGW32__ +#if !HAVE_GETNAMEDPIPECLIENTPROCESSID /* For older Visual Studio, and MinGW too (at least as of * Ubuntu 16.04), this function isn't available in the header * files to type-check. Ditto the toolchain I use for @@ -314,16 +348,26 @@ static const SocketVtable HandleSocket_sockvt = { .peer_info = sk_handle_peer_info, }; +static void sk_handle_connect_success_callback(void *ctx) +{ + HandleSocket *hs = (HandleSocket *)ctx; + plug_log(hs->plug, PLUGLOG_CONNECT_SUCCESS, hs->addr, hs->port, NULL, 0); +} + Socket *make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H, - Plug *plug, bool overlapped) + SockAddr *addr, int port, Plug *plug, + bool overlapped) { HandleSocket *hs; int flags = (overlapped ? HANDLE_FLAG_OVERLAPPED : 0); hs = snew(HandleSocket); hs->sock.vt = &HandleSocket_sockvt; + hs->addr = addr; + hs->port = port; hs->plug = plug; hs->error = NULL; + hs->frozen = UNFROZEN; bufchain_init(&hs->inputdata); psb_init(&hs->psb); @@ -339,5 +383,130 @@ Socket *make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H, hs->defer_close = hs->deferred_close = false; + queue_toplevel_callback(sk_handle_connect_success_callback, hs); + + return &hs->sock; +} + +void handle_socket_set_psb_prefix(Socket *s, const char *prefix) +{ + HandleSocket *hs = container_of(s, HandleSocket, sock); + assert(hs->sock.vt == &HandleSocket_sockvt); + psb_set_prefix(&hs->psb, prefix); +} + +static void sk_handle_deferred_close(Socket *s) +{ + HandleSocket *hs = container_of(s, HandleSocket, sock); + + deferred_socket_opener_free(hs->opener); + bufchain_clear(&hs->outputdata); + + if (hs->addr) + sk_addr_free(hs->addr); + + delete_callbacks_for_context(hs); + + sfree(hs); +} + +static size_t sk_handle_deferred_write(Socket *s, const void *data, size_t len) +{ + HandleSocket *hs = container_of(s, HandleSocket, sock); + assert(!hs->output_eof_pending); + bufchain_add(&hs->outputdata, data, len); + return bufchain_size(&hs->outputdata); +} + +static void sk_handle_deferred_write_eof(Socket *s) +{ + HandleSocket *hs = container_of(s, HandleSocket, sock); + assert(!hs->output_eof_pending); + hs->output_eof_pending = true; +} + +static void sk_handle_deferred_set_frozen(Socket *s, bool is_frozen) +{ + HandleSocket *hs = container_of(s, HandleSocket, sock); + hs->frozen = is_frozen; +} + +static SocketPeerInfo *sk_handle_deferred_peer_info(Socket *s) +{ + return NULL; +} + +static const SocketVtable HandleSocket_deferred_sockvt = { + .plug = sk_handle_plug, + .close = sk_handle_deferred_close, + .write = sk_handle_deferred_write, + .write_oob = sk_handle_deferred_write, + .write_eof = sk_handle_deferred_write_eof, + .set_frozen = sk_handle_deferred_set_frozen, + .socket_error = sk_handle_socket_error, + .peer_info = sk_handle_deferred_peer_info, +}; + +Socket *make_deferred_handle_socket(DeferredSocketOpener *opener, + SockAddr *addr, int port, Plug *plug) +{ + HandleSocket *hs = snew(HandleSocket); + hs->sock.vt = &HandleSocket_deferred_sockvt; + hs->addr = addr; + hs->port = port; + hs->plug = plug; + hs->error = NULL; + + hs->opener = opener; + bufchain_init(&hs->outputdata); + hs->output_eof_pending = false; + hs->start_frozen = false; + return &hs->sock; } + +void setup_handle_socket(Socket *s, HANDLE send_H, HANDLE recv_H, + HANDLE stderr_H, bool overlapped) +{ + HandleSocket *hs = container_of(s, HandleSocket, sock); + assert(hs->sock.vt == &HandleSocket_deferred_sockvt); + + int flags = (overlapped ? HANDLE_FLAG_OVERLAPPED : 0); + + struct handle *recv_h = handle_input_new( + recv_H, handle_gotdata, hs, flags); + struct handle *send_h = handle_output_new( + send_H, handle_sentdata, hs, flags); + struct handle *stderr_h = !stderr_H ? NULL : handle_input_new( + stderr_H, handle_stderr, hs, flags); + + while (bufchain_size(&hs->outputdata)) { + ptrlen data = bufchain_prefix(&hs->outputdata); + handle_write(send_h, data.ptr, data.len); + bufchain_consume(&hs->outputdata, data.len); + } + + if (hs->output_eof_pending) + handle_write_eof(send_h); + + bool start_frozen = hs->start_frozen; + + deferred_socket_opener_free(hs->opener); + bufchain_clear(&hs->outputdata); + + hs->sock.vt = &HandleSocket_sockvt; + hs->frozen = start_frozen ? FREEZING : UNFROZEN; + bufchain_init(&hs->inputdata); + psb_init(&hs->psb); + + hs->recv_H = recv_H; + hs->recv_h = recv_h; + hs->send_H = send_H; + hs->send_h = send_h; + hs->stderr_H = stderr_H; + hs->stderr_h = stderr_h; + + hs->defer_close = hs->deferred_close = false; + + queue_toplevel_callback(sk_handle_connect_success_callback, hs); +} diff --git a/windows/handle-wait.c b/windows/handle-wait.c new file mode 100644 index 00000000..9e4e522d --- /dev/null +++ b/windows/handle-wait.c @@ -0,0 +1,143 @@ +/* + * handle-wait.c: Manage a collection of HANDLEs to wait for (in a + * WaitFor{Single,Multiple}Objects sense), each with a callback to be + * called when it's activated. Tracks the list, and provides an API to + * event loops that let them get a list of things to wait for and a + * way to call back to here when one of them does something. + */ + +/* + * TODO: currently this system can't cope with more than + * MAXIMUM_WAIT_OBJECTS (= 64) handles at a time. It enforces that by + * assertion, so we'll at least find out if that assumption is ever + * violated. + * + * It should be OK for the moment. As of 2021-05-24, the only uses of + * this system are by the ConPTY backend (just once, to watch for its + * subprocess terminating); by Pageant (for the event that the + * WM_COPYDATA subthread uses to signal the main thread); and by + * named-pipe-server.c (once per named-pipe server, of which there is + * one in Pageant and one in connection-sharing upstreams). So the + * total number of handles has a pretty small upper bound. + * + * But sooner or later, I'm sure we'll find a reason why we really + * need to watch a squillion handles at once. When that happens, I + * can't see any alternative to setting up some kind of tree of + * subthreads in this module, each one condensing 64 of our handles + * into one, by doing its own WaitForMultipleObjects and setting an + * event object to indicate that one of them did something. It'll be + * horribly ugly. + */ + +#include "putty.h" + +struct HandleWait { + HANDLE handle; + handle_wait_callback_fn_t callback; + void *callback_ctx; + + int index; /* sort key for tree234 */ +}; + +struct HandleWaitListInner { + HandleWait *hws[MAXIMUM_WAIT_OBJECTS]; + HANDLE handles[MAXIMUM_WAIT_OBJECTS]; + + struct HandleWaitList hwl; +}; + +static int handlewait_cmp(void *av, void *bv) +{ + HandleWait *a = (HandleWait *)av, *b = (HandleWait *)bv; + if (a->index < b->index) + return -1; + if (a->index > b->index) + return +1; + return 0; +} + +static tree234 *handlewaits_tree_real; + +static inline tree234 *ensure_handlewaits_tree_exists(void) +{ + if (!handlewaits_tree_real) + handlewaits_tree_real = newtree234(handlewait_cmp); + return handlewaits_tree_real; +} + +static int allocate_index(void) +{ + tree234 *t = ensure_handlewaits_tree_exists(); + search234_state st[1]; + + search234_start(st, t); + while (st->element) { + HandleWait *hw = (HandleWait *)st->element; + if (st->index < hw->index) { + /* There are unused index slots to the left of this element */ + search234_step(st, -1); + } else { + assert(st->index == hw->index); + search234_step(st, +1); + } + } + + return st->index; +} + +HandleWait *add_handle_wait(HANDLE h, handle_wait_callback_fn_t callback, + void *callback_ctx) +{ + HandleWait *hw = snew(HandleWait); + hw->handle = h; + hw->callback = callback; + hw->callback_ctx = callback_ctx; + + tree234 *t = ensure_handlewaits_tree_exists(); + hw->index = allocate_index(); + HandleWait *added = add234(t, hw); + assert(added == hw); + + return hw; +} + +void delete_handle_wait(HandleWait *hw) +{ + tree234 *t = ensure_handlewaits_tree_exists(); + HandleWait *deleted = del234(t, hw); + assert(deleted == hw); + sfree(hw); +} + +HandleWaitList *get_handle_wait_list(void) +{ + tree234 *t = ensure_handlewaits_tree_exists(); + struct HandleWaitListInner *hwli = snew(struct HandleWaitListInner); + size_t n = 0; + HandleWait *hw; + for (int i = 0; (hw = index234(t, i)) != NULL; i++) { + assert(n < MAXIMUM_WAIT_OBJECTS); + hwli->hws[n] = hw; + hwli->hwl.handles[n] = hw->handle; + n++; + } + hwli->hwl.nhandles = n; + return &hwli->hwl; +} + +void handle_wait_activate(HandleWaitList *hwl, int index) +{ + struct HandleWaitListInner *hwli = + container_of(hwl, struct HandleWaitListInner, hwl); + assert(0 <= index); + assert(index < hwli->hwl.nhandles); + HandleWait *hw = hwli->hws[index]; + hw->callback(hw->callback_ctx); +} + +void handle_wait_list_free(HandleWaitList *hwl) +{ + struct HandleWaitListInner *hwli = + container_of(hwl, struct HandleWaitListInner, hwl); + sfree(hwli); +} diff --git a/windows/winhelp.c b/windows/help.c index df6ac37b..d087c722 100644 --- a/windows/winhelp.c +++ b/windows/help.c @@ -1,5 +1,5 @@ /* - * winhelp.c: centralised functions to launch Windows HTML Help files. + * help.c: centralised functions to launch Windows HTML Help files. */ #include <stdio.h> @@ -8,7 +8,7 @@ #include <assert.h> #include "putty.h" -#include "win_res.h" +#include "putty-rc.h" #ifdef NO_HTMLHELP @@ -149,7 +149,7 @@ static bool find_chm_from_installation(void) }; for (size_t i = 0; i < lenof(reg_paths); i++) { - char *filename = registry_get_string( + char *filename = get_reg_sz_simple( HKEY_LOCAL_MACHINE, reg_paths[i], NULL); if (filename) { diff --git a/windows/winhelp.h b/windows/help.h index 9011df45..de6ec0be 100644 --- a/windows/winhelp.h +++ b/windows/help.h @@ -1,5 +1,5 @@ /* - * winhelp.h - define Windows Help context names. + * help.h - define Windows Help context names. * Each definition is simply a string which matches up with the * section names in the Halibut source, and is used for HTML Help. */ @@ -9,7 +9,9 @@ /* These are used in the cross-platform configuration dialog code. */ -#define HELPCTX(x) P(WINHELP_CTX_ ## x) +typedef const char *HelpCtx; +#define NULL_HELPCTX NULL +#define HELPCTX(x) WINHELP_CTX_ ## x #define WINHELP_CTX_no_help NULL @@ -26,6 +28,7 @@ #define WINHELP_CTX_keyboard_backspace "config-backspace" #define WINHELP_CTX_keyboard_homeend "config-homeend" #define WINHELP_CTX_keyboard_funkeys "config-funkeys" +#define WINHELP_CTX_keyboard_sharrow "config-sharrow" #define WINHELP_CTX_keyboard_appkeypad "config-appkeypad" #define WINHELP_CTX_keyboard_appcursor "config-appcursor" #define WINHELP_CTX_keyboard_nethack "config-nethack" @@ -110,10 +113,15 @@ #define WINHELP_CTX_ssh_gssapi_kex_delegation "config-ssh-kex-gssapi-delegation" #define WINHELP_CTX_ssh_kex_repeat "config-ssh-kex-rekey" #define WINHELP_CTX_ssh_kex_manual_hostkeys "config-ssh-kex-manual-hostkeys" +#define WINHELP_CTX_ssh_kex_cert "config-ssh-kex-cert" +#define WINHELP_CTX_ssh_cert_valid_expr "config-ssh-cert-valid-expr" +#define WINHELP_CTX_ssh_cert_rsa_hash "config-ssh-cert-rsa-hash" #define WINHELP_CTX_ssh_auth_bypass "config-ssh-noauth" #define WINHELP_CTX_ssh_no_trivial_userauth "config-ssh-notrivialauth" #define WINHELP_CTX_ssh_auth_banner "config-ssh-banner" #define WINHELP_CTX_ssh_auth_privkey "config-ssh-privkey" +#define WINHELP_CTX_ssh_auth_plugin "config-ssh-authplugin" +#define WINHELP_CTX_ssh_auth_cert "config-ssh-cert" #define WINHELP_CTX_ssh_auth_agentfwd "config-ssh-agentfwd" #define WINHELP_CTX_ssh_auth_changeuser "config-ssh-changeuser" #define WINHELP_CTX_ssh_auth_pageant "config-ssh-tryagent" @@ -162,6 +170,8 @@ #define WINHELP_CTX_ssh_bugs_winadj "config-ssh-bug-winadj" #define WINHELP_CTX_ssh_bugs_chanreq "config-ssh-bug-chanreq" #define WINHELP_CTX_ssh_bugs_oldgex2 "config-ssh-bug-oldgex2" +#define WINHELP_CTX_ssh_bugs_dropstart "config-ssh-bug-dropstart" +#define WINHELP_CTX_ssh_bugs_filter_kexinit "config-ssh-bug-filter-kexinit" #define WINHELP_CTX_serial_line "config-serial-line" #define WINHELP_CTX_serial_speed "config-serial-speed" #define WINHELP_CTX_serial_databits "config-serial-databits" @@ -189,6 +199,7 @@ #define WINHELP_CTX_puttygen_conversions "puttygen-conversions" #define WINHELP_CTX_puttygen_ppkver "puttygen-save-ppk-version" #define WINHELP_CTX_puttygen_kdfparam "puttygen-save-passphrase-hashing" +#define WINHELP_CTX_errors_cert_mismatch "errors-cert-mismatch" /* These are used in Windows-specific bits of the frontend. * We (ab)use "help context identifiers" (dwContextId) to identify them. */ diff --git a/windows/help.rc2 b/windows/help.rc2 new file mode 100644 index 00000000..16bb41f0 --- /dev/null +++ b/windows/help.rc2 @@ -0,0 +1,8 @@ +#include "putty-rc.h" + +#ifdef EMBEDDED_CHM_FILE +ID_CUSTOM_CHMFILE TYPE_CUSTOM_CHMFILE EMBEDDED_CHM_FILE +#define HELPVER " (with embedded help)" +#else +#define HELPVER " (without embedded help)" +#endif diff --git a/windows/installer.wxs b/windows/installer.wxs index b221320e..65a7f379 100644 --- a/windows/installer.wxs +++ b/windows/installer.wxs @@ -91,6 +91,10 @@ <?define Desktop_Shortcut_Component_GUID = "8999BBE1-F99E-4301-B7A6-480C19DE13B9" ?> <?endif ?> +<?ifndef HelpFilePath ?> + <?define HelpFilePath = "../doc/putty.chm" ?> +<?endif ?> + <?define ProgramName = "PuTTY$(var.Bitness)" ?> <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"> @@ -117,12 +121,6 @@ Language="1033" Codepage="1252" Version="$(var.Winver)"> <!-- - We force the install scope to perMachine, largely because I - don't really understand how to make it usefully switchable - between the two. If anyone is a WiX expert and does want to - install PuTTY locally in a user account, I hope they'll send a - well explained patch! - $(var.Puttytextver) is again defined on the candle command line, and describes the version of PuTTY in human-readable form, e.g. "PuTTY 0.67" or "PuTTY development snapshot [foo]". @@ -131,8 +129,7 @@ Description="$(var.Puttytextver) installer" Manufacturer="Simon Tatham" InstallerVersion="$(var.InstallerVersion)" Languages="1033" - Compressed="yes" SummaryCodepage="1252" - InstallScope="perMachine" /> + Compressed="yes" SummaryCodepage="1252" /> <!-- Permit installing an arbitrary one of these PuTTY installers @@ -238,7 +235,7 @@ https://msdn.microsoft.com/en-us/library/windows/desktop/dd391569(v=vs.85).aspx <Component Id="HelpFile_Component" Guid="$(var.HelpFile_Component_GUID)"> <File Id="HelpFile_File" - Source="../doc/putty.chm" KeyPath="yes"> + Source="$(var.HelpFilePath)" KeyPath="yes"> <Shortcut Id="startmenuManual" Directory="ProgramMenuDir" Name="PuTTY Manual" Advertise="no" /> diff --git a/windows/winjump.c b/windows/jump-list.c index 358504fd..47c41a04 100644 --- a/windows/winjump.c +++ b/windows/jump-list.c @@ -1,5 +1,5 @@ /* - * winjump.c: support for Windows 7 jump lists. + * jump-list.c: support for Windows 7 jump lists. * * The Windows 7 jumplist is a customizable list defined by the * application. It is persistent across application restarts: the OS @@ -12,7 +12,7 @@ * * Since the jumplist is write-only: it can only be replaced and the * current list cannot be read, we must maintain the contents of the - * list persistantly in the registry. The file winstore.h contains + * list persistently in the registry. The file winstore.h contains * functions to directly manipulate these registry entries. This file * contains higher level functions to manipulate the jumplist. */ @@ -41,7 +41,7 @@ typedef struct _tagpropertykey { typedef PROPVARIANT *REFPROPVARIANT; #endif /* MinGW doesn't define this yet: */ -#ifndef _PROPVARIANTINIT_DEFINED_ +#if !defined _PROPVARIANTINIT_DEFINED_ && !defined _PROPVARIANT_INIT_DEFINED_ #define _PROPVARIANTINIT_DEFINED_ #define PropVariantInit(pvar) memset((pvar),0,sizeof(PROPVARIANT)) #endif @@ -544,13 +544,13 @@ static void update_jumplist_from_registry(void) */ for (i = 0, found = false; i < nremoved && !found; i++) { IShellLink *rlink; - if (SUCCEEDED(pRemoved->lpVtbl->GetAt - (pRemoved, i, COMPTR(IShellLink, &rlink)))) { + if (SUCCEEDED(pRemoved->lpVtbl->GetAt( + pRemoved, i, COMPTR(IShellLink, &rlink)))) { char desc1[2048], desc2[2048]; - if (SUCCEEDED(link->lpVtbl->GetDescription - (link, desc1, sizeof(desc1)-1)) && - SUCCEEDED(rlink->lpVtbl->GetDescription - (rlink, desc2, sizeof(desc2)-1)) && + if (SUCCEEDED(link->lpVtbl->GetDescription( + link, desc1, sizeof(desc1)-1)) && + SUCCEEDED(rlink->lpVtbl->GetDescription( + rlink, desc2, sizeof(desc2)-1)) && !strcmp(desc1, desc2)) { found = true; } @@ -575,8 +575,8 @@ static void update_jumplist_from_registry(void) * Get the array form of the collection we've just constructed, * and put it in the jump list. */ - if (!SUCCEEDED(collection->lpVtbl->QueryInterface - (collection, COMPTR(IObjectArray, &array)))) + if (!SUCCEEDED(collection->lpVtbl->QueryInterface( + collection, COMPTR(IObjectArray, &array)))) goto cleanup; pCDL->lpVtbl->AppendCategory(pCDL, L"Recent Sessions", array); @@ -608,8 +608,8 @@ static void update_jumplist_from_registry(void) * Get the array form of the collection we've just constructed, * and put it in the jump list. */ - if (!SUCCEEDED(collection->lpVtbl->QueryInterface - (collection, COMPTR(IObjectArray, &array)))) + if (!SUCCEEDED(collection->lpVtbl->QueryInterface( + collection, COMPTR(IObjectArray, &array)))) goto cleanup; pCDL->lpVtbl->AddUserTasks(pCDL, array); @@ -636,8 +636,8 @@ static void update_jumplist_from_registry(void) * Get the array form of the collection we've just constructed, * and put it in the jump list. */ - if (!SUCCEEDED(collection->lpVtbl->QueryInterface - (collection, COMPTR(IObjectArray, &array)))) + if (!SUCCEEDED(collection->lpVtbl->QueryInterface( + collection, COMPTR(IObjectArray, &array)))) goto cleanup; pCDL->lpVtbl->AddUserTasks(pCDL, array); @@ -716,13 +716,12 @@ void remove_session_from_jumplist(const char * const sessionname) bool set_explicit_app_user_model_id(void) { - DECL_WINDOWS_FUNCTION(static, HRESULT, SetCurrentProcessExplicitAppUserModelID, - (PCWSTR)); + DECL_WINDOWS_FUNCTION( + static, HRESULT, SetCurrentProcessExplicitAppUserModelID, (PCWSTR)); - static HMODULE shell32_module = 0; + static HMODULE shell32_module = 0; - if (!shell32_module) - { + if (!shell32_module) { shell32_module = load_system32_dll("Shell32.dll"); /* * We can't typecheck this function here, because it's defined @@ -733,11 +732,10 @@ bool set_explicit_app_user_model_id(void) shell32_module, SetCurrentProcessExplicitAppUserModelID); } - if (p_SetCurrentProcessExplicitAppUserModelID) - { - if (p_SetCurrentProcessExplicitAppUserModelID(L"SimonTatham.PuTTY") == S_OK) - { - return true; + if (p_SetCurrentProcessExplicitAppUserModelID) { + const wchar_t *id = get_app_user_model_id(); + if (p_SetCurrentProcessExplicitAppUserModelID(id) == S_OK) { + return true; } return false; } diff --git a/windows/winproxy.c b/windows/local-proxy.c index 94e31fcb..55e9cbf3 100644 --- a/windows/winproxy.c +++ b/windows/local-proxy.c @@ -1,6 +1,6 @@ /* - * winproxy.c: Windows implementation of platform_new_connection(), - * supporting an OpenSSH-like proxy command via the winhandl.c + * local-proxy.c: Windows implementation of platform_new_connection(), + * supporting an OpenSSH-like proxy command via the handle-io.c * mechanism. */ @@ -10,14 +10,10 @@ #include "tree234.h" #include "putty.h" #include "network.h" -#include "proxy.h" +#include "proxy/proxy.h" -Socket *platform_new_connection(SockAddr *addr, const char *hostname, - int port, bool privport, - bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf) +char *platform_setup_local_proxy(Socket *socket, const char *cmd) { - char *cmd; HANDLE us_to_cmd, cmd_from_us; HANDLE us_from_cmd, cmd_to_us; HANDLE us_from_cmd_err, cmd_err_to_us; @@ -25,20 +21,6 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname, STARTUPINFO si; PROCESS_INFORMATION pi; - if (conf_get_int(conf, CONF_proxy_type) != PROXY_CMD) - return NULL; - - cmd = format_telnet_command(addr, port, conf); - - /* We are responsible for this and don't need it any more */ - sk_addr_free(addr); - - { - char *msg = dupprintf("Starting local proxy command: %s", cmd); - plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0); - sfree(msg); - } - /* * Create the pipes to the proxy command, and spawn the proxy * command process. @@ -47,30 +29,24 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname, sa.lpSecurityDescriptor = NULL; /* default */ sa.bInheritHandle = true; if (!CreatePipe(&us_from_cmd, &cmd_to_us, &sa, 0)) { - sfree(cmd); - return new_error_socket_fmt( - plug, "Unable to create pipes for proxy command: %s", - win_strerror(GetLastError())); + return dupprintf("Unable to create pipes for proxy command: %s", + win_strerror(GetLastError())); } if (!CreatePipe(&cmd_from_us, &us_to_cmd, &sa, 0)) { - sfree(cmd); CloseHandle(us_from_cmd); CloseHandle(cmd_to_us); - return new_error_socket_fmt( - plug, "Unable to create pipes for proxy command: %s", - win_strerror(GetLastError())); + return dupprintf("Unable to create pipes for proxy command: %s", + win_strerror(GetLastError())); } if (!CreatePipe(&us_from_cmd_err, &cmd_err_to_us, &sa, 0)) { - sfree(cmd); CloseHandle(us_from_cmd); CloseHandle(cmd_to_us); CloseHandle(us_to_cmd); CloseHandle(cmd_from_us); - return new_error_socket_fmt( - plug, "Unable to create pipes for proxy command: %s", - win_strerror(GetLastError())); + return dupprintf("Unable to create pipes for proxy command: %s", + win_strerror(GetLastError())); } SetHandleInformation(us_to_cmd, HANDLE_FLAG_INHERIT, 0); @@ -88,20 +64,55 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname, si.hStdInput = cmd_from_us; si.hStdOutput = cmd_to_us; si.hStdError = cmd_err_to_us; - CreateProcess(NULL, cmd, NULL, NULL, true, + char *cmd_mutable = dupstr(cmd); /* CreateProcess needs non-const char * */ + CreateProcess(NULL, cmd_mutable, NULL, NULL, true, CREATE_NO_WINDOW | NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi); + sfree(cmd_mutable); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); - sfree(cmd); - CloseHandle(cmd_from_us); CloseHandle(cmd_to_us); if (cmd_err_to_us != NULL) CloseHandle(cmd_err_to_us); - return make_handle_socket(us_to_cmd, us_from_cmd, us_from_cmd_err, - plug, false); + setup_handle_socket(socket, us_to_cmd, us_from_cmd, us_from_cmd_err, + false); + + return NULL; +} + +Socket *platform_new_connection(SockAddr *addr, const char *hostname, + int port, bool privport, + bool oobinline, bool nodelay, bool keepalive, + Plug *plug, Conf *conf, Interactor *itr) +{ + if (conf_get_int(conf, CONF_proxy_type) != PROXY_CMD) + return NULL; + + DeferredSocketOpener *opener = local_proxy_opener( + addr, port, plug, conf, itr); + Socket *socket = make_deferred_handle_socket(opener, addr, port, plug); + local_proxy_opener_set_socket(opener, socket); + return socket; +} + +Socket *platform_start_subprocess(const char *cmd, Plug *plug, + const char *prefix) +{ + Socket *socket = make_deferred_handle_socket( + null_deferred_socket_opener(), + sk_nonamelookup("<local command>"), 0, plug); + char *err = platform_setup_local_proxy(socket, cmd); + handle_socket_set_psb_prefix(socket, prefix); + + if (err) { + sk_close(socket); + socket = new_error_socket_fmt(plug, "%s", err); + sfree(err); + } + + return socket; } diff --git a/windows/winnpc.c b/windows/named-pipe-client.c index eabfb4bc..2ab6a309 100644 --- a/windows/winnpc.c +++ b/windows/named-pipe-client.c @@ -8,12 +8,10 @@ #include "tree234.h" #include "putty.h" #include "network.h" -#include "proxy.h" +#include "proxy/proxy.h" #include "ssh.h" -#if !defined NO_SECURITY - -#include "winsecur.h" +#include "security-api.h" HANDLE connect_to_named_pipe(const char *pipename, char **err) { @@ -40,11 +38,11 @@ HANDLE connect_to_named_pipe(const char *pipename, char **err) } /* - * If we got ERROR_PIPE_BUSY, wait for the server to - * create a new pipe instance. (Since the server is - * expected to be winnps.c, which will do that immediately - * after a previous connection is accepted, that shouldn't - * take excessively long.) + * If we got ERROR_PIPE_BUSY, wait for the server to create a + * new pipe instance. (Since the server is expected to be + * named-pipe-server.c, which will do that immediately after a + * previous connection is accepted, that shouldn't take + * excessively long.) */ if (!WaitNamedPipe(pipename, NMPWAIT_USE_DEFAULT_WAIT)) { *err = dupprintf( @@ -92,7 +90,6 @@ Socket *new_named_pipe_client(const char *pipename, Plug *plug) if (pipehandle == INVALID_HANDLE_VALUE) return new_error_socket_consume_string(plug, err); else - return make_handle_socket(pipehandle, pipehandle, NULL, plug, true); + return make_handle_socket(pipehandle, pipehandle, NULL, NULL, 0, + plug, true); } - -#endif /* !defined NO_SECURITY */ diff --git a/windows/winnps.c b/windows/named-pipe-server.c index 1757cdbb..87adb940 100644 --- a/windows/winnps.c +++ b/windows/named-pipe-server.c @@ -8,12 +8,10 @@ #include "tree234.h" #include "putty.h" #include "network.h" -#include "proxy.h" +#include "proxy/proxy.h" #include "ssh.h" -#if !defined NO_SECURITY - -#include "winsecur.h" +#include "security-api.h" typedef struct NamedPipeServerSocket { /* Parameters for (repeated) creation of named pipe objects */ @@ -24,7 +22,7 @@ typedef struct NamedPipeServerSocket { /* The current named pipe object + attempt to connect to it */ HANDLE pipehandle; OVERLAPPED connect_ovl; - struct handle *callback_handle; /* winhandl.c's reference */ + HandleWait *callback_handle; /* handle-wait.c's reference */ /* PuTTY Socket machinery */ Plug *plug; @@ -47,7 +45,7 @@ static void sk_namedpipeserver_close(Socket *s) NamedPipeServerSocket *ps = container_of(s, NamedPipeServerSocket, sock); if (ps->callback_handle) - handle_free(ps->callback_handle); + delete_handle_wait(ps->callback_handle); CloseHandle(ps->pipehandle); CloseHandle(ps->connect_ovl.hEvent); sfree(ps->error); @@ -79,33 +77,33 @@ static bool create_named_pipe(NamedPipeServerSocket *ps, bool first_instance) sa.lpSecurityDescriptor = ps->psd; sa.bInheritHandle = false; - ps->pipehandle = CreateNamedPipe - (/* lpName */ - ps->pipename, + ps->pipehandle = CreateNamedPipe( + /* lpName */ + ps->pipename, - /* dwOpenMode */ - PIPE_ACCESS_DUPLEX | - FILE_FLAG_OVERLAPPED | - (first_instance ? FILE_FLAG_FIRST_PIPE_INSTANCE : 0), + /* dwOpenMode */ + PIPE_ACCESS_DUPLEX | + FILE_FLAG_OVERLAPPED | + (first_instance ? FILE_FLAG_FIRST_PIPE_INSTANCE : 0), - /* dwPipeMode */ - PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT + /* dwPipeMode */ + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT #ifdef PIPE_REJECT_REMOTE_CLIENTS - | PIPE_REJECT_REMOTE_CLIENTS + | PIPE_REJECT_REMOTE_CLIENTS #endif - , + , - /* nMaxInstances */ - PIPE_UNLIMITED_INSTANCES, + /* nMaxInstances */ + PIPE_UNLIMITED_INSTANCES, - /* nOutBufferSize, nInBufferSize */ - 4096, 4096, /* FIXME: think harder about buffer sizes? */ + /* nOutBufferSize, nInBufferSize */ + 4096, 4096, /* FIXME: think harder about buffer sizes? */ - /* nDefaultTimeOut */ - 0 /* default timeout */, + /* nDefaultTimeOut */ + 0 /* default timeout */, - /* lpSecurityAttributes */ - &sa); + /* lpSecurityAttributes */ + &sa); return ps->pipehandle != INVALID_HANDLE_VALUE; } @@ -114,7 +112,7 @@ static Socket *named_pipe_accept(accept_ctx_t ctx, Plug *plug) { HANDLE conn = (HANDLE)ctx.p; - return make_handle_socket(conn, conn, NULL, plug, true); + return make_handle_socket(conn, conn, NULL, NULL, 0, plug, true); } static void named_pipe_accept_loop(NamedPipeServerSocket *ps, @@ -228,13 +226,10 @@ Socket *new_named_pipe_listener(const char *pipename, Plug *plug) memset(&ret->connect_ovl, 0, sizeof(ret->connect_ovl)); ret->connect_ovl.hEvent = CreateEvent(NULL, true, false, NULL); - ret->callback_handle = - handle_add_foreign_event(ret->connect_ovl.hEvent, - named_pipe_connect_callback, ret); + ret->callback_handle = add_handle_wait( + ret->connect_ovl.hEvent, named_pipe_connect_callback, ret); named_pipe_accept_loop(ret, false); cleanup: return &ret->sock; } - -#endif /* !defined NO_SECURITY */ diff --git a/windows/winnet.c b/windows/network.c index 3b4da3cc..2c087b69 100644 --- a/windows/winnet.c +++ b/windows/network.c @@ -20,6 +20,10 @@ #include <ws2tcpip.h> +#if HAVE_AFUNIX_H +#include <afunix.h> +#endif + #ifndef NO_IPV6 #ifdef __clang__ #pragma clang diagnostic push @@ -79,12 +83,28 @@ struct NetSocket { Socket sock; }; +/* + * Top-level discriminator for SockAddr. + * + * UNRESOLVED means a host name not yet put through DNS; IP means a + * resolved IP address (or list of them); UNIX indicates the AF_UNIX + * network family (which Windows also has); NAMEDPIPE indicates that + * this SockAddr is phony, holding a Windows named pipe pathname + * instead of any address WinSock can understand. + */ +typedef enum SuperFamily { + UNRESOLVED, + IP, +#if HAVE_AFUNIX_H + UNIX, +#endif + NAMEDPIPE +} SuperFamily; + struct SockAddr { int refcount; - char *error; - bool resolved; - bool namedpipe; /* indicates that this SockAddr is phony, holding a Windows - * named pipe pathname instead of a network address */ + const char *error; + SuperFamily superfamily; #ifndef NO_IPV6 struct addrinfo *ais; /* Addresses IPv6 style. */ #endif @@ -95,18 +115,27 @@ struct SockAddr { /* * Which address family this address belongs to. AF_INET for IPv4; - * AF_INET6 for IPv6; AF_UNSPEC indicates that name resolution has - * not been done and a simple host name is held in this SockAddr - * structure. + * AF_INET6 for IPv6; AF_UNIX for Unix-domain sockets; AF_UNSPEC + * indicates that name resolution has not been done and a simple host + * name is held in this SockAddr structure. */ +static inline int sockaddr_family(SockAddr *addr, SockAddrStep step) +{ + switch (addr->superfamily) { + case IP: #ifndef NO_IPV6 -#define SOCKADDR_FAMILY(addr, step) \ - (!(addr)->resolved ? AF_UNSPEC : \ - (step).ai ? (step).ai->ai_family : AF_INET) -#else -#define SOCKADDR_FAMILY(addr, step) \ - (!(addr)->resolved ? AF_UNSPEC : AF_INET) + if (step.ai) + return step.ai->ai_family; +#endif + return AF_INET; +#if HAVE_AFUNIX_H + case UNIX: + return AF_UNIX; #endif + default: + return AF_UNSPEC; + } +} /* * Start a SockAddrStep structure to step through multiple @@ -151,16 +180,16 @@ static int cmpforsearch(void *av, void *bv) DECL_WINDOWS_FUNCTION(static, int, WSAStartup, (WORD, LPWSADATA)); DECL_WINDOWS_FUNCTION(static, int, WSACleanup, (void)); DECL_WINDOWS_FUNCTION(static, int, closesocket, (SOCKET)); -DECL_WINDOWS_FUNCTION(static, u_long, ntohl, (u_long)); -DECL_WINDOWS_FUNCTION(static, u_long, htonl, (u_long)); -DECL_WINDOWS_FUNCTION(static, u_short, htons, (u_short)); -DECL_WINDOWS_FUNCTION(static, u_short, ntohs, (u_short)); +DECL_WINDOWS_FUNCTION(static, ULONG, ntohl, (ULONG)); +DECL_WINDOWS_FUNCTION(static, ULONG, htonl, (ULONG)); +DECL_WINDOWS_FUNCTION(static, USHORT, htons, (USHORT)); +DECL_WINDOWS_FUNCTION(static, USHORT, ntohs, (USHORT)); DECL_WINDOWS_FUNCTION(static, int, gethostname, (char *, int)); DECL_WINDOWS_FUNCTION(static, struct hostent FAR *, gethostbyname, (const char FAR *)); DECL_WINDOWS_FUNCTION(static, struct servent FAR *, getservbyname, (const char FAR *, const char FAR *)); -DECL_WINDOWS_FUNCTION(static, unsigned long, inet_addr, (const char FAR *)); +DECL_WINDOWS_FUNCTION(static, ULONG, inet_addr, (const char FAR *)); DECL_WINDOWS_FUNCTION(static, char FAR *, inet_ntoa, (struct in_addr)); DECL_WINDOWS_FUNCTION(static, const char FAR *, inet_ntop, (int, void FAR *, char *, size_t)); @@ -175,7 +204,7 @@ DECL_WINDOWS_FUNCTION(static, int, listen, (SOCKET, int)); DECL_WINDOWS_FUNCTION(static, int, send, (SOCKET, const char FAR *, int, int)); DECL_WINDOWS_FUNCTION(static, int, shutdown, (SOCKET, int)); DECL_WINDOWS_FUNCTION(static, int, ioctlsocket, - (SOCKET, long, u_long FAR *)); + (SOCKET, LONG, ULONG FAR *)); DECL_WINDOWS_FUNCTION(static, SOCKET, accept, (SOCKET, struct sockaddr FAR *, int FAR *)); DECL_WINDOWS_FUNCTION(static, int, getpeername, @@ -191,10 +220,9 @@ DECL_WINDOWS_FUNCTION(static, int, getaddrinfo, const struct addrinfo *hints, struct addrinfo **res)); DECL_WINDOWS_FUNCTION(static, void, freeaddrinfo, (struct addrinfo *res)); DECL_WINDOWS_FUNCTION(static, int, getnameinfo, - (const struct sockaddr FAR * sa, socklen_t salen, - char FAR * host, DWORD hostlen, char FAR * serv, + (const struct sockaddr FAR *sa, socklen_t salen, + char FAR *host, DWORD hostlen, char FAR *serv, DWORD servlen, int flags)); -DECL_WINDOWS_FUNCTION(static, char *, gai_strerror, (int ecode)); DECL_WINDOWS_FUNCTION(static, int, WSAAddressToStringA, (LPSOCKADDR, DWORD, LPWSAPROTOCOL_INFO, LPSTR, LPDWORD)); @@ -251,7 +279,6 @@ void sk_init(void) /* This function would fail its type-check if we did one, * because the VS header file provides an inline definition * which is __cdecl instead of WINAPI. */ - GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, gai_strerror); } else { /* Fall back to wship6.dll for Windows 2000 */ wship6_module = load_system32_dll("wship6.dll"); @@ -260,7 +287,6 @@ void sk_init(void) GET_WINDOWS_FUNCTION(wship6_module, freeaddrinfo); /* See comment above about type check */ GET_WINDOWS_FUNCTION_NO_TYPECHECK(wship6_module, getnameinfo); - GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, gai_strerror); } else { } } @@ -279,10 +305,12 @@ void sk_init(void) GET_WINDOWS_FUNCTION(winsock_module, WSAStartup); GET_WINDOWS_FUNCTION(winsock_module, WSACleanup); GET_WINDOWS_FUNCTION(winsock_module, closesocket); - GET_WINDOWS_FUNCTION(winsock_module, ntohl); - GET_WINDOWS_FUNCTION(winsock_module, htonl); - GET_WINDOWS_FUNCTION(winsock_module, htons); - GET_WINDOWS_FUNCTION(winsock_module, ntohs); + /* Winelib maps ntohl and friends to things like + * __wine_ulong_swap, which fail these type checks hopelessly */ + GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, ntohl); + GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, htonl); + GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, htons); + GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, ntohs); GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, gethostname); GET_WINDOWS_FUNCTION(winsock_module, gethostbyname); GET_WINDOWS_FUNCTION(winsock_module, getservbyname); @@ -426,157 +454,129 @@ const char *winsock_error_string(int error) return win_strerror(error); } +static inline const char *namelookup_strerror(DWORD err) +{ + /* PuTTY has traditionally translated a few of the likely error + * messages into more concise strings than the standard Windows ones */ + return (err == WSAENETDOWN ? "Network is down" : + err == WSAHOST_NOT_FOUND ? "Host does not exist" : + err == WSATRY_AGAIN ? "Host not found" : + win_strerror(err)); +} + SockAddr *sk_namelookup(const char *host, char **canonicalname, int address_family) { - SockAddr *ret = snew(SockAddr); - unsigned long a; - char realhost[8192]; - int hint_family; + *canonicalname = NULL; - /* Default to IPv4. */ - hint_family = (address_family == ADDRTYPE_IPV4 ? AF_INET : -#ifndef NO_IPV6 - address_family == ADDRTYPE_IPV6 ? AF_INET6 : -#endif - AF_UNSPEC); - - /* Clear the structure and default to IPv4. */ - memset(ret, 0, sizeof(SockAddr)); -#ifndef NO_IPV6 - ret->ais = NULL; -#endif - ret->namedpipe = false; - ret->addresses = NULL; - ret->resolved = false; - ret->refcount = 1; - *realhost = '\0'; + SockAddr *addr = snew(SockAddr); + memset(addr, 0, sizeof(SockAddr)); + addr->superfamily = UNRESOLVED; + addr->refcount = 1; - if ((a = p_inet_addr(host)) == (unsigned long) INADDR_NONE) { - struct hostent *h = NULL; - int err = 0; #ifndef NO_IPV6 - /* - * Use getaddrinfo when it's available - */ - if (p_getaddrinfo) { - struct addrinfo hints; - memset(&hints, 0, sizeof(hints)); - hints.ai_family = hint_family; - hints.ai_flags = AI_CANONNAME; - { - /* strip [] on IPv6 address literals */ - char *trimmed_host = host_strduptrim(host); - err = p_getaddrinfo(trimmed_host, NULL, &hints, &ret->ais); - sfree(trimmed_host); - } - if (err == 0) - ret->resolved = true; - } else -#endif - { - /* - * Otherwise use the IPv4-only gethostbyname... - * (NOTE: we don't use gethostbyname as a fallback!) - */ - if ( (h = p_gethostbyname(host)) ) - ret->resolved = true; + /* + * Use getaddrinfo, as long as it's available. This should handle + * both IPv4 and IPv6 address literals, and hostnames, in one + * unified API. + */ + if (p_getaddrinfo) { + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = (address_family == ADDRTYPE_IPV4 ? AF_INET : + address_family == ADDRTYPE_IPV6 ? AF_INET6 : + AF_UNSPEC); + hints.ai_flags = AI_CANONNAME; + hints.ai_socktype = SOCK_STREAM; + + /* strip [] on IPv6 address literals */ + char *trimmed_host = host_strduptrim(host); + int err = p_getaddrinfo(trimmed_host, NULL, &hints, &addr->ais); + sfree(trimmed_host); + + if (addr->ais) { + addr->superfamily = IP; + if (addr->ais->ai_canonname) + *canonicalname = dupstr(addr->ais->ai_canonname); else - err = p_WSAGetLastError(); + *canonicalname = dupstr(host); + } else { + addr->error = namelookup_strerror(err); } - - if (!ret->resolved) { - ret->error = (err == WSAENETDOWN ? "Network is down" : - err == WSAHOST_NOT_FOUND ? "Host does not exist" : - err == WSATRY_AGAIN ? "Host not found" : -#ifndef NO_IPV6 - p_getaddrinfo&&p_gai_strerror ? p_gai_strerror(err) : + return addr; + } #endif - "gethostbyname: unknown error"); - } else { - ret->error = NULL; -#ifndef NO_IPV6 - /* If we got an address info use that... */ - if (ret->ais) { - /* Are we in IPv4 fallback mode? */ - /* We put the IPv4 address into the a variable so we can further-on use the IPv4 code... */ - if (ret->ais->ai_family == AF_INET) - memcpy(&a, - (char *) &((SOCKADDR_IN *) ret->ais-> - ai_addr)->sin_addr, sizeof(a)); - - if (ret->ais->ai_canonname) - strncpy(realhost, ret->ais->ai_canonname, lenof(realhost)); - else - strncpy(realhost, host, lenof(realhost)); - } - /* We used the IPv4-only gethostbyname()... */ - else -#endif - { - int n; - for (n = 0; h->h_addr_list[n]; n++); - ret->addresses = snewn(n, unsigned long); - ret->naddresses = n; - for (n = 0; n < ret->naddresses; n++) { - memcpy(&a, h->h_addr_list[n], sizeof(a)); - ret->addresses[n] = p_ntohl(a); - } - memcpy(&a, h->h_addr, sizeof(a)); - /* This way we are always sure the h->h_name is valid :) */ - strncpy(realhost, h->h_name, sizeof(realhost)); - } + /* + * Failing that (if IPv6 support was not compiled in, or if + * getaddrinfo turned out to be unavailable at run time), try the + * old-fashioned approach, which is to start by manually checking + * for an IPv4 literal and then use gethostbyname. + */ + unsigned long a = p_inet_addr(host); + if (a != (unsigned long) INADDR_NONE) { + addr->addresses = snew(unsigned long); + addr->naddresses = 1; + addr->addresses[0] = p_ntohl(a); + addr->superfamily = IP; + *canonicalname = dupstr(host); + return addr; + } + + struct hostent *h = p_gethostbyname(host); + if (h) { + addr->superfamily = IP; + + size_t n; + for (n = 0; h->h_addr_list[n]; n++); + addr->addresses = snewn(n, unsigned long); + addr->naddresses = n; + for (n = 0; n < addr->naddresses; n++) { + uint32_t a; + memcpy(&a, h->h_addr_list[n], sizeof(a)); + addr->addresses[n] = p_ntohl(a); } + + *canonicalname = dupstr(h->h_name); } else { - /* - * This must be a numeric IPv4 address because it caused a - * success return from inet_addr. - */ - ret->addresses = snewn(1, unsigned long); - ret->naddresses = 1; - ret->addresses[0] = p_ntohl(a); - ret->resolved = true; - strncpy(realhost, host, sizeof(realhost)); + DWORD err = p_WSAGetLastError(); + addr->error = namelookup_strerror(err); } - realhost[lenof(realhost)-1] = '\0'; - *canonicalname = dupstr(realhost); - return ret; + return addr; } -SockAddr *sk_nonamelookup(const char *host) +static SockAddr *sk_special_addr(SuperFamily superfamily, const char *name) { SockAddr *ret = snew(SockAddr); ret->error = NULL; - ret->resolved = false; + ret->superfamily = superfamily; #ifndef NO_IPV6 ret->ais = NULL; #endif - ret->namedpipe = false; ret->addresses = NULL; ret->naddresses = 0; ret->refcount = 1; - strncpy(ret->hostname, host, lenof(ret->hostname)); + strncpy(ret->hostname, name, lenof(ret->hostname)); ret->hostname[lenof(ret->hostname)-1] = '\0'; return ret; } +SockAddr *sk_nonamelookup(const char *host) +{ + return sk_special_addr(UNRESOLVED, host); +} + SockAddr *sk_namedpipe_addr(const char *pipename) { - SockAddr *ret = snew(SockAddr); - ret->error = NULL; - ret->resolved = false; -#ifndef NO_IPV6 - ret->ais = NULL; -#endif - ret->namedpipe = true; - ret->addresses = NULL; - ret->naddresses = 0; - ret->refcount = 1; - strncpy(ret->hostname, pipename, lenof(ret->hostname)); - ret->hostname[lenof(ret->hostname)-1] = '\0'; - return ret; + return sk_special_addr(NAMEDPIPE, pipename); +} + +#if HAVE_AFUNIX_H +SockAddr *sk_unix_addr(const char *sockpath) +{ + return sk_special_addr(UNIX, sockpath); } +#endif static bool sk_nextaddr(SockAddr *addr, SockAddrStep *step) { @@ -619,7 +619,7 @@ void sk_getaddr(SockAddr *addr, char *buf, int buflen) } } else #endif - if (SOCKADDR_FAMILY(addr, step) == AF_INET) { + if (sockaddr_family(addr, step) == AF_INET) { struct in_addr a; assert(addr->addresses && step.curraddr < addr->naddresses); a.s_addr = p_htonl(addr->addresses[step.curraddr]); @@ -650,7 +650,7 @@ static SockAddr sk_extractaddr_tmp( #ifndef NO_IPV6 toret.ais = step->ai; #endif - if (SOCKADDR_FAMILY(addr, *step) == AF_INET + if (sockaddr_family(addr, *step) == AF_INET #ifndef NO_IPV6 && !toret.ais #endif @@ -662,7 +662,11 @@ static SockAddr sk_extractaddr_tmp( bool sk_addr_needs_port(SockAddr *addr) { - return !addr->namedpipe; + return addr->superfamily != NAMEDPIPE +#if HAVE_AFUNIX_H + && addr->superfamily != UNIX +#endif + ; } bool sk_hostname_is_local(const char *name) @@ -710,7 +714,7 @@ bool sk_address_is_local(SockAddr *addr) SockAddrStep step; int family; START_STEP(addr, step); - family = SOCKADDR_FAMILY(addr, step); + family = sockaddr_family(addr, step); #ifndef NO_IPV6 if (family == AF_INET6) { @@ -746,7 +750,7 @@ int sk_addrtype(SockAddr *addr) SockAddrStep step; int family; START_STEP(addr, step); - family = SOCKADDR_FAMILY(addr, step); + family = sockaddr_family(addr, step); return (family == AF_INET ? ADDRTYPE_IPV4 : #ifndef NO_IPV6 @@ -760,7 +764,7 @@ void sk_addrcopy(SockAddr *addr, char *buf) SockAddrStep step; int family; START_STEP(addr, step); - family = SOCKADDR_FAMILY(addr, step); + family = sockaddr_family(addr, step); assert(family != AF_UNSPEC); #ifndef NO_IPV6 @@ -904,7 +908,7 @@ static DWORD try_connect(NetSocket *sock) /* * Open socket. */ - family = SOCKADDR_FAMILY(sock->addr, sock->step); + family = sockaddr_family(sock->addr, sock->step); /* * Remove the socket from the tree before we overwrite its @@ -923,7 +927,7 @@ static DWORD try_connect(NetSocket *sock) goto ret; } - SetHandleInformation((HANDLE)s, HANDLE_FLAG_INHERIT, 0); + SetHandleInformation((HANDLE)s, HANDLE_FLAG_INHERIT, 0); if (sock->oobinline) { BOOL b = true; @@ -1064,7 +1068,7 @@ static DWORD try_connect(NetSocket *sock) err = 0; - ret: + ret: /* * No matter what happened, put the socket back in the tree. @@ -1120,21 +1124,27 @@ Socket *sk_new(SockAddr *addr, int port, bool privport, bool oobinline, return &ret->sock; } -Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, - bool local_host_only, int orig_address_family) +static Socket *sk_newlistener_internal( + const char *srcaddr, int port, Plug *plug, + bool local_host_only, int orig_address_family) { SOCKET s; + SOCKADDR_IN a; #ifndef NO_IPV6 SOCKADDR_IN6 a6; #endif - SOCKADDR_IN a; +#if HAVE_AFUNIX_H + SOCKADDR_UN au; +#endif + struct sockaddr *bindaddr; + unsigned bindsize; DWORD err; const char *errstr; NetSocket *ret; int retcode; - int address_family; + int address_family = orig_address_family; /* * Create NetSocket structure. @@ -1155,16 +1165,6 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, ret->addr = NULL; /* - * Translate address_family from platform-independent constants - * into local reality. - */ - address_family = (orig_address_family == ADDRTYPE_IPV4 ? AF_INET : -#ifndef NO_IPV6 - orig_address_family == ADDRTYPE_IPV6 ? AF_INET6 : -#endif - AF_UNSPEC); - - /* * Our default, if passed the `don't care' value * ADDRTYPE_UNSPEC, is to listen on IPv4. If IPv6 is supported, * we will also set up a second socket listening on IPv6, but @@ -1189,85 +1189,100 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, ret->oobinline = false; +#if HAVE_AFUNIX_H + if (address_family != AF_UNIX) +#endif { BOOL on = true; p_setsockopt(s, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (const char *)&on, sizeof(on)); } + switch (address_family) { #ifndef NO_IPV6 - if (address_family == AF_INET6) { - memset(&a6, 0, sizeof(a6)); - a6.sin6_family = AF_INET6; - if (local_host_only) - a6.sin6_addr = in6addr_loopback; - else - a6.sin6_addr = in6addr_any; - if (srcaddr != NULL && p_getaddrinfo) { - struct addrinfo hints; - struct addrinfo *ai; - int err; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_INET6; - hints.ai_flags = 0; - { - /* strip [] on IPv6 address literals */ - char *trimmed_addr = host_strduptrim(srcaddr); - err = p_getaddrinfo(trimmed_addr, NULL, &hints, &ai); - sfree(trimmed_addr); - } - if (err == 0 && ai->ai_family == AF_INET6) { - a6.sin6_addr = - ((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr; - } - } - a6.sin6_port = p_htons(port); - } else -#endif - { - bool got_addr = false; - a.sin_family = AF_INET; + case AF_INET6: { + memset(&a6, 0, sizeof(a6)); + a6.sin6_family = AF_INET6; + if (local_host_only) + a6.sin6_addr = in6addr_loopback; + else + a6.sin6_addr = in6addr_any; + if (srcaddr != NULL && p_getaddrinfo) { + struct addrinfo hints; + struct addrinfo *ai; + int err; - /* - * Bind to source address. First try an explicitly - * specified one... - */ - if (srcaddr) { - a.sin_addr.s_addr = p_inet_addr(srcaddr); - if (a.sin_addr.s_addr != INADDR_NONE) { - /* Override localhost_only with specified listen addr. */ - ret->localhost_only = ipv4_is_loopback(a.sin_addr); - got_addr = true; - } + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + hints.ai_flags = 0; + { + /* strip [] on IPv6 address literals */ + char *trimmed_addr = host_strduptrim(srcaddr); + err = p_getaddrinfo(trimmed_addr, NULL, &hints, &ai); + sfree(trimmed_addr); + } + if (err == 0 && ai->ai_family == AF_INET6) { + a6.sin6_addr = + ((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr; } + } + a6.sin6_port = p_htons(port); + bindaddr = (struct sockaddr *)&a6; + bindsize = sizeof(a6); + break; + } +#endif + case AF_INET: { + bool got_addr = false; + a.sin_family = AF_INET; - /* - * ... and failing that, go with one of the standard ones. - */ - if (!got_addr) { - if (local_host_only) - a.sin_addr.s_addr = p_htonl(INADDR_LOOPBACK); - else - a.sin_addr.s_addr = p_htonl(INADDR_ANY); + /* + * Bind to source address. First try an explicitly + * specified one... + */ + if (srcaddr) { + a.sin_addr.s_addr = p_inet_addr(srcaddr); + if (a.sin_addr.s_addr != INADDR_NONE) { + /* Override localhost_only with specified listen addr. */ + ret->localhost_only = ipv4_is_loopback(a.sin_addr); + got_addr = true; } + } - a.sin_port = p_htons((short)port); + /* + * ... and failing that, go with one of the standard ones. + */ + if (!got_addr) { + if (local_host_only) + a.sin_addr.s_addr = p_htonl(INADDR_LOOPBACK); + else + a.sin_addr.s_addr = p_htonl(INADDR_ANY); } -#ifndef NO_IPV6 - retcode = p_bind(s, (address_family == AF_INET6 ? - (struct sockaddr *) &a6 : - (struct sockaddr *) &a), - (address_family == - AF_INET6 ? sizeof(a6) : sizeof(a))); -#else - retcode = p_bind(s, (struct sockaddr *) &a, sizeof(a)); + + a.sin_port = p_htons((short)port); + bindaddr = (struct sockaddr *)&a; + bindsize = sizeof(a); + break; + } +#if HAVE_AFUNIX_H + case AF_UNIX: { + au.sun_family = AF_UNIX; + strncpy(au.sun_path, srcaddr, sizeof(au.sun_path)); + bindaddr = (struct sockaddr *)&au; + bindsize = sizeof(au); + break; + } #endif - if (retcode != SOCKET_ERROR) { - err = 0; - } else { - err = p_WSAGetLastError(); - } + default: + unreachable("bad address family in sk_newlistener_internal"); + } + + retcode = p_bind(s, bindaddr, bindsize); + if (retcode != SOCKET_ERROR) { + err = 0; + } else { + err = p_WSAGetLastError(); + } if (err) { p_closesocket(s); @@ -1298,9 +1313,9 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, * If we were given ADDRTYPE_UNSPEC, we must also create an * IPv6 listening socket and link it to this one. */ - if (address_family == AF_INET && orig_address_family == ADDRTYPE_UNSPEC) { - Socket *other = sk_newlistener(srcaddr, port, plug, - local_host_only, ADDRTYPE_IPV6); + if (address_family == AF_INET && orig_address_family == AF_UNSPEC) { + Socket *other = sk_newlistener_internal(srcaddr, port, plug, + local_host_only, AF_INET6); if (other) { NetSocket *ns = container_of(other, NetSocket, sock); @@ -1317,6 +1332,33 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, return &ret->sock; } +Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, + bool local_host_only, int orig_address_family) +{ + /* + * Translate address_family from platform-independent constants + * into local reality. + */ + int address_family = (orig_address_family == ADDRTYPE_IPV4 ? AF_INET : +#ifndef NO_IPV6 + orig_address_family == ADDRTYPE_IPV6 ? AF_INET6 : +#endif + AF_UNSPEC); + + return sk_newlistener_internal(srcaddr, port, plug, local_host_only, + address_family); +} + +Socket *sk_newlistener_unix(const char *path, Plug *plug) +{ +#if HAVE_AFUNIX_H + return sk_newlistener_internal(path, 0, plug, false, AF_UNIX); +#else + return new_error_socket_fmt( + plug, "AF_UNIX support not compiled into this program"); +#endif +} + static void sk_net_close(Socket *sock) { NetSocket *s = container_of(sock, NetSocket, sock); @@ -1335,6 +1377,19 @@ static void sk_net_close(Socket *sock) sfree(s); } +void plug_closing_system_error(Plug *plug, DWORD error) +{ + PlugCloseType type = PLUGCLOSE_ERROR; + if (error == ERROR_BROKEN_PIPE) + type = PLUGCLOSE_BROKEN_PIPE; + plug_closing(plug, type, win_strerror(error)); +} + +void plug_closing_winsock_error(Plug *plug, DWORD error) +{ + plug_closing(plug, PLUGCLOSE_ERROR, winsock_error_string(error)); +} + /* * Deal with socket errors detected in try_send(). */ @@ -1352,15 +1407,14 @@ static void socket_error_callback(void *vs) /* * An error has occurred on this socket. Pass it to the plug. */ - plug_closing(s->plug, winsock_error_string(s->pending_error), - s->pending_error, 0); + plug_closing_winsock_error(s->plug, s->pending_error); } /* * The function which tries to send on a socket once it's deemed * writable. */ -void try_send(NetSocket *s) +static void try_send(NetSocket *s) { while (s->sending_oob || bufchain_size(&s->output_data) > 0) { int nsent; @@ -1528,7 +1582,7 @@ void select_result(WPARAM wParam, LPARAM lParam) } } if (err != 0) - plug_closing(s->plug, winsock_error_string(err), err, 0); + plug_closing_winsock_error(s->plug, err); return; } @@ -1590,9 +1644,9 @@ void select_result(WPARAM wParam, LPARAM lParam) } } if (ret < 0) { - plug_closing(s->plug, winsock_error_string(err), err, 0); + plug_closing_winsock_error(s->plug, err); } else if (0 == ret) { - plug_closing(s->plug, NULL, 0, 0); + plug_closing_normal(s->plug); } else { plug_receive(s->plug, atmark ? 0 : 1, buf, ret); } @@ -1608,7 +1662,7 @@ void select_result(WPARAM wParam, LPARAM lParam) noise_ultralight(NOISE_SOURCE_IOLEN, ret); if (ret <= 0) { int err = p_WSAGetLastError(); - plug_closing(s->plug, winsock_error_string(err), err, 0); + plug_closing_winsock_error(s->plug, err); } else { plug_receive(s->plug, 2, buf, ret); } @@ -1631,12 +1685,12 @@ void select_result(WPARAM wParam, LPARAM lParam) err = p_WSAGetLastError(); if (err == WSAEWOULDBLOCK) break; - plug_closing(s->plug, winsock_error_string(err), err, 0); + plug_closing_winsock_error(s->plug, err); } else { if (ret) plug_receive(s->plug, 0, buf, ret); else - plug_closing(s->plug, NULL, 0, 0); + plug_closing_normal(s->plug); } } while (ret > 0); return; @@ -1644,7 +1698,7 @@ void select_result(WPARAM wParam, LPARAM lParam) #ifdef NO_IPV6 struct sockaddr_in isa; #else - struct sockaddr_storage isa; + struct sockaddr_storage isa; // FIXME: also if Unix and no IPv6 #endif int addrlen = sizeof(isa); SOCKET t; /* socket of connection */ @@ -1655,9 +1709,9 @@ void select_result(WPARAM wParam, LPARAM lParam) t = p_accept(s->s,(struct sockaddr *)&isa,&addrlen); if (t == INVALID_SOCKET) { - err = p_WSAGetLastError(); - if (err == WSATRY_AGAIN) - break; + err = p_WSAGetLastError(); + if (err == WSATRY_AGAIN) + break; } actx.p = (void *)t; @@ -1670,9 +1724,9 @@ void select_result(WPARAM wParam, LPARAM lParam) if (s->localhost_only && !ipv4_is_local_addr(isa.sin_addr)) #endif { - p_closesocket(t); /* dodgy WinSock let nonlocal through */ + p_closesocket(t); /* dodgy WinSock let nonlocal through */ } else if (plug_accepting(s->plug, sk_net_accept, actx)) { - p_closesocket(t); /* denied or error */ + p_closesocket(t); /* denied or error */ } break; } @@ -1700,7 +1754,7 @@ static SocketPeerInfo *sk_net_peer_info(Socket *sock) #ifdef NO_IPV6 struct sockaddr_in addr; #else - struct sockaddr_storage addr; + struct sockaddr_storage addr; // FIXME: also if Unix and no IPv6 char buf[INET6_ADDRSTRLEN]; #endif int addrlen = sizeof(addr); @@ -1797,7 +1851,7 @@ bool socket_writable(SOCKET skt) return false; } -int net_service_lookup(char *service) +int net_service_lookup(const char *service) { struct servent *se; se = p_getservbyname(service, NULL); @@ -1819,7 +1873,7 @@ SockAddr *platform_get_x11_unix_address(const char *display, int displaynum) { SockAddr *ret = snew(SockAddr); memset(ret, 0, sizeof(SockAddr)); - ret->error = "unix sockets not supported on this platform"; + ret->error = "unix sockets for X11 not supported on this platform"; ret->refcount = 1; return ret; } diff --git a/windows/winnojmp.c b/windows/no-jump-list.c index dd61dc69..beabd107 100644 --- a/windows/winnojmp.c +++ b/windows/no-jump-list.c @@ -1,8 +1,10 @@ /* - * winnojmp.c: stub jump list functions for Windows executables that - * don't update the jump list. + * no-jump-list.c: stub jump list functions for Windows executables + * that don't update the jump list. */ +#include "putty.h" + void add_session_to_jumplist(const char * const sessionname) {} void remove_session_from_jumplist(const char * const sessionname) {} void clear_jumplist(void) {} diff --git a/windows/winnohlp.c b/windows/nohelp.c index 62ddc65c..1b748776 100644 --- a/windows/winnohlp.c +++ b/windows/nohelp.c @@ -1,6 +1,6 @@ /* * nohelp.c: implement the has_embedded_chm() function for - * applications that have no help file at all, so that misc.c's + * applications that have no help file at all, so that buildinfo.c's * buildinfo string knows not to talk meaninglessly about whether the * nonexistent help file is present. */ diff --git a/windows/winnoise.c b/windows/noise.c index 65c4c92d..65c4c92d 100644 --- a/windows/winnoise.c +++ b/windows/noise.c diff --git a/windows/winpgnt.c b/windows/pageant.c index d6e960b6..d1368903 100644 --- a/windows/winpgnt.c +++ b/windows/pageant.c @@ -13,27 +13,23 @@ #include "ssh.h" #include "misc.h" #include "tree234.h" -#include "winsecur.h" -#include "wincapi.h" +#include "security-api.h" +#include "cryptoapi.h" #include "pageant.h" #include "licence.h" #include "pageant-rc.h" #include <shellapi.h> -#ifndef NO_SECURITY #include <aclapi.h> #ifdef DEBUG_IPC #define _WIN32_WINNT 0x0500 /* for ConvertSidToStringSid */ #include <sddl.h> #endif -#endif #define WM_SYSTRAY (WM_APP + 6) #define WM_SYSTRAY2 (WM_APP + 7) -#define AGENT_COPYDATA_ID 0x804e50ba /* random goop */ - #define APPNAME "Pageant" /* Titles and class names for invisible windows. IPCWINTITLE and @@ -95,8 +91,6 @@ void modalfatalbox(const char *fmt, ...) exit(1); } -static bool has_security; - struct PassphraseProcStruct { bool modal; const char *help_topic; @@ -109,7 +103,7 @@ struct PassphraseProcStruct { * Dialog-box function for the Licence box. */ static INT_PTR CALLBACK LicenceProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) + WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_INITDIALOG: @@ -134,15 +128,15 @@ static INT_PTR CALLBACK LicenceProc(HWND hwnd, UINT msg, * Dialog-box function for the About box. */ static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) + WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_INITDIALOG: { char *buildinfo_text = buildinfo("\r\n"); - char *text = dupprintf - ("Pageant\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s", - ver, buildinfo_text, - "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved."); + char *text = dupprintf( + "Pageant\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s", + ver, buildinfo_text, + "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved."); sfree(buildinfo_text); SetDlgItemText(hwnd, IDC_ABOUT_TEXTBOX, text); MakeDlgItemBorderless(hwnd, IDC_ABOUT_TEXTBOX); @@ -191,7 +185,8 @@ static void end_passphrase_dialog(HWND hwnd, INT_PTR result) } else { /* * Destroy this passphrase dialog box before passing the - * results back to pageant.c, to avoid re-entrancy issues. + * results back to the main pageant.c, to avoid re-entrancy + * issues. * * If we successfully got a passphrase from the user, but it * was _wrong_, then pageant_passphrase_request_success will @@ -219,7 +214,7 @@ static void end_passphrase_dialog(HWND hwnd, INT_PTR result) * Dialog-box function for the passphrase box. */ static INT_PTR CALLBACK PassphraseProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) + WPARAM wParam, LPARAM lParam) { struct PassphraseProcStruct *p; @@ -318,18 +313,30 @@ void old_keyfile_warning(void) } struct keylist_update_ctx { + HDC hdc; + int algbitswidth, algwidth, bitswidth, hashwidth; bool enable_remove_controls; bool enable_reencrypt_controls; }; +struct keylist_display_data { + strbuf *alg, *bits, *hash, *comment, *info; +}; + static void keylist_update_callback( void *vctx, char **fingerprints, const char *comment, uint32_t ext_flags, struct pageant_pubkey *key) { struct keylist_update_ctx *ctx = (struct keylist_update_ctx *)vctx; FingerprintType this_type = ssh2_pick_fingerprint(fingerprints, fptype); - const char *fingerprint = fingerprints[this_type]; - strbuf *listentry = strbuf_new(); + ptrlen fingerprint = ptrlen_from_asciz(fingerprints[this_type]); + + struct keylist_display_data *disp = snew(struct keylist_display_data); + disp->alg = strbuf_new(); + disp->bits = strbuf_new(); + disp->hash = strbuf_new(); + disp->comment = strbuf_new(); + disp->info = strbuf_new(); /* There is at least one key, so the controls for removing keys * should be enabled */ @@ -337,86 +344,103 @@ static void keylist_update_callback( switch (key->ssh_version) { case 1: { - strbuf_catf(listentry, "ssh1\t%s\t%s", fingerprint, comment); - /* - * Replace the space in the fingerprint (between bit count and - * hash) with a tab, for nice alignment in the box. + * Expect the fingerprint to contain two words: bit count and + * hash. */ - char *p = strchr(listentry->s, ' '); - if (p) - *p = '\t'; + put_dataz(disp->alg, "SSH-1"); + put_datapl(disp->bits, ptrlen_get_word(&fingerprint, " ")); + put_datapl(disp->hash, ptrlen_get_word(&fingerprint, " ")); break; } case 2: { /* - * For nice alignment in the list box, we would ideally want - * every entry to align to the tab stop settings, and have a - * column for algorithm name, one for bit count, one for hex - * fingerprint, and one for key comment. - * - * Unfortunately, some of the algorithm names are so long that - * they overflow into the bit-count field. Fortunately, at the - * moment, those are _precisely_ the algorithm names that - * don't need a bit count displayed anyway (because for - * NIST-style ECDSA the bit count is mentioned in the - * algorithm name, and for ssh-ed25519 there is only one - * possible value anyway). So we fudge this by simply omitting - * the bit count field in that situation. - * - * This is fragile not only in the face of further key types - * that don't follow this pattern, but also in the face of - * font metrics changes - the Windows semantics for list box - * tab stops is that \t aligns to the next one you haven't - * already exceeded, so I have to guess when the key type will - * overflow past the bit-count tab stop and leave out a tab - * character. Urgh. + * Expect the fingerprint to contain three words: algorithm + * name, bit count, hash. */ - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(key->blob)); - ptrlen algname = get_string(src); - const ssh_keyalg *alg = find_pubkey_alg_len(algname); - - bool include_bit_count = (alg == &ssh_dss || alg == &ssh_rsa); - - int wordnumber = 0; - for (const char *p = fingerprint; *p; p++) { - char c = *p; - if (c == ' ') { - if (wordnumber < 2) - c = '\t'; - wordnumber++; - } - if (include_bit_count || wordnumber != 1) - put_byte(listentry, c); + const ssh_keyalg *alg = pubkey_blob_to_alg( + ptrlen_from_strbuf(key->blob)); + + ptrlen keytype_word = ptrlen_get_word(&fingerprint, " "); + if (alg) { + /* Use our own human-legible algorithm names if available, + * because they fit better in the space. (Certificate key + * algorithm names in particular are terribly long.) */ + char *alg_desc = ssh_keyalg_desc(alg); + put_dataz(disp->alg, alg_desc); + sfree(alg_desc); + } else { + put_datapl(disp->alg, keytype_word); } - strbuf_catf(listentry, "\t%s", comment); - break; + ptrlen bits_word = ptrlen_get_word(&fingerprint, " "); + if (alg && ssh_keyalg_variable_size(alg)) + put_datapl(disp->bits, bits_word); + + put_datapl(disp->hash, ptrlen_get_word(&fingerprint, " ")); } } + put_dataz(disp->comment, comment); + + SIZE sz; + if (disp->bits->len) { + GetTextExtentPoint32(ctx->hdc, disp->alg->s, disp->alg->len, &sz); + if (ctx->algwidth < sz.cx) ctx->algwidth = sz.cx; + GetTextExtentPoint32(ctx->hdc, disp->bits->s, disp->bits->len, &sz); + if (ctx->bitswidth < sz.cx) ctx->bitswidth = sz.cx; + } else { + GetTextExtentPoint32(ctx->hdc, disp->alg->s, disp->alg->len, &sz); + if (ctx->algbitswidth < sz.cx) ctx->algbitswidth = sz.cx; + } + GetTextExtentPoint32(ctx->hdc, disp->hash->s, disp->hash->len, &sz); + if (ctx->hashwidth < sz.cx) ctx->hashwidth = sz.cx; + if (ext_flags & LIST_EXTENDED_FLAG_HAS_NO_CLEARTEXT_KEY) { - strbuf_catf(listentry, "\t(encrypted)"); + put_fmt(disp->info, "(encrypted)"); } else if (ext_flags & LIST_EXTENDED_FLAG_HAS_ENCRYPTED_KEY_FILE) { - strbuf_catf(listentry, "\t(re-encryptable)"); + put_fmt(disp->info, "(re-encryptable)"); /* At least one key can be re-encrypted */ ctx->enable_reencrypt_controls = true; } + /* This list box is owner-drawn but doesn't have LBS_HASSTRINGS, + * so we can use LB_ADDSTRING to hand the list box our display + * info pointer */ SendDlgItemMessage(keylist, IDC_KEYLIST_LISTBOX, - LB_ADDSTRING, 0, (LPARAM)listentry->s); - strbuf_free(listentry); + LB_ADDSTRING, 0, (LPARAM)disp); } +/* Column start positions for the list box, in pixels (not dialog units). */ +static int colpos_bits, colpos_hash, colpos_comment; + /* * Update the visible key list. */ void keylist_update(void) { if (keylist) { + /* + * Clear the previous list box content and free their display + * structures. + */ + { + int nitems = SendDlgItemMessage(keylist, IDC_KEYLIST_LISTBOX, + LB_GETCOUNT, 0, 0); + for (int i = 0; i < nitems; i++) { + struct keylist_display_data *disp = + (struct keylist_display_data *)SendDlgItemMessage( + keylist, IDC_KEYLIST_LISTBOX, LB_GETITEMDATA, i, 0); + strbuf_free(disp->alg); + strbuf_free(disp->bits); + strbuf_free(disp->hash); + strbuf_free(disp->comment); + strbuf_free(disp->info); + sfree(disp); + } + } SendDlgItemMessage(keylist, IDC_KEYLIST_LISTBOX, LB_RESETCONTENT, 0, 0); @@ -424,7 +448,22 @@ void keylist_update(void) struct keylist_update_ctx ctx[1]; ctx->enable_remove_controls = false; ctx->enable_reencrypt_controls = false; + ctx->algbitswidth = ctx->algwidth = 0; + ctx->bitswidth = ctx->hashwidth = 0; + ctx->hdc = GetDC(keylist); + SelectObject(ctx->hdc, (HFONT)SendMessage(keylist, WM_GETFONT, 0, 0)); int status = pageant_enum_keys(keylist_update_callback, ctx, &errmsg); + + SIZE sz; + GetTextExtentPoint32(ctx->hdc, "MM", 2, &sz); + int gutter = sz.cx; + + DeleteDC(ctx->hdc); + colpos_hash = ctx->algwidth + ctx->bitswidth + 2*gutter; + if (colpos_hash < ctx->algbitswidth + gutter) + colpos_hash = ctx->algbitswidth + gutter; + colpos_bits = colpos_hash - ctx->bitswidth - gutter; + colpos_comment = colpos_hash + ctx->hashwidth + gutter; assert(status == PAGEANT_ACTION_OK); assert(!errmsg); @@ -553,7 +592,7 @@ static void prompt_add_keyfile(bool encrypted) * Dialog-box function for the key list box. */ static INT_PTR CALLBACK KeyListProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) + WPARAM wParam, LPARAM lParam) { static const struct { const char *name; @@ -561,6 +600,8 @@ static INT_PTR CALLBACK KeyListProc(HWND hwnd, UINT msg, } fptypes[] = { {"SHA256", SSH_FPTYPE_SHA256}, {"MD5", SSH_FPTYPE_MD5}, + {"SHA256 including certificate", SSH_FPTYPE_SHA256_CERT}, + {"MD5 including certificate", SSH_FPTYPE_MD5_CERT}, }; switch (msg) { @@ -583,18 +624,12 @@ static INT_PTR CALLBACK KeyListProc(HWND hwnd, UINT msg, GetWindowLongPtr(hwnd, GWL_EXSTYLE) | WS_EX_CONTEXTHELP); else { - HWND item = GetDlgItem(hwnd, IDC_KEYLIST_HELP); - if (item) - DestroyWindow(item); + HWND item = GetDlgItem(hwnd, IDC_KEYLIST_HELP); + if (item) + DestroyWindow(item); } keylist = hwnd; - { - static int tabs[] = { 35, 75, 300 }; - SendDlgItemMessage(hwnd, IDC_KEYLIST_LISTBOX, LB_SETTABSTOPS, - sizeof(tabs) / sizeof(*tabs), - (LPARAM) tabs); - } int selection = 0; for (size_t i = 0; i < lenof(fptypes); i++) { @@ -609,6 +644,96 @@ static INT_PTR CALLBACK KeyListProc(HWND hwnd, UINT msg, keylist_update(); return 0; } + case WM_MEASUREITEM: { + assert(wParam == IDC_KEYLIST_LISTBOX); + + MEASUREITEMSTRUCT *mi = (MEASUREITEMSTRUCT *)lParam; + + /* + * Our list box is owner-drawn, but we put normal text in it. + * So the line height is the same as it would normally be, + * which is 8 dialog units. + */ + RECT r; + r.left = r.right = r.top = 0; + r.bottom = 8; + MapDialogRect(hwnd, &r); + mi->itemHeight = r.bottom; + + return 0; + } + case WM_DRAWITEM: { + assert(wParam == IDC_KEYLIST_LISTBOX); + + DRAWITEMSTRUCT *di = (DRAWITEMSTRUCT *)lParam; + + if (di->itemAction == ODA_FOCUS) { + /* Just toggle the focus rectangle either on or off. This + * is an XOR-type function, so it's the same call in + * either case. */ + DrawFocusRect(di->hDC, &di->rcItem); + } else { + /* Draw the full text. */ + bool selected = (di->itemState & ODS_SELECTED); + COLORREF newfg = GetSysColor( + selected ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT); + COLORREF newbg = GetSysColor( + selected ? COLOR_HIGHLIGHT : COLOR_WINDOW); + COLORREF oldfg = SetTextColor(di->hDC, newfg); + COLORREF oldbg = SetBkColor(di->hDC, newbg); + + HFONT font = (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0); + HFONT oldfont = SelectObject(di->hDC, font); + + /* ExtTextOut("") is an easy way to just draw the + * background rectangle */ + ExtTextOut(di->hDC, di->rcItem.left, di->rcItem.top, + ETO_OPAQUE | ETO_CLIPPED, &di->rcItem, "", 0, NULL); + + struct keylist_display_data *disp = + (struct keylist_display_data *)di->itemData; + + RECT r; + + /* Apparently real list boxes start drawing at x=1, not x=0 */ + r.left = r.top = r.bottom = 0; + r.right = 1; + MapDialogRect(hwnd, &r); + ExtTextOut(di->hDC, di->rcItem.left + r.right, di->rcItem.top, + ETO_CLIPPED, &di->rcItem, disp->alg->s, + disp->alg->len, NULL); + + if (disp->bits->len) { + ExtTextOut(di->hDC, di->rcItem.left + r.right + colpos_bits, + di->rcItem.top, ETO_CLIPPED, &di->rcItem, + disp->bits->s, disp->bits->len, NULL); + } + + ExtTextOut(di->hDC, di->rcItem.left + r.right + colpos_hash, + di->rcItem.top, ETO_CLIPPED, &di->rcItem, + disp->hash->s, disp->hash->len, NULL); + + strbuf *sb = strbuf_new(); + put_datapl(sb, ptrlen_from_strbuf(disp->comment)); + if (disp->info->len) { + put_byte(sb, '\t'); + put_datapl(sb, ptrlen_from_strbuf(disp->info)); + } + + TabbedTextOut(di->hDC, di->rcItem.left + r.right + colpos_comment, + di->rcItem.top, sb->s, sb->len, 0, NULL, 0); + + strbuf_free(sb); + + SetTextColor(di->hDC, oldfg); + SetBkColor(di->hDC, oldbg); + SelectObject(di->hDC, oldfont); + + if (di->itemState & ODS_FOCUS) + DrawFocusRect(di->hDC, &di->rcItem); + } + return 0; + } case WM_COMMAND: switch (LOWORD(wParam)) { case IDOK: @@ -729,9 +854,9 @@ static INT_PTR CALLBACK KeyListProc(HWND hwnd, UINT msg, topic = WINHELP_CTX_pageant_deferred; break; } if (topic) { - launch_help(hwnd, topic); + launch_help(hwnd, topic); } else { - MessageBeep(0); + MessageBeep(0); } break; } @@ -827,13 +952,12 @@ static void update_sessions(void) } } -#ifndef NO_SECURITY /* * Versions of Pageant prior to 0.61 expected this SID on incoming * communications. For backwards compatibility, and more particularly * for compatibility with derived works of PuTTY still using the old * Pageant client code, we accept it as an alternative to the one - * returned from get_user_sid() in winpgntc.c. + * returned from get_user_sid(). */ PSID get_default_sid(void) { @@ -872,7 +996,6 @@ PSID get_default_sid(void) return ret; } -#endif struct WmCopydataTransaction { char *length, *body; @@ -982,12 +1105,10 @@ static char *answer_filemapping_message(const char *mapname) size_t mapsize; unsigned msglen; -#ifndef NO_SECURITY PSID mapsid = NULL; PSID expectedsid = NULL; PSID expectedsid_bc = NULL; PSECURITY_DESCRIPTOR psd = NULL; -#endif wmct.length = wmct.body = NULL; @@ -1006,8 +1127,7 @@ static char *answer_filemapping_message(const char *mapname) debug("maphandle = %p\n", maphandle); #endif -#ifndef NO_SECURITY - if (has_security) { + if (should_have_security()) { DWORD retd; if ((expectedsid = get_user_sid()) == NULL) { @@ -1048,9 +1168,7 @@ static char *answer_filemapping_message(const char *mapname) err = dupstr("wrong owning SID of file mapping"); goto cleanup; } - } else -#endif /* NO_SECURITY */ - { + } else { #ifdef DEBUG_IPC debug("security APIs not present\n"); #endif @@ -1195,8 +1313,8 @@ static LRESULT CALLBACK TrayWndProc(HWND hwnd, UINT message, if((INT_PTR)ShellExecute(hwnd, NULL, putty_path, cmdline, _T(""), SW_SHOW) <= 32) { - MessageBox(NULL, "Unable to execute PuTTY!", - "Error", MB_OK | MB_ICONERROR); + MessageBox(NULL, "Unable to execute PuTTY!", + "Error", MB_OK | MB_ICONERROR); } break; } @@ -1254,31 +1372,34 @@ static LRESULT CALLBACK TrayWndProc(HWND hwnd, UINT message, break; default: { if(wParam >= IDM_SESSIONS_BASE && wParam <= IDM_SESSIONS_MAX) { - MENUITEMINFO mii; - TCHAR buf[MAX_PATH + 1]; - TCHAR param[MAX_PATH + 1]; - memset(&mii, 0, sizeof(mii)); - mii.cbSize = sizeof(mii); - mii.fMask = MIIM_TYPE; - mii.cch = MAX_PATH; - mii.dwTypeData = buf; - GetMenuItemInfo(session_menu, wParam, false, &mii); - param[0] = '\0'; - if (restrict_putty_acl) - strcat(param, "&R"); - strcat(param, "@"); - strcat(param, mii.dwTypeData); - if((INT_PTR)ShellExecute(hwnd, NULL, putty_path, param, - _T(""), SW_SHOW) <= 32) { - MessageBox(NULL, "Unable to execute PuTTY!", "Error", - MB_OK | MB_ICONERROR); - } + MENUITEMINFO mii; + TCHAR buf[MAX_PATH + 1]; + TCHAR param[MAX_PATH + 1]; + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_TYPE; + mii.cch = MAX_PATH; + mii.dwTypeData = buf; + GetMenuItemInfo(session_menu, wParam, false, &mii); + param[0] = '\0'; + if (restrict_putty_acl) + strcat(param, "&R"); + strcat(param, "@"); + strcat(param, mii.dwTypeData); + if((INT_PTR)ShellExecute(hwnd, NULL, putty_path, param, + _T(""), SW_SHOW) <= 32) { + MessageBox(NULL, "Unable to execute PuTTY!", "Error", + MB_OK | MB_ICONERROR); + } } break; } } break; } + case WM_NETEVENT: + winselgui_response(wParam, lParam); + return 0; case WM_DESTROY: quit_help(hwnd); PostQuitMessage(0); @@ -1305,10 +1426,10 @@ static LRESULT CALLBACK wm_copydata_WndProc(HWND hwnd, UINT message, err = answer_filemapping_message(mapname); if (err) { #ifdef DEBUG_IPC - debug("IPC failed: %s\n", err); + debug("IPC failed: %s\n", err); #endif - sfree(err); - return 0; + sfree(err); + return 0; } return 1; } @@ -1351,11 +1472,6 @@ void spawn_cmd(const char *cmdline, const char *args, int show) } } -void logevent(LogContext *logctx, const char *event) -{ - unreachable("Pageant can't create a LogContext, so this can't be called"); -} - void noise_ultralight(NoiseSourceId id, unsigned long data) { /* Pageant doesn't use random numbers, so we ignore this */ @@ -1386,28 +1502,58 @@ static struct winpgnt_client wpc[1]; HINSTANCE hinst; +static NORETURN void opt_error(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + char *msg = dupvprintf(fmt, ap); + va_end(ap); + + MessageBox(NULL, msg, "Pageant command line error", MB_ICONERROR | MB_OK); + + exit(1); +} + +#ifdef LEGACY_WINDOWS +BOOL sw_PeekMessage(LPMSG msg, HWND hwnd, UINT min, UINT max, UINT remove) +{ + static bool unicode_unavailable = false; + if (!unicode_unavailable) { + BOOL ret = PeekMessageW(msg, hwnd, min, max, remove); + if (!ret && GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) + unicode_unavailable = true; /* don't try again */ + else + return ret; + } + return PeekMessageA(msg, hwnd, min, max, remove); +} +#else +#define sw_PeekMessage PeekMessageW +#endif + int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) { MSG msg; const char *command = NULL; - bool added_keys = false; + const char *unixsocket = NULL; bool show_keylist_on_startup = false; - int argc, i; + int argc; char **argv, **argstart; + const char *openssh_config_file = NULL; + + typedef struct CommandLineKey { + Filename *fn; + bool add_encrypted; + } CommandLineKey; + + CommandLineKey *clkeys = NULL; + size_t nclkeys = 0, clkeysize = 0; dll_hijacking_protection(); hinst = inst; - /* - * Determine whether we're an NT system (should have security - * APIs) or a non-NT system (don't do security). - */ - init_winver(); - has_security = (osPlatformId == VER_PLATFORM_WIN32_NT); - - if (has_security) { -#ifndef NO_SECURITY + if (should_have_security()) { /* * Attempt to get the security API we need. */ @@ -1418,13 +1564,6 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) "Pageant Fatal Error", MB_ICONERROR | MB_OK); return 1; } -#else - MessageBox(NULL, - "This program has been compiled for Win9X and will\n" - "not run on NT, in case it causes a security breach.", - "Pageant Fatal Error", MB_ICONERROR | MB_OK); - return 1; -#endif } /* @@ -1454,81 +1593,240 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) } /* + * Process the command line, handling anything that can be done + * immediately, but deferring adding keys until after we've + * started up the main agent. Details of keys to be added are + * stored in the 'clkeys' array. + */ + split_into_argv(cmdline, &argc, &argv, &argstart); + bool add_keys_encrypted = false; + AuxMatchOpt amo = aux_match_opt_init(argc, argv, 0, opt_error); + while (!aux_match_done(&amo)) { + char *val; + #define match_opt(...) aux_match_opt( \ + &amo, NULL, __VA_ARGS__, (const char *)NULL) + #define match_optval(...) aux_match_opt( \ + &amo, &val, __VA_ARGS__, (const char *)NULL) + + if (aux_match_arg(&amo, &val)) { + /* + * Non-option arguments are expected to be key files, and + * added to clkeys. + */ + sgrowarray(clkeys, clkeysize, nclkeys); + CommandLineKey *clkey = &clkeys[nclkeys++]; + clkey->fn = filename_from_str(val); + clkey->add_encrypted = add_keys_encrypted; + } else if (match_opt("-pgpfp")) { + pgp_fingerprints_msgbox(NULL); + return 1; + } else if (match_opt("-restrict-acl", "-restrict_acl", + "-restrictacl")) { + restrict_process_acl(); + } else if (match_opt("-restrict-putty-acl", "-restrict_putty_acl")) { + restrict_putty_acl = true; + } else if (match_opt("-no-decrypt", "-no_decrypt", + "-nodecrypt", "-encrypted")) { + add_keys_encrypted = true; + } else if (match_opt("-keylist")) { + show_keylist_on_startup = true; + } else if (match_optval("-openssh-config", "-openssh_config")) { + openssh_config_file = val; + } else if (match_optval("-unix")) { + unixsocket = val; + } else if (match_opt("-c")) { + /* + * If we see `-c', then the rest of the command line + * should be treated as a command to be spawned. + */ + if (amo.index < amo.argc) + command = argstart[amo.index]; + else + command = ""; + break; + } else { + opt_error("unrecognised option '%s'\n", amo.argv[amo.index]); + } + } + + /* + * Create and lock an interprocess mutex while we figure out + * whether we're going to be the Pageant server or a client. That + * way, two Pageant processes started up simultaneously will be + * able to agree on which one becomes the server without a race + * condition. + */ + HANDLE mutex; + { + char *err; + char *mutexname = agent_mutex_name(); + mutex = lock_interprocess_mutex(mutexname, &err); + sfree(mutexname); + if (!mutex) { + MessageBox(NULL, err, "Pageant Error", MB_ICONERROR | MB_OK); + return 1; + } + } + + /* * Find out if Pageant is already running. */ already_running = agent_exists(); /* - * Initialise the cross-platform Pageant code. + * If it isn't, we're going to be the primary Pageant that stays + * running, so set up all the machinery to answer requests. */ if (!already_running) { + /* + * Set up the window class for the hidden window that receives + * all the messages to do with our presence in the system tray. + */ + + if (!prev) { + WNDCLASS wndclass; + + memset(&wndclass, 0, sizeof(wndclass)); + wndclass.lpfnWndProc = TrayWndProc; + wndclass.hInstance = inst; + wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON)); + wndclass.lpszClassName = TRAYCLASSNAME; + + RegisterClass(&wndclass); + } + + keylist = NULL; + + traywindow = CreateWindow(TRAYCLASSNAME, TRAYWINTITLE, + WS_OVERLAPPEDWINDOW | WS_VSCROLL, + CW_USEDEFAULT, CW_USEDEFAULT, + 100, 100, NULL, NULL, inst, NULL); + winselgui_set_hwnd(traywindow); + + /* + * Initialise the cross-platform Pageant code. + */ pageant_init(); - } - /* - * Process the command line and add keys as listed on it. - */ - split_into_argv(cmdline, &argc, &argv, &argstart); - bool doing_opts = true; - bool add_keys_encrypted = false; - for (i = 0; i < argc; i++) { - char *p = argv[i]; - if (*p == '-' && doing_opts) { - if (!strcmp(p, "-pgpfp")) { - pgp_fingerprints_msgbox(NULL); + /* + * Set up a named-pipe listener. + */ + wpc->plc.vt = &winpgnt_vtable; + wpc->plc.suppress_logging = true; + if (should_have_security()) { + Plug *pl_plug; + struct pageant_listen_state *pl = + pageant_listener_new(&pl_plug, &wpc->plc); + char *pipename = agent_named_pipe_name(); + Socket *sock = new_named_pipe_listener(pipename, pl_plug); + if (sk_socket_error(sock)) { + char *err = dupprintf("Unable to open named pipe at %s " + "for SSH agent:\n%s", pipename, + sk_socket_error(sock)); + MessageBox(NULL, err, "Pageant Error", MB_ICONERROR | MB_OK); return 1; - } else if (!strcmp(p, "-restrict-acl") || - !strcmp(p, "-restrict_acl") || - !strcmp(p, "-restrictacl")) { - restrict_process_acl(); - } else if (!strcmp(p, "-restrict-putty-acl") || - !strcmp(p, "-restrict_putty_acl")) { - restrict_putty_acl = true; - } else if (!strcmp(p, "--no-decrypt") || - !strcmp(p, "-no-decrypt") || - !strcmp(p, "--no_decrypt") || - !strcmp(p, "-no_decrypt") || - !strcmp(p, "--nodecrypt") || - !strcmp(p, "-nodecrypt") || - !strcmp(p, "--encrypted") || - !strcmp(p, "-encrypted")) { - add_keys_encrypted = true; - } else if (!strcmp(p, "-keylist") || !strcmp(p, "--keylist")) { - show_keylist_on_startup = true; - } else if (!strcmp(p, "-c")) { - /* - * If we see `-c', then the rest of the - * command line should be treated as a - * command to be spawned. - */ - if (i < argc-1) - command = argstart[i+1]; - else - command = ""; - break; - } else if (!strcmp(p, "--")) { - doing_opts = false; - } else { - char *msg = dupprintf("unrecognised command-line option\n" - "'%s'", p); - MessageBox(NULL, msg, "Pageant command-line syntax error", - MB_ICONERROR | MB_OK); - exit(1); } - } else { - Filename *fn = filename_from_str(p); - win_add_keyfile(fn, add_keys_encrypted); - filename_free(fn); - added_keys = true; + pageant_listener_got_socket(pl, sock); + + /* + * If we've been asked to write out an OpenSSH config file + * pointing at the named pipe, do so. + */ + if (openssh_config_file) { + FILE *fp = fopen(openssh_config_file, "w"); + if (!fp) { + char *err = dupprintf("Unable to write OpenSSH config " + "file to %s", openssh_config_file); + MessageBox(NULL, err, "Pageant Error", + MB_ICONERROR | MB_OK); + return 1; + } + fprintf(fp, "IdentityAgent %s\n", pipename); + fclose(fp); + } + + sfree(pipename); + } + + /* + * Set up an AF_UNIX listener too, if we were asked to. + */ + if (unixsocket) { + sk_init(); + + /* FIXME: diagnose any error except file-not-found. Also, + * check the file type if possible? */ + remove(unixsocket); + + Plug *pl_plug; + struct pageant_listen_state *pl = + pageant_listener_new(&pl_plug, &wpc->plc); + Socket *sock = sk_newlistener_unix(unixsocket, pl_plug); + if (sk_socket_error(sock)) { + char *err = dupprintf("Unable to open AF_UNIX socket at %s " + "for SSH agent:\n%s", unixsocket, + sk_socket_error(sock)); + MessageBox(NULL, err, "Pageant Error", MB_ICONERROR | MB_OK); + return 1; + } + pageant_listener_got_socket(pl, sock); + } + + /* + * Set up the window class for the hidden window that receives + * the WM_COPYDATA message used by the old-style Pageant IPC + * system. + */ + if (!prev) { + WNDCLASS wndclass; + + memset(&wndclass, 0, sizeof(wndclass)); + wndclass.lpfnWndProc = wm_copydata_WndProc; + wndclass.hInstance = inst; + wndclass.lpszClassName = IPCCLASSNAME; + + RegisterClass(&wndclass); } + + /* + * And launch the subthread which will open that hidden window and + * handle WM_COPYDATA messages on it. + */ + wmcpc.vt = &wmcpc_vtable; + wmcpc.suppress_logging = true; + pageant_register_client(&wmcpc); + DWORD wm_copydata_threadid; + wmct.ev_msg_ready = CreateEvent(NULL, false, false, NULL); + wmct.ev_reply_ready = CreateEvent(NULL, false, false, NULL); + HANDLE hThread = CreateThread(NULL, 0, wm_copydata_threadfunc, + &inst, 0, &wm_copydata_threadid); + if (hThread) + CloseHandle(hThread); /* we don't need the thread handle */ + add_handle_wait(wmct.ev_msg_ready, wm_copydata_got_msg, NULL); } /* - * Forget any passphrase that we retained while going over - * command line keyfiles. + * Now we're either a fully set up Pageant server, or we know one + * is running somewhere else. Either way, now it's safe to unlock + * the mutex. */ + unlock_interprocess_mutex(mutex); + + /* + * Add any keys provided on the command line. + */ + for (size_t i = 0; i < nclkeys; i++) { + CommandLineKey *clkey = &clkeys[i]; + win_add_keyfile(clkey->fn, clkey->add_encrypted); + filename_free(clkey->fn); + } + sfree(clkeys); + /* And forget any passphrases we stashed during that loop. */ pageant_forget_passphrases(); + /* + * Now our keys are present, spawn a command, if we were asked to. + */ if (command) { char *args; if (command[0] == '"') @@ -1548,73 +1846,13 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) * keys), complain. */ if (already_running) { - if (!command && !added_keys) { + if (!command && !nclkeys) { MessageBox(NULL, "Pageant is already running", "Pageant Error", MB_ICONERROR | MB_OK); } return 0; } -#if !defined NO_SECURITY - - /* - * Set up a named-pipe listener. - */ - { - Plug *pl_plug; - wpc->plc.vt = &winpgnt_vtable; - wpc->plc.suppress_logging = true; - struct pageant_listen_state *pl = - pageant_listener_new(&pl_plug, &wpc->plc); - char *pipename = agent_named_pipe_name(); - Socket *sock = new_named_pipe_listener(pipename, pl_plug); - if (sk_socket_error(sock)) { - char *err = dupprintf("Unable to open named pipe at %s " - "for SSH agent:\n%s", pipename, - sk_socket_error(sock)); - MessageBox(NULL, err, "Pageant Error", MB_ICONERROR | MB_OK); - return 1; - } - pageant_listener_got_socket(pl, sock); - sfree(pipename); - } - -#endif /* !defined NO_SECURITY */ - - /* - * Set up window classes for two hidden windows: one that receives - * all the messages to do with our presence in the system tray, - * and one that receives the WM_COPYDATA message used by the - * old-style Pageant IPC system. - */ - - if (!prev) { - WNDCLASS wndclass; - - memset(&wndclass, 0, sizeof(wndclass)); - wndclass.lpfnWndProc = TrayWndProc; - wndclass.hInstance = inst; - wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON)); - wndclass.lpszClassName = TRAYCLASSNAME; - - RegisterClass(&wndclass); - - memset(&wndclass, 0, sizeof(wndclass)); - wndclass.lpfnWndProc = wm_copydata_WndProc; - wndclass.hInstance = inst; - wndclass.lpszClassName = IPCCLASSNAME; - - RegisterClass(&wndclass); - } - - keylist = NULL; - - traywindow = CreateWindow(TRAYCLASSNAME, TRAYWINTITLE, - WS_OVERLAPPEDWINDOW | WS_VSCROLL, - CW_USEDEFAULT, CW_USEDEFAULT, - 100, 100, NULL, NULL, inst, NULL); - winselgui_set_hwnd(traywindow); - /* Set up a system tray icon */ AddTrayIcon(traywindow); @@ -1628,7 +1866,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) AppendMenu(systray_menu, MF_SEPARATOR, 0, 0); } AppendMenu(systray_menu, MF_ENABLED, IDM_VIEWKEYS, - "&View Keys"); + "&View Keys"); AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key"); AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY_ENCRYPTED, "Add key (encrypted)"); @@ -1650,18 +1888,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) ShowWindow(traywindow, SW_HIDE); - wmcpc.vt = &wmcpc_vtable; - wmcpc.suppress_logging = true; - pageant_register_client(&wmcpc); - DWORD wm_copydata_threadid; - wmct.ev_msg_ready = CreateEvent(NULL, false, false, NULL); - wmct.ev_reply_ready = CreateEvent(NULL, false, false, NULL); - HANDLE hThread = CreateThread(NULL, 0, wm_copydata_threadfunc, - &inst, 0, &wm_copydata_threadid); - if (hThread) - CloseHandle(hThread); /* we don't need the thread handle */ - handle_add_foreign_event(wmct.ev_msg_ready, wm_copydata_got_msg, NULL); - + /* Open the visible key list window, if we've been asked to. */ if (show_keylist_on_startup) create_keylist_window(); @@ -1669,21 +1896,19 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) * Main message loop. */ while (true) { - HANDLE *handles; - int nhandles, n; + int n; - handles = handle_get_events(&nhandles); + HandleWaitList *hwl = get_handle_wait_list(); - n = MsgWaitForMultipleObjects(nhandles, handles, false, - INFINITE, QS_ALLINPUT); + DWORD timeout = toplevel_callback_pending() ? 0 : INFINITE; + n = MsgWaitForMultipleObjects(hwl->nhandles, hwl->handles, false, + timeout, QS_ALLINPUT); - if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) { - handle_got_event(handles[n - WAIT_OBJECT_0]); - sfree(handles); - } else - sfree(handles); + if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)hwl->nhandles) + handle_wait_activate(hwl, n - WAIT_OBJECT_0); + handle_wait_list_free(hwl); - while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { + while (sw_PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) goto finished; /* two-level break */ @@ -1718,6 +1943,16 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) if (keypath) filereq_free(keypath); + if (openssh_config_file) { + /* + * Leave this file around, but empty it, so that it doesn't + * refer to a pipe we aren't listening on any more. + */ + FILE *fp = fopen(openssh_config_file, "w"); + if (fp) + fclose(fp); + } + cleanup_exit(msg.wParam); return msg.wParam; /* just in case optimiser complains */ } diff --git a/windows/pageant.rc b/windows/pageant.rc index a4a15195..1bea78b4 100644 --- a/windows/pageant.rc +++ b/windows/pageant.rc @@ -9,7 +9,7 @@ #include "pageant-rc.h" -#include "winhelp.rc2" +#include "help.rc2" IDI_MAINICON ICON "pageant.ico" IDI_TRAYICON ICON "pageants.ico" @@ -51,8 +51,8 @@ STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Pageant Key List" FONT 8, "MS Shell Dlg" BEGIN - LISTBOX 100, 10, 10, 420, 155, - LBS_EXTENDEDSEL | LBS_HASSTRINGS | LBS_USETABSTOPS | WS_VSCROLL | WS_TABSTOP + LISTBOX IDC_KEYLIST_LISTBOX, 10, 10, 420, 155, + LBS_EXTENDEDSEL | LBS_OWNERDRAWFIXED | WS_VSCROLL | WS_TABSTOP PUSHBUTTON "&Add Key", IDC_KEYLIST_ADDKEY, 10, 187, 60, 14 PUSHBUTTON "Add Key (&encrypted)", IDC_KEYLIST_ADDKEY_ENC, 75, 187, 80, 14 PUSHBUTTON "Re-e&ncrypt", IDC_KEYLIST_REENCRYPT, 315, 187, 60, 14 @@ -60,7 +60,7 @@ BEGIN PUSHBUTTON "&Help", IDC_KEYLIST_HELP, 10, 212, 50, 14 DEFPUSHBUTTON "&Close", IDOK, 390, 212, 50, 14 LTEXT "&Fingerprint type:", IDC_KEYLIST_FPTYPE_STATIC, 10, 172, 60, 8 - COMBOBOX IDC_KEYLIST_FPTYPE, 70, 170, 60, 12, CBS_DROPDOWNLIST + COMBOBOX IDC_KEYLIST_FPTYPE, 70, 170, 100, 12, CBS_DROPDOWNLIST END /* Accelerators used: cl */ diff --git a/windows/winstuff.h b/windows/platform.h index c0df5a31..959a207c 100644 --- a/windows/winstuff.h +++ b/windows/platform.h @@ -1,25 +1,22 @@ /* - * winstuff.h: Windows-specific inter-module stuff. + * windows/platform.h: Windows-specific inter-module stuff. */ -#ifndef PUTTY_WINSTUFF_H -#define PUTTY_WINSTUFF_H +#ifndef PUTTY_WINDOWS_PLATFORM_H +#define PUTTY_WINDOWS_PLATFORM_H -#ifndef AUTO_WINSOCK #include <winsock2.h> -#endif #include <windows.h> #include <stdio.h> /* for FILENAME_MAX */ /* We use uintptr_t for Win32/Win64 portability, so we should in * principle include stdint.h, which defines it according to the C - * standard. But older versions of Visual Studio - including the one - * used for official PuTTY builds as of 2015-09-28 - don't provide + * standard. But older versions of Visual Studio don't provide * stdint.h at all, but do (non-standardly) define uintptr_t in * stddef.h. So here we try to make sure _some_ standard header is * included which defines uintptr_t. */ #include <stddef.h> -#if !defined _MSC_VER || _MSC_VER >= 1600 || defined __clang__ +#if !HAVE_NO_STDINT_H #include <stdint.h> #endif @@ -28,7 +25,7 @@ #include "tree234.h" -#include "winhelp.h" +#include "help.h" #if defined _M_IX86 || defined _M_AMD64 #define BUILDINFO_PLATFORM "x86 Windows" @@ -38,6 +35,18 @@ #define BUILDINFO_PLATFORM "Windows" #endif +#if defined __GNUC__ || defined __clang__ +#define THREADLOCAL __thread +#elif defined _MSC_VER +#define THREADLOCAL __declspec(thread) +#else +#error Do not know how to declare thread-local storage with this toolchain +#endif + +/* Randomly-chosen dwData value identifying a WM_COPYDATA message as + * being a Pageant transaction */ +#define AGENT_COPYDATA_ID 0x804e50ba + struct Filename { char *path; }; @@ -102,9 +111,19 @@ struct FontSpec *fontspec_new( #define LONG_PTR LONG #endif -#define BOXFLAGS DLGWINDOWEXTRA -#define BOXRESULT (DLGWINDOWEXTRA + sizeof(LONG_PTR)) -#define DF_END 0x0001 +#if !HAVE_STRTOUMAX +/* Work around lack of strtoumax in older MSVC libraries */ +static inline uintmax_t strtoumax(const char *nptr, char **endptr, int base) +{ return _strtoui64(nptr, endptr, base); } +#endif + +typedef INT_PTR (*ShinyDlgProc)(HWND hwnd, UINT msg, WPARAM wParam, + LPARAM lParam, void *ctx); +int ShinyDialogBox(HINSTANCE hinst, LPCTSTR tmpl, const char *winclass, + HWND hwndparent, ShinyDlgProc proc, void *ctx); +void ShinyEndDialog(HWND hwnd, int ret); + +void centre_window(HWND hwnd); #ifndef __WINE__ /* Up-to-date Windows headers warn that the unprefixed versions of @@ -118,8 +137,6 @@ struct FontSpec *fontspec_new( #define strnicmp strncasecmp #endif -#define BROKEN_PIPE_ERROR_CODE ERROR_BROKEN_PIPE /* used in sshshare.c */ - /* * Dynamically linked functions. These come in two flavours: * @@ -143,8 +160,6 @@ struct FontSpec *fontspec_new( /* If you DECL_WINDOWS_FUNCTION as extern in a header file, use this to * define the function pointer in a source file */ #define DEF_WINDOWS_FUNCTION(name) t_##name p_##name -#define STR1(x) #x -#define STR(x) STR1(x) #define GET_WINDOWS_FUNCTION_PP(module, name) \ TYPECHECK((t_##name)NULL == name, \ (p_##name = module ? \ @@ -204,7 +219,7 @@ typedef void *Ssh_gss_name; extern HINSTANCE hinst; /* - * Help file stuff in winhelp.c. + * Help file stuff in help.c. */ void init_help(void); void shutdown_help(void); @@ -214,22 +229,23 @@ void quit_help(HWND hwnd); int has_embedded_chm(void); /* 1 = yes, 0 = no, -1 = N/A */ /* - * GUI seat methods in windlg.c, so that the vtable definition in + * GUI seat methods in dialog.c, so that the vtable definition in * window.c can refer to them. */ -int win_seat_verify_ssh_host_key( +SeatPromptResult win_seat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **key_fingerprints, - void (*callback)(void *ctx, int result), void *ctx); -int win_seat_confirm_weak_crypto_primitive( + char *keystr, SeatDialogText *text, HelpCtx helpctx, + void (*callback)(void *ctx, SeatPromptResult result), void *ctx); +SeatPromptResult win_seat_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, - void (*callback)(void *ctx, int result), void *ctx); -int win_seat_confirm_weak_cached_hostkey( + void (*callback)(void *ctx, SeatPromptResult result), void *ctx); +SeatPromptResult win_seat_confirm_weak_cached_hostkey( Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, int result), void *ctx); + void (*callback)(void *ctx, SeatPromptResult result), void *ctx); +const SeatDialogPromptDescriptions *win_seat_prompt_descriptions(Seat *seat); /* - * Windows-specific clipboard helper function shared with windlg.c, + * Windows-specific clipboard helper function shared with dialog.c, * which takes the data string in the system code page instead of * Unicode. */ @@ -239,7 +255,7 @@ void write_aclip(int clipboard, char *, int, bool); /* * On Windows, we send MA_2CLK as the only event marking the second - * press of a mouse button. Compare unix.h. + * press of a mouse button. Compare unix/platform.h. */ #define MULTICLICK_ONLY_EVENT 1 @@ -258,7 +274,7 @@ void write_aclip(int clipboard, char *, int, bool); * couldn't write it if I wanted to, but I haven't bothered), so * it's a macro which always returns NULL. With any luck this will * cause the compiler to notice it can optimise away the - * implementation of XDM-AUTHORIZATION-1 in x11fwd.c :-) + * implementation of XDM-AUTHORIZATION-1 in ssh/x11fwd.c :-) */ #define sk_getxdmdata(socket, lenp) (NULL) @@ -275,14 +291,14 @@ void write_aclip(int clipboard, char *, int, bool); "All Files (*.*)\0*\0\0\0") /* - * Exports from winnet.c. + * Exports from network.c. */ /* Report an event notification from WSA*Select */ void select_result(WPARAM, LPARAM); /* Enumerate all currently live OS-level SOCKETs */ SOCKET first_socket(int *); SOCKET next_socket(int *); -/* Ask winnet.c whether we currently want to try to write to a SOCKET */ +/* Ask network.c whether we currently want to try to write to a SOCKET */ bool socket_writable(SOCKET skt); /* Force a refresh of the SOCKET list by re-calling do_select for each one */ void socket_reselect_all(void); @@ -290,17 +306,18 @@ void socket_reselect_all(void); SockAddr *sk_namedpipe_addr(const char *pipename); /* Turn a WinSock error code into a string. */ const char *winsock_error_string(int error); +Socket *sk_newlistener_unix(const char *socketpath, Plug *plug); /* - * winnet.c dynamically loads WinSock 2 or WinSock 1 depending on + * network.c dynamically loads WinSock 2 or WinSock 1 depending on * what it can get, which means any WinSock routines used outside * that module must be exported from it as function pointers. So * here they are. */ DECL_WINDOWS_FUNCTION(extern, int, WSAAsyncSelect, - (SOCKET, HWND, u_int, long)); + (SOCKET, HWND, u_int, LONG)); DECL_WINDOWS_FUNCTION(extern, int, WSAEventSelect, - (SOCKET, WSAEVENT, long)); + (SOCKET, WSAEVENT, LONG)); DECL_WINDOWS_FUNCTION(extern, int, WSAGetLastError, (void)); DECL_WINDOWS_FUNCTION(extern, int, WSAEnumNetworkEvents, (SOCKET, WSAEVENT, LPWSANETWORKEVENTS)); @@ -318,17 +335,18 @@ DECL_WINDOWS_FUNCTION(extern, int, select, #endif /* - * Implemented differently depending on the client of winnet.c, and - * called by winnet.c to turn on or off WSA*Select for a given socket. + * Implemented differently depending on the client of network.c, and + * called by network.c to turn on or off WSA*Select for a given socket. */ const char *do_select(SOCKET skt, bool enable); /* - * Exports from winselgui.c and winselcli.c, each of which provides an + * Exports from select-{gui,cli}.c, each of which provides an * implementation of do_select. */ void winselgui_set_hwnd(HWND hwnd); void winselgui_clear_hwnd(void); +void winselgui_response(WPARAM wParam, LPARAM lParam); void winselcli_setup(void); SOCKET winselcli_unique_socket(void); @@ -338,18 +356,24 @@ extern HANDLE winselcli_event; * Network-subsystem-related functions provided in other Windows modules. */ Socket *make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H, - Plug *plug, bool overlapped); /* winhsock */ + SockAddr *addr, int port, Plug *plug, + bool overlapped); /* winhsock */ +Socket *make_deferred_handle_socket(DeferredSocketOpener *opener, + SockAddr *addr, int port, Plug *plug); +void setup_handle_socket(Socket *s, HANDLE send_H, HANDLE recv_H, + HANDLE stderr_H, bool overlapped); +void handle_socket_set_psb_prefix(Socket *s, const char *prefix); Socket *new_named_pipe_client(const char *pipename, Plug *plug); /* winnpc */ Socket *new_named_pipe_listener(const char *pipename, Plug *plug); /* winnps */ -/* A lower-level function in winnpc.c, which does most of the work of - * new_named_pipe_client (including checking the ownership of what - * it's connected to), but returns a plain HANDLE instead of wrapping - * it into a Socket. */ +/* A lower-level function in named-pipe-client.c, which does most of + * the work of new_named_pipe_client (including checking the ownership + * of what it's connected to), but returns a plain HANDLE instead of + * wrapping it into a Socket. */ HANDLE connect_to_named_pipe(const char *pipename, char **err); /* - * Exports from winctrls.c. + * Exports from controls.c. */ struct ctlpos { @@ -359,12 +383,12 @@ struct ctlpos { int ypos, width; int xoff; int boxystart, boxid; - char *boxtext; + const char *boxtext; }; void init_common_controls(void); /* also does some DLL-loading */ /* - * Exports from winutils.c. + * Exports from utils. */ typedef struct filereq_tag filereq; /* cwd for file requester */ bool request_file(filereq *state, OPENFILENAME *of, bool preserve, bool save); @@ -399,7 +423,7 @@ struct dlgparam { char *wintitle; /* title of actual window */ char *errtitle; /* title of error sub-messageboxes */ void *data; /* data to pass in refresh events */ - union control *focused, *lastfocused; /* which ctrl has focus now/before */ + dlgcontrol *focused, *lastfocused; /* which ctrl has focus now/before */ bool shortcuts[128]; /* track which shortcuts in use */ bool coloursel_wanted; /* has an event handler asked for * a colour selector? */ @@ -414,60 +438,64 @@ struct dlgparam { }; /* - * Exports from winctrls.c. + * Exports from controls.c. */ void ctlposinit(struct ctlpos *cp, HWND hwnd, int leftborder, int rightborder, int topborder); -HWND doctl(struct ctlpos *cp, RECT r, - char *wclass, int wstyle, int exstyle, char *wtext, int wid); -void bartitle(struct ctlpos *cp, char *name, int id); -void beginbox(struct ctlpos *cp, char *name, int idbox); +HWND doctl(struct ctlpos *cp, RECT r, const char *wclass, int wstyle, + int exstyle, const char *wtext, int wid); +void bartitle(struct ctlpos *cp, const char *name, int id); +void beginbox(struct ctlpos *cp, const char *name, int idbox); void endbox(struct ctlpos *cp); -void editboxfw(struct ctlpos *cp, bool password, char *text, - int staticid, int editid); -void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...); +void editboxfw(struct ctlpos *cp, bool password, bool readonly, + const char *text, int staticid, int editid); +void radioline(struct ctlpos *cp, const char *text, int id, int nacross, ...); void bareradioline(struct ctlpos *cp, int nacross, ...); -void radiobig(struct ctlpos *cp, char *text, int id, ...); -void checkbox(struct ctlpos *cp, char *text, int id); -void statictext(struct ctlpos *cp, char *text, int lines, int id); -void staticbtn(struct ctlpos *cp, char *stext, int sid, - char *btext, int bid); -void static2btn(struct ctlpos *cp, char *stext, int sid, - char *btext1, int bid1, char *btext2, int bid2); -void staticedit(struct ctlpos *cp, char *stext, +void radiobig(struct ctlpos *cp, const char *text, int id, ...); +void checkbox(struct ctlpos *cp, const char *text, int id); +void button(struct ctlpos *cp, const char *btext, int bid, bool defbtn); +void statictext(struct ctlpos *cp, const char *text, int lines, int id); +void staticbtn(struct ctlpos *cp, const char *stext, int sid, + const char *btext, int bid); +void static2btn(struct ctlpos *cp, const char *stext, int sid, + const char *btext1, int bid1, const char *btext2, int bid2); +void staticedit(struct ctlpos *cp, const char *stext, int sid, int eid, int percentedit); -void staticddl(struct ctlpos *cp, char *stext, +void staticddl(struct ctlpos *cp, const char *stext, int sid, int lid, int percentlist); -void combobox(struct ctlpos *cp, char *text, int staticid, int listid); -void staticpassedit(struct ctlpos *cp, char *stext, +void combobox(struct ctlpos *cp, const char *text, int staticid, int listid); +void staticpassedit(struct ctlpos *cp, const char *stext, int sid, int eid, int percentedit); -void bigeditctrl(struct ctlpos *cp, char *stext, +void bigeditctrl(struct ctlpos *cp, const char *stext, int sid, int eid, int lines); -void ersatztab(struct ctlpos *cp, char *stext, int sid, int lid, int s2id); -void editbutton(struct ctlpos *cp, char *stext, int sid, - int eid, char *btext, int bid); -void sesssaver(struct ctlpos *cp, char *text, +void ersatztab(struct ctlpos *cp, const char *stext, int sid, int lid, + int s2id); +void editbutton(struct ctlpos *cp, const char *stext, int sid, + int eid, const char *btext, int bid); +void sesssaver(struct ctlpos *cp, const char *text, int staticid, int editid, int listid, ...); -void envsetter(struct ctlpos *cp, char *stext, int sid, - char *e1stext, int e1sid, int e1id, - char *e2stext, int e2sid, int e2id, - int listid, char *b1text, int b1id, char *b2text, int b2id); -void charclass(struct ctlpos *cp, char *stext, int sid, int listid, - char *btext, int bid, int eid, char *s2text, int s2id); -void colouredit(struct ctlpos *cp, char *stext, int sid, int listid, - char *btext, int bid, ...); +void envsetter(struct ctlpos *cp, const char *stext, int sid, + const char *e1stext, int e1sid, int e1id, + const char *e2stext, int e2sid, int e2id, + int listid, const char *b1text, int b1id, + const char *b2text, int b2id); +void charclass(struct ctlpos *cp, const char *stext, int sid, int listid, + const char *btext, int bid, int eid, const char *s2text, + int s2id); +void colouredit(struct ctlpos *cp, const char *stext, int sid, int listid, + const char *btext, int bid, ...); void prefslist(struct prefslist *hdl, struct ctlpos *cp, int lines, - char *stext, int sid, int listid, int upbid, int dnbid); + const char *stext, int sid, int listid, int upbid, int dnbid); int handle_prefslist(struct prefslist *hdl, int *array, int maxmemb, bool is_dlmsg, HWND hwnd, WPARAM wParam, LPARAM lParam); void progressbar(struct ctlpos *cp, int id); -void fwdsetter(struct ctlpos *cp, int listid, char *stext, int sid, - char *e1stext, int e1sid, int e1id, - char *e2stext, int e2sid, int e2id, - char *btext, int bid, - char *r1text, int r1id, char *r2text, int r2id); +void fwdsetter(struct ctlpos *cp, int listid, const char *stext, int sid, + const char *e1stext, int e1sid, int e1id, + const char *e2stext, int e2sid, int e2id, + const char *btext, int bid, + const char *r1text, int r1id, const char *r2text, int r2id); void dlg_auto_set_fixed_pitch_flag(dlgparam *dlg); bool dlg_get_fixed_pitch_flag(dlgparam *dlg); @@ -476,11 +504,11 @@ void dlg_set_fixed_pitch_flag(dlgparam *dlg, bool flag); #define MAX_SHORTCUTS_PER_CTRL 16 /* - * This structure is what's stored for each `union control' in the + * This structure is what's stored for each `dlgcontrol' in the * portable-dialog interface. */ struct winctrl { - union control *ctrl; + dlgcontrol *ctrl; /* * The control may have several components at the Windows * level, with different dialog IDs. To avoid needing N @@ -511,7 +539,7 @@ struct winctrl { }; /* * And this structure holds a set of the above, in two separate - * tree234s so that it can find an item by `union control' or by + * tree234s so that it can find an item by `dlgcontrol' or by * dialog ID. */ struct winctrls { @@ -524,7 +552,7 @@ void winctrl_init(struct winctrls *); void winctrl_cleanup(struct winctrls *); void winctrl_add(struct winctrls *, struct winctrl *); void winctrl_remove(struct winctrls *, struct winctrl *); -struct winctrl *winctrl_findbyctrl(struct winctrls *, union control *); +struct winctrl *winctrl_findbyctrl(struct winctrls *, dlgcontrol *); struct winctrl *winctrl_findbyid(struct winctrls *, int); struct winctrl *winctrl_findbyindex(struct winctrls *, int); void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, @@ -539,13 +567,13 @@ void dp_add_tree(struct dlgparam *dp, struct winctrls *tree); void dp_cleanup(struct dlgparam *dp); /* - * Exports from wincfg.c. + * Exports from config.c. */ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, bool midsession, int protocol); /* - * Exports from windlg.c. + * Exports from dialog.c. */ void defuse_showwindow(void); bool do_config(Conf *); @@ -558,13 +586,15 @@ void show_help(HWND hwnd); HWND event_log_window(void); /* - * Exports from winmisc.c. + * Exports from utils. */ extern DWORD osMajorVersion, osMinorVersion, osPlatformId; void init_winver(void); void dll_hijacking_protection(void); +const char *get_system_dir(void); HMODULE load_system32_dll(const char *libname); const char *win_strerror(int error); +bool should_have_security(void); void restrict_process_acl(void); bool restricted_acl(void); void escape_registry_key(const char *in, strbuf *out); @@ -597,11 +627,10 @@ void EnableSizeTip(bool bEnable); /* * Exports from unicode.c. */ -struct unicode_data; void init_ucs(Conf *, struct unicode_data *); /* - * Exports from winhandl.c. + * Exports from handle-io.c. */ #define HANDLE_FLAG_OVERLAPPED 1 #define HANDLE_FLAG_IGNOREEOF 2 @@ -610,21 +639,17 @@ struct handle; typedef size_t (*handle_inputfn_t)( struct handle *h, const void *data, size_t len, int err); typedef void (*handle_outputfn_t)( - struct handle *h, size_t new_backlog, int err); + struct handle *h, size_t new_backlog, int err, bool close); struct handle *handle_input_new(HANDLE handle, handle_inputfn_t gotdata, void *privdata, int flags); struct handle *handle_output_new(HANDLE handle, handle_outputfn_t sentdata, void *privdata, int flags); size_t handle_write(struct handle *h, const void *data, size_t len); void handle_write_eof(struct handle *h); -HANDLE *handle_get_events(int *nevents); void handle_free(struct handle *h); -void handle_got_event(HANDLE event); void handle_unthrottle(struct handle *h, size_t backlog); size_t handle_backlog(struct handle *h); void *handle_get_privdata(struct handle *h); -struct handle *handle_add_foreign_event(HANDLE event, - void (*callback)(void *), void *ctx); /* Analogue of stdio_sink in marshal.h, for a Windows handle */ struct handle_sink { struct handle *h; @@ -633,17 +658,35 @@ struct handle_sink { void handle_sink_init(handle_sink *sink, struct handle *h); /* - * Exports from winpgntc.c. + * Exports from handle-wait.c. */ +typedef struct HandleWait HandleWait; +typedef void (*handle_wait_callback_fn_t)(void *); +HandleWait *add_handle_wait(HANDLE h, handle_wait_callback_fn_t callback, + void *callback_ctx); +void delete_handle_wait(HandleWait *hw); + +typedef struct HandleWaitList { + HANDLE handles[MAXIMUM_WAIT_OBJECTS]; + int nhandles; +} HandleWaitList; +HandleWaitList *get_handle_wait_list(void); +void handle_wait_activate(HandleWaitList *hwl, int index); +void handle_wait_list_free(HandleWaitList *hwl); + +/* + * Pageant-related pathnames. + */ +char *agent_mutex_name(void); char *agent_named_pipe_name(void); /* - * Exports from winser.c. + * Exports from serial.c. */ extern const struct BackendVtable serial_backend; /* - * Exports from winjump.c. + * Exports from jump-list.c. */ #define JUMPLIST_SUPPORTED /* suppress #defines in putty.h */ void add_session_to_jumplist(const char * const sessionname); @@ -652,12 +695,12 @@ void clear_jumplist(void); bool set_explicit_app_user_model_id(void); /* - * Exports from winnoise.c. + * Exports from noise.c. */ bool win_read_random(void *buf, unsigned wanted); /* returns true on success */ /* - * Extra functions in winstore.c over and above the interface in + * Extra functions in storage.c over and above the interface in * storage.h. * * These functions manipulate the Registry section which mirrors the @@ -690,10 +733,23 @@ char *get_jumplist_registry_entries(void); #define CLIPUI_DEFAULT_MOUSE CLIPUI_EXPLICIT #define CLIPUI_DEFAULT_INS CLIPUI_EXPLICIT -/* In winmisc.c */ -char *registry_get_string(HKEY root, const char *path, const char *leaf); - -/* In wincliloop.c */ +/* In utils */ +HKEY open_regkey_fn(bool create, HKEY base, const char *path, ...); +#define open_regkey(create, base, ...) \ + open_regkey_fn(create, base, __VA_ARGS__, (const char *)NULL) +void close_regkey(HKEY key); +void del_regkey(HKEY key, const char *name); +char *enum_regkey(HKEY key, int index); +bool get_reg_dword(HKEY key, const char *name, DWORD *out); +bool put_reg_dword(HKEY key, const char *name, DWORD value); +char *get_reg_sz(HKEY key, const char *name); +bool put_reg_sz(HKEY key, const char *name, const char *str); +strbuf *get_reg_multi_sz(HKEY key, const char *name); +bool put_reg_multi_sz(HKEY key, const char *name, strbuf *str); + +char *get_reg_sz_simple(HKEY key, const char *name, const char *leaf); + +/* In cliloop.c */ typedef bool (*cliloop_pre_t)(void *vctx, const HANDLE **extra_handles, size_t *n_extra_handles); typedef bool (*cliloop_post_t)(void *vctx, size_t extra_handle_index); @@ -701,4 +757,40 @@ void cli_main_loop(cliloop_pre_t pre, cliloop_post_t post, void *ctx); bool cliloop_null_pre(void *vctx, const HANDLE **, size_t *); bool cliloop_null_post(void *vctx, size_t); -#endif +extern const struct BackendVtable conpty_backend; + +/* Functions that parametrise window.c between PuTTY and pterm */ +void gui_term_process_cmdline(Conf *conf, char *cmdline); +const struct BackendVtable *backend_vt_from_conf(Conf *conf); +const wchar_t *get_app_user_model_id(void); +/* And functions in window.c that those files call back to */ +char *handle_restrict_acl_cmdline_prefix(char *cmdline); +bool handle_special_sessionname_cmdline(char *cmdline, Conf *conf); +bool handle_special_filemapping_cmdline(char *cmdline, Conf *conf); + +/* network.c: network error reporting helpers taking OS error code */ +void plug_closing_system_error(Plug *plug, DWORD error); +void plug_closing_winsock_error(Plug *plug, DWORD error); + +SeatPromptResult make_spr_sw_abort_winerror(const char *prefix, DWORD error); + +HANDLE lock_interprocess_mutex(const char *mutexname, char **error); +void unlock_interprocess_mutex(HANDLE mutex); + +typedef void (*aux_opt_error_fn_t)(const char *, ...); +typedef struct AuxMatchOpt { + int index, argc; + char **argv; + bool doing_opts; + aux_opt_error_fn_t error; +} AuxMatchOpt; +AuxMatchOpt aux_match_opt_init(int argc, char **argv, int start_index, + aux_opt_error_fn_t opt_error); +bool aux_match_arg(AuxMatchOpt *amo, char **val); +bool aux_match_opt(AuxMatchOpt *amo, char **val, const char *optname, ...); +bool aux_match_done(AuxMatchOpt *amo); + +char *save_screenshot(HWND hwnd, const char *outfile); +void gui_terminal_ready(HWND hwnd, Seat *seat, Backend *backend); + +#endif /* PUTTY_WINDOWS_PLATFORM_H */ diff --git a/windows/winplink.c b/windows/plink.c index 58d43e6d..05c25073 100644 --- a/windows/winplink.c +++ b/windows/plink.c @@ -8,9 +8,10 @@ #include <stdarg.h> #include "putty.h" +#include "ssh.h" #include "storage.h" #include "tree234.h" -#include "winsecur.h" +#include "security-api.h" void cmdline_error(const char *fmt, ...) { @@ -50,8 +51,9 @@ static void plink_echoedit_update(Seat *seat, bool echo, bool edit) } static size_t plink_output( - Seat *seat, bool is_stderr, const void *data, size_t len) + Seat *seat, SeatOutputType type, const void *data, size_t len) { + bool is_stderr = type != SEAT_OUTPUT_STDOUT; BinarySink *bs = is_stderr ? stderr_bs : stdout_bs; put_data(bs, data, len); @@ -64,13 +66,18 @@ static bool plink_eof(Seat *seat) return false; /* do not respond to incoming EOF with outgoing */ } -static int plink_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input) +static SeatPromptResult plink_get_userpass_input(Seat *seat, prompts_t *p) { - int ret; - ret = cmdline_get_passwd_input(p); - if (ret == -1) - ret = console_get_userpass_input(p); - return ret; + /* Plink doesn't support Restart Session, so we can just have a + * single static cmdline_get_passwd_input_state that's never reset */ + static cmdline_get_passwd_input_state cmdline_state = + CMDLINE_GET_PASSWD_INPUT_STATE_INIT; + + SeatPromptResult spr; + spr = cmdline_get_passwd_input(p, &cmdline_state, false); + if (spr.kind == SPRK_INCOMPLETE) + spr = console_get_userpass_input(p); + return spr; } static bool plink_seat_interactive(Seat *seat) @@ -83,15 +90,20 @@ static bool plink_seat_interactive(Seat *seat) static const SeatVtable plink_seat_vt = { .output = plink_output, .eof = plink_eof, + .sent = nullseat_sent, + .banner = nullseat_banner_to_stderr, .get_userpass_input = plink_get_userpass_input, + .notify_session_started = nullseat_notify_session_started, .notify_remote_exit = nullseat_notify_remote_exit, + .notify_remote_disconnect = nullseat_notify_remote_disconnect, .connection_fatal = console_connection_fatal, .update_specials_menu = nullseat_update_specials_menu, .get_ttymode = nullseat_get_ttymode, .set_busy_status = nullseat_set_busy_status, - .verify_ssh_host_key = console_verify_ssh_host_key, + .confirm_ssh_host_key = console_confirm_ssh_host_key, .confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey, + .prompt_descriptions = console_prompt_descriptions, .is_utf8 = nullseat_is_never_utf8, .echoedit_update = plink_echoedit_update, .get_x_display = nullseat_get_x_display, @@ -99,6 +111,8 @@ static const SeatVtable plink_seat_vt = { .get_window_pixel_size = nullseat_get_window_pixel_size, .stripctrl_new = console_stripctrl_new, .set_trust_status = console_set_trust_status, + .can_set_trust_status = console_can_set_trust_status, + .has_mixed_input_stream = console_has_mixed_input_stream, .verbose = cmdline_seat_verbose, .interactive = plink_seat_interactive, .get_cursor_position = nullseat_get_cursor_position, @@ -133,7 +147,7 @@ static void usage(void) printf(" -sercfg configuration-string (e.g. 19200,8,n,1,X)\n"); printf(" Specify the serial configuration (serial only)\n"); printf("The following options only apply to SSH connections:\n"); - printf(" -pw passw login with specified password\n"); + printf(" -pwfile file login with password read from specified file\n"); printf(" -D [listen-IP:]listen-port\n"); printf(" Dynamic SOCKS-based port forwarding\n"); printf(" -L [listen-IP:]listen-port:host:port\n"); @@ -201,7 +215,8 @@ size_t stdin_gotdata(struct handle *h, const void *data, size_t len, int err) noise_ultralight(NOISE_SOURCE_IOLEN, len); if (backend_connected(backend)) { if (len > 0) { - return backend_send(backend, data, len); + backend_send(backend, data, len); + return backend_sendbuffer(backend); } else { backend_special(backend, SS_EOF, 0); return 0; @@ -210,8 +225,14 @@ size_t stdin_gotdata(struct handle *h, const void *data, size_t len, int err) return 0; } -void stdouterr_sent(struct handle *h, size_t new_backlog, int err) +void stdouterr_sent(struct handle *h, size_t new_backlog, int err, bool close) { + if (close) { + CloseHandle(outhandle); + CloseHandle(errhandle); + outhandle = errhandle = INVALID_HANDLE_VALUE; + } + if (err) { char buf[4096]; FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, 0, @@ -352,7 +373,7 @@ int main(int argc, char **argv) while (argc > 0) { if (cmdbuf->len > 0) put_byte(cmdbuf, ' '); /* add space separator */ - put_datapl(cmdbuf, ptrlen_from_asciz(p)); + put_dataz(cmdbuf, p); if (--argc > 0) p = *++argv; } @@ -403,7 +424,7 @@ int main(int argc, char **argv) if (vt->flags & BACKEND_NEEDS_TERMINAL) { fprintf(stderr, "Plink doesn't support %s, which needs terminal emulation\n", - vt->displayname); + vt->displayname_lc); return 1; } @@ -429,7 +450,7 @@ int main(int argc, char **argv) if (just_test_share_exists) { if (!vt->test_for_upstream) { fprintf(stderr, "Connection sharing not supported for this " - "connection type (%s)'\n", vt->displayname); + "connection type (%s)'\n", vt->displayname_lc); return 1; } if (vt->test_for_upstream(conf_get_str(conf, CONF_host), diff --git a/windows/winprint.c b/windows/printing.c index e6b3531d..2286c236 100644 --- a/windows/winprint.c +++ b/windows/printing.c @@ -67,11 +67,15 @@ static bool printer_add_enum(int param, DWORD level, char **buffer, /* * Exploratory call to EnumPrinters to determine how much space - * we'll need for the output. Discard the return value since it - * will almost certainly be a failure due to lack of space. + * we'll need for the output. + * + * If we get ERROR_INSUFFICIENT_BUFFER, that's fine, we're + * prepared to deal with it. Any other error, we return failure. */ - p_EnumPrinters(param, NULL, level, (LPBYTE)((*buffer)+offset), 512, - &needed, &nprinters); + if (p_EnumPrinters(param, NULL, level, (LPBYTE)((*buffer)+offset), 512, + &needed, &nprinters) == 0 && + GetLastError() != ERROR_INSUFFICIENT_BUFFER) + return false; if (needed < 512) needed = 512; @@ -127,7 +131,7 @@ printer_enum *printer_start_enum(int *nprinters_ptr) return ret; - error: + error: sfree(buffer); sfree(ret); *nprinters_ptr = 0; @@ -191,7 +195,7 @@ printer_job *printer_start_job(char *printer) return ret; - error: + error: if (pagestarted) p_EndPagePrinter(ret->hprinter); if (jobstarted) diff --git a/windows/winsocks.c b/windows/psocks.c index 83ba364c..83ba364c 100644 --- a/windows/winsocks.c +++ b/windows/psocks.c diff --git a/windows/pterm.c b/windows/pterm.c new file mode 100644 index 00000000..bb68245d --- /dev/null +++ b/windows/pterm.c @@ -0,0 +1,65 @@ +#include "putty.h" +#include "storage.h" + +const unsigned cmdline_tooltype = + TOOLTYPE_NONNETWORK | + TOOLTYPE_NO_VERBOSE_OPTION; + +void gui_term_process_cmdline(Conf *conf, char *cmdline) +{ + do_defaults(NULL, conf); + conf_set_str(conf, CONF_remote_cmd, ""); + + cmdline = handle_restrict_acl_cmdline_prefix(cmdline); + if (handle_special_sessionname_cmdline(cmdline, conf) || + handle_special_filemapping_cmdline(cmdline, conf)) + return; + + int argc; + char **argv, **argstart; + split_into_argv(cmdline, &argc, &argv, &argstart); + + for (int i = 0; i < argc; i++) { + char *arg = argv[i]; + int retd = cmdline_process_param( + arg, i+1<argc?argv[i+1]:NULL, 1, conf); + if (retd == -2) { + cmdline_error("option \"%s\" requires an argument", arg); + } else if (retd == 2) { + i++; /* skip next argument */ + } else if (retd == 1) { + continue; /* nothing further needs doing */ + } else if (!strcmp(arg, "-e")) { + if (i+1 < argc) { + /* The command to execute is taken to be the unparsed + * version of the whole remainder of the command line. */ + conf_set_str(conf, CONF_remote_cmd, argstart[i+1]); + return; + } else { + cmdline_error("option \"%s\" requires an argument", arg); + } + } else if (arg[0] == '-') { + cmdline_error("unrecognised option \"%s\"", arg); + } else { + cmdline_error("unexpected non-option argument \"%s\"", arg); + } + } + + cmdline_run_saved(conf); + + conf_set_int(conf, CONF_sharrow_type, SHARROW_BITMAP); +} + +const struct BackendVtable *backend_vt_from_conf(Conf *conf) +{ + return &conpty_backend; +} + +const wchar_t *get_app_user_model_id(void) +{ + return L"SimonTatham.Pterm"; +} + +void gui_terminal_ready(HWND hwnd, Seat *seat, Backend *backend) +{ +} diff --git a/windows/pterm.ico b/windows/pterm.ico Binary files differnew file mode 100644 index 00000000..6909a8d2 --- /dev/null +++ b/windows/pterm.ico diff --git a/windows/pterm.rc b/windows/pterm.rc new file mode 100644 index 00000000..8bd3a043 --- /dev/null +++ b/windows/pterm.rc @@ -0,0 +1,15 @@ +#include "rcstuff.h" +#include "putty-rc.h" + +#define APPNAME "pterm" +#define APPDESC "PuTTY-style wrapper for Windows command prompts" + +IDI_MAINICON ICON "pterm.ico" +IDI_CFGICON ICON "ptermcfg.ico" + +#include "help.rc2" +#include "putty-common.rc2" + +#ifndef NO_MANIFESTS +1 RT_MANIFEST "putty.mft" +#endif /* NO_MANIFESTS */ diff --git a/windows/ptermcfg.ico b/windows/ptermcfg.ico Binary files differnew file mode 100644 index 00000000..53bde87e --- /dev/null +++ b/windows/ptermcfg.ico diff --git a/windows/putty-common.rc2 b/windows/putty-common.rc2 new file mode 100644 index 00000000..a43e3ac1 --- /dev/null +++ b/windows/putty-common.rc2 @@ -0,0 +1,98 @@ +/* + * Windows resources shared between PuTTY and PuTTYtel, to be #include'd + * after defining appropriate macros. + * + * Note that many of these strings mention PuTTY. Due to restrictions in + * VC's handling of string concatenation, this can't easily be fixed. + * It's fixed up at runtime. + * + * This file has the more or less arbitrary extension '.rc2' to avoid + * IDEs taking it to be a top-level resource script in its own right + * (which has been known to happen if the extension was '.rc'), and + * also to avoid the resource compiler ignoring everything included + * from it (which happens if the extension is '.h'). + */ + +/* Accelerators used: clw */ +IDD_ABOUTBOX DIALOG DISCARDABLE 140, 40, 270, 136 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "About PuTTY" +FONT 8, "MS Shell Dlg" +BEGIN + DEFPUSHBUTTON "&Close", IDOK, 216, 118, 48, 14 + PUSHBUTTON "View &Licence", IDA_LICENCE, 6, 118, 70, 14 + PUSHBUTTON "Visit &Web Site", IDA_WEB, 140, 118, 70, 14 + EDITTEXT IDA_TEXT, 10, 6, 250, 110, ES_READONLY | ES_MULTILINE | ES_CENTER, WS_EX_STATICEDGE +END + +/* Accelerators used: aco */ +IDD_MAINBOX DIALOG DISCARDABLE 0, 0, 300, 252 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "PuTTY Configuration" +FONT 8, "MS Shell Dlg" +CLASS "PuTTYConfigBox" +BEGIN +END + +/* Accelerators used: co */ +IDD_LOGBOX DIALOG DISCARDABLE 100, 20, 300, 119 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "PuTTY Event Log" +FONT 8, "MS Shell Dlg" +BEGIN + DEFPUSHBUTTON "&Close", IDOK, 135, 102, 44, 14 + PUSHBUTTON "C&opy", IDN_COPY, 81, 102, 44, 14 + LISTBOX IDN_LIST, 3, 3, 294, 95, LBS_HASSTRINGS | LBS_USETABSTOPS | WS_VSCROLL | LBS_EXTENDEDSEL +END + +/* No accelerators used */ +IDD_LICENCEBOX DIALOG DISCARDABLE 50, 50, 326, 239 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "PuTTY Licence" +FONT 8, "MS Shell Dlg" +BEGIN + DEFPUSHBUTTON "OK", IDOK, 148, 219, 44, 14 + + EDITTEXT IDA_TEXT, 10, 10, 306, 200, ES_READONLY | ES_MULTILINE | ES_LEFT, WS_EX_STATICEDGE +END + +/* Accelerators used: achio */ +IDD_HOSTKEY DIALOG DISCARDABLE 50, 50, 340, 240 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "PuTTY Security Alert" +FONT 8, "MS Shell Dlg" +CLASS "PuTTYHostKeyDialog" +BEGIN + ICON "", IDC_HK_ICON, 10, 18, 0, 0 + + PUSHBUTTON "&Cancel", IDCANCEL, 288, 220, 40, 14 + PUSHBUTTON "&Accept", IDC_HK_ACCEPT, 168, 220, 40, 14 + PUSHBUTTON "Connect &Once", IDC_HK_ONCE, 216, 220, 64, 14 + PUSHBUTTON "More &info...", IDC_HK_MOREINFO, 60, 220, 64, 14 + PUSHBUTTON "&Help", IDHELP, 12, 220, 40, 14 + + LTEXT "", IDC_HK_TITLE, 40, 20, 300, 12 + + EDITTEXT IDC_HK_TEXT, 40, 20, 290, 200, ES_READONLY | ES_MULTILINE | ES_LEFT, WS_EX_STATICEDGE +END + +/* Accelerators used: c */ +IDD_HK_MOREINFO DIALOG DISCARDABLE 140, 40, 400, 300 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "PuTTY: information about the server's host key" +FONT 8, "MS Shell Dlg" +CLASS "PuTTYHostKeyMoreInfo" +BEGIN + DEFPUSHBUTTON "&Close", IDOK, 176, 130, 48, 14 +END + +/* Accelerators used: aco */ +IDD_CA_CONFIG DIALOG DISCARDABLE 0, 0, 350, 260 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "PuTTY trusted host certification authorities" +FONT 8, "MS Shell Dlg" +CLASS "PuTTYConfigBox" +BEGIN +END + +#include "version.rc2" diff --git a/windows/win_res.h b/windows/putty-rc.h index d34f6852..35d9bcda 100644 --- a/windows/win_res.h +++ b/windows/putty-rc.h @@ -1,5 +1,5 @@ /* - * win_res.h - constants shared between win_res.rc2 and the C code. + * putty-rc.h - constants shared between putty-common.rc2 and the C code. */ #ifndef PUTTY_WIN_RES_H @@ -13,9 +13,9 @@ #define IDD_ABOUTBOX 111 #define IDD_RECONF 112 #define IDD_LICENCEBOX 113 -#define IDD_HK_ABSENT 114 -#define IDD_HK_WRONG 115 +#define IDD_HOSTKEY 114 #define IDD_HK_MOREINFO 116 +#define IDD_CA_CONFIG 117 #define IDN_LIST 1001 #define IDN_COPY 1002 @@ -34,10 +34,12 @@ #define IDC_HK_ICON 98 #define IDC_HK_TITLE 99 +#define IDC_HK_TEXT 100 #define IDC_HK_ACCEPT 1001 #define IDC_HK_ONCE 1000 -#define IDC_HK_FINGERPRINT 1002 -#define IDC_HK_MOREINFO 1003 +#define IDC_HK_HOST 1002 +#define IDC_HK_FINGERPRINT 1003 +#define IDC_HK_MOREINFO 1004 #define IDC_HKI_SHA256 1000 #define IDC_HKI_MD5 1001 diff --git a/windows/putty.c b/windows/putty.c new file mode 100644 index 00000000..3c41b557 --- /dev/null +++ b/windows/putty.c @@ -0,0 +1,203 @@ +#include "putty.h" +#include "storage.h" + +extern bool sesslist_demo_mode; +extern const char *dialog_box_demo_screenshot_filename; +static strbuf *demo_terminal_data = NULL; +static const char *terminal_demo_screenshot_filename; + +const unsigned cmdline_tooltype = + TOOLTYPE_HOST_ARG | + TOOLTYPE_PORT_ARG | + TOOLTYPE_NO_VERBOSE_OPTION; + +void gui_term_process_cmdline(Conf *conf, char *cmdline) +{ + char *p; + bool special_launchable_argument = false; + bool demo_config_box = false; + + settings_set_default_protocol(be_default_protocol); + /* Find the appropriate default port. */ + { + const struct BackendVtable *vt = + backend_vt_from_proto(be_default_protocol); + settings_set_default_port(0); /* illegal */ + if (vt) + settings_set_default_port(vt->default_port); + } + conf_set_int(conf, CONF_logtype, LGTYP_NONE); + + do_defaults(NULL, conf); + + p = handle_restrict_acl_cmdline_prefix(cmdline); + + if (handle_special_sessionname_cmdline(p, conf)) { + if (!conf_launchable(conf) && !do_config(conf)) { + cleanup_exit(0); + } + special_launchable_argument = true; + } else if (handle_special_filemapping_cmdline(p, conf)) { + special_launchable_argument = true; + } else if (!*p) { + /* Do-nothing case for an empty command line - or rather, + * for a command line that's empty _after_ we strip off + * the &R prefix. */ + } else { + /* + * Otherwise, break up the command line and deal with + * it sensibly. + */ + int argc, i; + char **argv; + + split_into_argv(cmdline, &argc, &argv, NULL); + + for (i = 0; i < argc; i++) { + char *p = argv[i]; + int ret; + + ret = cmdline_process_param(p, i+1<argc?argv[i+1]:NULL, + 1, conf); + if (ret == -2) { + cmdline_error("option \"%s\" requires an argument", p); + } else if (ret == 2) { + i++; /* skip next argument */ + } else if (ret == 1) { + continue; /* nothing further needs doing */ + } else if (!strcmp(p, "-cleanup")) { + /* + * `putty -cleanup'. Remove all registry + * entries associated with PuTTY, and also find + * and delete the random seed file. + */ + char *s1, *s2; + s1 = dupprintf("This procedure will remove ALL Registry entries\n" + "associated with %s, and will also remove\n" + "the random seed file. (This only affects the\n" + "currently logged-in user.)\n" + "\n" + "THIS PROCESS WILL DESTROY YOUR SAVED SESSIONS.\n" + "Are you really sure you want to continue?", + appname); + s2 = dupprintf("%s Warning", appname); + if (message_box(NULL, s1, s2, + MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2, + HELPCTXID(option_cleanup)) == IDYES) { + cleanup_all(); + } + sfree(s1); + sfree(s2); + exit(0); + } else if (!strcmp(p, "-pgpfp")) { + pgp_fingerprints_msgbox(NULL); + exit(1); + } else if (has_ca_config_box && + (!strcmp(p, "-host-ca") || !strcmp(p, "--host-ca") || + !strcmp(p, "-host_ca") || !strcmp(p, "--host_ca"))) { + show_ca_config_box(NULL); + exit(0); + } else if (!strcmp(p, "-demo-config-box")) { + if (i+1 >= argc) { + cmdline_error("%s expects an output filename", p); + } else { + demo_config_box = true; + dialog_box_demo_screenshot_filename = argv[++i]; + } + } else if (!strcmp(p, "-demo-terminal")) { + if (i+2 >= argc) { + cmdline_error("%s expects input and output filenames", p); + } else { + const char *infile = argv[++i]; + terminal_demo_screenshot_filename = argv[++i]; + FILE *fp = fopen(infile, "rb"); + if (!fp) + cmdline_error("can't open input file '%s'", infile); + demo_terminal_data = strbuf_new(); + char buf[4096]; + int retd; + while ((retd = fread(buf, 1, sizeof(buf), fp)) > 0) + put_data(demo_terminal_data, buf, retd); + fclose(fp); + } + } else if (*p != '-') { + cmdline_error("unexpected argument \"%s\"", p); + } else { + cmdline_error("unknown option \"%s\"", p); + } + } + } + + cmdline_run_saved(conf); + + if (demo_config_box) { + sesslist_demo_mode = true; + load_open_settings(NULL, conf); + conf_set_str(conf, CONF_host, "demo-server.example.com"); + do_config(conf); + cleanup_exit(0); + } else if (demo_terminal_data) { + /* Ensure conf will cause an immediate session launch */ + load_open_settings(NULL, conf); + conf_set_str(conf, CONF_host, "demo-server.example.com"); + conf_set_int(conf, CONF_close_on_exit, FORCE_OFF); + } else { + /* + * Bring up the config dialog if the command line hasn't + * (explicitly) specified a launchable configuration. + */ + if (!(special_launchable_argument || cmdline_host_ok(conf))) { + if (!do_config(conf)) + cleanup_exit(0); + } + } + + prepare_session(conf); +} + +const struct BackendVtable *backend_vt_from_conf(Conf *conf) +{ + if (demo_terminal_data) { + return &null_backend; + } + + /* + * Select protocol. This is farmed out into a table in a + * separate file to enable an ssh-free variant. + */ + const struct BackendVtable *vt = backend_vt_from_proto( + conf_get_int(conf, CONF_protocol)); + if (!vt) { + char *str = dupprintf("%s Internal Error", appname); + MessageBox(NULL, "Unsupported protocol number found", + str, MB_OK | MB_ICONEXCLAMATION); + sfree(str); + cleanup_exit(1); + } + return vt; +} + +const wchar_t *get_app_user_model_id(void) +{ + return L"SimonTatham.PuTTY"; +} + +static void demo_terminal_screenshot(void *ctx, unsigned long now) +{ + HWND hwnd = (HWND)ctx; + char *err = save_screenshot(hwnd, terminal_demo_screenshot_filename); + if (err) { + MessageBox(hwnd, err, "Demo screenshot failure", MB_OK | MB_ICONERROR); + sfree(err); + } + cleanup_exit(0); +} + +void gui_terminal_ready(HWND hwnd, Seat *seat, Backend *backend) +{ + if (demo_terminal_data) { + ptrlen data = ptrlen_from_strbuf(demo_terminal_data); + seat_stdout(seat, data.ptr, data.len); + schedule_timer(TICKSPERSEC, demo_terminal_screenshot, (void *)hwnd); + } +} diff --git a/windows/putty.rc b/windows/putty.rc index 14f62f48..b8df49f2 100644 --- a/windows/putty.rc +++ b/windows/putty.rc @@ -1,10 +1,14 @@ #include "rcstuff.h" +#include "putty-rc.h" #define APPNAME "PuTTYNG" #define APPDESC "SSH, Telnet, Rlogin, and SUPDUP client" -#include "winhelp.rc2" -#include "win_res.rc2" +IDI_MAINICON ICON "putty.ico" +IDI_CFGICON ICON "puttycfg.ico" + +#include "help.rc2" +#include "putty-common.rc2" #ifndef NO_MANIFESTS 1 RT_MANIFEST "putty.mft" diff --git a/windows/winpgen.c b/windows/puttygen.c index 56c6a8db..457dbfa1 100644 --- a/windows/winpgen.c +++ b/windows/puttygen.c @@ -11,7 +11,7 @@ #include "ssh.h" #include "sshkeygen.h" #include "licence.h" -#include "winsecur.h" +#include "security-api.h" #include "puttygen-rc.h" #include <commctrl.h> @@ -27,6 +27,8 @@ #define DEFAULT_EDCURVE_INDEX 0 static char *cmdline_keyfile = NULL; +static ptrlen cmdline_demo_keystr; +static const char *demo_screenshot_filename = NULL; /* * Print a modal (Really Bad) message box and perform a fatal exit. @@ -201,7 +203,7 @@ struct PassphraseProcStruct { * Dialog-box function for the passphrase box. */ static INT_PTR CALLBACK PassphraseProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) + WPARAM wParam, LPARAM lParam) { static char **passphrase = NULL; struct PassphraseProcStruct *p; @@ -436,9 +438,9 @@ static INT_PTR CALLBACK PPKParamsProc(HWND hwnd, UINT msg, topic = WINHELP_CTX_puttygen_kdfparam; break; } if (topic) { - launch_help(hwnd, topic); + launch_help(hwnd, topic); } else { - MessageBeep(0); + MessageBeep(0); } break; } @@ -481,7 +483,7 @@ static bool prompt_keyfile(HWND hwnd, char *dlgtitle, * Dialog-box function for the Licence box. */ static INT_PTR CALLBACK LicenceProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) + WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_INITDIALOG: { @@ -520,7 +522,7 @@ static INT_PTR CALLBACK LicenceProc(HWND hwnd, UINT msg, * Dialog-box function for the About box. */ static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) + WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_INITDIALOG: @@ -541,10 +543,10 @@ static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg, { char *buildinfo_text = buildinfo("\r\n"); - char *text = dupprintf - ("PuTTYgen\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s", - ver, buildinfo_text, - "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved."); + char *text = dupprintf( + "PuTTYgen\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s", + ver, buildinfo_text, + "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved."); sfree(buildinfo_text); SetDlgItemText(hwnd, 1000, text); MakeDlgItemBorderless(hwnd, 1000); @@ -593,7 +595,7 @@ struct rsa_key_thread_params { bool rsa_strong; union { RSAKey *key; - struct dss_key *dsskey; + struct dsa_key *dsakey; struct ecdsa_key *eckey; struct eddsa_key *edkey; }; @@ -610,7 +612,7 @@ static DWORD WINAPI generate_key_thread(void *param) PrimeGenerationContext *pgc = primegen_new_context(params->primepolicy); if (params->keytype == DSA) - dsa_generate(params->dsskey, params->key_bits, pgc, &prog.rec); + dsa_generate(params->dsakey, params->key_bits, pgc, &prog.rec); else if (params->keytype == ECDSA) ecdsa_generate(params->eckey, params->curve_bits); else if (params->keytype == EDDSA) @@ -629,11 +631,21 @@ static DWORD WINAPI generate_key_thread(void *param) return 0; } +struct InitialParams { + int keybutton; + int primepolicybutton; + bool rsa_strong; + FingerprintType fptype; + int keybits; + int eccurve_index, edcurve_index; +}; + struct MainDlgState { - bool collecting_entropy; bool generation_thread_exists; bool key_exists; - int entropy_got, entropy_required, entropy_size; + int entropy_got, entropy_required; + strbuf *entropy; + ULONG entropy_prev_msgtime; int key_bits, curve_bits; bool ssh2; keytype keytype; @@ -642,16 +654,32 @@ struct MainDlgState { FingerprintType fptype; char **commentptr; /* points to key.comment or ssh2key.comment */ ssh2_userkey ssh2key; - unsigned *entropy; union { RSAKey key; - struct dss_key dsskey; + struct dsa_key dsakey; struct ecdsa_key eckey; struct eddsa_key edkey; }; HMENU filemenu, keymenu, cvtmenu; }; +/* + * Rate limit for incrementing the entropy_got counter. + * + * Some pointing devices (e.g. gaming mice) can be set to send + * mouse-movement events at an extremely high sample rate like 1kHz. + * In that situation, there's likely to be a strong correlation + * between the contents of successive movement events, so you have to + * regard the mouse movements as containing less entropy each. + * + * A reasonably simple approach to this is to continue to buffer all + * mouse data, but limit the rate at which we increment the counter + * for how much entropy we think we've collected. That way, the user + * still has to spend time wiggling the mouse back and forth in a way + * that varies with muscle motions and introduces randomness. + */ +#define ENTROPY_RATE_LIMIT 10 /* in units of GetMessageTime(), i.e. ms */ + static void hidemany(HWND hwnd, const int *ids, bool hideit) { while (*ids) { @@ -659,45 +687,6 @@ static void hidemany(HWND hwnd, const int *ids, bool hideit) } } -static void setupbigedit1(HWND hwnd, int id, int idstatic, RSAKey *key) -{ - char *buffer = ssh1_pubkey_str(key); - SetDlgItemText(hwnd, id, buffer); - SetDlgItemText(hwnd, idstatic, - "&Public key for pasting into authorized_keys file:"); - sfree(buffer); -} - -static void setupbigedit2(HWND hwnd, int id, int idstatic, - ssh2_userkey *key) -{ - char *buffer = ssh2_pubkey_openssh_str(key); - SetDlgItemText(hwnd, id, buffer); - SetDlgItemText(hwnd, idstatic, "&Public key for pasting into " - "OpenSSH authorized_keys file:"); - sfree(buffer); -} - -/* - * Warn about the obsolescent key file format. - */ -void old_keyfile_warning(void) -{ - static const char mbtitle[] = "PuTTY Key File Warning"; - static const char message[] = - "You are loading an SSH-2 private key which has an\n" - "old version of the file format. This means your key\n" - "file is not fully tamperproof. Future versions of\n" - "PuTTY may stop supporting this private key format,\n" - "so we recommend you convert your key to the new\n" - "format.\n" - "\n" - "Once the key is loaded into PuTTYgen, you can perform\n" - "this conversion simply by saving it again."; - - MessageBox(NULL, message, mbtitle, MB_OK); -} - enum { controlidstart = 100, IDC_QUIT, @@ -707,6 +696,7 @@ enum { IDC_GENERATING, IDC_PROGRESS, IDC_PKSTATIC, IDC_KEYDISPLAY, + IDC_CERTSTATIC, IDC_CERTMOREINFO, IDC_FPSTATIC, IDC_FINGERPRINT, IDC_COMMENTSTATIC, IDC_COMMENTEDIT, IDC_PASSPHRASE1STATIC, IDC_PASSPHRASE1EDIT, @@ -730,18 +720,82 @@ enum { IDC_GIVEHELP, IDC_IMPORT, IDC_EXPORT_OPENSSH_AUTO, IDC_EXPORT_OPENSSH_NEW, - IDC_EXPORT_SSHCOM + IDC_EXPORT_SSHCOM, + IDC_ADDCERT, IDC_REMCERT, }; +static void setupbigedit1(HWND hwnd, RSAKey *key) +{ + ShowWindow(GetDlgItem(hwnd, IDC_CERTSTATIC), SW_HIDE); + ShowWindow(GetDlgItem(hwnd, IDC_CERTMOREINFO), SW_HIDE); + ShowWindow(GetDlgItem(hwnd, IDC_PKSTATIC), SW_SHOW); + ShowWindow(GetDlgItem(hwnd, IDC_KEYDISPLAY), SW_SHOW); + + SetDlgItemText(hwnd, IDC_PKSTATIC, + "&Public key for pasting into authorized_keys file:"); + + char *buffer = ssh1_pubkey_str(key); + SetDlgItemText(hwnd, IDC_KEYDISPLAY, buffer); + sfree(buffer); +} + +static void setupbigedit2(HWND hwnd, ssh2_userkey *key) +{ + if (ssh_key_alg(key->key)->is_certificate) { + ShowWindow(GetDlgItem(hwnd, IDC_CERTSTATIC), SW_SHOW); + ShowWindow(GetDlgItem(hwnd, IDC_CERTMOREINFO), SW_SHOW); + ShowWindow(GetDlgItem(hwnd, IDC_PKSTATIC), SW_HIDE); + ShowWindow(GetDlgItem(hwnd, IDC_KEYDISPLAY), SW_HIDE); + + SetDlgItemText(hwnd, IDC_CERTSTATIC, + "This public key contains an OpenSSH certificate."); + } else { + ShowWindow(GetDlgItem(hwnd, IDC_CERTSTATIC), SW_HIDE); + ShowWindow(GetDlgItem(hwnd, IDC_CERTMOREINFO), SW_HIDE); + ShowWindow(GetDlgItem(hwnd, IDC_PKSTATIC), SW_SHOW); + ShowWindow(GetDlgItem(hwnd, IDC_KEYDISPLAY), SW_SHOW); + + SetDlgItemText(hwnd, IDC_PKSTATIC, "&Public key for pasting into " + "OpenSSH authorized_keys file:"); + + char *buffer = ssh2_pubkey_openssh_str(key); + SetDlgItemText(hwnd, IDC_KEYDISPLAY, buffer); + sfree(buffer); + } +} + +/* + * Warn about the obsolescent key file format. + */ +void old_keyfile_warning(void) +{ + static const char mbtitle[] = "PuTTY Key File Warning"; + static const char message[] = + "You are loading an SSH-2 private key which has an\n" + "old version of the file format. This means your key\n" + "file is not fully tamperproof. Future versions of\n" + "PuTTY may stop supporting this private key format,\n" + "so we recommend you convert your key to the new\n" + "format.\n" + "\n" + "Once the key is loaded into PuTTYgen, you can perform\n" + "this conversion simply by saving it again."; + + MessageBox(NULL, message, mbtitle, MB_OK); +} + static const int nokey_ids[] = { IDC_NOKEY, 0 }; static const int generating_ids[] = { IDC_GENERATING, IDC_PROGRESS, 0 }; -static const int gotkey_ids[] = { - IDC_PKSTATIC, IDC_KEYDISPLAY, +static const int gotkey_ids_unconditional[] = { IDC_FPSTATIC, IDC_FINGERPRINT, IDC_COMMENTSTATIC, IDC_COMMENTEDIT, IDC_PASSPHRASE1STATIC, IDC_PASSPHRASE1EDIT, IDC_PASSPHRASE2STATIC, IDC_PASSPHRASE2EDIT, 0 }; +static const int gotkey_ids_conditional[] = { + IDC_PKSTATIC, IDC_KEYDISPLAY, + IDC_CERTSTATIC, IDC_CERTMOREINFO, +}; /* * Small UI helper function to switch the state of the main dialog @@ -755,7 +809,8 @@ void ui_set_state(HWND hwnd, struct MainDlgState *state, int status) case 0: /* no key */ hidemany(hwnd, nokey_ids, false); hidemany(hwnd, generating_ids, true); - hidemany(hwnd, gotkey_ids, true); + hidemany(hwnd, gotkey_ids_unconditional, true); + hidemany(hwnd, gotkey_ids_conditional, true); EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 1); EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 1); EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 0); @@ -784,11 +839,14 @@ void ui_set_state(HWND hwnd, struct MainDlgState *state, int status) MF_GRAYED|MF_BYCOMMAND); EnableMenuItem(state->cvtmenu, IDC_EXPORT_SSHCOM, MF_GRAYED|MF_BYCOMMAND); + EnableMenuItem(state->keymenu, IDC_ADDCERT, MF_GRAYED|MF_BYCOMMAND); + EnableMenuItem(state->keymenu, IDC_REMCERT, MF_GRAYED|MF_BYCOMMAND); break; case 1: /* generating key */ hidemany(hwnd, nokey_ids, true); hidemany(hwnd, generating_ids, false); - hidemany(hwnd, gotkey_ids, true); + hidemany(hwnd, gotkey_ids_unconditional, true); + hidemany(hwnd, gotkey_ids_conditional, true); EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 0); EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 0); EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 0); @@ -817,11 +875,14 @@ void ui_set_state(HWND hwnd, struct MainDlgState *state, int status) MF_GRAYED|MF_BYCOMMAND); EnableMenuItem(state->cvtmenu, IDC_EXPORT_SSHCOM, MF_GRAYED|MF_BYCOMMAND); + EnableMenuItem(state->keymenu, IDC_ADDCERT, MF_GRAYED|MF_BYCOMMAND); + EnableMenuItem(state->keymenu, IDC_REMCERT, MF_GRAYED|MF_BYCOMMAND); break; case 2: hidemany(hwnd, nokey_ids, true); hidemany(hwnd, generating_ids, true); - hidemany(hwnd, gotkey_ids, false); + hidemany(hwnd, gotkey_ids_unconditional, false); + // gotkey_ids_conditional will be unhidden by setupbigedit2 EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 1); EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 1); EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 1); @@ -856,6 +917,35 @@ void ui_set_state(HWND hwnd, struct MainDlgState *state, int status) do_export_menuitem(IDC_EXPORT_OPENSSH_NEW, SSH_KEYTYPE_OPENSSH_NEW); do_export_menuitem(IDC_EXPORT_SSHCOM, SSH_KEYTYPE_SSHCOM); #undef do_export_menuitem + /* + * Enable certificate menu items similarly. + */ + { + bool add_cert_allowed = false, rem_cert_allowed = false; + + if (state->ssh2 && state->ssh2key.key) { + const ssh_keyalg *alg = ssh_key_alg(state->ssh2key.key); + if (alg->is_certificate) { + /* If there's a certificate, we can remove it */ + rem_cert_allowed = true; + /* And reset to the base algorithm for the next check */ + alg = alg->base_alg; + } + + /* Now, do we have any certified version of this alg? */ + for (size_t i = 0; i < n_keyalgs; i++) { + if (all_keyalgs[i]->base_alg == alg) { + add_cert_allowed = true; + break; + } + } + } + + EnableMenuItem(state->keymenu, IDC_ADDCERT, MF_BYCOMMAND | + (add_cert_allowed ? MF_ENABLED : MF_GRAYED)); + EnableMenuItem(state->keymenu, IDC_REMCERT, MF_BYCOMMAND | + (rem_cert_allowed ? MF_ENABLED : MF_GRAYED)); + } break; } } @@ -966,6 +1056,70 @@ void ui_set_fptype(HWND hwnd, struct MainDlgState *state, int option) } } +static void update_ui_after_ssh2_pubkey_change( + HWND hwnd, struct MainDlgState *state) +{ + /* Smaller version of update_ui_after_load which doesn't need to + * be told things like the passphrase, which we aren't changing + * anyway */ + char *savecomment = state->ssh2key.comment; + state->ssh2key.comment = NULL; + char *fp = ssh2_fingerprint(state->ssh2key.key, state->fptype); + state->ssh2key.comment = savecomment; + + SetDlgItemText(hwnd, IDC_FINGERPRINT, fp); + sfree(fp); + + setupbigedit2(hwnd, &state->ssh2key); +} + +static void update_ui_after_load(HWND hwnd, struct MainDlgState *state, + const char *passphrase, int type, + RSAKey *newkey1, ssh2_userkey *newkey2) +{ + SetDlgItemText(hwnd, IDC_PASSPHRASE1EDIT, passphrase); + SetDlgItemText(hwnd, IDC_PASSPHRASE2EDIT, passphrase); + + if (type == SSH_KEYTYPE_SSH1) { + char *fingerprint, *savecomment; + + state->ssh2 = false; + state->commentptr = &state->key.comment; + state->key = *newkey1; /* structure copy */ + + /* + * Set the key fingerprint. + */ + savecomment = state->key.comment; + state->key.comment = NULL; + fingerprint = rsa_ssh1_fingerprint(&state->key); + state->key.comment = savecomment; + SetDlgItemText(hwnd, IDC_FINGERPRINT, fingerprint); + sfree(fingerprint); + + /* + * Construct a decimal representation of the key, for pasting + * into .ssh/authorized_keys on a Unix box. + */ + setupbigedit1(hwnd, &state->key); + } else { + state->ssh2 = true; + state->commentptr = &state->ssh2key.comment; + state->ssh2key = *newkey2; /* structure copy */ + sfree(newkey2); + + update_ui_after_ssh2_pubkey_change(hwnd, state); + } + SetDlgItemText(hwnd, IDC_COMMENTEDIT, + *state->commentptr); + + /* + * Finally, hide the progress bar and show the key data. + */ + ui_set_state(hwnd, state, 2); + state->key_exists = true; +} + void load_key_file(HWND hwnd, struct MainDlgState *state, Filename *filename, bool was_import_cmd) { @@ -1055,65 +1209,7 @@ void load_key_file(HWND hwnd, struct MainDlgState *state, * Now update the key controls with all the * key data. */ - { - SetDlgItemText(hwnd, IDC_PASSPHRASE1EDIT, - passphrase); - SetDlgItemText(hwnd, IDC_PASSPHRASE2EDIT, - passphrase); - if (type == SSH_KEYTYPE_SSH1) { - char *fingerprint, *savecomment; - - state->ssh2 = false; - state->commentptr = &state->key.comment; - state->key = newkey1; - - /* - * Set the key fingerprint. - */ - savecomment = state->key.comment; - state->key.comment = NULL; - fingerprint = rsa_ssh1_fingerprint(&state->key); - state->key.comment = savecomment; - SetDlgItemText(hwnd, IDC_FINGERPRINT, fingerprint); - sfree(fingerprint); - - /* - * Construct a decimal representation - * of the key, for pasting into - * .ssh/authorized_keys on a Unix box. - */ - setupbigedit1(hwnd, IDC_KEYDISPLAY, - IDC_PKSTATIC, &state->key); - } else { - char *fp; - char *savecomment; - - state->ssh2 = true; - state->commentptr = - &state->ssh2key.comment; - state->ssh2key = *newkey2; /* structure copy */ - sfree(newkey2); - - savecomment = state->ssh2key.comment; - state->ssh2key.comment = NULL; - fp = ssh2_fingerprint(state->ssh2key.key, state->fptype); - state->ssh2key.comment = savecomment; - - SetDlgItemText(hwnd, IDC_FINGERPRINT, fp); - sfree(fp); - - setupbigedit2(hwnd, IDC_KEYDISPLAY, - IDC_PKSTATIC, &state->ssh2key); - } - SetDlgItemText(hwnd, IDC_COMMENTEDIT, - *state->commentptr); - } - /* - * Finally, hide the progress bar and show - * the key data. - */ - ui_set_state(hwnd, state, 2); - state->key_exists = true; + update_ui_after_load(hwnd, state, passphrase, type, &newkey1, newkey2); /* * If the user has imported a foreign key @@ -1136,6 +1232,106 @@ void load_key_file(HWND hwnd, struct MainDlgState *state, burnstr(passphrase); } +void add_certificate(HWND hwnd, struct MainDlgState *state, + Filename *filename) +{ + int type = key_type(filename); + if (type != SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 && + type != SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) { + char *msg = dupprintf("Couldn't load certificate (%s)", + key_type_to_str(type)); + message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR, + HELPCTXID(errors_cantloadkey)); + sfree(msg); + return; + } + + char *algname = NULL; + char *comment = NULL; + const char *error = NULL; + strbuf *pub = strbuf_new(); + if (!ppk_loadpub_f(filename, &algname, BinarySink_UPCAST(pub), &comment, + &error)) { + char *msg = dupprintf("Couldn't load certificate (%s)", error); + message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR, + HELPCTXID(errors_cantloadkey)); + sfree(msg); + strbuf_free(pub); + return; + } + + sfree(comment); + + const ssh_keyalg *alg = find_pubkey_alg(algname); + if (!alg) { + char *msg = dupprintf("Couldn't load certificate (unsupported " + "algorithm name '%s')", algname); + message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR, + HELPCTXID(errors_cantloadkey)); + sfree(msg); + sfree(algname); + strbuf_free(pub); + return; + } + + sfree(algname); + + /* Check the two public keys match apart from certificates */ + strbuf *old_basepub = strbuf_new(); + ssh_key_public_blob(ssh_key_base_key(state->ssh2key.key), + BinarySink_UPCAST(old_basepub)); + + ssh_key *new_pubkey = ssh_key_new_pub(alg, ptrlen_from_strbuf(pub)); + strbuf *new_basepub = strbuf_new(); + ssh_key_public_blob(ssh_key_base_key(new_pubkey), + BinarySink_UPCAST(new_basepub)); + ssh_key_free(new_pubkey); + + bool match = ptrlen_eq_ptrlen(ptrlen_from_strbuf(old_basepub), + ptrlen_from_strbuf(new_basepub)); + strbuf_free(old_basepub); + strbuf_free(new_basepub); + + if (!match) { + char *msg = dupprintf("Certificate is for a different public key"); + message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR, + HELPCTXID(errors_cantloadkey)); + sfree(msg); + strbuf_free(pub); + return; + } + + strbuf *priv = strbuf_new_nm(); + ssh_key_private_blob(state->ssh2key.key, BinarySink_UPCAST(priv)); + ssh_key *newkey = ssh_key_new_priv( + alg, ptrlen_from_strbuf(pub), ptrlen_from_strbuf(priv)); + strbuf_free(pub); + strbuf_free(priv); + + if (!newkey) { + char *msg = dupprintf("Couldn't combine certificate with key"); + message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR, + HELPCTXID(errors_cantloadkey)); + sfree(msg); + return; + } + + ssh_key_free(state->ssh2key.key); + state->ssh2key.key = newkey; + + update_ui_after_ssh2_pubkey_change(hwnd, state); + ui_set_state(hwnd, state, 2); +} + +void remove_certificate(HWND hwnd, struct MainDlgState *state) +{ + ssh_key *newkey = ssh_key_clone(ssh_key_base_key(state->ssh2key.key)); + ssh_key_free(state->ssh2key.key); + state->ssh2key.key = newkey; + update_ui_after_ssh2_pubkey_change(hwnd, state); + ui_set_state(hwnd, state, 2); +} + static void start_generating_key(HWND hwnd, struct MainDlgState *state) { static const char generating_msg[] = @@ -1158,7 +1354,7 @@ static void start_generating_key(HWND hwnd, struct MainDlgState *state) params->primepolicy = state->primepolicy; params->rsa_strong = state->rsa_strong; params->key = &state->key; - params->dsskey = &state->dsskey; + params->dsakey = &state->dsakey; HANDLE hThread = CreateThread(NULL, 0, generate_key_thread, params, 0, &threadid); @@ -1174,11 +1370,136 @@ static void start_generating_key(HWND hwnd, struct MainDlgState *state) } /* + * Dialog-box function and context structure for the 'Certificate + * info' button. + */ +struct certinfo_dialog_ctx { + SeatDialogText *text; +}; + +static INT_PTR CertInfoProc(HWND hwnd, UINT msg, WPARAM wParam, + LPARAM lParam, void *vctx) +{ + struct certinfo_dialog_ctx *ctx = (struct certinfo_dialog_ctx *)vctx; + + switch (msg) { + case WM_INITDIALOG: { + int index = 100, y = 12; + + WPARAM font = SendMessage(hwnd, WM_GETFONT, 0, 0); + + const char *key = NULL; + for (SeatDialogTextItem *item = ctx->text->items, + *end = item + ctx->text->nitems; item < end; item++) { + switch (item->type) { + case SDT_MORE_INFO_KEY: + key = item->text; + break; + case SDT_MORE_INFO_VALUE_SHORT: + case SDT_MORE_INFO_VALUE_BLOB: { + RECT rk, rv; + DWORD editstyle = WS_CHILD | WS_VISIBLE | WS_TABSTOP | + ES_AUTOHSCROLL | ES_READONLY; + if (item->type == SDT_MORE_INFO_VALUE_BLOB) { + rk.left = 12; + rk.right = 286; + rk.top = y; + rk.bottom = 8; + y += 10; + + editstyle |= ES_MULTILINE; + rv.left = 12; + rv.right = 286; + rv.top = y; + rv.bottom = 64; + y += 68; + } else { + rk.left = 12; + rk.right = 130; + rk.top = y+2; + rk.bottom = 8; + + rv.left = 150; + rv.right = 298; + rv.top = y; + rv.bottom = 12; + + y += 16; + } + + MapDialogRect(hwnd, &rk); + HWND ctl = CreateWindowEx( + 0, "STATIC", key, WS_CHILD | WS_VISIBLE, + rk.left, rk.top, rk.right, rk.bottom, + hwnd, (HMENU)(ULONG_PTR)index++, hinst, NULL); + SendMessage(ctl, WM_SETFONT, font, MAKELPARAM(true, 0)); + + MapDialogRect(hwnd, &rv); + ctl = CreateWindowEx( + WS_EX_CLIENTEDGE, "EDIT", item->text, editstyle, + rv.left, rv.top, rv.right, rv.bottom, + hwnd, (HMENU)(ULONG_PTR)index++, hinst, NULL); + SendMessage(ctl, WM_SETFONT, font, MAKELPARAM(true, 0)); + break; + } + default: + break; + } + } + + /* + * Now resize the overall window, and move the Close button at + * the bottom. + */ + RECT r; + r.left = 176; + r.top = y + 10; + r.right = r.bottom = 0; + MapDialogRect(hwnd, &r); + HWND ctl = GetDlgItem(hwnd, IDOK); + SetWindowPos(ctl, NULL, r.left, r.top, 0, 0, + SWP_NOSIZE | SWP_NOREDRAW | SWP_NOZORDER); + + r.left = r.top = r.right = 0; + r.bottom = 300; + MapDialogRect(hwnd, &r); + int oldheight = r.bottom; + + r.left = r.top = r.right = 0; + r.bottom = y + 30; + MapDialogRect(hwnd, &r); + int newheight = r.bottom; + + GetWindowRect(hwnd, &r); + + SetWindowPos(hwnd, NULL, 0, 0, r.right - r.left, + r.bottom - r.top + newheight - oldheight, + SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER); + + ShowWindow(hwnd, SW_SHOWNORMAL); + return 1; + } + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + ShinyEndDialog(hwnd, 0); + return 0; + } + return 0; + case WM_CLOSE: + ShinyEndDialog(hwnd, 0); + return 0; + } + return 0; +} + +/* * Dialog-box function for the main PuTTYgen dialog box. */ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) + WPARAM wParam, LPARAM lParam) { + const int DEMO_SCREENSHOT_TIMER_ID = 1230; static const char entropy_msg[] = "Please generate some randomness by moving the mouse over the blank area."; struct MainDlgState *state; @@ -1200,7 +1521,6 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, state = snew(struct MainDlgState); state->generation_thread_exists = false; - state->collecting_entropy = false; state->entropy = NULL; state->key_exists = false; SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) state); @@ -1221,6 +1541,11 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, menu1 = CreateMenu(); AppendMenu(menu1, MF_ENABLED, IDC_GENERATE, "&Generate key pair"); AppendMenu(menu1, MF_SEPARATOR, 0, 0); + AppendMenu(menu1, MF_ENABLED, IDC_ADDCERT, + "Add &certificate to key"); + AppendMenu(menu1, MF_ENABLED, IDC_REMCERT, + "Remove certificate from key"); + AppendMenu(menu1, MF_SEPARATOR, 0, 0); AppendMenu(menu1, MF_ENABLED, IDC_KEYSSH1, "SSH-&1 key (RSA)"); AppendMenu(menu1, MF_ENABLED, IDC_KEYSSH2RSA, "SSH-2 &RSA key"); AppendMenu(menu1, MF_ENABLED, IDC_KEYSSH2DSA, "SSH-2 &DSA key"); @@ -1297,6 +1622,18 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, cp2 = cp; statictext(&cp2, "", 1, IDC_GENERATING); progressbar(&cp2, IDC_PROGRESS); + cp2 = cp; + bigeditctrl(&cp2, NULL, -1, IDC_CERTSTATIC, 3); + { + HWND child = GetDlgItem(hwnd, IDC_CERTSTATIC); + LONG_PTR style = GetWindowLongPtr(child, GWL_STYLE); + style &= ~WS_VSCROLL; + SetWindowLongPtr(child, GWL_STYLE, style); + SendMessage(child, EM_SETREADONLY, true, 0); + } + MakeDlgItemBorderless(hwnd, IDC_CERTSTATIC); + cp2.xoff = cp2.width = cp2.width / 3; + button(&cp2, "Certificate info...", IDC_CERTMOREINFO, false); bigeditctrl(&cp, "&Public key for pasting into authorized_keys file:", IDC_PKSTATIC, IDC_KEYDISPLAY, 5); @@ -1377,15 +1714,16 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, cp.ypos = ymax; endbox(&cp); } - ui_set_key_type(hwnd, state, IDC_KEYSSH2RSA); - ui_set_primepolicy(hwnd, state, IDC_PRIMEGEN_PROB); - ui_set_rsa_strong(hwnd, state, false); - ui_set_fptype(hwnd, state, fptype_to_idc(SSH_FPTYPE_DEFAULT)); - SetDlgItemInt(hwnd, IDC_BITS, DEFAULT_KEY_BITS, false); + struct InitialParams *params = (struct InitialParams *)lParam; + ui_set_key_type(hwnd, state, params->keybutton); + ui_set_primepolicy(hwnd, state, params->primepolicybutton); + ui_set_rsa_strong(hwnd, state, params->rsa_strong); + ui_set_fptype(hwnd, state, fptype_to_idc(params->fptype)); + SetDlgItemInt(hwnd, IDC_BITS, params->keybits, false); SendDlgItemMessage(hwnd, IDC_ECCURVE, CB_SETCURSEL, - DEFAULT_ECCURVE_INDEX, 0); + params->eccurve_index, 0); SendDlgItemMessage(hwnd, IDC_EDCURVE, CB_SETCURSEL, - DEFAULT_EDCURVE_INDEX, 0); + params->edcurve_index, 0); /* * Initially, hide the progress bar and the key display, @@ -1402,26 +1740,51 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, Filename *fn = filename_from_str(cmdline_keyfile); load_key_file(hwnd, state, fn, false); filename_free(fn); + } else if (cmdline_demo_keystr.ptr) { + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, cmdline_demo_keystr); + const char *errmsg; + ssh2_userkey *k = ppk_load_s(src, NULL, &errmsg); + assert(!errmsg); + + update_ui_after_load(hwnd, state, "demo passphrase", + SSH_KEYTYPE_SSH2, NULL, k); + + SetTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID, TICKSPERSEC, NULL); } return 1; + case WM_TIMER: + if ((UINT_PTR)wParam == DEMO_SCREENSHOT_TIMER_ID) { + KillTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID); + char *err = save_screenshot(hwnd, demo_screenshot_filename); + if (err) { + MessageBox(hwnd, err, "Demo screenshot failure", + MB_OK | MB_ICONERROR); + sfree(err); + } + EndDialog(hwnd, 0); + } + return 0; case WM_MOUSEMOVE: state = (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); - if (state->collecting_entropy && - state->entropy && state->entropy_got < state->entropy_required) { - state->entropy[state->entropy_got++] = lParam; - state->entropy[state->entropy_got++] = GetMessageTime(); + if (state->entropy && state->entropy_got < state->entropy_required) { + ULONG msgtime = GetMessageTime(); + put_uint32(state->entropy, lParam); + put_uint32(state->entropy, msgtime); + if (msgtime - state->entropy_prev_msgtime > ENTROPY_RATE_LIMIT) { + state->entropy_got += 2; + state->entropy_prev_msgtime = msgtime; + } SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, state->entropy_got, 0); if (state->entropy_got >= state->entropy_required) { /* * Seed the entropy pool */ - random_reseed( - make_ptrlen(state->entropy, state->entropy_size)); - smemclr(state->entropy, state->entropy_size); - sfree(state->entropy); - state->collecting_entropy = false; + random_reseed(ptrlen_from_strbuf(state->entropy)); + strbuf_free(state->entropy); + state->entropy = NULL; start_generating_key(hwnd, state); } @@ -1497,11 +1860,9 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, *state->commentptr = snewn(len + 1, char); GetWindowText(editctl, *state->commentptr, len + 1); if (state->ssh2) { - setupbigedit2(hwnd, IDC_KEYDISPLAY, IDC_PKSTATIC, - &state->ssh2key); + setupbigedit2(hwnd, &state->ssh2key); } else { - setupbigedit1(hwnd, IDC_KEYDISPLAY, IDC_PKSTATIC, - &state->key); + setupbigedit1(hwnd, &state->key); } } } @@ -1562,10 +1923,10 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, if ((state->keytype == RSA || state->keytype == DSA) && state->key_bits < 256) { - char *message = dupprintf - ("PuTTYgen will not generate a key smaller than 256" - " bits.\nKey length reset to default %d. Continue?", - DEFAULT_KEY_BITS); + char *message = dupprintf( + "PuTTYgen will not generate a key smaller than 256" + " bits.\nKey length reset to default %d. Continue?", + DEFAULT_KEY_BITS); int ret = MessageBox(hwnd, message, "PuTTYgen Warning", MB_ICONWARNING | MB_OKCANCEL); sfree(message); @@ -1575,9 +1936,9 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, SetDlgItemInt(hwnd, IDC_BITS, DEFAULT_KEY_BITS, false); } else if ((state->keytype == RSA || state->keytype == DSA) && state->key_bits < DEFAULT_KEY_BITS) { - char *message = dupprintf - ("Keys shorter than %d bits are not recommended. " - "Really generate this key?", DEFAULT_KEY_BITS); + char *message = dupprintf( + "Keys shorter than %d bits are not recommended. " + "Really generate this key?", DEFAULT_KEY_BITS); int ret = MessageBox(hwnd, message, "PuTTYgen Warning", MB_ICONWARNING | MB_OKCANCEL); sfree(message); @@ -1635,12 +1996,10 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, ui_set_state(hwnd, state, 1); SetDlgItemText(hwnd, IDC_GENERATING, entropy_msg); state->key_exists = false; - state->collecting_entropy = true; state->entropy_got = 0; - state->entropy_size = (state->entropy_required * - sizeof(unsigned)); - state->entropy = snewn(state->entropy_required, unsigned); + state->entropy = strbuf_new_nm(); + state->entropy_prev_msgtime = GetMessageTime(); SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETRANGE, 0, MAKELPARAM(0, state->entropy_required)); @@ -1819,6 +2178,47 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, } } break; + case IDC_ADDCERT: + if (HIWORD(wParam) != BN_CLICKED) + break; + state = + (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + if (state->key_exists && !state->generation_thread_exists) { + char filename[FILENAME_MAX]; + if (prompt_keyfile(hwnd, "Load certificate:", filename, false, + false)) { + Filename *fn = filename_from_str(filename); + add_certificate(hwnd, state, fn); + filename_free(fn); + } + } + break; + case IDC_REMCERT: + if (HIWORD(wParam) != BN_CLICKED) + break; + state = + (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + if (state->key_exists && !state->generation_thread_exists) { + remove_certificate(hwnd, state); + } + break; + case IDC_CERTMOREINFO: { + if (HIWORD(wParam) != BN_CLICKED) + break; + state = + (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + if (!state->key_exists || !state->ssh2 || !state->ssh2key.key) + break; + if (!ssh_key_alg(state->ssh2key.key)->is_certificate) + break; + + struct certinfo_dialog_ctx ctx[1]; + ctx->text = ssh_key_cert_info(state->ssh2key.key); + ShinyDialogBox(hinst, MAKEINTRESOURCE(216), + "PuTTYgenCertInfo", hwnd, CertInfoProc, ctx); + seat_dialog_text_free(ctx->text); + break; + } } return 0; case WM_DONEKEY: @@ -1830,7 +2230,7 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, PROGRESSRANGE, 0); if (state->ssh2) { if (state->keytype == DSA) { - state->ssh2key.key = &state->dsskey.sshk; + state->ssh2key.key = &state->dsakey.sshk; } else if (state->keytype == ECDSA) { state->ssh2key.key = &state->eckey.sshk; } else if (state->keytype == EDDSA) { @@ -1896,11 +2296,9 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, * .ssh/authorized_keys2 on a Unix box. */ if (state->ssh2) { - setupbigedit2(hwnd, IDC_KEYDISPLAY, - IDC_PKSTATIC, &state->ssh2key); + setupbigedit2(hwnd, &state->ssh2key); } else { - setupbigedit1(hwnd, IDC_KEYDISPLAY, - IDC_PKSTATIC, &state->key); + setupbigedit1(hwnd, &state->key); } } /* @@ -1956,9 +2354,9 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, topic = WINHELP_CTX_puttygen_conversions; break; } if (topic) { - launch_help(hwnd, topic); + launch_help(hwnd, topic); } else { - MessageBeep(0); + MessageBeep(0); } break; } @@ -1980,11 +2378,24 @@ void cleanup_exit(int code) HINSTANCE hinst; +static NORETURN void opt_error(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + char *msg = dupvprintf(fmt, ap); + va_end(ap); + + MessageBox(NULL, msg, "PuTTYgen command line error", MB_ICONERROR | MB_OK); + + exit(1); +} + int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) { - int argc, i; + int argc; char **argv; int ret; + struct InitialParams params[1]; dll_hijacking_protection(); @@ -1996,30 +2407,205 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) */ init_help(); + params->keybutton = IDC_KEYSSH2RSA; + params->primepolicybutton = IDC_PRIMEGEN_PROB; + params->rsa_strong = false; + params->fptype = SSH_FPTYPE_DEFAULT; + params->keybits = DEFAULT_KEY_BITS; + params->eccurve_index = DEFAULT_ECCURVE_INDEX; + params->edcurve_index = DEFAULT_EDCURVE_INDEX; + + save_params = ppk_save_default_parameters; + split_into_argv(cmdline, &argc, &argv, NULL); - for (i = 0; i < argc; i++) { - if (!strcmp(argv[i], "-pgpfp")) { + int argbits = -1; + AuxMatchOpt amo = aux_match_opt_init(argc, argv, 0, opt_error); + while (!aux_match_done(&amo)) { + char *val; + #define match_opt(...) aux_match_opt( \ + &amo, NULL, __VA_ARGS__, (const char *)NULL) + #define match_optval(...) aux_match_opt( \ + &amo, &val, __VA_ARGS__, (const char *)NULL) + + if (aux_match_arg(&amo, &val)) { + if (!cmdline_keyfile) { + /* + * Assume the first argument to be a private key file, and + * attempt to load it. + */ + cmdline_keyfile = val; + continue; + } else { + opt_error("unexpected extra argument '%s'\n", val); + } + } else if (match_opt("-pgpfp")) { pgp_fingerprints_msgbox(NULL); return 1; - } else if (!strcmp(argv[i], "-restrict-acl") || - !strcmp(argv[i], "-restrict_acl") || - !strcmp(argv[i], "-restrictacl")) { + } else if (match_opt("-restrict-acl", "-restrict_acl", + "-restrictacl")) { restrict_process_acl(); + } else if (match_optval("-t")) { + if (!strcmp(val, "rsa") || !strcmp(val, "rsa2")) { + params->keybutton = IDC_KEYSSH2RSA; + } else if (!strcmp(val, "rsa1")) { + params->keybutton = IDC_KEYSSH1; + } else if (!strcmp(val, "dsa") || !strcmp(val, "dss")) { + params->keybutton = IDC_KEYSSH2DSA; + } else if (!strcmp(val, "ecdsa")) { + params->keybutton = IDC_KEYSSH2ECDSA; + } else if (!strcmp(val, "eddsa")) { + params->keybutton = IDC_KEYSSH2EDDSA; + } else if (!strcmp(val, "ed25519")) { + params->keybutton = IDC_KEYSSH2EDDSA; + argbits = 255; + } else if (!strcmp(val, "ed448")) { + params->keybutton = IDC_KEYSSH2EDDSA; + argbits = 448; + } else { + opt_error("unknown key type '%s'\n", val); + } + } else if (match_optval("-b")) { + argbits = atoi(val); + } else if (match_optval("-E")) { + if (!strcmp(val, "md5")) + params->fptype = SSH_FPTYPE_MD5; + else if (!strcmp(val, "sha256")) + params->fptype = SSH_FPTYPE_SHA256; + else + opt_error("unknown fingerprint type '%s'\n", val); + } else if (match_optval("-primes")) { + if (!strcmp(val, "probable") || + !strcmp(val, "probabilistic")) { + params->primepolicybutton = IDC_PRIMEGEN_PROB; + } else if (!strcmp(val, "provable") || + !strcmp(val, "proven") || + !strcmp(val, "simple") || + !strcmp(val, "maurer-simple")) { + params->primepolicybutton = IDC_PRIMEGEN_MAURER_SIMPLE; + } else if (!strcmp(val, "provable-even") || + !strcmp(val, "proven-even") || + !strcmp(val, "even") || + !strcmp(val, "complex") || + !strcmp(val, "maurer-complex")) { + params->primepolicybutton = IDC_PRIMEGEN_MAURER_COMPLEX; + } else { + opt_error("unrecognised prime-generation mode '%s'\n", val); + } + } else if (match_opt("-strong-rsa")) { + params->rsa_strong = true; + } else if (match_optval("-ppk-param", "-ppk-params")) { + char *nextval; + for (; val; val = nextval) { + nextval = strchr(val, ','); + if (nextval) + *nextval++ = '\0'; + + char *optvalue = strchr(val, '='); + if (!optvalue) + opt_error("PPK parameter '%s' expected a value\n", val); + *optvalue++ = '\0'; + + /* Non-numeric options */ + if (!strcmp(val, "kdf")) { + if (!strcmp(optvalue, "Argon2id") || + !strcmp(optvalue, "argon2id")) { + save_params.argon2_flavour = Argon2id; + } else if (!strcmp(optvalue, "Argon2i") || + !strcmp(optvalue, "argon2i")) { + save_params.argon2_flavour = Argon2i; + } else if (!strcmp(optvalue, "Argon2d") || + !strcmp(optvalue, "argon2d")) { + save_params.argon2_flavour = Argon2d; + } else { + opt_error("unrecognised kdf '%s'\n", optvalue); + } + continue; + } + + char *end; + unsigned long n = strtoul(optvalue, &end, 0); + if (!*optvalue || *end) + opt_error("value '%s' for PPK parameter '%s': expected a " + "number\n", optvalue, val); + + if (!strcmp(val, "version")) { + save_params.fmt_version = n; + } else if (!strcmp(val, "memory") || + !strcmp(val, "mem")) { + save_params.argon2_mem = n; + } else if (!strcmp(val, "time")) { + save_params.argon2_passes_auto = true; + save_params.argon2_milliseconds = n; + } else if (!strcmp(val, "passes")) { + save_params.argon2_passes_auto = false; + save_params.argon2_passes = n; + } else if (!strcmp(val, "parallelism") || + !strcmp(val, "parallel")) { + save_params.argon2_parallelism = n; + } else { + opt_error("unrecognised PPK parameter '%s'\n", val); + } + } + } else if (match_optval("-demo-screenshot")) { + demo_screenshot_filename = val; + cmdline_demo_keystr = PTRLEN_LITERAL( + "PuTTY-User-Key-File-3: ssh-ed25519\n" + "Encryption: none\n" + "Comment: ed25519-key-20220402\n" + "Public-Lines: 2\n" + "AAAAC3NzaC1lZDI1NTE5AAAAILzuIFwZ" + "8ZhgOlilcSb+9zPuCf/DmKJiloVlmWGy\n" + "xa/F\n" + "Private-Lines: 1\n" + "AAAAIPca6vLwtB2NJhZUpABQISR0gcQH8jjQLta19VyzA3wc\n" + "Private-MAC: 1159e9628259b35933b397379bbe8a14" + "a1f1d97fe91e446e45a9581a3408b70e\n"); + params->keybutton = IDC_KEYSSH2EDDSA; + argbits = 255; } else { - /* - * Assume the first argument to be a private key file, and - * attempt to load it. - */ - cmdline_keyfile = argv[i]; - break; + opt_error("unrecognised option '%s'\n", amo.argv[amo.index]); } } - save_params = ppk_save_default_parameters; + /* Translate argbits into eccurve_index and edcurve_index */ + if (argbits > 0) { + switch (params->keybutton) { + case IDC_KEYSSH2RSA: + case IDC_KEYSSH1: + case IDC_KEYSSH2DSA: + params->keybits = argbits; + break; + case IDC_KEYSSH2ECDSA: { + bool found = false; + for (int j = 0; j < n_ec_nist_curve_lengths; j++) + if (argbits == ec_nist_curve_lengths[j]) { + params->eccurve_index = j; + found = true; + break; + } + if (!found) + opt_error("unsupported ECDSA bit length %d", argbits); + break; + } + case IDC_KEYSSH2EDDSA: { + bool found = false; + for (int j = 0; j < n_ec_ed_curve_lengths; j++) + if (argbits == ec_ed_curve_lengths[j]) { + params->edcurve_index = j; + found = true; + break; + } + if (!found) + opt_error("unsupported EDDSA bit length %d", argbits); + break; + } + } + } random_setup_special(); - ret = DialogBox(hinst, MAKEINTRESOURCE(201), NULL, MainDlgProc) != IDOK; + ret = DialogBoxParam(hinst, MAKEINTRESOURCE(201), NULL, MainDlgProc, + (LPARAM)params) != IDOK; cleanup_exit(ret); return ret; /* just in case optimiser complains */ diff --git a/windows/puttygen.rc b/windows/puttygen.rc index b910b6a3..fbe14fc3 100644 --- a/windows/puttygen.rc +++ b/windows/puttygen.rc @@ -7,7 +7,7 @@ #define APPNAME "PuTTYgen" #define APPDESC "PuTTY SSH key generation utility" -#include "winhelp.rc2" +#include "help.rc2" #include "puttygen-rc.h" 200 ICON "puttygen.ico" @@ -82,6 +82,16 @@ BEGIN PUSHBUTTON "&Cancel", IDCANCEL, 134, 80, 40, 14 END +/* Accelerators used: clw */ +216 DIALOG DISCARDABLE 140, 40, 450, 300 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "PuTTYgen: certificate information" +FONT 8, "MS Shell Dlg" +CLASS "PuTTYgenCertInfo" +BEGIN + DEFPUSHBUTTON "&Close", IDOK, 201, 130, 48, 14 +END + #include "version.rc2" #ifndef NO_MANIFESTS diff --git a/windows/puttytel.rc b/windows/puttytel.rc index 259bc683..41767a10 100644 --- a/windows/puttytel.rc +++ b/windows/puttytel.rc @@ -1,10 +1,14 @@ #include "rcstuff.h" +#include "putty-rc.h" #define APPNAME "PuTTYtel" #define APPDESC "Telnet and Rlogin client" -#include "winhelp.rc2" -#include "win_res.rc2" +IDI_MAINICON ICON "putty.ico" +IDI_CFGICON ICON "puttycfg.ico" + +#include "help.rc2" +#include "putty-common.rc2" #ifndef NO_MANIFESTS 1 RT_MANIFEST "puttytel.mft" diff --git a/windows/rcstuff.h b/windows/rcstuff.h index ee2c7696..dbace3f5 100644 --- a/windows/rcstuff.h +++ b/windows/rcstuff.h @@ -5,20 +5,15 @@ #ifndef PUTTY_RCSTUFF_H #define PUTTY_RCSTUFF_H -#ifdef __LCC__ -#include <win.h> -#else +#ifdef HAVE_CMAKE_H +#include "cmake.h" +#endif -/* Some compilers don't have winresrc.h */ -#ifndef NO_WINRESRC_H -#ifndef MSVC4 +#if HAVE_WINRESRC_H #include <winresrc.h> -#else +#elif HAVE_WINRES_H #include <winres.h> #endif -#endif - -#endif /* end #ifdef __LCC__ */ /* Some systems don't define this, so I do it myself if necessary */ #ifndef TCS_MULTILINE diff --git a/windows/winsecur.h b/windows/security-api.h index fdd39d81..175486ef 100644 --- a/windows/winsecur.h +++ b/windows/security-api.h @@ -1,11 +1,9 @@ /* - * winsecur.h: some miscellaneous security-related helper functions, - * defined in winsecur.c, that use the advapi32 library. Also + * security-api.h: some miscellaneous security-related helper functions, + * defined in utils/security.c, that use the advapi32 library. Also * centralises the machinery for dynamically loading that library. */ -#if !defined NO_SECURITY - #include <aclapi.h> /* @@ -49,5 +47,3 @@ PSID get_user_sid(void); */ bool make_private_security_descriptor( DWORD permissions, PSECURITY_DESCRIPTOR *psd, PACL *acl, char **error); - -#endif diff --git a/windows/winselcli.c b/windows/select-cli.c index f19a0bbe..9bf8411e 100644 --- a/windows/winselcli.c +++ b/windows/select-cli.c @@ -1,5 +1,5 @@ /* - * Implementation of do_select() for winnet.c to use, suitable for use + * Implementation of do_select() for network.c to use, suitable for use * when there's no GUI window to have network activity reported to. * * It uses WSAEventSelect, where available, to convert network diff --git a/windows/select-gui.c b/windows/select-gui.c new file mode 100644 index 00000000..4c37c345 --- /dev/null +++ b/windows/select-gui.c @@ -0,0 +1,65 @@ +/* + * Implementation of do_select() for network.c to use, that uses + * WSAAsyncSelect to convert network activity into window messages, + * for integration into a GUI event loop. + */ + +#include "putty.h" + +static HWND winsel_hwnd = NULL; + +void winselgui_set_hwnd(HWND hwnd) +{ + winsel_hwnd = hwnd; +} + +void winselgui_clear_hwnd(void) +{ + winsel_hwnd = NULL; +} + +const char *do_select(SOCKET skt, bool enable) +{ + int msg, events; + if (enable) { + msg = WM_NETEVENT; + events = (FD_CONNECT | FD_READ | FD_WRITE | + FD_OOB | FD_CLOSE | FD_ACCEPT); + } else { + msg = events = 0; + } + + assert(winsel_hwnd); + + if (p_WSAAsyncSelect(skt, winsel_hwnd, msg, events) == SOCKET_ERROR) + return winsock_error_string(p_WSAGetLastError()); + + return NULL; +} + +struct wm_netevent_params { + /* Used to pass data to wm_netevent_callback */ + WPARAM wParam; + LPARAM lParam; +}; + +static void wm_netevent_callback(void *vctx) +{ + struct wm_netevent_params *params = (struct wm_netevent_params *)vctx; + select_result(params->wParam, params->lParam); + sfree(params); +} + +void winselgui_response(WPARAM wParam, LPARAM lParam) +{ + /* + * To protect against re-entrancy when Windows's recv() + * immediately triggers a new WSAAsyncSelect window message, we + * don't call select_result directly from this handler but instead + * wait until we're back out at the top level of the message loop. + */ + struct wm_netevent_params *params = snew(struct wm_netevent_params); + params->wParam = wParam; + params->lParam = lParam; + queue_toplevel_callback(wm_netevent_callback, params); +} diff --git a/windows/winser.c b/windows/serial.c index 7f4bcf2e..14a89822 100644 --- a/windows/winser.c +++ b/windows/serial.c @@ -73,7 +73,8 @@ static size_t serial_gotdata( } } -static void serial_sentdata(struct handle *h, size_t new_backlog, int err) +static void serial_sentdata(struct handle *h, size_t new_backlog, int err, + bool close) { Serial *serial = (Serial *)handle_get_privdata(h); if (err) { @@ -88,6 +89,7 @@ static void serial_sentdata(struct handle *h, size_t new_backlog, int err) seat_connection_fatal(serial->seat, "%s", error_msg); } else { serial->bufsize = new_backlog; + seat_sent(serial->seat, serial->bufsize); } } @@ -210,6 +212,7 @@ static char *serial_init(const BackendVtable *vt, Seat *seat, seat_set_trust_status(seat, false); serial = snew(Serial); + memset(serial, 0, sizeof(Serial)); serial->port = INVALID_HANDLE_VALUE; serial->out = serial->in = NULL; serial->bufsize = 0; @@ -305,15 +308,14 @@ static void serial_reconfig(Backend *be, Conf *conf) /* * Called to send data down the serial connection. */ -static size_t serial_send(Backend *be, const char *buf, size_t len) +static void serial_send(Backend *be, const char *buf, size_t len) { Serial *serial = container_of(be, Serial, backend); if (serial->out == NULL) - return 0; + return; serial->bufsize = handle_write(serial->out, buf, len); - return serial->bufsize; } /* @@ -451,7 +453,8 @@ const BackendVtable serial_backend = { .unthrottle = serial_unthrottle, .cfg_info = serial_cfg_info, .id = "serial", - .displayname = "Serial", + .displayname_tc = "Serial", + .displayname_lc = "serial", .protocol = PROT_SERIAL, .serial_parity_mask = ((1 << SER_PAR_NONE) | (1 << SER_PAR_ODD) | diff --git a/windows/winsftp.c b/windows/sftp.c index 0c695d2e..a6546269 100644 --- a/windows/winsftp.c +++ b/windows/sftp.c @@ -1,5 +1,5 @@ /* - * winsftp.c: the Windows-specific parts of PSFTP and PSCP. + * sftp.c: the Windows-specific parts of PSFTP and PSCP. */ #include <winsock2.h> /* need to put this first, for winelib builds */ @@ -10,15 +10,21 @@ #include "putty.h" #include "psftp.h" #include "ssh.h" -#include "winsecur.h" +#include "security-api.h" -int filexfer_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input) +SeatPromptResult filexfer_get_userpass_input(Seat *seat, prompts_t *p) { - int ret; - ret = cmdline_get_passwd_input(p); - if (ret == -1) - ret = console_get_userpass_input(p); - return ret; + /* The file transfer tools don't support Restart Session, so we + * can just have a single static cmdline_get_passwd_input_state + * that's never reset */ + static cmdline_get_passwd_input_state cmdline_state = + CMDLINE_GET_PASSWD_INPUT_STATE_INIT; + + SeatPromptResult spr; + spr = cmdline_get_passwd_input(p, &cmdline_state, false); + if (spr.kind == SPRK_INCOMPLETE) + spr = console_get_userpass_input(p); + return spr; } void platform_get_x11_auth(struct X11Display *display, Conf *conf) @@ -216,16 +222,16 @@ int seek_file(WFile *f, uint64_t offset, int whence) DWORD movemethod; switch (whence) { - case FROM_START: + case FROM_START: movemethod = FILE_BEGIN; break; - case FROM_CURRENT: + case FROM_CURRENT: movemethod = FILE_CURRENT; break; - case FROM_END: + case FROM_END: movemethod = FILE_END; break; - default: + default: return -1; } diff --git a/windows/winshare.c b/windows/sharing.c index f0e409ac..15b1da48 100644 --- a/windows/winshare.c +++ b/windows/sharing.c @@ -5,16 +5,14 @@ #include <stdio.h> #include <assert.h> -#if !defined NO_SECURITY - #include "tree234.h" #include "putty.h" #include "network.h" -#include "proxy.h" +#include "proxy/proxy.h" #include "ssh.h" -#include "wincapi.h" -#include "winsecur.h" +#include "cryptoapi.h" +#include "security-api.h" #define CONNSHARE_PIPE_PREFIX "\\\\.\\pipe\\putty-connshare" #define CONNSHARE_MUTEX_PREFIX "Local\\putty-connshare-mutex" @@ -38,8 +36,6 @@ int platform_ssh_share(const char *pi_name, Conf *conf, char *name, *mutexname, *pipename; HANDLE mutex; Socket *retsock; - PSECURITY_DESCRIPTOR psd; - PACL acl; /* * Transform the platform-independent version of the connection @@ -59,39 +55,12 @@ int platform_ssh_share(const char *pi_name, Conf *conf, * Make a mutex name out of the connection identifier, and lock it * while we decide whether to be upstream or downstream. */ - { - SECURITY_ATTRIBUTES sa; - - mutexname = make_name(CONNSHARE_MUTEX_PREFIX, name); - if (!make_private_security_descriptor(MUTEX_ALL_ACCESS, - &psd, &acl, logtext)) { - sfree(mutexname); - sfree(name); - return SHARE_NONE; - } - - memset(&sa, 0, sizeof(sa)); - sa.nLength = sizeof(sa); - sa.lpSecurityDescriptor = psd; - sa.bInheritHandle = false; - - mutex = CreateMutex(&sa, false, mutexname); - - if (!mutex) { - *logtext = dupprintf("CreateMutex(\"%s\") failed: %s", - mutexname, win_strerror(GetLastError())); - sfree(mutexname); - sfree(name); - LocalFree(psd); - LocalFree(acl); - return SHARE_NONE; - } - + mutexname = make_name(CONNSHARE_MUTEX_PREFIX, name); + mutex = lock_interprocess_mutex(mutexname, logtext); + if (!mutex) { sfree(mutexname); - LocalFree(psd); - LocalFree(acl); - - WaitForSingleObject(mutex, INFINITE); + sfree(name); + return SHARE_NONE; } pipename = make_name(CONNSHARE_PIPE_PREFIX, name); @@ -105,8 +74,7 @@ int platform_ssh_share(const char *pi_name, Conf *conf, *logtext = pipename; *sock = retsock; sfree(name); - ReleaseMutex(mutex); - CloseHandle(mutex); + unlock_interprocess_mutex(mutex); return SHARE_DOWNSTREAM; } sfree(*ds_err); @@ -143,9 +111,3 @@ int platform_ssh_share(const char *pi_name, Conf *conf, void platform_ssh_share_cleanup(const char *name) { } - -#else /* !defined NO_SECURITY */ - -#include "noshare.c" - -#endif /* !defined NO_SECURITY */ diff --git a/windows/winstore.c b/windows/storage.c index 09e5c028..f2c33feb 100644 --- a/windows/winstore.c +++ b/windows/storage.c @@ -1,5 +1,5 @@ /* - * winstore.c: Windows-specific implementation of the interface + * storage.c: Windows-specific implementation of the interface * defined in storage.h. */ @@ -21,6 +21,7 @@ static const char *const reg_jumplist_key = PUTTY_REG_POS "\\Jumplist"; static const char *const reg_jumplist_value = "Recent sessions"; static const char *const puttystr = PUTTY_REG_POS "\\Sessions"; +static const char *const host_ca_key = PUTTY_REG_POS "\\SshHostCAs"; static bool tried_shgetfolderpath = false; static HMODULE shell32_module = NULL; @@ -33,28 +34,16 @@ struct settings_w { settings_w *open_settings_w(const char *sessionname, char **errmsg) { - HKEY subkey1, sesskey; - int ret; - strbuf *sb; - *errmsg = NULL; if (!sessionname || !*sessionname) sessionname = "Default Settings"; - sb = strbuf_new(); + strbuf *sb = strbuf_new(); escape_registry_key(sessionname, sb); - ret = RegCreateKey(HKEY_CURRENT_USER, puttystr, &subkey1); - if (ret != ERROR_SUCCESS) { - strbuf_free(sb); - *errmsg = dupprintf("Unable to create registry key\n" - "HKEY_CURRENT_USER\\%s", puttystr); - return NULL; - } - ret = RegCreateKey(subkey1, sb->s, &sesskey); - RegCloseKey(subkey1); - if (ret != ERROR_SUCCESS) { + HKEY sesskey = open_regkey(true, HKEY_CURRENT_USER, puttystr, sb->s); + if (!sesskey) { *errmsg = dupprintf("Unable to create registry key\n" "HKEY_CURRENT_USER\\%s\\%s", puttystr, sb->s); strbuf_free(sb); @@ -70,20 +59,18 @@ settings_w *open_settings_w(const char *sessionname, char **errmsg) void write_setting_s(settings_w *handle, const char *key, const char *value) { if (handle) - RegSetValueEx(handle->sesskey, key, 0, REG_SZ, (CONST BYTE *)value, - 1 + strlen(value)); + put_reg_sz(handle->sesskey, key, value); } void write_setting_i(settings_w *handle, const char *key, int value) { if (handle) - RegSetValueEx(handle->sesskey, key, 0, REG_DWORD, - (CONST BYTE *) &value, sizeof(value)); + put_reg_dword(handle->sesskey, key, value); } void close_settings_w(settings_w *handle) { - RegCloseKey(handle->sesskey); + close_regkey(handle->sesskey); sfree(handle); } @@ -93,24 +80,12 @@ struct settings_r { settings_r *open_settings_r(const char *sessionname) { - HKEY subkey1, sesskey; - strbuf *sb; - if (!sessionname || !*sessionname) sessionname = "Default Settings"; - sb = strbuf_new(); + strbuf *sb = strbuf_new(); escape_registry_key(sessionname, sb); - - if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &subkey1) != ERROR_SUCCESS) { - sesskey = NULL; - } else { - if (RegOpenKey(subkey1, sb->s, &sesskey) != ERROR_SUCCESS) { - sesskey = NULL; - } - RegCloseKey(subkey1); - } - + HKEY sesskey = open_regkey(false, HKEY_CURRENT_USER, puttystr, sb->s); strbuf_free(sb); if (!sesskey) @@ -123,42 +98,15 @@ settings_r *open_settings_r(const char *sessionname) char *read_setting_s(settings_r *handle, const char *key) { - DWORD type, allocsize, size; - char *ret; - if (!handle) return NULL; - - /* Find out the type and size of the data. */ - if (RegQueryValueEx(handle->sesskey, key, 0, - &type, NULL, &size) != ERROR_SUCCESS || - type != REG_SZ) - return NULL; - - allocsize = size+1; /* allow for an extra NUL if needed */ - ret = snewn(allocsize, char); - if (RegQueryValueEx(handle->sesskey, key, 0, - &type, (BYTE *)ret, &size) != ERROR_SUCCESS || - type != REG_SZ) { - sfree(ret); - return NULL; - } - assert(size < allocsize); - ret[size] = '\0'; /* add an extra NUL in case RegQueryValueEx - * didn't supply one */ - - return ret; + return get_reg_sz(handle->sesskey, key); } int read_setting_i(settings_r *handle, const char *key, int defvalue) { - DWORD type, val, size; - size = sizeof(val); - - if (!handle || - RegQueryValueEx(handle->sesskey, key, 0, &type, - (BYTE *) &val, &size) != ERROR_SUCCESS || - size != sizeof(val) || type != REG_DWORD) + DWORD val; + if (!handle || !get_reg_dword(handle->sesskey, key, &val)) return defvalue; else return val; @@ -241,25 +189,23 @@ void write_setting_filename(settings_w *handle, void close_settings_r(settings_r *handle) { if (handle) { - RegCloseKey(handle->sesskey); + close_regkey(handle->sesskey); sfree(handle); } } void del_settings(const char *sessionname) { - HKEY subkey1; - strbuf *sb; - - if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &subkey1) != ERROR_SUCCESS) + HKEY rkey = open_regkey(false, HKEY_CURRENT_USER, puttystr); + if (!rkey) return; - sb = strbuf_new(); + strbuf *sb = strbuf_new(); escape_registry_key(sessionname, sb); - RegDeleteKey(subkey1, sb->s); + del_regkey(rkey, sb->s); strbuf_free(sb); - RegCloseKey(subkey1); + close_regkey(rkey); remove_session_from_jumplist(sessionname); } @@ -271,13 +217,11 @@ struct settings_e { settings_e *enum_settings_start(void) { - settings_e *ret; - HKEY key; - - if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &key) != ERROR_SUCCESS) + HKEY key = open_regkey(false, HKEY_CURRENT_USER, puttystr); + if (!key) return NULL; - ret = snew(settings_e); + settings_e *ret = snew(settings_e); if (ret) { ret->key = key; ret->i = 0; @@ -288,85 +232,56 @@ settings_e *enum_settings_start(void) bool enum_settings_next(settings_e *e, strbuf *sb) { - size_t regbuf_size = MAX_PATH + 1; - char *regbuf = snewn(regbuf_size, char); - bool success; - - while (1) { - DWORD retd = RegEnumKey(e->key, e->i, regbuf, regbuf_size); - if (retd != ERROR_MORE_DATA) { - success = (retd == ERROR_SUCCESS); - break; - } - sgrowarray(regbuf, regbuf_size, regbuf_size); - } - - if (success) - unescape_registry_key(regbuf, sb); + char *name = enum_regkey(e->key, e->i); + if (!name) + return false; + unescape_registry_key(name, sb); + sfree(name); e->i++; - sfree(regbuf); - return success; + return true; } void enum_settings_finish(settings_e *e) { - RegCloseKey(e->key); + close_regkey(e->key); sfree(e); } static void hostkey_regname(strbuf *sb, const char *hostname, int port, const char *keytype) { - strbuf_catf(sb, "%s@%d:", keytype, port); + put_fmt(sb, "%s@%d:", keytype, port); escape_registry_key(hostname, sb); } -int verify_host_key(const char *hostname, int port, - const char *keytype, const char *key) +int check_stored_host_key(const char *hostname, int port, + const char *keytype, const char *key) { - char *otherstr; - strbuf *regname; - int len; - HKEY rkey; - DWORD readlen; - DWORD type; - int ret, compare; - - len = 1 + strlen(key); - /* - * Now read a saved key in from the registry and see what it - * says. + * Read a saved key in from the registry and see what it says. */ - regname = strbuf_new(); + strbuf *regname = strbuf_new(); hostkey_regname(regname, hostname, port, keytype); - if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys", - &rkey) != ERROR_SUCCESS) { + HKEY rkey = open_regkey(false, HKEY_CURRENT_USER, + PUTTY_REG_POS "\\SshHostKeys"); + if (!rkey) { strbuf_free(regname); return 1; /* key does not exist in registry */ } - readlen = len; - otherstr = snewn(len, char); - ret = RegQueryValueEx(rkey, regname->s, NULL, - &type, (BYTE *)otherstr, &readlen); - - if (ret != ERROR_SUCCESS && ret != ERROR_MORE_DATA && - !strcmp(keytype, "rsa")) { + char *otherstr = get_reg_sz(rkey, regname->s); + if (!otherstr && !strcmp(keytype, "rsa")) { /* * Key didn't exist. If the key type is RSA, we'll try * another trick, which is to look up the _old_ key format * under just the hostname and translate that. */ char *justhost = regname->s + 1 + strcspn(regname->s, ":"); - char *oldstyle = snewn(len + 10, char); /* safety margin */ - readlen = len; - ret = RegQueryValueEx(rkey, justhost, NULL, &type, - (BYTE *)oldstyle, &readlen); + char *oldstyle = get_reg_sz(rkey, justhost); - if (ret == ERROR_SUCCESS && type == REG_SZ) { + if (oldstyle) { /* * The old format is two old-style bignums separated by * a slash. An old-style bignum is made of groups of @@ -379,29 +294,26 @@ int verify_host_key(const char *hostname, int port, * doesn't appear anyway in RSA keys) separated by a * comma. All hex digits are lowercase in both formats. */ - char *p = otherstr; - char *q = oldstyle; + strbuf *new = strbuf_new(); + const char *q = oldstyle; int i, j; for (i = 0; i < 2; i++) { int ndigits, nwords; - *p++ = '0'; - *p++ = 'x'; + put_datapl(new, PTRLEN_LITERAL("0x")); ndigits = strcspn(q, "/"); /* find / or end of string */ nwords = ndigits / 4; /* now trim ndigits to remove leading zeros */ while (q[(ndigits - 1) ^ 3] == '0' && ndigits > 1) ndigits--; /* now move digits over to new string */ - for (j = 0; j < ndigits; j++) - p[ndigits - 1 - j] = q[j ^ 3]; - p += ndigits; + for (j = ndigits; j-- > 0 ;) + put_byte(new, q[j ^ 3]); q += nwords * 4; if (*q) { - q++; /* eat the slash */ - *p++ = ','; /* add a comma */ + q++; /* eat the slash */ + put_byte(new, ','); /* add a comma */ } - *p = '\0'; /* terminate the string */ } /* @@ -409,59 +321,192 @@ int verify_host_key(const char *hostname, int port, * format. If not, we'll assume something odd went * wrong, and hyper-cautiously do nothing. */ - if (!strcmp(otherstr, key)) - RegSetValueEx(rkey, regname->s, 0, REG_SZ, (BYTE *)otherstr, - strlen(otherstr) + 1); + if (!strcmp(new->s, key)) { + put_reg_sz(rkey, regname->s, new->s); + otherstr = strbuf_to_str(new); + } else { + strbuf_free(new); + } } sfree(oldstyle); } - RegCloseKey(rkey); + close_regkey(rkey); - compare = strcmp(otherstr, key); + int compare = otherstr ? strcmp(otherstr, key) : -1; sfree(otherstr); strbuf_free(regname); - if (ret == ERROR_MORE_DATA || - (ret == ERROR_SUCCESS && type == REG_SZ && compare)) - return 2; /* key is different in registry */ - else if (ret != ERROR_SUCCESS || type != REG_SZ) + if (!otherstr) return 1; /* key does not exist in registry */ + else if (compare) + return 2; /* key is different in registry */ else return 0; /* key matched OK in registry */ } bool have_ssh_host_key(const char *hostname, int port, - const char *keytype) + const char *keytype) { /* - * If we have a host key, verify_host_key will return 0 or 2. + * If we have a host key, check_stored_host_key will return 0 or 2. * If we don't have one, it'll return 1. */ - return verify_host_key(hostname, port, keytype, "") != 1; + return check_stored_host_key(hostname, port, keytype, "") != 1; } void store_host_key(const char *hostname, int port, const char *keytype, const char *key) { - strbuf *regname; - HKEY rkey; - - regname = strbuf_new(); + strbuf *regname = strbuf_new(); hostkey_regname(regname, hostname, port, keytype); - if (RegCreateKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys", - &rkey) == ERROR_SUCCESS) { - RegSetValueEx(rkey, regname->s, 0, REG_SZ, - (BYTE *)key, strlen(key) + 1); - RegCloseKey(rkey); + HKEY rkey = open_regkey(true, HKEY_CURRENT_USER, + PUTTY_REG_POS "\\SshHostKeys"); + if (rkey) { + put_reg_sz(rkey, regname->s, key); + close_regkey(rkey); } /* else key does not exist in registry */ strbuf_free(regname); } +struct host_ca_enum { + HKEY key; + int i; +}; + +host_ca_enum *enum_host_ca_start(void) +{ + host_ca_enum *e; + HKEY key; + + if (!(key = open_regkey(false, HKEY_CURRENT_USER, host_ca_key))) + return NULL; + + e = snew(host_ca_enum); + e->key = key; + e->i = 0; + + return e; +} + +bool enum_host_ca_next(host_ca_enum *e, strbuf *sb) +{ + char *regbuf = enum_regkey(e->key, e->i); + if (!regbuf) + return false; + + unescape_registry_key(regbuf, sb); + sfree(regbuf); + e->i++; + return true; +} + +void enum_host_ca_finish(host_ca_enum *e) +{ + close_regkey(e->key); + sfree(e); +} + +host_ca *host_ca_load(const char *name) +{ + strbuf *sb; + const char *s; + + sb = strbuf_new(); + escape_registry_key(name, sb); + HKEY rkey = open_regkey(false, HKEY_CURRENT_USER, host_ca_key, sb->s); + strbuf_free(sb); + + if (!rkey) + return NULL; + + host_ca *hca = host_ca_new(); + hca->name = dupstr(name); + + DWORD val; + + if ((s = get_reg_sz(rkey, "PublicKey")) != NULL) + hca->ca_public_key = base64_decode_sb(ptrlen_from_asciz(s)); + + if ((s = get_reg_sz(rkey, "Validity")) != NULL) { + hca->validity_expression = strbuf_to_str( + percent_decode_sb(ptrlen_from_asciz(s))); + } else if ((sb = get_reg_multi_sz(rkey, "MatchHosts")) != NULL) { + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(sb)); + CertExprBuilder *eb = cert_expr_builder_new(); + + const char *wc; + while (wc = get_asciz(src), !get_err(src)) + cert_expr_builder_add(eb, wc); + + hca->validity_expression = cert_expr_expression(eb); + cert_expr_builder_free(eb); + } + + if (get_reg_dword(rkey, "PermitRSASHA1", &val)) + hca->opts.permit_rsa_sha1 = val; + if (get_reg_dword(rkey, "PermitRSASHA256", &val)) + hca->opts.permit_rsa_sha256 = val; + if (get_reg_dword(rkey, "PermitRSASHA512", &val)) + hca->opts.permit_rsa_sha512 = val; + + close_regkey(rkey); + return hca; +} + +char *host_ca_save(host_ca *hca) +{ + if (!*hca->name) + return dupstr("CA record must have a name"); + + strbuf *sb = strbuf_new(); + escape_registry_key(hca->name, sb); + HKEY rkey = open_regkey(true, HKEY_CURRENT_USER, host_ca_key, sb->s); + if (!rkey) { + char *err = dupprintf("Unable to create registry key\n" + "HKEY_CURRENT_USER\\%s\\%s", host_ca_key, sb->s); + strbuf_free(sb); + return err; + } + strbuf_free(sb); + + strbuf *base64_pubkey = base64_encode_sb( + ptrlen_from_strbuf(hca->ca_public_key), 0); + put_reg_sz(rkey, "PublicKey", base64_pubkey->s); + strbuf_free(base64_pubkey); + + strbuf *validity = percent_encode_sb( + ptrlen_from_asciz(hca->validity_expression), NULL); + put_reg_sz(rkey, "Validity", validity->s); + strbuf_free(validity); + + put_reg_dword(rkey, "PermitRSASHA1", hca->opts.permit_rsa_sha1); + put_reg_dword(rkey, "PermitRSASHA256", hca->opts.permit_rsa_sha256); + put_reg_dword(rkey, "PermitRSASHA512", hca->opts.permit_rsa_sha512); + + close_regkey(rkey); + return NULL; +} + +char *host_ca_delete(const char *name) +{ + HKEY rkey = open_regkey(false, HKEY_CURRENT_USER, host_ca_key); + if (!rkey) + return NULL; + + strbuf *sb = strbuf_new(); + escape_registry_key(name, sb); + del_regkey(rkey, sb->s); + strbuf_free(sb); + + return NULL; +} + /* * Open (or delete) the random seed file. */ @@ -498,7 +543,6 @@ static bool try_random_seed_and_free(char *path, int action, HANDLE *hout) static HANDLE access_random_seed(int action) { - HKEY rkey; HANDLE rethandle; /* @@ -517,16 +561,16 @@ static HANDLE access_random_seed(int action) * Registry, if any. */ { - char regpath[MAX_PATH + 1]; - DWORD type, size = sizeof(regpath); - if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &rkey) == - ERROR_SUCCESS) { - int ret = RegQueryValueEx(rkey, "RandSeedFile", - 0, &type, (BYTE *)regpath, &size); - RegCloseKey(rkey); - if (ret == ERROR_SUCCESS && type == REG_SZ && - try_random_seed(regpath, action, &rethandle)) - return rethandle; + HKEY rkey = open_regkey(false, HKEY_CURRENT_USER, PUTTY_REG_POS); + if (rkey) { + char *regpath = get_reg_sz(rkey, "RandSeedFile"); + close_regkey(rkey); + if (regpath) { + bool success = try_random_seed(regpath, action, &rethandle); + sfree(regpath); + if (success) + return rethandle; + } } } @@ -641,132 +685,69 @@ void write_random_seed(void *data, int len) * returning the resulting concatenated list of strings in 'out' (if * non-null). */ -static int transform_jumplist_registry - (const char *add, const char *rem, char **out) -{ - int ret; - HKEY pjumplist_key; - DWORD type; - DWORD value_length; - char *old_value, *new_value; - char *piterator_old, *piterator_new, *piterator_tmp; - - ret = RegCreateKeyEx(HKEY_CURRENT_USER, reg_jumplist_key, 0, NULL, - REG_OPTION_NON_VOLATILE, (KEY_READ | KEY_WRITE), NULL, - &pjumplist_key, NULL); - if (ret != ERROR_SUCCESS) { +static int transform_jumplist_registry( + const char *add, const char *rem, char **out) +{ + HKEY rkey = open_regkey(true, HKEY_CURRENT_USER, reg_jumplist_key); + if (!rkey) return JUMPLISTREG_ERROR_KEYOPENCREATE_FAILURE; - } /* Get current list of saved sessions in the registry. */ - value_length = 200; - old_value = snewn(value_length, char); - ret = RegQueryValueEx(pjumplist_key, reg_jumplist_value, NULL, &type, - (BYTE *)old_value, &value_length); - /* When the passed buffer is too small, ERROR_MORE_DATA is - * returned and the required size is returned in the length - * argument. */ - if (ret == ERROR_MORE_DATA) { - sfree(old_value); - old_value = snewn(value_length, char); - ret = RegQueryValueEx(pjumplist_key, reg_jumplist_value, NULL, &type, - (BYTE *)old_value, &value_length); - } - - if (ret == ERROR_FILE_NOT_FOUND) { - /* Value doesn't exist yet. Start from an empty value. */ - *old_value = '\0'; - *(old_value + 1) = '\0'; - } else if (ret != ERROR_SUCCESS) { - /* Some non-recoverable error occurred. */ - sfree(old_value); - RegCloseKey(pjumplist_key); - return JUMPLISTREG_ERROR_VALUEREAD_FAILURE; - } else if (type != REG_MULTI_SZ) { - /* The value present in the registry has the wrong type: we - * try to delete it and start from an empty value. */ - ret = RegDeleteValue(pjumplist_key, reg_jumplist_value); - if (ret != ERROR_SUCCESS) { - sfree(old_value); - RegCloseKey(pjumplist_key); - return JUMPLISTREG_ERROR_VALUEREAD_FAILURE; - } - - *old_value = '\0'; - *(old_value + 1) = '\0'; - } - - /* Check validity of registry data: REG_MULTI_SZ value must end - * with \0\0. */ - piterator_tmp = old_value; - while (((piterator_tmp - old_value) < (value_length - 1)) && - !(*piterator_tmp == '\0' && *(piterator_tmp+1) == '\0')) { - ++piterator_tmp; - } - - if ((piterator_tmp - old_value) >= (value_length-1)) { - /* Invalid value. Start from an empty value. */ - *old_value = '\0'; - *(old_value + 1) = '\0'; + strbuf *oldlist = get_reg_multi_sz(rkey, reg_jumplist_value); + if (!oldlist) { + /* Start again with the empty list. */ + oldlist = strbuf_new(); + put_data(oldlist, "\0\0", 2); } /* * Modify the list, if we're modifying. */ + bool write_failure = false; if (add || rem) { - /* Walk through the existing list and construct the new list of - * saved sessions. */ - new_value = snewn(value_length + (add ? strlen(add) + 1 : 0), char); - piterator_new = new_value; - piterator_old = old_value; + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(oldlist)); + strbuf *newlist = strbuf_new(); /* First add the new item to the beginning of the list. */ - if (add) { - strcpy(piterator_new, add); - piterator_new += strlen(piterator_new) + 1; - } + if (add) + put_asciz(newlist, add); + /* Now add the existing list, taking care to leave out the removed * item, if it was already in the existing list. */ - while (*piterator_old != '\0') { - if (!rem || strcmp(piterator_old, rem) != 0) { + while (true) { + const char *olditem = get_asciz(src); + if (get_err(src)) + break; + + if (!rem || strcmp(olditem, rem) != 0) { /* Check if this is a valid session, otherwise don't add. */ - settings_r *psettings_tmp = open_settings_r(piterator_old); + settings_r *psettings_tmp = open_settings_r(olditem); if (psettings_tmp != NULL) { close_settings_r(psettings_tmp); - strcpy(piterator_new, piterator_old); - piterator_new += strlen(piterator_new) + 1; + put_asciz(newlist, olditem); } } - piterator_old += strlen(piterator_old) + 1; } - *piterator_new = '\0'; - ++piterator_new; /* Save the new list to the registry. */ - ret = RegSetValueEx(pjumplist_key, reg_jumplist_value, 0, REG_MULTI_SZ, - (BYTE *)new_value, piterator_new - new_value); + write_failure = !put_reg_multi_sz(rkey, reg_jumplist_value, newlist); - sfree(old_value); - old_value = new_value; - } else - ret = ERROR_SUCCESS; + strbuf_free(oldlist); + oldlist = newlist; + } - /* - * Either return or free the result. - */ - if (out && ret == ERROR_SUCCESS) - *out = old_value; - else - sfree(old_value); + close_regkey(rkey); - /* Clean up and return. */ - RegCloseKey(pjumplist_key); + if (out && !write_failure) + *out = strbuf_to_str(oldlist); + else + strbuf_free(oldlist); - if (ret != ERROR_SUCCESS) { + if (write_failure) return JUMPLISTREG_ERROR_VALUEWRITE_FAILURE; - } else { + else return JUMPLISTREG_OK; - } } /* Adds a new entry to the jumplist entries in the registry. */ @@ -800,26 +781,22 @@ char *get_jumplist_registry_entries (void) */ static void registry_recursive_remove(HKEY key) { - DWORD i; - char name[MAX_PATH + 1]; - HKEY subkey; + char *name; - i = 0; - while (RegEnumKey(key, i, name, sizeof(name)) == ERROR_SUCCESS) { - if (RegOpenKey(key, name, &subkey) == ERROR_SUCCESS) { + DWORD i = 0; + while ((name = enum_regkey(key, i)) != NULL) { + HKEY subkey = open_regkey(false, key, name); + if (subkey) { registry_recursive_remove(subkey); - RegCloseKey(subkey); + close_regkey(subkey); } - RegDeleteKey(key, name); + del_regkey(key, name); + sfree(name); } } void cleanup_all(void) { - HKEY key; - int ret; - char name[MAX_PATH + 1]; - /* ------------------------------------------------------------ * Wipe out the random seed file, in all of its possible * locations. @@ -839,31 +816,34 @@ void cleanup_all(void) /* * Open the main PuTTY registry key and remove everything in it. */ - if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &key) == - ERROR_SUCCESS) { + HKEY key = open_regkey(false, HKEY_CURRENT_USER, PUTTY_REG_POS); + if (key) { registry_recursive_remove(key); - RegCloseKey(key); + close_regkey(key); } /* * Now open the parent key and remove the PuTTY main key. Once * we've done that, see if the parent key has any other * children. */ - if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_PARENT, - &key) == ERROR_SUCCESS) { - RegDeleteKey(key, PUTTY_REG_PARENT_CHILD); - ret = RegEnumKey(key, 0, name, sizeof(name)); - RegCloseKey(key); + if ((key = open_regkey(false, HKEY_CURRENT_USER, + PUTTY_REG_PARENT)) != NULL) { + del_regkey(key, PUTTY_REG_PARENT_CHILD); + char *name = enum_regkey(key, 0); + close_regkey(key); + /* * If the parent key had no other children, we must delete * it in its turn. That means opening the _grandparent_ * key. */ - if (ret != ERROR_SUCCESS) { - if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_GPARENT, - &key) == ERROR_SUCCESS) { - RegDeleteKey(key, PUTTY_REG_GPARENT_CHILD); - RegCloseKey(key); + if (name) { + sfree(name); + } else { + if ((key = open_regkey(false, HKEY_CURRENT_USER, + PUTTY_REG_GPARENT)) != NULL) { + del_regkey(key, PUTTY_REG_GPARENT_CHILD); + close_regkey(key); } } } diff --git a/windows/test_screenshot.c b/windows/test_screenshot.c new file mode 100644 index 00000000..1e3a20d7 --- /dev/null +++ b/windows/test_screenshot.c @@ -0,0 +1,45 @@ +#include "putty.h" + +static NORETURN PRINTF_LIKE(1, 2) void fatal_error(const char *p, ...) +{ + va_list ap; + fprintf(stderr, "screenshot: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + exit(1); +} + +void out_of_memory(void) { fatal_error("out of memory"); } + +int main(int argc, char **argv) +{ + const char *outfile = NULL; + + AuxMatchOpt amo = aux_match_opt_init(argc-1, argv+1, 0, fatal_error); + while (!aux_match_done(&amo)) { + char *val; + #define match_opt(...) aux_match_opt( \ + &amo, NULL, __VA_ARGS__, (const char *)NULL) + #define match_optval(...) aux_match_opt( \ + &amo, &val, __VA_ARGS__, (const char *)NULL) + + if (aux_match_arg(&amo, &val)) { + fatal_error("unexpected argument '%s'", val); + } else if (match_optval("-o", "--output")) { + outfile = val; + } else { + fatal_error("unrecognised option '%s'\n", amo.argv[amo.index]); + } + } + + if (!outfile) + fatal_error("expected an output file name"); + + char *err = save_screenshot(NULL, outfile); + if (err) + fatal_error("%s", err); + + return 0; +} diff --git a/windows/winucs.c b/windows/unicode.c index 9ffff5e9..b3f6d802 100644 --- a/windows/winucs.c +++ b/windows/unicode.c @@ -435,11 +435,116 @@ static const struct cp_list_item cp_list[] = { {0, 0} }; -static void link_font(WCHAR * line_tbl, WCHAR * font_tbl, WCHAR attr); +static void link_font(WCHAR *line_tbl, WCHAR *font_tbl, WCHAR attr); + +/* + * We keep a collection of reverse mappings from Unicode back to code pages, + * in the form of array[256] of array[256] of char. These live forever in a + * local tree234, and we just make a new one whenever we find a need. + */ +typedef struct reverse_mapping { + int codepage; + char **blocks; +} reverse_mapping; +static tree234 *reverse_mappings = NULL; + +static int reverse_mapping_cmp(void *av, void *bv) +{ + const reverse_mapping *a = (const reverse_mapping *)av; + const reverse_mapping *b = (const reverse_mapping *)bv; + if (a->codepage < b->codepage) + return -1; + if (a->codepage > b->codepage) + return +1; + return 0; +} + +static int reverse_mapping_find(void *av, void *bv) +{ + const reverse_mapping *a = (const reverse_mapping *)av; + int b_codepage = *(const int *)bv; + if (a->codepage < b_codepage) + return -1; + if (a->codepage > b_codepage) + return +1; + return 0; +} + +static reverse_mapping *get_existing_reverse_mapping(int codepage) +{ + if (!reverse_mappings) + return NULL; + return find234(reverse_mappings, &codepage, reverse_mapping_find); +} + +static reverse_mapping *make_reverse_mapping_inner( + int codepage, const wchar_t *mapping) +{ + if (!reverse_mappings) + reverse_mappings = newtree234(reverse_mapping_cmp); + + reverse_mapping *rmap = snew(reverse_mapping); + rmap->blocks = snewn(256, char *); + memset(rmap->blocks, 0, 256 * sizeof(char *)); + + for (size_t i = 0; i < 256; i++) { + /* These special kinds of value correspond to no Unicode character */ + if (DIRECT_CHAR(mapping[i])) + continue; + if (DIRECT_FONT(mapping[i])) + continue; + + size_t chr = mapping[i]; + size_t block = chr >> 8, index = chr & 0xFF; + + if (!rmap->blocks[block]) { + rmap->blocks[block] = snewn(256, char); + memset(rmap->blocks[block], 0, 256); + } + rmap->blocks[block][index] = i; + } + + rmap->codepage = codepage; + reverse_mapping *added = add234(reverse_mappings, rmap); + assert(added == rmap); /* we already checked it wasn't already in there */ + return added; +} + +static void make_reverse_mapping(int codepage, const wchar_t *mapping) +{ + if (get_existing_reverse_mapping(codepage)) + return; /* we've already got this one */ + make_reverse_mapping_inner(codepage, mapping); +} + +static reverse_mapping *get_reverse_mapping(int codepage) +{ + /* + * Try harder to get a reverse mapping for a codepage we implement + * internally via a translation table, by hastily making it if it doesn't + * already exist. + */ + + reverse_mapping *rmap = get_existing_reverse_mapping(codepage); + if (rmap) + return rmap; + + if (codepage < 65536) + return NULL; + if (codepage >= 65536 + lenof(cp_list)) + return NULL; + const struct cp_list_item *cp = &cp_list[codepage - 65536]; + if (!cp->cp_table) + return NULL; + + wchar_t mapping[256]; + get_unitab(codepage, mapping, 0); + return make_reverse_mapping_inner(codepage, mapping); +} void init_ucs(Conf *conf, struct unicode_data *ucsdata) { - int i, j; + int i; bool used_dtf = false; int vtmode; @@ -522,31 +627,9 @@ void init_ucs(Conf *conf, struct unicode_data *ucsdata) sizeof(unitab_xterm_std)); ucsdata->unitab_xterm['_'] = ' '; - /* Generate UCS ->line page table. */ - if (ucsdata->uni_tbl) { - for (i = 0; i < 256; i++) - if (ucsdata->uni_tbl[i]) - sfree(ucsdata->uni_tbl[i]); - sfree(ucsdata->uni_tbl); - ucsdata->uni_tbl = 0; - } if (!used_dtf) { - for (i = 0; i < 256; i++) { - if (DIRECT_CHAR(ucsdata->unitab_line[i])) - continue; - if (DIRECT_FONT(ucsdata->unitab_line[i])) - continue; - if (!ucsdata->uni_tbl) { - ucsdata->uni_tbl = snewn(256, char *); - memset(ucsdata->uni_tbl, 0, 256 * sizeof(char *)); - } - j = ((ucsdata->unitab_line[i] >> 8) & 0xFF); - if (!ucsdata->uni_tbl[j]) { - ucsdata->uni_tbl[j] = snewn(256, char); - memset(ucsdata->uni_tbl[j], 0, 256 * sizeof(char)); - } - ucsdata->uni_tbl[j][ucsdata->unitab_line[i] & 0xFF] = i; - } + /* Make sure a reverse mapping exists for this code page. */ + make_reverse_mapping(ucsdata->line_codepage, ucsdata->unitab_line); } /* Find the line control characters. */ @@ -598,7 +681,7 @@ void init_ucs(Conf *conf, struct unicode_data *ucsdata) for (i = 96; i < 127; i++) if (!DIRECT_FONT(ucsdata->unitab_xterm[i])) ucsdata->unitab_xterm[i] = - (WCHAR) (CSET_ACP + poorman_vt100[i - 96]); + (WCHAR) (CSET_ACP + poorman_vt100[i - 96]); for(i=128;i<256;i++) if (!DIRECT_FONT(ucsdata->unitab_scoacs[i])) ucsdata->unitab_scoacs[i] = @@ -606,7 +689,7 @@ void init_ucs(Conf *conf, struct unicode_data *ucsdata) } } -static void link_font(WCHAR * line_tbl, WCHAR * font_tbl, WCHAR attr) +static void link_font(WCHAR *line_tbl, WCHAR *font_tbl, WCHAR attr) { int font_index, line_index, i; for (line_index = 0; line_index < 256; line_index++) { @@ -641,11 +724,11 @@ wchar_t xlat_uskbd2cyrllic(int ch) 0x0440, 0x0448, 0x043e, 0x043b, 0x0434, 0x044c, 0x0442, 0x0449, 0x0437, 0x0439, 0x043a, 0x044b, 0x0435, 0x0433, 0x043c, 0x0446, 0x0447, 0x043d, 0x044f, 0x0425, 0x0407, 0x042a, 126, 127 - }; + }; return cyrtab[ch&0x7F]; } -int check_compose_internal(int first, int second, int recurse) +static int check_compose_internal(int first, int second, int recurse) { static const struct { @@ -1000,9 +1083,9 @@ int check_compose(int first, int second) return check_compose_internal(first, second, 0); } -int decode_codepage(char *cp_name) +int decode_codepage(const char *cp_name) { - char *s, *d; + const char *s, *d; const struct cp_list_item *cpi; int codepage = -1; CPINFO cpinfo; @@ -1117,7 +1200,7 @@ const char *cp_enumerate(int index) return cp_list[index].name; } -void get_unitab(int codepage, wchar_t * unitab, int ftype) +void get_unitab(int codepage, wchar_t *unitab, int ftype) { char tbuf[4]; int i, max = 256, flg = MB_ERR_INVALID_CHARS; @@ -1156,20 +1239,21 @@ void get_unitab(int codepage, wchar_t * unitab, int ftype) } int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen, - char *mbstr, int mblen, const char *defchr, - struct unicode_data *ucsdata) + char *mbstr, int mblen, const char *defchr) { - char *p; - int i; - if (ucsdata && codepage == ucsdata->line_codepage && ucsdata->uni_tbl) { + reverse_mapping *rmap = get_reverse_mapping(codepage); + + if (rmap) { /* Do this by array lookup if we can. */ if (wclen < 0) { for (wclen = 0; wcstr[wclen++] ;); /* will include the NUL */ } + char *p; + int i; for (p = mbstr, i = 0; i < wclen; i++) { wchar_t ch = wcstr[i]; int by; - char *p1; + const char *p1; #define WRITECH(chr) do \ { \ @@ -1177,8 +1261,7 @@ int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen, *p++ = (char)(chr); \ } while (0) - if (ucsdata->uni_tbl && - (p1 = ucsdata->uni_tbl[(ch >> 8) & 0xFF]) != NULL && + if ((p1 = rmap->blocks[(ch >> 8) & 0xFF]) != NULL && (by = p1[ch & 0xFF]) != '\0') WRITECH(by); else if (ch < 0x80) @@ -1195,16 +1278,122 @@ int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen, } return p - mbstr; } else { - int defused; - return WideCharToMultiByte(codepage, flags, wcstr, wclen, - mbstr, mblen, defchr, &defused); + int defused, ret; + ret = WideCharToMultiByte(codepage, flags, wcstr, wclen, + mbstr, mblen, defchr, &defused); + if (ret) + return ret; + +#ifdef LEGACY_WINDOWS + /* + * Fallback for legacy platforms too old to support UTF-8: if + * the codepage is UTF-8, we can do the translation ourselves. + */ + if (codepage == CP_UTF8 && mblen > 0 && wclen > 0) { + size_t remaining = mblen; + char *p = mbstr; + + while (wclen > 0) { + unsigned long wc = (wclen--, *wcstr++); + if (wclen > 0 && IS_SURROGATE_PAIR(wc, *wcstr)) { + wc = FROM_SURROGATES(wc, *wcstr); + wclen--, wcstr++; + } + + char utfbuf[6]; + size_t utflen = encode_utf8(utfbuf, wc); + if (utflen <= remaining) { + memcpy(p, utfbuf, utflen); + p += utflen; + remaining -= utflen; + } else { + return p - mbstr; + } + } + + return p - mbstr; + } +#endif + + /* No other fallbacks are available */ + return 0; } } int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen, wchar_t *wcstr, int wclen) { - return MultiByteToWideChar(codepage, flags, mbstr, mblen, wcstr, wclen); + if (codepage >= 65536) { + /* Character set not known to Windows, so we'll have to + * translate it ourself */ + size_t index = codepage - 65536; + if (index >= lenof(cp_list)) + return 0; + const struct cp_list_item *cp = &cp_list[index]; + if (!cp->cp_table) + return 0; + + size_t remaining = wclen; + wchar_t *p = wcstr; + unsigned tablebase = 256 - cp->cp_size; + + while (mblen > 0) { + mblen--; + unsigned c = 0xFF & *mbstr++; + wchar_t wc = (c < tablebase ? c : cp->cp_table[c - tablebase]); + if (remaining > 0) { + remaining--; + *p++ = wc; + } else { + return p - wcstr; + } + } + + return p - wcstr; + } + + int ret = MultiByteToWideChar(codepage, flags, mbstr, mblen, wcstr, wclen); + if (ret) + return ret; + +#ifdef LEGACY_WINDOWS + /* + * Fallback for legacy platforms too old to support UTF-8: if the + * codepage is UTF-8, we can do the translation ourselves. + */ + if (codepage == CP_UTF8 && mblen > 0 && wclen > 0) { + size_t remaining = wclen; + wchar_t *p = wcstr; + + while (mblen > 0) { + char utfbuf[7]; + int thissize = mblen < 6 ? mblen : 6; + memcpy(utfbuf, mbstr, thissize); + utfbuf[thissize] = '\0'; + + const char *utfptr = utfbuf; + wchar_t wcbuf[2]; + size_t nwc = decode_utf8_to_wchar(&utfptr, wcbuf); + + for (size_t i = 0; i < nwc; i++) { + if (remaining > 0) { + remaining--; + *p++ = wcbuf[i]; + } else { + return p - wcstr; + } + } + + mbstr += (utfptr - utfbuf); + mblen -= (utfptr - utfbuf); + } + + return p - wcstr; + } +#endif + + /* No other fallbacks are available */ + return 0; } bool is_dbcs_leadbyte(int codepage, char byte) diff --git a/windows/utils/agent_mutex_name.c b/windows/utils/agent_mutex_name.c new file mode 100644 index 00000000..949e8cfa --- /dev/null +++ b/windows/utils/agent_mutex_name.c @@ -0,0 +1,14 @@ +/* + * Return the full pathname of the global mutex that Pageant uses at + * startup to atomically decide whether to be a server or a client. + */ + +#include "putty.h" + +char *agent_mutex_name(void) +{ + char *username = get_username(); + char *mutexname = dupprintf("Local\\pageant-mutex.%s", username); + sfree(username); + return mutexname; +} diff --git a/windows/utils/agent_named_pipe_name.c b/windows/utils/agent_named_pipe_name.c new file mode 100644 index 00000000..aa64b3f6 --- /dev/null +++ b/windows/utils/agent_named_pipe_name.c @@ -0,0 +1,17 @@ +/* + * Return the full pathname of the named pipe Pageant will listen on. + * Used by both the Pageant server code and client code. + */ + +#include "putty.h" +#include "cryptoapi.h" + +char *agent_named_pipe_name(void) +{ + char *username = get_username(); + char *suffix = capi_obfuscate_string("Pageant"); + char *pipename = dupprintf("\\\\.\\pipe\\pageant.%s.%s", username, suffix); + sfree(username); + sfree(suffix); + return pipename; +} diff --git a/windows/utils/arm_arch_queries.c b/windows/utils/arm_arch_queries.c new file mode 100644 index 00000000..b0193276 --- /dev/null +++ b/windows/utils/arm_arch_queries.c @@ -0,0 +1,45 @@ +/* + * Windows implementation of the OS query functions that detect Arm + * architecture extensions. + */ + +#include "putty.h" +#include "ssh.h" + +#if !(defined _M_ARM || defined _M_ARM64) +/* + * For non-Arm, stub out these functions. This module shouldn't be + * _called_ in that situation anyway, but it will still be compiled + * (because that's easier than getting CMake to identify the + * architecture in all cases). + */ +#define IsProcessorFeaturePresent(...) false +#endif + +bool platform_aes_neon_available(void) +{ + return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); +} + +bool platform_pmull_neon_available(void) +{ + return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); +} + +bool platform_sha256_neon_available(void) +{ + return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); +} + +bool platform_sha1_neon_available(void) +{ + return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); +} + +bool platform_sha512_neon_available(void) +{ + /* As of 2020-12-24, as far as I can tell from docs.microsoft.com, + * Windows on Arm does not yet provide a PF_ARM_V8_* flag for the + * SHA-512 architecture extension. */ + return false; +} diff --git a/windows/utils/aux_match_opt.c b/windows/utils/aux_match_opt.c new file mode 100644 index 00000000..190eddac --- /dev/null +++ b/windows/utils/aux_match_opt.c @@ -0,0 +1,117 @@ +/* + * Helper function for matching command-line options in the Windows + * auxiliary tools (PuTTYgen and Pageant). + * + * Supports basically the usual kinds of option, including GNUish + * --foo long options and simple -f short options. But historically + * those tools have also supported long options with a single dash, so + * we don't go full GNU and report those as syntax errors. + */ + +#include "putty.h" + +/* + * Call this to initialise the state structure. + */ +AuxMatchOpt aux_match_opt_init(int argc, char **argv, int start_index, + aux_opt_error_fn_t opt_error) +{ + AuxMatchOpt amo[1]; + + amo->argc = argc; + amo->argv = argv; + amo->index = start_index; + amo->doing_opts = true; + amo->error = opt_error; + + return amo[0]; +} + +/* + * Call this with a NULL-terminated list of synonymous option names. + * Point 'val' at a char * to receive the option argument, if one is + * expected. Set 'val' to NULL if no argument is expected. + */ +bool aux_match_opt(AuxMatchOpt *amo, char **val, const char *optname, ...) +{ + assert(amo->index < amo->argc); + + /* Find the end of the option name */ + char *opt = amo->argv[amo->index]; + ptrlen argopt; + argopt.ptr = opt; + argopt.len = strcspn(opt, "="); + + /* Normalise GNU-style --foo long options to the -foo that this + * tool has historically used */ + ptrlen argopt2 = make_ptrlen(NULL, 0); + if (ptrlen_startswith(argopt, PTRLEN_LITERAL("--"), NULL)) + ptrlen_startswith(argopt, PTRLEN_LITERAL("-"), &argopt2); + + /* See if this option matches any of our synonyms */ + va_list ap; + va_start(ap, optname); + bool matched = false; + while (optname) { + if (ptrlen_eq_string(argopt, optname)) { + matched = true; + break; + } + if (argopt2.ptr && strlen(optname) > 2 && + ptrlen_eq_string(argopt2, optname)) { + matched = true; + break; + } + optname = va_arg(ap, const char *); + } + va_end(ap); + if (!matched) + return false; + + /* Check for a value */ + if (opt[argopt.len]) { + if (!val) + amo->error("option '%s' does not expect a value", opt); + *val = opt + argopt.len + 1; + amo->index++; + } else if (!val) { + amo->index++; + } else { + if (amo->index + 1 >= amo->argc) + amo->error("option '%s' expects a value", opt); + *val = amo->argv[amo->index + 1]; + amo->index += 2; + } + + return true; +} + +/* + * Call this to return a non-option argument in *val. + */ +bool aux_match_arg(AuxMatchOpt *amo, char **val) +{ + assert(amo->index < amo->argc); + char *opt = amo->argv[amo->index]; + + if (!amo->doing_opts || opt[0] != '-' || !strcmp(opt, "-")) { + *val = opt; + amo->index++; + return true; + } + + return false; +} + +/* + * And call this to test whether we're done. Also handles '--'. + */ +bool aux_match_done(AuxMatchOpt *amo) +{ + if (amo->index < amo->argc && !strcmp(amo->argv[amo->index], "--")) { + amo->doing_opts = false; + amo->index++; + } + + return amo->index >= amo->argc; +} diff --git a/windows/utils/centre_window.c b/windows/utils/centre_window.c new file mode 100644 index 00000000..651fc279 --- /dev/null +++ b/windows/utils/centre_window.c @@ -0,0 +1,20 @@ +/* + * Centre a window on the screen. Used to position the main config box. + */ + +#include "putty.h" + +void centre_window(HWND win) +{ + RECT rd, rw; + + if (!GetWindowRect(GetDesktopWindow(), &rd)) + return; + if (!GetWindowRect(win, &rw)) + return; + + MoveWindow(win, + (rd.right + rd.left + rw.left - rw.right) / 2, + (rd.bottom + rd.top + rw.top - rw.bottom) / 2, + rw.right - rw.left, rw.bottom - rw.top, true); +} diff --git a/windows/wincapi.c b/windows/utils/cryptoapi.c index de78988b..c3752c59 100644 --- a/windows/wincapi.c +++ b/windows/utils/cryptoapi.c @@ -1,15 +1,13 @@ /* - * wincapi.c: implementation of wincapi.h. + * windows/utils/cryptoapi.c: implementation of cryptoapi.h. */ #include "putty.h" -#if !defined NO_SECURITY - #include "putty.h" #include "ssh.h" -#include "wincapi.h" +#include "cryptoapi.h" DEF_WINDOWS_FUNCTION(CryptProtectMemory); @@ -89,5 +87,3 @@ char *capi_obfuscate_string(const char *realname) return dupstr(retbuf); } - -#endif /* !defined NO_SECURITY */ diff --git a/windows/windefs.c b/windows/utils/defaults.c index 006e8dc5..1a270009 100644 --- a/windows/windefs.c +++ b/windows/utils/defaults.c @@ -1,5 +1,5 @@ /* - * windefs.c: default settings that are specific to Windows. + * windows/utils/defaults.c: default settings that are specific to Windows. */ #include "putty.h" diff --git a/windows/utils/dll_hijacking_protection.c b/windows/utils/dll_hijacking_protection.c new file mode 100644 index 00000000..fe9ae59c --- /dev/null +++ b/windows/utils/dll_hijacking_protection.c @@ -0,0 +1,43 @@ +/* + * If the OS provides it, call SetDefaultDllDirectories() to prevent + * DLLs from being loaded from the directory containing our own + * binary, and instead only load from system32. + * + * This is a protection against hijacking attacks, if someone runs + * PuTTY directly from their web browser's download directory having + * previously been enticed into clicking on an unwise link that + * downloaded a malicious DLL to the same directory under one of + * various magic names that seem to be things that standard Windows + * DLLs delegate to. + * + * It shouldn't break deliberate loading of user-provided DLLs such as + * GSSAPI providers, because those are specified by their full + * pathname by the user-provided configuration. + */ + +#include "putty.h" + +void dll_hijacking_protection(void) +{ + static HMODULE kernel32_module; + DECL_WINDOWS_FUNCTION(static, BOOL, SetDefaultDllDirectories, (DWORD)); + + if (!kernel32_module) { + kernel32_module = load_system32_dll("kernel32.dll"); +#if !HAVE_SETDEFAULTDLLDIRECTORIES + /* For older Visual Studio, this function isn't available in + * the header files to type-check */ + GET_WINDOWS_FUNCTION_NO_TYPECHECK( + kernel32_module, SetDefaultDllDirectories); +#else + GET_WINDOWS_FUNCTION(kernel32_module, SetDefaultDllDirectories); +#endif + } + + if (p_SetDefaultDllDirectories) { + /* LOAD_LIBRARY_SEARCH_SYSTEM32 and explicitly specified + * directories only */ + p_SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32 | + LOAD_LIBRARY_SEARCH_USER_DIRS); + } +} diff --git a/windows/utils/dputs.c b/windows/utils/dputs.c new file mode 100644 index 00000000..f582509a --- /dev/null +++ b/windows/utils/dputs.c @@ -0,0 +1,39 @@ +/* + * Implementation of dputs() for Windows. + * + * The debug messages are written to STD_OUTPUT_HANDLE, except that + * first it has to make sure that handle _exists_, by calling + * AllocConsole first if necessary. + * + * They also go into a file called debug.log. + */ + +#include "putty.h" +#include "utils/utils.h" + +static HANDLE debug_fp = INVALID_HANDLE_VALUE; +static HANDLE debug_hdl = INVALID_HANDLE_VALUE; +static int debug_got_console = 0; + +void dputs(const char *buf) +{ + DWORD dw; + + if (!debug_got_console) { + if (AllocConsole()) { + debug_got_console = 1; + debug_hdl = GetStdHandle(STD_OUTPUT_HANDLE); + } + } + if (debug_fp == INVALID_HANDLE_VALUE) { + debug_fp = CreateFile("debug.log", GENERIC_WRITE, FILE_SHARE_READ, + NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + } + + if (debug_fp != INVALID_HANDLE_VALUE) { + WriteFile(debug_fp, buf, strlen(buf), &dw, NULL); + } + if (debug_hdl != INVALID_HANDLE_VALUE) { + WriteFile(debug_hdl, buf, strlen(buf), &dw, NULL); + } +} diff --git a/windows/utils/escape_registry_key.c b/windows/utils/escape_registry_key.c new file mode 100644 index 00000000..f5c9c19e --- /dev/null +++ b/windows/utils/escape_registry_key.c @@ -0,0 +1,48 @@ +/* + * Escaping/unescaping functions to translate between a saved session + * name, and the key name used to represent it in the Registry area + * where we store saved sessions. + * + * The basic technique is to %-escape characters we can't use in + * Registry keys. + */ + +#include "putty.h" + +void escape_registry_key(const char *in, strbuf *out) +{ + bool candot = false; + static const char hex[16] = "0123456789ABCDEF"; + + while (*in) { + if (*in == ' ' || *in == '\\' || *in == '*' || *in == '?' || + *in == '%' || *in < ' ' || *in > '~' || (*in == '.' + && !candot)) { + put_byte(out, '%'); + put_byte(out, hex[((unsigned char) *in) >> 4]); + put_byte(out, hex[((unsigned char) *in) & 15]); + } else + put_byte(out, *in); + in++; + candot = true; + } +} + +void unescape_registry_key(const char *in, strbuf *out) +{ + while (*in) { + if (*in == '%' && in[1] && in[2]) { + int i, j; + + i = in[1] - '0'; + i -= (i > 9 ? 7 : 0); + j = in[2] - '0'; + j -= (j > 9 ? 7 : 0); + + put_byte(out, (i << 4) + j); + in += 3; + } else { + put_byte(out, *in++); + } + } +} diff --git a/windows/utils/filename.c b/windows/utils/filename.c new file mode 100644 index 00000000..f4457ecb --- /dev/null +++ b/windows/utils/filename.c @@ -0,0 +1,54 @@ +/* + * Implementation of Filename for Windows. + */ + +#include "putty.h" + +Filename *filename_from_str(const char *str) +{ + Filename *ret = snew(Filename); + ret->path = dupstr(str); + return ret; +} + +Filename *filename_copy(const Filename *fn) +{ + return filename_from_str(fn->path); +} + +const char *filename_to_str(const Filename *fn) +{ + return fn->path; +} + +bool filename_equal(const Filename *f1, const Filename *f2) +{ + return !strcmp(f1->path, f2->path); +} + +bool filename_is_null(const Filename *fn) +{ + return !*fn->path; +} + +void filename_free(Filename *fn) +{ + sfree(fn->path); + sfree(fn); +} + +void filename_serialise(BinarySink *bs, const Filename *f) +{ + put_asciz(bs, f->path); +} +Filename *filename_deserialise(BinarySource *src) +{ + return filename_from_str(get_asciz(src)); +} + +char filename_char_sanitise(char c) +{ + if (strchr("<>:\"/\\|?*", c)) + return '.'; + return c; +} diff --git a/windows/utils/fontspec.c b/windows/utils/fontspec.c new file mode 100644 index 00000000..7e8d5175 --- /dev/null +++ b/windows/utils/fontspec.c @@ -0,0 +1,43 @@ +/* + * Implementation of FontSpec for Windows. + */ + +#include "putty.h" + +FontSpec *fontspec_new(const char *name, bool bold, int height, int charset) +{ + FontSpec *f = snew(FontSpec); + f->name = dupstr(name); + f->isbold = bold; + f->height = height; + f->charset = charset; + return f; +} + +FontSpec *fontspec_copy(const FontSpec *f) +{ + return fontspec_new(f->name, f->isbold, f->height, f->charset); +} + +void fontspec_free(FontSpec *f) +{ + sfree(f->name); + sfree(f); +} + +void fontspec_serialise(BinarySink *bs, FontSpec *f) +{ + put_asciz(bs, f->name); + put_uint32(bs, f->isbold); + put_uint32(bs, f->height); + put_uint32(bs, f->charset); +} + +FontSpec *fontspec_deserialise(BinarySource *src) +{ + const char *name = get_asciz(src); + unsigned isbold = get_uint32(src); + unsigned height = get_uint32(src); + unsigned charset = get_uint32(src); + return fontspec_new(name, isbold, height, charset); +} diff --git a/windows/utils/get_system_dir.c b/windows/utils/get_system_dir.c new file mode 100644 index 00000000..049cd7fc --- /dev/null +++ b/windows/utils/get_system_dir.c @@ -0,0 +1,21 @@ +/* + * Wrapper function around GetSystemDirectory that deals with + * allocating the output buffer, and also caches the result for future + * calls. + */ + +#include "putty.h" + +const char *get_system_dir(void) +{ + static char *sysdir = NULL; + static size_t sysdirsize = 0; + + if (!sysdir) { + size_t len; + while ((len = GetSystemDirectory(sysdir, sysdirsize)) >= sysdirsize) + sgrowarray(sysdir, sysdirsize, len); + } + + return sysdir; +} diff --git a/windows/utils/get_username.c b/windows/utils/get_username.c new file mode 100644 index 00000000..bc5780e4 --- /dev/null +++ b/windows/utils/get_username.c @@ -0,0 +1,77 @@ +/* + * Implementation of get_username() for Windows. + */ + +#include "putty.h" + +#ifndef SECURITY_WIN32 +#define SECURITY_WIN32 +#endif +#include <security.h> + +char *get_username(void) +{ + DWORD namelen; + char *user; + bool got_username = false; + DECL_WINDOWS_FUNCTION(static, BOOLEAN, GetUserNameExA, + (EXTENDED_NAME_FORMAT, LPSTR, PULONG)); + + { + static bool tried_usernameex = false; + if (!tried_usernameex) { + /* Not available on Win9x, so load dynamically */ + HMODULE secur32 = load_system32_dll("secur32.dll"); + /* If MIT Kerberos is installed, the following call to + GET_WINDOWS_FUNCTION makes Windows implicitly load + sspicli.dll WITHOUT proper path sanitizing, so better + load it properly before */ + HMODULE sspicli = load_system32_dll("sspicli.dll"); + (void)sspicli; /* squash compiler warning about unused variable */ + GET_WINDOWS_FUNCTION(secur32, GetUserNameExA); + tried_usernameex = true; + } + } + + if (p_GetUserNameExA) { + /* + * If available, use the principal -- this avoids the problem + * that the local username is case-insensitive but Kerberos + * usernames are case-sensitive. + */ + + /* Get the length */ + namelen = 0; + (void) p_GetUserNameExA(NameUserPrincipal, NULL, &namelen); + + user = snewn(namelen, char); + got_username = p_GetUserNameExA(NameUserPrincipal, user, &namelen); + if (got_username) { + char *p = strchr(user, '@'); + if (p) *p = 0; + } else { + sfree(user); + } + } + + if (!got_username) { + /* Fall back to local user name */ + namelen = 0; + if (!GetUserName(NULL, &namelen)) { + /* + * Apparently this doesn't work at least on Windows XP SP2. + * Thus assume a maximum of 256. It will fail again if it + * doesn't fit. + */ + namelen = 256; + } + + user = snewn(namelen, char); + got_username = GetUserName(user, &namelen); + if (!got_username) { + sfree(user); + } + } + + return got_username ? user : NULL; +} diff --git a/windows/utils/getdlgitemtext_alloc.c b/windows/utils/getdlgitemtext_alloc.c new file mode 100644 index 00000000..f0244d71 --- /dev/null +++ b/windows/utils/getdlgitemtext_alloc.c @@ -0,0 +1,20 @@ +/* + * Handy wrapper around GetDlgItemText which doesn't make you invent + * an arbitrary length limit on the output string. Returned string is + * dynamically allocated; caller must free. + */ + +#include "putty.h" + +char *GetDlgItemText_alloc(HWND hwnd, int id) +{ + char *ret = NULL; + size_t size = 0; + + do { + sgrowarray_nm(ret, size, size); + GetDlgItemText(hwnd, id, ret, size); + } while (!memchr(ret, '\0', size-1)); + + return ret; +} diff --git a/windows/utils/interprocess_mutex.c b/windows/utils/interprocess_mutex.c new file mode 100644 index 00000000..8612a298 --- /dev/null +++ b/windows/utils/interprocess_mutex.c @@ -0,0 +1,48 @@ +/* + * Lock and unlock a mutex with a globally visible name. Used to + * synchronise between unrelated processes, such as two + * connection-sharing PuTTYs deciding which will be the upstream. + */ + +#include "putty.h" +#include "security-api.h" + +HANDLE lock_interprocess_mutex(const char *mutexname, char **error) +{ + PSECURITY_DESCRIPTOR psd = NULL; + PACL acl = NULL; + HANDLE mutex = NULL; + + if (should_have_security() && !make_private_security_descriptor( + MUTEX_ALL_ACCESS, &psd, &acl, error)) + goto out; + + SECURITY_ATTRIBUTES sa; + memset(&sa, 0, sizeof(sa)); + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = psd; + sa.bInheritHandle = false; + + mutex = CreateMutex(&sa, false, mutexname); + if (!mutex) { + *error = dupprintf("CreateMutex(\"%s\") failed: %s", + mutexname, win_strerror(GetLastError())); + goto out; + } + + WaitForSingleObject(mutex, INFINITE); + + out: + if (psd) + LocalFree(psd); + if (acl) + LocalFree(acl); + + return mutex; +} + +void unlock_interprocess_mutex(HANDLE mutex) +{ + ReleaseMutex(mutex); + CloseHandle(mutex); +} diff --git a/windows/utils/is_console_handle.c b/windows/utils/is_console_handle.c new file mode 100644 index 00000000..887069c9 --- /dev/null +++ b/windows/utils/is_console_handle.c @@ -0,0 +1,13 @@ +/* + * Determine whether a Windows HANDLE points at a console device. + */ + +#include "putty.h" + +bool is_console_handle(HANDLE handle) +{ + DWORD ignored_output; + if (GetConsoleMode(handle, &ignored_output)) + return true; + return false; +} diff --git a/windows/utils/load_system32_dll.c b/windows/utils/load_system32_dll.c new file mode 100644 index 00000000..d227a264 --- /dev/null +++ b/windows/utils/load_system32_dll.c @@ -0,0 +1,18 @@ +/* + * Wrapper function to load a DLL out of c:\windows\system32 without + * going through the full DLL search path. (Hence no attack is + * possible by placing a substitute DLL earlier on that path.) + */ + +#include "putty.h" + +HMODULE load_system32_dll(const char *libname) +{ + char *fullpath; + HMODULE ret; + + fullpath = dupcat(get_system_dir(), "\\", libname); + ret = LoadLibrary(fullpath); + sfree(fullpath); + return ret; +} diff --git a/windows/wintime.c b/windows/utils/ltime.c index 5fa3b0de..d4364509 100644 --- a/windows/wintime.c +++ b/windows/utils/ltime.c @@ -1,5 +1,6 @@ /* - * wintime.c - Avoid trouble with time() returning (time_t)-1 on Windows. + * Implementation of ltime() that avoids trouble with time() returning + * (time_t)-1 on Windows. */ #include "putty.h" diff --git a/windows/utils/make_spr_sw_abort_winerror.c b/windows/utils/make_spr_sw_abort_winerror.c new file mode 100644 index 00000000..b05ef61f --- /dev/null +++ b/windows/utils/make_spr_sw_abort_winerror.c @@ -0,0 +1,22 @@ +/* + * Constructor function for a SeatPromptResult of the 'software abort' + * category, whose error message includes the translation of an OS + * error code. + */ + +#include "putty.h" + +static void spr_winerror_errfn(SeatPromptResult spr, BinarySink *bs) +{ + put_fmt(bs, "%s: %s", spr.errdata_lit, win_strerror(spr.errdata_u)); +} + +SeatPromptResult make_spr_sw_abort_winerror(const char *prefix, DWORD error) +{ + SeatPromptResult spr; + spr.kind = SPRK_SW_ABORT; + spr.errfn = spr_winerror_errfn; + spr.errdata_lit = prefix; + spr.errdata_u = error; + return spr; +} diff --git a/windows/utils/makedlgitemborderless.c b/windows/utils/makedlgitemborderless.c new file mode 100644 index 00000000..53975d06 --- /dev/null +++ b/windows/utils/makedlgitemborderless.c @@ -0,0 +1,19 @@ +/* + * Helper function to remove the border around a dialog item such as + * a read-only edit control. + */ + +#include "putty.h" + +void MakeDlgItemBorderless(HWND parent, int id) +{ + HWND child = GetDlgItem(parent, id); + LONG_PTR style = GetWindowLongPtr(child, GWL_STYLE); + LONG_PTR exstyle = GetWindowLongPtr(child, GWL_EXSTYLE); + style &= ~WS_BORDER; + exstyle &= ~(WS_EX_CLIENTEDGE | WS_EX_STATICEDGE | WS_EX_WINDOWEDGE); + SetWindowLongPtr(child, GWL_STYLE, style); + SetWindowLongPtr(child, GWL_EXSTYLE, exstyle); + SetWindowPos(child, NULL, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); +} diff --git a/windows/utils/message_box.c b/windows/utils/message_box.c new file mode 100644 index 00000000..ae78de4a --- /dev/null +++ b/windows/utils/message_box.c @@ -0,0 +1,49 @@ +/* + * Message box with optional context help. + */ + +#include "putty.h" + +static HWND message_box_owner; + +/* Callback function to launch context help. */ +static VOID CALLBACK message_box_help_callback(LPHELPINFO lpHelpInfo) +{ + const char *context = NULL; +#define CHECK_CTX(name) \ + do { \ + if (lpHelpInfo->dwContextId == WINHELP_CTXID_ ## name) \ + context = WINHELP_CTX_ ## name; \ + } while (0) + CHECK_CTX(errors_hostkey_absent); + CHECK_CTX(errors_hostkey_changed); + CHECK_CTX(errors_cantloadkey); + CHECK_CTX(option_cleanup); + CHECK_CTX(pgp_fingerprints); +#undef CHECK_CTX + if (context) + launch_help(message_box_owner, context); +} + +int message_box(HWND owner, LPCTSTR text, LPCTSTR caption, + DWORD style, DWORD helpctxid) +{ + MSGBOXPARAMS mbox; + + /* + * We use MessageBoxIndirect() because it allows us to specify a + * callback function for the Help button. + */ + mbox.cbSize = sizeof(mbox); + /* Assumes the globals `hinst' and `hwnd' have sensible values. */ + mbox.hInstance = hinst; + mbox.hwndOwner = message_box_owner = owner; + mbox.lpfnMsgBoxCallback = &message_box_help_callback; + mbox.dwLanguageId = LANG_NEUTRAL; + mbox.lpszText = text; + mbox.lpszCaption = caption; + mbox.dwContextHelpId = helpctxid; + mbox.dwStyle = style; + if (helpctxid != 0 && has_help()) mbox.dwStyle |= MB_HELP; + return MessageBoxIndirect(&mbox); +} diff --git a/windows/winmiscs.c b/windows/utils/minefield.c index 571a9122..c5262ae6 100644 --- a/windows/winmiscs.c +++ b/windows/utils/minefield.c @@ -1,25 +1,17 @@ /* - * winmiscs.c: Windows-specific standalone functions. Has the same - * relationship to winmisc.c that utils.c does to misc.c, but the - * corresponding name 'winutils.c' was already taken. + * 'Minefield' - a crude Windows memory debugger, similar in concept + * to the old Unix 'Electric Fence'. The main difference is that + * Electric Fence can be imposed on a program from outside, via + * LD_PRELOAD, whereas this has to be included in the program at + * compile time with its own cooperation. + * + * This module provides the Minefield allocator. Actually enabling it + * is done by a #define in force when the main utils/memory.c is + * compiled. */ #include "putty.h" - -#ifndef NO_SECUREZEROMEMORY -/* - * Windows implementation of smemclr (see misc.c) using SecureZeroMemory. - */ -void smemclr(void *b, size_t n) { - if (b && n > 0) - SecureZeroMemory(b, n); -} -#endif - -#ifdef MINEFIELD -/* - * Minefield - a Windows equivalent for Electric Fence - */ +#include "puttymem.h" #define PAGESIZE 4096 @@ -234,52 +226,3 @@ void *minefield_c_realloc(void *p, size_t size) minefield_free(p); return q; } - -#endif /* MINEFIELD */ - -#if defined _MSC_VER && _MSC_VER < 1800 - -/* - * Work around lack of strtoumax in older MSVC libraries - */ -uintmax_t strtoumax(const char *nptr, char **endptr, int base) -{ - return _strtoui64(nptr, endptr, base); -} - -#endif - -#if defined _M_ARM || defined _M_ARM64 - -bool platform_aes_hw_available(void) -{ - return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); -} - -bool platform_sha256_hw_available(void) -{ - return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); -} - -bool platform_sha1_hw_available(void) -{ - return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); -} - -bool platform_sha512_hw_available(void) -{ - /* As of 2020-12-24, as far as I can tell from docs.microsoft.com, - * Windows on Arm does not yet provide a PF_ARM_V8_* flag for the - * SHA-512 architecture extension. */ - return false; -} - -#endif - -bool is_console_handle(HANDLE handle) -{ - DWORD ignored_output; - if (GetConsoleMode(handle, &ignored_output)) - return true; - return false; -} diff --git a/windows/utils/open_for_write_would_lose_data.c b/windows/utils/open_for_write_would_lose_data.c new file mode 100644 index 00000000..2aef5c5a --- /dev/null +++ b/windows/utils/open_for_write_would_lose_data.c @@ -0,0 +1,76 @@ +/* + * Implementation of open_for_write_would_lose_data for Windows. + */ + +#include "putty.h" + +/* + * This is slightly fiddly because we want to be backwards-compatible + * with systems too old to have GetFileAttributesEx. The next best + * thing is FindFirstFile, which will return a different data + * structure, but one that also contains the fields we want. (But it + * will behave more unhelpfully - for this application - in the + * presence of wildcards, so we'd prefer to use GFAE if we can.) + */ + +static inline bool open_for_write_would_lose_data_impl( + DWORD dwFileAttributes, DWORD nFileSizeHigh, DWORD nFileSizeLow) +{ + if (dwFileAttributes & (FILE_ATTRIBUTE_DEVICE|FILE_ATTRIBUTE_DIRECTORY)) { + /* + * File is something other than an ordinary disk file, so + * opening it for writing will not cause truncation. (It may + * not _succeed_ either, but that's not our problem here!) + */ + return false; + } + if (nFileSizeHigh == 0 && nFileSizeLow == 0) { + /* + * File is zero-length (or may be a named pipe, which + * dwFileAttributes can't tell apart from a regular file), so + * opening it for writing won't truncate any data away because + * there's nothing to truncate anyway. + */ + return false; + } + return true; +} + +bool open_for_write_would_lose_data(const Filename *fn) +{ + static HMODULE kernel32_module; + DECL_WINDOWS_FUNCTION(static, BOOL, GetFileAttributesExA, + (LPCSTR, GET_FILEEX_INFO_LEVELS, LPVOID)); + + if (!kernel32_module) { + kernel32_module = load_system32_dll("kernel32.dll"); + GET_WINDOWS_FUNCTION(kernel32_module, GetFileAttributesExA); + } + + if (p_GetFileAttributesExA) { + WIN32_FILE_ATTRIBUTE_DATA attrs; + if (!p_GetFileAttributesExA(fn->path, GetFileExInfoStandard, &attrs)) { + /* + * Generally, if we don't identify a specific reason why we + * should return true from this function, we return false, and + * let the subsequent attempt to open the file for real give a + * more useful error message. + */ + return false; + } + return open_for_write_would_lose_data_impl( + attrs.dwFileAttributes, attrs.nFileSizeHigh, attrs.nFileSizeLow); + } else { + WIN32_FIND_DATA fd; + HANDLE h = FindFirstFile(fn->path, &fd); + if (h == INVALID_HANDLE_VALUE) { + /* + * As above, if we can't find the file at all, return false. + */ + return false; + } + CloseHandle(h); + return open_for_write_would_lose_data_impl( + fd.dwFileAttributes, fd.nFileSizeHigh, fd.nFileSizeLow); + } +} diff --git a/windows/utils/pgp_fingerprints_msgbox.c b/windows/utils/pgp_fingerprints_msgbox.c new file mode 100644 index 00000000..6618de82 --- /dev/null +++ b/windows/utils/pgp_fingerprints_msgbox.c @@ -0,0 +1,25 @@ +/* + * Display the fingerprints of the PGP Master Keys to the user as a + * GUI message box. + */ + +#include "putty.h" + +void pgp_fingerprints_msgbox(HWND owner) +{ + message_box( + owner, + "These are the fingerprints of the PuTTY PGP Master Keys. They can\n" + "be used to establish a trust path from this executable to another\n" + "one. See the manual for more information.\n" + "(Note: these fingerprints have nothing to do with SSH!)\n" + "\n" + "PuTTY Master Key as of " PGP_MASTER_KEY_YEAR + " (" PGP_MASTER_KEY_DETAILS "):\n" + " " PGP_MASTER_KEY_FP "\n\n" + "Previous Master Key (" PGP_PREV_MASTER_KEY_YEAR + ", " PGP_PREV_MASTER_KEY_DETAILS "):\n" + " " PGP_PREV_MASTER_KEY_FP, + "PGP fingerprints", MB_ICONINFORMATION | MB_OK, + HELPCTXID(pgp_fingerprints)); +} diff --git a/windows/utils/platform_get_x_display.c b/windows/utils/platform_get_x_display.c new file mode 100644 index 00000000..e2a9ddc6 --- /dev/null +++ b/windows/utils/platform_get_x_display.c @@ -0,0 +1,13 @@ +/* + * Implementation of platform_get_x_display for Windows, common to all + * tools. + */ + +#include "putty.h" +#include "ssh.h" + +char *platform_get_x_display(void) +{ + /* We may as well check for DISPLAY in case it's useful. */ + return dupstr(getenv("DISPLAY")); +} diff --git a/windows/utils/registry.c b/windows/utils/registry.c new file mode 100644 index 00000000..1f50e67a --- /dev/null +++ b/windows/utils/registry.c @@ -0,0 +1,184 @@ +/* + * Implement convenience wrappers on the awkward low-level functions + * for accessing the Windows registry. + */ + +#include "putty.h" + +HKEY open_regkey_fn(bool create, HKEY hk, const char *path, ...) +{ + HKEY toret = NULL; + bool hk_needs_close = false; + va_list ap; + va_start(ap, path); + + for (; path; path = va_arg(ap, const char *)) { + HKEY hk_sub = NULL; + + LONG status; + if (create) + status = RegCreateKeyEx( + hk, path, 0, NULL, REG_OPTION_NON_VOLATILE, + KEY_READ | KEY_WRITE, NULL, &hk_sub, NULL); + else + status = RegOpenKeyEx( + hk, path, 0, KEY_READ | KEY_WRITE, &hk_sub); + + if (status != ERROR_SUCCESS) + goto out; + + if (hk_needs_close) + RegCloseKey(hk); + hk = hk_sub; + hk_needs_close = true; + } + + toret = hk; + hk = NULL; + hk_needs_close = false; + + out: + va_end(ap); + if (hk_needs_close) + RegCloseKey(hk); + return toret; +} + +void close_regkey(HKEY key) +{ + RegCloseKey(key); +} + +void del_regkey(HKEY key, const char *name) +{ + RegDeleteKey(key, name); +} + +char *enum_regkey(HKEY key, int index) +{ + size_t regbuf_size = MAX_PATH + 1; + char *regbuf = snewn(regbuf_size, char); + + while (1) { + LONG status = RegEnumKey(key, index, regbuf, regbuf_size); + if (status == ERROR_SUCCESS) + return regbuf; + if (status != ERROR_MORE_DATA) { + sfree(regbuf); + return NULL; + } + sgrowarray(regbuf, regbuf_size, regbuf_size); + } +} + +bool get_reg_dword(HKEY key, const char *name, DWORD *out) +{ + DWORD type, size; + size = sizeof(*out); + + if (RegQueryValueEx(key, name, 0, &type, + (BYTE *)out, &size) != ERROR_SUCCESS || + size != sizeof(*out) || type != REG_DWORD) + return false; + else + return true; +} + +bool put_reg_dword(HKEY key, const char *name, DWORD value) +{ + return RegSetValueEx(key, name, 0, REG_DWORD, (CONST BYTE *) &value, + sizeof(value)) == ERROR_SUCCESS; +} + +char *get_reg_sz(HKEY key, const char *name) +{ + DWORD type, size; + + if (RegQueryValueEx(key, name, 0, &type, NULL, + &size) != ERROR_SUCCESS || type != REG_SZ) + return NULL; /* not a string */ + + size_t allocsize = size+1; /* allow for an extra NUL if needed */ + char *toret = snewn(allocsize, char); + if (RegQueryValueEx(key, name, 0, &type, (BYTE *)toret, + &size) != ERROR_SUCCESS || type != REG_SZ) { + sfree(toret); + return NULL; + } + assert(size < allocsize); + toret[size] = '\0'; /* add an extra NUL in case RegQueryValueEx + * didn't supply one */ + + return toret; +} + +bool put_reg_sz(HKEY key, const char *name, const char *str) +{ + /* You have to store the trailing NUL as well */ + return RegSetValueEx(key, name, 0, REG_SZ, (CONST BYTE *)str, + 1 + strlen(str)) == ERROR_SUCCESS; +} + +/* + * REG_MULTI_SZ items are stored as a concatenation of NUL-terminated + * strings, terminated in turn with an empty string, i.e. a second + * consecutive NUL. + * + * We represent these in their storage format, as a strbuf - but + * *without* the second consecutive NUL. + * + * So you can build up a new MULTI_SZ value in a strbuf by calling + * put_asciz once per output string and then put_reg_multi_sz; and you + * can consume one by initialising a BinarySource to the result of + * get_reg_multi_sz, and then calling get_asciz on it and assuming + * that !get_err(src) means you have a real output string. + * + * Also, calling strbuf_to_str on one of these will give you back a + * bare 'char *' with the same double-NUL termination, to pass back to + * a caller. + */ +strbuf *get_reg_multi_sz(HKEY key, const char *name) +{ + DWORD type, size; + + if (RegQueryValueEx(key, name, 0, &type, NULL, + &size) != ERROR_SUCCESS || type != REG_MULTI_SZ) + return NULL; /* not a string */ + + strbuf *toret = strbuf_new(); + void *ptr = strbuf_append(toret, (size_t)size + 2); + if (RegQueryValueEx(key, name, 0, &type, (BYTE *)ptr, + &size) != ERROR_SUCCESS || type != REG_MULTI_SZ) { + strbuf_free(toret); + return NULL; + } + strbuf_shrink_to(toret, size); + /* Ensure we end with exactly one \0 */ + while (strbuf_chomp(toret, '\0')); + put_byte(toret, '\0'); + return toret; +} + +bool put_reg_multi_sz(HKEY key, const char *name, strbuf *str) +{ + /* + * Of course, to write our string list into the registry, we _do_ + * have to include both trailing NULs. But this is easy, because a + * strbuf is also designed to hold a single string and make it + * conveniently accessible in NUL-terminated form, so it stores a + * NUL in its buffer just beyond its formal length. So we just + * include that extra byte in the data we write. + */ + return RegSetValueEx(key, name, 0, REG_MULTI_SZ, (CONST BYTE *)str->s, + str->len + 1) == ERROR_SUCCESS; +} + +char *get_reg_sz_simple(HKEY key, const char *name, const char *leaf) +{ + HKEY subkey = open_regkey(false, key, name); + if (!subkey) + return NULL; + char *toret = get_reg_sz(subkey, leaf); + RegCloseKey(subkey); + return toret; +} diff --git a/windows/utils/request_file.c b/windows/utils/request_file.c new file mode 100644 index 00000000..dd2cab18 --- /dev/null +++ b/windows/utils/request_file.c @@ -0,0 +1,71 @@ +/* + * GetOpenFileName/GetSaveFileName tend to muck around with the process' + * working directory on at least some versions of Windows. + * Here's a wrapper that gives more control over this, and hides a little + * bit of other grottiness. + */ + +#include "putty.h" + +struct filereq_tag { + TCHAR cwd[MAX_PATH]; +}; + +/* + * `of' is expected to be initialised with most interesting fields, but + * this function does some administrivia. (assume `of' was memset to 0) + * save==1 -> GetSaveFileName; save==0 -> GetOpenFileName + * `state' is optional. + */ +bool request_file(filereq *state, OPENFILENAME *of, bool preserve, bool save) +{ + TCHAR cwd[MAX_PATH]; /* process CWD */ + bool ret; + + /* Get process CWD */ + if (preserve) { + DWORD r = GetCurrentDirectory(lenof(cwd), cwd); + if (r == 0 || r >= lenof(cwd)) + /* Didn't work, oh well. Stop trying to be clever. */ + preserve = false; + } + + /* Open the file requester, maybe setting lpstrInitialDir */ + { +#ifdef OPENFILENAME_SIZE_VERSION_400 + of->lStructSize = OPENFILENAME_SIZE_VERSION_400; +#else + of->lStructSize = sizeof(*of); +#endif + of->lpstrInitialDir = (state && state->cwd[0]) ? state->cwd : NULL; + /* Actually put up the requester. */ + ret = save ? GetSaveFileName(of) : GetOpenFileName(of); + } + + /* Get CWD left by requester */ + if (state) { + DWORD r = GetCurrentDirectory(lenof(state->cwd), state->cwd); + if (r == 0 || r >= lenof(state->cwd)) + /* Didn't work, oh well. */ + state->cwd[0] = '\0'; + } + + /* Restore process CWD */ + if (preserve) + /* If it fails, there's not much we can do. */ + (void) SetCurrentDirectory(cwd); + + return ret; +} + +filereq *filereq_new(void) +{ + filereq *ret = snew(filereq); + ret->cwd[0] = '\0'; + return ret; +} + +void filereq_free(filereq *state) +{ + sfree(state); +} diff --git a/windows/utils/screenshot.c b/windows/utils/screenshot.c new file mode 100644 index 00000000..777520fd --- /dev/null +++ b/windows/utils/screenshot.c @@ -0,0 +1,126 @@ +#include "putty.h" + +#if HAVE_DWMAPI_H + +#include <dwmapi.h> + +char *save_screenshot(HWND hwnd, const char *outfile) +{ + HDC dcWindow = NULL, dcSave = NULL; + HBITMAP bmSave = NULL; + uint8_t *buffer = NULL; + char *err = NULL; + + static HMODULE dwmapi_module; + DECL_WINDOWS_FUNCTION(static, HRESULT, DwmGetWindowAttribute, + (HWND, DWORD, PVOID, DWORD)); + + if (!dwmapi_module) { + dwmapi_module = load_system32_dll("dwmapi.dll"); + GET_WINDOWS_FUNCTION(dwmapi_module, DwmGetWindowAttribute); + } + + dcWindow = GetDC(NULL); + if (!dcWindow) { + err = dupprintf("GetDC(window): %s", win_strerror(GetLastError())); + goto out; + } + + int x, y, w, h; + RECT wr; + + /* Use DwmGetWindowAttribute in place of GetWindowRect to exclude + * drop shadow, otherwise we get a load of unwanted desktop + * background under the shadow */ + if (p_DwmGetWindowAttribute && + 0 <= p_DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, + &wr, sizeof(wr))) { + x = wr.left; + y = wr.top; + w = wr.right - wr.left; + h = wr.bottom - wr.top; + } else { + BITMAP bmhdr; + memset(&bmhdr, 0, sizeof(bmhdr)); + GetObject(GetCurrentObject(dcWindow, OBJ_BITMAP), + sizeof(bmhdr), &bmhdr); + x = y = 0; + w = bmhdr.bmWidth; + h = bmhdr.bmHeight; + } + + dcSave = CreateCompatibleDC(dcWindow); + if (!dcSave) { + err = dupprintf("CreateCompatibleDC(desktop window dc): %s", + win_strerror(GetLastError())); + goto out; + } + + bmSave = CreateCompatibleBitmap(dcWindow, w, h); + if (!bmSave) { + err = dupprintf("CreateCompatibleBitmap: %s", + win_strerror(GetLastError())); + goto out; + } + + if (!SelectObject(dcSave, bmSave)) { + err = dupprintf("SelectObject: %s", win_strerror(GetLastError())); + goto out; + } + + if (!BitBlt(dcSave, 0, 0, w, h, dcWindow, x, y, SRCCOPY)) { + err = dupprintf("BitBlt: %s", win_strerror(GetLastError())); + goto out; + } + + BITMAPINFO bmInfo; + memset(&bmInfo, 0, sizeof(bmInfo)); + bmInfo.bmiHeader.biSize = sizeof(bmInfo.bmiHeader); + bmInfo.bmiHeader.biWidth = w; + bmInfo.bmiHeader.biHeight = h; + bmInfo.bmiHeader.biPlanes = 1; + bmInfo.bmiHeader.biBitCount = 32; + bmInfo.bmiHeader.biCompression = BI_RGB; + + size_t bmPixels = (size_t)w*h, bmBytes = bmPixels * 4; + buffer = snewn(bmBytes, uint8_t); + + if (!GetDIBits(dcWindow, bmSave, 0, h, buffer, &bmInfo, DIB_RGB_COLORS)) + err = dupprintf("GetDIBits (get data): %s", + win_strerror(GetLastError())); + + FILE *fp = fopen(outfile, "wb"); + if (!fp) { + err = dupprintf("'%s': unable to open file", outfile); + goto out; + } + + BITMAPFILEHEADER bmFileHdr; + bmFileHdr.bfType = 'B' | ('M' << 8); + bmFileHdr.bfSize = sizeof(bmFileHdr) + sizeof(bmInfo.bmiHeader) + bmBytes; + bmFileHdr.bfOffBits = sizeof(bmFileHdr) + sizeof(bmInfo.bmiHeader); + fwrite((void *)&bmFileHdr, 1, sizeof(bmFileHdr), fp); + fwrite((void *)&bmInfo.bmiHeader, 1, sizeof(bmInfo.bmiHeader), fp); + fwrite((void *)buffer, 1, bmBytes, fp); + fclose(fp); + + out: + if (dcWindow) + ReleaseDC(NULL, dcWindow); + if (bmSave) + DeleteObject(bmSave); + if (dcSave) + DeleteObject(dcSave); + sfree(buffer); + return err; +} + +#else /* HAVE_DWMAPI_H */ + +/* Without <dwmapi.h> we can't get the right window rectangle */ +char *save_screenshot(HWND hwnd, const char *outfile) +{ + return dupstr("Demo screenshots not compiled in to this build"); +} + +#endif /* HAVE_DWMAPI_H */ diff --git a/windows/winsecur.c b/windows/utils/security.c index a1164af5..52299420 100644 --- a/windows/winsecur.c +++ b/windows/utils/security.c @@ -1,5 +1,5 @@ /* - * winsecur.c: implementation of winsecur.h. + * windows/utils/security.c: implementation of security-api.h. */ #include <stdio.h> @@ -7,9 +7,7 @@ #include "putty.h" -#if !defined NO_SECURITY - -#include "winsecur.h" +#include "security-api.h" /* Initialised once, then kept around to reuse forever */ static PSID worldsid, networksid, usersid; @@ -22,6 +20,20 @@ DEF_WINDOWS_FUNCTION(GetSecurityInfo); DEF_WINDOWS_FUNCTION(SetSecurityInfo); DEF_WINDOWS_FUNCTION(SetEntriesInAclA); +bool should_have_security(void) +{ +#ifdef LEGACY_WINDOWS + /* Legacy pre-NT platforms are not expected to have any of these APIs */ + init_winver(); + return (osPlatformId == VER_PLATFORM_WIN32_NT); +#else + /* In the up-to-date PuTTY builds which do not support those + * platforms, unconditionally return true, to minimise the risk of + * compiling out security checks. */ + return true; +#endif +} + bool got_advapi(void) { static bool attempted = false; @@ -143,7 +155,7 @@ static bool getsids(char **error) ret = true; - cleanup: + cleanup: return ret; } @@ -163,7 +175,7 @@ bool make_private_security_descriptor(DWORD permissions, *error = NULL; if (!getsids(error)) - goto cleanup; + goto cleanup; memset(ea, 0, sizeof(ea)); ea[0].grfAccessPermissions = permissions; @@ -278,10 +290,10 @@ static bool really_restrict_process_acl(char **error) goto cleanup; } - if (ERROR_SUCCESS != p_SetSecurityInfo - (GetCurrentProcess(), SE_KERNEL_OBJECT, - OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, - usersid, NULL, acl, NULL)) { + if (ERROR_SUCCESS != p_SetSecurityInfo( + GetCurrentProcess(), SE_KERNEL_OBJECT, + OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, + usersid, NULL, acl, NULL)) { *error = dupprintf("Unable to set process ACL: %s", win_strerror(GetLastError())); goto cleanup; @@ -299,7 +311,6 @@ static bool really_restrict_process_acl(char **error) } return ret; } -#endif /* !defined NO_SECURITY */ /* * Lock down our process's ACL, to present an obstacle to malware @@ -323,12 +334,7 @@ void restrict_process_acl(void) char *error = NULL; bool ret; -#if !defined NO_SECURITY ret = really_restrict_process_acl(&error); -#else - ret = false; - error = dupstr("ACL restrictions not compiled into this binary"); -#endif if (!ret) modalfatalbox("Could not restrict process ACL: %s", error); } diff --git a/windows/utils/shinydialogbox.c b/windows/utils/shinydialogbox.c new file mode 100644 index 00000000..ab3201c1 --- /dev/null +++ b/windows/utils/shinydialogbox.c @@ -0,0 +1,111 @@ +/* + * PuTTY's own reimplementation of DialogBox() and EndDialog() which + * provide extra capabilities. + * + * Originally introduced in 2003 to work around a problem with our + * message loops, in which keystrokes pressed in the 'Change Settings' + * dialog in mid-session would _also_ be delivered to the main + * terminal window. + * + * But once we had our own wrapper it was convenient to put further + * things into it. In particular, this system allows you to provide a + * context pointer at setup time that's easy to retrieve from the + * window procedure. + */ + +#include "putty.h" + +struct ShinyDialogBoxState { + bool ended; + int result; + ShinyDlgProc proc; + void *ctx; +}; + +/* + * For use in re-entrant calls to the dialog procedure from + * CreateDialog itself, temporarily store the state pointer that we + * won't store in the usual window-memory slot until later. + * + * I don't _intend_ that this system will need to be used in multiple + * threads at all, let alone concurrently, but just in case, declaring + * sdb_tempstate as thread-local will protect against that possibility. + */ +static THREADLOCAL struct ShinyDialogBoxState *sdb_tempstate; + +static inline struct ShinyDialogBoxState *ShinyDialogGetState(HWND hwnd) +{ + if (sdb_tempstate) + return sdb_tempstate; + return (struct ShinyDialogBoxState *)GetWindowLongPtr( + hwnd, DLGWINDOWEXTRA); +} + +static INT_PTR CALLBACK ShinyRealDlgProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + struct ShinyDialogBoxState *state = ShinyDialogGetState(hwnd); + return state->proc(hwnd, msg, wParam, lParam, state->ctx); +} + +int ShinyDialogBox(HINSTANCE hinst, LPCTSTR tmpl, const char *winclass, + HWND hwndparent, ShinyDlgProc proc, void *ctx) +{ + /* + * Register the window class ourselves in such a way that we + * allocate an extra word of window memory to store the state + * pointer. + * + * It would be nice in principle to load the dialog template + * resource and dig the class name out of it. But DLGTEMPLATEEX is + * a nasty variable-layout structure not declared conveniently in + * a header file, so I think that's too much effort. Callers of + * this function will just have to provide the right window class + * name to match their template resource via the 'winclass' + * parameter. + */ + WNDCLASS wc; + wc.style = CS_DBLCLKS | CS_SAVEBITS | CS_BYTEALIGNWINDOW; + wc.lpfnWndProc = DefDlgProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = DLGWINDOWEXTRA + sizeof(LONG_PTR); + wc.hInstance = hinst; + wc.hIcon = NULL; + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND +1); + wc.lpszMenuName = NULL; + wc.lpszClassName = winclass; + RegisterClass(&wc); + + struct ShinyDialogBoxState state[1]; + state->ended = false; + state->proc = proc; + state->ctx = ctx; + + sdb_tempstate = state; + HWND hwnd = CreateDialog(hinst, tmpl, hwndparent, ShinyRealDlgProc); + SetWindowLongPtr(hwnd, DLGWINDOWEXTRA, (LONG_PTR)state); + sdb_tempstate = NULL; + + MSG msg; + int gm; + while ((gm = GetMessage(&msg, NULL, 0, 0)) > 0) { + if (!state->ended && !IsDialogMessage(hwnd, &msg)) + DispatchMessage(&msg); + if (state->ended) + break; + } + + if (gm == 0) + PostQuitMessage(msg.wParam); /* We got a WM_QUIT, pass it on */ + + DestroyWindow(hwnd); + return state->result; +} + +void ShinyEndDialog(HWND hwnd, int ret) +{ + struct ShinyDialogBoxState *state = ShinyDialogGetState(hwnd); + state->result = ret; + state->ended = true; +} diff --git a/windows/winutils.c b/windows/utils/split_into_argv.c index dec8984b..c42c7a0b 100644 --- a/windows/winutils.c +++ b/windows/utils/split_into_argv.c @@ -1,198 +1,4 @@ /* - * winutils.c: miscellaneous Windows utilities for GUI apps - */ - -#include <stdio.h> -#include <stdlib.h> -#include <ctype.h> - -#include "putty.h" -#include "misc.h" - -#ifdef TESTMODE -/* Definitions to allow this module to be compiled standalone for testing - * split_into_argv(). */ -#define smalloc malloc -#define srealloc realloc -#define sfree free -#endif - -/* - * GetOpenFileName/GetSaveFileName tend to muck around with the process' - * working directory on at least some versions of Windows. - * Here's a wrapper that gives more control over this, and hides a little - * bit of other grottiness. - */ - -struct filereq_tag { - TCHAR cwd[MAX_PATH]; -}; - -/* - * `of' is expected to be initialised with most interesting fields, but - * this function does some administrivia. (assume `of' was memset to 0) - * save==1 -> GetSaveFileName; save==0 -> GetOpenFileName - * `state' is optional. - */ -bool request_file(filereq *state, OPENFILENAME *of, bool preserve, bool save) -{ - TCHAR cwd[MAX_PATH]; /* process CWD */ - bool ret; - - /* Get process CWD */ - if (preserve) { - DWORD r = GetCurrentDirectory(lenof(cwd), cwd); - if (r == 0 || r >= lenof(cwd)) - /* Didn't work, oh well. Stop trying to be clever. */ - preserve = false; - } - - /* Open the file requester, maybe setting lpstrInitialDir */ - { -#ifdef OPENFILENAME_SIZE_VERSION_400 - of->lStructSize = OPENFILENAME_SIZE_VERSION_400; -#else - of->lStructSize = sizeof(*of); -#endif - of->lpstrInitialDir = (state && state->cwd[0]) ? state->cwd : NULL; - /* Actually put up the requester. */ - ret = save ? GetSaveFileName(of) : GetOpenFileName(of); - } - - /* Get CWD left by requester */ - if (state) { - DWORD r = GetCurrentDirectory(lenof(state->cwd), state->cwd); - if (r == 0 || r >= lenof(state->cwd)) - /* Didn't work, oh well. */ - state->cwd[0] = '\0'; - } - - /* Restore process CWD */ - if (preserve) - /* If it fails, there's not much we can do. */ - (void) SetCurrentDirectory(cwd); - - return ret; -} - -filereq *filereq_new(void) -{ - filereq *ret = snew(filereq); - ret->cwd[0] = '\0'; - return ret; -} - -void filereq_free(filereq *state) -{ - sfree(state); -} - -/* - * Message box with optional context help. - */ - -static HWND message_box_owner; - -/* Callback function to launch context help. */ -static VOID CALLBACK message_box_help_callback(LPHELPINFO lpHelpInfo) -{ - const char *context = NULL; -#define CHECK_CTX(name) \ - do { \ - if (lpHelpInfo->dwContextId == WINHELP_CTXID_ ## name) \ - context = WINHELP_CTX_ ## name; \ - } while (0) - CHECK_CTX(errors_hostkey_absent); - CHECK_CTX(errors_hostkey_changed); - CHECK_CTX(errors_cantloadkey); - CHECK_CTX(option_cleanup); - CHECK_CTX(pgp_fingerprints); -#undef CHECK_CTX - if (context) - launch_help(message_box_owner, context); -} - -int message_box(HWND owner, LPCTSTR text, LPCTSTR caption, - DWORD style, DWORD helpctxid) -{ - MSGBOXPARAMS mbox; - - /* - * We use MessageBoxIndirect() because it allows us to specify a - * callback function for the Help button. - */ - mbox.cbSize = sizeof(mbox); - /* Assumes the globals `hinst' and `hwnd' have sensible values. */ - mbox.hInstance = hinst; - mbox.hwndOwner = message_box_owner = owner; - mbox.lpfnMsgBoxCallback = &message_box_help_callback; - mbox.dwLanguageId = LANG_NEUTRAL; - mbox.lpszText = text; - mbox.lpszCaption = caption; - mbox.dwContextHelpId = helpctxid; - mbox.dwStyle = style; - if (helpctxid != 0 && has_help()) mbox.dwStyle |= MB_HELP; - return MessageBoxIndirect(&mbox); -} - -/* - * Display the fingerprints of the PGP Master Keys to the user. - */ -void pgp_fingerprints_msgbox(HWND owner) -{ - message_box( - owner, - "These are the fingerprints of the PuTTY PGP Master Keys. They can\n" - "be used to establish a trust path from this executable to another\n" - "one. See the manual for more information.\n" - "(Note: these fingerprints have nothing to do with SSH!)\n" - "\n" - "PuTTY Master Key as of " PGP_MASTER_KEY_YEAR - " (" PGP_MASTER_KEY_DETAILS "):\n" - " " PGP_MASTER_KEY_FP "\n\n" - "Previous Master Key (" PGP_PREV_MASTER_KEY_YEAR - ", " PGP_PREV_MASTER_KEY_DETAILS "):\n" - " " PGP_PREV_MASTER_KEY_FP, - "PGP fingerprints", MB_ICONINFORMATION | MB_OK, - HELPCTXID(pgp_fingerprints)); -} - -/* - * Helper function to remove the border around a dialog item such as - * a read-only edit control. - */ -void MakeDlgItemBorderless(HWND parent, int id) -{ - HWND child = GetDlgItem(parent, id); - LONG_PTR style = GetWindowLongPtr(child, GWL_STYLE); - LONG_PTR exstyle = GetWindowLongPtr(child, GWL_EXSTYLE); - style &= ~WS_BORDER; - exstyle &= ~(WS_EX_CLIENTEDGE | WS_EX_STATICEDGE | WS_EX_WINDOWEDGE); - SetWindowLongPtr(child, GWL_STYLE, style); - SetWindowLongPtr(child, GWL_EXSTYLE, exstyle); - SetWindowPos(child, NULL, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); -} - -/* - * Handy wrapper around GetDlgItemText which doesn't make you invent - * an arbitrary length limit on the output string. Returned string is - * dynamically allocated; caller must free. - */ -char *GetDlgItemText_alloc(HWND hwnd, int id) -{ - char *ret = NULL; - size_t size = 0; - - do { - sgrowarray_nm(ret, size, size); - GetDlgItemText(hwnd, id, ret, size); - } while (!memchr(ret, '\0', size-1)); - - return ret; -} - -/* * Split a complete command line into argc/argv, attempting to do it * exactly the same way the Visual Studio C library would do it (so * that our console utilities, which receive argc and argv already @@ -211,6 +17,8 @@ char *GetDlgItemText_alloc(HWND hwnd, int id) * `argstart' can be safely left NULL. */ +#include "putty.h" + /* * The precise argument-breaking rules vary with compiler version, or * rather, with the crt0-type startup code that comes with each @@ -243,6 +51,9 @@ char *GetDlgItemText_alloc(HWND hwnd, int id) * or more backslashes precedes two or more double quotes, starting * inside a double-quoted string. * + * Modern Visual Studio (as of 2021) + * --------------------------------- + * * I investigated this in an ordinary CLI program, using the * toolchain's crt0 to split a command line of the form * @@ -279,6 +90,9 @@ char *GetDlgItemText_alloc(HWND hwnd, int id) * either opens or closes a quoted string, and if it closes one, it * generates a literal " as a side effect. * + * Older Visual Studio + * ------------------- + * * But here's the corresponding table from the older Visual Studio 7: * * backslashes @@ -299,48 +113,47 @@ char *GetDlgItemText_alloc(HWND hwnd, int id) * 10 0,3,n | 0,4,y 1,3,n 1,4,y 2,3,n * 11 0,4,n | 0,4,n 1,4,n 1,4,n 2,4,n * - * There is very weird mod-3 behaviour going on here in the - * number of quotes, and it even applies when there aren't any - * backslashes! How ghastly. + * There is very weird mod-3 behaviour going on here in the number of + * quotes, and it even applies when there aren't any backslashes! How + * ghastly. * * With a bit of thought, this extremely odd diagram suddenly - * coalesced itself into a coherent, if still ghastly, model of - * how things work: + * coalesced itself into a coherent, if still ghastly, model of how + * things work: * - * - As before, backslashes are only special when one or more - * of them appear contiguously before at least one double - * quote. In this situation the backslashes do exactly what - * you'd expect: each one quotes the next thing in front of - * it, so you end up with n/2 literal backslashes (if n is - * even) or (n-1)/2 literal backslashes and a literal quote - * (if n is odd). In the latter case the double quote - * character right after the backslashes is used up. + * - As before, backslashes are only special when one or more of them + * appear contiguously before at least one double quote. In this + * situation the backslashes do exactly what you'd expect: each one + * quotes the next thing in front of it, so you end up with n/2 + * literal backslashes (if n is even) or (n-1)/2 literal + * backslashes and a literal quote (if n is odd). In the latter + * case the double quote character right after the backslashes is + * used up. * - * - After that, any remaining double quotes are processed. A - * string of contiguous unescaped double quotes has a mod-3 - * behaviour: + * - After that, any remaining double quotes are processed. A string + * of contiguous unescaped double quotes has a mod-3 behaviour: * * * inside a quoted segment, a quote ends the segment. - * * _immediately_ after ending a quoted segment, a quote - * simply produces a literal quote. - * * otherwise, outside a quoted segment, a quote begins a - * quoted segment. + * * _immediately_ after ending a quoted segment, a quote simply + * produces a literal quote. + * * otherwise, outside a quoted segment, a quote begins a quoted + * segment. * - * So, for example, if we started inside a quoted segment - * then two contiguous quotes would close the segment and - * produce a literal quote; three would close the segment, - * produce a literal quote, and open a new segment. If we - * started outside a quoted segment, then two contiguous - * quotes would open and then close a segment, producing no - * output (but potentially creating a zero-length argument); - * but three quotes would open and close a segment and then - * produce a literal quote. - */ - -/* - * We select between two behaviours depending on the version of Visual - * Studio (see large comment below). I don't know exactly when the bug - * fix happened, but I know that VS7 had the odd mod-3 behaviour. + * So, for example, if we started inside a quoted segment then two + * contiguous quotes would close the segment and produce a literal + * quote; three would close the segment, produce a literal quote, + * and open a new segment. If we started outside a quoted segment, + * then two contiguous quotes would open and then close a segment, + * producing no output (but potentially creating a zero-length + * argument); but three quotes would open and close a segment and + * then produce a literal quote. + * + * I don't know exactly when the bug fix happened, but I know that VS7 + * had the odd mod-3 behaviour. So the #if below will ensure that + * modern (2015 onwards) versions of VS use the new more sensible + * behaviour, and VS7 uses the old one. Things in between may be + * wrong; if anyone cares, patches to change the cutoff version in + * this #if are welcome. */ #if _MSC_VER < 1400 #define MOD3 1 @@ -455,7 +268,7 @@ void split_into_argv(char *cmdline, int *argc, char ***argv, if (argstart) *argstart = outputargstart; else sfree(outputargstart); } -#ifdef TESTMODE +#ifdef TEST const struct argv_test { const char *cmdline; @@ -652,6 +465,12 @@ const struct argv_test { #endif /* MOD3 */ }; +void out_of_memory(void) +{ + fprintf(stderr, "out of memory!\n"); + exit(2); +} + int main(int argc, char **argv) { int i, j; @@ -692,27 +511,29 @@ int main(int argc, char **argv) } if (!strcmp(argv[1], "-split") && argc > 2) { - char *str = malloc(20 + strlen(argv[0]) + strlen(argv[2])); - char *p, *q; + strbuf *cmdline = strbuf_new(); + char *p; - q = str + sprintf(str, "%s -splat ", argv[0]); + put_fmt(cmdline, "%s -splat ", argv[0]); printf(" {\""); - for (p = argv[2]; *p; p++, q++) { - switch (*p) { - case '/': printf("\\\\"); *q = '\\'; break; - case '\'': printf("\\\""); *q = '"'; break; - case '_': printf(" "); *q = ' '; break; - default: putchar(*p); *q = *p; break; - } + size_t args_start = cmdline->len; + for (p = argv[2]; *p; p++) { + char c = (*p == '/' ? '\\' : + *p == '\'' ? '"' : + *p == '_' ? ' ' : + *p); + put_byte(cmdline, c); } - *p = '\0'; + write_c_string_literal(stdout, ptrlen_from_asciz( + cmdline->s + args_start)); printf("\", {"); fflush(stdout); - system(str); + system(cmdline->s); printf("}},\n"); + strbuf_free(cmdline); return 0; } @@ -745,6 +566,83 @@ int main(int argc, char **argv) return 0; } + if (!strcmp(argv[1], "-tabulate")) { + char table[] = "\ + * backslashes \n\ + * \n\ + * 0 1 2 3 4 \n\ + * \n\ + * 0 | \n\ + * --------+----------------------------- \n\ + * 1 | \n\ + * q 2 | \n\ + * u 3 | \n\ + * o 4 | \n\ + * t 5 | \n\ + * e 6 | \n\ + * s 7 | \n\ + * 8 | \n\ +"; + char *linestarts[14]; + char *p = table; + for (i = 0; i < lenof(linestarts); i++) { + linestarts[i] = p; + p += strcspn(p, "\n"); + if (*p) p++; + } + + for (i = 0; i < lenof(argv_tests); i++) { + const struct argv_test *test = &argv_tests[i]; + const char *q = test->cmdline; + + /* Skip tests that aren't telling us something about + * the behaviour _inside_ a quoted string */ + if (*q != '"') + continue; + + q++; + + assert(*q == 'a'); + q++; + int backslashes_in = 0, quotes_in = 0; + while (*q == '\\') { + q++; + backslashes_in++; + } + while (*q == '"') { + q++; + quotes_in++; + } + + q = test->argv[0]; + assert(*q == 'a'); + q++; + int backslashes_out = 0, quotes_out = 0; + while (*q == '\\') { + q++; + backslashes_out++; + } + while (*q == '"') { + q++; + quotes_out++; + } + assert(*q == 'b'); + q++; + bool in_quoted_string = (*q == ' '); + + int x = (backslashes_in == 0 ? 15 : 18 + 7 * backslashes_in); + int y = (quotes_in == 0 ? 4 : 5 + quotes_in); + char *buf = dupprintf("%d,%d,%c", + backslashes_out, quotes_out, + in_quoted_string ? 'y' : 'n'); + memcpy(linestarts[y] + x, buf, strlen(buf)); + sfree(buf); + } + + fputs(table, stdout); + return 0; + } + fprintf(stderr, "unrecognised option: \"%s\"\n", argv[1]); return 1; } @@ -753,18 +651,21 @@ int main(int argc, char **argv) * If we get here, we were invoked with no arguments, so just * run the tests. */ + int passes = 0, fails = 0; for (i = 0; i < lenof(argv_tests); i++) { int ac; char **av; + bool failed = false; - split_into_argv(argv_tests[i].cmdline, &ac, &av); + split_into_argv((char *)argv_tests[i].cmdline, &ac, &av, NULL); for (j = 0; j < ac && argv_tests[i].argv[j]; j++) { if (strcmp(av[j], argv_tests[i].argv[j])) { printf("failed test %d (|%s|) arg %d: |%s| should be |%s|\n", i, argv_tests[i].cmdline, j, av[j], argv_tests[i].argv[j]); + failed = true; } #ifdef VERBOSE else { @@ -774,15 +675,33 @@ int main(int argc, char **argv) } #endif } - if (j < ac) + if (j < ac) { printf("failed test %d (|%s|): %d args returned, should be %d\n", i, argv_tests[i].cmdline, ac, j); - if (argv_tests[i].argv[j]) + failed = true; + } + if (argv_tests[i].argv[j]) { printf("failed test %d (|%s|): %d args returned, should be more\n", i, argv_tests[i].cmdline, ac); + failed = true; + } + + if (failed) + fails++; + else + passes++; } - return 0; + printf("passed %d failed %d (%s mode)\n", + passes, fails, +#if MOD3 + "mod 3" +#else + "mod 2" +#endif + ); + + return fails != 0; } -#endif +#endif /* TEST */ diff --git a/windows/utils/strtoumax.c b/windows/utils/strtoumax.c new file mode 100644 index 00000000..38d00014 --- /dev/null +++ b/windows/utils/strtoumax.c @@ -0,0 +1,12 @@ +/* + * Work around lack of strtoumax in older MSVC libraries. + */ + +#include <stdlib.h> + +#include "defs.h" + +uintmax_t strtoumax(const char *nptr, char **endptr, int base) +{ + return _strtoui64(nptr, endptr, base); +} diff --git a/windows/utils/version.c b/windows/utils/version.c new file mode 100644 index 00000000..b626710e --- /dev/null +++ b/windows/utils/version.c @@ -0,0 +1,45 @@ +#include "putty.h" + +DWORD osMajorVersion, osMinorVersion, osPlatformId; + +void init_winver(void) +{ + static bool initialised = false; + if (initialised) + return; + initialised = true; + + OSVERSIONINFO osVersion; + static HMODULE kernel32_module; + DECL_WINDOWS_FUNCTION(static, BOOL, GetVersionExA, (LPOSVERSIONINFO)); + + if (!kernel32_module) { + kernel32_module = load_system32_dll("kernel32.dll"); + /* Deliberately don't type-check this function, because that + * would involve using its declaration in a header file which + * triggers a deprecation warning. I know it's deprecated (see + * below) and don't need telling. */ + GET_WINDOWS_FUNCTION_NO_TYPECHECK(kernel32_module, GetVersionExA); + } + + ZeroMemory(&osVersion, sizeof(osVersion)); + osVersion.dwOSVersionInfoSize = sizeof (OSVERSIONINFO); + if (p_GetVersionExA && p_GetVersionExA(&osVersion)) { + osMajorVersion = osVersion.dwMajorVersion; + osMinorVersion = osVersion.dwMinorVersion; + osPlatformId = osVersion.dwPlatformId; + } else { + /* + * GetVersionEx is deprecated, so allow for it perhaps going + * away in future API versions. If it's not there, simply + * assume that's because Windows is too _new_, so fill in the + * variables we care about to a value that will always compare + * higher than any given test threshold. + * + * Normally we should be checking against the presence of a + * specific function if possible in any case. + */ + osMajorVersion = osMinorVersion = UINT_MAX; /* a very high number */ + osPlatformId = VER_PLATFORM_WIN32_NT; /* not Win32s or Win95-like */ + } +} diff --git a/windows/utils/win_strerror.c b/windows/utils/win_strerror.c new file mode 100644 index 00000000..5572bc8b --- /dev/null +++ b/windows/utils/win_strerror.c @@ -0,0 +1,72 @@ +/* + * Wrapper around the Windows FormatMessage system for retrieving the + * text of a system error code, with a simple API similar to strerror. + * + * Works by keeping a tree234 containing mappings from system error + * codes to strings. Entries allocated in this tree are simply never + * freed. + * + * Also, the returned string has its trailing newline removed (so it + * can go in places like the Event Log that never want a newline), and + * is prefixed with the error number (so that if a user sends an error + * report containing a translated error message we can't read, we can + * still find out what the error actually was). + */ + +#include "putty.h" + +struct errstring { + int error; + char *text; +}; + +static int errstring_find(void *av, void *bv) +{ + int *a = (int *)av; + struct errstring *b = (struct errstring *)bv; + if (*a < b->error) + return -1; + if (*a > b->error) + return +1; + return 0; +} +static int errstring_compare(void *av, void *bv) +{ + struct errstring *a = (struct errstring *)av; + return errstring_find(&a->error, bv); +} + +static tree234 *errstrings = NULL; + +const char *win_strerror(int error) +{ + struct errstring *es; + + if (!errstrings) + errstrings = newtree234(errstring_compare); + + es = find234(errstrings, &error, errstring_find); + + if (!es) { + char msgtext[65536]; /* maximum size for FormatMessage is 64K */ + + es = snew(struct errstring); + es->error = error; + if (!FormatMessage((FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS), NULL, error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + msgtext, lenof(msgtext)-1, NULL)) { + sprintf(msgtext, + "(unable to format: FormatMessage returned %u)", + (unsigned int)GetLastError()); + } else { + int len = strlen(msgtext); + if (len > 0 && msgtext[len-1] == '\n') + msgtext[len-1] = '\0'; + } + es->text = dupprintf("Error %d: %s", error, msgtext); + add234(errstrings, es); + } + + return es->text; +} diff --git a/windows/winseat.h b/windows/win-gui-seat.h index c6b5fa96..19c5cbea 100644 --- a/windows/winseat.h +++ b/windows/win-gui-seat.h @@ -1,6 +1,6 @@ /* * Small implementation of Seat and LogPolicy shared between window.c - * and windlg.c. + * and dialog.c. */ typedef struct WinGuiSeat WinGuiSeat; @@ -11,4 +11,4 @@ struct WinGuiSeat { LogPolicy logpolicy; }; -extern const LogPolicyVtable win_gui_logpolicy_vt; /* in windlg.c */ +extern const LogPolicyVtable win_gui_logpolicy_vt; /* in dialog.c */ diff --git a/windows/win_res.rc2 b/windows/win_res.rc2 deleted file mode 100644 index ccec3122..00000000 --- a/windows/win_res.rc2 +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Windows resources shared between PuTTY and PuTTYtel, to be #include'd - * after defining appropriate macros. - * - * Note that many of these strings mention PuTTY. Due to restrictions in - * VC's handling of string concatenation, this can't easily be fixed. - * It's fixed up at runtime. - * - * This file has the more or less arbitrary extension '.rc2' to avoid - * IDEs taking it to be a top-level resource script in its own right - * (which has been known to happen if the extension was '.rc'), and - * also to avoid the resource compiler ignoring everything included - * from it (which happens if the extension is '.h'). - */ - -#include "win_res.h" - -IDI_MAINICON ICON "putty.ico" - -IDI_CFGICON ICON "puttycfg.ico" - -/* Accelerators used: clw */ -IDD_ABOUTBOX DIALOG DISCARDABLE 140, 40, 270, 136 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "About PuTTY" -FONT 8, "MS Shell Dlg" -BEGIN - DEFPUSHBUTTON "&Close", IDOK, 216, 118, 48, 14 - PUSHBUTTON "View &Licence", IDA_LICENCE, 6, 118, 70, 14 - PUSHBUTTON "Visit &Web Site", IDA_WEB, 140, 118, 70, 14 - EDITTEXT IDA_TEXT, 10, 6, 250, 110, ES_READONLY | ES_MULTILINE | ES_CENTER, WS_EX_STATICEDGE -END - -/* Accelerators used: aco */ -IDD_MAINBOX DIALOG DISCARDABLE 0, 0, 300, 252 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "PuTTY Configuration" -FONT 8, "MS Shell Dlg" -CLASS "PuTTYConfigBox" -BEGIN -END - -/* Accelerators used: co */ -IDD_LOGBOX DIALOG DISCARDABLE 100, 20, 300, 119 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "PuTTY Event Log" -FONT 8, "MS Shell Dlg" -BEGIN - DEFPUSHBUTTON "&Close", IDOK, 135, 102, 44, 14 - PUSHBUTTON "C&opy", IDN_COPY, 81, 102, 44, 14 - LISTBOX IDN_LIST, 3, 3, 294, 95, LBS_HASSTRINGS | LBS_USETABSTOPS | WS_VSCROLL | LBS_EXTENDEDSEL -END - -/* No accelerators used */ -IDD_LICENCEBOX DIALOG DISCARDABLE 50, 50, 326, 239 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "PuTTY Licence" -FONT 8, "MS Shell Dlg" -BEGIN - DEFPUSHBUTTON "OK", IDOK, 148, 219, 44, 14 - - EDITTEXT IDA_TEXT, 10, 10, 306, 200, ES_READONLY | ES_MULTILINE | ES_LEFT, WS_EX_STATICEDGE -END - -/* No accelerators used */ -IDD_HK_ABSENT DIALOG DISCARDABLE 50, 50, 340, 148 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "PuTTY Security Alert" -FONT 8, "MS Shell Dlg" -BEGIN - LTEXT "The server's host key is not cached in the registry. You have no", 100, 40, 20, 300, 8 - LTEXT "guarantee that the server is the computer you think it is.", 101, 40, 28, 300, 8 - LTEXT "The server's {KEYTYPE} key fingerprint is:", 102, 40, 40, 300, 8 - LTEXT "If you trust this host, press ""Accept"" to add the key to {APPNAME}'s", 103, 40, 60, 300, 8 - LTEXT "cache and carry on connecting.", 104, 40, 68, 300, 8 - LTEXT "If you want to carry on connecting just once, without adding the key", 105, 40, 80, 300, 8 - LTEXT "to the cache, press ""Connect Once"".", 106, 40, 88, 300, 8 - LTEXT "If you do not trust this host, press ""Cancel"" to abandon the connection.", 107, 40, 100, 300, 8 - - ICON "", IDC_HK_ICON, 10, 18, 0, 0 - - PUSHBUTTON "Cancel", IDCANCEL, 288, 128, 40, 14 - PUSHBUTTON "Accept", IDC_HK_ACCEPT, 168, 128, 40, 14 - PUSHBUTTON "Connect Once", IDC_HK_ONCE, 216, 128, 64, 14 - PUSHBUTTON "More info...", IDC_HK_MOREINFO, 60, 128, 64, 14 - PUSHBUTTON "Help", IDHELP, 12, 128, 40, 14 - - EDITTEXT IDC_HK_FINGERPRINT, 40, 48, 300, 12, ES_READONLY | ES_LEFT, 0 -END - -/* No accelerators used */ -IDD_HK_WRONG DIALOG DISCARDABLE 50, 50, 340, 188 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "PuTTY Security Alert" -FONT 8, "MS Shell Dlg" -BEGIN - LTEXT "WARNING - POTENTIAL SECURITY BREACH!", IDC_HK_TITLE, 40, 20, 300, 12 - - LTEXT "The server's host key does not match the one {APPNAME} has cached in", 100, 40, 36, 300, 8 - LTEXT "the registry. This means that either the server administrator has", 101, 40, 44, 300, 8 - LTEXT "changed the host key, or you have actually connected to another", 102, 40, 52, 300, 8 - LTEXT "computer pretending to be the server.", 103, 40, 60, 300, 8 - LTEXT "The new {KEYTYPE} key fingerprint is:", 104, 40, 72, 300, 8 - LTEXT "If you were expecting this change and trust the new key, press", 105, 40, 92, 300, 8 - LTEXT """Accept"" to update {APPNAME}'s cache and continue connecting.", 106, 40, 100, 300, 8 - LTEXT "If you want to carry on connecting but without updating the cache,", 107, 40, 112, 300, 8 - LTEXT "press ""Connect Once"".", 108, 40, 120, 300, 8 - LTEXT "If you want to abandon the connection completely, press ""Cancel"".", 109, 40, 132, 300, 8 - LTEXT "Pressing ""Cancel"" is the ONLY guaranteed safe choice.", 110, 40, 140, 300, 8 - - ICON "", IDC_HK_ICON, 10, 16, 0, 0 - - PUSHBUTTON "Cancel", IDCANCEL, 288, 168, 40, 14 - PUSHBUTTON "Accept", IDC_HK_ACCEPT, 168, 168, 40, 14 - PUSHBUTTON "Connect Once", IDC_HK_ONCE, 216, 168, 64, 14 - PUSHBUTTON "More info...", IDC_HK_MOREINFO, 60, 168, 64, 14 - PUSHBUTTON "Help", IDHELP, 12, 168, 40, 14 - - EDITTEXT IDC_HK_FINGERPRINT, 40, 80, 300, 12, ES_READONLY | ES_LEFT, 0 -END - -/* Accelerators used: clw */ -IDD_HK_MOREINFO DIALOG DISCARDABLE 140, 40, 400, 156 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "PuTTY: information about the server's host key" -FONT 8, "MS Shell Dlg" -BEGIN - LTEXT "SHA256 fingerprint:", 100, 12, 12, 80, 8 - EDITTEXT IDC_HKI_SHA256, 100, 10, 288, 12, ES_READONLY - LTEXT "MD5 fingerprint:", 101, 12, 28, 80, 8 - EDITTEXT IDC_HKI_MD5, 100, 26, 288, 12, ES_READONLY - LTEXT "Full public key:", 102, 12, 44, 376, 8 - EDITTEXT IDC_HKI_PUBKEY, 12, 54, 376, 64, ES_READONLY | ES_MULTILINE | ES_LEFT | ES_AUTOVSCROLL, WS_EX_STATICEDGE - DEFPUSHBUTTON "&Close", IDOK, 176, 130, 48, 14 -END - -#include "version.rc2" diff --git a/windows/window.c b/windows/window.c index 82bbe8e6..979e2ed3 100644 --- a/windows/window.c +++ b/windows/window.c @@ -121,11 +121,51 @@ static int offset_width, offset_height; static bool was_zoomed = false; static int prev_rows, prev_cols; +<<<<<<< HEAD static void flash_window(WinGuiSeat *wgs, int mode); static void sys_cursor_update(WinGuiSeat *wgs); static bool get_fullscreen_rect(WinGuiSeat *wgs, RECT *ss); static void conf_cache_data(WinGuiSeat *wgs); +======= +static void flash_window(int mode); +static void sys_cursor_update(void); +static bool get_fullscreen_rect(RECT *ss); + +static int caret_x = -1, caret_y = -1; + +static int kbd_codepage; + +static Ldisc *ldisc; +static Backend *backend; + +static cmdline_get_passwd_input_state cmdline_get_passwd_state; + +static struct unicode_data ucsdata; +static bool session_closed; +static bool reconfiguring = false; + +static const SessionSpecial *specials = NULL; +static HMENU specials_menu = NULL; +static int n_specials = 0; + +#define TIMING_TIMER_ID 1234 +static long timing_next_time; + +static struct { + HMENU menu; +} popup_menus[2]; +enum { SYSMENU, CTXMENU }; +static HMENU savedsess_menu; + +static Conf *conf; +static LogContext *logctx; +static Terminal *term; + +static void conf_cache_data(void); +static int cursor_type; +static int vtmode; +>>>>>>> tags/0.78 static struct sesslist sesslist; /* for saved-session menu */ @@ -139,6 +179,24 @@ DECL_WINDOWS_FUNCTION(static, HRESULT, AdjustWindowRectExForDpi, (LPRECT lpRect, static HBITMAP caretbm; +<<<<<<< HEAD +======= +static int dbltime, lasttime, lastact; +static Mouse_Button lastbtn; + +/* this allows xterm-style mouse handling. */ +static bool send_raw_mouse = false; +static int wheel_accumulator = 0; + +static bool pointer_indicates_raw_mouse = false; + +static BusyStatus busy_status = BUSY_NOT; + +static wchar_t *window_name, *icon_name; + +static int compose_state = 0; + +>>>>>>> tags/0.78 static UINT wm_mousewheel = WM_MOUSEWHEEL; struct WinGuiSeatListNode wgslisthead = { @@ -289,7 +347,13 @@ static void start_backend(WinGuiSeat *wgs) char *error, *realhost; int i; +<<<<<<< HEAD wgs->cmdline_get_passwd_state = cmdline_get_passwd_input_state_new; +======= + cmdline_get_passwd_state = cmdline_get_passwd_input_state_new; + + vt = backend_vt_from_conf(conf); +>>>>>>> tags/0.78 vt = backend_vt_from_conf(wgs->conf); @@ -308,7 +372,11 @@ static void start_backend(WinGuiSeat *wgs) msg = dupprintf("Unable to open terminal:\n%s", error); } else { msg = dupprintf("Unable to open connection to\n%s\n%s", +<<<<<<< HEAD conf_dest(wgs->conf), error); +======= + conf_dest(conf), error); +>>>>>>> tags/0.78 } sfree(error); MessageBox(NULL, msg, str, MB_ICONERROR | MB_OK); @@ -351,8 +419,13 @@ static void close_session(void *vctx) wgs->session_closed = true; newtitle = dupprintf("%s (inactive)", appname); +<<<<<<< HEAD win_set_icon_title(&wgs->termwin, newtitle, DEFAULT_CODEPAGE); win_set_title(&wgs->termwin, newtitle, DEFAULT_CODEPAGE); +======= + win_set_icon_title(wintw, newtitle, DEFAULT_CODEPAGE); + win_set_title(wintw, newtitle, DEFAULT_CODEPAGE); +>>>>>>> tags/0.78 sfree(newtitle); if (wgs->ldisc) { @@ -514,11 +587,19 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) /* * Process the command line. */ +<<<<<<< HEAD gui_term_process_cmdline(wgs->conf, cmdline); memset(&wgs->ucsdata, 0, sizeof(wgs->ucsdata)); conf_cache_data(wgs); +======= + gui_term_process_cmdline(conf, cmdline); + + memset(&ucsdata, 0, sizeof(ucsdata)); + + conf_cache_data(); +>>>>>>> tags/0.78 /* * Guess some defaults for the window size. This all gets @@ -553,9 +634,15 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) if (vt && vt->flags & BACKEND_RESIZE_FORBIDDEN) resize_forbidden = true; wchar_t *uappname = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname); +<<<<<<< HEAD wgs->window_name = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname); wgs->icon_name = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname); if (!conf_get_bool(wgs->conf, CONF_scrollbar)) +======= + window_name = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname); + icon_name = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname); + if (!conf_get_bool(conf, CONF_scrollbar)) +>>>>>>> tags/0.78 winmode &= ~(WS_VSCROLL); if (conf_get_int(wgs->conf, CONF_resize_action) == RESIZE_DISABLED || resize_forbidden) @@ -568,40 +655,65 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) #ifdef TEST_ANSI_WINDOW /* For developer testing of ANSI window support, pretend * CreateWindowExW failed */ +<<<<<<< HEAD wgs->term_hwnd = NULL; +======= + wgs.term_hwnd = NULL; +>>>>>>> tags/0.78 SetLastError(ERROR_CALL_NOT_IMPLEMENTED); #else unicode_window = true; sw_PeekMessage = PeekMessageW; sw_DispatchMessage = DispatchMessageW; sw_DefWindowProc = DefWindowProcW; +<<<<<<< HEAD wgs->term_hwnd = CreateWindowExW( +======= + wgs.term_hwnd = CreateWindowExW( +>>>>>>> tags/0.78 exwinmode, terminal_window_class_w(), uappname, winmode, CW_USEDEFAULT, CW_USEDEFAULT, guess_width, guess_height, NULL, NULL, inst, NULL); #endif #if defined LEGACY_WINDOWS || defined TEST_ANSI_WINDOW +<<<<<<< HEAD if (!wgs->term_hwnd && GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) { +======= + if (!wgs.term_hwnd && GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) { +>>>>>>> tags/0.78 /* Fall back to an ANSI window, swapping in all the ANSI * window message handling functions */ unicode_window = false; sw_PeekMessage = PeekMessageA; sw_DispatchMessage = DispatchMessageA; sw_DefWindowProc = DefWindowProcA; +<<<<<<< HEAD wgs->term_hwnd = CreateWindowExA( +======= + wgs.term_hwnd = CreateWindowExA( +>>>>>>> tags/0.78 exwinmode, terminal_window_class_a(), appname, winmode, CW_USEDEFAULT, CW_USEDEFAULT, guess_width, guess_height, NULL, NULL, inst, NULL); } #endif +<<<<<<< HEAD if (!wgs->term_hwnd) { modalfatalbox("Unable to create terminal window: %s", win_strerror(GetLastError())); } memset(&wgs->dpi_info, 0, sizeof(struct _dpi_info)); init_dpi_info(wgs); +======= + if (!wgs.term_hwnd) { + modalfatalbox("Unable to create terminal window: %s", + win_strerror(GetLastError())); + } + memset(&dpi_info, 0, sizeof(struct _dpi_info)); + init_dpi_info(); +>>>>>>> tags/0.78 sfree(uappname); } @@ -759,6 +871,8 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) gui_terminal_ready(wgs->term_hwnd, &wgs->seat, wgs->backend); + gui_terminal_ready(wgs.term_hwnd, &wgs.seat, backend); + while (1) { int n; DWORD timeout; @@ -837,6 +951,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) return msg.wParam; /* ... but optimiser doesn't know */ } +<<<<<<< HEAD static void wgs_cleanup(WinGuiSeat *wgs) { deinit_fonts(wgs); @@ -847,6 +962,8 @@ static void wgs_cleanup(WinGuiSeat *wgs) sfree(wgs); } +======= +>>>>>>> tags/0.78 char *handle_restrict_acl_cmdline_prefix(char *p) { /* @@ -1345,11 +1462,19 @@ static int get_font_width(HDC hdc, const TEXTMETRIC *tm) static void init_dpi_info(WinGuiSeat *wgs) { +<<<<<<< HEAD if (wgs->dpi_info.cur_dpi.x == 0 || wgs->dpi_info.cur_dpi.y == 0) { if (p_GetDpiForMonitor && p_MonitorFromWindow) { UINT dpiX, dpiY; HMONITOR currentMonitor = p_MonitorFromWindow( wgs->term_hwnd, MONITOR_DEFAULTTOPRIMARY); +======= + if (dpi_info.cur_dpi.x == 0 || dpi_info.cur_dpi.y == 0) { + if (p_GetDpiForMonitor && p_MonitorFromWindow) { + UINT dpiX, dpiY; + HMONITOR currentMonitor = p_MonitorFromWindow( + wgs.term_hwnd, MONITOR_DEFAULTTOPRIMARY); +>>>>>>> tags/0.78 if (p_GetDpiForMonitor(currentMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY) == S_OK) { wgs->dpi_info.cur_dpi.x = (int)dpiX; @@ -1472,12 +1597,21 @@ static void init_fonts(WinGuiSeat *wgs, int pick_width, int pick_height) /* !!! Yes the next line is right */ if (cset == OEM_CHARSET) +<<<<<<< HEAD wgs->ucsdata.font_codepage = GetOEMCP(); else if (TranslateCharsetInfo ((DWORD *)(ULONG_PTR)cset, &info, TCI_SRCCHARSET)) wgs->ucsdata.font_codepage = info.ciACP; else wgs->ucsdata.font_codepage = -1; +======= + ucsdata.font_codepage = GetOEMCP(); + else if (TranslateCharsetInfo ((DWORD *)(ULONG_PTR)cset, + &info, TCI_SRCCHARSET)) + ucsdata.font_codepage = info.ciACP; + else + ucsdata.font_codepage = -1; +>>>>>>> tags/0.78 GetCPInfo(wgs->ucsdata.font_codepage, &cpinfo); wgs->ucsdata.dbcs_screenfont = (cpinfo.MaxCharSize > 1); @@ -1658,9 +1792,15 @@ static void wintw_request_resize(TermWin *tw, int w, int h) int width, height; /* If the window is maximized suppress resizing attempts */ +<<<<<<< HEAD if (IsZoomed(wgs->term_hwnd)) { if (conf_get_int(wgs->conf, CONF_resize_action) == RESIZE_TERM) { term_resize_request_completed(wgs->term); +======= + if (IsZoomed(wgs.term_hwnd)) { + if (conf_get_int(conf, CONF_resize_action) == RESIZE_TERM) { + term_resize_request_completed(term); +>>>>>>> tags/0.78 return; } } @@ -1674,13 +1814,21 @@ static void wintw_request_resize(TermWin *tw, int w, int h) /* Sanity checks ... */ { RECT ss; +<<<<<<< HEAD if (get_fullscreen_rect(wgs, &ss)) { +======= + if (get_fullscreen_rect(&ss)) { +>>>>>>> tags/0.78 /* Make sure the values aren't too big */ width = (ss.right - ss.left - extra_width) / 4; height = (ss.bottom - ss.top - extra_height) / 6; if (w > width || h > height) { +<<<<<<< HEAD term_resize_request_completed(wgs->term); +======= + term_resize_request_completed(term); +>>>>>>> tags/0.78 return; } if (w < 15) @@ -1690,12 +1838,21 @@ static void wintw_request_resize(TermWin *tw, int w, int h) } } +<<<<<<< HEAD if (conf_get_int(wgs->conf, CONF_resize_action) != RESIZE_FONT && !IsZoomed(wgs->term_hwnd)) { width = extra_width + font_width * w; height = extra_height + font_height * h; SetWindowPos(wgs->term_hwnd, NULL, 0, 0, width, height, +======= + if (conf_get_int(conf, CONF_resize_action) != RESIZE_FONT && + !IsZoomed(wgs.term_hwnd)) { + width = extra_width + font_width * w; + height = extra_height + font_height * h; + + SetWindowPos(wgs.term_hwnd, NULL, 0, 0, width, height, +>>>>>>> tags/0.78 SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOZORDER); } else { @@ -1704,12 +1861,21 @@ static void wintw_request_resize(TermWin *tw, int w, int h) * terminal the new size immediately, so that reset_window * will know what to do. */ +<<<<<<< HEAD term_size(wgs->term, h, w, conf_get_int(wgs->conf, CONF_savelines)); reset_window(wgs, 0); } term_resize_request_completed(wgs->term); InvalidateRect(wgs->term_hwnd, NULL, true); +======= + term_size(term, h, w, conf_get_int(conf, CONF_savelines)); + reset_window(0); + } + + term_resize_request_completed(term); + InvalidateRect(wgs.term_hwnd, NULL, true); +>>>>>>> tags/0.78 } static void recompute_window_offset(WinGuiSeat *wgs) @@ -1837,6 +2003,7 @@ static void reset_window(WinGuiSeat *wgs, int reinit) wgs->dpi_info.cur_dpi.x); rect.right += (window_border * 2); rect.bottom += (window_border * 2); +<<<<<<< HEAD OffsetRect(&wgs->dpi_info.new_wnd_rect, ((wgs->dpi_info.new_wnd_rect.right - wgs->dpi_info.new_wnd_rect.left) - @@ -1847,6 +2014,15 @@ static void reset_window(WinGuiSeat *wgs, int reinit) SetWindowPos(wgs->term_hwnd, NULL, wgs->dpi_info.new_wnd_rect.left, wgs->dpi_info.new_wnd_rect.top, +======= + OffsetRect(&dpi_info.new_wnd_rect, + ((dpi_info.new_wnd_rect.right - dpi_info.new_wnd_rect.left) - + (rect.right - rect.left)) / 2, + ((dpi_info.new_wnd_rect.bottom - dpi_info.new_wnd_rect.top) - + (rect.bottom - rect.top)) / 2); + SetWindowPos(wgs.term_hwnd, NULL, + dpi_info.new_wnd_rect.left, dpi_info.new_wnd_rect.top, +>>>>>>> tags/0.78 rect.right - rect.left, rect.bottom - rect.top, SWP_NOZORDER); @@ -1900,7 +2076,11 @@ static void reset_window(WinGuiSeat *wgs, int reinit) static RECT ss; int width, height; +<<<<<<< HEAD get_fullscreen_rect(wgs, &ss); +======= + get_fullscreen_rect(&ss); +>>>>>>> tags/0.78 width = (ss.right - ss.left - extra_width) / font_width; height = (ss.bottom - ss.top - extra_height) / font_height; @@ -2016,10 +2196,17 @@ static Mouse_Button translate_button(WinGuiSeat *wgs, Mouse_Button button) if (button == MBT_LEFT) return MBT_SELECT; if (button == MBT_MIDDLE) +<<<<<<< HEAD return conf_get_int(wgs->conf, CONF_mouse_is_xterm) == 1 ? MBT_PASTE : MBT_EXTEND; if (button == MBT_RIGHT) return conf_get_int(wgs->conf, CONF_mouse_is_xterm) == 1 ? +======= + return conf_get_int(conf, CONF_mouse_is_xterm) == 1 ? + MBT_PASTE : MBT_EXTEND; + if (button == MBT_RIGHT) + return conf_get_int(conf, CONF_mouse_is_xterm) == 1 ? +>>>>>>> tags/0.78 MBT_EXTEND : MBT_PASTE; return 0; /* shouldn't happen */ } @@ -2059,6 +2246,11 @@ static bool is_alt_pressed(void) return false; } +<<<<<<< HEAD +======= +static bool resizing; + +>>>>>>> tags/0.78 static void exit_callback(void *vctx) { WinGuiSeat *wgs = (WinGuiSeat *)vctx; @@ -2088,6 +2280,14 @@ static void exit_callback(void *vctx) } static void win_seat_notify_remote_exit(Seat *seat) +<<<<<<< HEAD +======= +{ + queue_toplevel_callback(exit_callback, NULL); +} + +void timer_change_notify(unsigned long next) +>>>>>>> tags/0.78 { WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); queue_toplevel_callback(exit_callback, wgs); @@ -2256,7 +2456,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, int size; serbuf = strbuf_new(); +<<<<<<< HEAD conf_serialise(BinarySink_UPCAST(serbuf), wgs->conf); +======= + conf_serialise(BinarySink_UPCAST(serbuf), conf); +>>>>>>> tags/0.78 size = serbuf->len; sa.nLength = sizeof(sa); @@ -2348,8 +2552,13 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, { /* Disable full-screen if resizing forbidden */ int i; +<<<<<<< HEAD for (i = 0; i < lenof(wgs->popup_menus); i++) EnableMenuItem(wgs->popup_menus[i].menu, IDM_FULLSCREEN, +======= + for (i = 0; i < lenof(popup_menus); i++) + EnableMenuItem(popup_menus[i].menu, IDM_FULLSCREEN, +>>>>>>> tags/0.78 MF_BYCOMMAND | (resize_action == RESIZE_DISABLED ? MF_GRAYED : MF_ENABLED)); @@ -2366,9 +2575,15 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, * Flush the line discipline's edit buffer in the * case where local editing has just been disabled. */ +<<<<<<< HEAD if (wgs->ldisc) { ldisc_configure(wgs->ldisc, wgs->conf); ldisc_echoedit_update(wgs->ldisc); +======= + if (ldisc) { + ldisc_configure(ldisc, conf); + ldisc_echoedit_update(ldisc); +>>>>>>> tags/0.78 } if (conf_get_bool(wgs->conf, CONF_system_colour) != @@ -2412,9 +2627,15 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, GetWindowLongPtr(hwnd, GWL_EXSTYLE); nexflag = exflag; +<<<<<<< HEAD if (conf_get_bool(wgs->conf, CONF_alwaysontop) != conf_get_bool(prev_conf, CONF_alwaysontop)) { if (conf_get_bool(wgs->conf, CONF_alwaysontop)) { +======= + if (conf_get_bool(conf, CONF_alwaysontop) != + conf_get_bool(prev_conf, CONF_alwaysontop)) { + if (conf_get_bool(conf, CONF_alwaysontop)) { +>>>>>>> tags/0.78 nexflag |= WS_EX_TOPMOST; SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); @@ -2424,13 +2645,21 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, SWP_NOMOVE | SWP_NOSIZE); } } +<<<<<<< HEAD if (conf_get_bool(wgs->conf, CONF_sunken_edge)) +======= + if (conf_get_bool(conf, CONF_sunken_edge)) +>>>>>>> tags/0.78 nexflag |= WS_EX_CLIENTEDGE; else nexflag &= ~(WS_EX_CLIENTEDGE); nflg = flag; +<<<<<<< HEAD if (conf_get_bool(wgs->conf, is_full_screen(wgs) ? +======= + if (conf_get_bool(conf, is_full_screen() ? +>>>>>>> tags/0.78 CONF_scrollbar_in_fullscreen : CONF_scrollbar)) nflg |= WS_VSCROLL; @@ -2438,7 +2667,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, nflg &= ~WS_VSCROLL; if (resize_action == RESIZE_DISABLED || +<<<<<<< HEAD is_full_screen(wgs)) +======= + is_full_screen()) +>>>>>>> tags/0.78 nflg &= ~WS_THICKFRAME; else nflg |= WS_THICKFRAME; @@ -2470,21 +2703,37 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, } { +<<<<<<< HEAD FontSpec *font = conf_get_fontspec(wgs->conf, CONF_font); +======= + FontSpec *font = conf_get_fontspec(conf, CONF_font); +>>>>>>> tags/0.78 FontSpec *prev_font = conf_get_fontspec(prev_conf, CONF_font); if (!strcmp(font->name, prev_font->name) || +<<<<<<< HEAD !strcmp(conf_get_str(wgs->conf, CONF_line_codepage), +======= + !strcmp(conf_get_str(conf, CONF_line_codepage), +>>>>>>> tags/0.78 conf_get_str(prev_conf, CONF_line_codepage)) || font->isbold != prev_font->isbold || font->height != prev_font->height || font->charset != prev_font->charset || +<<<<<<< HEAD conf_get_int(wgs->conf, CONF_font_quality) != conf_get_int(prev_conf, CONF_font_quality) || conf_get_int(wgs->conf, CONF_vtmode) != conf_get_int(prev_conf, CONF_vtmode) || conf_get_int(wgs->conf, CONF_bold_style) != +======= + conf_get_int(conf, CONF_font_quality) != + conf_get_int(prev_conf, CONF_font_quality) || + conf_get_int(conf, CONF_vtmode) != + conf_get_int(prev_conf, CONF_vtmode) || + conf_get_int(conf, CONF_bold_style) != +>>>>>>> tags/0.78 conf_get_int(prev_conf, CONF_bold_style) || resize_action == RESIZE_DISABLED || resize_action == RESIZE_EITHER || @@ -2582,9 +2831,15 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, POINT cursorpos; /* Just in case this happened in mid-select */ +<<<<<<< HEAD term_cancel_selection_drag(wgs->term); show_mouseptr(wgs, true); /* make sure pointer is visible */ +======= + term_cancel_selection_drag(term); + + show_mouseptr(true); /* make sure pointer is visible */ +>>>>>>> tags/0.78 GetCursorPos(&cursorpos); TrackPopupMenu(wgs->popup_menus[CTXMENU].menu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RIGHTBUTTON, @@ -2664,7 +2919,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, if (pt.x == 0 && pt.y == 0) { mouse_on_hotspot = true; } +<<<<<<< HEAD if (is_full_screen(wgs) && press && +======= + if (is_full_screen() && press && +>>>>>>> tags/0.78 button == MBT_LEFT && mouse_on_hotspot) { SendMessage(hwnd, WM_SYSCOMMAND, SC_MOUSEMENU, MAKELPARAM(pt.x, pt.y)); @@ -2698,7 +2957,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, static LPARAM lp = 0; if (wParam != wp || lParam != lp || last_mousemove != WM_MOUSEMOVE) { +<<<<<<< HEAD show_mouseptr(wgs, true); +======= + show_mouseptr(true); +>>>>>>> tags/0.78 wp = wParam; lp = lParam; last_mousemove = WM_MOUSEMOVE; } @@ -2729,7 +2992,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, static LPARAM lp = 0; if (wParam != wp || lParam != lp || last_mousemove != WM_NCMOUSEMOVE) { +<<<<<<< HEAD show_mouseptr(wgs, true); +======= + show_mouseptr(true); +>>>>>>> tags/0.78 wp = wParam; lp = lParam; last_mousemove = WM_NCMOUSEMOVE; } @@ -2749,8 +3016,13 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, HideCaret(hwnd); hdc = BeginPaint(hwnd, &p); +<<<<<<< HEAD if (wgs->pal) { SelectPalette(hdc, wgs->pal, true); +======= + if (pal) { + SelectPalette(hdc, pal, true); +>>>>>>> tags/0.78 RealizePalette(hdc); } @@ -2805,10 +3077,17 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, HBRUSH fillcolour, oldbrush; HPEN edge, oldpen; fillcolour = CreateSolidBrush ( +<<<<<<< HEAD wgs->colours[ATTR_DEFBG>>ATTR_BGSHIFT]); oldbrush = SelectObject(hdc, fillcolour); edge = CreatePen(PS_SOLID, 0, wgs->colours[ATTR_DEFBG>>ATTR_BGSHIFT]); +======= + colours[ATTR_DEFBG>>ATTR_BGSHIFT]); + oldbrush = SelectObject(hdc, fillcolour); + edge = CreatePen(PS_SOLID, 0, + colours[ATTR_DEFBG>>ATTR_BGSHIFT]); +>>>>>>> tags/0.78 oldpen = SelectObject(hdc, edge); /* @@ -2824,8 +3103,13 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, ExcludeClipRect(hdc, offset_width, offset_height, +<<<<<<< HEAD offset_width+font_width*wgs->term->cols, offset_height+font_height*wgs->term->rows); +======= + offset_width+font_width*term->cols, + offset_height+font_height*term->rows); +>>>>>>> tags/0.78 Rectangle(hdc, p.rcPaint.left, p.rcPaint.top, p.rcPaint.right, p.rcPaint.bottom); @@ -3008,10 +3292,17 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, } if (wParam == SIZE_MINIMIZED) sw_SetWindowText(hwnd, +<<<<<<< HEAD conf_get_bool(wgs->conf, CONF_win_name_always) ? wgs->window_name : wgs->icon_name); if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED) sw_SetWindowText(hwnd, wgs->window_name); +======= + conf_get_bool(conf, CONF_win_name_always) ? + window_name : icon_name); + if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED) + sw_SetWindowText(hwnd, window_name); +>>>>>>> tags/0.78 if (wParam == SIZE_RESTORED) { processed_resize = false; clear_full_screen(wgs); @@ -3288,20 +3579,32 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, * instead we send the characters one by one. */ /* don't divide SURROGATE PAIR */ +<<<<<<< HEAD if (wgs->ldisc) { +======= + if (ldisc) { +>>>>>>> tags/0.78 for (i = 0; i < n; i += 2) { WCHAR hs = *(unsigned short *)(buff+i); if (IS_HIGH_SURROGATE(hs) && i+2 < n) { WCHAR ls = *(unsigned short *)(buff+i+2); if (IS_LOW_SURROGATE(ls)) { term_keyinputw( +<<<<<<< HEAD wgs->term, (unsigned short *)(buff+i), 2); +======= + term, (unsigned short *)(buff+i), 2); +>>>>>>> tags/0.78 i += 2; continue; } } term_keyinputw( +<<<<<<< HEAD wgs->term, (unsigned short *)(buff+i), 1); +======= + term, (unsigned short *)(buff+i), 1); +>>>>>>> tags/0.78 } } free(buff); @@ -3347,9 +3650,15 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, } } else { char c = (unsigned char)wParam; +<<<<<<< HEAD term_seen_key_event(wgs->term); if (wgs->ldisc) term_keyinput(wgs->term, CP_ACP, &c, 1); +======= + term_seen_key_event(term); + if (ldisc) + term_keyinput(term, CP_ACP, &c, 1); +>>>>>>> tags/0.78 } return 0; case WM_SYSCOLORCHANGE: @@ -4280,11 +4589,19 @@ static int TranslateKey(WinGuiSeat *wgs, UINT message, WPARAM wParam, } if (wParam == compose_keycode) { +<<<<<<< HEAD if (wgs->compose_state == 0 && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0) wgs->compose_state = 1; else if (wgs->compose_state == 1 && (HIWORD(lParam) & KF_UP)) wgs->compose_state = 2; +======= + if (compose_state == 0 && + (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0) + compose_state = 1; + else if (compose_state == 1 && (HIWORD(lParam) & KF_UP)) + compose_state = 2; +>>>>>>> tags/0.78 else wgs->compose_state = 0; } else if (wgs->compose_state == 1 && wParam != VK_CONTROL) @@ -4592,7 +4909,11 @@ static int TranslateKey(WinGuiSeat *wgs, UINT message, WPARAM wParam, case VK_F20: fkey_number = 20; goto numbered_function_key; numbered_function_key: consumed_alt = false; +<<<<<<< HEAD p += format_function_key((char *)p, wgs->term, fkey_number, +======= + p += format_function_key((char *)p, term, fkey_number, +>>>>>>> tags/0.78 shift_state & 1, shift_state & 2, left_alt, &consumed_alt); if (consumed_alt) @@ -4611,7 +4932,11 @@ static int TranslateKey(WinGuiSeat *wgs, UINT message, WPARAM wParam, if (shift_state & 2) break; +<<<<<<< HEAD p += format_small_keypad_key((char *)p, wgs->term, sk_key, +======= + p += format_small_keypad_key((char *)p, term, sk_key, +>>>>>>> tags/0.78 shift_state & 1, shift_state & 2, left_alt, &consumed_alt); if (consumed_alt) @@ -4626,7 +4951,11 @@ static int TranslateKey(WinGuiSeat *wgs, UINT message, WPARAM wParam, case VK_CLEAR: xkey = 'G'; goto arrow_key; /* close enough */ arrow_key: consumed_alt = false; +<<<<<<< HEAD p += format_arrow_key((char *)p, wgs->term, xkey, shift_state & 1, +======= + p += format_arrow_key((char *)p, term, xkey, shift_state & 1, +>>>>>>> tags/0.78 shift_state & 2, left_alt, &consumed_alt); if (consumed_alt) left_alt = false; /* supersedes the usual prefixing of Esc */ @@ -4807,6 +5136,7 @@ static int TranslateKey(WinGuiSeat *wgs, UINT message, WPARAM wParam, static void wintw_set_title(TermWin *tw, const char *title, int codepage) { +<<<<<<< HEAD WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); wchar_t *new_window_name = dup_mb_to_wc(codepage, 0, title); if (!wcscmp(new_window_name, wgs->window_name)) { @@ -4818,10 +5148,22 @@ static void wintw_set_title(TermWin *tw, const char *title, int codepage) if (conf_get_bool(wgs->conf, CONF_win_name_always) || !IsIconic(wgs->term_hwnd)) sw_SetWindowText(wgs->term_hwnd, wgs->window_name); +======= + wchar_t *new_window_name = dup_mb_to_wc(codepage, 0, title); + if (!wcscmp(new_window_name, window_name)) { + sfree(new_window_name); + return; + } + sfree(window_name); + window_name = new_window_name; + if (conf_get_bool(conf, CONF_win_name_always) || !IsIconic(wgs.term_hwnd)) + sw_SetWindowText(wgs.term_hwnd, window_name); +>>>>>>> tags/0.78 } static void wintw_set_icon_title(TermWin *tw, const char *title, int codepage) { +<<<<<<< HEAD WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); wchar_t *new_icon_name = dup_mb_to_wc(codepage, 0, title); if (!wcscmp(new_icon_name, wgs->icon_name)) { @@ -4833,6 +5175,17 @@ static void wintw_set_icon_title(TermWin *tw, const char *title, int codepage) if (!conf_get_bool(wgs->conf, CONF_win_name_always) && IsIconic(wgs->term_hwnd)) sw_SetWindowText(wgs->term_hwnd, wgs->icon_name); +======= + wchar_t *new_icon_name = dup_mb_to_wc(codepage, 0, title); + if (!wcscmp(new_icon_name, icon_name)) { + sfree(new_icon_name); + return; + } + sfree(icon_name); + icon_name = new_icon_name; + if (!conf_get_bool(conf, CONF_win_name_always) && IsIconic(wgs.term_hwnd)) + sw_SetWindowText(wgs.term_hwnd, icon_name); +>>>>>>> tags/0.78 } static void wintw_set_scrollbar(TermWin *tw, int total, int start, int page) @@ -5134,7 +5487,11 @@ static void wintw_clip_write( for (i = 0; i < OSC4_NCOLOURS; i++) { if (palette[i] != 0) { +<<<<<<< HEAD const PALETTEENTRY *pe = &wgs->logpal->palPalEntry[i]; +======= + const PALETTEENTRY *pe = &logpal->palPalEntry[i]; +>>>>>>> tags/0.78 put_fmt(rtf, "\\red%d\\green%d\\blue%d;", pe->peRed, pe->peGreen, pe->peBlue); } @@ -5684,7 +6041,11 @@ static void wintw_move(TermWin *tw, int x, int y) int resize_action = conf_get_int(wgs->conf, CONF_resize_action); if (resize_action == RESIZE_DISABLED || resize_action == RESIZE_FONT || +<<<<<<< HEAD IsZoomed(wgs->term_hwnd)) +======= + IsZoomed(wgs.term_hwnd)) +>>>>>>> tags/0.78 return; SetWindowPos(wgs->term_hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER); @@ -5743,13 +6104,21 @@ static bool is_full_screen(WinGuiSeat *wgs) /* Get the rect/size of a full screen window using the nearest available * monitor in multimon systems; default to something sensible if only * one monitor is present. */ +<<<<<<< HEAD static bool get_fullscreen_rect(WinGuiSeat *wgs, RECT *ss) +======= +static bool get_fullscreen_rect(RECT *ss) +>>>>>>> tags/0.78 { #if defined(MONITOR_DEFAULTTONEAREST) && !defined(NO_MULTIMON) if (p_GetMonitorInfoA && p_MonitorFromWindow) { HMONITOR mon; MONITORINFO mi; +<<<<<<< HEAD mon = p_MonitorFromWindow(wgs->term_hwnd, MONITOR_DEFAULTTONEAREST); +======= + mon = p_MonitorFromWindow(wgs.term_hwnd, MONITOR_DEFAULTTONEAREST); +>>>>>>> tags/0.78 mi.cbSize = sizeof(mi); p_GetMonitorInfoA(mon, &mi); @@ -5778,7 +6147,11 @@ static void make_full_screen(WinGuiSeat *wgs) assert(IsZoomed(wgs->term_hwnd)); +<<<<<<< HEAD if (is_full_screen(wgs)) +======= + if (is_full_screen()) +>>>>>>> tags/0.78 return; /* Remove the window furniture. */ @@ -5791,8 +6164,13 @@ static void make_full_screen(WinGuiSeat *wgs) SetWindowLongPtr(wgs->term_hwnd, GWL_STYLE, style); /* Resize ourselves to exactly cover the nearest monitor. */ +<<<<<<< HEAD get_fullscreen_rect(wgs, &ss); SetWindowPos(wgs->term_hwnd, HWND_TOP, ss.left, ss.top, +======= + get_fullscreen_rect(&ss); + SetWindowPos(wgs.term_hwnd, HWND_TOP, ss.left, ss.top, +>>>>>>> tags/0.78 ss.right - ss.left, ss.bottom - ss.top, SWP_FRAMECHANGED); /* We may have changed size as a result */ @@ -5860,6 +6238,7 @@ static void flip_full_screen(WinGuiSeat *wgs) static size_t win_seat_output(Seat *seat, SeatOutputType type, const void *data, size_t len) { +<<<<<<< HEAD WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); return term_data(wgs->term, data, len); } @@ -5869,6 +6248,15 @@ static void wintw_unthrottle(TermWin *tw, size_t bufsize) WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); if (wgs->backend) backend_unthrottle(wgs->backend, bufsize); +======= + return term_data(term, data, len); +} + +static void wintw_unthrottle(TermWin *win, size_t bufsize) +{ + if (backend) + backend_unthrottle(backend, bufsize); +>>>>>>> tags/0.78 } static bool win_seat_eof(Seat *seat) @@ -5878,15 +6266,23 @@ static bool win_seat_eof(Seat *seat) static SeatPromptResult win_seat_get_userpass_input(Seat *seat, prompts_t *p) { +<<<<<<< HEAD WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); SeatPromptResult spr; spr = cmdline_get_passwd_input(p, &wgs->cmdline_get_passwd_state, true); if (spr.kind == SPRK_INCOMPLETE) spr = term_get_userpass_input(wgs->term, p); +======= + SeatPromptResult spr; + spr = cmdline_get_passwd_input(p, &cmdline_get_passwd_state, true); + if (spr.kind == SPRK_INCOMPLETE) + spr = term_get_userpass_input(term, p); +>>>>>>> tags/0.78 return spr; } static void win_seat_set_trust_status(Seat *seat, bool trusted) +<<<<<<< HEAD { WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); term_set_trust_status(wgs->term, trusted); @@ -5894,6 +6290,14 @@ static void win_seat_set_trust_status(Seat *seat, bool trusted) static bool win_seat_can_set_trust_status(Seat *seat) { +======= +{ + term_set_trust_status(term, trusted); +} + +static bool win_seat_can_set_trust_status(Seat *seat) +{ +>>>>>>> tags/0.78 return true; } diff --git a/windows/winhelp.rc2 b/windows/winhelp.rc2 deleted file mode 100644 index 3499d25e..00000000 --- a/windows/winhelp.rc2 +++ /dev/null @@ -1,8 +0,0 @@ -#include "win_res.h" - -#ifdef EMBED_CHM -ID_CUSTOM_CHMFILE TYPE_CUSTOM_CHMFILE "../doc/putty.chm" -#define HELPVER " (with embedded help)" -#else -#define HELPVER " (without embedded help)" -#endif diff --git a/windows/winmisc.c b/windows/winmisc.c deleted file mode 100644 index 759df011..00000000 --- a/windows/winmisc.c +++ /dev/null @@ -1,467 +0,0 @@ -/* - * winmisc.c: miscellaneous Windows-specific things - */ - -#include <stdio.h> -#include <stdlib.h> -#include <limits.h> -#include "putty.h" -#ifndef SECURITY_WIN32 -#define SECURITY_WIN32 -#endif -#include <security.h> - -DWORD osMajorVersion, osMinorVersion, osPlatformId; - -char *platform_get_x_display(void) { - /* We may as well check for DISPLAY in case it's useful. */ - return dupstr(getenv("DISPLAY")); -} - -Filename *filename_from_str(const char *str) -{ - Filename *ret = snew(Filename); - ret->path = dupstr(str); - return ret; -} - -Filename *filename_copy(const Filename *fn) -{ - return filename_from_str(fn->path); -} - -const char *filename_to_str(const Filename *fn) -{ - return fn->path; -} - -bool filename_equal(const Filename *f1, const Filename *f2) -{ - return !strcmp(f1->path, f2->path); -} - -bool filename_is_null(const Filename *fn) -{ - return !*fn->path; -} - -void filename_free(Filename *fn) -{ - sfree(fn->path); - sfree(fn); -} - -void filename_serialise(BinarySink *bs, const Filename *f) -{ - put_asciz(bs, f->path); -} -Filename *filename_deserialise(BinarySource *src) -{ - return filename_from_str(get_asciz(src)); -} - -char filename_char_sanitise(char c) -{ - if (strchr("<>:\"/\\|?*", c)) - return '.'; - return c; -} - -char *get_username(void) -{ - DWORD namelen; - char *user; - bool got_username = false; - DECL_WINDOWS_FUNCTION(static, BOOLEAN, GetUserNameExA, - (EXTENDED_NAME_FORMAT, LPSTR, PULONG)); - - { - static bool tried_usernameex = false; - if (!tried_usernameex) { - /* Not available on Win9x, so load dynamically */ - HMODULE secur32 = load_system32_dll("secur32.dll"); - /* If MIT Kerberos is installed, the following call to - GET_WINDOWS_FUNCTION makes Windows implicitly load - sspicli.dll WITHOUT proper path sanitizing, so better - load it properly before */ - HMODULE sspicli = load_system32_dll("sspicli.dll"); - (void)sspicli; /* squash compiler warning about unused variable */ - GET_WINDOWS_FUNCTION(secur32, GetUserNameExA); - tried_usernameex = true; - } - } - - if (p_GetUserNameExA) { - /* - * If available, use the principal -- this avoids the problem - * that the local username is case-insensitive but Kerberos - * usernames are case-sensitive. - */ - - /* Get the length */ - namelen = 0; - (void) p_GetUserNameExA(NameUserPrincipal, NULL, &namelen); - - user = snewn(namelen, char); - got_username = p_GetUserNameExA(NameUserPrincipal, user, &namelen); - if (got_username) { - char *p = strchr(user, '@'); - if (p) *p = 0; - } else { - sfree(user); - } - } - - if (!got_username) { - /* Fall back to local user name */ - namelen = 0; - if (!GetUserName(NULL, &namelen)) { - /* - * Apparently this doesn't work at least on Windows XP SP2. - * Thus assume a maximum of 256. It will fail again if it - * doesn't fit. - */ - namelen = 256; - } - - user = snewn(namelen, char); - got_username = GetUserName(user, &namelen); - if (!got_username) { - sfree(user); - } - } - - return got_username ? user : NULL; -} - -void dll_hijacking_protection(void) -{ - /* - * If the OS provides it, call SetDefaultDllDirectories() to - * prevent DLLs from being loaded from the directory containing - * our own binary, and instead only load from system32. - * - * This is a protection against hijacking attacks, if someone runs - * PuTTY directly from their web browser's download directory - * having previously been enticed into clicking on an unwise link - * that downloaded a malicious DLL to the same directory under one - * of various magic names that seem to be things that standard - * Windows DLLs delegate to. - * - * It shouldn't break deliberate loading of user-provided DLLs - * such as GSSAPI providers, because those are specified by their - * full pathname by the user-provided configuration. - */ - static HMODULE kernel32_module; - DECL_WINDOWS_FUNCTION(static, BOOL, SetDefaultDllDirectories, (DWORD)); - - if (!kernel32_module) { - kernel32_module = load_system32_dll("kernel32.dll"); -#if (defined _MSC_VER && _MSC_VER < 1900) - /* For older Visual Studio, this function isn't available in - * the header files to type-check */ - GET_WINDOWS_FUNCTION_NO_TYPECHECK( - kernel32_module, SetDefaultDllDirectories); -#else - GET_WINDOWS_FUNCTION(kernel32_module, SetDefaultDllDirectories); -#endif - } - - if (p_SetDefaultDllDirectories) { - /* LOAD_LIBRARY_SEARCH_SYSTEM32 and explicitly specified - * directories only */ - p_SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32 | - LOAD_LIBRARY_SEARCH_USER_DIRS); - } -} - -void init_winver(void) -{ - OSVERSIONINFO osVersion; - static HMODULE kernel32_module; - DECL_WINDOWS_FUNCTION(static, BOOL, GetVersionExA, (LPOSVERSIONINFO)); - - if (!kernel32_module) { - kernel32_module = load_system32_dll("kernel32.dll"); - /* Deliberately don't type-check this function, because that - * would involve using its declaration in a header file which - * triggers a deprecation warning. I know it's deprecated (see - * below) and don't need telling. */ - GET_WINDOWS_FUNCTION_NO_TYPECHECK(kernel32_module, GetVersionExA); - } - - ZeroMemory(&osVersion, sizeof(osVersion)); - osVersion.dwOSVersionInfoSize = sizeof (OSVERSIONINFO); - if (p_GetVersionExA && p_GetVersionExA(&osVersion)) { - osMajorVersion = osVersion.dwMajorVersion; - osMinorVersion = osVersion.dwMinorVersion; - osPlatformId = osVersion.dwPlatformId; - } else { - /* - * GetVersionEx is deprecated, so allow for it perhaps going - * away in future API versions. If it's not there, simply - * assume that's because Windows is too _new_, so fill in the - * variables we care about to a value that will always compare - * higher than any given test threshold. - * - * Normally we should be checking against the presence of a - * specific function if possible in any case. - */ - osMajorVersion = osMinorVersion = UINT_MAX; /* a very high number */ - osPlatformId = VER_PLATFORM_WIN32_NT; /* not Win32s or Win95-like */ - } -} - -HMODULE load_system32_dll(const char *libname) -{ - /* - * Wrapper function to load a DLL out of c:\windows\system32 - * without going through the full DLL search path. (Hence no - * attack is possible by placing a substitute DLL earlier on that - * path.) - */ - static char *sysdir = NULL; - static size_t sysdirsize = 0; - char *fullpath; - HMODULE ret; - - if (!sysdir) { - size_t len; - while ((len = GetSystemDirectory(sysdir, sysdirsize)) >= sysdirsize) - sgrowarray(sysdir, sysdirsize, len); - } - - fullpath = dupcat(sysdir, "\\", libname); - ret = LoadLibrary(fullpath); - sfree(fullpath); - return ret; -} - -/* - * A tree234 containing mappings from system error codes to strings. - */ - -struct errstring { - int error; - char *text; -}; - -static int errstring_find(void *av, void *bv) -{ - int *a = (int *)av; - struct errstring *b = (struct errstring *)bv; - if (*a < b->error) - return -1; - if (*a > b->error) - return +1; - return 0; -} -static int errstring_compare(void *av, void *bv) -{ - struct errstring *a = (struct errstring *)av; - return errstring_find(&a->error, bv); -} - -static tree234 *errstrings = NULL; - -const char *win_strerror(int error) -{ - struct errstring *es; - - if (!errstrings) - errstrings = newtree234(errstring_compare); - - es = find234(errstrings, &error, errstring_find); - - if (!es) { - char msgtext[65536]; /* maximum size for FormatMessage is 64K */ - - es = snew(struct errstring); - es->error = error; - if (!FormatMessage((FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS), NULL, error, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - msgtext, lenof(msgtext)-1, NULL)) { - sprintf(msgtext, - "(unable to format: FormatMessage returned %u)", - (unsigned int)GetLastError()); - } else { - int len = strlen(msgtext); - if (len > 0 && msgtext[len-1] == '\n') - msgtext[len-1] = '\0'; - } - es->text = dupprintf("Error %d: %s", error, msgtext); - add234(errstrings, es); - } - - return es->text; -} - -FontSpec *fontspec_new(const char *name, bool bold, int height, int charset) -{ - FontSpec *f = snew(FontSpec); - f->name = dupstr(name); - f->isbold = bold; - f->height = height; - f->charset = charset; - return f; -} -FontSpec *fontspec_copy(const FontSpec *f) -{ - return fontspec_new(f->name, f->isbold, f->height, f->charset); -} -void fontspec_free(FontSpec *f) -{ - sfree(f->name); - sfree(f); -} -void fontspec_serialise(BinarySink *bs, FontSpec *f) -{ - put_asciz(bs, f->name); - put_uint32(bs, f->isbold); - put_uint32(bs, f->height); - put_uint32(bs, f->charset); -} -FontSpec *fontspec_deserialise(BinarySource *src) -{ - const char *name = get_asciz(src); - unsigned isbold = get_uint32(src); - unsigned height = get_uint32(src); - unsigned charset = get_uint32(src); - return fontspec_new(name, isbold, height, charset); -} - -bool open_for_write_would_lose_data(const Filename *fn) -{ - WIN32_FILE_ATTRIBUTE_DATA attrs; - if (!GetFileAttributesEx(fn->path, GetFileExInfoStandard, &attrs)) { - /* - * Generally, if we don't identify a specific reason why we - * should return true from this function, we return false, and - * let the subsequent attempt to open the file for real give a - * more useful error message. - */ - return false; - } - if (attrs.dwFileAttributes & (FILE_ATTRIBUTE_DEVICE | - FILE_ATTRIBUTE_DIRECTORY)) { - /* - * File is something other than an ordinary disk file, so - * opening it for writing will not cause truncation. (It may - * not _succeed_ either, but that's not our problem here!) - */ - return false; - } - if (attrs.nFileSizeHigh == 0 && attrs.nFileSizeLow == 0) { - /* - * File is zero-length (or may be a named pipe, which - * dwFileAttributes can't tell apart from a regular file), so - * opening it for writing won't truncate any data away because - * there's nothing to truncate anyway. - */ - return false; - } - return true; -} - -void escape_registry_key(const char *in, strbuf *out) -{ - bool candot = false; - static const char hex[16] = "0123456789ABCDEF"; - - while (*in) { - if (*in == ' ' || *in == '\\' || *in == '*' || *in == '?' || - *in == '%' || *in < ' ' || *in > '~' || (*in == '.' - && !candot)) { - put_byte(out, '%'); - put_byte(out, hex[((unsigned char) *in) >> 4]); - put_byte(out, hex[((unsigned char) *in) & 15]); - } else - put_byte(out, *in); - in++; - candot = true; - } -} - -void unescape_registry_key(const char *in, strbuf *out) -{ - while (*in) { - if (*in == '%' && in[1] && in[2]) { - int i, j; - - i = in[1] - '0'; - i -= (i > 9 ? 7 : 0); - j = in[2] - '0'; - j -= (j > 9 ? 7 : 0); - - put_byte(out, (i << 4) + j); - in += 3; - } else { - put_byte(out, *in++); - } - } -} - -#ifdef DEBUG -static FILE *debug_fp = NULL; -static HANDLE debug_hdl = INVALID_HANDLE_VALUE; -static int debug_got_console = 0; - -void dputs(const char *buf) -{ - DWORD dw; - - if (!debug_got_console) { - if (AllocConsole()) { - debug_got_console = 1; - debug_hdl = GetStdHandle(STD_OUTPUT_HANDLE); - } - } - if (!debug_fp) { - debug_fp = fopen("debug.log", "w"); - } - - if (debug_hdl != INVALID_HANDLE_VALUE) { - WriteFile(debug_hdl, buf, strlen(buf), &dw, NULL); - } - fputs(buf, debug_fp); - fflush(debug_fp); -} -#endif - -char *registry_get_string(HKEY root, const char *path, const char *leaf) -{ - HKEY key = root; - bool need_close_key = false; - char *toret = NULL, *str = NULL; - - if (path) { - if (RegCreateKey(key, path, &key) != ERROR_SUCCESS) - goto out; - need_close_key = true; - } - - DWORD type, size; - if (RegQueryValueEx(key, leaf, 0, &type, NULL, &size) != ERROR_SUCCESS) - goto out; - if (type != REG_SZ) - goto out; - - str = snewn(size + 1, char); - DWORD size_got = size; - if (RegQueryValueEx(key, leaf, 0, &type, (LPBYTE)str, - &size_got) != ERROR_SUCCESS) - goto out; - if (type != REG_SZ || size_got > size) - goto out; - str[size_got] = '\0'; - - toret = str; - str = NULL; - - out: - if (need_close_key) - RegCloseKey(key); - sfree(str); - return toret; -} diff --git a/windows/winselgui.c b/windows/winselgui.c deleted file mode 100644 index 48a15212..00000000 --- a/windows/winselgui.c +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Implementation of do_select() for winnet.c to use, that uses - * WSAAsyncSelect to convert network activity into window messages, - * for integration into a GUI event loop. - */ - -#include "putty.h" - -static HWND winsel_hwnd = NULL; - -void winselgui_set_hwnd(HWND hwnd) -{ - winsel_hwnd = hwnd; -} - -void winselgui_clear_hwnd(void) -{ - winsel_hwnd = NULL; -} - -const char *do_select(SOCKET skt, bool enable) -{ - int msg, events; - if (enable) { - msg = WM_NETEVENT; - events = (FD_CONNECT | FD_READ | FD_WRITE | - FD_OOB | FD_CLOSE | FD_ACCEPT); - } else { - msg = events = 0; - } - - assert(winsel_hwnd); - - if (p_WSAAsyncSelect(skt, winsel_hwnd, msg, events) == SOCKET_ERROR) - return winsock_error_string(p_WSAGetLastError()); - - return NULL; -} diff --git a/windows/winx11.c b/windows/x11.c index 800d8509..98bbb627 100644 --- a/windows/winx11.c +++ b/windows/x11.c @@ -1,5 +1,5 @@ /* - * winx11.c: fetch local auth data for X forwarding. + * x11.c: fetch local auth data for X forwarding. */ #include <ctype.h> |