diff --git a/Buildscr b/Buildscr index dc5e338b..6b28db9a 100644 --- a/Buildscr +++ b/Buildscr @@ -35,7 +35,7 @@ module putty ifeq "$(RELEASE)" "" set Ndate $(!builddate) ifneq "$(Ndate)" "" in . do echo $(Ndate) | perl -pe 's/(....)(..)(..)/$$1-$$2-$$3/' > date ifneq "$(Ndate)" "" read Date date -set Epoch 17749 # update this at every release +set Epoch 17818 # update this at every release ifneq "$(Ndate)" "" in . do echo $(Ndate) | perl -ne 'use Time::Local; /(....)(..)(..)/ and print timegm(0,0,0,$$3,$$2-1,$$1) / 86400 - $(Epoch)' > days ifneq "$(Ndate)" "" read Days days diff --git a/LATEST.VER b/LATEST.VER index 39c64a9f..07b41f7d 100644 --- a/LATEST.VER +++ b/LATEST.VER @@ -1 +1 @@ -0.75 +0.76 diff --git a/doc/man-plink.but b/doc/man-plink.but index 33386227..26e65f71 100644 --- a/doc/man-plink.but +++ b/doc/man-plink.but @@ -203,6 +203,15 @@ which of the agent's keys to use. } \dd Allow use of an authentication agent. (This option is only necessary to override a setting in a saved session.) +\dt \cw{\-no\-trivial\-auth} + +\dd Disconnect from any SSH server which accepts authentication without +ever having asked for any kind of password or signature or token. (You +might want to enable this for a server you always expect to challenge +you, for instance to ensure you don't accidentally type your key file's +passphrase into a compromised server spoofing Plink's passphrase +prompt.) + \dt \cw{\-noshare} \dd Don't test and try to share an existing connection, always make diff --git a/doc/man-pscp.but b/doc/man-pscp.but index b62e8cc2..60ce4f5e 100644 --- a/doc/man-pscp.but +++ b/doc/man-pscp.but @@ -155,6 +155,14 @@ which of the agent's keys to use. } \dd Allow use of an authentication agent. (This option is only necessary to override a setting in a saved session.) +\dt \cw{\-no\-trivial\-auth} + +\dd Disconnect from any SSH server which accepts authentication without +ever having asked for any kind of password or signature or token. (You +might want to enable this for a server you always expect to challenge +you, for instance to ensure you don't accidentally type your key file's +passphrase into a compromised server spoofing PSCP's passphrase prompt.) + \dt \cw{\-hostkey} \e{key} \dd Specify an acceptable host public key. This option may be specified diff --git a/doc/man-psftp.but b/doc/man-psftp.but index 19f820e3..52617291 100644 --- a/doc/man-psftp.but +++ b/doc/man-psftp.but @@ -143,6 +143,15 @@ which of the agent's keys to use. } \dd Allow use of an authentication agent. (This option is only necessary to override a setting in a saved session.) +\dt \cw{\-no\-trivial\-auth} + +\dd Disconnect from any SSH server which accepts authentication without +ever having asked for any kind of password or signature or token. (You +might want to enable this for a server you always expect to challenge +you, for instance to ensure you don't accidentally type your key file's +passphrase into a compromised server spoofing PSFTP's passphrase +prompt.) + \dt \cw{\-hostkey} \e{key} \dd Specify an acceptable host public key. This option may be specified diff --git a/doc/man-putty.but b/doc/man-putty.but index a1656d6c..858ec0b0 100644 --- a/doc/man-putty.but +++ b/doc/man-putty.but @@ -287,6 +287,15 @@ which of the agent's keys to use. } \dd Allow use of an authentication agent. (This option is only necessary to override a setting in a saved session.) +\dt \cw{\-no\-trivial\-auth} + +\dd Disconnect from any SSH server which accepts authentication without +ever having asked for any kind of password or signature or token. (You +might want to enable this for a server you always expect to challenge +you, for instance to ensure you don't accidentally type your key file's +passphrase into a compromised server spoofing PuTTY's passphrase +prompt.) + \dt \cw{\-hostkey} \e{key} \dd Specify an acceptable host public key. This option may be specified diff --git a/doc/plink.but b/doc/plink.but index fcfb5f68..30dcead1 100644 --- a/doc/plink.but +++ b/doc/plink.but @@ -41,7 +41,7 @@ use Plink: \c C:\>plink \c Plink: command-line connection utility -\c Release 0.75 +\c Release 0.76 \c Usage: plink [options] [user@]host [command] \c ("host" can also be a PuTTY saved session name) \c Options: @@ -77,6 +77,8 @@ use Plink: \c -i key private key file for user authentication \c -noagent disable use of Pageant \c -agent enable use of Pageant +\c -no-trivial-auth +\c disconnect if SSH authentication succeeds trivially \c -noshare disable use of connection sharing \c -share enable use of connection sharing \c -hostkey keyid diff --git a/doc/pscp.but b/doc/pscp.but index 9d8daccd..e816f3e5 100644 --- a/doc/pscp.but +++ b/doc/pscp.but @@ -39,7 +39,7 @@ use PSCP: \c C:\>pscp \c PuTTY Secure Copy client -\c Release 0.75 +\c Release 0.76 \c Usage: pscp [options] [user@]host:source target \c pscp [options] source [source...] [user@]host:target \c pscp [options] -ls [user@]host:filespec @@ -62,6 +62,8 @@ use PSCP: \c -i key private key file for user authentication \c -noagent disable use of Pageant \c -agent enable use of Pageant +\c -no-trivial-auth +\c disconnect if SSH authentication succeeds trivially \c -hostkey keyid \c manually specify a host key (may be repeated) \c -batch disable all interactive prompts diff --git a/doc/using.but b/doc/using.but index b583dc8c..02a67808 100644 --- a/doc/using.but +++ b/doc/using.but @@ -1014,6 +1014,15 @@ This option is equivalent to the \q{Private key file for authentication} box in the Auth panel of the PuTTY configuration box (see \k{config-ssh-privkey}). +\S2{using-cmdline-no-trivial-auth} \i\c{-no-trivial-auth}: disconnect +if SSH authentication succeeds trivially + +This option causes PuTTY to abandon an SSH session if the server +accepts authentication without ever having asked for any kind of +password or signature or token. + +See \k{config-ssh-notrivialauth} for why you might want this. + \S2{using-cmdline-loghost} \i\c{-loghost}: specify a \i{logical host name} diff --git a/windows/window.c b/windows/window.c index c3c57eb5..702a98f7 100644 --- a/windows/window.c +++ b/windows/window.c @@ -3761,57 +3761,54 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, if (GetScrollInfo(hwnd, SB_VERT, &si) == 0) si.nTrackPos = HIWORD(wParam); term_scroll(term, 1, si.nTrackPos); - - if (in_scrollbar_loop) { - /* - * Allow window updates to happen during interactive - * scroll. - * - * When the user takes hold of our window's scrollbar - * and wobbles it interactively back and forth, the - * first thing that happens is that this window - * procedure receives WM_SYSCOMMAND / SC_VSCROLL. [1] - * The default handler for that window message starts - * a subsidiary message loop, which continues to run - * until the user lets go of the scrollbar again. All - * WM_VSCROLL / SB_THUMBTRACK messages are generated - * by the handlers within that subsidiary message - * loop. - * - * So, during that time, _our_ message loop is not - * running, which means toplevel callbacks and timers - * and so forth are not happening, which means that - * when we redraw the window and set a timer to clear - * the cooldown flag 20ms later, that timer never - * fires, and we aren't able to keep redrawing the - * window. - * - * The 'obvious' answer would be to seize that - * SYSCOMMAND ourselves and inhibit the default - * handler, so that our message loop carries on - * running. But that would mean we'd have to - * reimplement the whole of the scrollbar handler! - * - * So instead we apply a bodge: set a static variable - * that indicates that we're _in_ that sub-loop, and - * if so, decide it's OK to manually call - * term_update() proper, bypassing the timer and - * cooldown and rate-limiting systems completely, - * whenever we see an SB_THUMBTRACK. This shouldn't - * cause a rate overload, because we're only doing it - * once per UI event! - * - * [1] Actually, there's an extra oddity where - * SC_HSCROLL and SC_VSCROLL have their documented - * values the wrong way round. Many people on the - * Internet have noticed this, e.g. - * https://stackoverflow.com/q/55528397 - */ - term_update(term); - } break; } } + + if (in_scrollbar_loop) { + /* + * Allow window updates to happen during interactive + * scroll. + * + * When the user takes hold of our window's scrollbar and + * wobbles it interactively back and forth, or presses on + * one of the arrow buttons at the ends, the first thing + * that happens is that this window procedure receives + * WM_SYSCOMMAND / SC_VSCROLL. [1] The default handler for + * that window message starts a subsidiary message loop, + * which continues to run until the user lets go of the + * scrollbar again. All WM_VSCROLL / SB_THUMBTRACK + * messages are generated by the handlers within that + * subsidiary message loop. + * + * So, during that time, _our_ message loop is not + * running, which means toplevel callbacks and timers and + * so forth are not happening, which means that when we + * redraw the window and set a timer to clear the cooldown + * flag 20ms later, that timer never fires, and we aren't + * able to keep redrawing the window. + * + * The 'obvious' answer would be to seize that SYSCOMMAND + * ourselves and inhibit the default handler, so that our + * message loop carries on running. But that would mean + * we'd have to reimplement the whole of the scrollbar + * handler! + * + * So instead we apply a bodge: set a static variable that + * indicates that we're _in_ that sub-loop, and if so, + * decide it's OK to manually call term_update() proper, + * bypassing the timer and cooldown and rate-limiting + * systems completely, whenever we see an SB_THUMBTRACK. + * This shouldn't cause a rate overload, because we're + * only doing it once per UI event! + * + * [1] Actually, there's an extra oddity where SC_HSCROLL + * and SC_VSCROLL have their documented values the wrong + * way round. Many people on the Internet have noticed + * this, e.g. https://stackoverflow.com/q/55528397 + */ + term_update(term); + } break; case WM_PALETTECHANGED: if ((HWND) wParam != hwnd && pal != NULL) { @@ -6221,8 +6218,10 @@ static void wintw_clip_request_paste(TermWin *tw, int clipboard) * that tells us it's OK to paste. */ DWORD in_threadid; /* required for Win9x */ - CreateThread(NULL, 0, clipboard_read_threadfunc, - wgs.term_hwnd, 0, &in_threadid); + HANDLE hThread = CreateThread(NULL, 0, clipboard_read_threadfunc, + wgs.term_hwnd, 0, &in_threadid); + if (hThread) + CloseHandle(hThread); /* we don't need the thread handle */ } /* diff --git a/windows/wingss.c b/windows/wingss.c index 7d7b5d4c..d2f3634f 100644 --- a/windows/wingss.c +++ b/windows/wingss.c @@ -95,6 +95,28 @@ const char *gsslogmsg = NULL; static void ssh_sspi_bind_fns(struct ssh_gss_library *lib); +static tree234 *libraries_to_never_unload; +static int library_to_never_unload_cmp(void *av, void *bv) +{ + uintptr_t a = (uintptr_t)av, b = (uintptr_t)bv; + return a < b ? -1 : a > b ? +1 : 0; +} +static void ensure_library_tree_exists(void) +{ + if (!libraries_to_never_unload) + libraries_to_never_unload = newtree234(library_to_never_unload_cmp); +} +static bool library_is_in_never_unload_tree(HMODULE module) +{ + ensure_library_tree_exists(); + return find234(libraries_to_never_unload, module, NULL); +} +static void add_library_to_never_unload_tree(HMODULE module) +{ + ensure_library_tree_exists(); + add234(libraries_to_never_unload, module); +} + struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) { HMODULE module; @@ -145,6 +167,23 @@ struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) LOAD_LIBRARY_SEARCH_SYSTEM32 | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_USER_DIRS); + + /* + * The MIT Kerberos DLL suffers an internal segfault + * for some reason if you unload and reload one within + * the same process. So, make sure that after we load + * this library, we never free it. + * + * Or rather: after we've loaded it once, if any + * _further_ load returns the same module handle, we + * immediately free it again (to prevent the Windows + * API's internal reference count growing without + * bound). But on the other hand we never free it in + * ssh_gss_cleanup. + */ + if (library_is_in_never_unload_tree(module)) + FreeLibrary(module); + add_library_to_never_unload_tree(module); } sfree(buffer); } @@ -280,7 +319,11 @@ void ssh_gss_cleanup(struct ssh_gss_liblist *list) * another SSH instance still using it. */ for (i = 0; i < list->nlibraries; i++) { - FreeLibrary((HMODULE)list->libraries[i].handle); + if (list->libraries[i].id != 0) { + HMODULE module = (HMODULE)list->libraries[i].handle; + if (!library_is_in_never_unload_tree(module)) + FreeLibrary(module); + } if (list->libraries[i].id == 2) { /* The 'custom' id involves a dynamically allocated message. * Note that we must cast away the 'const' to free it. */ diff --git a/windows/winhandl.c b/windows/winhandl.c index 085f30a0..82d2aded 100644 --- a/windows/winhandl.c +++ b/windows/winhandl.c @@ -451,8 +451,10 @@ struct handle *handle_input_new(HANDLE handle, handle_inputfn_t gotdata, handles_by_evtomain = newtree234(handle_cmp_evtomain); add234(handles_by_evtomain, h); - CreateThread(NULL, 0, handle_input_threadfunc, - &h->u.i, 0, &in_threadid); + HANDLE hThread = CreateThread(NULL, 0, handle_input_threadfunc, + &h->u.i, 0, &in_threadid); + if (hThread) + CloseHandle(hThread); /* we don't need the thread handle */ h->u.i.busy = true; return h; @@ -482,8 +484,10 @@ struct handle *handle_output_new(HANDLE handle, handle_outputfn_t sentdata, handles_by_evtomain = newtree234(handle_cmp_evtomain); add234(handles_by_evtomain, h); - CreateThread(NULL, 0, handle_output_threadfunc, - &h->u.o, 0, &out_threadid); + HANDLE hThread = CreateThread(NULL, 0, handle_output_threadfunc, + &h->u.o, 0, &out_threadid); + if (hThread) + CloseHandle(hThread); /* we don't need the thread handle */ return h; } diff --git a/windows/winpgen.c b/windows/winpgen.c index 0157cc67..2f7ab4ec 100644 --- a/windows/winpgen.c +++ b/windows/winpgen.c @@ -1168,13 +1168,15 @@ static void start_generating_key(HWND hwnd, struct MainDlgState *state) params->key = &state->key; params->dsskey = &state->dsskey; - if (!CreateThread(NULL, 0, generate_key_thread, - params, 0, &threadid)) { + HANDLE hThread = CreateThread(NULL, 0, generate_key_thread, + params, 0, &threadid); + if (!hThread) { MessageBox(hwnd, "Out of thread resources", "Key generation error", MB_OK | MB_ICONERROR); sfree(params); } else { + CloseHandle(hThread); /* we don't need the thread handle */ state->generation_thread_exists = true; } } diff --git a/windows/winpgnt.c b/windows/winpgnt.c index d19f57d7..aec82e10 100644 --- a/windows/winpgnt.c +++ b/windows/winpgnt.c @@ -2238,8 +2238,10 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) DWORD wm_copydata_threadid; wmct.ev_msg_ready = CreateEvent(NULL, false, false, NULL); wmct.ev_reply_ready = CreateEvent(NULL, false, false, NULL); - CreateThread(NULL, 0, wm_copydata_threadfunc, - &inst, 0, &wm_copydata_threadid); + 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); if (show_keylist_on_startup)