• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * libwebsockets - small server side websockets and web server implementation
3  *
4  * Copyright (C) 2010 - 2021 Andy Green <andy@warmcat.com>
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to
8  * deal in the Software without restriction, including without limitation the
9  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10  * sell copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22  * IN THE SOFTWARE.
23  *
24  * Implements a cache backing store compatible with netscape cookies.txt format
25  * There is one entry per "line", and fields are tab-delimited
26  *
27  * We need to know the format here, because while the unique cookie tag consists
28  * of "hostname|urlpath|cookiename", that does not appear like that in the file;
29  * we have to go parse the fields and synthesize the corresponding tag.
30  *
31  * We rely on all the fields except the cookie value fitting in a 256 byte
32  * buffer, and allow eating multiple buffers to get a huge cookie values.
33  *
34  * Because the cookie file is a device-wide asset, although lws will change it
35  * from the lws thread without conflict, there may be other processes that will
36  * change it by removal and regenerating the file asynchronously.  For that
37  * reason, file handles are opened fresh each time we want to use the file, so
38  * we always get the latest version.
39  *
40  * When updating the file ourselves, we use a lockfile to ensure our process
41  * has exclusive access.
42  *
43  *
44  * Tag Matching rules
45  *
46  * There are three kinds of tag matching rules
47  *
48  * 1) specific - tag strigs must be the same
49  * 2) wilcard - tags matched using optional wildcards
50  * 3) wildcard + lookup - wildcard, but path part matches using cookie scope rules
51  *
52  */
53 
54 #include <private-lib-core.h>
55 #include "private-lib-misc-cache-ttl.h"
56 
57 typedef enum nsc_iterator_ret {
58 	NIR_CONTINUE		= 0,
59 	NIR_FINISH_OK		= 1,
60 	NIR_FINISH_ERROR	= -1
61 } nsc_iterator_ret_t;
62 
63 typedef enum cbreason {
64 	LCN_SOL			= (1 << 0),
65 	LCN_EOL			= (1 << 1)
66 } cbreason_t;
67 
68 typedef int (*nsc_cb_t)(lws_cache_nscookiejar_t *cache, void *opaque, int flags,
69 			const char *buf, size_t size);
70 
71 static void
72 expiry_cb(lws_sorted_usec_list_t *sul);
73 
74 static int
nsc_backing_open_lock(lws_cache_nscookiejar_t * cache,int mode,const char * par)75 nsc_backing_open_lock(lws_cache_nscookiejar_t *cache, int mode, const char *par)
76 {
77 	int sanity = 50;
78 	char lock[128];
79 	int fd_lock, fd;
80 
81 	lwsl_debug("%s: %s\n", __func__, par);
82 
83 	lws_snprintf(lock, sizeof(lock), "%s.LCK",
84 			cache->cache.info.u.nscookiejar.filepath);
85 
86 	do {
87 		fd_lock = open(lock, LWS_O_CREAT | O_EXCL, 0600);
88 		if (fd_lock >= 0) {
89 			close(fd_lock);
90 			break;
91 		}
92 
93 		if (!sanity--) {
94 			lwsl_warn("%s: unable to lock %s: errno %d\n", __func__,
95 					lock, errno);
96 			return -1;
97 		}
98 
99 #if defined(WIN32)
100 		Sleep(100);
101 #else
102 		usleep(100000);
103 #endif
104 	} while (1);
105 
106 	fd = open(cache->cache.info.u.nscookiejar.filepath,
107 		      LWS_O_CREAT | mode, 0600);
108 
109 	if (fd == -1) {
110 		lwsl_warn("%s: unable to open or create %s\n", __func__,
111 				cache->cache.info.u.nscookiejar.filepath);
112 		unlink(lock);
113 	}
114 
115 	return fd;
116 }
117 
118 static void
nsc_backing_close_unlock(lws_cache_nscookiejar_t * cache,int fd)119 nsc_backing_close_unlock(lws_cache_nscookiejar_t *cache, int fd)
120 {
121 	char lock[128];
122 
123 	lwsl_debug("%s\n", __func__);
124 
125 	lws_snprintf(lock, sizeof(lock), "%s.LCK",
126 			cache->cache.info.u.nscookiejar.filepath);
127 	if (fd >= 0)
128 		close(fd);
129 	unlink(lock);
130 }
131 
132 /*
133  * We're going to call the callback with chunks of the file with flags
134  * indicating we're giving it the start of a line and / or giving it the end
135  * of a line.
136  *
137  * It's like this because the cookie value may be huge (and to a lesser extent
138  * the path may also be big).
139  *
140  * If it's the start of a line (flags on the cb has LCN_SOL), then the buffer
141  * contains up to the first 256 chars of the line, it's enough to match with.
142  *
143  * We cannot hold the file open inbetweentimes, since other processes may
144  * regenerate it, so we need to bind to a new inode.  We open it with an
145  * exclusive flock() so other processes can't replace conflicting changes
146  * while we also write changes, without having to wait and see our changes.
147  */
148 
149 static int
nscookiejar_iterate(lws_cache_nscookiejar_t * cache,int fd,nsc_cb_t cb,void * opaque)150 nscookiejar_iterate(lws_cache_nscookiejar_t *cache, int fd,
151 		    nsc_cb_t cb, void *opaque)
152 {
153 	int m = 0, n = 0, e, r = LCN_SOL, ignore = 0, ret = 0;
154 	char temp[256], eof = 0;
155 
156 	if (lseek(fd, 0, SEEK_SET) == (off_t)-1)
157 		return -1;
158 
159 	do { /* for as many buffers in the file */
160 
161 		int n1;
162 
163 		lwsl_debug("%s: n %d, m %d\n", __func__, n, m);
164 
165 read:
166 		if ((size_t)n >= sizeof(temp) - 1)
167 			/* there's no space left in temp */
168 			n1 = 0;
169 		else
170 			n1 = (int)read(fd, temp + n, sizeof(temp) - (size_t)n);
171 
172 		lwsl_debug("%s: n1 %d\n", __func__, n1);
173 
174 		if (n1 <= 0) {
175 			eof = 1;
176 			if (m == n)
177 				continue;
178 		} else {
179 			n += n1;
180 
181 			if ((size_t)n > sizeof(temp)) { /* coverity */
182 				ret = -1;
183 				goto bail;
184 			}
185 		}
186 
187 		while (m < n) {
188 
189 			m++; /* m can == n nw then */
190 
191 			if (temp[m - 1] != '\n')
192 				continue;
193 
194 			/* ie, we hit EOL */
195 
196 			if (temp[0] == '#')
197 				/* lines starting with # are comments */
198 				e = 0;
199 			else
200 				e = cb(cache, opaque, r | LCN_EOL, temp,
201 				       (size_t)m - 1);
202 			r = LCN_SOL;
203 			ignore = 0;
204 			/*
205 			 * Move back remainder and prefill the gap that opened
206 			 * up: we want to pass enough in the start chunk so the
207 			 * cb can classify it even if it can't get all the
208 			 * value part in one go
209 			 */
210 
211 			/* coverity: we will blow up if m > n */
212 			if (m > n) {
213 				ret = -1;
214 				goto bail;
215 			}
216 
217 			memmove(temp, temp + m, (size_t)(n - m));
218 			n -= m;
219 			m = 0;
220 
221 			if (e) {
222 				ret = e;
223 				goto bail;
224 			}
225 
226 			goto read;
227 		}
228 
229 		if (m) {
230 			/* we ran out of buffer */
231 			if (ignore || (r == LCN_SOL && n && temp[0] == '#')) {
232 				e = 0;
233 				ignore = 1;
234 			} else {
235 				e = cb(cache, opaque,
236 				       r | (n == m && eof ? LCN_EOL : 0),
237 				       temp, (size_t)m);
238 
239 				m = 0;
240 				n = 0;
241 			}
242 
243 			if (e) {
244 				/*
245 				 * We have to call off the whole thing if any
246 				 * step, eg, OOMs
247 				 */
248 				ret = e;
249 				goto bail;
250 			}
251 			r = 0;
252 		}
253 
254 	} while (!eof || n != m);
255 
256 	ret = 0;
257 
258 bail:
259 
260 	return ret;
261 }
262 
263 /*
264  * lookup() just handles wildcard resolution, it doesn't deal with moving the
265  * hits to L1.  That has to be done individually by non-wildcard names.
266  */
267 
268 enum {
269 	NSC_COL_HOST		= 0, /* wc idx 0 */
270 	NSC_COL_PATH		= 2, /* wc idx 1 */
271 	NSC_COL_EXPIRY		= 4,
272 	NSC_COL_NAME		= 5, /* wc idx 2 */
273 
274 	NSC_COL_COUNT		= 6
275 };
276 
277 /*
278  * This performs the specialized wildcard that knows about cookie path match
279  * rules.
280  *
281  * To defeat the lookup path matching, lie to it about idx being NSC_COL_PATH
282  */
283 
284 static int
nsc_match(const char * wc,size_t wc_len,const char * col,size_t col_len,int idx)285 nsc_match(const char *wc, size_t wc_len, const char *col, size_t col_len,
286 	  int idx)
287 {
288 	size_t n = 0;
289 
290 	if (idx != NSC_COL_PATH)
291 		return lws_strcmp_wildcard(wc, wc_len, col, col_len);
292 
293 	/*
294 	 * Cookie path match is special, if we lookup on a path like /my/path,
295 	 * we must match on cookie paths for every dir level including /, so
296 	 * match on /, /my, and /my/path.  But we must not match on /m or
297 	 * /my/pa etc.  If we lookup on /, we must not match /my/path
298 	 *
299 	 * Let's go through wc checking at / and for every complete subpath if
300 	 * it is an explicit match
301 	 */
302 
303 	if (!strcmp(col, wc))
304 		return 0; /* exact hit */
305 
306 	while (n <= wc_len) {
307 		if (n == wc_len || wc[n] == '/') {
308 			if (n && col_len <= n && !strncmp(wc, col, n))
309 				return 0; /* hit */
310 
311 			if (n != wc_len && col_len <= n + 1 &&
312 			    !strncmp(wc, col, n + 1)) /* check for trailing / */
313 				return 0; /* hit */
314 		}
315 		n++;
316 	}
317 
318 	return 1; /* fail */
319 }
320 
321 static const uint8_t nsc_cols[] = { NSC_COL_HOST, NSC_COL_PATH, NSC_COL_NAME };
322 
323 static int
lws_cache_nscookiejar_tag_match(struct lws_cache_ttl_lru * cache,const char * wc,const char * tag,char lookup)324 lws_cache_nscookiejar_tag_match(struct lws_cache_ttl_lru *cache,
325 				const char *wc, const char *tag, char lookup)
326 {
327 	const char *wc_end = wc + strlen(wc), *tag_end = tag + strlen(tag),
328 			*start_wc, *start_tag;
329 	int n = 0;
330 
331 	lwsl_cache("%s: '%s' vs '%s'\n", __func__, wc, tag);
332 
333 	/*
334 	 * Given a well-formed host|path|name tag and a wildcard term,
335 	 * make the determination if the tag matches the wildcard or not,
336 	 * using lookup rules that apply at this cache level.
337 	 */
338 
339 	while (n < 3) {
340 		start_wc = wc;
341 		while (wc < wc_end && *wc != LWSCTAG_SEP)
342 			wc++;
343 
344 		start_tag = tag;
345 		while (tag < tag_end && *tag != LWSCTAG_SEP)
346 			tag++;
347 
348 		lwsl_cache("%s:   '%.*s' vs '%.*s'\n", __func__,
349 				lws_ptr_diff(wc, start_wc), start_wc,
350 				lws_ptr_diff(tag, start_tag), start_tag);
351 		if (nsc_match(start_wc, lws_ptr_diff_size_t(wc, start_wc),
352 			      start_tag, lws_ptr_diff_size_t(tag, start_tag),
353 			      lookup ? nsc_cols[n] : NSC_COL_HOST)) {
354 			lwsl_cache("%s: fail\n", __func__);
355 			return 1;
356 		}
357 
358 		if (wc < wc_end)
359 			wc++;
360 		if (tag < tag_end)
361 			tag++;
362 
363 		n++;
364 	}
365 
366 	lwsl_cache("%s: hit\n", __func__);
367 
368 	return 0; /* match */
369 }
370 
371 /*
372  * Converts the start of a cookie file line into a tag
373  */
374 
375 static int
nsc_line_to_tag(const char * buf,size_t size,char * tag,size_t max_tag,lws_usec_t * pexpiry)376 nsc_line_to_tag(const char *buf, size_t size, char *tag, size_t max_tag,
377 		lws_usec_t *pexpiry)
378 {
379 	int n, idx = 0, tl = 0;
380 	lws_usec_t expiry = 0;
381 	size_t bn = 0;
382 	char col[64];
383 
384 	if (size < 3)
385 		return 1;
386 
387 	while (bn < size && idx <= NSC_COL_NAME) {
388 
389 		n = 0;
390 		while (bn < size && n < (int)sizeof(col) - 1 &&
391 		       buf[bn] != '\t')
392 			col[n++] = buf[bn++];
393 		col[n] = '\0';
394 		if (buf[bn] == '\t')
395 			bn++;
396 
397 		switch (idx) {
398 		case NSC_COL_EXPIRY:
399 			expiry = (lws_usec_t)((unsigned long long)atoll(col) *
400 					(lws_usec_t)LWS_US_PER_SEC);
401 			break;
402 
403 		case NSC_COL_HOST:
404 		case NSC_COL_PATH:
405 		case NSC_COL_NAME:
406 
407 			/*
408 			 * As we match the pieces of the wildcard,
409 			 * compose the matches into a specific tag
410 			 */
411 
412 			if (tl + n + 2 > (int)max_tag)
413 				return 1;
414 			if (tl)
415 				tag[tl++] = LWSCTAG_SEP;
416 			memcpy(tag + tl, col, (size_t)n);
417 			tl += n;
418 			tag[tl] = '\0';
419 			break;
420 		default:
421 			break;
422 		}
423 
424 		idx++;
425 	}
426 
427 	if (pexpiry)
428 		*pexpiry = expiry;
429 
430 	lwsl_info("%s: %.*s: tag '%s'\n", __func__, (int)size, buf, tag);
431 
432 	return 0;
433 }
434 
435 struct nsc_lookup_ctx {
436 	const char		*wildcard_key;
437 	lws_dll2_owner_t	*results_owner;
438 	lws_cache_match_t	*match; /* current match if any */
439 	size_t			wklen;
440 };
441 
442 
443 static int
nsc_lookup_cb(lws_cache_nscookiejar_t * cache,void * opaque,int flags,const char * buf,size_t size)444 nsc_lookup_cb(lws_cache_nscookiejar_t *cache, void *opaque, int flags,
445 	      const char *buf, size_t size)
446 {
447 	struct nsc_lookup_ctx *ctx = (struct nsc_lookup_ctx *)opaque;
448 	lws_usec_t expiry;
449 	char tag[200];
450 	int tl;
451 
452 	if (!(flags & LCN_SOL)) {
453 		if (ctx->match)
454 			ctx->match->payload_size += size;
455 
456 		return NIR_CONTINUE;
457 	}
458 
459 	/*
460 	 * There should be enough in buf to match or reject it... let's
461 	 * synthesize a tag from the text "line" and then check the tags for
462 	 * a match
463 	 */
464 
465 	ctx->match = NULL; /* new SOL means stop tracking payload len */
466 
467 	if (nsc_line_to_tag(buf, size, tag, sizeof(tag), &expiry))
468 		return NIR_CONTINUE;
469 
470 	if (lws_cache_nscookiejar_tag_match(&cache->cache,
471 					    ctx->wildcard_key, tag, 1))
472 		return NIR_CONTINUE;
473 
474 	tl = (int)strlen(tag);
475 
476 	/*
477 	 * ... it looks like a match then... create new match
478 	 * object with the specific tag, and add it to the owner list
479 	 */
480 
481 	ctx->match = lws_fi(&cache->cache.info.cx->fic, "cache_lookup_oom") ? NULL :
482 			lws_malloc(sizeof(*ctx->match) + (unsigned int)tl + 1u,
483 				__func__);
484 	if (!ctx->match)
485 		/* caller of lookup will clean results list on fail */
486 		return NIR_FINISH_ERROR;
487 
488 	ctx->match->payload_size = size;
489 	ctx->match->tag_size = (size_t)tl;
490 	ctx->match->expiry = expiry;
491 
492 	memset(&ctx->match->list, 0, sizeof(ctx->match->list));
493 	memcpy(&ctx->match[1], tag, (size_t)tl + 1u);
494 	lws_dll2_add_tail(&ctx->match->list, ctx->results_owner);
495 
496 	return NIR_CONTINUE;
497 }
498 
499 static int
lws_cache_nscookiejar_lookup(struct lws_cache_ttl_lru * _c,const char * wildcard_key,lws_dll2_owner_t * results_owner)500 lws_cache_nscookiejar_lookup(struct lws_cache_ttl_lru *_c,
501 			     const char *wildcard_key,
502 			     lws_dll2_owner_t *results_owner)
503 {
504 	lws_cache_nscookiejar_t *cache = (lws_cache_nscookiejar_t *)_c;
505 	struct nsc_lookup_ctx ctx;
506 	int ret, fd;
507 
508 	fd = nsc_backing_open_lock(cache, LWS_O_RDONLY, __func__);
509 	if (fd < 0)
510 		return 1;
511 
512 	ctx.wildcard_key = wildcard_key;
513 	ctx.results_owner = results_owner;
514 	ctx.wklen = strlen(wildcard_key);
515 	ctx.match = 0;
516 
517 	ret = nscookiejar_iterate(cache, fd, nsc_lookup_cb, &ctx);
518 		/*
519 		 * The cb can fail, eg, with OOM, making the whole lookup
520 		 * invalid and returning fail.  Caller will clean
521 		 * results_owner on fail.
522 		 */
523 	nsc_backing_close_unlock(cache, fd);
524 
525 	return ret == NIR_FINISH_ERROR;
526 }
527 
528 /*
529  * It's pretty horrible having to implement add or remove individual items by
530  * file regeneration, but if we don't want to keep it all in heap, and we want
531  * this cookie jar format, that is what we are into.
532  *
533  * Allow to optionally add a "line", optionally wildcard delete tags, and always
534  * delete expired entries.
535  *
536  * Although we can rely on the lws thread to be doing this, multiple processes
537  * may be using the cookie jar and can tread on each other.  So we use flock()
538  * (linux only) to get exclusive access while we are processing this.
539  *
540  * We leave the existing file alone and generate a new one alongside it, with a
541  * fixed name.tmp format so it can't leak, if that went OK then we unlink the
542  * old and rename the new.
543  */
544 
545 struct nsc_regen_ctx {
546 	const char		*wildcard_key_delete;
547 	const void		*add_data;
548 	lws_usec_t		curr;
549 	size_t			add_size;
550 	int			fdt;
551 	char			drop;
552 };
553 
554 /* only used by nsc_regen() */
555 
556 static int
nsc_regen_cb(lws_cache_nscookiejar_t * cache,void * opaque,int flags,const char * buf,size_t size)557 nsc_regen_cb(lws_cache_nscookiejar_t *cache, void *opaque, int flags,
558 	      const char *buf, size_t size)
559 {
560 	struct nsc_regen_ctx *ctx = (struct nsc_regen_ctx *)opaque;
561 	char tag[256];
562 	lws_usec_t expiry;
563 
564 	if (flags & LCN_SOL) {
565 
566 		ctx->drop = 0;
567 
568 		if (nsc_line_to_tag(buf, size, tag, sizeof(tag), &expiry))
569 			/* filter it out if it is unparseable */
570 			goto drop;
571 
572 		/* routinely track the earliest expiry */
573 
574 		if (!cache->earliest_expiry ||
575 		    (expiry && cache->earliest_expiry > expiry))
576 			cache->earliest_expiry = expiry;
577 
578 		if (expiry && expiry < ctx->curr)
579 			/* routinely strip anything beyond its expiry */
580 			goto drop;
581 
582 		if (ctx->wildcard_key_delete)
583 			lwsl_cache("%s: %s vs %s\n", __func__,
584 					tag, ctx->wildcard_key_delete);
585 		if (ctx->wildcard_key_delete &&
586 		    !lws_cache_nscookiejar_tag_match(&cache->cache,
587 						     ctx->wildcard_key_delete,
588 						     tag, 0)) {
589 			lwsl_cache("%s: %s matches wc delete %s\n", __func__,
590 					tag, ctx->wildcard_key_delete);
591 			goto drop;
592 		}
593 	}
594 
595 	if (ctx->drop)
596 		return 0;
597 
598 	cache->cache.current_footprint += (uint64_t)size;
599 
600 	if (write(ctx->fdt, buf, /*msvc*/(unsigned int)size) != (ssize_t)size)
601 		return NIR_FINISH_ERROR;
602 
603 	if (flags & LCN_EOL)
604 		if ((size_t)write(ctx->fdt, "\n", 1) != 1)
605 			return NIR_FINISH_ERROR;
606 
607 	return 0;
608 
609 drop:
610 	ctx->drop = 1;
611 
612 	return NIR_CONTINUE;
613 }
614 
615 static int
nsc_regen(lws_cache_nscookiejar_t * cache,const char * wc_delete,const void * pay,size_t pay_size)616 nsc_regen(lws_cache_nscookiejar_t *cache, const char *wc_delete,
617 	  const void *pay, size_t pay_size)
618 {
619 	struct nsc_regen_ctx ctx;
620 	char filepath[128];
621 	int fd, ret = 1;
622 
623 	fd = nsc_backing_open_lock(cache, LWS_O_RDONLY, __func__);
624 	if (fd < 0)
625 		return 1;
626 
627 	lws_snprintf(filepath, sizeof(filepath), "%s.tmp",
628 			cache->cache.info.u.nscookiejar.filepath);
629 	unlink(filepath);
630 
631 	if (lws_fi(&cache->cache.info.cx->fic, "cache_regen_temp_open"))
632 		goto bail;
633 
634 	ctx.fdt = open(filepath, LWS_O_CREAT | LWS_O_WRONLY, 0600);
635 	if (ctx.fdt < 0)
636 		goto bail;
637 
638 	/* magic header */
639 
640 	if (lws_fi(&cache->cache.info.cx->fic, "cache_regen_temp_write") ||
641 	/* other consumers insist to see this at start of cookie jar */
642 	    write(ctx.fdt, "# Netscape HTTP Cookie File\n", 28) != 28)
643 		goto bail1;
644 
645 	/* if we are adding something, put it first */
646 
647 	if (pay &&
648 	    write(ctx.fdt, pay, /*msvc*/(unsigned int)pay_size) !=
649 						    (ssize_t)pay_size)
650 		goto bail1;
651 	if (pay && write(ctx.fdt, "\n", 1u) != (ssize_t)1)
652 		goto bail1;
653 
654 	cache->cache.current_footprint = 0;
655 
656 	ctx.wildcard_key_delete = wc_delete;
657 	ctx.add_data = pay;
658 	ctx.add_size = pay_size;
659 	ctx.curr = lws_now_usecs();
660 	ctx.drop = 0;
661 
662 	cache->earliest_expiry = 0;
663 
664 	if (lws_fi(&cache->cache.info.cx->fic, "cache_regen_iter_fail") ||
665 	    nscookiejar_iterate(cache, fd, nsc_regen_cb, &ctx))
666 		goto bail1;
667 
668 	close(ctx.fdt);
669 	ctx.fdt = -1;
670 
671 	if (unlink(cache->cache.info.u.nscookiejar.filepath) == -1)
672 		lwsl_info("%s: unlink %s failed\n", __func__,
673 			  cache->cache.info.u.nscookiejar.filepath);
674 	if (rename(filepath, cache->cache.info.u.nscookiejar.filepath) == -1)
675 		lwsl_info("%s: rename %s failed\n", __func__,
676 			  cache->cache.info.u.nscookiejar.filepath);
677 
678 	if (cache->earliest_expiry)
679 		lws_cache_schedule(&cache->cache, expiry_cb,
680 				   cache->earliest_expiry);
681 
682 	ret = 0;
683 	goto bail;
684 
685 bail1:
686 	if (ctx.fdt >= 0)
687 		close(ctx.fdt);
688 bail:
689 	unlink(filepath);
690 
691 	nsc_backing_close_unlock(cache, fd);
692 
693 	return ret;
694 }
695 
696 static void
expiry_cb(lws_sorted_usec_list_t * sul)697 expiry_cb(lws_sorted_usec_list_t *sul)
698 {
699 	lws_cache_nscookiejar_t *cache = lws_container_of(sul,
700 					lws_cache_nscookiejar_t, cache.sul);
701 
702 	/*
703 	 * regen the cookie jar without changes, so expired are removed and
704 	 * new earliest expired computed
705 	 */
706 	if (nsc_regen(cache, NULL, NULL, 0))
707 		return;
708 
709 	if (cache->earliest_expiry)
710 		lws_cache_schedule(&cache->cache, expiry_cb,
711 				   cache->earliest_expiry);
712 }
713 
714 
715 /* specific_key and expiry are ignored, since it must be encoded in payload */
716 
717 static int
lws_cache_nscookiejar_write(struct lws_cache_ttl_lru * _c,const char * specific_key,const uint8_t * source,size_t size,lws_usec_t expiry,void ** ppvoid)718 lws_cache_nscookiejar_write(struct lws_cache_ttl_lru *_c,
719 			    const char *specific_key, const uint8_t *source,
720 			    size_t size, lws_usec_t expiry, void **ppvoid)
721 {
722 	lws_cache_nscookiejar_t *cache = (lws_cache_nscookiejar_t *)_c;
723 	char tag[128];
724 
725 	lwsl_cache("%s: %s: len %d\n", __func__, _c->info.name, (int)size);
726 
727 	assert(source);
728 
729 	if (nsc_line_to_tag((const char *)source, size, tag, sizeof(tag), NULL))
730 		return 1;
731 
732 	if (ppvoid)
733 		*ppvoid = NULL;
734 
735 	if (nsc_regen(cache, tag, source, size)) {
736 		lwsl_err("%s: regen failed\n", __func__);
737 
738 		return 1;
739 	}
740 
741 	return 0;
742 }
743 
744 struct nsc_get_ctx {
745 	struct lws_buflist	*buflist;
746 	const char		*specific_key;
747 	const void		**pdata;
748 	size_t			*psize;
749 	lws_cache_ttl_lru_t	*l1;
750 	lws_usec_t		expiry;
751 };
752 
753 /*
754  * We're looking for a specific key, if found, we want to make an entry for it
755  * in L1 and return information about that
756  */
757 
758 static int
nsc_get_cb(lws_cache_nscookiejar_t * cache,void * opaque,int flags,const char * buf,size_t size)759 nsc_get_cb(lws_cache_nscookiejar_t *cache, void *opaque, int flags,
760 	   const char *buf, size_t size)
761 {
762 	struct nsc_get_ctx *ctx = (struct nsc_get_ctx *)opaque;
763 	char tag[200];
764 	uint8_t *q;
765 
766 	if (ctx->buflist)
767 		goto collect;
768 
769 	if (!(flags & LCN_SOL))
770 		return NIR_CONTINUE;
771 
772 	if (nsc_line_to_tag(buf, size, tag, sizeof(tag), &ctx->expiry)) {
773 		lwsl_err("%s: can't get tag\n", __func__);
774 		return NIR_CONTINUE;
775 	}
776 
777 	lwsl_cache("%s: %s %s\n", __func__, ctx->specific_key, tag);
778 
779 	if (strcmp(ctx->specific_key, tag)) {
780 		lwsl_cache("%s: no match\n", __func__);
781 		return NIR_CONTINUE;
782 	}
783 
784 	/* it's a match */
785 
786 	lwsl_cache("%s: IS match\n", __func__);
787 
788 	if (!(flags & LCN_EOL))
789 		goto collect;
790 
791 	/* it all fit in the buffer, let's create it in L1 now */
792 
793 	*ctx->psize = size;
794 	if (ctx->l1->info.ops->write(ctx->l1,
795 				     ctx->specific_key, (const uint8_t *)buf,
796 				     size, ctx->expiry, (void **)ctx->pdata))
797 		return NIR_FINISH_ERROR;
798 
799 	return NIR_FINISH_OK;
800 
801 collect:
802 	/*
803 	 * it's bigger than one buffer-load, we have to stash what we're getting
804 	 * on a buflist and create it when we have it all
805 	 */
806 
807 	if (lws_buflist_append_segment(&ctx->buflist, (const uint8_t *)buf,
808 				       size))
809 		goto cleanup;
810 
811 	if (!(flags & LCN_EOL))
812 		return NIR_CONTINUE;
813 
814 	/* we have all the payload, create the L1 entry without payload yet */
815 
816 	*ctx->psize = size;
817 	if (ctx->l1->info.ops->write(ctx->l1, ctx->specific_key, NULL,
818 				     lws_buflist_total_len(&ctx->buflist),
819 				     ctx->expiry, (void **)&q))
820 		goto cleanup;
821 	*ctx->pdata = q;
822 
823 	/* dump the buflist into the L1 cache entry */
824 
825 	do {
826 		uint8_t *p;
827 		size_t len = lws_buflist_next_segment_len(&ctx->buflist, &p);
828 
829 		memcpy(q, p, len);
830 		q += len;
831 
832 		lws_buflist_use_segment(&ctx->buflist, len);
833 	} while (ctx->buflist);
834 
835 	return NIR_FINISH_OK;
836 
837 cleanup:
838 	lws_buflist_destroy_all_segments(&ctx->buflist);
839 
840 	return NIR_FINISH_ERROR;
841 }
842 
843 static int
lws_cache_nscookiejar_get(struct lws_cache_ttl_lru * _c,const char * specific_key,const void ** pdata,size_t * psize)844 lws_cache_nscookiejar_get(struct lws_cache_ttl_lru *_c,
845 			  const char *specific_key, const void **pdata,
846 			  size_t *psize)
847 {
848 	lws_cache_nscookiejar_t *cache = (lws_cache_nscookiejar_t *)_c;
849 	struct nsc_get_ctx ctx;
850 	int ret, fd;
851 
852 	fd = nsc_backing_open_lock(cache, LWS_O_RDONLY, __func__);
853 	if (fd < 0)
854 		return 1;
855 
856 	/* get a pointer to l1 */
857 	ctx.l1 = &cache->cache;
858 	while (ctx.l1->child)
859 		ctx.l1 = ctx.l1->child;
860 
861 	ctx.pdata = pdata;
862 	ctx.psize = psize;
863 	ctx.specific_key = specific_key;
864 	ctx.buflist = NULL;
865 	ctx.expiry = 0;
866 
867 	ret = nscookiejar_iterate(cache, fd, nsc_get_cb, &ctx);
868 
869 	nsc_backing_close_unlock(cache, fd);
870 
871 	return ret != NIR_FINISH_OK;
872 }
873 
874 static int
lws_cache_nscookiejar_invalidate(struct lws_cache_ttl_lru * _c,const char * wc_key)875 lws_cache_nscookiejar_invalidate(struct lws_cache_ttl_lru *_c,
876 				 const char *wc_key)
877 {
878 	lws_cache_nscookiejar_t *cache = (lws_cache_nscookiejar_t *)_c;
879 
880 	return nsc_regen(cache, wc_key, NULL, 0);
881 }
882 
883 static struct lws_cache_ttl_lru *
lws_cache_nscookiejar_create(const struct lws_cache_creation_info * info)884 lws_cache_nscookiejar_create(const struct lws_cache_creation_info *info)
885 {
886 	lws_cache_nscookiejar_t *cache;
887 
888 	cache = lws_fi(&info->cx->fic, "cache_createfail") ? NULL :
889 					lws_zalloc(sizeof(*cache), __func__);
890 	if (!cache)
891 		return NULL;
892 
893 	cache->cache.info = *info;
894 
895 	/*
896 	 * We need to scan the file, if it exists, and find the earliest
897 	 * expiry while cleaning out any expired entries
898 	 */
899 	expiry_cb(&cache->cache.sul);
900 
901 	lwsl_notice("%s: create %s\n", __func__, info->name ? info->name : "?");
902 
903 	return (struct lws_cache_ttl_lru *)cache;
904 }
905 
906 static int
lws_cache_nscookiejar_expunge(struct lws_cache_ttl_lru * _c)907 lws_cache_nscookiejar_expunge(struct lws_cache_ttl_lru *_c)
908 {
909 	lws_cache_nscookiejar_t *cache = (lws_cache_nscookiejar_t *)_c;
910 	int r;
911 
912 	if (!cache)
913 		return 0;
914 
915 	r = unlink(cache->cache.info.u.nscookiejar.filepath);
916 	if (r)
917 		lwsl_warn("%s: failed to unlink %s\n", __func__,
918 				cache->cache.info.u.nscookiejar.filepath);
919 
920 	return r;
921 }
922 
923 static void
lws_cache_nscookiejar_destroy(struct lws_cache_ttl_lru ** _pc)924 lws_cache_nscookiejar_destroy(struct lws_cache_ttl_lru **_pc)
925 {
926 	lws_cache_nscookiejar_t *cache = (lws_cache_nscookiejar_t *)*_pc;
927 
928 	if (!cache)
929 		return;
930 
931 	lws_sul_cancel(&cache->cache.sul);
932 
933 	lws_free_set_NULL(*_pc);
934 }
935 
936 #if defined(_DEBUG)
937 
938 static int
nsc_dump_cb(lws_cache_nscookiejar_t * cache,void * opaque,int flags,const char * buf,size_t size)939 nsc_dump_cb(lws_cache_nscookiejar_t *cache, void *opaque, int flags,
940 	      const char *buf, size_t size)
941 {
942 	lwsl_hexdump_cache(buf, size);
943 
944 	return 0;
945 }
946 
947 static void
lws_cache_nscookiejar_debug_dump(struct lws_cache_ttl_lru * _c)948 lws_cache_nscookiejar_debug_dump(struct lws_cache_ttl_lru *_c)
949 {
950 	lws_cache_nscookiejar_t *cache = (lws_cache_nscookiejar_t *)_c;
951 	int fd = nsc_backing_open_lock(cache, LWS_O_RDONLY, __func__);
952 
953 	if (fd < 0)
954 		return;
955 
956 	lwsl_cache("%s: %s\n", __func__, _c->info.name);
957 
958 	nscookiejar_iterate(cache, fd, nsc_dump_cb, NULL);
959 
960 	nsc_backing_close_unlock(cache, fd);
961 }
962 #endif
963 
964 const struct lws_cache_ops lws_cache_ops_nscookiejar = {
965 	.create			= lws_cache_nscookiejar_create,
966 	.destroy		= lws_cache_nscookiejar_destroy,
967 	.expunge		= lws_cache_nscookiejar_expunge,
968 
969 	.write			= lws_cache_nscookiejar_write,
970 	.tag_match		= lws_cache_nscookiejar_tag_match,
971 	.lookup			= lws_cache_nscookiejar_lookup,
972 	.invalidate		= lws_cache_nscookiejar_invalidate,
973 	.get			= lws_cache_nscookiejar_get,
974 #if defined(_DEBUG)
975 	.debug_dump		= lws_cache_nscookiejar_debug_dump,
976 #endif
977 };
978