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 620
cobj_free(VRT_CTX, void *p)
75
{
76
        struct vmod_cookie *vcp;
77
78 620
        CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
79 620
        CAST_OBJ_NOTNULL(vcp, p, VMOD_COOKIE_MAGIC);
80 620
        FREE_OBJ(vcp);
81 620
}
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 2910
cobj_get(struct vmod_priv *priv)
91
{
92
        struct vmod_cookie *vcp;
93
94 2910
        if (priv->priv == NULL) {
95 620
                ALLOC_OBJ(vcp, VMOD_COOKIE_MAGIC);
96 620
                AN(vcp);
97 620
                VTAILQ_INIT(&vcp->cookielist);
98 620
                priv->priv = vcp;
99 620
                priv->methods = cookie_cobj_priv_methods;
100 620
        } else
101 2290
                CAST_OBJ_NOTNULL(vcp, priv->priv, VMOD_COOKIE_MAGIC);
102
103 2910
        return (vcp);
104
}
105
106
VCL_VOID
107 560
vmod_parse(VRT_CTX, struct vmod_priv *priv, VCL_STRING cookieheader)
108
{
109 560
        struct vmod_cookie *vcp = cobj_get(priv);
110
        char *name, *value;
111
        const char *p, *sep;
112 560
        int i = 0;
113
114 560
        if (cookieheader == NULL || *cookieheader == '\0') {
115 20
                VSLb(ctx->vsl, SLT_Debug, "cookie: nothing to parse");
116 20
                return;
117
        }
118
119
        /* If called twice during the same request, clean out old state. */
120 540
        if (!VTAILQ_EMPTY(&vcp->cookielist))
121 30
                vmod_clean(ctx, priv);
122
123 540
        p = cookieheader;
124 1110
        while (*p != '\0') {
125 1550
                while (isspace(*p))
126 500
                        p++;
127 1050
                sep = strchr(p, '=');
128 1050
                if (sep == NULL)
129 0
                        break;
130 1050
                name = strndup(p, pdiff(p, sep));
131 1050
                p = sep + 1;
132
133 1050
                sep = p;
134 92130
                while (*sep != '\0' && *sep != ';')
135 91080
                        sep++;
136 1050
                value = strndup(p, pdiff(p, sep));
137
138 1050
                vmod_set(ctx, priv, name, value);
139 1050
                free(name);
140 1050
                free(value);
141 1050
                i++;
142 1050
                if (*sep == '\0')
143 480
                        break;
144 570
                p = sep + 1;
145
        }
146
147 540
        VSLb(ctx->vsl, SLT_Debug, "cookie: parsed %i cookies.", i);
148 560
}
149
150
static struct cookie *
151 1400
find_cookie(const struct vmod_cookie *vcp, VCL_STRING name)
152
{
153
        struct cookie *cookie;
154
155 2280
        VTAILQ_FOREACH(cookie, &vcp->cookielist, list) {
156 950
                CHECK_OBJ_NOTNULL(cookie, VMOD_COOKIE_ENTRY_MAGIC);
157 950
                if (!strcmp(cookie->name, name))
158 70
                        break;
159 880
        }
160 1400
        return (cookie);
161
}
162
163
VCL_VOID
164 1340
vmod_set(VRT_CTX, struct vmod_priv *priv, VCL_STRING name, VCL_STRING value)
165
{
166 1340
        struct vmod_cookie *vcp = cobj_get(priv);
167
        struct cookie *cookie;
168
        const char *p;
169
170 1340
        CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
171 1340
        CHECK_OBJ_NOTNULL(ctx->ws, WS_MAGIC);
172
173
        /* Empty cookies should be ignored. */
174 1340
        if (name == NULL || *name == '\0')
175 30
                return;
176 1310
        if (value == NULL || *value == '\0')
177 20
                return;
178
179 1290
        cookie = find_cookie(vcp, name);
180 1290
        if (cookie != NULL) {
181 10
                p = WS_Printf(ctx->ws, "%s", value);
182 10
                if (p == NULL) {
183 0
                        VSLb(ctx->vsl, SLT_Error,
184
                            "cookie: Workspace overflow in set()");
185 0
                } else
186 10
                        cookie->value = p;
187 10
                return;
188
        }
189
190 1280
        cookie = WS_Alloc(ctx->ws, sizeof *cookie);
191 1280
        if (cookie == NULL) {
192 0
                VSLb(ctx->vsl, SLT_Error,
193
                    "cookie: unable to get storage for cookie");
194 0
                return;
195
        }
196 1280
        INIT_OBJ(cookie, VMOD_COOKIE_ENTRY_MAGIC);
197 1280
        cookie->name = WS_Printf(ctx->ws, "%s", name);
198 1280
        cookie->value = WS_Printf(ctx->ws, "%s", value);
199 1280
        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 1280
        VTAILQ_INSERT_TAIL(&vcp->cookielist, cookie, list);
205 1340
}
206
207
VCL_BOOL
208 60
vmod_isset(VRT_CTX, struct vmod_priv *priv, const char *name)
209
{
210 60
        struct vmod_cookie *vcp = cobj_get(priv);
211
        struct cookie *cookie;
212
213 60
        CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
214 60
        if (name == NULL || *name == '\0')
215 20
                return (0);
216
217 40
        cookie = find_cookie(vcp, name);
218 40
        return (cookie ? 1 : 0);
219 60
}
220
221
VCL_STRING
222 40
vmod_get(VRT_CTX, struct vmod_priv *priv, VCL_STRING name)
223
{
224 40
        struct vmod_cookie *vcp = cobj_get(priv);
225
        struct cookie *cookie;
226
227 40
        CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
228 40
        if (name == NULL || *name == '\0')
229 0
                return (NULL);
230
231 40
        cookie = find_cookie(vcp, name);
232 40
        return (cookie ? cookie->value : NULL);
233 40
}
234
235
236
VCL_STRING
237 40
vmod_get_re(VRT_CTX, struct vmod_priv *priv, VCL_REGEX re)
238
{
239 40
        struct vmod_cookie *vcp = cobj_get(priv);
240 40
        struct cookie *cookie = NULL;
241
        struct cookie *current;
242
        unsigned match;
243
244 40
        CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
245 40
        AN(re);
246
247 100
        VTAILQ_FOREACH(current, &vcp->cookielist, list) {
248 80
                CHECK_OBJ_NOTNULL(current, VMOD_COOKIE_ENTRY_MAGIC);
249 80
                VSLb(ctx->vsl, SLT_Debug, "cookie: checking %s", current->name);
250 80
                match = VRT_re_match(ctx, current->name, re);
251 80
                if (!match)
252 60
                        continue;
253 20
                cookie = current;
254 20
                break;
255
        }
256
257 40
        return (cookie ? cookie->value : NULL);
258
}
259
260
VCL_VOID
261 40
vmod_delete(VRT_CTX, struct vmod_priv *priv, VCL_STRING name)
262
{
263 40
        struct vmod_cookie *vcp = cobj_get(priv);
264
        struct cookie *cookie;
265
266 40
        CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
267 40
        if (name == NULL || *name == '\0')
268 10
                return;
269
270 30
        cookie = find_cookie(vcp, name);
271
272 30
        if (cookie != NULL)
273 10
                VTAILQ_REMOVE(&vcp->cookielist, cookie, list);
274 40
}
275
276
VCL_VOID
277 50
vmod_clean(VRT_CTX, struct vmod_priv *priv)
278
{
279 50
        struct vmod_cookie *vcp = cobj_get(priv);
280
281 50
        CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
282 50
        AN(vcp);
283 50
        VTAILQ_INIT(&vcp->cookielist);
284 50
}
285
286
static void
287 60
filter_cookies(struct vmod_priv *priv, VCL_STRING list_s,
288
    enum filter_action mode)
289
{
290
        struct cookie *cookieptr, *safeptr;
291 60
        struct vmod_cookie *vcp = cobj_get(priv);
292
        struct matchlist *mlentry, *mlsafe;
293 60
        char const *p = list_s, *q;
294 60
        int matched = 0;
295
        VTAILQ_HEAD(, matchlist) matchlist_head;
296
297 60
        VTAILQ_INIT(&matchlist_head);
298
299
        /* Parse the supplied list. */
300 270
        while (p && *p != '\0') {
301 290
                while (isspace(*p))
302 80
                        p++;
303 210
                if (*p == '\0')
304 0
                        break;
305
306 210
                q = p;
307 790
                while (*q != '\0' && *q != ',')
308 580
                        q++;
309
310 210
                if (q == p) {
311 100
                        p++;
312 100
                        continue;
313
                }
314
315
                /* XXX: can we reserve/release lumps of txt instead of
316
                 * malloc/free?
317
                 */
318 110
                mlentry = malloc(sizeof *mlentry);
319 110
                AN(mlentry);
320 110
                mlentry->name = strndup(p, q - p);
321 110
                AN(mlentry->name);
322
323 110
                VTAILQ_INSERT_TAIL(&matchlist_head, mlentry, list);
324
325 110
                p = q;
326 110
                if (*p != '\0')
327 90
                        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 220
        VTAILQ_FOREACH_SAFE(cookieptr, &vcp->cookielist, list, safeptr) {
333 160
                CHECK_OBJ_NOTNULL(cookieptr, VMOD_COOKIE_ENTRY_MAGIC);
334 160
                matched = 0;
335
336 330
                VTAILQ_FOREACH(mlentry, &matchlist_head, list) {
337 240
                        if (strcmp(cookieptr->name, mlentry->name) == 0) {
338 70
                                matched = 1;
339 70
                                break;
340
                        }
341 170
                }
342 160
                if (matched != mode)
343 70
                        VTAILQ_REMOVE(&vcp->cookielist, cookieptr, list);
344 160
        }
345
346 170
        VTAILQ_FOREACH_SAFE(mlentry, &matchlist_head, list, mlsafe) {
347 110
                VTAILQ_REMOVE(&matchlist_head, mlentry, list);
348 110
                free(mlentry->name);
349 110
                free(mlentry);
350 110
        }
351 60
}
352
353
VCL_VOID
354 30
vmod_keep(VRT_CTX, struct vmod_priv *priv, VCL_STRING whitelist_s)
355
{
356
357 30
        CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
358 30
        filter_cookies(priv, whitelist_s, whitelist);
359 30
}
360
361
362
VCL_VOID
363 30
vmod_filter(VRT_CTX, struct vmod_priv *priv, VCL_STRING blacklist_s)
364
{
365
366 30
        CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
367 30
        filter_cookies(priv, blacklist_s, blacklist);
368 30
}
369
370
static VCL_VOID
371 70
re_filter(VRT_CTX, struct vmod_priv *priv, VCL_REGEX re, enum filter_action mode)
372
{
373 70
        struct vmod_cookie *vcp = cobj_get(priv);
374
        struct cookie *current, *safeptr;
375
        unsigned match;
376
377 70
        CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
378 70
        AN(re);
379
380 260
        VTAILQ_FOREACH_SAFE(current, &vcp->cookielist, list, safeptr) {
381 190
                CHECK_OBJ_NOTNULL(current, VMOD_COOKIE_ENTRY_MAGIC);
382
383 190
                match = VRT_re_match(ctx, current->name, re);
384
385 190
                switch (mode) {
386
                case blacklist:
387 110
                        if (!match)
388 80
                                continue;
389 60
                        VSLb(ctx->vsl, SLT_Debug,
390
                            "Removing matching cookie %s (value: %s)",
391 30
                            current->name, current->value);
392 30
                        VTAILQ_REMOVE(&vcp->cookielist, current, list);
393 30
                        break;
394
                case whitelist:
395 80
                        if (match)
396 50
                                continue;
397
398 60
                        VSLb(ctx->vsl, SLT_Debug,
399
                            "Removing cookie %s (value: %s)",
400 30
                            current->name, current->value);
401 30
                        VTAILQ_REMOVE(&vcp->cookielist, current, list);
402 30
                        break;
403
                default:
404 0
                        WRONG("invalid mode");
405 0
                }
406 60
        }
407 70
}
408
409
410
VCL_VOID
411 30
vmod_keep_re(VRT_CTX, struct vmod_priv *priv, VCL_REGEX re)
412
{
413
414 30
        CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
415 30
        re_filter(ctx, priv, re, whitelist);
416 30
}
417
418
419
VCL_VOID
420 40
vmod_filter_re(VRT_CTX, struct vmod_priv *priv, VCL_REGEX re)
421
{
422
423 40
        CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
424 40
        re_filter(ctx, priv, re, blacklist);
425 40
}
426
427
428
VCL_STRING
429 650
vmod_get_string(VRT_CTX, struct vmod_priv *priv)
430
{
431
        struct cookie *curr;
432
        struct vsb output[1];
433 650
        struct vmod_cookie *vcp = cobj_get(priv);
434 650
        const char *sep = "", *res;
435
436 650
        CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
437 650
        CHECK_OBJ_NOTNULL(ctx->ws, WS_MAGIC);
438 650
        WS_VSB_new(output, ctx->ws);
439
440 1780
        VTAILQ_FOREACH(curr, &vcp->cookielist, list) {
441 1130
                CHECK_OBJ_NOTNULL(curr, VMOD_COOKIE_ENTRY_MAGIC);
442 1130
                AN(curr->name);
443 1130
                AN(curr->value);
444 1130
                VSB_printf(output, "%s%s=%s;", sep, curr->name, curr->value);
445 1130
                sep = " ";
446 1130
        }
447 650
        res = WS_VSB_finish(output, ctx->ws, NULL);
448 650
        if (res == NULL)
449 0
                VSLb(ctx->vsl, SLT_Error, "cookie: Workspace overflow");
450 650
        return (res);
451
}
452
453
VCL_STRING
454 10
vmod_format_rfc1123(VRT_CTX, VCL_TIME ts, VCL_DURATION duration)
455
{
456
457 10
        CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
458 10
        return (VRT_TIME_string(ctx, ts + duration));
459
}