VirtualBox

source: vbox/trunk/src/libs/curl-8.7.1/lib/hsts.c@ 106165

最後變更 在這個檔案從106165是 104083,由 vboxsync 提交於 8 月 前

curl-8.7.1: Applied and adjusted our curl changes to 8.4.0. bugref:10639

  • 屬性 svn:eol-style 設為 native
檔案大小: 14.6 KB
 
1/***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) Daniel Stenberg, <[email protected]>, 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 Strict-Transport-Security header is defined in RFC 6797:
26 * https://datatracker.ietf.org/doc/html/rfc6797
27 */
28#include "curl_setup.h"
29
30#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_HSTS)
31#include <curl/curl.h>
32#include "urldata.h"
33#include "llist.h"
34#include "hsts.h"
35#include "curl_get_line.h"
36#include "strcase.h"
37#include "sendf.h"
38#include "strtoofft.h"
39#include "parsedate.h"
40#include "fopen.h"
41#include "rename.h"
42#include "share.h"
43#include "strdup.h"
44
45/* The last 3 #include files should be in this order */
46#include "curl_printf.h"
47#include "curl_memory.h"
48#include "memdebug.h"
49
50#define MAX_HSTS_LINE 4095
51#define MAX_HSTS_HOSTLEN 256
52#define MAX_HSTS_HOSTLENSTR "256"
53#define MAX_HSTS_DATELEN 64
54#define MAX_HSTS_DATELENSTR "64"
55#define UNLIMITED "unlimited"
56
57#ifdef DEBUGBUILD
58/* to play well with debug builds, we can *set* a fixed time this will
59 return */
60time_t deltatime; /* allow for "adjustments" for unit test purposes */
61static time_t hsts_debugtime(void *unused)
62{
63 char *timestr = getenv("CURL_TIME");
64 (void)unused;
65 if(timestr) {
66 curl_off_t val;
67 (void)curlx_strtoofft(timestr, NULL, 10, &val);
68
69 val += (curl_off_t)deltatime;
70 return (time_t)val;
71 }
72 return time(NULL);
73}
74#undef time
75#define time(x) hsts_debugtime(x)
76#endif
77
78struct hsts *Curl_hsts_init(void)
79{
80 struct hsts *h = calloc(1, sizeof(struct hsts));
81 if(h) {
82 Curl_llist_init(&h->list, NULL);
83 }
84 return h;
85}
86
87static void hsts_free(struct stsentry *e)
88{
89 free((char *)e->host);
90 free(e);
91}
92
93void Curl_hsts_cleanup(struct hsts **hp)
94{
95 struct hsts *h = *hp;
96 if(h) {
97 struct Curl_llist_element *e;
98 struct Curl_llist_element *n;
99 for(e = h->list.head; e; e = n) {
100 struct stsentry *sts = e->ptr;
101 n = e->next;
102 hsts_free(sts);
103 }
104 free(h->filename);
105 free(h);
106 *hp = NULL;
107 }
108}
109
110static struct stsentry *hsts_entry(void)
111{
112 return calloc(1, sizeof(struct stsentry));
113}
114
115static CURLcode hsts_create(struct hsts *h,
116 const char *hostname,
117 bool subdomains,
118 curl_off_t expires)
119{
120 size_t hlen;
121 DEBUGASSERT(h);
122 DEBUGASSERT(hostname);
123
124 hlen = strlen(hostname);
125 if(hlen && (hostname[hlen - 1] == '.'))
126 /* strip off any trailing dot */
127 --hlen;
128 if(hlen) {
129 char *duphost;
130 struct stsentry *sts = hsts_entry();
131 if(!sts)
132 return CURLE_OUT_OF_MEMORY;
133
134 duphost = Curl_memdup0(hostname, hlen);
135 if(!duphost) {
136 free(sts);
137 return CURLE_OUT_OF_MEMORY;
138 }
139
140 sts->host = duphost;
141 sts->expires = expires;
142 sts->includeSubDomains = subdomains;
143 Curl_llist_insert_next(&h->list, h->list.tail, sts, &sts->node);
144 }
145 return CURLE_OK;
146}
147
148CURLcode Curl_hsts_parse(struct hsts *h, const char *hostname,
149 const char *header)
150{
151 const char *p = header;
152 curl_off_t expires = 0;
153 bool gotma = FALSE;
154 bool gotinc = FALSE;
155 bool subdomains = FALSE;
156 struct stsentry *sts;
157 time_t now = time(NULL);
158
159 if(Curl_host_is_ipnum(hostname))
160 /* "explicit IP address identification of all forms is excluded."
161 / RFC 6797 */
162 return CURLE_OK;
163
164 do {
165 while(*p && ISBLANK(*p))
166 p++;
167 if(strncasecompare("max-age=", p, 8)) {
168 bool quoted = FALSE;
169 CURLofft offt;
170 char *endp;
171
172 if(gotma)
173 return CURLE_BAD_FUNCTION_ARGUMENT;
174
175 p += 8;
176 while(*p && ISBLANK(*p))
177 p++;
178 if(*p == '\"') {
179 p++;
180 quoted = TRUE;
181 }
182 offt = curlx_strtoofft(p, &endp, 10, &expires);
183 if(offt == CURL_OFFT_FLOW)
184 expires = CURL_OFF_T_MAX;
185 else if(offt)
186 /* invalid max-age */
187 return CURLE_BAD_FUNCTION_ARGUMENT;
188 p = endp;
189 if(quoted) {
190 if(*p != '\"')
191 return CURLE_BAD_FUNCTION_ARGUMENT;
192 p++;
193 }
194 gotma = TRUE;
195 }
196 else if(strncasecompare("includesubdomains", p, 17)) {
197 if(gotinc)
198 return CURLE_BAD_FUNCTION_ARGUMENT;
199 subdomains = TRUE;
200 p += 17;
201 gotinc = TRUE;
202 }
203 else {
204 /* unknown directive, do a lame attempt to skip */
205 while(*p && (*p != ';'))
206 p++;
207 }
208
209 while(*p && ISBLANK(*p))
210 p++;
211 if(*p == ';')
212 p++;
213 } while(*p);
214
215 if(!gotma)
216 /* max-age is mandatory */
217 return CURLE_BAD_FUNCTION_ARGUMENT;
218
219 if(!expires) {
220 /* remove the entry if present verbatim (without subdomain match) */
221 sts = Curl_hsts(h, hostname, FALSE);
222 if(sts) {
223 Curl_llist_remove(&h->list, &sts->node, NULL);
224 hsts_free(sts);
225 }
226 return CURLE_OK;
227 }
228
229 if(CURL_OFF_T_MAX - now < expires)
230 /* would overflow, use maximum value */
231 expires = CURL_OFF_T_MAX;
232 else
233 expires += now;
234
235 /* check if it already exists */
236 sts = Curl_hsts(h, hostname, FALSE);
237 if(sts) {
238 /* just update these fields */
239 sts->expires = expires;
240 sts->includeSubDomains = subdomains;
241 }
242 else
243 return hsts_create(h, hostname, subdomains, expires);
244
245 return CURLE_OK;
246}
247
248/*
249 * Return TRUE if the given host name is currently an HSTS one.
250 *
251 * The 'subdomain' argument tells the function if subdomain matching should be
252 * attempted.
253 */
254struct stsentry *Curl_hsts(struct hsts *h, const char *hostname,
255 bool subdomain)
256{
257 if(h) {
258 char buffer[MAX_HSTS_HOSTLEN + 1];
259 time_t now = time(NULL);
260 size_t hlen = strlen(hostname);
261 struct Curl_llist_element *e;
262 struct Curl_llist_element *n;
263
264 if((hlen > MAX_HSTS_HOSTLEN) || !hlen)
265 return NULL;
266 memcpy(buffer, hostname, hlen);
267 if(hostname[hlen-1] == '.')
268 /* remove the trailing dot */
269 --hlen;
270 buffer[hlen] = 0;
271 hostname = buffer;
272
273 for(e = h->list.head; e; e = n) {
274 struct stsentry *sts = e->ptr;
275 n = e->next;
276 if(sts->expires <= now) {
277 /* remove expired entries */
278 Curl_llist_remove(&h->list, &sts->node, NULL);
279 hsts_free(sts);
280 continue;
281 }
282 if(subdomain && sts->includeSubDomains) {
283 size_t ntail = strlen(sts->host);
284 if(ntail < hlen) {
285 size_t offs = hlen - ntail;
286 if((hostname[offs-1] == '.') &&
287 strncasecompare(&hostname[offs], sts->host, ntail))
288 return sts;
289 }
290 }
291 if(strcasecompare(hostname, sts->host))
292 return sts;
293 }
294 }
295 return NULL; /* no match */
296}
297
298/*
299 * Send this HSTS entry to the write callback.
300 */
301static CURLcode hsts_push(struct Curl_easy *data,
302 struct curl_index *i,
303 struct stsentry *sts,
304 bool *stop)
305{
306 struct curl_hstsentry e;
307 CURLSTScode sc;
308 struct tm stamp;
309 CURLcode result;
310
311 e.name = (char *)sts->host;
312 e.namelen = strlen(sts->host);
313 e.includeSubDomains = sts->includeSubDomains;
314
315 if(sts->expires != TIME_T_MAX) {
316 result = Curl_gmtime((time_t)sts->expires, &stamp);
317 if(result)
318 return result;
319
320 msnprintf(e.expire, sizeof(e.expire), "%d%02d%02d %02d:%02d:%02d",
321 stamp.tm_year + 1900, stamp.tm_mon + 1, stamp.tm_mday,
322 stamp.tm_hour, stamp.tm_min, stamp.tm_sec);
323 }
324 else
325 strcpy(e.expire, UNLIMITED);
326
327 sc = data->set.hsts_write(data, &e, i,
328 data->set.hsts_write_userp);
329 *stop = (sc != CURLSTS_OK);
330 return sc == CURLSTS_FAIL ? CURLE_BAD_FUNCTION_ARGUMENT : CURLE_OK;
331}
332
333/*
334 * Write this single hsts entry to a single output line
335 */
336static CURLcode hsts_out(struct stsentry *sts, FILE *fp)
337{
338 struct tm stamp;
339 if(sts->expires != TIME_T_MAX) {
340 CURLcode result = Curl_gmtime((time_t)sts->expires, &stamp);
341 if(result)
342 return result;
343 fprintf(fp, "%s%s \"%d%02d%02d %02d:%02d:%02d\"\n",
344 sts->includeSubDomains ? ".": "", sts->host,
345 stamp.tm_year + 1900, stamp.tm_mon + 1, stamp.tm_mday,
346 stamp.tm_hour, stamp.tm_min, stamp.tm_sec);
347 }
348 else
349 fprintf(fp, "%s%s \"%s\"\n",
350 sts->includeSubDomains ? ".": "", sts->host, UNLIMITED);
351 return CURLE_OK;
352}
353
354
355/*
356 * Curl_https_save() writes the HSTS cache to file and callback.
357 */
358CURLcode Curl_hsts_save(struct Curl_easy *data, struct hsts *h,
359 const char *file)
360{
361 struct Curl_llist_element *e;
362 struct Curl_llist_element *n;
363 CURLcode result = CURLE_OK;
364 FILE *out;
365 char *tempstore = NULL;
366
367 if(!h)
368 /* no cache activated */
369 return CURLE_OK;
370
371 /* if no new name is given, use the one we stored from the load */
372 if(!file && h->filename)
373 file = h->filename;
374
375 if((h->flags & CURLHSTS_READONLYFILE) || !file || !file[0])
376 /* marked as read-only, no file or zero length file name */
377 goto skipsave;
378
379 result = Curl_fopen(data, file, &out, &tempstore);
380 if(!result) {
381 fputs("# Your HSTS cache. https://curl.se/docs/hsts.html\n"
382 "# This file was generated by libcurl! Edit at your own risk.\n",
383 out);
384 for(e = h->list.head; e; e = n) {
385 struct stsentry *sts = e->ptr;
386 n = e->next;
387 result = hsts_out(sts, out);
388 if(result)
389 break;
390 }
391 fclose(out);
392 if(!result && tempstore && Curl_rename(tempstore, file))
393 result = CURLE_WRITE_ERROR;
394
395 if(result && tempstore)
396 unlink(tempstore);
397 }
398 free(tempstore);
399skipsave:
400 if(data->set.hsts_write) {
401 /* if there's a write callback */
402 struct curl_index i; /* count */
403 i.total = h->list.size;
404 i.index = 0;
405 for(e = h->list.head; e; e = n) {
406 struct stsentry *sts = e->ptr;
407 bool stop;
408 n = e->next;
409 result = hsts_push(data, &i, sts, &stop);
410 if(result || stop)
411 break;
412 i.index++;
413 }
414 }
415 return result;
416}
417
418/* only returns SERIOUS errors */
419static CURLcode hsts_add(struct hsts *h, char *line)
420{
421 /* Example lines:
422 example.com "20191231 10:00:00"
423 .example.net "20191231 10:00:00"
424 */
425 char host[MAX_HSTS_HOSTLEN + 1];
426 char date[MAX_HSTS_DATELEN + 1];
427 int rc;
428
429 rc = sscanf(line,
430 "%" MAX_HSTS_HOSTLENSTR "s \"%" MAX_HSTS_DATELENSTR "[^\"]\"",
431 host, date);
432 if(2 == rc) {
433 time_t expires = strcmp(date, UNLIMITED) ? Curl_getdate_capped(date) :
434 TIME_T_MAX;
435 CURLcode result = CURLE_OK;
436 char *p = host;
437 bool subdomain = FALSE;
438 struct stsentry *e;
439 if(p[0] == '.') {
440 p++;
441 subdomain = TRUE;
442 }
443 /* only add it if not already present */
444 e = Curl_hsts(h, p, subdomain);
445 if(!e)
446 result = hsts_create(h, p, subdomain, expires);
447 else {
448 /* the same host name, use the largest expire time */
449 if(expires > e->expires)
450 e->expires = expires;
451 }
452 if(result)
453 return result;
454 }
455
456 return CURLE_OK;
457}
458
459/*
460 * Load HSTS data from callback.
461 *
462 */
463static CURLcode hsts_pull(struct Curl_easy *data, struct hsts *h)
464{
465 /* if the HSTS read callback is set, use it */
466 if(data->set.hsts_read) {
467 CURLSTScode sc;
468 DEBUGASSERT(h);
469 do {
470 char buffer[MAX_HSTS_HOSTLEN + 1];
471 struct curl_hstsentry e;
472 e.name = buffer;
473 e.namelen = sizeof(buffer)-1;
474 e.includeSubDomains = FALSE; /* default */
475 e.expire[0] = 0;
476 e.name[0] = 0; /* just to make it clean */
477 sc = data->set.hsts_read(data, &e, data->set.hsts_read_userp);
478 if(sc == CURLSTS_OK) {
479 time_t expires;
480 CURLcode result;
481 DEBUGASSERT(e.name[0]);
482 if(!e.name[0])
483 /* bail out if no name was stored */
484 return CURLE_BAD_FUNCTION_ARGUMENT;
485 if(e.expire[0])
486 expires = Curl_getdate_capped(e.expire);
487 else
488 expires = TIME_T_MAX; /* the end of time */
489 result = hsts_create(h, e.name,
490 /* bitfield to bool conversion: */
491 e.includeSubDomains ? TRUE : FALSE,
492 expires);
493 if(result)
494 return result;
495 }
496 else if(sc == CURLSTS_FAIL)
497 return CURLE_ABORTED_BY_CALLBACK;
498 } while(sc == CURLSTS_OK);
499 }
500 return CURLE_OK;
501}
502
503/*
504 * Load the HSTS cache from the given file. The text based line-oriented file
505 * format is documented here: https://curl.se/docs/hsts.html
506 *
507 * This function only returns error on major problems that prevent hsts
508 * handling to work completely. It will ignore individual syntactical errors
509 * etc.
510 */
511static CURLcode hsts_load(struct hsts *h, const char *file)
512{
513 CURLcode result = CURLE_OK;
514 FILE *fp;
515
516 /* we need a private copy of the file name so that the hsts cache file
517 name survives an easy handle reset */
518 free(h->filename);
519 h->filename = strdup(file);
520 if(!h->filename)
521 return CURLE_OUT_OF_MEMORY;
522
523 fp = fopen(file, FOPEN_READTEXT);
524 if(fp) {
525 struct dynbuf buf;
526 Curl_dyn_init(&buf, MAX_HSTS_LINE);
527 while(Curl_get_line(&buf, fp)) {
528 char *lineptr = Curl_dyn_ptr(&buf);
529 while(*lineptr && ISBLANK(*lineptr))
530 lineptr++;
531 if(*lineptr == '#')
532 /* skip commented lines */
533 continue;
534
535 hsts_add(h, lineptr);
536 }
537 Curl_dyn_free(&buf); /* free the line buffer */
538 fclose(fp);
539 }
540 return result;
541}
542
543/*
544 * Curl_hsts_loadfile() loads HSTS from file
545 */
546CURLcode Curl_hsts_loadfile(struct Curl_easy *data,
547 struct hsts *h, const char *file)
548{
549 DEBUGASSERT(h);
550 (void)data;
551 return hsts_load(h, file);
552}
553
554/*
555 * Curl_hsts_loadcb() loads HSTS from callback
556 */
557CURLcode Curl_hsts_loadcb(struct Curl_easy *data, struct hsts *h)
558{
559 if(h)
560 return hsts_pull(data, h);
561 return CURLE_OK;
562}
563
564void Curl_hsts_loadfiles(struct Curl_easy *data)
565{
566 struct curl_slist *l = data->state.hstslist;
567 if(l) {
568 Curl_share_lock(data, CURL_LOCK_DATA_HSTS, CURL_LOCK_ACCESS_SINGLE);
569
570 while(l) {
571 (void)Curl_hsts_loadfile(data, data->hsts, l->data);
572 l = l->next;
573 }
574 Curl_share_unlock(data, CURL_LOCK_DATA_HSTS);
575 }
576}
577
578#endif /* CURL_DISABLE_HTTP || CURL_DISABLE_HSTS */
注意: 瀏覽 TracBrowser 來幫助您使用儲存庫瀏覽器

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette