From 4a4b63daaa01ef59b131d91e8e6e6dfe275c0f08 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Fri, 14 Feb 2020 16:16:54 +0100 Subject: [PATCH] socks: make the connect phase non-blocking Removes two entries from KNOWN_BUGS. Closes #4907 --- docs/KNOWN_BUGS | 17 - docs/TODO | 1 - lib/connect.c | 81 ++-- lib/ftp.c | 9 +- lib/hostip.c | 24 +- lib/hostip.h | 27 +- lib/multi.c | 4 + lib/sendf.c | 11 +- lib/socks.c | 1158 ++++++++++++++++++++++++++------------------ lib/socks.h | 15 +- lib/socks_gssapi.c | 6 +- lib/socks_sspi.c | 5 +- lib/urldata.h | 38 +- 13 files changed, 830 insertions(+), 566 deletions(-) diff --git a/docs/KNOWN_BUGS b/docs/KNOWN_BUGS index 60221ff7b..351eca29f 100644 --- a/docs/KNOWN_BUGS +++ b/docs/KNOWN_BUGS @@ -87,8 +87,6 @@ problems may have been fixed or changed somewhat since this was written! 9.1 SFTP doesn't do CURLOPT_POSTQUOTE correct 10. SOCKS - 10.1 SOCKS proxy connections are done blocking - 10.2 SOCKS don't support timeouts 10.3 FTPS over SOCKS 10.4 active FTP over a SOCKS @@ -621,21 +619,6 @@ problems may have been fixed or changed somewhat since this was written! 10. SOCKS -10.1 SOCKS proxy connections are done blocking - - Both SOCKS5 and SOCKS4 proxy connections are done blocking, which is very bad - when used with the multi interface. - -10.2 SOCKS don't support timeouts - - The SOCKS4 connection codes don't properly acknowledge (connect) timeouts. - According to bug #1556528, even the SOCKS5 connect code does not do it right: - https://curl.haxx.se/bug/view.cgi?id=604 - - When connecting to a SOCK proxy, the (connect) timeout is not properly - acknowledged after the actual TCP connect (during the SOCKS "negotiate" - phase). - 10.3 FTPS over SOCKS libcurl doesn't support FTPS over a SOCKS proxy. diff --git a/docs/TODO b/docs/TODO index 4ed23b456..1d43e7f63 100644 --- a/docs/TODO +++ b/docs/TODO @@ -405,7 +405,6 @@ EWOULDBLOCK or similar. Blocking cases include: - Name resolves on non-windows unless c-ares or the threaded resolver is used - - SOCKS proxy handshakes - file:// transfers - TELNET transfers - The "DONE" operation (post transfer protocol-specific actions) for the diff --git a/lib/connect.c b/lib/connect.c index 9baadb565..0cfd6886f 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -745,58 +745,82 @@ void Curl_updateconninfo(struct connectdata *conn, curl_socket_t sockfd) Curl_persistconninfo(conn); } -/* after a TCP connection to the proxy has been verified, this function does - the next magic step. +/* After a TCP connection to the proxy has been verified, this function does + the next magic steps. If 'done' isn't set TRUE, it is not done yet and + must be called again. Note: this function's sub-functions call failf() */ -static CURLcode connected_proxy(struct connectdata *conn, int sockindex) +static CURLcode connect_SOCKS(struct connectdata *conn, int sockindex, + bool *done) { CURLcode result = CURLE_OK; + infof(conn->data, "connect_SOCKS is called [socks state %d]\n", + conn->cnnct.state); + if(conn->bits.socksproxy) { #ifndef CURL_DISABLE_PROXY /* for the secondary socket (FTP), use the "connect to host" * but ignore the "connect to port" (use the secondary port) */ - const char * const host = conn->bits.httpproxy ? - conn->http_proxy.host.name : - conn->bits.conn_to_host ? - conn->conn_to_host.name : - sockindex == SECONDARYSOCKET ? - conn->secondaryhostname : conn->host.name; - const int port = conn->bits.httpproxy ? (int)conn->http_proxy.port : - sockindex == SECONDARYSOCKET ? conn->secondary_port : - conn->bits.conn_to_port ? conn->conn_to_port : - conn->remote_port; - conn->bits.socksproxy_connecting = TRUE; + const char * const host = + conn->bits.httpproxy ? + conn->http_proxy.host.name : + conn->bits.conn_to_host ? + conn->conn_to_host.name : + sockindex == SECONDARYSOCKET ? + conn->secondaryhostname : conn->host.name; + const int port = + conn->bits.httpproxy ? (int)conn->http_proxy.port : + sockindex == SECONDARYSOCKET ? conn->secondary_port : + conn->bits.conn_to_port ? conn->conn_to_port : + conn->remote_port; switch(conn->socks_proxy.proxytype) { case CURLPROXY_SOCKS5: case CURLPROXY_SOCKS5_HOSTNAME: result = Curl_SOCKS5(conn->socks_proxy.user, conn->socks_proxy.passwd, - host, port, sockindex, conn); + host, port, sockindex, conn, done); break; case CURLPROXY_SOCKS4: case CURLPROXY_SOCKS4A: result = Curl_SOCKS4(conn->socks_proxy.user, host, port, sockindex, - conn); + conn, done); break; default: failf(conn->data, "unknown proxytype option given"); result = CURLE_COULDNT_CONNECT; } /* switch proxytype */ - conn->bits.socksproxy_connecting = FALSE; #else (void)sockindex; #endif /* CURL_DISABLE_PROXY */ } + else + *done = TRUE; /* no SOCKS proxy, so consider us connected */ return result; } +/* + * post_SOCKS() is called after a successful connect to the peer, which + * *could* be a SOCKS proxy + */ +static void post_SOCKS(struct connectdata *conn, + int sockindex, + bool *connected) +{ + conn->bits.tcpconnect[sockindex] = TRUE; + + *connected = TRUE; + if(sockindex == FIRSTSOCKET) + Curl_pgrsTime(conn->data, TIMER_CONNECT); /* connect done */ + Curl_updateconninfo(conn, conn->sock[sockindex]); + Curl_verboseconnect(conn); +} + /* * Curl_is_connected() checks if the socket has connected. */ @@ -834,6 +858,14 @@ CURLcode Curl_is_connected(struct connectdata *conn, return CURLE_OPERATION_TIMEDOUT; } + if(SOCKS_STATE(conn->cnnct.state)) { + /* still doing SOCKS */ + result = connect_SOCKS(conn, sockindex, connected); + if(!result && *connected) + post_SOCKS(conn, sockindex, connected); + return result; + } + for(i = 0; i<2; i++) { const int other = i ^ 1; if(conn->tempsock[i] == CURL_SOCKET_BAD) @@ -900,18 +932,13 @@ CURLcode Curl_is_connected(struct connectdata *conn, conn->tempsock[other] = CURL_SOCKET_BAD; } - /* see if we need to do any proxy magic first once we connected */ - result = connected_proxy(conn, sockindex); - if(result) + /* see if we need to kick off any SOCKS proxy magic once we + connected */ + result = connect_SOCKS(conn, sockindex, connected); + if(result || !*connected) return result; - conn->bits.tcpconnect[sockindex] = TRUE; - - *connected = TRUE; - if(sockindex == FIRSTSOCKET) - Curl_pgrsTime(data, TIMER_CONNECT); /* connect done */ - Curl_updateconninfo(conn, conn->sock[sockindex]); - Curl_verboseconnect(conn); + post_SOCKS(conn, sockindex, connected); return CURLE_OK; } diff --git a/lib/ftp.c b/lib/ftp.c index 537fd82d1..57b22ade9 100644 --- a/lib/ftp.c +++ b/lib/ftp.c @@ -55,7 +55,6 @@ #include "transfer.h" #include "escape.h" #include "http.h" /* for HTTP proxy tunnel stuff */ -#include "socks.h" #include "ftp.h" #include "fileinfo.h" #include "ftplistparser.h" @@ -78,6 +77,7 @@ #include "warnless.h" #include "http_proxy.h" #include "non-ascii.h" +#include "socks.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" @@ -810,6 +810,9 @@ static int ftp_domore_getsock(struct connectdata *conn, curl_socket_t *socks) * handle ordinary commands. */ + if(SOCKS_STATE(conn->cnnct.state)) + return Curl_SOCKS_getsock(conn, socks, SECONDARYSOCKET); + if(FTP_STOP == ftpc->state) { int bits = GETSOCK_READSOCK(0); @@ -919,7 +922,7 @@ static CURLcode ftp_state_use_port(struct connectdata *conn, struct sockaddr_in6 * const sa6 = (void *)sa; #endif static const char mode[][5] = { "EPRT", "PORT" }; - int rc; + enum resolve_t rc; int error; char *host = NULL; char *string_ftpport = data->set.str[STRING_FTPPORT]; @@ -1794,7 +1797,7 @@ static CURLcode ftp_state_pasv_resp(struct connectdata *conn, CURLcode result; struct Curl_easy *data = conn->data; struct Curl_dns_entry *addr = NULL; - int rc; + enum resolve_t rc; unsigned short connectport; /* the local port connect() should use! */ char *str = &data->state.buffer[4]; /* start on the first letter */ diff --git a/lib/hostip.c b/lib/hostip.c index 9c0464ceb..c0feb79fb 100644 --- a/lib/hostip.c +++ b/lib/hostip.c @@ -483,16 +483,16 @@ Curl_cache_addr(struct Curl_easy *data, * CURLRESOLV_PENDING (1) = waiting for response, no pointer */ -int Curl_resolv(struct connectdata *conn, - const char *hostname, - int port, - bool allowDOH, - struct Curl_dns_entry **entry) +enum resolve_t Curl_resolv(struct connectdata *conn, + const char *hostname, + int port, + bool allowDOH, + struct Curl_dns_entry **entry) { struct Curl_dns_entry *dns = NULL; struct Curl_easy *data = conn->data; CURLcode result; - int rc = CURLRESOLV_ERROR; /* default to failure */ + enum resolve_t rc = CURLRESOLV_ERROR; /* default to failure */ *entry = NULL; @@ -642,11 +642,11 @@ RETSIGTYPE alarmfunc(int sig) * CURLRESOLV_PENDING (1) = waiting for response, no pointer */ -int Curl_resolv_timeout(struct connectdata *conn, - const char *hostname, - int port, - struct Curl_dns_entry **entry, - timediff_t timeoutms) +enum resolve_t Curl_resolv_timeout(struct connectdata *conn, + const char *hostname, + int port, + struct Curl_dns_entry **entry, + timediff_t timeoutms) { #ifdef USE_ALARM_TIMEOUT #ifdef HAVE_SIGACTION @@ -662,7 +662,7 @@ int Curl_resolv_timeout(struct connectdata *conn, volatile unsigned int prev_alarm = 0; struct Curl_easy *data = conn->data; #endif /* USE_ALARM_TIMEOUT */ - int rc; + enum resolve_t rc; *entry = NULL; diff --git a/lib/hostip.h b/lib/hostip.h index a1c343ad7..baf1e5860 100644 --- a/lib/hostip.h +++ b/lib/hostip.h @@ -79,18 +79,21 @@ struct Curl_dns_entry { * use, or we'll leak memory! */ /* return codes */ -#define CURLRESOLV_TIMEDOUT -2 -#define CURLRESOLV_ERROR -1 -#define CURLRESOLV_RESOLVED 0 -#define CURLRESOLV_PENDING 1 -int Curl_resolv(struct connectdata *conn, - const char *hostname, - int port, - bool allowDOH, - struct Curl_dns_entry **dnsentry); -int Curl_resolv_timeout(struct connectdata *conn, const char *hostname, - int port, struct Curl_dns_entry **dnsentry, - timediff_t timeoutms); +enum resolve_t { + CURLRESOLV_TIMEDOUT = -2, + CURLRESOLV_ERROR = -1, + CURLRESOLV_RESOLVED = 0, + CURLRESOLV_PENDING = 1 +}; +enum resolve_t Curl_resolv(struct connectdata *conn, + const char *hostname, + int port, + bool allowDOH, + struct Curl_dns_entry **dnsentry); +enum resolve_t Curl_resolv_timeout(struct connectdata *conn, + const char *hostname, int port, + struct Curl_dns_entry **dnsentry, + timediff_t timeoutms); #ifdef CURLRES_IPV6 /* diff --git a/lib/multi.c b/lib/multi.c index 51154d6b2..ef86f7c22 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -47,6 +47,7 @@ #include "http_proxy.h" #include "http2.h" #include "socketpair.h" +#include "socks.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" @@ -856,6 +857,9 @@ static int waitconnect_getsock(struct connectdata *conn, return Curl_ssl_getsock(conn, sock); #endif + if(SOCKS_STATE(conn->cnnct.state)) + return Curl_SOCKS_getsock(conn, sock, FIRSTSOCKET); + for(i = 0; i<2; i++) { if(conn->tempsock[i] != CURL_SOCKET_BAD) { sock[s] = conn->tempsock[i]; diff --git a/lib/sendf.c b/lib/sendf.c index 62071c4f2..51bbca75e 100644 --- a/lib/sendf.c +++ b/lib/sendf.c @@ -692,19 +692,20 @@ CURLcode Curl_read_plain(curl_socket_t sockfd, ssize_t nread = sread(sockfd, buf, bytesfromsocket); if(-1 == nread) { - int err = SOCKERRNO; - int return_error; + const int err = SOCKERRNO; + const bool return_error = #ifdef USE_WINSOCK - return_error = WSAEWOULDBLOCK == err; + WSAEWOULDBLOCK == err #else - return_error = EWOULDBLOCK == err || EAGAIN == err || EINTR == err; + EWOULDBLOCK == err || EAGAIN == err || EINTR == err #endif + ; + *n = 0; /* no data returned */ if(return_error) return CURLE_AGAIN; return CURLE_RECV_ERROR; } - /* we only return number of bytes read when we return OK */ *n = nread; return CURLE_OK; } diff --git a/lib/socks.c b/lib/socks.c index 6ae98184d..ba05602be 100644 --- a/lib/socks.c +++ b/lib/socks.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2019, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2020, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -37,18 +37,19 @@ #include "connect.h" #include "timeval.h" #include "socks.h" +#include "multiif.h" /* for getsock macros */ /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) /* * Helper read-from-socket functions. Does the same as Curl_read() but it * blocks until all bytes amount of buffersize will be read. No more, no less. * - * This is STUPID BLOCKING behaviour which we frown upon, but right now this - * is what we have... + * This is STUPID BLOCKING behavior. Only used by the SOCKS GSSAPI functions. */ int Curl_blockread_all(struct connectdata *conn, /* connection data */ curl_socket_t sockfd, /* read from this socket */ @@ -94,6 +95,81 @@ int Curl_blockread_all(struct connectdata *conn, /* connection data */ } return result; } +#endif + +#ifndef DEBUGBUILD +#define sxstate(x,y) socksstate(x,y) +#else +#define sxstate(x,y) socksstate(x,y, __LINE__) +#endif + + +/* always use this function to change state, to make debugging easier */ +static void socksstate(struct connectdata *conn, + enum connect_t state +#ifdef DEBUGBUILD + , int lineno +#endif +) +{ + enum connect_t oldstate = conn->cnnct.state; +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + /* synced with the state list in urldata.h */ + static const char * const 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 + + if(oldstate == state) + /* don't bother when the new state is the same as the old state */ + return; + + conn->cnnct.state = state; + +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + infof(conn->data, + "SXSTATE: %s => %s conn %p; line %d\n", + statename[oldstate], statename[conn->cnnct.state], conn, + lineno); +#endif +} + +int Curl_SOCKS_getsock(struct connectdata *conn, curl_socket_t *sock, + int sockindex) +{ + int rc = 0; + sock[0] = conn->sock[sockindex]; + switch(conn->cnnct.state) { + case CONNECT_RESOLVING: + case CONNECT_SOCKS_READ: + case CONNECT_AUTH_READ: + case CONNECT_REQ_READ: + case CONNECT_REQ_READ_MORE: + rc = GETSOCK_READSOCK(0); + break; + default: + rc = GETSOCK_WRITESOCK(0); + break; + } + return rc; +} /* * This function logs in to a SOCKS4 proxy and sends the specifics to the final @@ -110,62 +186,89 @@ CURLcode Curl_SOCKS4(const char *proxy_user, const char *hostname, int remote_port, int sockindex, - struct connectdata *conn) + struct connectdata *conn, + bool *done) { const bool protocol4a = (conn->socks_proxy.proxytype == CURLPROXY_SOCKS4A) ? TRUE : FALSE; -#define SOCKS4REQLEN 262 - unsigned char socksreq[SOCKS4REQLEN]; /* room for SOCKS4 request incl. user - id */ - CURLcode code; - curl_socket_t sock = conn->sock[sockindex]; + unsigned char *socksreq = &conn->cnnct.socksreq[0]; + CURLcode result; + curl_socket_t sockfd = conn->sock[sockindex]; struct Curl_easy *data = conn->data; + struct connstate *sx = &conn->cnnct; + struct Curl_dns_entry *dns = NULL; + ssize_t actualread; + ssize_t written; - if(Curl_timeleft(data, NULL, TRUE) < 0) { - /* time-out, bail out, go home */ - failf(data, "Connection time-out"); - return CURLE_OPERATION_TIMEDOUT; - } + if(!SOCKS_STATE(sx->state) && !*done) + sxstate(conn, CONNECT_SOCKS_INIT); - if(conn->bits.httpproxy) - infof(conn->data, "SOCKS4%s: connecting to HTTP proxy %s port %d\n", - protocol4a ? "a" : "", hostname, remote_port); + switch(sx->state) { + case CONNECT_SOCKS_INIT: + if(conn->bits.httpproxy) + infof(conn->data, "SOCKS4%s: connecting to HTTP proxy %s port %d\n", + protocol4a ? "a" : "", hostname, remote_port); - (void)curlx_nonblock(sock, FALSE); + infof(data, "SOCKS4 communication to %s:%d\n", hostname, remote_port); - infof(data, "SOCKS4 communication to %s:%d\n", hostname, remote_port); + /* + * Compose socks4 request + * + * Request format + * + * +----+----+----+----+----+----+----+----+----+----+....+----+ + * | VN | CD | DSTPORT | DSTIP | USERID |NULL| + * +----+----+----+----+----+----+----+----+----+----+....+----+ + * # of bytes: 1 1 2 4 variable 1 + */ - /* - * Compose socks4 request - * - * Request format - * - * +----+----+----+----+----+----+----+----+----+----+....+----+ - * | VN | CD | DSTPORT | DSTIP | USERID |NULL| - * +----+----+----+----+----+----+----+----+----+----+....+----+ - * # of bytes: 1 1 2 4 variable 1 - */ + socksreq[0] = 4; /* version (SOCKS4) */ + socksreq[1] = 1; /* connect */ + socksreq[2] = (unsigned char)((remote_port >> 8) & 0xff); /* PORT MSB */ + socksreq[3] = (unsigned char)(remote_port & 0xff); /* PORT LSB */ - socksreq[0] = 4; /* version (SOCKS4) */ - socksreq[1] = 1; /* connect */ - socksreq[2] = (unsigned char)((remote_port >> 8) & 0xff); /* PORT MSB */ - socksreq[3] = (unsigned char)(remote_port & 0xff); /* PORT LSB */ + /* DNS resolve only for SOCKS4, not SOCKS4a */ + if(!protocol4a) { + enum resolve_t rc = + Curl_resolv(conn, hostname, remote_port, FALSE, &dns); - /* DNS resolve only for SOCKS4, not SOCKS4a */ - if(!protocol4a) { - struct Curl_dns_entry *dns; + if(rc == CURLRESOLV_ERROR) + return CURLE_COULDNT_RESOLVE_PROXY; + else if(rc == CURLRESOLV_PENDING) { + sxstate(conn, CONNECT_RESOLVING); + infof(data, "SOCKS4 non-blocking resolve of %s\n", hostname); + return CURLE_OK; + } + sxstate(conn, CONNECT_RESOLVED); + goto CONNECT_RESOLVED; + } + + /* socks4a doesn't resolve anything locally */ + sxstate(conn, CONNECT_REQ_INIT); + goto CONNECT_REQ_INIT; + + case CONNECT_RESOLVING: + /* check if we have the name resolved by now */ + dns = Curl_fetch_addr(conn, hostname, (int)conn->port); + + if(dns) { +#ifdef CURLRES_ASYNCH + conn->async.dns = dns; + conn->async.done = TRUE; +#endif + infof(data, "Hostname '%s' was found\n", hostname); + sxstate(conn, CONNECT_RESOLVED); + } + else { + result = Curl_resolv_check(data->conn, &dns); + /* stay in the state or error out */ + return result; + } + /* FALLTHROUGH */ + CONNECT_RESOLVED: + case CONNECT_RESOLVED: { Curl_addrinfo *hp = NULL; - int rc; - - rc = Curl_resolv(conn, hostname, remote_port, FALSE, &dns); - - if(rc == CURLRESOLV_ERROR) - return CURLE_COULDNT_RESOLVE_PROXY; - - if(rc == CURLRESOLV_PENDING) - /* ignores the return code, but 'dns' remains NULL on failure */ - (void)Curl_resolver_wait_resolv(conn, &dns); - + char buf[64]; /* * We cannot use 'hostent' as a struct that Curl_resolv() returns. It * returns a Curl_addrinfo pointer that may not always look the same. @@ -173,7 +276,6 @@ CURLcode Curl_SOCKS4(const char *proxy_user, if(dns) hp = dns->addr; if(hp) { - char buf[64]; Curl_printable_address(hp, buf, sizeof(buf)); if(hp->ai_family == AF_INET) { @@ -189,7 +291,6 @@ CURLcode Curl_SOCKS4(const char *proxy_user, } else { hp = NULL; /* fail! */ - failf(data, "SOCKS4 connection to %s not supported\n", buf); } @@ -201,149 +302,166 @@ CURLcode Curl_SOCKS4(const char *proxy_user, return CURLE_COULDNT_RESOLVE_HOST; } } - - /* - * This is currently not supporting "Identification Protocol (RFC1413)". - */ - socksreq[8] = 0; /* ensure empty userid is NUL-terminated */ - if(proxy_user) { - size_t plen = strlen(proxy_user); - if(plen >= sizeof(socksreq) - 8) { - failf(data, "Too long SOCKS proxy name, can't use!\n"); - return CURLE_COULDNT_CONNECT; - } - /* copy the proxy name WITH trailing zero */ - memcpy(socksreq + 8, proxy_user, plen + 1); - } - - /* - * Make connection - */ - { - int result; - ssize_t actualread; - ssize_t written; - ssize_t hostnamelen = 0; - ssize_t packetsize = 9 + - strlen((char *)socksreq + 8); /* size including NUL */ - - /* If SOCKS4a, set special invalid IP address 0.0.0.x */ - if(protocol4a) { - socksreq[4] = 0; - socksreq[5] = 0; - socksreq[6] = 0; - socksreq[7] = 1; - /* If still enough room in buffer, also append hostname */ - hostnamelen = (ssize_t)strlen(hostname) + 1; /* length including NUL */ - if(packetsize + hostnamelen <= SOCKS4REQLEN) - strcpy((char *)socksreq + packetsize, hostname); - else - hostnamelen = 0; /* Flag: hostname did not fit in buffer */ - } - - /* Send request */ - code = Curl_write_plain(conn, sock, (char *)socksreq, - packetsize + hostnamelen, - &written); - if(code || (written != packetsize + hostnamelen)) { - failf(data, "Failed to send SOCKS4 connect request."); - return CURLE_COULDNT_CONNECT; - } - if(protocol4a && hostnamelen == 0) { - /* SOCKS4a with very long hostname - send that name separately */ - hostnamelen = (ssize_t)strlen(hostname) + 1; - code = Curl_write_plain(conn, sock, (char *)hostname, hostnamelen, - &written); - if(code || (written != hostnamelen)) { - failf(data, "Failed to send SOCKS4 connect request."); + /* FALLTHROUGH */ + CONNECT_REQ_INIT: + case CONNECT_REQ_INIT: + /* + * This is currently not supporting "Identification Protocol (RFC1413)". + */ + socksreq[8] = 0; /* ensure empty userid is NUL-terminated */ + if(proxy_user) { + size_t plen = strlen(proxy_user); + if(plen >= sizeof(sx->socksreq) - 8) { + failf(data, "Too long SOCKS proxy name, can't use!\n"); return CURLE_COULDNT_CONNECT; } - } - - packetsize = 8; /* receive data size */ - - /* Receive response */ - result = Curl_blockread_all(conn, sock, (char *)socksreq, packetsize, - &actualread); - if(result || (actualread != packetsize)) { - failf(data, "Failed to receive SOCKS4 connect request ack."); - return CURLE_COULDNT_CONNECT; + /* copy the proxy name WITH trailing zero */ + memcpy(socksreq + 8, proxy_user, plen + 1); } /* - * Response format - * - * +----+----+----+----+----+----+----+----+ - * | VN | CD | DSTPORT | DSTIP | - * +----+----+----+----+----+----+----+----+ - * # of bytes: 1 1 2 4 - * - * VN is the version of the reply code and should be 0. CD is the result - * code with one of the following values: - * - * 90: request granted - * 91: request rejected or failed - * 92: request rejected because SOCKS server cannot connect to - * identd on the client - * 93: request rejected because the client program and identd - * report different user-ids + * Make connection */ + { + ssize_t packetsize = 9 + + strlen((char *)socksreq + 8); /* size including NUL */ - /* wrong version ? */ - if(socksreq[0] != 0) { - failf(data, - "SOCKS4 reply has wrong version, version should be 0."); + /* If SOCKS4a, set special invalid IP address 0.0.0.x */ + if(protocol4a) { + ssize_t hostnamelen = 0; + socksreq[4] = 0; + socksreq[5] = 0; + socksreq[6] = 0; + socksreq[7] = 1; + /* append hostname */ + hostnamelen = (ssize_t)strlen(hostname) + 1; /* length including NUL */ + if(hostnamelen <= 255) + strcpy((char *)socksreq + packetsize, hostname); + else { + failf(data, "SOCKS4: too long host name"); + return CURLE_COULDNT_CONNECT; + } + packetsize += hostnamelen; + } + sx->outp = socksreq; + sx->outstanding = packetsize; + sxstate(conn, CONNECT_REQ_SENDING); + } + /* FALLTHROUGH */ + case CONNECT_REQ_SENDING: + /* Send request */ + result = Curl_write_plain(conn, sockfd, (char *)sx->outp, + sx->outstanding, &written); + if(result && (CURLE_AGAIN != result)) { + failf(data, "Failed to send SOCKS4 connect request."); return CURLE_COULDNT_CONNECT; } + if(written != sx->outstanding) { + /* not done, remain in state */ + sx->outstanding -= written; + sx->outp += written; + return CURLE_OK; + } - /* Result */ - switch(socksreq[1]) { - case 90: - infof(data, "SOCKS4%s request granted.\n", protocol4a?"a":""); - break; - case 91: - failf(data, - "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" - ", request rejected or failed.", - (unsigned char)socksreq[4], (unsigned char)socksreq[5], - (unsigned char)socksreq[6], (unsigned char)socksreq[7], - (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]), - (unsigned char)socksreq[1]); - return CURLE_COULDNT_CONNECT; - case 92: - failf(data, - "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" - ", request rejected because SOCKS server cannot connect to " - "identd on the client.", - (unsigned char)socksreq[4], (unsigned char)socksreq[5], - (unsigned char)socksreq[6], (unsigned char)socksreq[7], - (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]), - (unsigned char)socksreq[1]); - return CURLE_COULDNT_CONNECT; - case 93: - failf(data, - "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" - ", request rejected because the client program and identd " - "report different user-ids.", - (unsigned char)socksreq[4], (unsigned char)socksreq[5], - (unsigned char)socksreq[6], (unsigned char)socksreq[7], - (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]), - (unsigned char)socksreq[1]); - return CURLE_COULDNT_CONNECT; - default: - failf(data, - "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" - ", Unknown.", - (unsigned char)socksreq[4], (unsigned char)socksreq[5], - (unsigned char)socksreq[6], (unsigned char)socksreq[7], - (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]), - (unsigned char)socksreq[1]); + /* done sending! */ + sx->outstanding = 8; /* receive data size */ + sx->outp = socksreq; + sxstate(conn, CONNECT_SOCKS_READ); + + /* FALLTHROUGH */ + case CONNECT_SOCKS_READ: + /* Receive response */ + result = Curl_read_plain(sockfd, (char *)sx->outp, + sx->outstanding, &actualread); + if(result && (CURLE_AGAIN != result)) { + failf(data, "SOCKS4: Failed receiving connect request ack: %s", + curl_easy_strerror(result)); return CURLE_COULDNT_CONNECT; } + else if(actualread != sx->outstanding) { + /* remain in reading state */ + sx->outstanding -= actualread; + sx->outp += actualread; + return CURLE_OK; + } + sxstate(conn, CONNECT_DONE); + break; + default: /* lots of unused states in SOCKS4 */ + break; } - (void)curlx_nonblock(sock, TRUE); + /* + * Response format + * + * +----+----+----+----+----+----+----+----+ + * | VN | CD | DSTPORT | DSTIP | + * +----+----+----+----+----+----+----+----+ + * # of bytes: 1 1 2 4 + * + * VN is the version of the reply code and should be 0. CD is the result + * code with one of the following values: + * + * 90: request granted + * 91: request rejected or failed + * 92: request rejected because SOCKS server cannot connect to + * identd on the client + * 93: request rejected because the client program and identd + * report different user-ids + */ + /* wrong version ? */ + if(socksreq[0] != 0) { + failf(data, + "SOCKS4 reply has wrong version, version should be 0."); + return CURLE_COULDNT_CONNECT; + } + + /* Result */ + switch(socksreq[1]) { + case 90: + infof(data, "SOCKS4%s request granted.\n", protocol4a?"a":""); + break; + case 91: + failf(data, + "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" + ", request rejected or failed.", + (unsigned char)socksreq[4], (unsigned char)socksreq[5], + (unsigned char)socksreq[6], (unsigned char)socksreq[7], + (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]), + (unsigned char)socksreq[1]); + return CURLE_COULDNT_CONNECT; + case 92: + failf(data, + "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" + ", request rejected because SOCKS server cannot connect to " + "identd on the client.", + (unsigned char)socksreq[4], (unsigned char)socksreq[5], + (unsigned char)socksreq[6], (unsigned char)socksreq[7], + (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]), + (unsigned char)socksreq[1]); + return CURLE_COULDNT_CONNECT; + case 93: + failf(data, + "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" + ", request rejected because the client program and identd " + "report different user-ids.", + (unsigned char)socksreq[4], (unsigned char)socksreq[5], + (unsigned char)socksreq[6], (unsigned char)socksreq[7], + (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]), + (unsigned char)socksreq[1]); + return CURLE_COULDNT_CONNECT; + default: + failf(data, + "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" + ", Unknown.", + (unsigned char)socksreq[4], (unsigned char)socksreq[5], + (unsigned char)socksreq[6], (unsigned char)socksreq[7], + (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]), + (unsigned char)socksreq[1]); + return CURLE_COULDNT_CONNECT; + } + + *done = TRUE; return CURLE_OK; /* Proxy was successful! */ } @@ -356,7 +474,8 @@ CURLcode Curl_SOCKS5(const char *proxy_user, const char *hostname, int remote_port, int sockindex, - struct connectdata *conn) + struct connectdata *conn, + bool *done) { /* According to the RFC1928, section "6. Replies". This is what a SOCK5 @@ -374,141 +493,158 @@ CURLcode Curl_SOCKS5(const char *proxy_user, o REP Reply field: o X'00' succeeded */ -#define REQUEST_BUFSIZE 600 /* room for large user/pw (255 max each) */ - unsigned char socksreq[REQUEST_BUFSIZE]; - char dest[REQUEST_BUFSIZE] = "unknown"; /* printable hostname:port */ + unsigned char *socksreq = &conn->cnnct.socksreq[0]; + char dest[256] = "unknown"; /* printable hostname:port */ int idx; ssize_t actualread; ssize_t written; - int result; - CURLcode code; - curl_socket_t sock = conn->sock[sockindex]; + CURLcode result; + curl_socket_t sockfd = conn->sock[sockindex]; struct Curl_easy *data = conn->data; - timediff_t timeout; bool socks5_resolve_local = (conn->socks_proxy.proxytype == CURLPROXY_SOCKS5) ? TRUE : FALSE; const size_t hostname_len = strlen(hostname); ssize_t len = 0; const unsigned long auth = data->set.socks5auth; bool allow_gssapi = FALSE; + struct connstate *sx = &conn->cnnct; + struct Curl_dns_entry *dns = NULL; - if(conn->bits.httpproxy) - infof(conn->data, "SOCKS5: connecting to HTTP proxy %s port %d\n", - hostname, remote_port); + if(!SOCKS_STATE(sx->state) && !*done) + sxstate(conn, CONNECT_SOCKS_INIT); - /* RFC1928 chapter 5 specifies max 255 chars for domain name in packet */ - if(!socks5_resolve_local && hostname_len > 255) { - infof(conn->data, "SOCKS5: server resolving disabled for hostnames of " - "length > 255 [actual len=%zu]\n", hostname_len); - socks5_resolve_local = TRUE; - } + switch(sx->state) { + case CONNECT_SOCKS_INIT: + if(conn->bits.httpproxy) + infof(conn->data, "SOCKS5: connecting to HTTP proxy %s port %d\n", + hostname, remote_port); - /* get timeout */ - timeout = Curl_timeleft(data, NULL, TRUE); + /* RFC1928 chapter 5 specifies max 255 chars for domain name in packet */ + if(!socks5_resolve_local && hostname_len > 255) { + infof(conn->data, "SOCKS5: server resolving disabled for hostnames of " + "length > 255 [actual len=%zu]\n", hostname_len); + socks5_resolve_local = TRUE; + } - if(timeout < 0) { - /* time-out, bail out, go home */ - failf(data, "Connection time-out"); - return CURLE_OPERATION_TIMEDOUT; - } - - (void)curlx_nonblock(sock, TRUE); - - /* wait until socket gets connected */ - result = SOCKET_WRITABLE(sock, timeout); - - if(-1 == result) { - failf(conn->data, "SOCKS5: no connection here"); - return CURLE_COULDNT_CONNECT; - } - if(0 == result) { - failf(conn->data, "SOCKS5: connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - if(result & CURL_CSELECT_ERR) { - failf(conn->data, "SOCKS5: error occurred during connection"); - return CURLE_COULDNT_CONNECT; - } - - if(auth & ~(CURLAUTH_BASIC | CURLAUTH_GSSAPI)) - infof(conn->data, - "warning: unsupported value passed to CURLOPT_SOCKS5_AUTH: %lu\n", - auth); - if(!(auth & CURLAUTH_BASIC)) - /* disable username/password auth */ - proxy_user = NULL; + if(auth & ~(CURLAUTH_BASIC | CURLAUTH_GSSAPI)) + infof(conn->data, + "warning: unsupported value passed to CURLOPT_SOCKS5_AUTH: %lu\n", + auth); + if(!(auth & CURLAUTH_BASIC)) + /* disable username/password auth */ + proxy_user = NULL; #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) - if(auth & CURLAUTH_GSSAPI) - allow_gssapi = TRUE; + if(auth & CURLAUTH_GSSAPI) + allow_gssapi = TRUE; #endif - idx = 0; - socksreq[idx++] = 5; /* version */ - idx++; /* reserve for the number of authentication methods */ - socksreq[idx++] = 0; /* no authentication */ - if(allow_gssapi) - socksreq[idx++] = 1; /* GSS-API */ - if(proxy_user) - socksreq[idx++] = 2; /* username/password */ - /* write the number of authentication methods */ - socksreq[1] = (unsigned char) (idx - 2); + idx = 0; + socksreq[idx++] = 5; /* version */ + idx++; /* number of authentication methods */ + socksreq[idx++] = 0; /* no authentication */ + if(allow_gssapi) + socksreq[idx++] = 1; /* GSS-API */ + if(proxy_user) + socksreq[idx++] = 2; /* username/password */ + /* write the number of authentication methods */ + socksreq[1] = (unsigned char) (idx - 2); - (void)curlx_nonblock(sock, FALSE); - - infof(data, "SOCKS5 communication to %s:%d\n", hostname, remote_port); - - code = Curl_write_plain(conn, sock, (char *)socksreq, (2 + (int)socksreq[1]), - &written); - if(code || (written != (2 + (int)socksreq[1]))) { - failf(data, "Unable to send initial SOCKS5 request."); - return CURLE_COULDNT_CONNECT; - } - - (void)curlx_nonblock(sock, TRUE); - - result = SOCKET_READABLE(sock, timeout); - - if(-1 == result) { - failf(conn->data, "SOCKS5 nothing to read"); - return CURLE_COULDNT_CONNECT; - } - if(0 == result) { - failf(conn->data, "SOCKS5 read timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - if(result & CURL_CSELECT_ERR) { - failf(conn->data, "SOCKS5 read error occurred"); - return CURLE_RECV_ERROR; - } - - (void)curlx_nonblock(sock, FALSE); - - result = Curl_blockread_all(conn, sock, (char *)socksreq, 2, &actualread); - if(result || (actualread != 2)) { - failf(data, "Unable to receive initial SOCKS5 response."); - return CURLE_COULDNT_CONNECT; - } - - if(socksreq[0] != 5) { - failf(data, "Received invalid version in initial SOCKS5 response."); - return CURLE_COULDNT_CONNECT; - } - if(socksreq[1] == 0) { - /* Nothing to do, no authentication needed */ - ; - } -#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) - else if(allow_gssapi && (socksreq[1] == 1)) { - code = Curl_SOCKS5_gssapi_negotiate(sockindex, conn); - if(code) { - failf(data, "Unable to negotiate SOCKS5 GSS-API context."); + result = Curl_write_plain(conn, sockfd, (char *)socksreq, idx, &written); + if(result && (CURLE_AGAIN != result)) { + failf(data, "Unable to send initial SOCKS5 request."); return CURLE_COULDNT_CONNECT; } - } + if(written != idx) { + sxstate(conn, CONNECT_SOCKS_SEND); + sx->outstanding = idx - written; + sx->outp = &socksreq[written]; + return CURLE_OK; + } + sxstate(conn, CONNECT_SOCKS_READ); + goto CONNECT_SOCKS_READ_INIT; + case CONNECT_SOCKS_SEND: + result = Curl_write_plain(conn, sockfd, (char *)sx->outp, + sx->outstanding, &written); + if(result && (CURLE_AGAIN != result)) { + failf(data, "Unable to send initial SOCKS5 request."); + return CURLE_COULDNT_CONNECT; + } + if(written != sx->outstanding) { + /* not done, remain in state */ + sx->outstanding -= written; + sx->outp += written; + return CURLE_OK; + } + /* FALLTHROUGH */ + CONNECT_SOCKS_READ_INIT: + case CONNECT_SOCKS_READ_INIT: + sx->outstanding = 2; /* expect two bytes */ + sx->outp = socksreq; /* store it here */ + /* FALLTHROUGH */ + case CONNECT_SOCKS_READ: + result = Curl_read_plain(sockfd, (char *)sx->outp, + sx->outstanding, &actualread); + if(result && (CURLE_AGAIN != result)) { + failf(data, "Unable to receive initial SOCKS5 response."); + return CURLE_COULDNT_CONNECT; + } + else if(actualread != sx->outstanding) { + /* remain in reading state */ + sx->outstanding -= actualread; + sx->outp += actualread; + return CURLE_OK; + } + else if(socksreq[0] != 5) { + failf(data, "Received invalid version in initial SOCKS5 response."); + return CURLE_COULDNT_CONNECT; + } + else if(socksreq[1] == 0) { + /* DONE! No authentication needed. Send request. */ + sxstate(conn, CONNECT_REQ_INIT); + goto CONNECT_REQ_INIT; + } + else if(socksreq[1] == 2) { + /* regular name + password authentication */ + sxstate(conn, CONNECT_AUTH_INIT); + goto CONNECT_AUTH_INIT; + } +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + else if(allow_gssapi && (socksreq[1] == 1)) { + sxstate(conn, CONNECT_GSSAPI_INIT); + result = Curl_SOCKS5_gssapi_negotiate(sockindex, conn); + if(result) { + failf(data, "Unable to negotiate SOCKS5 GSS-API context."); + return CURLE_COULDNT_CONNECT; + } + } #endif - else if(socksreq[1] == 2) { + else { + /* error */ + if(!allow_gssapi && (socksreq[1] == 1)) { + failf(data, + "SOCKS5 GSSAPI per-message authentication is not supported."); + return CURLE_COULDNT_CONNECT; + } + else if(socksreq[1] == 255) { + failf(data, "No authentication method was acceptable."); + return CURLE_COULDNT_CONNECT; + } + failf(data, + "Undocumented SOCKS5 mode attempted to be used by server."); + return CURLE_COULDNT_CONNECT; + } + break; +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + case CONNECT_GSSAPI_INIT: + /* GSSAPI stuff done non-blocking */ + break; +#endif + + default: /* do nothing! */ + break; + + CONNECT_AUTH_INIT: + case CONNECT_AUTH_INIT: { /* Needs user name and password */ size_t proxy_user_len, proxy_password_len; if(proxy_user && proxy_password) { @@ -549,18 +685,41 @@ CURLcode Curl_SOCKS5(const char *proxy_user, memcpy(socksreq + len, proxy_password, proxy_password_len); } len += proxy_password_len; - - code = Curl_write_plain(conn, sock, (char *)socksreq, len, &written); - if(code || (len != written)) { + sxstate(conn, CONNECT_AUTH_SEND); + sx->outstanding = len; + sx->outp = socksreq; + } + /* FALLTHROUGH */ + case CONNECT_AUTH_SEND: + result = Curl_write_plain(conn, sockfd, (char *)sx->outp, + sx->outstanding, &written); + if(result && (CURLE_AGAIN != result)) { failf(data, "Failed to send SOCKS5 sub-negotiation request."); return CURLE_COULDNT_CONNECT; } - - result = Curl_blockread_all(conn, sock, (char *)socksreq, 2, &actualread); - if(result || (actualread != 2)) { + if(sx->outstanding != written) { + /* remain in state */ + sx->outstanding -= written; + sx->outp += written; + return CURLE_OK; + } + sx->outp = socksreq; + sx->outstanding = 2; + sxstate(conn, CONNECT_AUTH_READ); + /* FALLTHROUGH */ + case CONNECT_AUTH_READ: + result = Curl_read_plain(sockfd, (char *)sx->outp, + sx->outstanding, &actualread); + if(result && (CURLE_AGAIN != result)) { failf(data, "Unable to receive SOCKS5 sub-negotiation response."); return CURLE_COULDNT_CONNECT; } + if(actualread != sx->outstanding) { + /* remain in state */ + sx->outstanding -= actualread; + sx->outp += actualread; + return CURLE_OK; + } /* ignore the first (VER) byte */ if(socksreq[1] != 0) { /* status */ @@ -570,209 +729,248 @@ CURLcode Curl_SOCKS5(const char *proxy_user, } /* Everything is good so far, user was authenticated! */ - } - else { - /* error */ - if(!allow_gssapi && (socksreq[1] == 1)) { - failf(data, - "SOCKS5 GSSAPI per-message authentication is not supported."); - return CURLE_COULDNT_CONNECT; - } - if(socksreq[1] == 255) { - if(!proxy_user || !*proxy_user) { - failf(data, - "No authentication method was acceptable. (It is quite likely" - " that the SOCKS5 server wanted a username/password, since none" - " was supplied to the server on this connection.)"); - } - else { - failf(data, "No authentication method was acceptable."); - } - return CURLE_COULDNT_CONNECT; - } - else { - failf(data, - "Undocumented SOCKS5 mode attempted to be used by server."); - return CURLE_COULDNT_CONNECT; - } - } + sxstate(conn, CONNECT_REQ_INIT); + /* FALLTHROUGH */ + CONNECT_REQ_INIT: + case CONNECT_REQ_INIT: + if(socks5_resolve_local) { + enum resolve_t rc = Curl_resolv(conn, hostname, remote_port, + FALSE, &dns); - /* 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(rc == CURLRESOLV_ERROR) + return CURLE_COULDNT_RESOLVE_HOST; - if(!socks5_resolve_local) { - socksreq[len++] = 3; /* ATYP: domain name = 3 */ - socksreq[len++] = (char) hostname_len; /* address length */ - memcpy(&socksreq[len], hostname, hostname_len); /* address str w/o NULL */ - len += hostname_len; - msnprintf(dest, sizeof(dest), "%s:%d", hostname, remote_port); - infof(data, "SOCKS5 connect to %s (remotely resolved)\n", dest); - } - else { - struct Curl_dns_entry *dns; + if(rc == CURLRESOLV_PENDING) { + sxstate(conn, CONNECT_RESOLVING); + return CURLE_OK; + } + sxstate(conn, CONNECT_RESOLVED); + goto CONNECT_RESOLVED; + } + goto CONNECT_RESOLVE_REMOTE; + + case CONNECT_RESOLVING: + /* check if we have the name resolved by now */ + dns = Curl_fetch_addr(conn, hostname, (int)conn->port); + + if(dns) { +#ifdef CURLRES_ASYNCH + conn->async.dns = dns; + conn->async.done = TRUE; +#endif + infof(data, "SOCKS5: hostname '%s' found\n", hostname); + } + + if(!dns) { + result = Curl_resolv_check(data->conn, &dns); + /* stay in the state or error out */ + return result; + } + /* FALLTHROUGH */ + CONNECT_RESOLVED: + case CONNECT_RESOLVED: { Curl_addrinfo *hp = NULL; - int rc = Curl_resolv(conn, hostname, remote_port, FALSE, &dns); - - if(rc == CURLRESOLV_ERROR) - return CURLE_COULDNT_RESOLVE_HOST; - - if(rc == CURLRESOLV_PENDING) { - /* this requires that we're in "wait for resolve" state */ - code = Curl_resolver_wait_resolv(conn, &dns); - if(code) - return code; - } - - /* - * 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; - if(hp) { - if(Curl_printable_address(hp, dest, sizeof(dest))) { - size_t destlen = strlen(dest); - msnprintf(dest + destlen, sizeof(dest) - destlen, ":%d", remote_port); - } - else { - strcpy(dest, "unknown"); - } - - if(hp->ai_family == AF_INET) { - int i; - struct sockaddr_in *saddr_in; - socksreq[len++] = 1; /* ATYP: IPv4 = 1 */ - - 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]; - } - - infof(data, "SOCKS5 connect to IPv4 %s (locally resolved)\n", dest); - } -#ifdef ENABLE_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]; - } - - infof(data, "SOCKS5 connect to IPv6 %s (locally resolved)\n", dest); - } -#endif - else { - hp = NULL; /* fail! */ - - failf(data, "SOCKS5 connection to %s not supported\n", dest); - } - - Curl_resolv_unlock(data, dns); /* not used anymore from now on */ - } if(!hp) { failf(data, "Failed to resolve \"%s\" for SOCKS5 connect.", hostname); return CURLE_COULDNT_RESOLVE_HOST; } - } - socksreq[len++] = (unsigned char)((remote_port >> 8) & 0xff); /* PORT MSB */ - socksreq[len++] = (unsigned char)(remote_port & 0xff); /* PORT LSB */ + if(Curl_printable_address(hp, dest, sizeof(dest))) { + size_t destlen = strlen(dest); + msnprintf(dest + destlen, sizeof(dest) - destlen, ":%d", remote_port); + } + else { + strcpy(dest, "unknown"); + } -#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) - if(conn->socks5_gssapi_enctype) { - failf(data, "SOCKS5 GSS-API protection not yet implemented."); - } - else -#endif - code = Curl_write_plain(conn, sock, (char *)socksreq, len, &written); + 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(code || (len != written)) { - failf(data, "Failed to send SOCKS5 connect request."); - return CURLE_COULDNT_CONNECT; - } - - len = 10; /* minimum packet size is 10 */ - -#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) - if(conn->socks5_gssapi_enctype) { - failf(data, "SOCKS5 GSS-API protection not yet implemented."); - } - else -#endif - result = Curl_blockread_all(conn, sock, (char *)socksreq, - len, &actualread); - - if(result || (len != actualread)) { - failf(data, "Failed to receive SOCKS5 connect request ack."); - return CURLE_COULDNT_CONNECT; - } - - if(socksreq[0] != 5) { /* version */ - failf(data, - "SOCKS5 reply has wrong version, version should be 5."); - return CURLE_COULDNT_CONNECT; - } - - /* 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; - } - - /* 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) { - result = Curl_blockread_all(conn, sock, (char *)&socksreq[10], - len - 10, &actualread); - if(result || ((len - 10) != actualread)) { - failf(data, "Failed to receive SOCKS5 connect request ack."); - return CURLE_COULDNT_CONNECT; + 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]; } + + infof(data, "SOCKS5 connect to IPv4 %s (locally resolved)\n", dest); + } +#ifdef ENABLE_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]; + } + + infof(data, "SOCKS5 connect to IPv6 %s (locally resolved)\n", dest); + } +#endif + else { + hp = NULL; /* fail! */ + failf(data, "SOCKS5 connection to %s not supported\n", dest); + } + + Curl_resolv_unlock(data, dns); /* not used anymore from now on */ + goto CONNECT_REQ_SEND; + } + 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) { + socksreq[len++] = 3; /* ATYP: domain name = 3 */ + socksreq[len++] = (char) hostname_len; /* one byte address length */ + memcpy(&socksreq[len], hostname, hostname_len); /* address w/o NULL */ + len += hostname_len; + infof(data, "SOCKS5 connect to %s:5d (remotely resolved)\n", + hostname, remote_port); + } + /* FALLTHROUGH */ + + CONNECT_REQ_SEND: + case CONNECT_REQ_SEND: + /* PORT MSB */ + socksreq[len++] = (unsigned char)((remote_port >> 8) & 0xff); + /* PORT LSB */ + socksreq[len++] = (unsigned char)(remote_port & 0xff); + +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + if(conn->socks5_gssapi_enctype) { + failf(data, "SOCKS5 GSS-API protection not yet implemented."); + return CURLE_COULDNT_CONNECT; + } +#endif + sx->outp = socksreq; + sx->outstanding = len; + sxstate(conn, CONNECT_REQ_SENDING); + /* FALLTHROUGH */ + case CONNECT_REQ_SENDING: + result = Curl_write_plain(conn, sockfd, (char *)sx->outp, + sx->outstanding, &written); + if(result && (CURLE_AGAIN != result)) { + failf(data, "Failed to send SOCKS5 connect request."); + return CURLE_COULDNT_CONNECT; + } + if(sx->outstanding != written) { + /* remain in state */ + sx->outstanding -= written; + sx->outp += written; + return CURLE_OK; } #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) - } + if(conn->socks5_gssapi_enctype) { + failf(data, "SOCKS5 GSS-API protection not yet implemented."); + return CURLE_COULDNT_CONNECT; + } #endif + sx->outstanding = 10; /* minimum packet size is 10 */ + sx->outp = socksreq; + sxstate(conn, CONNECT_REQ_READ); + /* FALLTHROUGH */ + case CONNECT_REQ_READ: + result = Curl_read_plain(sockfd, (char *)sx->outp, + sx->outstanding, &actualread); + if(result && (CURLE_AGAIN != result)) { + failf(data, "Failed to receive SOCKS5 connect request ack."); + return CURLE_COULDNT_CONNECT; + } + else if(actualread != sx->outstanding) { + /* remain in state */ + sx->outstanding -= actualread; + sx->outp += actualread; + return CURLE_OK; + } - if(socksreq[1] != 0) { /* Anything besides 0 is an error */ - failf(data, "Can't complete SOCKS5 connection to %s. (%d)", - dest, (unsigned char)socksreq[1]); - return CURLE_COULDNT_CONNECT; + if(socksreq[0] != 5) { /* version */ + failf(data, + "SOCKS5 reply has wrong version, version should be 5."); + return CURLE_COULDNT_CONNECT; + } + else if(socksreq[1] != 0) { /* Anything besides 0 is an error */ + failf(data, "Can't complete SOCKS5 connection to %s. (%d)", + hostname, (unsigned char)socksreq[1]); + return CURLE_COULDNT_CONNECT; + } + + /* 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; + } + + /* 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) { + sx->outstanding = len - 10; /* get the rest */ + sx->outp = &socksreq[10]; + sxstate(conn, CONNECT_REQ_READ_MORE); + } + else { + sxstate(conn, CONNECT_DONE); + break; + } +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + } +#endif + /* FALLTHROUGH */ + case CONNECT_REQ_READ_MORE: + result = Curl_read_plain(sockfd, (char *)sx->outp, + sx->outstanding, &actualread); + if(result && (CURLE_AGAIN != result)) { + failf(data, "Failed to receive SOCKS5 connect request ack."); + return CURLE_COULDNT_CONNECT; + } + if(actualread != sx->outstanding) { + /* remain in state */ + sx->outstanding -= actualread; + sx->outp += actualread; + return CURLE_OK; + } + sxstate(conn, CONNECT_DONE); } infof(data, "SOCKS5 request granted.\n"); - (void)curlx_nonblock(sock, TRUE); + *done = TRUE; return CURLE_OK; /* Proxy was successful! */ } diff --git a/lib/socks.h b/lib/socks.h index 3b319a6ef..64a756337 100644 --- a/lib/socks.h +++ b/lib/socks.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2019, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2020, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -27,13 +27,13 @@ #ifdef CURL_DISABLE_PROXY #define Curl_SOCKS4(a,b,c,d,e) CURLE_NOT_BUILT_IN #define Curl_SOCKS5(a,b,c,d,e,f) CURLE_NOT_BUILT_IN +#define Curl_SOCKS_getsock(x,y,z) 0 #else /* * Helper read-from-socket functions. Does the same as Curl_read() but it * blocks until all bytes amount of buffersize will be read. No more, no less. * - * This is STUPID BLOCKING behaviour which we frown upon, but right now this - * is what we have... + * This is STUPID BLOCKING behavior */ int Curl_blockread_all(struct connectdata *conn, curl_socket_t sockfd, @@ -41,6 +41,9 @@ int Curl_blockread_all(struct connectdata *conn, ssize_t buffersize, ssize_t *n); +int Curl_SOCKS_getsock(struct connectdata *conn, + curl_socket_t *sock, + int sockindex); /* * This function logs in to a SOCKS4(a) proxy and sends the specifics to the * final destination server. @@ -49,7 +52,8 @@ CURLcode Curl_SOCKS4(const char *proxy_name, const char *hostname, int remote_port, int sockindex, - struct connectdata *conn); + struct connectdata *conn, + bool *done); /* * This function logs in to a SOCKS5 proxy and sends the specifics to the @@ -60,7 +64,8 @@ CURLcode Curl_SOCKS5(const char *proxy_name, const char *hostname, int remote_port, int sockindex, - struct connectdata *conn); + struct connectdata *conn, + bool *done); #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) /* diff --git a/lib/socks_gssapi.c b/lib/socks_gssapi.c index 65294bbeb..97ee7183e 100644 --- a/lib/socks_gssapi.c +++ b/lib/socks_gssapi.c @@ -5,8 +5,8 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * + * Copyright (C) 2012 - 2020, Daniel Stenberg, , et al. * Copyright (C) 2009, Markus Moeller, - * Copyright (C) 2012 - 2018, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -167,6 +167,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, return CURLE_COULDNT_CONNECT; } + (void)curlx_nonblock(sock, FALSE); + /* As long as we need to keep sending some context info, and there's no */ /* errors, keep sending it... */ for(;;) { @@ -513,6 +515,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, gss_release_buffer(&gss_status, &gss_recv_token); } + (void)curlx_nonblock(sock, TRUE); + infof(data, "SOCKS5 access with%s protection granted.\n", (socksreq[0] == 0)?"out GSS-API data": ((socksreq[0] == 1)?" GSS-API integrity":" GSS-API confidentiality")); diff --git a/lib/socks_sspi.c b/lib/socks_sspi.c index 57027ef68..d5be64a3c 100644 --- a/lib/socks_sspi.c +++ b/lib/socks_sspi.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2012 - 2019, Daniel Stenberg, , et al. + * Copyright (C) 2012 - 2020, Daniel Stenberg, , et al. * Copyright (C) 2009, 2011, Markus Moeller, * * This software is licensed as described in the file COPYING, which @@ -153,6 +153,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, return CURLE_COULDNT_CONNECT; } + (void)curlx_nonblock(sock, FALSE); + /* As long as we need to keep sending some context info, and there's no */ /* errors, keep sending it... */ for(;;) { @@ -587,6 +589,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, memcpy(socksreq, sspi_w_token[0].pvBuffer, sspi_w_token[0].cbBuffer); s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); } + (void)curlx_nonblock(sock, TRUE); infof(data, "SOCKS5 access with%s protection granted.\n", (socksreq[0] == 0)?"out GSS-API data": diff --git a/lib/urldata.h b/lib/urldata.h index e1c3e181b..6401f49f2 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -476,7 +476,6 @@ struct ConnectBits { BIT(tcp_fastopen); /* use TCP Fast Open */ BIT(tls_enable_npn); /* TLS NPN extension? */ BIT(tls_enable_alpn); /* TLS ALPN extension? */ - BIT(socksproxy_connecting); /* connecting through a socks proxy */ BIT(connect_only); }; @@ -817,6 +816,41 @@ struct http_connect_state { struct ldapconninfo; +/* 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 */ +}; + +#define SOCKS_STATE(x) (((x) >= CONNECT_SOCKS_INIT) && \ + ((x) < CONNECT_DONE)) +#define SOCKS_REQUEST_BUFSIZE 600 /* room for large user/pw (255 max each) */ + +struct connstate { + enum connect_t state; + unsigned char socksreq[SOCKS_REQUEST_BUFSIZE]; + + /* CONNECT_SOCKS_SEND */ + ssize_t outstanding; /* send this many bytes more */ + unsigned char *outp; /* send from this pointer */ +}; + /* * The connectdata struct contains all fields and variables that should be * unique for an entire connection. @@ -826,7 +860,7 @@ struct connectdata { caution that this might very well vary between different times this connection is used! */ struct Curl_easy *data; - + struct connstate cnnct; struct curl_llist_element bundle_node; /* conncache */ /* chunk is for HTTP chunked encoding, but is in the general connectdata