1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 17:38:00 +00:00
putty-source/sesschan.c
Simon Tatham a081dd0a4c Add an SFTP server to the SSH server code.
Unlike the traditional Unix SSH server organisation, the SFTP server
is built into the same process as all the rest of the code. sesschan.c
spots a subsystem request for "sftp", and responds to it by
instantiating an SftpServer object and swapping out its own vtable for
one that talks to it.

(I rather like the idea of an object swapping its own vtable for a
different one in the middle of its lifetime! This is one of those
tricks that would be absurdly hard to implement in a 'proper' OO
language, but when you're doing vtables by hand in C, it's no more
difficult than any other piece of ordinary pointer manipulation. As
long as the methods in both vtables expect the same physical structure
layout, it doesn't cause a problem.)

The SftpServer object doesn't deal directly with SFTP packet formats;
it implements the SFTP server logic in a more abstract way, by having
a vtable method for each SFTP request type with an appropriate
parameter list. It sends its replies by calling methods in another
vtable called SftpReplyBuilder, which in the normal case will write an
SFTP reply packet to send back to the client. So SftpServer can focus
more or less completely on the details of a particular filesystem API
- and hence, the implementation I've got lives in the unix source
directory, and works directly with file descriptors and struct stat
and the like.

(One purpose of this abstraction layer is that I may well want to
write a second dummy implementation, for test-suite purposes, with
completely controllable behaviour, and now I have a handy place to
plug it in in place of the live filesystem.)

In between sesschan's parsing of the byte stream into SFTP packets and
the SftpServer object, there's a layer in the new file sftpserver.c
which does the actual packet decoding and encoding: each request
packet is passed to that, which pulls the fields out of the request
packet and calls the appropriate method of SftpServer. It also
provides the default SftpReplyBuilder which makes the output packet.

I've moved some code out of the previous SFTP client implementation -
basic packet construction code, and in particular the BinarySink/
BinarySource marshalling fuinction for fxp_attrs - into sftpcommon.c,
so that the two directions can share as much as possible.
2018-10-21 10:02:10 +01:00

654 lines
18 KiB
C

/*
* 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"
#include "sftp.h"
typedef struct sesschan {
SshChannel *c;
LogContext *parent_logctx, *child_logctx;
Conf *conf;
const SftpServerVtable *sftpserver_vt;
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;
SftpServer *sftpsrv;
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 int sftp_chan_send(Channel *chan, int is_stderr, const void *, int);
static void sftp_chan_send_eof(Channel *chan);
static char *sftp_log_close_msg(Channel *chan);
static const struct ChannelVtable sftp_channelvt = {
sesschan_free,
chan_remotely_opened_confirmation,
chan_remotely_opened_failure,
sftp_chan_send,
sftp_chan_send_eof,
sesschan_set_input_wanted,
sftp_log_close_msg,
chan_default_want_close,
chan_no_exit_status,
chan_no_exit_signal,
chan_no_exit_signal_numeric,
chan_no_run_shell,
chan_no_run_command,
chan_no_run_subsystem,
chan_no_enable_x11_forwarding,
chan_no_enable_agent_forwarding,
chan_no_allocate_pty,
chan_no_set_env,
chan_no_send_break,
chan_no_send_signal,
chan_no_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,
const SftpServerVtable *sftpserver_vt)
{
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);
sess->sftpserver_vt = sftpserver_vt;
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);
if (sess->sftpsrv)
sftpsrv_free(sess->sftpsrv);
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)
{
sesschan *sess = container_of(chan, sesschan, chan);
if (ptrlen_eq_string(subsys, "sftp") && sess->sftpserver_vt) {
sess->sftpsrv = sftpsrv_new(sess->sftpserver_vt);
sess->chan.vt = &sftp_channelvt;
logevent(sess->parent_logctx, "Starting built-in SFTP subsystem");
return TRUE;
}
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;
}
/* ----------------------------------------------------------------------
* Built-in SFTP subsystem.
*/
static int sftp_chan_send(Channel *chan, int is_stderr,
const void *data, int length)
{
sesschan *sess = container_of(chan, sesschan, chan);
bufchain_add(&sess->subsys_input, data, length);
while (bufchain_size(&sess->subsys_input) >= 4) {
char lenbuf[4];
unsigned pktlen;
struct sftp_packet *pkt, *reply;
bufchain_fetch(&sess->subsys_input, lenbuf, 4);
pktlen = GET_32BIT(lenbuf);
if (bufchain_size(&sess->subsys_input) - 4 < pktlen)
break; /* wait for more data */
bufchain_consume(&sess->subsys_input, 4);
pkt = sftp_recv_prepare(pktlen);
bufchain_fetch_consume(&sess->subsys_input, pkt->data, pktlen);
sftp_recv_finish(pkt);
reply = sftp_handle_request(sess->sftpsrv, pkt);
sftp_pkt_free(pkt);
sftp_send_prepare(reply);
sshfwd_write(sess->c, reply->data, reply->length);
sftp_pkt_free(reply);
}
return 0;
}
static void sftp_chan_send_eof(Channel *chan)
{
sesschan *sess = container_of(chan, sesschan, chan);
sshfwd_write_eof(sess->c);
}
static char *sftp_log_close_msg(Channel *chan)
{
return dupstr("Session channel (SFTP) closed");
}