• 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 		n1 = (int)read(fd, temp + n, sizeof(temp) - (size_t)n);
167 
168 		lwsl_debug("%s: n1 %d\n", __func__, n1);
169 
170 		if (n1 <= 0) {
171 			eof = 1;
172 			if (m == n)
173 				continue;
174 		} else
175 			n += n1;
176 
177 		while (m < n) {
178 
179 			m++;
180 
181 			if (temp[m - 1] != '\n')
182 				continue;
183 
184 			/* ie, we hit EOL */
185 
186 			if (temp[0] == '#')
187 				/* lines starting with # are comments */
188 				e = 0;
189 			else
190 				e = cb(cache, opaque, r | LCN_EOL, temp,
191 				       (size_t)m - 1);
192 			r = LCN_SOL;
193 			ignore = 0;
194 			/*
195 			 * Move back remainder and prefill the gap that opened
196 			 * up: we want to pass enough in the start chunk so the
197 			 * cb can classify it even if it can't get all the
198 			 * value part in one go
199 			 */
200 			memmove(temp, temp + m, (size_t)(n - m));
201 			n -= m;
202 			m = 0;
203 
204 			if (e) {
205 				ret = e;
206 				goto bail;
207 			}
208 
209 			goto read;
210 		}
211 
212 		if (m) {
213 			/* we ran out of buffer */
214 			if (ignore || (r == LCN_SOL && n && temp[0] == '#')) {
215 				e = 0;
216 				ignore = 1;
217 			} else {
218 				e = cb(cache, opaque,
219 				       r | (n == m && eof ? LCN_EOL : 0),
220 				       temp, (size_t)m);
221 
222 				m = 0;
223 				n = 0;
224 			}
225 
226 			if (e) {
227 				/*
228 				 * We have to call off the whole thing if any
229 				 * step, eg, OOMs
230 				 */
231 				ret = e;
232 				goto bail;
233 			}
234 			r = 0;
235 		}
236 
237 	} while (!eof || n != m);
238 
239 	ret = 0;
240 
241 bail:
242 
243 	return ret;
244 }
245 
246 /*
247  * lookup() just handles wildcard resolution, it doesn't deal with moving the
248  * hits to L1.  That has to be done individually by non-wildcard names.
249  */
250 
251 enum {
252 	NSC_COL_HOST		= 0, /* wc idx 0 */
253 	NSC_COL_PATH		= 2, /* wc idx 1 */
254 	NSC_COL_EXPIRY		= 4,
255 	NSC_COL_NAME		= 5, /* wc idx 2 */
256 
257 	NSC_COL_COUNT		= 6
258 };
259 
260 /*
261  * This performs the specialized wildcard that knows about cookie path match
262  * rules.
263  *
264  * To defeat the lookup path matching, lie to it about idx being NSC_COL_PATH
265  */
266 
267 static int
nsc_match(const char * wc,size_t wc_len,const char * col,size_t col_len,int idx)268 nsc_match(const char *wc, size_t wc_len, const char *col, size_t col_len,
269 	  int idx)
270 {
271 	size_t n = 0;
272 
273 	if (idx != NSC_COL_PATH)
274 		return lws_strcmp_wildcard(wc, wc_len, col, col_len);
275 
276 	/*
277 	 * Cookie path match is special, if we lookup on a path like /my/path,
278 	 * we must match on cookie paths for every dir level including /, so
279 	 * match on /, /my, and /my/path.  But we must not match on /m or
280 	 * /my/pa etc.  If we lookup on /, we must not match /my/path
281 	 *
282 	 * Let's go through wc checking at / and for every complete subpath if
283 	 * it is an explicit match
284 	 */
285 
286 	if (!strcmp(col, wc))
287 		return 0; /* exact hit */
288 
289 	while (n <= wc_len) {
290 		if (n == wc_len || wc[n] == '/') {
291 			if (n && col_len <= n && !strncmp(wc, col, n))
292 				return 0; /* hit */
293 
294 			if (n != wc_len && col_len <= n + 1 &&
295 			    !strncmp(wc, col, n + 1)) /* check for trailing / */
296 				return 0; /* hit */
297 		}
298 		n++;
299 	}
300 
301 	return 1; /* fail */
302 }
303 
304 static const uint8_t nsc_cols[] = { NSC_COL_HOST, NSC_COL_PATH, NSC_COL_NAME };
305 
306 static int
lws_cache_nscookiejar_tag_match(struct lws_cache_ttl_lru * cache,const char * wc,const char * tag,char lookup)307 lws_cache_nscookiejar_tag_match(struct lws_cache_ttl_lru *cache,
308 				const char *wc, const char *tag, char lookup)
309 {
310 	const char *wc_end = wc + strlen(wc), *tag_end = tag + strlen(tag),
311 			*start_wc, *start_tag;
312 	int n = 0;
313 
314 	lwsl_cache("%s: '%s' vs '%s'\n", __func__, wc, tag);
315 
316 	/*
317 	 * Given a well-formed host|path|name tag and a wildcard term,
318 	 * make the determination if the tag matches the wildcard or not,
319 	 * using lookup rules that apply at this cache level.
320 	 */
321 
322 	while (n < 3) {
323 		start_wc = wc;
324 		while (wc < wc_end && *wc != LWSCTAG_SEP)
325 			wc++;
326 
327 		start_tag = tag;
328 		while (tag < tag_end && *tag != LWSCTAG_SEP)
329 			tag++;
330 
331 		lwsl_cache("%s:   '%.*s' vs '%.*s'\n", __func__,
332 				lws_ptr_diff(wc, start_wc), start_wc,
333 				lws_ptr_diff(tag, start_tag), start_tag);
334 		if (nsc_match(start_wc, lws_ptr_diff_size_t(wc, start_wc),
335 			      start_tag, lws_ptr_diff_size_t(tag, start_tag),
336 			      lookup ? nsc_cols[n] : NSC_COL_HOST)) {
337 			lwsl_cache("%s: fail\n", __func__);
338 			return 1;
339 		}
340 
341 		if (wc < wc_end)
342 			wc++;
343 		if (tag < tag_end)
344 			tag++;
345 
346 		n++;
347 	}
348 
349 	lwsl_cache("%s: hit\n", __func__);
350 
351 	return 0; /* match */
352 }
353 
354 /*
355  * Converts the start of a cookie file line into a tag
356  */
357 
358 static int
nsc_line_to_tag(const char * buf,size_t size,char * tag,size_t max_tag,lws_usec_t * pexpiry)359 nsc_line_to_tag(const char *buf, size_t size, char *tag, size_t max_tag,
360 		lws_usec_t *pexpiry)
361 {
362 	int n, idx = 0, tl = 0;
363 	lws_usec_t expiry = 0;
364 	size_t bn = 0;
365 	char col[64];
366 
367 	if (size < 3)
368 		return 1;
369 
370 	while (bn < size && idx <= NSC_COL_NAME) {
371 
372 		n = 0;
373 		while (bn < size && n < (int)sizeof(col) - 1 &&
374 		       buf[bn] != '\t')
375 			col[n++] = buf[bn++];
376 		col[n] = '\0';
377 		if (buf[bn] == '\t')
378 			bn++;
379 
380 		switch (idx) {
381 		case NSC_COL_EXPIRY:
382 			expiry = (lws_usec_t)((unsigned long long)atoll(col) *
383 					(lws_usec_t)LWS_US_PER_SEC);
384 			break;
385 
386 		case NSC_COL_HOST:
387 		case NSC_COL_PATH:
388 		case NSC_COL_NAME:
389 
390 			/*
391 			 * As we match the pieces of the wildcard,
392 			 * compose the matches into a specific tag
393 			 */
394 
395 			if (tl + n + 2 > (int)max_tag)
396 				return 1;
397 			if (tl)
398 				tag[tl++] = LWSCTAG_SEP;
399 			memcpy(tag + tl, col, (size_t)n);
400 			tl += n;
401 			tag[tl] = '\0';
402 			break;
403 		default:
404 			break;
405 		}
406 
407 		idx++;
408 	}
409 
410 	if (pexpiry)
411 		*pexpiry = expiry;
412 
413 	lwsl_info("%s: %.*s: tag '%s'\n", __func__, (int)size, buf, tag);
414 
415 	return 0;
416 }
417 
418 struct nsc_lookup_ctx {
419 	const char		*wildcard_key;
420 	lws_dll2_owner_t	*results_owner;
421 	lws_cache_match_t	*match; /* current match if any */
422 	size_t			wklen;
423 };
424 
425 
426 static int
nsc_lookup_cb(lws_cache_nscookiejar_t * cache,void * opaque,int flags,const char * buf,size_t size)427 nsc_lookup_cb(lws_cache_nscookiejar_t *cache, void *opaque, int flags,
428 	      const char *buf, size_t size)
429 {
430 	struct nsc_lookup_ctx *ctx = (struct nsc_lookup_ctx *)opaque;
431 	lws_usec_t expiry;
432 	char tag[200];
433 	int tl;
434 
435 	if (!(flags & LCN_SOL)) {
436 		if (ctx->match)
437 			ctx->match->payload_size += size;
438 
439 		return NIR_CONTINUE;
440 	}
441 
442 	/*
443 	 * There should be enough in buf to match or reject it... let's
444 	 * synthesize a tag from the text "line" and then check the tags for
445 	 * a match
446 	 */
447 
448 	ctx->match = NULL; /* new SOL means stop tracking payload len */
449 
450 	if (nsc_line_to_tag(buf, size, tag, sizeof(tag), &expiry))
451 		return NIR_CONTINUE;
452 
453 	if (lws_cache_nscookiejar_tag_match(&cache->cache,
454 					    ctx->wildcard_key, tag, 1))
455 		return NIR_CONTINUE;
456 
457 	tl = (int)strlen(tag);
458 
459 	/*
460 	 * ... it looks like a match then... create new match
461 	 * object with the specific tag, and add it to the owner list
462 	 */
463 
464 	ctx->match = lws_fi(&cache->cache.info.cx->fic, "cache_lookup_oom") ? NULL :
465 			lws_malloc(sizeof(*ctx->match) + (unsigned int)tl + 1u,
466 				__func__);
467 	if (!ctx->match)
468 		/* caller of lookup will clean results list on fail */
469 		return NIR_FINISH_ERROR;
470 
471 	ctx->match->payload_size = size;
472 	ctx->match->tag_size = (size_t)tl;
473 	ctx->match->expiry = expiry;
474 
475 	memset(&ctx->match->list, 0, sizeof(ctx->match->list));
476 	memcpy(&ctx->match[1], tag, (size_t)tl + 1u);
477 	lws_dll2_add_tail(&ctx->match->list, ctx->results_owner);
478 
479 	return NIR_CONTINUE;
480 }
481 
482 static int
lws_cache_nscookiejar_lookup(struct lws_cache_ttl_lru * _c,const char * wildcard_key,lws_dll2_owner_t * results_owner)483 lws_cache_nscookiejar_lookup(struct lws_cache_ttl_lru *_c,
484 			     const char *wildcard_key,
485 			     lws_dll2_owner_t *results_owner)
486 {
487 	lws_cache_nscookiejar_t *cache = (lws_cache_nscookiejar_t *)_c;
488 	struct nsc_lookup_ctx ctx;
489 	int ret, fd;
490 
491 	fd = nsc_backing_open_lock(cache, LWS_O_RDONLY, __func__);
492 	if (fd < 0)
493 		return 1;
494 
495 	ctx.wildcard_key = wildcard_key;
496 	ctx.results_owner = results_owner;
497 	ctx.wklen = strlen(wildcard_key);
498 	ctx.match = 0;
499 
500 	ret = nscookiejar_iterate(cache, fd, nsc_lookup_cb, &ctx);
501 		/*
502 		 * The cb can fail, eg, with OOM, making the whole lookup
503 		 * invalid and returning fail.  Caller will clean
504 		 * results_owner on fail.
505 		 */
506 	nsc_backing_close_unlock(cache, fd);
507 
508 	return ret == NIR_FINISH_ERROR;
509 }
510 
511 /*
512  * It's pretty horrible having to implement add or remove individual items by
513  * file regeneration, but if we don't want to keep it all in heap, and we want
514  * this cookie jar format, that is what we are into.
515  *
516  * Allow to optionally add a "line", optionally wildcard delete tags, and always
517  * delete expired entries.
518  *
519  * Although we can rely on the lws thread to be doing this, multiple processes
520  * may be using the cookie jar and can tread on each other.  So we use flock()
521  * (linux only) to get exclusive access while we are processing this.
522  *
523  * We leave the existing file alone and generate a new one alongside it, with a
524  * fixed name.tmp format so it can't leak, if that went OK then we unlink the
525  * old and rename the new.
526  */
527 
528 struct nsc_regen_ctx {
529 	const char		*wildcard_key_delete;
530 	const void		*add_data;
531 	lws_usec_t		curr;
532 	size_t			add_size;
533 	int			fdt;
534 	char			drop;
535 };
536 
537 /* only used by nsc_regen() */
538 
539 static int
nsc_regen_cb(lws_cache_nscookiejar_t * cache,void * opaque,int flags,const char * buf,size_t size)540 nsc_regen_cb(lws_cache_nscookiejar_t *cache, void *opaque, int flags,
541 	      const char *buf, size_t size)
542 {
543 	struct nsc_regen_ctx *ctx = (struct nsc_regen_ctx *)opaque;
544 	char tag[256];
545 	lws_usec_t expiry;
546 
547 	if (flags & LCN_SOL) {
548 
549 		ctx->drop = 0;
550 
551 		if (nsc_line_to_tag(buf, size, tag, sizeof(tag), &expiry))
552 			/* filter it out if it is unparseable */
553 			goto drop;
554 
555 		/* routinely track the earliest expiry */
556 
557 		if (!cache->earliest_expiry ||
558 		    (expiry && cache->earliest_expiry > expiry))
559 			cache->earliest_expiry = expiry;
560 
561 		if (expiry && expiry < ctx->curr)
562 			/* routinely strip anything beyond its expiry */
563 			goto drop;
564 
565 		if (ctx->wildcard_key_delete)
566 			lwsl_cache("%s: %s vs %s\n", __func__,
567 					tag, ctx->wildcard_key_delete);
568 		if (ctx->wildcard_key_delete &&
569 		    !lws_cache_nscookiejar_tag_match(&cache->cache,
570 						     ctx->wildcard_key_delete,
571 						     tag, 0)) {
572 			lwsl_cache("%s: %s matches wc delete %s\n", __func__,
573 					tag, ctx->wildcard_key_delete);
574 			goto drop;
575 		}
576 	}
577 
578 	if (ctx->drop)
579 		return 0;
580 
581 	cache->cache.current_footprint += (uint64_t)size;
582 
583 	if (write(ctx->fdt, buf, /*msvc*/(unsigned int)size) != (ssize_t)size)
584 		return NIR_FINISH_ERROR;
585 
586 	if (flags & LCN_EOL)
587 		if ((size_t)write(ctx->fdt, "\n", 1) != 1)
588 			return NIR_FINISH_ERROR;
589 
590 	return 0;
591 
592 drop:
593 	ctx->drop = 1;
594 
595 	return NIR_CONTINUE;
596 }
597 
598 static int
nsc_regen(lws_cache_nscookiejar_t * cache,const char * wc_delete,const void * pay,size_t pay_size)599 nsc_regen(lws_cache_nscookiejar_t *cache, const char *wc_delete,
600 	  const void *pay, size_t pay_size)
601 {
602 	struct nsc_regen_ctx ctx;
603 	char filepath[128];
604 	int fd, ret = 1;
605 
606 	fd = nsc_backing_open_lock(cache, LWS_O_RDONLY, __func__);
607 	if (fd < 0)
608 		return 1;
609 
610 	lws_snprintf(filepath, sizeof(filepath), "%s.tmp",
611 			cache->cache.info.u.nscookiejar.filepath);
612 	unlink(filepath);
613 
614 	if (lws_fi(&cache->cache.info.cx->fic, "cache_regen_temp_open"))
615 		goto bail;
616 
617 	ctx.fdt = open(filepath, LWS_O_CREAT | LWS_O_WRONLY, 0600);
618 	if (ctx.fdt < 0)
619 		goto bail;
620 
621 	/* magic header */
622 
623 	if (lws_fi(&cache->cache.info.cx->fic, "cache_regen_temp_write") ||
624 	/* other consumers insist to see this at start of cookie jar */
625 	    write(ctx.fdt, "# Netscape HTTP Cookie File\n", 28) != 28)
626 		goto bail1;
627 
628 	/* if we are adding something, put it first */
629 
630 	if (pay &&
631 	    write(ctx.fdt, pay, /*msvc*/(unsigned int)pay_size) !=
632 						    (ssize_t)pay_size)
633 		goto bail1;
634 	if (pay && write(ctx.fdt, "\n", 1u) != (ssize_t)1)
635 		goto bail1;
636 
637 	cache->cache.current_footprint = 0;
638 
639 	ctx.wildcard_key_delete = wc_delete;
640 	ctx.add_data = pay;
641 	ctx.add_size = pay_size;
642 	ctx.curr = lws_now_usecs();
643 	ctx.drop = 0;
644 
645 	cache->earliest_expiry = 0;
646 
647 	if (lws_fi(&cache->cache.info.cx->fic, "cache_regen_iter_fail") ||
648 	    nscookiejar_iterate(cache, fd, nsc_regen_cb, &ctx))
649 		goto bail1;
650 
651 	close(ctx.fdt);
652 	ctx.fdt = -1;
653 
654 	if (unlink(cache->cache.info.u.nscookiejar.filepath) == -1)
655 		lwsl_info("%s: unlink %s failed\n", __func__,
656 			  cache->cache.info.u.nscookiejar.filepath);
657 	if (rename(filepath, cache->cache.info.u.nscookiejar.filepath) == -1)
658 		lwsl_info("%s: rename %s failed\n", __func__,
659 			  cache->cache.info.u.nscookiejar.filepath);
660 
661 	if (cache->earliest_expiry)
662 		lws_cache_schedule(&cache->cache, expiry_cb,
663 				   cache->earliest_expiry);
664 
665 	ret = 0;
666 	goto bail;
667 
668 bail1:
669 	if (ctx.fdt >= 0)
670 		close(ctx.fdt);
671 bail:
672 	unlink(filepath);
673 
674 	nsc_backing_close_unlock(cache, fd);
675 
676 	return ret;
677 }
678 
679 static void
expiry_cb(lws_sorted_usec_list_t * sul)680 expiry_cb(lws_sorted_usec_list_t *sul)
681 {
682 	lws_cache_nscookiejar_t *cache = lws_container_of(sul,
683 					lws_cache_nscookiejar_t, cache.sul);
684 
685 	/*
686 	 * regen the cookie jar without changes, so expired are removed and
687 	 * new earliest expired computed
688 	 */
689 	if (nsc_regen(cache, NULL, NULL, 0))
690 		return;
691 
692 	if (cache->earliest_expiry)
693 		lws_cache_schedule(&cache->cache, expiry_cb,
694 				   cache->earliest_expiry);
695 }
696 
697 
698 /* specific_key and expiry are ignored, since it must be encoded in payload */
699 
700 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)701 lws_cache_nscookiejar_write(struct lws_cache_ttl_lru *_c,
702 			    const char *specific_key, const uint8_t *source,
703 			    size_t size, lws_usec_t expiry, void **ppvoid)
704 {
705 	lws_cache_nscookiejar_t *cache = (lws_cache_nscookiejar_t *)_c;
706 	char tag[128];
707 
708 	lwsl_cache("%s: %s: len %d\n", __func__, _c->info.name, (int)size);
709 
710 	assert(source);
711 
712 	if (nsc_line_to_tag((const char *)source, size, tag, sizeof(tag), NULL))
713 		return 1;
714 
715 	if (ppvoid)
716 		*ppvoid = NULL;
717 
718 	if (nsc_regen(cache, tag, source, size)) {
719 		lwsl_err("%s: regen failed\n", __func__);
720 
721 		return 1;
722 	}
723 
724 	return 0;
725 }
726 
727 struct nsc_get_ctx {
728 	struct lws_buflist	*buflist;
729 	const char		*specific_key;
730 	const void		**pdata;
731 	size_t			*psize;
732 	lws_cache_ttl_lru_t	*l1;
733 	lws_usec_t		expiry;
734 };
735 
736 /*
737  * We're looking for a specific key, if found, we want to make an entry for it
738  * in L1 and return information about that
739  */
740 
741 static int
nsc_get_cb(lws_cache_nscookiejar_t * cache,void * opaque,int flags,const char * buf,size_t size)742 nsc_get_cb(lws_cache_nscookiejar_t *cache, void *opaque, int flags,
743 	   const char *buf, size_t size)
744 {
745 	struct nsc_get_ctx *ctx = (struct nsc_get_ctx *)opaque;
746 	char tag[200];
747 	uint8_t *q;
748 
749 	if (ctx->buflist)
750 		goto collect;
751 
752 	if (!(flags & LCN_SOL))
753 		return NIR_CONTINUE;
754 
755 	if (nsc_line_to_tag(buf, size, tag, sizeof(tag), &ctx->expiry)) {
756 		lwsl_err("%s: can't get tag\n", __func__);
757 		return NIR_CONTINUE;
758 	}
759 
760 	lwsl_cache("%s: %s %s\n", __func__, ctx->specific_key, tag);
761 
762 	if (strcmp(ctx->specific_key, tag)) {
763 		lwsl_cache("%s: no match\n", __func__);
764 		return NIR_CONTINUE;
765 	}
766 
767 	/* it's a match */
768 
769 	lwsl_cache("%s: IS match\n", __func__);
770 
771 	if (!(flags & LCN_EOL))
772 		goto collect;
773 
774 	/* it all fit in the buffer, let's create it in L1 now */
775 
776 	*ctx->psize = size;
777 	if (ctx->l1->info.ops->write(ctx->l1,
778 				     ctx->specific_key, (const uint8_t *)buf,
779 				     size, ctx->expiry, (void **)ctx->pdata))
780 		return NIR_FINISH_ERROR;
781 
782 	return NIR_FINISH_OK;
783 
784 collect:
785 	/*
786 	 * it's bigger than one buffer-load, we have to stash what we're getting
787 	 * on a buflist and create it when we have it all
788 	 */
789 
790 	if (lws_buflist_append_segment(&ctx->buflist, (const uint8_t *)buf,
791 				       size))
792 		goto cleanup;
793 
794 	if (!(flags & LCN_EOL))
795 		return NIR_CONTINUE;
796 
797 	/* we have all the payload, create the L1 entry without payload yet */
798 
799 	*ctx->psize = size;
800 	if (ctx->l1->info.ops->write(ctx->l1, ctx->specific_key, NULL,
801 				     lws_buflist_total_len(&ctx->buflist),
802 				     ctx->expiry, (void **)&q))
803 		goto cleanup;
804 	*ctx->pdata = q;
805 
806 	/* dump the buflist into the L1 cache entry */
807 
808 	do {
809 		uint8_t *p;
810 		size_t len = lws_buflist_next_segment_len(&ctx->buflist, &p);
811 
812 		memcpy(q, p, len);
813 		q += len;
814 
815 		lws_buflist_use_segment(&ctx->buflist, len);
816 	} while (ctx->buflist);
817 
818 	return NIR_FINISH_OK;
819 
820 cleanup:
821 	lws_buflist_destroy_all_segments(&ctx->buflist);
822 
823 	return NIR_FINISH_ERROR;
824 }
825 
826 static int
lws_cache_nscookiejar_get(struct lws_cache_ttl_lru * _c,const char * specific_key,const void ** pdata,size_t * psize)827 lws_cache_nscookiejar_get(struct lws_cache_ttl_lru *_c,
828 			  const char *specific_key, const void **pdata,
829 			  size_t *psize)
830 {
831 	lws_cache_nscookiejar_t *cache = (lws_cache_nscookiejar_t *)_c;
832 	struct nsc_get_ctx ctx;
833 	int ret, fd;
834 
835 	fd = nsc_backing_open_lock(cache, LWS_O_RDONLY, __func__);
836 	if (fd < 0)
837 		return 1;
838 
839 	/* get a pointer to l1 */
840 	ctx.l1 = &cache->cache;
841 	while (ctx.l1->child)
842 		ctx.l1 = ctx.l1->child;
843 
844 	ctx.pdata = pdata;
845 	ctx.psize = psize;
846 	ctx.specific_key = specific_key;
847 	ctx.buflist = NULL;
848 	ctx.expiry = 0;
849 
850 	ret = nscookiejar_iterate(cache, fd, nsc_get_cb, &ctx);
851 
852 	nsc_backing_close_unlock(cache, fd);
853 
854 	return ret != NIR_FINISH_OK;
855 }
856 
857 static int
lws_cache_nscookiejar_invalidate(struct lws_cache_ttl_lru * _c,const char * wc_key)858 lws_cache_nscookiejar_invalidate(struct lws_cache_ttl_lru *_c,
859 				 const char *wc_key)
860 {
861 	lws_cache_nscookiejar_t *cache = (lws_cache_nscookiejar_t *)_c;
862 
863 	return nsc_regen(cache, wc_key, NULL, 0);
864 }
865 
866 static struct lws_cache_ttl_lru *
lws_cache_nscookiejar_create(const struct lws_cache_creation_info * info)867 lws_cache_nscookiejar_create(const struct lws_cache_creation_info *info)
868 {
869 	lws_cache_nscookiejar_t *cache;
870 
871 	cache = lws_fi(&info->cx->fic, "cache_createfail") ? NULL :
872 					lws_zalloc(sizeof(*cache), __func__);
873 	if (!cache)
874 		return NULL;
875 
876 	cache->cache.info = *info;
877 
878 	/*
879 	 * We need to scan the file, if it exists, and find the earliest
880 	 * expiry while cleaning out any expired entries
881 	 */
882 	expiry_cb(&cache->cache.sul);
883 
884 	lwsl_notice("%s: create %s\n", __func__, info->name ? info->name : "?");
885 
886 	return (struct lws_cache_ttl_lru *)cache;
887 }
888 
889 static int
lws_cache_nscookiejar_expunge(struct lws_cache_ttl_lru * _c)890 lws_cache_nscookiejar_expunge(struct lws_cache_ttl_lru *_c)
891 {
892 	lws_cache_nscookiejar_t *cache = (lws_cache_nscookiejar_t *)_c;
893 	int r;
894 
895 	if (!cache)
896 		return 0;
897 
898 	r = unlink(cache->cache.info.u.nscookiejar.filepath);
899 	if (r)
900 		lwsl_warn("%s: failed to unlink %s\n", __func__,
901 				cache->cache.info.u.nscookiejar.filepath);
902 
903 	return r;
904 }
905 
906 static void
lws_cache_nscookiejar_destroy(struct lws_cache_ttl_lru ** _pc)907 lws_cache_nscookiejar_destroy(struct lws_cache_ttl_lru **_pc)
908 {
909 	lws_cache_nscookiejar_t *cache = (lws_cache_nscookiejar_t *)*_pc;
910 
911 	if (!cache)
912 		return;
913 
914 	lws_sul_cancel(&cache->cache.sul);
915 
916 	lws_free_set_NULL(*_pc);
917 }
918 
919 #if defined(_DEBUG)
920 
921 static int
nsc_dump_cb(lws_cache_nscookiejar_t * cache,void * opaque,int flags,const char * buf,size_t size)922 nsc_dump_cb(lws_cache_nscookiejar_t *cache, void *opaque, int flags,
923 	      const char *buf, size_t size)
924 {
925 	lwsl_hexdump_cache(buf, size);
926 
927 	return 0;
928 }
929 
930 static void
lws_cache_nscookiejar_debug_dump(struct lws_cache_ttl_lru * _c)931 lws_cache_nscookiejar_debug_dump(struct lws_cache_ttl_lru *_c)
932 {
933 	lws_cache_nscookiejar_t *cache = (lws_cache_nscookiejar_t *)_c;
934 	int fd = nsc_backing_open_lock(cache, LWS_O_RDONLY, __func__);
935 
936 	if (fd < 0)
937 		return;
938 
939 	lwsl_cache("%s: %s\n", __func__, _c->info.name);
940 
941 	nscookiejar_iterate(cache, fd, nsc_dump_cb, NULL);
942 
943 	nsc_backing_close_unlock(cache, fd);
944 }
945 #endif
946 
947 const struct lws_cache_ops lws_cache_ops_nscookiejar = {
948 	.create			= lws_cache_nscookiejar_create,
949 	.destroy		= lws_cache_nscookiejar_destroy,
950 	.expunge		= lws_cache_nscookiejar_expunge,
951 
952 	.write			= lws_cache_nscookiejar_write,
953 	.tag_match		= lws_cache_nscookiejar_tag_match,
954 	.lookup			= lws_cache_nscookiejar_lookup,
955 	.invalidate		= lws_cache_nscookiejar_invalidate,
956 	.get			= lws_cache_nscookiejar_get,
957 #if defined(_DEBUG)
958 	.debug_dump		= lws_cache_nscookiejar_debug_dump,
959 #endif
960 };
961