1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-07-01 11:32:48 -05:00

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.
This commit is contained in:
Simon Tatham
2021-11-19 10:26:41 +00:00
parent 23c64fa00e
commit b7bf2aec74
9 changed files with 1092 additions and 1108 deletions

View File

@ -13,163 +13,14 @@
#include "ssh.h" /* For MD5 support */
#include "network.h"
#include "proxy.h"
#include "socks.h"
#include "marshal.h"
static void hmacmd5_chap(const unsigned char *challenge, int challen,
const char *passwd, unsigned char *response)
const bool socks5_chap_available = true;
strbuf *chap_response(ptrlen challenge, ptrlen password)
{
mac_simple(&ssh_hmac_md5, ptrlen_from_asciz(passwd),
make_ptrlen(challenge, challen), response);
}
void proxy_socks5_offerencryptedauth(BinarySink *bs)
{
put_byte(bs, SOCKS5_AUTH_CHAP);
}
int proxy_socks5_handlechap (ProxySocket *ps)
{
/* CHAP authentication reply format:
* version number (1 bytes) = 1
* number of commands (1 byte)
*
* For each command:
* command identifier (1 byte)
* data length (1 byte)
*/
unsigned char data[260];
unsigned char outbuf[20];
while(ps->chap_num_attributes == 0 ||
ps->chap_num_attributes_processed < ps->chap_num_attributes) {
if (ps->chap_num_attributes == 0 ||
ps->chap_current_attribute == -1) {
/* CHAP normally reads in two bytes, either at the
* beginning or for each attribute/value pair. But if
* we're waiting for the value's data, we might not want
* to read 2 bytes.
*/
if (bufchain_size(&ps->pending_input_data) < 2)
return 1; /* not got anything yet */
/* get the response */
bufchain_fetch(&ps->pending_input_data, data, 2);
bufchain_consume(&ps->pending_input_data, 2);
}
if (ps->chap_num_attributes == 0) {
/* If there are no attributes, this is our first msg
* with the server, where we negotiate version and
* number of attributes
*/
if (data[0] != SOCKS5_AUTH_CHAP_VERSION) {
plug_closing_error(ps->plug, "Proxy error: SOCKS proxy wants "
"a different CHAP version");
return 1;
}
if (data[1] == 0x00) {
plug_closing_error(ps->plug, "Proxy error: SOCKS proxy won't "
"negotiate CHAP with us");
return 1;
}
ps->chap_num_attributes = data[1];
} else {
if (ps->chap_current_attribute == -1) {
/* We have to read in each attribute/value pair -
* those we don't understand can be ignored, but
* there are a few we'll need to handle.
*/
ps->chap_current_attribute = data[0];
ps->chap_current_datalen = data[1];
}
if (bufchain_size(&ps->pending_input_data) <
ps->chap_current_datalen)
return 1; /* not got everything yet */
/* get the response */
bufchain_fetch(&ps->pending_input_data, data,
ps->chap_current_datalen);
bufchain_consume(&ps->pending_input_data,
ps->chap_current_datalen);
switch (ps->chap_current_attribute) {
case SOCKS5_AUTH_CHAP_ATTR_STATUS:
/* Successful authentication */
if (data[0] == 0x00)
ps->state = 2;
else {
plug_closing_error(ps->plug, "Proxy error: SOCKS proxy "
"refused CHAP authentication");
return 1;
}
break;
case SOCKS5_AUTH_CHAP_ATTR_CHALLENGE:
outbuf[0] = SOCKS5_AUTH_CHAP_VERSION;
outbuf[1] = 0x01; /* One attribute */
outbuf[2] = SOCKS5_AUTH_CHAP_ATTR_RESPONSE;
outbuf[3] = 0x10; /* Length */
hmacmd5_chap(data, ps->chap_current_datalen,
conf_get_str(ps->conf, CONF_proxy_password),
&outbuf[4]);
sk_write(ps->sub_socket, outbuf, 20);
break;
case SOCKS5_AUTH_CHAP_ATTR_ALGLIST:
/* Chose a protocol */
if (data[0] != SOCKS5_AUTH_CHAP_ALG_HMACMD5) {
plug_closing_error(ps->plug, "Proxy error: Server chose "
"CHAP of other than HMAC-MD5 but we "
"didn't offer it!");
return 1;
}
break;
}
ps->chap_current_attribute = -1;
ps->chap_num_attributes_processed++;
}
if (ps->state == 8 &&
ps->chap_num_attributes_processed >= ps->chap_num_attributes) {
ps->chap_num_attributes = 0;
ps->chap_num_attributes_processed = 0;
ps->chap_current_datalen = 0;
}
}
return 0;
}
int proxy_socks5_selectchap(ProxySocket *ps)
{
char *username = conf_get_str(ps->conf, CONF_proxy_username);
char *password = conf_get_str(ps->conf, CONF_proxy_password);
if (username[0] || password[0]) {
char chapbuf[514];
int ulen;
chapbuf[0] = SOCKS5_AUTH_CHAP_VERSION;
chapbuf[1] = '\x02'; /* Number of attributes sent */
chapbuf[2] = SOCKS5_AUTH_CHAP_ATTR_ALGLIST;
chapbuf[3] = '\x01'; /* Only one CHAP algorithm */
chapbuf[4] = SOCKS5_AUTH_CHAP_ALG_HMACMD5;
chapbuf[5] = SOCKS5_AUTH_CHAP_ATTR_USERNAME;
ulen = strlen(username);
if (ulen > 255) ulen = 255;
if (ulen < 1) ulen = 1;
chapbuf[6] = ulen;
memcpy(chapbuf+7, username, ulen);
sk_write(ps->sub_socket, chapbuf, ulen + 7);
ps->chap_num_attributes = 0;
ps->chap_num_attributes_processed = 0;
ps->chap_current_attribute = -1;
ps->chap_current_datalen = 0;
ps->state = 8;
} else
plug_closing_error(ps->plug, "Proxy error: Server chose "
"CHAP authentication but we didn't offer it!");
return 1;
strbuf *sb = strbuf_new_nm();
const ssh2_macalg *alg = &ssh_hmac_md5;
mac_simple(alg, password, challenge, strbuf_append(sb, alg->len));
return sb;
}