mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-09 17:38:00 +00:00
a2ff884512
All the seat functions that request an interactive prompt of some kind to the user - both the main seat_get_userpass_input and the various confirmation dialogs for things like host keys - were using a simple int return value, with the general semantics of 0 = "fail", 1 = "proceed" (and in the case of seat_get_userpass_input, answers to the prompts were provided), and -1 = "request in progress, wait for a callback". In this commit I change all those functions' return types to a new struct called SeatPromptResult, whose primary field is an enum replacing those simple integer values. The main purpose is that the enum has not three but _four_ values: the "fail" result has been split into 'user abort' and 'software abort'. The distinction is that a user abort occurs as a result of an interactive UI action, such as the user clicking 'cancel' in a dialog box or hitting ^D or ^C at a terminal password prompt - and therefore, there's no need to display an error message telling the user that the interactive operation has failed, because the user already knows, because they _did_ it. 'Software abort' is from any other cause, where PuTTY is the first to know there was a problem, and has to tell the user. We already had this 'user abort' vs 'software abort' distinction in other parts of the code - the SSH backend has separate termination functions which protocol layers can call. But we assumed that any failure from an interactive prompt request fell into the 'user abort' category, which is not true. A couple of examples: if you configure a host key fingerprint in your saved session via the SSH > Host keys pane, and the server presents a host key that doesn't match it, then verify_ssh_host_key would report that the user had aborted the connection, and feel no need to tell the user what had gone wrong! Similarly, if a password provided on the command line was not accepted, then (after I fixed the semantics of that in the previous commit) the same wrong handling would occur. So now, those Seat prompt functions too can communicate whether the user or the software originated a connection abort. And in the latter case, we also provide an error message to present to the user. Result: in those two example cases (and others), error messages should no longer go missing. Implementation note: to avoid the hassle of having the error message in a SeatPromptResult being a dynamically allocated string (and hence, every recipient of one must always check whether it's non-NULL and free it on every exit path, plus being careful about copying the struct around), I've instead arranged that the structure contains a function pointer and a couple of parameters, so that the string form of the message can be constructed on demand. That way, the only users who need to free it are the ones who actually _asked_ for it in the first place, which is a much smaller set. (This is one of the rare occasions that I regret not having C++'s extra features available in this code base - a unique_ptr or shared_ptr to a string would have been just the thing here, and the compiler would have done all the hard work for me of remembering where to insert the frees!)
812 lines
26 KiB
C
812 lines
26 KiB
C
/*
|
|
* Packet protocol layer for the SSH-1 'connection protocol', i.e.
|
|
* everything after authentication finishes.
|
|
*/
|
|
|
|
#include <assert.h>
|
|
|
|
#include "putty.h"
|
|
#include "ssh.h"
|
|
#include "bpp.h"
|
|
#include "ppl.h"
|
|
#include "channel.h"
|
|
#include "sshcr.h"
|
|
#include "connection1.h"
|
|
|
|
static int ssh1_rportfwd_cmp(void *av, void *bv)
|
|
{
|
|
struct ssh_rportfwd *a = (struct ssh_rportfwd *) av;
|
|
struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv;
|
|
int i;
|
|
if ( (i = strcmp(a->dhost, b->dhost)) != 0)
|
|
return i < 0 ? -1 : +1;
|
|
if (a->dport > b->dport)
|
|
return +1;
|
|
if (a->dport < b->dport)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
static void ssh1_connection_free(PacketProtocolLayer *);
|
|
static void ssh1_connection_process_queue(PacketProtocolLayer *);
|
|
static void ssh1_connection_special_cmd(PacketProtocolLayer *ppl,
|
|
SessionSpecialCode code, int arg);
|
|
static void ssh1_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf);
|
|
|
|
static const PacketProtocolLayerVtable ssh1_connection_vtable = {
|
|
.free = ssh1_connection_free,
|
|
.process_queue = ssh1_connection_process_queue,
|
|
.get_specials = ssh1_common_get_specials,
|
|
.special_cmd = ssh1_connection_special_cmd,
|
|
.reconfigure = ssh1_connection_reconfigure,
|
|
.queued_data_size = ssh_ppl_default_queued_data_size,
|
|
.name = NULL, /* no layer names in SSH-1 */
|
|
};
|
|
|
|
static void ssh1_rportfwd_remove(
|
|
ConnectionLayer *cl, struct ssh_rportfwd *rpf);
|
|
static SshChannel *ssh1_lportfwd_open(
|
|
ConnectionLayer *cl, const char *hostname, int port,
|
|
const char *description, const SocketPeerInfo *pi, Channel *chan);
|
|
static struct X11FakeAuth *ssh1_add_x11_display(
|
|
ConnectionLayer *cl, int authtype, struct X11Display *disp);
|
|
static bool ssh1_agent_forwarding_permitted(ConnectionLayer *cl);
|
|
static void ssh1_terminal_size(ConnectionLayer *cl, int width, int height);
|
|
static void ssh1_stdout_unthrottle(ConnectionLayer *cl, size_t bufsize);
|
|
static size_t ssh1_stdin_backlog(ConnectionLayer *cl);
|
|
static void ssh1_throttle_all_channels(ConnectionLayer *cl, bool throttled);
|
|
static bool ssh1_ldisc_option(ConnectionLayer *cl, int option);
|
|
static void ssh1_set_ldisc_option(ConnectionLayer *cl, int option, bool value);
|
|
static void ssh1_enable_x_fwd(ConnectionLayer *cl);
|
|
static void ssh1_set_wants_user_input(ConnectionLayer *cl, bool wanted);
|
|
static bool ssh1_get_wants_user_input(ConnectionLayer *cl);
|
|
static void ssh1_got_user_input(ConnectionLayer *cl);
|
|
|
|
static const ConnectionLayerVtable ssh1_connlayer_vtable = {
|
|
.rportfwd_alloc = ssh1_rportfwd_alloc,
|
|
.rportfwd_remove = ssh1_rportfwd_remove,
|
|
.lportfwd_open = ssh1_lportfwd_open,
|
|
.session_open = ssh1_session_open,
|
|
.serverside_x11_open = ssh1_serverside_x11_open,
|
|
.serverside_agent_open = ssh1_serverside_agent_open,
|
|
.add_x11_display = ssh1_add_x11_display,
|
|
.agent_forwarding_permitted = ssh1_agent_forwarding_permitted,
|
|
.terminal_size = ssh1_terminal_size,
|
|
.stdout_unthrottle = ssh1_stdout_unthrottle,
|
|
.stdin_backlog = ssh1_stdin_backlog,
|
|
.throttle_all_channels = ssh1_throttle_all_channels,
|
|
.ldisc_option = ssh1_ldisc_option,
|
|
.set_ldisc_option = ssh1_set_ldisc_option,
|
|
.enable_x_fwd = ssh1_enable_x_fwd,
|
|
.set_wants_user_input = ssh1_set_wants_user_input,
|
|
.get_wants_user_input = ssh1_get_wants_user_input,
|
|
.got_user_input = ssh1_got_user_input,
|
|
/* other methods are NULL */
|
|
};
|
|
|
|
static size_t ssh1channel_write(
|
|
SshChannel *c, bool is_stderr, const void *buf, size_t len);
|
|
static void ssh1channel_write_eof(SshChannel *c);
|
|
static void ssh1channel_initiate_close(SshChannel *c, const char *err);
|
|
static void ssh1channel_unthrottle(SshChannel *c, size_t bufsize);
|
|
static Conf *ssh1channel_get_conf(SshChannel *c);
|
|
static void ssh1channel_window_override_removed(SshChannel *c) { /* ignore */ }
|
|
|
|
static const SshChannelVtable ssh1channel_vtable = {
|
|
.write = ssh1channel_write,
|
|
.write_eof = ssh1channel_write_eof,
|
|
.initiate_close = ssh1channel_initiate_close,
|
|
.unthrottle = ssh1channel_unthrottle,
|
|
.get_conf = ssh1channel_get_conf,
|
|
.window_override_removed = ssh1channel_window_override_removed,
|
|
/* everything else is NULL */
|
|
};
|
|
|
|
static void ssh1_channel_try_eof(struct ssh1_channel *c);
|
|
static void ssh1_channel_close_local(struct ssh1_channel *c,
|
|
const char *reason);
|
|
static void ssh1_channel_destroy(struct ssh1_channel *c);
|
|
static void ssh1_channel_check_close(struct ssh1_channel *c);
|
|
|
|
static int ssh1_channelcmp(void *av, void *bv)
|
|
{
|
|
const struct ssh1_channel *a = (const struct ssh1_channel *) av;
|
|
const struct ssh1_channel *b = (const struct ssh1_channel *) bv;
|
|
if (a->localid < b->localid)
|
|
return -1;
|
|
if (a->localid > b->localid)
|
|
return +1;
|
|
return 0;
|
|
}
|
|
|
|
static int ssh1_channelfind(void *av, void *bv)
|
|
{
|
|
const unsigned *a = (const unsigned *) av;
|
|
const struct ssh1_channel *b = (const struct ssh1_channel *) bv;
|
|
if (*a < b->localid)
|
|
return -1;
|
|
if (*a > b->localid)
|
|
return +1;
|
|
return 0;
|
|
}
|
|
|
|
void ssh1_channel_free(struct ssh1_channel *c)
|
|
{
|
|
if (c->chan)
|
|
chan_free(c->chan);
|
|
sfree(c);
|
|
}
|
|
|
|
PacketProtocolLayer *ssh1_connection_new(
|
|
Ssh *ssh, Conf *conf, bufchain *user_input, ConnectionLayer **cl_out)
|
|
{
|
|
struct ssh1_connection_state *s = snew(struct ssh1_connection_state);
|
|
memset(s, 0, sizeof(*s));
|
|
s->ppl.vt = &ssh1_connection_vtable;
|
|
|
|
s->conf = conf_copy(conf);
|
|
|
|
s->channels = newtree234(ssh1_channelcmp);
|
|
|
|
s->x11authtree = newtree234(x11_authcmp);
|
|
|
|
s->user_input = user_input;
|
|
|
|
/* Need to get the log context for s->cl now, because we won't be
|
|
* helpfully notified when a copy is written into s->ppl by our
|
|
* owner. */
|
|
s->cl.vt = &ssh1_connlayer_vtable;
|
|
s->cl.logctx = ssh_get_logctx(ssh);
|
|
|
|
s->portfwdmgr = portfwdmgr_new(&s->cl);
|
|
s->rportfwds = newtree234(ssh1_rportfwd_cmp);
|
|
|
|
*cl_out = &s->cl;
|
|
return &s->ppl;
|
|
}
|
|
|
|
static void ssh1_connection_free(PacketProtocolLayer *ppl)
|
|
{
|
|
struct ssh1_connection_state *s =
|
|
container_of(ppl, struct ssh1_connection_state, ppl);
|
|
struct X11FakeAuth *auth;
|
|
struct ssh1_channel *c;
|
|
struct ssh_rportfwd *rpf;
|
|
|
|
conf_free(s->conf);
|
|
|
|
while ((c = delpos234(s->channels, 0)) != NULL)
|
|
ssh1_channel_free(c);
|
|
freetree234(s->channels);
|
|
if (s->mainchan_chan)
|
|
chan_free(s->mainchan_chan);
|
|
|
|
if (s->x11disp)
|
|
x11_free_display(s->x11disp);
|
|
while ((auth = delpos234(s->x11authtree, 0)) != NULL)
|
|
x11_free_fake_auth(auth);
|
|
freetree234(s->x11authtree);
|
|
|
|
while ((rpf = delpos234(s->rportfwds, 0)) != NULL)
|
|
free_rportfwd(rpf);
|
|
freetree234(s->rportfwds);
|
|
portfwdmgr_free(s->portfwdmgr);
|
|
|
|
if (s->antispoof_prompt)
|
|
free_prompts(s->antispoof_prompt);
|
|
|
|
delete_callbacks_for_context(s);
|
|
|
|
sfree(s);
|
|
}
|
|
|
|
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 = local;
|
|
s->remote_protoflags = remote;
|
|
}
|
|
|
|
static bool ssh1_connection_filter_queue(struct ssh1_connection_state *s)
|
|
{
|
|
PktIn *pktin;
|
|
ptrlen data;
|
|
struct ssh1_channel *c;
|
|
unsigned localid;
|
|
bool expect_halfopen;
|
|
|
|
while (1) {
|
|
if (ssh1_common_filter_queue(&s->ppl))
|
|
return true;
|
|
if ((pktin = pq_peek(s->ppl.in_pq)) == NULL)
|
|
return false;
|
|
|
|
switch (pktin->type) {
|
|
case SSH1_MSG_CHANNEL_DATA:
|
|
case SSH1_MSG_CHANNEL_OPEN_CONFIRMATION:
|
|
case SSH1_MSG_CHANNEL_OPEN_FAILURE:
|
|
case SSH1_MSG_CHANNEL_CLOSE:
|
|
case SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION:
|
|
/*
|
|
* Common preliminary code for all the messages from the
|
|
* server that cite one of our channel ids: look up that
|
|
* channel id, check it exists, and if it's for a sharing
|
|
* downstream, pass it on.
|
|
*/
|
|
localid = get_uint32(pktin);
|
|
c = find234(s->channels, &localid, ssh1_channelfind);
|
|
|
|
expect_halfopen = (
|
|
pktin->type == SSH1_MSG_CHANNEL_OPEN_CONFIRMATION ||
|
|
pktin->type == SSH1_MSG_CHANNEL_OPEN_FAILURE);
|
|
|
|
if (!c || c->halfopen != expect_halfopen) {
|
|
ssh_remote_error(
|
|
s->ppl.ssh, "Received %s for %s channel %u",
|
|
ssh1_pkt_type(pktin->type),
|
|
!c ? "nonexistent" : c->halfopen ? "half-open" : "open",
|
|
localid);
|
|
return true;
|
|
}
|
|
|
|
switch (pktin->type) {
|
|
case SSH1_MSG_CHANNEL_OPEN_CONFIRMATION:
|
|
assert(c->halfopen);
|
|
c->remoteid = get_uint32(pktin);
|
|
c->halfopen = false;
|
|
c->throttling_conn = false;
|
|
|
|
chan_open_confirmation(c->chan);
|
|
|
|
/*
|
|
* Now that the channel is fully open, it's possible
|
|
* in principle to immediately close it. Check whether
|
|
* it wants us to!
|
|
*
|
|
* This can occur if a local socket error occurred
|
|
* between us sending out CHANNEL_OPEN and receiving
|
|
* OPEN_CONFIRMATION. If that happens, all we can do
|
|
* is immediately initiate close proceedings now that
|
|
* we know the server's id to put in the close
|
|
* message. We'll have handled that in this code by
|
|
* having already turned c->chan into a zombie, so its
|
|
* want_close method (which ssh1_channel_check_close
|
|
* will consult) will already be returning true.
|
|
*/
|
|
ssh1_channel_check_close(c);
|
|
|
|
if (c->pending_eof)
|
|
ssh1_channel_try_eof(c); /* in case we had a pending EOF */
|
|
break;
|
|
|
|
case SSH1_MSG_CHANNEL_OPEN_FAILURE:
|
|
assert(c->halfopen);
|
|
|
|
chan_open_failed(c->chan, NULL);
|
|
chan_free(c->chan);
|
|
|
|
del234(s->channels, c);
|
|
ssh1_channel_free(c);
|
|
break;
|
|
|
|
case SSH1_MSG_CHANNEL_DATA:
|
|
data = get_string(pktin);
|
|
if (!get_err(pktin)) {
|
|
int bufsize = chan_send(
|
|
c->chan, false, data.ptr, data.len);
|
|
|
|
if (!c->throttling_conn && bufsize > SSH1_BUFFER_LIMIT) {
|
|
c->throttling_conn = true;
|
|
ssh_throttle_conn(s->ppl.ssh, +1);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SSH1_MSG_CHANNEL_CLOSE:
|
|
if (!(c->closes & CLOSES_RCVD_CLOSE)) {
|
|
c->closes |= CLOSES_RCVD_CLOSE;
|
|
chan_send_eof(c->chan);
|
|
ssh1_channel_check_close(c);
|
|
}
|
|
break;
|
|
|
|
case SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION:
|
|
if (!(c->closes & CLOSES_RCVD_CLOSECONF)) {
|
|
if (!(c->closes & CLOSES_SENT_CLOSE)) {
|
|
ssh_remote_error(
|
|
s->ppl.ssh,
|
|
"Received CHANNEL_CLOSE_CONFIRMATION for channel"
|
|
" %u for which we never sent CHANNEL_CLOSE\n",
|
|
c->localid);
|
|
return true;
|
|
}
|
|
|
|
c->closes |= CLOSES_RCVD_CLOSECONF;
|
|
ssh1_channel_check_close(c);
|
|
}
|
|
break;
|
|
}
|
|
|
|
pq_pop(s->ppl.in_pq);
|
|
break;
|
|
|
|
default:
|
|
if (ssh1_handle_direction_specific_packet(s, pktin)) {
|
|
pq_pop(s->ppl.in_pq);
|
|
if (ssh1_check_termination(s))
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static PktIn *ssh1_connection_pop(struct ssh1_connection_state *s)
|
|
{
|
|
ssh1_connection_filter_queue(s);
|
|
return pq_pop(s->ppl.in_pq);
|
|
}
|
|
|
|
static void ssh1_connection_process_queue(PacketProtocolLayer *ppl)
|
|
{
|
|
struct ssh1_connection_state *s =
|
|
container_of(ppl, struct ssh1_connection_state, ppl);
|
|
PktIn *pktin;
|
|
|
|
if (ssh1_connection_filter_queue(s)) /* no matter why we were called */
|
|
return;
|
|
|
|
crBegin(s->crState);
|
|
|
|
/*
|
|
* Signal the seat that authentication is done, so that it can
|
|
* deploy spoofing defences. If it doesn't have any, deploy our
|
|
* own fallback one.
|
|
*
|
|
* We do this here rather than at the end of userauth, because we
|
|
* might not have gone through userauth at all (if we're a
|
|
* connection-sharing downstream).
|
|
*/
|
|
if (ssh1_connection_need_antispoof_prompt(s)) {
|
|
s->antispoof_prompt = ssh_ppl_new_prompts(&s->ppl);
|
|
s->antispoof_prompt->to_server = true;
|
|
s->antispoof_prompt->from_server = false;
|
|
s->antispoof_prompt->name = dupstr("Authentication successful");
|
|
add_prompt(
|
|
s->antispoof_prompt,
|
|
dupstr("Access granted. Press Return to begin session. "), false);
|
|
s->antispoof_ret = seat_get_userpass_input(
|
|
ppl_get_iseat(&s->ppl), s->antispoof_prompt);
|
|
while (s->antispoof_ret.kind == SPRK_INCOMPLETE) {
|
|
crReturnV;
|
|
s->antispoof_ret = seat_get_userpass_input(
|
|
ppl_get_iseat(&s->ppl), s->antispoof_prompt);
|
|
}
|
|
free_prompts(s->antispoof_prompt);
|
|
s->antispoof_prompt = NULL;
|
|
}
|
|
|
|
portfwdmgr_config(s->portfwdmgr, s->conf);
|
|
s->portfwdmgr_configured = true;
|
|
|
|
while (!s->finished_setup) {
|
|
ssh1_connection_direction_specific_setup(s);
|
|
crReturnV;
|
|
}
|
|
|
|
while (1) {
|
|
|
|
/*
|
|
* By this point, most incoming packets are already being
|
|
* handled by filter_queue, and we need only pay attention to
|
|
* the unusual ones.
|
|
*/
|
|
|
|
if ((pktin = ssh1_connection_pop(s)) != NULL) {
|
|
ssh_proto_error(s->ppl.ssh, "Unexpected packet received, "
|
|
"type %d (%s)", pktin->type,
|
|
ssh1_pkt_type(pktin->type));
|
|
return;
|
|
}
|
|
crReturnV;
|
|
}
|
|
|
|
crFinishV;
|
|
}
|
|
|
|
static void ssh1_channel_check_close(struct ssh1_channel *c)
|
|
{
|
|
struct ssh1_connection_state *s = c->connlayer;
|
|
PktOut *pktout;
|
|
|
|
if (c->halfopen) {
|
|
/*
|
|
* If we've sent out our own CHANNEL_OPEN but not yet seen
|
|
* either OPEN_CONFIRMATION or OPEN_FAILURE in response, then
|
|
* it's too early to be sending close messages of any kind.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
if ((!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes) ||
|
|
chan_want_close(c->chan, (c->closes & CLOSES_SENT_CLOSE),
|
|
(c->closes & CLOSES_RCVD_CLOSE))) &&
|
|
!(c->closes & CLOSES_SENT_CLOSECONF)) {
|
|
/*
|
|
* We have both sent and received CLOSE (or the channel type
|
|
* doesn't need us to), which means the channel is in final
|
|
* wind-up. Send CLOSE and/or CLOSE_CONFIRMATION, whichever we
|
|
* haven't sent yet.
|
|
*/
|
|
if (!(c->closes & CLOSES_SENT_CLOSE)) {
|
|
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_CHANNEL_CLOSE);
|
|
put_uint32(pktout, c->remoteid);
|
|
pq_push(s->ppl.out_pq, pktout);
|
|
c->closes |= CLOSES_SENT_CLOSE;
|
|
}
|
|
if (c->closes & CLOSES_RCVD_CLOSE) {
|
|
pktout = ssh_bpp_new_pktout(
|
|
s->ppl.bpp, SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION);
|
|
put_uint32(pktout, c->remoteid);
|
|
pq_push(s->ppl.out_pq, pktout);
|
|
c->closes |= CLOSES_SENT_CLOSECONF;
|
|
}
|
|
}
|
|
|
|
if (!((CLOSES_SENT_CLOSECONF | CLOSES_RCVD_CLOSECONF) & ~c->closes)) {
|
|
/*
|
|
* We have both sent and received CLOSE_CONFIRMATION, which
|
|
* means we're completely done with the channel.
|
|
*/
|
|
ssh1_channel_destroy(c);
|
|
}
|
|
}
|
|
|
|
static void ssh1_channel_try_eof(struct ssh1_channel *c)
|
|
{
|
|
struct ssh1_connection_state *s = c->connlayer;
|
|
PktOut *pktout;
|
|
assert(c->pending_eof); /* precondition for calling us */
|
|
if (c->halfopen)
|
|
return; /* can't close: not even opened yet */
|
|
|
|
c->pending_eof = false; /* we're about to send it */
|
|
|
|
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_CHANNEL_CLOSE);
|
|
put_uint32(pktout, c->remoteid);
|
|
pq_push(s->ppl.out_pq, pktout);
|
|
c->closes |= CLOSES_SENT_CLOSE;
|
|
|
|
ssh1_channel_check_close(c);
|
|
}
|
|
|
|
/*
|
|
* Close any local socket and free any local resources associated with
|
|
* a channel. This converts the channel into a zombie.
|
|
*/
|
|
static void ssh1_channel_close_local(struct ssh1_channel *c,
|
|
const char *reason)
|
|
{
|
|
struct ssh1_connection_state *s = c->connlayer;
|
|
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
|
|
char *msg = chan_log_close_msg(c->chan);
|
|
|
|
if (msg != NULL) {
|
|
ppl_logevent("%s%s%s", msg, reason ? " " : "", reason ? reason : "");
|
|
sfree(msg);
|
|
}
|
|
|
|
chan_free(c->chan);
|
|
c->chan = zombiechan_new();
|
|
}
|
|
|
|
static void ssh1_check_termination_callback(void *vctx)
|
|
{
|
|
struct ssh1_connection_state *s = (struct ssh1_connection_state *)vctx;
|
|
ssh1_check_termination(s);
|
|
}
|
|
|
|
static void ssh1_channel_destroy(struct ssh1_channel *c)
|
|
{
|
|
struct ssh1_connection_state *s = c->connlayer;
|
|
|
|
ssh1_channel_close_local(c, NULL);
|
|
del234(s->channels, c);
|
|
ssh1_channel_free(c);
|
|
|
|
/*
|
|
* If that was the last channel left open, we might need to
|
|
* terminate. But we'll be a bit cautious, by doing that in a
|
|
* toplevel callback, just in case anything on the current call
|
|
* stack objects to this entire PPL being freed.
|
|
*/
|
|
queue_toplevel_callback(ssh1_check_termination_callback, s);
|
|
}
|
|
|
|
bool ssh1_check_termination(struct ssh1_connection_state *s)
|
|
{
|
|
/*
|
|
* Decide whether we should terminate the SSH connection now.
|
|
* Called after a channel goes away, or when the main session
|
|
* returns SSH1_SMSG_EXIT_STATUS; we terminate when none of either
|
|
* is left.
|
|
*/
|
|
if (s->session_terminated && count234(s->channels) == 0) {
|
|
PktOut *pktout = ssh_bpp_new_pktout(
|
|
s->ppl.bpp, SSH1_CMSG_EXIT_CONFIRMATION);
|
|
pq_push(s->ppl.out_pq, pktout);
|
|
|
|
ssh_user_close(s->ppl.ssh, "Session finished");
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Set up most of a new ssh1_channel. Leaves chan untouched (since it
|
|
* will sometimes have been filled in before calling this).
|
|
*/
|
|
void ssh1_channel_init(struct ssh1_channel *c)
|
|
{
|
|
struct ssh1_connection_state *s = c->connlayer;
|
|
c->closes = 0;
|
|
c->pending_eof = false;
|
|
c->throttling_conn = false;
|
|
c->sc.vt = &ssh1channel_vtable;
|
|
c->sc.cl = &s->cl;
|
|
c->localid = alloc_channel_id(s->channels, struct ssh1_channel);
|
|
add234(s->channels, c);
|
|
}
|
|
|
|
static Conf *ssh1channel_get_conf(SshChannel *sc)
|
|
{
|
|
struct ssh1_channel *c = container_of(sc, struct ssh1_channel, sc);
|
|
struct ssh1_connection_state *s = c->connlayer;
|
|
return s->conf;
|
|
}
|
|
|
|
static void ssh1channel_write_eof(SshChannel *sc)
|
|
{
|
|
struct ssh1_channel *c = container_of(sc, struct ssh1_channel, sc);
|
|
|
|
if (c->closes & CLOSES_SENT_CLOSE)
|
|
return;
|
|
|
|
c->pending_eof = true;
|
|
ssh1_channel_try_eof(c);
|
|
}
|
|
|
|
static void ssh1channel_initiate_close(SshChannel *sc, const char *err)
|
|
{
|
|
struct ssh1_channel *c = container_of(sc, struct ssh1_channel, sc);
|
|
char *reason;
|
|
|
|
reason = err ? dupprintf("due to local error: %s", err) : NULL;
|
|
ssh1_channel_close_local(c, reason);
|
|
sfree(reason);
|
|
c->pending_eof = false; /* this will confuse a zombie channel */
|
|
|
|
ssh1_channel_check_close(c);
|
|
}
|
|
|
|
static void ssh1channel_unthrottle(SshChannel *sc, size_t bufsize)
|
|
{
|
|
struct ssh1_channel *c = container_of(sc, struct ssh1_channel, sc);
|
|
struct ssh1_connection_state *s = c->connlayer;
|
|
|
|
if (c->throttling_conn && bufsize <= SSH1_BUFFER_LIMIT) {
|
|
c->throttling_conn = false;
|
|
ssh_throttle_conn(s->ppl.ssh, -1);
|
|
}
|
|
}
|
|
|
|
static size_t ssh1channel_write(
|
|
SshChannel *sc, bool is_stderr, const void *buf, size_t len)
|
|
{
|
|
struct ssh1_channel *c = container_of(sc, struct ssh1_channel, sc);
|
|
struct ssh1_connection_state *s = c->connlayer;
|
|
|
|
assert(!(c->closes & CLOSES_SENT_CLOSE));
|
|
|
|
PktOut *pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_CHANNEL_DATA);
|
|
put_uint32(pktout, c->remoteid);
|
|
put_string(pktout, buf, len);
|
|
pq_push(s->ppl.out_pq, pktout);
|
|
|
|
/*
|
|
* In SSH-1 we can return 0 here - implying that channels are
|
|
* never individually throttled - because the only circumstance
|
|
* that can cause throttling will be the whole SSH connection
|
|
* backing up, in which case _everything_ will be throttled as a
|
|
* whole.
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
static struct X11FakeAuth *ssh1_add_x11_display(
|
|
ConnectionLayer *cl, int authtype, struct X11Display *disp)
|
|
{
|
|
struct ssh1_connection_state *s =
|
|
container_of(cl, struct ssh1_connection_state, cl);
|
|
struct X11FakeAuth *auth = x11_invent_fake_auth(s->x11authtree, authtype);
|
|
auth->disp = disp;
|
|
return auth;
|
|
}
|
|
|
|
static SshChannel *ssh1_lportfwd_open(
|
|
ConnectionLayer *cl, const char *hostname, int port,
|
|
const char *description, const SocketPeerInfo *pi, 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("Opening connection to %s:%d for %s",
|
|
hostname, port, description);
|
|
|
|
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_PORT_OPEN);
|
|
put_uint32(pktout, c->localid);
|
|
put_stringz(pktout, hostname);
|
|
put_uint32(pktout, port);
|
|
/* originator string would go here, but we didn't specify
|
|
* SSH_PROTOFLAG_HOST_IN_FWD_OPEN */
|
|
pq_push(s->ppl.out_pq, pktout);
|
|
|
|
return &c->sc;
|
|
}
|
|
|
|
static void ssh1_rportfwd_remove(ConnectionLayer *cl, struct ssh_rportfwd *rpf)
|
|
{
|
|
/*
|
|
* We cannot cancel listening ports on the server side in SSH-1!
|
|
* There's no message to support it.
|
|
*/
|
|
}
|
|
|
|
static bool ssh1_agent_forwarding_permitted(ConnectionLayer *cl)
|
|
{
|
|
struct ssh1_connection_state *s =
|
|
container_of(cl, struct ssh1_connection_state, cl);
|
|
return conf_get_bool(s->conf, CONF_agentfwd) && agent_exists();
|
|
}
|
|
|
|
static void ssh1_connection_special_cmd(PacketProtocolLayer *ppl,
|
|
SessionSpecialCode code, int arg)
|
|
{
|
|
struct ssh1_connection_state *s =
|
|
container_of(ppl, struct ssh1_connection_state, ppl);
|
|
PktOut *pktout;
|
|
|
|
if (code == SS_PING || code == SS_NOP) {
|
|
if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) {
|
|
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_IGNORE);
|
|
put_stringz(pktout, "");
|
|
pq_push(s->ppl.out_pq, pktout);
|
|
}
|
|
} else if (s->mainchan) {
|
|
mainchan_special_cmd(s->mainchan, code, arg);
|
|
}
|
|
}
|
|
|
|
static void ssh1_terminal_size(ConnectionLayer *cl, int width, int height)
|
|
{
|
|
struct ssh1_connection_state *s =
|
|
container_of(cl, struct ssh1_connection_state, cl);
|
|
|
|
s->term_width = width;
|
|
s->term_height = height;
|
|
if (s->mainchan)
|
|
mainchan_terminal_size(s->mainchan, width, height);
|
|
}
|
|
|
|
static void ssh1_stdout_unthrottle(ConnectionLayer *cl, size_t bufsize)
|
|
{
|
|
struct ssh1_connection_state *s =
|
|
container_of(cl, struct ssh1_connection_state, cl);
|
|
|
|
if (s->stdout_throttling && bufsize < SSH1_BUFFER_LIMIT) {
|
|
s->stdout_throttling = false;
|
|
ssh_throttle_conn(s->ppl.ssh, -1);
|
|
}
|
|
}
|
|
|
|
static size_t ssh1_stdin_backlog(ConnectionLayer *cl)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void ssh1_throttle_all_channels(ConnectionLayer *cl, bool throttled)
|
|
{
|
|
struct ssh1_connection_state *s =
|
|
container_of(cl, struct ssh1_connection_state, cl);
|
|
struct ssh1_channel *c;
|
|
int i;
|
|
|
|
for (i = 0; NULL != (c = index234(s->channels, i)); i++)
|
|
chan_set_input_wanted(c->chan, !throttled);
|
|
}
|
|
|
|
static bool ssh1_ldisc_option(ConnectionLayer *cl, int option)
|
|
{
|
|
struct ssh1_connection_state *s =
|
|
container_of(cl, struct ssh1_connection_state, cl);
|
|
|
|
return s->ldisc_opts[option];
|
|
}
|
|
|
|
static void ssh1_set_ldisc_option(ConnectionLayer *cl, int option, bool value)
|
|
{
|
|
struct ssh1_connection_state *s =
|
|
container_of(cl, struct ssh1_connection_state, cl);
|
|
|
|
s->ldisc_opts[option] = value;
|
|
}
|
|
|
|
static void ssh1_enable_x_fwd(ConnectionLayer *cl)
|
|
{
|
|
struct ssh1_connection_state *s =
|
|
container_of(cl, struct ssh1_connection_state, cl);
|
|
|
|
s->X11_fwd_enabled = true;
|
|
}
|
|
|
|
static void ssh1_set_wants_user_input(ConnectionLayer *cl, bool wanted)
|
|
{
|
|
struct ssh1_connection_state *s =
|
|
container_of(cl, struct ssh1_connection_state, cl);
|
|
|
|
s->want_user_input = wanted;
|
|
s->finished_setup = true;
|
|
if (wanted)
|
|
ssh_check_sendok(s->ppl.ssh);
|
|
}
|
|
|
|
static bool ssh1_get_wants_user_input(ConnectionLayer *cl)
|
|
{
|
|
struct ssh1_connection_state *s =
|
|
container_of(cl, struct ssh1_connection_state, cl);
|
|
|
|
return s->want_user_input;
|
|
}
|
|
|
|
static void ssh1_got_user_input(ConnectionLayer *cl)
|
|
{
|
|
struct ssh1_connection_state *s =
|
|
container_of(cl, struct ssh1_connection_state, cl);
|
|
|
|
while (s->mainchan && bufchain_size(s->user_input) > 0) {
|
|
/*
|
|
* Add user input to the main channel's buffer.
|
|
*/
|
|
ptrlen data = bufchain_prefix(s->user_input);
|
|
if (data.len > 512)
|
|
data.len = 512;
|
|
sshfwd_write(&s->mainchan_sc, data.ptr, data.len);
|
|
bufchain_consume(s->user_input, data.len);
|
|
}
|
|
}
|
|
|
|
static void ssh1_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf)
|
|
{
|
|
struct ssh1_connection_state *s =
|
|
container_of(ppl, struct ssh1_connection_state, ppl);
|
|
|
|
conf_free(s->conf);
|
|
s->conf = conf_copy(conf);
|
|
|
|
if (s->portfwdmgr_configured)
|
|
portfwdmgr_config(s->portfwdmgr, s->conf);
|
|
}
|