socks: rewwork, cleaning up socks state handling
No binary (build failed)
—
(log yok)
| @@ -225,7 +225,7 @@ typedef CURLcode Curl_bufq_reader(void *reader_ctx, | |
| size_t *pnread); | |
| /** | |
| - | * Read date and append it to the end of the buffer queue until the |
| + | * Read bytes and append them to the end of the buffer queue until the |
| * reader returns blocking or the queue is full. A reader returns | |
| * CURLE_AGAIN to indicate blocking. | |
| * Returns the total amount of buf read (may be 0) in `pnread` on success. | |
| @@ -34,6 +34,7 @@ | |
| #endif | |
| #include "urldata.h" | |
| + | #include "bufq.h" |
| #include "sendf.h" | |
| #include "select.h" | |
| #include "cfilters.h" | |
| @@ -49,46 +50,74 @@ | |
| #include "curl_memory.h" | |
| #include "memdebug.h" | |
| + | #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) |
| + | #define DEBUG_AND_VERBOSE |
| + | #endif |
| + | |
| /* for the (SOCKS) connect state machine */ | |
| - | enum connect_t { |
| - | CONNECT_INIT, |
| - | CONNECT_SOCKS_INIT, /* 1 */ |
| - | CONNECT_SOCKS_SEND, /* 2 waiting to send more first data */ |
| - | CONNECT_SOCKS_READ_INIT, /* 3 set up read */ |
| - | CONNECT_SOCKS_READ, /* 4 read server response */ |
| - | CONNECT_GSSAPI_INIT, /* 5 */ |
| - | CONNECT_AUTH_INIT, /* 6 setup outgoing auth buffer */ |
| - | CONNECT_AUTH_SEND, /* 7 send auth */ |
| - | CONNECT_AUTH_READ, /* 8 read auth response */ |
| - | CONNECT_REQ_INIT, /* 9 init SOCKS "request" */ |
| - | CONNECT_RESOLVING, /* 10 */ |
| - | CONNECT_RESOLVED, /* 11 */ |
| - | CONNECT_RESOLVE_REMOTE, /* 12 */ |
| - | CONNECT_REQ_SEND, /* 13 */ |
| - | CONNECT_REQ_SENDING, /* 14 */ |
| - | CONNECT_REQ_READ, /* 15 */ |
| - | CONNECT_REQ_READ_MORE, /* 16 */ |
| - | CONNECT_DONE /* 17 connected fine to the remote or the SOCKS proxy */ |
| + | enum socks_state_t { |
| + | SOCKS_ST_INIT, |
| + | /* SOCKS Version 4 states */ |
| + | SOCKS4_ST_START, |
| + | SOCKS4_ST_RESOLVING, |
| + | SOCKS4_ST_SEND, |
| + | SOCKS4_ST_RECV, |
| + | /* SOCKS Version 5 states */ |
| + | SOCKS5_ST_START, |
| + | SOCKS5_ST_REQ0_SEND, |
| + | SOCKS5_ST_RESP0_RECV, /* set up read */ |
| + | SOCKS5_ST_GSSAPI_INIT, |
| + | SOCKS5_ST_AUTH_INIT, /* setup outgoing auth buffer */ |
| + | SOCKS5_ST_AUTH_SEND, /* send auth */ |
| + | SOCKS5_ST_AUTH_RECV, /* read auth response */ |
| + | SOCKS5_ST_REQ1_INIT, /* init SOCKS "request" */ |
| + | SOCKS5_ST_RESOLVING, |
| + | SOCKS5_ST_REQ1_SEND, |
| + | SOCKS5_ST_RESP1_RECV, |
| + | /* Terminal states, all SOCKS versions */ |
| + | SOCKS_ST_SUCCESS, |
| + | SOCKS_ST_FAILED |
| }; | |
| - | #define CURL_SOCKS_BUF_SIZE 600 |
| - | |
| - | /* make sure we configure it not too low */ |
| - | #if CURL_SOCKS_BUF_SIZE < 600 |
| - | #error CURL_SOCKS_BUF_SIZE must be at least 600 |
| + | #ifdef DEBUG_AND_VERBOSE |
| + | static const char * const cf_socks_statename[] = { |
| + | "SOCKS_INIT", |
| + | "SOCKS4_START", |
| + | "SOCKS4_RESOLVING", |
| + | "SOCKS4_SEND", |
| + | "SOCKS4_RECV", |
| + | "SOCKS5_START", |
| + | "SOCKS5_REQ0_SEND", |
| + | "SOCKS5_RESP0_RECV", |
| + | "SOCKS5_GSSAPI_INIT", |
| + | "SOCKS5_AUTH_INIT", |
| + | "SOCKS5_AUTH_SEND", |
| + | "SOCKS5_AUTH_RECV", |
| + | "SOCKS5_REQ1_INIT", |
| + | "SOCKS5_RESOLVING", |
| + | "SOCKS5_REQ1_SEND", |
| + | "SOCKS5_RESP1_RECV", |
| + | "SOCKS_SUCCESS", |
| + | "SOCKS_FAILED" |
| + | }; |
| #endif | |
| + | #define SOCKS_CHUNK_SIZE 1024 |
| + | #define SOCKS_CHUNKS 1 |
| - | struct socks_state { |
| - | enum connect_t state; |
| - | size_t outstanding; /* send this many bytes more */ |
| - | unsigned char buffer[CURL_SOCKS_BUF_SIZE]; |
| - | unsigned char *outp; /* send from this pointer */ |
| + | struct socks_state { |
| + | enum socks_state_t state; |
| + | struct bufq iobuf; |
| const char *hostname; | |
| int remote_port; | |
| const char *proxy_user; | |
| const char *proxy_password; | |
| + | CURLproxycode presult; |
| + | unsigned char version; |
| + | BIT(resolve_local); |
| + | BIT(start_resolving); |
| + | BIT(socks4a); |
| }; | |
| #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) | |
| @@ -149,37 +178,13 @@ CURLcode Curl_blockread_all(struct Curl_cfilter *cf, | |
| static void socksstate(struct socks_state *sx, | |
| struct Curl_cfilter *cf, | |
| struct Curl_easy *data, | |
| - | enum connect_t state |
| + | enum socks_state_t state |
| #ifdef DEBUG_AND_VERBOSE | |
| , int lineno | |
| #endif | |
| ) | |
| { | |
| - | enum connect_t oldstate = sx->state; |
| - | #ifdef DEBUG_AND_VERBOSE |
| - | /* synced with the state list in urldata.h */ |
| - | static const char * const socks_statename[] = { |
| - | "INIT", |
| - | "SOCKS_INIT", |
| - | "SOCKS_SEND", |
| - | "SOCKS_READ_INIT", |
| - | "SOCKS_READ", |
| - | "GSSAPI_INIT", |
| - | "AUTH_INIT", |
| - | "AUTH_SEND", |
| - | "AUTH_READ", |
| - | "REQ_INIT", |
| - | "RESOLVING", |
| - | "RESOLVED", |
| - | "RESOLVE_REMOTE", |
| - | "REQ_SEND", |
| - | "REQ_SENDING", |
| - | "REQ_READ", |
| - | "REQ_READ_MORE", |
| - | "DONE" |
| - | }; |
| - | #endif |
| - | |
| + | enum socks_state_t oldstate = sx->state; |
| (void)cf; | |
| (void)data; | |
| if(oldstate == state) | |
| @@ -190,274 +195,209 @@ static void socksstate(struct socks_state *sx, | |
| #ifdef DEBUG_AND_VERBOSE | |
| CURL_TRC_CF(data, cf, "[%s] -> [%s] (line %d)", | |
| - | socks_statename[oldstate], socks_statename[sx->state], lineno); |
| + | cf_socks_statename[oldstate], |
| + | cf_socks_statename[sx->state], lineno); |
| #endif | |
| } | |
| - | static CURLproxycode socks_state_send(struct Curl_cfilter *cf, |
| - | struct socks_state *sx, |
| - | struct Curl_easy *data, |
| - | CURLproxycode failcode, |
| - | const char *description) |
| + | static CURLproxycode socks_failed(struct socks_state *sx, |
| + | struct Curl_cfilter *cf, |
| + | struct Curl_easy *data, |
| + | CURLproxycode presult) |
| + | { |
| + | sxstate(sx, cf, data, SOCKS_ST_FAILED); |
| + | sx->presult = presult; |
| + | return presult; |
| + | } |
| + | |
| + | static CURLproxycode socks_flush(struct socks_state *sx, |
| + | struct Curl_cfilter *cf, |
| + | struct Curl_easy *data, |
| + | bool *done) |
| { | |
| - | size_t nwritten; |
| CURLcode result; | |
| + | size_t nwritten; |
| - | result = Curl_conn_cf_send(cf->next, data, (char *)sx->outp, |
| - | sx->outstanding, FALSE, &nwritten); |
| - | if(result) { |
| - | if(CURLE_AGAIN == result) |
| + | *done = FALSE; |
| + | while(!Curl_bufq_is_empty(&sx->iobuf)) { |
| + | result = Curl_cf_send_bufq(cf->next, data, &sx->iobuf, NULL, 0, |
| + | &nwritten); |
| + | if(result == CURLE_AGAIN) |
| return CURLPX_OK; | |
| - | |
| - | failf(data, "Failed to send %s: %s", description, |
| - | curl_easy_strerror(result)); |
| - | return failcode; |
| - | } |
| - | else if(!nwritten) { |
| - | /* connection closed */ |
| - | failf(data, "connection to proxy closed"); |
| - | return CURLPX_CLOSED; |
| + | else if(result) { |
| + | failf(data, "Failed to send SOCKS request: %s", |
| + | curl_easy_strerror(result)); |
| + | return socks_failed(sx, cf, data, CURLPX_SEND_CONNECT); |
| + | } |
| } | |
| - | |
| - | DEBUGASSERT(sx->outstanding >= nwritten); |
| - | /* not done, remain in state */ |
| - | sx->outstanding -= nwritten; |
| - | sx->outp += nwritten; |
| + | *done = TRUE; |
| return CURLPX_OK; | |
| } | |
| - | static CURLproxycode socks_state_recv(struct Curl_cfilter *cf, |
| - | struct socks_state *sx, |
| - | struct Curl_easy *data, |
| - | CURLproxycode failcode, |
| - | const char *description) |
| + | static CURLproxycode socks_recv(struct socks_state *sx, |
| + | struct Curl_cfilter *cf, |
| + | struct Curl_easy *data, |
| + | size_t min_bytes, |
| + | bool *done) |
| { | |
| - | size_t nread; |
| CURLcode result; | |
| + | size_t nread; |
| - | result = Curl_conn_cf_recv(cf->next, data, (char *)sx->outp, |
| - | sx->outstanding, &nread); |
| - | if(result) { |
| - | if(CURLE_AGAIN == result) |
| + | *done = FALSE; |
| + | while(Curl_bufq_len(&sx->iobuf) < min_bytes) { |
| + | result = Curl_cf_recv_bufq(cf->next, data, &sx->iobuf, |
| + | min_bytes - Curl_bufq_len(&sx->iobuf), |
| + | &nread); |
| + | if(result == CURLE_AGAIN) |
| return CURLPX_OK; | |
| - | |
| - | failf(data, "SOCKS: Failed receiving %s: %s", description, |
| - | curl_easy_strerror(result)); |
| - | return failcode; |
| - | } |
| - | else if(!nread) { |
| - | /* connection closed */ |
| - | failf(data, "connection to proxy closed"); |
| - | return CURLPX_CLOSED; |
| + | else if(result) { |
| + | failf(data, "Failed to receive SOCKS response: %s", |
| + | curl_easy_strerror(result)); |
| + | return CURLPX_RECV_CONNECT; |
| + | } |
| + | else if(!nread) /* EOF */ |
| + | break; |
| } | |
| - | /* remain in reading state */ |
| - | DEBUGASSERT(sx->outstanding >= nread); |
| - | sx->outstanding -= nread; |
| - | sx->outp += nread; |
| + | *done = TRUE; |
| return CURLPX_OK; | |
| } | |
| - | /* |
| - | * This function logs in to a SOCKS4 proxy and sends the specifics to the final |
| - | * destination server. |
| - | * |
| - | * Reference : |
| - | * https://www.openssh.com/txt/socks4.protocol |
| - | * |
| - | * Note : |
| - | * Set protocol4a=true for "SOCKS 4A (Simple Extension to SOCKS 4 Protocol)" |
| - | * Nonsupport "Identification Protocol (RFC1413)" |
| - | */ |
| - | static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, |
| - | struct socks_state *sx, |
| - | struct Curl_easy *data) |
| + | static CURLproxycode socks4_req_add_hd(struct socks_state *sx, |
| + | struct Curl_easy *data) |
| { | |
| - | struct connectdata *conn = cf->conn; |
| - | const bool protocol4a = |
| - | (conn->socks_proxy.proxytype == CURLPROXY_SOCKS4A); |
| - | unsigned char *socksreq = sx->buffer; |
| + | unsigned char buf[4]; |
| + | size_t nwritten; |
| CURLcode result; | |
| - | CURLproxycode presult; |
| - | struct Curl_dns_entry *dns = NULL; |
| - | |
| - | switch(sx->state) { |
| - | case CONNECT_SOCKS_INIT: |
| - | /* SOCKS4 can only do IPv4, insist! */ |
| - | conn->ip_version = CURL_IPRESOLVE_V4; |
| - | CURL_TRC_CF(data, cf, "SOCKS4%s communication to%s %s:%d", |
| - | protocol4a ? "a" : "", |
| - | conn->bits.httpproxy ? " HTTP proxy" : "", |
| - | sx->hostname, sx->remote_port); |
| - | /* |
| - | * Compose socks4 request |
| - | * |
| - | * Request format |
| - | * |
| - | * +----+----+----+----+----+----+----+----+----+----+....+----+ |
| - | * | VN | CD | DSTPORT | DSTIP | USERID |NULL| |
| - | * +----+----+----+----+----+----+----+----+----+----+....+----+ |
| - | * # of bytes: 1 1 2 4 variable 1 |
| - | */ |
| + | (void)data; |
| + | buf[0] = 4; /* version (SOCKS4) */ |
| + | buf[1] = 1; /* connect */ |
| + | buf[2] = (unsigned char)((sx->remote_port >> 8) & 0xffu); /* MSB */ |
| + | buf[3] = (unsigned char)(sx->remote_port & 0xffu); /* LSB */ |
| + | |
| + | result = Curl_bufq_write(&sx->iobuf, buf, 4, &nwritten); |
| + | if(result || (nwritten != 4)) |
| + | return CURLPX_SEND_REQUEST; |
| + | return CURLPX_OK; |
| + | } |
| - | socksreq[0] = 4; /* version (SOCKS4) */ |
| - | socksreq[1] = 1; /* connect */ |
| - | socksreq[2] = (unsigned char)((sx->remote_port >> 8) & 0xff); /* MSB */ |
| - | socksreq[3] = (unsigned char)(sx->remote_port & 0xff); /* LSB */ |
| + | static CURLproxycode socks4_req_add_user(struct socks_state *sx, |
| + | struct Curl_easy *data) |
| + | { |
| + | CURLcode result; |
| + | size_t nwritten; |
| - | /* DNS resolve only for SOCKS4, not SOCKS4a */ |
| - | if(!protocol4a) { |
| - | result = Curl_resolv(data, sx->hostname, sx->remote_port, |
| - | cf->conn->ip_version, TRUE, &dns); |
| - | |
| - | if(result == CURLE_AGAIN) { |
| - | sxstate(sx, cf, data, CONNECT_RESOLVING); |
| - | CURL_TRC_CF(data, cf, "SOCKS4 non-blocking resolve of %s", |
| - | sx->hostname); |
| - | return CURLPX_OK; |
| - | } |
| - | else if(result) |
| - | return CURLPX_RESOLVE_HOST; |
| - | sxstate(sx, cf, data, CONNECT_RESOLVED); |
| - | goto CONNECT_RESOLVED; |
| + | if(sx->proxy_user) { |
| + | size_t plen = strlen(sx->proxy_user); |
| + | if(plen > 255) { |
| + | /* there is no real size limit to this field in the protocol, but |
| + | SOCKS5 limits the proxy user field to 255 bytes and it seems likely |
| + | that a longer field is either a mistake or malicious input */ |
| + | failf(data, "Too long SOCKS proxy username"); |
| + | return CURLPX_LONG_USER; |
| } | |
| + | /* add proxy name WITH trailing zero */ |
| + | result = Curl_bufq_cwrite(&sx->iobuf, sx->proxy_user, plen + 1, |
| + | &nwritten); |
| + | if(result || (nwritten != (plen + 1))) |
| + | return CURLPX_SEND_REQUEST; |
| + | } |
| + | else { |
| + | /* empty user name */ |
| + | unsigned char b = 0; |
| + | result = Curl_bufq_write(&sx->iobuf, &b, 1, &nwritten); |
| + | if(result || (nwritten != 1)) |
| + | return CURLPX_SEND_REQUEST; |
| + | } |
| + | return CURLPX_OK; |
| + | } |
| + | |
| + | static CURLproxycode socks4_resolving(struct socks_state *sx, |
| + | struct Curl_cfilter *cf, |
| + | struct Curl_easy *data, |
| + | bool *done) |
| + | { |
| + | struct Curl_dns_entry *dns = NULL; |
| + | struct Curl_addrinfo *hp = NULL; |
| + | CURLcode result; |
| + | size_t nwritten; |
| - | /* socks4a does not resolve anything locally */ |
| - | sxstate(sx, cf, data, CONNECT_REQ_INIT); |
| - | goto CONNECT_REQ_INIT; |
| + | *done = FALSE; |
| + | if(sx->start_resolving) { |
| + | /* need to resolve hostname to add destination address */ |
| + | sx->start_resolving = FALSE; |
| - | case CONNECT_RESOLVING: |
| + | result = Curl_resolv(data, sx->hostname, sx->remote_port, |
| + | cf->conn->ip_version, TRUE, &dns); |
| + | if(result == CURLE_AGAIN) { |
| + | CURL_TRC_CF(data, cf, "SOCKS4 non-blocking resolve of %s", |
| + | sx->hostname); |
| + | return CURLPX_OK; |
| + | } |
| + | else if(result) |
| + | return CURLPX_RESOLVE_HOST; |
| + | } |
| + | else { |
| /* check if we have the name resolved by now */ | |
| result = Curl_resolv_check(data, &dns); | |
| - | if(!dns) { |
| - | if(result) |
| - | return CURLPX_RESOLVE_HOST; |
| + | if(!result && !dns) |
| return CURLPX_OK; | |
| - | } |
| - | FALLTHROUGH(); |
| - | case CONNECT_RESOLVED: |
| - | CONNECT_RESOLVED: |
| - | { |
| - | struct Curl_addrinfo *hp = NULL; |
| - | /* |
| - | * We cannot use 'hostent' as a struct that Curl_resolv() returns. It |
| - | * returns a Curl_addrinfo pointer that may not always look the same. |
| - | */ |
| - | if(dns) { |
| - | hp = dns->addr; |
| - | |
| - | /* scan for the first IPv4 address */ |
| - | while(hp && (hp->ai_family != AF_INET)) |
| - | hp = hp->ai_next; |
| - | |
| - | if(hp) { |
| - | struct sockaddr_in *saddr_in; |
| - | char buf[64]; |
| - | Curl_printable_address(hp, buf, sizeof(buf)); |
| - | |
| - | saddr_in = (struct sockaddr_in *)(void *)hp->ai_addr; |
| - | socksreq[4] = ((unsigned char *)&saddr_in->sin_addr.s_addr)[0]; |
| - | socksreq[5] = ((unsigned char *)&saddr_in->sin_addr.s_addr)[1]; |
| - | socksreq[6] = ((unsigned char *)&saddr_in->sin_addr.s_addr)[2]; |
| - | socksreq[7] = ((unsigned char *)&saddr_in->sin_addr.s_addr)[3]; |
| - | |
| - | CURL_TRC_CF(data, cf, "SOCKS4 connect to IPv4 %s (locally resolved)", |
| - | buf); |
| - | Curl_resolv_unlink(data, &dns); /* not used anymore from now on */ |
| - | } |
| - | else |
| - | failf(data, "SOCKS4 connection to %s not supported", sx->hostname); |
| - | } |
| - | else |
| - | failf(data, "Failed to resolve \"%s\" for SOCKS4 connect.", |
| - | sx->hostname); |
| + | } |
| - | if(!hp) |
| - | return CURLPX_RESOLVE_HOST; |
| + | if(result || !dns) { |
| + | failf(data, "Failed to resolve \"%s\" for SOCKS4 connect.", |
| + | sx->hostname); |
| + | return CURLPX_RESOLVE_HOST; |
| } | |
| - | FALLTHROUGH(); |
| - | case CONNECT_REQ_INIT: |
| - | CONNECT_REQ_INIT: |
| - | /* |
| - | * This is currently not supporting "Identification Protocol (RFC1413)". |
| - | */ |
| - | socksreq[8] = 0; /* ensure empty userid is null-terminated */ |
| - | if(sx->proxy_user) { |
| - | size_t plen = strlen(sx->proxy_user); |
| - | if(plen > 255) { |
| - | /* there is no real size limit to this field in the protocol, but |
| - | SOCKS5 limits the proxy user field to 255 bytes and it seems likely |
| - | that a longer field is either a mistake or malicious input */ |
| - | failf(data, "Too long SOCKS proxy username"); |
| - | return CURLPX_LONG_USER; |
| - | } |
| - | /* copy the proxy name WITH trailing zero */ |
| - | memcpy(socksreq + 8, sx->proxy_user, plen + 1); |
| - | } |
| - | /* |
| - | * Make connection |
| - | */ |
| - | { |
| - | size_t packetsize = 9 + |
| - | strlen((char *)socksreq + 8); /* size including NUL */ |
| - | |
| - | /* If SOCKS4a, set special invalid IP address 0.0.0.x */ |
| - | if(protocol4a) { |
| - | size_t hostnamelen = 0; |
| - | socksreq[4] = 0; |
| - | socksreq[5] = 0; |
| - | socksreq[6] = 0; |
| - | socksreq[7] = 1; |
| - | /* append hostname */ |
| - | hostnamelen = strlen(sx->hostname) + 1; /* length including NUL */ |
| - | if((hostnamelen <= 255) && |
| - | (packetsize + hostnamelen < sizeof(sx->buffer))) |
| - | strcpy((char *)socksreq + packetsize, sx->hostname); |
| - | else { |
| - | failf(data, "SOCKS4: too long hostname"); |
| - | return CURLPX_LONG_HOSTNAME; |
| - | } |
| - | packetsize += hostnamelen; |
| - | } |
| - | sx->outp = socksreq; |
| - | DEBUGASSERT(packetsize <= sizeof(sx->buffer)); |
| - | sx->outstanding = packetsize; |
| - | sxstate(sx, cf, data, CONNECT_REQ_SENDING); |
| - | } |
| - | FALLTHROUGH(); |
| - | case CONNECT_REQ_SENDING: |
| - | /* Send request */ |
| - | presult = socks_state_send(cf, sx, data, CURLPX_SEND_CONNECT, |
| - | "SOCKS4 connect request"); |
| - | if(CURLPX_OK != presult) |
| - | return presult; |
| - | else if(sx->outstanding) { |
| - | /* remain in sending state */ |
| - | return CURLPX_OK; |
| - | } |
| - | /* done sending! */ |
| - | sx->outstanding = 8; /* receive data size */ |
| - | sx->outp = socksreq; |
| - | sxstate(sx, cf, data, CONNECT_SOCKS_READ); |
| + | /* |
| + | * We cannot use 'hostent' as a struct that Curl_resolv() returns. It |
| + | * returns a Curl_addrinfo pointer that may not always look the same. |
| + | */ |
| + | /* scan for the first IPv4 address */ |
| + | hp = dns->addr; |
| + | while(hp && (hp->ai_family != AF_INET)) |
| + | hp = hp->ai_next; |
| - | FALLTHROUGH(); |
| - | case CONNECT_SOCKS_READ: |
| - | /* Receive response */ |
| - | presult = socks_state_recv(cf, sx, data, CURLPX_RECV_CONNECT, |
| - | "connect request ack"); |
| - | if(CURLPX_OK != presult) |
| - | return presult; |
| - | else if(sx->outstanding) { |
| - | /* remain in reading state */ |
| - | return CURLPX_OK; |
| - | } |
| - | sxstate(sx, cf, data, CONNECT_DONE); |
| - | break; |
| - | default: /* lots of unused states in SOCKS4 */ |
| - | break; |
| + | if(hp) { |
| + | struct sockaddr_in *saddr_in; |
| + | char ipbuf[64]; |
| + | |
| + | Curl_printable_address(hp, ipbuf, sizeof(ipbuf)); |
| + | CURL_TRC_CF(data, cf, "SOCKS4 connect to IPv4 %s (locally resolved)", |
| + | ipbuf); |
| + | |
| + | saddr_in = (struct sockaddr_in *)(void *)hp->ai_addr; |
| + | result = Curl_bufq_write(&sx->iobuf, |
| + | (unsigned char *)&saddr_in->sin_addr.s_addr, 4, |
| + | &nwritten); |
| + | |
| + | Curl_resolv_unlink(data, &dns); /* not used anymore from now on */ |
| + | if(result || (nwritten != 4)) |
| + | return CURLPX_SEND_REQUEST; |
| + | } |
| + | else { |
| + | failf(data, "SOCKS4 connection to %s not supported", sx->hostname); |
| + | return CURLPX_RESOLVE_HOST; |
| + | } |
| + | |
| + | *done = TRUE; |
| + | return CURLPX_OK; |
| + | } |
| + | |
| + | static CURLproxycode socks4_check_resp(struct socks_state *sx, |
| + | struct Curl_cfilter *cf, |
| + | struct Curl_easy *data) |
| + | { |
| + | const unsigned char *resp; |
| + | size_t rlen; |
| + | |
| + | if(!Curl_bufq_peek(&sx->iobuf, &resp, &rlen) || rlen < 8) { |
| + | failf(data, "SOCKS4 reply is incomplete."); |
| + | return CURLPX_RECV_CONNECT; |
| } | |
| + | DEBUGASSERT(rlen == 8); |
| /* | |
| * Response format | |
| * | |
| @@ -478,72 +418,194 @@ CONNECT_REQ_INIT: | |
| */ | |
| /* wrong version ? */ | |
| - | if(socksreq[0]) { |
| - | failf(data, |
| - | "SOCKS4 reply has wrong version, version should be 0."); |
| + | if(resp[0]) { |
| + | failf(data, "SOCKS4 reply has wrong version, version should be 0."); |
| return CURLPX_BAD_VERSION; | |
| } | |
| /* Result */ | |
| - | switch(socksreq[1]) { |
| + | switch(resp[1]) { |
| case 90: | |
| - | CURL_TRC_CF(data, cf, "SOCKS4%s request granted.", protocol4a ? "a" : ""); |
| - | break; |
| + | CURL_TRC_CF(data, cf, "SOCKS4%s request granted.", sx->socks4a ? "a" : ""); |
| + | Curl_bufq_reset(&sx->iobuf); |
| + | return CURLPX_OK; |
| case 91: | |
| failf(data, | |
| - | "[SOCKS] cannot complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" |
| + | "[SOCKS] cannot complete SOCKS4 connection to %u.%u.%u.%u:%u. (%u)" |
| ", request rejected or failed.", | |
| - | socksreq[4], socksreq[5], socksreq[6], socksreq[7], |
| - | (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]), |
| - | (unsigned char)socksreq[1]); |
| + | resp[4], resp[5], resp[6], resp[7], |
| + | ((resp[2] << 8) | resp[3]), resp[1]); |
| return CURLPX_REQUEST_FAILED; | |
| case 92: | |
| failf(data, | |
| - | "[SOCKS] cannot complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" |
| + | "[SOCKS] cannot complete SOCKS4 connection to %u.%u.%u.%u:%u. (%u)" |
| ", request rejected because SOCKS server cannot connect to " | |
| "identd on the client.", | |
| - | socksreq[4], socksreq[5], socksreq[6], socksreq[7], |
| - | (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]), |
| - | (unsigned char)socksreq[1]); |
| + | resp[4], resp[5], resp[6], resp[7], |
| + | ((resp[2] << 8) | resp[3]), resp[1]); |
| return CURLPX_IDENTD; | |
| case 93: | |
| failf(data, | |
| - | "[SOCKS] cannot complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" |
| + | "[SOCKS] cannot complete SOCKS4 connection to %u.%u.%u.%u:%u. (%u)" |
| ", request rejected because the client program and identd " | |
| "report different user-ids.", | |
| - | socksreq[4], socksreq[5], socksreq[6], socksreq[7], |
| - | (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]), |
| - | (unsigned char)socksreq[1]); |
| + | resp[4], resp[5], resp[6], resp[7], |
| + | ((resp[2] << 8) | resp[3]), resp[1]); |
| return CURLPX_IDENTD_DIFFER; | |
| default: | |
| failf(data, | |
| - | "[SOCKS] cannot complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" |
| + | "[SOCKS] cannot complete SOCKS4 connection to %u.%u.%u.%u:%u. (%u)" |
| ", Unknown.", | |
| - | socksreq[4], socksreq[5], socksreq[6], socksreq[7], |
| - | (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]), |
| - | (unsigned char)socksreq[1]); |
| + | resp[4], resp[5], resp[6], resp[7], |
| + | ((resp[2] << 8) | resp[3]), resp[1]); |
| return CURLPX_UNKNOWN_FAIL; | |
| } | |
| - | |
| - | return CURLPX_OK; /* Proxy was successful! */ |
| } | |
| - | static CURLproxycode socks5_init(struct Curl_cfilter *cf, |
| - | struct socks_state *sx, |
| - | struct Curl_easy *data, |
| - | const bool socks5_resolve_local, |
| - | const size_t hostname_len) |
| + | /* |
| + | * This function logs in to a SOCKS4 proxy and sends the specifics to the final |
| + | * destination server. |
| + | * |
| + | * Reference : |
| + | * https://www.openssh.com/txt/socks4.protocol |
| + | * |
| + | * Note : |
| + | * Set protocol4a=true for "SOCKS 4A (Simple Extension to SOCKS 4 Protocol)" |
| + | * Nonsupport "Identification Protocol (RFC1413)" |
| + | */ |
| + | static CURLproxycode socks4_connect(struct Curl_cfilter *cf, |
| + | struct socks_state *sx, |
| + | struct Curl_easy *data) |
| { | |
| - | struct connectdata *conn = cf->conn; |
| - | const unsigned char auth = data->set.socks5auth; |
| - | unsigned char *socksreq = sx->buffer; |
| + | size_t nwritten; |
| + | CURLproxycode presult; |
| + | CURLcode result; |
| + | bool done; |
| + | |
| + | process_state: |
| + | switch(sx->state) { |
| + | case SOCKS_ST_INIT: |
| + | sx->version = 4; |
| + | sxstate(sx, cf, data, SOCKS4_ST_START); |
| + | FALLTHROUGH(); |
| - | if(conn->bits.httpproxy) |
| - | CURL_TRC_CF(data, cf, "SOCKS5: connecting to HTTP proxy %s port %d", |
| + | case SOCKS4_ST_START: |
| + | Curl_bufq_reset(&sx->iobuf); |
| + | sx->start_resolving = FALSE; |
| + | sx->socks4a = (cf->conn->socks_proxy.proxytype == CURLPROXY_SOCKS4A); |
| + | sx->resolve_local = !sx->socks4a; |
| + | sx->presult = CURLPX_OK; |
| + | |
| + | /* SOCKS4 can only do IPv4, insist! */ |
| + | cf->conn->ip_version = CURL_IPRESOLVE_V4; |
| + | CURL_TRC_CF(data, cf, "SOCKS4%s communication to%s %s:%d", |
| + | sx->socks4a ? "a" : "", |
| + | cf->conn->bits.httpproxy ? " HTTP proxy" : "", |
| sx->hostname, sx->remote_port); | |
| + | /* |
| + | * Compose socks4 request |
| + | * |
| + | * Request format |
| + | * |
| + | * +----+----+----+----+----+----+----+----+----+----+....+----+ |
| + | * | VN | CD | DSTPORT | DSTIP | USERID |NULL| |
| + | * +----+----+----+----+----+----+----+----+----+----+....+----+ |
| + | * # of bytes: 1 1 2 4 variable 1 |
| + | */ |
| + | presult = socks4_req_add_hd(sx, data); |
| + | if(presult) |
| + | return socks_failed(sx, cf, data, presult); |
| + | |
| + | /* DNS resolve only for SOCKS4, not SOCKS4a */ |
| + | if(!sx->resolve_local) { |
| + | /* socks4a, not resolving locally, sends the hostname. |
| + | * add an invalid address + user + hostname */ |
| + | unsigned char buf[4] = { 0, 0, 0, 1 }; |
| + | size_t hlen = strlen(sx->hostname) + 1; /* including NUL */ |
| + | |
| + | if(hlen > 255) { |
| + | failf(data, "SOCKS4: too long hostname"); |
| + | return socks_failed(sx, cf, data, CURLPX_LONG_HOSTNAME); |
| + | } |
| + | result = Curl_bufq_write(&sx->iobuf, buf, 4, &nwritten); |
| + | if(result || (nwritten != 4)) |
| + | return socks_failed(sx, cf, data, CURLPX_SEND_REQUEST); |
| + | presult = socks4_req_add_user(sx, data); |
| + | if(presult) |
| + | return socks_failed(sx, cf, data, presult); |
| + | result = Curl_bufq_cwrite(&sx->iobuf, sx->hostname, hlen, &nwritten); |
| + | if(result || (nwritten != hlen)) |
| + | return socks_failed(sx, cf, data, CURLPX_SEND_REQUEST); |
| + | /* request complete */ |
| + | sxstate(sx, cf, data, SOCKS4_ST_SEND); |
| + | goto process_state; |
| + | } |
| + | sx->start_resolving = TRUE; |
| + | sxstate(sx, cf, data, SOCKS4_ST_RESOLVING); |
| + | FALLTHROUGH(); |
| + | |
| + | case SOCKS4_ST_RESOLVING: |
| + | presult = socks4_resolving(sx, cf, data, &done); |
| + | if(presult) |
| + | return socks_failed(sx, cf, data, presult); |
| + | if(!done) |
| + | return CURLPX_OK; |
| + | /* append user */ |
| + | presult = socks4_req_add_user(sx, data); |
| + | if(presult) |
| + | return socks_failed(sx, cf, data, presult); |
| + | sxstate(sx, cf, data, SOCKS4_ST_SEND); |
| + | FALLTHROUGH(); |
| + | |
| + | case SOCKS4_ST_SEND: |
| + | presult = socks_flush(sx, cf, data, &done); |
| + | if(presult) |
| + | return socks_failed(sx, cf, data, presult); |
| + | else if(!done) |
| + | return CURLPX_OK; |
| + | sxstate(sx, cf, data, SOCKS4_ST_RECV); |
| + | FALLTHROUGH(); |
| + | |
| + | case SOCKS4_ST_RECV: |
| + | /* Receive 8 byte response */ |
| + | presult = socks_recv(sx, cf, data, 8, &done); |
| + | if(presult) |
| + | return socks_failed(sx, cf, data, presult); |
| + | else if(!done) |
| + | return CURLPX_OK; |
| + | presult = socks4_check_resp(sx, cf, data); |
| + | if(presult) |
| + | return socks_failed(sx, cf, data, presult); |
| + | sxstate(sx, cf, data, SOCKS_ST_SUCCESS); |
| + | FALLTHROUGH(); |
| + | |
| + | case SOCKS_ST_SUCCESS: |
| + | return CURLPX_OK; |
| + | |
| + | case SOCKS_ST_FAILED: |
| + | DEBUGASSERT(sx->presult); |
| + | return sx->presult; |
| + | |
| + | default: |
| + | DEBUGASSERT(0); |
| + | return socks_failed(sx, cf, data, CURLPX_SEND_REQUEST); |
| + | } |
| + | } |
| + | |
| + | static CURLproxycode socks5_req0_init(struct Curl_cfilter *cf, |
| + | struct socks_state *sx, |
| + | struct Curl_easy *data) |
| + | { |
| + | const unsigned char auth = data->set.socks5auth; |
| + | unsigned char req[5]; /* version + len + 3 possible auth methods */ |
| + | unsigned char nauths; |
| + | size_t req_len, nwritten; |
| + | CURLcode result; |
| + | |
| + | (void)cf; |
| /* RFC1928 chapter 5 specifies max 255 chars for domain name in packet */ | |
| - | if(!socks5_resolve_local && hostname_len > 255) { |
| + | if(!sx->resolve_local && strlen(sx->hostname) > 255) { |
| failf(data, "SOCKS5: the destination hostname is too long to be " | |
| "resolved remotely by the proxy."); | |
| return CURLPX_LONG_HOSTNAME; | |
| @@ -556,27 +618,73 @@ static CURLproxycode socks5_init(struct Curl_cfilter *cf, | |
| /* disable username/password auth */ | |
| sx->proxy_user = NULL; | |
| - | if(!sx->outstanding) { |
| - | size_t idx = 0; |
| - | socksreq[idx++] = 5; /* version */ |
| - | idx++; /* number of authentication methods */ |
| - | socksreq[idx++] = 0; /* no authentication */ |
| + | req[0] = 5; /* version */ |
| + | nauths = 1; |
| + | req[1 + nauths] = 0; /* 1. no authentication */ |
| #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) | |
| - | if(auth & CURLAUTH_GSSAPI) |
| - | socksreq[idx++] = 1; /* GSS-API */ |
| + | if(auth & CURLAUTH_GSSAPI) { |
| + | ++nauths; |
| + | req[1 + nauths] = 1; /* GSS-API */ |
| + | } |
| #endif | |
| - | if(sx->proxy_user) |
| - | socksreq[idx++] = 2; /* username/password */ |
| - | /* write the number of authentication methods */ |
| - | socksreq[1] = (unsigned char) (idx - 2); |
| - | |
| - | sx->outp = socksreq; |
| - | DEBUGASSERT(idx <= sizeof(sx->buffer)); |
| - | sx->outstanding = idx; |
| + | if(sx->proxy_user) { |
| + | ++nauths; |
| + | req[1 + nauths] = 2; /* username/password */ |
| } | |
| + | req[1] = nauths; |
| + | req_len = 2 + nauths; |
| + | |
| + | result = Curl_bufq_write(&sx->iobuf, req, req_len, &nwritten); |
| + | if(result || (nwritten != req_len)) |
| + | return CURLPX_SEND_REQUEST; |
| + | return CURLPX_OK; |
| + | } |
| + | |
| + | static CURLproxycode socks5_check_resp0(struct socks_state *sx, |
| + | struct Curl_cfilter *cf, |
| + | struct Curl_easy *data) |
| + | { |
| + | const unsigned char *resp; |
| + | unsigned char auth_mode; |
| + | size_t rlen; |
| - | return socks_state_send(cf, sx, data, CURLPX_SEND_CONNECT, |
| - | "initial SOCKS5 request"); |
| + | if(!Curl_bufq_peek(&sx->iobuf, &resp, &rlen) || rlen < 2) { |
| + | failf(data, "SOCKS5 initial reply is incomplete."); |
| + | return CURLPX_RECV_CONNECT; |
| + | } |
| + | |
| + | if(resp[0] != 5) { |
| + | failf(data, "Received invalid version in initial SOCKS5 response."); |
| + | return CURLPX_BAD_VERSION; |
| + | } |
| + | |
| + | auth_mode = resp[1]; |
| + | Curl_bufq_reset(&sx->iobuf); |
| + | |
| + | switch(auth_mode) { |
| + | case 0: |
| + | /* DONE! No authentication needed. Send request. */ |
| + | sxstate(sx, cf, data, SOCKS5_ST_REQ1_INIT); |
| + | return CURLPX_OK; |
| + | case 1: |
| + | if(data->set.socks5auth & CURLAUTH_GSSAPI) { |
| + | sxstate(sx, cf, data, SOCKS5_ST_GSSAPI_INIT); |
| + | return CURLPX_OK; |
| + | } |
| + | failf(data, |
| + | "SOCKS5 GSSAPI per-message authentication is not enabled."); |
| + | return CURLPX_GSSAPI_PERMSG; |
| + | case 2: |
| + | /* regular name + password authentication */ |
| + | sxstate(sx, cf, data, SOCKS5_ST_AUTH_INIT); |
| + | return CURLPX_OK; |
| + | case 255: |
| + | failf(data, "No authentication method was acceptable."); |
| + | return CURLPX_NO_AUTH; |
| + | default: |
| + | failf(data, "Unknown SOCKS5 mode attempted to be used by server."); |
| + | return CURLPX_UNKNOWN_MODE; |
| + | } |
| } | |
| static CURLproxycode socks5_auth_init(struct Curl_cfilter *cf, | |
| @@ -584,17 +692,22 @@ static CURLproxycode socks5_auth_init(struct Curl_cfilter *cf, | |
| struct Curl_easy *data) | |
| { | |
| /* Needs username and password */ | |
| - | size_t proxy_user_len, proxy_password_len; |
| - | size_t len = 0; |
| - | unsigned char *socksreq = sx->buffer; |
| + | size_t ulen = 0, plen = 0, nwritten; |
| + | unsigned char buf[2]; |
| + | CURLcode result; |
| if(sx->proxy_user && sx->proxy_password) { | |
| - | proxy_user_len = strlen(sx->proxy_user); |
| - | proxy_password_len = strlen(sx->proxy_password); |
| - | } |
| - | else { |
| - | proxy_user_len = 0; |
| - | proxy_password_len = 0; |
| + | ulen = strlen(sx->proxy_user); |
| + | plen = strlen(sx->proxy_password); |
| + | /* the lengths must fit in a single byte */ |
| + | if(ulen > 255) { |
| + | failf(data, "Excessive username length for proxy auth"); |
| + | return CURLPX_LONG_USER; |
| + | } |
| + | if(plen > 255) { |
| + | failf(data, "Excessive password length for proxy auth"); |
| + | return CURLPX_LONG_PASSWD; |
| + | } |
| } | |
| /* username/password request looks like | |
| @@ -604,499 +717,494 @@ static CURLproxycode socks5_auth_init(struct Curl_cfilter *cf, | |
| * | 1 | 1 | 1 to 255 | 1 | 1 to 255 | | |
| * +----+------+----------+------+----------+ | |
| */ | |
| - | socksreq[len++] = 1; /* username/pw subnegotiation version */ |
| - | socksreq[len++] = (unsigned char) proxy_user_len; |
| - | if(sx->proxy_user && proxy_user_len) { |
| - | /* the length must fit in a single byte */ |
| - | if(proxy_user_len > 255) { |
| - | failf(data, "Excessive username length for proxy auth"); |
| - | return CURLPX_LONG_USER; |
| - | } |
| - | memcpy(socksreq + len, sx->proxy_user, proxy_user_len); |
| + | buf[0] = 1; /* username/pw subnegotiation version */ |
| + | buf[1] = (unsigned char)ulen; |
| + | result = Curl_bufq_write(&sx->iobuf, buf, 2, &nwritten); |
| + | if(result || (nwritten != 2)) |
| + | return CURLPX_SEND_REQUEST; |
| + | if(ulen) { |
| + | result = Curl_bufq_cwrite(&sx->iobuf, sx->proxy_user, ulen, &nwritten); |
| + | if(result || (nwritten != ulen)) |
| + | return CURLPX_SEND_REQUEST; |
| } | |
| - | len += proxy_user_len; |
| - | socksreq[len++] = (unsigned char) proxy_password_len; |
| - | if(sx->proxy_password && proxy_password_len) { |
| - | /* the length must fit in a single byte */ |
| - | if(proxy_password_len > 255) { |
| - | failf(data, "Excessive password length for proxy auth"); |
| - | return CURLPX_LONG_PASSWD; |
| - | } |
| - | memcpy(socksreq + len, sx->proxy_password, proxy_password_len); |
| + | buf[0] = (unsigned char) plen; |
| + | result = Curl_bufq_write(&sx->iobuf, buf, 1, &nwritten); |
| + | if(result || (nwritten != 1)) |
| + | return CURLPX_SEND_REQUEST; |
| + | if(plen) { |
| + | result = Curl_bufq_cwrite(&sx->iobuf, sx->proxy_password, plen, &nwritten); |
| + | if(result || (nwritten != plen)) |
| + | return CURLPX_SEND_REQUEST; |
| } | |
| - | len += proxy_password_len; |
| - | sxstate(sx, cf, data, CONNECT_AUTH_SEND); |
| - | DEBUGASSERT(len <= sizeof(sx->buffer)); |
| - | sx->outstanding = len; |
| - | sx->outp = socksreq; |
| + | sxstate(sx, cf, data, SOCKS5_ST_AUTH_SEND); |
| return CURLPX_OK; | |
| } | |
| - | /* |
| - | * This function logs in to a SOCKS5 proxy and sends the specifics to the final |
| - | * destination server. |
| - | */ |
| - | static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, |
| - | struct socks_state *sx, |
| - | struct Curl_easy *data) |
| + | static CURLproxycode socks5_check_auth_resp(struct socks_state *sx, |
| + | struct Curl_cfilter *cf, |
| + | struct Curl_easy *data) |
| { | |
| - | /* |
| - | According to the RFC1928, section "6. Replies". This is what a SOCK5 |
| - | replies: |
| + | const unsigned char *resp; |
| + | unsigned char auth_status; |
| + | size_t rlen; |
| - | +----+-----+-------+------+----------+----------+ |
| - | |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | |
| - | +----+-----+-------+------+----------+----------+ |
| - | | 1 | 1 | X'00' | 1 | Variable | 2 | |
| - | +----+-----+-------+------+----------+----------+ |
| + | (void)cf; |
| + | if(!Curl_bufq_peek(&sx->iobuf, &resp, &rlen) || rlen < 2) { |
| + | failf(data, "SOCKS5 sub-negotiation response incomplete."); |
| + | return CURLPX_RECV_CONNECT; |
| + | } |
| - | Where: |
| + | /* ignore the first (VER) byte */ |
| + | auth_status = resp[1]; |
| + | Curl_bufq_reset(&sx->iobuf); |
| - | o VER protocol version: X'05' |
| - | o REP Reply field: |
| - | o X'00' succeeded |
| - | */ |
| - | struct connectdata *conn = cf->conn; |
| - | unsigned char *socksreq = sx->buffer; |
| + | if(auth_status) { |
| + | failf(data, "User was rejected by the SOCKS5 server (%d %d).", |
| + | resp[0], resp[1]); |
| + | return CURLPX_USER_REJECTED; |
| + | } |
| + | return CURLPX_OK; |
| + | } |
| + | |
| + | static CURLproxycode socks5_req1_init(struct socks_state *sx, |
| + | struct Curl_cfilter *cf, |
| + | struct Curl_easy *data) |
| + | { |
| + | unsigned char req[5]; |
| + | unsigned char ipbuf[16]; |
| + | const unsigned char *destination; |
| + | unsigned char desttype, destlen, hdlen; |
| + | size_t nwritten; |
| CURLcode result; | |
| - | CURLproxycode presult; |
| - | bool socks5_resolve_local = |
| - | (conn->socks_proxy.proxytype == CURLPROXY_SOCKS5); |
| - | const size_t hostname_len = strlen(sx->hostname); |
| - | size_t len = 0; |
| - | bool allow_gssapi = FALSE; |
| - | struct Curl_dns_entry *dns = NULL; |
| - | switch(sx->state) { |
| - | case CONNECT_SOCKS_INIT: |
| - | presult = socks5_init(cf, sx, data, socks5_resolve_local, hostname_len); |
| - | if(presult || sx->outstanding) |
| - | return presult; |
| - | sxstate(sx, cf, data, CONNECT_SOCKS_READ); |
| - | goto CONNECT_SOCKS_READ_INIT; |
| - | case CONNECT_SOCKS_SEND: |
| - | presult = socks_state_send(cf, sx, data, CURLPX_SEND_CONNECT, |
| - | "initial SOCKS5 request"); |
| - | if(CURLPX_OK != presult) |
| - | return presult; |
| - | else if(sx->outstanding) { |
| - | /* remain in sending state */ |
| - | return CURLPX_OK; |
| - | } |
| - | FALLTHROUGH(); |
| - | case CONNECT_SOCKS_READ_INIT: |
| - | CONNECT_SOCKS_READ_INIT: |
| - | sx->outstanding = 2; /* expect two bytes */ |
| - | sx->outp = socksreq; /* store it here */ |
| - | FALLTHROUGH(); |
| - | case CONNECT_SOCKS_READ: |
| - | presult = socks_state_recv(cf, sx, data, CURLPX_RECV_CONNECT, |
| - | "initial SOCKS5 response"); |
| - | #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) |
| - | if(data->set.socks5auth & CURLAUTH_GSSAPI) |
| - | allow_gssapi = TRUE; |
| - | #endif |
| - | if(CURLPX_OK != presult) |
| - | return presult; |
| - | else if(sx->outstanding) { |
| - | /* remain in reading state */ |
| - | return CURLPX_OK; |
| - | } |
| - | else if(socksreq[0] != 5) { |
| - | failf(data, "Received invalid version in initial SOCKS5 response."); |
| - | return CURLPX_BAD_VERSION; |
| - | } |
| - | else if(socksreq[1] == 0) { |
| - | /* DONE! No authentication needed. Send request. */ |
| - | sxstate(sx, cf, data, CONNECT_REQ_INIT); |
| - | goto CONNECT_REQ_INIT; |
| - | } |
| - | else if(socksreq[1] == 2) { |
| - | /* regular name + password authentication */ |
| - | sxstate(sx, cf, data, CONNECT_AUTH_INIT); |
| - | goto CONNECT_AUTH_INIT; |
| - | } |
| - | #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) |
| - | else if(allow_gssapi && (socksreq[1] == 1)) { |
| - | sxstate(sx, cf, data, CONNECT_GSSAPI_INIT); |
| - | result = Curl_SOCKS5_gssapi_negotiate(cf, data); |
| - | if(result) { |
| - | failf(data, "Unable to negotiate SOCKS5 GSS-API context."); |
| - | return CURLPX_GSSAPI; |
| - | } |
| - | } |
| - | #endif |
| - | else { |
| - | /* error */ |
| - | if(!allow_gssapi && (socksreq[1] == 1)) { |
| - | failf(data, |
| - | "SOCKS5 GSSAPI per-message authentication is not supported."); |
| - | return CURLPX_GSSAPI_PERMSG; |
| - | } |
| - | else if(socksreq[1] == 255) { |
| - | failf(data, "No authentication method was acceptable."); |
| - | return CURLPX_NO_AUTH; |
| - | } |
| - | } |
| - | failf(data, |
| - | "Undocumented SOCKS5 mode attempted to be used by server."); |
| - | return CURLPX_UNKNOWN_MODE; |
| - | #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) |
| - | case CONNECT_GSSAPI_INIT: |
| - | /* GSSAPI stuff done non-blocking */ |
| - | break; |
| + | req[0] = 5; /* version (SOCKS5) */ |
| + | req[1] = 1; /* connect */ |
| + | req[2] = 0; /* must be zero */ |
| + | if(sx->resolve_local) { |
| + | /* rest of request is added after resolving */ |
| + | result = Curl_bufq_write(&sx->iobuf, req, 3, &nwritten); |
| + | if(result || (nwritten != 3)) |
| + | return CURLPX_SEND_REQUEST; |
| + | return CURLPX_OK; |
| + | } |
| + | |
| + | /* remote resolving, send what type+addr/string to resolve */ |
| + | #ifdef USE_IPV6 |
| + | if(cf->conn->bits.ipv6_ip) { |
| + | desttype = 4; |
| + | destination = ipbuf; |
| + | destlen = 16; |
| + | if(curlx_inet_pton(AF_INET6, sx->hostname, ipbuf) != 1) |
| + | return CURLPX_BAD_ADDRESS_TYPE; |
| + | } |
| + | else |
| #endif | |
| + | if(curlx_inet_pton(AF_INET, sx->hostname, ipbuf) == 1) { |
| + | desttype = 1; |
| + | destination = ipbuf; |
| + | destlen = 4; |
| + | } |
| + | else { |
| + | const size_t hostname_len = strlen(sx->hostname); |
| + | desttype = 3; |
| + | destination = (const unsigned char *)sx->hostname; |
| + | destlen = (unsigned char) hostname_len; /* one byte length */ |
| + | } |
| - | default: /* do nothing! */ |
| - | break; |
| + | req[3] = desttype; |
| + | req[4] = destlen; |
| + | hdlen = (desttype == 3) ? 5 : 4; /* no length byte for ip addresses */ |
| + | result = Curl_bufq_write(&sx->iobuf, req, hdlen, &nwritten); |
| + | if(result || (nwritten != hdlen)) |
| + | return CURLPX_SEND_REQUEST; |
| + | result = Curl_bufq_write(&sx->iobuf, destination, destlen, &nwritten); |
| + | if(result || (nwritten != destlen)) |
| + | return CURLPX_SEND_REQUEST; |
| + | /* PORT MSB+LSB */ |
| + | req[0] = (unsigned char)((sx->remote_port >> 8) & 0xff); |
| + | req[1] = (unsigned char)(sx->remote_port & 0xff); |
| + | result = Curl_bufq_write(&sx->iobuf, req, 2, &nwritten); |
| + | if(result || (nwritten != 2)) |
| + | return CURLPX_SEND_REQUEST; |
| + | CURL_TRC_CF(data, cf, "SOCKS5 connect to %s:%d (remotely resolved)", |
| + | sx->hostname, sx->remote_port); |
| + | return CURLPX_OK; |
| + | } |
| - | CONNECT_AUTH_INIT: |
| - | case CONNECT_AUTH_INIT: |
| - | presult = socks5_auth_init(cf, sx, data); |
| - | if(presult) |
| - | return presult; |
| - | FALLTHROUGH(); |
| - | case CONNECT_AUTH_SEND: |
| - | presult = socks_state_send(cf, sx, data, CURLPX_SEND_AUTH, |
| - | "SOCKS5 sub-negotiation request"); |
| - | if(CURLPX_OK != presult) |
| - | return presult; |
| - | else if(sx->outstanding) { |
| - | /* remain in sending state */ |
| - | return CURLPX_OK; |
| - | } |
| - | sx->outp = socksreq; |
| - | sx->outstanding = 2; |
| - | sxstate(sx, cf, data, CONNECT_AUTH_READ); |
| - | FALLTHROUGH(); |
| - | case CONNECT_AUTH_READ: |
| - | presult = socks_state_recv(cf, sx, data, CURLPX_RECV_AUTH, |
| - | "SOCKS5 sub-negotiation response"); |
| - | if(CURLPX_OK != presult) |
| - | return presult; |
| - | else if(sx->outstanding) { |
| - | /* remain in reading state */ |
| - | return CURLPX_OK; |
| - | } |
| - | /* ignore the first (VER) byte */ |
| - | else if(socksreq[1]) { /* status */ |
| - | failf(data, "User was rejected by the SOCKS5 server (%d %d).", |
| - | socksreq[0], socksreq[1]); |
| - | return CURLPX_USER_REJECTED; |
| - | } |
| + | static CURLproxycode socks5_resolving(struct socks_state *sx, |
| + | struct Curl_cfilter *cf, |
| + | struct Curl_easy *data, |
| + | bool *done) |
| + | { |
| + | struct Curl_dns_entry *dns = NULL; |
| + | struct Curl_addrinfo *hp = NULL; |
| + | char dest[MAX_IPADR_LEN]; /* printable address */ |
| + | unsigned char *destination = NULL; |
| + | unsigned char desttype = 1, destlen = 4; |
| + | unsigned char req[2]; |
| + | CURLcode result; |
| + | CURLproxycode presult = CURLPX_OK; |
| + | size_t nwritten; |
| - | /* Everything is good so far, user was authenticated! */ |
| - | sxstate(sx, cf, data, CONNECT_REQ_INIT); |
| - | FALLTHROUGH(); |
| - | case CONNECT_REQ_INIT: |
| - | CONNECT_REQ_INIT: |
| - | if(socks5_resolve_local) { |
| - | result = Curl_resolv(data, sx->hostname, sx->remote_port, |
| - | cf->conn->ip_version, TRUE, &dns); |
| - | |
| - | if(result == CURLE_AGAIN) { |
| - | sxstate(sx, cf, data, CONNECT_RESOLVING); |
| - | return CURLPX_OK; |
| - | } |
| - | else if(result) |
| - | return CURLPX_RESOLVE_HOST; |
| - | sxstate(sx, cf, data, CONNECT_RESOLVED); |
| - | goto CONNECT_RESOLVED; |
| - | } |
| - | goto CONNECT_RESOLVE_REMOTE; |
| + | *done = FALSE; |
| + | if(sx->start_resolving) { |
| + | /* need to resolve hostname to add destination address */ |
| + | sx->start_resolving = FALSE; |
| - | case CONNECT_RESOLVING: |
| + | result = Curl_resolv(data, sx->hostname, sx->remote_port, |
| + | cf->conn->ip_version, TRUE, &dns); |
| + | if(result == CURLE_AGAIN) { |
| + | CURL_TRC_CF(data, cf, "SOCKS5 non-blocking resolve of %s", |
| + | sx->hostname); |
| + | return CURLPX_OK; |
| + | } |
| + | else if(result) |
| + | return CURLPX_RESOLVE_HOST; |
| + | } |
| + | else { |
| /* check if we have the name resolved by now */ | |
| result = Curl_resolv_check(data, &dns); | |
| - | if(!dns) { |
| - | if(result) |
| - | return CURLPX_RESOLVE_HOST; |
| + | if(!result && !dns) |
| return CURLPX_OK; | |
| - | } |
| - | FALLTHROUGH(); |
| - | case CONNECT_RESOLVED: |
| - | CONNECT_RESOLVED: |
| - | { |
| - | char dest[MAX_IPADR_LEN]; /* printable address */ |
| - | struct Curl_addrinfo *hp = NULL; |
| - | if(dns) |
| - | hp = dns->addr; |
| + | } |
| + | |
| + | if(result || !dns) { |
| + | failf(data, "Failed to resolve \"%s\" for SOCKS5 connect.", |
| + | sx->hostname); |
| + | presult = CURLPX_RESOLVE_HOST; |
| + | goto out; |
| + | } |
| + | |
| + | if(dns) |
| + | hp = dns->addr; |
| #ifdef USE_IPV6 | |
| - | if(data->set.ipver != CURL_IPRESOLVE_WHATEVER) { |
| - | int wanted_family = data->set.ipver == CURL_IPRESOLVE_V4 ? |
| - | AF_INET : AF_INET6; |
| - | /* scan for the first proper address */ |
| - | while(hp && (hp->ai_family != wanted_family)) |
| - | hp = hp->ai_next; |
| - | } |
| + | if(data->set.ipver != CURL_IPRESOLVE_WHATEVER) { |
| + | int wanted_family = data->set.ipver == CURL_IPRESOLVE_V4 ? |
| + | AF_INET : AF_INET6; |
| + | /* scan for the first proper address */ |
| + | while(hp && (hp->ai_family != wanted_family)) |
| + | hp = hp->ai_next; |
| + | } |
| #endif | |
| - | if(!hp) { |
| - | failf(data, "Failed to resolve \"%s\" for SOCKS5 connect.", |
| - | sx->hostname); |
| - | return CURLPX_RESOLVE_HOST; |
| - | } |
| + | if(!hp) { |
| + | failf(data, "Failed to resolve \"%s\" for SOCKS5 connect.", |
| + | sx->hostname); |
| + | presult = CURLPX_RESOLVE_HOST; |
| + | goto out; |
| + | } |
| - | Curl_printable_address(hp, dest, sizeof(dest)); |
| + | Curl_printable_address(hp, dest, sizeof(dest)); |
| - | len = 0; |
| - | socksreq[len++] = 5; /* version (SOCKS5) */ |
| - | socksreq[len++] = 1; /* connect */ |
| - | socksreq[len++] = 0; /* must be zero */ |
| - | if(hp->ai_family == AF_INET) { |
| - | int i; |
| - | struct sockaddr_in *saddr_in; |
| - | socksreq[len++] = 1; /* ATYP: IPv4 = 1 */ |
| + | if(hp->ai_family == AF_INET) { |
| + | struct sockaddr_in *saddr_in; |
| + | desttype = 1; /* ATYP: IPv4 = 1 */ |
| + | destlen = 4; |
| + | saddr_in = (struct sockaddr_in *)(void *)hp->ai_addr; |
| + | destination = (unsigned char *)&saddr_in->sin_addr.s_addr; |
| + | CURL_TRC_CF(data, cf, "SOCKS5 connect to %s:%d (locally resolved)", |
| + | dest, sx->remote_port); |
| + | } |
| + | #ifdef USE_IPV6 |
| + | else if(hp->ai_family == AF_INET6) { |
| + | struct sockaddr_in6 *saddr_in6; |
| + | desttype = 4; /* ATYP: IPv6 = 4 */ |
| + | destlen = 16; |
| + | saddr_in6 = (struct sockaddr_in6 *)(void *)hp->ai_addr; |
| + | destination = (unsigned char *)&saddr_in6->sin6_addr.s6_addr; |
| + | CURL_TRC_CF(data, cf, "SOCKS5 connect to [%s]:%d (locally resolved)", |
| + | dest, sx->remote_port); |
| + | } |
| + | #endif |
| - | saddr_in = (struct sockaddr_in *)(void *)hp->ai_addr; |
| - | for(i = 0; i < 4; i++) { |
| - | socksreq[len++] = ((unsigned char *)&saddr_in->sin_addr.s_addr)[i]; |
| - | } |
| + | if(!destination) { |
| + | failf(data, "SOCKS5 connection to %s not supported", dest); |
| + | presult = CURLPX_RESOLVE_HOST; |
| + | goto out; |
| + | } |
| - | CURL_TRC_CF(data, cf, "SOCKS5 connect to %s:%d (locally resolved)", |
| - | dest, sx->remote_port); |
| - | } |
| - | #ifdef USE_IPV6 |
| - | else if(hp->ai_family == AF_INET6) { |
| - | int i; |
| - | struct sockaddr_in6 *saddr_in6; |
| - | socksreq[len++] = 4; /* ATYP: IPv6 = 4 */ |
| - | |
| - | saddr_in6 = (struct sockaddr_in6 *)(void *)hp->ai_addr; |
| - | for(i = 0; i < 16; i++) { |
| - | socksreq[len++] = |
| - | ((unsigned char *)&saddr_in6->sin6_addr.s6_addr)[i]; |
| - | } |
| + | req[0] = desttype; |
| + | result = Curl_bufq_write(&sx->iobuf, req, 1, &nwritten); |
| + | if(result || (nwritten != 1)) { |
| + | presult = CURLPX_SEND_REQUEST; |
| + | goto out; |
| + | } |
| + | result = Curl_bufq_write(&sx->iobuf, destination, destlen, &nwritten); |
| + | if(result || (nwritten != destlen)) { |
| + | presult = CURLPX_SEND_REQUEST; |
| + | goto out; |
| + | } |
| + | /* PORT MSB+LSB */ |
| + | req[0] = (unsigned char)((sx->remote_port >> 8) & 0xffu); |
| + | req[1] = (unsigned char)(sx->remote_port & 0xffu); |
| + | result = Curl_bufq_write(&sx->iobuf, req, 2, &nwritten); |
| + | if(result || (nwritten != 2)) { |
| + | presult = CURLPX_SEND_REQUEST; |
| + | goto out; |
| + | } |
| - | CURL_TRC_CF(data, cf, "SOCKS5 connect to [%s]:%d (locally resolved)", |
| - | dest, sx->remote_port); |
| - | } |
| - | #endif |
| - | else { |
| - | hp = NULL; /* fail! */ |
| - | failf(data, "SOCKS5 connection to %s not supported", dest); |
| + | out: |
| + | if(dns) |
| + | Curl_resolv_unlink(data, &dns); |
| + | *done = (presult == CURLPX_OK); |
| + | return presult; |
| + | } |
| + | |
| + | static CURLproxycode socks5_recv_resp1(struct socks_state *sx, |
| + | struct Curl_cfilter *cf, |
| + | struct Curl_easy *data, |
| + | bool *done) |
| + | { |
| + | const unsigned char *resp; |
| + | size_t rlen, resp_len = 8; /* minimum response length */ |
| + | CURLproxycode presult; |
| + | |
| + | presult = socks_recv(sx, cf, data, resp_len, done); |
| + | if(presult) |
| + | return presult; |
| + | else if(!*done) |
| + | return CURLPX_OK; |
| + | |
| + | if(!Curl_bufq_peek(&sx->iobuf, &resp, &rlen) || rlen < resp_len) { |
| + | failf(data, "SOCKS5 response is incomplete."); |
| + | return CURLPX_RECV_CONNECT; |
| + | } |
| + | |
| + | /* Response packet includes BND.ADDR is variable length parameter by RFC |
| + | 1928, so the response packet MUST be read until the end to avoid errors |
| + | at subsequent protocol level. |
| + | |
| + | +----+-----+-------+------+----------+----------+ |
| + | |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | |
| + | +----+-----+-------+------+----------+----------+ |
| + | | 1 | 1 | X'00' | 1 | Variable | 2 | |
| + | +----+-----+-------+------+----------+----------+ |
| + | |
| + | ATYP: |
| + | o IP v4 address: X'01', BND.ADDR = 4 byte |
| + | o domain name: X'03', BND.ADDR = [ 1 byte length, string ] |
| + | o IP v6 address: X'04', BND.ADDR = 16 byte |
| + | */ |
| + | if(resp[0] != 5) { /* version */ |
| + | failf(data, "SOCKS5 reply has wrong version, version should be 5."); |
| + | return CURLPX_BAD_VERSION; |
| + | } |
| + | else if(resp[1]) { /* Anything besides 0 is an error */ |
| + | CURLproxycode rc = CURLPX_REPLY_UNASSIGNED; |
| + | int code = resp[1]; |
| + | failf(data, "cannot complete SOCKS5 connection to %s. (%d)", |
| + | sx->hostname, code); |
| + | if(code < 9) { |
| + | /* RFC 1928 section 6 lists: */ |
| + | static const CURLproxycode lookup[] = { |
| + | CURLPX_OK, |
| + | CURLPX_REPLY_GENERAL_SERVER_FAILURE, |
| + | CURLPX_REPLY_NOT_ALLOWED, |
| + | CURLPX_REPLY_NETWORK_UNREACHABLE, |
| + | CURLPX_REPLY_HOST_UNREACHABLE, |
| + | CURLPX_REPLY_CONNECTION_REFUSED, |
| + | CURLPX_REPLY_TTL_EXPIRED, |
| + | CURLPX_REPLY_COMMAND_NOT_SUPPORTED, |
| + | CURLPX_REPLY_ADDRESS_TYPE_NOT_SUPPORTED, |
| + | }; |
| + | rc = lookup[code]; |
| } | |
| + | return rc; |
| + | } |
| - | Curl_resolv_unlink(data, &dns); /* not used anymore from now on */ |
| - | goto CONNECT_REQ_SEND; |
| + | /* Calculate real packet size */ |
| + | switch(resp[3]) { |
| + | case 1: /* IPv4 */ |
| + | resp_len = 4 + 4 + 2; |
| + | break; |
| + | case 3: /* domain name */ |
| + | resp_len = 4 + 1 + resp[4] + 2; /* header, var length, var bytes, port */ |
| + | break; |
| + | case 4: /* IPv6 */ |
| + | resp_len = 4 + 16 + 2; |
| + | break; |
| + | default: |
| + | failf(data, "SOCKS5 reply has wrong address type."); |
| + | return CURLPX_BAD_ADDRESS_TYPE; |
| } | |
| - | CONNECT_RESOLVE_REMOTE: |
| - | case CONNECT_RESOLVE_REMOTE: |
| - | /* Authentication is complete, now specify destination to the proxy */ |
| - | len = 0; |
| - | socksreq[len++] = 5; /* version (SOCKS5) */ |
| - | socksreq[len++] = 1; /* connect */ |
| - | socksreq[len++] = 0; /* must be zero */ |
| - | |
| - | if(!socks5_resolve_local) { |
| - | /* ATYP: domain name = 3, |
| - | IPv6 == 4, |
| - | IPv4 == 1 */ |
| - | unsigned char ip4[4]; |
| - | #ifdef USE_IPV6 |
| - | if(conn->bits.ipv6_ip) { |
| - | char ip6[16]; |
| - | if(curlx_inet_pton(AF_INET6, sx->hostname, ip6) != 1) |
| - | return CURLPX_BAD_ADDRESS_TYPE; |
| - | socksreq[len++] = 4; |
| - | memcpy(&socksreq[len], ip6, sizeof(ip6)); |
| - | len += sizeof(ip6); |
| - | } |
| - | else |
| - | #endif |
| - | if(curlx_inet_pton(AF_INET, sx->hostname, ip4) == 1) { |
| - | socksreq[len++] = 1; |
| - | memcpy(&socksreq[len], ip4, sizeof(ip4)); |
| - | len += sizeof(ip4); |
| - | } |
| - | else { |
| - | socksreq[len++] = 3; |
| - | socksreq[len++] = (unsigned char) hostname_len; /* one byte length */ |
| - | memcpy(&socksreq[len], sx->hostname, hostname_len); /* w/o NULL */ |
| - | len += hostname_len; |
| - | } |
| - | CURL_TRC_CF(data, cf, "SOCKS5 connect to %s:%d (remotely resolved)", |
| + | |
| + | /* receive the rest of the response */ |
| + | presult = socks_recv(sx, cf, data, resp_len, done); |
| + | if(presult) |
| + | return presult; |
| + | else if(!*done) |
| + | return CURLPX_OK; |
| + | |
| + | if(!Curl_bufq_peek(&sx->iobuf, &resp, &rlen) || rlen < resp_len) { |
| + | failf(data, "SOCKS5 response is incomplete."); |
| + | return CURLPX_RECV_CONNECT; |
| + | } |
| + | /* got it all */ |
| + | *done = TRUE; |
| + | return CURLPX_OK; |
| + | } |
| + | |
| + | /* |
| + | * This function logs in to a SOCKS5 proxy and sends the specifics to the final |
| + | * destination server. |
| + | */ |
| + | static CURLproxycode socks5_connect(struct Curl_cfilter *cf, |
| + | struct socks_state *sx, |
| + | struct Curl_easy *data) |
| + | { |
| + | CURLproxycode presult; |
| + | bool done; |
| + | |
| + | process_state: |
| + | switch(sx->state) { |
| + | case SOCKS_ST_INIT: |
| + | sx->version = 5; |
| + | sx->resolve_local = (cf->conn->socks_proxy.proxytype == CURLPROXY_SOCKS5); |
| + | sxstate(sx, cf, data, SOCKS5_ST_START); |
| + | FALLTHROUGH(); |
| + | |
| + | case SOCKS5_ST_START: |
| + | if(cf->conn->bits.httpproxy) |
| + | CURL_TRC_CF(data, cf, "SOCKS5: connecting to HTTP proxy %s port %d", |
| sx->hostname, sx->remote_port); | |
| - | } |
| + | presult = socks5_req0_init(cf, sx, data); |
| + | if(presult) |
| + | return socks_failed(sx, cf, data, presult); |
| + | sxstate(sx, cf, data, SOCKS5_ST_REQ0_SEND); |
| FALLTHROUGH(); | |
| - | case CONNECT_REQ_SEND: |
| - | CONNECT_REQ_SEND: |
| - | /* PORT MSB */ |
| - | socksreq[len++] = (unsigned char)((sx->remote_port >> 8) & 0xff); |
| - | /* PORT LSB */ |
| - | socksreq[len++] = (unsigned char)(sx->remote_port & 0xff); |
| + | case SOCKS5_ST_REQ0_SEND: |
| + | presult = socks_flush(sx, cf, data, &done); |
| + | if(presult) |
| + | return socks_failed(sx, cf, data, presult); |
| + | else if(!done) |
| + | return CURLPX_OK; |
| + | /* done sending! */ |
| + | sxstate(sx, cf, data, SOCKS5_ST_RESP0_RECV); |
| + | FALLTHROUGH(); |
| + | |
| + | case SOCKS5_ST_RESP0_RECV: |
| + | presult = socks_recv(sx, cf, data, 2, &done); |
| + | if(presult) |
| + | return socks_failed(sx, cf, data, presult); |
| + | else if(!done) |
| + | return CURLPX_OK; |
| + | presult = socks5_check_resp0(sx, cf, data); |
| + | if(presult) |
| + | return socks_failed(sx, cf, data, presult); |
| + | /* socks5_check_resp0() sets next socks state */ |
| + | goto process_state; |
| + | case SOCKS5_ST_GSSAPI_INIT: { |
| #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) | |
| - | if(conn->socks5_gssapi_enctype) { |
| - | failf(data, "SOCKS5 GSS-API protection not yet implemented."); |
| - | return CURLPX_GSSAPI_PROTECTION; |
| + | /* GSSAPI stuff done non-blocking */ |
| + | CURLcode result = Curl_SOCKS5_gssapi_negotiate(cf, data); |
| + | if(result) { |
| + | failf(data, "Unable to negotiate SOCKS5 GSS-API context."); |
| + | return CURLPX_GSSAPI; |
| } | |
| + | sxstate(sx, cf, data, SOCKS5_ST_REQ1_INIT); |
| + | goto process_state; |
| + | #else |
| + | failf(data, |
| + | "SOCKS5 GSSAPI per-message authentication is not supported."); |
| + | return socks_failed(sx, cf, data, CURLPX_GSSAPI_PERMSG); |
| #endif | |
| - | sx->outp = socksreq; |
| - | DEBUGASSERT(len <= sizeof(sx->buffer)); |
| - | sx->outstanding = len; |
| - | sxstate(sx, cf, data, CONNECT_REQ_SENDING); |
| + | } |
| + | |
| + | case SOCKS5_ST_AUTH_INIT: |
| + | presult = socks5_auth_init(cf, sx, data); |
| + | if(presult) |
| + | return socks_failed(sx, cf, data, presult); |
| + | sxstate(sx, cf, data, SOCKS5_ST_AUTH_SEND); |
| FALLTHROUGH(); | |
| - | case CONNECT_REQ_SENDING: |
| - | presult = socks_state_send(cf, sx, data, CURLPX_SEND_REQUEST, |
| - | "SOCKS5 connect request"); |
| - | if(CURLPX_OK != presult) |
| - | return presult; |
| - | else if(sx->outstanding) { |
| - | /* remain in send state */ |
| + | |
| + | case SOCKS5_ST_AUTH_SEND: |
| + | presult = socks_flush(sx, cf, data, &done); |
| + | if(presult) |
| + | return socks_failed(sx, cf, data, presult); |
| + | else if(!done) |
| return CURLPX_OK; | |
| - | } |
| - | #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) |
| - | if(conn->socks5_gssapi_enctype) { |
| - | failf(data, "SOCKS5 GSS-API protection not yet implemented."); |
| - | return CURLPX_GSSAPI_PROTECTION; |
| - | } |
| - | #endif |
| - | sx->outstanding = 10; /* minimum packet size is 10 */ |
| - | sx->outp = socksreq; |
| - | sxstate(sx, cf, data, CONNECT_REQ_READ); |
| + | sxstate(sx, cf, data, SOCKS5_ST_AUTH_RECV); |
| FALLTHROUGH(); | |
| - | case CONNECT_REQ_READ: |
| - | presult = socks_state_recv(cf, sx, data, CURLPX_RECV_REQACK, |
| - | "SOCKS5 connect request ack"); |
| - | if(CURLPX_OK != presult) |
| - | return presult; |
| - | else if(sx->outstanding) { |
| - | /* remain in reading state */ |
| + | |
| + | case SOCKS5_ST_AUTH_RECV: |
| + | presult = socks_recv(sx, cf, data, 2, &done); |
| + | if(presult) |
| + | return socks_failed(sx, cf, data, presult); |
| + | else if(!done) |
| return CURLPX_OK; | |
| - | } |
| - | else if(socksreq[0] != 5) { /* version */ |
| - | failf(data, |
| - | "SOCKS5 reply has wrong version, version should be 5."); |
| - | return CURLPX_BAD_VERSION; |
| - | } |
| - | else if(socksreq[1]) { /* Anything besides 0 is an error */ |
| - | CURLproxycode rc = CURLPX_REPLY_UNASSIGNED; |
| - | int code = socksreq[1]; |
| - | failf(data, "cannot complete SOCKS5 connection to %s. (%d)", |
| - | sx->hostname, (unsigned char)socksreq[1]); |
| - | if(code < 9) { |
| - | /* RFC 1928 section 6 lists: */ |
| - | static const CURLproxycode lookup[] = { |
| - | CURLPX_OK, |
| - | CURLPX_REPLY_GENERAL_SERVER_FAILURE, |
| - | CURLPX_REPLY_NOT_ALLOWED, |
| - | CURLPX_REPLY_NETWORK_UNREACHABLE, |
| - | CURLPX_REPLY_HOST_UNREACHABLE, |
| - | CURLPX_REPLY_CONNECTION_REFUSED, |
| - | CURLPX_REPLY_TTL_EXPIRED, |
| - | CURLPX_REPLY_COMMAND_NOT_SUPPORTED, |
| - | CURLPX_REPLY_ADDRESS_TYPE_NOT_SUPPORTED, |
| - | }; |
| - | rc = lookup[code]; |
| - | } |
| - | return rc; |
| - | } |
| + | presult = socks5_check_auth_resp(sx, cf, data); |
| + | if(presult) |
| + | return socks_failed(sx, cf, data, presult); |
| + | /* Everything is good so far, user was authenticated! */ |
| + | sxstate(sx, cf, data, SOCKS5_ST_REQ1_INIT); |
| + | FALLTHROUGH(); |
| - | /* Fix: in general, returned BND.ADDR is variable length parameter by RFC |
| - | 1928, so the reply packet should be read until the end to avoid errors |
| - | at subsequent protocol level. |
| - | |
| - | +----+-----+-------+------+----------+----------+ |
| - | |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | |
| - | +----+-----+-------+------+----------+----------+ |
| - | | 1 | 1 | X'00' | 1 | Variable | 2 | |
| - | +----+-----+-------+------+----------+----------+ |
| - | |
| - | ATYP: |
| - | o IP v4 address: X'01', BND.ADDR = 4 byte |
| - | o domain name: X'03', BND.ADDR = [ 1 byte length, string ] |
| - | o IP v6 address: X'04', BND.ADDR = 16 byte |
| - | */ |
| - | |
| - | /* Calculate real packet size */ |
| - | if(socksreq[3] == 3) { |
| - | /* domain name */ |
| - | int addrlen = (int) socksreq[4]; |
| - | len = 5 + addrlen + 2; |
| - | } |
| - | else if(socksreq[3] == 4) { |
| - | /* IPv6 */ |
| - | len = 4 + 16 + 2; |
| - | } |
| - | else if(socksreq[3] == 1) { |
| - | len = 4 + 4 + 2; |
| - | } |
| - | else { |
| - | failf(data, "SOCKS5 reply has wrong address type."); |
| - | return CURLPX_BAD_ADDRESS_TYPE; |
| + | case SOCKS5_ST_REQ1_INIT: |
| + | presult = socks5_req1_init(sx, cf, data); |
| + | if(presult) |
| + | return socks_failed(sx, cf, data, presult); |
| + | if(!sx->resolve_local) { |
| + | /* we do not resolve, request is complete */ |
| + | sxstate(sx, cf, data, SOCKS5_ST_REQ1_SEND); |
| + | goto process_state; |
| } | |
| + | sx->start_resolving = TRUE; |
| + | sxstate(sx, cf, data, SOCKS5_ST_RESOLVING); |
| + | FALLTHROUGH(); |
| - | /* At this point we already read first 10 bytes */ |
| - | #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) |
| - | if(!conn->socks5_gssapi_enctype) { |
| - | /* decrypt_gssapi_blockread already read the whole packet */ |
| - | #endif |
| - | if(len > 10) { |
| - | DEBUGASSERT(len <= sizeof(sx->buffer)); |
| - | sx->outstanding = len - 10; /* get the rest */ |
| - | sx->outp = &socksreq[10]; |
| - | sxstate(sx, cf, data, CONNECT_REQ_READ_MORE); |
| - | } |
| - | else { |
| - | sxstate(sx, cf, data, CONNECT_DONE); |
| - | break; |
| - | } |
| + | case SOCKS5_ST_RESOLVING: |
| + | presult = socks5_resolving(sx, cf, data, &done); |
| + | if(presult) |
| + | return socks_failed(sx, cf, data, presult); |
| + | if(!done) |
| + | return CURLPX_OK; |
| + | sxstate(sx, cf, data, SOCKS5_ST_REQ1_SEND); |
| + | FALLTHROUGH(); |
| + | |
| + | case SOCKS5_ST_REQ1_SEND: |
| + | presult = socks_flush(sx, cf, data, &done); |
| + | if(presult) |
| + | return socks_failed(sx, cf, data, presult); |
| + | else if(!done) |
| + | return CURLPX_OK; |
| #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) | |
| + | if(cf->conn->socks5_gssapi_enctype) { |
| + | failf(data, "SOCKS5 GSS-API protection not yet implemented."); |
| + | return CURLPX_GSSAPI_PROTECTION; |
| } | |
| #endif | |
| + | sxstate(sx, cf, data, SOCKS5_ST_RESP1_RECV); |
| FALLTHROUGH(); | |
| - | case CONNECT_REQ_READ_MORE: |
| - | presult = socks_state_recv(cf, sx, data, CURLPX_RECV_ADDRESS, |
| - | "SOCKS5 connect request address"); |
| - | if(CURLPX_OK != presult) |
| - | return presult; |
| - | else if(sx->outstanding) { |
| - | /* remain in reading state */ |
| - | return CURLPX_OK; |
| - | } |
| - | sxstate(sx, cf, data, CONNECT_DONE); |
| - | } |
| - | CURL_TRC_CF(data, cf, "SOCKS5 request granted."); |
| - | return CURLPX_OK; /* Proxy was successful! */ |
| - | } |
| - | |
| - | static CURLcode connect_SOCKS(struct Curl_cfilter *cf, |
| - | struct socks_state *sxstate, |
| - | struct Curl_easy *data) |
| - | { |
| - | CURLcode result = CURLE_OK; |
| - | CURLproxycode pxresult = CURLPX_OK; |
| - | struct connectdata *conn = cf->conn; |
| + | case SOCKS5_ST_RESP1_RECV: |
| + | presult = socks5_recv_resp1(sx, cf, data, &done); |
| + | if(presult) |
| + | return socks_failed(sx, cf, data, presult); |
| + | if(!done) |
| + | return CURLPX_OK; |
| + | CURL_TRC_CF(data, cf, "SOCKS5 request granted."); |
| + | sxstate(sx, cf, data, SOCKS_ST_SUCCESS); |
| + | FALLTHROUGH(); |
| - | switch(conn->socks_proxy.proxytype) { |
| - | case CURLPROXY_SOCKS5: |
| - | case CURLPROXY_SOCKS5_HOSTNAME: |
| - | pxresult = do_SOCKS5(cf, sxstate, data); |
| - | break; |
| + | case SOCKS_ST_SUCCESS: |
| + | return CURLPX_OK; |
| - | case CURLPROXY_SOCKS4: |
| - | case CURLPROXY_SOCKS4A: |
| - | pxresult = do_SOCKS4(cf, sxstate, data); |
| - | break; |
| + | case SOCKS_ST_FAILED: |
| + | DEBUGASSERT(sx->presult); |
| + | return sx->presult; |
| default: | |
| - | failf(data, "unknown proxytype option given"); |
| - | result = CURLE_COULDNT_CONNECT; |
| - | } /* switch proxytype */ |
| - | if(pxresult) { |
| - | result = CURLE_PROXY; |
| - | data->info.pxcode = pxresult; |
| + | DEBUGASSERT(0); |
| + | return socks_failed(sx, cf, data, CURLPX_SEND_REQUEST); |
| } | |
| - | |
| - | return result; |
| } | |
| static void socks_proxy_cf_free(struct Curl_cfilter *cf) | |
| { | |
| struct socks_state *sxstate = cf->ctx; | |
| if(sxstate) { | |
| + | Curl_bufq_free(&sxstate->iobuf); |
| free(sxstate); | |
| cf->ctx = NULL; | |
| } | |
| @@ -1117,6 +1225,7 @@ static CURLcode socks_proxy_cf_connect(struct Curl_cfilter *cf, | |
| struct connectdata *conn = cf->conn; | |
| int sockindex = cf->sockindex; | |
| struct socks_state *sx = cf->ctx; | |
| + | CURLproxycode pxresult = CURLPX_OK; |
| if(cf->connected) { | |
| *done = TRUE; | |
| @@ -1128,17 +1237,13 @@ static CURLcode socks_proxy_cf_connect(struct Curl_cfilter *cf, | |
| return result; | |
| if(!sx) { | |
| - | sx = calloc(1, sizeof(*sx)); |
| + | cf->ctx = sx = calloc(1, sizeof(*sx)); |
| if(!sx) | |
| return CURLE_OUT_OF_MEMORY; | |
| - | cf->ctx = sx; |
| - | } |
| - | if(sx->state == CONNECT_INIT) { |
| /* for the secondary socket (FTP), use the "connect to host" | |
| * but ignore the "connect to port" (use the secondary port) | |
| */ | |
| - | sxstate(sx, cf, data, CONNECT_SOCKS_INIT); |
| sx->hostname = | |
| conn->bits.httpproxy ? | |
| conn->http_proxy.host.name : | |
| @@ -1153,29 +1258,54 @@ static CURLcode socks_proxy_cf_connect(struct Curl_cfilter *cf, | |
| conn->remote_port; | |
| sx->proxy_user = conn->socks_proxy.user; | |
| sx->proxy_password = conn->socks_proxy.passwd; | |
| + | Curl_bufq_init2(&sx->iobuf, SOCKS_CHUNK_SIZE, SOCKS_CHUNKS, |
| + | BUFQ_OPT_SOFT_LIMIT); |
| + | } |
| + | |
| + | switch(conn->socks_proxy.proxytype) { |
| + | case CURLPROXY_SOCKS5: |
| + | case CURLPROXY_SOCKS5_HOSTNAME: |
| + | pxresult = socks5_connect(cf, sx, data); |
| + | break; |
| + | |
| + | case CURLPROXY_SOCKS4: |
| + | case CURLPROXY_SOCKS4A: |
| + | pxresult = socks4_connect(cf, sx, data); |
| + | break; |
| + | |
| + | default: |
| + | failf(data, "unknown proxytype option given"); |
| + | result = CURLE_COULDNT_CONNECT; |
| + | goto out; |
| + | } |
| + | |
| + | if(pxresult) { |
| + | result = CURLE_PROXY; |
| + | data->info.pxcode = pxresult; |
| + | goto out; |
| } | |
| + | else if(sx->state != SOCKS_ST_SUCCESS) |
| + | goto out; |
| - | result = connect_SOCKS(cf, sx, data); |
| - | if(!result && sx->state == CONNECT_DONE) { |
| - | cf->connected = TRUE; |
| #ifndef CURL_DISABLE_VERBOSE_STRINGS | |
| - | if(Curl_trc_is_verbose(data)) { |
| - | struct ip_quadruple ipquad; |
| - | bool is_ipv6; |
| - | result = Curl_conn_cf_get_ip_info(cf->next, data, &is_ipv6, &ipquad); |
| - | if(result) |
| - | return result; |
| - | infof(data, "Opened %sSOCKS connection from %s port %u to %s port %u " |
| - | "(via %s port %u)", |
| - | (sockindex == SECONDARYSOCKET) ? "2nd " : "", |
| - | ipquad.local_ip, ipquad.local_port, |
| - | sx->hostname, sx->remote_port, |
| - | ipquad.remote_ip, ipquad.remote_port); |
| - | } |
| - | #endif |
| - | socks_proxy_cf_free(cf); |
| + | if(Curl_trc_is_verbose(data)) { |
| + | struct ip_quadruple ipquad; |
| + | bool is_ipv6; |
| + | result = Curl_conn_cf_get_ip_info(cf->next, data, &is_ipv6, &ipquad); |
| + | if(result) |
| + | return result; |
| + | infof(data, "Opened %sSOCKS connection from %s port %u to %s port %u " |
| + | "(via %s port %u)", |
| + | (sockindex == SECONDARYSOCKET) ? "2nd " : "", |
| + | ipquad.local_ip, ipquad.local_port, |
| + | sx->hostname, sx->remote_port, |
| + | ipquad.remote_ip, ipquad.remote_port); |
| } | |
| + | #endif |
| + | socks_proxy_cf_free(cf); |
| + | cf->connected = TRUE; |
| + | out: |
| *done = cf->connected; | |
| return result; | |
| } | |
| @@ -1192,15 +1322,16 @@ static CURLcode socks_cf_adjust_pollset(struct Curl_cfilter *cf, | |
| * to wait on, we determine what to wait for. */ | |
| curl_socket_t sock = Curl_conn_cf_get_socket(cf, data); | |
| switch(sx->state) { | |
| - | case CONNECT_RESOLVING: |
| - | case CONNECT_SOCKS_READ: |
| - | case CONNECT_AUTH_READ: |
| - | case CONNECT_REQ_READ: |
| - | case CONNECT_REQ_READ_MORE: |
| - | result = Curl_pollset_set_in_only(data, ps, sock); |
| + | case SOCKS4_ST_SEND: |
| + | case SOCKS5_ST_REQ0_SEND: |
| + | case SOCKS5_ST_AUTH_SEND: |
| + | case SOCKS5_ST_REQ1_SEND: |
| + | CURL_TRC_CF(data, cf, "adjust pollset out (%d)", sx->state); |
| + | result = Curl_pollset_set_out_only(data, ps, sock); |
| break; | |
| default: | |
| - | result = Curl_pollset_set_out_only(data, ps, sock); |
| + | CURL_TRC_CF(data, cf, "adjust pollset in (%d)", sx->state); |
| + | result = Curl_pollset_set_in_only(data, ps, sock); |
| break; | |
| } | |
| } | |
| @@ -23,6 +23,7 @@ | |
| ***************************************************************************/ | |
| #include "first.h" | |
| + | #include "testtrace.h" |
| #include "memdebug.h" | |
| static CURLcode test_lib564(const char *URL) | |
| @@ -32,6 +33,9 @@ static CURLcode test_lib564(const char *URL) | |
| int running; | |
| CURLM *m = NULL; | |
| + | debug_config.nohex = TRUE; |
| + | debug_config.tracetime = TRUE; |
| + | |
| start_test_timing(); | |
| global_init(CURL_GLOBAL_ALL); | |
| @@ -39,6 +43,8 @@ static CURLcode test_lib564(const char *URL) | |
| easy_init(curl); | |
| easy_setopt(curl, CURLOPT_URL, URL); | |
| + | easy_setopt(curl, CURLOPT_DEBUGDATA, &debug_config); |
| + | easy_setopt(curl, CURLOPT_DEBUGFUNCTION, libtest_debug_cb); |
| easy_setopt(curl, CURLOPT_VERBOSE, 1L); | |
| easy_setopt(curl, CURLOPT_PROXY, libtest_arg2); | |
| easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4); | |