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:
163
proxy/cproxy.c
163
proxy/cproxy.c
@ -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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user