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
}