Move client-specific SSH code into new files.
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.
2018-10-20 16:57:37 +00:00
|
|
|
/*
|
|
|
|
* Client-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"
|
|
|
|
|
|
|
|
static ChanopenResult chan_open_x11(
|
|
|
|
struct ssh2_connection_state *s, SshChannel *sc,
|
|
|
|
ptrlen peeraddr, int peerport)
|
|
|
|
{
|
|
|
|
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
|
|
|
|
char *peeraddr_str;
|
|
|
|
Channel *ch;
|
|
|
|
|
|
|
|
ppl_logevent(("Received X11 connect request from %.*s:%d",
|
|
|
|
PTRLEN_PRINTF(peeraddr), peerport));
|
|
|
|
|
|
|
|
if (!s->X11_fwd_enabled && !s->connshare) {
|
|
|
|
CHANOPEN_RETURN_FAILURE(
|
|
|
|
SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED,
|
|
|
|
("X11 forwarding is not enabled"));
|
|
|
|
}
|
|
|
|
|
|
|
|
peeraddr_str = peeraddr.ptr ? mkstr(peeraddr) : NULL;
|
|
|
|
ch = x11_new_channel(
|
|
|
|
s->x11authtree, sc, peeraddr_str, peerport, s->connshare != NULL);
|
|
|
|
sfree(peeraddr_str);
|
|
|
|
ppl_logevent(("Opened X11 forward channel"));
|
|
|
|
CHANOPEN_RETURN_SUCCESS(ch);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ChanopenResult chan_open_forwarded_tcpip(
|
|
|
|
struct ssh2_connection_state *s, SshChannel *sc,
|
|
|
|
ptrlen fwdaddr, int fwdport, ptrlen peeraddr, int peerport)
|
|
|
|
{
|
|
|
|
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
|
|
|
|
struct ssh_rportfwd pf, *realpf;
|
|
|
|
Channel *ch;
|
|
|
|
char *err;
|
|
|
|
|
|
|
|
ppl_logevent(("Received remote port %.*s:%d open request from %.*s:%d",
|
|
|
|
PTRLEN_PRINTF(fwdaddr), fwdport,
|
|
|
|
PTRLEN_PRINTF(peeraddr), peerport));
|
|
|
|
|
|
|
|
pf.shost = mkstr(fwdaddr);
|
|
|
|
pf.sport = fwdport;
|
|
|
|
realpf = find234(s->rportfwds, &pf, NULL);
|
|
|
|
sfree(pf.shost);
|
|
|
|
|
|
|
|
if (realpf == NULL) {
|
|
|
|
CHANOPEN_RETURN_FAILURE(
|
|
|
|
SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED,
|
|
|
|
("Remote port is not recognised"));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (realpf->share_ctx) {
|
|
|
|
/*
|
|
|
|
* This port forwarding is on behalf of a connection-sharing
|
|
|
|
* downstream.
|
|
|
|
*/
|
|
|
|
CHANOPEN_RETURN_DOWNSTREAM(realpf->share_ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
err = portfwdmgr_connect(
|
|
|
|
s->portfwdmgr, &ch, realpf->dhost, realpf->dport,
|
|
|
|
sc, realpf->addressfamily);
|
|
|
|
ppl_logevent(("Attempting to forward remote port to %s:%d",
|
|
|
|
realpf->dhost, realpf->dport));
|
|
|
|
if (err != NULL) {
|
|
|
|
ppl_logevent(("Port open failed: %s", err));
|
|
|
|
sfree(err);
|
|
|
|
CHANOPEN_RETURN_FAILURE(
|
|
|
|
SSH2_OPEN_CONNECT_FAILED,
|
|
|
|
("Port open failed"));
|
|
|
|
}
|
|
|
|
|
|
|
|
ppl_logevent(("Forwarded port opened successfully"));
|
|
|
|
CHANOPEN_RETURN_SUCCESS(ch);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ChanopenResult chan_open_auth_agent(
|
|
|
|
struct ssh2_connection_state *s, SshChannel *sc)
|
|
|
|
{
|
|
|
|
if (!s->agent_fwd_enabled) {
|
|
|
|
CHANOPEN_RETURN_FAILURE(
|
|
|
|
SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED,
|
|
|
|
("Agent forwarding is not enabled"));
|
|
|
|
}
|
|
|
|
|
|
|
|
CHANOPEN_RETURN_SUCCESS(agentf_new(sc));
|
|
|
|
}
|
|
|
|
|
|
|
|
ChanopenResult ssh2_connection_parse_channel_open(
|
|
|
|
struct ssh2_connection_state *s, ptrlen type,
|
|
|
|
PktIn *pktin, SshChannel *sc)
|
|
|
|
{
|
|
|
|
if (ptrlen_eq_string(type, "x11")) {
|
|
|
|
ptrlen peeraddr = get_string(pktin);
|
|
|
|
int peerport = get_uint32(pktin);
|
|
|
|
|
|
|
|
return chan_open_x11(s, sc, peeraddr, peerport);
|
|
|
|
} else if (ptrlen_eq_string(type, "forwarded-tcpip")) {
|
|
|
|
ptrlen fwdaddr = get_string(pktin);
|
|
|
|
int fwdport = toint(get_uint32(pktin));
|
|
|
|
ptrlen peeraddr = get_string(pktin);
|
|
|
|
int peerport = toint(get_uint32(pktin));
|
|
|
|
|
|
|
|
return chan_open_forwarded_tcpip(
|
|
|
|
s, sc, fwdaddr, fwdport, peeraddr, peerport);
|
|
|
|
} else if (ptrlen_eq_string(type, "auth-agent@openssh.com")) {
|
|
|
|
return chan_open_auth_agent(s, sc);
|
|
|
|
} 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)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* We don't know of any global requests that an SSH client needs
|
|
|
|
* to honour.
|
|
|
|
*/
|
|
|
|
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 *peerinfo)
|
|
|
|
{
|
|
|
|
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
|
|
|
|
PktOut *pktout;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* In client mode, this function is called by portfwdmgr in
|
|
|
|
* response to PortListeners that were set up in
|
|
|
|
* portfwdmgr_config, which means that the hostname and port
|
|
|
|
* parameters will indicate the host we want to tell the server to
|
|
|
|
* connect _to_.
|
|
|
|
*/
|
|
|
|
|
|
|
|
ppl_logevent(("Opening connection to %s:%d for %s",
|
|
|
|
hostname, port, description));
|
|
|
|
|
|
|
|
pktout = ssh2_chanopen_init(c, "direct-tcpip");
|
|
|
|
{
|
|
|
|
char *trimmed_host = host_strduptrim(hostname);
|
|
|
|
put_stringz(pktout, trimmed_host);
|
|
|
|
sfree(trimmed_host);
|
|
|
|
}
|
|
|
|
put_uint32(pktout, port);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We make up values for the originator data; partly it's too much
|
|
|
|
* hassle to keep track, and partly I'm not convinced the server
|
|
|
|
* should be told details like that about my local network
|
|
|
|
* configuration. The "originator IP address" is syntactically a
|
|
|
|
* numeric IP address, and some servers (e.g., Tectia) get upset
|
|
|
|
* if it doesn't match this syntax.
|
|
|
|
*/
|
|
|
|
put_stringz(pktout, "0.0.0.0");
|
|
|
|
put_uint32(pktout, 0);
|
|
|
|
|
|
|
|
return pktout;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ssh2_rportfwd_cmp(void *av, void *bv)
|
|
|
|
{
|
|
|
|
struct ssh_rportfwd *a = (struct ssh_rportfwd *) av;
|
|
|
|
struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv;
|
|
|
|
int i;
|
|
|
|
if ( (i = strcmp(a->shost, b->shost)) != 0)
|
|
|
|
return i < 0 ? -1 : +1;
|
|
|
|
if (a->sport > b->sport)
|
|
|
|
return +1;
|
|
|
|
if (a->sport < b->sport)
|
|
|
|
return -1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ssh2_rportfwd_globreq_response(struct ssh2_connection_state *s,
|
|
|
|
PktIn *pktin, void *ctx)
|
|
|
|
{
|
|
|
|
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
|
|
|
|
struct ssh_rportfwd *rpf = (struct ssh_rportfwd *)ctx;
|
|
|
|
|
|
|
|
if (pktin->type == SSH2_MSG_REQUEST_SUCCESS) {
|
|
|
|
ppl_logevent(("Remote port forwarding from %s enabled",
|
|
|
|
rpf->log_description));
|
|
|
|
} else {
|
|
|
|
ppl_logevent(("Remote port forwarding from %s refused",
|
|
|
|
rpf->log_description));
|
|
|
|
|
|
|
|
struct ssh_rportfwd *realpf = del234(s->rportfwds, rpf);
|
|
|
|
assert(realpf == rpf);
|
|
|
|
portfwdmgr_close(s->portfwdmgr, rpf->pfr);
|
|
|
|
free_rportfwd(rpf);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
struct ssh2_connection_state *s =
|
|
|
|
container_of(cl, struct ssh2_connection_state, cl);
|
|
|
|
struct ssh_rportfwd *rpf = snew(struct ssh_rportfwd);
|
|
|
|
|
|
|
|
if (!s->rportfwds)
|
|
|
|
s->rportfwds = newtree234(ssh2_rportfwd_cmp);
|
|
|
|
|
|
|
|
rpf->shost = dupstr(shost);
|
|
|
|
rpf->sport = sport;
|
|
|
|
rpf->dhost = dupstr(dhost);
|
|
|
|
rpf->dport = dport;
|
|
|
|
rpf->addressfamily = addressfamily;
|
|
|
|
rpf->log_description = dupstr(log_description);
|
|
|
|
rpf->pfr = pfr;
|
|
|
|
rpf->share_ctx = share_ctx;
|
|
|
|
|
|
|
|
if (add234(s->rportfwds, rpf) != rpf) {
|
|
|
|
free_rportfwd(rpf);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!rpf->share_ctx) {
|
|
|
|
PktOut *pktout = ssh_bpp_new_pktout(
|
|
|
|
s->ppl.bpp, SSH2_MSG_GLOBAL_REQUEST);
|
|
|
|
put_stringz(pktout, "tcpip-forward");
|
|
|
|
put_bool(pktout, 1); /* want reply */
|
|
|
|
put_stringz(pktout, rpf->shost);
|
|
|
|
put_uint32(pktout, rpf->sport);
|
|
|
|
pq_push(s->ppl.out_pq, pktout);
|
|
|
|
|
|
|
|
ssh2_queue_global_request_handler(
|
|
|
|
s, ssh2_rportfwd_globreq_response, rpf);
|
|
|
|
}
|
|
|
|
|
|
|
|
return rpf;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ssh2_rportfwd_remove(ConnectionLayer *cl, struct ssh_rportfwd *rpf)
|
|
|
|
{
|
|
|
|
struct ssh2_connection_state *s =
|
|
|
|
container_of(cl, struct ssh2_connection_state, cl);
|
|
|
|
|
|
|
|
if (rpf->share_ctx) {
|
|
|
|
/*
|
|
|
|
* We don't manufacture a cancel-tcpip-forward message for
|
|
|
|
* remote port forwardings being removed on behalf of a
|
|
|
|
* downstream; we just pass through the one the downstream
|
|
|
|
* sent to us.
|
|
|
|
*/
|
|
|
|
} else {
|
|
|
|
PktOut *pktout = ssh_bpp_new_pktout(
|
|
|
|
s->ppl.bpp, SSH2_MSG_GLOBAL_REQUEST);
|
|
|
|
put_stringz(pktout, "cancel-tcpip-forward");
|
|
|
|
put_bool(pktout, 0); /* _don't_ want reply */
|
|
|
|
put_stringz(pktout, rpf->shost);
|
|
|
|
put_uint32(pktout, rpf->sport);
|
|
|
|
pq_push(s->ppl.out_pq, pktout);
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(s->rportfwds);
|
|
|
|
struct ssh_rportfwd *realpf = del234(s->rportfwds, rpf);
|
|
|
|
assert(realpf == rpf);
|
|
|
|
free_rportfwd(rpf);
|
|
|
|
}
|
|
|
|
|
|
|
|
SshChannel *ssh2_session_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(("Opening main session channel"));
|
|
|
|
|
|
|
|
pktout = ssh2_chanopen_init(c, "session");
|
|
|
|
pq_push(s->ppl.out_pq, pktout);
|
|
|
|
|
|
|
|
return &c->sc;
|
|
|
|
}
|
|
|
|
|
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-20 21:09:54 +00:00
|
|
|
SshChannel *ssh2_serverside_x11_open(
|
|
|
|
ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi)
|
|
|
|
{
|
|
|
|
assert(FALSE && "Should never be called in the client");
|
2018-10-25 17:33:56 +00:00
|
|
|
return 0; /* placate optimiser */
|
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-20 21:09:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
SshChannel *ssh2_serverside_agent_open(ConnectionLayer *cl, Channel *chan)
|
|
|
|
{
|
|
|
|
assert(FALSE && "Should never be called in the client");
|
2018-10-25 17:33:56 +00:00
|
|
|
return 0; /* placate optimiser */
|
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-20 21:09:54 +00:00
|
|
|
}
|
|
|
|
|
Move client-specific SSH code into new files.
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.
2018-10-20 16:57:37 +00:00
|
|
|
static void ssh2_channel_response(
|
|
|
|
struct ssh2_channel *c, PktIn *pkt, void *ctx)
|
|
|
|
{
|
|
|
|
chan_request_response(c->chan, pkt->type == SSH2_MSG_CHANNEL_SUCCESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ssh2channel_start_shell(SshChannel *sc, int want_reply)
|
|
|
|
{
|
|
|
|
struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
|
|
|
|
struct ssh2_connection_state *s = c->connlayer;
|
|
|
|
|
|
|
|
PktOut *pktout = ssh2_chanreq_init(
|
|
|
|
c, "shell", want_reply ? ssh2_channel_response : NULL, NULL);
|
|
|
|
pq_push(s->ppl.out_pq, pktout);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ssh2channel_start_command(
|
|
|
|
SshChannel *sc, int want_reply, const char *command)
|
|
|
|
{
|
|
|
|
struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
|
|
|
|
struct ssh2_connection_state *s = c->connlayer;
|
|
|
|
|
|
|
|
PktOut *pktout = ssh2_chanreq_init(
|
|
|
|
c, "exec", want_reply ? ssh2_channel_response : NULL, NULL);
|
|
|
|
put_stringz(pktout, command);
|
|
|
|
pq_push(s->ppl.out_pq, pktout);
|
|
|
|
}
|
|
|
|
|
|
|
|
int ssh2channel_start_subsystem(
|
|
|
|
SshChannel *sc, int want_reply, const char *subsystem)
|
|
|
|
{
|
|
|
|
struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
|
|
|
|
struct ssh2_connection_state *s = c->connlayer;
|
|
|
|
|
|
|
|
PktOut *pktout = ssh2_chanreq_init(
|
|
|
|
c, "subsystem", want_reply ? ssh2_channel_response : NULL, NULL);
|
|
|
|
put_stringz(pktout, subsystem);
|
|
|
|
pq_push(s->ppl.out_pq, pktout);
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2018-10-20 20:48:49 +00:00
|
|
|
void ssh2channel_send_exit_status(SshChannel *sc, int status)
|
|
|
|
{
|
|
|
|
assert(FALSE && "Should never be called in the client");
|
|
|
|
}
|
|
|
|
|
|
|
|
void ssh2channel_send_exit_signal(
|
|
|
|
SshChannel *sc, ptrlen signame, int core_dumped, ptrlen msg)
|
|
|
|
{
|
|
|
|
assert(FALSE && "Should never be called in the client");
|
|
|
|
}
|
|
|
|
|
|
|
|
void ssh2channel_send_exit_signal_numeric(
|
|
|
|
SshChannel *sc, int signum, int core_dumped, ptrlen msg)
|
|
|
|
{
|
|
|
|
assert(FALSE && "Should never be called in the client");
|
|
|
|
}
|
|
|
|
|
Move client-specific SSH code into new files.
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.
2018-10-20 16:57:37 +00:00
|
|
|
void ssh2channel_request_x11_forwarding(
|
|
|
|
SshChannel *sc, int want_reply, const char *authproto,
|
|
|
|
const char *authdata, int screen_number, int oneshot)
|
|
|
|
{
|
|
|
|
struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
|
|
|
|
struct ssh2_connection_state *s = c->connlayer;
|
|
|
|
|
|
|
|
PktOut *pktout = ssh2_chanreq_init(
|
|
|
|
c, "x11-req", want_reply ? ssh2_channel_response : NULL, NULL);
|
|
|
|
put_bool(pktout, oneshot);
|
|
|
|
put_stringz(pktout, authproto);
|
|
|
|
put_stringz(pktout, authdata);
|
|
|
|
put_uint32(pktout, screen_number);
|
|
|
|
pq_push(s->ppl.out_pq, pktout);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ssh2channel_request_agent_forwarding(SshChannel *sc, int want_reply)
|
|
|
|
{
|
|
|
|
struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
|
|
|
|
struct ssh2_connection_state *s = c->connlayer;
|
|
|
|
|
|
|
|
PktOut *pktout = ssh2_chanreq_init(
|
|
|
|
c, "auth-agent-req@openssh.com",
|
|
|
|
want_reply ? ssh2_channel_response : NULL, NULL);
|
|
|
|
pq_push(s->ppl.out_pq, pktout);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ssh2channel_request_pty(
|
|
|
|
SshChannel *sc, int want_reply, Conf *conf, int w, int h)
|
|
|
|
{
|
|
|
|
struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
|
|
|
|
struct ssh2_connection_state *s = c->connlayer;
|
|
|
|
strbuf *modebuf;
|
|
|
|
|
|
|
|
PktOut *pktout = ssh2_chanreq_init(
|
|
|
|
c, "pty-req", want_reply ? ssh2_channel_response : NULL, NULL);
|
|
|
|
put_stringz(pktout, conf_get_str(conf, CONF_termtype));
|
|
|
|
put_uint32(pktout, w);
|
|
|
|
put_uint32(pktout, h);
|
|
|
|
put_uint32(pktout, 0); /* pixel width */
|
|
|
|
put_uint32(pktout, 0); /* pixel height */
|
|
|
|
modebuf = strbuf_new();
|
|
|
|
write_ttymodes_to_packet(
|
|
|
|
BinarySink_UPCAST(modebuf), 2,
|
|
|
|
get_ttymodes_from_conf(s->ppl.seat, conf));
|
|
|
|
put_stringsb(pktout, modebuf);
|
|
|
|
pq_push(s->ppl.out_pq, pktout);
|
|
|
|
}
|
|
|
|
|
|
|
|
int ssh2channel_send_env_var(
|
|
|
|
SshChannel *sc, int want_reply, const char *var, const char *value)
|
|
|
|
{
|
|
|
|
struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
|
|
|
|
struct ssh2_connection_state *s = c->connlayer;
|
|
|
|
|
|
|
|
PktOut *pktout = ssh2_chanreq_init(
|
|
|
|
c, "env", want_reply ? ssh2_channel_response : NULL, NULL);
|
|
|
|
put_stringz(pktout, var);
|
|
|
|
put_stringz(pktout, value);
|
|
|
|
pq_push(s->ppl.out_pq, pktout);
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ssh2channel_send_serial_break(SshChannel *sc, int want_reply, int length)
|
|
|
|
{
|
|
|
|
struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
|
|
|
|
struct ssh2_connection_state *s = c->connlayer;
|
|
|
|
|
|
|
|
PktOut *pktout = ssh2_chanreq_init(
|
|
|
|
c, "break", want_reply ? ssh2_channel_response : NULL, NULL);
|
|
|
|
put_uint32(pktout, length);
|
|
|
|
pq_push(s->ppl.out_pq, pktout);
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ssh2channel_send_signal(
|
|
|
|
SshChannel *sc, int want_reply, const char *signame)
|
|
|
|
{
|
|
|
|
struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
|
|
|
|
struct ssh2_connection_state *s = c->connlayer;
|
|
|
|
|
|
|
|
PktOut *pktout = ssh2_chanreq_init(
|
|
|
|
c, "signal", want_reply ? ssh2_channel_response : NULL, NULL);
|
|
|
|
put_stringz(pktout, signame);
|
|
|
|
pq_push(s->ppl.out_pq, pktout);
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ssh2channel_send_terminal_size_change(SshChannel *sc, int w, int h)
|
|
|
|
{
|
|
|
|
struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
|
|
|
|
struct ssh2_connection_state *s = c->connlayer;
|
|
|
|
|
|
|
|
PktOut *pktout = ssh2_chanreq_init(c, "window-change", NULL, NULL);
|
|
|
|
put_uint32(pktout, w);
|
|
|
|
put_uint32(pktout, h);
|
|
|
|
put_uint32(pktout, 0); /* pixel width */
|
|
|
|
put_uint32(pktout, 0); /* pixel height */
|
|
|
|
pq_push(s->ppl.out_pq, pktout);
|
|
|
|
}
|