mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-25 01:02:24 +00:00
b7bf2aec74
Previously, the proxy negotiation functions were written as explicit state machines, with ps->state being manually set to a sequence of positive integer values which would be tested by if statements in the next call to the same negotiation function. That's not how this code base likes to do things! We have a coroutine system to allow those state machines to be implicit rather than explicit, so that we can use ordinary control flow statements like while loops. Reorganised each proxy negotiation function into a coroutine-based system like that. While I'm at it, I've also moved each proxy negotiator out into its own source file, to make proxy.c less overcrowded and monolithic. And _that_ gave me the opportunity to define each negotiator as an implementation of a trait rather than as a single function - which means now each one can define its own local variables and have its own cleanup function, instead of all of them having to share the variables inside the main ProxySocket struct. In the new coroutine system, negotiators don't have to worry about the mechanics of actually sending data down the underlying Socket any more. The negotiator coroutine just appends to a bufchain (via a provided bufchain_sink), and after every call to the coroutine, central code in proxy.c transfers the data to the Socket itself. This avoids a lot of intermediate allocations within the negotiators, which previously kept having to make temporary strbufs or arrays in order to have something to point an sk_write() at; now they can just put formatted data directly into the output bufchain via the marshal.h interface. In this version of the code, I've also moved most of the SOCKS5 CHAP implementation from cproxy.c into socks5.c, so that it can sit in the same coroutine as the rest of the proxy negotiation control flow. That's because calling a sub-coroutine (co-subroutine?) is awkward to set up (though it is _possible_ - we do SSH-2 kex that way), and there's no real need to bother in this case, since the only thing that really needs to go in cproxy.c is the actual cryptography plus a flag to tell socks5.c whether to offer CHAP authentication in the first place.
247 lines
7.2 KiB
C
247 lines
7.2 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"
|
|
|
|
char *format_telnet_command(SockAddr *addr, int port, Conf *conf)
|
|
{
|
|
char *fmt = conf_get_str(conf, CONF_proxy_telnet_command);
|
|
int so = 0, eo = 0;
|
|
strbuf *buf = strbuf_new();
|
|
|
|
/* 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;
|
|
}
|
|
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;
|
|
}
|
|
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);
|
|
}
|
|
|
|
return strbuf_to_str(buf);
|
|
}
|
|
|
|
typedef struct TelnetProxyNegotiator {
|
|
ProxyNegotiator pn;
|
|
} TelnetProxyNegotiator;
|
|
|
|
static ProxyNegotiator *proxy_telnet_new(const ProxyNegotiatorVT *vt)
|
|
{
|
|
TelnetProxyNegotiator *s = snew(TelnetProxyNegotiator);
|
|
s->pn.vt = vt;
|
|
return &s->pn;
|
|
}
|
|
|
|
static void proxy_telnet_free(ProxyNegotiator *pn)
|
|
{
|
|
TelnetProxyNegotiator *s = container_of(pn, TelnetProxyNegotiator, pn);
|
|
sfree(s);
|
|
}
|
|
|
|
static void proxy_telnet_process_queue(ProxyNegotiator *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);
|
|
|
|
/*
|
|
* Re-escape control chars in the command, for logging.
|
|
*/
|
|
strbuf *logmsg = strbuf_new();
|
|
const char *in;
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
/*
|
|
* Unconditionally report success.
|
|
*/
|
|
pn->done = true;
|
|
}
|
|
|
|
const struct ProxyNegotiatorVT telnet_proxy_negotiator_vt = {
|
|
.new = proxy_telnet_new,
|
|
.free = proxy_telnet_free,
|
|
.process_queue = proxy_telnet_process_queue,
|
|
.type = "Telnet",
|
|
};
|