1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 17:38:00 +00:00
putty-source/proxy/telnet.c
Simon Tatham c1d9da67a2 Pass the calling Socket to plug_log.
This enables plug_log to run query methods on the socket in order to
find out useful information to log. I don't expect it's sensible to do
anything else with it.
2024-06-29 12:00:12 +01:00

370 lines
12 KiB
C

/*
* "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",
};