1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-07-01 11:32:48 -05:00

Move proxy-related source files into a subdirectory.

There are quite a few of them already, and I'm about to make another
one, so let's start with a bit of tidying up.

The CMake build organisation is unchanged: I haven't put the proxy
object files into a separate library, just moved the locations of the
source files. (Organising proxying as a library would be tricky
anyway, because of the various overrides for tools that want to avoid
cryptography.)
This commit is contained in:
Simon Tatham
2021-10-30 11:02:28 +01:00
parent 76dc28552c
commit 971c70e603
17 changed files with 18 additions and 18 deletions

179
proxy/cproxy.c Normal file
View File

@ -0,0 +1,179 @@
/*
* Routines to do cryptographic interaction with proxies in PuTTY.
* This is in a separate module from proxy.c, so that it can be
* conveniently removed in PuTTYtel by replacing this module with
* the stub version nocproxy.c.
*/
#include <assert.h>
#include <ctype.h>
#include <string.h>
#include "putty.h"
#include "ssh.h" /* For MD5 support */
#include "network.h"
#include "proxy.h"
#include "marshal.h"
static void hmacmd5_chap(const unsigned char *challenge, int challen,
const char *passwd, unsigned char *response)
{
mac_simple(&ssh_hmac_md5, ptrlen_from_asciz(passwd),
make_ptrlen(challenge, challen), response);
}
void proxy_socks5_offerencryptedauth(BinarySink *bs)
{
put_byte(bs, 0x03); /* CHAP */
}
int proxy_socks5_handlechap (ProxySocket *p)
{
/* CHAP authentication reply format:
* version number (1 bytes) = 1
* number of commands (1 byte)
*
* For each command:
* command identifier (1 byte)
* data length (1 byte)
*/
unsigned char data[260];
unsigned char outbuf[20];
while(p->chap_num_attributes == 0 ||
p->chap_num_attributes_processed < p->chap_num_attributes) {
if (p->chap_num_attributes == 0 ||
p->chap_current_attribute == -1) {
/* CHAP normally reads in two bytes, either at the
* beginning or for each attribute/value pair. But if
* we're waiting for the value's data, we might not want
* to read 2 bytes.
*/
if (bufchain_size(&p->pending_input_data) < 2)
return 1; /* not got anything yet */
/* get the response */
bufchain_fetch(&p->pending_input_data, data, 2);
bufchain_consume(&p->pending_input_data, 2);
}
if (p->chap_num_attributes == 0) {
/* If there are no attributes, this is our first msg
* with the server, where we negotiate version and
* number of attributes
*/
if (data[0] != 0x01) {
plug_closing(p->plug, "Proxy error: SOCKS proxy wants"
" a different CHAP version",
PROXY_ERROR_GENERAL);
return 1;
}
if (data[1] == 0x00) {
plug_closing(p->plug, "Proxy error: SOCKS proxy won't"
" negotiate CHAP with us",
PROXY_ERROR_GENERAL);
return 1;
}
p->chap_num_attributes = data[1];
} else {
if (p->chap_current_attribute == -1) {
/* We have to read in each attribute/value pair -
* those we don't understand can be ignored, but
* there are a few we'll need to handle.
*/
p->chap_current_attribute = data[0];
p->chap_current_datalen = data[1];
}
if (bufchain_size(&p->pending_input_data) <
p->chap_current_datalen)
return 1; /* not got everything yet */
/* get the response */
bufchain_fetch(&p->pending_input_data, data,
p->chap_current_datalen);
bufchain_consume(&p->pending_input_data,
p->chap_current_datalen);
switch (p->chap_current_attribute) {
case 0x00:
/* Successful authentication */
if (data[0] == 0x00)
p->state = 2;
else {
plug_closing(p->plug, "Proxy error: SOCKS proxy"
" refused CHAP authentication",
PROXY_ERROR_GENERAL);
return 1;
}
break;
case 0x03:
outbuf[0] = 0x01; /* Version */
outbuf[1] = 0x01; /* One attribute */
outbuf[2] = 0x04; /* Response */
outbuf[3] = 0x10; /* Length */
hmacmd5_chap(data, p->chap_current_datalen,
conf_get_str(p->conf, CONF_proxy_password),
&outbuf[4]);
sk_write(p->sub_socket, outbuf, 20);
break;
case 0x11:
/* Chose a protocol */
if (data[0] != 0x85) {
plug_closing(p->plug, "Proxy error: Server chose "
"CHAP of other than HMAC-MD5 but we "
"didn't offer it!",
PROXY_ERROR_GENERAL);
return 1;
}
break;
}
p->chap_current_attribute = -1;
p->chap_num_attributes_processed++;
}
if (p->state == 8 &&
p->chap_num_attributes_processed >= p->chap_num_attributes) {
p->chap_num_attributes = 0;
p->chap_num_attributes_processed = 0;
p->chap_current_datalen = 0;
}
}
return 0;
}
int proxy_socks5_selectchap(ProxySocket *p)
{
char *username = conf_get_str(p->conf, CONF_proxy_username);
char *password = conf_get_str(p->conf, CONF_proxy_password);
if (username[0] || password[0]) {
char chapbuf[514];
int ulen;
chapbuf[0] = '\x01'; /* Version */
chapbuf[1] = '\x02'; /* Number of attributes sent */
chapbuf[2] = '\x11'; /* First attribute - algorithms list */
chapbuf[3] = '\x01'; /* Only one CHAP algorithm */
chapbuf[4] = '\x85'; /* ...and it's HMAC-MD5, the core one */
chapbuf[5] = '\x02'; /* Second attribute - username */
ulen = strlen(username);
if (ulen > 255) ulen = 255;
if (ulen < 1) ulen = 1;
chapbuf[6] = ulen;
memcpy(chapbuf+7, username, ulen);
sk_write(p->sub_socket, chapbuf, ulen + 7);
p->chap_num_attributes = 0;
p->chap_num_attributes_processed = 0;
p->chap_current_attribute = -1;
p->chap_current_datalen = 0;
p->state = 8;
} else
plug_closing(p->plug, "Proxy error: Server chose "
"CHAP authentication but we didn't offer it!",
PROXY_ERROR_GENERAL);
return 1;
}

35
proxy/nocproxy.c Normal file
View File

@ -0,0 +1,35 @@
/*
* Routines to refuse to do cryptographic interaction with proxies
* in PuTTY. This is a stub implementation of the same interfaces
* provided by cproxy.c, for use in PuTTYtel.
*/
#include <assert.h>
#include <ctype.h>
#include <string.h>
#include "putty.h"
#include "network.h"
#include "proxy.h"
void proxy_socks5_offerencryptedauth(BinarySink *bs)
{
/* For telnet, don't add any new encrypted authentication routines */
}
int proxy_socks5_handlechap (ProxySocket *p)
{
plug_closing(p->plug, "Proxy error: Trying to handle a SOCKS5 CHAP request"
" in telnet-only build",
PROXY_ERROR_GENERAL);
return 1;
}
int proxy_socks5_selectchap(ProxySocket *p)
{
plug_closing(p->plug, "Proxy error: Trying to handle a SOCKS5 CHAP request"
" in telnet-only build",
PROXY_ERROR_GENERAL);
return 1;
}

32
proxy/noproxy.c Normal file
View File

@ -0,0 +1,32 @@
/*
* noproxy.c: an alternative to proxy.c, for use by auxiliary programs
* that need to make network connections but don't want to include all
* the full-on support for endless network proxies (and its
* configuration requirements). Implements the primary APIs of
* proxy.c, but maps them straight to the underlying network layer.
*/
#include "putty.h"
#include "network.h"
#include "proxy.h"
SockAddr *name_lookup(const char *host, int port, char **canonicalname,
Conf *conf, int addressfamily, LogContext *logctx,
const char *reason)
{
return sk_namelookup(host, canonicalname, addressfamily);
}
Socket *new_connection(SockAddr *addr, const char *hostname,
int port, bool privport,
bool oobinline, bool nodelay, bool keepalive,
Plug *plug, Conf *conf, LogPolicy *lp, Seat **seat)
{
return sk_new(addr, port, privport, oobinline, nodelay, keepalive, plug);
}
Socket *new_listener(const char *srcaddr, int port, Plug *plug,
bool local_host_only, Conf *conf, int addressfamily)
{
return sk_newlistener(srcaddr, port, plug, local_host_only, addressfamily);
}

17
proxy/nosshproxy.c Normal file
View File

@ -0,0 +1,17 @@
/*
* nosshproxy.c: stub implementation of sshproxy_new_connection().
*/
#include "putty.h"
#include "network.h"
const bool ssh_proxy_supported = false;
Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname,
int port, bool privport,
bool oobinline, bool nodelay, bool keepalive,
Plug *plug, Conf *conf,
LogPolicy *clientlp, Seat **clientseat)
{
return NULL;
}

17
proxy/pproxy.c Normal file
View File

@ -0,0 +1,17 @@
/*
* pproxy.c: dummy implementation of platform_new_connection(), to
* be supplanted on any platform which has its own local proxy
* method.
*/
#include "putty.h"
#include "network.h"
#include "proxy.h"
Socket *platform_new_connection(SockAddr *addr, const char *hostname,
int port, int privport,
int oobinline, int nodelay, int keepalive,
Plug *plug, Conf *conf)
{
return NULL;
}

1515
proxy/proxy.c Normal file

File diff suppressed because it is too large Load Diff

110
proxy/proxy.h Normal file
View File

@ -0,0 +1,110 @@
/*
* Network proxy abstraction in PuTTY
*
* A proxy layer, if necessary, wedges itself between the
* network code and the higher level backend.
*
* Supported proxies: HTTP CONNECT, generic telnet, SOCKS 4 & 5
*/
#ifndef PUTTY_PROXY_H
#define PUTTY_PROXY_H
#define PROXY_ERROR_GENERAL 8000
#define PROXY_ERROR_UNEXPECTED 8001
typedef struct ProxySocket ProxySocket;
struct ProxySocket {
const char *error;
Socket *sub_socket;
Plug *plug;
SockAddr *remote_addr;
int remote_port;
bufchain pending_output_data;
bufchain pending_oob_output_data;
bufchain pending_input_data;
bool pending_eof;
#define PROXY_STATE_NEW -1
#define PROXY_STATE_ACTIVE 0
int state; /* proxy states greater than 0 are implementation
* dependent, but represent various stages/states
* of the initialization/setup/negotiation with the
* proxy server.
*/
bool freeze; /* should we freeze the underlying socket when
* we are done with the proxy negotiation? this
* simply caches the value of sk_set_frozen calls.
*/
#define PROXY_CHANGE_NEW -1
#define PROXY_CHANGE_CLOSING 0
#define PROXY_CHANGE_SENT 1
#define PROXY_CHANGE_RECEIVE 2
#define PROXY_CHANGE_ACCEPTING 3
/* something has changed (a call from the sub socket
* layer into our Proxy Plug layer, or we were just
* created, etc), so the proxy layer needs to handle
* this change (the type of which is the second argument)
* and further the proxy negotiation process.
*/
int (*negotiate) (ProxySocket * /* this */, int /* change type */);
/* current arguments of plug handlers
* (for use by proxy's negotiate function)
*/
/* closing */
const char *closing_error_msg;
int closing_error_code;
/* receive */
bool receive_urgent;
const char *receive_data;
int receive_len;
/* accepting */
accept_fn_t accepting_constructor;
accept_ctx_t accepting_ctx;
/* configuration, used to look up proxy settings */
Conf *conf;
/* CHAP transient data */
int chap_num_attributes;
int chap_num_attributes_processed;
int chap_current_attribute;
int chap_current_datalen;
Socket sock;
Plug plugimpl;
};
extern void proxy_activate (ProxySocket *);
extern int proxy_http_negotiate (ProxySocket *, int);
extern int proxy_telnet_negotiate (ProxySocket *, int);
extern int proxy_socks4_negotiate (ProxySocket *, int);
extern int proxy_socks5_negotiate (ProxySocket *, int);
/*
* This may be reused by local-command proxies on individual
* platforms.
*/
char *format_telnet_command(SockAddr *addr, int port, Conf *conf);
/*
* These are implemented in cproxy.c or nocproxy.c, depending on
* whether encrypted proxy authentication is available.
*/
extern void proxy_socks5_offerencryptedauth(BinarySink *);
extern int proxy_socks5_handlechap (ProxySocket *);
extern int proxy_socks5_selectchap(ProxySocket *);
#endif

616
proxy/sshproxy.c Normal file
View File

@ -0,0 +1,616 @@
/*
* 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"
const bool ssh_proxy_supported = true;
/*
* TODO for future work:
*
* All the interactive prompts we present to the main Seat - the host
* key and weak-crypto dialog boxes, and all prompts presented via the
* userpass_input system - need adjusting so that it's clear to the
* user _which_ SSH connection they come from. At the moment, you just
* get shown a host key fingerprint or a cryptic "login as:" prompt,
* and you have to guess which server you're currently supposed to be
* interpreting it relative to.
*
* If the user manually aborts the attempt to make the proxy SSH
* connection (e.g. by hitting ^C at a userpass prompt, or refusing to
* accept the proxy server's host key), then an assertion failure
* occurs, because the main backend receives an indication of
* connection failure that causes it to want to call
* seat_connection_fatal("Remote side unexpectedly closed network
* connection"), which fails an assertion in tempseat.c because that
* method of TempSeat expects never to be called. To fix this, I think
* we need to distinguish 'connection attempt unexpectedly failed, in
* a way the user needs to be told about' from 'connection attempt was
* aborted by deliberate user action, so the user already knows'.
*/
typedef struct SshProxy {
char *errmsg;
Conf *conf;
LogContext *logctx;
Backend *backend;
LogPolicy *clientlp;
Seat *clientseat;
ProxyStderrBuf psb;
Plug *plug;
bool frozen;
bufchain ssh_to_socket;
bool rcvd_eof_ssh_to_socket, sent_eof_ssh_to_socket;
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);
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 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, 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(sp->plug, sp->errmsg, 0);
}
}
static void sshproxy_notify_session_started(Seat *seat)
{
SshProxy *sp = container_of(seat, SshProxy, seat);
if (sp->clientseat)
seat_set_trust_status(sp->clientseat, 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);
if (type == SEAT_OUTPUT_AUTH_BANNER) {
if (sp->clientseat) {
/*
* If we have access to the outer Seat, pass the SSH login
* banner on to it.
*/
return seat_output(sp->clientseat, type, data, len);
} else {
return 0;
}
} else {
bufchain_add(&sp->ssh_to_socket, data, len);
try_send_ssh_to_socket(sp);
return bufchain_size(&sp->ssh_to_socket);
}
}
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_notify_remote_disconnect(Seat *seat)
{
SshProxy *sp = container_of(seat, SshProxy, seat);
if (!sp->rcvd_eof_ssh_to_socket && !backend_connected(sp->backend))
sshproxy_eof(seat);
}
static int sshproxy_get_userpass_input(Seat *seat, prompts_t *p)
{
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. FIXME: appropriately adjusted
*/
return seat_get_userpass_input(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 0;
}
static void sshproxy_connection_fatal_callback(void *vctx)
{
SshProxy *sp = (SshProxy *)vctx;
plug_closing(sp->plug, sp->errmsg, 0);
}
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 int sshproxy_confirm_ssh_host_key(
Seat *seat, const char *host, int port, const char *keytype,
char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch,
void (*callback)(void *ctx, int 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. FIXME: appropriately adjusted
*/
return seat_confirm_ssh_host_key(
sp->clientseat, host, port, keytype, keystr, keydisp,
key_fingerprints, mismatch, 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 0;
}
static int sshproxy_confirm_weak_crypto_primitive(
Seat *seat, const char *algtype, const char *algname,
void (*callback)(void *ctx, int 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. FIXME: appropriately adjusted
*/
return seat_confirm_weak_crypto_primitive(
sp->clientseat, algtype, algname, callback, ctx);
}
/*
* Otherwise, behave as if we're in batch mode: take the safest
* option.
*/
sshproxy_error(sp, "First %s supported by server is %s, below warning "
"threshold. Abandoning proxy SSH connection.",
algtype, algname);
return 0;
}
static int sshproxy_confirm_weak_cached_hostkey(
Seat *seat, const char *algname, const char *betteralgs,
void (*callback)(void *ctx, int 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. FIXME: appropriately adjusted
*/
return seat_confirm_weak_cached_hostkey(
sp->clientseat, algname, betteralgs, callback, ctx);
}
/*
* Otherwise, behave as if we're in batch mode: take the safest
* option.
*/
sshproxy_error(sp, "First host key type stored for server is %s, below "
"warning threshold. Abandoning proxy SSH connection.",
algname);
return 0;
}
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,
.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,
.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,
.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,
LogPolicy *clientlp, Seat **clientseat)
{
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 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->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);
/*
* 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);
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 useful bits and pieces for interacting with
* the end user, squirrel them away now.
*/
sp->clientlp = clientlp;
if (clientseat && (backvt->flags & BACKEND_NOTIFIES_SESSION_START)) {
/*
* We can only keep 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.)
*
* Also, check if the client already has a TempSeat, and if
* so, don't wrap it with another one.
*/
if (is_tempseat(*clientseat)) {
sp->clientseat = tempseat_get_real(*clientseat);
} else {
sp->clientseat = *clientseat;
*clientseat = tempseat_new(sp->clientseat);
}
}
return &sp->sock;
}