mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-03-21 22:28:37 -05:00

The system for handling seat_get_userpass_input has always been structured differently between GUI PuTTY and CLI tools like Plink. In the CLI tools, password input is read directly from the OS terminal/console device by console_get_userpass_input; this means that you need to ensure the same terminal input data _hasn't_ already been consumed by the main event loop and sent on to the backend. This is achieved by the backend_sendok() method, which tells the event loop when the backend has finished issuing password prompts, and hence, when it's safe to start passing standard input to backend_send(). But in the GUI tools, input generated by the terminal window has always been sent straight to backend_send(), regardless of whether backend_sendok() says it wants it. So the terminal-based implementation of username and password prompts has to work by consuming input data that had _already_ been passed to the backend - hence, any backend that needs to do that must keep its input on a bufchain, and pass that bufchain to seat_get_userpass_input. It's awkward that these two totally different systems coexist in the first place. And now that SSH proxying needs to present interactive prompts of its own, it's clear which one should win: the CLI style is the Right Thing. So this change reworks the GUI side of the mechanism to be more similar: terminal data now goes into a queue in the Ldisc, and is not sent on to the backend until the backend says it's ready for it via backend_sendok(). So terminal-based userpass prompts can now consume data directly from that queue during the connection setup stage. As a result, the 'bufchain *' parameter has vanished from all the userpass_input functions (both the official implementations of the Seat trait method, and term_get_userpass_input() to which some of those implementations delegate). The only function that actually used that bufchain, namely term_get_userpass_input(), now instead reads from the ldisc's input queue via a couple of new Ldisc functions. (Not _trivial_ functions, since input buffered by Ldisc can be a mixture of raw bytes and session specials like SS_EOL! The input queue inside Ldisc is a bufchain containing a fiddly binary encoding that can represent an arbitrary interleaving of those things.) This greatly simplifies the calls to seat_get_userpass_input in backends, which now don't have to mess about with passing their own user_input bufchain around, or toggling their want_user_input flag back and forth to request data to put on to that bufchain. But the flip side is that now there has to be some _other_ method for notifying the terminal when there's more input to be consumed during an interactive prompt, and for notifying the backend when prompt input has finished so that it can proceed to the next stage of the protocol. This is done by a pair of extra callbacks: when more data is put on to Ldisc's input queue, it triggers a call to term_get_userpass_input, and when term_get_userpass_input finishes, it calls a callback function provided in the prompts_t. Therefore, any use of a prompts_t which *might* be asynchronous must fill in the latter callback when setting up the prompts_t. In SSH, the callback is centralised into a common PPL helper function, which reinvokes the same PPL's process_queue coroutine; in rlogin we have to set it up ourselves. I'm sorry for this large and sprawling patch: I tried fairly hard to break it up into individually comprehensible sub-patches, but I just couldn't tease out any part of it that would stand sensibly alone.
810 lines
26 KiB
C
810 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 bool ssh1_connection_want_user_input(PacketProtocolLayer *ppl);
|
|
static void ssh1_connection_got_user_input(PacketProtocolLayer *ppl);
|
|
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,
|
|
.want_user_input = ssh1_connection_want_user_input,
|
|
.got_user_input = ssh1_connection_got_user_input,
|
|
.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 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,
|
|
/* 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, 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);
|
|
|
|
/* 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(
|
|
s->ppl.seat, s->antispoof_prompt);
|
|
while (s->antispoof_ret < 0) {
|
|
crReturnV;
|
|
s->antispoof_ret = seat_get_userpass_input(
|
|
s->ppl.seat, 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_connection_want_user_input(PacketProtocolLayer *ppl)
|
|
{
|
|
struct ssh1_connection_state *s =
|
|
container_of(ppl, struct ssh1_connection_state, ppl);
|
|
|
|
return s->want_user_input;
|
|
}
|
|
|
|
static void ssh1_connection_got_user_input(PacketProtocolLayer *ppl)
|
|
{
|
|
struct ssh1_connection_state *s =
|
|
container_of(ppl, struct ssh1_connection_state, ppl);
|
|
|
|
while (s->mainchan && bufchain_size(s->ppl.user_input) > 0) {
|
|
/*
|
|
* Add user input to the main channel's buffer.
|
|
*/
|
|
ptrlen data = bufchain_prefix(s->ppl.user_input);
|
|
if (data.len > 512)
|
|
data.len = 512;
|
|
sshfwd_write(&s->mainchan_sc, data.ptr, data.len);
|
|
bufchain_consume(s->ppl.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);
|
|
}
|