diff --git a/proxy/proxy.h b/proxy/proxy.h index f099541d..9268fcb4 100644 --- a/proxy/proxy.h +++ b/proxy/proxy.h @@ -100,7 +100,10 @@ prompts_t *proxy_new_prompts(ProxySocket *ps); * This may be reused by local-command proxies on individual * platforms. */ -char *format_telnet_command(SockAddr *addr, int port, Conf *conf); +#define TELNET_CMD_MISSING_USERNAME 0x0001 +#define TELNET_CMD_MISSING_PASSWORD 0x0002 +char *format_telnet_command(SockAddr *addr, int port, Conf *conf, + unsigned *flags_out); /* * These are implemented in cproxy.c or nocproxy.c, depending on diff --git a/proxy/telnet.c b/proxy/telnet.c index 91e6060c..ebba3948 100644 --- a/proxy/telnet.c +++ b/proxy/telnet.c @@ -10,12 +10,15 @@ #include "putty.h" #include "network.h" #include "proxy.h" +#include "sshcr.h" -char *format_telnet_command(SockAddr *addr, int port, Conf *conf) +char *format_telnet_command(SockAddr *addr, int port, Conf *conf, + unsigned *flags_out) { char *fmt = conf_get_str(conf, CONF_proxy_telnet_command); int so = 0, eo = 0; strbuf *buf = strbuf_new(); + unsigned flags = 0; /* we need to escape \\, \%, \r, \n, \t, \x??, \0???, * %%, %host, %port, %user, and %pass @@ -141,11 +144,15 @@ char *format_telnet_command(SockAddr *addr, int port, Conf *conf) const char *username = conf_get_str(conf, CONF_proxy_username); put_data(buf, username, strlen(username)); eo += 4; + if (!*username) + flags |= TELNET_CMD_MISSING_USERNAME; } else if (strnicmp(fmt + eo, "pass", 4) == 0) { const char *password = conf_get_str(conf, CONF_proxy_password); put_data(buf, password, strlen(password)); eo += 4; + if (!*password) + flags |= TELNET_CMD_MISSING_PASSWORD; } else if (strnicmp(fmt + eo, "proxyhost", 9) == 0) { const char *host = conf_get_str(conf, CONF_proxy_host); @@ -175,16 +182,24 @@ char *format_telnet_command(SockAddr *addr, int port, Conf *conf) put_data(buf, fmt + so, eo - so); } + if (flags_out) + *flags_out = flags; return strbuf_to_str(buf); } typedef struct TelnetProxyNegotiator { + int crLine; + Conf *conf; + char *formatted_cmd; + prompts_t *prompts; + int username_prompt_index, password_prompt_index; ProxyNegotiator pn; } TelnetProxyNegotiator; static ProxyNegotiator *proxy_telnet_new(const ProxyNegotiatorVT *vt) { TelnetProxyNegotiator *s = snew(TelnetProxyNegotiator); + memset(s, 0, sizeof(*s)); s->pn.vt = vt; return &s->pn; } @@ -192,50 +207,167 @@ static ProxyNegotiator *proxy_telnet_new(const ProxyNegotiatorVT *vt) static void proxy_telnet_free(ProxyNegotiator *pn) { TelnetProxyNegotiator *s = container_of(pn, TelnetProxyNegotiator, pn); + if (s->conf) + conf_free(s->conf); + if (s->prompts) + free_prompts(s->prompts); + burnstr(s->formatted_cmd); + delete_callbacks_for_context(s); sfree(s); } +static void proxy_telnet_process_queue_callback(void *vctx) +{ + TelnetProxyNegotiator *s = (TelnetProxyNegotiator *)vctx; + proxy_negotiator_process_queue(&s->pn); +} + static void proxy_telnet_process_queue(ProxyNegotiator *pn) { - // TelnetProxyNegotiator *s = container_of(pn, TelnetProxyNegotiator, pn); + TelnetProxyNegotiator *s = container_of(pn, TelnetProxyNegotiator, pn); - char *formatted_cmd = format_telnet_command( - pn->ps->remote_addr, pn->ps->remote_port, pn->ps->conf); + crBegin(s->crLine); + + s->conf = conf_copy(pn->ps->conf); + + /* + * 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; + s->formatted_cmd = format_telnet_command( + pn->ps->remote_addr, pn->ps->remote_port, s->conf, &flags); + + if (pn->itr && (flags & (TELNET_CMD_MISSING_USERNAME | + TELNET_CMD_MISSING_PASSWORD))) { + burnstr(s->formatted_cmd); + s->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. + */ + s->prompts = proxy_new_prompts(pn->ps); + s->prompts->to_server = true; + s->prompts->from_server = false; + s->prompts->name = dupstr("Telnet proxy authentication"); + if (flags & TELNET_CMD_MISSING_USERNAME) { + s->username_prompt_index = s->prompts->n_prompts; + add_prompt(s->prompts, dupstr("Proxy username: "), true); + } else { + s->username_prompt_index = -1; + } + if (flags & TELNET_CMD_MISSING_PASSWORD) { + s->password_prompt_index = s->prompts->n_prompts; + add_prompt(s->prompts, dupstr("Proxy password: "), false); + } else { + s->password_prompt_index = -1; + } + + /* + * This prompt is presented extremely early in PuTTY's + * setup. (Very promptly, you might say.) + * + * In particular, we can get here through a chain of + * synchronous calls from backend_init, which means (in + * GUI PuTTY) that the terminal we'll be sending this + * prompt to may not have its Ldisc set up yet (due to + * cyclic dependencies among all the things that have to + * be initialised). + * + * So we'll start by having ourself called back via a + * toplevel callback, to make sure we don't call + * seat_get_userpass_input until we've returned from + * backend_init and the frontend has finished getting + * everything ready. + */ + queue_toplevel_callback(proxy_telnet_process_queue_callback, s); + crReturnV; + + while (true) { + int prompt_result = seat_get_userpass_input( + interactor_announce(pn->itr), s->prompts); + if (prompt_result > 0) { + break; + } else if (prompt_result == 0) { + pn->aborted = true; + crStopV; + } + crReturnV; + } + + if (s->username_prompt_index != -1) { + conf_set_str( + s->conf, CONF_proxy_username, + prompt_get_result_ref( + s->prompts->prompts[s->username_prompt_index])); + } + + if (s->password_prompt_index != -1) { + conf_set_str( + s->conf, CONF_proxy_password, + prompt_get_result_ref( + s->prompts->prompts[s->password_prompt_index])); + } + + free_prompts(s->prompts); + s->prompts = NULL; + } + + /* + * Now format the command a second time, with the results of + * those prompts written into s->conf. + */ + s->formatted_cmd = format_telnet_command( + pn->ps->remote_addr, pn->ps->remote_port, s->conf, NULL); + } /* * Re-escape control chars in the command, for logging. */ - strbuf *logmsg = strbuf_new(); - const char *in; + { + strbuf *logmsg = strbuf_new(); + const char *in; - put_datapl(logmsg, PTRLEN_LITERAL("Sending Telnet proxy command: ")); + put_datapl(logmsg, PTRLEN_LITERAL("Sending Telnet proxy command: ")); - for (in = formatted_cmd; *in; in++) { - if (*in == '\n') { - put_datapl(logmsg, PTRLEN_LITERAL("\\n")); - } else if (*in == '\r') { - put_datapl(logmsg, PTRLEN_LITERAL("\\r")); - } else if (*in == '\t') { - put_datapl(logmsg, PTRLEN_LITERAL("\\t")); - } else if (*in == '\\') { - put_datapl(logmsg, PTRLEN_LITERAL("\\\\")); - } else if (0x20 <= *in && *in < 0x7F) { - put_byte(logmsg, *in); - } else { - put_fmt(logmsg, "\\x%02X", (unsigned)*in & 0xFF); + for (in = s->formatted_cmd; *in; in++) { + if (*in == '\n') { + put_datapl(logmsg, PTRLEN_LITERAL("\\n")); + } else if (*in == '\r') { + put_datapl(logmsg, PTRLEN_LITERAL("\\r")); + } else if (*in == '\t') { + put_datapl(logmsg, PTRLEN_LITERAL("\\t")); + } else if (*in == '\\') { + put_datapl(logmsg, PTRLEN_LITERAL("\\\\")); + } else if (0x20 <= *in && *in < 0x7F) { + put_byte(logmsg, *in); + } else { + put_fmt(logmsg, "\\x%02X", (unsigned)*in & 0xFF); + } } + + plug_log(pn->ps->plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg->s, 0); + strbuf_free(logmsg); } - plug_log(pn->ps->plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg->s, 0); - strbuf_free(logmsg); - - put_dataz(pn->output, formatted_cmd); - sfree(formatted_cmd); + /* + * Actually send the command. + */ + put_dataz(pn->output, s->formatted_cmd); /* - * Unconditionally report success. + * Unconditionally report success. We don't hang around waiting + * for error messages from the proxy, because this proxy type is + * so ad-hoc that we wouldn't know how to even recognise an error + * message if we saw one, let alone what to do about it. */ pn->done = true; + + crFinishV; } const struct ProxyNegotiatorVT telnet_proxy_negotiator_vt = { diff --git a/unix/local-proxy.c b/unix/local-proxy.c index 583bb75a..f4c98ced 100644 --- a/unix/local-proxy.c +++ b/unix/local-proxy.c @@ -29,7 +29,7 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname, return NULL; if (proxytype == PROXY_CMD) { - cmd = format_telnet_command(addr, port, conf); + cmd = format_telnet_command(addr, port, conf, NULL); { char *logmsg = dupprintf("Starting local proxy command: %s", cmd); @@ -79,7 +79,7 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname, infd = from_cmd_pipe[0]; inerrfd = cmd_err_pipe[0]; } else { - cmd = format_telnet_command(addr, port, conf); + cmd = format_telnet_command(addr, port, conf, NULL); outfd = open("/dev/null", O_WRONLY); if (outfd == -1) { sfree(cmd); diff --git a/windows/local-proxy.c b/windows/local-proxy.c index 9672ddbe..19ceb726 100644 --- a/windows/local-proxy.c +++ b/windows/local-proxy.c @@ -28,7 +28,7 @@ Socket *platform_new_connection(SockAddr *addr, const char *hostname, if (conf_get_int(conf, CONF_proxy_type) != PROXY_CMD) return NULL; - cmd = format_telnet_command(addr, port, conf); + cmd = format_telnet_command(addr, port, conf, NULL); { char *msg = dupprintf("Starting local proxy command: %s", cmd);