1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-07-01 11:32:48 -05:00

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).
This commit is contained in:
Simon Tatham
2018-10-20 22:09:54 +01:00
parent a48e897f26
commit 1d323d5c80
29 changed files with 3526 additions and 96 deletions

290
ssh2connection-server.c Normal file
View File

@ -0,0 +1,290 @@
/*
* Server-specific parts of the SSH-2 connection layer.
*/
#include <assert.h>
#include "putty.h"
#include "ssh.h"
#include "sshbpp.h"
#include "sshppl.h"
#include "sshchan.h"
#include "sshcr.h"
#include "ssh2connection.h"
#include "sshserver.h"
static ChanopenResult chan_open_session(
struct ssh2_connection_state *s, SshChannel *sc)
{
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
ppl_logevent(("Opened session channel"));
CHANOPEN_RETURN_SUCCESS(sesschan_new(sc, s->ppl.logctx));
}
static ChanopenResult chan_open_direct_tcpip(
struct ssh2_connection_state *s, SshChannel *sc,
ptrlen dstaddr, int dstport, ptrlen peeraddr, int peerport)
{
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
Channel *ch;
char *dstaddr_str, *err;
dstaddr_str = mkstr(dstaddr);
ppl_logevent(("Received request to connect to port %s:%d (from %.*s:%d)",
dstaddr_str, dstport, PTRLEN_PRINTF(peeraddr), peerport));
err = portfwdmgr_connect(
s->portfwdmgr, &ch, dstaddr_str, dstport, sc, ADDRTYPE_UNSPEC);
sfree(dstaddr_str);
if (err != NULL) {
ppl_logevent(("Port open failed: %s", err));
sfree(err);
CHANOPEN_RETURN_FAILURE(
SSH2_OPEN_CONNECT_FAILED, ("Connection failed"));
}
ppl_logevent(("Port opened successfully"));
CHANOPEN_RETURN_SUCCESS(ch);
}
ChanopenResult ssh2_connection_parse_channel_open(
struct ssh2_connection_state *s, ptrlen type,
PktIn *pktin, SshChannel *sc)
{
if (ptrlen_eq_string(type, "session")) {
return chan_open_session(s, sc);
} else if (ptrlen_eq_string(type, "direct-tcpip")) {
ptrlen dstaddr = get_string(pktin);
int dstport = toint(get_uint32(pktin));
ptrlen peeraddr = get_string(pktin);
int peerport = toint(get_uint32(pktin));
return chan_open_direct_tcpip(
s, sc, dstaddr, dstport, peeraddr, peerport);
} else {
CHANOPEN_RETURN_FAILURE(
SSH2_OPEN_UNKNOWN_CHANNEL_TYPE,
("Unsupported channel type requested"));
}
}
int ssh2_connection_parse_global_request(
struct ssh2_connection_state *s, ptrlen type, PktIn *pktin)
{
if (ptrlen_eq_string(type, "tcpip-forward")) {
char *host = mkstr(get_string(pktin));
unsigned port = get_uint32(pktin);
/* In SSH-2, the host/port we listen on are the same host/port
* we want reported back to us when a connection comes in,
* because that's what we tell the client */
int toret = portfwdmgr_listen(
s->portfwdmgr, host, port, host, port, s->conf);
sfree(host);
return toret;
} else if (ptrlen_eq_string(type, "cancel-tcpip-forward")) {
char *host = mkstr(get_string(pktin));
unsigned port = get_uint32(pktin);
int toret = portfwdmgr_unlisten(s->portfwdmgr, host, port);
sfree(host);
return toret;
} else {
/* Unrecognised request. */
return FALSE;
}
}
PktOut *ssh2_portfwd_chanopen(
struct ssh2_connection_state *s, struct ssh2_channel *c,
const char *hostname, int port,
const char *description, const SocketPeerInfo *pi)
{
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
PktOut *pktout;
/*
* In server mode, this function is called by portfwdmgr in
* response to PortListeners that were set up by calling
* portfwdmgr_listen, which means that the hostname and port
* parameters will identify the listening socket on which a
* connection just came in.
*/
if (pi && pi->log_text)
ppl_logevent(("Forwarding connection to listening port %s:%d from %s",
hostname, port, pi->log_text));
else
ppl_logevent(("Forwarding connection to listening port %s:%d",
hostname, port));
pktout = ssh2_chanopen_init(c, "forwarded-tcpip");
put_stringz(pktout, hostname);
put_uint32(pktout, port);
put_stringz(pktout, (pi && pi->addr_text ? pi->addr_text : "0.0.0.0"));
put_uint32(pktout, (pi && pi->port >= 0 ? pi->port : 0));
return pktout;
}
struct ssh_rportfwd *ssh2_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");
}
void ssh2_rportfwd_remove(ConnectionLayer *cl, struct ssh_rportfwd *rpf)
{
assert(FALSE && "Should never be called in the server");
}
SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan)
{
assert(FALSE && "Should never be called in the server");
}
SshChannel *ssh2_serverside_x11_open(
ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi)
{
struct ssh2_connection_state *s =
container_of(cl, struct ssh2_connection_state, cl);
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
struct ssh2_channel *c = snew(struct ssh2_channel);
PktOut *pktout;
c->connlayer = s;
ssh2_channel_init(c);
c->halfopen = TRUE;
c->chan = chan;
ppl_logevent(("Forwarding X11 channel to client"));
pktout = ssh2_chanopen_init(c, "x11");
put_stringz(pktout, (pi && pi->addr_text ? pi->addr_text : "0.0.0.0"));
put_uint32(pktout, (pi && pi->port >= 0 ? pi->port : 0));
pq_push(s->ppl.out_pq, pktout);
return &c->sc;
}
SshChannel *ssh2_serverside_agent_open(ConnectionLayer *cl, Channel *chan)
{
struct ssh2_connection_state *s =
container_of(cl, struct ssh2_connection_state, cl);
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
struct ssh2_channel *c = snew(struct ssh2_channel);
PktOut *pktout;
c->connlayer = s;
ssh2_channel_init(c);
c->halfopen = TRUE;
c->chan = chan;
ppl_logevent(("Forwarding SSH agent to client"));
pktout = ssh2_chanopen_init(c, "auth-agent@openssh.com");
pq_push(s->ppl.out_pq, pktout);
return &c->sc;
}
void ssh2channel_start_shell(SshChannel *sc, int want_reply)
{
assert(FALSE && "Should never be called in the server");
}
void ssh2channel_start_command(
SshChannel *sc, int want_reply, const char *command)
{
assert(FALSE && "Should never be called in the server");
}
int ssh2channel_start_subsystem(
SshChannel *sc, int want_reply, const char *subsystem)
{
assert(FALSE && "Should never be called in the server");
}
void ssh2channel_send_exit_status(SshChannel *sc, int status)
{
struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
struct ssh2_connection_state *s = c->connlayer;
PktOut *pktout = ssh2_chanreq_init(c, "exit-status", NULL, NULL);
put_uint32(pktout, status);
pq_push(s->ppl.out_pq, pktout);
}
void ssh2channel_send_exit_signal(
SshChannel *sc, ptrlen signame, int core_dumped, ptrlen msg)
{
struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
struct ssh2_connection_state *s = c->connlayer;
PktOut *pktout = ssh2_chanreq_init(c, "exit-signal", NULL, NULL);
put_stringpl(pktout, signame);
put_bool(pktout, core_dumped);
put_stringpl(pktout, msg);
put_stringz(pktout, ""); /* language tag */
pq_push(s->ppl.out_pq, pktout);
}
void ssh2channel_send_exit_signal_numeric(
SshChannel *sc, int signum, int core_dumped, ptrlen msg)
{
struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
struct ssh2_connection_state *s = c->connlayer;
PktOut *pktout = ssh2_chanreq_init(c, "exit-signal", NULL, NULL);
put_uint32(pktout, signum);
put_bool(pktout, core_dumped);
put_stringpl(pktout, msg);
put_stringz(pktout, ""); /* language tag */
pq_push(s->ppl.out_pq, pktout);
}
void ssh2channel_request_x11_forwarding(
SshChannel *sc, int want_reply, const char *authproto,
const char *authdata, int screen_number, int oneshot)
{
assert(FALSE && "Should never be called in the server");
}
void ssh2channel_request_agent_forwarding(SshChannel *sc, int want_reply)
{
assert(FALSE && "Should never be called in the server");
}
void ssh2channel_request_pty(
SshChannel *sc, int want_reply, Conf *conf, int w, int h)
{
assert(FALSE && "Should never be called in the server");
}
int ssh2channel_send_env_var(
SshChannel *sc, int want_reply, const char *var, const char *value)
{
assert(FALSE && "Should never be called in the server");
}
int ssh2channel_send_serial_break(SshChannel *sc, int want_reply, int length)
{
assert(FALSE && "Should never be called in the server");
}
int ssh2channel_send_signal(
SshChannel *sc, int want_reply, const char *signame)
{
assert(FALSE && "Should never be called in the server");
}
void ssh2channel_send_terminal_size_change(SshChannel *sc, int w, int h)
{
assert(FALSE && "Should never be called in the server");
}