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:
179
proxy/cproxy.c
Normal file
179
proxy/cproxy.c
Normal 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
35
proxy/nocproxy.c
Normal 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
32
proxy/noproxy.c
Normal 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
17
proxy/nosshproxy.c
Normal 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
17
proxy/pproxy.c
Normal 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
1515
proxy/proxy.c
Normal file
File diff suppressed because it is too large
Load Diff
110
proxy/proxy.h
Normal file
110
proxy/proxy.h
Normal 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
616
proxy/sshproxy.c
Normal 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;
|
||||
}
|
Reference in New Issue
Block a user