1/***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9 *
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at https://curl.se/docs/copyright.html.
13 *
14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15 * copies of the Software, and permit persons to whom the Software is
16 * furnished to do so, under the terms of the COPYING file.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 * SPDX-License-Identifier: curl
22 *
23 ***************************************************************************/
24
25#include "curl_setup.h"
26
27#include "http_proxy.h"
28
29#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_PROXY)
30
31#include <curl/curl.h>
32#ifdef USE_HYPER
33#include <hyper.h>
34#endif
35#include "sendf.h"
36#include "http.h"
37#include "url.h"
38#include "select.h"
39#include "progress.h"
40#include "cfilters.h"
41#include "cf-h1-proxy.h"
42#include "cf-h2-proxy.h"
43#include "connect.h"
44#include "curlx.h"
45#include "vtls/vtls.h"
46#include "transfer.h"
47#include "multiif.h"
48
49/* The last 3 #include files should be in this order */
50#include "curl_printf.h"
51#include "curl_memory.h"
52#include "memdebug.h"
53
54
55CURLcode Curl_http_proxy_get_destination(struct Curl_cfilter *cf,
56 const char **phostname,
57 int *pport, bool *pipv6_ip)
58{
59 DEBUGASSERT(cf);
60 DEBUGASSERT(cf->conn);
61
62 if(cf->conn->bits.conn_to_host)
63 *phostname = cf->conn->conn_to_host.name;
64 else if(cf->sockindex == SECONDARYSOCKET)
65 *phostname = cf->conn->secondaryhostname;
66 else
67 *phostname = cf->conn->host.name;
68
69 if(cf->sockindex == SECONDARYSOCKET)
70 *pport = cf->conn->secondary_port;
71 else if(cf->conn->bits.conn_to_port)
72 *pport = cf->conn->conn_to_port;
73 else
74 *pport = cf->conn->remote_port;
75
76 if(*phostname != cf->conn->host.name)
77 *pipv6_ip = (strchr(s: *phostname, c: ':') != NULL);
78 else
79 *pipv6_ip = cf->conn->bits.ipv6_ip;
80
81 return CURLE_OK;
82}
83
84CURLcode Curl_http_proxy_create_CONNECT(struct httpreq **preq,
85 struct Curl_cfilter *cf,
86 struct Curl_easy *data,
87 int http_version_major)
88{
89 const char *hostname = NULL;
90 char *authority = NULL;
91 int port;
92 bool ipv6_ip;
93 CURLcode result;
94 struct httpreq *req = NULL;
95
96 result = Curl_http_proxy_get_destination(cf, phostname: &hostname, pport: &port, pipv6_ip: &ipv6_ip);
97 if(result)
98 goto out;
99
100 authority = aprintf(format: "%s%s%s:%d", ipv6_ip?"[":"", hostname,
101 ipv6_ip?"]":"", port);
102 if(!authority) {
103 result = CURLE_OUT_OF_MEMORY;
104 goto out;
105 }
106
107 result = Curl_http_req_make(preq: &req, method: "CONNECT", m_len: sizeof("CONNECT")-1,
108 NULL, s_len: 0, authority, a_len: strlen(s: authority),
109 NULL, p_len: 0);
110 if(result)
111 goto out;
112
113 /* Setup the proxy-authorization header, if any */
114 result = Curl_http_output_auth(data, conn: cf->conn, request: req->method, httpreq: HTTPREQ_GET,
115 path: req->authority, TRUE);
116 if(result)
117 goto out;
118
119 /* If user is not overriding Host: header, we add for HTTP/1.x */
120 if(http_version_major == 1 &&
121 !Curl_checkProxyheaders(data, conn: cf->conn, STRCONST("Host"))) {
122 result = Curl_dynhds_cadd(dynhds: &req->headers, name: "Host", value: authority);
123 if(result)
124 goto out;
125 }
126
127 if(data->state.aptr.proxyuserpwd) {
128 result = Curl_dynhds_h1_cadd_line(dynhds: &req->headers,
129 line: data->state.aptr.proxyuserpwd);
130 if(result)
131 goto out;
132 }
133
134 if(!Curl_checkProxyheaders(data, conn: cf->conn, STRCONST("User-Agent"))
135 && data->set.str[STRING_USERAGENT]) {
136 result = Curl_dynhds_cadd(dynhds: &req->headers, name: "User-Agent",
137 value: data->set.str[STRING_USERAGENT]);
138 if(result)
139 goto out;
140 }
141
142 if(http_version_major == 1 &&
143 !Curl_checkProxyheaders(data, conn: cf->conn, STRCONST("Proxy-Connection"))) {
144 result = Curl_dynhds_cadd(dynhds: &req->headers, name: "Proxy-Connection", value: "Keep-Alive");
145 if(result)
146 goto out;
147 }
148
149 result = Curl_dynhds_add_custom(data, TRUE, hds: &req->headers);
150
151out:
152 if(result && req) {
153 Curl_http_req_free(req);
154 req = NULL;
155 }
156 free(authority);
157 *preq = req;
158 return result;
159}
160
161
162struct cf_proxy_ctx {
163 /* the protocol specific sub-filter we install during connect */
164 struct Curl_cfilter *cf_protocol;
165};
166
167static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf,
168 struct Curl_easy *data,
169 bool blocking, bool *done)
170{
171 struct cf_proxy_ctx *ctx = cf->ctx;
172 CURLcode result;
173
174 if(cf->connected) {
175 *done = TRUE;
176 return CURLE_OK;
177 }
178
179 CURL_TRC_CF(data, cf, "connect");
180connect_sub:
181 result = cf->next->cft->do_connect(cf->next, data, blocking, done);
182 if(result || !*done)
183 return result;
184
185 *done = FALSE;
186 if(!ctx->cf_protocol) {
187 struct Curl_cfilter *cf_protocol = NULL;
188 int alpn = Curl_conn_cf_is_ssl(cf: cf->next)?
189 cf->conn->proxy_alpn : CURL_HTTP_VERSION_1_1;
190
191 /* First time call after the subchain connected */
192 switch(alpn) {
193 case CURL_HTTP_VERSION_NONE:
194 case CURL_HTTP_VERSION_1_0:
195 case CURL_HTTP_VERSION_1_1:
196 CURL_TRC_CF(data, cf, "installing subfilter for HTTP/1.1");
197 infof(data, "CONNECT tunnel: HTTP/1.%d negotiated",
198 (alpn == CURL_HTTP_VERSION_1_0)? 0 : 1);
199 result = Curl_cf_h1_proxy_insert_after(cf, data);
200 if(result)
201 goto out;
202 cf_protocol = cf->next;
203 break;
204#ifdef USE_NGHTTP2
205 case CURL_HTTP_VERSION_2:
206 CURL_TRC_CF(data, cf, "installing subfilter for HTTP/2");
207 infof(data, "CONNECT tunnel: HTTP/2 negotiated");
208 result = Curl_cf_h2_proxy_insert_after(cf, data);
209 if(result)
210 goto out;
211 cf_protocol = cf->next;
212 break;
213#endif
214 default:
215 infof(data, "CONNECT tunnel: unsupported ALPN(%d) negotiated", alpn);
216 result = CURLE_COULDNT_CONNECT;
217 goto out;
218 }
219
220 ctx->cf_protocol = cf_protocol;
221 /* after we installed the filter "below" us, we call connect
222 * on out sub-chain again.
223 */
224 goto connect_sub;
225 }
226 else {
227 /* subchain connected and we had already installed the protocol filter.
228 * This means the protocol tunnel is established, we are done.
229 */
230 DEBUGASSERT(ctx->cf_protocol);
231 result = CURLE_OK;
232 }
233
234out:
235 if(!result) {
236 cf->connected = TRUE;
237 *done = TRUE;
238 }
239 return result;
240}
241
242void Curl_cf_http_proxy_get_host(struct Curl_cfilter *cf,
243 struct Curl_easy *data,
244 const char **phost,
245 const char **pdisplay_host,
246 int *pport)
247{
248 (void)data;
249 if(!cf->connected) {
250 *phost = cf->conn->http_proxy.host.name;
251 *pdisplay_host = cf->conn->http_proxy.host.dispname;
252 *pport = (int)cf->conn->http_proxy.port;
253 }
254 else {
255 cf->next->cft->get_host(cf->next, data, phost, pdisplay_host, pport);
256 }
257}
258
259static void http_proxy_cf_destroy(struct Curl_cfilter *cf,
260 struct Curl_easy *data)
261{
262 struct cf_proxy_ctx *ctx = cf->ctx;
263
264 (void)data;
265 CURL_TRC_CF(data, cf, "destroy");
266 free(ctx);
267}
268
269static void http_proxy_cf_close(struct Curl_cfilter *cf,
270 struct Curl_easy *data)
271{
272 struct cf_proxy_ctx *ctx = cf->ctx;
273
274 CURL_TRC_CF(data, cf, "close");
275 cf->connected = FALSE;
276 if(ctx->cf_protocol) {
277 struct Curl_cfilter *f;
278 /* if someone already removed it, we assume he also
279 * took care of destroying it. */
280 for(f = cf->next; f; f = f->next) {
281 if(f == ctx->cf_protocol) {
282 /* still in our sub-chain */
283 Curl_conn_cf_discard_sub(cf, discard: ctx->cf_protocol, data, FALSE);
284 break;
285 }
286 }
287 ctx->cf_protocol = NULL;
288 }
289 if(cf->next)
290 cf->next->cft->do_close(cf->next, data);
291}
292
293
294struct Curl_cftype Curl_cft_http_proxy = {
295 "HTTP-PROXY",
296 CF_TYPE_IP_CONNECT,
297 0,
298 http_proxy_cf_destroy,
299 http_proxy_cf_connect,
300 http_proxy_cf_close,
301 Curl_cf_http_proxy_get_host,
302 Curl_cf_def_get_select_socks,
303 Curl_cf_def_data_pending,
304 Curl_cf_def_send,
305 Curl_cf_def_recv,
306 Curl_cf_def_cntrl,
307 Curl_cf_def_conn_is_alive,
308 Curl_cf_def_conn_keep_alive,
309 Curl_cf_def_query,
310};
311
312CURLcode Curl_cf_http_proxy_insert_after(struct Curl_cfilter *cf_at,
313 struct Curl_easy *data)
314{
315 struct Curl_cfilter *cf;
316 struct cf_proxy_ctx *ctx = NULL;
317 CURLcode result;
318
319 (void)data;
320 ctx = calloc(1, sizeof(*ctx));
321 if(!ctx) {
322 result = CURLE_OUT_OF_MEMORY;
323 goto out;
324 }
325 result = Curl_cf_create(pcf: &cf, cft: &Curl_cft_http_proxy, ctx);
326 if(result)
327 goto out;
328 ctx = NULL;
329 Curl_conn_cf_insert_after(cf_at, cf_new: cf);
330
331out:
332 free(ctx);
333 return result;
334}
335
336#endif /* ! CURL_DISABLE_HTTP && !CURL_DISABLE_PROXY */
337