1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-03-18 21:05:26 -05:00
Simon Tatham b7bf2aec74 Reorganise proxy system into coroutines.
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.
2021-11-19 15:09:17 +00:00

159 lines
4.3 KiB
C

/*
* HTTP CONNECT proxy negotiation.
*/
#include "putty.h"
#include "network.h"
#include "proxy.h"
#include "sshcr.h"
static bool read_line(bufchain *input, strbuf *output, bool is_header)
{
char c;
while (bufchain_try_fetch(input, &c, 1)) {
if (is_header && output->len > 0 &&
output->s[output->len - 1] == '\n') {
/*
* A newline terminates the header, provided we're sure it
* is _not_ followed by a space or a tab.
*/
if (c != ' ' && c != '\t')
goto done; /* we have a complete header line */
} else {
put_byte(output, c);
bufchain_consume(input, 1);
if (!is_header && output->len > 0 &&
output->s[output->len - 1] == '\n') {
/* If we're looking for just a line, not an HTTP
* header, then any newline terminates it. */
goto done;
}
}
}
return false;
done:
strbuf_chomp(output, '\n');
strbuf_chomp(output, '\r');
return true;
}
typedef struct HttpProxyNegotiator {
int crLine;
strbuf *line;
ProxyNegotiator pn;
} HttpProxyNegotiator;
static ProxyNegotiator *proxy_http_new(const ProxyNegotiatorVT *vt)
{
HttpProxyNegotiator *s = snew(HttpProxyNegotiator);
s->pn.vt = vt;
s->crLine = 0;
s->line = strbuf_new();
return &s->pn;
}
static void proxy_http_free(ProxyNegotiator *pn)
{
HttpProxyNegotiator *s = container_of(pn, HttpProxyNegotiator, pn);
strbuf_free(s->line);
sfree(s);
}
static void proxy_http_process_queue(ProxyNegotiator *pn)
{
HttpProxyNegotiator *s = container_of(pn, HttpProxyNegotiator, pn);
crBegin(s->crLine);
/*
* Standard prefix for the HTTP CONNECT request.
*/
{
char dest[512];
sk_getaddr(pn->ps->remote_addr, dest, lenof(dest));
put_fmt(pn->output,
"CONNECT %s:%d HTTP/1.1\r\n"
"Host: %s:%d\r\n",
dest, pn->ps->remote_port, dest, pn->ps->remote_port);
}
/*
* Optionally send an HTTP Basic auth header with the username and
* password.
*/
{
const char *username = conf_get_str(pn->ps->conf, CONF_proxy_username);
const char *password = conf_get_str(pn->ps->conf, CONF_proxy_password);
if (username[0] || password[0]) {
put_datalit(pn->output, "Proxy-Authorization: Basic ");
char *base64_input = dupcat(username, ":", password);
char base64_output[4];
for (size_t i = 0, e = strlen(base64_input); i < e; i += 3) {
base64_encode_atom((const unsigned char *)base64_input + i,
e-i > 3 ? 3 : e-i, base64_output);
put_data(pn->output, base64_output, 4);
}
burnstr(base64_input);
smemclr(base64_output, sizeof(base64_output));
put_datalit(pn->output, "\r\n");
}
}
/*
* Blank line to terminate the HTTP request.
*/
put_datalit(pn->output, "\r\n");
crReturnV;
/*
* Read and parse the HTTP status line, and check if it's a 2xx
* for success.
*/
strbuf_clear(s->line);
crMaybeWaitUntilV(read_line(pn->input, s->line, false));
{
int maj_ver, min_ver, status_pos = -1;
sscanf(s->line->s, "HTTP/%d.%d %n", &maj_ver, &min_ver, &status_pos);
/* If status_pos is still -1 then the sscanf didn't get right
* to the end of the string */
if (status_pos == -1) {
pn->error = dupstr("HTTP response was absent or malformed");
crStopV;
}
if (s->line->s[status_pos] != '2') {
pn->error = dupprintf("HTTP response %s", s->line->s + status_pos);
crStopV;
}
}
/*
* Read and skip the rest of the HTTP response headers, terminated
* by a blank line.
*/
do {
strbuf_clear(s->line);
crMaybeWaitUntilV(read_line(pn->input, s->line, true));
} while (s->line->len > 0);
/*
* Success! Hand over to the main connection.
*/
pn->done = true;
crFinishV;
}
const struct ProxyNegotiatorVT http_proxy_negotiator_vt = {
.new = proxy_http_new,
.free = proxy_http_free,
.process_queue = proxy_http_process_queue,
.type = "HTTP",
};