socks filter: pass operation parameters

Pass all operations parameters to a SOCKS filter at creation
time, not relying on "global" connectdata values.

Eliminate modifications to `conn->ip_version` when local resolving
for SOCKS4.

Do not retrieve the socket for GSSAPI blocking calls from connectdata,
but from the filters "below" the SOCKS one.

Closes #21436
This commit is contained in:
Stefan Eissing 2026-04-24 10:38:22 +02:00 committed by Daniel Stenberg
parent 7d295145eb
commit 4840fe3f8a
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
3 changed files with 143 additions and 104 deletions

View file

@ -374,7 +374,27 @@ connect_sub_chain:
/* sub-chain connected, do we need to add more? */
#ifndef CURL_DISABLE_PROXY
if(ctx->state < CF_SETUP_CNNCT_SOCKS && cf->conn->bits.socksproxy) {
result = Curl_cf_socks_proxy_insert_after(cf, data);
/* for the secondary socket (FTP), use the "connect to host"
* but ignore the "connect to port" (use the secondary port)
*/
const char *hostname =
cf->conn->bits.httpproxy ?
cf->conn->http_proxy.host.name :
cf->conn->bits.conn_to_host ?
cf->conn->conn_to_host.name :
cf->sockindex == SECONDARYSOCKET ?
cf->conn->secondaryhostname : cf->conn->host.name;
uint16_t port =
cf->conn->bits.httpproxy ? cf->conn->http_proxy.port :
cf->sockindex == SECONDARYSOCKET ? cf->conn->secondary_port :
cf->conn->bits.conn_to_port ? cf->conn->conn_to_port :
cf->conn->remote_port;
const char *user = cf->conn->socks_proxy.user;
const char *passwd = cf->conn->socks_proxy.passwd;
result = Curl_cf_socks_proxy_insert_after(
cf, data, hostname, port, cf->conn->ip_version,
cf->conn->socks_proxy.proxytype, user, passwd);
if(result)
return result;
ctx->state = CF_SETUP_CNNCT_SOCKS;

View file

@ -95,19 +95,21 @@ static const char * const cf_socks_statename[] = {
#define SOCKS_CHUNKS 1
struct socks_state {
struct socks_ctx {
enum socks_state_t state;
struct bufq iobuf;
const char *hostname;
uint16_t remote_port;
const char *proxy_user;
const char *proxy_password;
const char *user;
const char *passwd;
CURLproxycode presult;
uint32_t resolv_id;
uint8_t ip_version;
uint8_t proxy_type;
unsigned char version;
BIT(resolve_local);
BIT(start_resolving);
BIT(socks4a);
char hostname[1];
};
#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
@ -129,13 +131,15 @@ CURLcode Curl_blockread_all(struct Curl_cfilter *cf,
*pnread = 0;
for(;;) {
timediff_t timeout_ms = Curl_timeleft_ms(data);
curl_socket_t sock = Curl_conn_cf_get_socket(cf, data);
if(timeout_ms < 0) {
/* we already got the timeout */
return CURLE_OPERATION_TIMEDOUT;
}
if(!timeout_ms)
timeout_ms = TIMEDIFF_T_MAX;
if(SOCKET_READABLE(cf->conn->sock[cf->sockindex], timeout_ms) <= 0)
if(SOCKET_READABLE(sock, timeout_ms) <= 0)
return CURLE_OPERATION_TIMEDOUT;
result = Curl_conn_cf_recv(cf->next, data, buf, blen, &nread);
if(result == CURLE_AGAIN)
@ -164,7 +168,7 @@ CURLcode Curl_blockread_all(struct Curl_cfilter *cf,
#endif
/* always use this function to change state, to make debugging easier */
static void socksstate(struct socks_state *sx,
static void socksstate(struct socks_ctx *sx,
struct Curl_cfilter *cf,
struct Curl_easy *data,
enum socks_state_t state
@ -191,7 +195,7 @@ static void socksstate(struct socks_state *sx,
#endif
}
static CURLproxycode socks_failed(struct socks_state *sx,
static CURLproxycode socks_failed(struct socks_ctx *sx,
struct Curl_cfilter *cf,
struct Curl_easy *data,
CURLproxycode presult)
@ -201,7 +205,7 @@ static CURLproxycode socks_failed(struct socks_state *sx,
return presult;
}
static CURLproxycode socks_flush(struct socks_state *sx,
static CURLproxycode socks_flush(struct socks_ctx *sx,
struct Curl_cfilter *cf,
struct Curl_easy *data,
bool *done)
@ -225,7 +229,7 @@ static CURLproxycode socks_flush(struct socks_state *sx,
return CURLPX_OK;
}
static CURLproxycode socks_recv(struct socks_state *sx,
static CURLproxycode socks_recv(struct socks_ctx *sx,
struct Curl_cfilter *cf,
struct Curl_easy *data,
size_t min_bytes,
@ -259,7 +263,7 @@ static CURLproxycode socks_recv(struct socks_state *sx,
return CURLPX_OK;
}
static CURLproxycode socks4_req_add_hd(struct socks_state *sx,
static CURLproxycode socks4_req_add_hd(struct socks_ctx *sx,
struct Curl_easy *data)
{
unsigned char buf[4];
@ -278,14 +282,14 @@ static CURLproxycode socks4_req_add_hd(struct socks_state *sx,
return CURLPX_OK;
}
static CURLproxycode socks4_req_add_user(struct socks_state *sx,
static CURLproxycode socks4_req_add_user(struct socks_ctx *sx,
struct Curl_easy *data)
{
CURLcode result;
size_t nwritten;
if(sx->proxy_user) {
size_t plen = strlen(sx->proxy_user);
if(sx->user) {
size_t plen = strlen(sx->user);
if(plen > 255) {
/* there is no real size limit to this field in the protocol, but
SOCKS5 limits the proxy user field to 255 bytes and it seems likely
@ -294,7 +298,7 @@ static CURLproxycode socks4_req_add_user(struct socks_state *sx,
return CURLPX_LONG_USER;
}
/* add proxy name WITH trailing zero */
result = Curl_bufq_cwrite(&sx->iobuf, sx->proxy_user, plen + 1,
result = Curl_bufq_cwrite(&sx->iobuf, sx->user, plen + 1,
&nwritten);
if(result || (nwritten != (plen + 1)))
return CURLPX_SEND_REQUEST;
@ -309,7 +313,7 @@ static CURLproxycode socks4_req_add_user(struct socks_state *sx,
return CURLPX_OK;
}
static CURLproxycode socks4_resolving(struct socks_state *sx,
static CURLproxycode socks4_resolving(struct socks_ctx *sx,
struct Curl_cfilter *cf,
struct Curl_easy *data,
bool *done)
@ -323,9 +327,8 @@ static CURLproxycode socks4_resolving(struct socks_state *sx,
if(sx->start_resolving) {
/* need to resolve hostname to add destination address */
sx->start_resolving = FALSE;
DEBUGASSERT(sx->hostname && *sx->hostname);
result = Curl_cf_dns_insert_after(
cf, data, Curl_resolv_dns_queries(data, cf->conn->ip_version),
cf, data, Curl_resolv_dns_queries(data, sx->ip_version),
sx->hostname, sx->remote_port, TRNSPRT_TCP, TRUE);
if(result) {
failf(data, "unable to create DNS filter for socks");
@ -370,7 +373,7 @@ static CURLproxycode socks4_resolving(struct socks_state *sx,
return CURLPX_OK;
}
static CURLproxycode socks4_check_resp(struct socks_state *sx,
static CURLproxycode socks4_check_resp(struct socks_ctx *sx,
struct Curl_cfilter *cf,
struct Curl_easy *data)
{
@ -459,7 +462,7 @@ static CURLproxycode socks4_check_resp(struct socks_state *sx,
* Nonsupport "Identification Protocol (RFC1413)"
*/
static CURLproxycode socks4_connect(struct Curl_cfilter *cf,
struct socks_state *sx,
struct socks_ctx *sx,
struct Curl_easy *data)
{
size_t nwritten;
@ -477,16 +480,14 @@ process_state:
case SOCKS4_ST_START:
Curl_bufq_reset(&sx->iobuf);
sx->start_resolving = FALSE;
sx->socks4a = (cf->conn->socks_proxy.proxytype == CURLPROXY_SOCKS4A);
sx->socks4a = (sx->proxy_type == CURLPROXY_SOCKS4A);
sx->resolve_local = !sx->socks4a;
sx->presult = CURLPX_OK;
/* SOCKS4 can only do IPv4, insist! */
cf->conn->ip_version = CURL_IPRESOLVE_V4;
CURL_TRC_CF(data, cf, "SOCKS4%s communication to%s %s:%u",
sx->socks4a ? "a" : "",
cf->conn->bits.httpproxy ? " HTTP proxy" : "",
sx->hostname, sx->remote_port);
sx->ip_version = CURL_IPRESOLVE_V4;
CURL_TRC_CF(data, cf, "SOCKS4%s connecting to %s:%u",
sx->socks4a ? "a" : "", sx->hostname, sx->remote_port);
/*
* Compose socks4 request
@ -579,7 +580,7 @@ process_state:
}
static CURLproxycode socks5_req0_init(struct Curl_cfilter *cf,
struct socks_state *sx,
struct socks_ctx *sx,
struct Curl_easy *data)
{
const unsigned char auth = data->set.socks5auth;
@ -601,7 +602,7 @@ static CURLproxycode socks5_req0_init(struct Curl_cfilter *cf,
"CURLOPT_SOCKS5_AUTH: %u", auth);
if(!(auth & CURLAUTH_BASIC))
/* disable username/password auth */
sx->proxy_user = NULL;
sx->user = NULL;
req[0] = 5; /* version */
nauths = 1;
@ -612,7 +613,7 @@ static CURLproxycode socks5_req0_init(struct Curl_cfilter *cf,
req[1 + nauths] = 1; /* GSS-API */
}
#endif
if(sx->proxy_user) {
if(sx->user) {
++nauths;
req[1 + nauths] = 2; /* username/password */
}
@ -625,7 +626,7 @@ static CURLproxycode socks5_req0_init(struct Curl_cfilter *cf,
return CURLPX_OK;
}
static CURLproxycode socks5_check_resp0(struct socks_state *sx,
static CURLproxycode socks5_check_resp0(struct socks_ctx *sx,
struct Curl_cfilter *cf,
struct Curl_easy *data)
{
@ -677,7 +678,7 @@ static CURLproxycode socks5_check_resp0(struct socks_state *sx,
}
static CURLproxycode socks5_auth_init(struct Curl_cfilter *cf,
struct socks_state *sx,
struct socks_ctx *sx,
struct Curl_easy *data)
{
/* Needs username and password */
@ -685,9 +686,9 @@ static CURLproxycode socks5_auth_init(struct Curl_cfilter *cf,
unsigned char buf[2];
CURLcode result;
if(sx->proxy_user && sx->proxy_password) {
ulen = strlen(sx->proxy_user);
plen = strlen(sx->proxy_password);
if(sx->user && sx->passwd) {
ulen = strlen(sx->user);
plen = strlen(sx->passwd);
/* the lengths must fit in a single byte */
if(ulen > 255) {
failf(data, "Excessive username length for proxy auth");
@ -712,7 +713,7 @@ static CURLproxycode socks5_auth_init(struct Curl_cfilter *cf,
if(result || (nwritten != 2))
return CURLPX_SEND_REQUEST;
if(ulen) {
result = Curl_bufq_cwrite(&sx->iobuf, sx->proxy_user, ulen, &nwritten);
result = Curl_bufq_cwrite(&sx->iobuf, sx->user, ulen, &nwritten);
if(result || (nwritten != ulen))
return CURLPX_SEND_REQUEST;
}
@ -721,7 +722,7 @@ static CURLproxycode socks5_auth_init(struct Curl_cfilter *cf,
if(result || (nwritten != 1))
return CURLPX_SEND_REQUEST;
if(plen) {
result = Curl_bufq_cwrite(&sx->iobuf, sx->proxy_password, plen, &nwritten);
result = Curl_bufq_cwrite(&sx->iobuf, sx->passwd, plen, &nwritten);
if(result || (nwritten != plen))
return CURLPX_SEND_REQUEST;
}
@ -729,7 +730,7 @@ static CURLproxycode socks5_auth_init(struct Curl_cfilter *cf,
return CURLPX_OK;
}
static CURLproxycode socks5_check_auth_resp(struct socks_state *sx,
static CURLproxycode socks5_check_auth_resp(struct socks_ctx *sx,
struct Curl_cfilter *cf,
struct Curl_easy *data)
{
@ -754,7 +755,7 @@ static CURLproxycode socks5_check_auth_resp(struct socks_state *sx,
return CURLPX_OK;
}
static CURLproxycode socks5_req1_init(struct socks_state *sx,
static CURLproxycode socks5_req1_init(struct socks_ctx *sx,
struct Curl_cfilter *cf,
struct Curl_easy *data)
{
@ -823,7 +824,7 @@ static CURLproxycode socks5_req1_init(struct socks_state *sx,
return CURLPX_OK;
}
static CURLproxycode socks5_resolving(struct socks_state *sx,
static CURLproxycode socks5_resolving(struct socks_ctx *sx,
struct Curl_cfilter *cf,
struct Curl_easy *data,
bool *done)
@ -842,9 +843,8 @@ static CURLproxycode socks5_resolving(struct socks_state *sx,
if(sx->start_resolving) {
/* need to resolve hostname to add destination address */
sx->start_resolving = FALSE;
DEBUGASSERT(sx->hostname && *sx->hostname);
result = Curl_cf_dns_insert_after(
cf, data, Curl_resolv_dns_queries(data, cf->conn->ip_version),
cf, data, Curl_resolv_dns_queries(data, sx->ip_version),
sx->hostname, sx->remote_port, TRNSPRT_TCP, TRUE);
if(result) {
failf(data, "unable to create DNS filter for socks");
@ -928,7 +928,7 @@ out:
return presult;
}
static CURLproxycode socks5_recv_resp1(struct socks_state *sx,
static CURLproxycode socks5_recv_resp1(struct socks_ctx *sx,
struct Curl_cfilter *cf,
struct Curl_easy *data,
bool *done)
@ -1027,7 +1027,7 @@ static CURLproxycode socks5_recv_resp1(struct socks_state *sx,
* destination server.
*/
static CURLproxycode socks5_connect(struct Curl_cfilter *cf,
struct socks_state *sx,
struct socks_ctx *sx,
struct Curl_easy *data)
{
CURLproxycode presult;
@ -1037,14 +1037,13 @@ process_state:
switch(sx->state) {
case SOCKS_ST_INIT:
sx->version = 5;
sx->resolve_local = (cf->conn->socks_proxy.proxytype == CURLPROXY_SOCKS5);
sx->resolve_local = (sx->proxy_type == CURLPROXY_SOCKS5);
sxstate(sx, cf, data, SOCKS5_ST_START);
FALLTHROUGH();
case SOCKS5_ST_START:
if(cf->conn->bits.httpproxy)
CURL_TRC_CF(data, cf, "SOCKS5: connecting to HTTP proxy %s port %u",
sx->hostname, sx->remote_port);
CURL_TRC_CF(data, cf, "SOCKS5: connecting to %s:%u",
sx->hostname, sx->remote_port);
presult = socks5_req0_init(cf, sx, data);
if(presult)
return socks_failed(sx, cf, data, presult);
@ -1179,13 +1178,11 @@ process_state:
}
}
static void socks_proxy_cf_free(struct Curl_cfilter *cf)
static void socks_proxy_ctx_free(struct socks_ctx *ctx)
{
struct socks_state *sxstate = cf->ctx;
if(sxstate) {
Curl_bufq_free(&sxstate->iobuf);
curlx_free(sxstate);
cf->ctx = NULL;
if(ctx) {
Curl_bufq_free(&ctx->iobuf);
curlx_free(ctx);
}
}
@ -1200,11 +1197,9 @@ static CURLcode socks_proxy_cf_connect(struct Curl_cfilter *cf,
struct Curl_easy *data,
bool *done)
{
CURLcode result;
struct connectdata *conn = cf->conn;
int sockindex = cf->sockindex;
struct socks_state *sx = cf->ctx;
struct socks_ctx *ctx = cf->ctx;
CURLproxycode pxresult = CURLPX_OK;
CURLcode result;
if(cf->connected) {
*done = TRUE;
@ -1215,47 +1210,19 @@ static CURLcode socks_proxy_cf_connect(struct Curl_cfilter *cf,
if(result || !*done)
return result;
if(!sx) {
cf->ctx = sx = curlx_calloc(1, sizeof(*sx));
if(!sx) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
/* for the secondary socket (FTP), use the "connect to host"
* but ignore the "connect to port" (use the secondary port)
*/
sx->hostname =
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;
sx->remote_port =
conn->bits.httpproxy ? conn->http_proxy.port :
sockindex == SECONDARYSOCKET ? conn->secondary_port :
conn->bits.conn_to_port ? conn->conn_to_port :
(uint16_t)conn->remote_port;
sx->proxy_user = conn->socks_proxy.user;
sx->proxy_password = conn->socks_proxy.passwd;
Curl_bufq_init2(&sx->iobuf, SOCKS_CHUNK_SIZE, SOCKS_CHUNKS,
BUFQ_OPT_SOFT_LIMIT);
}
switch(conn->socks_proxy.proxytype) {
switch(ctx->proxy_type) {
case CURLPROXY_SOCKS5:
case CURLPROXY_SOCKS5_HOSTNAME:
pxresult = socks5_connect(cf, sx, data);
pxresult = socks5_connect(cf, ctx, data);
break;
case CURLPROXY_SOCKS4:
case CURLPROXY_SOCKS4A:
pxresult = socks4_connect(cf, sx, data);
pxresult = socks4_connect(cf, ctx, data);
break;
default:
failf(data, "unknown proxytype option given");
DEBUGASSERT(0); /* should not come here, checked it at creation time */
result = CURLE_COULDNT_CONNECT;
goto out;
}
@ -1265,7 +1232,7 @@ static CURLcode socks_proxy_cf_connect(struct Curl_cfilter *cf,
data->info.pxcode = pxresult;
goto out;
}
else if(sx->state != SOCKS_ST_SUCCESS)
else if(ctx->state != SOCKS_ST_SUCCESS)
goto out;
#ifdef CURLVERBOSE
@ -1275,20 +1242,23 @@ static CURLcode socks_proxy_cf_connect(struct Curl_cfilter *cf,
if(!Curl_conn_cf_get_ip_info(cf->next, data, &is_ipv6, &ipquad))
infof(data, "Opened %sSOCKS connection from %s port %d to %s port %d "
"(via %s port %u)",
(sockindex == SECONDARYSOCKET) ? "2nd " : "",
(cf->sockindex == SECONDARYSOCKET) ? "2nd " : "",
ipquad.local_ip, ipquad.local_port,
sx->hostname, sx->remote_port,
ctx->hostname, ctx->remote_port,
ipquad.remote_ip, ipquad.remote_port);
else
infof(data, "Opened %sSOCKS connection",
(sockindex == SECONDARYSOCKET) ? "2nd " : "");
(cf->sockindex == SECONDARYSOCKET) ? "2nd " : "");
}
#endif
socks_proxy_cf_free(cf);
cf->connected = TRUE;
out:
*done = (bool)cf->connected;
if(*done || result) {
ctx->user = NULL;
ctx->passwd = NULL;
}
return result;
}
@ -1296,7 +1266,7 @@ static CURLcode socks_cf_adjust_pollset(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct easy_pollset *ps)
{
struct socks_state *sx = cf->ctx;
struct socks_ctx *sx = cf->ctx;
CURLcode result = CURLE_OK;
if(!cf->connected && sx) {
@ -1323,24 +1293,24 @@ static CURLcode socks_cf_adjust_pollset(struct Curl_cfilter *cf,
static void socks_proxy_cf_close(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
DEBUGASSERT(cf->next);
cf->connected = FALSE;
socks_proxy_cf_free(cf);
cf->next->cft->do_close(cf->next, data);
if(cf->next)
cf->next->cft->do_close(cf->next, data);
}
static void socks_proxy_cf_destroy(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
(void)data;
socks_proxy_cf_free(cf);
socks_proxy_ctx_free(cf->ctx);
cf->ctx = NULL;
}
static CURLcode socks_cf_query(struct Curl_cfilter *cf,
struct Curl_easy *data,
int query, int *pres1, void *pres2)
{
struct socks_state *sx = cf->ctx;
struct socks_ctx *sx = cf->ctx;
switch(query) {
case CF_QUERY_HOST_PORT:
@ -1383,15 +1353,53 @@ struct Curl_cftype Curl_cft_socks_proxy = {
};
CURLcode Curl_cf_socks_proxy_insert_after(struct Curl_cfilter *cf_at,
struct Curl_easy *data)
struct Curl_easy *data,
const char *hostname,
uint16_t port,
uint8_t ip_version,
uint8_t proxy_type,
const char *user,
const char *passwd)
{
struct Curl_cfilter *cf;
struct socks_ctx *ctx;
size_t hostlen = hostname ? strlen(hostname) : 0;
CURLcode result;
(void)data;
result = Curl_cf_create(&cf, &Curl_cft_socks_proxy, NULL);
if(!hostlen)
return CURLE_FAILED_INIT;
switch(proxy_type) {
case CURLPROXY_SOCKS5:
case CURLPROXY_SOCKS5_HOSTNAME:
case CURLPROXY_SOCKS4:
case CURLPROXY_SOCKS4A:
break; /* all supported */
default:
failf(data, "unknown proxytype %d option given", proxy_type);
return CURLE_COULDNT_CONNECT;
}
/* NUL byte already part of struct size */
ctx = curlx_calloc(1, sizeof(*ctx) + hostlen);
if(!ctx) {
return CURLE_OUT_OF_MEMORY;
}
memcpy(ctx->hostname, hostname, hostlen);
ctx->remote_port = port;
ctx->ip_version = ip_version;
ctx->proxy_type = proxy_type;
ctx->user = user;
ctx->passwd = passwd;
Curl_bufq_init2(&ctx->iobuf, SOCKS_CHUNK_SIZE, SOCKS_CHUNKS,
BUFQ_OPT_SOFT_LIMIT);
result = Curl_cf_create(&cf, &Curl_cft_socks_proxy, ctx);
if(!result)
Curl_conn_cf_insert_after(cf_at, cf);
else
socks_proxy_ctx_free(ctx);
return result;
}

View file

@ -46,8 +46,19 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf,
struct Curl_easy *data);
#endif
/* Insert a SOCKS filter after `cf_at` for connecting to `hostname`
* and `port` with optional credentials.
* Credentials are NOT duplicated and are
* expected to exist during connect phase.
*/
CURLcode Curl_cf_socks_proxy_insert_after(struct Curl_cfilter *cf_at,
struct Curl_easy *data);
struct Curl_easy *data,
const char *hostname,
uint16_t port,
uint8_t ip_version,
uint8_t proxy_type,
const char *user,
const char *passwd);
extern struct Curl_cftype Curl_cft_socks_proxy;