/*************************************************************************** * _ _ ____ _ * 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 "http_proxy.h" #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_PROXY) #include #ifdef USE_HYPER #include #endif #include "sendf.h" #include "http.h" #include "url.h" #include "select.h" #include "progress.h" #include "cfilters.h" #include "cf-h1-proxy.h" #include "cf-h2-proxy.h" #include "connect.h" #include "curlx.h" #include "vtls/vtls.h" #include "transfer.h" #include "multiif.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" CURLcode Curl_http_proxy_get_destination(struct Curl_cfilter *cf, const char **phostname, int *pport, bool *pipv6_ip) { DEBUGASSERT(cf); DEBUGASSERT(cf->conn); if(cf->conn->bits.conn_to_host) *phostname = cf->conn->conn_to_host.name; else if(cf->sockindex == SECONDARYSOCKET) *phostname = cf->conn->secondaryhostname; else *phostname = cf->conn->host.name; if(cf->sockindex == SECONDARYSOCKET) *pport = cf->conn->secondary_port; else if(cf->conn->bits.conn_to_port) *pport = cf->conn->conn_to_port; else *pport = cf->conn->remote_port; if(*phostname != cf->conn->host.name) *pipv6_ip = (strchr(*phostname, ':') != NULL); else *pipv6_ip = cf->conn->bits.ipv6_ip; return CURLE_OK; } CURLcode Curl_http_proxy_create_CONNECT(struct httpreq **preq, struct Curl_cfilter *cf, struct Curl_easy *data, int http_version_major) { const char *hostname = NULL; char *authority = NULL; int port; bool ipv6_ip; CURLcode result; struct httpreq *req = NULL; result = Curl_http_proxy_get_destination(cf, &hostname, &port, &ipv6_ip); if(result) goto out; authority = aprintf("%s%s%s:%d", ipv6_ip?"[":"", hostname, ipv6_ip?"]":"", port); if(!authority) { result = CURLE_OUT_OF_MEMORY; goto out; } result = Curl_http_req_make(&req, "CONNECT", sizeof("CONNECT")-1, NULL, 0, authority, strlen(authority), NULL, 0); if(result) goto out; /* Setup the proxy-authorization header, if any */ result = Curl_http_output_auth(data, cf->conn, req->method, HTTPREQ_GET, req->authority, TRUE); if(result) goto out; /* If user is not overriding Host: header, we add for HTTP/1.x */ if(http_version_major == 1 && !Curl_checkProxyheaders(data, cf->conn, STRCONST("Host"))) { result = Curl_dynhds_cadd(&req->headers, "Host", authority); if(result) goto out; } if(data->state.aptr.proxyuserpwd) { result = Curl_dynhds_h1_cadd_line(&req->headers, data->state.aptr.proxyuserpwd); if(result) goto out; } if(!Curl_checkProxyheaders(data, cf->conn, STRCONST("User-Agent")) && data->set.str[STRING_USERAGENT] && *data->set.str[STRING_USERAGENT]) { result = Curl_dynhds_cadd(&req->headers, "User-Agent", data->set.str[STRING_USERAGENT]); if(result) goto out; } if(http_version_major == 1 && !Curl_checkProxyheaders(data, cf->conn, STRCONST("Proxy-Connection"))) { result = Curl_dynhds_cadd(&req->headers, "Proxy-Connection", "Keep-Alive"); if(result) goto out; } result = Curl_dynhds_add_custom(data, TRUE, &req->headers); out: if(result && req) { Curl_http_req_free(req); req = NULL; } free(authority); *preq = req; return result; } struct cf_proxy_ctx { /* the protocol specific sub-filter we install during connect */ struct Curl_cfilter *cf_protocol; }; static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf, struct Curl_easy *data, bool blocking, bool *done) { struct cf_proxy_ctx *ctx = cf->ctx; CURLcode result; if(cf->connected) { *done = TRUE; return CURLE_OK; } CURL_TRC_CF(data, cf, "connect"); connect_sub: result = cf->next->cft->do_connect(cf->next, data, blocking, done); if(result || !*done) return result; *done = FALSE; if(!ctx->cf_protocol) { struct Curl_cfilter *cf_protocol = NULL; int alpn = Curl_conn_cf_is_ssl(cf->next)? cf->conn->proxy_alpn : CURL_HTTP_VERSION_1_1; /* First time call after the subchain connected */ switch(alpn) { case CURL_HTTP_VERSION_NONE: case CURL_HTTP_VERSION_1_0: case CURL_HTTP_VERSION_1_1: CURL_TRC_CF(data, cf, "installing subfilter for HTTP/1.1"); infof(data, "CONNECT tunnel: HTTP/1.%d negotiated", (alpn == CURL_HTTP_VERSION_1_0)? 0 : 1); result = Curl_cf_h1_proxy_insert_after(cf, data); if(result) goto out; cf_protocol = cf->next; break; #ifdef USE_NGHTTP2 case CURL_HTTP_VERSION_2: CURL_TRC_CF(data, cf, "installing subfilter for HTTP/2"); infof(data, "CONNECT tunnel: HTTP/2 negotiated"); result = Curl_cf_h2_proxy_insert_after(cf, data); if(result) goto out; cf_protocol = cf->next; break; #endif default: infof(data, "CONNECT tunnel: unsupported ALPN(%d) negotiated", alpn); result = CURLE_COULDNT_CONNECT; goto out; } ctx->cf_protocol = cf_protocol; /* after we installed the filter "below" us, we call connect * on out sub-chain again. */ goto connect_sub; } else { /* subchain connected and we had already installed the protocol filter. * This means the protocol tunnel is established, we are done. */ DEBUGASSERT(ctx->cf_protocol); result = CURLE_OK; } out: if(!result) { cf->connected = TRUE; *done = TRUE; } return result; } void Curl_cf_http_proxy_get_host(struct Curl_cfilter *cf, struct Curl_easy *data, const char **phost, const char **pdisplay_host, int *pport) { (void)data; if(!cf->connected) { *phost = cf->conn->http_proxy.host.name; *pdisplay_host = cf->conn->http_proxy.host.dispname; *pport = (int)cf->conn->http_proxy.port; } else { cf->next->cft->get_host(cf->next, data, phost, pdisplay_host, pport); } } static void http_proxy_cf_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_proxy_ctx *ctx = cf->ctx; (void)data; CURL_TRC_CF(data, cf, "destroy"); free(ctx); } static void http_proxy_cf_close(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_proxy_ctx *ctx = cf->ctx; CURL_TRC_CF(data, cf, "close"); cf->connected = FALSE; if(ctx->cf_protocol) { struct Curl_cfilter *f; /* if someone already removed it, we assume he also * took care of destroying it. */ for(f = cf->next; f; f = f->next) { if(f == ctx->cf_protocol) { /* still in our sub-chain */ Curl_conn_cf_discard_sub(cf, ctx->cf_protocol, data, FALSE); break; } } ctx->cf_protocol = NULL; } if(cf->next) cf->next->cft->do_close(cf->next, data); } struct Curl_cftype Curl_cft_http_proxy = { "HTTP-PROXY", CF_TYPE_IP_CONNECT|CF_TYPE_PROXY, 0, http_proxy_cf_destroy, http_proxy_cf_connect, http_proxy_cf_close, Curl_cf_http_proxy_get_host, 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, }; CURLcode Curl_cf_http_proxy_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data) { struct Curl_cfilter *cf; struct cf_proxy_ctx *ctx = NULL; CURLcode result; (void)data; ctx = calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; } result = Curl_cf_create(&cf, &Curl_cft_http_proxy, ctx); if(result) goto out; ctx = NULL; Curl_conn_cf_insert_after(cf_at, cf); out: free(ctx); return result; } #endif /* ! CURL_DISABLE_HTTP && !CURL_DISABLE_PROXY */