1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 17:38:00 +00:00

Support interactive password prompts in Telnet proxy.

The Telnet proxy system is not a proper network protocol - we have no
reliable way to receive communication from the proxy telling us
whether a password is even required. However, we _do_ know (a) whether
the keywords '%user' or '%pass' appeared in the format string stored
in the Conf, and (b) whether we actually had a username or a password
to substitute into them. So that's how we know whether to ask for a
username or a password: if the format string asks for them and the
Conf doesn't provide them, we prompt for them at startup.

This involved turning TelnetProxyNegotiator into a coroutine (matching
all the other proxy types, but previously, it was the only one simple
enough not to need to be one), so that it can wait until a response
arrives to that prompt. (And also, as it turned out, so that it can
wait until setup is finished before even presenting the prompt!)

It also involves having format_telnet_command grow an extra output
parameter, in the form of 'unsigned *flags', with which it can
communicate back to the caller that a username or password was wanted
but not found. The other clients of that function (the local proxy
implementations) don't use those flags, but if necessary, they could.
This commit is contained in:
Simon Tatham 2021-11-19 16:03:22 +00:00
parent 445bcd7030
commit a864f7bb57
4 changed files with 165 additions and 30 deletions

View File

@ -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

View File

@ -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 = {

View File

@ -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);

View File

@ -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);