mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-09 17:38:00 +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.
137 lines
3.7 KiB
C
137 lines
3.7 KiB
C
/*
|
|
* SOCKS 4 proxy negotiation.
|
|
*/
|
|
|
|
#include "putty.h"
|
|
#include "network.h"
|
|
#include "proxy.h"
|
|
#include "socks.h"
|
|
#include "sshcr.h"
|
|
|
|
typedef struct Socks4ProxyNegotiator {
|
|
int crLine;
|
|
ProxyNegotiator pn;
|
|
} Socks4ProxyNegotiator;
|
|
|
|
static ProxyNegotiator *proxy_socks4_new(const ProxyNegotiatorVT *vt)
|
|
{
|
|
Socks4ProxyNegotiator *s = snew(Socks4ProxyNegotiator);
|
|
s->pn.vt = vt;
|
|
s->crLine = 0;
|
|
return &s->pn;
|
|
}
|
|
|
|
static void proxy_socks4_free(ProxyNegotiator *pn)
|
|
{
|
|
Socks4ProxyNegotiator *s = container_of(pn, Socks4ProxyNegotiator, pn);
|
|
sfree(s);
|
|
}
|
|
|
|
static void proxy_socks4_process_queue(ProxyNegotiator *pn)
|
|
{
|
|
Socks4ProxyNegotiator *s = container_of(pn, Socks4ProxyNegotiator, pn);
|
|
|
|
crBegin(s->crLine);
|
|
|
|
{
|
|
char hostname[512];
|
|
bool write_hostname = false;
|
|
|
|
/*
|
|
* SOCKS 4 request packet:
|
|
*
|
|
* byte version
|
|
* byte command
|
|
* uint16 destination port number
|
|
* uint32 destination IPv4 address (or something in the
|
|
* SOCKS4A_NAME_FOLLOWS range)
|
|
* asciz username
|
|
* asciz destination hostname (if we sent SOCKS4A_NAME_FOLLOWS_*)
|
|
*/
|
|
|
|
put_byte(pn->output, SOCKS4_REQUEST_VERSION);
|
|
put_byte(pn->output, SOCKS_CMD_CONNECT);
|
|
put_uint16(pn->output, pn->ps->remote_port);
|
|
|
|
switch (sk_addrtype(pn->ps->remote_addr)) {
|
|
case ADDRTYPE_IPV4: {
|
|
char addr[4];
|
|
sk_addrcopy(pn->ps->remote_addr, addr);
|
|
put_data(pn->output, addr, 4);
|
|
break;
|
|
}
|
|
case ADDRTYPE_NAME:
|
|
put_uint32(pn->output, SOCKS4A_NAME_FOLLOWS_BASE);
|
|
sk_getaddr(pn->ps->remote_addr, hostname, lenof(hostname));
|
|
write_hostname = true;
|
|
break;
|
|
case ADDRTYPE_IPV6:
|
|
pn->error = dupstr("SOCKS version 4 does not support IPv6");
|
|
crStopV;
|
|
}
|
|
|
|
put_asciz(pn->output, conf_get_str(pn->ps->conf, CONF_proxy_username));
|
|
|
|
if (write_hostname)
|
|
put_asciz(pn->output, hostname);
|
|
}
|
|
|
|
crReturnV;
|
|
|
|
{
|
|
unsigned char data[8];
|
|
crMaybeWaitUntilV(bufchain_try_fetch_consume(pn->input, data, 8));
|
|
|
|
/*
|
|
* SOCKS 4 response packet:
|
|
*
|
|
* byte version
|
|
* byte status
|
|
* uint16 port number
|
|
* uint32 IPv4 address
|
|
*
|
|
* We don't need to worry about the port and destination address.
|
|
*/
|
|
|
|
if (data[0] != SOCKS4_REPLY_VERSION) {
|
|
pn->error = dupprintf("SOCKS proxy response contained reply "
|
|
"version number %d (expected 0)",
|
|
(int)data[0]);
|
|
crStopV;
|
|
}
|
|
|
|
switch (data[1]) {
|
|
case SOCKS4_RESP_SUCCESS:
|
|
pn->done = true;
|
|
break;
|
|
|
|
case SOCKS4_RESP_FAILURE:
|
|
pn->error = dupstr("SOCKS server reported failure to connect");
|
|
break;
|
|
|
|
case SOCKS4_RESP_WANT_IDENTD:
|
|
pn->error = dupstr("SOCKS server wanted IDENTD on client");
|
|
break;
|
|
|
|
case SOCKS4_RESP_IDENTD_MISMATCH:
|
|
pn->error = dupstr("Username and IDENTD on client don't agree");
|
|
break;
|
|
|
|
default:
|
|
pn->error = dupprintf("SOCKS server sent unrecognised error "
|
|
"code %d", (int)data[1]);
|
|
break;
|
|
}
|
|
crStopV;
|
|
}
|
|
|
|
crFinishV;
|
|
}
|
|
|
|
const struct ProxyNegotiatorVT socks4_proxy_negotiator_vt = {
|
|
.new = proxy_socks4_new,
|
|
.free = proxy_socks4_free,
|
|
.process_queue = proxy_socks4_process_queue,
|
|
.type = "SOCKS 4",
|
|
};
|