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