| | varnish-cache/vmod/vmod_cookie.c |
0 |
|
/*- |
1 |
|
* Copyright (c) 2012-2020 Varnish Software AS |
2 |
|
* |
3 |
|
* Author: Lasse Karstensen <lasse.karstensen@gmail.com> |
4 |
|
* Author: Lasse Karstensen <lkarsten@varnish-software.com> |
5 |
|
* Author: Dridi Boukelmoune <dridi.boukelmoune@gmail.com> |
6 |
|
* |
7 |
|
* SPDX-License-Identifier: BSD-2-Clause |
8 |
|
* |
9 |
|
* Redistribution and use in source and binary forms, with or without |
10 |
|
* modification, are permitted provided that the following conditions |
11 |
|
* are met: |
12 |
|
* 1. Redistributions of source code must retain the above copyright |
13 |
|
* notice, this list of conditions and the following disclaimer. |
14 |
|
* 2. Redistributions in binary form must reproduce the above copyright |
15 |
|
* notice, this list of conditions and the following disclaimer in the |
16 |
|
* documentation and/or other materials provided with the distribution. |
17 |
|
* |
18 |
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
19 |
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
20 |
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
21 |
|
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE |
22 |
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
23 |
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
24 |
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
25 |
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
26 |
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
27 |
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
28 |
|
* SUCH DAMAGE. |
29 |
|
* |
30 |
|
* Cookie VMOD that simplifies handling of the Cookie request header. |
31 |
|
*/ |
32 |
|
|
33 |
|
#include "config.h" |
34 |
|
|
35 |
|
#include <stdlib.h> |
36 |
|
#include <stdio.h> |
37 |
|
#include <string.h> |
38 |
|
#include <ctype.h> |
39 |
|
#include <pthread.h> |
40 |
|
|
41 |
|
#include <cache/cache.h> |
42 |
|
|
43 |
|
#include <vsb.h> |
44 |
|
|
45 |
|
#include "vcc_cookie_if.h" |
46 |
|
|
47 |
|
enum filter_action { |
48 |
|
blacklist, |
49 |
|
whitelist |
50 |
|
}; |
51 |
|
|
52 |
|
struct cookie { |
53 |
|
unsigned magic; |
54 |
|
#define VMOD_COOKIE_ENTRY_MAGIC 0x3BB41543 |
55 |
|
const char *name; |
56 |
|
const char *value; |
57 |
|
VTAILQ_ENTRY(cookie) list; |
58 |
|
}; |
59 |
|
|
60 |
|
/* A structure to represent both whitelists and blacklists */ |
61 |
|
struct matchlist { |
62 |
|
char *name; |
63 |
|
VTAILQ_ENTRY(matchlist) list; |
64 |
|
}; |
65 |
|
|
66 |
|
struct vmod_cookie { |
67 |
|
unsigned magic; |
68 |
|
#define VMOD_COOKIE_MAGIC 0x4EE5FB2E |
69 |
|
VTAILQ_HEAD(, cookie) cookielist; |
70 |
|
}; |
71 |
|
|
72 |
|
static void |
73 |
2480 |
cobj_free(VRT_CTX, void *p) |
74 |
|
{ |
75 |
|
struct vmod_cookie *vcp; |
76 |
|
|
77 |
2480 |
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); |
78 |
2480 |
CAST_OBJ_NOTNULL(vcp, p, VMOD_COOKIE_MAGIC); |
79 |
2480 |
FREE_OBJ(vcp); |
80 |
2480 |
} |
81 |
|
|
82 |
|
static const struct vmod_priv_methods cookie_cobj_priv_methods[1] = {{ |
83 |
|
.magic = VMOD_PRIV_METHODS_MAGIC, |
84 |
|
.type = "vmod_cookie_cobj", |
85 |
|
.fini = cobj_free |
86 |
|
}}; |
87 |
|
|
88 |
|
static struct vmod_cookie * |
89 |
11640 |
cobj_get(struct vmod_priv *priv) |
90 |
|
{ |
91 |
|
struct vmod_cookie *vcp; |
92 |
|
|
93 |
11640 |
if (priv->priv == NULL) { |
94 |
2480 |
ALLOC_OBJ(vcp, VMOD_COOKIE_MAGIC); |
95 |
2480 |
AN(vcp); |
96 |
2480 |
VTAILQ_INIT(&vcp->cookielist); |
97 |
2480 |
priv->priv = vcp; |
98 |
2480 |
priv->methods = cookie_cobj_priv_methods; |
99 |
2480 |
} else |
100 |
9160 |
CAST_OBJ_NOTNULL(vcp, priv->priv, VMOD_COOKIE_MAGIC); |
101 |
|
|
102 |
11640 |
return (vcp); |
103 |
|
} |
104 |
|
|
105 |
|
VCL_VOID |
106 |
2240 |
vmod_parse(VRT_CTX, struct vmod_priv *priv, VCL_STRING cookieheader) |
107 |
|
{ |
108 |
2240 |
struct vmod_cookie *vcp = cobj_get(priv); |
109 |
|
char *name, *value; |
110 |
|
const char *p, *sep; |
111 |
2240 |
int i = 0; |
112 |
|
|
113 |
2240 |
if (cookieheader == NULL || *cookieheader == '\0') { |
114 |
80 |
VSLb(ctx->vsl, SLT_Debug, "cookie: nothing to parse"); |
115 |
80 |
return; |
116 |
|
} |
117 |
|
|
118 |
|
/* If called twice during the same request, clean out old state. */ |
119 |
2160 |
if (!VTAILQ_EMPTY(&vcp->cookielist)) |
120 |
120 |
vmod_clean(ctx, priv); |
121 |
|
|
122 |
2160 |
p = cookieheader; |
123 |
4240 |
while (*p != '\0') { |
124 |
6200 |
while (isspace(*p)) |
125 |
2000 |
p++; |
126 |
4200 |
sep = strchr(p, '='); |
127 |
4200 |
if (sep == NULL) |
128 |
0 |
break; |
129 |
4200 |
name = strndup(p, pdiff(p, sep)); |
130 |
4200 |
p = sep + 1; |
131 |
|
|
132 |
4200 |
sep = p; |
133 |
368520 |
while (*sep != '\0' && *sep != ';') |
134 |
364320 |
sep++; |
135 |
4200 |
value = strndup(p, pdiff(p, sep)); |
136 |
|
|
137 |
4200 |
vmod_set(ctx, priv, name, value); |
138 |
4200 |
free(name); |
139 |
4200 |
free(value); |
140 |
4200 |
i++; |
141 |
4200 |
if (*sep == '\0') |
142 |
2120 |
break; |
143 |
2080 |
p = sep + 1; |
144 |
|
} |
145 |
|
|
146 |
2160 |
VSLb(ctx->vsl, SLT_Debug, "cookie: parsed %i cookies.", i); |
147 |
2240 |
} |
148 |
|
|
149 |
|
static struct cookie * |
150 |
5600 |
find_cookie(const struct vmod_cookie *vcp, VCL_STRING name) |
151 |
|
{ |
152 |
|
struct cookie *cookie; |
153 |
|
|
154 |
9120 |
VTAILQ_FOREACH(cookie, &vcp->cookielist, list) { |
155 |
3800 |
CHECK_OBJ_NOTNULL(cookie, VMOD_COOKIE_ENTRY_MAGIC); |
156 |
3800 |
if (!strcmp(cookie->name, name)) |
157 |
280 |
break; |
158 |
3520 |
} |
159 |
5600 |
return (cookie); |
160 |
|
} |
161 |
|
|
162 |
|
VCL_VOID |
163 |
5360 |
vmod_set(VRT_CTX, struct vmod_priv *priv, VCL_STRING name, VCL_STRING value) |
164 |
|
{ |
165 |
5360 |
struct vmod_cookie *vcp = cobj_get(priv); |
166 |
|
struct cookie *cookie; |
167 |
|
const char *p; |
168 |
|
|
169 |
5360 |
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); |
170 |
5360 |
CHECK_OBJ_NOTNULL(ctx->ws, WS_MAGIC); |
171 |
|
|
172 |
|
/* Empty cookies should be ignored. */ |
173 |
5360 |
if (name == NULL || *name == '\0') |
174 |
120 |
return; |
175 |
5240 |
if (value == NULL || *value == '\0') |
176 |
80 |
return; |
177 |
|
|
178 |
5160 |
cookie = find_cookie(vcp, name); |
179 |
5160 |
if (cookie != NULL) { |
180 |
40 |
p = WS_Printf(ctx->ws, "%s", value); |
181 |
40 |
if (p == NULL) { |
182 |
0 |
VSLb(ctx->vsl, SLT_Error, |
183 |
|
"cookie: Workspace overflow in set()"); |
184 |
0 |
} else |
185 |
40 |
cookie->value = p; |
186 |
40 |
return; |
187 |
|
} |
188 |
|
|
189 |
10240 |
WS_TASK_ALLOC_OBJ(ctx, cookie, VMOD_COOKIE_ENTRY_MAGIC); |
190 |
5120 |
if (cookie == NULL) |
191 |
0 |
return; |
192 |
|
|
193 |
5120 |
cookie->name = WS_Printf(ctx->ws, "%s", name); |
194 |
5120 |
cookie->value = WS_Printf(ctx->ws, "%s", value); |
195 |
5120 |
if (cookie->name == NULL || cookie->value == NULL) { |
196 |
0 |
VSLb(ctx->vsl, SLT_Error, |
197 |
|
"cookie: unable to get storage for cookie"); |
198 |
0 |
return; |
199 |
|
} |
200 |
5120 |
VTAILQ_INSERT_TAIL(&vcp->cookielist, cookie, list); |
201 |
5360 |
} |
202 |
|
|
203 |
|
VCL_BOOL |
204 |
240 |
vmod_isset(VRT_CTX, struct vmod_priv *priv, const char *name) |
205 |
|
{ |
206 |
240 |
struct vmod_cookie *vcp = cobj_get(priv); |
207 |
|
struct cookie *cookie; |
208 |
|
|
209 |
240 |
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); |
210 |
240 |
if (name == NULL || *name == '\0') |
211 |
80 |
return (0); |
212 |
|
|
213 |
160 |
cookie = find_cookie(vcp, name); |
214 |
160 |
return (cookie ? 1 : 0); |
215 |
240 |
} |
216 |
|
|
217 |
|
VCL_STRING |
218 |
160 |
vmod_get(VRT_CTX, struct vmod_priv *priv, VCL_STRING name) |
219 |
|
{ |
220 |
160 |
struct vmod_cookie *vcp = cobj_get(priv); |
221 |
|
struct cookie *cookie; |
222 |
|
|
223 |
160 |
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); |
224 |
160 |
if (name == NULL || *name == '\0') |
225 |
0 |
return (NULL); |
226 |
|
|
227 |
160 |
cookie = find_cookie(vcp, name); |
228 |
160 |
return (cookie ? cookie->value : NULL); |
229 |
160 |
} |
230 |
|
|
231 |
|
|
232 |
|
VCL_STRING |
233 |
160 |
vmod_get_re(VRT_CTX, struct vmod_priv *priv, VCL_REGEX re) |
234 |
|
{ |
235 |
160 |
struct vmod_cookie *vcp = cobj_get(priv); |
236 |
160 |
struct cookie *cookie = NULL; |
237 |
|
struct cookie *current; |
238 |
|
unsigned match; |
239 |
|
|
240 |
160 |
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); |
241 |
160 |
AN(re); |
242 |
|
|
243 |
400 |
VTAILQ_FOREACH(current, &vcp->cookielist, list) { |
244 |
320 |
CHECK_OBJ_NOTNULL(current, VMOD_COOKIE_ENTRY_MAGIC); |
245 |
320 |
VSLb(ctx->vsl, SLT_Debug, "cookie: checking %s", current->name); |
246 |
320 |
match = VRT_re_match(ctx, current->name, re); |
247 |
320 |
if (!match) |
248 |
240 |
continue; |
249 |
80 |
cookie = current; |
250 |
80 |
break; |
251 |
|
} |
252 |
|
|
253 |
160 |
return (cookie ? cookie->value : NULL); |
254 |
|
} |
255 |
|
|
256 |
|
VCL_VOID |
257 |
160 |
vmod_delete(VRT_CTX, struct vmod_priv *priv, VCL_STRING name) |
258 |
|
{ |
259 |
160 |
struct vmod_cookie *vcp = cobj_get(priv); |
260 |
|
struct cookie *cookie; |
261 |
|
|
262 |
160 |
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); |
263 |
160 |
if (name == NULL || *name == '\0') |
264 |
40 |
return; |
265 |
|
|
266 |
120 |
cookie = find_cookie(vcp, name); |
267 |
|
|
268 |
120 |
if (cookie != NULL) |
269 |
40 |
VTAILQ_REMOVE(&vcp->cookielist, cookie, list); |
270 |
160 |
} |
271 |
|
|
272 |
|
VCL_VOID |
273 |
200 |
vmod_clean(VRT_CTX, struct vmod_priv *priv) |
274 |
|
{ |
275 |
200 |
struct vmod_cookie *vcp = cobj_get(priv); |
276 |
|
|
277 |
200 |
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); |
278 |
200 |
AN(vcp); |
279 |
200 |
VTAILQ_INIT(&vcp->cookielist); |
280 |
200 |
} |
281 |
|
|
282 |
|
static void |
283 |
240 |
filter_cookies(struct vmod_priv *priv, VCL_STRING list_s, |
284 |
|
enum filter_action mode) |
285 |
|
{ |
286 |
|
struct cookie *cookieptr, *safeptr; |
287 |
240 |
struct vmod_cookie *vcp = cobj_get(priv); |
288 |
|
struct matchlist *mlentry, *mlsafe; |
289 |
240 |
char const *p = list_s, *q; |
290 |
240 |
int matched = 0; |
291 |
|
VTAILQ_HEAD(, matchlist) matchlist_head; |
292 |
|
|
293 |
240 |
VTAILQ_INIT(&matchlist_head); |
294 |
|
|
295 |
|
/* Parse the supplied list. */ |
296 |
1080 |
while (p && *p != '\0') { |
297 |
1160 |
while (isspace(*p)) |
298 |
320 |
p++; |
299 |
840 |
if (*p == '\0') |
300 |
0 |
break; |
301 |
|
|
302 |
840 |
q = p; |
303 |
3160 |
while (*q != '\0' && *q != ',') |
304 |
2320 |
q++; |
305 |
|
|
306 |
840 |
if (q == p) { |
307 |
400 |
p++; |
308 |
400 |
continue; |
309 |
|
} |
310 |
|
|
311 |
|
/* XXX: can we reserve/release lumps of txt instead of |
312 |
|
* malloc/free? |
313 |
|
*/ |
314 |
440 |
mlentry = malloc(sizeof *mlentry); |
315 |
440 |
AN(mlentry); |
316 |
440 |
mlentry->name = strndup(p, q - p); |
317 |
440 |
AN(mlentry->name); |
318 |
|
|
319 |
440 |
VTAILQ_INSERT_TAIL(&matchlist_head, mlentry, list); |
320 |
|
|
321 |
440 |
p = q; |
322 |
440 |
if (*p != '\0') |
323 |
360 |
p++; |
324 |
|
} |
325 |
|
|
326 |
|
/* Filter existing cookies that either aren't in the whitelist or |
327 |
|
* are in the blacklist (depending on the filter_action) */ |
328 |
880 |
VTAILQ_FOREACH_SAFE(cookieptr, &vcp->cookielist, list, safeptr) { |
329 |
640 |
CHECK_OBJ_NOTNULL(cookieptr, VMOD_COOKIE_ENTRY_MAGIC); |
330 |
640 |
matched = 0; |
331 |
|
|
332 |
1320 |
VTAILQ_FOREACH(mlentry, &matchlist_head, list) { |
333 |
960 |
if (strcmp(cookieptr->name, mlentry->name) == 0) { |
334 |
280 |
matched = 1; |
335 |
280 |
break; |
336 |
|
} |
337 |
680 |
} |
338 |
640 |
if (matched != mode) |
339 |
280 |
VTAILQ_REMOVE(&vcp->cookielist, cookieptr, list); |
340 |
640 |
} |
341 |
|
|
342 |
680 |
VTAILQ_FOREACH_SAFE(mlentry, &matchlist_head, list, mlsafe) { |
343 |
440 |
VTAILQ_REMOVE(&matchlist_head, mlentry, list); |
344 |
440 |
free(mlentry->name); |
345 |
440 |
free(mlentry); |
346 |
440 |
} |
347 |
240 |
} |
348 |
|
|
349 |
|
VCL_VOID |
350 |
120 |
vmod_keep(VRT_CTX, struct vmod_priv *priv, VCL_STRING whitelist_s) |
351 |
|
{ |
352 |
|
|
353 |
120 |
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); |
354 |
120 |
filter_cookies(priv, whitelist_s, whitelist); |
355 |
120 |
} |
356 |
|
|
357 |
|
|
358 |
|
VCL_VOID |
359 |
120 |
vmod_filter(VRT_CTX, struct vmod_priv *priv, VCL_STRING blacklist_s) |
360 |
|
{ |
361 |
|
|
362 |
120 |
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); |
363 |
120 |
filter_cookies(priv, blacklist_s, blacklist); |
364 |
120 |
} |
365 |
|
|
366 |
|
static VCL_VOID |
367 |
280 |
re_filter(VRT_CTX, struct vmod_priv *priv, VCL_REGEX re, enum filter_action mode) |
368 |
|
{ |
369 |
280 |
struct vmod_cookie *vcp = cobj_get(priv); |
370 |
|
struct cookie *current, *safeptr; |
371 |
|
unsigned match; |
372 |
|
|
373 |
280 |
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); |
374 |
280 |
AN(re); |
375 |
|
|
376 |
1040 |
VTAILQ_FOREACH_SAFE(current, &vcp->cookielist, list, safeptr) { |
377 |
760 |
CHECK_OBJ_NOTNULL(current, VMOD_COOKIE_ENTRY_MAGIC); |
378 |
|
|
379 |
760 |
match = VRT_re_match(ctx, current->name, re); |
380 |
|
|
381 |
760 |
switch (mode) { |
382 |
|
case blacklist: |
383 |
440 |
if (!match) |
384 |
320 |
continue; |
385 |
240 |
VSLb(ctx->vsl, SLT_Debug, |
386 |
|
"Removing matching cookie %s (value: %s)", |
387 |
120 |
current->name, current->value); |
388 |
120 |
VTAILQ_REMOVE(&vcp->cookielist, current, list); |
389 |
120 |
break; |
390 |
|
case whitelist: |
391 |
320 |
if (match) |
392 |
200 |
continue; |
393 |
|
|
394 |
240 |
VSLb(ctx->vsl, SLT_Debug, |
395 |
|
"Removing cookie %s (value: %s)", |
396 |
120 |
current->name, current->value); |
397 |
120 |
VTAILQ_REMOVE(&vcp->cookielist, current, list); |
398 |
120 |
break; |
399 |
|
default: |
400 |
0 |
WRONG("invalid mode"); |
401 |
0 |
} |
402 |
240 |
} |
403 |
280 |
} |
404 |
|
|
405 |
|
|
406 |
|
VCL_VOID |
407 |
120 |
vmod_keep_re(VRT_CTX, struct vmod_priv *priv, VCL_REGEX re) |
408 |
|
{ |
409 |
|
|
410 |
120 |
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); |
411 |
120 |
re_filter(ctx, priv, re, whitelist); |
412 |
120 |
} |
413 |
|
|
414 |
|
|
415 |
|
VCL_VOID |
416 |
160 |
vmod_filter_re(VRT_CTX, struct vmod_priv *priv, VCL_REGEX re) |
417 |
|
{ |
418 |
|
|
419 |
160 |
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); |
420 |
160 |
re_filter(ctx, priv, re, blacklist); |
421 |
160 |
} |
422 |
|
|
423 |
|
|
424 |
|
VCL_STRING |
425 |
2600 |
vmod_get_string(VRT_CTX, struct vmod_priv *priv) |
426 |
|
{ |
427 |
|
struct cookie *curr; |
428 |
|
struct vsb output[1]; |
429 |
2600 |
struct vmod_cookie *vcp = cobj_get(priv); |
430 |
2600 |
const char *sep = "", *res; |
431 |
|
|
432 |
2600 |
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); |
433 |
2600 |
CHECK_OBJ_NOTNULL(ctx->ws, WS_MAGIC); |
434 |
2600 |
WS_VSB_new(output, ctx->ws); |
435 |
|
|
436 |
7120 |
VTAILQ_FOREACH(curr, &vcp->cookielist, list) { |
437 |
4520 |
CHECK_OBJ_NOTNULL(curr, VMOD_COOKIE_ENTRY_MAGIC); |
438 |
4520 |
AN(curr->name); |
439 |
4520 |
AN(curr->value); |
440 |
4520 |
VSB_printf(output, "%s%s=%s", sep, curr->name, curr->value); |
441 |
4520 |
sep = "; "; |
442 |
4520 |
} |
443 |
2600 |
res = WS_VSB_finish(output, ctx->ws, NULL); |
444 |
2600 |
if (res == NULL) |
445 |
0 |
VSLb(ctx->vsl, SLT_Error, "cookie: Workspace overflow"); |
446 |
2600 |
return (res); |
447 |
|
} |
448 |
|
|
449 |
|
VCL_STRING |
450 |
40 |
vmod_format_date(VRT_CTX, VCL_TIME ts, VCL_DURATION duration) |
451 |
|
{ |
452 |
|
|
453 |
40 |
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); |
454 |
40 |
return (VRT_TIME_string(ctx, ts + duration)); |
455 |
|
} |