• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 2019, 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 
38 /* The last 3 #include files should be in this order */
39 #include "curl_printf.h"
40 #include "curl_memory.h"
41 #include "memdebug.h"
42 
43 #define MAX_ALTSVC_LINE 4095
44 #define MAX_ALTSVC_DATELENSTR "64"
45 #define MAX_ALTSVC_DATELEN 64
46 #define MAX_ALTSVC_HOSTLENSTR "512"
47 #define MAX_ALTSVC_HOSTLEN 512
48 #define MAX_ALTSVC_ALPNLENSTR "10"
49 #define MAX_ALTSVC_ALPNLEN 10
50 
alpn2alpnid(char * name)51 static enum alpnid alpn2alpnid(char *name)
52 {
53   if(strcasecompare(name, "h1"))
54     return ALPN_h1;
55   if(strcasecompare(name, "h2"))
56     return ALPN_h2;
57 #if (defined(USE_QUICHE) || defined(USE_NGTCP2)) && !defined(UNITTESTS)
58   if(strcasecompare(name, "h3-23"))
59     return ALPN_h3;
60 #else
61   if(strcasecompare(name, "h3"))
62     return ALPN_h3;
63 #endif
64   return ALPN_none; /* unknown, probably rubbish input */
65 }
66 
67 /* Given the ALPN ID, return the name */
Curl_alpnid2str(enum alpnid id)68 const char *Curl_alpnid2str(enum alpnid id)
69 {
70   switch(id) {
71   case ALPN_h1:
72     return "h1";
73   case ALPN_h2:
74     return "h2";
75   case ALPN_h3:
76 #if (defined(USE_QUICHE) || defined(USE_NGTCP2)) && !defined(UNITTESTS)
77     return "h3-23";
78 #else
79     return "h3";
80 #endif
81   default:
82     return ""; /* bad */
83   }
84 }
85 
86 
altsvc_free(struct altsvc * as)87 static void altsvc_free(struct altsvc *as)
88 {
89   free(as->src.host);
90   free(as->dst.host);
91   free(as);
92 }
93 
altsvc_createid(const char * srchost,const char * dsthost,enum alpnid srcalpnid,enum alpnid dstalpnid,unsigned int srcport,unsigned int dstport)94 static struct altsvc *altsvc_createid(const char *srchost,
95                                       const char *dsthost,
96                                       enum alpnid srcalpnid,
97                                       enum alpnid dstalpnid,
98                                       unsigned int srcport,
99                                       unsigned int dstport)
100 {
101   struct altsvc *as = calloc(sizeof(struct altsvc), 1);
102   if(!as)
103     return NULL;
104 
105   as->src.host = strdup(srchost);
106   if(!as->src.host)
107     goto error;
108   as->dst.host = strdup(dsthost);
109   if(!as->dst.host)
110     goto error;
111 
112   as->src.alpnid = srcalpnid;
113   as->dst.alpnid = dstalpnid;
114   as->src.port = curlx_ultous(srcport);
115   as->dst.port = curlx_ultous(dstport);
116 
117   return as;
118   error:
119   altsvc_free(as);
120   return NULL;
121 }
122 
altsvc_create(char * srchost,char * dsthost,char * srcalpn,char * dstalpn,unsigned int srcport,unsigned int dstport)123 static struct altsvc *altsvc_create(char *srchost,
124                                     char *dsthost,
125                                     char *srcalpn,
126                                     char *dstalpn,
127                                     unsigned int srcport,
128                                     unsigned int dstport)
129 {
130   enum alpnid dstalpnid = alpn2alpnid(dstalpn);
131   enum alpnid srcalpnid = alpn2alpnid(srcalpn);
132   if(!srcalpnid || !dstalpnid)
133     return NULL;
134   return altsvc_createid(srchost, dsthost, srcalpnid, dstalpnid,
135                          srcport, dstport);
136 }
137 
138 /* only returns SERIOUS errors */
altsvc_add(struct altsvcinfo * asi,char * line)139 static CURLcode altsvc_add(struct altsvcinfo *asi, char *line)
140 {
141   /* Example line:
142      h2 example.com 443 h3 shiny.example.com 8443 "20191231 10:00:00" 1
143    */
144   char srchost[MAX_ALTSVC_HOSTLEN + 1];
145   char dsthost[MAX_ALTSVC_HOSTLEN + 1];
146   char srcalpn[MAX_ALTSVC_ALPNLEN + 1];
147   char dstalpn[MAX_ALTSVC_ALPNLEN + 1];
148   char date[MAX_ALTSVC_DATELEN + 1];
149   unsigned int srcport;
150   unsigned int dstport;
151   unsigned int prio;
152   unsigned int persist;
153   int rc;
154 
155   rc = sscanf(line,
156               "%" MAX_ALTSVC_ALPNLENSTR "s %" MAX_ALTSVC_HOSTLENSTR "s %u "
157               "%" MAX_ALTSVC_ALPNLENSTR "s %" MAX_ALTSVC_HOSTLENSTR "s %u "
158               "\"%" MAX_ALTSVC_DATELENSTR "[^\"]\" %u %u",
159               srcalpn, srchost, &srcport,
160               dstalpn, dsthost, &dstport,
161               date, &persist, &prio);
162   if(9 == rc) {
163     struct altsvc *as;
164     time_t expires = curl_getdate(date, NULL);
165     as = altsvc_create(srchost, dsthost, srcalpn, dstalpn, srcport, dstport);
166     if(as) {
167       as->expires = expires;
168       as->prio = prio;
169       as->persist = persist ? 1 : 0;
170       Curl_llist_insert_next(&asi->list, asi->list.tail, as, &as->node);
171       asi->num++; /* one more entry */
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 = fopen(file, FOPEN_READTEXT);
192   if(fp) {
193     line = malloc(MAX_ALTSVC_LINE);
194     if(!line)
195       goto fail;
196     while(Curl_get_line(line, MAX_ALTSVC_LINE, fp)) {
197       char *lineptr = line;
198       while(*lineptr && ISBLANK(*lineptr))
199         lineptr++;
200       if(*lineptr == '#')
201         /* skip commented lines */
202         continue;
203 
204       altsvc_add(asi, lineptr);
205     }
206     free(line); /* free the line buffer */
207     fclose(fp);
208   }
209   return result;
210 
211   fail:
212   free(line);
213   fclose(fp);
214   return CURLE_OUT_OF_MEMORY;
215 }
216 
217 /*
218  * Write this single altsvc entry to a single output line
219  */
220 
altsvc_out(struct altsvc * as,FILE * fp)221 static CURLcode altsvc_out(struct altsvc *as, FILE *fp)
222 {
223   struct tm stamp;
224   CURLcode result = Curl_gmtime(as->expires, &stamp);
225   if(result)
226     return result;
227 
228   fprintf(fp,
229           "%s %s %u "
230           "%s %s %u "
231           "\"%d%02d%02d "
232           "%02d:%02d:%02d\" "
233           "%u %d\n",
234           Curl_alpnid2str(as->src.alpnid), as->src.host, as->src.port,
235           Curl_alpnid2str(as->dst.alpnid), as->dst.host, as->dst.port,
236           stamp.tm_year + 1900, stamp.tm_mon + 1, stamp.tm_mday,
237           stamp.tm_hour, stamp.tm_min, stamp.tm_sec,
238           as->persist, as->prio);
239   return CURLE_OK;
240 }
241 
242 /* ---- library-wide functions below ---- */
243 
244 /*
245  * Curl_altsvc_init() creates a new altsvc cache.
246  * It returns the new instance or NULL if something goes wrong.
247  */
Curl_altsvc_init(void)248 struct altsvcinfo *Curl_altsvc_init(void)
249 {
250   struct altsvcinfo *asi = calloc(sizeof(struct altsvcinfo), 1);
251   if(!asi)
252     return NULL;
253   Curl_llist_init(&asi->list, NULL);
254 
255   /* set default behavior */
256   asi->flags = CURLALTSVC_H1
257 #ifdef USE_NGHTTP2
258     | CURLALTSVC_H2
259 #endif
260 #ifdef ENABLE_QUIC
261     | CURLALTSVC_H3
262 #endif
263     ;
264   return asi;
265 }
266 
267 /*
268  * Curl_altsvc_load() loads alt-svc from file.
269  */
Curl_altsvc_load(struct altsvcinfo * asi,const char * file)270 CURLcode Curl_altsvc_load(struct altsvcinfo *asi, const char *file)
271 {
272   CURLcode result;
273   DEBUGASSERT(asi);
274   result = altsvc_load(asi, file);
275   return result;
276 }
277 
278 /*
279  * Curl_altsvc_ctrl() passes on the external bitmask.
280  */
Curl_altsvc_ctrl(struct altsvcinfo * asi,const long ctrl)281 CURLcode Curl_altsvc_ctrl(struct altsvcinfo *asi, const long ctrl)
282 {
283   DEBUGASSERT(asi);
284   if(!ctrl)
285     /* unexpected */
286     return CURLE_BAD_FUNCTION_ARGUMENT;
287   asi->flags = ctrl;
288   return CURLE_OK;
289 }
290 
291 /*
292  * Curl_altsvc_cleanup() frees an altsvc cache instance and all associated
293  * resources.
294  */
Curl_altsvc_cleanup(struct altsvcinfo * altsvc)295 void Curl_altsvc_cleanup(struct altsvcinfo *altsvc)
296 {
297   struct curl_llist_element *e;
298   struct curl_llist_element *n;
299   if(altsvc) {
300     for(e = altsvc->list.head; e; e = n) {
301       struct altsvc *as = e->ptr;
302       n = e->next;
303       altsvc_free(as);
304     }
305     free(altsvc);
306   }
307 }
308 
309 /*
310  * Curl_altsvc_save() writes the altsvc cache to a file.
311  */
Curl_altsvc_save(struct altsvcinfo * altsvc,const char * file)312 CURLcode Curl_altsvc_save(struct altsvcinfo *altsvc, const char *file)
313 {
314   struct curl_llist_element *e;
315   struct curl_llist_element *n;
316   CURLcode result = CURLE_OK;
317   FILE *out;
318 
319   if(!altsvc)
320     /* no cache activated */
321     return CURLE_OK;
322 
323   if((altsvc->flags & CURLALTSVC_READONLYFILE) || !file[0])
324     /* marked as read-only or zero length file name */
325     return CURLE_OK;
326   out = fopen(file, FOPEN_WRITETEXT);
327   if(!out)
328     return CURLE_WRITE_ERROR;
329   fputs("# Your alt-svc cache. https://curl.haxx.se/docs/alt-svc.html\n"
330         "# This file was generated by libcurl! Edit at your own risk.\n",
331         out);
332   for(e = altsvc->list.head; e; e = n) {
333     struct altsvc *as = e->ptr;
334     n = e->next;
335     result = altsvc_out(as, out);
336     if(result)
337       break;
338   }
339   fclose(out);
340   return result;
341 }
342 
getalnum(const char ** ptr,char * alpnbuf,size_t buflen)343 static CURLcode getalnum(const char **ptr, char *alpnbuf, size_t buflen)
344 {
345   size_t len;
346   const char *protop;
347   const char *p = *ptr;
348   while(*p && ISBLANK(*p))
349     p++;
350   protop = p;
351   while(*p && !ISBLANK(*p) && (*p != ';') && (*p != '='))
352     p++;
353   len = p - protop;
354 
355   if(!len || (len >= buflen))
356     return CURLE_BAD_FUNCTION_ARGUMENT;
357   memcpy(alpnbuf, protop, len);
358   alpnbuf[len] = 0;
359   *ptr = p;
360   return CURLE_OK;
361 }
362 
363 /* altsvc_flush() removes all alternatives for this source origin from the
364    list */
altsvc_flush(struct altsvcinfo * asi,enum alpnid srcalpnid,const char * srchost,unsigned short srcport)365 static void altsvc_flush(struct altsvcinfo *asi, enum alpnid srcalpnid,
366                          const char *srchost, unsigned short srcport)
367 {
368   struct curl_llist_element *e;
369   struct curl_llist_element *n;
370   for(e = asi->list.head; e; e = n) {
371     struct altsvc *as = e->ptr;
372     n = e->next;
373     if((srcalpnid == as->src.alpnid) &&
374        (srcport == as->src.port) &&
375        strcasecompare(srchost, as->src.host)) {
376       Curl_llist_remove(&asi->list, e, NULL);
377       altsvc_free(as);
378       asi->num--;
379     }
380   }
381 }
382 
383 #ifdef DEBUGBUILD
384 /* to play well with debug builds, we can *set* a fixed time this will
385    return */
debugtime(void * unused)386 static time_t debugtime(void *unused)
387 {
388   char *timestr = getenv("CURL_TIME");
389   (void)unused;
390   if(timestr) {
391     unsigned long val = strtol(timestr, NULL, 10);
392     return (time_t)val;
393   }
394   return time(NULL);
395 }
396 #define time(x) debugtime(x)
397 #endif
398 
399 /*
400  * Curl_altsvc_parse() takes an incoming alt-svc response header and stores
401  * the data correctly in the cache.
402  *
403  * 'value' points to the header *value*. That's contents to the right of the
404  * header name.
405  */
Curl_altsvc_parse(struct Curl_easy * data,struct altsvcinfo * asi,const char * value,enum alpnid srcalpnid,const char * srchost,unsigned short srcport)406 CURLcode Curl_altsvc_parse(struct Curl_easy *data,
407                            struct altsvcinfo *asi, const char *value,
408                            enum alpnid srcalpnid, const char *srchost,
409                            unsigned short srcport)
410 {
411   const char *p = value;
412   size_t len;
413   enum alpnid dstalpnid = srcalpnid; /* the same by default */
414   char namebuf[MAX_ALTSVC_HOSTLEN] = "";
415   char alpnbuf[MAX_ALTSVC_ALPNLEN] = "";
416   struct altsvc *as;
417   unsigned short dstport = srcport; /* the same by default */
418   const char *semip;
419   time_t maxage = 24 * 3600; /* default is 24 hours */
420   bool persist = FALSE;
421   CURLcode result = getalnum(&p, alpnbuf, sizeof(alpnbuf));
422   if(result)
423     return result;
424 
425   DEBUGASSERT(asi);
426 
427   /* Flush all cached alternatives for this source origin, if any */
428   altsvc_flush(asi, srcalpnid, srchost, srcport);
429 
430   /* "clear" is a magic keyword */
431   if(strcasecompare(alpnbuf, "clear")) {
432     return CURLE_OK;
433   }
434 
435   /* The 'ma' and 'persist' flags are annoyingly meant for all alternatives
436      but are set after the list on the line. Scan for the semicolons and get
437      those fields first! */
438   semip = p;
439   do {
440     semip = strchr(semip, ';');
441     if(semip) {
442       char option[32];
443       unsigned long num;
444       char *end_ptr;
445       bool quoted = FALSE;
446       semip++; /* pass the semicolon */
447       result = getalnum(&semip, option, sizeof(option));
448       if(result)
449         break;
450       while(*semip && ISBLANK(*semip))
451         semip++;
452       if(*semip != '=')
453         continue;
454       semip++;
455       while(*semip && ISBLANK(*semip))
456         semip++;
457       if(*semip == '\"') {
458         /* quoted value */
459         semip++;
460         quoted = TRUE;
461       }
462       num = strtoul(semip, &end_ptr, 10);
463       if((end_ptr != semip) && num && (num < ULONG_MAX)) {
464         if(strcasecompare("ma", option))
465           maxage = num;
466         else if(strcasecompare("persist", option) && (num == 1))
467           persist = TRUE;
468         if(quoted && (*end_ptr == '\"'))
469           end_ptr++;
470       }
471       semip = end_ptr;
472     }
473   } while(semip);
474 
475   do {
476     if(*p == '=') {
477       /* [protocol]="[host][:port]" */
478       dstalpnid = alpn2alpnid(alpnbuf);
479       if(!dstalpnid) {
480         infof(data, "Unknown alt-svc protocol \"%s\", ignoring...\n", alpnbuf);
481         return CURLE_OK;
482       }
483       p++;
484       if(*p == '\"') {
485         const char *dsthost;
486         p++;
487         if(*p != ':') {
488           /* host name starts here */
489           const char *hostp = p;
490           while(*p && (ISALNUM(*p) || (*p == '.') || (*p == '-')))
491             p++;
492           len = p - hostp;
493           if(!len || (len >= MAX_ALTSVC_HOSTLEN))
494             return CURLE_BAD_FUNCTION_ARGUMENT;
495           memcpy(namebuf, hostp, len);
496           namebuf[len] = 0;
497           dsthost = namebuf;
498         }
499         else {
500           /* no destination name, use source host */
501           dsthost = srchost;
502         }
503         if(*p == ':') {
504           /* a port number */
505           char *end_ptr;
506           unsigned long port = strtoul(++p, &end_ptr, 10);
507           if(port > USHRT_MAX || end_ptr == p || *end_ptr != '\"') {
508             infof(data, "Unknown alt-svc port number, ignoring...\n");
509             return CURLE_OK;
510           }
511           p = end_ptr;
512           dstport = curlx_ultous(port);
513         }
514         if(*p++ != '\"')
515           return CURLE_BAD_FUNCTION_ARGUMENT;
516         as = altsvc_createid(srchost, dsthost,
517                              srcalpnid, dstalpnid,
518                              srcport, dstport);
519         if(as) {
520           /* The expires time also needs to take the Age: value (if any) into
521              account. [See RFC 7838 section 3.1] */
522           as->expires = maxage + time(NULL);
523           as->persist = persist;
524           Curl_llist_insert_next(&asi->list, asi->list.tail, as, &as->node);
525           asi->num++; /* one more entry */
526           infof(data, "Added alt-svc: %s:%d over %s\n", dsthost, dstport,
527                 Curl_alpnid2str(dstalpnid));
528         }
529       }
530       /* after the double quote there can be a comma if there's another
531          string or a semicolon if no more */
532       if(*p == ',') {
533         /* comma means another alternative is presented */
534         p++;
535         result = getalnum(&p, alpnbuf, sizeof(alpnbuf));
536         if(result)
537           /* failed to parse, but since we already did at least one host we
538              return OK */
539           return CURLE_OK;
540       }
541     }
542   } while(*p && (*p != ';') && (*p != '\n') && (*p != '\r'));
543 
544   return CURLE_OK;
545 }
546 
547 /*
548  * Return TRUE on a match
549  */
Curl_altsvc_lookup(struct altsvcinfo * asi,enum alpnid srcalpnid,const char * srchost,int srcport,struct altsvc ** dstentry,const int versions)550 bool Curl_altsvc_lookup(struct altsvcinfo *asi,
551                         enum alpnid srcalpnid, const char *srchost,
552                         int srcport,
553                         struct altsvc **dstentry,
554                         const int versions) /* one or more bits */
555 {
556   struct curl_llist_element *e;
557   struct curl_llist_element *n;
558   time_t now = time(NULL);
559   DEBUGASSERT(asi);
560   DEBUGASSERT(srchost);
561   DEBUGASSERT(dstentry);
562 
563   for(e = asi->list.head; e; e = n) {
564     struct altsvc *as = e->ptr;
565     n = e->next;
566     if(as->expires < now) {
567       /* an expired entry, remove */
568       Curl_llist_remove(&asi->list, e, NULL);
569       altsvc_free(as);
570       continue;
571     }
572     if((as->src.alpnid == srcalpnid) &&
573        strcasecompare(as->src.host, srchost) &&
574        (as->src.port == srcport) &&
575        (versions & as->dst.alpnid)) {
576       /* match */
577       *dstentry = as;
578       return TRUE;
579     }
580   }
581   return FALSE;
582 }
583 
584 #endif /* CURL_DISABLE_HTTP || USE_ALTSVC */
585