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
Curl_http_proxy_get_destination(struct Curl_cfilter * cf,const char ** phostname,int * pport,bool * pipv6_ip)55 CURLcode 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(*phostname, ':') != NULL);
78 else
79 *pipv6_ip = cf->conn->bits.ipv6_ip;
80
81 return CURLE_OK;
82 }
83
Curl_http_proxy_create_CONNECT(struct httpreq ** preq,struct Curl_cfilter * cf,struct Curl_easy * data,int http_version_major)84 CURLcode 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, &hostname, &port, &ipv6_ip);
97 if(result)
98 goto out;
99
100 authority = aprintf("%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(&req, "CONNECT", sizeof("CONNECT")-1,
108 NULL, 0, authority, strlen(authority),
109 NULL, 0);
110 if(result)
111 goto out;
112
113 /* Setup the proxy-authorization header, if any */
114 result = Curl_http_output_auth(data, cf->conn, req->method, HTTPREQ_GET,
115 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, cf->conn, STRCONST("Host"))) {
122 result = Curl_dynhds_cadd(&req->headers, "Host", authority);
123 if(result)
124 goto out;
125 }
126
127 if(data->state.aptr.proxyuserpwd) {
128 result = Curl_dynhds_h1_cadd_line(&req->headers,
129 data->state.aptr.proxyuserpwd);
130 if(result)
131 goto out;
132 }
133
134 if(!Curl_checkProxyheaders(data, cf->conn, STRCONST("User-Agent")) &&
135 data->set.str[STRING_USERAGENT] && *data->set.str[STRING_USERAGENT]) {
136 result = Curl_dynhds_cadd(&req->headers, "User-Agent",
137 data->set.str[STRING_USERAGENT]);
138 if(result)
139 goto out;
140 }
141
142 if(http_version_major == 1 &&
143 !Curl_checkProxyheaders(data, cf->conn, STRCONST("Proxy-Connection"))) {
144 result = Curl_dynhds_cadd(&req->headers, "Proxy-Connection", "Keep-Alive");
145 if(result)
146 goto out;
147 }
148
149 result = Curl_dynhds_add_custom(data, TRUE, &req->headers);
150
151 out:
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
162 struct cf_proxy_ctx {
163 /* the protocol specific sub-filter we install during connect */
164 struct Curl_cfilter *cf_protocol;
165 };
166
http_proxy_cf_connect(struct Curl_cfilter * cf,struct Curl_easy * data,bool blocking,bool * done)167 static 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");
180 connect_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->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
234 out:
235 if(!result) {
236 cf->connected = TRUE;
237 *done = TRUE;
238 }
239 return result;
240 }
241
Curl_cf_http_proxy_get_host(struct Curl_cfilter * cf,struct Curl_easy * data,const char ** phost,const char ** pdisplay_host,int * pport)242 void 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
http_proxy_cf_destroy(struct Curl_cfilter * cf,struct Curl_easy * data)259 static 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
http_proxy_cf_close(struct Curl_cfilter * cf,struct Curl_easy * data)269 static 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, 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
294 struct Curl_cftype Curl_cft_http_proxy = {
295 "HTTP-PROXY",
296 CF_TYPE_IP_CONNECT|CF_TYPE_PROXY,
297 0,
298 http_proxy_cf_destroy,
299 http_proxy_cf_connect,
300 http_proxy_cf_close,
301 Curl_cf_def_shutdown,
302 Curl_cf_http_proxy_get_host,
303 Curl_cf_def_adjust_pollset,
304 Curl_cf_def_data_pending,
305 Curl_cf_def_send,
306 Curl_cf_def_recv,
307 Curl_cf_def_cntrl,
308 Curl_cf_def_conn_is_alive,
309 Curl_cf_def_conn_keep_alive,
310 Curl_cf_def_query,
311 };
312
Curl_cf_http_proxy_insert_after(struct Curl_cfilter * cf_at,struct Curl_easy * data)313 CURLcode Curl_cf_http_proxy_insert_after(struct Curl_cfilter *cf_at,
314 struct Curl_easy *data)
315 {
316 struct Curl_cfilter *cf;
317 struct cf_proxy_ctx *ctx = NULL;
318 CURLcode result;
319
320 (void)data;
321 ctx = calloc(1, sizeof(*ctx));
322 if(!ctx) {
323 result = CURLE_OUT_OF_MEMORY;
324 goto out;
325 }
326 result = Curl_cf_create(&cf, &Curl_cft_http_proxy, ctx);
327 if(result)
328 goto out;
329 ctx = NULL;
330 Curl_conn_cf_insert_after(cf_at, cf);
331
332 out:
333 free(ctx);
334 return result;
335 }
336
337 #endif /* ! CURL_DISABLE_HTTP && !CURL_DISABLE_PROXY */
338