hsts.c 16 KB

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