1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 17:38:00 +00: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

1
.gitignore vendored
View File

@ -38,6 +38,7 @@
/puttyapp
/ptermapp
/osxlaunch
/uppity
/unix/PuTTY.app
/unix/Pterm.app
/fuzzterm

37
Recipe
View File

@ -250,14 +250,18 @@ GTKMAIN = gtkmain cmdline
NONSSH = telnet raw rlogin ldisc pinger
# SSH back end (putty, plink, pscp, psftp).
SSH = ssh sshcommon ssh1bpp ssh2bpp ssh2bpp-bare ssh1censor ssh2censor
+ ssh1login ssh1connection ssh2transport ssh2userauth ssh2connection
+ sshverstring sshcrc sshdes sshmd5 sshrsa sshrand sshsha sshblowf
+ sshdh sshcrcda sshpubk sshzlib sshdss x11fwd portfwd
+ sshaes sshccp sshsh256 sshsh512 sshbn wildcard pinger ssharcf
+ sshgssc pgssapi sshshare sshecc aqsync marshal nullplug agentf
+ sshmac mainchan ssh2transhk ssh2kex-client ssh2connection-client
+ ssh1connection-client
SSHCOMMON = sshcommon sshrand
+ sshverstring sshcrc sshdes sshmd5 sshrsa sshsha sshblowf
+ sshdh sshcrcda sshpubk sshzlib sshdss ssharcf
+ sshaes sshccp sshsh256 sshsh512 sshbn sshmac marshal nullplug
+ sshgssc pgssapi sshecc wildcard ssh1censor ssh2censor ssh2bpp
+ ssh2transport ssh2transhk ssh2connection portfwd x11fwd
+ ssh1connection ssh1bpp
SSH = SSHCOMMON ssh ssh2bpp-bare
+ ssh1login ssh2userauth
+ pinger
+ sshshare aqsync agentf
+ mainchan ssh2kex-client ssh2connection-client ssh1connection-client
WINSSH = SSH winnoise wincapi winpgntc wingss winshare winnps winnpc
+ winhsock errsock
UXSSH = SSH uxnoise uxagentc uxgss uxshare
@ -268,11 +272,19 @@ SFTP = sftp int64 logging cmdline
# Miscellaneous objects appearing in all the utilities, or all the
# network ones, or the Unix or Windows subsets of those in turn.
MISC = misc marshal
MISCNET = timing callback MISC version settings tree234 proxy CONF be_misc
MISCNETCOMMON = timing callback MISC version tree234 CONF
MISCNET = MISCNETCOMMON be_misc settings proxy
WINMISC = MISCNET winstore winnet winhandl cmdline windefs winmisc winproxy
+ wintime winhsock errsock winsecur winucs miscucs
UXMISC = MISCNET uxstore uxsel uxnet uxpeer uxmisc time
+ uxproxy uxfdsock errsock
UXMISCCOMMON = MISCNETCOMMON uxstore uxsel uxnet uxpeer uxmisc time
+ uxfdsock errsock
UXMISC = MISCNET UXMISCCOMMON uxproxy
# SSH server.
SSHSERVER = SSHCOMMON sshserver settings be_none logging ssh2kex-server
+ ssh2userauth-server sshrsag sshprime ssh2connection-server
+ sesschan int64 proxy cproxy ssh1login-server
+ ssh1connection-server
# import.c and dependencies, for PuTTYgen-like utilities that have to
# load foreign key files.
@ -364,6 +376,9 @@ fuzzterm : [UT] UXTERM CHARSET misc version uxmisc uxucs fuzzterm time settings
testbn : [UT] testbn sshbn MISC version CONF tree234 uxmisc uxnogtk
testbn : [C] testbn sshbn MISC version CONF tree234 winmisc LIBS
uppity : [UT] uxserver SSHSERVER UXMISC uxsignal uxnoise uxgss uxnogtk
+ uxpty ux_x11 uxagentsock
# ----------------------------------------------------------------------
# On Windows, provide a means of removing local test binaries that we
# aren't going to actually ship. (I prefer this to not building them

View File

@ -1050,6 +1050,72 @@ void portfwdmgr_config(PortFwdManager *mgr, Conf *conf)
}
}
int portfwdmgr_listen(PortFwdManager *mgr, const char *host, int port,
const char *keyhost, int keyport, Conf *conf)
{
PortFwdRecord *pfr;
pfr = snew(PortFwdRecord);
pfr->type = 'L';
pfr->saddr = host ? dupstr(host) : NULL;
pfr->daddr = keyhost ? dupstr(keyhost) : NULL;
pfr->sserv = pfr->dserv = NULL;
pfr->sport = port;
pfr->dport = keyport;
pfr->local = NULL;
pfr->remote = NULL;
pfr->addressfamily = ADDRTYPE_UNSPEC;
PortFwdRecord *existing = add234(mgr->forwardings, pfr);
if (existing != pfr) {
/*
* We had this record already. Return failure.
*/
pfr_free(pfr);
return FALSE;
}
char *err = pfl_listen(keyhost, keyport, host, port,
mgr->cl, conf, &pfr->local, pfr->addressfamily);
logeventf(mgr->cl->logctx,
"%s on port %s:%d to forward to client%s%s",
err ? "Failed to listen" : "Listening", host, port,
err ? ": " : "", err ? err : "");
if (err) {
sfree(err);
del234(mgr->forwardings, pfr);
pfr_free(pfr);
return FALSE;
}
return TRUE;
}
int portfwdmgr_unlisten(PortFwdManager *mgr, const char *host, int port)
{
PortFwdRecord pfr_key;
pfr_key.type = 'L';
/* Safe to cast the const away here, because it will only be used
* by pfr_cmp, which won't write to the string */
pfr_key.saddr = pfr_key.daddr = (char *)host;
pfr_key.sserv = pfr_key.dserv = NULL;
pfr_key.sport = pfr_key.dport = port;
pfr_key.local = NULL;
pfr_key.remote = NULL;
pfr_key.addressfamily = ADDRTYPE_UNSPEC;
PortFwdRecord *pfr = del234(mgr->forwardings, &pfr_key);
if (!pfr)
return FALSE;
logeventf(mgr->cl->logctx, "Closing listening port %s:%d", host, port);
pfr_free(pfr);
return TRUE;
}
/*
* Called when receiving a PORT OPEN from the server to make a
* connection to a destination host.

559
sesschan.c Normal file
View File

@ -0,0 +1,559 @@
/*
* Implement the "session" channel type for the SSH server.
*/
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include "putty.h"
#include "ssh.h"
#include "sshchan.h"
#include "sshserver.h"
typedef struct sesschan {
SshChannel *c;
LogContext *parent_logctx, *child_logctx;
Conf *conf;
LogPolicy logpolicy;
Seat seat;
int want_pty;
struct ssh_ttymodes ttymodes;
int wc, hc, wp, hp;
strbuf *termtype;
int ignoring_input;
int seen_eof, seen_exit;
Plug xfwd_plug;
int n_x11_sockets;
Socket *x11_sockets[MAX_X11_SOCKETS];
Plug agentfwd_plug;
Socket *agentfwd_socket;
Backend *backend;
bufchain subsys_input;
Channel chan;
} sesschan;
static void sesschan_free(Channel *chan);
static int sesschan_send(Channel *chan, int is_stderr, const void *, int);
static void sesschan_send_eof(Channel *chan);
static char *sesschan_log_close_msg(Channel *chan);
static int sesschan_want_close(Channel *, int, int);
static void sesschan_set_input_wanted(Channel *chan, int wanted);
static int sesschan_run_shell(Channel *chan);
static int sesschan_run_command(Channel *chan, ptrlen command);
static int sesschan_run_subsystem(Channel *chan, ptrlen subsys);
static int sesschan_enable_x11_forwarding(
Channel *chan, int oneshot, ptrlen authproto, ptrlen authdata,
unsigned screen_number);
static int sesschan_enable_agent_forwarding(Channel *chan);
static int sesschan_allocate_pty(
Channel *chan, ptrlen termtype, unsigned width, unsigned height,
unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes);
static int sesschan_set_env(Channel *chan, ptrlen var, ptrlen value);
static int sesschan_send_break(Channel *chan, unsigned length);
static int sesschan_send_signal(Channel *chan, ptrlen signame);
static int sesschan_change_window_size(
Channel *chan, unsigned width, unsigned height,
unsigned pixwidth, unsigned pixheight);
static const struct ChannelVtable sesschan_channelvt = {
sesschan_free,
chan_remotely_opened_confirmation,
chan_remotely_opened_failure,
sesschan_send,
sesschan_send_eof,
sesschan_set_input_wanted,
sesschan_log_close_msg,
sesschan_want_close,
chan_no_exit_status,
chan_no_exit_signal,
chan_no_exit_signal_numeric,
sesschan_run_shell,
sesschan_run_command,
sesschan_run_subsystem,
sesschan_enable_x11_forwarding,
sesschan_enable_agent_forwarding,
sesschan_allocate_pty,
sesschan_set_env,
sesschan_send_break,
sesschan_send_signal,
sesschan_change_window_size,
chan_no_request_response,
};
static void sesschan_eventlog(LogPolicy *lp, const char *event) {}
static void sesschan_logging_error(LogPolicy *lp, const char *event) {}
static int sesschan_askappend(
LogPolicy *lp, Filename *filename,
void (*callback)(void *ctx, int result), void *ctx) { return 2; }
static const LogPolicyVtable sesschan_logpolicy_vt = {
sesschan_eventlog,
sesschan_askappend,
sesschan_logging_error,
};
static int sesschan_seat_output(Seat *, int is_stderr, const void *, int);
static int sesschan_seat_eof(Seat *);
static void sesschan_notify_remote_exit(Seat *seat);
static void sesschan_connection_fatal(Seat *seat, const char *message);
static int sesschan_get_window_pixel_size(Seat *seat, int *width, int *height);
static const SeatVtable sesschan_seat_vt = {
sesschan_seat_output,
sesschan_seat_eof,
nullseat_get_userpass_input,
sesschan_notify_remote_exit,
sesschan_connection_fatal,
nullseat_update_specials_menu,
nullseat_get_ttymode,
nullseat_set_busy_status,
nullseat_verify_ssh_host_key,
nullseat_confirm_weak_crypto_primitive,
nullseat_confirm_weak_cached_hostkey,
nullseat_is_never_utf8,
nullseat_echoedit_update,
nullseat_get_x_display,
nullseat_get_windowid,
sesschan_get_window_pixel_size,
};
Channel *sesschan_new(SshChannel *c, LogContext *logctx)
{
sesschan *sess = snew(sesschan);
memset(sess, 0, sizeof(sesschan));
sess->c = c;
sess->chan.vt = &sesschan_channelvt;
sess->chan.initial_fixed_window_size = 0;
sess->parent_logctx = logctx;
/* Start with a completely default Conf */
sess->conf = conf_new();
load_open_settings(NULL, sess->conf);
/* Set close-on-exit = TRUE to suppress uxpty.c's "[pterm: process
* terminated with status x]" message */
conf_set_int(sess->conf, CONF_close_on_exit, FORCE_ON);
sess->seat.vt = &sesschan_seat_vt;
sess->logpolicy.vt = &sesschan_logpolicy_vt;
sess->child_logctx = log_init(&sess->logpolicy, sess->conf);
bufchain_init(&sess->subsys_input);
return &sess->chan;
}
static void sesschan_free(Channel *chan)
{
sesschan *sess = container_of(chan, sesschan, chan);
int i;
delete_callbacks_for_context(sess);
conf_free(sess->conf);
if (sess->backend)
backend_free(sess->backend);
bufchain_clear(&sess->subsys_input);
for (i = 0; i < sess->n_x11_sockets; i++)
sk_close(sess->x11_sockets[i]);
if (sess->agentfwd_socket)
sk_close(sess->agentfwd_socket);
sfree(sess);
}
static int sesschan_send(Channel *chan, int is_stderr,
const void *data, int length)
{
sesschan *sess = container_of(chan, sesschan, chan);
if (!sess->backend || sess->ignoring_input)
return 0;
return backend_send(sess->backend, data, length);
}
static void sesschan_send_eof(Channel *chan)
{
sesschan *sess = container_of(chan, sesschan, chan);
if (sess->backend)
backend_special(sess->backend, SS_EOF, 0);
}
static char *sesschan_log_close_msg(Channel *chan)
{
return dupstr("Session channel closed");
}
static void sesschan_set_input_wanted(Channel *chan, int wanted)
{
/* I don't think we need to do anything here */
}
static void sesschan_start_backend(sesschan *sess, const char *cmd)
{
sess->backend = pty_backend_create(
&sess->seat, sess->child_logctx, sess->conf, NULL, cmd,
sess->ttymodes, !sess->want_pty);
backend_size(sess->backend, sess->wc, sess->hc);
}
int sesschan_run_shell(Channel *chan)
{
sesschan *sess = container_of(chan, sesschan, chan);
if (sess->backend)
return FALSE;
sesschan_start_backend(sess, NULL);
return TRUE;
}
int sesschan_run_command(Channel *chan, ptrlen command)
{
sesschan *sess = container_of(chan, sesschan, chan);
if (sess->backend)
return FALSE;
char *command_str = mkstr(command);
sesschan_start_backend(sess, command_str);
sfree(command_str);
return TRUE;
}
int sesschan_run_subsystem(Channel *chan, ptrlen subsys)
{
return FALSE;
}
static void fwd_log(Plug *plug, int type, SockAddr *addr, int port,
const char *error_msg, int error_code)
{ /* don't expect any weirdnesses from a listening socket */ }
static void fwd_closing(Plug *plug, const char *error_msg, int error_code,
int calling_back)
{ /* not here, either */ }
static int xfwd_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx)
{
sesschan *sess = container_of(p, sesschan, xfwd_plug);
Plug *plug;
Channel *chan;
Socket *s;
SocketPeerInfo *pi;
const char *err;
chan = portfwd_raw_new(sess->c->cl, &plug);
s = constructor(ctx, plug);
if ((err = sk_socket_error(s)) != NULL) {
portfwd_raw_free(chan);
return TRUE;
}
pi = sk_peer_info(s);
portfwd_raw_setup(chan, s, ssh_serverside_x11_open(sess->c->cl, chan, pi));
sk_free_peer_info(pi);
return FALSE;
}
static const PlugVtable xfwd_plugvt = {
fwd_log,
fwd_closing,
NULL, /* recv */
NULL, /* send */
xfwd_accepting,
};
int sesschan_enable_x11_forwarding(
Channel *chan, int oneshot, ptrlen authproto, ptrlen authdata_hex,
unsigned screen_number)
{
sesschan *sess = container_of(chan, sesschan, chan);
strbuf *authdata_bin;
size_t i;
char screensuffix[32];
if (oneshot)
return FALSE; /* not supported */
snprintf(screensuffix, sizeof(screensuffix), ".%u", screen_number);
/*
* Decode the authorisation data from ASCII hex into binary.
*/
if (authdata_hex.len % 2)
return FALSE; /* expected an even number of digits */
authdata_bin = strbuf_new();
for (i = 0; i < authdata_hex.len; i += 2) {
const unsigned char *hex = authdata_hex.ptr;
char hexbuf[3];
if (!isxdigit(hex[i]) || !isxdigit(hex[i+1])) {
strbuf_free(authdata_bin);
return FALSE; /* not hex */
}
hexbuf[0] = hex[i];
hexbuf[1] = hex[i+1];
hexbuf[2] = '\0';
put_byte(authdata_bin, strtoul(hexbuf, NULL, 16));
}
sess->xfwd_plug.vt = &xfwd_plugvt;
sess->n_x11_sockets = platform_make_x11_server(
&sess->xfwd_plug, appname, 10, screensuffix,
authproto, ptrlen_from_strbuf(authdata_bin),
sess->x11_sockets, sess->conf);
strbuf_free(authdata_bin);
return sess->n_x11_sockets != 0;
}
static int agentfwd_accepting(
Plug *p, accept_fn_t constructor, accept_ctx_t ctx)
{
sesschan *sess = container_of(p, sesschan, agentfwd_plug);
Plug *plug;
Channel *chan;
Socket *s;
const char *err;
chan = portfwd_raw_new(sess->c->cl, &plug);
s = constructor(ctx, plug);
if ((err = sk_socket_error(s)) != NULL) {
portfwd_raw_free(chan);
return TRUE;
}
portfwd_raw_setup(chan, s, ssh_serverside_agent_open(sess->c->cl, chan));
return FALSE;
}
static const PlugVtable agentfwd_plugvt = {
fwd_log,
fwd_closing,
NULL, /* recv */
NULL, /* send */
agentfwd_accepting,
};
int sesschan_enable_agent_forwarding(Channel *chan)
{
sesschan *sess = container_of(chan, sesschan, chan);
char *error, *socketname, *dir_prefix;
dir_prefix = dupprintf("/tmp/%s-agentfwd", appname);
sess->agentfwd_plug.vt = &agentfwd_plugvt;
sess->agentfwd_socket = platform_make_agent_socket(
&sess->agentfwd_plug, dir_prefix, &error, &socketname);
sfree(dir_prefix);
if (sess->agentfwd_socket) {
conf_set_str_str(sess->conf, CONF_environmt,
"SSH_AUTH_SOCK", socketname);
}
sfree(error);
sfree(socketname);
return sess->agentfwd_socket != NULL;
}
int sesschan_allocate_pty(
Channel *chan, ptrlen termtype, unsigned width, unsigned height,
unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes)
{
sesschan *sess = container_of(chan, sesschan, chan);
char *s;
if (sess->want_pty)
return FALSE;
s = mkstr(termtype);
conf_set_str(sess->conf, CONF_termtype, s);
sfree(s);
sess->want_pty = TRUE;
sess->ttymodes = modes;
sess->wc = width;
sess->hc = height;
sess->wp = pixwidth;
sess->hp = pixheight;
return TRUE;
}
int sesschan_set_env(Channel *chan, ptrlen var, ptrlen value)
{
sesschan *sess = container_of(chan, sesschan, chan);
char *svar = mkstr(var), *svalue = mkstr(value);
conf_set_str_str(sess->conf, CONF_environmt, svar, svalue);
sfree(svar);
sfree(svalue);
return TRUE;
}
int sesschan_send_break(Channel *chan, unsigned length)
{
sesschan *sess = container_of(chan, sesschan, chan);
if (sess->backend) {
/* We ignore the break length. We could pass it through as the
* 'arg' parameter, and have uxpty.c collect it and pass it on
* to tcsendbreak, but since tcsendbreak in turn assigns
* implementation-defined semantics to _its_ duration
* parameter, this all just sounds too difficult. */
backend_special(sess->backend, SS_BRK, 0);
return TRUE;
}
return FALSE;
}
int sesschan_send_signal(Channel *chan, ptrlen signame)
{
sesschan *sess = container_of(chan, sesschan, chan);
/* Start with a code that definitely isn't a signal (or indeed a
* special command at all), to indicate 'nothing matched'. */
SessionSpecialCode code = SS_EXITMENU;
#define SIGNAL_SUB(name) \
if (ptrlen_eq_string(signame, #name)) code = SS_SIG ## name;
#define SIGNAL_MAIN(name, text) SIGNAL_SUB(name)
#include "sshsignals.h"
#undef SIGNAL_MAIN
#undef SIGNAL_SUB
if (code == SS_EXITMENU)
return FALSE;
backend_special(sess->backend, code, 0);
return TRUE;
}
int sesschan_change_window_size(
Channel *chan, unsigned width, unsigned height,
unsigned pixwidth, unsigned pixheight)
{
sesschan *sess = container_of(chan, sesschan, chan);
if (!sess->want_pty)
return FALSE;
sess->wc = width;
sess->hc = height;
sess->wp = pixwidth;
sess->hp = pixheight;
if (sess->backend)
backend_size(sess->backend, sess->wc, sess->hc);
return TRUE;
}
static int sesschan_seat_output(
Seat *seat, int is_stderr, const void *data, int len)
{
sesschan *sess = container_of(seat, sesschan, seat);
return sshfwd_write_ext(sess->c, is_stderr, data, len);
}
static void sesschan_check_close_callback(void *vctx)
{
sesschan *sess = (sesschan *)vctx;
/*
* Once we've seen incoming EOF from the backend (aka EIO from the
* pty master) and also passed on the process's exit status, we
* should proactively initiate closure of the session channel.
*/
if (sess->seen_eof && sess->seen_exit)
sshfwd_initiate_close(sess->c, NULL);
}
static int sesschan_want_close(Channel *chan, int seen_eof, int rcvd_eof)
{
sesschan *sess = container_of(chan, sesschan, chan);
/*
* Similarly to above, we don't want to initiate channel closure
* until we've sent the process's exit status, _even_ if EOF of
* the actual data stream has happened in both directions.
*/
return (sess->seen_eof && sess->seen_exit);
}
static int sesschan_seat_eof(Seat *seat)
{
sesschan *sess = container_of(seat, sesschan, seat);
sshfwd_write_eof(sess->c);
sess->seen_eof = TRUE;
queue_toplevel_callback(sesschan_check_close_callback, sess);
return TRUE;
}
static void sesschan_notify_remote_exit(Seat *seat)
{
sesschan *sess = container_of(seat, sesschan, seat);
ptrlen signame;
char *sigmsg;
if (!sess->backend)
return;
signame = pty_backend_exit_signame(sess->backend, &sigmsg);
if (signame.len) {
if (!sigmsg)
sigmsg = dupstr("");
sshfwd_send_exit_signal(
sess->c, signame, FALSE, ptrlen_from_asciz(sigmsg));
sfree(sigmsg);
} else {
sshfwd_send_exit_status(sess->c, backend_exitcode(sess->backend));
}
sess->seen_exit = TRUE;
queue_toplevel_callback(sesschan_check_close_callback, sess);
}
static void sesschan_connection_fatal(Seat *seat, const char *message)
{
sesschan *sess = container_of(seat, sesschan, seat);
/* Closest translation I can think of */
sshfwd_send_exit_signal(
sess->c, PTRLEN_LITERAL("HUP"), FALSE, ptrlen_from_asciz(message));
sess->ignoring_input = TRUE;
}
static int sesschan_get_window_pixel_size(Seat *seat, int *width, int *height)
{
sesschan *sess = container_of(seat, sesschan, seat);
*width = sess->wp;
*height = sess->hp;
return TRUE;
}

7
ssh.c
View File

@ -180,7 +180,7 @@ static void ssh_got_ssh_version(struct ssh_version_receiver *rcv,
int is_simple =
(conf_get_int(ssh->conf, CONF_ssh_simple) && !ssh->connshare);
ssh->bpp = ssh2_bpp_new(ssh->logctx, &ssh->stats);
ssh->bpp = ssh2_bpp_new(ssh->logctx, &ssh->stats, FALSE);
ssh_connect_bpp(ssh);
#ifndef NO_GSSAPI
@ -246,7 +246,7 @@ static void ssh_got_ssh_version(struct ssh_version_receiver *rcv,
ssh_verstring_get_local(old_bpp),
ssh_verstring_get_remote(old_bpp),
&ssh->gss_state,
&ssh->stats, transport_child_layer);
&ssh->stats, transport_child_layer, FALSE);
ssh_connect_ppl(ssh, ssh->base_layer);
if (userauth_layer)
@ -717,7 +717,8 @@ static const char *connect_to_host(Ssh *ssh, const char *host, int port,
ssh->version_receiver.got_ssh_version = ssh_got_ssh_version;
ssh->bpp = ssh_verstring_new(
ssh->conf, ssh->logctx, ssh->bare_connection,
ssh->version == 1 ? "1.5" : "2.0", &ssh->version_receiver, "PuTTY");
ssh->version == 1 ? "1.5" : "2.0", &ssh->version_receiver,
FALSE, "PuTTY");
ssh_connect_bpp(ssh);
queue_idempotent_callback(&ssh->bpp->ic_in_raw);

18
ssh.h
View File

@ -222,6 +222,12 @@ struct ConnectionLayerVtable {
/* Initiate opening of a 'session'-type channel */
SshChannel *(*session_open)(ConnectionLayer *cl, Channel *chan);
/* Open outgoing channels for X and agent forwarding. (Used in the
* SSH server.) */
SshChannel *(*serverside_x11_open)(ConnectionLayer *cl, Channel *chan,
const SocketPeerInfo *pi);
SshChannel *(*serverside_agent_open)(ConnectionLayer *cl, Channel *chan);
/* Add an X11 display for ordinary X forwarding */
struct X11FakeAuth *(*add_x11_display)(
ConnectionLayer *cl, int authtype, struct X11Display *x11disp);
@ -300,6 +306,10 @@ struct ConnectionLayer {
#define ssh_rportfwd_remove(cl, rpf) ((cl)->vt->rportfwd_remove(cl, rpf))
#define ssh_lportfwd_open(cl, h, p, desc, pi, chan) \
((cl)->vt->lportfwd_open(cl, h, p, desc, pi, chan))
#define ssh_serverside_x11_open(cl, chan, pi) \
((cl)->vt->serverside_x11_open(cl, chan, pi))
#define ssh_serverside_agent_open(cl, chan) \
((cl)->vt->serverside_agent_open(cl, chan))
#define ssh_session_open(cl, chan) \
((cl)->vt->session_open(cl, chan))
#define ssh_add_x11_display(cl, auth, disp) \
@ -333,6 +343,8 @@ struct ConnectionLayer {
#define ssh_enable_agent_fwd(cl) ((cl)->vt->enable_agent_fwd(cl))
#define ssh_set_wants_user_input(cl, wanted) \
((cl)->vt->set_wants_user_input(cl, wanted))
#define ssh_setup_server_x_forwarding(cl, conf, ap, ad, scr) \
((cl)->vt->setup_server_x_forwarding(cl, conf, ap, ad, scr))
/* Exports from portfwd.c */
PortFwdManager *portfwdmgr_new(ConnectionLayer *cl);
@ -343,6 +355,12 @@ void portfwdmgr_close_all(PortFwdManager *mgr);
char *portfwdmgr_connect(PortFwdManager *mgr, Channel **chan_ret,
char *hostname, int port, SshChannel *c,
int addressfamily);
int portfwdmgr_listen(PortFwdManager *mgr, const char *host, int port,
const char *keyhost, int keyport, Conf *conf);
int portfwdmgr_unlisten(PortFwdManager *mgr, const char *host, int port);
Channel *portfwd_raw_new(ConnectionLayer *cl, Plug **plug);
void portfwd_raw_free(Channel *pfchan);
void portfwd_raw_setup(Channel *pfchan, Socket *s, SshChannel *sc);
Socket *platform_make_agent_socket(Plug *plug, const char *dirprefix,
char **error, char **name);

View File

@ -89,6 +89,21 @@ void ssh1_bpp_new_cipher(BinaryPacketProtocol *bpp,
}
}
void ssh1_bpp_start_compression(BinaryPacketProtocol *bpp)
{
struct ssh1_bpp_state *s;
assert(bpp->vt == &ssh1_bpp_vtable);
s = container_of(bpp, struct ssh1_bpp_state, bpp);
assert(!s->compctx);
assert(!s->decompctx);
s->compctx = ssh_compressor_new(&ssh_zlib);
s->decompctx = ssh_decompressor_new(&ssh_zlib);
bpp_logevent(("Started zlib (RFC1950) compression"));
}
#define BPP_READ(ptr, len) do \
{ \
crMaybeWaitUntilV(s->bpp.input_eof || \
@ -221,13 +236,7 @@ static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp)
* If the response was positive, start
* compression.
*/
assert(!s->compctx);
assert(!s->decompctx);
s->compctx = ssh_compressor_new(&ssh_zlib);
s->decompctx = ssh_decompressor_new(&ssh_zlib);
bpp_logevent(("Started zlib (RFC1950) compression"));
ssh1_bpp_start_compression(&s->bpp);
}
/*

View File

@ -516,3 +516,16 @@ struct ssh_rportfwd *ssh1_rportfwd_alloc(
return rpf;
}
SshChannel *ssh1_serverside_x11_open(
ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi)
{
assert(FALSE && "Should never be called in the client");
return NULL;
}
SshChannel *ssh1_serverside_agent_open(ConnectionLayer *cl, Channel *chan)
{
assert(FALSE && "Should never be called in the client");
return NULL;
}

355
ssh1connection-server.c Normal file
View File

@ -0,0 +1,355 @@
/*
* 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;
}

View File

@ -69,6 +69,8 @@ static const struct ConnectionLayerVtable ssh1_connlayer_vtable = {
ssh1_rportfwd_remove,
ssh1_lportfwd_open,
ssh1_session_open,
ssh1_serverside_x11_open,
ssh1_serverside_agent_open,
ssh1_add_x11_display,
NULL /* add_sharing_x11_display */,
NULL /* remove_sharing_x11_display */,
@ -212,12 +214,14 @@ static void ssh1_connection_free(PacketProtocolLayer *ppl)
sfree(s);
}
void ssh1_connection_set_local_protoflags(PacketProtocolLayer *ppl, int flags)
void ssh1_connection_set_protoflags(PacketProtocolLayer *ppl,
int local, int remote)
{
assert(ppl->vt == &ssh1_connection_vtable);
struct ssh1_connection_state *s =
container_of(ppl, struct ssh1_connection_state, ppl);
s->local_protoflags = flags;
s->local_protoflags = local;
s->remote_protoflags = remote;
}
static int ssh1_connection_filter_queue(struct ssh1_connection_state *s)

View File

@ -8,7 +8,7 @@ struct ssh1_connection_state {
Ssh *ssh;
Conf *conf;
int local_protoflags;
int local_protoflags, remote_protoflags;
tree234 *channels; /* indexed by local id */
@ -49,6 +49,8 @@ struct ssh1_connection_state {
*/
struct outstanding_succfail *succfail_head, *succfail_tail;
int compressing; /* used in server mode only */
ConnectionLayer cl;
PacketProtocolLayer ppl;
};
@ -105,6 +107,9 @@ struct ssh_rportfwd *ssh1_rportfwd_alloc(
const char *shost, int sport, const char *dhost, int dport,
int addressfamily, const char *log_description, PortFwdRecord *pfr,
ssh_sharing_connstate *share_ctx);
SshChannel *ssh1_serverside_x11_open(
ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi);
SshChannel *ssh1_serverside_agent_open(ConnectionLayer *cl, Channel *chan);
void ssh1_connection_direction_specific_setup(
struct ssh1_connection_state *s);

376
ssh1login-server.c Normal file
View File

@ -0,0 +1,376 @@
/*
* Packet protocol layer for the SSH-1 login phase, from the server side.
*/
#include <assert.h>
#include "putty.h"
#include "ssh.h"
#include "sshbpp.h"
#include "sshppl.h"
#include "sshcr.h"
#include "sshserver.h"
struct ssh1_login_server_state {
int crState;
PacketProtocolLayer *successor_layer;
int remote_protoflags;
int local_protoflags;
unsigned long supported_ciphers_mask, supported_auths_mask;
unsigned cipher_type;
unsigned char cookie[8];
unsigned char session_key[32];
unsigned char session_id[16];
char *username_str;
ptrlen username;
struct RSAKey *servkey, *hostkey;
int servkey_generated_here;
Bignum sesskey;
AuthPolicy *authpolicy;
unsigned ap_methods, current_method;
unsigned char auth_rsa_expected_response[16];
struct RSAKey *authkey;
int auth_successful;
PacketProtocolLayer ppl;
};
static void ssh1_login_server_free(PacketProtocolLayer *);
static void ssh1_login_server_process_queue(PacketProtocolLayer *);
static int ssh1_login_server_get_specials(
PacketProtocolLayer *ppl, add_special_fn_t add_special,
void *ctx) { return FALSE; }
static void ssh1_login_server_special_cmd(PacketProtocolLayer *ppl,
SessionSpecialCode code, int arg) {}
static int ssh1_login_server_want_user_input(
PacketProtocolLayer *ppl) { return FALSE; }
static void ssh1_login_server_got_user_input(PacketProtocolLayer *ppl) {}
static void ssh1_login_server_reconfigure(
PacketProtocolLayer *ppl, Conf *conf) {}
static const struct PacketProtocolLayerVtable ssh1_login_server_vtable = {
ssh1_login_server_free,
ssh1_login_server_process_queue,
ssh1_login_server_get_specials,
ssh1_login_server_special_cmd,
ssh1_login_server_want_user_input,
ssh1_login_server_got_user_input,
ssh1_login_server_reconfigure,
NULL /* no layer names in SSH-1 */,
};
static void no_progress(void *param, int action, int phase, int iprogress) {}
PacketProtocolLayer *ssh1_login_server_new(
PacketProtocolLayer *successor_layer, struct RSAKey *hostkey,
AuthPolicy *authpolicy)
{
struct ssh1_login_server_state *s = snew(struct ssh1_login_server_state);
memset(s, 0, sizeof(*s));
s->ppl.vt = &ssh1_login_server_vtable;
s->hostkey = hostkey;
s->authpolicy = authpolicy;
s->successor_layer = successor_layer;
return &s->ppl;
}
static void ssh1_login_server_free(PacketProtocolLayer *ppl)
{
struct ssh1_login_server_state *s =
container_of(ppl, struct ssh1_login_server_state, ppl);
if (s->successor_layer)
ssh_ppl_free(s->successor_layer);
if (s->servkey_generated_here && s->servkey) {
freersakey(s->servkey);
sfree(s->servkey);
}
smemclr(s->session_key, sizeof(s->session_key));
sfree(s->username_str);
sfree(s);
}
static int ssh1_login_server_filter_queue(struct ssh1_login_server_state *s)
{
return ssh1_common_filter_queue(&s->ppl);
}
static PktIn *ssh1_login_server_pop(struct ssh1_login_server_state *s)
{
if (ssh1_login_server_filter_queue(s))
return NULL;
return pq_pop(s->ppl.in_pq);
}
static void ssh1_login_server_process_queue(PacketProtocolLayer *ppl)
{
struct ssh1_login_server_state *s =
container_of(ppl, struct ssh1_login_server_state, ppl);
PktIn *pktin;
PktOut *pktout;
int i;
/* Filter centrally handled messages off the front of the queue on
* every entry to this coroutine, no matter where we're resuming
* from, even if we're _not_ looping on pq_pop. That way we can
* still proactively handle those messages even if we're waiting
* for a user response. */
if (ssh1_login_server_filter_queue(s))
return;
crBegin(s->crState);
if (!s->servkey) {
int server_key_bits = s->hostkey->bytes - 256;
if (server_key_bits < 512)
server_key_bits = s->hostkey->bytes + 256;
s->servkey = snew(struct RSAKey);
rsa_generate(s->servkey, server_key_bits, no_progress, NULL);
s->servkey->comment = NULL;
s->servkey_generated_here = TRUE;
}
s->local_protoflags = SSH1_PROTOFLAGS_SUPPORTED;
/* FIXME: ability to configure this to a subset */
s->supported_ciphers_mask = ((1U << SSH_CIPHER_3DES) |
(1U << SSH_CIPHER_BLOWFISH) |
(1U << SSH_CIPHER_DES));
s->supported_auths_mask = 0;
s->ap_methods = auth_methods(s->authpolicy);
if (s->ap_methods & AUTHMETHOD_PASSWORD)
s->supported_auths_mask |= (1U << SSH1_AUTH_PASSWORD);
if (s->ap_methods & AUTHMETHOD_PUBLICKEY)
s->supported_auths_mask |= (1U << SSH1_AUTH_RSA);
/* FIXME: TIS, CCARD */
for (i = 0; i < 8; i++)
s->cookie[i] = random_byte();
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_PUBLIC_KEY);
put_data(pktout, s->cookie, 8);
rsa_ssh1_public_blob(BinarySink_UPCAST(pktout),
s->servkey, RSA_SSH1_EXPONENT_FIRST);
rsa_ssh1_public_blob(BinarySink_UPCAST(pktout),
s->hostkey, RSA_SSH1_EXPONENT_FIRST);
put_uint32(pktout, s->local_protoflags);
put_uint32(pktout, s->supported_ciphers_mask);
put_uint32(pktout, s->supported_auths_mask);
pq_push(s->ppl.out_pq, pktout);
crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL);
if (pktin->type != SSH1_CMSG_SESSION_KEY) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet in response"
" to initial public key packet, type %d (%s)",
pktin->type, ssh1_pkt_type(pktin->type));
return;
}
{
ptrlen client_cookie;
s->cipher_type = get_byte(pktin);
client_cookie = get_data(pktin, 8);
s->sesskey = get_mp_ssh1(pktin);
s->remote_protoflags = get_uint32(pktin);
if (get_err(pktin)) {
ssh_proto_error(s->ppl.ssh, "Unable to parse session key packet");
return;
}
if (!ptrlen_eq_ptrlen(client_cookie, make_ptrlen(s->cookie, 8))) {
ssh_proto_error(s->ppl.ssh,
"Client sent incorrect anti-spoofing cookie");
return;
}
}
if (s->cipher_type >= 32 ||
!((s->supported_ciphers_mask >> s->cipher_type) & 1)) {
ssh_proto_error(s->ppl.ssh, "Client selected an unsupported cipher");
return;
}
{
struct RSAKey *smaller, *larger;
strbuf *data = strbuf_new();
if (bignum_bitcount(s->hostkey->modulus) >
bignum_bitcount(s->servkey->modulus)) {
larger = s->hostkey;
smaller = s->servkey;
} else {
smaller = s->hostkey;
larger = s->servkey;
}
if (rsa_ssh1_decrypt_pkcs1(s->sesskey, larger, data)) {
freebn(s->sesskey);
s->sesskey = bignum_from_bytes(data->u, data->len);
data->len = 0;
if (rsa_ssh1_decrypt_pkcs1(s->sesskey, smaller, data) &&
data->len == sizeof(s->session_key)) {
memcpy(s->session_key, data->u, sizeof(s->session_key));
freebn(s->sesskey);
s->sesskey = NULL; /* indicates success */
}
}
strbuf_free(data);
}
if (s->sesskey) {
ssh_proto_error(s->ppl.ssh, "Failed to decrypt session key");
return;
}
ssh1_compute_session_id(s->session_id, s->cookie, s->hostkey, s->servkey);
for (i = 0; i < 16; i++)
s->session_key[i] ^= s->session_id[i];
{
const struct ssh1_cipheralg *cipher =
(s->cipher_type == SSH_CIPHER_BLOWFISH ? &ssh1_blowfish :
s->cipher_type == SSH_CIPHER_DES ? &ssh1_des : &ssh1_3des);
ssh1_bpp_new_cipher(s->ppl.bpp, cipher, s->session_key);
}
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_SUCCESS);
pq_push(s->ppl.out_pq, pktout);
crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL);
if (pktin->type != SSH1_CMSG_USER) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet while "
"expecting username, type %d (%s)",
pktin->type, ssh1_pkt_type(pktin->type));
return;
}
s->username = get_string(pktin);
s->username.ptr = s->username_str = mkstr(s->username);
ppl_logevent(("Received username '%.*s'", PTRLEN_PRINTF(s->username)));
s->auth_successful = auth_none(s->authpolicy, s->username);
while (1) {
/* Signal failed authentication */
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_FAILURE);
pq_push(s->ppl.out_pq, pktout);
crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL);
if (pktin->type == SSH1_CMSG_AUTH_PASSWORD) {
s->current_method = AUTHMETHOD_PASSWORD;
if (!(s->ap_methods & s->current_method))
continue;
ptrlen password = get_string(pktin);
/* Tolerate historic traffic-analysis defence of NUL +
* garbage on the end of the binary password string */
char *nul = memchr(password.ptr, '\0', password.len);
if (nul)
password.len = (const char *)nul - (const char *)password.ptr;
if (auth_password(s->authpolicy, s->username, password))
goto auth_success;
} else if (pktin->type == SSH1_CMSG_AUTH_RSA) {
s->current_method = AUTHMETHOD_PUBLICKEY;
if (!(s->ap_methods & s->current_method))
continue;
{
Bignum modulus = get_mp_ssh1(pktin);
s->authkey = auth_publickey_ssh1(
s->authpolicy, s->username, modulus);
freebn(modulus);
}
if (!s->authkey)
continue;
if (s->authkey->bytes < 32) {
ppl_logevent(("Auth key far too small"));
continue;
}
{
unsigned char *rsabuf =
snewn(s->authkey->bytes, unsigned char);
struct MD5Context md5c;
for (i = 0; i < 32; i++)
rsabuf[i] = random_byte();
MD5Init(&md5c);
put_data(&md5c, rsabuf, 32);
put_data(&md5c, s->session_id, 16);
MD5Final(s->auth_rsa_expected_response, &md5c);
if (!rsa_ssh1_encrypt(rsabuf, 32, s->authkey)) {
sfree(rsabuf);
ppl_logevent(("Failed to encrypt auth challenge"));
continue;
}
Bignum bn = bignum_from_bytes(rsabuf, s->authkey->bytes);
smemclr(rsabuf, s->authkey->bytes);
sfree(rsabuf);
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, SSH1_SMSG_AUTH_RSA_CHALLENGE);
put_mp_ssh1(pktout, bn);
pq_push(s->ppl.out_pq, pktout);
freebn(bn);
}
crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL);
if (pktin->type != SSH1_CMSG_AUTH_RSA_RESPONSE) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet in "
"response to RSA auth challenge, type %d (%s)",
pktin->type, ssh1_pkt_type(pktin->type));
return;
}
{
ptrlen response = get_data(pktin, 16);
ptrlen expected = make_ptrlen(
s->auth_rsa_expected_response, 16);
if (!ptrlen_eq_ptrlen(response, expected)) {
ppl_logevent(("Wrong response to auth challenge"));
continue;
}
}
goto auth_success;
}
}
auth_success:
if (!auth_successful(s->authpolicy, s->username, s->current_method)) {
ssh_sw_abort(s->ppl.ssh, "Multiple authentications required but SSH-1"
" cannot perform them");
return;
}
/* Signal successful authentication */
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_SUCCESS);
pq_push(s->ppl.out_pq, pktout);
ssh1_connection_set_protoflags(
s->successor_layer, s->local_protoflags, s->remote_protoflags);
{
PacketProtocolLayer *successor = s->successor_layer;
s->successor_layer = NULL; /* avoid freeing it ourself */
ssh_ppl_replace(&s->ppl, successor);
return; /* we've just freed s, so avoid even touching s->crState */
}
crFinishV;
}

View File

@ -1086,8 +1086,8 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
}
}
ssh1_connection_set_local_protoflags(
s->successor_layer, s->local_protoflags);
ssh1_connection_set_protoflags(
s->successor_layer, s->local_protoflags, s->remote_protoflags);
{
PacketProtocolLayer *successor = s->successor_layer;
s->successor_layer = NULL; /* avoid freeing it ourself */

View File

@ -34,6 +34,7 @@ struct ssh2_bpp_state {
ssh_decompressor *in_decomp;
ssh_compressor *out_comp;
int is_server;
int pending_newkeys;
int pending_compression, seen_userauth_success;
@ -54,13 +55,14 @@ static const struct BinaryPacketProtocolVtable ssh2_bpp_vtable = {
};
BinaryPacketProtocol *ssh2_bpp_new(
LogContext *logctx, struct DataTransferStats *stats)
LogContext *logctx, struct DataTransferStats *stats, int is_server)
{
struct ssh2_bpp_state *s = snew(struct ssh2_bpp_state);
memset(s, 0, sizeof(*s));
s->bpp.vt = &ssh2_bpp_vtable;
s->bpp.logctx = logctx;
s->stats = stats;
s->is_server = is_server;
ssh_bpp_common_setup(&s->bpp);
return &s->bpp;
}
@ -216,6 +218,10 @@ void ssh2_bpp_new_incoming_crypto(
/* Clear the pending_newkeys flag, so that handle_input below will
* start consuming the input data again. */
s->pending_newkeys = FALSE;
/* And schedule a run of handle_input, in case there's already
* input data in the queue. */
queue_idempotent_callback(&s->bpp.ic_in_raw);
}
int ssh2_bpp_rekey_inadvisable(BinaryPacketProtocol *bpp)
@ -227,6 +233,24 @@ int ssh2_bpp_rekey_inadvisable(BinaryPacketProtocol *bpp)
return s->pending_compression;
}
static void ssh2_bpp_enable_pending_compression(struct ssh2_bpp_state *s)
{
BinaryPacketProtocol *bpp = &s->bpp; /* for bpp_logevent */
if (s->in.pending_compression) {
s->in_decomp = ssh_decompressor_new(s->in.pending_compression);
bpp_logevent(("Initialised delayed %s decompression",
ssh_decompressor_alg(s->in_decomp)->text_name));
s->in.pending_compression = NULL;
}
if (s->out.pending_compression) {
s->out_comp = ssh_compressor_new(s->out.pending_compression);
bpp_logevent(("Initialised delayed %s compression",
ssh_compressor_alg(s->out_comp)->text_name));
s->out.pending_compression = NULL;
}
}
#define BPP_READ(ptr, len) do \
{ \
crMaybeWaitUntilV(s->bpp.input_eof || \
@ -571,29 +595,14 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp)
continue;
}
if (type == SSH2_MSG_USERAUTH_SUCCESS) {
if (type == SSH2_MSG_USERAUTH_SUCCESS && !s->is_server) {
/*
* Another one: if we were configured with OpenSSH's
* deferred compression which is triggered on receipt
* of USERAUTH_SUCCESS, then this is the moment to
* turn on compression.
*/
if (s->in.pending_compression) {
s->in_decomp =
ssh_decompressor_new(s->in.pending_compression);
bpp_logevent(("Initialised delayed %s decompression",
ssh_decompressor_alg(
s->in_decomp)->text_name));
s->in.pending_compression = NULL;
}
if (s->out.pending_compression) {
s->out_comp =
ssh_compressor_new(s->out.pending_compression);
bpp_logevent(("Initialised delayed %s compression",
ssh_compressor_alg(
s->out_comp)->text_name));
s->out.pending_compression = NULL;
}
ssh2_bpp_enable_pending_compression(s);
/*
* Whether or not we were doing delayed compression in
@ -869,13 +878,15 @@ static void ssh2_bpp_handle_output(BinaryPacketProtocol *bpp)
}
while ((pkt = pq_pop(&s->bpp.out_pq)) != NULL) {
if (userauth_range(pkt->type))
int type = pkt->type;
if (userauth_range(type))
n_userauth--;
ssh2_bpp_format_packet(s, pkt);
ssh_free_pktout(pkt);
if (n_userauth == 0 && s->out.pending_compression) {
if (n_userauth == 0 && s->out.pending_compression && !s->is_server) {
/*
* This is the last userauth packet in the queue, so
* unless our side decides to send another one in future,
@ -885,6 +896,8 @@ static void ssh2_bpp_handle_output(BinaryPacketProtocol *bpp)
*/
s->pending_compression = TRUE;
return;
} else if (type == SSH2_MSG_USERAUTH_SUCCESS && s->is_server) {
ssh2_bpp_enable_pending_compression(s);
}
}
}

View File

@ -301,6 +301,17 @@ SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan)
return &c->sc;
}
SshChannel *ssh2_serverside_x11_open(
ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi)
{
assert(FALSE && "Should never be called in the client");
}
SshChannel *ssh2_serverside_agent_open(ConnectionLayer *cl, Channel *chan)
{
assert(FALSE && "Should never be called in the client");
}
static void ssh2_channel_response(
struct ssh2_channel *c, PktIn *pkt, void *ctx)
{

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");
}

View File

@ -69,6 +69,8 @@ static const struct ConnectionLayerVtable ssh2_connlayer_vtable = {
ssh2_rportfwd_remove,
ssh2_lportfwd_open,
ssh2_session_open,
ssh2_serverside_x11_open,
ssh2_serverside_agent_open,
ssh2_add_x11_display,
ssh2_add_sharing_x11_display,
ssh2_remove_sharing_x11_display,

View File

@ -163,8 +163,10 @@ struct ssh_rportfwd *ssh2_rportfwd_alloc(
ssh_sharing_connstate *share_ctx);
void ssh2_rportfwd_remove(
ConnectionLayer *cl, struct ssh_rportfwd *rpf);
SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan);
SshChannel *ssh2_serverside_x11_open(
ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi);
SshChannel *ssh2_serverside_agent_open(ConnectionLayer *cl, Channel *chan);
void ssh2channel_send_exit_status(SshChannel *c, int status);
void ssh2channel_send_exit_signal(

289
ssh2kex-server.c Normal file
View File

@ -0,0 +1,289 @@
/*
* Server side of key exchange for the SSH-2 transport protocol (RFC 4253).
*/
#include <assert.h>
#include "putty.h"
#include "ssh.h"
#include "sshbpp.h"
#include "sshppl.h"
#include "sshcr.h"
#include "storage.h"
#include "ssh2transport.h"
void ssh2_transport_provide_hostkeys(PacketProtocolLayer *ppl,
ssh_key *const *hostkeys, int nhostkeys)
{
struct ssh2_transport_state *s =
container_of(ppl, struct ssh2_transport_state, ppl);
s->hostkeys = hostkeys;
s->nhostkeys = nhostkeys;
}
static strbuf *finalise_and_sign_exhash(struct ssh2_transport_state *s)
{
strbuf *sb;
ssh2transport_finalise_exhash(s);
sb = strbuf_new();
ssh_key_sign(s->hkey, s->exchange_hash, s->kex_alg->hash->hlen,
BinarySink_UPCAST(sb));
return sb;
}
static void no_progress(void *param, int action, int phase, int iprogress)
{
}
void ssh2kex_coroutine(struct ssh2_transport_state *s)
{
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
PktIn *pktin;
PktOut *pktout;
crBegin(s->crStateKex);
{
int i;
for (i = 0; i < s->nhostkeys; i++)
if (ssh_key_alg(s->hostkeys[i]) == s->hostkey_alg) {
s->hkey = s->hostkeys[i];
break;
}
assert(s->hkey);
}
s->hostkeyblob->len = 0;
ssh_key_public_blob(s->hkey, BinarySink_UPCAST(s->hostkeyblob));
s->hostkeydata = ptrlen_from_strbuf(s->hostkeyblob);
put_stringpl(s->exhash, s->hostkeydata);
if (s->kex_alg->main_type == KEXTYPE_DH) {
/*
* If we're doing Diffie-Hellman group exchange, start by
* waiting for the group request.
*/
if (dh_is_gex(s->kex_alg)) {
ppl_logevent(("Doing Diffie-Hellman group exchange"));
s->ppl.bpp->pls->kctx = SSH2_PKTCTX_DHGEX;
crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
if (pktin->type != SSH2_MSG_KEX_DH_GEX_REQUEST &&
pktin->type != SSH2_MSG_KEX_DH_GEX_REQUEST_OLD) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
"expecting Diffie-Hellman group exchange "
"request, type %d (%s)", pktin->type,
ssh2_pkt_type(s->ppl.bpp->pls->kctx,
s->ppl.bpp->pls->actx,
pktin->type));
return;
}
if (pktin->type != SSH2_MSG_KEX_DH_GEX_REQUEST_OLD) {
s->dh_got_size_bounds = TRUE;
s->dh_min_size = get_uint32(pktin);
s->pbits = get_uint32(pktin);
s->dh_max_size = get_uint32(pktin);
} else {
s->dh_got_size_bounds = FALSE;
s->pbits = get_uint32(pktin);
}
/*
* This is a hopeless strategy for making a secure DH
* group! It's good enough for testing a client against,
* but not for serious use.
*/
s->p = primegen(s->pbits, 2, 2, NULL, 1, no_progress, NULL, 1);
s->g = bignum_from_long(2);
s->dh_ctx = dh_setup_gex(s->p, s->g);
s->kex_init_value = SSH2_MSG_KEX_DH_GEX_INIT;
s->kex_reply_value = SSH2_MSG_KEX_DH_GEX_REPLY;
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEX_DH_GEX_GROUP);
put_mp_ssh2(pktout, s->p);
put_mp_ssh2(pktout, s->g);
pq_push(s->ppl.out_pq, pktout);
} else {
s->ppl.bpp->pls->kctx = SSH2_PKTCTX_DHGROUP;
s->dh_ctx = dh_setup_group(s->kex_alg);
s->kex_init_value = SSH2_MSG_KEXDH_INIT;
s->kex_reply_value = SSH2_MSG_KEXDH_REPLY;
ppl_logevent(("Using Diffie-Hellman with standard group \"%s\"",
s->kex_alg->groupname));
}
ppl_logevent(("Doing Diffie-Hellman key exchange with hash %s",
s->kex_alg->hash->text_name));
/*
* Generate e for Diffie-Hellman.
*/
s->e = dh_create_e(s->dh_ctx, s->nbits * 2);
/*
* Wait to receive f.
*/
crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
if (pktin->type != s->kex_init_value) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
"expecting Diffie-Hellman initial packet, "
"type %d (%s)", pktin->type,
ssh2_pkt_type(s->ppl.bpp->pls->kctx,
s->ppl.bpp->pls->actx,
pktin->type));
return;
}
s->f = get_mp_ssh2(pktin);
if (get_err(pktin)) {
ssh_proto_error(s->ppl.ssh,
"Unable to parse Diffie-Hellman initial packet");
return;
}
{
const char *err = dh_validate_f(s->dh_ctx, s->f);
if (err) {
ssh_proto_error(s->ppl.ssh, "Diffie-Hellman initial packet "
"failed validation: %s", err);
return;
}
}
s->K = dh_find_K(s->dh_ctx, s->f);
if (dh_is_gex(s->kex_alg)) {
if (s->dh_got_size_bounds)
put_uint32(s->exhash, s->dh_min_size);
put_uint32(s->exhash, s->pbits);
if (s->dh_got_size_bounds)
put_uint32(s->exhash, s->dh_max_size);
put_mp_ssh2(s->exhash, s->p);
put_mp_ssh2(s->exhash, s->g);
}
put_mp_ssh2(s->exhash, s->f);
put_mp_ssh2(s->exhash, s->e);
pktout = ssh_bpp_new_pktout(s->ppl.bpp, s->kex_reply_value);
put_stringpl(pktout, s->hostkeydata);
put_mp_ssh2(pktout, s->e);
put_stringsb(pktout, finalise_and_sign_exhash(s));
pq_push(s->ppl.out_pq, pktout);
dh_cleanup(s->dh_ctx);
s->dh_ctx = NULL;
freebn(s->f); s->f = NULL;
if (dh_is_gex(s->kex_alg)) {
freebn(s->g); s->g = NULL;
freebn(s->p); s->p = NULL;
}
} else if (s->kex_alg->main_type == KEXTYPE_ECDH) {
ppl_logevent(("Doing ECDH key exchange with curve %s and hash %s",
ssh_ecdhkex_curve_textname(s->kex_alg),
s->kex_alg->hash->text_name));
s->ppl.bpp->pls->kctx = SSH2_PKTCTX_ECDHKEX;
s->ecdh_key = ssh_ecdhkex_newkey(s->kex_alg);
if (!s->ecdh_key) {
ssh_sw_abort(s->ppl.ssh, "Unable to generate key for ECDH");
return;
}
crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
if (pktin->type != SSH2_MSG_KEX_ECDH_INIT) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
"expecting ECDH initial packet, type %d (%s)",
pktin->type,
ssh2_pkt_type(s->ppl.bpp->pls->kctx,
s->ppl.bpp->pls->actx,
pktin->type));
return;
}
{
ptrlen keydata = get_string(pktin);
put_stringpl(s->exhash, keydata);
s->K = ssh_ecdhkex_getkey(s->ecdh_key, keydata.ptr, keydata.len);
if (!get_err(pktin) && !s->K) {
ssh_proto_error(s->ppl.ssh, "Received invalid elliptic curve "
"point in ECDH initial packet");
return;
}
}
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEX_ECDH_REPLY);
put_stringpl(pktout, s->hostkeydata);
{
strbuf *pubpoint = strbuf_new();
ssh_ecdhkex_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint));
put_string(s->exhash, pubpoint->u, pubpoint->len);
put_stringsb(pktout, pubpoint);
}
put_stringsb(pktout, finalise_and_sign_exhash(s));
pq_push(s->ppl.out_pq, pktout);
ssh_ecdhkex_freekey(s->ecdh_key);
s->ecdh_key = NULL;
} else if (s->kex_alg->main_type == KEXTYPE_GSS) {
ssh_sw_abort(s->ppl.ssh, "GSS key exchange not supported in server");
} else {
assert(s->kex_alg->main_type == KEXTYPE_RSA);
ppl_logevent(("Doing RSA key exchange with hash %s",
s->kex_alg->hash->text_name));
s->ppl.bpp->pls->kctx = SSH2_PKTCTX_RSAKEX;
{
const struct ssh_rsa_kex_extra *extra =
(const struct ssh_rsa_kex_extra *)s->kex_alg->extra;
s->rsa_kex_key = snew(struct RSAKey);
rsa_generate(s->rsa_kex_key, extra->minklen, no_progress, NULL);
s->rsa_kex_key->comment = NULL;
}
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXRSA_PUBKEY);
put_stringpl(pktout, s->hostkeydata);
{
strbuf *pubblob = strbuf_new();
ssh_key_public_blob(&s->rsa_kex_key->sshk,
BinarySink_UPCAST(pubblob));
put_string(s->exhash, pubblob->u, pubblob->len);
put_stringsb(pktout, pubblob);
}
pq_push(s->ppl.out_pq, pktout);
crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
if (pktin->type != SSH2_MSG_KEXRSA_SECRET) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
"expecting RSA kex secret, type %d (%s)",
pktin->type,
ssh2_pkt_type(s->ppl.bpp->pls->kctx,
s->ppl.bpp->pls->actx,
pktin->type));
return;
}
{
ptrlen encrypted_secret = get_string(pktin);
put_stringpl(s->exhash, encrypted_secret);
s->K = ssh_rsakex_decrypt(
s->kex_alg->hash, encrypted_secret, s->rsa_kex_key);
}
if (!s->K) {
ssh_proto_error(s->ppl.ssh, "Unable to decrypt RSA kex secret");
return;
}
ssh_rsakex_freekey(s->rsa_kex_key);
s->rsa_kex_key = NULL;
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXRSA_DONE);
put_stringsb(pktout, finalise_and_sign_exhash(s));
pq_push(s->ppl.out_pq, pktout);
}
crFinishV;
}

View File

@ -108,8 +108,8 @@ PacketProtocolLayer *ssh2_transport_new(
Conf *conf, const char *host, int port, const char *fullhostname,
const char *client_greeting, const char *server_greeting,
struct ssh_connection_shared_gss_state *shgss,
struct DataTransferStats *stats,
PacketProtocolLayer *higher_layer)
struct DataTransferStats *stats, PacketProtocolLayer *higher_layer,
int is_server)
{
struct ssh2_transport_state *s = snew(struct ssh2_transport_state);
memset(s, 0, sizeof(*s));
@ -123,6 +123,7 @@ PacketProtocolLayer *ssh2_transport_new(
s->client_greeting = dupstr(client_greeting);
s->server_greeting = dupstr(server_greeting);
s->stats = stats;
s->hostkeyblob = strbuf_new();
pq_in_init(&s->pq_in_higher);
pq_out_init(&s->pq_out_higher);
@ -142,8 +143,17 @@ PacketProtocolLayer *ssh2_transport_new(
s->thc = ssh_transient_hostkey_cache_new();
s->gss_kex_used = FALSE;
s->client_kexinit = strbuf_new();
s->server_kexinit = strbuf_new();
s->outgoing_kexinit = strbuf_new();
s->incoming_kexinit = strbuf_new();
if (is_server) {
s->client_kexinit = s->incoming_kexinit;
s->server_kexinit = s->outgoing_kexinit;
s->out.mkkey_adjust = 1;
} else {
s->client_kexinit = s->outgoing_kexinit;
s->server_kexinit = s->incoming_kexinit;
s->in.mkkey_adjust = 1;
}
ssh2_transport_set_max_data_size(s);
@ -184,8 +194,9 @@ static void ssh2_transport_free(PacketProtocolLayer *ppl)
sfree(s->server_greeting);
sfree(s->keystr);
sfree(s->hostkey_str);
strbuf_free(s->hostkeyblob);
sfree(s->fingerprint);
if (s->hkey) {
if (s->hkey && !s->hostkeys) {
ssh_key_free(s->hkey);
s->hkey = NULL;
}
@ -201,8 +212,8 @@ static void ssh2_transport_free(PacketProtocolLayer *ppl)
ssh_ecdhkex_freekey(s->ecdh_key);
if (s->exhash)
ssh_hash_free(s->exhash);
strbuf_free(s->client_kexinit);
strbuf_free(s->server_kexinit);
strbuf_free(s->outgoing_kexinit);
strbuf_free(s->incoming_kexinit);
ssh_transient_hostkey_cache_free(s->thc);
sfree(s);
}
@ -395,6 +406,7 @@ static void ssh2_write_kexinit_lists(
Conf *conf, int remote_bugs,
const char *hk_host, int hk_port, const ssh_keyalg *hk_prev,
ssh_transient_hostkey_cache *thc,
ssh_key *const *our_hostkeys, int our_nhostkeys,
int first_time, int can_gssapi_keyex, int transient_hostkey_mode)
{
int i, j, k, warn;
@ -522,7 +534,18 @@ static void ssh2_write_kexinit_lists(
}
}
/* List server host key algorithms. */
if (first_time) {
if (our_hostkeys) {
/*
* In server mode, we just list the algorithms that match the
* host keys we actually have.
*/
for (i = 0; i < our_nhostkeys; i++) {
alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY],
ssh_key_alg(our_hostkeys[i])->ssh_id);
alg->u.hk.hostkey = ssh_key_alg(our_hostkeys[i]);
alg->u.hk.warn = FALSE;
}
} else if (first_time) {
/*
* In the first key exchange, we list all the algorithms
* we're prepared to cope with, but prefer those algorithms
@ -1016,28 +1039,29 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
* later.
*/
s->client_kexinit->len = 0;
put_byte(s->client_kexinit, SSH2_MSG_KEXINIT);
put_byte(s->outgoing_kexinit, SSH2_MSG_KEXINIT);
{
int i;
for (i = 0; i < 16; i++)
put_byte(s->client_kexinit, (unsigned char) random_byte());
put_byte(s->outgoing_kexinit, (unsigned char) random_byte());
}
ssh2_write_kexinit_lists(
BinarySink_UPCAST(s->client_kexinit), s->kexlists,
BinarySink_UPCAST(s->outgoing_kexinit), s->kexlists,
s->conf, s->ppl.remote_bugs,
s->savedhost, s->savedport, s->hostkey_alg, s->thc,
s->hostkeys, s->nhostkeys,
!s->got_session_id, s->can_gssapi_keyex,
s->gss_kex_used && !s->need_gss_transient_hostkey);
/* First KEX packet does _not_ follow, because we're not that brave. */
put_bool(s->client_kexinit, FALSE);
put_uint32(s->client_kexinit, 0); /* reserved */
put_bool(s->outgoing_kexinit, FALSE);
put_uint32(s->outgoing_kexinit, 0); /* reserved */
/*
* Send our KEXINIT.
*/
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXINIT);
put_data(pktout, s->client_kexinit->u + 1,
s->client_kexinit->len - 1); /* omit initial packet type byte */
put_data(pktout, s->outgoing_kexinit->u + 1,
s->outgoing_kexinit->len - 1); /* omit initial packet type byte */
pq_push(s->ppl.out_pq, pktout);
/*
@ -1056,9 +1080,9 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
s->ppl.bpp->pls->actx, pktin->type));
return;
}
s->server_kexinit->len = 0;
put_byte(s->server_kexinit, SSH2_MSG_KEXINIT);
put_data(s->server_kexinit, get_ptr(pktin), get_avail(pktin));
s->incoming_kexinit->len = 0;
put_byte(s->incoming_kexinit, SSH2_MSG_KEXINIT);
put_data(s->incoming_kexinit, get_ptr(pktin), get_avail(pktin));
/*
* Work through the two KEXINIT packets in parallel to find the
@ -1241,8 +1265,8 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
ssh_bpp_handle_output(s->ppl.bpp);
/*
* We've sent client NEWKEYS, so create and initialise
* client-to-server session keys.
* We've sent outgoing NEWKEYS, so create and initialise outgoing
* session keys.
*/
{
strbuf *cipher_key = strbuf_new();
@ -1250,14 +1274,15 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
strbuf *mac_key = strbuf_new();
if (s->out.cipher) {
ssh2_mkkey(s, cipher_iv, s->K, s->exchange_hash, 'A',
s->out.cipher->blksize);
ssh2_mkkey(s, cipher_key, s->K, s->exchange_hash, 'C',
ssh2_mkkey(s, cipher_iv, s->K, s->exchange_hash,
'A' + s->out.mkkey_adjust, s->out.cipher->blksize);
ssh2_mkkey(s, cipher_key, s->K, s->exchange_hash,
'C' + s->out.mkkey_adjust,
s->out.cipher->padded_keybytes);
}
if (s->out.mac) {
ssh2_mkkey(s, mac_key, s->K, s->exchange_hash, 'E',
s->out.mac->keylen);
ssh2_mkkey(s, mac_key, s->K, s->exchange_hash,
'E' + s->out.mkkey_adjust, s->out.mac->keylen);
}
ssh2_bpp_new_outgoing_crypto(
@ -1296,8 +1321,8 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
s->stats->in.remaining = s->max_data_size;
/*
* We've seen server NEWKEYS, so create and initialise
* server-to-client session keys.
* We've seen incoming NEWKEYS, so create and initialise
* incoming session keys.
*/
{
strbuf *cipher_key = strbuf_new();
@ -1305,14 +1330,15 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
strbuf *mac_key = strbuf_new();
if (s->in.cipher) {
ssh2_mkkey(s, cipher_iv, s->K, s->exchange_hash, 'B',
s->in.cipher->blksize);
ssh2_mkkey(s, cipher_key, s->K, s->exchange_hash, 'D',
ssh2_mkkey(s, cipher_iv, s->K, s->exchange_hash,
'A' + s->in.mkkey_adjust, s->in.cipher->blksize);
ssh2_mkkey(s, cipher_key, s->K, s->exchange_hash,
'C' + s->in.mkkey_adjust,
s->in.cipher->padded_keybytes);
}
if (s->in.mac) {
ssh2_mkkey(s, mac_key, s->K, s->exchange_hash, 'F',
s->in.mac->keylen);
ssh2_mkkey(s, mac_key, s->K, s->exchange_hash,
'E' + s->in.mkkey_adjust, s->in.mac->keylen);
}
ssh2_bpp_new_incoming_crypto(
@ -1364,14 +1390,43 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
* decided to initiate a rekey ourselves for some reason.
*/
if (!s->higher_layer_ok) {
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_SERVICE_REQUEST);
put_stringz(pktout, s->higher_layer->vt->name);
pq_push(s->ppl.out_pq, pktout);
crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
if (pktin->type != SSH2_MSG_SERVICE_ACCEPT) {
ssh_sw_abort(s->ppl.ssh, "Server refused request to start "
"'%s' protocol", s->higher_layer->vt->name);
return;
if (!s->hostkeys) {
/* We're the client, so send SERVICE_REQUEST. */
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_SERVICE_REQUEST);
put_stringz(pktout, s->higher_layer->vt->name);
pq_push(s->ppl.out_pq, pktout);
crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
if (pktin->type != SSH2_MSG_SERVICE_ACCEPT) {
ssh_sw_abort(s->ppl.ssh, "Server refused request to start "
"'%s' protocol", s->higher_layer->vt->name);
return;
}
} else {
ptrlen service_name;
/* We're the server, so expect SERVICE_REQUEST. */
crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
if (pktin->type != SSH2_MSG_SERVICE_REQUEST) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
"expecting SERVICE_REQUEST, type %d (%s)",
pktin->type,
ssh2_pkt_type(s->ppl.bpp->pls->kctx,
s->ppl.bpp->pls->actx,
pktin->type));
return;
}
service_name = get_string(pktin);
if (!ptrlen_eq_string(service_name, s->higher_layer->vt->name)) {
ssh_proto_error(s->ppl.ssh, "Client requested service "
"'%.*s' when we only support '%s'",
PTRLEN_PRINTF(service_name),
s->higher_layer->vt->name);
return;
}
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_SERVICE_ACCEPT);
put_stringz(pktout, s->higher_layer->vt->name);
pq_push(s->ppl.out_pq, pktout);
}
s->higher_layer_ok = TRUE;

View File

@ -108,6 +108,7 @@ typedef struct transport_direction {
int etm_mode;
const struct ssh_compression_alg *comp;
int comp_delayed;
int mkkey_adjust;
} transport_direction;
struct ssh2_transport_state {
@ -131,6 +132,7 @@ struct ssh2_transport_state {
char *hostkey_str; /* string representation, for easy checking in rekeys */
unsigned char session_id[SSH2_KEX_MAX_HASH_LEN];
int session_id_len;
int dh_min_size, dh_max_size, dh_got_size_bounds;
struct dh_ctx *dh_ctx;
ssh_hash *exhash;
@ -163,10 +165,12 @@ struct ssh2_transport_state {
int nbits, pbits, warn_kex, warn_hk, warn_cscipher, warn_sccipher;
Bignum p, g, e, f, K;
strbuf *client_kexinit, *server_kexinit;
strbuf *outgoing_kexinit, *incoming_kexinit;
strbuf *client_kexinit, *server_kexinit; /* aliases to the above */
int kex_init_value, kex_reply_value;
transport_direction in, out;
ptrlen hostkeydata, sigdata;
strbuf *hostkeyblob;
char *keystr, *fingerprint;
ssh_key *hkey; /* actual host key */
struct RSAKey *rsa_kex_key; /* for RSA kex */
@ -203,6 +207,9 @@ struct ssh2_transport_state {
*/
int cross_certifying;
ssh_key *const *hostkeys;
int nhostkeys;
PacketProtocolLayer ppl;
};

269
ssh2userauth-server.c Normal file
View File

@ -0,0 +1,269 @@
/*
* Packet protocol layer for the server side of the SSH-2 userauth
* protocol (RFC 4252).
*/
#include <assert.h>
#include "putty.h"
#include "ssh.h"
#include "sshbpp.h"
#include "sshppl.h"
#include "sshcr.h"
#include "sshserver.h"
#ifndef NO_GSSAPI
#include "sshgssc.h"
#include "sshgss.h"
#endif
struct ssh2_userauth_server_state {
int crState;
PacketProtocolLayer *transport_layer, *successor_layer;
ptrlen session_id;
AuthPolicy *authpolicy;
ptrlen username, service, method;
unsigned methods, this_method;
int partial_success;
PacketProtocolLayer ppl;
};
static void ssh2_userauth_server_free(PacketProtocolLayer *);
static void ssh2_userauth_server_process_queue(PacketProtocolLayer *);
static const struct PacketProtocolLayerVtable ssh2_userauth_server_vtable = {
ssh2_userauth_server_free,
ssh2_userauth_server_process_queue,
NULL /* get_specials */,
NULL /* special_cmd */,
NULL /* want_user_input */,
NULL /* got_user_input */,
NULL /* reconfigure */,
"ssh-userauth",
};
PacketProtocolLayer *ssh2_userauth_server_new(
PacketProtocolLayer *successor_layer, AuthPolicy *authpolicy)
{
struct ssh2_userauth_server_state *s =
snew(struct ssh2_userauth_server_state);
memset(s, 0, sizeof(*s));
s->ppl.vt = &ssh2_userauth_server_vtable;
s->successor_layer = successor_layer;
s->authpolicy = authpolicy;
return &s->ppl;
}
void ssh2_userauth_server_set_transport_layer(PacketProtocolLayer *userauth,
PacketProtocolLayer *transport)
{
struct ssh2_userauth_server_state *s =
container_of(userauth, struct ssh2_userauth_server_state, ppl);
s->transport_layer = transport;
}
static void ssh2_userauth_server_free(PacketProtocolLayer *ppl)
{
struct ssh2_userauth_server_state *s =
container_of(ppl, struct ssh2_userauth_server_state, ppl);
if (s->successor_layer)
ssh_ppl_free(s->successor_layer);
sfree(s);
}
static PktIn *ssh2_userauth_server_pop(struct ssh2_userauth_server_state *s)
{
return pq_pop(s->ppl.in_pq);
}
static void ssh2_userauth_server_add_session_id(
struct ssh2_userauth_server_state *s, strbuf *sigdata)
{
if (s->ppl.remote_bugs & BUG_SSH2_PK_SESSIONID) {
put_data(sigdata, s->session_id.ptr, s->session_id.len);
} else {
put_stringpl(sigdata, s->session_id);
}
}
static void ssh2_userauth_server_process_queue(PacketProtocolLayer *ppl)
{
struct ssh2_userauth_server_state *s =
container_of(ppl, struct ssh2_userauth_server_state, ppl);
PktIn *pktin;
PktOut *pktout;
crBegin(s->crState);
s->session_id = ssh2_transport_get_session_id(s->transport_layer);
while (1) {
crMaybeWaitUntilV((pktin = ssh2_userauth_server_pop(s)) != NULL);
if (pktin->type != SSH2_MSG_USERAUTH_REQUEST) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
"expecting USERAUTH_REQUEST, type %d (%s)",
pktin->type,
ssh2_pkt_type(s->ppl.bpp->pls->kctx,
s->ppl.bpp->pls->actx, pktin->type));
return;
}
s->username = get_string(pktin);
s->service = get_string(pktin);
s->method = get_string(pktin);
if (!ptrlen_eq_string(s->service, s->successor_layer->vt->name)) {
/*
* Unconditionally reject authentication for any service
* other than the one we're going to hand over to.
*/
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_FAILURE);
put_stringz(pktout, "");
put_bool(pktout, FALSE);
pq_push(s->ppl.out_pq, pktout);
continue;
}
s->methods = auth_methods(s->authpolicy);
s->partial_success = FALSE;
if (ptrlen_eq_string(s->method, "none")) {
s->this_method = AUTHMETHOD_NONE;
if (!(s->methods & s->this_method))
goto failure;
if (!auth_none(s->authpolicy, s->username))
goto failure;
} else if (ptrlen_eq_string(s->method, "password")) {
int changing;
ptrlen password;
s->this_method = AUTHMETHOD_PASSWORD;
if (!(s->methods & s->this_method))
goto failure;
changing = get_bool(pktin);
if (changing)
goto failure; /* FIXME: not yet supported */
password = get_string(pktin);
if (!auth_password(s->authpolicy, s->username, password))
goto failure;
} else if (ptrlen_eq_string(s->method, "publickey")) {
int has_signature, success;
ptrlen algorithm, blob, signature;
const ssh_keyalg *keyalg;
ssh_key *key;
strbuf *sigdata;
s->this_method = AUTHMETHOD_PUBLICKEY;
if (!(s->methods & s->this_method))
goto failure;
has_signature = get_bool(pktin);
algorithm = get_string(pktin);
blob = get_string(pktin);
if (!auth_publickey(s->authpolicy, s->username, blob))
goto failure;
keyalg = find_pubkey_alg_len(algorithm);
if (!keyalg)
goto failure;
key = ssh_key_new_pub(keyalg, blob);
if (!key)
goto failure;
if (!has_signature) {
ssh_key_free(key);
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, SSH2_MSG_USERAUTH_PK_OK);
put_stringpl(pktout, algorithm);
put_stringpl(pktout, blob);
pq_push(s->ppl.out_pq, pktout);
continue; /* skip USERAUTH_{SUCCESS,FAILURE} epilogue */
}
sigdata = strbuf_new();
ssh2_userauth_server_add_session_id(s, sigdata);
put_byte(sigdata, SSH2_MSG_USERAUTH_REQUEST);
put_stringpl(sigdata, s->username);
put_stringpl(sigdata, s->service);
put_stringpl(sigdata, s->method);
put_bool(sigdata, has_signature);
put_stringpl(sigdata, algorithm);
put_stringpl(sigdata, blob);
signature = get_string(pktin);
success = ssh_key_verify(key, signature,
ptrlen_from_strbuf(sigdata));
ssh_key_free(key);
strbuf_free(sigdata);
if (!success)
goto failure;
} else {
goto failure;
}
/*
* If we get here, we've successfully completed this
* authentication step.
*/
if (auth_successful(s->authpolicy, s->username, s->this_method)) {
/*
* ... and it was the last one, so we're completely done.
*/
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_SUCCESS);
pq_push(s->ppl.out_pq, pktout);
break;
} else {
/*
* ... but another is required, so fall through to
* generation of USERAUTH_FAILURE, having first refreshed
* the bit mask of available methods.
*/
s->methods = auth_methods(s->authpolicy);
}
s->partial_success = TRUE;
failure:
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_FAILURE);
{
strbuf *list = strbuf_new();
if (s->methods & AUTHMETHOD_NONE)
add_to_commasep(list, "none");
if (s->methods & AUTHMETHOD_PASSWORD)
add_to_commasep(list, "password");
if (s->methods & AUTHMETHOD_PUBLICKEY)
add_to_commasep(list, "publickey");
put_stringsb(pktout, list);
}
put_bool(pktout, s->partial_success);
pq_push(s->ppl.out_pq, pktout);
}
/*
* Finally, hand over to our successor layer, and return
* immediately without reaching the crFinishV: ssh_ppl_replace
* will have freed us, so crFinishV's zeroing-out of crState would
* be a use-after-free bug.
*/
{
PacketProtocolLayer *successor = s->successor_layer;
s->successor_layer = NULL; /* avoid freeing it ourself */
ssh_ppl_replace(&s->ppl, successor);
return; /* we've just freed s, so avoid even touching s->crState */
}
crFinishV;
}

View File

@ -1,5 +1,6 @@
/*
* Packet protocol layer for the SSH-2 userauth protocol (RFC 4252).
* Packet protocol layer for the client side of the SSH-2 userauth
* protocol (RFC 4252).
*/
#include <assert.h>

View File

@ -56,6 +56,10 @@ BinaryPacketProtocol *ssh1_bpp_new(LogContext *logctx);
void ssh1_bpp_new_cipher(BinaryPacketProtocol *bpp,
const struct ssh1_cipheralg *cipher,
const void *session_key);
/* This is only called from outside the BPP in server mode; in client
* mode the BPP detects compression start time automatically by
* snooping message types */
void ssh1_bpp_start_compression(BinaryPacketProtocol *bpp);
/* Helper routine which does common BPP initialisation, e.g. setting
* up in_pq and out_pq, and initialising input_consumer. */
@ -99,7 +103,7 @@ struct DataTransferStats {
((stats)->direction.remaining -= (size), FALSE))
BinaryPacketProtocol *ssh2_bpp_new(
LogContext *logctx, struct DataTransferStats *stats);
LogContext *logctx, struct DataTransferStats *stats, int is_server);
void ssh2_bpp_new_outgoing_crypto(
BinaryPacketProtocol *bpp,
const struct ssh2_cipheralg *cipher, const void *ckey, const void *iv,
@ -137,7 +141,7 @@ struct ssh_version_receiver {
BinaryPacketProtocol *ssh_verstring_new(
Conf *conf, LogContext *logctx, int bare_connection_mode,
const char *protoversion, struct ssh_version_receiver *rcv,
const char *impl_name);
int server_mode, const char *impl_name);
const char *ssh_verstring_get_remote(BinaryPacketProtocol *);
const char *ssh_verstring_get_local(BinaryPacketProtocol *);
int ssh_verstring_get_bugs(BinaryPacketProtocol *);

View File

@ -96,8 +96,8 @@ PacketProtocolLayer *ssh2_transport_new(
Conf *conf, const char *host, int port, const char *fullhostname,
const char *client_greeting, const char *server_greeting,
struct ssh_connection_shared_gss_state *shgss,
struct DataTransferStats *stats,
PacketProtocolLayer *higher_layer);
struct DataTransferStats *stats, PacketProtocolLayer *higher_layer,
int is_server);
PacketProtocolLayer *ssh2_userauth_new(
PacketProtocolLayer *successor_layer,
const char *hostname, const char *fullhostname,
@ -135,7 +135,8 @@ ptrlen ssh2_transport_get_session_id(PacketProtocolLayer *ssh2_transport_ptr);
void ssh2_transport_notify_auth_done(PacketProtocolLayer *ssh2_transport_ptr);
/* Methods for ssh1login to pass protocol flags to ssh1connection */
void ssh1_connection_set_local_protoflags(PacketProtocolLayer *ppl, int flags);
void ssh1_connection_set_protoflags(
PacketProtocolLayer *ppl, int local, int remote);
/* Shared get_specials method between the two ssh1 layers */
int ssh1_common_get_specials(PacketProtocolLayer *, add_special_fn_t, void *);
@ -146,4 +147,8 @@ void ssh1_compute_session_id(
unsigned char *session_id, const unsigned char *cookie,
struct RSAKey *hostkey, struct RSAKey *servkey);
/* Method used by the SSH server */
void ssh2_transport_provide_hostkeys(PacketProtocolLayer *ssh2_transport_ptr,
ssh_key *const *hostkeys, int nhostkeys);
#endif /* PUTTY_SSHPPL_H */

464
sshserver.c Normal file
View File

@ -0,0 +1,464 @@
/*
* Top-level code for SSH server implementation.
*/
#include <assert.h>
#include <stddef.h>
#include "putty.h"
#include "ssh.h"
#include "sshbpp.h"
#include "sshppl.h"
#include "sshserver.h"
#ifndef NO_GSSAPI
#include "sshgssc.h"
#include "sshgss.h"
#endif
struct Ssh { int dummy; };
typedef struct server server;
struct server {
bufchain in_raw, out_raw;
IdempotentCallback ic_out_raw;
bufchain dummy_user_input; /* we never put anything on this */
PacketLogSettings pls;
LogContext *logctx;
struct DataTransferStats stats;
int remote_bugs;
Socket *socket;
Plug plug;
int conn_throttle_count;
int frozen;
Conf *conf;
ssh_key *const *hostkeys;
int nhostkeys;
struct RSAKey *hostkey1;
AuthPolicy *authpolicy;
Seat seat;
Ssh ssh;
struct ssh_version_receiver version_receiver;
BinaryPacketProtocol *bpp;
PacketProtocolLayer *base_layer;
ConnectionLayer *cl;
struct ssh_connection_shared_gss_state gss_state;
};
static void ssh_server_free_callback(void *vsrv);
static void server_got_ssh_version(struct ssh_version_receiver *rcv,
int major_version);
static void server_connect_bpp(server *srv);
static void server_bpp_output_raw_data_callback(void *vctx);
void share_activate(ssh_sharing_state *sharestate,
const char *server_verstring) {}
void ssh_connshare_provide_connlayer(ssh_sharing_state *sharestate,
ConnectionLayer *cl) {}
int share_ndownstreams(ssh_sharing_state *sharestate) { return 0; }
void share_got_pkt_from_server(ssh_sharing_connstate *cs, int type,
const void *vpkt, int pktlen) {}
void share_setup_x11_channel(ssh_sharing_connstate *cs, share_channel *chan,
unsigned upstream_id, unsigned server_id,
unsigned server_currwin, unsigned server_maxpkt,
unsigned client_adjusted_window,
const char *peer_addr, int peer_port, int endian,
int protomajor, int protominor,
const void *initial_data, int initial_len) {}
Channel *agentf_new(SshChannel *c) { return NULL; }
int agent_exists(void) { return FALSE; }
void ssh_got_exitcode(Ssh *ssh, int exitcode) {}
mainchan *mainchan_new(
PacketProtocolLayer *ppl, ConnectionLayer *cl, Conf *conf,
int term_width, int term_height, int is_simple, SshChannel **sc_out)
{ return NULL; }
void mainchan_get_specials(
mainchan *mc, add_special_fn_t add_special, void *ctx) {}
void mainchan_special_cmd(mainchan *mc, SessionSpecialCode code, int arg) {}
void mainchan_terminal_size(mainchan *mc, int width, int height) {}
/* Seat functions to ensure we don't get choosy about crypto - as the
* server, it's not up to us to give user warnings */
static int server_confirm_weak_crypto_primitive(
Seat *seat, const char *algtype, const char *algname,
void (*callback)(void *ctx, int result), void *ctx) { return 1; }
static int server_confirm_weak_cached_hostkey(
Seat *seat, const char *algname, const char *betteralgs,
void (*callback)(void *ctx, int result), void *ctx) { return 1; }
static const SeatVtable server_seat_vt = {
nullseat_output,
nullseat_eof,
nullseat_get_userpass_input,
nullseat_notify_remote_exit,
nullseat_connection_fatal,
nullseat_update_specials_menu,
nullseat_get_ttymode,
nullseat_set_busy_status,
nullseat_verify_ssh_host_key,
server_confirm_weak_crypto_primitive,
server_confirm_weak_cached_hostkey,
nullseat_is_never_utf8,
nullseat_echoedit_update,
nullseat_get_x_display,
nullseat_get_windowid,
nullseat_get_window_pixel_size,
};
static void server_socket_log(Plug *plug, int type, SockAddr *addr, int port,
const char *error_msg, int error_code)
{
/* server *srv = container_of(plug, server, plug); */
/* FIXME */
}
static void server_closing(Plug *plug, const char *error_msg, int error_code,
int calling_back)
{
server *srv = container_of(plug, server, plug);
if (error_msg) {
ssh_remote_error(&srv->ssh, "Network error: %s", error_msg);
} else if (srv->bpp) {
srv->bpp->input_eof = TRUE;
queue_idempotent_callback(&srv->bpp->ic_in_raw);
}
}
static void server_receive(Plug *plug, int urgent, char *data, int len)
{
server *srv = container_of(plug, server, plug);
/* Log raw data, if we're in that mode. */
if (srv->logctx)
log_packet(srv->logctx, PKT_INCOMING, -1, NULL, data, len,
0, NULL, NULL, 0, NULL);
bufchain_add(&srv->in_raw, data, len);
if (!srv->frozen && srv->bpp)
queue_idempotent_callback(&srv->bpp->ic_in_raw);
}
static void server_sent(Plug *plug, int bufsize)
{
#ifdef FIXME
server *srv = container_of(plug, server, plug);
/*
* If the send backlog on the SSH socket itself clears, we should
* unthrottle the whole world if it was throttled. Also trigger an
* extra call to the consumer of the BPP's output, to try to send
* some more data off its bufchain.
*/
if (bufsize < SSH_MAX_BACKLOG) {
srv_throttle_all(srv, 0, bufsize);
queue_idempotent_callback(&srv->ic_out_raw);
}
#endif
}
LogContext *ssh_get_logctx(Ssh *ssh)
{
server *srv = container_of(ssh, server, ssh);
return srv->logctx;
}
void ssh_throttle_conn(Ssh *ssh, int adjust)
{
server *srv = container_of(ssh, server, ssh);
int old_count = srv->conn_throttle_count;
int frozen;
srv->conn_throttle_count += adjust;
assert(srv->conn_throttle_count >= 0);
if (srv->conn_throttle_count && !old_count) {
frozen = TRUE;
} else if (!srv->conn_throttle_count && old_count) {
frozen = FALSE;
} else {
return; /* don't change current frozen state */
}
srv->frozen = frozen;
if (srv->socket) {
sk_set_frozen(srv->socket, frozen);
/*
* Now process any SSH connection data that was stashed in our
* queue while we were frozen.
*/
queue_idempotent_callback(&srv->bpp->ic_in_raw);
}
}
static const PlugVtable ssh_server_plugvt = {
server_socket_log,
server_closing,
server_receive,
server_sent,
NULL
};
Plug *ssh_server_plug(
Conf *conf, ssh_key *const *hostkeys, int nhostkeys,
struct RSAKey *hostkey1, AuthPolicy *authpolicy, LogPolicy *logpolicy)
{
server *srv = snew(server);
memset(srv, 0, sizeof(server));
srv->plug.vt = &ssh_server_plugvt;
srv->conf = conf_copy(conf);
srv->logctx = log_init(logpolicy, conf);
conf_set_int(srv->conf, CONF_ssh_no_shell, TRUE);
srv->nhostkeys = nhostkeys;
srv->hostkeys = hostkeys;
srv->hostkey1 = hostkey1;
srv->authpolicy = authpolicy;
srv->seat.vt = &server_seat_vt;
bufchain_init(&srv->in_raw);
bufchain_init(&srv->out_raw);
bufchain_init(&srv->dummy_user_input);
/* FIXME: replace with sensible */
srv->gss_state.libs = snew(struct ssh_gss_liblist);
srv->gss_state.libs->nlibraries = 0;
return &srv->plug;
}
void ssh_server_start(Plug *plug, Socket *socket)
{
server *srv = container_of(plug, server, plug);
const char *our_protoversion;
if (srv->hostkey1 && srv->nhostkeys) {
our_protoversion = "1.99"; /* offer both SSH-1 and SSH-2 */
} else if (srv->hostkey1) {
our_protoversion = "1.5"; /* SSH-1 only */
} else {
assert(srv->nhostkeys);
our_protoversion = "2.0"; /* SSH-2 only */
}
srv->socket = socket;
srv->ic_out_raw.fn = server_bpp_output_raw_data_callback;
srv->ic_out_raw.ctx = srv;
srv->version_receiver.got_ssh_version = server_got_ssh_version;
srv->bpp = ssh_verstring_new(
srv->conf, srv->logctx, FALSE /* bare_connection */,
our_protoversion, &srv->version_receiver,
TRUE, "Uppity");
server_connect_bpp(srv);
queue_idempotent_callback(&srv->bpp->ic_in_raw);
}
static void ssh_server_free_callback(void *vsrv)
{
server *srv = (server *)vsrv;
bufchain_clear(&srv->in_raw);
bufchain_clear(&srv->out_raw);
bufchain_clear(&srv->dummy_user_input);
sk_close(srv->socket);
if (srv->bpp)
ssh_bpp_free(srv->bpp);
delete_callbacks_for_context(srv);
conf_free(srv->conf);
log_free(srv->logctx);
sfree(srv->gss_state.libs); /* FIXME: replace with sensible */
sfree(srv);
server_instance_terminated();
}
static void server_connect_bpp(server *srv)
{
srv->bpp->ssh = &srv->ssh;
srv->bpp->in_raw = &srv->in_raw;
srv->bpp->out_raw = &srv->out_raw;
srv->bpp->out_raw->ic = &srv->ic_out_raw;
srv->bpp->pls = &srv->pls;
srv->bpp->logctx = srv->logctx;
srv->bpp->remote_bugs = srv->remote_bugs;
/* Servers don't really have a notion of 'unexpected' connection
* closure. The client is free to close if it likes. */
srv->bpp->expect_close = TRUE;
}
static void server_connect_ppl(server *srv, PacketProtocolLayer *ppl)
{
ppl->bpp = srv->bpp;
ppl->user_input = &srv->dummy_user_input;
ppl->logctx = srv->logctx;
ppl->ssh = &srv->ssh;
ppl->seat = &srv->seat;
ppl->remote_bugs = srv->remote_bugs;
}
static void server_bpp_output_raw_data_callback(void *vctx)
{
server *srv = (server *)vctx;
if (!srv->socket)
return;
while (bufchain_size(&srv->out_raw) > 0) {
void *data;
int len, backlog;
bufchain_prefix(&srv->out_raw, &data, &len);
if (srv->logctx)
log_packet(srv->logctx, PKT_OUTGOING, -1, NULL, data, len,
0, NULL, NULL, 0, NULL);
backlog = sk_write(srv->socket, data, len);
bufchain_consume(&srv->out_raw, len);
if (backlog > SSH_MAX_BACKLOG) {
#ifdef FIXME
ssh_throttle_all(ssh, 1, backlog);
#endif
return;
}
}
#ifdef FIXME
if (ssh->pending_close) {
sk_close(ssh->s);
ssh->s = NULL;
}
#endif
}
#define LOG_FORMATTED_MSG(logctx, fmt) do \
{ \
va_list ap; \
va_start(ap, fmt); \
logeventvf(logctx, fmt, ap); \
va_end(ap); \
} while (0)
void ssh_remote_error(Ssh *ssh, const char *fmt, ...)
{
server *srv = container_of(ssh, server, ssh);
LOG_FORMATTED_MSG(srv->logctx, fmt);
queue_toplevel_callback(ssh_server_free_callback, srv);
}
void ssh_remote_eof(Ssh *ssh, const char *fmt, ...)
{
server *srv = container_of(ssh, server, ssh);
LOG_FORMATTED_MSG(srv->logctx, fmt);
queue_toplevel_callback(ssh_server_free_callback, srv);
}
void ssh_proto_error(Ssh *ssh, const char *fmt, ...)
{
server *srv = container_of(ssh, server, ssh);
LOG_FORMATTED_MSG(srv->logctx, fmt);
queue_toplevel_callback(ssh_server_free_callback, srv);
}
void ssh_sw_abort(Ssh *ssh, const char *fmt, ...)
{
server *srv = container_of(ssh, server, ssh);
LOG_FORMATTED_MSG(srv->logctx, fmt);
queue_toplevel_callback(ssh_server_free_callback, srv);
}
void ssh_user_close(Ssh *ssh, const char *fmt, ...)
{
server *srv = container_of(ssh, server, ssh);
LOG_FORMATTED_MSG(srv->logctx, fmt);
queue_toplevel_callback(ssh_server_free_callback, srv);
}
static void server_got_ssh_version(struct ssh_version_receiver *rcv,
int major_version)
{
server *srv = container_of(rcv, server, version_receiver);
BinaryPacketProtocol *old_bpp;
PacketProtocolLayer *connection_layer;
old_bpp = srv->bpp;
srv->remote_bugs = ssh_verstring_get_bugs(old_bpp);
if (major_version == 2) {
PacketProtocolLayer *userauth_layer, *transport_child_layer;
srv->bpp = ssh2_bpp_new(srv->logctx, &srv->stats, TRUE);
server_connect_bpp(srv);
connection_layer = ssh2_connection_new(
&srv->ssh, NULL, FALSE, srv->conf,
ssh_verstring_get_local(old_bpp), &srv->cl);
server_connect_ppl(srv, connection_layer);
if (conf_get_int(srv->conf, CONF_ssh_no_userauth)) {
userauth_layer = NULL;
transport_child_layer = connection_layer;
} else {
userauth_layer = ssh2_userauth_server_new(
connection_layer, srv->authpolicy);
server_connect_ppl(srv, userauth_layer);
transport_child_layer = userauth_layer;
}
srv->base_layer = ssh2_transport_new(
srv->conf, NULL, 0, NULL,
ssh_verstring_get_remote(old_bpp),
ssh_verstring_get_local(old_bpp),
&srv->gss_state, &srv->stats, transport_child_layer, TRUE);
ssh2_transport_provide_hostkeys(
srv->base_layer, srv->hostkeys, srv->nhostkeys);
if (userauth_layer)
ssh2_userauth_server_set_transport_layer(
userauth_layer, srv->base_layer);
server_connect_ppl(srv, srv->base_layer);
} else {
srv->bpp = ssh1_bpp_new(srv->logctx);
server_connect_bpp(srv);
connection_layer = ssh1_connection_new(&srv->ssh, srv->conf, &srv->cl);
server_connect_ppl(srv, connection_layer);
srv->base_layer = ssh1_login_server_new(
connection_layer, srv->hostkey1, srv->authpolicy);
server_connect_ppl(srv, srv->base_layer);
}
/* Connect the base layer - whichever it is - to the BPP, and set
* up its selfptr. */
srv->base_layer->selfptr = &srv->base_layer;
ssh_ppl_setup_queues(srv->base_layer, &srv->bpp->in_pq, &srv->bpp->out_pq);
#ifdef FIXME // we probably will want one of these, in the end
srv->pinger = pinger_new(srv->conf, &srv->backend);
#endif
queue_idempotent_callback(&srv->bpp->ic_in_raw);
ssh_ppl_process_queue(srv->base_layer);
ssh_bpp_free(old_bpp);
}

65
sshserver.h Normal file
View File

@ -0,0 +1,65 @@
typedef struct AuthPolicy AuthPolicy;
Plug *ssh_server_plug(
Conf *conf, ssh_key *const *hostkeys, int nhostkeys,
struct RSAKey *hostkey1, AuthPolicy *authpolicy, LogPolicy *logpolicy);
void ssh_server_start(Plug *plug, Socket *socket);
void server_instance_terminated(void);
void platform_logevent(const char *msg);
#define AUTHMETHODS(X) \
X(NONE) \
X(PASSWORD) \
X(PUBLICKEY) \
/* end of list */
#define AUTHMETHOD_BIT_INDEX(name) AUTHMETHOD_BIT_INDEX_##name,
enum { AUTHMETHODS(AUTHMETHOD_BIT_INDEX) AUTHMETHOD_BIT_INDEX_dummy };
#define AUTHMETHOD_BIT_VALUE(name) \
AUTHMETHOD_##name = 1 << AUTHMETHOD_BIT_INDEX_##name,
enum { AUTHMETHODS(AUTHMETHOD_BIT_VALUE) AUTHMETHOD_BIT_VALUE_dummy };
unsigned auth_methods(AuthPolicy *);
int auth_none(AuthPolicy *, ptrlen username);
int auth_password(AuthPolicy *, ptrlen username, ptrlen password);
int auth_publickey(AuthPolicy *, ptrlen username, ptrlen public_blob);
/* auth_publickey_ssh1 must return the whole public key given the modulus,
* because the SSH-1 client never transmits the exponent over the wire.
* The key remains owned by the AuthPolicy. */
struct RSAKey *auth_publickey_ssh1(
AuthPolicy *ap, ptrlen username, Bignum rsa_modulus);
/* auth_successful returns FALSE if further authentication is needed */
int auth_successful(AuthPolicy *, ptrlen username, unsigned method);
PacketProtocolLayer *ssh2_userauth_server_new(
PacketProtocolLayer *successor_layer, AuthPolicy *authpolicy);
void ssh2_userauth_server_set_transport_layer(
PacketProtocolLayer *userauth, PacketProtocolLayer *transport);
PacketProtocolLayer *ssh1_login_server_new(
PacketProtocolLayer *successor_layer, struct RSAKey *hostkey,
AuthPolicy *authpolicy);
Channel *sesschan_new(SshChannel *c, LogContext *logctx);
Backend *pty_backend_create(
Seat *seat, LogContext *logctx, Conf *conf, char **argv, const char *cmd,
struct ssh_ttymodes ttymodes, int pipes_instead_of_pty);
ptrlen pty_backend_exit_signame(Backend *be, char **aux_msg);
/*
* Establish a listening X server. Return value is the _number_ of
* Sockets that it established pointing at the given Plug. (0
* indicates complete failure.) The socket pointers themselves are
* written into sockets[], up to a possible total of MAX_X11_SOCKETS.
*
* The supplied Conf has necessary environment variables written into
* it. (And is also used to open the port listeners, though that
* shouldn't affect anything.)
*/
#define MAX_X11_SOCKETS 2
int platform_make_x11_server(Plug *plug, const char *progname, int mindisp,
const char *screen_number_suffix,
ptrlen authproto, ptrlen authdata,
Socket **sockets, Conf *conf);

View File

@ -61,7 +61,7 @@ static int ssh_version_includes_v2(const char *ver);
BinaryPacketProtocol *ssh_verstring_new(
Conf *conf, LogContext *logctx, int bare_connection_mode,
const char *protoversion, struct ssh_version_receiver *rcv,
const char *impl_name)
int server_mode, const char *impl_name)
{
struct ssh_verstring_state *s = snew(struct ssh_verstring_state);
@ -98,8 +98,10 @@ BinaryPacketProtocol *ssh_verstring_new(
* We send our version string early if we can. But if it includes
* SSH-1, we can't, because we have to take the other end into
* account too (see below).
*
* In server mode, we do send early.
*/
s->send_early = !ssh_version_includes_v1(protoversion);
s->send_early = server_mode || !ssh_version_includes_v1(protoversion);
s->bpp.vt = &ssh_verstring_vtable;
ssh_bpp_common_setup(&s->bpp);

529
unix/uxserver.c Normal file
View File

@ -0,0 +1,529 @@
/*
* SSH server for Unix: main program.
*
* ======================================================================
*
* This server is NOT SECURE!
*
* 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.
*
* It has no interaction with the OS's authentication system: the
* authentications it will accept are configurable by command-line
* option, and once you authenticate, it will run the connection
* protocol - including all subprocesses and shells - under the same
* Unix user id you started it under.
*
* It really is only suitable for testing the actual SSH protocol.
* Don't use it for anything more serious!
*
* ======================================================================
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <stdarg.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <pwd.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#ifndef HAVE_NO_SYS_SELECT_H
#include <sys/select.h>
#endif
#define PUTTY_DO_GLOBALS /* actually _define_ globals */
#include "putty.h"
#include "ssh.h"
#include "sshserver.h"
const char *const appname = "uppity";
void modalfatalbox(const char *p, ...)
{
va_list ap;
fprintf(stderr, "FATAL ERROR: ");
va_start(ap, p);
vfprintf(stderr, p, ap);
va_end(ap);
fputc('\n', stderr);
exit(1);
}
void nonfatal(const char *p, ...)
{
va_list ap;
fprintf(stderr, "ERROR: ");
va_start(ap, p);
vfprintf(stderr, p, ap);
va_end(ap);
fputc('\n', stderr);
}
char *platform_default_s(const char *name)
{
return NULL;
}
int platform_default_i(const char *name, int def)
{
return def;
}
FontSpec *platform_default_fontspec(const char *name)
{
return fontspec_new("");
}
Filename *platform_default_filename(const char *name)
{
return filename_from_str("");
}
char *x_get_default(const char *key)
{
return NULL; /* this is a stub */
}
/*
* Our selects are synchronous, so these functions are empty stubs.
*/
uxsel_id *uxsel_input_add(int fd, int rwx) { return NULL; }
void uxsel_input_remove(uxsel_id *id) { }
void old_keyfile_warning(void) { }
void timer_change_notify(unsigned long next)
{
}
char *platform_get_x_display(void) { return NULL; }
static int verbose;
static void log_to_stderr(const char *msg)
{
/*
* FIXME: in multi-connection proper-socket mode, prefix this with
* a connection id of some kind. We'll surely pass this in to
* sshserver.c by way of constructing a distinct LogPolicy per
* instance and making its containing structure contain the id -
* but we'll also have to arrange for those LogPolicy structs to
* be freed when the server instance terminates.
*
* For now, though, we only have one server instance, so no need.
*/
fputs(msg, stderr);
fputc('\n', stderr);
fflush(stderr);
}
static void server_eventlog(LogPolicy *lp, const char *event)
{
if (verbose)
log_to_stderr(event);
}
static void server_logging_error(LogPolicy *lp, const char *event)
{
log_to_stderr(event); /* unconditional */
}
static int server_askappend(
LogPolicy *lp, Filename *filename,
void (*callback)(void *ctx, int result), void *ctx)
{
return 2; /* always overwrite (FIXME: could make this a cmdline option) */
}
static const LogPolicyVtable server_logpolicy_vt = {
server_eventlog,
server_askappend,
server_logging_error,
};
LogPolicy server_logpolicy[1] = {{ &server_logpolicy_vt }};
struct AuthPolicy_ssh1_pubkey {
struct RSAKey key;
struct AuthPolicy_ssh1_pubkey *next;
};
struct AuthPolicy_ssh2_pubkey {
ptrlen public_blob;
struct AuthPolicy_ssh2_pubkey *next;
};
struct AuthPolicy {
struct AuthPolicy_ssh1_pubkey *ssh1keys;
struct AuthPolicy_ssh2_pubkey *ssh2keys;
};
unsigned auth_methods(AuthPolicy *ap)
{
return AUTHMETHOD_PUBLICKEY | AUTHMETHOD_PASSWORD;
}
int auth_none(AuthPolicy *ap, ptrlen username)
{
return FALSE;
}
int auth_password(AuthPolicy *ap, ptrlen username, ptrlen password)
{
return ptrlen_eq_string(password, "weasel");
}
int auth_publickey(AuthPolicy *ap, ptrlen username, ptrlen public_blob)
{
struct AuthPolicy_ssh2_pubkey *iter;
for (iter = ap->ssh2keys; iter; iter = iter->next) {
if (ptrlen_eq_ptrlen(public_blob, iter->public_blob))
return TRUE;
}
return FALSE;
}
struct RSAKey *auth_publickey_ssh1(
AuthPolicy *ap, ptrlen username, Bignum rsa_modulus)
{
struct AuthPolicy_ssh1_pubkey *iter;
for (iter = ap->ssh1keys; iter; iter = iter->next) {
if (!bignum_cmp(rsa_modulus, iter->key.modulus))
return &iter->key;
}
return NULL;
}
int auth_successful(AuthPolicy *ap, ptrlen username, unsigned method)
{
return TRUE;
}
static void show_help_and_exit(void)
{
printf("usage: uppity [options]\n");
printf("options: --hostkey KEY SSH host key (need at least one)\n");
printf(" --userkey KEY public key"
" acceptable for user authentication\n");
printf("also: uppity --help show this text\n");
printf(" uppity --version show version information\n");
exit(0);
}
static void show_version_and_exit(void)
{
char *buildinfo_text = buildinfo("\n");
printf("uppity: %s\n%s\n", ver, buildinfo_text);
sfree(buildinfo_text);
exit(0);
}
const int buildinfo_gtk_relevant = FALSE;
static int finished = FALSE;
void server_instance_terminated(void)
{
/* FIXME: change policy here if we're running in a listening loop */
finished = TRUE;
}
static int longoptarg(const char *arg, const char *expected,
const char **val, int *argcp, char ***argvp)
{
int len = strlen(expected);
if (memcmp(arg, expected, len))
return FALSE;
if (arg[len] == '=') {
*val = arg + len + 1;
return TRUE;
} else if (arg[len] == '\0') {
if (--*argcp > 0) {
*val = *++*argvp;
return TRUE;
} else {
fprintf(stderr, "uppity: option %s expects an argument\n",
expected);
exit(1);
}
}
return FALSE;
}
int main(int argc, char **argv)
{
int *fdlist;
int fd;
int i, fdcount, fdsize, fdstate;
unsigned long now;
ssh_key **hostkeys = NULL;
int nhostkeys = 0, hostkeysize = 0;
struct RSAKey *hostkey1 = NULL;
AuthPolicy ap;
Conf *conf = conf_new();
load_open_settings(NULL, conf);
ap.ssh1keys = NULL;
ap.ssh2keys = NULL;
while (--argc > 0) {
const char *arg = *++argv;
const char *val;
if (!strcmp(arg, "--help")) {
show_help_and_exit();
} else if (!strcmp(arg, "--version")) {
show_version_and_exit();
} else if (!strcmp(arg, "--verbose") || !strcmp(arg, "-v")) {
verbose = TRUE;
} else if (longoptarg(arg, "--hostkey", &val, &argc, &argv)) {
Filename *keyfile;
int keytype;
const char *error;
keyfile = filename_from_str(val);
keytype = key_type(keyfile);
if (keytype == SSH_KEYTYPE_SSH2) {
struct ssh2_userkey *uk;
ssh_key *key;
uk = ssh2_load_userkey(keyfile, NULL, &error);
filename_free(keyfile);
if (!uk || !uk->key) {
fprintf(stderr, "uppity: unable to load host key '%s': "
"%s\n", val, error);
exit(1);
}
key = uk->key;
sfree(uk->comment);
sfree(uk);
for (i = 0; i < nhostkeys; i++)
if (ssh_key_alg(hostkeys[i]) == ssh_key_alg(key)) {
fprintf(stderr, "uppity: host key '%s' duplicates key "
"type %s\n", val, ssh_key_alg(key)->ssh_id);
exit(1);
}
if (nhostkeys >= hostkeysize) {
hostkeysize = nhostkeys * 5 / 4 + 16;
hostkeys = sresize(hostkeys, hostkeysize, ssh_key *);
}
hostkeys[nhostkeys++] = key;
} else if (keytype == SSH_KEYTYPE_SSH1) {
if (hostkey1) {
fprintf(stderr, "uppity: host key '%s' is a redundant "
"SSH-1 host key\n", val);
exit(1);
}
hostkey1 = snew(struct RSAKey);
if (!rsa_ssh1_loadkey(keyfile, hostkey1, NULL, &error)) {
fprintf(stderr, "uppity: unable to load host key '%s': "
"%s\n", val, error);
exit(1);
}
} else {
fprintf(stderr, "uppity: '%s' is not loadable as a "
"private key (%s)", val, key_type_to_str(keytype));
exit(1);
}
} else if (longoptarg(arg, "--userkey", &val, &argc, &argv)) {
Filename *keyfile;
int keytype;
const char *error;
keyfile = filename_from_str(val);
keytype = key_type(keyfile);
if (keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 ||
keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) {
strbuf *sb = strbuf_new();
struct AuthPolicy_ssh2_pubkey *node;
void *blob;
if (!ssh2_userkey_loadpub(keyfile, NULL, BinarySink_UPCAST(sb),
NULL, &error)) {
fprintf(stderr, "uppity: unable to load user key '%s': "
"%s\n", val, error);
exit(1);
}
node = snew_plus(struct AuthPolicy_ssh2_pubkey, sb->len);
blob = snew_plus_get_aux(node);
memcpy(blob, sb->u, sb->len);
node->public_blob = make_ptrlen(blob, sb->len);
node->next = ap.ssh2keys;
ap.ssh2keys = node;
strbuf_free(sb);
} else if (keytype == SSH_KEYTYPE_SSH1_PUBLIC) {
strbuf *sb = strbuf_new();
BinarySource src[1];
struct AuthPolicy_ssh1_pubkey *node;
if (!rsa_ssh1_loadpub(keyfile, BinarySink_UPCAST(sb),
NULL, &error)) {
fprintf(stderr, "uppity: unable to load user key '%s': "
"%s\n", val, error);
exit(1);
}
node = snew(struct AuthPolicy_ssh1_pubkey);
BinarySource_BARE_INIT(src, sb->u, sb->len);
get_rsa_ssh1_pub(src, &node->key, RSA_SSH1_EXPONENT_FIRST);
node->next = ap.ssh1keys;
ap.ssh1keys = node;
strbuf_free(sb);
} else {
fprintf(stderr, "uppity: '%s' is not loadable as a public key "
"(%s)\n", val, key_type_to_str(keytype));
exit(1);
}
} else if (longoptarg(arg, "--sshlog", &val, &argc, &argv) ||
longoptarg(arg, "-sshlog", &val, &argc, &argv)) {
Filename *logfile = filename_from_str(val);
conf_set_filename(conf, CONF_logfilename, logfile);
filename_free(logfile);
conf_set_int(conf, CONF_logtype, LGTYP_PACKETS);
conf_set_int(conf, CONF_logxfovr, LGXF_OVR);
} else if (longoptarg(arg, "--sshrawlog", &val, &argc, &argv) ||
longoptarg(arg, "-sshrawlog", &val, &argc, &argv)) {
Filename *logfile = filename_from_str(val);
conf_set_filename(conf, CONF_logfilename, logfile);
filename_free(logfile);
conf_set_int(conf, CONF_logtype, LGTYP_SSHRAW);
conf_set_int(conf, CONF_logxfovr, LGXF_OVR);
} else {
fprintf(stderr, "uppity: unrecognised option '%s'\n", arg);
exit(1);
}
}
if (nhostkeys == 0 && !hostkey1) {
fprintf(stderr, "uppity: specify at least one host key\n");
exit(1);
}
fdlist = NULL;
fdcount = fdsize = 0;
random_ref();
/*
* Block SIGPIPE, so that we'll get EPIPE individually on
* particular network connections that go wrong.
*/
putty_signal(SIGPIPE, SIG_IGN);
sk_init();
uxsel_init();
{
Plug *plug = ssh_server_plug(
conf, hostkeys, nhostkeys, hostkey1, &ap, server_logpolicy);
ssh_server_start(plug, make_fd_socket(0, 1, -1, plug));
}
now = GETTICKCOUNT();
while (!finished) {
fd_set rset, wset, xset;
int maxfd;
int rwx;
int ret;
unsigned long next;
FD_ZERO(&rset);
FD_ZERO(&wset);
FD_ZERO(&xset);
maxfd = 0;
/* Count the currently active fds. */
i = 0;
for (fd = first_fd(&fdstate, &rwx); fd >= 0;
fd = next_fd(&fdstate, &rwx)) i++;
/* Expand the fdlist buffer if necessary. */
if (i > fdsize) {
fdsize = i + 16;
fdlist = sresize(fdlist, fdsize, int);
}
/*
* Add all currently open fds to the select sets, and store
* them in fdlist as well.
*/
fdcount = 0;
for (fd = first_fd(&fdstate, &rwx); fd >= 0;
fd = next_fd(&fdstate, &rwx)) {
fdlist[fdcount++] = fd;
if (rwx & 1)
FD_SET_MAX(fd, maxfd, rset);
if (rwx & 2)
FD_SET_MAX(fd, maxfd, wset);
if (rwx & 4)
FD_SET_MAX(fd, maxfd, xset);
}
if (toplevel_callback_pending()) {
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 0;
ret = select(maxfd, &rset, &wset, &xset, &tv);
} else if (run_timers(now, &next)) {
do {
unsigned long then;
long ticks;
struct timeval tv;
then = now;
now = GETTICKCOUNT();
if (now - then > next - then)
ticks = 0;
else
ticks = next - now;
tv.tv_sec = ticks / 1000;
tv.tv_usec = ticks % 1000 * 1000;
ret = select(maxfd, &rset, &wset, &xset, &tv);
if (ret == 0)
now = next;
else
now = GETTICKCOUNT();
} while (ret < 0 && errno == EINTR);
} else {
ret = select(maxfd, &rset, &wset, &xset, NULL);
}
if (ret < 0 && errno == EINTR)
continue;
if (ret < 0) {
perror("select");
exit(1);
}
for (i = 0; i < fdcount; i++) {
fd = fdlist[i];
/*
* We must process exceptional notifications before
* ordinary readability ones, or we may go straight
* past the urgent marker.
*/
if (FD_ISSET(fd, &xset))
select_result(fd, 4);
if (FD_ISSET(fd, &rset))
select_result(fd, 1);
if (FD_ISSET(fd, &wset))
select_result(fd, 2);
}
run_toplevel_callbacks();
}
}