123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370 |
- /*
- * "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, &pn->ps->sock, 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",
- };
|