/*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | * / __| | | | |_) | | * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * * Copyright (C) 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 * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is * furnished to do so, under the terms of the COPYING file. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * * SPDX-License-Identifier: curl * ***************************************************************************/ #include "curl_setup.h" #include "urldata.h" #include "cfilters.h" #include "cf-haproxy.h" #include "cf-ip-happy.h" #include "cf-setup.h" #include "curl_trc.h" #include "connect.h" #include "http_proxy.h" #include "socks.h" #include "vquic/cf-capsule.h" #include "vquic/vquic.h" #include "vtls/vtls.h" typedef enum { CF_SETUP_INIT, CF_SETUP_CNNCT_EYEBALLS, CF_SETUP_CNNCT_SOCKS, CF_SETUP_CNNCT_HTTP_PROXY, CF_SETUP_CNNCT_HAPROXY, CF_SETUP_CNNCT_SSL, CF_SETUP_DONE } cf_setup_state; struct cf_setup_ctx { cf_setup_state state; int ssl_mode; uint8_t transport; uint8_t retry_count; }; #ifndef CURL_DISABLE_PROXY static CURLcode cf_setup_add_haproxy(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_setup_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; if(ctx->state < CF_SETUP_CNNCT_HAPROXY) { if(data->set.haproxyprotocol) { if(ctx->transport == TRNSPRT_QUIC) { failf(data, "haproxy protocol does not support QUIC"); return CURLE_UNSUPPORTED_PROTOCOL; } result = Curl_cf_haproxy_insert_after(cf, data); if(result) { CURL_TRC_CF(data, cf, "adding HAPROXY filter failed -> %d", (int)result); return result; } CURL_TRC_CF(data, cf, "added HAPROXY filter"); } ctx->state = CF_SETUP_CNNCT_HAPROXY; } return result; } static CURLcode cf_setup_add_socks(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_setup_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; if(ctx->state < CF_SETUP_CNNCT_SOCKS && cf->conn->socks_proxy.peer) { /* Add a SOCKS proxy to go through `first_peer` to `second_peer`*/ struct Curl_peer *second_peer; if(cf->conn->http_proxy.peer) second_peer = cf->conn->http_proxy.peer; else second_peer = Curl_conn_get_destination(cf->conn, cf->sockindex); if(!second_peer) return CURLE_FAILED_INIT; result = Curl_cf_socks_proxy_insert_after( cf, data, second_peer, cf->conn->ip_version, cf->conn->socks_proxy.proxytype, cf->conn->socks_proxy.creds); if(result) { CURL_TRC_CF(data, cf, "adding SOCKS filter failed -> %d", (int)result); return result; } CURL_TRC_CF(data, cf, "added SOCKS filter to %s:%u", second_peer->hostname, second_peer->port); ctx->state = CF_SETUP_CNNCT_SOCKS; } return result; } #ifndef CURL_DISABLE_HTTP static CURLcode cf_setup_add_http_proxy(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_setup_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; if(ctx->state < CF_SETUP_CNNCT_HTTP_PROXY && cf->conn->http_proxy.peer && !cf->conn->bits.origin_is_proxy) { struct Curl_peer *peer = cf->conn->http_proxy.peer; struct Curl_peer *tunnel_peer = Curl_conn_get_destination(cf->conn, cf->sockindex); #ifdef USE_SSL if(CURL_PROXY_IS_HTTPS(cf->conn->http_proxy.proxytype) && !Curl_conn_is_ssl(cf->conn, cf->sockindex)) { result = Curl_cf_ssl_proxy_insert_after( cf, data, cf->conn->http_proxy.peer); if(result) { CURL_TRC_CF(data, cf, "adding SSL filter for HTTP proxy failed -> %d", (int)result); return result; } CURL_TRC_CF(data, cf, "added SSL filter for HTTP proxy"); } #endif /* USE_SSL */ result = Curl_cf_http_proxy_insert_after( cf, data, peer, tunnel_peer, ctx->transport, cf->conn->http_proxy.proxytype); if(result) { CURL_TRC_CF(data, cf, "adding HTTP proxy tunnel filter failed -> %d", (int)result); return result; } CURL_TRC_CF(data, cf, "added HTTP proxy tunnel filter"); ctx->state = CF_SETUP_CNNCT_HTTP_PROXY; } return result; } #endif /* !CURL_DISABLE_HTTP */ #endif /* CURL_DISABLE_PROXY */ /* Get the origin curl connects its socket to. * Can be origin or the first proxy. */ static struct Curl_peer *conn_get_first_origin(struct connectdata *conn, int sockindex) { #ifndef CURL_DISABLE_PROXY if(conn->socks_proxy.peer) return conn->socks_proxy.peer; if(conn->http_proxy.peer) return conn->http_proxy.peer; #endif return (sockindex == SECONDARYSOCKET) ? conn->origin2 : conn->origin; } static CURLcode cf_setup_add_ip_happy(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_setup_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; if(ctx->state < CF_SETUP_CNNCT_EYEBALLS) { /* What is the first hop we directly connect to and what transport * do we use for it? Only on the first hop we can do Happy Eyeballs. * first_origin and first_peer differ on --connect-to. */ struct Curl_peer *first_origin = conn_get_first_origin(cf->conn, cf->sockindex); struct Curl_peer *first_peer = Curl_conn_get_first_peer(cf->conn, cf->sockindex); struct Curl_peer *tunnel_peer = NULL; uint8_t first_transport = ctx->transport; if(!first_peer) return CURLE_FAILED_INIT; #if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP) if(cf->conn->http_proxy.peer && !cf->conn->bits.origin_is_proxy) { first_transport = Curl_http_proxy_transport(cf->conn->http_proxy.proxytype); tunnel_peer = Curl_conn_get_destination(cf->conn, cf->sockindex); if((first_transport == TRNSPRT_QUIC) && cf->conn->socks_proxy.peer) { failf(data, "HTTP/3 proxy not possible via SOCKS"); return CURLE_UNSUPPORTED_PROTOCOL; } } #endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */ result = cf_ip_happy_insert_after(cf, data, first_origin, first_peer, first_transport, tunnel_peer, ctx->transport); if(result) { CURL_TRC_CF(data, cf, "adding happy eyeballs failed -> %d", (int)result); return result; } if(tunnel_peer && (first_transport == TRNSPRT_QUIC)) { CURL_TRC_CF(data, cf, "happy eyeballing to HTTP/3 proxy %s:%u", first_peer->hostname, first_peer->port); ctx->state = CF_SETUP_CNNCT_HTTP_PROXY; } else { CURL_TRC_CF(data, cf, "happy eyeballing to %s %s:%u", tunnel_peer ? "proxy" : "origin", first_peer->hostname, first_peer->port); ctx->state = CF_SETUP_CNNCT_EYEBALLS; } } return result; } static CURLcode cf_setup_add_origin_filters(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_setup_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; (void)data; /* not used in all builds */ if(ctx->state < CF_SETUP_CNNCT_SSL) { #if !defined(CURL_DISABLE_HTTP) && defined(USE_HTTP3) && \ !defined(CURL_DISABLE_PROXY) /* Wanting QUIC with an HTTP tunneling filter, we now need to add * the QUIC filter on top. Without tunneling, this has already * happened in the Happy Eyeball filter. */ if(ctx->transport == TRNSPRT_QUIC && cf->conn->http_proxy.peer && !cf->conn->bits.origin_is_proxy) { struct Curl_peer *origin = Curl_conn_get_origin(cf->conn, cf->sockindex); struct Curl_peer *peer = Curl_conn_get_destination(cf->conn, cf->sockindex); result = Curl_cf_capsule_insert_after(cf, data); if(result) { CURL_TRC_CF(data, cf, "adding capsule filter failed -> %d", (int)result); return result; } result = Curl_cf_quic_insert_after(cf, origin, peer); if(result) { CURL_TRC_CF(data, cf, "adding QUIC filter failed -> %d", (int)result); return result; } CURL_TRC_CF(data, cf, "added QUIC filter for origin"); } else #endif /* !CURL_DISABLE_HTTP && USE_HTTP3 && CURL_DISABLE_PROXY */ #ifdef USE_SSL if((ctx->ssl_mode == CURL_CF_SSL_ENABLE || (ctx->ssl_mode != CURL_CF_SSL_DISABLE && cf->conn->scheme->flags & PROTOPT_SSL)) && /* we want SSL */ !Curl_conn_is_ssl(cf->conn, cf->sockindex)) { /* it is missing */ #ifndef CURL_DISABLE_PROXY if(cf->conn->bits.origin_is_proxy) { result = Curl_cf_ssl_proxy_insert_after(cf, data, cf->conn->origin); } else #endif { /* Another FTP quirk: when adding SSL verification, to a DATA * connection, always verify against the control's origin */ struct Curl_peer *origin = Curl_conn_get_origin(cf->conn, FIRSTSOCKET); struct Curl_peer *peer = Curl_conn_get_destination(cf->conn, cf->sockindex); result = Curl_cf_ssl_insert_after(cf, data, origin, peer); } if(result) { CURL_TRC_CF(data, cf, "adding SSL filter for origin failed -> %d", (int)result); return result; } CURL_TRC_CF(data, cf, "added SSL filter for origin"); } #endif /* USE_SSL */ ctx->state = CF_SETUP_CNNCT_SSL; } return result; } static CURLcode cf_setup_connect_steps(struct Curl_cfilter *cf, struct Curl_easy *data, bool *done) { struct cf_setup_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; if(cf->connected) { *done = TRUE; return CURLE_OK; } /* connect current sub-chain */ connect_sub_chain: VERBOSE(Curl_conn_trc_filters(data, cf->sockindex, "cf_setup_connect")); if(cf->next && !cf->next->connected) { result = Curl_conn_cf_connect(cf->next, data, done); if(result || !*done) return result; } result = cf_setup_add_ip_happy(cf, data); if(result) return result; if(!cf->next || !cf->next->connected) goto connect_sub_chain; #ifndef CURL_DISABLE_PROXY result = cf_setup_add_socks(cf, data); if(result) return result; if(!cf->next || !cf->next->connected) goto connect_sub_chain; #ifndef CURL_DISABLE_HTTP result = cf_setup_add_http_proxy(cf, data); if(result) return result; if(!cf->next || !cf->next->connected) goto connect_sub_chain; #endif /* !CURL_DISABLE_HTTP */ result = cf_setup_add_haproxy(cf, data); if(result) return result; if(!cf->next || !cf->next->connected) goto connect_sub_chain; #endif /* !CURL_DISABLE_PROXY */ result = cf_setup_add_origin_filters(cf, data); if(result) return result; if(!cf->next || !cf->next->connected) goto connect_sub_chain; ctx->state = CF_SETUP_DONE; cf->connected = TRUE; *done = TRUE; return CURLE_OK; } static CURLcode cf_setup_connect(struct Curl_cfilter *cf, struct Curl_easy *data, bool *done) { struct cf_setup_ctx *ctx = cf->ctx; CURLcode result; /* In some situations, a server/proxy may close the connection and * we need to connect again (HTTP/1.x proxy auth, for example). * We used to close the filters and reuse them for another attempt, * however that complicates filter code and it is simpler to tear them * all down and start over. */ retry: result = cf_setup_connect_steps(cf, data, done); if(result == CURLE_AGAIN) { ++ctx->retry_count; if(ctx->retry_count > 5) /* arbitrary limit, better just timeout? */ return CURLE_COULDNT_CONNECT; CURL_TRC_CF(data, cf, "retrying connect, %d. time", ctx->retry_count); Curl_conn_cf_discard_chain(&cf->next, data); ctx->state = CF_SETUP_INIT; goto retry; } return result; } static void cf_setup_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_setup_ctx *ctx = cf->ctx; CURL_TRC_CF(data, cf, "destroy"); curlx_safefree(ctx); } struct Curl_cftype Curl_cft_setup = { "SETUP", CF_TYPE_SETUP, CURL_LOG_LVL_NONE, cf_setup_destroy, cf_setup_connect, Curl_cf_def_shutdown, Curl_cf_def_adjust_pollset, Curl_cf_def_data_pending, Curl_cf_def_send, Curl_cf_def_recv, Curl_cf_def_cntrl, Curl_cf_def_conn_is_alive, Curl_cf_def_conn_keep_alive, Curl_cf_def_query, }; static CURLcode cf_setup_create(struct Curl_cfilter **pcf, struct Curl_easy *data, uint8_t transport, int ssl_mode) { struct Curl_cfilter *cf = NULL; struct cf_setup_ctx *ctx; CURLcode result = CURLE_OK; (void)data; ctx = curlx_calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; } ctx->state = CF_SETUP_INIT; ctx->ssl_mode = ssl_mode; ctx->transport = transport; result = Curl_cf_create(&cf, &Curl_cft_setup, ctx); if(result) goto out; ctx = NULL; out: *pcf = result ? NULL : cf; if(ctx) { curlx_free(ctx); } return result; } CURLcode Curl_cf_setup_add(struct Curl_easy *data, struct connectdata *conn, int sockindex, uint8_t transport, int ssl_mode) { struct Curl_cfilter *cf; CURLcode result = CURLE_OK; DEBUGASSERT(data); result = cf_setup_create(&cf, data, transport, ssl_mode); if(result) goto out; Curl_conn_cf_add(data, conn, sockindex, cf); out: return result; } CURLcode Curl_cf_setup_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data, uint8_t transport, int ssl_mode) { struct Curl_cfilter *cf; CURLcode result; DEBUGASSERT(data); result = cf_setup_create(&cf, data, transport, ssl_mode); if(result) goto out; Curl_conn_cf_insert_after(cf_at, cf); out: return result; }