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