mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-09 17:38:00 +00:00
9fcbb86f71
This centralises the messages for weak crypto algorithms (general, and host keys in particular, the latter including a list of all the other available host key types) into ssh/common.c, in much the same way as we previously did for ordinary host key warnings. The reason is the same too: I'm about to want to vary the text in one of those dialog boxes, so it's convenient to start by putting it somewhere that I can modify just once.
775 lines
24 KiB
C
775 lines
24 KiB
C
/*
|
|
* sshproxy.c: implement a Socket type that talks to an entire
|
|
* subsidiary SSH connection (sometimes called a 'jump host').
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
|
|
#include "putty.h"
|
|
#include "ssh.h"
|
|
#include "network.h"
|
|
#include "storage.h"
|
|
#include "proxy.h"
|
|
|
|
const bool ssh_proxy_supported = true;
|
|
|
|
typedef struct SshProxy {
|
|
char *errmsg;
|
|
Conf *conf;
|
|
LogContext *logctx;
|
|
Backend *backend;
|
|
LogPolicy *clientlp;
|
|
Seat *clientseat;
|
|
Interactor *clientitr;
|
|
|
|
bool got_proxy_password, tried_proxy_password;
|
|
char *proxy_password;
|
|
|
|
ProxyStderrBuf psb;
|
|
Plug *plug;
|
|
|
|
bool frozen;
|
|
bufchain ssh_to_socket;
|
|
bool rcvd_eof_ssh_to_socket, sent_eof_ssh_to_socket;
|
|
bool conn_established;
|
|
|
|
SockAddr *addr;
|
|
int port;
|
|
|
|
/* Traits implemented: we're a Socket from the point of view of
|
|
* the client connection, and a Seat from the POV of the SSH
|
|
* backend we instantiate. */
|
|
Socket sock;
|
|
LogPolicy logpolicy;
|
|
Seat seat;
|
|
} SshProxy;
|
|
|
|
static Plug *sshproxy_plug(Socket *s, Plug *p)
|
|
{
|
|
SshProxy *sp = container_of(s, SshProxy, sock);
|
|
Plug *oldplug = sp->plug;
|
|
if (p)
|
|
sp->plug = p;
|
|
return oldplug;
|
|
}
|
|
|
|
static void sshproxy_close(Socket *s)
|
|
{
|
|
SshProxy *sp = container_of(s, SshProxy, sock);
|
|
|
|
sk_addr_free(sp->addr);
|
|
sfree(sp->errmsg);
|
|
conf_free(sp->conf);
|
|
if (sp->backend)
|
|
backend_free(sp->backend);
|
|
if (sp->logctx)
|
|
log_free(sp->logctx);
|
|
if (sp->proxy_password)
|
|
burnstr(sp->proxy_password);
|
|
bufchain_clear(&sp->ssh_to_socket);
|
|
|
|
delete_callbacks_for_context(sp);
|
|
sfree(sp);
|
|
}
|
|
|
|
static size_t sshproxy_write(Socket *s, const void *data, size_t len)
|
|
{
|
|
SshProxy *sp = container_of(s, SshProxy, sock);
|
|
if (!sp->backend)
|
|
return 0;
|
|
backend_send(sp->backend, data, len);
|
|
return backend_sendbuffer(sp->backend);
|
|
}
|
|
|
|
static size_t sshproxy_write_oob(Socket *s, const void *data, size_t len)
|
|
{
|
|
/*
|
|
* oob data is treated as inband; nasty, but nothing really
|
|
* better we can do
|
|
*/
|
|
return sshproxy_write(s, data, len);
|
|
}
|
|
|
|
static void sshproxy_write_eof(Socket *s)
|
|
{
|
|
SshProxy *sp = container_of(s, SshProxy, sock);
|
|
if (!sp->backend)
|
|
return;
|
|
backend_special(sp->backend, SS_EOF, 0);
|
|
}
|
|
|
|
static void try_send_ssh_to_socket(void *ctx);
|
|
|
|
static void try_send_ssh_to_socket_cb(void *ctx)
|
|
{
|
|
SshProxy *sp = (SshProxy *)ctx;
|
|
try_send_ssh_to_socket(sp);
|
|
if (sp->backend)
|
|
backend_unthrottle(sp->backend, bufchain_size(&sp->ssh_to_socket));
|
|
}
|
|
|
|
static void sshproxy_set_frozen(Socket *s, bool is_frozen)
|
|
{
|
|
SshProxy *sp = container_of(s, SshProxy, sock);
|
|
sp->frozen = is_frozen;
|
|
if (!sp->frozen)
|
|
queue_toplevel_callback(try_send_ssh_to_socket_cb, sp);
|
|
}
|
|
|
|
static const char *sshproxy_socket_error(Socket *s)
|
|
{
|
|
SshProxy *sp = container_of(s, SshProxy, sock);
|
|
return sp->errmsg;
|
|
}
|
|
|
|
static SocketPeerInfo *sshproxy_peer_info(Socket *s)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
static const SocketVtable SshProxy_sock_vt = {
|
|
.plug = sshproxy_plug,
|
|
.close = sshproxy_close,
|
|
.write = sshproxy_write,
|
|
.write_oob = sshproxy_write_oob,
|
|
.write_eof = sshproxy_write_eof,
|
|
.set_frozen = sshproxy_set_frozen,
|
|
.socket_error = sshproxy_socket_error,
|
|
.peer_info = sshproxy_peer_info,
|
|
};
|
|
|
|
static void sshproxy_eventlog(LogPolicy *lp, const char *event)
|
|
{
|
|
SshProxy *sp = container_of(lp, SshProxy, logpolicy);
|
|
log_proxy_stderr(sp->plug, &sp->psb, event, strlen(event));
|
|
log_proxy_stderr(sp->plug, &sp->psb, "\n", 1);
|
|
}
|
|
|
|
static int sshproxy_askappend(LogPolicy *lp, Filename *filename,
|
|
void (*callback)(void *ctx, int result),
|
|
void *ctx)
|
|
{
|
|
SshProxy *sp = container_of(lp, SshProxy, logpolicy);
|
|
|
|
/*
|
|
* If we have access to the outer LogPolicy, pass on this request
|
|
* to the end user.
|
|
*/
|
|
if (sp->clientlp)
|
|
return lp_askappend(sp->clientlp, filename, callback, ctx);
|
|
|
|
/*
|
|
* Otherwise, fall back to the safe noninteractive assumption.
|
|
*/
|
|
char *msg = dupprintf("Log file \"%s\" already exists; logging cancelled",
|
|
filename_to_str(filename));
|
|
sshproxy_eventlog(lp, msg);
|
|
sfree(msg);
|
|
return 0;
|
|
}
|
|
|
|
static void sshproxy_logging_error(LogPolicy *lp, const char *event)
|
|
{
|
|
SshProxy *sp = container_of(lp, SshProxy, logpolicy);
|
|
|
|
/*
|
|
* If we have access to the outer LogPolicy, pass on this request
|
|
* to it.
|
|
*/
|
|
if (sp->clientlp) {
|
|
lp_logging_error(sp->clientlp, event);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Otherwise, the best we can do is to put it in the outer SSH
|
|
* connection's Event Log.
|
|
*/
|
|
char *msg = dupprintf("Logging error: %s", event);
|
|
sshproxy_eventlog(lp, msg);
|
|
sfree(msg);
|
|
}
|
|
|
|
static const LogPolicyVtable SshProxy_logpolicy_vt = {
|
|
.eventlog = sshproxy_eventlog,
|
|
.askappend = sshproxy_askappend,
|
|
.logging_error = sshproxy_logging_error,
|
|
.verbose = null_lp_verbose_no,
|
|
};
|
|
|
|
/*
|
|
* Function called when we encounter an error during connection setup that's
|
|
* likely to be the cause of terminating the proxy SSH connection. Putting it
|
|
* in the Event Log is useful on general principles; also putting it in
|
|
* sp->errmsg meaks that it will be passed back through plug_closing when the
|
|
* proxy SSH connection actually terminates, so that the end user will see
|
|
* what went wrong in the proxy connection.
|
|
*/
|
|
static void sshproxy_error(SshProxy *sp, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
char *msg = dupvprintf(fmt, ap);
|
|
va_end(ap);
|
|
|
|
if (!sp->errmsg)
|
|
sp->errmsg = dupstr(msg);
|
|
|
|
sshproxy_eventlog(&sp->logpolicy, msg);
|
|
sfree(msg);
|
|
}
|
|
|
|
static void try_send_ssh_to_socket(void *ctx)
|
|
{
|
|
SshProxy *sp = (SshProxy *)ctx;
|
|
|
|
if (sp->frozen)
|
|
return;
|
|
|
|
while (bufchain_size(&sp->ssh_to_socket)) {
|
|
ptrlen pl = bufchain_prefix(&sp->ssh_to_socket);
|
|
plug_receive(sp->plug, 0, pl.ptr, pl.len);
|
|
bufchain_consume(&sp->ssh_to_socket, pl.len);
|
|
}
|
|
|
|
if (sp->rcvd_eof_ssh_to_socket &&
|
|
!sp->sent_eof_ssh_to_socket) {
|
|
sp->sent_eof_ssh_to_socket = true;
|
|
plug_closing_normal(sp->plug);
|
|
}
|
|
}
|
|
|
|
static void sshproxy_notify_session_started(Seat *seat)
|
|
{
|
|
SshProxy *sp = container_of(seat, SshProxy, seat);
|
|
|
|
if (sp->clientseat)
|
|
interactor_return_seat(sp->clientitr);
|
|
sp->conn_established = true;
|
|
|
|
plug_log(sp->plug, PLUGLOG_CONNECT_SUCCESS, sp->addr, sp->port, NULL, 0);
|
|
}
|
|
|
|
static size_t sshproxy_output(Seat *seat, SeatOutputType type,
|
|
const void *data, size_t len)
|
|
{
|
|
SshProxy *sp = container_of(seat, SshProxy, seat);
|
|
switch (type) {
|
|
case SEAT_OUTPUT_STDOUT:
|
|
bufchain_add(&sp->ssh_to_socket, data, len);
|
|
try_send_ssh_to_socket(sp);
|
|
break;
|
|
case SEAT_OUTPUT_STDERR:
|
|
log_proxy_stderr(sp->plug, &sp->psb, data, len);
|
|
break;
|
|
}
|
|
return bufchain_size(&sp->ssh_to_socket);
|
|
}
|
|
|
|
static inline InteractionReadySeat wrap(Seat *seat)
|
|
{
|
|
/*
|
|
* When we receive interaction requests from the proxy and want to
|
|
* pass them on to our client Seat, we have to present them to the
|
|
* latter in the form of an InteractionReadySeat. This forwarding
|
|
* scenario is the one case where we _mustn't_ get an
|
|
* InteractionReadySeat by calling interactor_announce(), because
|
|
* the point is that we're _not_ the originating Interactor, we're
|
|
* just forwarding the request from the real one, which has
|
|
* already announced itself.
|
|
*
|
|
* So, just here in the code, it really is the right thing to make
|
|
* an InteractionReadySeat out of a plain Seat * without an
|
|
* announcement.
|
|
*/
|
|
InteractionReadySeat iseat;
|
|
iseat.seat = seat;
|
|
return iseat;
|
|
}
|
|
|
|
static size_t sshproxy_banner(Seat *seat, const void *data, size_t len)
|
|
{
|
|
SshProxy *sp = container_of(seat, SshProxy, seat);
|
|
if (sp->clientseat) {
|
|
/*
|
|
* If we have access to the outer Seat, pass the SSH login
|
|
* banner on to it.
|
|
*/
|
|
return seat_banner(wrap(sp->clientseat), data, len);
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static bool sshproxy_eof(Seat *seat)
|
|
{
|
|
SshProxy *sp = container_of(seat, SshProxy, seat);
|
|
sp->rcvd_eof_ssh_to_socket = true;
|
|
try_send_ssh_to_socket(sp);
|
|
return false;
|
|
}
|
|
|
|
static void sshproxy_sent(Seat *seat, size_t new_bufsize)
|
|
{
|
|
SshProxy *sp = container_of(seat, SshProxy, seat);
|
|
plug_sent(sp->plug, new_bufsize);
|
|
}
|
|
|
|
static void sshproxy_send_close(SshProxy *sp)
|
|
{
|
|
if (sp->clientseat)
|
|
interactor_return_seat(sp->clientitr);
|
|
|
|
if (!sp->conn_established)
|
|
plug_log(sp->plug, PLUGLOG_CONNECT_FAILED, sp->addr, sp->port,
|
|
sp->errmsg, 0);
|
|
|
|
if (sp->errmsg)
|
|
plug_closing_error(sp->plug, sp->errmsg);
|
|
else if (!sp->conn_established && backend_exitcode(sp->backend) == 0)
|
|
plug_closing_user_abort(sp->plug);
|
|
else
|
|
plug_closing_normal(sp->plug);
|
|
}
|
|
|
|
static void sshproxy_notify_remote_disconnect_callback(void *vctx)
|
|
{
|
|
SshProxy *sp = (SshProxy *)vctx;
|
|
|
|
/* notify_remote_disconnect can be called redundantly, so first
|
|
* check if the backend really has become disconnected */
|
|
if (backend_connected(sp->backend))
|
|
return;
|
|
|
|
sshproxy_send_close(sp);
|
|
}
|
|
|
|
static void sshproxy_notify_remote_disconnect(Seat *seat)
|
|
{
|
|
SshProxy *sp = container_of(seat, SshProxy, seat);
|
|
queue_toplevel_callback(sshproxy_notify_remote_disconnect_callback, sp);
|
|
}
|
|
|
|
static SeatPromptResult sshproxy_get_userpass_input(Seat *seat, prompts_t *p)
|
|
{
|
|
SshProxy *sp = container_of(seat, SshProxy, seat);
|
|
|
|
/*
|
|
* If we have a stored proxy_password, use that, via logic similar
|
|
* to cmdline_get_passwd_input: we only try it if we're given a
|
|
* prompts_t containing exactly one prompt, and that prompt is set
|
|
* to non-echoing.
|
|
*/
|
|
if (sp->got_proxy_password && !sp->tried_proxy_password &&
|
|
p->n_prompts == 1 && !p->prompts[0]->echo) {
|
|
prompt_set_result(p->prompts[0], sp->proxy_password);
|
|
burnstr(sp->proxy_password);
|
|
sp->proxy_password = NULL;
|
|
sp->tried_proxy_password = true;
|
|
return SPR_OK;
|
|
}
|
|
|
|
if (sp->clientseat) {
|
|
/*
|
|
* If we have access to the outer Seat, pass this prompt
|
|
* request on to it.
|
|
*/
|
|
return seat_get_userpass_input(wrap(sp->clientseat), p);
|
|
}
|
|
|
|
/*
|
|
* Otherwise, behave as if noninteractive (like plink -batch):
|
|
* reject all attempts to present a prompt to the user, and log in
|
|
* the Event Log to say why not.
|
|
*/
|
|
sshproxy_error(sp, "Unable to provide interactive authentication "
|
|
"requested by proxy SSH connection");
|
|
return SPR_SW_ABORT("Noninteractive SSH proxy cannot perform "
|
|
"interactive authentication");
|
|
}
|
|
|
|
static void sshproxy_connection_fatal_callback(void *vctx)
|
|
{
|
|
SshProxy *sp = (SshProxy *)vctx;
|
|
sshproxy_send_close(sp);
|
|
}
|
|
|
|
static void sshproxy_connection_fatal(Seat *seat, const char *message)
|
|
{
|
|
SshProxy *sp = container_of(seat, SshProxy, seat);
|
|
if (!sp->errmsg) {
|
|
sp->errmsg = dupprintf(
|
|
"fatal error in proxy SSH connection: %s", message);
|
|
queue_toplevel_callback(sshproxy_connection_fatal_callback, sp);
|
|
}
|
|
}
|
|
|
|
static SeatPromptResult sshproxy_confirm_ssh_host_key(
|
|
Seat *seat, const char *host, int port, const char *keytype,
|
|
char *keystr, SeatDialogText *text, HelpCtx helpctx,
|
|
void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
|
|
{
|
|
SshProxy *sp = container_of(seat, SshProxy, seat);
|
|
|
|
if (sp->clientseat) {
|
|
/*
|
|
* If we have access to the outer Seat, pass this prompt
|
|
* request on to it.
|
|
*/
|
|
return seat_confirm_ssh_host_key(
|
|
wrap(sp->clientseat), host, port, keytype, keystr, text,
|
|
helpctx, callback, ctx);
|
|
}
|
|
|
|
/*
|
|
* Otherwise, behave as if we're in batch mode, i.e. take the safe
|
|
* option in the absence of interactive confirmation, i.e. abort
|
|
* the connection.
|
|
*/
|
|
return SPR_SW_ABORT("Noninteractive SSH proxy cannot confirm host key");
|
|
}
|
|
|
|
static void sshproxy_format_seatdialogtext(strbuf *sb, SeatDialogText *text)
|
|
{
|
|
for (SeatDialogTextItem *item = text->items,
|
|
*end = item+text->nitems; item < end; item++) {
|
|
switch (item->type) {
|
|
case SDT_SCARY_HEADING:
|
|
case SDT_PARA:
|
|
case SDT_DISPLAY:
|
|
put_stringz(sb, item->text);
|
|
put_byte(sb, '\n');
|
|
break;
|
|
case SDT_BATCH_ABORT:
|
|
put_stringz(sb, item->text);
|
|
put_byte(sb, '\n');
|
|
goto endloop;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
endloop:
|
|
while (strbuf_chomp(sb, '\n'));
|
|
}
|
|
|
|
static SeatPromptResult sshproxy_confirm_weak_crypto_primitive(
|
|
Seat *seat, SeatDialogText *text,
|
|
void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
|
|
{
|
|
SshProxy *sp = container_of(seat, SshProxy, seat);
|
|
|
|
if (sp->clientseat) {
|
|
/*
|
|
* If we have access to the outer Seat, pass this prompt
|
|
* request on to it.
|
|
*/
|
|
return seat_confirm_weak_crypto_primitive(
|
|
wrap(sp->clientseat), text, callback, ctx);
|
|
}
|
|
|
|
/*
|
|
* Otherwise, behave as if we're in batch mode: take the safest
|
|
* option.
|
|
*/
|
|
strbuf *sb = strbuf_new();
|
|
sshproxy_format_seatdialogtext(sb, text);
|
|
sshproxy_error(sp, sb->s);
|
|
strbuf_free(sb);
|
|
|
|
return SPR_SW_ABORT("Noninteractive SSH proxy cannot confirm "
|
|
"weak crypto primitive");
|
|
}
|
|
|
|
static SeatPromptResult sshproxy_confirm_weak_cached_hostkey(
|
|
Seat *seat, SeatDialogText *text,
|
|
void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
|
|
{
|
|
SshProxy *sp = container_of(seat, SshProxy, seat);
|
|
|
|
if (sp->clientseat) {
|
|
/*
|
|
* If we have access to the outer Seat, pass this prompt
|
|
* request on to it.
|
|
*/
|
|
return seat_confirm_weak_cached_hostkey(
|
|
wrap(sp->clientseat), text, callback, ctx);
|
|
}
|
|
|
|
/*
|
|
* Otherwise, behave as if we're in batch mode: take the safest
|
|
* option.
|
|
*/
|
|
strbuf *sb = strbuf_new();
|
|
sshproxy_format_seatdialogtext(sb, text);
|
|
sshproxy_error(sp, sb->s);
|
|
strbuf_free(sb);
|
|
|
|
return SPR_SW_ABORT("Noninteractive SSH proxy cannot confirm "
|
|
"weak cached host key");
|
|
}
|
|
|
|
static const SeatDialogPromptDescriptions *sshproxy_prompt_descriptions(
|
|
Seat *seat)
|
|
{
|
|
SshProxy *sp = container_of(seat, SshProxy, seat);
|
|
|
|
/* If we have a client seat, return their prompt descriptions, so
|
|
* that prompts passed on to them will make sense. */
|
|
if (sp->clientseat)
|
|
return seat_prompt_descriptions(sp->clientseat);
|
|
|
|
/* Otherwise, it doesn't matter what we return, so do the easiest thing. */
|
|
return nullseat_prompt_descriptions(NULL);
|
|
}
|
|
|
|
static StripCtrlChars *sshproxy_stripctrl_new(
|
|
Seat *seat, BinarySink *bs_out, SeatInteractionContext sic)
|
|
{
|
|
SshProxy *sp = container_of(seat, SshProxy, seat);
|
|
if (sp->clientseat)
|
|
return seat_stripctrl_new(sp->clientseat, bs_out, sic);
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
static void sshproxy_set_trust_status(Seat *seat, bool trusted)
|
|
{
|
|
SshProxy *sp = container_of(seat, SshProxy, seat);
|
|
if (sp->clientseat)
|
|
seat_set_trust_status(sp->clientseat, trusted);
|
|
}
|
|
|
|
static bool sshproxy_can_set_trust_status(Seat *seat)
|
|
{
|
|
SshProxy *sp = container_of(seat, SshProxy, seat);
|
|
return sp->clientseat && seat_can_set_trust_status(sp->clientseat);
|
|
}
|
|
|
|
static bool sshproxy_verbose(Seat *seat)
|
|
{
|
|
SshProxy *sp = container_of(seat, SshProxy, seat);
|
|
return sp->clientseat && seat_verbose(sp->clientseat);
|
|
}
|
|
|
|
static bool sshproxy_interactive(Seat *seat)
|
|
{
|
|
SshProxy *sp = container_of(seat, SshProxy, seat);
|
|
return sp->clientseat && seat_interactive(sp->clientseat);
|
|
}
|
|
|
|
static const SeatVtable SshProxy_seat_vt = {
|
|
.output = sshproxy_output,
|
|
.eof = sshproxy_eof,
|
|
.sent = sshproxy_sent,
|
|
.banner = sshproxy_banner,
|
|
.get_userpass_input = sshproxy_get_userpass_input,
|
|
.notify_session_started = sshproxy_notify_session_started,
|
|
.notify_remote_exit = nullseat_notify_remote_exit,
|
|
.notify_remote_disconnect = sshproxy_notify_remote_disconnect,
|
|
.connection_fatal = sshproxy_connection_fatal,
|
|
.update_specials_menu = nullseat_update_specials_menu,
|
|
.get_ttymode = nullseat_get_ttymode,
|
|
.set_busy_status = nullseat_set_busy_status,
|
|
.confirm_ssh_host_key = sshproxy_confirm_ssh_host_key,
|
|
.confirm_weak_crypto_primitive = sshproxy_confirm_weak_crypto_primitive,
|
|
.confirm_weak_cached_hostkey = sshproxy_confirm_weak_cached_hostkey,
|
|
.prompt_descriptions = sshproxy_prompt_descriptions,
|
|
.is_utf8 = nullseat_is_never_utf8,
|
|
.echoedit_update = nullseat_echoedit_update,
|
|
.get_x_display = nullseat_get_x_display,
|
|
.get_windowid = nullseat_get_windowid,
|
|
.get_window_pixel_size = nullseat_get_window_pixel_size,
|
|
.stripctrl_new = sshproxy_stripctrl_new,
|
|
.set_trust_status = sshproxy_set_trust_status,
|
|
.can_set_trust_status = sshproxy_can_set_trust_status,
|
|
.has_mixed_input_stream = nullseat_has_mixed_input_stream_no,
|
|
.verbose = sshproxy_verbose,
|
|
.interactive = sshproxy_interactive,
|
|
.get_cursor_position = nullseat_get_cursor_position,
|
|
};
|
|
|
|
Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname,
|
|
int port, bool privport,
|
|
bool oobinline, bool nodelay, bool keepalive,
|
|
Plug *plug, Conf *clientconf,
|
|
Interactor *clientitr)
|
|
{
|
|
SshProxy *sp = snew(SshProxy);
|
|
memset(sp, 0, sizeof(*sp));
|
|
|
|
sp->sock.vt = &SshProxy_sock_vt;
|
|
sp->logpolicy.vt = &SshProxy_logpolicy_vt;
|
|
sp->seat.vt = &SshProxy_seat_vt;
|
|
sp->plug = plug;
|
|
psb_init(&sp->psb);
|
|
bufchain_init(&sp->ssh_to_socket);
|
|
|
|
sp->addr = addr;
|
|
sp->port = port;
|
|
|
|
sp->conf = conf_new();
|
|
/* Try to treat proxy_hostname as the title of a saved session. If
|
|
* that fails, set up a default Conf of our own treating it as a
|
|
* hostname. */
|
|
const char *proxy_hostname = conf_get_str(clientconf, CONF_proxy_host);
|
|
if (do_defaults(proxy_hostname, sp->conf)) {
|
|
if (!conf_launchable(sp->conf)) {
|
|
sp->errmsg = dupprintf("saved session '%s' is not launchable",
|
|
proxy_hostname);
|
|
return &sp->sock;
|
|
}
|
|
} else {
|
|
do_defaults(NULL, sp->conf);
|
|
/* In hostname mode, we default to PROT_SSH. This is more useful than
|
|
* the obvious approach of defaulting to the protocol defined in
|
|
* Default Settings, because only SSH (ok, and bare ssh-connection)
|
|
* can be used for this kind of proxy. */
|
|
conf_set_int(sp->conf, CONF_protocol, PROT_SSH);
|
|
conf_set_str(sp->conf, CONF_host, proxy_hostname);
|
|
conf_set_int(sp->conf, CONF_port,
|
|
conf_get_int(clientconf, CONF_proxy_port));
|
|
}
|
|
const char *proxy_username = conf_get_str(clientconf, CONF_proxy_username);
|
|
if (*proxy_username)
|
|
conf_set_str(sp->conf, CONF_username, proxy_username);
|
|
|
|
const char *proxy_password = conf_get_str(clientconf, CONF_proxy_password);
|
|
if (*proxy_password) {
|
|
sp->proxy_password = dupstr(proxy_password);
|
|
sp->got_proxy_password = true;
|
|
}
|
|
|
|
const struct BackendVtable *backvt = backend_vt_from_proto(
|
|
conf_get_int(sp->conf, CONF_protocol));
|
|
|
|
/*
|
|
* We don't actually need an _SSH_ session specifically: it's also
|
|
* OK to use PROT_SSHCONN, because really, the criterion is
|
|
* whether setting CONF_ssh_nc_host will do anything useful. So
|
|
* our check is for whether the backend sets the flag promising
|
|
* that it does.
|
|
*/
|
|
if (!backvt || !(backvt->flags & BACKEND_SUPPORTS_NC_HOST)) {
|
|
sp->errmsg = dupprintf("saved session '%s' is not an SSH session",
|
|
proxy_hostname);
|
|
return &sp->sock;
|
|
}
|
|
|
|
/*
|
|
* We also expect that the backend will announce a willingness to
|
|
* notify us that the session has started. Any backend providing
|
|
* NC_HOST should also provide this.
|
|
*/
|
|
assert(backvt->flags & BACKEND_NOTIFIES_SESSION_START &&
|
|
"Backend provides NC_HOST without SESSION_START!");
|
|
|
|
/*
|
|
* Turn off SSH features we definitely don't want. It would be
|
|
* awkward and counterintuitive to have the proxy SSH connection
|
|
* become a connection-sharing upstream (but it's fine to have it
|
|
* be a downstream, if that's configured). And we don't want to
|
|
* open X forwardings, agent forwardings or (other) port
|
|
* forwardings as a side effect of this one operation.
|
|
*/
|
|
conf_set_bool(sp->conf, CONF_ssh_connection_sharing_upstream, false);
|
|
conf_set_bool(sp->conf, CONF_x11_forward, false);
|
|
conf_set_bool(sp->conf, CONF_agentfwd, false);
|
|
for (const char *subkey;
|
|
(subkey = conf_get_str_nthstrkey(sp->conf, CONF_portfwd, 0)) != NULL;)
|
|
conf_del_str_str(sp->conf, CONF_portfwd, subkey);
|
|
|
|
/*
|
|
* We'll only be running one channel through this connection
|
|
* (since we've just turned off all the other things we might have
|
|
* done with it), so we can configure it as simple.
|
|
*/
|
|
conf_set_bool(sp->conf, CONF_ssh_simple, true);
|
|
|
|
int proxy_type = conf_get_int(clientconf, CONF_proxy_type);
|
|
switch (proxy_type) {
|
|
case PROXY_SSH_TCPIP:
|
|
/*
|
|
* Configure the main channel of this SSH session to be a
|
|
* direct-tcpip connection to the destination host/port.
|
|
*/
|
|
conf_set_str(sp->conf, CONF_ssh_nc_host, hostname);
|
|
conf_set_int(sp->conf, CONF_ssh_nc_port, port);
|
|
break;
|
|
|
|
case PROXY_SSH_SUBSYSTEM:
|
|
case PROXY_SSH_EXEC: {
|
|
Conf *cmd_conf = conf_copy(clientconf);
|
|
|
|
/*
|
|
* Unlike the Telnet and Local proxy types, we don't use the
|
|
* proxy username and password fields in the formatted
|
|
* command, because if we use them at all, it's for
|
|
* authenticating to the proxy SSH server.
|
|
*/
|
|
conf_set_str(cmd_conf, CONF_proxy_username, "");
|
|
conf_set_str(cmd_conf, CONF_proxy_password, "");
|
|
|
|
char *cmd = format_telnet_command(sp->addr, sp->port, cmd_conf, NULL);
|
|
conf_free(cmd_conf);
|
|
|
|
conf_set_str(sp->conf, CONF_remote_cmd, cmd);
|
|
sfree(cmd);
|
|
|
|
conf_set_bool(sp->conf, CONF_nopty, true);
|
|
|
|
if (proxy_type == PROXY_SSH_SUBSYSTEM)
|
|
conf_set_bool(sp->conf, CONF_ssh_subsys, true);
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
unreachable("bad SSH proxy type");
|
|
}
|
|
|
|
/*
|
|
* Do the usual normalisation of things in the Conf like a "user@"
|
|
* prefix on the hostname field.
|
|
*/
|
|
prepare_session(sp->conf);
|
|
|
|
sp->logctx = log_init(&sp->logpolicy, sp->conf);
|
|
|
|
char *error, *realhost;
|
|
error = backend_init(backvt, &sp->seat, &sp->backend, sp->logctx, sp->conf,
|
|
conf_get_str(sp->conf, CONF_host),
|
|
conf_get_int(sp->conf, CONF_port),
|
|
&realhost, nodelay,
|
|
conf_get_bool(sp->conf, CONF_tcp_keepalives));
|
|
if (error) {
|
|
sp->errmsg = dupprintf("unable to open SSH proxy connection: %s",
|
|
error);
|
|
return &sp->sock;
|
|
}
|
|
|
|
sfree(realhost);
|
|
|
|
/*
|
|
* If we've been given an Interactor by the caller, set ourselves
|
|
* up to work with it.
|
|
*/
|
|
if (clientitr) {
|
|
sp->clientitr = clientitr;
|
|
interactor_set_child(sp->clientitr, sp->backend->interactor);
|
|
|
|
sp->clientlp = interactor_logpolicy(clientitr);
|
|
|
|
/*
|
|
* We can only borrow the client's Seat if our own backend
|
|
* will tell us when to give it back. (SSH-based backends
|
|
* _should_ do that, but we check the flag here anyway.)
|
|
*/
|
|
if (backvt->flags & BACKEND_NOTIFIES_SESSION_START)
|
|
sp->clientseat = interactor_borrow_seat(clientitr);
|
|
}
|
|
|
|
return &sp->sock;
|
|
}
|