http_proxy.c (13860B)
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 #include "sendf.h" 33 #include "http.h" 34 #include "url.h" 35 #include "select.h" 36 #include "progress.h" 37 #include "cfilters.h" 38 #include "cf-h1-proxy.h" 39 #include "cf-h2-proxy.h" 40 #include "connect.h" 41 #include "vtls/vtls.h" 42 #include "transfer.h" 43 #include "multiif.h" 44 #include "vauth/vauth.h" 45 #include "curlx/strparse.h" 46 47 /* The last 3 #include files should be in this order */ 48 #include "curl_printf.h" 49 #include "curl_memory.h" 50 #include "memdebug.h" 51 52 static bool hd_name_eq(const char *n1, size_t n1len, 53 const char *n2, size_t n2len) 54 { 55 return (n1len == n2len) ? curl_strnequal(n1, n2, n1len) : FALSE; 56 } 57 58 static CURLcode dynhds_add_custom(struct Curl_easy *data, 59 bool is_connect, int httpversion, 60 struct dynhds *hds) 61 { 62 struct connectdata *conn = data->conn; 63 const char *ptr; 64 struct curl_slist *h[2]; 65 struct curl_slist *headers; 66 int numlists = 1; /* by default */ 67 int i; 68 69 enum Curl_proxy_use proxy; 70 71 if(is_connect) 72 proxy = HEADER_CONNECT; 73 else 74 proxy = conn->bits.httpproxy && !conn->bits.tunnel_proxy ? 75 HEADER_PROXY : HEADER_SERVER; 76 77 switch(proxy) { 78 case HEADER_SERVER: 79 h[0] = data->set.headers; 80 break; 81 case HEADER_PROXY: 82 h[0] = data->set.headers; 83 if(data->set.sep_headers) { 84 h[1] = data->set.proxyheaders; 85 numlists++; 86 } 87 break; 88 case HEADER_CONNECT: 89 if(data->set.sep_headers) 90 h[0] = data->set.proxyheaders; 91 else 92 h[0] = data->set.headers; 93 break; 94 } 95 96 /* loop through one or two lists */ 97 for(i = 0; i < numlists; i++) { 98 for(headers = h[i]; headers; headers = headers->next) { 99 const char *name, *value; 100 size_t namelen, valuelen; 101 102 /* There are 2 quirks in place for custom headers: 103 * 1. setting only 'name:' to suppress a header from being sent 104 * 2. setting only 'name;' to send an empty (illegal) header 105 */ 106 ptr = strchr(headers->data, ':'); 107 if(ptr) { 108 name = headers->data; 109 namelen = ptr - headers->data; 110 ptr++; /* pass the colon */ 111 curlx_str_passblanks(&ptr); 112 if(*ptr) { 113 value = ptr; 114 valuelen = strlen(value); 115 } 116 else { 117 /* quirk #1, suppress this header */ 118 continue; 119 } 120 } 121 else { 122 ptr = strchr(headers->data, ';'); 123 124 if(!ptr) { 125 /* neither : nor ; in provided header value. We seem 126 * to ignore this silently */ 127 continue; 128 } 129 130 name = headers->data; 131 namelen = ptr - headers->data; 132 ptr++; /* pass the semicolon */ 133 curlx_str_passblanks(&ptr); 134 if(!*ptr) { 135 /* quirk #2, send an empty header */ 136 value = ""; 137 valuelen = 0; 138 } 139 else { 140 /* this may be used for something else in the future, 141 * ignore this for now */ 142 continue; 143 } 144 } 145 146 DEBUGASSERT(name && value); 147 if(data->state.aptr.host && 148 /* a Host: header was sent already, do not pass on any custom Host: 149 header as that will produce *two* in the same request! */ 150 hd_name_eq(name, namelen, STRCONST("Host:"))) 151 ; 152 else if(data->state.httpreq == HTTPREQ_POST_FORM && 153 /* this header (extended by formdata.c) is sent later */ 154 hd_name_eq(name, namelen, STRCONST("Content-Type:"))) 155 ; 156 else if(data->state.httpreq == HTTPREQ_POST_MIME && 157 /* this header is sent later */ 158 hd_name_eq(name, namelen, STRCONST("Content-Type:"))) 159 ; 160 else if(data->req.authneg && 161 /* while doing auth neg, do not allow the custom length since 162 we will force length zero then */ 163 hd_name_eq(name, namelen, STRCONST("Content-Length:"))) 164 ; 165 else if(data->state.aptr.te && 166 /* when asking for Transfer-Encoding, do not pass on a custom 167 Connection: */ 168 hd_name_eq(name, namelen, STRCONST("Connection:"))) 169 ; 170 else if((httpversion >= 20) && 171 hd_name_eq(name, namelen, STRCONST("Transfer-Encoding:"))) 172 /* HTTP/2 and HTTP/3 do not support chunked requests */ 173 ; 174 else if((hd_name_eq(name, namelen, STRCONST("Authorization:")) || 175 hd_name_eq(name, namelen, STRCONST("Cookie:"))) && 176 /* be careful of sending this potentially sensitive header to 177 other hosts */ 178 !Curl_auth_allowed_to_host(data)) 179 ; 180 else { 181 CURLcode result; 182 183 result = Curl_dynhds_add(hds, name, namelen, value, valuelen); 184 if(result) 185 return result; 186 } 187 } 188 } 189 190 return CURLE_OK; 191 } 192 193 CURLcode Curl_http_proxy_get_destination(struct Curl_cfilter *cf, 194 const char **phostname, 195 int *pport, bool *pipv6_ip) 196 { 197 DEBUGASSERT(cf); 198 DEBUGASSERT(cf->conn); 199 200 if(cf->conn->bits.conn_to_host) 201 *phostname = cf->conn->conn_to_host.name; 202 else if(cf->sockindex == SECONDARYSOCKET) 203 *phostname = cf->conn->secondaryhostname; 204 else 205 *phostname = cf->conn->host.name; 206 207 if(cf->sockindex == SECONDARYSOCKET) 208 *pport = cf->conn->secondary_port; 209 else if(cf->conn->bits.conn_to_port) 210 *pport = cf->conn->conn_to_port; 211 else 212 *pport = cf->conn->remote_port; 213 214 if(*phostname != cf->conn->host.name) 215 *pipv6_ip = (strchr(*phostname, ':') != NULL); 216 else 217 *pipv6_ip = cf->conn->bits.ipv6_ip; 218 219 return CURLE_OK; 220 } 221 222 struct cf_proxy_ctx { 223 /* the protocol specific sub-filter we install during connect */ 224 struct Curl_cfilter *cf_protocol; 225 int httpversion; /* HTTP version used to CONNECT */ 226 }; 227 228 CURLcode Curl_http_proxy_create_CONNECT(struct httpreq **preq, 229 struct Curl_cfilter *cf, 230 struct Curl_easy *data, 231 int http_version_major) 232 { 233 struct cf_proxy_ctx *ctx = cf->ctx; 234 const char *hostname = NULL; 235 char *authority = NULL; 236 int port; 237 bool ipv6_ip; 238 CURLcode result; 239 struct httpreq *req = NULL; 240 241 result = Curl_http_proxy_get_destination(cf, &hostname, &port, &ipv6_ip); 242 if(result) 243 goto out; 244 245 authority = aprintf("%s%s%s:%d", ipv6_ip ? "[" : "", hostname, 246 ipv6_ip ?"]" : "", port); 247 if(!authority) { 248 result = CURLE_OUT_OF_MEMORY; 249 goto out; 250 } 251 252 result = Curl_http_req_make(&req, "CONNECT", sizeof("CONNECT")-1, 253 NULL, 0, authority, strlen(authority), 254 NULL, 0); 255 if(result) 256 goto out; 257 258 /* Setup the proxy-authorization header, if any */ 259 result = Curl_http_output_auth(data, cf->conn, req->method, HTTPREQ_GET, 260 req->authority, TRUE); 261 if(result) 262 goto out; 263 264 /* If user is not overriding Host: header, we add for HTTP/1.x */ 265 if(http_version_major == 1 && 266 !Curl_checkProxyheaders(data, cf->conn, STRCONST("Host"))) { 267 result = Curl_dynhds_cadd(&req->headers, "Host", authority); 268 if(result) 269 goto out; 270 } 271 272 if(data->state.aptr.proxyuserpwd) { 273 result = Curl_dynhds_h1_cadd_line(&req->headers, 274 data->state.aptr.proxyuserpwd); 275 if(result) 276 goto out; 277 } 278 279 if(!Curl_checkProxyheaders(data, cf->conn, STRCONST("User-Agent")) && 280 data->set.str[STRING_USERAGENT] && *data->set.str[STRING_USERAGENT]) { 281 result = Curl_dynhds_cadd(&req->headers, "User-Agent", 282 data->set.str[STRING_USERAGENT]); 283 if(result) 284 goto out; 285 } 286 287 if(http_version_major == 1 && 288 !Curl_checkProxyheaders(data, cf->conn, STRCONST("Proxy-Connection"))) { 289 result = Curl_dynhds_cadd(&req->headers, "Proxy-Connection", "Keep-Alive"); 290 if(result) 291 goto out; 292 } 293 294 result = dynhds_add_custom(data, TRUE, ctx->httpversion, &req->headers); 295 296 out: 297 if(result && req) { 298 Curl_http_req_free(req); 299 req = NULL; 300 } 301 free(authority); 302 *preq = req; 303 return result; 304 } 305 306 static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf, 307 struct Curl_easy *data, 308 bool *done) 309 { 310 struct cf_proxy_ctx *ctx = cf->ctx; 311 CURLcode result; 312 313 if(cf->connected) { 314 *done = TRUE; 315 return CURLE_OK; 316 } 317 318 CURL_TRC_CF(data, cf, "connect"); 319 connect_sub: 320 result = cf->next->cft->do_connect(cf->next, data, done); 321 if(result || !*done) 322 return result; 323 324 *done = FALSE; 325 if(!ctx->cf_protocol) { 326 struct Curl_cfilter *cf_protocol = NULL; 327 int httpversion = 0; 328 int alpn = Curl_conn_cf_is_ssl(cf->next) ? 329 cf->conn->proxy_alpn : CURL_HTTP_VERSION_1_1; 330 331 /* First time call after the subchain connected */ 332 switch(alpn) { 333 case CURL_HTTP_VERSION_NONE: 334 case CURL_HTTP_VERSION_1_0: 335 case CURL_HTTP_VERSION_1_1: 336 CURL_TRC_CF(data, cf, "installing subfilter for HTTP/1.1"); 337 infof(data, "CONNECT tunnel: HTTP/1.%d negotiated", 338 (alpn == CURL_HTTP_VERSION_1_0) ? 0 : 1); 339 result = Curl_cf_h1_proxy_insert_after(cf, data); 340 if(result) 341 goto out; 342 cf_protocol = cf->next; 343 httpversion = (alpn == CURL_HTTP_VERSION_1_0) ? 10 : 11; 344 break; 345 #ifdef USE_NGHTTP2 346 case CURL_HTTP_VERSION_2: 347 CURL_TRC_CF(data, cf, "installing subfilter for HTTP/2"); 348 infof(data, "CONNECT tunnel: HTTP/2 negotiated"); 349 result = Curl_cf_h2_proxy_insert_after(cf, data); 350 if(result) 351 goto out; 352 cf_protocol = cf->next; 353 httpversion = 20; 354 break; 355 #endif 356 default: 357 infof(data, "CONNECT tunnel: unsupported ALPN(%d) negotiated", alpn); 358 result = CURLE_COULDNT_CONNECT; 359 goto out; 360 } 361 362 ctx->cf_protocol = cf_protocol; 363 ctx->httpversion = httpversion; 364 /* after we installed the filter "below" us, we call connect 365 * on out sub-chain again. 366 */ 367 goto connect_sub; 368 } 369 else { 370 /* subchain connected and we had already installed the protocol filter. 371 * This means the protocol tunnel is established, we are done. 372 */ 373 DEBUGASSERT(ctx->cf_protocol); 374 result = CURLE_OK; 375 } 376 377 out: 378 if(!result) { 379 cf->connected = TRUE; 380 *done = TRUE; 381 } 382 return result; 383 } 384 385 CURLcode Curl_cf_http_proxy_query(struct Curl_cfilter *cf, 386 struct Curl_easy *data, 387 int query, int *pres1, void *pres2) 388 { 389 switch(query) { 390 case CF_QUERY_HOST_PORT: 391 *pres1 = (int)cf->conn->http_proxy.port; 392 *((const char **)pres2) = cf->conn->http_proxy.host.name; 393 return CURLE_OK; 394 default: 395 break; 396 } 397 return cf->next ? 398 cf->next->cft->query(cf->next, data, query, pres1, pres2) : 399 CURLE_UNKNOWN_OPTION; 400 } 401 402 static void http_proxy_cf_destroy(struct Curl_cfilter *cf, 403 struct Curl_easy *data) 404 { 405 struct cf_proxy_ctx *ctx = cf->ctx; 406 407 (void)data; 408 CURL_TRC_CF(data, cf, "destroy"); 409 free(ctx); 410 } 411 412 static void http_proxy_cf_close(struct Curl_cfilter *cf, 413 struct Curl_easy *data) 414 { 415 struct cf_proxy_ctx *ctx = cf->ctx; 416 417 CURL_TRC_CF(data, cf, "close"); 418 cf->connected = FALSE; 419 if(ctx->cf_protocol) { 420 struct Curl_cfilter *f; 421 /* if someone already removed it, we assume he also 422 * took care of destroying it. */ 423 for(f = cf->next; f; f = f->next) { 424 if(f == ctx->cf_protocol) { 425 /* still in our sub-chain */ 426 Curl_conn_cf_discard_sub(cf, ctx->cf_protocol, data, FALSE); 427 break; 428 } 429 } 430 ctx->cf_protocol = NULL; 431 } 432 if(cf->next) 433 cf->next->cft->do_close(cf->next, data); 434 } 435 436 437 struct Curl_cftype Curl_cft_http_proxy = { 438 "HTTP-PROXY", 439 CF_TYPE_IP_CONNECT|CF_TYPE_PROXY, 440 0, 441 http_proxy_cf_destroy, 442 http_proxy_cf_connect, 443 http_proxy_cf_close, 444 Curl_cf_def_shutdown, 445 Curl_cf_def_adjust_pollset, 446 Curl_cf_def_data_pending, 447 Curl_cf_def_send, 448 Curl_cf_def_recv, 449 Curl_cf_def_cntrl, 450 Curl_cf_def_conn_is_alive, 451 Curl_cf_def_conn_keep_alive, 452 Curl_cf_http_proxy_query, 453 }; 454 455 CURLcode Curl_cf_http_proxy_insert_after(struct Curl_cfilter *cf_at, 456 struct Curl_easy *data) 457 { 458 struct Curl_cfilter *cf; 459 struct cf_proxy_ctx *ctx = NULL; 460 CURLcode result; 461 462 (void)data; 463 ctx = calloc(1, sizeof(*ctx)); 464 if(!ctx) { 465 result = CURLE_OUT_OF_MEMORY; 466 goto out; 467 } 468 result = Curl_cf_create(&cf, &Curl_cft_http_proxy, ctx); 469 if(result) 470 goto out; 471 ctx = NULL; 472 Curl_conn_cf_insert_after(cf_at, cf); 473 474 out: 475 free(ctx); 476 return result; 477 } 478 479 #endif /* ! CURL_DISABLE_HTTP && !CURL_DISABLE_PROXY */