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