mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-09 01:18:00 +00:00
1b851758bd
My experimental build with clang-cl at -Wall did show up a few things that are safe enough to fix right now. One was this list of missing includes, which was causing a lot of -Wmissing-prototype warnings, and is a real risk because it means the declarations in headers weren't being type-checked against the actual function definitions. Happily, no actual mismatches.
570 lines
17 KiB
C
570 lines
17 KiB
C
/*
|
|
* Platform-independent parts of a standalone SOCKS server program
|
|
* based on the PuTTY SOCKS code.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
#include "putty.h"
|
|
#include "storage.h"
|
|
#include "misc.h"
|
|
#include "ssh.h"
|
|
#include "ssh/channel.h"
|
|
#include "psocks.h"
|
|
|
|
/*
|
|
* Possible later TODOs:
|
|
*
|
|
* - verbosity setting for log messages
|
|
*
|
|
* - could import proxy.c and use name_lookup rather than
|
|
* sk_namelookup, to allow forwarding via some other proxy type
|
|
*/
|
|
|
|
#define BUFLIMIT 16384
|
|
|
|
#define LOGBITS(X) \
|
|
X(CONNSTATUS) \
|
|
X(DIALOGUE) \
|
|
/* end of list */
|
|
|
|
#define BITINDEX_ENUM(x) LOG_##x##_bitindex,
|
|
enum { LOGBITS(BITINDEX_ENUM) };
|
|
#define BITFLAG_ENUM(x) LOG_##x = 1 << LOG_##x##_bitindex,
|
|
enum { LOGBITS(BITFLAG_ENUM) };
|
|
|
|
typedef struct psocks_connection psocks_connection;
|
|
|
|
typedef enum RecordDestination {
|
|
REC_NONE, REC_FILE, REC_PIPE
|
|
} RecordDestination;
|
|
|
|
struct psocks_state {
|
|
const PsocksPlatform *platform;
|
|
int listen_port;
|
|
bool acceptall;
|
|
PortFwdManager *portfwdmgr;
|
|
uint64_t next_conn_index;
|
|
FILE *logging_fp;
|
|
unsigned log_flags;
|
|
RecordDestination rec_dest;
|
|
char *rec_cmd;
|
|
strbuf *subcmd;
|
|
|
|
ConnectionLayer cl;
|
|
};
|
|
|
|
struct psocks_connection {
|
|
psocks_state *ps;
|
|
Channel *chan;
|
|
char *host, *realhost;
|
|
int port;
|
|
SockAddr *addr;
|
|
Socket *socket;
|
|
bool connecting, eof_pfmgr_to_socket, eof_socket_to_pfmgr;
|
|
uint64_t index;
|
|
PsocksDataSink *rec_sink;
|
|
|
|
Plug plug;
|
|
SshChannel sc;
|
|
};
|
|
|
|
static SshChannel *psocks_lportfwd_open(
|
|
ConnectionLayer *cl, const char *hostname, int port,
|
|
const char *description, const SocketPeerInfo *pi, Channel *chan);
|
|
|
|
static const ConnectionLayerVtable psocks_clvt = {
|
|
.lportfwd_open = psocks_lportfwd_open,
|
|
/* everything else is NULL */
|
|
};
|
|
|
|
static size_t psocks_sc_write(SshChannel *sc, bool is_stderr, const void *,
|
|
size_t);
|
|
static void psocks_sc_write_eof(SshChannel *sc);
|
|
static void psocks_sc_initiate_close(SshChannel *sc, const char *err);
|
|
static void psocks_sc_unthrottle(SshChannel *sc, size_t bufsize);
|
|
|
|
static const SshChannelVtable psocks_scvt = {
|
|
.write = psocks_sc_write,
|
|
.write_eof = psocks_sc_write_eof,
|
|
.initiate_close = psocks_sc_initiate_close,
|
|
.unthrottle = psocks_sc_unthrottle,
|
|
/* all the rest are NULL */
|
|
};
|
|
|
|
static void psocks_plug_log(Plug *p, PlugLogType type, SockAddr *addr,
|
|
int port, const char *error_msg, int error_code);
|
|
static void psocks_plug_closing(Plug *p, PlugCloseType, const char *error_msg);
|
|
static void psocks_plug_receive(Plug *p, int urgent,
|
|
const char *data, size_t len);
|
|
static void psocks_plug_sent(Plug *p, size_t bufsize);
|
|
|
|
static const PlugVtable psocks_plugvt = {
|
|
.log = psocks_plug_log,
|
|
.closing = psocks_plug_closing,
|
|
.receive = psocks_plug_receive,
|
|
.sent = psocks_plug_sent,
|
|
};
|
|
|
|
static void psocks_conn_log(psocks_connection *conn, const char *fmt, ...)
|
|
{
|
|
if (!conn->ps->logging_fp)
|
|
return;
|
|
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
char *msg = dupvprintf(fmt, ap);
|
|
va_end(ap);
|
|
fprintf(conn->ps->logging_fp, "c#%"PRIu64": %s\n", conn->index, msg);
|
|
sfree(msg);
|
|
fflush(conn->ps->logging_fp);
|
|
}
|
|
|
|
static void psocks_conn_log_data(psocks_connection *conn, PsocksDirection dir,
|
|
const void *vdata, size_t len)
|
|
{
|
|
if ((conn->ps->log_flags & LOG_DIALOGUE) && conn->ps->logging_fp) {
|
|
const char *data = vdata;
|
|
while (len > 0) {
|
|
const char *nl = memchr(data, '\n', len);
|
|
size_t thislen = nl ? (nl+1) - data : len;
|
|
const char *thisdata = data;
|
|
data += thislen;
|
|
len -= thislen;
|
|
|
|
static const char *const direction_names[2] = {
|
|
[UP] = "send", [DN] = "recv" };
|
|
|
|
fprintf(conn->ps->logging_fp, "c#%"PRIu64": %s \"", conn->index,
|
|
direction_names[dir]);
|
|
write_c_string_literal(conn->ps->logging_fp,
|
|
make_ptrlen(thisdata, thislen));
|
|
fprintf(conn->ps->logging_fp, "\"\n");
|
|
}
|
|
|
|
fflush(conn->ps->logging_fp);
|
|
}
|
|
|
|
if (conn->rec_sink)
|
|
put_data(conn->rec_sink->s[dir], vdata, len);
|
|
}
|
|
|
|
static void psocks_connection_establish(void *vctx);
|
|
|
|
static SshChannel *psocks_lportfwd_open(
|
|
ConnectionLayer *cl, const char *hostname, int port,
|
|
const char *description, const SocketPeerInfo *pi, Channel *chan)
|
|
{
|
|
psocks_state *ps = container_of(cl, psocks_state, cl);
|
|
psocks_connection *conn = snew(psocks_connection);
|
|
memset(conn, 0, sizeof(*conn));
|
|
conn->ps = ps;
|
|
conn->sc.vt = &psocks_scvt;
|
|
conn->plug.vt = &psocks_plugvt;
|
|
conn->chan = chan;
|
|
conn->host = dupstr(hostname);
|
|
conn->port = port;
|
|
conn->index = ps->next_conn_index++;
|
|
if (conn->ps->log_flags & LOG_CONNSTATUS)
|
|
psocks_conn_log(conn, "request from %s for %s port %d",
|
|
pi->log_text, hostname, port);
|
|
switch (conn->ps->rec_dest) {
|
|
case REC_FILE:
|
|
{
|
|
char *fnames[2];
|
|
FILE *fp[2];
|
|
bool ok = true;
|
|
|
|
static const char *const direction_names[2] = {
|
|
[UP] = "sockout", [DN] = "sockin" };
|
|
|
|
for (size_t i = 0; i < 2; i++) {
|
|
fnames[i] = dupprintf("%s.%"PRIu64, direction_names[i],
|
|
conn->index);
|
|
fp[i] = fopen(fnames[i], "wb");
|
|
if (!fp[i]) {
|
|
psocks_conn_log(conn, "cannot log this connection: "
|
|
"creating file '%s': %s",
|
|
fnames[i], strerror(errno));
|
|
ok = false;
|
|
}
|
|
}
|
|
if (ok) {
|
|
if (conn->ps->log_flags & LOG_CONNSTATUS)
|
|
psocks_conn_log(conn, "logging to '%s' / '%s'",
|
|
fnames[0], fnames[1]);
|
|
conn->rec_sink = pds_stdio(fp);
|
|
} else {
|
|
for (size_t i = 0; i < 2; i++) {
|
|
if (fp[i]) {
|
|
remove(fnames[i]);
|
|
fclose(fp[i]);
|
|
}
|
|
}
|
|
}
|
|
for (size_t i = 0; i < 2; i++)
|
|
sfree(fnames[i]);
|
|
}
|
|
break;
|
|
case REC_PIPE:
|
|
{
|
|
static const char *const direction_args[2] = {
|
|
[UP] = "out", [DN] = "in" };
|
|
char *index_arg = dupprintf("%"PRIu64, conn->index);
|
|
char *err;
|
|
conn->rec_sink = conn->ps->platform->open_pipes(
|
|
conn->ps->rec_cmd, direction_args, index_arg, &err);
|
|
if (!conn->rec_sink) {
|
|
psocks_conn_log(conn, "cannot log this connection: "
|
|
"creating pipes: %s", err);
|
|
sfree(err);
|
|
}
|
|
sfree(index_arg);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
queue_toplevel_callback(psocks_connection_establish, conn);
|
|
return &conn->sc;
|
|
}
|
|
|
|
static void psocks_conn_free(psocks_connection *conn)
|
|
{
|
|
if (conn->ps->log_flags & LOG_CONNSTATUS)
|
|
psocks_conn_log(conn, "closed");
|
|
|
|
sfree(conn->host);
|
|
sfree(conn->realhost);
|
|
if (conn->socket)
|
|
sk_close(conn->socket);
|
|
if (conn->chan)
|
|
chan_free(conn->chan);
|
|
if (conn->rec_sink)
|
|
pds_free(conn->rec_sink);
|
|
delete_callbacks_for_context(conn);
|
|
sfree(conn);
|
|
}
|
|
|
|
static void psocks_connection_establish(void *vctx)
|
|
{
|
|
psocks_connection *conn = (psocks_connection *)vctx;
|
|
|
|
/*
|
|
* Look up destination host name.
|
|
*/
|
|
conn->addr = sk_namelookup(conn->host, &conn->realhost, ADDRTYPE_UNSPEC);
|
|
|
|
const char *err = sk_addr_error(conn->addr);
|
|
if (err) {
|
|
char *msg = dupprintf("name lookup failed: %s", err);
|
|
chan_open_failed(conn->chan, msg);
|
|
sfree(msg);
|
|
|
|
psocks_conn_free(conn);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Make the connection.
|
|
*/
|
|
conn->connecting = true;
|
|
conn->socket = sk_new(conn->addr, conn->port, false, false, false, false,
|
|
&conn->plug);
|
|
}
|
|
|
|
static size_t psocks_sc_write(SshChannel *sc, bool is_stderr,
|
|
const void *data, size_t len)
|
|
{
|
|
psocks_connection *conn = container_of(sc, psocks_connection, sc);
|
|
if (!conn->socket) return 0;
|
|
|
|
psocks_conn_log_data(conn, UP, data, len);
|
|
|
|
return sk_write(conn->socket, data, len);
|
|
}
|
|
|
|
static void psocks_check_close(void *vctx)
|
|
{
|
|
psocks_connection *conn = (psocks_connection *)vctx;
|
|
if (chan_want_close(conn->chan, conn->eof_pfmgr_to_socket,
|
|
conn->eof_socket_to_pfmgr))
|
|
psocks_conn_free(conn);
|
|
}
|
|
|
|
static void psocks_sc_write_eof(SshChannel *sc)
|
|
{
|
|
psocks_connection *conn = container_of(sc, psocks_connection, sc);
|
|
if (!conn->socket) return;
|
|
sk_write_eof(conn->socket);
|
|
conn->eof_pfmgr_to_socket = true;
|
|
|
|
if (conn->ps->log_flags & LOG_DIALOGUE)
|
|
psocks_conn_log(conn, "send eof");
|
|
|
|
queue_toplevel_callback(psocks_check_close, conn);
|
|
}
|
|
|
|
static void psocks_sc_initiate_close(SshChannel *sc, const char *err)
|
|
{
|
|
psocks_connection *conn = container_of(sc, psocks_connection, sc);
|
|
sk_close(conn->socket);
|
|
conn->socket = NULL;
|
|
}
|
|
|
|
static void psocks_sc_unthrottle(SshChannel *sc, size_t bufsize)
|
|
{
|
|
psocks_connection *conn = container_of(sc, psocks_connection, sc);
|
|
if (bufsize < BUFLIMIT)
|
|
sk_set_frozen(conn->socket, false);
|
|
}
|
|
|
|
static void psocks_plug_log(Plug *plug, PlugLogType type, SockAddr *addr,
|
|
int port, const char *error_msg, int error_code)
|
|
{
|
|
psocks_connection *conn = container_of(plug, psocks_connection, plug);
|
|
char addrbuf[256];
|
|
|
|
if (!(conn->ps->log_flags & LOG_CONNSTATUS))
|
|
return;
|
|
|
|
switch (type) {
|
|
case PLUGLOG_CONNECT_TRYING:
|
|
sk_getaddr(addr, addrbuf, sizeof(addrbuf));
|
|
if (sk_addr_needs_port(addr))
|
|
psocks_conn_log(conn, "trying to connect to %s port %d",
|
|
addrbuf, port);
|
|
else
|
|
psocks_conn_log(conn, "trying to connect to %s", addrbuf);
|
|
break;
|
|
case PLUGLOG_CONNECT_FAILED:
|
|
psocks_conn_log(conn, "connection attempt failed: %s", error_msg);
|
|
break;
|
|
case PLUGLOG_CONNECT_SUCCESS:
|
|
psocks_conn_log(conn, "connection established", error_msg);
|
|
if (conn->connecting) {
|
|
chan_open_confirmation(conn->chan);
|
|
conn->connecting = false;
|
|
}
|
|
break;
|
|
case PLUGLOG_PROXY_MSG:
|
|
psocks_conn_log(conn, "connection setup: %s", error_msg);
|
|
break;
|
|
};
|
|
}
|
|
|
|
static void psocks_plug_closing(Plug *plug, PlugCloseType type,
|
|
const char *error_msg)
|
|
{
|
|
psocks_connection *conn = container_of(plug, psocks_connection, plug);
|
|
if (conn->connecting) {
|
|
if (conn->ps->log_flags & LOG_CONNSTATUS)
|
|
psocks_conn_log(conn, "unable to connect: %s", error_msg);
|
|
|
|
chan_open_failed(conn->chan, error_msg);
|
|
conn->eof_socket_to_pfmgr = true;
|
|
conn->eof_pfmgr_to_socket = true;
|
|
conn->connecting = false;
|
|
} else {
|
|
if (conn->ps->log_flags & LOG_DIALOGUE)
|
|
psocks_conn_log(conn, "recv eof");
|
|
|
|
chan_send_eof(conn->chan);
|
|
conn->eof_socket_to_pfmgr = true;
|
|
}
|
|
queue_toplevel_callback(psocks_check_close, conn);
|
|
}
|
|
|
|
static void psocks_plug_receive(Plug *plug, int urgent,
|
|
const char *data, size_t len)
|
|
{
|
|
psocks_connection *conn = container_of(plug, psocks_connection, plug);
|
|
size_t bufsize = chan_send(conn->chan, false, data, len);
|
|
sk_set_frozen(conn->socket, bufsize > BUFLIMIT);
|
|
|
|
psocks_conn_log_data(conn, DN, data, len);
|
|
}
|
|
|
|
static void psocks_plug_sent(Plug *plug, size_t bufsize)
|
|
{
|
|
psocks_connection *conn = container_of(plug, psocks_connection, plug);
|
|
sk_set_frozen(conn->socket, bufsize > BUFLIMIT);
|
|
}
|
|
|
|
psocks_state *psocks_new(const PsocksPlatform *platform)
|
|
{
|
|
psocks_state *ps = snew(psocks_state);
|
|
memset(ps, 0, sizeof(*ps));
|
|
|
|
ps->listen_port = 1080;
|
|
ps->acceptall = false;
|
|
|
|
ps->cl.vt = &psocks_clvt;
|
|
ps->portfwdmgr = portfwdmgr_new(&ps->cl);
|
|
|
|
ps->logging_fp = stderr; /* could make this configurable later */
|
|
ps->log_flags = LOG_CONNSTATUS;
|
|
ps->rec_dest = REC_NONE;
|
|
ps->platform = platform;
|
|
ps->subcmd = strbuf_new();
|
|
|
|
return ps;
|
|
}
|
|
|
|
void psocks_free(psocks_state *ps)
|
|
{
|
|
portfwdmgr_free(ps->portfwdmgr);
|
|
strbuf_free(ps->subcmd);
|
|
sfree(ps->rec_cmd);
|
|
sfree(ps);
|
|
}
|
|
|
|
void psocks_cmdline(psocks_state *ps, int argc, char **argv)
|
|
{
|
|
bool doing_opts = true;
|
|
bool accumulating_exec_args = false;
|
|
size_t args_seen = 0;
|
|
|
|
while (--argc > 0) {
|
|
const char *p = *++argv;
|
|
|
|
if (doing_opts && p[0] == '-' && p[1]) {
|
|
if (!strcmp(p, "--")) {
|
|
doing_opts = false;
|
|
} else if (!strcmp(p, "-g")) {
|
|
ps->acceptall = true;
|
|
} else if (!strcmp(p, "-d")) {
|
|
ps->log_flags |= LOG_DIALOGUE;
|
|
} else if (!strcmp(p, "-f")) {
|
|
ps->rec_dest = REC_FILE;
|
|
} else if (!strcmp(p, "-p")) {
|
|
if (!ps->platform->open_pipes) {
|
|
fprintf(stderr, "psocks: '-p' is not supported on this "
|
|
"platform\n");
|
|
exit(1);
|
|
}
|
|
if (--argc > 0) {
|
|
ps->rec_cmd = dupstr(*++argv);
|
|
} else {
|
|
fprintf(stderr, "psocks: expected an argument to '-p'\n");
|
|
exit(1);
|
|
}
|
|
ps->rec_dest = REC_PIPE;
|
|
} else if (!strcmp(p, "--exec")) {
|
|
if (!ps->platform->start_subcommand) {
|
|
fprintf(stderr, "psocks: running a subcommand is not "
|
|
"supported on this platform\n");
|
|
exit(1);
|
|
}
|
|
accumulating_exec_args = true;
|
|
/* Now consume all further argv words for the
|
|
* subcommand, even if they look like options */
|
|
doing_opts = false;
|
|
} else if (!strcmp(p, "--help")) {
|
|
printf("usage: psocks [ -d ] [ -f");
|
|
if (ps->platform->open_pipes)
|
|
printf(" | -p pipe-cmd");
|
|
printf(" ] [ -g ] port-number");
|
|
printf("\n");
|
|
printf("where: -d log all connection contents to"
|
|
" standard output\n");
|
|
printf(" -f record each half-connection to "
|
|
"a file sockin.N/sockout.N\n");
|
|
if (ps->platform->open_pipes)
|
|
printf(" -p pipe-cmd pipe each half-connection"
|
|
" to 'pipe-cmd [in|out] N'\n");
|
|
printf(" -g accept connections from anywhere,"
|
|
" not just localhost\n");
|
|
if (ps->platform->start_subcommand)
|
|
printf(" --exec subcmd [args...] run command, and "
|
|
"terminate when it exits\n");
|
|
printf(" port-number listen on this port"
|
|
" (default 1080)\n");
|
|
printf("also: psocks --help display this help text\n");
|
|
exit(0);
|
|
} else {
|
|
fprintf(stderr, "psocks: unrecognised option '%s'\n", p);
|
|
exit(1);
|
|
}
|
|
} else {
|
|
if (accumulating_exec_args) {
|
|
put_asciz(ps->subcmd, p);
|
|
} else switch (args_seen++) {
|
|
case 0:
|
|
ps->listen_port = atoi(p);
|
|
break;
|
|
default:
|
|
fprintf(stderr, "psocks: unexpected extra argument '%s'\n", p);
|
|
exit(1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void psocks_start(psocks_state *ps)
|
|
{
|
|
Conf *conf = conf_new();
|
|
conf_set_bool(conf, CONF_lport_acceptall, ps->acceptall);
|
|
char *key = dupprintf("AL%d", ps->listen_port);
|
|
conf_set_str_str(conf, CONF_portfwd, key, "D");
|
|
sfree(key);
|
|
|
|
portfwdmgr_config(ps->portfwdmgr, conf);
|
|
|
|
if (ps->subcmd->len)
|
|
ps->platform->start_subcommand(ps->subcmd);
|
|
|
|
conf_free(conf);
|
|
}
|
|
|
|
/*
|
|
* Some stubs that are needed to link against PuTTY modules.
|
|
*/
|
|
|
|
int check_stored_host_key(const char *hostname, int port,
|
|
const char *keytype, const char *key)
|
|
{
|
|
unreachable("host keys not handled in this tool");
|
|
}
|
|
|
|
void store_host_key(const char *hostname, int port,
|
|
const char *keytype, const char *key)
|
|
{
|
|
unreachable("host keys not handled in this tool");
|
|
}
|
|
|
|
/*
|
|
* stdio-targeted PsocksDataSink.
|
|
*/
|
|
typedef struct PsocksDataSinkStdio {
|
|
stdio_sink sink[2];
|
|
PsocksDataSink pds;
|
|
} PsocksDataSinkStdio;
|
|
|
|
static void stdio_free(PsocksDataSink *pds)
|
|
{
|
|
PsocksDataSinkStdio *pdss = container_of(pds, PsocksDataSinkStdio, pds);
|
|
|
|
for (size_t i = 0; i < 2; i++)
|
|
fclose(pdss->sink[i].fp);
|
|
|
|
sfree(pdss);
|
|
}
|
|
|
|
PsocksDataSink *pds_stdio(FILE *fp[2])
|
|
{
|
|
PsocksDataSinkStdio *pdss = snew(PsocksDataSinkStdio);
|
|
|
|
for (size_t i = 0; i < 2; i++) {
|
|
setvbuf(fp[i], NULL, _IONBF, 0);
|
|
stdio_sink_init(&pdss->sink[i], fp[i]);
|
|
pdss->pds.s[i] = BinarySink_UPCAST(&pdss->sink[i]);
|
|
}
|
|
|
|
pdss->pds.free = stdio_free;
|
|
|
|
return &pdss->pds;
|
|
}
|