• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 2019 - 2021, 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  ***************************************************************************/
22 /*
23  * The Alt-Svc: header is defined in RFC 7838:
24  * https://tools.ietf.org/html/rfc7838
25  */
26 #include "curl_setup.h"
27 
28 #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_ALTSVC)
29 #include <curl/curl.h>
30 #include "urldata.h"
31 #include "altsvc.h"
32 #include "curl_get_line.h"
33 #include "strcase.h"
34 #include "parsedate.h"
35 #include "sendf.h"
36 #include "warnless.h"
37 #include "rand.h"
38 #include "rename.h"
39 
40 /* The last 3 #include files should be in this order */
41 #include "curl_printf.h"
42 #include "curl_memory.h"
43 #include "memdebug.h"
44 
45 #define MAX_ALTSVC_LINE 4095
46 #define MAX_ALTSVC_DATELENSTR "64"
47 #define MAX_ALTSVC_DATELEN 64
48 #define MAX_ALTSVC_HOSTLENSTR "512"
49 #define MAX_ALTSVC_HOSTLEN 512
50 #define MAX_ALTSVC_ALPNLENSTR "10"
51 #define MAX_ALTSVC_ALPNLEN 10
52 
53 #if defined(USE_QUICHE) && !defined(UNITTESTS)
54 #define H3VERSION "h3-29"
55 #elif defined(USE_NGTCP2) && !defined(UNITTESTS)
56 #define H3VERSION "h3-29"
57 #else
58 #define H3VERSION "h3"
59 #endif
60 
alpn2alpnid(char * name)61 static enum alpnid alpn2alpnid(char *name)
62 {
63   if(strcasecompare(name, "h1"))
64     return ALPN_h1;
65   if(strcasecompare(name, "h2"))
66     return ALPN_h2;
67   if(strcasecompare(name, H3VERSION))
68     return ALPN_h3;
69   return ALPN_none; /* unknown, probably rubbish input */
70 }
71 
72 /* Given the ALPN ID, return the name */
Curl_alpnid2str(enum alpnid id)73 const char *Curl_alpnid2str(enum alpnid id)
74 {
75   switch(id) {
76   case ALPN_h1:
77     return "h1";
78   case ALPN_h2:
79     return "h2";
80   case ALPN_h3:
81     return H3VERSION;
82   default:
83     return ""; /* bad */
84   }
85 }
86 
87 
altsvc_free(struct altsvc * as)88 static void altsvc_free(struct altsvc *as)
89 {
90   free(as->src.host);
91   free(as->dst.host);
92   free(as);
93 }
94 
altsvc_createid(const char * srchost,const char * dsthost,enum alpnid srcalpnid,enum alpnid dstalpnid,unsigned int srcport,unsigned int dstport)95 static struct altsvc *altsvc_createid(const char *srchost,
96                                       const char *dsthost,
97                                       enum alpnid srcalpnid,
98                                       enum alpnid dstalpnid,
99                                       unsigned int srcport,
100                                       unsigned int dstport)
101 {
102   struct altsvc *as = calloc(sizeof(struct altsvc), 1);
103   if(!as)
104     return NULL;
105 
106   as->src.host = strdup(srchost);
107   if(!as->src.host)
108     goto error;
109   as->dst.host = strdup(dsthost);
110   if(!as->dst.host)
111     goto error;
112 
113   as->src.alpnid = srcalpnid;
114   as->dst.alpnid = dstalpnid;
115   as->src.port = curlx_ultous(srcport);
116   as->dst.port = curlx_ultous(dstport);
117 
118   return as;
119   error:
120   altsvc_free(as);
121   return NULL;
122 }
123 
altsvc_create(char * srchost,char * dsthost,char * srcalpn,char * dstalpn,unsigned int srcport,unsigned int dstport)124 static struct altsvc *altsvc_create(char *srchost,
125                                     char *dsthost,
126                                     char *srcalpn,
127                                     char *dstalpn,
128                                     unsigned int srcport,
129                                     unsigned int dstport)
130 {
131   enum alpnid dstalpnid = alpn2alpnid(dstalpn);
132   enum alpnid srcalpnid = alpn2alpnid(srcalpn);
133   if(!srcalpnid || !dstalpnid)
134     return NULL;
135   return altsvc_createid(srchost, dsthost, srcalpnid, dstalpnid,
136                          srcport, dstport);
137 }
138 
139 /* only returns SERIOUS errors */
altsvc_add(struct altsvcinfo * asi,char * line)140 static CURLcode altsvc_add(struct altsvcinfo *asi, char *line)
141 {
142   /* Example line:
143      h2 example.com 443 h3 shiny.example.com 8443 "20191231 10:00:00" 1
144    */
145   char srchost[MAX_ALTSVC_HOSTLEN + 1];
146   char dsthost[MAX_ALTSVC_HOSTLEN + 1];
147   char srcalpn[MAX_ALTSVC_ALPNLEN + 1];
148   char dstalpn[MAX_ALTSVC_ALPNLEN + 1];
149   char date[MAX_ALTSVC_DATELEN + 1];
150   unsigned int srcport;
151   unsigned int dstport;
152   unsigned int prio;
153   unsigned int persist;
154   int rc;
155 
156   rc = sscanf(line,
157               "%" MAX_ALTSVC_ALPNLENSTR "s %" MAX_ALTSVC_HOSTLENSTR "s %u "
158               "%" MAX_ALTSVC_ALPNLENSTR "s %" MAX_ALTSVC_HOSTLENSTR "s %u "
159               "\"%" MAX_ALTSVC_DATELENSTR "[^\"]\" %u %u",
160               srcalpn, srchost, &srcport,
161               dstalpn, dsthost, &dstport,
162               date, &persist, &prio);
163   if(9 == rc) {
164     struct altsvc *as;
165     time_t expires = Curl_getdate_capped(date);
166     as = altsvc_create(srchost, dsthost, srcalpn, dstalpn, srcport, dstport);
167     if(as) {
168       as->expires = expires;
169       as->prio = prio;
170       as->persist = persist ? 1 : 0;
171       Curl_llist_insert_next(&asi->list, asi->list.tail, as, &as->node);
172     }
173   }
174 
175   return CURLE_OK;
176 }
177 
178 /*
179  * Load alt-svc entries from the given file. The text based line-oriented file
180  * format is documented here:
181  * https://github.com/curl/curl/wiki/QUIC-implementation
182  *
183  * This function only returns error on major problems that prevents 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_NGHTTP2
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;
333   unsigned char randsuffix[9];
334 
335   if(!altsvc)
336     /* no cache activated */
337     return CURLE_OK;
338 
339   /* if not new name is given, use the one we stored from the load */
340   if(!file && altsvc->filename)
341     file = altsvc->filename;
342 
343   if((altsvc->flags & CURLALTSVC_READONLYFILE) || !file || !file[0])
344     /* marked as read-only, no file or zero length file name */
345     return CURLE_OK;
346 
347   if(Curl_rand_hex(data, randsuffix, sizeof(randsuffix)))
348     return CURLE_FAILED_INIT;
349 
350   tempstore = aprintf("%s.%s.tmp", file, randsuffix);
351   if(!tempstore)
352     return CURLE_OUT_OF_MEMORY;
353 
354   out = fopen(tempstore, FOPEN_WRITETEXT);
355   if(!out)
356     result = CURLE_WRITE_ERROR;
357   else {
358     fputs("# Your alt-svc cache. https://curl.se/docs/alt-svc.html\n"
359           "# This file was generated by libcurl! Edit at your own risk.\n",
360           out);
361     for(e = altsvc->list.head; e; e = n) {
362       struct altsvc *as = e->ptr;
363       n = e->next;
364       result = altsvc_out(as, out);
365       if(result)
366         break;
367     }
368     fclose(out);
369     if(!result && Curl_rename(tempstore, file))
370       result = CURLE_WRITE_ERROR;
371 
372     if(result)
373       unlink(tempstore);
374   }
375   free(tempstore);
376   return result;
377 }
378 
getalnum(const char ** ptr,char * alpnbuf,size_t buflen)379 static CURLcode getalnum(const char **ptr, char *alpnbuf, size_t buflen)
380 {
381   size_t len;
382   const char *protop;
383   const char *p = *ptr;
384   while(*p && ISBLANK(*p))
385     p++;
386   protop = p;
387   while(*p && !ISBLANK(*p) && (*p != ';') && (*p != '='))
388     p++;
389   len = p - protop;
390   *ptr = p;
391 
392   if(!len || (len >= buflen))
393     return CURLE_BAD_FUNCTION_ARGUMENT;
394   memcpy(alpnbuf, protop, len);
395   alpnbuf[len] = 0;
396   return CURLE_OK;
397 }
398 
399 /* altsvc_flush() removes all alternatives for this source origin from the
400    list */
altsvc_flush(struct altsvcinfo * asi,enum alpnid srcalpnid,const char * srchost,unsigned short srcport)401 static void altsvc_flush(struct altsvcinfo *asi, enum alpnid srcalpnid,
402                          const char *srchost, unsigned short srcport)
403 {
404   struct Curl_llist_element *e;
405   struct Curl_llist_element *n;
406   for(e = asi->list.head; e; e = n) {
407     struct altsvc *as = e->ptr;
408     n = e->next;
409     if((srcalpnid == as->src.alpnid) &&
410        (srcport == as->src.port) &&
411        strcasecompare(srchost, as->src.host)) {
412       Curl_llist_remove(&asi->list, e, NULL);
413       altsvc_free(as);
414     }
415   }
416 }
417 
418 #ifdef DEBUGBUILD
419 /* to play well with debug builds, we can *set* a fixed time this will
420    return */
debugtime(void * unused)421 static time_t debugtime(void *unused)
422 {
423   char *timestr = getenv("CURL_TIME");
424   (void)unused;
425   if(timestr) {
426     unsigned long val = strtol(timestr, NULL, 10);
427     return (time_t)val;
428   }
429   return time(NULL);
430 }
431 #define time(x) debugtime(x)
432 #endif
433 
434 #define ISNEWLINE(x) (((x) == '\n') || (x) == '\r')
435 
436 /*
437  * Curl_altsvc_parse() takes an incoming alt-svc response header and stores
438  * the data correctly in the cache.
439  *
440  * 'value' points to the header *value*. That's contents to the right of the
441  * header name.
442  *
443  * Currently this function rejects invalid data without returning an error.
444  * Invalid host name, port number will result in the specific alternative
445  * being rejected. Unknown protocols are skipped.
446  */
Curl_altsvc_parse(struct Curl_easy * data,struct altsvcinfo * asi,const char * value,enum alpnid srcalpnid,const char * srchost,unsigned short srcport)447 CURLcode Curl_altsvc_parse(struct Curl_easy *data,
448                            struct altsvcinfo *asi, const char *value,
449                            enum alpnid srcalpnid, const char *srchost,
450                            unsigned short srcport)
451 {
452   const char *p = value;
453   size_t len;
454   char namebuf[MAX_ALTSVC_HOSTLEN] = "";
455   char alpnbuf[MAX_ALTSVC_ALPNLEN] = "";
456   struct altsvc *as;
457   unsigned short dstport = srcport; /* the same by default */
458   CURLcode result = getalnum(&p, alpnbuf, sizeof(alpnbuf));
459 #ifdef CURL_DISABLE_VERBOSE_STRINGS
460   (void)data;
461 #endif
462   if(result) {
463     infof(data, "Excessive alt-svc header, ignoring.");
464     return CURLE_OK;
465   }
466 
467   DEBUGASSERT(asi);
468 
469   /* Flush all cached alternatives for this source origin, if any */
470   altsvc_flush(asi, srcalpnid, srchost, srcport);
471 
472   /* "clear" is a magic keyword */
473   if(strcasecompare(alpnbuf, "clear")) {
474     return CURLE_OK;
475   }
476 
477   do {
478     if(*p == '=') {
479       /* [protocol]="[host][:port]" */
480       enum alpnid dstalpnid = alpn2alpnid(alpnbuf); /* the same by default */
481       p++;
482       if(*p == '\"') {
483         const char *dsthost = "";
484         const char *value_ptr;
485         char option[32];
486         unsigned long num;
487         char *end_ptr;
488         bool quoted = FALSE;
489         time_t maxage = 24 * 3600; /* default is 24 hours */
490         bool persist = FALSE;
491         p++;
492         if(*p != ':') {
493           /* host name starts here */
494           const char *hostp = p;
495           while(*p && (ISALNUM(*p) || (*p == '.') || (*p == '-')))
496             p++;
497           len = p - hostp;
498           if(!len || (len >= MAX_ALTSVC_HOSTLEN)) {
499             infof(data, "Excessive alt-svc host name, ignoring.");
500             dstalpnid = ALPN_none;
501           }
502           else {
503             memcpy(namebuf, hostp, len);
504             namebuf[len] = 0;
505             dsthost = namebuf;
506           }
507         }
508         else {
509           /* no destination name, use source host */
510           dsthost = srchost;
511         }
512         if(*p == ':') {
513           /* a port number */
514           unsigned long port = strtoul(++p, &end_ptr, 10);
515           if(port > USHRT_MAX || end_ptr == p || *end_ptr != '\"') {
516             infof(data, "Unknown alt-svc port number, ignoring.");
517             dstalpnid = ALPN_none;
518           }
519           p = end_ptr;
520           dstport = curlx_ultous(port);
521         }
522         if(*p++ != '\"')
523           break;
524         /* Handle the optional 'ma' and 'persist' flags. Unknown flags
525            are skipped. */
526         for(;;) {
527           while(ISBLANK(*p))
528             p++;
529           if(*p != ';')
530             break;
531           p++; /* pass the semicolon */
532           if(!*p || ISNEWLINE(*p))
533             break;
534           result = getalnum(&p, option, sizeof(option));
535           if(result) {
536             /* skip option if name is too long */
537             option[0] = '\0';
538           }
539           while(*p && ISBLANK(*p))
540             p++;
541           if(*p != '=')
542             return CURLE_OK;
543           p++;
544           while(*p && ISBLANK(*p))
545             p++;
546           if(!*p)
547             return CURLE_OK;
548           if(*p == '\"') {
549             /* quoted value */
550             p++;
551             quoted = TRUE;
552           }
553           value_ptr = p;
554           if(quoted) {
555             while(*p && *p != '\"')
556               p++;
557             if(!*p++)
558               return CURLE_OK;
559           }
560           else {
561             while(*p && !ISBLANK(*p) && *p!= ';' && *p != ',')
562               p++;
563           }
564           num = strtoul(value_ptr, &end_ptr, 10);
565           if((end_ptr != value_ptr) && (num < ULONG_MAX)) {
566             if(strcasecompare("ma", option))
567               maxage = num;
568             else if(strcasecompare("persist", option) && (num == 1))
569               persist = TRUE;
570           }
571         }
572         if(dstalpnid) {
573           as = altsvc_createid(srchost, dsthost,
574                                srcalpnid, dstalpnid,
575                                srcport, dstport);
576           if(as) {
577             /* The expires time also needs to take the Age: value (if any) into
578                account. [See RFC 7838 section 3.1] */
579             as->expires = maxage + time(NULL);
580             as->persist = persist;
581             Curl_llist_insert_next(&asi->list, asi->list.tail, as, &as->node);
582             infof(data, "Added alt-svc: %s:%d over %s", dsthost, dstport,
583                   Curl_alpnid2str(dstalpnid));
584           }
585         }
586         else {
587           infof(data, "Unknown alt-svc protocol \"%s\", skipping.",
588                 alpnbuf);
589         }
590       }
591       else
592         break;
593       /* after the double quote there can be a comma if there's another
594          string or a semicolon if no more */
595       if(*p == ',') {
596         /* comma means another alternative is presented */
597         p++;
598         result = getalnum(&p, alpnbuf, sizeof(alpnbuf));
599         if(result)
600           break;
601       }
602     }
603     else
604       break;
605   } while(*p && (*p != ';') && (*p != '\n') && (*p != '\r'));
606 
607   return CURLE_OK;
608 }
609 
610 /*
611  * Return TRUE on a match
612  */
Curl_altsvc_lookup(struct altsvcinfo * asi,enum alpnid srcalpnid,const char * srchost,int srcport,struct altsvc ** dstentry,const int versions)613 bool Curl_altsvc_lookup(struct altsvcinfo *asi,
614                         enum alpnid srcalpnid, const char *srchost,
615                         int srcport,
616                         struct altsvc **dstentry,
617                         const int versions) /* one or more bits */
618 {
619   struct Curl_llist_element *e;
620   struct Curl_llist_element *n;
621   time_t now = time(NULL);
622   DEBUGASSERT(asi);
623   DEBUGASSERT(srchost);
624   DEBUGASSERT(dstentry);
625 
626   for(e = asi->list.head; e; e = n) {
627     struct altsvc *as = e->ptr;
628     n = e->next;
629     if(as->expires < now) {
630       /* an expired entry, remove */
631       Curl_llist_remove(&asi->list, e, NULL);
632       altsvc_free(as);
633       continue;
634     }
635     if((as->src.alpnid == srcalpnid) &&
636        strcasecompare(as->src.host, srchost) &&
637        (as->src.port == srcport) &&
638        (versions & as->dst.alpnid)) {
639       /* match */
640       *dstentry = as;
641       return TRUE;
642     }
643   }
644   return FALSE;
645 }
646 
647 #endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_ALTSVC */
648