1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 17:38:00 +00:00
putty-source/proxy/socks5.c
Simon Tatham 7582ce3cd6 proxy_socks5_free: fix inadequate smemclr.
Thanks to Coverity for pointing out that I'd only cleared
sizeof(pointer) amount of the struct, not sizeof(the whole thing).
2022-01-29 18:24:31 +00:00

499 lines
17 KiB
C

/*
* SOCKS 5 proxy negotiation.
*/
#include "putty.h"
#include "network.h"
#include "proxy.h"
#include "socks.h"
#include "sshcr.h"
static inline const char *socks5_auth_name(unsigned char m)
{
switch (m) {
case SOCKS5_AUTH_NONE: return "none";
case SOCKS5_AUTH_GSSAPI: return "GSSAPI";
case SOCKS5_AUTH_PASSWORD: return "password";
case SOCKS5_AUTH_CHAP: return "CHAP";
default: return "unknown";
}
}
static inline const char *socks5_response_text(unsigned char m)
{
switch (m) {
case SOCKS5_RESP_SUCCESS: return "success";
case SOCKS5_RESP_FAILURE: return "unspecified failure";
case SOCKS5_RESP_CONNECTION_NOT_ALLOWED_BY_RULESET:
return "connection not allowed by ruleset";
case SOCKS5_RESP_NETWORK_UNREACHABLE: return "network unreachable";
case SOCKS5_RESP_HOST_UNREACHABLE: return "host unreachable";
case SOCKS5_RESP_CONNECTION_REFUSED: return "connection refused";
case SOCKS5_RESP_TTL_EXPIRED: return "TTL expired";
case SOCKS5_RESP_COMMAND_NOT_SUPPORTED: return "command not supported";
case SOCKS5_RESP_ADDRTYPE_NOT_SUPPORTED:
return "address type not supported";
default: return "unknown";
}
}
typedef struct Socks5ProxyNegotiator {
int crLine;
strbuf *auth_methods_offered;
unsigned char auth_method;
unsigned n_chap_attrs;
unsigned chap_attr, chap_attr_len;
unsigned char chap_buf[256];
strbuf *username, *password;
prompts_t *prompts;
int username_prompt_index, password_prompt_index;
int response_addr_length;
ProxyNegotiator pn;
} Socks5ProxyNegotiator;
static ProxyNegotiator *proxy_socks5_new(const ProxyNegotiatorVT *vt)
{
Socks5ProxyNegotiator *s = snew(Socks5ProxyNegotiator);
memset(s, 0, sizeof(*s));
s->pn.vt = vt;
s->auth_methods_offered = strbuf_new();
s->username = strbuf_new();
s->password = strbuf_new_nm();
return &s->pn;
}
static void proxy_socks5_free(ProxyNegotiator *pn)
{
Socks5ProxyNegotiator *s = container_of(pn, Socks5ProxyNegotiator, pn);
strbuf_free(s->auth_methods_offered);
strbuf_free(s->username);
strbuf_free(s->password);
if (s->prompts)
free_prompts(s->prompts);
smemclr(s, sizeof(*s));
sfree(s);
}
static void proxy_socks5_process_queue(ProxyNegotiator *pn)
{
Socks5ProxyNegotiator *s = container_of(pn, Socks5ProxyNegotiator, pn);
crBegin(s->crLine);
/*
* SOCKS 5 initial client packet:
*
* byte version
* byte number of available auth methods
* byte[] that many bytes indicating auth types
*/
put_byte(pn->output, SOCKS5_REQUEST_VERSION);
strbuf_clear(s->auth_methods_offered);
/*
* We have two basic kinds of authentication to offer: none at
* all, and password-based systems (whether the password is sent
* in cleartext or proved via CHAP).
*
* We always offer 'none' as an option. We offer 'password' if we
* either have a username and password already from the Conf, or
* we have a Seat available to ask for them interactively. If
* neither, we don't offer those options in the first place.
*/
put_byte(s->auth_methods_offered, SOCKS5_AUTH_NONE);
put_dataz(s->username, conf_get_str(pn->ps->conf, CONF_proxy_username));
put_dataz(s->password, conf_get_str(pn->ps->conf, CONF_proxy_password));
if (pn->itr || (s->username->len && s->password->len)) {
if (socks5_chap_available)
put_byte(s->auth_methods_offered, SOCKS5_AUTH_CHAP);
put_byte(s->auth_methods_offered, SOCKS5_AUTH_PASSWORD);
}
put_byte(pn->output, s->auth_methods_offered->len);
put_datapl(pn->output, ptrlen_from_strbuf(s->auth_methods_offered));
crReturnV;
/*
* SOCKS 5 initial server packet:
*
* byte version
* byte selected auth method, or SOCKS5_AUTH_REJECTED
*/
{
unsigned char data[2];
crMaybeWaitUntilV(bufchain_try_fetch_consume(pn->input, data, 2));
if (data[0] != SOCKS5_REPLY_VERSION) {
pn->error = dupprintf("SOCKS proxy returned unexpected "
"reply version %d (expected %d)",
(int)data[0], SOCKS5_REPLY_VERSION);
crStopV;
}
if (data[1] == SOCKS5_AUTH_REJECTED) {
pn->error = dupstr("SOCKS server rejected every authentication "
"method we offered");
crStopV;
}
{
bool found = false;
for (size_t i = 0; i < s->auth_methods_offered->len; i++)
if (s->auth_methods_offered->u[i] == data[1]) {
found = true;
break;
}
if (!found) {
pn->error = dupprintf("SOCKS server asked for auth method %d "
"(%s), which we did not offer",
(int)data[1], socks5_auth_name(data[1]));
crStopV;
}
}
s->auth_method = data[1];
}
/*
* The 'none' auth option requires no further negotiation. If that
* was the one we selected, go straight to making the connection.
*/
if (s->auth_method == SOCKS5_AUTH_NONE)
goto authenticated;
/*
* Otherwise, we're going to need a username and password, so this
* is the moment to stop and ask for one if we don't already have
* them.
*/
if (pn->itr && (!s->username->len || !s->password->len)) {
s->prompts = proxy_new_prompts(pn->ps);
s->prompts->to_server = true;
s->prompts->from_server = false;
s->prompts->name = dupstr("SOCKS proxy authentication");
if (!s->username->len) {
s->username_prompt_index = s->prompts->n_prompts;
add_prompt(s->prompts, dupstr("Proxy username: "), true);
} else {
s->username_prompt_index = -1;
}
if (!s->password->len) {
s->password_prompt_index = s->prompts->n_prompts;
add_prompt(s->prompts, dupstr("Proxy password: "), false);
} else {
s->password_prompt_index = -1;
}
while (true) {
SeatPromptResult spr = seat_get_userpass_input(
interactor_announce(pn->itr), s->prompts);
if (spr.kind == SPRK_OK) {
break;
} else if (spr_is_abort(spr)) {
proxy_spr_abort(pn, spr);
crStopV;
}
crReturnV;
}
if (s->username_prompt_index != -1) {
strbuf_clear(s->username);
put_dataz(s->username,
prompt_get_result_ref(
s->prompts->prompts[s->username_prompt_index]));
}
if (s->password_prompt_index != -1) {
strbuf_clear(s->password);
put_dataz(s->password,
prompt_get_result_ref(
s->prompts->prompts[s->password_prompt_index]));
}
free_prompts(s->prompts);
s->prompts = NULL;
}
/*
* Now process the different auth methods that will use that
* username and password. Note we can't do this using the natural
* idiom of a switch statement, because there are crReturns inside
* some cases.
*/
if (s->auth_method == SOCKS5_AUTH_PASSWORD) {
/*
* SOCKS 5 password auth packet:
*
* byte version
* pstring username
* pstring password
*/
put_byte(pn->output, SOCKS5_AUTH_PASSWORD_VERSION);
if (!put_pstring(pn->output, s->username->s)) {
pn->error = dupstr("SOCKS 5 authentication cannot support "
"usernames longer than 255 chars");
crStopV;
}
if (!put_pstring(pn->output, s->password->s)) {
pn->error = dupstr("SOCKS 5 authentication cannot support "
"passwords longer than 255 chars");
crStopV;
}
/*
* SOCKS 5 password reply packet:
*
* byte version
* byte 0 for success, >0 for failure
*/
unsigned char data[2];
crMaybeWaitUntilV(bufchain_try_fetch_consume(pn->input, data, 2));
if (data[0] != SOCKS5_AUTH_PASSWORD_VERSION) {
pn->error = dupprintf(
"SOCKS 5 password reply had version number %d (expected "
"%d)", (int)data[0], SOCKS5_AUTH_PASSWORD_VERSION);
crStopV;
}
if (data[1] != 0) {
pn->error = dupstr("SOCKS 5 server rejected our password");
crStopV;
}
} else if (s->auth_method == SOCKS5_AUTH_CHAP) {
assert(socks5_chap_available);
/*
* All CHAP packets, in both directions, have the same
* overall format:
*
* byte version
* byte number of attributes
*
* and then for each attribute:
*
* byte attribute type
* byte length
* byte[] that many bytes of payload
*
* In the initial outgoing packet we send two attributes:
* the list of supported algorithm names, and the
* username.
*
* (It's possible that we ought to delay sending the
* username until the second packet, in case the proxy
* sent back an attribute indicating which character set
* it would like us to use.)
*/
put_byte(pn->output, SOCKS5_AUTH_CHAP_VERSION);
put_byte(pn->output, 2); /* number of attributes */
put_byte(pn->output, SOCKS5_AUTH_CHAP_ATTR_ALGLIST);
put_byte(pn->output, 1); /* string length */
put_byte(pn->output, SOCKS5_AUTH_CHAP_ALG_HMACMD5);
/* Second attribute: username */
{
put_byte(pn->output, SOCKS5_AUTH_CHAP_ATTR_USERNAME);
if (!put_pstring(pn->output, s->username->s)) {
pn->error = dupstr(
"SOCKS 5 CHAP authentication cannot support "
"usernames longer than 255 chars");
crStopV;
}
}
while (true) {
/*
* Process a CHAP response packet, which has the same
* overall format as the outgoing packet shown above.
*/
unsigned char data[2];
crMaybeWaitUntilV(bufchain_try_fetch_consume(
pn->input, data, 2));
if (data[0] != SOCKS5_AUTH_CHAP_VERSION) {
pn->error = dupprintf(
"SOCKS 5 CHAP reply had version number %d (expected "
"%d)", (int)data[0], SOCKS5_AUTH_CHAP_VERSION);
crStopV;
}
s->n_chap_attrs = data[1];
if (s->n_chap_attrs == 0) {
/*
* If we receive a CHAP packet containing no
* attributes, then we have nothing we didn't have
* before, and can't make further progress.
*/
pn->error = dupprintf(
"SOCKS 5 CHAP reply sent no attributes");
crStopV;
}
while (s->n_chap_attrs-- > 0) {
unsigned char data[2];
crMaybeWaitUntilV(bufchain_try_fetch_consume(
pn->input, data, 2));
s->chap_attr = data[0];
s->chap_attr_len = data[1];
crMaybeWaitUntilV(bufchain_try_fetch_consume(
pn->input, s->chap_buf, s->chap_attr_len));
if (s->chap_attr == SOCKS5_AUTH_CHAP_ATTR_STATUS) {
if (s->chap_attr_len == 1 && s->chap_buf[0] == 0) {
/* Status 0 means success: we are authenticated! */
goto authenticated;
} else {
pn->error = dupstr(
"SOCKS 5 CHAP authentication failed");
crStopV;
}
} else if (s->chap_attr==SOCKS5_AUTH_CHAP_ATTR_CHALLENGE) {
/* The CHAP challenge string. Send the response */
strbuf *response = chap_response(
make_ptrlen(s->chap_buf, s->chap_attr_len),
ptrlen_from_strbuf(s->password));
put_byte(pn->output, SOCKS5_AUTH_CHAP_VERSION);
put_byte(pn->output, 1); /* number of attributes */
put_byte(pn->output, SOCKS5_AUTH_CHAP_ATTR_RESPONSE);
put_byte(pn->output, response->len);
put_datapl(pn->output, ptrlen_from_strbuf(response));
strbuf_free(response);
} else {
/* ignore all other attributes */
}
}
}
} else {
unreachable("bad auth method in SOCKS 5 negotiation");
}
authenticated:
/*
* SOCKS 5 connection command:
*
* byte version
* byte command
* byte reserved (send as zero)
* byte address type
* byte[] address, with variable size (see below)
* uint16 port
*/
put_byte(pn->output, SOCKS5_REPLY_VERSION);
put_byte(pn->output, SOCKS_CMD_CONNECT);
put_byte(pn->output, 0); /* reserved byte */
switch (sk_addrtype(pn->ps->remote_addr)) {
case ADDRTYPE_IPV4: {
/* IPv4: address is 4 raw bytes */
put_byte(pn->output, SOCKS5_ADDR_IPV4);
char buf[4];
sk_addrcopy(pn->ps->remote_addr, buf);
put_data(pn->output, buf, sizeof(buf));
break;
}
case ADDRTYPE_IPV6: {
/* IPv6: address is 16 raw bytes */
put_byte(pn->output, SOCKS5_ADDR_IPV6);
char buf[16];
sk_addrcopy(pn->ps->remote_addr, buf);
put_data(pn->output, buf, sizeof(buf));
break;
}
case ADDRTYPE_NAME: {
/* Hostname: address is a pstring (Pascal-style string,
* unterminated but with a one-byte prefix length) */
put_byte(pn->output, SOCKS5_ADDR_HOSTNAME);
char hostname[512];
sk_getaddr(pn->ps->remote_addr, hostname, lenof(hostname));
if (!put_pstring(pn->output, hostname)) {
pn->error = dupstr(
"SOCKS 5 cannot support host names longer than 255 chars");
crStopV;
}
break;
}
default:
unreachable("Unexpected addrtype in SOCKS 5 proxy");
}
put_uint16(pn->output, pn->ps->remote_port);
crReturnV;
/*
* SOCKS 5 connection response:
*
* byte version
* byte status
* byte reserved
* byte address type
* byte[] address bound to (same formats as in connection request)
* uint16 port
*
* We read the first four bytes and then decide what to do next.
*/
{
unsigned char data[4];
crMaybeWaitUntilV(bufchain_try_fetch_consume(pn->input, data, 4));
if (data[0] != SOCKS5_REPLY_VERSION) {
pn->error = dupprintf("SOCKS proxy returned unexpected "
"reply version %d (expected %d)",
(int)data[0], SOCKS5_REPLY_VERSION);
crStopV;
}
if (data[1] != SOCKS5_RESP_SUCCESS) {
pn->error = dupprintf("SOCKS proxy failed to connect, error %d "
"(%s)", (int)data[1],
socks5_response_text(data[1]));
crStopV;
}
/*
* Process each address type to find out the size of the rest
* of the packet. Note we can't do this using the natural
* idiom of a switch statement, because there are crReturns
* inside some cases.
*/
if (data[3] == SOCKS5_ADDR_IPV4) {
s->response_addr_length = 4;
} else if (data[3] == SOCKS5_ADDR_IPV6) {
s->response_addr_length = 16;
} else if (data[3] == SOCKS5_ADDR_HOSTNAME) {
/* Read the hostname length byte to find out how much to read */
unsigned char len;
crMaybeWaitUntilV(bufchain_try_fetch_consume(pn->input, &len, 1));
s->response_addr_length = len;
break;
} else {
pn->error = dupprintf("SOCKS proxy response included unknown "
"address type %d", (int)data[3]);
crStopV;
}
/* Read and ignore the address and port fields */
crMaybeWaitUntilV(bufchain_try_consume(
pn->input, s->response_addr_length + 2));
}
/* And we're done! */
pn->done = true;
crFinishV;
}
const struct ProxyNegotiatorVT socks5_proxy_negotiator_vt = {
.new = proxy_socks5_new,
.free = proxy_socks5_free,
.process_queue = proxy_socks5_process_queue,
.type = "SOCKS 5",
};