Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mRemoteNG/PuTTYNG.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'proxy/telnet.c')
-rw-r--r--proxy/telnet.c368
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",
+};