123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572 |
- /*
- * 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 SocketEndpointInfo *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, Socket *s, 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 SocketEndpointInfo *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, Socket *s, 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(Seat *seat, 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;
- }
|