diff options
Diffstat (limited to 'proxy/telnet.c')
-rw-r--r-- | proxy/telnet.c | 368 |
1 files changed, 368 insertions, 0 deletions
diff --git a/proxy/telnet.c b/proxy/telnet.c new file mode 100644 index 00000000..a2efb7b4 --- /dev/null +++ b/proxy/telnet.c @@ -0,0 +1,368 @@ +/* + * "Telnet" proxy negotiation. + * + * (This is for ad-hoc proxies where you connect to the proxy's + * telnet port and send a command such as `connect host port'. The + * command is configurable, since this proxy type is typically not + * standardised or at all well-defined.) + */ + +#include "putty.h" +#include "network.h" +#include "proxy.h" +#include "sshcr.h" + +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 + */ + + while (fmt[eo] != 0) { + + /* scan forward until we hit end-of-line, + * or an escape character (\ or %) */ + while (fmt[eo] != 0 && fmt[eo] != '%' && fmt[eo] != '\\') + eo++; + + /* if we hit eol, break out of our escaping loop */ + if (fmt[eo] == 0) break; + + /* if there was any unescaped text before the escape + * character, send that now */ + if (eo != so) + put_data(buf, fmt + so, eo - so); + + so = eo++; + + /* if the escape character was the last character of + * the line, we'll just stop and send it. */ + if (fmt[eo] == 0) break; + + if (fmt[so] == '\\') { + + /* we recognize \\, \%, \r, \n, \t, \x??. + * anything else, we just send unescaped (including the \). + */ + + switch (fmt[eo]) { + + case '\\': + put_byte(buf, '\\'); + eo++; + break; + + case '%': + put_byte(buf, '%'); + eo++; + break; + + case 'r': + put_byte(buf, '\r'); + eo++; + break; + + case 'n': + put_byte(buf, '\n'); + eo++; + break; + + case 't': + put_byte(buf, '\t'); + eo++; + break; + + case 'x': + case 'X': { + /* escaped hexadecimal value (ie. \xff) */ + unsigned char v = 0; + int i = 0; + + for (;;) { + eo++; + if (fmt[eo] >= '0' && fmt[eo] <= '9') + v += fmt[eo] - '0'; + else if (fmt[eo] >= 'a' && fmt[eo] <= 'f') + v += fmt[eo] - 'a' + 10; + else if (fmt[eo] >= 'A' && fmt[eo] <= 'F') + v += fmt[eo] - 'A' + 10; + else { + /* non hex character, so we abort and just + * send the whole thing unescaped (including \x) + */ + put_byte(buf, '\\'); + eo = so + 1; + break; + } + + /* we only extract two hex characters */ + if (i == 1) { + put_byte(buf, v); + eo++; + break; + } + + i++; + v <<= 4; + } + break; + } + + default: + put_data(buf, fmt + so, 2); + eo++; + break; + } + } else { + + /* % escape. we recognize %%, %host, %port, %user, %pass. + * %proxyhost, %proxyport. Anything else we just send + * unescaped (including the %). + */ + + if (fmt[eo] == '%') { + put_byte(buf, '%'); + eo++; + } + else if (strnicmp(fmt + eo, "host", 4) == 0) { + char dest[512]; + sk_getaddr(addr, dest, lenof(dest)); + put_data(buf, dest, strlen(dest)); + eo += 4; + } + else if (strnicmp(fmt + eo, "port", 4) == 0) { + put_fmt(buf, "%d", port); + eo += 4; + } + else if (strnicmp(fmt + eo, "user", 4) == 0) { + 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); + put_data(buf, host, strlen(host)); + eo += 9; + } + else if (strnicmp(fmt + eo, "proxyport", 9) == 0) { + int port = conf_get_int(conf, CONF_proxy_port); + put_fmt(buf, "%d", port); + eo += 9; + } + else { + /* we don't escape this, so send the % now, and + * don't advance eo, so that we'll consider the + * text immediately following the % as unescaped. + */ + put_byte(buf, '%'); + } + } + + /* resume scanning for additional escapes after this one. */ + so = eo; + } + + /* if there is any unescaped text at the end of the line, send it */ + if (eo != so) { + 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; +} + +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); + + 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) { + SeatPromptResult spr = seat_get_userpass_input( + interactor_announce(pn->itr), s->prompts); + if (spr.kind == SPRK_OK) { + break; + } else if (spr_is_abort(spr)) { + proxy_spr_abort(pn, spr); + 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); + } + + /* + * 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(s->conf, CONF_proxy_password, "*password*"); + { + char *censored_cmd = format_telnet_command( + pn->ps->remote_addr, pn->ps->remote_port, s->conf, NULL); + + strbuf *logmsg = strbuf_new(); + put_datapl(logmsg, PTRLEN_LITERAL("Sending Telnet proxy command: ")); + put_c_string_literal(logmsg, ptrlen_from_asciz(censored_cmd)); + + plug_log(pn->ps->plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg->s, 0); + strbuf_free(logmsg); + sfree(censored_cmd); + } + + /* + * Actually send the command. + */ + put_dataz(pn->output, s->formatted_cmd); + + /* + * 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 = { + .new = proxy_telnet_new, + .free = proxy_telnet_free, + .process_queue = proxy_telnet_process_queue, + .type = "Telnet", +}; |