1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 17:38:00 +00:00
putty-source/ssh1connection-server.c
Simon Tatham 1d323d5c80 Add an actual SSH server program.
This server is NOT SECURE! If anyone is reading this commit message,
DO NOT DEPLOY IT IN A HOSTILE-FACING ENVIRONMENT! Its purpose is to
speak the server end of everything PuTTY speaks on the client side, so
that I can test that I haven't broken PuTTY when I reorganise its
code, even things like RSA key exchange or chained auth methods which
it's hard to find a server that speaks at all.

(For this reason, it's declared with [UT] in the Recipe file, so that
it falls into the same category as programs like testbn, which won't
be installed by 'make install'.)

Working title is 'Uppity', partly for 'Universal PuTTY Protocol
Interaction Test Yoke', but mostly because it looks quite like the
word 'PuTTY' with part of it reversed. (Apparently 'test yoke' is a
very rarely used term meaning something not altogether unlike 'test
harness', which is a bit of a stretch, but it'll do.)

It doesn't actually _support_ everything I want yet. At the moment,
it's a proof of concept only. But it has most of the machinery
present, and the parts it's missing - such as chained auth methods -
should be easy enough to add because I've built in the required
flexibility, in the form of an AuthPolicy object which can request
them if it wants to. However, the current AuthPolicy object is
entirely trivial, and will let in any user with the password "weasel".

(Another way in which this is not a production-ready server is that it
also has no interaction with the OS's authentication system. In
particular, it will not only let in any user with the same password,
but it won't even change uid - it will open shells and forwardings
under whatever user id you started it up as.)

Currently, the program can only speak the SSH protocol on its standard
I/O channels (using the new FdSocket facility), so if you want it to
listen on a network port, you'll have to run it from some kind of
separate listening program similar to inetd. For my own tests, I'm not
even doing that: I'm just having PuTTY spawn it as a local proxy
process, which also conveniently eliminates the risk of anyone hostile
connecting to it.

The bulk of the actual code reorganisation is already done by previous
commits, so this change is _mostly_ just dropping in a new set of
server-specific source files alongside the client-specific ones I
created recently. The remaining changes in the shared SSH code are
numerous, but all minor:

 - a few extra parameters to BPP and PPL constructors (e.g. 'are you
   in server mode?'), and pass both sets of SSH-1 protocol flags from
   the login to the connection layer
 - in server mode, unconditionally send our version string _before_
   waiting for the remote one
 - a new hook in the SSH-1 BPP to handle enabling compression in
   server mode, where the message exchange works the other way round
 - new code in the SSH-2 BPP to do _deferred_ compression the other
   way round (the non-deferred version is still nicely symmetric)
 - in the SSH-2 transport layer, some adjustments to do key derivation
   either way round (swapping round the identifying letters in the
   various hash preimages, and making sure to list the KEXINITs in the
   right order)
 - also in the SSH-2 transport layer, an if statement that controls
   whether we send SERVICE_REQUEST and wait for SERVICE_ACCEPT, or
   vice versa
 - new ConnectionLayer methods for opening outgoing channels for X and
   agent forwardings
 - new functions in portfwd.c to establish listening sockets suitable
   for remote-to-local port forwarding (i.e. not under the direction
   of a Conf the way it's done on the client side).
2018-10-21 10:02:10 +01:00

356 lines
11 KiB
C

/*
* Server-specific parts of the SSH-1 connection layer.
*/
#include <assert.h>
#include "putty.h"
#include "ssh.h"
#include "sshbpp.h"
#include "sshppl.h"
#include "sshchan.h"
#include "sshcr.h"
#include "ssh1connection.h"
#include "sshserver.h"
static int ssh1sesschan_write(SshChannel *c, int is_stderr, const void *, int);
static void ssh1sesschan_write_eof(SshChannel *c);
static void ssh1sesschan_initiate_close(SshChannel *c, const char *err);
static void ssh1sesschan_send_exit_status(SshChannel *c, int status);
static void ssh1sesschan_send_exit_signal(
SshChannel *c, ptrlen signame, int core_dumped, ptrlen msg);
static const struct SshChannelVtable ssh1sesschan_vtable = {
ssh1sesschan_write,
ssh1sesschan_write_eof,
ssh1sesschan_initiate_close,
NULL /* unthrottle */,
NULL /* get_conf */,
NULL /* window_override_removed is only used by SSH-2 sharing */,
NULL /* x11_sharing_handover, likewise */,
ssh1sesschan_send_exit_status,
ssh1sesschan_send_exit_signal,
NULL /* send_exit_signal_numeric */,
NULL /* request_x11_forwarding */,
NULL /* request_agent_forwarding */,
NULL /* request_pty */,
NULL /* send_env_var */,
NULL /* start_shell */,
NULL /* start_command */,
NULL /* start_subsystem */,
NULL /* send_serial_break */,
NULL /* send_signal */,
NULL /* send_terminal_size_change */,
NULL /* hint_channel_is_simple */,
};
void ssh1_connection_direction_specific_setup(
struct ssh1_connection_state *s)
{
if (!s->mainchan_chan) {
s->mainchan_sc.vt = &ssh1sesschan_vtable;
s->mainchan_sc.cl = &s->cl;
s->mainchan_chan = sesschan_new(&s->mainchan_sc, s->ppl.logctx);
}
}
int ssh1_handle_direction_specific_packet(
struct ssh1_connection_state *s, PktIn *pktin)
{
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
PktOut *pktout;
struct ssh1_channel *c;
unsigned remid;
ptrlen host, cmd, data;
char *host_str, *err;
int port, listenport, success;
switch (pktin->type) {
case SSH1_CMSG_EXEC_SHELL:
if (s->finished_setup)
goto unexpected_setup_packet;
ppl_logevent(("Client requested a shell"));
chan_run_shell(s->mainchan_chan);
s->finished_setup = TRUE;
return TRUE;
case SSH1_CMSG_EXEC_CMD:
if (s->finished_setup)
goto unexpected_setup_packet;
cmd = get_string(pktin);
ppl_logevent(("Client sent command '%.*s'", PTRLEN_PRINTF(cmd)));
chan_run_command(s->mainchan_chan, cmd);
s->finished_setup = TRUE;
return TRUE;
case SSH1_CMSG_REQUEST_COMPRESSION:
if (s->compressing) {
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_FAILURE);
pq_push(s->ppl.out_pq, pktout);
} else {
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_SUCCESS);
pq_push(s->ppl.out_pq, pktout);
/* Synchronous run of output formatting, to ensure that
* success packet is converted into wire format before we
* start compressing */
ssh_bpp_handle_output(s->ppl.bpp);
/* And now ensure that the _next_ packet will be the first
* compressed one. */
ssh1_bpp_start_compression(s->ppl.bpp);
s->compressing = TRUE;
}
return TRUE;
case SSH1_CMSG_REQUEST_PTY:
if (s->finished_setup)
goto unexpected_setup_packet;
{
ptrlen termtype = get_string(pktin);
unsigned height = get_uint32(pktin);
unsigned width = get_uint32(pktin);
unsigned pixwidth = get_uint32(pktin);
unsigned pixheight = get_uint32(pktin);
struct ssh_ttymodes modes = read_ttymodes_from_packet(
BinarySource_UPCAST(pktin), 1);
if (get_err(pktin)) {
ppl_logevent(("Unable to decode pty request packet"));
success = FALSE;
} else if (!chan_allocate_pty(
s->mainchan_chan, termtype, width, height,
pixwidth, pixheight, modes)) {
ppl_logevent(("Unable to allocate a pty"));
success = FALSE;
} else {
success = TRUE;
}
}
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, (success ? SSH1_SMSG_SUCCESS : SSH1_SMSG_FAILURE));
pq_push(s->ppl.out_pq, pktout);
return TRUE;
case SSH1_CMSG_PORT_FORWARD_REQUEST:
if (s->finished_setup)
goto unexpected_setup_packet;
listenport = toint(get_uint32(pktin));
host = get_string(pktin);
port = toint(get_uint32(pktin));
ppl_logevent(("Client requested port %d forward to %.*s:%d",
listenport, PTRLEN_PRINTF(host), port));
host_str = mkstr(host);
success = portfwdmgr_listen(
s->portfwdmgr, NULL, listenport, host_str, port, s->conf);
sfree(host_str);
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, (success ? SSH1_SMSG_SUCCESS : SSH1_SMSG_FAILURE));
pq_push(s->ppl.out_pq, pktout);
return TRUE;
case SSH1_CMSG_X11_REQUEST_FORWARDING:
if (s->finished_setup)
goto unexpected_setup_packet;
{
ptrlen authproto = get_string(pktin);
ptrlen authdata = get_string(pktin);
unsigned screen_number = 0;
if (s->remote_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER)
screen_number = get_uint32(pktin);
success = chan_enable_x11_forwarding(
s->mainchan_chan, FALSE, authproto, authdata, screen_number);
}
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, (success ? SSH1_SMSG_SUCCESS : SSH1_SMSG_FAILURE));
pq_push(s->ppl.out_pq, pktout);
return TRUE;
case SSH1_CMSG_AGENT_REQUEST_FORWARDING:
if (s->finished_setup)
goto unexpected_setup_packet;
success = chan_enable_agent_forwarding(s->mainchan_chan);
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, (success ? SSH1_SMSG_SUCCESS : SSH1_SMSG_FAILURE));
pq_push(s->ppl.out_pq, pktout);
return TRUE;
case SSH1_CMSG_STDIN_DATA:
data = get_string(pktin);
chan_send(s->mainchan_chan, FALSE, data.ptr, data.len);
return TRUE;
case SSH1_CMSG_EOF:
chan_send_eof(s->mainchan_chan);
return TRUE;
case SSH1_CMSG_WINDOW_SIZE:
return TRUE;
case SSH1_MSG_PORT_OPEN:
remid = get_uint32(pktin);
host = get_string(pktin);
port = toint(get_uint32(pktin));
host_str = mkstr(host);
ppl_logevent(("Received request to connect to port %s:%d",
host_str, port));
c = snew(struct ssh1_channel);
c->connlayer = s;
err = portfwdmgr_connect(
s->portfwdmgr, &c->chan, host_str, port,
&c->sc, ADDRTYPE_UNSPEC);
sfree(host_str);
if (err) {
ppl_logevent(("Port open failed: %s", err));
sfree(err);
ssh1_channel_free(c);
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE);
put_uint32(pktout, remid);
pq_push(s->ppl.out_pq, pktout);
} else {
ssh1_channel_init(c);
c->remoteid = remid;
c->halfopen = FALSE;
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION);
put_uint32(pktout, c->remoteid);
put_uint32(pktout, c->localid);
pq_push(s->ppl.out_pq, pktout);
ppl_logevent(("Forwarded port opened successfully"));
}
return TRUE;
default:
return FALSE;
}
unexpected_setup_packet:
ssh_proto_error(s->ppl.ssh, "Received unexpected setup packet after the "
"setup phase, type %d (%s)", pktin->type,
ssh1_pkt_type(pktin->type));
/* FIXME: ensure caller copes with us just having freed the whole layer */
return TRUE;
}
SshChannel *ssh1_session_open(ConnectionLayer *cl, Channel *chan)
{
assert(FALSE && "Should never be called in the server");
}
struct ssh_rportfwd *ssh1_rportfwd_alloc(
ConnectionLayer *cl,
const char *shost, int sport, const char *dhost, int dport,
int addressfamily, const char *log_description, PortFwdRecord *pfr,
ssh_sharing_connstate *share_ctx)
{
assert(FALSE && "Should never be called in the server");
return NULL;
}
static int ssh1sesschan_write(SshChannel *sc, int is_stderr,
const void *data, int len)
{
struct ssh1_connection_state *s =
container_of(sc, struct ssh1_connection_state, mainchan_sc);
PktOut *pktout;
pktout = ssh_bpp_new_pktout(
s->ppl.bpp,
(is_stderr ? SSH1_SMSG_STDERR_DATA : SSH1_SMSG_STDOUT_DATA));
put_string(pktout, data, len);
pq_push(s->ppl.out_pq, pktout);
return 0;
}
static void ssh1sesschan_write_eof(SshChannel *sc)
{
/* SSH-1 can't represent server-side EOF */
/* FIXME: some kind of check-termination system, whereby once this has been called _and_ we've had an exit status _and_ we've got no other channels open, we send the actual EXIT_STATUS message */
}
static void ssh1sesschan_initiate_close(SshChannel *sc, const char *err)
{
/* SSH-1 relies on the client to close the connection in the end */
}
static void ssh1sesschan_send_exit_status(SshChannel *sc, int status)
{
struct ssh1_connection_state *s =
container_of(sc, struct ssh1_connection_state, mainchan_sc);
PktOut *pktout;
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_EXIT_STATUS);
put_uint32(pktout, status);
pq_push(s->ppl.out_pq, pktout);
}
static void ssh1sesschan_send_exit_signal(
SshChannel *sc, ptrlen signame, int core_dumped, ptrlen msg)
{
/* SSH-1 has no separate representation for signals */
ssh1sesschan_send_exit_status(sc, 128);
}
SshChannel *ssh1_serverside_x11_open(
ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi)
{
struct ssh1_connection_state *s =
container_of(cl, struct ssh1_connection_state, cl);
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
struct ssh1_channel *c = snew(struct ssh1_channel);
PktOut *pktout;
c->connlayer = s;
ssh1_channel_init(c);
c->halfopen = TRUE;
c->chan = chan;
ppl_logevent(("Forwarding X11 connection to client"));
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_X11_OPEN);
put_uint32(pktout, c->localid);
pq_push(s->ppl.out_pq, pktout);
return &c->sc;
}
SshChannel *ssh1_serverside_agent_open(ConnectionLayer *cl, Channel *chan)
{
struct ssh1_connection_state *s =
container_of(cl, struct ssh1_connection_state, cl);
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
struct ssh1_channel *c = snew(struct ssh1_channel);
PktOut *pktout;
c->connlayer = s;
ssh1_channel_init(c);
c->halfopen = TRUE;
c->chan = chan;
ppl_logevent(("Forwarding agent connection to client"));
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_AGENT_OPEN);
put_uint32(pktout, c->localid);
pq_push(s->ppl.out_pq, pktout);
return &c->sc;
}