Browse Source

PuTTY snapshot 6c924ba (new files that are needed for WinSCP + documentation update)

Source commit: bab474ceb1c91489becd637b0ee486bd2f3f4220
Martin Prikryl 6 years ago
parent
commit
07ead4124b

+ 80 - 0
source/putty/defs.h

@@ -0,0 +1,80 @@
+/*
+ * defs.h: initial definitions for PuTTY.
+ *
+ * The rule about this header file is that it can't depend on any
+ * other header file in this code base. This is where we define
+ * things, as much as we can, that other headers will want to refer
+ * to, such as opaque structure types and their associated typedefs,
+ * or macros that are used by other headers.
+ */
+
+#ifndef PUTTY_DEFS_H
+#define PUTTY_DEFS_H
+
+#include <stddef.h>
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+typedef struct conf_tag Conf;
+typedef struct backend_tag Backend;
+typedef struct terminal_tag Terminal;
+
+typedef struct Filename Filename;
+typedef struct FontSpec FontSpec;
+
+typedef struct bufchain_tag bufchain;
+
+typedef struct strbuf strbuf;
+
+struct RSAKey;
+
+#include <stdint.h>
+typedef uint32_t uint32;
+
+typedef struct BinarySink BinarySink;
+typedef struct BinarySource BinarySource;
+
+typedef struct SockAddr_tag *SockAddr;
+
+typedef struct Socket_vtable Socket_vtable;
+typedef struct Plug_vtable Plug_vtable;
+
+/* Note indirection: for historical reasons (it used to be closer to
+ * the OS socket type), the type that most code uses for a socket is
+ * 'Socket', not 'Socket *'. So an implementation of Socket or Plug
+ * has a 'const Socket *' field for the vtable pointer, and the
+ * 'Socket' type returned to client code is a pointer to _that_ in
+ * turn. */
+typedef const Socket_vtable **Socket;
+typedef const Plug_vtable **Plug;
+
+/*
+ * A small structure wrapping up a (pointer, length) pair so that it
+ * can be conveniently passed to or from a function.
+ */
+typedef struct ptrlen {
+    const void *ptr;
+    size_t len;
+} ptrlen;
+
+typedef struct logblank_t logblank_t;
+
+/* Do a compile-time type-check of 'to_check' (without evaluating it),
+ * as a side effect of returning the value 'to_return'. Note that
+ * although this macro double-*expands* to_return, it always
+ * *evaluates* exactly one copy of it, so it's side-effect safe. */
+#define TYPECHECK(to_check, to_return)                  \
+    (sizeof(to_check) ? (to_return) : (to_return))
+
+/* Return a pointer to the object of structure type 'type' whose field
+ * with name 'field' is pointed at by 'object'. */
+#define FROMFIELD(object, type, field)                                  \
+    TYPECHECK(object == &((type *)0)->field,                            \
+              ((type *)(((char *)(object)) - offsetof(type, field))))
+
+#endif /* PUTTY_DEFS_H */

+ 237 - 36
source/putty/doc/config.but

@@ -1381,31 +1381,47 @@ Note that this option only applies to line-drawing characters which
 characters that were received as Unicode code points will paste as
 Unicode always.
 
+\S{config-utf8linedraw} Combining VT100 line-drawing with UTF-8
+
+\cfg{winhelp-topic}{translation.utf8linedraw}
+
+If PuTTY is configured to treat data from the server as encoded in
+UTF-8, then by default it disables the older VT100-style system of
+control sequences that cause the lower-case letters to be temporarily
+replaced by line drawing characters.
+
+The rationale is that in UTF-8 mode you don't need those control
+sequences anyway, because all the line-drawing characters they access
+are available as Unicode characters already, so there's no need for
+applications to put the terminal into a special state to get at them.
+
+Also, it removes a risk of the terminal \e{accidentally} getting into
+that state: if you accidentally write uncontrolled binary data to a
+non-UTF-8 terminal, it can be surprisingly common to find that your
+next shell prompt appears as a sequence of line-drawing characters and
+then you have to remember or look up how to get out of that mode. So
+by default, UTF-8 mode simply doesn't \e{have} a confusing mode like
+that to get into, accidentally or on purpose.
+
+However, not all applications will see it that way. Even UTF-8
+terminal users will still sometimes have to run software that tries to
+print line-drawing characters in the old-fashioned way. So the
+configuration option \q{Enable VT100 line drawing even in UTF-8 mode}
+puts PuTTY into a hybrid mode in which it understands the VT100-style
+control sequences that change the meaning of the ASCII lower case
+letters, \e{and} understands UTF-8.
+
 \H{config-selection} The Selection panel
 
 The Selection panel allows you to control the way \i{copy and paste}
 work in the PuTTY window.
 
-\S{config-rtfpaste} Pasting in \i{Rich Text Format}
-
-\cfg{winhelp-topic}{selection.rtf}
-
-If you enable \q{Paste to clipboard in RTF as well as plain text},
-PuTTY will write formatting information to the clipboard as well as
-the actual text you copy. The effect of this is
-that if you paste into (say) a word processor, the text will appear
-in the word processor in the same \i{font}, \i{colour}, and style 
-(e.g. bold, underline) PuTTY was using to display it.
-
-This option can easily be inconvenient, so by default it is
-disabled.
-
 \S{config-mouse} Changing the actions of the mouse buttons
 
 \cfg{winhelp-topic}{selection.buttons}
 
 PuTTY's copy and paste mechanism is by default modelled on the Unix
-\c{xterm} application. The X Window System uses a three-button mouse,
+\i\c{xterm} application. The X Window System uses a three-button mouse,
 and the convention is that the \i{left button} \I{selecting text}selects,
 the \i{right button} extends an existing selection, and the
 \i{middle button} pastes.
@@ -1469,13 +1485,112 @@ select a rectangular block. Using the \q{Default selection mode}
 control, you can set \i{rectangular selection} as the default, and then
 you have to hold down Alt to get the \e{normal} behaviour.
 
-\S{config-charclasses} Configuring \i{word-by-word selection}
+\S{config-clipboards} Assigning copy and paste actions to clipboards
+
+Here you can configure which clipboard(s) are written or read by
+PuTTY's various copy and paste actions.
+
+The X Window System (which underlies most Unix graphical interfaces)
+provides multiple clipboards (or \q{\i{selections}}), and many
+applications support more than one of them by a different user
+interface mechanism.
+
+The two most commonly used selections are called \cq{\i{PRIMARY}} and
+\cq{\I{CLIPBOARD selection}CLIPBOARD}; in applications supporting both,
+the usual behaviour is that \cw{PRIMARY} is used by mouse-only actions
+(selecting text automatically copies it to \cw{PRIMARY}, and
+\i{middle-clicking} pastes from \cw{PRIMARY}), whereas \cw{CLIPBOARD}
+is used by explicit Copy and Paste menu items or keypresses such as
+\i{Ctrl-C} and \i{Ctrl-V}.
+
+On other platforms such as Windows, where there is a single system
+clipboard, PuTTY provides a second clipboard-like facility by permitting
+you to paste the text you last selected in \e{this window}, whether or
+not it is currently also in the system clipboard. This is not enabled
+by default.
+
+\S2{config-selection-autocopy} \q{Auto-copy selected text}
+
+\cfg{winhelp-topic}{selection.autocopy}
+
+The checkbox \q{Auto-copy selected text to system clipboard} controls
+whether or not selecting text in the PuTTY terminal window
+automatically has the side effect of copying it to the system
+clipboard, without requiring a separate user interface action.
+
+On X, the wording of this option is changed slightly so that
+\cq{CLIPBOARD} is mentioned in place of the \q{system clipboard}. Text
+selected in the terminal window will \e{always} be automatically
+placed in the \cw{PRIMARY} selection, but if you tick this box, it
+will \e{also} be placed in \cq{CLIPBOARD} at the same time.
+
+\S2{config-selection-clipactions} Choosing a clipboard for UI actions
+
+\cfg{winhelp-topic}{selection.clipactions}
+
+PuTTY has three user-interface actions which can be configured to
+paste into the terminal (not counting menu items). You can click
+whichever mouse button (if any) is configured to paste (see
+\k{config-mouse}); you can press \i{Shift-Ins}; or you can press
+\i{Ctrl-Shift-V}, although that action is not enabled by default.
+
+You can configure which of the available clipboards each of these
+actions pastes from (including turning the paste action off
+completely). On platforms with a single system clipboard, the
+available options are to paste from that clipboard or to paste from
+PuTTY's internal memory of the \i{last selected text} within that
+window. On X, the standard options are \cw{CLIPBOARD} or \cw{PRIMARY}.
+
+(\cw{PRIMARY} is conceptually similar in that it \e{also} refers to
+the last selected text \dash just across all applications instead of
+just this window.)
+
+The two keyboard options each come with a corresponding key to copy
+\e{to} the same clipboard. Whatever you configure Shift-Ins to paste
+from, \i{Ctrl-Ins} will copy to the same location; similarly,
+\i{Ctrl-Shift-C} will copy to whatever Ctrl-Shift-V pastes from.
+
+On X, you can also enter a selection name of your choice. For example,
+there is a rarely-used standard selection called \cq{\i{SECONDARY}}, which
+Emacs (for example) can work with if you hold down the Meta key while
+dragging to select or clicking to paste; if you configure a PuTTY
+keyboard action to access this clipboard, then you can interoperate
+with other applications' use of it. Another thing you could do would
+be to invent a clipboard name yourself, to create a special clipboard
+shared \e{only} between instances of PuTTY, or between just instances
+configured in that particular way.
+
+\S{config-paste-ctrl-char} \q{Permit control characters in pasted text}
 
-\cfg{winhelp-topic}{selection.charclasses}
+\cfg{winhelp-topic}{selection.pastectrl}
 
-PuTTY will select a word at a time in the terminal window if you
-\i{double-click} to begin the drag. This panel allows you to control
-precisely what is considered to be a word.
+It is possible for the clipboard to contain not just text (with
+newlines and tabs) but also control characters such as ESC which could
+have surprising effects if pasted into a terminal session, depending
+on what program is running on the server side. Copying text from a
+mischievous web page could put such characters onto the clipboard.
+
+By default, PuTTY filters out the more unusual control characters,
+only letting through the more obvious text-formatting characters
+(newlines, tab, backspace, and DEL).
+
+Setting this option stops this filtering; on paste, any character on
+the clipboard is sent to the session uncensored. This might be useful
+if you are deliberately using control character pasting as a simple
+form of scripting, for instance.
+
+\H{config-selection-copy} The Copy panel
+
+The Copy configuration panel controls behaviour specifically related to
+copying from the terminal window to the clipboard.
+
+\S{config-charclasses} Character classes
+
+\cfg{winhelp-topic}{copy.charclasses}
+
+PuTTY will \I{word-by-word selection}select a word at a time in the
+terminal window if you \i{double-click} to begin the drag. This section
+allows you to control precisely what is considered to be a word.
 
 Each character is given a \e{class}, which is a small number
 (typically 0, 1 or 2). PuTTY considers a single word to be any
@@ -1511,6 +1626,20 @@ terminal (see \k{reset-terminal}). However, if you modify this
 option in mid-session using \q{Change Settings}, it will take effect
 immediately.
 
+\S{config-rtfcopy} Copying in \i{Rich Text Format}
+
+\cfg{winhelp-topic}{copy.rtf}
+
+If you enable \q{Copy to clipboard in RTF as well as plain text},
+PuTTY will write formatting information to the clipboard as well as
+the actual text you copy. The effect of this is
+that if you paste into (say) a word processor, the text will appear
+in the word processor in the same \i{font}, \i{colour}, and style 
+(e.g. bold, underline) PuTTY was using to display it.
+
+This option can easily be inconvenient, so by default it is
+disabled.
+
 \H{config-colours} The Colours panel
 
 The Colours panel allows you to control PuTTY's use of \i{colour}.
@@ -1549,6 +1678,15 @@ If you do not see \cq{colors#256} in the output, you may need to
 change your terminal setting. On modern Linux machines, you could
 try \cq{xterm-256color}.
 
+\S{config-truecolour} \q{Allow terminal to use 24-bit colour}
+
+\cfg{winhelp-topic}{colours.truecolour}
+
+This option is enabled by default. If it is disabled, PuTTY will
+ignore any control sequences sent by the server which use the control
+sequences supported by modern terminals to specify arbitrary 24-bit
+RGB colour value.
+
 \S{config-boldcolour} \q{Indicate bolded text by changing...}
 
 \cfg{winhelp-topic}{colours.bold}
@@ -1821,9 +1959,10 @@ PuTTY will prompt for a username at the time you make a connection.
 In some environments, such as the networks of large organisations
 implementing \i{single sign-on}, a more sensible default may be to use
 the name of the user logged in to the local operating system (if any);
-this is particularly likely to be useful with \i{GSSAPI} authentication
-(see \k{config-ssh-auth-gssapi}). This control allows you to change
-the default behaviour.
+this is particularly likely to be useful with \i{GSSAPI} key exchange
+and user authentication (see \k{config-ssh-auth-gssapi} and
+\k{config-ssh-gssapi-kex}). This control allows you to change the default
+behaviour.
 
 The current system username is displayed in the dialog as a
 convenience. It is not saved in the configuration; if a saved session
@@ -2440,10 +2579,47 @@ well-known groups, if possible.
 effort on the part of the client, and somewhat less on the part of
 the server, than Diffie-Hellman key exchange.
 
+\b \q{GSSAPI key exchange}: see \k{config-ssh-gssapi-kex}.
+
 If the first algorithm PuTTY finds is below the \q{warn below here}
 line, you will see a warning box when you make the connection, similar
 to that for cipher selection (see \k{config-ssh-encryption}).
 
+\S2{config-ssh-gssapi-kex} GSSAPI-based key exchange
+
+PuTTY supports a set of key exchange methods that also incorporates
+GSSAPI-based authentication. They are enabled with the
+\q{Attempt GSSAPI key exchange} checkbox (which also appears on the
+\q{GSSAPI} panel).
+
+PuTTY can only perform the GSSAPI-authenticated key exchange methods
+when using Kerberos V5, and not other GSSAPI mechanisms. If the user
+running PuTTY has current Kerberos V5 credentials, then PuTTY will
+select the GSSAPI key exchange methods in preference to any of the
+ordinary SSH key exchange methods configured in the preference list.
+
+The advantage of doing GSSAPI authentication as part of the SSH key
+exchange is apparent when you are using credential delegation (see
+\k{config-ssh-auth-gssapi-delegation}). The SSH key exchange can be
+repeated later in the session, and this allows your Kerberos V5
+credentials (which are typically short-lived) to be automatically
+re-delegated to the server when they are refreshed on the client.
+(This feature is commonly referred to as \q{\i{cascading credentials}}.)
+
+If your server doesn't support GSSAPI key exchange, it may still
+support GSSAPI in the SSH user authentication phase. This will still
+let you log in using your Kerberos credentials, but will only allow
+you to delegate the credentials that are active at the beginning of
+the session; they can't be refreshed automatically later, in a
+long-running session.
+
+Another effect of GSSAPI key exchange is that it replaces the usual
+SSH mechanism of permanent host keys described in \k{gs-hostkey}.
+So if you use this method, then you won't be asked any interactive
+questions about whether to accept the server's host key. Instead, the
+Kerberos exchange will verify the identity of the host you connect to,
+at the same time as verifying your identity to it.
+
 \S{config-ssh-kex-rekey} \ii{Repeat key exchange}
 
 \cfg{winhelp-topic}{ssh.kex.repeat}
@@ -2486,6 +2662,14 @@ purposes, rekeys have much the same properties as keepalives.
 should bear that in mind when deciding whether to turn them off.)
 Note, however, the the SSH \e{server} can still initiate rekeys.
 
+\b \q{Minutes between GSSAPI checks}, if you're using GSSAPI key
+exchange, specifies how often the GSSAPI credential cache is checked
+to see whether new tickets are available for delegation, or current
+ones are near expiration. If forwarding of GSSAPI credentials is
+enabled, PuTTY will try to rekey as necessary to keep the delegated
+credentials from expiring. Frequent checks are recommended; rekeying
+only happens when needed.
+
 \b \q{Max data before rekey} specifies the amount of data (in bytes)
 that is permitted to flow in either direction before a rekey is
 initiated. If this is set to zero, PuTTY will not rekey due to
@@ -2839,15 +3023,28 @@ machine, which in principle can authenticate in many different ways
 but in practice is usually used with the \i{Kerberos} \i{single sign-on}
 protocol to implement \i{passwordless login}.
 
-GSSAPI is only available in the SSH-2 protocol.
+GSSAPI authentication is only available in the SSH-2 protocol.
+
+PuTTY supports two forms of GSSAPI-based authentication. In one of
+them, the SSH key exchange happens in the normal way, and GSSAPI is
+only involved in authenticating the user. The checkbox labelled
+\q{Attempt GSSAPI authentication} controls this form.
 
-The topmost control on the GSSAPI subpanel is the checkbox labelled
-\q{Attempt GSSAPI authentication}. If this is disabled, GSSAPI will
-not be attempted at all and the rest of this panel is unused. If it
-is enabled, GSSAPI authentication will be attempted, and (typically)
-if your client machine has valid Kerberos credentials loaded, then
-PuTTY should be able to authenticate automatically to servers that
-support Kerberos logins.
+In the other method, GSSAPI-based authentication is combined with the
+SSH key exchange phase. If this succeeds, then the SSH authentication
+step has nothing left to do. See \k{config-ssh-gssapi-kex} for more
+information about this method. The checkbox labelled \q{Attempt GSSAPI
+key exchange} controls this form. (The same checkbox appears on the
+\q{Kex} panel.)
+
+If one or both of these controls is enabled, then GSSAPI
+authentication will be attempted in one form or the other, and
+(typically) if your client machine has valid Kerberos credentials
+loaded, then PuTTY should be able to authenticate automatically to
+servers that support Kerberos logins.
+
+If both of those checkboxes are disabled, PuTTY will not try any form
+of GSSAPI at all, and the rest of this panel will be unused.
 
 \S{config-ssh-auth-gssapi-delegation} \q{Allow GSSAPI credential
 delegation}
@@ -2875,6 +3072,10 @@ administrator of one server is likely to already have access to the
 other services too; so this would typically be less of a risk than
 SSH agent forwarding.
 
+If your connection is not using GSSAPI key exchange, it is possible
+for the delegation to expire during your session. See
+\k{config-ssh-gssapi-kex} for more information.
+
 \S{config-ssh-auth-gssapi-libraries} Preference order for GSSAPI
 libraries
 
@@ -2885,11 +3086,11 @@ method to be accessed through the same interface. Therefore, more
 than one authentication library may exist on your system which can
 be accessed using GSSAPI.
 
-PuTTY contains native support for a few well-known such libraries,
-and will look for all of them on your system and use whichever it
-finds. If more than one exists on your system and you need to use a
-specific one, you can adjust the order in which it will search using
-this preference list control.
+PuTTY contains native support for a few well-known such libraries
+(including Windows' \i{SSPI}), and will look for all of them on your system
+and use whichever it finds. If more than one exists on your system and
+you need to use a specific one, you can adjust the order in which it
+will search using this preference list control.
 
 One of the options in the preference list is to use a user-specified
 GSSAPI library. If the library you want to use is not mentioned by

+ 0 - 5
source/putty/doc/copy.but

@@ -1,5 +0,0 @@
-\# Generated by licence.pl from LICENCE.
-\# You should edit those files rather than editing this one.
-
-\define{shortcopyrightdetails} 1997-2017 Simon Tatham
-

+ 20 - 0
source/putty/doc/faq.but

@@ -1002,6 +1002,26 @@ appropriate kind of binaries in \cw{SYSTEM32}. Thus, operations in
 the PuTTY suite that involve it accessing its own executables, such as
 \i{\q{New Session}} and \q{Duplicate Session}, will not work.
 
+\S{faq-iutf8}{Question} After I upgraded PuTTY to 0.68, I can no longer
+connect to my embedded device or appliance.
+
+If your SSH server has started unexpectedly closing SSH connections
+after you enter your password, and it worked before 0.68, you may have
+a buggy server that objects to certain SSH protocol extensions.
+
+The SSH protocol recently gained a new \q{terminal mode}, \cw{IUTF8},
+which PuTTY sends by default; see \k{config-ttymodes}. This is the
+first new terminal mode since the SSH-2 protocol was defined. While
+servers are supposed to ignore modes they don't know about, some buggy
+servers will unceremoniously close the connection if they see anything
+they don't recognise. SSH servers in embedded devices, network
+appliances, and the like seem to disproportionately have this bug.
+
+If you think you have such a server, from 0.69 onwards you can disable
+sending of the \cw{IUTF8} mode: on the SSH / TTY panel, select
+\cw{IUTF8} on the list, select \q{Nothing}, and press \q{Set}. (It's
+not possible to disable sending this mode in 0.68.)
+
 \H{faq-secure} Security questions
 
 \S{faq-publicpc}{Question} Is it safe for me to download PuTTY and

+ 21 - 1
source/putty/doc/index.but

@@ -73,7 +73,7 @@ from SSH and Telnet
 \IM{three-button mouse} mouse, three-button
 
 \IM{left mouse button}{left button} left mouse button
-\IM{middle mouse button}{middle button} middle mouse button
+\IM{middle mouse button}{middle button}{middle-clicking} middle mouse button
 \IM{right mouse button}{right button} right mouse button
 
 \IM{selecting words}{word-by-word selection} selecting whole words
@@ -92,6 +92,18 @@ from SSH and Telnet
 \IM{right mouse button, with Ctrl} right mouse button, with Ctrl
 \IM{right mouse button, with Ctrl} Ctrl, with right mouse button
 
+\IM{selections} selections, multiple
+\IM{selections} clipboards, multiple
+
+\IM{PRIMARY} \c{PRIMARY} selection
+\IM{PRIMARY} selection, \c{PRIMARY}
+
+\IM{CLIPBOARD selection} \c{CLIPBOARD} selection
+\IM{CLIPBOARD selection} selection, \c{CLIPBOARD}
+
+\IM{SECONDARY} \c{SECONDARY} selection
+\IM{SECONDARY} selection, \c{SECONDARY}
+
 \IM{system menu} system menu
 \IM{system menu} menu, system
 \IM{system menu} window menu
@@ -342,6 +354,11 @@ saved sessions from
 \IM{remote-controlled printing} printing, remote-controlled
 \IM{remote-controlled printing} passthrough printing
 
+\IM{Control-H} Control-H
+\IM{Control-H} Ctrl-H
+\IM{Control-?} Control-?
+\IM{Control-?} Ctrl-?
+
 \IM{Home and End keys} Home key
 \IM{Home and End keys} End key
 
@@ -857,6 +874,9 @@ saved sessions from
 \IM{GSSAPI credential delegation} credential delegation, GSSAPI
 \IM{GSSAPI credential delegation} delegation, of GSSAPI credentials
 
+\IM{cascading credentials} cascading credentials
+\IM{cascading credentials} credentials, cascading
+
 \IM{SYSTEM32} \cw{SYSTEM32} directory, on Windows
 
 \IM{32-bit Windows} 32-bit Windows

+ 0 - 15
source/putty/doc/licence.but

@@ -1,15 +0,0 @@
-\# Generated by licence.pl from LICENCE.
-\# You should edit those files rather than editing this one.
-
-\A{licence} PuTTY \ii{Licence}
-
-PuTTY is \i{copyright} 1997-2017 Simon Tatham.
-
-Portions copyright Robert de Bath, Joris van Rantwijk, Delian Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus Kuhn, Colin Watson, Christopher Staite, and CORE SDI S.A.
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \q{Software}), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED \q{AS IS}, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-

+ 9 - 0
source/putty/doc/man-pl.but

@@ -182,6 +182,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{\-noshare}
+
+\dd Don't test and try to share an existing connection, always make
+a new connection.
+
+\dt \cw{\-share}
+
+\dd Test and try to share an existing connection.
+
 \dt \cw{\-hostkey} \e{key}
 
 \dd Specify an acceptable host public key. This option may be specified

+ 55 - 36
source/putty/doc/pgpkeys.but

@@ -53,31 +53,25 @@ The current issue of those keys are available for download from the
 PuTTY website, and are also available on PGP keyservers using the key
 IDs listed below.
 
-\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-2015.asc}{\s{Master Key}}
+\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-2018.asc}{\s{Master Key} (2018)}
 
-\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}
+\dd RSA, 4096-bit. Key ID: \cw{76BC7FE4EBFD2D9E}. Fingerprint:
+\cw{24E1\_B1C5\_75EA\_3C9F\_F752\_\_A922\_76BC\_7FE4\_EBFD\_2D9E}
 
-\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-2015.asc}{\s{Release Key}}
+\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-2018.asc}{\s{Release Key} (2018)}
 
-\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}
+\dd RSA, 3072-bit. Key ID: \cw{6289A25F4AE8DA82}. Fingerprint:
+\cw{E273\_94AC\_A3F9\_D904\_9522\_\_E054\_6289\_A25F\_4AE8\_DA82}
 
-\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/contact-2016.asc}{\s{Secure Contact Key}}
+\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-2018.asc}{\s{Snapshot Key} (2018)}
 
-\dd RSA, 2048-bit. Main key ID: \cw{2048R/8A0AF00B} (long version:
-\cw{2048R/C4FCAAD08A0AF00B}). Encryption subkey ID:
-\cw{2048R/50C2CF5C} (long version: \cw{2048R/9EB39CC150C2CF5C}).
-Fingerprint:
-\cw{8A26\_250E\_763F\_E359\_75F3\_\_118F\_C4FC\_AAD0\_8A0A\_F00B}
+\dd RSA, 3072-bit. Key ID: \cw{38BA7229B7588FD1}. Fingerprint:
+\cw{C92B\_52E9\_9AB6\_1DDA\_33DB\_\_2B7A\_38BA\_7229\_B758\_8FD1}
 
-\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-2015.asc}{\s{Snapshot Key}}
+\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/contact-2018.asc}{\s{Secure Contact Key} (2018)}
 
-\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}
+\dd RSA, 3072-bit. Key ID: \cw{657D487977F95C98}. Fingerprint:
+\cw{A680\_0082\_2998\_6E46\_22CA\_\_0E43\_657D\_4879\_77F9\_5C98}
 
 \H{pgpkeys-security} Security details
 
@@ -156,28 +150,53 @@ once.
 
 \H{pgpkeys-rollover} Key rollover
 
-Our current keys were generated in September 2015, except for the
-Secure Contact Key which was generated in February 2016 (we didn't
-think of it until later).
+Our current keys were generated in August 2018.
+
+Each new Master Key is signed with the old one, to show that it really
+is owned by the same people and not substituted by an attacker.
 
-Prior to that, we had a much older set of keys generated in 2000. For
-each of the key types above (other than the Secure Contact Key), 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).
+Each new Master Key also signs the previous Release Keys, 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.
 
-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.
+Each release is signed with the Release Key that was current at the
+time of release. We don't go back and re-sign old releases with newly
+generated keys.
 
-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.
+The details of all previous keys are given here.
+
+\s{Key generated in 2016} (when we first introduced the Secure Contact Key)
+
+\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/contact-2016.asc}{\s{Secure Contact Key} (2016)}
+
+\dd RSA, 2048-bit. Main key ID: \cw{2048R/8A0AF00B} (long version:
+\cw{2048R/C4FCAAD08A0AF00B}). Encryption subkey ID:
+\cw{2048R/50C2CF5C} (long version: \cw{2048R/9EB39CC150C2CF5C}).
+Fingerprint:
+\cw{8A26\_250E\_763F\_E359\_75F3\_\_118F\_C4FC\_AAD0\_8A0A\_F00B}
+
+\s{Keys generated in the 2015 rollover}
+
+\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-2015.asc}{\s{Master Key} (2015)}
+
+\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{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-2015.asc}{\s{Release Key} (2015)}
+
+\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{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-2015.asc}{\s{Snapshot Key} (2015)}
+
+\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}
 
-For completeness, those old keys are given here:
+\s{Original keys generated in 2000} (two sets, RSA and DSA)
 
 \dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-rsa.asc}{\s{Master Key} (original RSA)}
 

+ 24 - 0
source/putty/doc/plink.but

@@ -75,6 +75,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   -noshare  disable use of connection sharing
+\c   -share    enable use of connection sharing
 \c   -hostkey aa:bb:cc:...
 \c             manually specify a host key (may be repeated)
 \c   -m file   read remote command(s) from file
@@ -237,6 +239,28 @@ line.
 
 (This option is only meaningful with the SSH-2 protocol.)
 
+\S2{plink-option-share} \I{-share-plink}\c{-share}:
+Test and try to share an existing connection.
+
+This option tris to detect if an existing connection can be shared
+(See \k{config-ssh-sharing} for more information about SSH connection
+sharing.) and reuses that connection.
+
+A Plink invocation of the form:
+
+\c plink -share <session>
+\e              iiiiiiiii
+
+will test whether there is currently a viable \q{upstream} for the
+session in question, which can be specified using any syntax you'd
+normally use with Plink to make an actual connection (a host/port
+number, a bare saved session name, \c{-load}, etc). If no \q{upstream}
+viable session is found and \c{-share} is specified, this connection
+will be become the \q{upstream} connection for subsequent connection
+sharing tries.
+
+(This option is only meaningful with the SSH-2 protocol.)
+
 \S2{plink-option-shareexists} \I{-shareexists-plink}\c{-shareexists}:
 test for connection-sharing upstream
 

+ 3 - 3
source/putty/doc/udp.but

@@ -138,9 +138,9 @@ construct. Use these wherever possible.
 
 \H{udp-multi-compiler} Independence of specific compiler
 
-Windows PuTTY can currently be compiled with any of four Windows
-compilers: MS Visual C, Borland's freely downloadable C compiler,
-the Cygwin / \cw{mingw32} GNU tools, and \cw{lcc-win32}.
+Windows PuTTY can currently be compiled with any of three Windows
+compilers: MS Visual C, the Cygwin / \cw{mingw32} GNU tools, and
+\cw{clang} (in MS compatibility mode).
 
 This is a really useful property of PuTTY, because it means people
 who want to contribute to the coding don't depend on having a

+ 49 - 19
source/putty/doc/using.but

@@ -21,27 +21,31 @@ the \I{Windows clipboard}Windows \i{clipboard}, so that you can
 paste (for example) URLs into a web browser, or paste from a word
 processor or spreadsheet into your terminal session.
 
-PuTTY's copy and paste works entirely with the \i{mouse}. In order
-to copy text to the clipboard, you just click the \i{left mouse
-button} in the \i{terminal window}, and drag to \I{selecting text}select
-text. When you let go of the button, the text is \e{automatically}
-copied to the clipboard. You do not need to press Ctrl-C or
-Ctrl-Ins; in fact, if you do press Ctrl-C, PuTTY will send a Ctrl-C
-character down your session to the server where it will probably
-cause a process to be interrupted.
-
-Pasting is done using the right button (or the middle mouse button,
-if you have a \i{three-button mouse} and have set it up; see
+By default, PuTTY's copy and paste works entirely with the \i{mouse}.
+(This will be familiar to people who have used \i\c{xterm} on Unix.)
+In order to copy text to the clipboard, you just click the \i{left
+mouse button} in the \i{terminal window}, and drag to
+\I{selecting text}select text. When you let go of the button, the text
+is \e{automatically} copied to the clipboard. You do not need to press
+\i{Ctrl-C} or \i{Ctrl-Ins}; in fact, if you do press Ctrl-C, PuTTY will
+send a Ctrl-C character down your session to the server where it will
+probably cause a process to be interrupted.
+
+Pasting into PuTTY is done using the right button (or the middle mouse
+button, if you have a \i{three-button mouse} and have set it up; see
 \k{config-mouse}). (Pressing \i{Shift-Ins}, or selecting \q{Paste}
 from the \I{right mouse button, with Ctrl}Ctrl+right-click
 \i{context menu}, have the same effect.) When
 you click the \i{right mouse button}, PuTTY will read whatever is in
-the Windows clipboard and paste it into your session, \e{exactly} as
-if it had been typed at the keyboard. (Therefore, be careful of
-pasting formatted text into an editor that does automatic indenting;
-you may find that the spaces pasted from the clipboard plus the
-spaces added by the editor add up to too many spaces and ruin the
-formatting. There is nothing PuTTY can do about this.)
+the Windows clipboard and paste it into your session. By default, this
+behaves \e{exactly} as if the clipboard contents had been typed at the
+keyboard; therefore, be careful of pasting formatted text into an
+editor that does automatic \i{indenting}, as you may find that the spaces
+pasted from the clipboard plus the spaces added by the editor add up
+to too many spaces and ruin the formatting. (Some remote applications
+can ask PuTTY to identify text that is being pasted, to avoid this
+sort of problem; but if your application does not, there is nothing
+PuTTY can do to avoid this.)
 
 If you \i{double-click} the left mouse button, PuTTY will
 \I{selecting words}select a whole word. If you double-click, hold
@@ -69,6 +73,15 @@ middle mouse button to paste, then the right mouse button does this
 instead.) Click the button on the screen, and you can pick up the
 nearest end of the selection and drag it to somewhere else.
 
+If you are running PuTTY itself on Unix (not just using it to connect
+to a Unix system from Windows), by default you will likely have to use
+similar mouse actions in other applications to paste the text you
+copied from PuTTY, and to copy text for pasting into PuTTY; actions
+like \i{Ctrl-C} and Ctrl-V will likely not behave as you expect.
+\K{config-clipboards} explains why this is, and how you can change the
+behaviour. (On Windows there is only a single selection shared with other
+applications, so this confusion does not arise.)
+
 It's possible for the server to ask to \I{mouse reporting}handle mouse
 clicks in the PuTTY window itself.  If this happens, the \i{mouse pointer}
 will turn into an arrow, and using the mouse to copy and paste will only
@@ -76,6 +89,9 @@ work if you hold down Shift.  See \k{config-features-mouse} and
 \k{config-mouseshift} for details of this feature and how to configure
 it.
 
+You can customise much of this behaviour, for instance to enable copy
+and paste from the keyboard; see \k{config-selection}.
+
 \S{using-scrollback} \I{scrollback}Scrolling the screen back
 
 PuTTY keeps track of text that has scrolled up off the top of the
@@ -86,8 +102,10 @@ window to look back up the session \i{history} and find it again.
 
 As well as using the scrollbar, you can also page the scrollback up
 and down by pressing \i{Shift-PgUp} and \i{Shift-PgDn}. You can
-scroll a line at a time using \i{Ctrl-PgUp} and \i{Ctrl-PgDn}. These
-are still available if you configure the scrollbar to be invisible.
+scroll a line at a time using \i{Ctrl-PgUp} and \i{Ctrl-PgDn}, or
+to the top/bottom of the scrollback with \i{Ctrl-Shift-PgUp} and
+\i{Ctrl-Shift-PgDn}. These are still available if you configure the
+scrollbar to be invisible.
 
 By default the last 2000 lines scrolled off the top are
 preserved for you to look at. You can increase (or decrease) this
@@ -1042,3 +1060,15 @@ any processes started with Duplicate Session, New Session etc.
 (However, if you're invoking PuTTY tools explicitly, for instance as a
 proxy command, you'll need to arrange to pass them the
 \c{-restrict-acl} option yourself, if that's what you want.)
+
+If Pageant is started with the \c{-restrict-acl} option, and you use
+it to launch a PuTTY session from its System Tray submenu, then
+Pageant will \e{not} default to starting the PuTTY subprocess with a
+restricted ACL. This is because PuTTY is more likely to suffer reduced
+functionality as a result of restricted ACLs (e.g. screen reader
+software will have a greater need to interact with it), whereas
+Pageant stores the more critical information (hence benefits more from
+the extra protection), so it's reasonable to want to run Pageant but
+not PuTTY with the ACL restrictions. You can force Pageant to start
+subsidiary PuTTY processes with a restricted ACL if you also pass the
+\c{-restrict-putty-acl} option.

+ 236 - 0
source/putty/marshal.c

@@ -0,0 +1,236 @@
+#include <assert.h>
+#include <stddef.h>
+#include <string.h>
+
+#include "marshal.h"
+#include "misc.h"
+#include "int64.h"
+
+void BinarySink_put_data(BinarySink *bs, const void *data, size_t len)
+{
+    bs->write(bs, data, len);
+}
+
+void BinarySink_put_padding(BinarySink *bs, size_t len, unsigned char padbyte)
+{
+    char buf[16];
+    memset(buf, padbyte, sizeof(buf));
+    while (len > 0) {
+        size_t thislen = len < sizeof(buf) ? len : sizeof(buf);
+        bs->write(bs, buf, thislen);
+        len -= thislen;
+    }
+}
+
+void BinarySink_put_byte(BinarySink *bs, unsigned char val)
+{
+    bs->write(bs, &val, 1);
+}
+
+void BinarySink_put_bool(BinarySink *bs, int val)
+{
+    unsigned char cval = val ? 1 : 0;
+    bs->write(bs, &cval, 1);
+}
+
+void BinarySink_put_uint16(BinarySink *bs, unsigned long val)
+{
+    unsigned char data[2];
+    PUT_16BIT_MSB_FIRST(data, val);
+    bs->write(bs, data, sizeof(data));
+}
+
+void BinarySink_put_uint32(BinarySink *bs, unsigned long val)
+{
+    unsigned char data[4];
+    PUT_32BIT_MSB_FIRST(data, val);
+    bs->write(bs, data, sizeof(data));
+}
+
+void BinarySink_put_uint64(BinarySink *bs, uint64 val)
+{
+    BinarySink_put_uint32(bs, val.hi);
+    BinarySink_put_uint32(bs, val.lo);
+}
+
+void BinarySink_put_string(BinarySink *bs, const void *data, size_t len)
+{
+    /* Check that the string length fits in a uint32, without doing a
+     * potentially implementation-defined shift of more than 31 bits */
+    assert((len >> 31) < 2);
+
+    BinarySink_put_uint32(bs, len);
+    bs->write(bs, data, len);
+}
+
+void BinarySink_put_stringpl(BinarySink *bs, ptrlen pl)
+{
+    BinarySink_put_string(bs, pl.ptr, pl.len);
+}
+
+void BinarySink_put_stringz(BinarySink *bs, const char *str)
+{
+    BinarySink_put_string(bs, str, strlen(str));
+}
+
+void BinarySink_put_stringsb(BinarySink *bs, struct strbuf *buf)
+{
+    BinarySink_put_string(bs, buf->s, buf->len);
+    strbuf_free(buf);
+}
+
+void BinarySink_put_asciz(BinarySink *bs, const char *str)
+{
+    bs->write(bs, str, strlen(str) + 1);
+}
+
+int BinarySink_put_pstring(BinarySink *bs, const char *str)
+{
+    size_t len = strlen(str);
+    if (len > 255)
+        return FALSE; /* can't write a Pascal-style string this long */
+    BinarySink_put_byte(bs, len);
+    bs->write(bs, str, len);
+    return TRUE;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static int BinarySource_data_avail(BinarySource *src, size_t wanted)
+{
+    if (src->err)
+        return FALSE;
+
+    if (wanted <= src->len - src->pos)
+        return TRUE;
+
+    src->err = BSE_OUT_OF_DATA;
+    return FALSE;
+}
+
+#define avail(wanted) BinarySource_data_avail(src, wanted)
+#define advance(dist) (src->pos += dist)
+#define here ((const void *)((const unsigned char *)src->data + src->pos))
+#define consume(dist)                           \
+    ((const void *)((const unsigned char *)src->data + \
+                    ((src->pos += dist) - dist)))
+
+ptrlen BinarySource_get_data(BinarySource *src, size_t wanted)
+{
+    if (!avail(wanted))
+        return make_ptrlen("", 0);
+
+    return make_ptrlen(consume(wanted), wanted);
+}
+
+unsigned char BinarySource_get_byte(BinarySource *src)
+{
+    const unsigned char *ucp;
+
+    if (!avail(1))
+        return 0;
+
+    ucp = consume(1);
+    return *ucp;
+}
+
+int BinarySource_get_bool(BinarySource *src)
+{
+    const unsigned char *ucp;
+
+    if (!avail(1))
+        return 0;
+
+    ucp = consume(1);
+    return *ucp != 0;
+}
+
+unsigned BinarySource_get_uint16(BinarySource *src)
+{
+    const unsigned char *ucp;
+
+    if (!avail(2))
+        return 0;
+
+    ucp = consume(2);
+    return GET_16BIT_MSB_FIRST(ucp);
+}
+
+unsigned long BinarySource_get_uint32(BinarySource *src)
+{
+    const unsigned char *ucp;
+
+    if (!avail(4))
+        return 0;
+
+    ucp = consume(4);
+    return GET_32BIT_MSB_FIRST(ucp);
+}
+
+uint64 BinarySource_get_uint64(BinarySource *src)
+{
+    const unsigned char *ucp;
+    uint64 toret;
+
+    if (!avail(8)) {
+        toret.hi = toret.lo = 0;
+        return toret;
+    }
+
+    ucp = consume(8);
+    toret.hi = GET_32BIT_MSB_FIRST(ucp);
+    toret.lo = GET_32BIT_MSB_FIRST(ucp + 4);
+    return toret;
+}
+
+ptrlen BinarySource_get_string(BinarySource *src)
+{
+    const unsigned char *ucp;
+    size_t len;
+
+    if (!avail(4))
+        return make_ptrlen("", 0);
+
+    ucp = consume(4);
+    len = GET_32BIT_MSB_FIRST(ucp);
+
+    if (!avail(len))
+        return make_ptrlen("", 0);
+
+    return make_ptrlen(consume(len), len);
+}
+
+const char *BinarySource_get_asciz(BinarySource *src)
+{
+    const char *start, *end;
+
+    if (src->err)
+        return "";
+
+    start = here;
+    end = memchr(start, '\0', src->len - src->pos);
+    if (!end) {
+        src->err = BSE_OUT_OF_DATA;
+        return "";
+    }
+
+    advance(end + 1 - start);
+    return start;
+}
+
+ptrlen BinarySource_get_pstring(BinarySource *src)
+{
+    const unsigned char *ucp;
+    size_t len;
+
+    if (!avail(1))
+        return make_ptrlen("", 0);
+
+    ucp = consume(1);
+    len = *ucp;
+
+    if (!avail(len))
+        return make_ptrlen("", 0);
+
+    return make_ptrlen(consume(len), len);
+}

+ 273 - 0
source/putty/marshal.h

@@ -0,0 +1,273 @@
+#ifndef PUTTY_MARSHAL_H
+#define PUTTY_MARSHAL_H
+
+#include "defs.h"
+
+/*
+ * A sort of 'abstract base class' or 'interface' or 'trait' which is
+ * the common feature of all types that want to accept data formatted
+ * using the SSH binary conventions of uint32, string, mpint etc.
+ */
+struct BinarySink {
+    void (*write)(BinarySink *sink, const void *data, size_t len);
+    BinarySink *binarysink_;
+};
+
+/*
+ * To define a structure type as a valid target for binary formatted
+ * data, put 'BinarySink_IMPLEMENTATION' in its declaration, and when
+ * an instance is set up, use 'BinarySink_INIT' to initialise the
+ * 'base class' state, providing a function pointer to be the
+ * implementation of the write() call above.
+ */
+#define BinarySink_IMPLEMENTATION BinarySink binarysink_[1]
+#define BinarySink_INIT(obj, writefn) \
+    ((obj)->binarysink_->write = (writefn), \
+     (obj)->binarysink_->binarysink_ = (obj)->binarysink_)
+
+/*
+ * The implementing type's write function will want to downcast its
+ * 'BinarySink *' parameter back to the more specific type. Also,
+ * sometimes you'll want to upcast a pointer to a particular
+ * implementing type into an abstract 'BinarySink *' to pass to
+ * generic subroutines not defined in this file. These macros do that
+ * job.
+ *
+ * Importantly, BinarySink_UPCAST can also be applied to a BinarySink
+ * * itself (and leaves it unchanged). That's achieved by a small
+ * piece of C trickery: implementing structures and the BinarySink
+ * structure itself both contain a field called binarysink_, but in
+ * implementing objects it's a BinarySink[1] whereas in the abstract
+ * type it's a 'BinarySink *' pointing back to the same structure,
+ * meaning that you can say 'foo->binarysink_' in either case and get
+ * a pointer type by different methods.
+ */
+#define BinarySink_DOWNCAST(object, type)                               \
+    TYPECHECK((object) == ((type *)0)->binarysink_,                     \
+              ((type *)(((char *)(object)) - offsetof(type, binarysink_))))
+#define BinarySink_UPCAST(object)                                       \
+    TYPECHECK((object)->binarysink_ == (BinarySink *)0,                 \
+              (object)->binarysink_)
+
+/*
+ * If you structure-copy an object that's implementing BinarySink,
+ * then that tricky self-pointer in its trait subobject will point to
+ * the wrong place. You could call BinarySink_INIT again, but this
+ * macro is terser and does all that's needed to fix up the copied
+ * object.
+ */
+#define BinarySink_COPIED(obj) \
+    ((obj)->binarysink_->binarysink_ = (obj)->binarysink_)
+
+/*
+ * The put_* macros are the main client to this system. Any structure
+ * which implements the BinarySink 'trait' is valid for use as the
+ * first parameter of any of these put_* macros.
+ */
+
+/* Basic big-endian integer types. uint64 is the structure type
+ * defined in int64.h, not the C99 built-in type. */
+#define put_byte(bs, val) \
+    BinarySink_put_byte(BinarySink_UPCAST(bs), val)
+#define put_uint16(bs, val) \
+    BinarySink_put_uint16(BinarySink_UPCAST(bs), val)
+#define put_uint32(bs, val) \
+    BinarySink_put_uint32(BinarySink_UPCAST(bs), val)
+#define put_uint64(bs, val) \
+    BinarySink_put_uint64(BinarySink_UPCAST(bs), val)
+
+/* SSH booleans, encoded as a single byte storing either 0 or 1. */
+#define put_bool(bs, val) \
+    BinarySink_put_bool(BinarySink_UPCAST(bs), val)
+
+/* SSH strings, with a leading uint32 length field. 'stringz' is a
+ * convenience function that takes an ordinary C zero-terminated
+ * string as input. 'stringsb' takes a strbuf * as input, and
+ * finalises it as a side effect (handy for multi-level marshalling in
+ * which you use these same functions to format an inner blob of data
+ * that then gets wrapped into a string container in an outer one). */
+#define put_string(bs, val, len) \
+    BinarySink_put_string(BinarySink_UPCAST(bs),val,len)
+#define put_stringpl(bs, ptrlen) \
+    BinarySink_put_stringpl(BinarySink_UPCAST(bs),ptrlen)
+#define put_stringz(bs, val) \
+    BinarySink_put_stringz(BinarySink_UPCAST(bs), val)
+#define put_stringsb(bs, val) \
+    BinarySink_put_stringsb(BinarySink_UPCAST(bs), val)
+
+/* Other string outputs: 'asciz' emits the string data directly into
+ * the output including the terminating \0, and 'pstring' emits the
+ * string in Pascal style with a leading _one_-byte length field.
+ * pstring can fail if the string is too long. */
+#define put_asciz(bs, val) \
+    BinarySink_put_asciz(BinarySink_UPCAST(bs), val)
+#define put_pstring(bs, val) \
+    BinarySink_put_pstring(BinarySink_UPCAST(bs), val)
+
+/* Multiprecision integers, in both the SSH-1 and SSH-2 formats. */
+#define put_mp_ssh1(bs, val) \
+    BinarySink_put_mp_ssh1(BinarySink_UPCAST(bs), val)
+#define put_mp_ssh2(bs, val) \
+    BinarySink_put_mp_ssh2(BinarySink_UPCAST(bs), val)
+
+/* Padding with a specified byte. */
+#define put_padding(bs, len, padbyte) \
+    BinarySink_put_padding(BinarySink_UPCAST(bs), len, padbyte)
+
+/* Fallback: just emit raw data bytes, using a syntax that matches the
+ * rest of these macros. */
+#define put_data(bs, val, len) \
+    BinarySink_put_data(BinarySink_UPCAST(bs), val, len)
+
+/*
+ * The underlying real C functions that implement most of those
+ * macros. Generally you won't want to call these directly, because
+ * they have such cumbersome names; you call the wrapper macros above
+ * instead.
+ *
+ * A few functions whose wrapper macros are defined above are actually
+ * declared in other headers, so as to guarantee that the
+ * declaration(s) of their other parameter type(s) are in scope.
+ */
+void BinarySink_put_data(BinarySink *, const void *data, size_t len);
+void BinarySink_put_padding(BinarySink *, size_t len, unsigned char padbyte);
+void BinarySink_put_byte(BinarySink *, unsigned char);
+void BinarySink_put_bool(BinarySink *, int);
+void BinarySink_put_uint16(BinarySink *, unsigned long);
+void BinarySink_put_uint32(BinarySink *, unsigned long);
+void BinarySink_put_string(BinarySink *, const void *data, size_t len);
+void BinarySink_put_stringpl(BinarySink *, ptrlen);
+void BinarySink_put_stringz(BinarySink *, const char *str);
+struct strbuf;
+void BinarySink_put_stringsb(BinarySink *, struct strbuf *);
+void BinarySink_put_asciz(BinarySink *, const char *str);
+int BinarySink_put_pstring(BinarySink *, const char *str);
+
+/* ---------------------------------------------------------------------- */
+
+/*
+ * A complementary trait structure for _un_-marshalling.
+ *
+ * This structure contains client-visible data fields rather than
+ * methods, because that seemed more useful than leaving it totally
+ * opaque. But it's still got the self-pointer system that will allow
+ * the set of get_* macros to target one of these itself or any other
+ * type that 'derives' from it. So, for example, an SSH packet
+ * structure can act as a BinarySource while also having additional
+ * fields like the packet type.
+ */
+typedef enum BinarySourceError {
+    BSE_NO_ERROR,
+    BSE_OUT_OF_DATA,
+    BSE_INVALID
+} BinarySourceError;
+struct BinarySource {
+    /*
+     * (data, len) is the data block being decoded. pos is the current
+     * position within the block.
+     */
+    const void *data;
+    size_t pos, len;
+
+    /*
+     * 'err' indicates whether a decoding error has happened at any
+     * point. Once this has been set to something other than
+     * BSE_NO_ERROR, it shouldn't be changed by any unmarshalling
+     * function. So you can safely do a long sequence of get_foo()
+     * operations and then test err just once at the end, rather than
+     * having to conditionalise every single get.
+     *
+     * The unmarshalling functions should always return some value,
+     * even if a decoding error occurs. Generally on error they'll
+     * return zero (if numeric) or the empty string (if string-based),
+     * or some other appropriate default value for more complicated
+     * types.
+     *
+     * If the usual return value is dynamically allocated (e.g. a
+     * Bignum, or a normal C 'char *' string), then the error value is
+     * also dynamic in the same way. So you have to free exactly the
+     * same set of things whether or not there was a decoding error,
+     * which simplifies exit paths - for example, you could call a big
+     * pile of get_foo functions, then put the actual handling of the
+     * results under 'if (!get_err(src))', and then free everything
+     * outside that if.
+     */
+    BinarySourceError err;
+
+    /*
+     * Self-pointer for the implicit derivation trick, same as
+     * BinarySink above.
+     */
+    BinarySource *binarysource_;
+};
+
+/*
+ * Implementation macros, similar to BinarySink.
+ */
+#define BinarySource_IMPLEMENTATION BinarySource binarysource_[1]
+#define BinarySource_INIT__(obj, data_, len_)    \
+    ((obj)->data = (data_),                             \
+     (obj)->len = (len_),                               \
+     (obj)->pos = 0,                                    \
+     (obj)->err = BSE_NO_ERROR,                         \
+     (obj)->binarysource_ = (obj))
+#define BinarySource_BARE_INIT(obj, data_, len_)                \
+    TYPECHECK(&(obj)->binarysource_ == (BinarySource **)0,      \
+              BinarySource_INIT__(obj, data_, len_))
+#define BinarySource_INIT(obj, data_, len_)                             \
+    TYPECHECK(&(obj)->binarysource_ == (BinarySource (*)[1])0,          \
+              BinarySource_INIT__(BinarySource_UPCAST(obj), data_, len_))
+#define BinarySource_DOWNCAST(object, type)                               \
+    TYPECHECK((object) == ((type *)0)->binarysource_,                     \
+              ((type *)(((char *)(object)) - offsetof(type, binarysource_))))
+#define BinarySource_UPCAST(object)                                       \
+    TYPECHECK((object)->binarysource_ == (BinarySource *)0,                 \
+              (object)->binarysource_)
+#define BinarySource_COPIED(obj) \
+    ((obj)->binarysource_->binarysource_ = (obj)->binarysource_)
+
+#define get_data(src, len) \
+    BinarySource_get_data(BinarySource_UPCAST(src), len)
+#define get_byte(src) \
+    BinarySource_get_byte(BinarySource_UPCAST(src))
+#define get_bool(src) \
+    BinarySource_get_bool(BinarySource_UPCAST(src))
+#define get_uint16(src) \
+    BinarySource_get_uint16(BinarySource_UPCAST(src))
+#define get_uint32(src) \
+    BinarySource_get_uint32(BinarySource_UPCAST(src))
+#define get_uint64(src) \
+    BinarySource_get_uint64(BinarySource_UPCAST(src))
+#define get_string(src) \
+    BinarySource_get_string(BinarySource_UPCAST(src))
+#define get_asciz(src) \
+    BinarySource_get_asciz(BinarySource_UPCAST(src))
+#define get_pstring(src) \
+    BinarySource_get_pstring(BinarySource_UPCAST(src))
+#define get_mp_ssh1(src) \
+    BinarySource_get_mp_ssh1(BinarySource_UPCAST(src))
+#define get_mp_ssh2(src) \
+    BinarySource_get_mp_ssh2(BinarySource_UPCAST(src))
+#define get_rsa_ssh1_pub(src, rsa, order) \
+    BinarySource_get_rsa_ssh1_pub(BinarySource_UPCAST(src), rsa, order)
+#define get_rsa_ssh1_priv(src, rsa) \
+    BinarySource_get_rsa_ssh1_priv(BinarySource_UPCAST(src), rsa)
+
+#define get_err(src) (BinarySource_UPCAST(src)->err)
+#define get_avail(src) (BinarySource_UPCAST(src)->len - \
+                       BinarySource_UPCAST(src)->pos)
+#define get_ptr(src)                                                    \
+    ((const void *)(                                                    \
+        (const unsigned char *)(BinarySource_UPCAST(src)->data) +       \
+        BinarySource_UPCAST(src)->pos))
+
+ptrlen BinarySource_get_data(BinarySource *, size_t);
+unsigned char BinarySource_get_byte(BinarySource *);
+int BinarySource_get_bool(BinarySource *);
+unsigned BinarySource_get_uint16(BinarySource *);
+unsigned long BinarySource_get_uint32(BinarySource *);
+ptrlen BinarySource_get_string(BinarySource *);
+const char *BinarySource_get_asciz(BinarySource *);
+ptrlen BinarySource_get_pstring(BinarySource *);
+
+#endif /* PUTTY_MARSHAL_H */

+ 42 - 0
source/putty/nullplug.c

@@ -0,0 +1,42 @@
+/*
+ * nullplug.c: provide a null implementation of the Plug vtable which
+ * ignores all calls. Occasionally useful in cases where we want to
+ * make a network connection just to see if it works, but not do
+ * anything with it afterwards except close it again.
+ */
+
+#include "putty.h"
+
+static void nullplug_socket_log(Plug plug, int type, SockAddr addr, int port,
+                                const char *error_msg, int error_code)
+{
+}
+
+static void nullplug_closing(Plug plug, const char *error_msg, int error_code,
+			     int calling_back)
+{
+}
+
+static void nullplug_receive(Plug plug, int urgent, char *data, int len)
+{
+}
+
+static void nullplug_sent(Plug plug, int bufsize)
+{
+}
+
+static const Plug_vtable nullplug_plugvt = {
+    nullplug_socket_log,
+    nullplug_closing,
+    nullplug_receive,
+    nullplug_sent,
+    NULL
+};
+
+static const Plug_vtable *nullplug_plugvt_ptr = &nullplug_plugvt;
+
+/*
+ * There's a singleton instance of nullplug, because it's not
+ * interesting enough to worry about making more than one of them.
+ */
+Plug nullplug = &nullplug_plugvt_ptr;

+ 282 - 0
source/putty/ssh1bpp.c

@@ -0,0 +1,282 @@
+/*
+ * Binary packet protocol for SSH-1.
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "sshbpp.h"
+#include "sshcr.h"
+
+struct ssh1_bpp_state {
+    int crState;
+    long len, pad, biglen, length, maxlen;
+    unsigned char *data;
+    unsigned long realcrc, gotcrc;
+    int chunk;
+    PktIn *pktin;
+
+    const struct ssh_cipher *cipher;
+    void *cipher_ctx;
+
+    void *crcda_ctx;
+
+    void *compctx, *decompctx;
+
+    BinaryPacketProtocol bpp;
+};
+
+static void ssh1_bpp_free(BinaryPacketProtocol *bpp);
+static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp);
+static PktOut *ssh1_bpp_new_pktout(int type);
+static void ssh1_bpp_format_packet(BinaryPacketProtocol *bpp, PktOut *pkt);
+
+const struct BinaryPacketProtocolVtable ssh1_bpp_vtable = {
+    ssh1_bpp_free,
+    ssh1_bpp_handle_input,
+    ssh1_bpp_new_pktout,
+    ssh1_bpp_format_packet,
+};
+
+BinaryPacketProtocol *ssh1_bpp_new(void)
+{
+    struct ssh1_bpp_state *s = snew(struct ssh1_bpp_state);
+    memset(s, 0, sizeof(*s));
+    s->bpp.vt = &ssh1_bpp_vtable;
+    return &s->bpp;
+}
+
+static void ssh1_bpp_free(BinaryPacketProtocol *bpp)
+{
+    struct ssh1_bpp_state *s = FROMFIELD(bpp, struct ssh1_bpp_state, bpp);
+    if (s->cipher)
+        s->cipher->free_context(s->cipher_ctx);
+    if (s->compctx)
+        zlib_compress_cleanup(s->compctx);
+    if (s->decompctx)
+        zlib_decompress_cleanup(s->decompctx);
+    if (s->crcda_ctx)
+        crcda_free_context(s->crcda_ctx);
+    if (s->pktin)
+        ssh_unref_packet(s->pktin);
+    sfree(s);
+}
+
+void ssh1_bpp_new_cipher(BinaryPacketProtocol *bpp,
+                         const struct ssh_cipher *cipher,
+                         const void *session_key)
+{
+    struct ssh1_bpp_state *s;
+    assert(bpp->vt == &ssh1_bpp_vtable);
+    s = FROMFIELD(bpp, struct ssh1_bpp_state, bpp);
+
+    assert(!s->cipher);
+
+    s->cipher = cipher;
+    if (s->cipher) {
+        s->cipher_ctx = cipher->make_context();
+        cipher->sesskey(s->cipher_ctx, session_key);
+
+        assert(!s->crcda_ctx);
+        s->crcda_ctx = crcda_make_context();
+    }
+}
+
+void ssh1_bpp_start_compression(BinaryPacketProtocol *bpp)
+{
+    struct ssh1_bpp_state *s;
+    assert(bpp->vt == &ssh1_bpp_vtable);
+    s = FROMFIELD(bpp, struct ssh1_bpp_state, bpp);
+
+    assert(!s->compctx);
+    assert(!s->decompctx);
+
+    s->compctx = zlib_compress_init();
+    s->decompctx = zlib_decompress_init();
+}
+
+static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp)
+{
+    struct ssh1_bpp_state *s = FROMFIELD(bpp, struct ssh1_bpp_state, bpp);
+
+    crBegin(s->crState);
+
+    while (1) {
+        s->maxlen = 0;
+        s->length = 0;
+
+        {
+            unsigned char lenbuf[4];
+            crMaybeWaitUntilV(bufchain_try_fetch_consume(
+                                  bpp->in_raw, lenbuf, 4));
+            s->len = toint(GET_32BIT_MSB_FIRST(lenbuf));
+        }
+
+        if (s->len < 0 || s->len > 262144) { /* SSH1.5-mandated max size */
+            s->bpp.error = dupprintf(
+                "Extremely large packet length from server suggests"
+                " data stream corruption");
+            crStopV;
+        }
+
+        s->pad = 8 - (s->len % 8);
+        s->biglen = s->len + s->pad;
+        s->length = s->len - 5;
+
+        /*
+         * Allocate the packet to return, now we know its length.
+         */
+        s->pktin = snew_plus(PktIn, s->biglen);
+        s->pktin->qnode.prev = s->pktin->qnode.next = NULL;
+        s->pktin->refcount = 1;
+        s->pktin->type = 0;
+
+        s->maxlen = s->biglen;
+        s->data = snew_plus_get_aux(s->pktin);
+
+        crMaybeWaitUntilV(bufchain_try_fetch_consume(
+                              bpp->in_raw, s->data, s->biglen));
+
+        if (s->cipher && detect_attack(s->crcda_ctx,
+                                       s->data, s->biglen, NULL)) {
+            s->bpp.error = dupprintf(
+                "Network attack (CRC compensation) detected!");
+            crStopV;
+        }
+
+        if (s->cipher)
+            s->cipher->decrypt(s->cipher_ctx, s->data, s->biglen);
+
+        s->realcrc = crc32_compute(s->data, s->biglen - 4);
+        s->gotcrc = GET_32BIT(s->data + s->biglen - 4);
+        if (s->gotcrc != s->realcrc) {
+            s->bpp.error = dupprintf(
+                "Incorrect CRC received on packet");
+            crStopV;
+        }
+
+        if (s->decompctx) {
+            unsigned char *decompblk;
+            int decomplen;
+            if (!zlib_decompress_block(s->decompctx,
+                                       s->data + s->pad, s->length + 1,
+                                       &decompblk, &decomplen)) {
+                s->bpp.error = dupprintf(
+                    "Zlib decompression encountered invalid data");
+                crStopV;
+            }
+
+            if (s->maxlen < s->pad + decomplen) {
+                PktIn *old_pktin = s->pktin;
+
+                s->maxlen = s->pad + decomplen;
+                s->pktin = snew_plus(PktIn, s->maxlen);
+                *s->pktin = *old_pktin; /* structure copy */
+                s->data = snew_plus_get_aux(s->pktin);
+
+                smemclr(old_pktin, s->biglen);
+                sfree(old_pktin);
+            }
+
+            memcpy(s->data + s->pad, decompblk, decomplen);
+            sfree(decompblk);
+            s->length = decomplen - 1;
+        }
+
+        /*
+         * Now we can find the bounds of the semantic content of the
+         * packet, and the initial type byte.
+         */
+        s->data += s->pad;
+        s->pktin->type = *s->data++;
+        BinarySource_INIT(s->pktin, s->data, s->length);
+
+        if (s->bpp.logctx) {
+            logblank_t blanks[MAX_BLANKS];
+            int nblanks = ssh1_censor_packet(
+                s->bpp.pls, s->pktin->type, FALSE,
+                make_ptrlen(s->data, s->length), blanks);
+            log_packet(s->bpp.logctx, PKT_INCOMING, s->pktin->type,
+                       ssh1_pkt_type(s->pktin->type),
+                       get_ptr(s->pktin), get_avail(s->pktin), nblanks, blanks,
+                       NULL, 0, NULL);
+        }
+
+        pq_push(s->bpp.in_pq, s->pktin);
+
+        {
+            int type = s->pktin->type;
+            s->pktin = NULL;
+
+            if (type == SSH1_MSG_DISCONNECT)
+                s->bpp.seen_disconnect = TRUE;
+        }
+    }
+    crFinishV;
+}
+
+static PktOut *ssh1_bpp_new_pktout(int pkt_type)
+{
+    PktOut *pkt = ssh_new_packet();
+    pkt->length = 4 + 8;	    /* space for length + max padding */
+    put_byte(pkt, pkt_type);
+    pkt->prefix = pkt->length;
+    pkt->type = pkt_type;
+    pkt->downstream_id = 0;
+    pkt->additional_log_text = NULL;
+    return pkt;
+}
+
+static void ssh1_bpp_format_packet(BinaryPacketProtocol *bpp, PktOut *pkt)
+{
+    struct ssh1_bpp_state *s = FROMFIELD(bpp, struct ssh1_bpp_state, bpp);
+    int pad, biglen, i, pktoffs;
+    unsigned long crc;
+    int len;
+
+    if (s->bpp.logctx) {
+        ptrlen pktdata = make_ptrlen(pkt->data + pkt->prefix,
+                                     pkt->length - pkt->prefix);
+        logblank_t blanks[MAX_BLANKS];
+        int nblanks = ssh1_censor_packet(
+            s->bpp.pls, pkt->type, TRUE, pktdata, blanks);
+        log_packet(s->bpp.logctx, PKT_OUTGOING, pkt->type,
+                   ssh1_pkt_type(pkt->type),
+                   pktdata.ptr, pktdata.len, nblanks, blanks,
+                   NULL, 0, NULL);
+    }
+
+    if (s->compctx) {
+        unsigned char *compblk;
+        int complen;
+        zlib_compress_block(s->compctx, pkt->data + 12, pkt->length - 12,
+                            &compblk, &complen, 0);
+        /* Replace the uncompressed packet data with the compressed
+         * version. */
+        pkt->length = 12;
+        put_data(pkt, compblk, complen);
+        sfree(compblk);
+    }
+
+    put_uint32(pkt, 0); /* space for CRC */
+    len = pkt->length - 4 - 8;  /* len(type+data+CRC) */
+    pad = 8 - (len % 8);
+    pktoffs = 8 - pad;
+    biglen = len + pad;         /* len(padding+type+data+CRC) */
+
+    for (i = pktoffs; i < 4+8; i++)
+        pkt->data[i] = random_byte();
+    crc = crc32_compute(pkt->data + pktoffs + 4,
+                        biglen - 4); /* all ex len */
+    PUT_32BIT(pkt->data + pktoffs + 4 + biglen - 4, crc);
+    PUT_32BIT(pkt->data + pktoffs, len);
+
+    if (s->cipher)
+        s->cipher->encrypt(s->cipher_ctx, pkt->data + pktoffs + 4, biglen);
+
+    bufchain_add(s->bpp.out_raw, pkt->data + pktoffs,
+                 biglen + 4); /* len(length+padding+type+data+CRC) */
+
+    ssh_free_pktout(pkt);
+}

+ 76 - 0
source/putty/ssh1censor.c

@@ -0,0 +1,76 @@
+/*
+ * Packet-censoring code for SSH-1, used to identify sensitive fields
+ * like passwords so that the logging system can avoid writing them
+ * into log files.
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+
+int ssh1_censor_packet(
+    const PacketLogSettings *pls, int type, int sender_is_client,
+    ptrlen pkt, logblank_t *blanks)
+{
+    int nblanks = 0;
+    ptrlen str;
+    BinarySource src[1];
+
+    BinarySource_BARE_INIT(src, pkt.ptr, pkt.len);
+
+    if (pls->omit_data &&
+        (type == SSH1_SMSG_STDOUT_DATA ||
+         type == SSH1_SMSG_STDERR_DATA ||
+         type == SSH1_CMSG_STDIN_DATA ||
+         type == SSH1_MSG_CHANNEL_DATA)) {
+        /* "Session data" packets - omit the data string. */
+        if (type == SSH1_MSG_CHANNEL_DATA)
+            get_uint32(src);           /* skip channel id */
+        str = get_string(src);
+        if (!get_err(src)) {
+            assert(nblanks < MAX_BLANKS);
+            blanks[nblanks].offset = src->pos - str.len;
+            blanks[nblanks].type = PKTLOG_OMIT;
+            blanks[nblanks].len = str.len;
+            nblanks++;
+        }
+    }
+
+    if (sender_is_client && pls->omit_passwords) {
+        if (type == SSH1_CMSG_AUTH_PASSWORD ||
+            type == SSH1_CMSG_AUTH_TIS_RESPONSE ||
+            type == SSH1_CMSG_AUTH_CCARD_RESPONSE) {
+            /* If this is a password or similar packet, blank the
+             * password(s). */
+            assert(nblanks < MAX_BLANKS);
+            blanks[nblanks].offset = 0;
+            blanks[nblanks].len = pkt.len;
+            blanks[nblanks].type = PKTLOG_BLANK;
+            nblanks++;
+        } else if (type == SSH1_CMSG_X11_REQUEST_FORWARDING) {
+            /*
+             * If this is an X forwarding request packet, blank the
+             * fake auth data.
+             *
+             * Note that while we blank the X authentication data
+             * here, we don't take any special action to blank the
+             * start of an X11 channel, so using MIT-MAGIC-COOKIE-1
+             * and actually opening an X connection without having
+             * session blanking enabled is likely to leak your cookie
+             * into the log.
+             */
+            get_string(src);              /* skip protocol name */
+            str = get_string(src);
+            if (!get_err(src)) {
+                assert(nblanks < MAX_BLANKS);
+                blanks[nblanks].offset = src->pos - str.len;
+                blanks[nblanks].type = PKTLOG_BLANK;
+                blanks[nblanks].len = str.len;
+                nblanks++;
+            }
+        }
+    }
+
+    return nblanks;
+}

+ 162 - 0
source/putty/ssh2bpp-bare.c

@@ -0,0 +1,162 @@
+/*
+ * Trivial binary packet protocol for the 'bare' ssh-connection
+ * protocol used in PuTTY's SSH-2 connection sharing system.
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "sshbpp.h"
+#include "sshcr.h"
+
+struct ssh2_bare_bpp_state {
+    int crState;
+    long packetlen, maxlen;
+    unsigned char *data;
+    unsigned long incoming_sequence, outgoing_sequence;
+    PktIn *pktin;
+
+    BinaryPacketProtocol bpp;
+};
+
+static void ssh2_bare_bpp_free(BinaryPacketProtocol *bpp);
+static void ssh2_bare_bpp_handle_input(BinaryPacketProtocol *bpp);
+static PktOut *ssh2_bare_bpp_new_pktout(int type);
+static void ssh2_bare_bpp_format_packet(BinaryPacketProtocol *bpp, PktOut *);
+
+const struct BinaryPacketProtocolVtable ssh2_bare_bpp_vtable = {
+    ssh2_bare_bpp_free,
+    ssh2_bare_bpp_handle_input,
+    ssh2_bare_bpp_new_pktout,
+    ssh2_bare_bpp_format_packet,
+};
+
+BinaryPacketProtocol *ssh2_bare_bpp_new(void)
+{
+    struct ssh2_bare_bpp_state *s = snew(struct ssh2_bare_bpp_state);
+    memset(s, 0, sizeof(*s));
+    s->bpp.vt = &ssh2_bare_bpp_vtable;
+    return &s->bpp;
+}
+
+static void ssh2_bare_bpp_free(BinaryPacketProtocol *bpp)
+{
+    struct ssh2_bare_bpp_state *s =
+        FROMFIELD(bpp, struct ssh2_bare_bpp_state, bpp);
+    if (s->pktin)
+        ssh_unref_packet(s->pktin);
+    sfree(s);
+}
+
+static void ssh2_bare_bpp_handle_input(BinaryPacketProtocol *bpp)
+{
+    struct ssh2_bare_bpp_state *s =
+        FROMFIELD(bpp, struct ssh2_bare_bpp_state, bpp);
+
+    crBegin(s->crState);
+
+    while (1) {
+        /* Read the length field. */
+        {
+            unsigned char lenbuf[4];
+            crMaybeWaitUntilV(bufchain_try_fetch_consume(
+                                  s->bpp.in_raw, lenbuf, 4));
+            s->packetlen = toint(GET_32BIT_MSB_FIRST(lenbuf));
+        }
+
+        if (s->packetlen <= 0 || s->packetlen >= (long)OUR_V2_PACKETLIMIT) {
+            s->bpp.error = dupstr("Invalid packet length received");
+            crStopV;
+        }
+
+        /*
+         * Allocate the packet to return, now we know its length.
+         */
+        s->pktin = snew_plus(PktIn, s->packetlen);
+        s->pktin->qnode.prev = s->pktin->qnode.next = NULL;
+        s->maxlen = 0;
+        s->pktin->refcount = 1;
+        s->data = snew_plus_get_aux(s->pktin);
+
+        s->pktin->encrypted_len = s->packetlen;
+
+        s->pktin->sequence = s->incoming_sequence++;
+
+        /*
+         * Read the remainder of the packet.
+         */
+        crMaybeWaitUntilV(bufchain_try_fetch_consume(
+                              s->bpp.in_raw, s->data, s->packetlen));
+
+        /*
+         * The data we just read is precisely the initial type byte
+         * followed by the packet payload.
+         */
+        s->pktin->type = s->data[0];
+        s->data++;
+        s->packetlen--;
+        BinarySource_INIT(s->pktin, s->data, s->packetlen);
+
+        /*
+         * Log incoming packet, possibly omitting sensitive fields.
+         */
+        if (s->bpp.logctx) {
+            logblank_t blanks[MAX_BLANKS];
+            int nblanks = ssh2_censor_packet(
+                s->bpp.pls, s->pktin->type, FALSE,
+                make_ptrlen(s->data, s->packetlen), blanks);
+            log_packet(s->bpp.logctx, PKT_INCOMING, s->pktin->type,
+                       ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx,
+                                     s->pktin->type),
+                       get_ptr(s->pktin), get_avail(s->pktin), nblanks, blanks,
+                       &s->pktin->sequence, 0, NULL);
+        }
+
+        pq_push(s->bpp.in_pq, s->pktin);
+
+        {
+            int type = s->pktin->type;
+            s->pktin = NULL;
+
+            if (type == SSH2_MSG_DISCONNECT)
+                s->bpp.seen_disconnect = TRUE;
+        }
+    }
+    crFinishV;
+}
+
+static PktOut *ssh2_bare_bpp_new_pktout(int pkt_type)
+{
+    PktOut *pkt = ssh_new_packet();
+    pkt->length = 4; /* space for packet length */
+    pkt->type = pkt_type;
+    put_byte(pkt, pkt_type);
+    return pkt;
+}
+
+static void ssh2_bare_bpp_format_packet(BinaryPacketProtocol *bpp, PktOut *pkt)
+{
+    struct ssh2_bare_bpp_state *s =
+        FROMFIELD(bpp, struct ssh2_bare_bpp_state, bpp);
+
+    if (s->bpp.logctx) {
+        ptrlen pktdata = make_ptrlen(pkt->data + 5, pkt->length - 5);
+        logblank_t blanks[MAX_BLANKS];
+        int nblanks = ssh2_censor_packet(
+            s->bpp.pls, pkt->type, TRUE, pktdata, blanks);
+        log_packet(s->bpp.logctx, PKT_OUTGOING, pkt->type,
+                   ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx,
+                                 pkt->type),
+                   pktdata.ptr, pktdata.len, nblanks, blanks,
+                   &s->outgoing_sequence,
+                   pkt->downstream_id, pkt->additional_log_text);
+    }
+
+    s->outgoing_sequence++;        /* only for diagnostics, really */
+
+    PUT_32BIT(pkt->data, pkt->length - 4);
+    bufchain_add(s->bpp.out_raw, pkt->data, pkt->length);
+
+    ssh_free_pktout(pkt);
+}

+ 679 - 0
source/putty/ssh2bpp.c

@@ -0,0 +1,679 @@
+/*
+ * Binary packet protocol for SSH-2.
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "sshbpp.h"
+#include "sshcr.h"
+
+struct ssh2_bpp_direction {
+    unsigned long sequence;
+    const struct ssh2_cipher *cipher;
+    void *cipher_ctx;
+    const struct ssh_mac *mac;
+    int etm_mode;
+    void *mac_ctx;
+    const struct ssh_compress *comp;
+    void *comp_ctx;
+};
+
+struct ssh2_bpp_state {
+    int crState;
+    long len, pad, payload, packetlen, maclen, length, maxlen;
+    unsigned char *buf;
+    size_t bufsize;
+    unsigned char *data;
+    unsigned cipherblk;
+    PktIn *pktin;
+    BinarySink *sc_mac_bs;
+
+    struct ssh2_bpp_direction in, out;
+    int pending_newkeys;
+
+    BinaryPacketProtocol bpp;
+};
+
+static void ssh2_bpp_free(BinaryPacketProtocol *bpp);
+static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp);
+static PktOut *ssh2_bpp_new_pktout(int type);
+static void ssh2_bpp_format_packet(BinaryPacketProtocol *bpp, PktOut *pkt);
+
+const struct BinaryPacketProtocolVtable ssh2_bpp_vtable = {
+    ssh2_bpp_free,
+    ssh2_bpp_handle_input,
+    ssh2_bpp_new_pktout,
+    ssh2_bpp_format_packet,
+};
+
+BinaryPacketProtocol *ssh2_bpp_new(void)
+{
+    struct ssh2_bpp_state *s = snew(struct ssh2_bpp_state);
+    memset(s, 0, sizeof(*s));
+    s->bpp.vt = &ssh2_bpp_vtable;
+    return &s->bpp;
+}
+
+static void ssh2_bpp_free(BinaryPacketProtocol *bpp)
+{
+    struct ssh2_bpp_state *s = FROMFIELD(bpp, struct ssh2_bpp_state, bpp);
+    sfree(s->buf);
+    if (s->out.cipher_ctx)
+        s->out.cipher->free_context(s->out.cipher_ctx);
+    if (s->out.mac_ctx)
+        s->out.mac->free_context(s->out.mac_ctx);
+    if (s->out.comp_ctx)
+        s->out.comp->compress_cleanup(s->out.comp_ctx);
+    if (s->in.cipher_ctx)
+        s->in.cipher->free_context(s->in.cipher_ctx);
+    if (s->in.mac_ctx)
+        s->in.mac->free_context(s->in.mac_ctx);
+    if (s->in.comp_ctx)
+        s->in.comp->decompress_cleanup(s->in.comp_ctx);
+    if (s->pktin)
+        ssh_unref_packet(s->pktin);
+    sfree(s);
+}
+
+void ssh2_bpp_new_outgoing_crypto(
+    BinaryPacketProtocol *bpp,
+    const struct ssh2_cipher *cipher, const void *ckey, const void *iv,
+    const struct ssh_mac *mac, int etm_mode, const void *mac_key,
+    const struct ssh_compress *compression)
+{
+    struct ssh2_bpp_state *s;
+    assert(bpp->vt == &ssh2_bpp_vtable);
+    s = FROMFIELD(bpp, struct ssh2_bpp_state, bpp);
+
+    if (s->out.cipher_ctx)
+        s->out.cipher->free_context(s->out.cipher_ctx);
+    if (s->out.mac_ctx)
+        s->out.mac->free_context(s->out.mac_ctx);
+    if (s->out.comp_ctx)
+        s->out.comp->compress_cleanup(s->out.comp_ctx);
+
+    s->out.cipher = cipher;
+    if (cipher) {
+        s->out.cipher_ctx = cipher->make_context();
+        cipher->setkey(s->out.cipher_ctx, ckey);
+        cipher->setiv(s->out.cipher_ctx, iv);
+    }
+    s->out.mac = mac;
+    s->out.etm_mode = etm_mode;
+    if (mac) {
+        s->out.mac_ctx = mac->make_context(s->out.cipher_ctx);
+        mac->setkey(s->out.mac_ctx, mac_key);
+    }
+
+    s->out.comp = compression;
+    /* out_comp is always non-NULL, because no compression is
+     * indicated by ssh_comp_none. So compress_init always exists, but
+     * it may return a null out_comp_ctx. */
+    s->out.comp_ctx = compression->compress_init();
+}
+
+void ssh2_bpp_new_incoming_crypto(
+    BinaryPacketProtocol *bpp,
+    const struct ssh2_cipher *cipher, const void *ckey, const void *iv,
+    const struct ssh_mac *mac, int etm_mode, const void *mac_key,
+    const struct ssh_compress *compression)
+{
+    struct ssh2_bpp_state *s;
+    assert(bpp->vt == &ssh2_bpp_vtable);
+    s = FROMFIELD(bpp, struct ssh2_bpp_state, bpp);
+
+    if (s->in.cipher_ctx)
+        s->in.cipher->free_context(s->in.cipher_ctx);
+    if (s->in.mac_ctx)
+        s->in.mac->free_context(s->in.mac_ctx);
+    if (s->in.comp_ctx)
+        s->in.comp->decompress_cleanup(s->in.comp_ctx);
+
+    s->in.cipher = cipher;
+    if (cipher) {
+        s->in.cipher_ctx = cipher->make_context();
+        cipher->setkey(s->in.cipher_ctx, ckey);
+        cipher->setiv(s->in.cipher_ctx, iv);
+    }
+    s->in.mac = mac;
+    s->in.etm_mode = etm_mode;
+    if (mac) {
+        s->in.mac_ctx = mac->make_context(s->in.cipher_ctx);
+        mac->setkey(s->in.mac_ctx, mac_key);
+    }
+
+    s->in.comp = compression;
+    /* in_comp is always non-NULL, because no compression is
+     * indicated by ssh_comp_none. So compress_init always exists, but
+     * it may return a null in_comp_ctx. */
+    s->in.comp_ctx = compression->decompress_init();
+
+    /* Clear the pending_newkeys flag, so that handle_input below will
+     * start consuming the input data again. */
+    s->pending_newkeys = FALSE;
+}
+
+static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp)
+{
+    struct ssh2_bpp_state *s = FROMFIELD(bpp, struct ssh2_bpp_state, bpp);
+
+    crBegin(s->crState);
+
+    while (1) {
+        s->maxlen = 0;
+        s->length = 0;
+        if (s->in.cipher)
+            s->cipherblk = s->in.cipher->blksize;
+        else
+            s->cipherblk = 8;
+        if (s->cipherblk < 8)
+            s->cipherblk = 8;
+        s->maclen = s->in.mac ? s->in.mac->len : 0;
+
+        if (s->in.cipher && (s->in.cipher->flags & SSH_CIPHER_IS_CBC) &&
+            s->in.mac && !s->in.etm_mode) {
+            /*
+             * When dealing with a CBC-mode cipher, we want to avoid the
+             * possibility of an attacker's tweaking the ciphertext stream
+             * so as to cause us to feed the same block to the block
+             * cipher more than once and thus leak information
+             * (VU#958563).  The way we do this is not to take any
+             * decisions on the basis of anything we've decrypted until
+             * we've verified it with a MAC.  That includes the packet
+             * length, so we just read data and check the MAC repeatedly,
+             * and when the MAC passes, see if the length we've got is
+             * plausible.
+             *
+             * This defence is unnecessary in OpenSSH ETM mode, because
+             * the whole point of ETM mode is that the attacker can't
+             * tweak the ciphertext stream at all without the MAC
+             * detecting it before we decrypt anything.
+             */
+
+            /*
+             * Make sure we have buffer space for a maximum-size packet.
+             */
+            unsigned buflimit = OUR_V2_PACKETLIMIT + s->maclen;
+            if (s->bufsize < buflimit) {
+                s->bufsize = buflimit;
+                s->buf = sresize(s->buf, s->bufsize, unsigned char);
+            }
+
+            /* Read an amount corresponding to the MAC. */
+            crMaybeWaitUntilV(bufchain_try_fetch_consume(
+                                  s->bpp.in_raw, s->buf, s->maclen));
+
+            s->packetlen = 0;
+            s->in.mac->start(s->in.mac_ctx);
+            s->sc_mac_bs = s->in.mac->sink(s->in.mac_ctx);
+            put_uint32(s->sc_mac_bs, s->in.sequence);
+
+            for (;;) { /* Once around this loop per cipher block. */
+                /* Read another cipher-block's worth, and tack it on to
+                 * the end. */
+                crMaybeWaitUntilV(bufchain_try_fetch_consume(
+                                      s->bpp.in_raw,
+                                      s->buf + (s->packetlen + s->maclen),
+                                      s->cipherblk));
+                /* Decrypt one more block (a little further back in
+                 * the stream). */
+                s->in.cipher->decrypt(
+                    s->in.cipher_ctx,
+                    s->buf + s->packetlen, s->cipherblk);
+
+                /* Feed that block to the MAC. */
+                put_data(s->sc_mac_bs,
+                         s->buf + s->packetlen, s->cipherblk);
+                s->packetlen += s->cipherblk;
+
+                /* See if that gives us a valid packet. */
+                if (s->in.mac->verresult(
+                        s->in.mac_ctx, s->buf + s->packetlen) &&
+                    ((s->len = toint(GET_32BIT(s->buf))) ==
+                     s->packetlen-4))
+                    break;
+                if (s->packetlen >= (long)OUR_V2_PACKETLIMIT) {
+                    s->bpp.error = dupprintf(
+                        "No valid incoming packet found");
+                    crStopV;
+                }
+            }
+            s->maxlen = s->packetlen + s->maclen;
+
+            /*
+             * Now transfer the data into an output packet.
+             */
+            s->pktin = snew_plus(PktIn, s->maxlen);
+            s->pktin->qnode.prev = s->pktin->qnode.next = NULL;
+            s->pktin->refcount = 1;
+            s->pktin->type = 0;
+            s->data = snew_plus_get_aux(s->pktin);
+            memcpy(s->data, s->buf, s->maxlen);
+        } else if (s->in.mac && s->in.etm_mode) {
+            if (s->bufsize < 4) {
+                s->bufsize = 4;
+                s->buf = sresize(s->buf, s->bufsize, unsigned char);
+            }
+
+            /*
+             * OpenSSH encrypt-then-MAC mode: the packet length is
+             * unencrypted, unless the cipher supports length encryption.
+             */
+            crMaybeWaitUntilV(bufchain_try_fetch_consume(
+                                  s->bpp.in_raw, s->buf, 4));
+
+            /* Cipher supports length decryption, so do it */
+            if (s->in.cipher &&
+                (s->in.cipher->flags & SSH_CIPHER_SEPARATE_LENGTH)) {
+                /* Keep the packet the same though, so the MAC passes */
+                unsigned char len[4];
+                memcpy(len, s->buf, 4);
+                s->in.cipher->decrypt_length(
+                    s->in.cipher_ctx, len, 4, s->in.sequence);
+                s->len = toint(GET_32BIT(len));
+            } else {
+                s->len = toint(GET_32BIT(s->buf));
+            }
+
+            /*
+             * _Completely_ silly lengths should be stomped on before they
+             * do us any more damage.
+             */
+            if (s->len < 0 || s->len > (long)OUR_V2_PACKETLIMIT ||
+                s->len % s->cipherblk != 0) {
+                s->bpp.error = dupprintf(
+                    "Incoming packet length field was garbled");
+                crStopV;
+            }
+
+            /*
+             * So now we can work out the total packet length.
+             */
+            s->packetlen = s->len + 4;
+
+            /*
+             * Allocate the packet to return, now we know its length.
+             */
+            s->pktin = snew_plus(PktIn, OUR_V2_PACKETLIMIT + s->maclen);
+            s->pktin->qnode.prev = s->pktin->qnode.next = NULL;
+            s->pktin->refcount = 1;
+            s->pktin->type = 0;
+            s->data = snew_plus_get_aux(s->pktin);
+            memcpy(s->data, s->buf, 4);
+
+            /*
+             * Read the remainder of the packet.
+             */
+            crMaybeWaitUntilV(bufchain_try_fetch_consume(
+                                  s->bpp.in_raw, s->data + 4,
+                                  s->packetlen + s->maclen - 4));
+
+            /*
+             * Check the MAC.
+             */
+            if (s->in.mac && !s->in.mac->verify(
+                    s->in.mac_ctx, s->data, s->len + 4, s->in.sequence)) {
+                s->bpp.error = dupprintf("Incorrect MAC received on packet");
+                crStopV;
+            }
+
+            /* Decrypt everything between the length field and the MAC. */
+            if (s->in.cipher)
+                s->in.cipher->decrypt(
+                    s->in.cipher_ctx, s->data + 4, s->packetlen - 4);
+        } else {
+            if (s->bufsize < s->cipherblk) {
+                s->bufsize = s->cipherblk;
+                s->buf = sresize(s->buf, s->bufsize, unsigned char);
+            }
+
+            /*
+             * Acquire and decrypt the first block of the packet. This will
+             * contain the length and padding details.
+             */
+            crMaybeWaitUntilV(bufchain_try_fetch_consume(
+                                  s->bpp.in_raw, s->buf, s->cipherblk));
+
+            if (s->in.cipher)
+                s->in.cipher->decrypt(
+                    s->in.cipher_ctx, s->buf, s->cipherblk);
+
+            /*
+             * Now get the length figure.
+             */
+            s->len = toint(GET_32BIT(s->buf));
+
+            /*
+             * _Completely_ silly lengths should be stomped on before they
+             * do us any more damage.
+             */
+            if (s->len < 0 || s->len > (long)OUR_V2_PACKETLIMIT ||
+                (s->len + 4) % s->cipherblk != 0) {
+                s->bpp.error = dupprintf(
+                    "Incoming packet was garbled on decryption");
+                crStopV;
+            }
+
+            /*
+             * So now we can work out the total packet length.
+             */
+            s->packetlen = s->len + 4;
+
+            /*
+             * Allocate the packet to return, now we know its length.
+             */
+            s->maxlen = s->packetlen + s->maclen;
+            s->pktin = snew_plus(PktIn, s->maxlen);
+            s->pktin->qnode.prev = s->pktin->qnode.next = NULL;
+            s->pktin->refcount = 1;
+            s->pktin->type = 0;
+            s->data = snew_plus_get_aux(s->pktin);
+            memcpy(s->data, s->buf, s->cipherblk);
+
+            /*
+             * Read and decrypt the remainder of the packet.
+             */
+            crMaybeWaitUntilV(bufchain_try_fetch_consume(
+                                  s->bpp.in_raw, s->data + s->cipherblk,
+                                  s->packetlen + s->maclen - s->cipherblk));
+
+            /* Decrypt everything _except_ the MAC. */
+            if (s->in.cipher)
+                s->in.cipher->decrypt(
+                    s->in.cipher_ctx,
+                    s->data + s->cipherblk, s->packetlen - s->cipherblk);
+
+            /*
+             * Check the MAC.
+             */
+            if (s->in.mac && !s->in.mac->verify(
+                    s->in.mac_ctx, s->data, s->len + 4, s->in.sequence)) {
+                s->bpp.error = dupprintf("Incorrect MAC received on packet");
+                crStopV;
+            }
+        }
+        /* Get and sanity-check the amount of random padding. */
+        s->pad = s->data[4];
+        if (s->pad < 4 || s->len - s->pad < 1) {
+            s->bpp.error = dupprintf(
+                "Invalid padding length on received packet");
+            crStopV;
+        }
+        /*
+         * This enables us to deduce the payload length.
+         */
+        s->payload = s->len - s->pad - 1;
+
+        s->length = s->payload + 5;
+        s->pktin->encrypted_len = s->packetlen;
+
+        s->pktin->sequence = s->in.sequence++;
+
+        s->length = s->packetlen - s->pad;
+        assert(s->length >= 0);
+
+        /*
+         * Decompress packet payload.
+         */
+        {
+            unsigned char *newpayload;
+            int newlen;
+            if (s->in.comp && s->in.comp->decompress(
+                    s->in.comp_ctx, s->data + 5, s->length - 5,
+                    &newpayload, &newlen)) {
+                if (s->maxlen < newlen + 5) {
+                    PktIn *old_pktin = s->pktin;
+
+                    s->maxlen = newlen + 5;
+                    s->pktin = snew_plus(PktIn, s->maxlen);
+                    *s->pktin = *old_pktin; /* structure copy */
+                    s->data = snew_plus_get_aux(s->pktin);
+
+                    smemclr(old_pktin, s->packetlen + s->maclen);
+                    sfree(old_pktin);
+                }
+                s->length = 5 + newlen;
+                memcpy(s->data + 5, newpayload, newlen);
+                sfree(newpayload);
+            }
+        }
+
+        /*
+         * Now we can identify the semantic content of the packet,
+         * and also the initial type byte.
+         */
+        if (s->length <= 5) { /* == 5 we hope, but robustness */
+            /*
+             * RFC 4253 doesn't explicitly say that completely empty
+             * packets with no type byte are forbidden. We handle them
+             * here by giving them a type code larger than 0xFF, which
+             * will be picked up at the next layer and trigger
+             * SSH_MSG_UNIMPLEMENTED.
+             */
+            s->pktin->type = SSH_MSG_NO_TYPE_CODE;
+            s->length = 0;
+            BinarySource_INIT(s->pktin, s->data + 5, 0);
+        } else {
+            s->pktin->type = s->data[5];
+            s->length -= 6;
+            BinarySource_INIT(s->pktin, s->data + 6, s->length);
+        }
+
+        if (s->bpp.logctx) {
+            logblank_t blanks[MAX_BLANKS];
+            int nblanks = ssh2_censor_packet(
+                s->bpp.pls, s->pktin->type, FALSE,
+                make_ptrlen(s->data, s->length), blanks);
+            log_packet(s->bpp.logctx, PKT_INCOMING, s->pktin->type,
+                       ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx,
+                                     s->pktin->type),
+                       get_ptr(s->pktin), get_avail(s->pktin), nblanks, blanks,
+                       &s->pktin->sequence, 0, NULL);
+        }
+
+        pq_push(s->bpp.in_pq, s->pktin);
+
+        {
+            int type = s->pktin->type;
+            s->pktin = NULL;
+
+            if (type == SSH2_MSG_DISCONNECT)
+                s->bpp.seen_disconnect = TRUE;
+            if (type == SSH2_MSG_NEWKEYS) {
+                /*
+                 * Mild layer violation: in this situation we must
+                 * suspend processing of the input byte stream until
+                 * the transport layer has initialised the new keys by
+                 * calling ssh2_bpp_new_incoming_crypto above.
+                 */
+                s->pending_newkeys = TRUE;
+                crWaitUntilV(!s->pending_newkeys);
+            }
+        }
+    }
+    crFinishV;
+}
+
+static PktOut *ssh2_bpp_new_pktout(int pkt_type)
+{
+    PktOut *pkt = ssh_new_packet();
+    pkt->length = 5; /* space for packet length + padding length */
+    pkt->minlen = 0;
+    pkt->type = pkt_type;
+    put_byte(pkt, pkt_type);
+    pkt->prefix = pkt->length;
+    return pkt;
+}
+
+static void ssh2_bpp_format_packet_inner(struct ssh2_bpp_state *s, PktOut *pkt)
+{
+    int origlen, cipherblk, maclen, padding, unencrypted_prefix, i;
+
+    if (s->bpp.logctx) {
+        ptrlen pktdata = make_ptrlen(pkt->data + pkt->prefix,
+                                     pkt->length - pkt->prefix);
+        logblank_t blanks[MAX_BLANKS];
+        int nblanks = ssh2_censor_packet(
+            s->bpp.pls, pkt->type, TRUE, pktdata, blanks);
+        log_packet(s->bpp.logctx, PKT_OUTGOING, pkt->type,
+                   ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx,
+                                 pkt->type),
+                   pktdata.ptr, pktdata.len, nblanks, blanks, &s->out.sequence,
+                   pkt->downstream_id, pkt->additional_log_text);
+    }
+
+    cipherblk = s->out.cipher ? s->out.cipher->blksize : 8;
+    cipherblk = cipherblk < 8 ? 8 : cipherblk;  /* or 8 if blksize < 8 */
+
+    if (s->out.comp && s->out.comp_ctx) {
+        unsigned char *newpayload;
+        int minlen, newlen;
+
+        /*
+         * Compress packet payload.
+         */
+        minlen = pkt->minlen;
+        if (minlen) {
+            /*
+             * Work out how much compressed data we need (at least) to
+             * make the overall packet length come to pkt->minlen.
+             */
+            if (s->out.mac)
+                minlen -= s->out.mac->len;
+            minlen -= 8;              /* length field + min padding */
+        }
+
+        s->out.comp->compress(s->out.comp_ctx, pkt->data + 5, pkt->length - 5,
+                              &newpayload, &newlen, minlen);
+        pkt->length = 5;
+        put_data(pkt, newpayload, newlen);
+        sfree(newpayload);
+    }
+
+    /*
+     * Add padding. At least four bytes, and must also bring total
+     * length (minus MAC) up to a multiple of the block size.
+     * If pkt->forcepad is set, make sure the packet is at least that size
+     * after padding.
+     */
+    padding = 4;
+    unencrypted_prefix = (s->out.mac && s->out.etm_mode) ? 4 : 0;
+    padding +=
+        (cipherblk - (pkt->length - unencrypted_prefix + padding) % cipherblk)
+        % cipherblk;
+    assert(padding <= 255);
+    maclen = s->out.mac ? s->out.mac->len : 0;
+    origlen = pkt->length;
+    for (i = 0; i < padding; i++)
+        put_byte(pkt, random_byte());
+    pkt->data[4] = padding;
+    PUT_32BIT(pkt->data, origlen + padding - 4);
+
+    /* Encrypt length if the scheme requires it */
+    if (s->out.cipher &&
+        (s->out.cipher->flags & SSH_CIPHER_SEPARATE_LENGTH)) {
+        s->out.cipher->encrypt_length(s->out.cipher_ctx, pkt->data, 4,
+                                      s->out.sequence);
+    }
+
+    put_padding(pkt, maclen, 0);
+
+    if (s->out.mac && s->out.etm_mode) {
+        /*
+         * OpenSSH-defined encrypt-then-MAC protocol.
+         */
+        if (s->out.cipher)
+            s->out.cipher->encrypt(s->out.cipher_ctx,
+                                   pkt->data + 4, origlen + padding - 4);
+        s->out.mac->generate(s->out.mac_ctx, pkt->data, origlen + padding,
+                             s->out.sequence);
+    } else {
+        /*
+         * SSH-2 standard protocol.
+         */
+        if (s->out.mac)
+            s->out.mac->generate(
+                s->out.mac_ctx, pkt->data, origlen + padding,
+                s->out.sequence);
+        if (s->out.cipher)
+            s->out.cipher->encrypt(s->out.cipher_ctx,
+                                   pkt->data, origlen + padding);
+    }
+
+    s->out.sequence++;       /* whether or not we MACed */
+    pkt->encrypted_len = origlen + padding;
+
+}
+
+static void ssh2_bpp_format_packet(BinaryPacketProtocol *bpp, PktOut *pkt)
+{
+    struct ssh2_bpp_state *s = FROMFIELD(bpp, struct ssh2_bpp_state, bpp);
+
+    if (pkt->minlen > 0 && !(s->out.comp && s->out.comp_ctx)) {
+        /*
+         * If we've been told to pad the packet out to a given minimum
+         * length, but we're not compressing (and hence can't get the
+         * compression to do the padding by pointlessly opening and
+         * closing zlib blocks), then our other strategy is to precede
+         * this message with an SSH_MSG_IGNORE that makes it up to the
+         * right length.
+         *
+         * A third option in principle, and the most obviously
+         * sensible, would be to set the explicit padding field in the
+         * packet to more than its minimum value. Sadly, that turns
+         * out to break some servers (our institutional memory thinks
+         * Cisco in particular) and so we abandoned that idea shortly
+         * after trying it.
+         */
+
+        /*
+         * Calculate the length we expect the real packet to have.
+         */
+        int block, length;
+        PktOut *ignore_pkt;
+
+        block = s->out.cipher ? s->out.cipher->blksize : 0;
+        if (block < 8)
+            block = 8;
+        length = pkt->length;
+        length += 4;       /* minimum 4 byte padding */
+        length += block-1;
+        length -= (length % block);
+        if (s->out.mac)
+            length += s->out.mac->len;
+
+        if (length < pkt->minlen) {
+            /*
+             * We need an ignore message. Calculate its length.
+             */
+            length = pkt->minlen - length;
+
+            /*
+             * And work backwards from that to the length of the
+             * contained string.
+             */
+            if (s->out.mac)
+                length -= s->out.mac->len;
+            length -= 8;               /* length field + min padding */
+            length -= 5;               /* type code + string length prefix */
+
+            if (length < 0)
+                length = 0;
+
+            ignore_pkt = ssh2_bpp_new_pktout(SSH2_MSG_IGNORE);
+            put_uint32(ignore_pkt, length);
+            while (length-- > 0)
+                put_byte(ignore_pkt, random_byte());
+            ssh2_bpp_format_packet_inner(s, ignore_pkt);
+            bufchain_add(s->bpp.out_raw, ignore_pkt->data, ignore_pkt->length);
+            ssh_free_pktout(ignore_pkt);
+        }
+    }
+
+    ssh2_bpp_format_packet_inner(s, pkt);
+    bufchain_add(s->bpp.out_raw, pkt->data, pkt->length);
+
+    ssh_free_pktout(pkt);
+}

+ 107 - 0
source/putty/ssh2censor.c

@@ -0,0 +1,107 @@
+/*
+ * Packet-censoring code for SSH-2, used to identify sensitive fields
+ * like passwords so that the logging system can avoid writing them
+ * into log files.
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+
+int ssh2_censor_packet(
+    const PacketLogSettings *pls, int type, int sender_is_client,
+    ptrlen pkt, logblank_t *blanks)
+{
+    int nblanks = 0;
+    ptrlen str;
+    BinarySource src[1];
+
+    BinarySource_BARE_INIT(src, pkt.ptr, pkt.len);
+
+    if (pls->omit_data &&
+        (type == SSH2_MSG_CHANNEL_DATA ||
+         type == SSH2_MSG_CHANNEL_EXTENDED_DATA)) {
+        /* "Session data" packets - omit the data string. */
+        get_uint32(src);              /* skip channel id */
+        if (type == SSH2_MSG_CHANNEL_EXTENDED_DATA)
+            get_uint32(src);          /* skip extended data type */
+        str = get_string(src);
+        if (!get_err(src)) {
+            assert(nblanks < MAX_BLANKS);
+            blanks[nblanks].offset = src->pos - str.len;
+            blanks[nblanks].type = PKTLOG_OMIT;
+            blanks[nblanks].len = str.len;
+            nblanks++;
+        }
+    }
+
+    if (sender_is_client && pls->omit_passwords) {
+        if (type == SSH2_MSG_USERAUTH_REQUEST) {
+            /* If this is a password packet, blank the password(s). */
+            get_string(src);              /* username */
+            get_string(src);              /* service name */
+            str = get_string(src);        /* auth method */
+            if (ptrlen_eq_string(str, "password")) {
+                get_bool(src);
+                /* Blank the password field. */
+                str = get_string(src);
+                if (!get_err(src)) {
+                    assert(nblanks < MAX_BLANKS);
+                    blanks[nblanks].offset = src->pos - str.len;
+                    blanks[nblanks].type = PKTLOG_BLANK;
+                    blanks[nblanks].len = str.len;
+                    nblanks++;
+                    /* If there's another password field beyond it
+                     * (change of password), blank that too. */
+                    str = get_string(src);
+                    if (!get_err(src))
+                        blanks[nblanks-1].len =
+                            src->pos - blanks[nblanks].offset;
+                }
+            }
+        } else if (pls->actx == SSH2_PKTCTX_KBDINTER &&
+                   type == SSH2_MSG_USERAUTH_INFO_RESPONSE) {
+            /* If this is a keyboard-interactive response packet,
+             * blank the responses. */
+            get_uint32(src);
+            assert(nblanks < MAX_BLANKS);
+            blanks[nblanks].offset = src->pos;
+            blanks[nblanks].type = PKTLOG_BLANK;
+            do {
+                str = get_string(src);
+            } while (!get_err(src));
+            blanks[nblanks].len = src->pos - blanks[nblanks].offset;
+            nblanks++;
+        } else if (type == SSH2_MSG_CHANNEL_REQUEST) {
+            /*
+             * If this is an X forwarding request packet, blank the
+             * fake auth data.
+             *
+             * Note that while we blank the X authentication data
+             * here, we don't take any special action to blank the
+             * start of an X11 channel, so using MIT-MAGIC-COOKIE-1
+             * and actually opening an X connection without having
+             * session blanking enabled is likely to leak your cookie
+             * into the log.
+             */
+            get_uint32(src);
+            str = get_string(src);
+            if (ptrlen_eq_string(str, "x11-req")) {
+                get_bool(src);
+                get_bool(src);
+                get_string(src);
+                str = get_string(src);
+                if (!get_err(src)) {
+                    assert(nblanks < MAX_BLANKS);
+                    blanks[nblanks].offset = src->pos - str.len;
+                    blanks[nblanks].type = PKTLOG_BLANK;
+                    blanks[nblanks].len = str.len;
+                    nblanks++;
+                }
+            }
+        }
+    }
+
+    return nblanks;
+}

+ 53 - 0
source/putty/sshbpp.h

@@ -0,0 +1,53 @@
+/*
+ * Abstraction of the binary packet protocols used in SSH.
+ */
+
+#ifndef PUTTY_SSHBPP_H
+#define PUTTY_SSHBPP_H
+
+typedef struct BinaryPacketProtocol BinaryPacketProtocol;
+
+struct BinaryPacketProtocolVtable {
+    void (*free)(BinaryPacketProtocol *); 
+    void (*handle_input)(BinaryPacketProtocol *);
+    PktOut *(*new_pktout)(int type);
+    void (*format_packet)(BinaryPacketProtocol *, PktOut *);
+};
+
+struct BinaryPacketProtocol {
+    const struct BinaryPacketProtocolVtable *vt;
+    bufchain *in_raw, *out_raw;
+    PacketQueue *in_pq;
+    PacketLogSettings *pls;
+    void *logctx;
+
+    int seen_disconnect;
+    char *error;
+};
+
+#define ssh_bpp_free(bpp) ((bpp)->vt->free(bpp))
+#define ssh_bpp_handle_input(bpp) ((bpp)->vt->handle_input(bpp))
+#define ssh_bpp_new_pktout(bpp, type) ((bpp)->vt->new_pktout(type))
+#define ssh_bpp_format_packet(bpp, pkt) ((bpp)->vt->format_packet(bpp, pkt))
+
+BinaryPacketProtocol *ssh1_bpp_new(void);
+void ssh1_bpp_new_cipher(BinaryPacketProtocol *bpp,
+                         const struct ssh_cipher *cipher,
+                         const void *session_key);
+void ssh1_bpp_start_compression(BinaryPacketProtocol *bpp);
+
+BinaryPacketProtocol *ssh2_bpp_new(void);
+void ssh2_bpp_new_outgoing_crypto(
+    BinaryPacketProtocol *bpp,
+    const struct ssh2_cipher *cipher, const void *ckey, const void *iv,
+    const struct ssh_mac *mac, int etm_mode, const void *mac_key,
+    const struct ssh_compress *compression);
+void ssh2_bpp_new_incoming_crypto(
+    BinaryPacketProtocol *bpp,
+    const struct ssh2_cipher *cipher, const void *ckey, const void *iv,
+    const struct ssh_mac *mac, int etm_mode, const void *mac_key,
+    const struct ssh_compress *compression);
+
+BinaryPacketProtocol *ssh2_bare_bpp_new(void);
+
+#endif /* PUTTY_SSHBPP_H */

+ 54 - 0
source/putty/sshcr.h

@@ -0,0 +1,54 @@
+/*
+ * Coroutine mechanics used in PuTTY's SSH code.
+ */
+
+#ifndef PUTTY_SSHCR_H
+#define PUTTY_SSHCR_H
+
+/*
+ * If these macros look impenetrable to you, you might find it helpful
+ * to read
+ * 
+ *   https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html
+ * 
+ * which explains the theory behind these macros.
+ * 
+ * In particular, if you are getting `case expression not constant'
+ * errors when building with MS Visual Studio, this is because MS's
+ * Edit and Continue debugging feature causes their compiler to
+ * violate ANSI C. To disable Edit and Continue debugging:
+ * 
+ *  - right-click ssh.c in the FileView
+ *  - click Settings
+ *  - select the C/C++ tab and the General category
+ *  - under `Debug info:', select anything _other_ than `Program
+ *    Database for Edit and Continue'.
+ */
+
+#define crBegin(v)      { int *crLine = &v; switch(v) { case 0:;
+#define crBeginState    crBegin(s->crLine)
+#define crStateP(t, v)                          \
+    struct t *s;                                \
+    if (!(v)) { s = (v) = snew(struct t); s->crLine = 0; }      \
+    s = (v);
+#define crState(t)      crStateP(t, ssh->t)
+#define crFinish(z)     } *crLine = 0; return (z); }
+#define crFinishV       } *crLine = 0; return; }
+#define crFinishFree(z) } sfree(s); return (z); }
+#define crFinishFreeV   } sfree(s); return; }
+#define crReturn(z)     \
+        do {\
+            *crLine =__LINE__; return (z); case __LINE__:;\
+        } while (0)
+#define crReturnV       \
+        do {\
+            *crLine=__LINE__; return; case __LINE__:;\
+        } while (0)
+#define crStop(z)       do{ *crLine = 0; return (z); }while(0)
+#define crStopV         do{ *crLine = 0; return; }while(0)
+#define crWaitUntil(c)  do { crReturn(0); } while (!(c))
+#define crWaitUntilV(c) do { crReturnV; } while (!(c))
+#define crMaybeWaitUntil(c) do { while (!(c)) crReturn(0); } while (0)
+#define crMaybeWaitUntilV(c) do { while (!(c)) crReturnV; } while (0)
+
+#endif /* PUTTY_SSHCR_H */