diff --git a/.gitignore b/.gitignore index a124edac..92b7c10a 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,7 @@ /psusan /osxlaunch /uppity +/psocks /unix/PuTTY.app /unix/Pterm.app /fuzzterm diff --git a/Recipe b/Recipe index 84c93d8f..df040ab1 100644 --- a/Recipe +++ b/Recipe @@ -411,15 +411,22 @@ uppity : [UT] uxserver SSHSERVER UXMISC uxsignal uxnoise uxgss uxnogtk psusan : [UT] uxpsusan SSHSERVER UXMISC uxsignal uxnoise nogss uxnogtk + uxpty uxsftpserver ux_x11 uxagentsock procnet uxcliloop +PSOCKS = psocks portfwd conf sshutils logging proxy nocproxy timing callback + + time tree234 version errsock be_misc norand MISC +psocks : [C] PSOCKS winsocks wincons winproxy winnet winmisc winselcli + + winhsock winhandl winmiscs winnohlp wincliloop LIBS +psocks : [UT] PSOCKS uxsocks uxcons uxproxy uxnet uxmisc uxpoll uxsel uxnogtk + + uxpeer uxfdsock uxcliloop uxsignal + # ---------------------------------------------------------------------- # On Windows, provide a means of removing local test binaries that we # aren't going to actually ship. (I prefer this to not building them # in the first place, so that we find out about build breakage early.) !begin vc cleantestprogs: - -del $(BUILDDIR)testcrypt.exe + -del $(BUILDDIR)testcrypt.exe $(BUILDDIR)psocks.exe !end !begin clangcl cleantestprogs: - -rm -f $(BUILDDIR)testcrypt.exe + -rm -f $(BUILDDIR)testcrypt.exe $(BUILDDIR)psocks.exe !end diff --git a/psocks.c b/psocks.c new file mode 100644 index 00000000..ddf3d4b1 --- /dev/null +++ b/psocks.c @@ -0,0 +1,630 @@ +/* + * Platform-independent parts of a standalone SOCKS server program + * based on the PuTTY SOCKS code. + */ + +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "sshchan.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 struct ConnectionLayerVtable psocks_clvt = { + NULL /* rportfwd_alloc */, + NULL /* rportfwd_remove */, + psocks_lportfwd_open, + NULL /* session_open */, + NULL /* serverside_x11_open */, + NULL /* serverside_agent_open */, + NULL /* add_x11_display */, + NULL /* add_sharing_x11_display */, + NULL /* remove_sharing_x11_display */, + NULL /* send_packet_from_downstream */, + NULL /* alloc_sharing_channel */, + NULL /* delete_sharing_channel */, + NULL /* sharing_queue_global_request */, + NULL /* sharing_no_more_downstreams */, + NULL /* agent_forwarding_permitted */, + NULL /* terminal_size */, + NULL /* stdout_unthrottle */, + NULL /* stdin_backlog */, + NULL /* throttle_all_channels */, + NULL /* ldisc_option */, + NULL /* set_ldisc_option */, + NULL /* enable_x_fwd */, + NULL /* enable_agent_fwd */, + NULL /* set_wants_user_input */, +}; + +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 struct SshChannelVtable psocks_scvt = { + psocks_sc_write, + psocks_sc_write_eof, + psocks_sc_initiate_close, + psocks_sc_unthrottle, + NULL /* get_conf */, + NULL /* window_override_removed */, + NULL /* x11_sharing_handover */, + NULL /* send_exit_status */, + NULL /* send_exit_signal */, + NULL /* send_exit_signal_numeric */, + NULL /* request_x11_forwarding */, + NULL /* request_agent_forwarding */, + NULL /* request_pty */, + NULL /* send_env_var */, + NULL /* start_shell */, + NULL /* start_command */, + NULL /* start_subsystem */, + NULL /* send_serial_break */, + NULL /* send_signal */, + NULL /* send_terminal_size_change */, + NULL /* hint_channel_is_simple */, +}; + +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, const char *error_msg, + int error_code, bool calling_back); +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 = { + psocks_plug_log, + psocks_plug_closing, + psocks_plug_receive, + psocks_plug_sent, + NULL /* accepting */, +}; + +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 print_c_string(FILE *fp, const char *data, size_t len) +{ + while (len--) { + char c = *data++; + + if (c == '\n') + fputs("\\n", fp); + else if (c == '\r') + fputs("\\r", fp); + else if (c == '\t') + fputs("\\t", fp); + else if (c == '\b') + fputs("\\b", fp); + else if (c == '\\') + fputs("\\\\", fp); + else if (c == '"') + fputs("\\\"", fp); + else if (c >= 32 && c <= 126) + fputc(c, fp); + else + fprintf(fp, "\\%03o", (unsigned char)c); + } +} + +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]); + print_c_string(conn->ps->logging_fp, 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, const char *error_msg, + int error_code, bool calling_back) +{ + 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 verify_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; +} diff --git a/psocks.h b/psocks.h new file mode 100644 index 00000000..d1120a36 --- /dev/null +++ b/psocks.h @@ -0,0 +1,28 @@ +typedef struct psocks_state psocks_state; + +typedef struct PsocksPlatform PsocksPlatform; +typedef struct PsocksDataSink PsocksDataSink; + +/* indices into PsocksDataSink arrays */ +typedef enum PsocksDirection { UP, DN } PsocksDirection; + +typedef struct PsocksDataSink { + void (*free)(PsocksDataSink *); + BinarySink *s[2]; +} PsocksDataSink; +static inline void pds_free(PsocksDataSink *pds) +{ pds->free(pds); } + +PsocksDataSink *pds_stdio(FILE *fp[2]); + +struct PsocksPlatform { + PsocksDataSink *(*open_pipes)( + const char *cmd, const char *const *direction_args, + const char *index_arg, char **err); + void (*start_subcommand)(strbuf *args); +}; + +psocks_state *psocks_new(const PsocksPlatform *); +void psocks_free(psocks_state *ps); +void psocks_cmdline(psocks_state *ps, int argc, char **argv); +void psocks_start(psocks_state *ps); diff --git a/unix/uxsocks.c b/unix/uxsocks.c new file mode 100644 index 00000000..91613afd --- /dev/null +++ b/unix/uxsocks.c @@ -0,0 +1,178 @@ +/* + * Main program for Unix psocks. + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "psocks.h" + +const bool buildinfo_gtk_relevant = false; + +typedef struct PsocksDataSinkPopen { + stdio_sink sink[2]; + PsocksDataSink pds; +} PsocksDataSinkPopen; + +static void popen_free(PsocksDataSink *pds) +{ + PsocksDataSinkPopen *pdsp = container_of(pds, PsocksDataSinkPopen, pds); + for (size_t i = 0; i < 2; i++) + pclose(pdsp->sink[i].fp); + sfree(pdsp); +} + +static PsocksDataSink *open_pipes( + const char *cmd, const char *const *direction_args, + const char *index_arg, char **err) +{ + FILE *fp[2]; + char *errmsg = NULL; + + for (size_t i = 0; i < 2; i++) { + /* No escaping needed: the provided command is already + * shell-quoted, and our extra arguments are simple */ + char *command = dupprintf("%s %s %s", cmd, + direction_args[i], index_arg); + + fp[i] = popen(command, "w"); + sfree(command); + + if (!fp[i]) { + if (!errmsg) + errmsg = dupprintf("%s", strerror(errno)); + } + } + + if (errmsg) { + for (size_t i = 0; i < 2; i++) + if (fp[i]) + pclose(fp[i]); + *err = errmsg; + return NULL; + } + + PsocksDataSinkPopen *pdsp = snew(PsocksDataSinkPopen); + + for (size_t i = 0; i < 2; i++) { + setvbuf(fp[i], NULL, _IONBF, 0); + stdio_sink_init(&pdsp->sink[i], fp[i]); + pdsp->pds.s[i] = BinarySink_UPCAST(&pdsp->sink[i]); + } + + pdsp->pds.free = popen_free; + + return &pdsp->pds; +} + +static int signalpipe[2] = { -1, -1 }; +static void sigchld(int signum) +{ + if (write(signalpipe[1], "x", 1) <= 0) + /* not much we can do about it */; +} + +static pid_t subcommand_pid = -1; + +static bool still_running = true; + +static void start_subcommand(strbuf *args) +{ + pid_t pid; + + /* + * Set up the pipe we'll use to tell us about SIGCHLD. + */ + if (pipe(signalpipe) < 0) { + perror("pipe"); + exit(1); + } + putty_signal(SIGCHLD, sigchld); + + /* + * Make an array of argument pointers that execvp will like. + */ + size_t nargs = 0; + for (size_t i = 0; i < args->len; i++) + if (args->s[i] == '\0') + nargs++; + + char **exec_args = snewn(nargs + 1, char *); + char *p = args->s; + for (size_t a = 0; a < nargs; a++) { + exec_args[a] = p; + size_t len = strlen(p); + assert(len < args->len - (p - args->s)); + p += 1 + len; + } + exec_args[nargs] = NULL; + + pid = fork(); + if (pid < 0) { + perror("fork"); + exit(1); + } else if (pid == 0) { + execvp(exec_args[0], exec_args); + perror("exec"); + _exit(127); + } else { + subcommand_pid = pid; + sfree(exec_args); + } +} + +static const PsocksPlatform platform = { + open_pipes, + start_subcommand, +}; + +static bool psocks_pw_setup(void *ctx, pollwrapper *pw) +{ + if (signalpipe[0] >= 0) + pollwrap_add_fd_rwx(pw, signalpipe[0], SELECT_R); + return true; +} + +static void psocks_pw_check(void *ctx, pollwrapper *pw) +{ + if (signalpipe[0] >= 0 && + pollwrap_check_fd_rwx(pw, signalpipe[0], SELECT_R)) { + while (true) { + int status; + pid_t pid = waitpid(-1, &status, WNOHANG); + if (pid <= 0) + break; + if (pid == subcommand_pid) + still_running = false; + } + } +} + +static bool psocks_continue(void *ctx, bool found_any_fd, + bool ran_any_callback) +{ + return still_running; +} + +typedef bool (*cliloop_continue_t)(void *ctx, bool found_any_fd, + bool ran_any_callback); + +int main(int argc, char **argv) +{ + psocks_state *ps = psocks_new(&platform); + psocks_cmdline(ps, argc, argv); + + sk_init(); + uxsel_init(); + psocks_start(ps); + + cli_main_loop(psocks_pw_setup, psocks_pw_check, psocks_continue, NULL); +} diff --git a/windows/winsocks.c b/windows/winsocks.c new file mode 100644 index 00000000..83ba364c --- /dev/null +++ b/windows/winsocks.c @@ -0,0 +1,24 @@ +/* + * Main program for Windows psocks. + */ + +#include "putty.h" +#include "ssh.h" +#include "psocks.h" + +static const PsocksPlatform platform = { + NULL /* open_pipes */, + NULL /* start_subcommand */, +}; + +int main(int argc, char **argv) +{ + psocks_state *ps = psocks_new(&platform); + psocks_cmdline(ps, argc, argv); + + sk_init(); + winselcli_setup(); + psocks_start(ps); + + cli_main_loop(cliloop_null_pre, cliloop_null_post, NULL); +}