• 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 "fopen.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 = 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 /* altsvc_flush() removes all alternatives for this source origin from the
390    list */
altsvc_flush(struct altsvcinfo * asi,enum alpnid srcalpnid,const char * srchost,unsigned short srcport)391 static void altsvc_flush(struct altsvcinfo *asi, enum alpnid srcalpnid,
392                          const char *srchost, unsigned short srcport)
393 {
394   struct Curl_llist_element *e;
395   struct Curl_llist_element *n;
396   for(e = asi->list.head; e; e = n) {
397     struct altsvc *as = e->ptr;
398     n = e->next;
399     if((srcalpnid == as->src.alpnid) &&
400        (srcport == as->src.port) &&
401        strcasecompare(srchost, as->src.host)) {
402       Curl_llist_remove(&asi->list, e, NULL);
403       altsvc_free(as);
404     }
405   }
406 }
407 
408 #ifdef DEBUGBUILD
409 /* to play well with debug builds, we can *set* a fixed time this will
410    return */
debugtime(void * unused)411 static time_t debugtime(void *unused)
412 {
413   char *timestr = getenv("CURL_TIME");
414   (void)unused;
415   if(timestr) {
416     unsigned long val = strtol(timestr, NULL, 10);
417     return (time_t)val;
418   }
419   return time(NULL);
420 }
421 #define time(x) debugtime(x)
422 #endif
423 
424 #define ISNEWLINE(x) (((x) == '\n') || (x) == '\r')
425 
426 /*
427  * Curl_altsvc_parse() takes an incoming alt-svc response header and stores
428  * the data correctly in the cache.
429  *
430  * 'value' points to the header *value*. That's contents to the right of the
431  * header name.
432  *
433  * Currently this function rejects invalid data without returning an error.
434  * Invalid host name, port number will result in the specific alternative
435  * being rejected. Unknown protocols are skipped.
436  */
Curl_altsvc_parse(struct Curl_easy * data,struct altsvcinfo * asi,const char * value,enum alpnid srcalpnid,const char * srchost,unsigned short srcport)437 CURLcode Curl_altsvc_parse(struct Curl_easy *data,
438                            struct altsvcinfo *asi, const char *value,
439                            enum alpnid srcalpnid, const char *srchost,
440                            unsigned short srcport)
441 {
442   const char *p = value;
443   size_t len;
444   char namebuf[MAX_ALTSVC_HOSTLEN] = "";
445   char alpnbuf[MAX_ALTSVC_ALPNLEN] = "";
446   struct altsvc *as;
447   unsigned short dstport = srcport; /* the same by default */
448   CURLcode result = getalnum(&p, alpnbuf, sizeof(alpnbuf));
449 #ifdef CURL_DISABLE_VERBOSE_STRINGS
450   (void)data;
451 #endif
452   if(result) {
453     infof(data, "Excessive alt-svc header, ignoring.");
454     return CURLE_OK;
455   }
456 
457   DEBUGASSERT(asi);
458 
459   /* Flush all cached alternatives for this source origin, if any */
460   altsvc_flush(asi, srcalpnid, srchost, srcport);
461 
462   /* "clear" is a magic keyword */
463   if(strcasecompare(alpnbuf, "clear")) {
464     return CURLE_OK;
465   }
466 
467   do {
468     if(*p == '=') {
469       /* [protocol]="[host][:port]" */
470       enum alpnid dstalpnid = alpn2alpnid(alpnbuf); /* the same by default */
471       p++;
472       if(*p == '\"') {
473         const char *dsthost = "";
474         const char *value_ptr;
475         char option[32];
476         unsigned long num;
477         char *end_ptr;
478         bool quoted = FALSE;
479         time_t maxage = 24 * 3600; /* default is 24 hours */
480         bool persist = FALSE;
481         p++;
482         if(*p != ':') {
483           /* host name starts here */
484           const char *hostp = p;
485           while(*p && (ISALNUM(*p) || (*p == '.') || (*p == '-')))
486             p++;
487           len = p - hostp;
488           if(!len || (len >= MAX_ALTSVC_HOSTLEN)) {
489             infof(data, "Excessive alt-svc host name, ignoring.");
490             dstalpnid = ALPN_none;
491           }
492           else {
493             memcpy(namebuf, hostp, len);
494             namebuf[len] = 0;
495             dsthost = namebuf;
496           }
497         }
498         else {
499           /* no destination name, use source host */
500           dsthost = srchost;
501         }
502         if(*p == ':') {
503           /* a port number */
504           unsigned long port = strtoul(++p, &end_ptr, 10);
505           if(port > USHRT_MAX || end_ptr == p || *end_ptr != '\"') {
506             infof(data, "Unknown alt-svc port number, ignoring.");
507             dstalpnid = ALPN_none;
508           }
509           p = end_ptr;
510           dstport = curlx_ultous(port);
511         }
512         if(*p++ != '\"')
513           break;
514         /* Handle the optional 'ma' and 'persist' flags. Unknown flags
515            are skipped. */
516         for(;;) {
517           while(ISBLANK(*p))
518             p++;
519           if(*p != ';')
520             break;
521           p++; /* pass the semicolon */
522           if(!*p || ISNEWLINE(*p))
523             break;
524           result = getalnum(&p, option, sizeof(option));
525           if(result) {
526             /* skip option if name is too long */
527             option[0] = '\0';
528           }
529           while(*p && ISBLANK(*p))
530             p++;
531           if(*p != '=')
532             return CURLE_OK;
533           p++;
534           while(*p && ISBLANK(*p))
535             p++;
536           if(!*p)
537             return CURLE_OK;
538           if(*p == '\"') {
539             /* quoted value */
540             p++;
541             quoted = TRUE;
542           }
543           value_ptr = p;
544           if(quoted) {
545             while(*p && *p != '\"')
546               p++;
547             if(!*p++)
548               return CURLE_OK;
549           }
550           else {
551             while(*p && !ISBLANK(*p) && *p!= ';' && *p != ',')
552               p++;
553           }
554           num = strtoul(value_ptr, &end_ptr, 10);
555           if((end_ptr != value_ptr) && (num < ULONG_MAX)) {
556             if(strcasecompare("ma", option))
557               maxage = num;
558             else if(strcasecompare("persist", option) && (num == 1))
559               persist = TRUE;
560           }
561         }
562         if(dstalpnid) {
563           as = altsvc_createid(srchost, dsthost,
564                                srcalpnid, dstalpnid,
565                                srcport, dstport);
566           if(as) {
567             /* The expires time also needs to take the Age: value (if any) into
568                account. [See RFC 7838 section 3.1] */
569             as->expires = maxage + time(NULL);
570             as->persist = persist;
571             Curl_llist_insert_next(&asi->list, asi->list.tail, as, &as->node);
572             infof(data, "Added alt-svc: %s:%d over %s", dsthost, dstport,
573                   Curl_alpnid2str(dstalpnid));
574           }
575         }
576         else {
577           infof(data, "Unknown alt-svc protocol \"%s\", skipping.",
578                 alpnbuf);
579         }
580       }
581       else
582         break;
583       /* after the double quote there can be a comma if there's another
584          string or a semicolon if no more */
585       if(*p == ',') {
586         /* comma means another alternative is presented */
587         p++;
588         result = getalnum(&p, alpnbuf, sizeof(alpnbuf));
589         if(result)
590           break;
591       }
592     }
593     else
594       break;
595   } while(*p && (*p != ';') && (*p != '\n') && (*p != '\r'));
596 
597   return CURLE_OK;
598 }
599 
600 /*
601  * Return TRUE on a match
602  */
Curl_altsvc_lookup(struct altsvcinfo * asi,enum alpnid srcalpnid,const char * srchost,int srcport,struct altsvc ** dstentry,const int versions)603 bool Curl_altsvc_lookup(struct altsvcinfo *asi,
604                         enum alpnid srcalpnid, const char *srchost,
605                         int srcport,
606                         struct altsvc **dstentry,
607                         const int versions) /* one or more bits */
608 {
609   struct Curl_llist_element *e;
610   struct Curl_llist_element *n;
611   time_t now = time(NULL);
612   DEBUGASSERT(asi);
613   DEBUGASSERT(srchost);
614   DEBUGASSERT(dstentry);
615 
616   for(e = asi->list.head; e; e = n) {
617     struct altsvc *as = e->ptr;
618     n = e->next;
619     if(as->expires < now) {
620       /* an expired entry, remove */
621       Curl_llist_remove(&asi->list, e, NULL);
622       altsvc_free(as);
623       continue;
624     }
625     if((as->src.alpnid == srcalpnid) &&
626        strcasecompare(as->src.host, srchost) &&
627        (as->src.port == srcport) &&
628        (versions & as->dst.alpnid)) {
629       /* match */
630       *dstentry = as;
631       return TRUE;
632     }
633   }
634   return FALSE;
635 }
636 
637 #endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_ALTSVC */
638