Not every "session" channel in SSH allocates a pty at all, of course,
and so I'll need a way to run a subprocess without doing so. The
simplest approach seems to be to expand uxpty's remit so that the pty
is optional: now it can open either a pty or a set of pipes for
stdin/out/err, according to an option provided to pty_backend_create.
(It amuses me that without this option I'd have an SSH server which is
incapable of _not_ honouring the "pty-req" channel request. That's
normally the easy part!)
This breaks the previous one-to-one coupling between pty backend
instances and file descriptors passed to uxsel, which I was using to
look up the Pty structure in a tree234 indexed by fd when an uxsel
notification came back. So now each Pty structure contains a
collection of subobjects of a new type PtyFd, and _those_ are what's
stored in the fd-indexed tree.
Another awkward part is that uxsel_set is not incremental: the rwx
flags you pass to it completely supersede the previous set for that
file descriptor, so I had to set up the logic that decides whether
we're trying to read or write each fd in a way that can cope equally
well with the fd aliasing another one (if it's the pty master) or not
(if there are three completely separate pipes).
The SS_SIGFOO family are implemented by sending a signal directly to
the pid of the immediate child process.
I had had the vague idea that it might be more desirable to send the
specified signal to the foreground process group in the tty. That way,
you'd be able to SIGINT (say) the foreground job in a shell session,
and return to the shell _prompt_ without terminating the whole
session, and you could do this in an emergency even if the job was a
full-screen application which had configured termios so that no
keystroke generated SIGINT.
But as far as I can see there's no actual way to do that. I wasn't
able to find any ioctl or termios call to send a signal to a pty's
foreground pgrp, and you can't even do it manually via kill(2) because
first you'd have to find out what the pgrp id _is_, and according to
the man pages, you can only call tcgetpgrp on the slave end of the pty
and even then only if it's your controlling terminal.
So SS_SIGFOO goes to the child process, because that's the only place
I can find that I _can_ send it to sensibly.
SS_BRK translates to tcsendbreak, of course (though I haven't actually
seen any effect of calling this on a pty master, not even if I set
PARMRK on the slave end which by my understanding _ought_ to show me
when break events occur).
This will be applied to the pty's termios settings at creation time,
superseding the default settings uxpty has always used. It works by
including the new sshttymodes.h with TTYMODES_LOCAL_ONLY defined, so
that modes not supported by a particular Unix system are automatically
quietly ignored.
Of course, a struct ssh_ttymodes always has the option of representing
"please make no change to the defaults", and of course, that's
precisely what is done by the one that pty_init constructs for clients
that aren't calling pty_backend_create directly.
The function that does the main pty setup is now called
pty_backend_create(), and has an API better suited to uxpty in
particular than the standard backend_init() virtual constructor. It
leaves off a load of standard parameters to backend_init() which
aren't really relevant to this backend, and it adds the 'argv'
parameter to pass in a split-up command line, which is unique to it.
The old creation function still exists, as a tiny wrapper that calls
the new pty_backend_create. And that version still gets the argv
parameter from the process-global variable pty_argv[], so the call
sites in pterm haven't had to change for this.
This will make it possible to instantiate a pty backend directly from
the SSH server code, without having to do anything really excessively
cumbersome to pass in a subcommand in the form of pre-split argv. (And
I'll add a few more specialist parameters to the new function shortly.)
There was a bit of a race condition depending on whether uxpty spotted
the EOF/EIO on the process's output first, or the SIGCHLD for its
actual termination: if the former came first, it would never bother to
reap the exit code at all.
It still doesn't bother if it's closing the session immediately and
the process genuinely _hasn't_ died (say, if it detaches itself
completely from the controlling tty to run in the background like a
weird parody of an old DOS TSR). But now when we see EOF, we make an
immediate (but nonblocking) attempt to wait for the child process, in
case its exit code was already available and we just hadn't noticed
yet.
The uxpty backend is going to be reused to implement the "session"
channel type in the upcoming SSH server implementation, which puts
quite a few new requirements on it. The first of them is that when we
get EOF from the subprocess's output channel (or rather, EIO from the
pty), we should actually notify the Seat of this.
In principle we should have been doing this all along, I'm pretty
sure. It hasn't happened to matter until now because the receiving
Seats haven't done much with that notification. But it will matter
when that's what controls the sending of SSH_MSG_CHANNEL_EOF.
The word 'PuTTY' in the outgoing SSH version string has always
represented the name of the *SSH implementation* as opposed to the
name of the specific program containing it (for example, PSCP and
PSFTP don't announce themselves with a different banner). But I think
that a change from client to server merits a change in that
implementation name, so I'm removing the prefix "PuTTY" from the
constant string sshver[], and moving it to a parameter passed in
separately to ssh_verstring_new, so that the upcoming server can pass
in a different one.
This new function contains the core setup for a PortForwarding
structure, and should be reusable for any kind of forwarding that will
simply be passing data between a local socket and an SSH channel
without any tricky modifications. On the server side, X11 and agent
forwarding both work exactly like this, so they will find this
refactored function useful during setup.
The contents of the function was originally part of pfl_accepting,
which now does all that by calling the new function. pfl_accepting is
not _quite_ doing a simple unmodified forwarding, because it might
have to prefix it with a SOCKS exchange; in that situation it rewrites
a few fields of the PortForwarding to some less generic values once
portfwd_raw_new() has returned.
I've written the decryption side of the PKCS#1 encryption used in
SSH-1, and also the RSAES-OAEP system used by SSH-2 RSA kex. Also, the
RSA kex structures now each come with an 'extra' pointer giving the
minimum key length.
Lots of user-facing messages that claim that the 'server' just did
something or other unexpected will now need to be issued _by_ the
server, when the client does the same unexpected thing. So I've
reworded them all to talk about the 'remote side' instead of the
'server', and the SSH-2 key setup messages talk about initialising
inbound and outbound crypto primitives rather than client->server and
server->client.
ssh2connection.c now knows how to unmarshal the message formats for
all the channel requests we'll need to handle when we're the server
and a client sends them. Each one is translated into a call to a new
method in the Channel vtable, which is implemented by a trivial
'always fail' routine in every channel type we know about so far.
The vtable method underneath sshfwd_write now takes an is_stderr
parameter, and in SSH-2, this is implemented by having separate stdout
and stderr bufchains in each outgoing channel, and counting the size
of both for the purposes of measuring backlog and so forth.
To avoid making _most_ call sites more verbose, the usual macro
wrapper hasn't changed its API; it just sets is_stderr=FALSE. To use
the new feature, there's an sshfwd_write_ext macro that exposes the
extra parameter.
This will be used for the server side of X forwarding. It wraps up the
mechanics of listening on the right TCP port and (if possible) the
associated AF_UNIX socket, and also creates an appropriate X authority
file containing authorisation data provided by its caller.
Like the new platform_create_agent_socket, this function spawns a
watchdog subprocess to clean up the mess afterwards, in the hope of at
least _most_ of the time not leaving old sockets and authority files
lying around /tmp,
The code in Pageant that sets up the Unix socket and its containing
directory now lives in a separate file, uxagentsock.c, where it will
also be callable from the upcoming new SSH server when it wants to
create a similar socket for agent forwarding.
While I'm at it, I've also added a feature to create a watchdog
subprocess that will try to clean up the socket and directory once
Pageant itself terminates, in the hope of leaving less cruft lying
around /tmp.
This is a major code reorganisation in preparation for making this
code base into one that can build an SSH server as well as a client.
(Mostly for purposes of using the server as a regression test suite
for the client, though I have some other possible uses in mind too.
However, it's currently no part of my plan to harden the server to the
point where it can sensibly be deployed in a hostile environment.)
In this preparatory commit, I've broken up the SSH-2 transport and
connection layers, and the SSH-1 connection layer, into multiple
source files, with each layer having its own header file containing
the shared type definitions. In each case, the new source file
contains code that's specific to the client side of the protocol, so
that a new file can be swapped in in its place when building the
server.
Mostly this is just a straightforward moving of code without changing
it very much, but there are a couple of actual changes in the process:
The parsing of SSH-2 global-request and channel open-messages is now
done by a new pair of functions in the client module. For channel
opens, I've invented a new union data type to be the return value from
that function, representing either failure (plus error message),
success (plus Channel instance to manage the new channel), or an
instruction to hand the channel over to a sharing downstream (plus a
pointer to the downstream in question).
Also, the tree234 of remote port forwardings in ssh2connection is now
initialised on first use by the client-specific code, so that's where
its compare function lives. The shared ssh2connection_free() still
takes responsibility for freeing it, but now has to check if it's
non-null first.
The outer shell of the ssh2_lportfwd_open method, for making a
local-to-remote port forwarding, is still centralised in
ssh2connection.c, but the part of it that actually constructs the
outgoing channel-open message has moved into the client code, because
that will have to change depending on whether the channel-open has to
have type direct-tcpip or forwarded-tcpip.
In the SSH-1 connection layer, half the filter_queue method has moved
out into the new client-specific code, but not all of it -
bidirectional channel maintenance messages are still handled
centrally. One exception is SSH_MSG_PORT_OPEN, which can be sent in
both directions, but with subtly different semantics - from server to
client, it's referring to a previously established remote forwarding
(and must be rejected if there isn't one that matches it), but from
client to server it's just a "direct-tcpip" request with no prior
context. So that one is in the client-specific module, and when I add
the server code it will have its own different handler.
Some kinds of channel, even after they've sent EOF in both directions,
still have something to do before they initiate the CLOSE mechanism
and wind up the channel completely. For example, a session channel
with a subprocess running inside it will want to be sure to send the
"exit-status" or "exit-signal" notification, even if that happens
after bidirectional EOF of the data channels.
Previously, the SSH-2 connection layer had the standard policy that
once EOF had been both sent and received, it would start the final
close procedure. There's a method chan_want_close() by which a Channel
could vary this policy in one direction, by indicating that it wanted
the close procedure to commence after EOF was sent in only one
direction. Its parameters are a pair of booleans saying whether EOF
has been sent, and whether it's been received.
Now chan_want_close can vary the policy in the other direction as
well: if it returns FALSE even when _both_ parameters are true, the
connection layer will honour that, and not send CHANNEL_CLOSE. If it
does that, the Channel is responsible for indicating when it _does_
want close later, by calling sshfwd_initiate_close.
Previously, it returned a human-readable string suitable for log
files, which tried to say something useful about the remote end of a
socket. Now it returns a whole SocketPeerInfo structure, of which that
human-friendly log string is just one field, but also some of the same
information - remote IP address and port, in particular - is provided
in machine-readable form where it's available.
Turns out that initiation of a CHANNEL_CLOSE message before both sides
have sent EOF is not only for _unclean_ closures or emergencies; it's
actually a perfectly normal thing that some channel types want to do.
(For example, a channel with a pty at the server end of it has no real
concept of sending EOF independently in both directions: when the pty
master sends EIO, the pty is no longer functioning, and you can no
longer send to it any more than you can receive.)
That's more directly useful in uxpty.c (which is currently the only
actual client of the function), and also matches the data that SSH
clients send in "pty-req". Also, it makes that method behave more like
the GUI query function get_window_pixels used by terminal.c (with the
sole exception that unlike g_w_p it's allowed to return failure), so
it becomes even more trivial to implement in the GUI front ends.
The new FdSocket just takes an arbitrary pair of file descriptors to
read and write, optionally with an extra input fd providing the
standard error output from a command. uxproxy.c now just does the
forking and pipe setup, and once it's got all its fds, it hands off to
FdSocket to actually do the reading and writing.
This is very like the reorganisation I did on the Windows side in
commit 98a6a3553 (back in 2013, in preparation for named-pipe sockets
and connection sharing). The idea is that it should enable me to make
a thing that the PuTTY code base sees as a Socket, but which actually
connects to the standard I/O handles of the process it lives in.
The function takes the two KEXINIT packets in their string form,
together with a list of mappings from names to known algorithm
implementations, and returns the selected one of each kind, along with
all the other necessary auxiliary stuff.
This has nice effects on code tidiness (quite a few variables now
become local to the new function instead of living permanently in the
transport layer), but mostly, the idea is to add flexibility by
introducing a convenient place to change the policy for how we write
the negotiation lists in our KEXINIT.
In SSH-1, the channel system isn't rich enough to represent the
complicated main shell session, so it's all done with a separate set
of custom message types. But PuTTY now abstracts away that difference,
by representing both as different implementations of the SshChannel
class: ssh1channel is for things that the protocol thinks are 'really'
channels, and ssh1mainchan is for the shell session. All the same
methods are implemented, but generate different wire messages.
This means that the logic to decide _when_ to enable X forwarding,
agent forwarding etc is all centralised into mainchan.c, where it
doesn't have to be repeated for both protocol versions.
It also simplifies the final loop in the connection protocol, which no
longer has to contain the code to move data from the user input
bufchain to the channel's output; that's now done by the mainchan
write method, the same as it is in SSH-2 where mainchan is just like
other channels.
This is in much the same style as the ttymodes revamp, using a header
file which can be included in different ways to either iterate over
_all_ the signals in the known list or just the ones for which a
definition exists on the target OS.
So this doesn't actually _remove_ the horrid pile of ifdefs in
mainchan_rcvd_exit_signal, but at least it puts it somewhere less
intrusive and more reusable.
I've introduced a new POD struct type 'ssh_ttymodes' which stores an
encoding of everything you can specify in the "pty-req" packet or the
SSH-1 equivalent. This allows me to split up
write_ttymodes_to_packet_from_conf() into two separate functions, one
to parse all the ttymode data out of a Conf (and a Seat for fallback)
and return one of those structures, and the other to write it into an
SSH packet.
While I'm at it, I've moved the special case of terminal speeds into
the same mechanism, simplifying the call sites in both versions of the
SSH protocol.
The new master definition of all terminal modes lives in a header
file, with an ifdef around each item, so that later on I'll be able to
include it in a context that only enumerates the modes supported by
the particular target Unix platform.
This gets another big pile of logic out of ssh2connection and puts it
somewhere more central. Now the only thing left in ssh2connection is
the formatting and parsing of the various channel requests; the logic
deciding which ones to issue and what to do about them is devolved to
the Channel implementation, as it properly should be.
In my future plans, some SshChannels are going to need to be able to
ask favours from the connection layer as a whole. And an SshChannel is
inextricably tied to an instance of the connection layer, so there's
no real reason _not_ to make the pointer generally available.
Each of the new subroutines corresponds to one of the channel types
for which we know how to parse a CHANNEL_OPEN, and has a collection of
parameters corresponding to the fields of that message structure.
ssh2_connection_filter_queue now confines itself to parsing the
message, calling one of those functions, and constructing an
appropriate reply message if any.
Instead of the central code in ssh2_connection_filter_queue doing both
the job of parsing the channel request and deciding whether it's
acceptable, each Channel vtable now has a method for every channel
request type we recognise.
All the lowest-level helper functions in settings.c that read a single
setting from a settings_r are now prepared to tolerate being passed a
null settings_r pointer, which will be treated as if reading from it
always failed. This means you can call load_open_settings(NULL, conf)
to populate a Conf with all of the _built-in_ internal defaults,
without ever loading from the saved-session storage at all (not even
Default Settings).
(Doing this will still call the platform_default_foo function family,
if nothing else because Filenames and FontSpecs can't be constructed
in any platform-independent way at all.)
A function to compare two strings _both_ in ptrlen form (I've had
ptrlen_eq_string for ages, but for some reason, never quite needed
ptrlen_eq_ptrlen). A function to ask whether one ptrlen starts with
another (and, optionally, return a ptrlen giving the remaining part of
the longer string). And the va_list version of logeventf, which I
really ought to have written in the first place by sheer habit, even
if it was only needed by logeventf itself.
The SSH-1 SshChannel vtable didn't bother to provide the
window_override_removed method, because I wrongly remembered that it
was only called when connection sharing. In fact, it's _called_ in any
X forwarding, but it only has to _do_ anything when connection
sharing: SSH-1 has to provide an empty implementation to avoid
segfaulting by calling a null function pointer.
One to make one from a NUL-terminated string, and another to make one
from a strbuf. I've switched over all the obvious cases where I should
have been using these functions.
The SSH-1 spec says that it's legitimate to write an mp-int in which
the prefixed uint16 bit count is greater than the minimum number of
bits required to represent the number. I was enforcing that they had
to be actually equal, on pain of a BinarySource decoding error.
When logging an SSH_MSG_DISCONNECT, the log message has newlines in,
because it's also displayed in the GUI dialog box or on Plink's
standard error, where that makes some sense. But in the Event Log, all
messages should be one-liners: anything else makes the GUI list boxes
go weird, and also breaks convenient parsability of packet lot files.
So we turn newlines into spaces for Event Log purposes, which is
conveniently easy now that Event Log entries always go through
logging.c first.
Somehow I managed to leave that line out in both SSH-1 and SSH-2's
functions for handling DISCONNECT, IGNORE and DEBUG, and in both
cases, only for DISCONNECT. Oops.
In commit ad0c502ce I forgot to arrange for ssh_connect_ppl to fill in
ppl->logctx, and without it, logevent() was cheerfully throwing away
all those log messages.
In the recent refactoring, when I rewrote the loop in the SSH-2
connection layer startup which tries the primary and then the fallback
command, I failed to reproduce a subtlety of the previous code, namely
that if CONF_remote_cmd2 holds the empty string, we don't even look
for CONF_ssh_subsys2. This is because no application other than pscp
will have set the latter, and looking it up when it's absent triggers
an assertion failure in conf.c.
This is a new vtable-based abstraction which is passed to a backend in
place of Frontend, and it implements only the subset of the Frontend
functions needed by a backend. (Many other Frontend functions still
exist, notably the wide range of things called by terminal.c providing
platform-independent operations on the GUI terminal window.)
The purpose of making it a vtable is that this opens up the
possibility of creating a backend as an internal implementation detail
of some other activity, by providing just that one backend with a
custom Seat that implements the methods differently.
For example, this refactoring should make it feasible to directly
implement an SSH proxy type, aka the 'jump host' feature supported by
OpenSSH, aka 'open a secondary SSH session in MAINCHAN_DIRECT_TCP
mode, and then expose the main channel of that as the Socket for the
primary connection'. (Which of course you can already do by spawning
'plink -nc' as a separate proxy process, but this would permit it in
the _same_ process without anything getting confused.)
I've centralised a full set of stub methods in misc.c for the new
abstraction, which allows me to get rid of several annoying stubs in
the previous code. Also, while I'm here, I've moved a lot of
duplicated modalfatalbox() type functions from application main
program files into wincons.c / uxcons.c, which I think saves
duplication overall. (A minor visible effect is that the prefixes on
those console-based fatal error messages will now be more consistent
between applications.)
This was used by ldisc to communicate back to the front end that a key
had been pressed (or rather, that a keypress had caused a nonzero
amount of session input data). Its only nontrivial implementation was
in gtkwin.c, which used that notification to implement the Unix GUI's
"close window on keypress, if the session was already over" policy.
(Which in turn is Unix-specific, because the rationale is that
sometimes X servers don't have a functioning window manager, so it's
useful to have a way of telling any application to close without using
WM-provided facilities like a close button.)
But gtkwin.c doesn't need to be told by the ldisc that a keypress
happened - it's the one _sending_ those keypresses to ldisc in the
first place! So I've thrown away the three stub implementations of
frontend_keypress, removed the call to it in ldisc.c, and replaced it
with calls in gtkwin.c at all the points during keypress handling
that call ldisc_send.
A visible effect is that pterm's close-on-keypress behaviour will now
only trigger on an actual (input-generating) _keypress_, and not on
other input generation such as a paste action. I think that's an
improvement.
One quite recent - an unused variable in the Windows code that was
obsoleted by commit cea1329b9 last month - and one not recent at all,
namely the obsolete declaration of begin_session() in putty.h that
hasn't existed since commit 7a79df8fe replaced it with the ldisc
system in *2001*!
LogContext is now the owner of the logevent() function that back ends
and so forth are constantly calling. Previously, logevent was owned by
the Frontend, which would store the message into its list for the GUI
Event Log dialog (or print it to standard error, or whatever) and then
pass it _back_ to LogContext to write to the currently open log file.
Now it's the other way round: LogContext gets the message from the
back end first, writes it to its log file if it feels so inclined, and
communicates it back to the front end.
This means that lots of parts of the back end system no longer need to
have a pointer to a full-on Frontend; the only thing they needed it
for was logging, so now they just have a LogContext (which many of
them had to have anyway, e.g. for logging SSH packets or session
traffic).
LogContext itself also doesn't get a full Frontend pointer any more:
it now talks back to the front end via a little vtable of its own
called LogPolicy, which contains the method that passes Event Log
entries through, the old askappend() function that decides whether to
truncate a pre-existing log file, and an emergency function for
printing an especially prominent message if the log file can't be
created. One minor nice effect of this is that console and GUI apps
can implement that last function subtly differently, so that Unix
console apps can write it with a plain \n instead of the \r\n
(harmless but inelegant) that the old centralised implementation
generated.
One other consequence of this is that the LogContext has to be
provided to backend_init() so that it's available to backends from the
instant of creation, rather than being provided via a separate API
call a couple of function calls later, because backends have typically
started doing things that need logging (like making network
connections) before the call to backend_provide_logctx. Fortunately,
there's no case in the whole code base where we don't already have
logctx by the time we make a backend (so I don't actually remember why
I ever delayed providing one). So that shortens the backend API by one
function, which is always nice.
While I'm tidying up, I've also moved the printf-style logeventf() and
the handy logevent_and_free() into logging.c, instead of having copies
of them scattered around other places. This has also let me remove
some stub functions from a couple of outlying applications like
Pageant. Finally, I've removed the pointless "_tag" at the end of
LogContext's official struct name.
Not that we ever actually _support_ trying to authenticate for any SSH
subprotocol other than "ssh-connection", or any plans to add such
support. But it's inelegant to hardcode it at all when we have it
right there in the successor layer's vtable.
s->done_service_req was set but never read; it's not needed at all in
the current code structure, where the service request has already
happened in an entirely different source file and userauth never has
to track it at all.
If the file is empty, or otherwise fails to start with a recognised
'PuTTY-User-Key-File' header line, we forgot to fill in the error
message before returning failure, leading to a null pointer
dereference.
Commit 733fcca2c introduced named types settings_r and settings_w and
made the per-platform storage abstraction use those in place of 'void
*'. But a lot of the intermediate helper functions in the centralised
settings.c, like gpps() and wmap(), were still taking 'void *'. It
still worked, because of C's implicit casting, but it was avoiding the
type-checking advantages from making that change in the first place.