diff --git a/CMakeLists.txt b/CMakeLists.txt index 6acd9181..3aba5e20 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,7 @@ add_library(network STATIC proxy/socks4.c proxy/socks5.c proxy/telnet.c + proxy/local.c proxy/interactor.c) add_library(keygen STATIC diff --git a/proxy/local.c b/proxy/local.c new file mode 100644 index 00000000..278d0b12 --- /dev/null +++ b/proxy/local.c @@ -0,0 +1,266 @@ +/* + * Implement LocalProxyOpener, a centralised system for setting up the + * command string to be run by platform-specific local-subprocess + * proxy types. + * + * The platform-specific local proxy code is expected to use this + * system by calling local_proxy_opener() from + * platform_new_connection(); then using the resulting + * DeferredSocketOpener to make a deferred version of whatever local + * socket type is used for talking to subcommands (Unix FdSocket, + * Windows HandleSocket); then passing the 'Socket *' back to us via + * local_proxy_opener_set_socket(). + * + * The LocalProxyOpener object implemented by this code will set + * itself up as an Interactor if possible, so that it can prompt for + * the proxy username and/or password if they're referred to in the + * command string but not given in the config (exactly as the Telnet + * proxy does). Once it knows the exact command it wants to run - + * whether that was done immediately or after user interaction - it + * calls back to platform_setup_local_proxy() with the full command, + * which is expected to actually start the subprocess and fill in the + * missing details in the deferred socket, freeing the + * LocalProxyOpener as a side effect. + */ + +#include "tree234.h" +#include "putty.h" +#include "network.h" +#include "sshcr.h" +#include "proxy/proxy.h" + +typedef struct LocalProxyOpener { + int crLine; + + Socket *socket; + char *formatted_cmd; + Plug *plug; + SockAddr *addr; + int port; + Conf *conf; + + Interactor *clientitr; + LogPolicy *clientlp; + Seat *clientseat; + prompts_t *prompts; + int username_prompt_index, password_prompt_index; + + Interactor interactor; + DeferredSocketOpener opener; +} LocalProxyOpener; + +static void local_proxy_opener_free(DeferredSocketOpener *opener) +{ + LocalProxyOpener *lp = container_of(opener, LocalProxyOpener, opener); + burnstr(lp->formatted_cmd); + if (lp->prompts) + free_prompts(lp->prompts); + sk_addr_free(lp->addr); + conf_free(lp->conf); + sfree(lp); +} + +static const DeferredSocketOpenerVtable LocalProxyOpener_openervt = { + .free = local_proxy_opener_free, +}; + +static char *local_proxy_opener_description(Interactor *itr) +{ + return dupstr("connection via local command"); +} + +static LogPolicy *local_proxy_opener_logpolicy(Interactor *itr) +{ + LocalProxyOpener *lp = container_of(itr, LocalProxyOpener, interactor); + return lp->clientlp; +} + +static Seat *local_proxy_opener_get_seat(Interactor *itr) +{ + LocalProxyOpener *lp = container_of(itr, LocalProxyOpener, interactor); + return lp->clientseat; +} + +static void local_proxy_opener_set_seat(Interactor *itr, Seat *seat) +{ + LocalProxyOpener *lp = container_of(itr, LocalProxyOpener, interactor); + lp->clientseat = seat; +} + +static const InteractorVtable LocalProxyOpener_interactorvt = { + .description = local_proxy_opener_description, + .logpolicy = local_proxy_opener_logpolicy, + .get_seat = local_proxy_opener_get_seat, + .set_seat = local_proxy_opener_set_seat, +}; + +static void local_proxy_opener_cleanup_interactor(LocalProxyOpener *lp) +{ + if (lp->clientseat) { + interactor_return_seat(lp->clientitr); + lp->clientitr = NULL; + lp->clientseat = NULL; + } +} + +static void local_proxy_opener_coroutine(void *vctx) +{ + LocalProxyOpener *lp = (LocalProxyOpener *)vctx; + + crBegin(lp->crLine); + + /* + * Make an initial attempt to figure out the command we want, and + * see if it tried to include a username or password that we don't + * have. + */ + { + unsigned flags; + lp->formatted_cmd = format_telnet_command( + lp->addr, lp->port, lp->conf, &flags); + + if (lp->clientseat && (flags & (TELNET_CMD_MISSING_USERNAME | + TELNET_CMD_MISSING_PASSWORD))) { + burnstr(lp->formatted_cmd); + lp->formatted_cmd = NULL; + + /* + * We're missing at least one of the two parts, and we + * have an Interactor we can use to prompt for them, so + * try it. + */ + lp->prompts = new_prompts(); + lp->prompts->callback = local_proxy_opener_coroutine; + lp->prompts->callback_ctx = lp; + lp->prompts->to_server = true; + lp->prompts->from_server = false; + lp->prompts->name = dupstr("Local proxy authentication"); + if (flags & TELNET_CMD_MISSING_USERNAME) { + lp->username_prompt_index = lp->prompts->n_prompts; + add_prompt(lp->prompts, dupstr("Proxy username: "), true); + } else { + lp->username_prompt_index = -1; + } + if (flags & TELNET_CMD_MISSING_PASSWORD) { + lp->password_prompt_index = lp->prompts->n_prompts; + add_prompt(lp->prompts, dupstr("Proxy password: "), false); + } else { + lp->password_prompt_index = -1; + } + + while (true) { + int prompt_result = seat_get_userpass_input( + interactor_announce(&lp->interactor), lp->prompts); + if (prompt_result > 0) { + break; + } else if (prompt_result == 0) { + local_proxy_opener_cleanup_interactor(lp); + plug_closing_user_abort(lp->plug); + /* That will have freed us, so we must just return + * without calling any crStop */ + return; + } + crReturnV; + } + + if (lp->username_prompt_index != -1) { + conf_set_str( + lp->conf, CONF_proxy_username, + prompt_get_result_ref( + lp->prompts->prompts[lp->username_prompt_index])); + } + + if (lp->password_prompt_index != -1) { + conf_set_str( + lp->conf, CONF_proxy_password, + prompt_get_result_ref( + lp->prompts->prompts[lp->password_prompt_index])); + } + + free_prompts(lp->prompts); + lp->prompts = NULL; + } + + /* + * Now format the command a second time, with the results of + * those prompts written into lp->conf. + */ + lp->formatted_cmd = format_telnet_command( + lp->addr, lp->port, lp->conf, NULL); + } + + /* + * Log the command, with some changes. Firstly, we regenerate it + * with the password masked; secondly, we escape control + * characters so that the log message is printable. + */ + conf_set_str(lp->conf, CONF_proxy_password, "*password*"); + { + char *censored_cmd = format_telnet_command( + lp->addr, lp->port, lp->conf, NULL); + + strbuf *logmsg = strbuf_new(); + put_datapl(logmsg, PTRLEN_LITERAL("Starting local proxy command: ")); + put_c_string_literal(logmsg, ptrlen_from_asciz(censored_cmd)); + + plug_log(lp->plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg->s, 0); + strbuf_free(logmsg); + sfree(censored_cmd); + } + + /* + * Now we're ready to actually do the platform-specific socket + * setup. + */ + char *cmd = lp->formatted_cmd; + lp->formatted_cmd = NULL; + + local_proxy_opener_cleanup_interactor(lp); + + char *error_msg = platform_setup_local_proxy(lp->socket, cmd); + burnstr(cmd); + + if (error_msg) { + plug_closing_error(lp->plug, error_msg); + sfree(error_msg); + } else { + /* If error_msg was NULL, there was no error in setup, + * which means that platform_setup_local_proxy will have + * called back to free us. So return without calling any + * crStop. */ + return; + } + + crFinishV; +} + +DeferredSocketOpener *local_proxy_opener( + SockAddr *addr, int port, Plug *plug, Conf *conf, Interactor *itr) +{ + LocalProxyOpener *lp = snew(LocalProxyOpener); + memset(lp, 0, sizeof(*lp)); + lp->plug = plug; + lp->opener.vt = &LocalProxyOpener_openervt; + lp->interactor.vt = &LocalProxyOpener_interactorvt; + lp->addr = sk_addr_dup(addr); + lp->port = port; + lp->conf = conf_copy(conf); + + if (itr) { + lp->clientitr = itr; + interactor_set_child(lp->clientitr, &lp->interactor); + lp->clientlp = interactor_logpolicy(lp->clientitr); + lp->clientseat = interactor_borrow_seat(lp->clientitr); + } + + return &lp->opener; +} + +void local_proxy_opener_set_socket(DeferredSocketOpener *opener, + Socket *socket) +{ + assert(opener->vt == &LocalProxyOpener_openervt); + LocalProxyOpener *lp = container_of(opener, LocalProxyOpener, opener); + lp->socket = socket; + queue_toplevel_callback(local_proxy_opener_coroutine, lp); +} diff --git a/proxy/proxy.h b/proxy/proxy.h index f44a0c55..aacc1cb1 100644 --- a/proxy/proxy.h +++ b/proxy/proxy.h @@ -105,6 +105,12 @@ prompts_t *proxy_new_prompts(ProxySocket *ps); char *format_telnet_command(SockAddr *addr, int port, Conf *conf, unsigned *flags_out); +DeferredSocketOpener *local_proxy_opener( + SockAddr *addr, int port, Plug *plug, Conf *conf, Interactor *itr); +void local_proxy_opener_set_socket(DeferredSocketOpener *opener, + Socket *socket); +char *platform_setup_local_proxy(Socket *socket, const char *cmd); + #include "cproxy.h" #endif diff --git a/unix/local-proxy.c b/unix/local-proxy.c index 7275bd5a..0f52931f 100644 --- a/unix/local-proxy.c +++ b/unix/local-proxy.c @@ -14,79 +14,73 @@ #include "network.h" #include "proxy/proxy.h" +char *platform_setup_local_proxy(Socket *socket, const char *cmd) +{ + /* + * Create the pipes to the proxy command, and spawn the proxy + * command process. + */ + int to_cmd_pipe[2], from_cmd_pipe[2], cmd_err_pipe[2]; + if (pipe(to_cmd_pipe) < 0 || + pipe(from_cmd_pipe) < 0 || + pipe(cmd_err_pipe) < 0) { + return dupprintf("pipe: %s", strerror(errno)); + } + cloexec(to_cmd_pipe[1]); + cloexec(from_cmd_pipe[0]); + cloexec(cmd_err_pipe[0]); + + int pid = fork(); + if (pid == 0) { + close(0); + close(1); + dup2(to_cmd_pipe[0], 0); + dup2(from_cmd_pipe[1], 1); + close(to_cmd_pipe[0]); + close(from_cmd_pipe[1]); + dup2(cmd_err_pipe[1], 2); + noncloexec(0); + noncloexec(1); + execl("/bin/sh", "sh", "-c", cmd, (void *)NULL); + _exit(255); + } + + if (pid < 0) { + return dupprintf("fork: %s", strerror(errno)); + } + + close(to_cmd_pipe[0]); + close(from_cmd_pipe[1]); + close(cmd_err_pipe[1]); + + setup_fd_socket(socket, from_cmd_pipe[0], to_cmd_pipe[1], cmd_err_pipe[0]); + + return NULL; +} + Socket *platform_new_connection(SockAddr *addr, const char *hostname, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, Plug *plug, Conf *conf, Interactor *itr) { - char *cmd; + switch (conf_get_int(conf, CONF_proxy_type)) { + case PROXY_CMD: { + DeferredSocketOpener *opener = local_proxy_opener( + addr, port, plug, conf, itr); + Socket *socket = make_deferred_fd_socket(opener, addr, port, plug); + local_proxy_opener_set_socket(opener, socket); + return socket; + } - int to_cmd_pipe[2], from_cmd_pipe[2], cmd_err_pipe[2], pid, proxytype; - int infd, outfd, inerrfd; - - proxytype = conf_get_int(conf, CONF_proxy_type); - if (proxytype != PROXY_CMD && proxytype != PROXY_FUZZ) - return NULL; - - if (proxytype == PROXY_CMD) { - cmd = format_telnet_command(addr, port, conf, NULL); - - { - char *logmsg = dupprintf("Starting local proxy command: %s", cmd); - plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0); - sfree(logmsg); - } - - /* - * Create the pipes to the proxy command, and spawn the proxy - * command process. - */ - if (pipe(to_cmd_pipe) < 0 || - pipe(from_cmd_pipe) < 0 || - pipe(cmd_err_pipe) < 0) { - sfree(cmd); - return new_error_socket_fmt(plug, "pipe: %s", strerror(errno)); - } - cloexec(to_cmd_pipe[1]); - cloexec(from_cmd_pipe[0]); - cloexec(cmd_err_pipe[0]); - - pid = fork(); - if (pid == 0) { - close(0); - close(1); - dup2(to_cmd_pipe[0], 0); - dup2(from_cmd_pipe[1], 1); - close(to_cmd_pipe[0]); - close(from_cmd_pipe[1]); - dup2(cmd_err_pipe[1], 2); - noncloexec(0); - noncloexec(1); - execl("/bin/sh", "sh", "-c", cmd, (void *)NULL); - _exit(255); - } - - sfree(cmd); - - if (pid < 0) - return new_error_socket_fmt(plug, "fork: %s", strerror(errno)); - - close(to_cmd_pipe[0]); - close(from_cmd_pipe[1]); - close(cmd_err_pipe[1]); - - outfd = to_cmd_pipe[1]; - infd = from_cmd_pipe[0]; - inerrfd = cmd_err_pipe[0]; - } else { - cmd = format_telnet_command(addr, port, conf, NULL); - outfd = open("/dev/null", O_WRONLY); + case PROXY_FUZZ: { + char *cmd = format_telnet_command(addr, port, conf, NULL); + int outfd = open("/dev/null", O_WRONLY); if (outfd == -1) { sfree(cmd); return new_error_socket_fmt( plug, "/dev/null: %s", strerror(errno)); } - infd = open(cmd, O_RDONLY); + int infd = open(cmd, O_RDONLY); if (infd == -1) { Socket *toret = new_error_socket_fmt( plug, "%s: %s", cmd, strerror(errno)); @@ -95,8 +89,10 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname, return toret; } sfree(cmd); - inerrfd = -1; - } + return make_fd_socket(infd, outfd, -1, addr, port, plug); + } - return make_fd_socket(infd, outfd, inerrfd, addr, port, plug); + default: + return NULL; + } } diff --git a/windows/local-proxy.c b/windows/local-proxy.c index 7e4e7bed..3c4922f0 100644 --- a/windows/local-proxy.c +++ b/windows/local-proxy.c @@ -12,12 +12,8 @@ #include "network.h" #include "proxy/proxy.h" -Socket *platform_new_connection(SockAddr *addr, const char *hostname, - int port, bool privport, - bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf, Interactor *itr) +char *platform_setup_local_proxy(Socket *socket, const char *cmd) { - char *cmd; HANDLE us_to_cmd, cmd_from_us; HANDLE us_from_cmd, cmd_to_us; HANDLE us_from_cmd_err, cmd_err_to_us; @@ -25,17 +21,6 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname, STARTUPINFO si; PROCESS_INFORMATION pi; - if (conf_get_int(conf, CONF_proxy_type) != PROXY_CMD) - return NULL; - - cmd = format_telnet_command(addr, port, conf, NULL); - - { - char *msg = dupprintf("Starting local proxy command: %s", cmd); - plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0); - sfree(msg); - } - /* * Create the pipes to the proxy command, and spawn the proxy * command process. @@ -44,30 +29,24 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname, sa.lpSecurityDescriptor = NULL; /* default */ sa.bInheritHandle = true; if (!CreatePipe(&us_from_cmd, &cmd_to_us, &sa, 0)) { - sfree(cmd); - return new_error_socket_fmt( - plug, "Unable to create pipes for proxy command: %s", - win_strerror(GetLastError())); + return dupprintf("Unable to create pipes for proxy command: %s", + win_strerror(GetLastError())); } if (!CreatePipe(&cmd_from_us, &us_to_cmd, &sa, 0)) { - sfree(cmd); CloseHandle(us_from_cmd); CloseHandle(cmd_to_us); - return new_error_socket_fmt( - plug, "Unable to create pipes for proxy command: %s", - win_strerror(GetLastError())); + return dupprintf("Unable to create pipes for proxy command: %s", + win_strerror(GetLastError())); } if (!CreatePipe(&us_from_cmd_err, &cmd_err_to_us, &sa, 0)) { - sfree(cmd); CloseHandle(us_from_cmd); CloseHandle(cmd_to_us); CloseHandle(us_to_cmd); CloseHandle(cmd_from_us); - return new_error_socket_fmt( - plug, "Unable to create pipes for proxy command: %s", - win_strerror(GetLastError())); + return dupprintf("Unable to create pipes for proxy command: %s", + win_strerror(GetLastError())); } SetHandleInformation(us_to_cmd, HANDLE_FLAG_INHERIT, 0); @@ -85,20 +64,37 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname, si.hStdInput = cmd_from_us; si.hStdOutput = cmd_to_us; si.hStdError = cmd_err_to_us; - CreateProcess(NULL, cmd, NULL, NULL, true, + char *cmd_mutable = dupstr(cmd); /* CreateProcess needs non-const char * */ + CreateProcess(NULL, cmd_mutable, NULL, NULL, true, CREATE_NO_WINDOW | NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi); + sfree(cmd_mutable); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); - sfree(cmd); - CloseHandle(cmd_from_us); CloseHandle(cmd_to_us); if (cmd_err_to_us != NULL) CloseHandle(cmd_err_to_us); - return make_handle_socket(us_to_cmd, us_from_cmd, us_from_cmd_err, - addr, port, plug, false); + setup_handle_socket(socket, us_to_cmd, us_from_cmd, us_from_cmd_err, + false); + + return NULL; +} + +Socket *platform_new_connection(SockAddr *addr, const char *hostname, + int port, bool privport, + bool oobinline, bool nodelay, bool keepalive, + Plug *plug, Conf *conf, Interactor *itr) +{ + if (conf_get_int(conf, CONF_proxy_type) != PROXY_CMD) + return NULL; + + DeferredSocketOpener *opener = local_proxy_opener( + addr, port, plug, conf, itr); + Socket *socket = make_deferred_handle_socket(opener, addr, port, plug); + local_proxy_opener_set_socket(opener, socket); + return socket; }