• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 2019 - 2020, 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.haxx.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(USE_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.haxx.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   enum alpnid dstalpnid = srcalpnid; /* the same by default */
455   char namebuf[MAX_ALTSVC_HOSTLEN] = "";
456   char alpnbuf[MAX_ALTSVC_ALPNLEN] = "";
457   struct altsvc *as;
458   unsigned short dstport = srcport; /* the same by default */
459   CURLcode result = getalnum(&p, alpnbuf, sizeof(alpnbuf));
460   if(result) {
461     infof(data, "Excessive alt-svc header, ignoring...\n");
462     return CURLE_OK;
463   }
464 
465   DEBUGASSERT(asi);
466 
467   /* Flush all cached alternatives for this source origin, if any */
468   altsvc_flush(asi, srcalpnid, srchost, srcport);
469 
470   /* "clear" is a magic keyword */
471   if(strcasecompare(alpnbuf, "clear")) {
472     return CURLE_OK;
473   }
474 
475   do {
476     if(*p == '=') {
477       /* [protocol]="[host][:port]" */
478       dstalpnid = alpn2alpnid(alpnbuf);
479       p++;
480       if(*p == '\"') {
481         const char *dsthost = "";
482         const char *value_ptr;
483         char option[32];
484         unsigned long num;
485         char *end_ptr;
486         bool quoted = FALSE;
487         time_t maxage = 24 * 3600; /* default is 24 hours */
488         bool persist = FALSE;
489         p++;
490         if(*p != ':') {
491           /* host name starts here */
492           const char *hostp = p;
493           while(*p && (ISALNUM(*p) || (*p == '.') || (*p == '-')))
494             p++;
495           len = p - hostp;
496           if(!len || (len >= MAX_ALTSVC_HOSTLEN)) {
497             infof(data, "Excessive alt-svc host name, ignoring...\n");
498             dstalpnid = ALPN_none;
499           }
500           else {
501             memcpy(namebuf, hostp, len);
502             namebuf[len] = 0;
503             dsthost = namebuf;
504           }
505         }
506         else {
507           /* no destination name, use source host */
508           dsthost = srchost;
509         }
510         if(*p == ':') {
511           /* a port number */
512           unsigned long port = strtoul(++p, &end_ptr, 10);
513           if(port > USHRT_MAX || end_ptr == p || *end_ptr != '\"') {
514             infof(data, "Unknown alt-svc port number, ignoring...\n");
515             dstalpnid = ALPN_none;
516           }
517           p = end_ptr;
518           dstport = curlx_ultous(port);
519         }
520         if(*p++ != '\"')
521           break;
522         /* Handle the optional 'ma' and 'persist' flags. Unknown flags
523            are skipped. */
524         for(;;) {
525           while(ISBLANK(*p))
526             p++;
527           if(*p != ';')
528             break;
529           p++; /* pass the semicolon */
530           if(!*p || ISNEWLINE(*p))
531             break;
532           result = getalnum(&p, option, sizeof(option));
533           if(result) {
534             /* skip option if name is too long */
535             option[0] = '\0';
536           }
537           while(*p && ISBLANK(*p))
538             p++;
539           if(*p != '=')
540             return CURLE_OK;
541           p++;
542           while(*p && ISBLANK(*p))
543             p++;
544           if(!*p)
545             return CURLE_OK;
546           if(*p == '\"') {
547             /* quoted value */
548             p++;
549             quoted = TRUE;
550           }
551           value_ptr = p;
552           if(quoted) {
553             while(*p && *p != '\"')
554               p++;
555             if(!*p++)
556               return CURLE_OK;
557           }
558           else {
559             while(*p && !ISBLANK(*p) && *p!= ';' && *p != ',')
560               p++;
561           }
562           num = strtoul(value_ptr, &end_ptr, 10);
563           if((end_ptr != value_ptr) && (num < ULONG_MAX)) {
564             if(strcasecompare("ma", option))
565               maxage = num;
566             else if(strcasecompare("persist", option) && (num == 1))
567               persist = TRUE;
568           }
569         }
570         if(dstalpnid) {
571           as = altsvc_createid(srchost, dsthost,
572                                srcalpnid, dstalpnid,
573                                srcport, dstport);
574           if(as) {
575             /* The expires time also needs to take the Age: value (if any) into
576                account. [See RFC 7838 section 3.1] */
577             as->expires = maxage + time(NULL);
578             as->persist = persist;
579             Curl_llist_insert_next(&asi->list, asi->list.tail, as, &as->node);
580             infof(data, "Added alt-svc: %s:%d over %s\n", dsthost, dstport,
581                   Curl_alpnid2str(dstalpnid));
582           }
583         }
584         else {
585           infof(data, "Unknown alt-svc protocol \"%s\", skipping...\n",
586                 alpnbuf);
587         }
588       }
589       else
590         break;
591       /* after the double quote there can be a comma if there's another
592          string or a semicolon if no more */
593       if(*p == ',') {
594         /* comma means another alternative is presented */
595         p++;
596         result = getalnum(&p, alpnbuf, sizeof(alpnbuf));
597         if(result)
598           break;
599       }
600     }
601     else
602       break;
603   } while(*p && (*p != ';') && (*p != '\n') && (*p != '\r'));
604 
605   return CURLE_OK;
606 }
607 
608 /*
609  * Return TRUE on a match
610  */
Curl_altsvc_lookup(struct altsvcinfo * asi,enum alpnid srcalpnid,const char * srchost,int srcport,struct altsvc ** dstentry,const int versions)611 bool Curl_altsvc_lookup(struct altsvcinfo *asi,
612                         enum alpnid srcalpnid, const char *srchost,
613                         int srcport,
614                         struct altsvc **dstentry,
615                         const int versions) /* one or more bits */
616 {
617   struct Curl_llist_element *e;
618   struct Curl_llist_element *n;
619   time_t now = time(NULL);
620   DEBUGASSERT(asi);
621   DEBUGASSERT(srchost);
622   DEBUGASSERT(dstentry);
623 
624   for(e = asi->list.head; e; e = n) {
625     struct altsvc *as = e->ptr;
626     n = e->next;
627     if(as->expires < now) {
628       /* an expired entry, remove */
629       Curl_llist_remove(&asi->list, e, NULL);
630       altsvc_free(as);
631       continue;
632     }
633     if((as->src.alpnid == srcalpnid) &&
634        strcasecompare(as->src.host, srchost) &&
635        (as->src.port == srcport) &&
636        (versions & as->dst.alpnid)) {
637       /* match */
638       *dstentry = as;
639       return TRUE;
640     }
641   }
642   return FALSE;
643 }
644 
645 #endif /* CURL_DISABLE_HTTP || USE_ALTSVC */
646