/src/wget2/libwget/hpkp_db.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2015-2026 Free Software Foundation, Inc. |
3 | | * |
4 | | * This file is part of libwget. |
5 | | * |
6 | | * Libwget is free software: you can redistribute it and/or modify |
7 | | * it under the terms of the GNU Lesser General Public License as published by |
8 | | * the Free Software Foundation, either version 3 of the License, or |
9 | | * (at your option) any later version. |
10 | | * |
11 | | * Libwget is distributed in the hope that it will be useful, |
12 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | | * GNU Lesser General Public License for more details. |
15 | | * |
16 | | * You should have received a copy of the GNU Lesser General Public License |
17 | | * along with libwget. If not, see <https://www.gnu.org/licenses/>. |
18 | | * |
19 | | * |
20 | | * HTTP Public Key Pinning database |
21 | | */ |
22 | | |
23 | | #include <config.h> |
24 | | |
25 | | #include <wget.h> |
26 | | #include <string.h> |
27 | | #include <stddef.h> |
28 | | #include <ctype.h> |
29 | | #include <sys/stat.h> |
30 | | #include <limits.h> |
31 | | #include <inttypes.h> |
32 | | #include "private.h" |
33 | | #include "hpkp.h" |
34 | | |
35 | | /** |
36 | | * \ingroup libwget-hpkp |
37 | | * |
38 | | * HTTP Public Key Pinning (RFC 7469) database implementation |
39 | | * |
40 | | * @{ |
41 | | */ |
42 | | |
43 | | struct wget_hpkp_db_st { |
44 | | char * |
45 | | fname; |
46 | | wget_hashmap * |
47 | | entries; |
48 | | wget_thread_mutex |
49 | | mutex; |
50 | | int64_t |
51 | | load_time; |
52 | | }; |
53 | | |
54 | | /// Pointer to the function table |
55 | | static const wget_hpkp_db_vtable |
56 | | *plugin_vtable; |
57 | | |
58 | | void wget_hpkp_set_plugin(const wget_hpkp_db_vtable *vtable) |
59 | 0 | { |
60 | 0 | plugin_vtable = vtable; |
61 | 0 | } |
62 | | |
63 | | #ifdef __clang__ |
64 | | __attribute__((no_sanitize("integer"))) |
65 | | #endif |
66 | | WGET_GCC_PURE |
67 | | static unsigned int hash_hpkp(const wget_hpkp *hpkp) |
68 | 6.89k | { |
69 | 6.89k | unsigned int hash = 0; |
70 | 6.89k | const unsigned char *p; |
71 | | |
72 | 28.1k | for (p = (unsigned char *)hpkp->host; *p; p++) |
73 | 21.2k | hash = hash * 101 + *p; // possible integer overflow, suppression above |
74 | | |
75 | 6.89k | return hash; |
76 | 6.89k | } |
77 | | |
78 | | WGET_GCC_NONNULL_ALL WGET_GCC_PURE |
79 | | static int compare_hpkp(const wget_hpkp *h1, const wget_hpkp *h2) |
80 | 1.31k | { |
81 | 1.31k | return strcmp(h1->host, h2->host); |
82 | 1.31k | } |
83 | | |
84 | | /** |
85 | | * \param[in] hpkp_db Pointer to the pointer of an HPKP database, provided by wget_hpkp_db_init() |
86 | | * |
87 | | * Frees all resources allocated for the HPKP database, except for the structure. |
88 | | * |
89 | | * Works only for databases created by wget_hpkp_db_init(). |
90 | | * The parameter \p hpkp_db can then be passed to \ref wget_hpkp_db_init "wget_hpkp_db_init()". |
91 | | * |
92 | | * If \p hpkp_db is NULL then this function does nothing. |
93 | | */ |
94 | | void wget_hpkp_db_deinit(wget_hpkp_db *hpkp_db) |
95 | 1.72k | { |
96 | 1.72k | if (plugin_vtable) { |
97 | 0 | plugin_vtable->deinit(hpkp_db); |
98 | 0 | return; |
99 | 0 | } |
100 | | |
101 | 1.72k | if (hpkp_db) { |
102 | 1.72k | xfree(hpkp_db->fname); |
103 | 1.72k | wget_thread_mutex_lock(hpkp_db->mutex); |
104 | 1.72k | wget_hashmap_free(&hpkp_db->entries); |
105 | 1.72k | wget_thread_mutex_unlock(hpkp_db->mutex); |
106 | | |
107 | 1.72k | wget_thread_mutex_destroy(&hpkp_db->mutex); |
108 | 1.72k | } |
109 | 1.72k | } |
110 | | |
111 | | /** |
112 | | * \param[in] hpkp_db Pointer to the pointer of an HPKP database |
113 | | * |
114 | | * Closes and frees the HPKP database. A double pointer is required because this function will |
115 | | * set the handle (pointer) to the HPKP database to NULL to prevent potential use-after-free conditions. |
116 | | * |
117 | | * Newly added entries will be lost unless committed to persistent storage using wget_hsts_db_save(). |
118 | | * |
119 | | * If \p hpkp_db or the pointer it points to is NULL then this function does nothing. |
120 | | */ |
121 | | void wget_hpkp_db_free(wget_hpkp_db **hpkp_db) |
122 | 10.9k | { |
123 | 10.9k | if (plugin_vtable) { |
124 | 0 | plugin_vtable->free(hpkp_db); |
125 | 0 | return; |
126 | 0 | } |
127 | | |
128 | 10.9k | if (hpkp_db && *hpkp_db) { |
129 | 1.72k | wget_hpkp_db_deinit(*hpkp_db); |
130 | 1.72k | xfree(*hpkp_db); |
131 | 1.72k | } |
132 | 10.9k | } |
133 | | |
134 | | /** |
135 | | * \param[in] hpkp_db An HPKP database |
136 | | * \param[in] host The hostname in question. |
137 | | * \param[in] pubkey The public key in DER format |
138 | | * \param[in] pubkeysize Size of `pubkey` |
139 | | * \return 1 if both host and public key was found in the database, |
140 | | * -2 if host was found and public key was not found, |
141 | | * 0 if host was not found, |
142 | | * -1 for any other error condition. |
143 | | * |
144 | | * Checks the validity of the given hostname and public key combination. |
145 | | * |
146 | | * This function is thread-safe and can be called from multiple threads concurrently. |
147 | | * Any implementation for this function must be thread-safe as well. |
148 | | */ |
149 | | int wget_hpkp_db_check_pubkey(wget_hpkp_db *hpkp_db, const char *host, const void *pubkey, size_t pubkeysize) |
150 | 1.72k | { |
151 | 1.72k | if (plugin_vtable) |
152 | 0 | return plugin_vtable->check_pubkey(hpkp_db, host, pubkey, pubkeysize); |
153 | | |
154 | 1.72k | wget_hpkp *hpkp = NULL; |
155 | 1.72k | int subdomain = 0; |
156 | 1.72k | char digest[32]; |
157 | 1.72k | size_t digestlen = wget_hash_get_len(WGET_DIGTYPE_SHA256); |
158 | | |
159 | 1.72k | if (digestlen > sizeof(digest)) { |
160 | 0 | error_printf(_("%s: Unexpected hash len %zu > %zu\n"), __func__, digestlen, sizeof(digest)); |
161 | 0 | return -1; |
162 | 0 | } |
163 | | |
164 | 5.15k | for (const char *domain = host; *domain && !hpkp; domain = strchrnul(domain, '.')) { |
165 | 5.14k | while (*domain == '.') |
166 | 1.71k | domain++; |
167 | | |
168 | 3.43k | wget_hpkp key = { .host = domain }; |
169 | | |
170 | 3.43k | if (!wget_hashmap_get(hpkp_db->entries, &key, &hpkp)) |
171 | 3.05k | subdomain = 1; |
172 | 3.43k | } |
173 | | |
174 | 1.72k | if (!hpkp) |
175 | 1.34k | return 0; // OK, host is not in database |
176 | | |
177 | 376 | if (subdomain && !hpkp->include_subdomains) |
178 | 32 | return 0; // OK, found a matching super domain which isn't responsible for <host> |
179 | | |
180 | 344 | if (wget_hash_fast(WGET_DIGTYPE_SHA256, pubkey, pubkeysize, digest)) |
181 | 0 | return -1; |
182 | | |
183 | 344 | wget_hpkp_pin pinkey = { .pin = digest, .pinsize = digestlen, .hash_type = "sha256" }; |
184 | | |
185 | 344 | if (wget_vector_find(hpkp->pins, &pinkey) != -1) |
186 | 0 | return 1; // OK, pinned pubkey found |
187 | | |
188 | 344 | return -2; |
189 | 344 | } |
190 | | |
191 | | /* We 'consume' _hpkp and thus set *_hpkp to NULL, so that the calling function |
192 | | * can't access it any more */ |
193 | | /** |
194 | | * \param[in] hpkp_db An HPKP database |
195 | | * \param[in] hpkp pointer to HPKP database entry (will be set to NULL) |
196 | | * |
197 | | * Adds an entry to given HPKP database. The entry will replace any entry with same `host` (see wget_hpkp_set_host()). |
198 | | * If `maxage` property of `hpkp` is zero, any existing entry with same `host` property will be removed. |
199 | | * |
200 | | * The database takes the ownership of the HPKP entry and the calling function must not access the entry afterwards. |
201 | | * |
202 | | * This function is thread-safe and can be called from multiple threads concurrently. |
203 | | * Any implementation for this function must be thread-safe as well. |
204 | | */ |
205 | | void wget_hpkp_db_add(wget_hpkp_db *hpkp_db, wget_hpkp **_hpkp) |
206 | 4.76k | { |
207 | 4.76k | if (plugin_vtable) { |
208 | 0 | plugin_vtable->add(hpkp_db, _hpkp); |
209 | 0 | *_hpkp = NULL; |
210 | 0 | return; |
211 | 0 | } |
212 | | |
213 | 4.76k | if (!_hpkp || !*_hpkp) |
214 | 2.68k | return; |
215 | | |
216 | 2.07k | wget_hpkp *hpkp = *_hpkp; |
217 | | |
218 | 2.07k | wget_thread_mutex_lock(hpkp_db->mutex); |
219 | | |
220 | 2.07k | if (hpkp->maxage == 0 || wget_vector_size(hpkp->pins) == 0) { |
221 | 640 | if (wget_hashmap_remove(hpkp_db->entries, hpkp)) |
222 | 29 | debug_printf("removed HPKP %s\n", hpkp->host); |
223 | 640 | wget_hpkp_free(hpkp); |
224 | 1.43k | } else { |
225 | 1.43k | wget_hpkp *old; |
226 | | |
227 | 1.43k | if (wget_hashmap_get(hpkp_db->entries, hpkp, &old)) { |
228 | 54 | old->created = hpkp->created; |
229 | 54 | old->maxage = hpkp->maxage; |
230 | 54 | old->expires = hpkp->expires; |
231 | 54 | old->include_subdomains = hpkp->include_subdomains; |
232 | 54 | wget_vector_free(&old->pins); |
233 | 54 | old->pins = hpkp->pins; |
234 | 54 | hpkp->pins = NULL; |
235 | 54 | debug_printf("update HPKP %s (maxage=%" PRId64 ", includeSubDomains=%d)\n", old->host, old->maxage, old->include_subdomains); |
236 | 54 | wget_hpkp_free(hpkp); |
237 | 1.38k | } else { |
238 | | // key and value are the same to make wget_hashmap_get() return old 'hpkp' |
239 | | /* debug_printf("add HPKP %s (maxage=%" PRId64 ", includeSubDomains=%d)\n", hpkp->host, hpkp->maxage, hpkp->include_subdomains); */ |
240 | 1.38k | wget_hashmap_put(hpkp_db->entries, hpkp, hpkp); |
241 | | // no need to free anything here |
242 | 1.38k | } |
243 | 1.43k | } |
244 | | |
245 | 2.07k | wget_thread_mutex_unlock(hpkp_db->mutex); |
246 | | |
247 | 2.07k | *_hpkp = NULL; |
248 | 2.07k | } |
249 | | |
250 | | static int hpkp_db_load(wget_hpkp_db *hpkp_db, FILE *fp) |
251 | 1.72k | { |
252 | 1.72k | int64_t created, max_age; |
253 | 1.72k | int include_subdomains; |
254 | | |
255 | 1.72k | wget_hpkp *hpkp = NULL; |
256 | 1.72k | struct stat st; |
257 | 1.72k | char *buf = NULL; |
258 | 1.72k | size_t bufsize = 0; |
259 | 1.72k | ssize_t buflen; |
260 | 1.72k | char hash_type[32], host[256], pin_b64[256]; |
261 | 1.72k | int64_t now = time(NULL); |
262 | | |
263 | | // if the database file hasn't changed since the last read |
264 | | // there's no need to reload |
265 | | |
266 | 1.72k | if (fstat(fileno(fp), &st) == 0) { |
267 | 0 | if (st.st_mtime != hpkp_db->load_time) |
268 | 0 | hpkp_db->load_time = st.st_mtime; |
269 | 0 | else |
270 | 0 | return 0; |
271 | 0 | } |
272 | | |
273 | 9.40k | while ((buflen = wget_getline(&buf, &bufsize, fp)) >= 0) { |
274 | 7.68k | char *linep = buf; |
275 | | |
276 | 7.68k | while (isspace(*linep)) linep++; // ignore leading whitespace |
277 | 7.68k | if (!*linep) continue; // skip empty lines |
278 | | |
279 | 6.58k | if (*linep == '#') |
280 | 200 | continue; // skip comments |
281 | | |
282 | | // strip off \r\n |
283 | 6.38k | while (buflen > 0 && (buf[buflen] == '\n' || buf[buflen] == '\r')) |
284 | 0 | buf[--buflen] = 0; |
285 | | |
286 | 6.38k | if (*linep != '*') { |
287 | 3.04k | wget_hpkp_db_add(hpkp_db, &hpkp); |
288 | | |
289 | 3.04k | if (sscanf(linep, "%255s %d %" SCNi64 " %" SCNi64, host, &include_subdomains, &created, &max_age) == 4) { |
290 | 2.58k | if (created < 0 || max_age < 0 || created >= INT64_MAX / 2 || max_age >= INT64_MAX / 2) { |
291 | 335 | max_age = 0; // avoid integer overflow here |
292 | 335 | } |
293 | 2.58k | int64_t expires = created + max_age; |
294 | 2.58k | if (max_age && expires >= now) { |
295 | 2.07k | hpkp = wget_hpkp_new(); |
296 | 2.07k | if (hpkp) { |
297 | 2.07k | if (!(hpkp->host = wget_strdup(host))) |
298 | 0 | xfree(hpkp); |
299 | 2.07k | else { |
300 | 2.07k | hpkp->maxage = max_age; |
301 | 2.07k | hpkp->created = created; |
302 | 2.07k | hpkp->expires = expires; |
303 | 2.07k | hpkp->include_subdomains = include_subdomains != 0; |
304 | 2.07k | } |
305 | 2.07k | } |
306 | 2.07k | } else |
307 | 510 | debug_printf("HPKP: entry '%s' is expired\n", host); |
308 | 2.58k | } else { |
309 | 457 | error_printf(_("HPKP: could not parse host line '%s'\n"), buf); |
310 | 457 | } |
311 | 3.33k | } else if (hpkp) { |
312 | 2.96k | if (sscanf(linep, "*%31s %255s", hash_type, pin_b64) == 2) { |
313 | 2.89k | wget_hpkp_pin_add(hpkp, hash_type, pin_b64); |
314 | 2.89k | } else { |
315 | 70 | error_printf(_("HPKP: could not parse pin line '%s'\n"), buf); |
316 | 70 | } |
317 | 2.96k | } else { |
318 | 377 | debug_printf("HPKP: skipping PIN entry: '%s'\n", buf); |
319 | 377 | } |
320 | 6.38k | } |
321 | | |
322 | 1.72k | wget_hpkp_db_add(hpkp_db, &hpkp); |
323 | | |
324 | 1.72k | xfree(buf); |
325 | | |
326 | 1.72k | if (ferror(fp)) { |
327 | 0 | hpkp_db->load_time = 0; // reload on next call to this function |
328 | 0 | return -1; |
329 | 0 | } |
330 | | |
331 | 1.72k | return 0; |
332 | 1.72k | } |
333 | | |
334 | | /** |
335 | | * \param[in] hpkp_db Handle to an HPKP database, obtained with wget_hpkp_db_init() |
336 | | * \return 0 on success, or a negative number on error |
337 | | * |
338 | | * Performs all operations necessary to access the HPKP database entries from persistent storage |
339 | | * using wget_hpkp_db_check_pubkey() for example. |
340 | | * |
341 | | * For databases created by wget_hpkp_db_init() data is loaded from `fname` parameter of wget_hpkp_db_init(). |
342 | | * If this function cannot correctly parse the whole file, -1 is returned. |
343 | | * |
344 | | * If `hpkp_db` is NULL then this function returns 0 and does nothing else. |
345 | | */ |
346 | | int wget_hpkp_db_load(wget_hpkp_db *hpkp_db) |
347 | 1.72k | { |
348 | 1.72k | if (plugin_vtable) |
349 | 0 | return plugin_vtable->load(hpkp_db); |
350 | | |
351 | 1.72k | if (!hpkp_db) |
352 | 0 | return 0; |
353 | | |
354 | 1.72k | if (!hpkp_db->fname || !*hpkp_db->fname) |
355 | 0 | return 0; |
356 | | |
357 | 1.72k | if (wget_update_file(hpkp_db->fname, (wget_update_load_fn *) hpkp_db_load, NULL, hpkp_db)) { |
358 | 0 | error_printf(_("Failed to read HPKP data\n")); |
359 | 0 | return -1; |
360 | 1.72k | } else { |
361 | 1.72k | debug_printf("Fetched HPKP data from '%s'\n", hpkp_db->fname); |
362 | 1.72k | return 0; |
363 | 1.72k | } |
364 | 1.72k | } |
365 | | |
366 | | static int hpkp_save_pin(void *_fp, void *_pin) |
367 | 0 | { |
368 | 0 | FILE *fp = _fp; |
369 | 0 | wget_hpkp_pin *pin = _pin; |
370 | |
|
371 | 0 | wget_fprintf(fp, "*%s %s\n", pin->hash_type, pin->pin_b64); |
372 | |
|
373 | 0 | if (ferror(fp)) |
374 | 0 | return -1; |
375 | | |
376 | 0 | return 0; |
377 | 0 | } |
378 | | |
379 | | WGET_GCC_NONNULL_ALL |
380 | | static int hpkp_save(void *_fp, const void *_hpkp, WGET_GCC_UNUSED void *v) |
381 | 0 | { |
382 | 0 | FILE *fp = _fp; |
383 | 0 | const wget_hpkp *hpkp = _hpkp; |
384 | |
|
385 | 0 | if (wget_vector_size(hpkp->pins) == 0) |
386 | 0 | debug_printf("HPKP: drop '%s', no PIN entries\n", hpkp->host); |
387 | 0 | else if (hpkp->expires < time(NULL)) |
388 | 0 | debug_printf("HPKP: drop '%s', expired\n", hpkp->host); |
389 | 0 | else { |
390 | 0 | wget_fprintf(fp, "%s %d %" PRIi64 " %" PRIi64 "\n", hpkp->host, hpkp->include_subdomains, hpkp->created, hpkp->maxage); |
391 | |
|
392 | 0 | if (ferror(fp)) |
393 | 0 | return -1; |
394 | | |
395 | 0 | return wget_vector_browse(hpkp->pins, hpkp_save_pin, fp); |
396 | 0 | } |
397 | | |
398 | 0 | return 0; |
399 | 0 | } |
400 | | |
401 | | static int hpkp_db_save(wget_hpkp_db *hpkp_db, FILE *fp) |
402 | 0 | { |
403 | 0 | wget_hashmap *entries = hpkp_db->entries; |
404 | |
|
405 | 0 | if (wget_hashmap_size(entries) > 0) { |
406 | 0 | fputs("# HPKP 1.0 file\n", fp); |
407 | 0 | fputs("#Generated by libwget " PACKAGE_VERSION ". Edit at your own risk.\n", fp); |
408 | 0 | fputs("#<hostname> <incl. subdomains> <created> <max-age>\n\n", fp); |
409 | |
|
410 | 0 | if (ferror(fp)) |
411 | 0 | return -1; |
412 | | |
413 | 0 | return wget_hashmap_browse(entries, hpkp_save, fp); |
414 | 0 | } |
415 | | |
416 | 0 | return 0; |
417 | 0 | } |
418 | | |
419 | | /** |
420 | | * \param[in] hpkp_db Handle to an HPKP database |
421 | | * \return 0 if the operation was successful, negative number in case of error. |
422 | | * |
423 | | * Saves the current HPKP database to persistent storage |
424 | | * |
425 | | * In case of databases created by wget_hpkp_db_init(), HPKP entries will be saved into file specified by |
426 | | * \p fname parameter of wget_hpkp_db_init(). In case of failure -1 will be returned with errno set. |
427 | | * |
428 | | * If \p fname is NULL then this function returns -1 and does nothing else. |
429 | | */ |
430 | | int wget_hpkp_db_save(wget_hpkp_db *hpkp_db) |
431 | 0 | { |
432 | 0 | if (plugin_vtable) |
433 | 0 | return plugin_vtable->save(hpkp_db); |
434 | | |
435 | 0 | if (!hpkp_db) |
436 | 0 | return -1; |
437 | | |
438 | 0 | int size; |
439 | |
|
440 | 0 | if (!hpkp_db->fname || !*hpkp_db->fname) |
441 | 0 | return -1; |
442 | | |
443 | 0 | if (wget_update_file(hpkp_db->fname, |
444 | 0 | (wget_update_load_fn *) hpkp_db_load, |
445 | 0 | (wget_update_load_fn *) hpkp_db_save, |
446 | 0 | hpkp_db)) |
447 | 0 | { |
448 | 0 | error_printf(_("Failed to write HPKP file '%s'\n"), hpkp_db->fname); |
449 | 0 | return -1; |
450 | 0 | } |
451 | | |
452 | 0 | if ((size = wget_hashmap_size(hpkp_db->entries))) |
453 | 0 | debug_printf("Saved %d HPKP entr%s into '%s'\n", size, size != 1 ? "ies" : "y", hpkp_db->fname); |
454 | 0 | else |
455 | 0 | debug_printf("No HPKP entries to save. Table is empty.\n"); |
456 | |
|
457 | 0 | return 0; |
458 | 0 | } |
459 | | |
460 | | /** |
461 | | * \param[in] hpkp_db Older HPKP database already passed to wget_hpkp_db_deinit(), or NULL |
462 | | * \param[in] fname Name of the file where the data should be stored, or NULL |
463 | | * \return Handle (pointer) to an HPKP database |
464 | | * |
465 | | * Constructor for the default implementation of HSTS database. |
466 | | * |
467 | | * This function does no file IO, data is loaded from file specified by `fname` when wget_hpkp_db_load() is called. |
468 | | * The entries in the file are subject to sanity checks as if they were added to the HPKP database |
469 | | * via wget_hpkp_db_add(). In particular, if an entry is expired due to `creation_time + max_age > cur_time` |
470 | | * it will not be added to the database, and a subsequent call to wget_hpkp_db_save() with the same `hpkp_db_priv` |
471 | | * handle and file name will overwrite the file without all the expired entries. |
472 | | * |
473 | | * Since the format of the file might change without notice, hand-crafted files are discouraged. |
474 | | * To create an HPKP database file that is guaranteed to be correctly parsed by this function, |
475 | | * wget_hpkp_db_save() should be used. |
476 | | * |
477 | | */ |
478 | | wget_hpkp_db *wget_hpkp_db_init(wget_hpkp_db *hpkp_db, const char *fname) |
479 | 1.72k | { |
480 | 1.72k | if (plugin_vtable) |
481 | 0 | return plugin_vtable->init(hpkp_db, fname); |
482 | | |
483 | 1.72k | if (!hpkp_db) { |
484 | 1.72k | hpkp_db = wget_calloc(1, sizeof(struct wget_hpkp_db_st)); |
485 | 1.72k | if (!hpkp_db) |
486 | 0 | return NULL; |
487 | 1.72k | } else |
488 | 0 | memset(hpkp_db, 0, sizeof(*hpkp_db)); |
489 | | |
490 | 1.72k | if (fname) |
491 | 1.72k | hpkp_db->fname = wget_strdup(fname); |
492 | 1.72k | hpkp_db->entries = wget_hashmap_create(16, (wget_hashmap_hash_fn *) hash_hpkp, (wget_hashmap_compare_fn *) compare_hpkp); |
493 | 1.72k | wget_hashmap_set_key_destructor(hpkp_db->entries, (wget_hashmap_key_destructor *) wget_hpkp_free); |
494 | | |
495 | | /* |
496 | | * Keys and values for the hashmap are 'hpkp' entries, so value == key. |
497 | | * The hash function hashes hostname. |
498 | | * The compare function compares hostname. |
499 | | * |
500 | | * Since the value == key, we just need the value destructor for freeing hashmap entries. |
501 | | */ |
502 | | |
503 | 1.72k | wget_thread_mutex_init(&hpkp_db->mutex); |
504 | | |
505 | 1.72k | return hpkp_db; |
506 | 1.72k | } |
507 | | |
508 | | /** |
509 | | * \param[in] hpkp_db HPKP database created using wget_hpkp_db_init() |
510 | | * \param[in] fname Name of the file where the data should be stored, or NULL |
511 | | * |
512 | | * Changes the file where data should be stored. Works only for databases created by wget_hpkp_db_init(). |
513 | | * This function does no file IO, data is loaded when wget_hpkp_db_load() is called. |
514 | | */ |
515 | | void wget_hpkp_db_set_fname(wget_hpkp_db *hpkp_db, const char *fname) |
516 | 0 | { |
517 | | xfree(hpkp_db->fname); |
518 | 0 | hpkp_db->fname = wget_strdup(fname); |
519 | 0 | } |
520 | | |
521 | | /**@}*/ |