123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499 |
- /*
- * 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_REQUEST_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",
- };
|