From 557a99e78e51e791eac0216aa5a4b01005ac5115 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 25 Jul 2015 11:28:32 +0100 Subject: [PATCH 01/22] Post-0.65 release checklist updates. The -F option is no longer needed to bob in this situation; that hasn't been the directory I keep release announcements in for a long time; the Docs page needs adjusting for pre-release retirement as well as the Downloads page. (cherry picked from commit 9bea08a298580c98889834cbaffe289301753989) --- CHECKLST.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHECKLST.txt b/CHECKLST.txt index da3f2542..d23b3f4e 100644 --- a/CHECKLST.txt +++ b/CHECKLST.txt @@ -93,7 +93,7 @@ for it: - Write a release announcement (basically a summary of the changes since the last release). Squirrel it away in - atreus:src/putty/local/announce- in case it's needed again + atreus:src/putty-local/announce- in case it's needed again within days of the release going out. - Update the web site, in a local checkout. @@ -106,6 +106,7 @@ for it: pre-releases, if there were any before this release. Comment out the big pre-release section at the top, and also adjust the sections about source archives at the bottom. + + Do the same on the Docs page. + Adjust header text on Changelog page. (That includes changing `are new' in previous version to `were new'!) + .htaccess has an individual redirect for each version number. Add @@ -121,7 +122,7 @@ for it: - Build the release, by checking out the release tag: git checkout 0.XX - bob -F . RELEASE=0.XX + bob . RELEASE=0.XX This should generate a basically valid release directory as `build.out/putty', and provide link maps and sign.sh alongside that in build.out. From 1f7f422d7aef019a6e17526f97ba9d15b56e78d3 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 27 Jul 2015 20:06:02 +0100 Subject: [PATCH 02/22] New centralised helper function dup_mb_to_wc(). PuTTY's main mb_to_wc() function is all very well for embedding in fiddly data pipelines, but for the simple job of turning a C string into a C wide string, really I want something much more like dupprintf. So here is one. I've had to put it in a new separate source file miscucs.c rather than throwing it into misc.c, because misc.c is linked into tools that don't also include a module providing the internal Unicode API (winucs or uxucs). The new miscucs.c appears only in Unicode-using tools. (cherry picked from commit 7762d7122609207059cf5cf58fb2b9c2de98dd36) --- Recipe | 4 ++-- misc.h | 9 +++++++++ miscucs.c | 28 ++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 miscucs.c diff --git a/Recipe b/Recipe index ca6369d5..fc663296 100644 --- a/Recipe +++ b/Recipe @@ -203,10 +203,10 @@ TERMINAL = terminal wcwidth ldiscucs logging tree234 minibidi # GUI front end and terminal emulator (putty, puttytel). GUITERM = TERMINAL window windlg winctrls sizetip winucs winprint - + winutils wincfg sercfg winhelp winjump + + winutils wincfg sercfg winhelp winjump miscucs # Same thing on Unix. -UXTERM = TERMINAL uxcfg sercfg uxucs uxprint timing callback +UXTERM = TERMINAL uxcfg sercfg uxucs uxprint timing callback miscucs GTKTERM = UXTERM gtkwin gtkcfg gtkdlg gtkfont gtkcols xkeysym OSXTERM = UXTERM osxwin osxdlg osxctrls diff --git a/misc.h b/misc.h index c38266d0..a16a2fa0 100644 --- a/misc.h +++ b/misc.h @@ -39,6 +39,15 @@ char *dupprintf(const char *fmt, ...) char *dupvprintf(const char *fmt, va_list ap); void burnstr(char *string); +/* String-to-Unicode converters that auto-allocate the destination and + * work around the rather deficient interface of mb_to_wc. + * + * These actually live in miscucs.c, not misc.c (the distinction being + * that the former is only linked into tools that also have the main + * Unicode support). */ +wchar_t *dup_mb_to_wc_c(int codepage, int flags, const char *string, int len); +wchar_t *dup_mb_to_wc(int codepage, int flags, const char *string); + int toint(unsigned); char *fgetline(FILE *fp); diff --git a/miscucs.c b/miscucs.c new file mode 100644 index 00000000..7785f9b6 --- /dev/null +++ b/miscucs.c @@ -0,0 +1,28 @@ +/* + * Centralised Unicode-related helper functions, separate from misc.c + * so that they can be omitted from tools that aren't including + * Unicode handling. + */ + +#include "putty.h" +#include "misc.h" + +wchar_t *dup_mb_to_wc_c(int codepage, int flags, const char *string, int len) +{ + int mult; + for (mult = 1 ;; mult++) { + wchar_t *ret = snewn(mult*len + 2, wchar_t); + int outlen; + outlen = mb_to_wc(codepage, flags, string, len, ret, mult*len + 1); + if (outlen < mult*len+1) { + ret[outlen] = L'\0'; + return ret; + } + sfree(ret); + } +} + +wchar_t *dup_mb_to_wc(int codepage, int flags, const char *string) +{ + return dup_mb_to_wc_c(codepage, flags, string, strlen(string)); +} From 3dfb9ac8851fda0599b32495099cb91c2bbf4a62 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 27 Jul 2015 20:06:02 +0100 Subject: [PATCH 03/22] Turn the Windows PuTTY main window into a Unicode window. This causes WM_CHAR messages sent to us to have a wParam containing a 16-bit value encoded in UTF-16, rather than an 8-bit value encoded in the system code page. As far as I can tell, there aren't many other knock-on effects - e.g. you can still interact with the window using ordinary char-based API functions such as SetWindowText, and the Windows API will do the necessary conversions behind the scenes. However, even so, I'm half expecting some sort of unforeseen bug to show up as a result of this. (cherry picked from commit 67e5ceb9a8e6bc20fa0e0cf82ee9f89582e94112) --- windows/window.c | 44 +++++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/windows/window.c b/windows/window.c index ebcecac7..5bacdce5 100644 --- a/windows/window.c +++ b/windows/window.c @@ -332,7 +332,6 @@ static void close_session(void *ignored_context) int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) { - WNDCLASS wndclass; MSG msg; HRESULT hr; int guess_width, guess_height; @@ -645,6 +644,8 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) } if (!prev) { + WNDCLASSW wndclass; + wndclass.style = 0; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0; @@ -654,9 +655,9 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM); wndclass.hbrBackground = NULL; wndclass.lpszMenuName = NULL; - wndclass.lpszClassName = appname; + wndclass.lpszClassName = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname); - RegisterClass(&wndclass); + RegisterClassW(&wndclass); } memset(&ucsdata, 0, sizeof(ucsdata)); @@ -690,6 +691,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) { int winmode = WS_OVERLAPPEDWINDOW | WS_VSCROLL; int exwinmode = 0; + wchar_t *uappname = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname); if (!conf_get_int(conf, CONF_scrollbar)) winmode &= ~(WS_VSCROLL); if (conf_get_int(conf, CONF_resize_action) == RESIZE_DISABLED) @@ -698,10 +700,11 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) exwinmode |= WS_EX_TOPMOST; if (conf_get_int(conf, CONF_sunken_edge)) exwinmode |= WS_EX_CLIENTEDGE; - hwnd = CreateWindowEx(exwinmode, appname, appname, - winmode, CW_USEDEFAULT, CW_USEDEFAULT, - guess_width, guess_height, - NULL, NULL, inst, NULL); + hwnd = CreateWindowExW(exwinmode, uappname, uappname, + winmode, CW_USEDEFAULT, CW_USEDEFAULT, + guess_width, guess_height, + NULL, NULL, inst, NULL); + sfree(uappname); } /* @@ -888,12 +891,12 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) } else sfree(handles); - while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) goto finished; /* two-level break */ if (!(IsWindow(logbox) && IsDialogMessage(logbox, &msg))) - DispatchMessage(&msg); + DispatchMessageW(&msg); /* * WM_NETEVENT messages seem to jump ahead of others in @@ -3095,7 +3098,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, } else { len = TranslateKey(message, wParam, lParam, buf); if (len == -1) - return DefWindowProc(hwnd, message, wParam, lParam); + return DefWindowProcW(hwnd, message, wParam, lParam); if (len != 0) { /* @@ -3199,10 +3202,21 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, * we're ready to cope. */ { - char c = (unsigned char)wParam; - term_seen_key_event(term); - if (ldisc) - lpage_send(ldisc, CP_ACP, &c, 1, 1); + static wchar_t pending_surrogate = 0; + wchar_t c = wParam; + + if (IS_HIGH_SURROGATE(c)) { + pending_surrogate = c; + } else if (IS_SURROGATE_PAIR(pending_surrogate, c)) { + wchar_t pair[2]; + pair[0] = pending_surrogate; + pair[1] = c; + term_seen_key_event(term); + luni_send(ldisc, pair, 2, 1); + } else if (!IS_SURROGATE(c)) { + term_seen_key_event(term); + luni_send(ldisc, &c, 1, 1); + } } return 0; case WM_SYSCOLORCHANGE: @@ -3287,7 +3301,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, * Any messages we don't process completely above are passed through to * DefWindowProc() for default processing. */ - return DefWindowProc(hwnd, message, wParam, lParam); + return DefWindowProcW(hwnd, message, wParam, lParam); } /* From d4e5b0dd1c2fbdc49bc5196918a489972349dc93 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 27 Jul 2015 20:06:02 +0100 Subject: [PATCH 04/22] Handle the VK_PACKET virtual key code. This is generated in response to the SendInput() Windows API call, if that in turn is passed an KEYBDINPUT structure with KEYEVENTF_UNICODE set. That method of input generation is used by programs such as 'WinCompose' to send an arbitrary Unicode character as if it had been typed at the keyboard, even if the keyboard doesn't actually provide a key for it. Like VK_PROCESSKEY, this key code is an exception to our usual policy of manually translating keystrokes: we handle it by calling TranslateMessage, to get back the Unicode character it contains as a WM_CHAR message. (If that Unicode character in turn is outside the BMP, it may come back as a pair of WM_CHARs in succession containing UTF-16 surrogates; if so, that's OK, because the new Unicode WM_CHAR handler can cope.) (cherry picked from commit 65f3500906c38ee3cf66cc75a015058e5bc6e56d) --- windows/window.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/windows/window.c b/windows/window.c index 5bacdce5..2bbdc263 100644 --- a/windows/window.c +++ b/windows/window.c @@ -76,6 +76,11 @@ #define WHEEL_DELTA 120 #endif +/* VK_PACKET, used to send Unicode characters in WM_KEYDOWNs */ +#ifndef VK_PACKET +#define VK_PACKET 0xE7 +#endif + static Mouse_Button translate_button(Mouse_Button button); static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, @@ -3086,7 +3091,8 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, unsigned char buf[20]; int len; - if (wParam == VK_PROCESSKEY) { /* IME PROCESS key */ + if (wParam == VK_PROCESSKEY || /* IME PROCESS key */ + wParam == VK_PACKET) { /* 'this key is a Unicode char' */ if (message == WM_KEYDOWN) { MSG m; m.hwnd = hwnd; From d61c6cad0bb2854f88ced9bf7f347d81e33ec7a3 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 1 Aug 2015 22:11:16 +0100 Subject: [PATCH 05/22] Don't try to load GSSAPI libs unless we'll use them. A user reports that in a particular situation one of the calls to LoadLibrary from wingss.c has unwanted side effects, and points out that this happens even when the saved session has GSSAPI disabled. So I've evaluated as much as possible of the condition under which we check the results of GSS library loading, and deferred the library loading itself until after that condition says we even care about the results. (cherry picked from commit 9a08d9a7c10458356b934af54206f0b642ecf715) --- ssh.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/ssh.c b/ssh.c index 34500821..cf4f0bfc 100644 --- a/ssh.c +++ b/ssh.c @@ -9148,11 +9148,20 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, s->can_keyb_inter = conf_get_int(ssh->conf, CONF_try_ki_auth) && in_commasep_string("keyboard-interactive", methods, methlen); #ifndef NO_GSSAPI - if (!ssh->gsslibs) - ssh->gsslibs = ssh_gss_setup(ssh->conf); - s->can_gssapi = conf_get_int(ssh->conf, CONF_try_gssapi_auth) && - in_commasep_string("gssapi-with-mic", methods, methlen) && - ssh->gsslibs->nlibraries > 0; + if (conf_get_int(ssh->conf, CONF_try_gssapi_auth) && + in_commasep_string("gssapi-with-mic", methods, methlen)) { + /* Try loading the GSS libraries and see if we + * have any. */ + if (!ssh->gsslibs) + ssh->gsslibs = ssh_gss_setup(ssh->conf); + s->can_gssapi = (ssh->gsslibs->nlibraries > 0); + } else { + /* No point in even bothering to try to load the + * GSS libraries, if the user configuration and + * server aren't both prepared to attempt GSSAPI + * auth in the first place. */ + s->can_gssapi = FALSE; + } #endif } From f59445004e8748d4be12066bb0511f7d06d4806a Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 6 Aug 2015 19:25:56 +0100 Subject: [PATCH 06/22] Work around a failure in Windows 10 jump lists. We've had several reports that launching saved sessions from the Windows 10 jump list fails; Changyu Li reports that this is because we create those IShellLink objects with a command line string starting with @, and in Windows 10 that causes the SetArguments method to silently do the wrong thing. (cherry picked from commit 8bf5c1b31f1a1449d694e3604e293b0831eb2657) --- windows/winjump.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/windows/winjump.c b/windows/winjump.c index f674133b..eca5041a 100644 --- a/windows/winjump.c +++ b/windows/winjump.c @@ -429,7 +429,11 @@ static IShellLink *make_shell_link(const char *appname, ret->lpVtbl->SetPath(ret, app_path); if (sessionname) { - param_string = dupcat("@", sessionname, NULL); + /* The leading space is reported to work around a Windows 10 + * behaviour change in which an argument string starting with + * '@' causes the SetArguments method to silently do the wrong + * thing. */ + param_string = dupcat(" @", sessionname, NULL); } else { param_string = dupstr(""); } From 417421caced7a133a66a3a7bcd1e427457c85db3 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 8 Aug 2015 13:35:44 +0100 Subject: [PATCH 07/22] New formatting directive in logfile naming: &P for port number. Users have requested this from time to time, for distinguishing log file names when there's more than one SSH server running on different ports of the same host. Since we do take account of that possibility in other areas (e.g. we cache host keys indexed by (host,port) rather than just host), it doesn't seem unreasonable to do so here too. (cherry picked from commit 0550943b51c538400e31ce18483032e446178120) --- config.c | 2 +- doc/config.but | 3 +++ logging.c | 14 ++++++++++---- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/config.c b/config.c index 3100503f..086956fc 100644 --- a/config.c +++ b/config.c @@ -1474,7 +1474,7 @@ void setup_config_box(struct controlbox *b, int midsession, HELPCTX(logging_filename), conf_filesel_handler, I(CONF_logfilename)); ctrl_text(s, "(Log file name can contain &Y, &M, &D for date," - " &T for time, and &H for host name)", + " &T for time, &H for host name, and &P for port number)", HELPCTX(logging_filename)); ctrl_radiobuttons(s, "What to do if the log file already exists:", 'e', 1, HELPCTX(logging_exists), diff --git a/doc/config.but b/doc/config.but index c23f4ddb..6d35f9c8 100644 --- a/doc/config.but +++ b/doc/config.but @@ -207,6 +207,9 @@ digits. \b \c{&H} will be replaced by the host name you are connecting to. +\b \c{&P} will be replaced by the port number you are connecting to on +the target host. + For example, if you enter the host name \c{c:\\puttylogs\\log-&h-&y&m&d-&t.dat}, you will end up with files looking like diff --git a/logging.c b/logging.c index 26634f77..b57888fd 100644 --- a/logging.c +++ b/logging.c @@ -22,7 +22,8 @@ struct LogContext { int logtype; /* cached out of conf */ }; -static Filename *xlatlognam(Filename *s, char *hostname, struct tm *tm); +static Filename *xlatlognam(Filename *s, char *hostname, int port, + struct tm *tm); /* * Internal wrapper function which must be called for _all_ output @@ -159,7 +160,8 @@ void logfopen(void *handle) filename_free(ctx->currlogfilename); ctx->currlogfilename = xlatlognam(conf_get_filename(ctx->conf, CONF_logfilename), - conf_get_str(ctx->conf, CONF_host), &tm); + conf_get_str(ctx->conf, CONF_host), + conf_get_int(ctx->conf, CONF_port), &tm); ctx->lgfp = f_open(ctx->currlogfilename, "r", FALSE); /* file already present? */ if (ctx->lgfp) { @@ -408,9 +410,10 @@ void log_reconfig(void *handle, Conf *conf) * * "&Y":YYYY "&m":MM "&d":DD "&T":hhmmss "&h": "&&":& */ -static Filename *xlatlognam(Filename *src, char *hostname, struct tm *tm) +static Filename *xlatlognam(Filename *src, char *hostname, int port, + struct tm *tm) { - char buf[10], *bufp; + char buf[32], *bufp; int size; char *buffer; int buflen, bufsize; @@ -446,6 +449,9 @@ static Filename *xlatlognam(Filename *src, char *hostname, struct tm *tm) bufp = hostname; size = strlen(bufp); break; + case 'p': + size = sprintf(buf, "%d", port); + break; default: buf[0] = '&'; size = 1; From 0eb3bf07fc3f89a7cec499f1730f48fa89a7d37f Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 22 Aug 2015 15:05:12 +0100 Subject: [PATCH 08/22] 'pterm --display' should set $DISPLAY inside the terminal. If you open a pterm on a different display via the --display command-line option rather than by setting $DISPLAY, I think (and other terminals seem to agree) that it's sensible to set $DISPLAY anyway for processes running inside the terminal. (cherry picked from commit dc16dd5aa4a3fb2e367bc6303963321e628594ac) --- unix/uxpty.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/unix/uxpty.c b/unix/uxpty.c index 54ba082c..307690d6 100644 --- a/unix/uxpty.c +++ b/unix/uxpty.c @@ -834,6 +834,19 @@ static const char *pty_init(void *frontend, void **backend_handle, Conf *conf, * environment in place. */ } + { + /* + * In case we were invoked with a --display argument that + * doesn't match DISPLAY in our actual environment, we + * should set DISPLAY for processes running inside the + * terminal to match the display the terminal itself is + * on. + */ + const char *x_display = get_x_display(pty->frontend); + char *x_display_env_var = dupprintf("DISPLAY=%s", x_display); + putenv(x_display_env_var); + /* As above, we don't free this. */ + } #endif { char *key, *val; From 14464764da5d86934169d17188534fbdc5fd4b5b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 23 Aug 2015 14:13:30 +0100 Subject: [PATCH 09/22] Performance: cache character widths returned from Pango. Profiling reveals that pterm in Pango rendering mode uses an absurd amount of CPU when it's not even actually _drawing_ the text, because of all the calls to pango_layout_get_pixel_extents() while pangofont_draw_text tries to work out which characters it can safely draw as part of a long string. Caching the results speeds things up greatly. (cherry picked from commit c3ef30c883e3657ff57679fb611f1f6ee6f33dba) --- unix/gtkfont.c | 96 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 65 insertions(+), 31 deletions(-) diff --git a/unix/gtkfont.c b/unix/gtkfont.c index d241db0e..b152f2bb 100644 --- a/unix/gtkfont.c +++ b/unix/gtkfont.c @@ -870,6 +870,13 @@ struct pangofont { * Data passed in to unifont_create(). */ int bold, shadowoffset, shadowalways; + /* + * Cache of character widths, indexed by Unicode code point. In + * pixels; -1 means we haven't asked Pango about this character + * before. + */ + int *widthcache; + unsigned nwidthcache; }; static const struct unifont_vtable pangofont_vtable = { @@ -986,6 +993,8 @@ static unifont *pangofont_create_internal(GtkWidget *widget, pfont->bold = bold; pfont->shadowoffset = shadowoffset; pfont->shadowalways = shadowalways; + pfont->widthcache = NULL; + pfont->nwidthcache = 0; pango_font_metrics_unref(metrics); @@ -1039,10 +1048,40 @@ static void pangofont_destroy(unifont *font) { struct pangofont *pfont = (struct pangofont *)font; pango_font_description_free(pfont->desc); + sfree(pfont->widthcache); g_object_unref(pfont->fset); sfree(font); } +static int pangofont_char_width(PangoLayout *layout, struct pangofont *pfont, + wchar_t uchr, const char *utfchr, int utflen) +{ + /* + * Here we check whether a character has the same width as the + * character cell it'll be drawn in. Because profiling showed that + * pango_layout_get_pixel_extents() was a huge bottleneck when we + * were calling it every time we needed to know this, we instead + * call it only on characters we don't already know about, and + * cache the results. + */ + + if ((unsigned)uchr >= pfont->nwidthcache) { + unsigned newsize = ((int)uchr + 0x100) & ~0xFF; + pfont->widthcache = sresize(pfont->widthcache, newsize, int); + while (pfont->nwidthcache < newsize) + pfont->widthcache[pfont->nwidthcache++] = -1; + } + + if (pfont->widthcache[uchr] < 0) { + PangoRectangle rect; + pango_layout_set_text(layout, utfchr, utflen); + pango_layout_get_pixel_extents(layout, NULL, &rect); + pfont->widthcache[uchr] = rect.width; + } + + return pfont->widthcache[uchr]; +} + static int pangofont_has_glyph(unifont *font, wchar_t glyph) { /* Pango implements font fallback, so assume it has everything */ @@ -1125,39 +1164,34 @@ static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, clen++; n = 1; - /* - * If it's a right-to-left character, we must display it on - * its own, to stop Pango helpfully re-reversing our already - * reversed text. - */ - if (!is_rtl(string[0])) { - + if (is_rtl(string[0]) || + pangofont_char_width(layout, pfont, string[n-1], + utfptr, clen) != cellwidth) { /* - * See if that character has the width we expect. + * If this character is a right-to-left one, or has an + * unusual width, then we must display it on its own. */ - pango_layout_set_text(layout, utfptr, clen); - pango_layout_get_pixel_extents(layout, NULL, &rect); - - if (rect.width == cellwidth) { - /* - * Try extracting more characters, for as long as they - * stay well-behaved. - */ - while (clen < utflen) { - int oldclen = clen; - clen++; /* skip UTF-8 introducer byte */ - while (clen < utflen && - (unsigned char)utfptr[clen] >= 0x80 && - (unsigned char)utfptr[clen] < 0xC0) - clen++; - n++; - pango_layout_set_text(layout, utfptr, clen); - pango_layout_get_pixel_extents(layout, NULL, &rect); - if (rect.width != n * cellwidth) { - clen = oldclen; - n--; - break; - } + } else { + /* + * Try to amalgamate a contiguous string of characters + * with the expected sensible width, for the common case + * in which we're using a monospaced font and everything + * works as expected. + */ + while (clen < utflen) { + int oldclen = clen; + clen++; /* skip UTF-8 introducer byte */ + while (clen < utflen && + (unsigned char)utfptr[clen] >= 0x80 && + (unsigned char)utfptr[clen] < 0xC0) + clen++; + n++; + if (pangofont_char_width(layout, pfont, + string[n-1], utfptr + oldclen, + clen - oldclen) != cellwidth) { + clen = oldclen; + n--; + break; } } } From eb319f9b6e960b6c00a10c31c747ee484aad6ee1 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 1 Sep 2015 18:35:38 +0100 Subject: [PATCH 10/22] pterm: set IUTF8 on pty devices depending on charset. In a UTF-8 pterm, it makes sense to set the IUTF8 flag (on systems that have one) on the pty device, so that line editing will take account of UTF-8 multibyte characters. (cherry picked from commit 1840103c05d10ba1c45353282b4ad7f742a75b92) --- unix/gtkwin.c | 6 ++++++ unix/unix.h | 1 + unix/uxpty.c | 19 +++++++++++++++++-- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/unix/gtkwin.c b/unix/gtkwin.c index 4cce11e6..1c29b41e 100644 --- a/unix/gtkwin.c +++ b/unix/gtkwin.c @@ -2914,6 +2914,12 @@ void uxsel_input_remove(int id) { gdk_input_remove(id); } +int frontend_is_utf8(void *frontend) +{ + struct gui_data *inst = (struct gui_data *)frontend; + return inst->ucsdata.line_codepage == CS_UTF8; +} + char *setup_fonts_ucs(struct gui_data *inst) { int shadowbold = conf_get_int(inst->conf, CONF_shadowbold); diff --git a/unix/unix.h b/unix/unix.h index 917976d6..e78800b5 100644 --- a/unix/unix.h +++ b/unix/unix.h @@ -77,6 +77,7 @@ unsigned long getticks(void); /* based on gettimeofday(2) */ char *get_x_display(void *frontend); int font_dimension(void *frontend, int which);/* 0 for width, 1 for height */ long get_windowid(void *frontend); +int frontend_is_utf8(void *frontend); /* Things gtkdlg.c needs from pterm.c */ void *get_window(void *frontend); /* void * to avoid depending on gtk.h */ diff --git a/unix/uxpty.c b/unix/uxpty.c index 307690d6..e504b705 100644 --- a/unix/uxpty.c +++ b/unix/uxpty.c @@ -738,14 +738,29 @@ static const char *pty_init(void *frontend, void **backend_handle, Conf *conf, pty_open_master(pty); /* - * Set the backspace character to be whichever of ^H and ^? is - * specified by bksp_is_delete. + * Set up configuration-dependent termios settings on the new pty. */ { struct termios attrs; tcgetattr(pty->master_fd, &attrs); + + /* + * Set the backspace character to be whichever of ^H and ^? is + * specified by bksp_is_delete. + */ attrs.c_cc[VERASE] = conf_get_int(conf, CONF_bksp_is_delete) ? '\177' : '\010'; + + /* + * Set the IUTF8 bit iff the character set is UTF-8. + */ +#ifdef IUTF8 + if (frontend_is_utf8(frontend)) + attrs.c_iflag |= IUTF8; + else + attrs.c_iflag &= ~IUTF8; +#endif + tcsetattr(pty->master_fd, TCSANOW, &attrs); } From a063e522970946bf7d5dc052079d7773c0dee76d Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 2 Sep 2015 18:24:39 +0100 Subject: [PATCH 11/22] Key rollover: rewrite the PGP keys manual appendix. This gives pride of place to the new set of keys we've recently generated, and relegates the old ones to an afterthought. (cherry picked from commit bb68baf53bacfc71bb0144780cf0b2b63bd76f98) --- doc/index.but | 5 +- doc/pgpkeys.but | 202 ++++++++++++++++++++++++++++++------------------ 2 files changed, 131 insertions(+), 76 deletions(-) diff --git a/doc/index.but b/doc/index.but index 683b8ddc..77b3493a 100644 --- a/doc/index.but +++ b/doc/index.but @@ -843,8 +843,9 @@ saved sessions from \IM{version of PuTTY} version, of PuTTY -\IM{PGP signatures} PGP signatures, of PuTTY binaries -\IM{PGP signatures} signatures, of PuTTY binaries +\IM{GPG signatures} PGP signatures, of PuTTY binaries +\IM{GPG signatures} GPG signatures, of PuTTY binaries +\IM{GPG signatures} signatures, of PuTTY binaries \IM{logical host name} logical host name \IM{logical host name} host name, logical diff --git a/doc/pgpkeys.but b/doc/pgpkeys.but index e6f25c6b..e31bbdf6 100644 --- a/doc/pgpkeys.but +++ b/doc/pgpkeys.but @@ -2,7 +2,7 @@ \cfg{winhelp-topic}{pgpfingerprints} -\I{verifying new versions}We create \i{PGP signatures} for all the PuTTY +\I{verifying new versions}We create \i{GPG signatures} for all the PuTTY files distributed from our web site, so that users can be confident that the files have not been tampered with. Here we identify our public keys, and explain our signature policy so you can have an @@ -22,40 +22,49 @@ the origin of files distributed by the PuTTY team.) \H{pgpkeys-pubkey} Public keys -We supply two complete sets of keys. We supply a set of RSA keys, -compatible with both \W{http://www.gnupg.org/}{GnuPG} and PGP2, -and also a set of DSA keys compatible with GnuPG. +We maintain a set of three keys, stored with different levels of +security due to being used in different ways. See \k{pgpkeys-security} +below for details. -In each format, we have three keys: +The three keys we provide are: -\b A Development Snapshots key, used to sign the nightly builds. +\dt Snapshot Key -\b A Releases key, used to sign actual releases. +\dd Used to sign routine development builds of PuTTY: nightly +snapshots, pre-releases, and sometimes also custom diagnostic builds +we send to particular users. -\b A Master Key. The Master Key is used to sign the other two keys, and -they sign it in return. +\dt Release Key -Therefore, we have six public keys in total: +\dd Used to sign manually released versions of PuTTY. -\b RSA: -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-rsa.asc}{Master Key}, -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-rsa.asc}{Release key}, -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-rsa.asc}{Snapshot key} +\dt Master Key -\lcont{ -Master Key: 1024-bit; \I{PGP key fingerprint}fingerprint: -\cw{8F\_15\_97\_DA\_25\_30\_AB\_0D\_\_88\_D1\_92\_54\_11\_CF\_0C\_4C} -} +\dd Used to tie the other two keys into the GPG web of trust. The +Master Key signs the other two keys, and other GPG users have signed +it in turn. -\b DSA: -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-dsa.asc}{Master Key}, -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-dsa.asc}{Release key}, -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-dsa.asc}{Snapshot key} +The current issue of those three keys are available for download from +the PuTTY website, and are also available on PGP keyservers using the +key IDs listed below. -\lcont{ -Master Key: 1024-bit; fingerprint: -\cw{313C\_3E76\_4B74\_C2C5\_F2AE\_\_83A8\_4F5E\_6DF5\_6A93\_B34E} -} +\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-2015.asc}{\s{Master Key}} + +\dd RSA, 4096-bit. Key ID: \cw{4096R/04676F7C} (long version: +\cw{4096R/AB585DC604676F7C}). Fingerprint: +\cw{440D\_E3B5\_B7A1\_CA85\_B3CC\_\_1718\_AB58\_5DC6\_0467\_6F7C} + +\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-2015.asc}{\s{Release Key}} + +\dd RSA, 2048-bit. Key ID: \cw{2048R/B43434E4} (long version: +\cw{2048R/9DFE2648B43434E4}). Fingerprint: +\cw{0054\_DDAA\_8ADA\_15D2\_768A\_\_6DE7\_9DFE\_2648\_B434\_34E4} + +\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-2015.asc}{\s{Snapshot Key}} + +\dd RSA, 2048-bit. Key ID: \cw{2048R/D15F7E8A} (long version: +\cw{2048R/EEF20295D15F7E8A}). Fingerprint: +\cw{0A3B\_0048\_FE49\_9B67\_A234\_\_FEB6\_EEF2\_0295\_D15F\_7E8A} \H{pgpkeys-security} Security details @@ -63,77 +72,122 @@ The various keys have various different security levels. This section explains what those security levels are, and how far you can expect to trust each key. -\S{pgpkeys-snapshot} The Development Snapshots keys +\S{pgpkeys-snapshot} The Development Snapshots key -These keys are stored \e{without passphrases}. This is -necessary, because the snapshots are generated every night without -human intervention, so nobody would be able to type a passphrase. +The Development Snapshots private key is stored \e{without a +passphrase}. This is necessary, because the snapshots are generated +every night without human intervention, so nobody would be able to +type a passphrase. -The actual snapshots are built on a team member's home Windows box. -The keys themselves are stored on an independently run Unix box -(the same one that hosts our Git repository). After -being built, the binaries are uploaded to this Unix box and then -signed automatically. +The snapshots are built and signed on a team member's home computers, +before being uploaded to the web server from which you download them. -Therefore, a signature from one of the Development Snapshots keys -\e{DOES} protect you against: +Therefore, a signature from the Development Snapshots key \e{DOES} +protect you against: \b People tampering with the PuTTY binaries between the PuTTY web site and you. +\b The maintainers of our web server attempting to abuse their root +privilege to tamper with the binaries. + But it \e{DOES NOT} protect you against: -\b People tampering with the binaries before they are uploaded to the -independent Unix box. +\b People tampering with the binaries before they are uploaded to our +download servers. -\b The sysadmin of the independent Unix box using his root privilege to -steal the private keys and abuse them, or tampering with the -binaries before they are signed. +\b People tampering with the build machines so that the next set of +binaries they build will be malicious in some way. -\b Somebody getting root on the Unix box. +\b People stealing the unencrypted private key from the build machine +it lives on. -Of course, we don't believe any of those things is very likely. We -know our sysadmin personally and trust him (both to be competent and -to be non-malicious), and we take all reasonable precautions to -guard the build machine. But when you see a signature, you should -always be certain of precisely what it guarantees and precisely what -it does not. +Of course, we take all reasonable precautions to guard the build +machines. But when you see a signature, you should always be certain +of precisely what it guarantees and precisely what it does not. -\S{pgpkeys-release} The Releases keys +\S{pgpkeys-release} The Releases key -The Release keys have passphrases and we can be more careful about -how we use them. +The Releases key is more secure: because it is only used at release +time, to sign each release by hand, we can store it encrypted. -The Release keys are kept safe on the developers' own local -machines, and only used to sign releases that have been built by -hand. A signature from a Release key protects you from almost any -plausible attack. - -(Some of the developers' machines have cable modem connections and -might in theory be crackable, but of course the private keys are -still encrypted, so the crack would have to go unnoticed for long -enough to steal a passphrase.) +The Releases private key is kept encrypted on the developers' own +local machines. So an attacker wanting to steal it would have to also +steal the passphrase. \S{pgpkeys-master} The Master Keys -The Master Keys sign almost nothing. Their purpose is to bind the -other keys together and certify that they are all owned by the same -people and part of the same integrated setup. The only signatures -produced by the Master Keys, \e{ever}, should be the signatures -on the other keys. +The Master Key signs almost nothing. Its purpose is to bind the other +keys together and certify that they are all owned by the same people +and part of the same integrated setup. The only signatures produced by +the Master Key, \e{ever}, should be the signatures on the other keys. -We intend to arrange for the Master Keys to sign each other, to -certify that the DSA keys and RSA keys are part of the same setup. -We have not yet got round to this at the time of writing. +The Master Key is especially long, and its private key and passphrase +are stored with special care. -We have collected a few third-party signatures on the Master Keys, -in order to increase the chances that you can find a suitable trust -path to them. We intend to collect more. (Note that the keys on the -keyservers appear to have also collected some signatures from people -who haven't performed any verification of the Master Keys.) +We have collected some third-party signatures on the Master Key, in +order to increase the chances that you can find a suitable trust path +to them. We have uploaded our various keys to public keyservers, so that even if you don't know any of the people who have signed our keys, you can still be reasonably confident that an attacker would find it hard to substitute fake keys on all the public keyservers at once. + +\H{pgpkeys-rollover} Key rollover + +Our current three keys were generated in September 2015. Prior to +that, we had a much older set of keys generated in 2000. For each of +the three key types above, we provided both an RSA key \e{and} a DSA +key (because at the time we generated them, RSA was not in practice +available to everyone, due to export restrictions). + +The new Master Key is signed with both of the old ones, to show that +it really is owned by the same people and not substituted by an +attacker. Also, we have retrospectively signed the old Release Keys +with the new Master Key, in case you're trying to verify the +signatures on a release prior to the rollover and can find a chain of +trust to those keys from any of the people who have signed our new +Master Key. + +Future releases will be signed with the up-to-date keys shown above. +Releases prior to the rollover are signed with the old Release Keys. + +For completeness, those old keys are given here: + +\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-rsa.asc}{\s{Master Key} (original RSA)} + +\dd RSA, 1024-bit. Key ID: \cw{1024R/1E34AC41} (long version: +\cw{1024R/9D5877BF1E34AC41}). Fingerprint: +\cw{8F\_15\_97\_DA\_25\_30\_AB\_0D\_\_88\_D1\_92\_54\_11\_CF\_0C\_4C} + +\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-rsa.asc}{\s{Master Key} (original DSA)} + +\dd DSA, 1024-bit. Key ID: \cw{1024D/6A93B34E} (long version: +\cw{1024D/4F5E6DF56A93B34E}). Fingerprint: +\cw{313C\_3E76\_4B74\_C2C5\_F2AE\_\_83A8\_4F5E\_6DF5\_6A93\_B34E} + +\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-rsa.asc}{\s{Release Key} (original RSA)} + +\dd RSA, 1024-bit. Key ID: \cw{1024R/B41CAE29} (long version: +\cw{1024R/EF39CCC0B41CAE29}). Fingerprint: +\cw{AE\_65\_D3\_F7\_85\_D3\_18\_E0\_\_3B\_0C\_9B\_02\_FF\_3A\_81\_FE} + +\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-rsa.asc}{\s{Release Key} (original DSA)} + +\dd DSA, 1024-bit. Key ID: \cw{1024D/08B0A90B} (long version: +\cw{1024D/FECD6F3F08B0A90B}). Fingerprint: +\cw{00B1\_1009\_38E6\_9800\_6518\_\_F0AB\_FECD\_6F3F\_08B0\_A90B} + +\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-rsa.asc}{\s{Snapshot Key} (original RSA)} + +\dd RSA, 1024-bit. Key ID: \cw{1024R/32B903A9} (long version: +\cw{1024R/FAAED21532B903A9}). Fingerprint: +\cw{86\_8B\_1F\_79\_9C\_F4\_7F\_BD\_\_8B\_1B\_D7\_8E\_C6\_4E\_4C\_03} + +\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-rsa.asc}{\s{Snapshot Key} (original DSA)} + +\dd DSA, 1024-bit. Key ID: \cw{1024D/7D3E4A00} (long version: +\cw{1024D/165E56F77D3E4A00}). Fingerprint: +\cw{63DD\_8EF8\_32F5\_D777\_9FF0\_\_2947\_165E\_56F7\_7D3E\_4A00} From 43865aa1615bf0ca65914344dbef85199d3130d0 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 2 Sep 2015 18:30:10 +0100 Subject: [PATCH 12/22] Key rollover: switch to signing using the new keys. sign.sh's command-line syntax has changed, so I've updated the sample command line in CHECKLST as well. Also the file extensions of the signatures have changed, so I've updated the pre-release verification command line in CHECKLST too. (cherry picked from commit 11eb75a260ca1c6e48a19afe241d423f6e7b0e4e) --- CHECKLST.txt | 4 ++-- sign.sh | 30 +++++++++++++++++------------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/CHECKLST.txt b/CHECKLST.txt index d23b3f4e..93996f8d 100644 --- a/CHECKLST.txt +++ b/CHECKLST.txt @@ -135,7 +135,7 @@ for it: installer and the Unix source tarball. - Sign the release: in the `build.out' directory, type - sh sign.sh putty Releases + sh sign.sh -r putty and enter the passphrases a lot of times. The actual release procedure @@ -151,7 +151,7 @@ locally, this is the procedure for putting it up on the web. - Do final checks on the release directory in its new location: + verify all the signatures: - for i in `find . -name '*.*SA'`; do case $i in *sums*) gpg --verify $i;; *) gpg --verify $i ${i%%.?SA};; esac; done + for i in `find . -name '*.gpg'`; do case $i in *sums*) gpg --verify $i;; *) gpg --verify $i ${i%%.gpg};; esac; done + check the checksum files: md5sum -c md5sums sha1sum -c sha1sums diff --git a/sign.sh b/sign.sh index 2d348aa3..ea63b4bf 100755 --- a/sign.sh +++ b/sign.sh @@ -3,29 +3,33 @@ # Generate GPG signatures on a PuTTY release/snapshot directory as # delivered by Buildscr. -# Usage: sh sign.sh -# e.g. sh sign.sh putty Snapshots (probably in the build.out directory) -# or sh sign.sh 0.60 Releases +# Usage: sh sign.sh [-r] +# e.g. sh sign.sh putty (probably in the build.out directory) +# or sh sign.sh -r 0.60 (-r means use the release keys) set -e +keyname=EEF20295D15F7E8A + +if test "x$1" = "x-r"; then + shift + keyname=9DFE2648B43434E4 +fi + sign() { # Check for the prior existence of the signature, so we can # re-run this script if it encounters an error part way # through. - echo "----- Signing $2 with '$keyname'" + echo "----- Signing $2 with key '$keyname'" test -f "$3" || \ gpg --load-extension=idea "$1" -u "$keyname" -o "$3" "$2" } cd "$1" -for t in DSA RSA; do - keyname="$2 ($t)" - echo "===== Signing with '$keyname'" - for i in putty*src.zip putty*.tar.gz x86/*.exe x86/*.zip; do - sign --detach-sign "$i" "$i.$t" - done - for i in md5sums sha1sums sha256sums sha512sums; do - sign --clearsign $i ${i}.$t - done +echo "===== Signing with key '$keyname'" +for i in putty*src.zip putty*.tar.gz x86/*.exe x86/*.zip; do + sign --detach-sign "$i" "$i.gpg" +done +for i in md5sums sha1sums sha256sums sha512sums; do + sign --clearsign "$i" "$i.gpg" done From aaeaae00a9e652afe5bf1300f00f45ef1904033d Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 2 Sep 2015 18:31:24 +0100 Subject: [PATCH 13/22] Key rollover: put the new Master Key fingerprint in the tools. For the moment we're also retaining the old ones. Not sure when will be the best time to get rid of those; after the next release, perhaps? (cherry picked from commit e88b8d21f2f7a73cd9e2f21bcb408b2abebd0667) --- putty.h | 2 ++ unix/uxmisc.c | 6 ++++-- windows/wincons.c | 6 ++++-- windows/winutils.c | 10 ++++++---- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/putty.h b/putty.h index e030df79..add92c8b 100644 --- a/putty.h +++ b/putty.h @@ -31,6 +31,8 @@ typedef struct terminal_tag Terminal; * Fingerprints of the PGP master keys that can be used to establish a trust * path between an executable and other files. */ +#define PGP_MASTER_KEY_FP \ + "440D E3B5 B7A1 CA85 B3CC 1718 AB58 5DC6 0467 6F7C" #define PGP_RSA_MASTER_KEY_FP \ "8F 15 97 DA 25 30 AB 0D 88 D1 92 54 11 CF 0C 4C" #define PGP_DSA_MASTER_KEY_FP \ diff --git a/unix/uxmisc.c b/unix/uxmisc.c index e65a3869..61a44d57 100644 --- a/unix/uxmisc.c +++ b/unix/uxmisc.c @@ -162,9 +162,11 @@ void pgp_fingerprints(void) "one. See the manual for more information.\n" "(Note: these fingerprints have nothing to do with SSH!)\n" "\n" - "PuTTY Master Key (RSA), 1024-bit:\n" + "PuTTY Master Key as of 2015 (RSA, 4096-bit):\n" + " " PGP_MASTER_KEY_FP "\n\n" + "Original PuTTY Master Key (RSA, 1024-bit):\n" " " PGP_RSA_MASTER_KEY_FP "\n" - "PuTTY Master Key (DSA), 1024-bit:\n" + "Original PuTTY Master Key (DSA, 1024-bit):\n" " " PGP_DSA_MASTER_KEY_FP "\n", stdout); } diff --git a/windows/wincons.c b/windows/wincons.c index 508be3f8..198ff9a7 100644 --- a/windows/wincons.c +++ b/windows/wincons.c @@ -281,9 +281,11 @@ void pgp_fingerprints(void) "one. See the manual for more information.\n" "(Note: these fingerprints have nothing to do with SSH!)\n" "\n" - "PuTTY Master Key (RSA), 1024-bit:\n" + "PuTTY Master Key as of 2015 (RSA, 4096-bit):\n" + " " PGP_MASTER_KEY_FP "\n\n" + "Original PuTTY Master Key (RSA, 1024-bit):\n" " " PGP_RSA_MASTER_KEY_FP "\n" - "PuTTY Master Key (DSA), 1024-bit:\n" + "Original PuTTY Master Key (DSA, 1024-bit):\n" " " PGP_DSA_MASTER_KEY_FP "\n", stdout); } diff --git a/windows/winutils.c b/windows/winutils.c index f68405bc..ef0db921 100644 --- a/windows/winutils.c +++ b/windows/winutils.c @@ -142,10 +142,12 @@ void pgp_fingerprints(void) "one. See the manual for more information.\n" "(Note: these fingerprints have nothing to do with SSH!)\n" "\n" - "PuTTY Master Key (RSA), 1024-bit:\n" - " " PGP_RSA_MASTER_KEY_FP "\n" - "PuTTY Master Key (DSA), 1024-bit:\n" - " " PGP_DSA_MASTER_KEY_FP, + "PuTTY Master Key as of 2015 (RSA, 4096-bit):\n" + " " PGP_MASTER_KEY_FP "\n\n" + "Original PuTTY Master Key (RSA, 1024-bit):\n" + " " PGP_RSA_MASTER_KEY_FP "\n" + "Original PuTTY Master Key (DSA, 1024-bit):\n" + " " PGP_DSA_MASTER_KEY_FP, "PGP fingerprints", MB_ICONINFORMATION | MB_OK, HELPCTXID(pgp_fingerprints)); } From 6c041657199a000e26b983c283648f080dae9f03 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 2 Sep 2015 18:39:32 +0100 Subject: [PATCH 14/22] Key rollover: add a checklist item for the Download page. Next time I do a release, I'll have to remember to adjust the download page links to the GPG signature files. (cherry picked from commit 7524da621b1689b3384020cd6d83c990ef86bfa1) --- CHECKLST.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHECKLST.txt b/CHECKLST.txt index 93996f8d..78e9d223 100644 --- a/CHECKLST.txt +++ b/CHECKLST.txt @@ -112,6 +112,11 @@ for it: + .htaccess has an individual redirect for each version number. Add a new one. + - For 0.66 only: if it's not already done, switch the remaining + signature links on the Download page over to using the new + signature style. Then remove this checklist item, since it'll only + need doing this once. + - If there are any last-minute wishlist entries (e.g. security vulnerabilities fixed in the new release), write entries for them in a local checkout of putty-wishlist. From 4252cdbd82605f8860b6914b7b158b9d4d68b9e5 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 3 Sep 2015 19:04:26 +0100 Subject: [PATCH 15/22] Key rollover: cut and paste errors in pgpkeys.but. What should have been links to the old DSA keys were actually a second copy of the links to the old RSA ones. Ahem. (cherry picked from commit b62af0f40aa15c3ab79c8166c34f60f6e4192214) --- doc/pgpkeys.but | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/pgpkeys.but b/doc/pgpkeys.but index e31bbdf6..3f772793 100644 --- a/doc/pgpkeys.but +++ b/doc/pgpkeys.but @@ -162,7 +162,7 @@ For completeness, those old keys are given here: \cw{1024R/9D5877BF1E34AC41}). Fingerprint: \cw{8F\_15\_97\_DA\_25\_30\_AB\_0D\_\_88\_D1\_92\_54\_11\_CF\_0C\_4C} -\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-rsa.asc}{\s{Master Key} (original DSA)} +\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-dsa.asc}{\s{Master Key} (original DSA)} \dd DSA, 1024-bit. Key ID: \cw{1024D/6A93B34E} (long version: \cw{1024D/4F5E6DF56A93B34E}). Fingerprint: @@ -174,7 +174,7 @@ For completeness, those old keys are given here: \cw{1024R/EF39CCC0B41CAE29}). Fingerprint: \cw{AE\_65\_D3\_F7\_85\_D3\_18\_E0\_\_3B\_0C\_9B\_02\_FF\_3A\_81\_FE} -\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-rsa.asc}{\s{Release Key} (original DSA)} +\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-dsa.asc}{\s{Release Key} (original DSA)} \dd DSA, 1024-bit. Key ID: \cw{1024D/08B0A90B} (long version: \cw{1024D/FECD6F3F08B0A90B}). Fingerprint: @@ -186,7 +186,7 @@ For completeness, those old keys are given here: \cw{1024R/FAAED21532B903A9}). Fingerprint: \cw{86\_8B\_1F\_79\_9C\_F4\_7F\_BD\_\_8B\_1B\_D7\_8E\_C6\_4E\_4C\_03} -\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-rsa.asc}{\s{Snapshot Key} (original DSA)} +\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-dsa.asc}{\s{Snapshot Key} (original DSA)} \dd DSA, 1024-bit. Key ID: \cw{1024D/7D3E4A00} (long version: \cw{1024D/165E56F77D3E4A00}). Fingerprint: From 8c803e725e0b0071b5a454f423a805e1bd9b6be9 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 3 Sep 2015 19:04:54 +0100 Subject: [PATCH 16/22] Key rollover: fix the .htaccess files built by Buildscr. The build script generates the .htaccess files that go in each individual build and redirect generic names like 'putty.tar.gz' to the real filenames including that build's version number. Those .htaccess files redirect the corresponding signatures as well, so they need updating now that we're generating signature files with a different extension. (cherry picked from commit 6744387924835792147f73644e1eed10e146b5c8) --- Buildscr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Buildscr b/Buildscr index 0169c2d8..bc606ca4 100644 --- a/Buildscr +++ b/Buildscr @@ -202,6 +202,6 @@ in-dest putty do a=`\find * -type f -print`; md5sum $$a > md5sums && sha1sum $$a in-dest putty do echo "AddType application/octet-stream .chm" >> .htaccess in-dest putty do echo "AddType application/octet-stream .hlp" >> .htaccess in-dest putty do echo "AddType application/octet-stream .cnt" >> .htaccess -in-dest putty do set -- putty*.tar.gz; for k in '' .DSA .RSA; do echo RedirectMatch temp '(.*/)'putty.tar.gz$$k\$$ '$$1'"$$1$$k" >> .htaccess; done +in-dest putty do set -- putty*.tar.gz; for k in '' .gpg; do echo RedirectMatch temp '(.*/)'putty.tar.gz$$k\$$ '$$1'"$$1$$k" >> .htaccess; done # And one in the x86 directory, providing a link for the installer. -in-dest putty/x86 do set -- putty*installer.exe; for k in '' .DSA .RSA; do echo RedirectMatch temp '(.*/)'putty-installer.exe$$k\$$ '$$1'"$$1$$k" >> .htaccess; done +in-dest putty/x86 do set -- putty*installer.exe; for k in '' .gpg; do echo RedirectMatch temp '(.*/)'putty-installer.exe$$k\$$ '$$1'"$$1$$k" >> .htaccess; done From a815c3a8e1855e20e458571376039d36c5810642 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 17 Oct 2015 17:30:53 +0100 Subject: [PATCH 17/22] Fix spurious EAGAIN in Plink host key (and other) prompts. Plink sets standard input into nonblocking mode, meaning that read() from fd 0 in an interactive context will typically return -1 EAGAIN. But the prompt functions in uxcons.c, used for verifying SSH host keys and suchlike, were doing an unguarded read() from fd 0, and then panicking and aborting the session when they got EAGAIN. Fixed by inventing a wrapper around read(2) which handles EAGAIN but passes all other errors back to the caller. (Seemed slightly less dangerous than the stateful alternative of temporarily re-blockifying the file descriptor.) (cherry picked from commit bea758a7ae0507e0d4a24b370f8401661cc1a2c8) Conflicts: unix/uxcons.c Cherry-picker's notes: the conflict was a trivial one. The new function block_and_read() by this commit appears just before verify_ssh_host_key(), which has a new prototype on the source branch, close enough to disrupt the patch hunk's context. Easily fixed. --- unix/uxcons.c | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/unix/uxcons.c b/unix/uxcons.c index 882d2c9b..fa1c43f2 100644 --- a/unix/uxcons.c +++ b/unix/uxcons.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -74,6 +75,38 @@ void timer_change_notify(unsigned long next) { } +/* + * Wrapper around Unix read(2), suitable for use on a file descriptor + * that's been set into nonblocking mode. Handles EAGAIN/EWOULDBLOCK + * by means of doing a one-fd select and then trying again; all other + * errors (including errors from select) are returned to the caller. + */ +static int block_and_read(int fd, void *buf, size_t len) +{ + int ret; + + while ((ret = read(fd, buf, len)) < 0 && ( +#ifdef EAGAIN + (errno == EAGAIN) || +#endif +#ifdef EWOULDBLOCK + (errno == EWOULDBLOCK) || +#endif + 0)) { + + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + ret = select(fd+1, &rfds, NULL, NULL, NULL); + assert(ret != 0); + if (ret < 0) + return ret; + assert(FD_ISSET(fd, &rfds)); + } + + return ret; +} + int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype, char *keystr, char *fingerprint, void (*callback)(void *ctx, int result), void *ctx) @@ -163,7 +196,7 @@ int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype, newmode.c_lflag |= ECHO | ISIG | ICANON; tcsetattr(0, TCSANOW, &newmode); line[0] = '\0'; - if (read(0, line, sizeof(line) - 1) <= 0) + if (block_and_read(0, line, sizeof(line) - 1) <= 0) /* handled below */; tcsetattr(0, TCSANOW, &oldmode); } @@ -216,7 +249,7 @@ int askalg(void *frontend, const char *algtype, const char *algname, newmode.c_lflag |= ECHO | ISIG | ICANON; tcsetattr(0, TCSANOW, &newmode); line[0] = '\0'; - if (read(0, line, sizeof(line) - 1) <= 0) + if (block_and_read(0, line, sizeof(line) - 1) <= 0) /* handled below */; tcsetattr(0, TCSANOW, &oldmode); } @@ -270,7 +303,7 @@ int askappend(void *frontend, Filename *filename, newmode.c_lflag |= ECHO | ISIG | ICANON; tcsetattr(0, TCSANOW, &newmode); line[0] = '\0'; - if (read(0, line, sizeof(line) - 1) <= 0) + if (block_and_read(0, line, sizeof(line) - 1) <= 0) /* handled below */; tcsetattr(0, TCSANOW, &oldmode); } From 31c5784d4b514becf0028f3b3d38d8e28305dad7 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 24 Sep 2015 17:30:04 +0100 Subject: [PATCH 18/22] Command-line options to log sessions. Log files, especially SSH packet logs, are often things you want to generate in unusual circumstances, so it's good to have lots of ways to ask for them. Particularly, it's especially painful to have to set up a custom saved session to get diagnostics out of the command-line tools. I've added options '-sessionlog', '-sshlog' and '-sshrawlog', each of which takes a filename argument. I think the fourth option (session output but filtered down to the printable subset) is not really a _debugging_ log in the same sense, so it's not as critical to have an option for it. (cherry picked from commit 13edf90e0a4397088085cfcd53a4311319b708b4) --- cmdline.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/cmdline.c b/cmdline.c index 327a585a..bcfdcf88 100644 --- a/cmdline.c +++ b/cmdline.c @@ -572,6 +572,23 @@ int cmdline_process_param(char *p, char *value, int need_save, Conf *conf) nextitem += length + skip; } } + + if (!strcmp(p, "-sessionlog") || + !strcmp(p, "-sshlog") || + !strcmp(p, "-sshrawlog")) { + Filename *fn; + RETURN(2); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(0); + fn = filename_from_str(value); + conf_set_filename(conf, CONF_logfilename, fn); + conf_set_int(conf, CONF_logtype, + !strcmp(p, "-sessionlog") ? LGTYP_DEBUG : + !strcmp(p, "-sshlog") ? LGTYP_PACKETS : + /* !strcmp(p, "-sshrawlog") ? */ LGTYP_SSHRAW); + filename_free(fn); + } + return ret; /* unrecognised */ } From fbea11f44b972ce48ae5912f9e30df4e0c03e8c9 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 25 Sep 2015 09:15:21 +0100 Subject: [PATCH 19/22] Shout more loudly if we can't open a log file. A user points out that logging fopen failures to the Event Log is a bit obscure, and it's possible to proceed for months in the assumption that your sessions are being correctly logged when in fact the partition was full or you were aiming them at the wrong directory. Now we produce output visibly in the PuTTY window. (cherry picked from commit e1628105163135ca21abb6a841d109969d7979ec) --- logging.c | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/logging.c b/logging.c index b57888fd..c4538300 100644 --- a/logging.c +++ b/logging.c @@ -88,16 +88,19 @@ static void logfopen_callback(void *handle, int mode) char buf[256], *event; struct tm tm; const char *fmode; + int shout = FALSE; if (mode == 0) { ctx->state = L_ERROR; /* disable logging */ } else { fmode = (mode == 1 ? "ab" : "wb"); ctx->lgfp = f_open(ctx->currlogfilename, fmode, FALSE); - if (ctx->lgfp) + if (ctx->lgfp) { ctx->state = L_OPEN; - else + } else { ctx->state = L_ERROR; + shout = TRUE; + } } if (ctx->state == L_OPEN) { @@ -119,6 +122,23 @@ static void logfopen_callback(void *handle, int mode) "unknown"), filename_to_str(ctx->currlogfilename)); logevent(ctx->frontend, event); + if (shout) { + /* + * If we failed to open the log file due to filesystem error + * (as opposed to user action such as clicking Cancel in the + * askappend box), we should log it more prominently. We do + * this by sending it to the same place that stderr output + * from the main session goes (so, either a console tool's + * actual stderr, or a terminal window). + * + * Of course this is one case in which that policy won't cause + * it to turn up embarrassingly in a log file of real server + * output, because the whole point is that we haven't managed + * to open any such log file :-) + */ + from_backend(ctx->frontend, 1, event, strlen(event)); + from_backend(ctx->frontend, 1, "\r\n", 2); + } sfree(event); /* From 5c76a93a44bdd0d6d15e644176a0257d9ce7bf9b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 25 Sep 2015 09:23:26 +0100 Subject: [PATCH 20/22] Sanitise bad characters in log file names. On Windows, colons are illegal in filenames, because they're part of the path syntax. But colons can appear in automatically constructed log file names, if an IPv6 address is expanded from the &H placeholder. Now we coerce any such illegal characters to '.', which is a bit of a bodge but should at least cause a log file to be generated. (cherry picked from commit 64ec5e03d5362ed036e9de1a765085c571eaa3b7) --- logging.c | 15 +++++++++++++-- putty.h | 1 + unix/uxmisc.c | 7 +++++++ windows/winmisc.c | 7 +++++++ 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/logging.c b/logging.c index c4538300..954721b9 100644 --- a/logging.c +++ b/logging.c @@ -446,6 +446,7 @@ static Filename *xlatlognam(Filename *src, char *hostname, int port, s = filename_to_str(src); while (*s) { + int sanitise = FALSE; /* Let (bufp, len) be the string to append. */ bufp = buf; /* don't usually override this */ if (*s == '&') { @@ -478,6 +479,12 @@ static Filename *xlatlognam(Filename *src, char *hostname, int port, if (c != '&') buf[size++] = c; } + /* Never allow path separators - or any other illegal + * filename character - to come out of any of these + * auto-format directives. E.g. 'hostname' can contain + * colons, if it's an IPv6 address, and colons aren't + * legal in filenames on Windows. */ + sanitise = TRUE; } else { buf[0] = *s++; size = 1; @@ -486,8 +493,12 @@ static Filename *xlatlognam(Filename *src, char *hostname, int port, bufsize = (buflen + size) * 5 / 4 + 512; buffer = sresize(buffer, bufsize, char); } - memcpy(buffer + buflen, bufp, size); - buflen += size; + while (size-- > 0) { + char c = *bufp++; + if (sanitise) + c = filename_char_sanitise(c); + buffer[buflen++] = c; + } } buffer[buflen] = '\0'; diff --git a/putty.h b/putty.h index add92c8b..9580c3cd 100644 --- a/putty.h +++ b/putty.h @@ -1305,6 +1305,7 @@ int filename_serialise(const Filename *f, void *data); Filename *filename_deserialise(void *data, int maxsize, int *used); char *get_username(void); /* return value needs freeing */ char *get_random_data(int bytes); /* used in cmdgen.c */ +char filename_char_sanitise(char c); /* rewrite special pathname chars */ /* * Exports and imports from timing.c. diff --git a/unix/uxmisc.c b/unix/uxmisc.c index 61a44d57..b7727cb2 100644 --- a/unix/uxmisc.c +++ b/unix/uxmisc.c @@ -94,6 +94,13 @@ Filename *filename_deserialise(void *vdata, int maxsize, int *used) return filename_from_str(data); } +char filename_char_sanitise(char c) +{ + if (c == '/') + return '.'; + return c; +} + #ifdef DEBUG static FILE *debug_fp = NULL; diff --git a/windows/winmisc.c b/windows/winmisc.c index 5bf52141..ba15bad6 100644 --- a/windows/winmisc.c +++ b/windows/winmisc.c @@ -69,6 +69,13 @@ Filename *filename_deserialise(void *vdata, int maxsize, int *used) return filename_from_str(data); } +char filename_char_sanitise(char c) +{ + if (strchr("<>:\"/\\|?*", c)) + return '.'; + return c; +} + #ifndef NO_SECUREZEROMEMORY /* * Windows implementation of smemclr (see misc.c) using SecureZeroMemory. From 9c8a3cb6fbdd2fe620fa68343177b351a5b47f95 Mon Sep 17 00:00:00 2001 From: Tim Kosse Date: Fri, 1 May 2015 15:54:51 +0200 Subject: [PATCH 21/22] Fix format string vulnerabilities. Reported by Jong-Gwon Kim. Also fixes a few memory leaks in the process. (cherry picked from commit 6a70f944f648fedc7e866b4561372caa9091bf1a) --- unix/uxstore.c | 41 +++++++++++++++-------------------------- 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/unix/uxstore.c b/unix/uxstore.c index d97185e1..567b08d6 100644 --- a/unix/uxstore.c +++ b/unix/uxstore.c @@ -607,9 +607,8 @@ void store_host_key(const char *hostname, int port, dir = make_filename(INDEX_DIR, NULL); if (mkdir(dir, 0700) < 0) { - char *msg = dupprintf("Unable to store host key: mkdir(\"%s\") " - "returned '%s'", dir, strerror(errno)); - nonfatal(msg); + nonfatal("Unable to store host key: mkdir(\"%s\") " + "returned '%s'", dir, strerror(errno)); sfree(dir); sfree(tmpfilename); return; @@ -619,9 +618,8 @@ void store_host_key(const char *hostname, int port, wfp = fopen(tmpfilename, "w"); } if (!wfp) { - char *msg = dupprintf("Unable to store host key: open(\"%s\") " - "returned '%s'", tmpfilename, strerror(errno)); - nonfatal(msg); + nonfatal("Unable to store host key: open(\"%s\") " + "returned '%s'", tmpfilename, strerror(errno)); sfree(tmpfilename); return; } @@ -652,10 +650,9 @@ void store_host_key(const char *hostname, int port, fclose(wfp); if (rename(tmpfilename, filename) < 0) { - char *msg = dupprintf("Unable to store host key: rename(\"%s\",\"%s\")" - " returned '%s'", tmpfilename, filename, - strerror(errno)); - nonfatal(msg); + nonfatal("Unable to store host key: rename(\"%s\",\"%s\")" + " returned '%s'", tmpfilename, filename, + strerror(errno)); } sfree(tmpfilename); @@ -694,10 +691,8 @@ void write_random_seed(void *data, int len) fd = open(fname, O_CREAT | O_WRONLY, 0600); if (fd < 0) { if (errno != ENOENT) { - char *msg = dupprintf("Unable to write random seed: open(\"%s\") " - "returned '%s'", fname, strerror(errno)); - nonfatal(msg); - sfree(msg); + nonfatal("Unable to write random seed: open(\"%s\") " + "returned '%s'", fname, strerror(errno)); sfree(fname); return; } @@ -705,10 +700,8 @@ void write_random_seed(void *data, int len) dir = make_filename(INDEX_DIR, NULL); if (mkdir(dir, 0700) < 0) { - char *msg = dupprintf("Unable to write random seed: mkdir(\"%s\") " - "returned '%s'", dir, strerror(errno)); - nonfatal(msg); - sfree(msg); + nonfatal("Unable to write random seed: mkdir(\"%s\") " + "returned '%s'", dir, strerror(errno)); sfree(fname); sfree(dir); return; @@ -717,10 +710,8 @@ void write_random_seed(void *data, int len) fd = open(fname, O_CREAT | O_WRONLY, 0600); if (fd < 0) { - char *msg = dupprintf("Unable to write random seed: open(\"%s\") " - "returned '%s'", fname, strerror(errno)); - nonfatal(msg); - sfree(msg); + nonfatal("Unable to write random seed: open(\"%s\") " + "returned '%s'", fname, strerror(errno)); sfree(fname); return; } @@ -729,10 +720,8 @@ void write_random_seed(void *data, int len) while (len > 0) { int ret = write(fd, data, len); if (ret < 0) { - char *msg = dupprintf("Unable to write random seed: write " - "returned '%s'", strerror(errno)); - nonfatal(msg); - sfree(msg); + nonfatal("Unable to write random seed: write " + "returned '%s'", strerror(errno)); break; } len -= ret; From 3a43bec44c870b6c9a0fc1a3aa8207aeb4fd59eb Mon Sep 17 00:00:00 2001 From: Tim Kosse Date: Fri, 1 May 2015 15:55:37 +0200 Subject: [PATCH 22/22] Fix a format string vulnerability if MALLOC_LOG is set. (cherry picked from commit e443fd3a77f8c138b458fb8759dc0747703541ac) --- misc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misc.c b/misc.c index 94b5ac8a..507837f9 100644 --- a/misc.c +++ b/misc.c @@ -736,7 +736,7 @@ void *safemalloc(size_t n, size_t size) #else strcpy(str, "Out of memory!"); #endif - modalfatalbox(str); + modalfatalbox("%s", str); } #ifdef MALLOC_LOG if (fp) @@ -778,7 +778,7 @@ void *saferealloc(void *ptr, size_t n, size_t size) #else strcpy(str, "Out of memory!"); #endif - modalfatalbox(str); + modalfatalbox("%s", str); } #ifdef MALLOC_LOG if (fp)