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 * The Alt-Svc: header is defined in RFC 7838: 26 * https://datatracker.ietf.org/doc/html/rfc7838 27 */ 28#include "curl_setup.h" 29 30#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_ALTSVC) 31#include <curl/curl.h> 32#include "urldata.h" 33#include "altsvc.h" 34#include "curl_get_line.h" 35#include "strcase.h" 36#include "parsedate.h" 37#include "sendf.h" 38#include "warnless.h" 39#include "fopen.h" 40#include "rename.h" 41#include "strdup.h" 42#include "inet_pton.h" 43 44/* The last 3 #include files should be in this order */ 45#include "curl_printf.h" 46#include "curl_memory.h" 47#include "memdebug.h" 48 49#define MAX_ALTSVC_LINE 4095 50#define MAX_ALTSVC_DATELENSTR "64" 51#define MAX_ALTSVC_DATELEN 64 52#define MAX_ALTSVC_HOSTLENSTR "512" 53#define MAX_ALTSVC_HOSTLEN 512 54#define MAX_ALTSVC_ALPNLENSTR "10" 55#define MAX_ALTSVC_ALPNLEN 10 56 57#define H3VERSION "h3" 58 59static enum alpnid alpn2alpnid(char *name) 60{ 61 if(strcasecompare(name, "h1")) 62 return ALPN_h1; 63 if(strcasecompare(name, "h2")) 64 return ALPN_h2; 65 if(strcasecompare(name, H3VERSION)) 66 return ALPN_h3; 67 return ALPN_none; /* unknown, probably rubbish input */ 68} 69 70/* Given the ALPN ID, return the name */ 71const char *Curl_alpnid2str(enum alpnid id) 72{ 73 switch(id) { 74 case ALPN_h1: 75 return "h1"; 76 case ALPN_h2: 77 return "h2"; 78 case ALPN_h3: 79 return H3VERSION; 80 default: 81 return ""; /* bad */ 82 } 83} 84 85 86static void altsvc_free(struct altsvc *as) 87{ 88 free(as->src.host); 89 free(as->dst.host); 90 free(as); 91} 92 93static struct altsvc *altsvc_createid(const char *srchost, 94 const char *dsthost, 95 enum alpnid srcalpnid, 96 enum alpnid dstalpnid, 97 unsigned int srcport, 98 unsigned int dstport) 99{ 100 struct altsvc *as = calloc(1, sizeof(struct altsvc)); 101 size_t hlen; 102 size_t dlen; 103 if(!as) 104 return NULL; 105 hlen = strlen(srchost); 106 dlen = strlen(dsthost); 107 DEBUGASSERT(hlen); 108 DEBUGASSERT(dlen); 109 if(!hlen || !dlen) { 110 /* bad input */ 111 free(as); 112 return NULL; 113 } 114 if((hlen > 2) && srchost[0] == '[') { 115 /* IPv6 address, strip off brackets */ 116 srchost++; 117 hlen -= 2; 118 } 119 else if(srchost[hlen - 1] == '.') 120 /* strip off trailing dot */ 121 hlen--; 122 if((dlen > 2) && dsthost[0] == '[') { 123 /* IPv6 address, strip off brackets */ 124 dsthost++; 125 dlen -= 2; 126 } 127 128 as->src.host = Curl_memdup0(srchost, hlen); 129 if(!as->src.host) 130 goto error; 131 132 as->dst.host = Curl_memdup0(dsthost, dlen); 133 if(!as->dst.host) 134 goto error; 135 136 as->src.alpnid = srcalpnid; 137 as->dst.alpnid = dstalpnid; 138 as->src.port = curlx_ultous(srcport); 139 as->dst.port = curlx_ultous(dstport); 140 141 return as; 142error: 143 altsvc_free(as); 144 return NULL; 145} 146 147static struct altsvc *altsvc_create(char *srchost, 148 char *dsthost, 149 char *srcalpn, 150 char *dstalpn, 151 unsigned int srcport, 152 unsigned int dstport) 153{ 154 enum alpnid dstalpnid = alpn2alpnid(dstalpn); 155 enum alpnid srcalpnid = alpn2alpnid(srcalpn); 156 if(!srcalpnid || !dstalpnid) 157 return NULL; 158 return altsvc_createid(srchost, dsthost, srcalpnid, dstalpnid, 159 srcport, dstport); 160} 161 162/* only returns SERIOUS errors */ 163static CURLcode altsvc_add(struct altsvcinfo *asi, char *line) 164{ 165 /* Example line: 166 h2 example.com 443 h3 shiny.example.com 8443 "20191231 10:00:00" 1 167 */ 168 char srchost[MAX_ALTSVC_HOSTLEN + 1]; 169 char dsthost[MAX_ALTSVC_HOSTLEN + 1]; 170 char srcalpn[MAX_ALTSVC_ALPNLEN + 1]; 171 char dstalpn[MAX_ALTSVC_ALPNLEN + 1]; 172 char date[MAX_ALTSVC_DATELEN + 1]; 173 unsigned int srcport; 174 unsigned int dstport; 175 unsigned int prio; 176 unsigned int persist; 177 int rc; 178 179 rc = sscanf(line, 180 "%" MAX_ALTSVC_ALPNLENSTR "s %" MAX_ALTSVC_HOSTLENSTR "s %u " 181 "%" MAX_ALTSVC_ALPNLENSTR "s %" MAX_ALTSVC_HOSTLENSTR "s %u " 182 "\"%" MAX_ALTSVC_DATELENSTR "[^\"]\" %u %u", 183 srcalpn, srchost, &srcport, 184 dstalpn, dsthost, &dstport, 185 date, &persist, &prio); 186 if(9 == rc) { 187 struct altsvc *as; 188 time_t expires = Curl_getdate_capped(date); 189 as = altsvc_create(srchost, dsthost, srcalpn, dstalpn, srcport, dstport); 190 if(as) { 191 as->expires = expires; 192 as->prio = prio; 193 as->persist = persist ? 1 : 0; 194 Curl_llist_insert_next(&asi->list, asi->list.tail, as, &as->node); 195 } 196 } 197 198 return CURLE_OK; 199} 200 201/* 202 * Load alt-svc entries from the given file. The text based line-oriented file 203 * format is documented here: https://curl.se/docs/alt-svc.html 204 * 205 * This function only returns error on major problems that prevent alt-svc 206 * handling to work completely. It will ignore individual syntactical errors 207 * etc. 208 */ 209static CURLcode altsvc_load(struct altsvcinfo *asi, const char *file) 210{ 211 CURLcode result = CURLE_OK; 212 char *line = NULL; 213 FILE *fp; 214 215 /* we need a private copy of the file name so that the altsvc cache file 216 name survives an easy handle reset */ 217 free(asi->filename); 218 asi->filename = strdup(file); 219 if(!asi->filename) 220 return CURLE_OUT_OF_MEMORY; 221 222 fp = fopen(file, FOPEN_READTEXT); 223 if(fp) { 224 line = malloc(MAX_ALTSVC_LINE); 225 if(!line) 226 goto fail; 227 while(Curl_get_line(line, MAX_ALTSVC_LINE, fp)) { 228 char *lineptr = line; 229 while(*lineptr && ISBLANK(*lineptr)) 230 lineptr++; 231 if(*lineptr == '#') 232 /* skip commented lines */ 233 continue; 234 235 altsvc_add(asi, lineptr); 236 } 237 free(line); /* free the line buffer */ 238 fclose(fp); 239 } 240 return result; 241 242fail: 243 Curl_safefree(asi->filename); 244 free(line); 245 fclose(fp); 246 return CURLE_OUT_OF_MEMORY; 247} 248 249/* 250 * Write this single altsvc entry to a single output line 251 */ 252 253static CURLcode altsvc_out(struct altsvc *as, FILE *fp) 254{ 255 struct tm stamp; 256 const char *dst6_pre = ""; 257 const char *dst6_post = ""; 258 const char *src6_pre = ""; 259 const char *src6_post = ""; 260 CURLcode result = Curl_gmtime(as->expires, &stamp); 261 if(result) 262 return result; 263#ifdef ENABLE_IPV6 264 else { 265 char ipv6_unused[16]; 266 if(1 == Curl_inet_pton(AF_INET6, as->dst.host, ipv6_unused)) { 267 dst6_pre = "["; 268 dst6_post = "]"; 269 } 270 if(1 == Curl_inet_pton(AF_INET6, as->src.host, ipv6_unused)) { 271 src6_pre = "["; 272 src6_post = "]"; 273 } 274 } 275#endif 276 fprintf(fp, 277 "%s %s%s%s %u " 278 "%s %s%s%s %u " 279 "\"%d%02d%02d " 280 "%02d:%02d:%02d\" " 281 "%u %d\n", 282 Curl_alpnid2str(as->src.alpnid), 283 src6_pre, as->src.host, src6_post, 284 as->src.port, 285 286 Curl_alpnid2str(as->dst.alpnid), 287 dst6_pre, as->dst.host, dst6_post, 288 as->dst.port, 289 290 stamp.tm_year + 1900, stamp.tm_mon + 1, stamp.tm_mday, 291 stamp.tm_hour, stamp.tm_min, stamp.tm_sec, 292 as->persist, as->prio); 293 return CURLE_OK; 294} 295 296/* ---- library-wide functions below ---- */ 297 298/* 299 * Curl_altsvc_init() creates a new altsvc cache. 300 * It returns the new instance or NULL if something goes wrong. 301 */ 302struct altsvcinfo *Curl_altsvc_init(void) 303{ 304 struct altsvcinfo *asi = calloc(1, sizeof(struct altsvcinfo)); 305 if(!asi) 306 return NULL; 307 Curl_llist_init(&asi->list, NULL); 308 309 /* set default behavior */ 310 asi->flags = CURLALTSVC_H1 311#ifdef USE_HTTP2 312 | CURLALTSVC_H2 313#endif 314#ifdef ENABLE_QUIC 315 | CURLALTSVC_H3 316#endif 317 ; 318 return asi; 319} 320 321/* 322 * Curl_altsvc_load() loads alt-svc from file. 323 */ 324CURLcode Curl_altsvc_load(struct altsvcinfo *asi, const char *file) 325{ 326 CURLcode result; 327 DEBUGASSERT(asi); 328 result = altsvc_load(asi, file); 329 return result; 330} 331 332/* 333 * Curl_altsvc_ctrl() passes on the external bitmask. 334 */ 335CURLcode Curl_altsvc_ctrl(struct altsvcinfo *asi, const long ctrl) 336{ 337 DEBUGASSERT(asi); 338 asi->flags = ctrl; 339 return CURLE_OK; 340} 341 342/* 343 * Curl_altsvc_cleanup() frees an altsvc cache instance and all associated 344 * resources. 345 */ 346void Curl_altsvc_cleanup(struct altsvcinfo **altsvcp) 347{ 348 struct Curl_llist_element *e; 349 struct Curl_llist_element *n; 350 if(*altsvcp) { 351 struct altsvcinfo *altsvc = *altsvcp; 352 for(e = altsvc->list.head; e; e = n) { 353 struct altsvc *as = e->ptr; 354 n = e->next; 355 altsvc_free(as); 356 } 357 free(altsvc->filename); 358 free(altsvc); 359 *altsvcp = NULL; /* clear the pointer */ 360 } 361} 362 363/* 364 * Curl_altsvc_save() writes the altsvc cache to a file. 365 */ 366CURLcode Curl_altsvc_save(struct Curl_easy *data, 367 struct altsvcinfo *altsvc, const char *file) 368{ 369 struct Curl_llist_element *e; 370 struct Curl_llist_element *n; 371 CURLcode result = CURLE_OK; 372 FILE *out; 373 char *tempstore = NULL; 374 375 if(!altsvc) 376 /* no cache activated */ 377 return CURLE_OK; 378 379 /* if not new name is given, use the one we stored from the load */ 380 if(!file && altsvc->filename) 381 file = altsvc->filename; 382 383 if((altsvc->flags & CURLALTSVC_READONLYFILE) || !file || !file[0]) 384 /* marked as read-only, no file or zero length file name */ 385 return CURLE_OK; 386 387 result = Curl_fopen(data, file, &out, &tempstore); 388 if(!result) { 389 fputs("# Your alt-svc cache. https://curl.se/docs/alt-svc.html\n" 390 "# This file was generated by libcurl! Edit at your own risk.\n", 391 out); 392 for(e = altsvc->list.head; e; e = n) { 393 struct altsvc *as = e->ptr; 394 n = e->next; 395 result = altsvc_out(as, out); 396 if(result) 397 break; 398 } 399 fclose(out); 400 if(!result && tempstore && Curl_rename(tempstore, file)) 401 result = CURLE_WRITE_ERROR; 402 403 if(result && tempstore) 404 unlink(tempstore); 405 } 406 free(tempstore); 407 return result; 408} 409 410static CURLcode getalnum(const char **ptr, char *alpnbuf, size_t buflen) 411{ 412 size_t len; 413 const char *protop; 414 const char *p = *ptr; 415 while(*p && ISBLANK(*p)) 416 p++; 417 protop = p; 418 while(*p && !ISBLANK(*p) && (*p != ';') && (*p != '=')) 419 p++; 420 len = p - protop; 421 *ptr = p; 422 423 if(!len || (len >= buflen)) 424 return CURLE_BAD_FUNCTION_ARGUMENT; 425 memcpy(alpnbuf, protop, len); 426 alpnbuf[len] = 0; 427 return CURLE_OK; 428} 429 430/* hostcompare() returns true if 'host' matches 'check'. The first host 431 * argument may have a trailing dot present that will be ignored. 432 */ 433static bool hostcompare(const char *host, const char *check) 434{ 435 size_t hlen = strlen(host); 436 size_t clen = strlen(check); 437 438 if(hlen && (host[hlen - 1] == '.')) 439 hlen--; 440 if(hlen != clen) 441 /* they can't match if they have different lengths */ 442 return FALSE; 443 return strncasecompare(host, check, hlen); 444} 445 446/* altsvc_flush() removes all alternatives for this source origin from the 447 list */ 448static void altsvc_flush(struct altsvcinfo *asi, enum alpnid srcalpnid, 449 const char *srchost, unsigned short srcport) 450{ 451 struct Curl_llist_element *e; 452 struct Curl_llist_element *n; 453 for(e = asi->list.head; e; e = n) { 454 struct altsvc *as = e->ptr; 455 n = e->next; 456 if((srcalpnid == as->src.alpnid) && 457 (srcport == as->src.port) && 458 hostcompare(srchost, as->src.host)) { 459 Curl_llist_remove(&asi->list, e, NULL); 460 altsvc_free(as); 461 } 462 } 463} 464 465#ifdef DEBUGBUILD 466/* to play well with debug builds, we can *set* a fixed time this will 467 return */ 468static time_t altsvc_debugtime(void *unused) 469{ 470 char *timestr = getenv("CURL_TIME"); 471 (void)unused; 472 if(timestr) { 473 unsigned long val = strtol(timestr, NULL, 10); 474 return (time_t)val; 475 } 476 return time(NULL); 477} 478#undef time 479#define time(x) altsvc_debugtime(x) 480#endif 481 482#define ISNEWLINE(x) (((x) == '\n') || (x) == '\r') 483 484/* 485 * Curl_altsvc_parse() takes an incoming alt-svc response header and stores 486 * the data correctly in the cache. 487 * 488 * 'value' points to the header *value*. That's contents to the right of the 489 * header name. 490 * 491 * Currently this function rejects invalid data without returning an error. 492 * Invalid host name, port number will result in the specific alternative 493 * being rejected. Unknown protocols are skipped. 494 */ 495CURLcode Curl_altsvc_parse(struct Curl_easy *data, 496 struct altsvcinfo *asi, const char *value, 497 enum alpnid srcalpnid, const char *srchost, 498 unsigned short srcport) 499{ 500 const char *p = value; 501 size_t len; 502 char namebuf[MAX_ALTSVC_HOSTLEN] = ""; 503 char alpnbuf[MAX_ALTSVC_ALPNLEN] = ""; 504 struct altsvc *as; 505 unsigned short dstport = srcport; /* the same by default */ 506 CURLcode result = getalnum(&p, alpnbuf, sizeof(alpnbuf)); 507 size_t entries = 0; 508#ifdef CURL_DISABLE_VERBOSE_STRINGS 509 (void)data; 510#endif 511 if(result) { 512 infof(data, "Excessive alt-svc header, ignoring."); 513 return CURLE_OK; 514 } 515 516 DEBUGASSERT(asi); 517 518 /* "clear" is a magic keyword */ 519 if(strcasecompare(alpnbuf, "clear")) { 520 /* Flush cached alternatives for this source origin */ 521 altsvc_flush(asi, srcalpnid, srchost, srcport); 522 return CURLE_OK; 523 } 524 525 do { 526 if(*p == '=') { 527 /* [protocol]="[host][:port]" */ 528 enum alpnid dstalpnid = alpn2alpnid(alpnbuf); /* the same by default */ 529 p++; 530 if(*p == '\"') { 531 const char *dsthost = ""; 532 const char *value_ptr; 533 char option[32]; 534 unsigned long num; 535 char *end_ptr; 536 bool quoted = FALSE; 537 time_t maxage = 24 * 3600; /* default is 24 hours */ 538 bool persist = FALSE; 539 bool valid = TRUE; 540 p++; 541 if(*p != ':') { 542 /* host name starts here */ 543 const char *hostp = p; 544 if(*p == '[') { 545 /* pass all valid IPv6 letters - does not handle zone id */ 546 len = strspn(++p, "0123456789abcdefABCDEF:."); 547 if(p[len] != ']') 548 /* invalid host syntax, bail out */ 549 break; 550 /* we store the IPv6 numerical address *with* brackets */ 551 len += 2; 552 p = &p[len-1]; 553 } 554 else { 555 while(*p && (ISALNUM(*p) || (*p == '.') || (*p == '-'))) 556 p++; 557 len = p - hostp; 558 } 559 if(!len || (len >= MAX_ALTSVC_HOSTLEN)) { 560 infof(data, "Excessive alt-svc host name, ignoring."); 561 valid = FALSE; 562 } 563 else { 564 memcpy(namebuf, hostp, len); 565 namebuf[len] = 0; 566 dsthost = namebuf; 567 } 568 } 569 else { 570 /* no destination name, use source host */ 571 dsthost = srchost; 572 } 573 if(*p == ':') { 574 unsigned long port = 0; 575 p++; 576 if(ISDIGIT(*p)) 577 /* a port number */ 578 port = strtoul(p, &end_ptr, 10); 579 else 580 end_ptr = (char *)p; /* not left uninitialized */ 581 if(!port || port > USHRT_MAX || end_ptr == p || *end_ptr != '\"') { 582 infof(data, "Unknown alt-svc port number, ignoring."); 583 valid = FALSE; 584 } 585 else { 586 dstport = curlx_ultous(port); 587 p = end_ptr; 588 } 589 } 590 if(*p++ != '\"') 591 break; 592 /* Handle the optional 'ma' and 'persist' flags. Unknown flags 593 are skipped. */ 594 for(;;) { 595 while(ISBLANK(*p)) 596 p++; 597 if(*p != ';') 598 break; 599 p++; /* pass the semicolon */ 600 if(!*p || ISNEWLINE(*p)) 601 break; 602 result = getalnum(&p, option, sizeof(option)); 603 if(result) { 604 /* skip option if name is too long */ 605 option[0] = '\0'; 606 } 607 while(*p && ISBLANK(*p)) 608 p++; 609 if(*p != '=') 610 return CURLE_OK; 611 p++; 612 while(*p && ISBLANK(*p)) 613 p++; 614 if(!*p) 615 return CURLE_OK; 616 if(*p == '\"') { 617 /* quoted value */ 618 p++; 619 quoted = TRUE; 620 } 621 value_ptr = p; 622 if(quoted) { 623 while(*p && *p != '\"') 624 p++; 625 if(!*p++) 626 return CURLE_OK; 627 } 628 else { 629 while(*p && !ISBLANK(*p) && *p!= ';' && *p != ',') 630 p++; 631 } 632 num = strtoul(value_ptr, &end_ptr, 10); 633 if((end_ptr != value_ptr) && (num < ULONG_MAX)) { 634 if(strcasecompare("ma", option)) 635 maxage = num; 636 else if(strcasecompare("persist", option) && (num == 1)) 637 persist = TRUE; 638 } 639 } 640 if(dstalpnid && valid) { 641 if(!entries++) 642 /* Flush cached alternatives for this source origin, if any - when 643 this is the first entry of the line. */ 644 altsvc_flush(asi, srcalpnid, srchost, srcport); 645 646 as = altsvc_createid(srchost, dsthost, 647 srcalpnid, dstalpnid, 648 srcport, dstport); 649 if(as) { 650 /* The expires time also needs to take the Age: value (if any) into 651 account. [See RFC 7838 section 3.1] */ 652 as->expires = maxage + time(NULL); 653 as->persist = persist; 654 Curl_llist_insert_next(&asi->list, asi->list.tail, as, &as->node); 655 infof(data, "Added alt-svc: %s:%d over %s", dsthost, dstport, 656 Curl_alpnid2str(dstalpnid)); 657 } 658 } 659 } 660 else 661 break; 662 /* after the double quote there can be a comma if there's another 663 string or a semicolon if no more */ 664 if(*p == ',') { 665 /* comma means another alternative is presented */ 666 p++; 667 result = getalnum(&p, alpnbuf, sizeof(alpnbuf)); 668 if(result) 669 break; 670 } 671 } 672 else 673 break; 674 } while(*p && (*p != ';') && (*p != '\n') && (*p != '\r')); 675 676 return CURLE_OK; 677} 678 679/* 680 * Return TRUE on a match 681 */ 682bool Curl_altsvc_lookup(struct altsvcinfo *asi, 683 enum alpnid srcalpnid, const char *srchost, 684 int srcport, 685 struct altsvc **dstentry, 686 const int versions) /* one or more bits */ 687{ 688 struct Curl_llist_element *e; 689 struct Curl_llist_element *n; 690 time_t now = time(NULL); 691 DEBUGASSERT(asi); 692 DEBUGASSERT(srchost); 693 DEBUGASSERT(dstentry); 694 695 for(e = asi->list.head; e; e = n) { 696 struct altsvc *as = e->ptr; 697 n = e->next; 698 if(as->expires < now) { 699 /* an expired entry, remove */ 700 Curl_llist_remove(&asi->list, e, NULL); 701 altsvc_free(as); 702 continue; 703 } 704 if((as->src.alpnid == srcalpnid) && 705 hostcompare(srchost, as->src.host) && 706 (as->src.port == srcport) && 707 (versions & as->dst.alpnid)) { 708 /* match */ 709 *dstentry = as; 710 return TRUE; 711 } 712 } 713 return FALSE; 714} 715 716#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_ALTSVC */ 717