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:
parent
445bcd7030
commit
a864f7bb57
@ -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
|
||||
|
184
proxy/telnet.c
184
proxy/telnet.c
@ -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 = {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user