From 1bf93289c933e5f59f35d0c65a296750b4596ae5 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 19 Nov 2021 10:33:57 +0000 Subject: [PATCH] Pull out SOCKS protocol constants into a header. This seemed like a worthwhile cleanup to do while I was working on this code anyway. Now all the magic numbers are defined in a header file by macro names indicating their meaning, and used by both the SOCKS client code in the proxy subdirectory and the SOCKS server code in portfwd.c. --- proxy/cproxy.c | 25 +++++++++-------- proxy/proxy.c | 75 +++++++++++++++++++++++++------------------------- proxy/socks.h | 72 ++++++++++++++++++++++++++++++++++++++++++++++++ ssh/portfwd.c | 68 ++++++++++++++++++++++++--------------------- 4 files changed, 160 insertions(+), 80 deletions(-) create mode 100644 proxy/socks.h diff --git a/proxy/cproxy.c b/proxy/cproxy.c index 1ae4cf9a..14c67357 100644 --- a/proxy/cproxy.c +++ b/proxy/cproxy.c @@ -13,6 +13,7 @@ #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, @@ -24,7 +25,7 @@ static void hmacmd5_chap(const unsigned char *challenge, int challen, void proxy_socks5_offerencryptedauth(BinarySink *bs) { - put_byte(bs, 0x03); /* CHAP */ + put_byte(bs, SOCKS5_AUTH_CHAP); } int proxy_socks5_handlechap (ProxySocket *ps) @@ -64,7 +65,7 @@ int proxy_socks5_handlechap (ProxySocket *ps) * with the server, where we negotiate version and * number of attributes */ - if (data[0] != 0x01) { + if (data[0] != SOCKS5_AUTH_CHAP_VERSION) { plug_closing_error(ps->plug, "Proxy error: SOCKS proxy wants " "a different CHAP version"); return 1; @@ -96,7 +97,7 @@ int proxy_socks5_handlechap (ProxySocket *ps) ps->chap_current_datalen); switch (ps->chap_current_attribute) { - case 0x00: + case SOCKS5_AUTH_CHAP_ATTR_STATUS: /* Successful authentication */ if (data[0] == 0x00) ps->state = 2; @@ -106,19 +107,19 @@ int proxy_socks5_handlechap (ProxySocket *ps) return 1; } break; - case 0x03: - outbuf[0] = 0x01; /* Version */ + case SOCKS5_AUTH_CHAP_ATTR_CHALLENGE: + outbuf[0] = SOCKS5_AUTH_CHAP_VERSION; outbuf[1] = 0x01; /* One attribute */ - outbuf[2] = 0x04; /* Response */ + 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 0x11: + case SOCKS5_AUTH_CHAP_ATTR_ALGLIST: /* Chose a protocol */ - if (data[0] != 0x85) { + 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!"); @@ -146,12 +147,12 @@ int proxy_socks5_selectchap(ProxySocket *ps) if (username[0] || password[0]) { char chapbuf[514]; int ulen; - chapbuf[0] = '\x01'; /* Version */ + chapbuf[0] = SOCKS5_AUTH_CHAP_VERSION; chapbuf[1] = '\x02'; /* Number of attributes sent */ - chapbuf[2] = '\x11'; /* First attribute - algorithms list */ + chapbuf[2] = SOCKS5_AUTH_CHAP_ATTR_ALGLIST; chapbuf[3] = '\x01'; /* Only one CHAP algorithm */ - chapbuf[4] = '\x85'; /* ...and it's HMAC-MD5, the core one */ - chapbuf[5] = '\x02'; /* Second attribute - username */ + chapbuf[4] = SOCKS5_AUTH_CHAP_ALG_HMACMD5; + chapbuf[5] = SOCKS5_AUTH_CHAP_ATTR_USERNAME; ulen = strlen(username); if (ulen > 255) ulen = 255; diff --git a/proxy/proxy.c b/proxy/proxy.c index 28e64253..dbf5c035 100644 --- a/proxy/proxy.c +++ b/proxy/proxy.c @@ -12,6 +12,7 @@ #include "putty.h" #include "network.h" #include "proxy.h" +#include "socks.h" #define do_proxy_dns(conf) \ (conf_get_int(conf, CONF_proxy_dns) == FORCE_ON || \ @@ -765,8 +766,8 @@ int proxy_socks4_negotiate (ProxySocket *ps, int change) char hostname[512]; bool write_hostname = false; - put_byte(command, 4); /* SOCKS version 4 */ - put_byte(command, 1); /* CONNECT command */ + put_byte(command, SOCKS4_REQUEST_VERSION); + put_byte(command, SOCKS_CMD_CONNECT); put_uint16(command, ps->remote_port); switch (sk_addrtype(ps->remote_addr)) { @@ -778,7 +779,7 @@ int proxy_socks4_negotiate (ProxySocket *ps, int change) } case ADDRTYPE_NAME: sk_getaddr(ps->remote_addr, hostname, lenof(hostname)); - put_uint32(command, 1); + put_uint32(command, SOCKS4A_NAME_FOLLOWS_BASE); write_hostname = true; break; case ADDRTYPE_IPV6: @@ -834,7 +835,7 @@ int proxy_socks4_negotiate (ProxySocket *ps, int change) if (ps->state == 1) { /* response format: - * version number (1 byte) = 4 + * version number (1 byte) = 0 * reply code (1 byte) * 90 = request granted * 91 = request rejected or failed @@ -853,25 +854,25 @@ int proxy_socks4_negotiate (ProxySocket *ps, int change) /* get the response */ bufchain_fetch(&ps->pending_input_data, data, 8); - if (data[0] != 0) { + if (data[0] != SOCKS4_REPLY_VERSION) { plug_closing_error(ps->plug, "Proxy error: SOCKS proxy " "responded with unexpected " "reply code version"); return 1; } - if (data[1] != 90) { + if (data[1] != SOCKS4_RESP_SUCCESS) { switch (data[1]) { - case 92: + case SOCKS4_RESP_WANT_IDENTD: plug_closing_error(ps->plug, "Proxy error: SOCKS server " "wanted IDENTD on client"); break; - case 93: + case SOCKS4_RESP_IDENTD_MISMATCH: plug_closing_error(ps->plug, "Proxy error: Username and " "IDENTD on client don't agree"); break; - case 91: + case SOCKS4_RESP_FAILURE: default: plug_closing_error(ps->plug, "Proxy error: Error while " "communicating with proxy"); @@ -915,7 +916,7 @@ int proxy_socks5_negotiate (ProxySocket *ps, int change) int method_count_offset, methods_start; command = strbuf_new(); - put_byte(command, 5); /* SOCKS version 5 */ + put_byte(command, SOCKS5_REQUEST_VERSION); username = conf_get_str(ps->conf, CONF_proxy_username); password = conf_get_str(ps->conf, CONF_proxy_password); @@ -923,11 +924,11 @@ int proxy_socks5_negotiate (ProxySocket *ps, int change) put_byte(command, 0); methods_start = command->len; - put_byte(command, 0x00); /* no authentication */ + put_byte(command, SOCKS5_AUTH_NONE); if (username[0] || password[0]) { proxy_socks5_offerencryptedauth(BinarySink_UPCAST(command)); - put_byte(command, 0x02); /* username/password */ + put_byte(command, SOCKS5_AUTH_PASSWORD); } command->u[method_count_offset] = command->len - methods_start; @@ -994,16 +995,16 @@ int proxy_socks5_negotiate (ProxySocket *ps, int change) /* get the response */ bufchain_fetch(&ps->pending_input_data, data, 2); - if (data[0] != 5) { + if (data[0] != SOCKS5_REPLY_VERSION) { plug_closing_error(ps->plug, "Proxy error: SOCKS proxy " "returned unexpected version"); return 1; } - if (data[1] == 0x00) ps->state = 2; /* no authentication needed */ - else if (data[1] == 0x01) ps->state = 4; /* GSSAPI authentication */ - else if (data[1] == 0x02) ps->state = 5; /* username/password authentication */ - else if (data[1] == 0x03) ps->state = 6; /* CHAP authentication */ + if (data[1] == SOCKS5_AUTH_NONE) ps->state = 2; + else if (data[1] == SOCKS5_AUTH_GSSAPI) ps->state = 4; + else if (data[1] == SOCKS5_AUTH_PASSWORD) ps->state = 5; + else if (data[1] == SOCKS5_AUTH_CHAP) ps->state = 6; else { plug_closing_error(ps->plug, "Proxy error: SOCKS proxy did not " "accept our authentication"); @@ -1028,7 +1029,7 @@ int proxy_socks5_negotiate (ProxySocket *ps, int change) /* get the response */ bufchain_fetch(&ps->pending_input_data, data, 2); - if (data[0] != 1) { + if (data[0] != SOCKS5_AUTH_PASSWORD_VERSION) { plug_closing_error(ps->plug, "Proxy error: SOCKS password " "subnegotiation contained wrong version " "number"); @@ -1069,22 +1070,22 @@ int proxy_socks5_negotiate (ProxySocket *ps, int change) */ strbuf *command = strbuf_new(); - put_byte(command, 5); /* SOCKS version 5 */ - put_byte(command, 1); /* CONNECT command */ + put_byte(command, SOCKS5_REQUEST_VERSION); + put_byte(command, SOCKS_CMD_CONNECT); put_byte(command, 0x00); /* reserved byte */ switch (sk_addrtype(ps->remote_addr)) { case ADDRTYPE_IPV4: - put_byte(command, 1); /* IPv4 */ + put_byte(command, SOCKS5_ADDR_IPV4); sk_addrcopy(ps->remote_addr, strbuf_append(command, 4)); break; case ADDRTYPE_IPV6: - put_byte(command, 4); /* IPv6 */ + put_byte(command, SOCKS5_ADDR_IPV6); sk_addrcopy(ps->remote_addr, strbuf_append(command, 16)); break; case ADDRTYPE_NAME: { char hostname[512]; - put_byte(command, 3); /* domain name */ + put_byte(command, SOCKS5_ADDR_HOSTNAME); sk_getaddr(ps->remote_addr, hostname, lenof(hostname)); if (!put_pstring(command, hostname)) { ps->error = "Proxy error: SOCKS 5 cannot " @@ -1138,26 +1139,26 @@ int proxy_socks5_negotiate (ProxySocket *ps, int change) /* get the response */ bufchain_fetch(&ps->pending_input_data, data, 5); - if (data[0] != 5) { + if (data[0] != SOCKS5_REPLY_VERSION) { plug_closing_error(ps->plug, "Proxy error: SOCKS proxy " "returned wrong version number"); return 1; } - if (data[1] != 0) { + if (data[1] != SOCKS5_RESP_SUCCESS) { char buf[256]; strcpy(buf, "Proxy error: "); switch (data[1]) { - case 1: strcat(buf, "General SOCKS server failure"); break; - case 2: strcat(buf, "Connection not allowed by ruleset"); break; - case 3: strcat(buf, "Network unreachable"); break; - case 4: strcat(buf, "Host unreachable"); break; - case 5: strcat(buf, "Connection refused"); break; - case 6: strcat(buf, "TTL expired"); break; - case 7: strcat(buf, "Command not supported"); break; - case 8: strcat(buf, "Address type not supported"); break; + case SOCKS5_RESP_FAILURE: strcat(buf, "General SOCKS server failure"); break; + case SOCKS5_RESP_CONNECTION_NOT_ALLOWED_BY_RULESET: strcat(buf, "Connection not allowed by ruleset"); break; + case SOCKS5_RESP_NETWORK_UNREACHABLE: strcat(buf, "Network unreachable"); break; + case SOCKS5_RESP_HOST_UNREACHABLE: strcat(buf, "Host unreachable"); break; + case SOCKS5_RESP_CONNECTION_REFUSED: strcat(buf, "Connection refused"); break; + case SOCKS5_RESP_TTL_EXPIRED: strcat(buf, "TTL expired"); break; + case SOCKS5_RESP_COMMAND_NOT_SUPPORTED: strcat(buf, "Command not supported"); break; + case SOCKS5_RESP_ADDRTYPE_NOT_SUPPORTED: strcat(buf, "Address type not supported"); break; default: sprintf(buf+strlen(buf), "Unrecognised SOCKS error code %d", data[1]); @@ -1173,9 +1174,9 @@ int proxy_socks5_negotiate (ProxySocket *ps, int change) */ len = 6; /* first 4 bytes, last 2 */ switch (data[3]) { - case 1: len += 4; break; /* IPv4 address */ - case 4: len += 16; break;/* IPv6 address */ - case 3: len += 1+(unsigned char)data[4]; break; /* domain name */ + case SOCKS5_ADDR_IPV4: len += 4; break; /* IPv4 address */ + case SOCKS5_ADDR_IPV6: len += 16; break;/* IPv6 address */ + case SOCKS5_ADDR_HOSTNAME: len += 1+(unsigned char)data[4]; break; /* domain name */ default: plug_closing_error(ps->plug, "Proxy error: SOCKS proxy " "returned unrecognised address format"); @@ -1202,7 +1203,7 @@ int proxy_socks5_negotiate (ProxySocket *ps, int change) const char *password = conf_get_str(ps->conf, CONF_proxy_password); if (username[0] || password[0]) { strbuf *auth = strbuf_new_nm(); - put_byte(auth, 1); /* version number of subnegotiation */ + put_byte(auth, SOCKS5_AUTH_PASSWORD_VERSION); if (!put_pstring(auth, username)) { ps->error = "Proxy error: SOCKS 5 authentication cannot " "support usernames longer than 255 chars"; diff --git a/proxy/socks.h b/proxy/socks.h new file mode 100644 index 00000000..3e86ae23 --- /dev/null +++ b/proxy/socks.h @@ -0,0 +1,72 @@ +/* + * Constants used in the SOCKS protocols. + */ + +/* Command codes common to both versions */ +#define SOCKS_CMD_CONNECT 1 +#define SOCKS_CMD_BIND 2 + +/* SOCKS 4 definitions */ + +#define SOCKS4_REQUEST_VERSION 4 +#define SOCKS4_REPLY_VERSION 0 + +#define SOCKS4_RESP_SUCCESS 90 +#define SOCKS4_RESP_FAILURE 91 +#define SOCKS4_RESP_WANT_IDENTD 92 +#define SOCKS4_RESP_IDENTD_MISMATCH 93 + +/* + * Special nonsense IP address range, used as a signal to indicate + * that an ASCIZ hostname follows the user id field. + * + * Strictly speaking, the use of this extension indicates that we're + * speaking SOCKS 4A rather than vanilla SOCKS 4, although we don't + * bother to draw the distinction. + */ +#define SOCKS4A_NAME_FOLLOWS_BASE 0x00000001 /* inclusive */ +#define SOCKS4A_NAME_FOLLOWS_LIMIT 0x00000100 /* exclusive */ + +/* SOCKS 5 definitions */ + +#define SOCKS5_REQUEST_VERSION 5 +#define SOCKS5_REPLY_VERSION 5 + +/* Extra command codes extending the SOCKS_CMD_* list above */ +#define SOCKS5_CMD_UDP_ASSOCIATE 3 + +#define SOCKS5_AUTH_NONE 0 +#define SOCKS5_AUTH_GSSAPI 1 +#define SOCKS5_AUTH_PASSWORD 2 +#define SOCKS5_AUTH_CHAP 3 +#define SOCKS5_AUTH_REJECTED 0xFF /* used in reply to indicate 'no + * acceptable method offered' */ + +#define SOCKS5_AUTH_PASSWORD_VERSION 1 + +#define SOCKS5_AUTH_CHAP_VERSION 1 + +#define SOCKS5_AUTH_CHAP_ATTR_STATUS 0x00 +#define SOCKS5_AUTH_CHAP_ATTR_INFO 0x01 +#define SOCKS5_AUTH_CHAP_ATTR_USERNAME 0x02 +#define SOCKS5_AUTH_CHAP_ATTR_CHALLENGE 0x03 +#define SOCKS5_AUTH_CHAP_ATTR_RESPONSE 0x04 +#define SOCKS5_AUTH_CHAP_ATTR_CHARSET 0x05 +#define SOCKS5_AUTH_CHAP_ATTR_IDENTIFIER 0x10 +#define SOCKS5_AUTH_CHAP_ATTR_ALGLIST 0x11 + +#define SOCKS5_AUTH_CHAP_ALG_HMACMD5 0x85 + +#define SOCKS5_ADDR_IPV4 1 +#define SOCKS5_ADDR_IPV6 4 +#define SOCKS5_ADDR_HOSTNAME 3 + +#define SOCKS5_RESP_SUCCESS 0 +#define SOCKS5_RESP_FAILURE 1 +#define SOCKS5_RESP_CONNECTION_NOT_ALLOWED_BY_RULESET 2 +#define SOCKS5_RESP_NETWORK_UNREACHABLE 3 +#define SOCKS5_RESP_HOST_UNREACHABLE 4 +#define SOCKS5_RESP_CONNECTION_REFUSED 5 +#define SOCKS5_RESP_TTL_EXPIRED 6 +#define SOCKS5_RESP_COMMAND_NOT_SUPPORTED 7 +#define SOCKS5_RESP_ADDRTYPE_NOT_SUPPORTED 8 diff --git a/ssh/portfwd.c b/ssh/portfwd.c index 2afa9507..11544564 100644 --- a/ssh/portfwd.c +++ b/ssh/portfwd.c @@ -9,6 +9,7 @@ #include "putty.h" #include "ssh.h" #include "channel.h" +#include "proxy/socks.h" /* * Enumeration of values that live in the 'socks_state' field of @@ -216,10 +217,10 @@ static void pfd_receive(Plug *plug, int urgent, const char *data, size_t len) * _must_ have by now) to find out which SOCKS major * version we're speaking. */ switch (pf->socksbuf->u[0]) { - case 4: + case SOCKS4_REQUEST_VERSION: pf->socks_state = SOCKS_4; break; - case 5: + case SOCKS5_REQUEST_VERSION: pf->socks_state = SOCKS_5_INITIAL; break; default: @@ -250,13 +251,15 @@ static void pfd_receive(Plug *plug, int urgent, const char *data, size_t len) if (get_err(src) == BSE_OUT_OF_DATA) return; - if (socks_version == 4 && message_type == 1) { + if (socks_version == SOCKS4_REQUEST_VERSION && + message_type == SOCKS_CMD_CONNECT) { /* CONNECT message */ bool name_based = false; port = get_uint16(src); ipv4 = get_uint32(src); - if (ipv4 > 0x00000000 && ipv4 < 0x00000100) { + if (ipv4 >= SOCKS4A_NAME_FOLLOWS_BASE && + ipv4 < SOCKS4A_NAME_FOLLOWS_LIMIT) { /* * Addresses in this range indicate the SOCKS 4A * extension to specify a hostname, which comes @@ -280,8 +283,8 @@ static void pfd_receive(Plug *plug, int urgent, const char *data, size_t len) } output = strbuf_new(); - put_byte(output, 0); /* reply version */ - put_byte(output, 90); /* SOCKS 4 'request granted' */ + put_byte(output, SOCKS4_REPLY_VERSION); + put_byte(output, SOCKS4_RESP_SUCCESS); put_uint16(output, 0); /* null port field */ put_uint32(output, 0); /* null address field */ sk_write(pf->s, output->u, output->len); @@ -294,8 +297,8 @@ static void pfd_receive(Plug *plug, int urgent, const char *data, size_t len) socks4_reject: output = strbuf_new(); - put_byte(output, 0); /* reply version */ - put_byte(output, 91); /* SOCKS 4 'request rejected' */ + put_byte(output, SOCKS4_REPLY_VERSION); + put_byte(output, SOCKS4_RESP_FAILURE); put_uint16(output, 0); /* null port field */ put_uint32(output, 0); /* null address field */ sk_write(pf->s, output->u, output->len); @@ -308,29 +311,31 @@ static void pfd_receive(Plug *plug, int urgent, const char *data, size_t len) socks_version = get_byte(src); methods = get_pstring(src); - method = 0xFF; /* means 'no usable method found' */ - { - int i; - for (i = 0; i < methods.len; i++) { - if (((const unsigned char *)methods.ptr)[i] == 0 ) { - method = 0; /* no auth */ - break; - } + method = SOCKS5_AUTH_REJECTED; + + /* Search the method list for AUTH_NONE, which is the + * only one this client code can speak */ + for (size_t i = 0; i < methods.len; i++) { + unsigned char this_method = + ((const unsigned char *)methods.ptr)[i]; + if (this_method == SOCKS5_AUTH_NONE) { + method = this_method; + break; } } if (get_err(src) == BSE_OUT_OF_DATA) return; if (get_err(src)) - method = 0xFF; + method = SOCKS5_AUTH_REJECTED; output = strbuf_new(); - put_byte(output, 5); /* SOCKS version */ - put_byte(output, method); /* selected auth method */ + put_byte(output, SOCKS5_REPLY_VERSION); + put_byte(output, method); sk_write(pf->s, output->u, output->len); strbuf_free(output); - if (method == 0xFF) { + if (method == SOCKS5_AUTH_REJECTED) { pfd_close(pf); return; } @@ -345,48 +350,49 @@ static void pfd_receive(Plug *plug, int urgent, const char *data, size_t len) message_type = get_byte(src); reserved_byte = get_byte(src); - if (socks_version == 5 && message_type == 1 && + if (socks_version == SOCKS5_REQUEST_VERSION && + message_type == SOCKS_CMD_CONNECT && reserved_byte == 0) { - reply_code = 0; /* success */ + reply_code = SOCKS5_RESP_SUCCESS; switch (get_byte(src)) { - case 1: /* IPv4 */ + case SOCKS5_ADDR_IPV4: pf->hostname = ipv4_to_string(get_uint32(src)); break; - case 4: /* IPv6 */ + case SOCKS5_ADDR_IPV6: pf->hostname = ipv6_to_string(get_data(src, 16)); break; - case 3: /* unresolved domain name */ + case SOCKS5_ADDR_HOSTNAME: pf->hostname = mkstr(get_pstring(src)); break; default: pf->hostname = NULL; - reply_code = 8; /* address type not supported */ + reply_code = SOCKS5_RESP_ADDRTYPE_NOT_SUPPORTED; break; } pf->port = get_uint16(src); } else { - reply_code = 7; /* command not supported */ + reply_code = SOCKS5_RESP_COMMAND_NOT_SUPPORTED; } if (get_err(src) == BSE_OUT_OF_DATA) return; if (get_err(src)) - reply_code = 1; /* general server failure */ + reply_code = SOCKS5_RESP_FAILURE; output = strbuf_new(); - put_byte(output, 5); /* SOCKS version */ + put_byte(output, SOCKS5_REPLY_VERSION); put_byte(output, reply_code); put_byte(output, 0); /* reserved */ - put_byte(output, 1); /* IPv4 address follows */ + put_byte(output, SOCKS5_ADDR_IPV4); /* IPv4 address follows */ put_uint32(output, 0); /* bound IPv4 address (unused) */ put_uint16(output, 0); /* bound port number (unused) */ sk_write(pf->s, output->u, output->len); strbuf_free(output); - if (reply_code != 0) { + if (reply_code != SOCKS5_RESP_SUCCESS) { pfd_close(pf); return; }