socks: make the connect phase non-blocking

Removes two entries from KNOWN_BUGS.

Closes #4907
This commit is contained in:
Daniel Stenberg 2020-02-14 16:16:54 +01:00
parent d60b1b37a1
commit 4a4b63daaa
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
13 changed files with 830 additions and 566 deletions

View File

@ -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 9.1 SFTP doesn't do CURLOPT_POSTQUOTE correct
10. SOCKS 10. SOCKS
10.1 SOCKS proxy connections are done blocking
10.2 SOCKS don't support timeouts
10.3 FTPS over SOCKS 10.3 FTPS over SOCKS
10.4 active FTP over a 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. 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 10.3 FTPS over SOCKS
libcurl doesn't support FTPS over a SOCKS proxy. libcurl doesn't support FTPS over a SOCKS proxy.

View File

@ -405,7 +405,6 @@
EWOULDBLOCK or similar. Blocking cases include: EWOULDBLOCK or similar. Blocking cases include:
- Name resolves on non-windows unless c-ares or the threaded resolver is used - Name resolves on non-windows unless c-ares or the threaded resolver is used
- SOCKS proxy handshakes
- file:// transfers - file:// transfers
- TELNET transfers - TELNET transfers
- The "DONE" operation (post transfer protocol-specific actions) for the - The "DONE" operation (post transfer protocol-specific actions) for the

View File

@ -745,58 +745,82 @@ void Curl_updateconninfo(struct connectdata *conn, curl_socket_t sockfd)
Curl_persistconninfo(conn); Curl_persistconninfo(conn);
} }
/* after a TCP connection to the proxy has been verified, this function does /* After a TCP connection to the proxy has been verified, this function does
the next magic step. 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() 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; CURLcode result = CURLE_OK;
infof(conn->data, "connect_SOCKS is called [socks state %d]\n",
conn->cnnct.state);
if(conn->bits.socksproxy) { if(conn->bits.socksproxy) {
#ifndef CURL_DISABLE_PROXY #ifndef CURL_DISABLE_PROXY
/* for the secondary socket (FTP), use the "connect to host" /* for the secondary socket (FTP), use the "connect to host"
* but ignore the "connect to port" (use the secondary port) * but ignore the "connect to port" (use the secondary port)
*/ */
const char * const host = conn->bits.httpproxy ? const char * const host =
conn->bits.httpproxy ?
conn->http_proxy.host.name : conn->http_proxy.host.name :
conn->bits.conn_to_host ? conn->bits.conn_to_host ?
conn->conn_to_host.name : conn->conn_to_host.name :
sockindex == SECONDARYSOCKET ? sockindex == SECONDARYSOCKET ?
conn->secondaryhostname : conn->host.name; conn->secondaryhostname : conn->host.name;
const int port = conn->bits.httpproxy ? (int)conn->http_proxy.port : const int port =
conn->bits.httpproxy ? (int)conn->http_proxy.port :
sockindex == SECONDARYSOCKET ? conn->secondary_port : sockindex == SECONDARYSOCKET ? conn->secondary_port :
conn->bits.conn_to_port ? conn->conn_to_port : conn->bits.conn_to_port ? conn->conn_to_port :
conn->remote_port; conn->remote_port;
conn->bits.socksproxy_connecting = TRUE;
switch(conn->socks_proxy.proxytype) { switch(conn->socks_proxy.proxytype) {
case CURLPROXY_SOCKS5: case CURLPROXY_SOCKS5:
case CURLPROXY_SOCKS5_HOSTNAME: case CURLPROXY_SOCKS5_HOSTNAME:
result = Curl_SOCKS5(conn->socks_proxy.user, conn->socks_proxy.passwd, result = Curl_SOCKS5(conn->socks_proxy.user, conn->socks_proxy.passwd,
host, port, sockindex, conn); host, port, sockindex, conn, done);
break; break;
case CURLPROXY_SOCKS4: case CURLPROXY_SOCKS4:
case CURLPROXY_SOCKS4A: case CURLPROXY_SOCKS4A:
result = Curl_SOCKS4(conn->socks_proxy.user, host, port, sockindex, result = Curl_SOCKS4(conn->socks_proxy.user, host, port, sockindex,
conn); conn, done);
break; break;
default: default:
failf(conn->data, "unknown proxytype option given"); failf(conn->data, "unknown proxytype option given");
result = CURLE_COULDNT_CONNECT; result = CURLE_COULDNT_CONNECT;
} /* switch proxytype */ } /* switch proxytype */
conn->bits.socksproxy_connecting = FALSE;
#else #else
(void)sockindex; (void)sockindex;
#endif /* CURL_DISABLE_PROXY */ #endif /* CURL_DISABLE_PROXY */
} }
else
*done = TRUE; /* no SOCKS proxy, so consider us connected */
return result; 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. * Curl_is_connected() checks if the socket has connected.
*/ */
@ -834,6 +858,14 @@ CURLcode Curl_is_connected(struct connectdata *conn,
return CURLE_OPERATION_TIMEDOUT; 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++) { for(i = 0; i<2; i++) {
const int other = i ^ 1; const int other = i ^ 1;
if(conn->tempsock[i] == CURL_SOCKET_BAD) if(conn->tempsock[i] == CURL_SOCKET_BAD)
@ -900,18 +932,13 @@ CURLcode Curl_is_connected(struct connectdata *conn,
conn->tempsock[other] = CURL_SOCKET_BAD; conn->tempsock[other] = CURL_SOCKET_BAD;
} }
/* see if we need to do any proxy magic first once we connected */ /* see if we need to kick off any SOCKS proxy magic once we
result = connected_proxy(conn, sockindex); connected */
if(result) result = connect_SOCKS(conn, sockindex, connected);
if(result || !*connected)
return result; return result;
conn->bits.tcpconnect[sockindex] = TRUE; post_SOCKS(conn, sockindex, connected);
*connected = TRUE;
if(sockindex == FIRSTSOCKET)
Curl_pgrsTime(data, TIMER_CONNECT); /* connect done */
Curl_updateconninfo(conn, conn->sock[sockindex]);
Curl_verboseconnect(conn);
return CURLE_OK; return CURLE_OK;
} }

View File

@ -55,7 +55,6 @@
#include "transfer.h" #include "transfer.h"
#include "escape.h" #include "escape.h"
#include "http.h" /* for HTTP proxy tunnel stuff */ #include "http.h" /* for HTTP proxy tunnel stuff */
#include "socks.h"
#include "ftp.h" #include "ftp.h"
#include "fileinfo.h" #include "fileinfo.h"
#include "ftplistparser.h" #include "ftplistparser.h"
@ -78,6 +77,7 @@
#include "warnless.h" #include "warnless.h"
#include "http_proxy.h" #include "http_proxy.h"
#include "non-ascii.h" #include "non-ascii.h"
#include "socks.h"
/* The last 3 #include files should be in this order */ /* The last 3 #include files should be in this order */
#include "curl_printf.h" #include "curl_printf.h"
#include "curl_memory.h" #include "curl_memory.h"
@ -810,6 +810,9 @@ static int ftp_domore_getsock(struct connectdata *conn, curl_socket_t *socks)
* handle ordinary commands. * handle ordinary commands.
*/ */
if(SOCKS_STATE(conn->cnnct.state))
return Curl_SOCKS_getsock(conn, socks, SECONDARYSOCKET);
if(FTP_STOP == ftpc->state) { if(FTP_STOP == ftpc->state) {
int bits = GETSOCK_READSOCK(0); 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; struct sockaddr_in6 * const sa6 = (void *)sa;
#endif #endif
static const char mode[][5] = { "EPRT", "PORT" }; static const char mode[][5] = { "EPRT", "PORT" };
int rc; enum resolve_t rc;
int error; int error;
char *host = NULL; char *host = NULL;
char *string_ftpport = data->set.str[STRING_FTPPORT]; char *string_ftpport = data->set.str[STRING_FTPPORT];
@ -1794,7 +1797,7 @@ static CURLcode ftp_state_pasv_resp(struct connectdata *conn,
CURLcode result; CURLcode result;
struct Curl_easy *data = conn->data; struct Curl_easy *data = conn->data;
struct Curl_dns_entry *addr = NULL; struct Curl_dns_entry *addr = NULL;
int rc; enum resolve_t rc;
unsigned short connectport; /* the local port connect() should use! */ unsigned short connectport; /* the local port connect() should use! */
char *str = &data->state.buffer[4]; /* start on the first letter */ char *str = &data->state.buffer[4]; /* start on the first letter */

View File

@ -483,7 +483,7 @@ Curl_cache_addr(struct Curl_easy *data,
* CURLRESOLV_PENDING (1) = waiting for response, no pointer * CURLRESOLV_PENDING (1) = waiting for response, no pointer
*/ */
int Curl_resolv(struct connectdata *conn, enum resolve_t Curl_resolv(struct connectdata *conn,
const char *hostname, const char *hostname,
int port, int port,
bool allowDOH, bool allowDOH,
@ -492,7 +492,7 @@ int Curl_resolv(struct connectdata *conn,
struct Curl_dns_entry *dns = NULL; struct Curl_dns_entry *dns = NULL;
struct Curl_easy *data = conn->data; struct Curl_easy *data = conn->data;
CURLcode result; CURLcode result;
int rc = CURLRESOLV_ERROR; /* default to failure */ enum resolve_t rc = CURLRESOLV_ERROR; /* default to failure */
*entry = NULL; *entry = NULL;
@ -642,7 +642,7 @@ RETSIGTYPE alarmfunc(int sig)
* CURLRESOLV_PENDING (1) = waiting for response, no pointer * CURLRESOLV_PENDING (1) = waiting for response, no pointer
*/ */
int Curl_resolv_timeout(struct connectdata *conn, enum resolve_t Curl_resolv_timeout(struct connectdata *conn,
const char *hostname, const char *hostname,
int port, int port,
struct Curl_dns_entry **entry, struct Curl_dns_entry **entry,
@ -662,7 +662,7 @@ int Curl_resolv_timeout(struct connectdata *conn,
volatile unsigned int prev_alarm = 0; volatile unsigned int prev_alarm = 0;
struct Curl_easy *data = conn->data; struct Curl_easy *data = conn->data;
#endif /* USE_ALARM_TIMEOUT */ #endif /* USE_ALARM_TIMEOUT */
int rc; enum resolve_t rc;
*entry = NULL; *entry = NULL;

View File

@ -79,17 +79,20 @@ struct Curl_dns_entry {
* use, or we'll leak memory! * use, or we'll leak memory!
*/ */
/* return codes */ /* return codes */
#define CURLRESOLV_TIMEDOUT -2 enum resolve_t {
#define CURLRESOLV_ERROR -1 CURLRESOLV_TIMEDOUT = -2,
#define CURLRESOLV_RESOLVED 0 CURLRESOLV_ERROR = -1,
#define CURLRESOLV_PENDING 1 CURLRESOLV_RESOLVED = 0,
int Curl_resolv(struct connectdata *conn, CURLRESOLV_PENDING = 1
};
enum resolve_t Curl_resolv(struct connectdata *conn,
const char *hostname, const char *hostname,
int port, int port,
bool allowDOH, bool allowDOH,
struct Curl_dns_entry **dnsentry); struct Curl_dns_entry **dnsentry);
int Curl_resolv_timeout(struct connectdata *conn, const char *hostname, enum resolve_t Curl_resolv_timeout(struct connectdata *conn,
int port, struct Curl_dns_entry **dnsentry, const char *hostname, int port,
struct Curl_dns_entry **dnsentry,
timediff_t timeoutms); timediff_t timeoutms);
#ifdef CURLRES_IPV6 #ifdef CURLRES_IPV6

View File

@ -47,6 +47,7 @@
#include "http_proxy.h" #include "http_proxy.h"
#include "http2.h" #include "http2.h"
#include "socketpair.h" #include "socketpair.h"
#include "socks.h"
/* The last 3 #include files should be in this order */ /* The last 3 #include files should be in this order */
#include "curl_printf.h" #include "curl_printf.h"
#include "curl_memory.h" #include "curl_memory.h"
@ -856,6 +857,9 @@ static int waitconnect_getsock(struct connectdata *conn,
return Curl_ssl_getsock(conn, sock); return Curl_ssl_getsock(conn, sock);
#endif #endif
if(SOCKS_STATE(conn->cnnct.state))
return Curl_SOCKS_getsock(conn, sock, FIRSTSOCKET);
for(i = 0; i<2; i++) { for(i = 0; i<2; i++) {
if(conn->tempsock[i] != CURL_SOCKET_BAD) { if(conn->tempsock[i] != CURL_SOCKET_BAD) {
sock[s] = conn->tempsock[i]; sock[s] = conn->tempsock[i];

View File

@ -692,19 +692,20 @@ CURLcode Curl_read_plain(curl_socket_t sockfd,
ssize_t nread = sread(sockfd, buf, bytesfromsocket); ssize_t nread = sread(sockfd, buf, bytesfromsocket);
if(-1 == nread) { if(-1 == nread) {
int err = SOCKERRNO; const int err = SOCKERRNO;
int return_error; const bool return_error =
#ifdef USE_WINSOCK #ifdef USE_WINSOCK
return_error = WSAEWOULDBLOCK == err; WSAEWOULDBLOCK == err
#else #else
return_error = EWOULDBLOCK == err || EAGAIN == err || EINTR == err; EWOULDBLOCK == err || EAGAIN == err || EINTR == err
#endif #endif
;
*n = 0; /* no data returned */
if(return_error) if(return_error)
return CURLE_AGAIN; return CURLE_AGAIN;
return CURLE_RECV_ERROR; return CURLE_RECV_ERROR;
} }
/* we only return number of bytes read when we return OK */
*n = nread; *n = nread;
return CURLE_OK; return CURLE_OK;
} }

View File

@ -5,7 +5,7 @@
* | (__| |_| | _ <| |___ * | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____| * \___|\___/|_| \_\_____|
* *
* Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al. * Copyright (C) 1998 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.
* *
* This software is licensed as described in the file COPYING, which * This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms * you should have received as part of this distribution. The terms
@ -37,18 +37,19 @@
#include "connect.h" #include "connect.h"
#include "timeval.h" #include "timeval.h"
#include "socks.h" #include "socks.h"
#include "multiif.h" /* for getsock macros */
/* The last 3 #include files should be in this order */ /* The last 3 #include files should be in this order */
#include "curl_printf.h" #include "curl_printf.h"
#include "curl_memory.h" #include "curl_memory.h"
#include "memdebug.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 * 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. * 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 * This is STUPID BLOCKING behavior. Only used by the SOCKS GSSAPI functions.
* is what we have...
*/ */
int Curl_blockread_all(struct connectdata *conn, /* connection data */ int Curl_blockread_all(struct connectdata *conn, /* connection data */
curl_socket_t sockfd, /* read from this socket */ curl_socket_t sockfd, /* read from this socket */
@ -94,6 +95,81 @@ int Curl_blockread_all(struct connectdata *conn, /* connection data */
} }
return result; 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 * This function logs in to a SOCKS4 proxy and sends the specifics to the final
@ -110,29 +186,29 @@ CURLcode Curl_SOCKS4(const char *proxy_user,
const char *hostname, const char *hostname,
int remote_port, int remote_port,
int sockindex, int sockindex,
struct connectdata *conn) struct connectdata *conn,
bool *done)
{ {
const bool protocol4a = const bool protocol4a =
(conn->socks_proxy.proxytype == CURLPROXY_SOCKS4A) ? TRUE : FALSE; (conn->socks_proxy.proxytype == CURLPROXY_SOCKS4A) ? TRUE : FALSE;
#define SOCKS4REQLEN 262 unsigned char *socksreq = &conn->cnnct.socksreq[0];
unsigned char socksreq[SOCKS4REQLEN]; /* room for SOCKS4 request incl. user CURLcode result;
id */ curl_socket_t sockfd = conn->sock[sockindex];
CURLcode code;
curl_socket_t sock = conn->sock[sockindex];
struct Curl_easy *data = conn->data; 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) { if(!SOCKS_STATE(sx->state) && !*done)
/* time-out, bail out, go home */ sxstate(conn, CONNECT_SOCKS_INIT);
failf(data, "Connection time-out");
return CURLE_OPERATION_TIMEDOUT;
}
switch(sx->state) {
case CONNECT_SOCKS_INIT:
if(conn->bits.httpproxy) if(conn->bits.httpproxy)
infof(conn->data, "SOCKS4%s: connecting to HTTP proxy %s port %d\n", infof(conn->data, "SOCKS4%s: connecting to HTTP proxy %s port %d\n",
protocol4a ? "a" : "", hostname, remote_port); 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);
/* /*
@ -153,19 +229,46 @@ CURLcode Curl_SOCKS4(const char *proxy_user,
/* DNS resolve only for SOCKS4, not SOCKS4a */ /* DNS resolve only for SOCKS4, not SOCKS4a */
if(!protocol4a) { if(!protocol4a) {
struct Curl_dns_entry *dns; enum resolve_t rc =
Curl_addrinfo *hp = NULL; Curl_resolv(conn, hostname, remote_port, FALSE, &dns);
int rc;
rc = Curl_resolv(conn, hostname, remote_port, FALSE, &dns);
if(rc == CURLRESOLV_ERROR) if(rc == CURLRESOLV_ERROR)
return CURLE_COULDNT_RESOLVE_PROXY; 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;
}
if(rc == CURLRESOLV_PENDING) /* socks4a doesn't resolve anything locally */
/* ignores the return code, but 'dns' remains NULL on failure */ sxstate(conn, CONNECT_REQ_INIT);
(void)Curl_resolver_wait_resolv(conn, &dns); 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;
char buf[64];
/* /*
* We cannot use 'hostent' as a struct that Curl_resolv() returns. It * We cannot use 'hostent' as a struct that Curl_resolv() returns. It
* returns a Curl_addrinfo pointer that may not always look the same. * 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) if(dns)
hp = dns->addr; hp = dns->addr;
if(hp) { if(hp) {
char buf[64];
Curl_printable_address(hp, buf, sizeof(buf)); Curl_printable_address(hp, buf, sizeof(buf));
if(hp->ai_family == AF_INET) { if(hp->ai_family == AF_INET) {
@ -189,7 +291,6 @@ CURLcode Curl_SOCKS4(const char *proxy_user,
} }
else { else {
hp = NULL; /* fail! */ hp = NULL; /* fail! */
failf(data, "SOCKS4 connection to %s not supported\n", buf); failf(data, "SOCKS4 connection to %s not supported\n", buf);
} }
@ -201,14 +302,16 @@ CURLcode Curl_SOCKS4(const char *proxy_user,
return CURLE_COULDNT_RESOLVE_HOST; return CURLE_COULDNT_RESOLVE_HOST;
} }
} }
/* FALLTHROUGH */
CONNECT_REQ_INIT:
case CONNECT_REQ_INIT:
/* /*
* This is currently not supporting "Identification Protocol (RFC1413)". * This is currently not supporting "Identification Protocol (RFC1413)".
*/ */
socksreq[8] = 0; /* ensure empty userid is NUL-terminated */ socksreq[8] = 0; /* ensure empty userid is NUL-terminated */
if(proxy_user) { if(proxy_user) {
size_t plen = strlen(proxy_user); size_t plen = strlen(proxy_user);
if(plen >= sizeof(socksreq) - 8) { if(plen >= sizeof(sx->socksreq) - 8) {
failf(data, "Too long SOCKS proxy name, can't use!\n"); failf(data, "Too long SOCKS proxy name, can't use!\n");
return CURLE_COULDNT_CONNECT; return CURLE_COULDNT_CONNECT;
} }
@ -220,55 +323,72 @@ CURLcode Curl_SOCKS4(const char *proxy_user,
* Make connection * Make connection
*/ */
{ {
int result;
ssize_t actualread;
ssize_t written;
ssize_t hostnamelen = 0;
ssize_t packetsize = 9 + ssize_t packetsize = 9 +
strlen((char *)socksreq + 8); /* size including NUL */ strlen((char *)socksreq + 8); /* size including NUL */
/* If SOCKS4a, set special invalid IP address 0.0.0.x */ /* If SOCKS4a, set special invalid IP address 0.0.0.x */
if(protocol4a) { if(protocol4a) {
ssize_t hostnamelen = 0;
socksreq[4] = 0; socksreq[4] = 0;
socksreq[5] = 0; socksreq[5] = 0;
socksreq[6] = 0; socksreq[6] = 0;
socksreq[7] = 1; socksreq[7] = 1;
/* If still enough room in buffer, also append hostname */ /* append hostname */
hostnamelen = (ssize_t)strlen(hostname) + 1; /* length including NUL */ hostnamelen = (ssize_t)strlen(hostname) + 1; /* length including NUL */
if(packetsize + hostnamelen <= SOCKS4REQLEN) if(hostnamelen <= 255)
strcpy((char *)socksreq + packetsize, hostname); strcpy((char *)socksreq + packetsize, hostname);
else else {
hostnamelen = 0; /* Flag: hostname did not fit in buffer */ 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 */ /* Send request */
code = Curl_write_plain(conn, sock, (char *)socksreq, result = Curl_write_plain(conn, sockfd, (char *)sx->outp,
packetsize + hostnamelen, sx->outstanding, &written);
&written); if(result && (CURLE_AGAIN != result)) {
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."); failf(data, "Failed to send SOCKS4 connect request.");
return CURLE_COULDNT_CONNECT; return CURLE_COULDNT_CONNECT;
} }
if(written != sx->outstanding) {
/* not done, remain in state */
sx->outstanding -= written;
sx->outp += written;
return CURLE_OK;
} }
packetsize = 8; /* receive data size */ /* done sending! */
sx->outstanding = 8; /* receive data size */
sx->outp = socksreq;
sxstate(conn, CONNECT_SOCKS_READ);
/* FALLTHROUGH */
case CONNECT_SOCKS_READ:
/* Receive response */ /* Receive response */
result = Curl_blockread_all(conn, sock, (char *)socksreq, packetsize, result = Curl_read_plain(sockfd, (char *)sx->outp,
&actualread); sx->outstanding, &actualread);
if(result || (actualread != packetsize)) { if(result && (CURLE_AGAIN != result)) {
failf(data, "Failed to receive SOCKS4 connect request ack."); failf(data, "SOCKS4: Failed receiving connect request ack: %s",
curl_easy_strerror(result));
return CURLE_COULDNT_CONNECT; 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;
}
/* /*
* Response format * Response format
@ -340,10 +460,8 @@ CURLcode Curl_SOCKS4(const char *proxy_user,
(unsigned char)socksreq[1]); (unsigned char)socksreq[1]);
return CURLE_COULDNT_CONNECT; return CURLE_COULDNT_CONNECT;
} }
}
(void)curlx_nonblock(sock, TRUE);
*done = TRUE;
return CURLE_OK; /* Proxy was successful! */ return CURLE_OK; /* Proxy was successful! */
} }
@ -356,7 +474,8 @@ CURLcode Curl_SOCKS5(const char *proxy_user,
const char *hostname, const char *hostname,
int remote_port, int remote_port,
int sockindex, int sockindex,
struct connectdata *conn) struct connectdata *conn,
bool *done)
{ {
/* /*
According to the RFC1928, section "6. Replies". This is what a SOCK5 According to the RFC1928, section "6. Replies". This is what a SOCK5
@ -374,24 +493,28 @@ CURLcode Curl_SOCKS5(const char *proxy_user,
o REP Reply field: o REP Reply field:
o X'00' succeeded o X'00' succeeded
*/ */
#define REQUEST_BUFSIZE 600 /* room for large user/pw (255 max each) */ unsigned char *socksreq = &conn->cnnct.socksreq[0];
unsigned char socksreq[REQUEST_BUFSIZE]; char dest[256] = "unknown"; /* printable hostname:port */
char dest[REQUEST_BUFSIZE] = "unknown"; /* printable hostname:port */
int idx; int idx;
ssize_t actualread; ssize_t actualread;
ssize_t written; ssize_t written;
int result; CURLcode result;
CURLcode code; curl_socket_t sockfd = conn->sock[sockindex];
curl_socket_t sock = conn->sock[sockindex];
struct Curl_easy *data = conn->data; struct Curl_easy *data = conn->data;
timediff_t timeout;
bool socks5_resolve_local = bool socks5_resolve_local =
(conn->socks_proxy.proxytype == CURLPROXY_SOCKS5) ? TRUE : FALSE; (conn->socks_proxy.proxytype == CURLPROXY_SOCKS5) ? TRUE : FALSE;
const size_t hostname_len = strlen(hostname); const size_t hostname_len = strlen(hostname);
ssize_t len = 0; ssize_t len = 0;
const unsigned long auth = data->set.socks5auth; const unsigned long auth = data->set.socks5auth;
bool allow_gssapi = FALSE; bool allow_gssapi = FALSE;
struct connstate *sx = &conn->cnnct;
struct Curl_dns_entry *dns = NULL;
if(!SOCKS_STATE(sx->state) && !*done)
sxstate(conn, CONNECT_SOCKS_INIT);
switch(sx->state) {
case CONNECT_SOCKS_INIT:
if(conn->bits.httpproxy) if(conn->bits.httpproxy)
infof(conn->data, "SOCKS5: connecting to HTTP proxy %s port %d\n", infof(conn->data, "SOCKS5: connecting to HTTP proxy %s port %d\n",
hostname, remote_port); hostname, remote_port);
@ -403,34 +526,6 @@ CURLcode Curl_SOCKS5(const char *proxy_user,
socks5_resolve_local = TRUE; socks5_resolve_local = TRUE;
} }
/* get timeout */
timeout = Curl_timeleft(data, NULL, 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)) if(auth & ~(CURLAUTH_BASIC | CURLAUTH_GSSAPI))
infof(conn->data, infof(conn->data,
"warning: unsupported value passed to CURLOPT_SOCKS5_AUTH: %lu\n", "warning: unsupported value passed to CURLOPT_SOCKS5_AUTH: %lu\n",
@ -445,7 +540,7 @@ CURLcode Curl_SOCKS5(const char *proxy_user,
idx = 0; idx = 0;
socksreq[idx++] = 5; /* version */ socksreq[idx++] = 5; /* version */
idx++; /* reserve for the number of authentication methods */ idx++; /* number of authentication methods */
socksreq[idx++] = 0; /* no authentication */ socksreq[idx++] = 0; /* no authentication */
if(allow_gssapi) if(allow_gssapi)
socksreq[idx++] = 1; /* GSS-API */ socksreq[idx++] = 1; /* GSS-API */
@ -454,61 +549,102 @@ CURLcode Curl_SOCKS5(const char *proxy_user,
/* write the number of authentication methods */ /* write the number of authentication methods */
socksreq[1] = (unsigned char) (idx - 2); socksreq[1] = (unsigned char) (idx - 2);
(void)curlx_nonblock(sock, FALSE); result = Curl_write_plain(conn, sockfd, (char *)socksreq, idx, &written);
if(result && (CURLE_AGAIN != result)) {
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."); failf(data, "Unable to send initial SOCKS5 request.");
return CURLE_COULDNT_CONNECT; return CURLE_COULDNT_CONNECT;
} }
if(written != idx) {
(void)curlx_nonblock(sock, TRUE); sxstate(conn, CONNECT_SOCKS_SEND);
sx->outstanding = idx - written;
result = SOCKET_READABLE(sock, timeout); sx->outp = &socksreq[written];
return CURLE_OK;
if(-1 == result) { }
failf(conn->data, "SOCKS5 nothing to read"); 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; return CURLE_COULDNT_CONNECT;
} }
if(0 == result) { if(written != sx->outstanding) {
failf(conn->data, "SOCKS5 read timeout"); /* not done, remain in state */
return CURLE_OPERATION_TIMEDOUT; sx->outstanding -= written;
sx->outp += written;
return CURLE_OK;
} }
/* FALLTHROUGH */
if(result & CURL_CSELECT_ERR) { CONNECT_SOCKS_READ_INIT:
failf(conn->data, "SOCKS5 read error occurred"); case CONNECT_SOCKS_READ_INIT:
return CURLE_RECV_ERROR; sx->outstanding = 2; /* expect two bytes */
} sx->outp = socksreq; /* store it here */
/* FALLTHROUGH */
(void)curlx_nonblock(sock, FALSE); case CONNECT_SOCKS_READ:
result = Curl_read_plain(sockfd, (char *)sx->outp,
result = Curl_blockread_all(conn, sock, (char *)socksreq, 2, &actualread); sx->outstanding, &actualread);
if(result || (actualread != 2)) { if(result && (CURLE_AGAIN != result)) {
failf(data, "Unable to receive initial SOCKS5 response."); failf(data, "Unable to receive initial SOCKS5 response.");
return CURLE_COULDNT_CONNECT; return CURLE_COULDNT_CONNECT;
} }
else if(actualread != sx->outstanding) {
if(socksreq[0] != 5) { /* 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."); failf(data, "Received invalid version in initial SOCKS5 response.");
return CURLE_COULDNT_CONNECT; return CURLE_COULDNT_CONNECT;
} }
if(socksreq[1] == 0) { else if(socksreq[1] == 0) {
/* Nothing to do, no authentication needed */ /* 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) #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
else if(allow_gssapi && (socksreq[1] == 1)) { else if(allow_gssapi && (socksreq[1] == 1)) {
code = Curl_SOCKS5_gssapi_negotiate(sockindex, conn); sxstate(conn, CONNECT_GSSAPI_INIT);
if(code) { result = Curl_SOCKS5_gssapi_negotiate(sockindex, conn);
if(result) {
failf(data, "Unable to negotiate SOCKS5 GSS-API context."); failf(data, "Unable to negotiate SOCKS5 GSS-API context.");
return CURLE_COULDNT_CONNECT; return CURLE_COULDNT_CONNECT;
} }
} }
#endif #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 */ /* Needs user name and password */
size_t proxy_user_len, proxy_password_len; size_t proxy_user_len, proxy_password_len;
if(proxy_user && proxy_password) { if(proxy_user && proxy_password) {
@ -549,18 +685,41 @@ CURLcode Curl_SOCKS5(const char *proxy_user,
memcpy(socksreq + len, proxy_password, proxy_password_len); memcpy(socksreq + len, proxy_password, proxy_password_len);
} }
len += proxy_password_len; len += proxy_password_len;
sxstate(conn, CONNECT_AUTH_SEND);
code = Curl_write_plain(conn, sock, (char *)socksreq, len, &written); sx->outstanding = len;
if(code || (len != written)) { 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."); failf(data, "Failed to send SOCKS5 sub-negotiation request.");
return CURLE_COULDNT_CONNECT; return CURLE_COULDNT_CONNECT;
} }
if(sx->outstanding != written) {
result = Curl_blockread_all(conn, sock, (char *)socksreq, 2, &actualread); /* remain in state */
if(result || (actualread != 2)) { 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."); failf(data, "Unable to receive SOCKS5 sub-negotiation response.");
return CURLE_COULDNT_CONNECT; 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 */ /* ignore the first (VER) byte */
if(socksreq[1] != 0) { /* status */ if(socksreq[1] != 0) { /* status */
@ -570,69 +729,55 @@ CURLcode Curl_SOCKS5(const char *proxy_user,
} }
/* Everything is good so far, user was authenticated! */ /* Everything is good so far, user was authenticated! */
} sxstate(conn, CONNECT_REQ_INIT);
else { /* FALLTHROUGH */
/* error */ CONNECT_REQ_INIT:
if(!allow_gssapi && (socksreq[1] == 1)) { case CONNECT_REQ_INIT:
failf(data, if(socks5_resolve_local) {
"SOCKS5 GSSAPI per-message authentication is not supported."); enum resolve_t rc = Curl_resolv(conn, hostname, remote_port,
return CURLE_COULDNT_CONNECT; FALSE, &dns);
}
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;
}
}
/* 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; /* 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;
Curl_addrinfo *hp = NULL;
int rc = Curl_resolv(conn, hostname, remote_port, FALSE, &dns);
if(rc == CURLRESOLV_ERROR) if(rc == CURLRESOLV_ERROR)
return CURLE_COULDNT_RESOLVE_HOST; return CURLE_COULDNT_RESOLVE_HOST;
if(rc == CURLRESOLV_PENDING) { if(rc == CURLRESOLV_PENDING) {
/* this requires that we're in "wait for resolve" state */ sxstate(conn, CONNECT_RESOLVING);
code = Curl_resolver_wait_resolv(conn, &dns); return CURLE_OK;
if(code) }
return code; 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) {
* We cannot use 'hostent' as a struct that Curl_resolv() returns. It result = Curl_resolv_check(data->conn, &dns);
* returns a Curl_addrinfo pointer that may not always look the same. /* stay in the state or error out */
*/ return result;
}
/* FALLTHROUGH */
CONNECT_RESOLVED:
case CONNECT_RESOLVED: {
Curl_addrinfo *hp = NULL;
if(dns) if(dns)
hp = dns->addr; hp = dns->addr;
if(hp) { if(!hp) {
failf(data, "Failed to resolve \"%s\" for SOCKS5 connect.",
hostname);
return CURLE_COULDNT_RESOLVE_HOST;
}
if(Curl_printable_address(hp, dest, sizeof(dest))) { if(Curl_printable_address(hp, dest, sizeof(dest))) {
size_t destlen = strlen(dest); size_t destlen = strlen(dest);
msnprintf(dest + destlen, sizeof(dest) - destlen, ":%d", remote_port); msnprintf(dest + destlen, sizeof(dest) - destlen, ":%d", remote_port);
@ -641,6 +786,10 @@ CURLcode Curl_SOCKS5(const char *proxy_user,
strcpy(dest, "unknown"); strcpy(dest, "unknown");
} }
len = 0;
socksreq[len++] = 5; /* version (SOCKS5) */
socksreq[len++] = 1; /* connect */
socksreq[len++] = 0; /* must be zero */
if(hp->ai_family == AF_INET) { if(hp->ai_family == AF_INET) {
int i; int i;
struct sockaddr_in *saddr_in; struct sockaddr_in *saddr_in;
@ -670,60 +819,98 @@ CURLcode Curl_SOCKS5(const char *proxy_user,
#endif #endif
else { else {
hp = NULL; /* fail! */ hp = NULL; /* fail! */
failf(data, "SOCKS5 connection to %s not supported\n", dest); failf(data, "SOCKS5 connection to %s not supported\n", dest);
} }
Curl_resolv_unlock(data, dns); /* not used anymore from now on */ Curl_resolv_unlock(data, dns); /* not used anymore from now on */
goto CONNECT_REQ_SEND;
} }
if(!hp) { CONNECT_RESOLVE_REMOTE:
failf(data, "Failed to resolve \"%s\" for SOCKS5 connect.", case CONNECT_RESOLVE_REMOTE:
hostname); /* Authentication is complete, now specify destination to the proxy */
return CURLE_COULDNT_RESOLVE_HOST; len = 0;
} socksreq[len++] = 5; /* version (SOCKS5) */
} socksreq[len++] = 1; /* connect */
socksreq[len++] = 0; /* must be zero */
socksreq[len++] = (unsigned char)((remote_port >> 8) & 0xff); /* PORT MSB */ if(!socks5_resolve_local) {
socksreq[len++] = (unsigned char)(remote_port & 0xff); /* PORT LSB */ 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 defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
if(conn->socks5_gssapi_enctype) { if(conn->socks5_gssapi_enctype) {
failf(data, "SOCKS5 GSS-API protection not yet implemented."); failf(data, "SOCKS5 GSS-API protection not yet implemented.");
return CURLE_COULDNT_CONNECT;
} }
else
#endif #endif
code = Curl_write_plain(conn, sock, (char *)socksreq, len, &written); sx->outp = socksreq;
sx->outstanding = len;
if(code || (len != written)) { 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."); failf(data, "Failed to send SOCKS5 connect request.");
return CURLE_COULDNT_CONNECT; return CURLE_COULDNT_CONNECT;
} }
if(sx->outstanding != written) {
len = 10; /* minimum packet size is 10 */ /* remain in state */
sx->outstanding -= written;
sx->outp += written;
return CURLE_OK;
}
#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
if(conn->socks5_gssapi_enctype) { if(conn->socks5_gssapi_enctype) {
failf(data, "SOCKS5 GSS-API protection not yet implemented."); failf(data, "SOCKS5 GSS-API protection not yet implemented.");
return CURLE_COULDNT_CONNECT;
} }
else
#endif #endif
result = Curl_blockread_all(conn, sock, (char *)socksreq, sx->outstanding = 10; /* minimum packet size is 10 */
len, &actualread); sx->outp = socksreq;
sxstate(conn, CONNECT_REQ_READ);
if(result || (len != actualread)) { /* 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."); failf(data, "Failed to receive SOCKS5 connect request ack.");
return CURLE_COULDNT_CONNECT; return CURLE_COULDNT_CONNECT;
} }
else if(actualread != sx->outstanding) {
/* remain in state */
sx->outstanding -= actualread;
sx->outp += actualread;
return CURLE_OK;
}
if(socksreq[0] != 5) { /* version */ if(socksreq[0] != 5) { /* version */
failf(data, failf(data,
"SOCKS5 reply has wrong version, version should be 5."); "SOCKS5 reply has wrong version, version should be 5.");
return CURLE_COULDNT_CONNECT; 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 /* 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 1928, so the reply packet should be read until the end to avoid errors
subsequent protocol level. at subsequent protocol level.
+----+-----+-------+------+----------+----------+ +----+-----+-------+------+----------+----------+
|VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
@ -754,25 +941,36 @@ CURLcode Curl_SOCKS5(const char *proxy_user,
/* decrypt_gssapi_blockread already read the whole packet */ /* decrypt_gssapi_blockread already read the whole packet */
#endif #endif
if(len > 10) { if(len > 10) {
result = Curl_blockread_all(conn, sock, (char *)&socksreq[10], sx->outstanding = len - 10; /* get the rest */
len - 10, &actualread); sx->outp = &socksreq[10];
if(result || ((len - 10) != actualread)) { sxstate(conn, CONNECT_REQ_READ_MORE);
failf(data, "Failed to receive SOCKS5 connect request ack.");
return CURLE_COULDNT_CONNECT;
} }
else {
sxstate(conn, CONNECT_DONE);
break;
} }
#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
} }
#endif #endif
/* FALLTHROUGH */
if(socksreq[1] != 0) { /* Anything besides 0 is an error */ case CONNECT_REQ_READ_MORE:
failf(data, "Can't complete SOCKS5 connection to %s. (%d)", result = Curl_read_plain(sockfd, (char *)sx->outp,
dest, (unsigned char)socksreq[1]); sx->outstanding, &actualread);
if(result && (CURLE_AGAIN != result)) {
failf(data, "Failed to receive SOCKS5 connect request ack.");
return CURLE_COULDNT_CONNECT; 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"); infof(data, "SOCKS5 request granted.\n");
(void)curlx_nonblock(sock, TRUE); *done = TRUE;
return CURLE_OK; /* Proxy was successful! */ return CURLE_OK; /* Proxy was successful! */
} }

View File

@ -7,7 +7,7 @@
* | (__| |_| | _ <| |___ * | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____| * \___|\___/|_| \_\_____|
* *
* Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al. * Copyright (C) 1998 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.
* *
* This software is licensed as described in the file COPYING, which * This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms * you should have received as part of this distribution. The terms
@ -27,13 +27,13 @@
#ifdef CURL_DISABLE_PROXY #ifdef CURL_DISABLE_PROXY
#define Curl_SOCKS4(a,b,c,d,e) CURLE_NOT_BUILT_IN #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_SOCKS5(a,b,c,d,e,f) CURLE_NOT_BUILT_IN
#define Curl_SOCKS_getsock(x,y,z) 0
#else #else
/* /*
* Helper read-from-socket functions. Does the same as Curl_read() but it * 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. * 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 * This is STUPID BLOCKING behavior
* is what we have...
*/ */
int Curl_blockread_all(struct connectdata *conn, int Curl_blockread_all(struct connectdata *conn,
curl_socket_t sockfd, curl_socket_t sockfd,
@ -41,6 +41,9 @@ int Curl_blockread_all(struct connectdata *conn,
ssize_t buffersize, ssize_t buffersize,
ssize_t *n); 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 * This function logs in to a SOCKS4(a) proxy and sends the specifics to the
* final destination server. * final destination server.
@ -49,7 +52,8 @@ CURLcode Curl_SOCKS4(const char *proxy_name,
const char *hostname, const char *hostname,
int remote_port, int remote_port,
int sockindex, int sockindex,
struct connectdata *conn); struct connectdata *conn,
bool *done);
/* /*
* This function logs in to a SOCKS5 proxy and sends the specifics to the * 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, const char *hostname,
int remote_port, int remote_port,
int sockindex, int sockindex,
struct connectdata *conn); struct connectdata *conn,
bool *done);
#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
/* /*

View File

@ -5,8 +5,8 @@
* | (__| |_| | _ <| |___ * | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____| * \___|\___/|_| \_\_____|
* *
* Copyright (C) 2012 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.
* Copyright (C) 2009, Markus Moeller, <markus_moeller@compuserve.com> * Copyright (C) 2009, Markus Moeller, <markus_moeller@compuserve.com>
* Copyright (C) 2012 - 2018, Daniel Stenberg, <daniel@haxx.se>, et al.
* *
* This software is licensed as described in the file COPYING, which * This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms * 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; return CURLE_COULDNT_CONNECT;
} }
(void)curlx_nonblock(sock, FALSE);
/* As long as we need to keep sending some context info, and there's no */ /* As long as we need to keep sending some context info, and there's no */
/* errors, keep sending it... */ /* errors, keep sending it... */
for(;;) { for(;;) {
@ -513,6 +515,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex,
gss_release_buffer(&gss_status, &gss_recv_token); gss_release_buffer(&gss_status, &gss_recv_token);
} }
(void)curlx_nonblock(sock, TRUE);
infof(data, "SOCKS5 access with%s protection granted.\n", infof(data, "SOCKS5 access with%s protection granted.\n",
(socksreq[0] == 0)?"out GSS-API data": (socksreq[0] == 0)?"out GSS-API data":
((socksreq[0] == 1)?" GSS-API integrity":" GSS-API confidentiality")); ((socksreq[0] == 1)?" GSS-API integrity":" GSS-API confidentiality"));

View File

@ -5,7 +5,7 @@
* | (__| |_| | _ <| |___ * | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____| * \___|\___/|_| \_\_____|
* *
* Copyright (C) 2012 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al. * Copyright (C) 2012 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.
* Copyright (C) 2009, 2011, Markus Moeller, <markus_moeller@compuserve.com> * Copyright (C) 2009, 2011, Markus Moeller, <markus_moeller@compuserve.com>
* *
* This software is licensed as described in the file COPYING, which * 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; return CURLE_COULDNT_CONNECT;
} }
(void)curlx_nonblock(sock, FALSE);
/* As long as we need to keep sending some context info, and there's no */ /* As long as we need to keep sending some context info, and there's no */
/* errors, keep sending it... */ /* errors, keep sending it... */
for(;;) { for(;;) {
@ -587,6 +589,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex,
memcpy(socksreq, sspi_w_token[0].pvBuffer, sspi_w_token[0].cbBuffer); memcpy(socksreq, sspi_w_token[0].pvBuffer, sspi_w_token[0].cbBuffer);
s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer);
} }
(void)curlx_nonblock(sock, TRUE);
infof(data, "SOCKS5 access with%s protection granted.\n", infof(data, "SOCKS5 access with%s protection granted.\n",
(socksreq[0] == 0)?"out GSS-API data": (socksreq[0] == 0)?"out GSS-API data":

View File

@ -476,7 +476,6 @@ struct ConnectBits {
BIT(tcp_fastopen); /* use TCP Fast Open */ BIT(tcp_fastopen); /* use TCP Fast Open */
BIT(tls_enable_npn); /* TLS NPN extension? */ BIT(tls_enable_npn); /* TLS NPN extension? */
BIT(tls_enable_alpn); /* TLS ALPN extension? */ BIT(tls_enable_alpn); /* TLS ALPN extension? */
BIT(socksproxy_connecting); /* connecting through a socks proxy */
BIT(connect_only); BIT(connect_only);
}; };
@ -817,6 +816,41 @@ struct http_connect_state {
struct ldapconninfo; 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 * The connectdata struct contains all fields and variables that should be
* unique for an entire connection. * unique for an entire connection.
@ -826,7 +860,7 @@ struct connectdata {
caution that this might very well vary between different times this caution that this might very well vary between different times this
connection is used! */ connection is used! */
struct Curl_easy *data; struct Curl_easy *data;
struct connstate cnnct;
struct curl_llist_element bundle_node; /* conncache */ struct curl_llist_element bundle_node; /* conncache */
/* chunk is for HTTP chunked encoding, but is in the general connectdata /* chunk is for HTTP chunked encoding, but is in the general connectdata