Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mRemoteNG/PuTTYNG.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'proxy/socks5.c')
-rw-r--r--proxy/socks5.c498
1 files changed, 498 insertions, 0 deletions
diff --git a/proxy/socks5.c b/proxy/socks5.c
new file mode 100644
index 00000000..87a0bbc8
--- /dev/null
+++ b/proxy/socks5.c
@@ -0,0 +1,498 @@
+/*
+ * 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",
+};