/* * 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) { SeatPromptResult spr = seat_get_userpass_input( interactor_announce(&lp->interactor), lp->prompts); if (spr.kind == SPRK_OK) { break; } else if (spr.kind == SPRK_USER_ABORT) { 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; } else if (spr.kind == SPRK_SW_ABORT) { local_proxy_opener_cleanup_interactor(lp); char *err = spr_get_error_message(spr); plug_closing_error(lp->plug, err); sfree(err); return; /* without crStop, as above */ } 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, lp->socket, 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); }