blob: e5d7594ea769f036f18e4420353b45fb4331c7e2 [file] [log] [blame]
Willy Tarreaua84d3742007-05-07 00:36:48 +02001/*
2 * ACL management functions.
3 *
4 * Copyright 2000-2007 Willy Tarreau <w@1wt.eu>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version
9 * 2 of the License, or (at your option) any later version.
10 *
11 */
12
13#include <stdio.h>
14#include <string.h>
15
16#include <common/config.h>
17#include <common/mini-clist.h>
18#include <common/standard.h>
19
20#include <proto/acl.h>
21
22#include <types/acl.h>
23#include <types/proxy.h>
24#include <types/session.h>
25
26/* List head of all known ACL keywords */
27static struct acl_kw_list acl_keywords = {
28 .list = LIST_HEAD_INIT(acl_keywords.list)
29};
30
31
32/* This one always returns 1 because its only purpose is to check that the
33 * value is present, which is already checked by getval().
34 */
35int acl_match_pst(struct acl_test *test, struct acl_pattern *pattern)
36{
37 return 1;
38}
39
40/* NB: For two strings to be identical, it is required that their lengths match */
41int acl_match_str(struct acl_test *test, struct acl_pattern *pattern)
42{
43 if (pattern->len != test->len)
44 return 0;
45 if (strncmp(pattern->ptr.str, test->ptr, test->len) == 0)
46 return 1;
47 return 0;
48}
49
50/* Checks that the pattern matches the beginning of the tested string. */
51int acl_match_beg(struct acl_test *test, struct acl_pattern *pattern)
52{
53 if (pattern->len > test->len)
54 return 0;
55 if (strncmp(pattern->ptr.str, test->ptr, pattern->len) != 0)
56 return 0;
57 return 1;
58}
59
60/* Checks that the pattern matches the end of the tested string. */
61int acl_match_end(struct acl_test *test, struct acl_pattern *pattern)
62{
63 if (pattern->len > test->len)
64 return 0;
65 if (strncmp(pattern->ptr.str, test->ptr + test->len - pattern->len, pattern->len) != 0)
66 return 0;
67 return 1;
68}
69
70/* Checks that the pattern is included inside the tested string.
71 * NB: Suboptimal, should be rewritten using a Boyer-Moore method.
72 */
73int acl_match_sub(struct acl_test *test, struct acl_pattern *pattern)
74{
75 char *end;
76 char *c;
77
78 if (pattern->len > test->len)
79 return 0;
80
81 end = test->ptr + test->len - pattern->len;
82 for (c = test->ptr; c <= end; c++) {
83 if (*c != *pattern->ptr.str)
84 continue;
85 if (strncmp(pattern->ptr.str, c, pattern->len) == 0)
86 return 1;
87 }
88 return 0;
89}
90
91/* This one is used by other real functions. It checks that the pattern is
92 * included inside the tested string, but enclosed between the specified
93 * delimitor, or a '/' or a '?' or at the beginning or end of the string.
94 * The delimitor is stripped at the beginning or end of the pattern are
95 * ignored.
96 */
97static int match_word(struct acl_test *test, struct acl_pattern *pattern, char delim)
98{
99 int may_match;
100 char *c, *end;
101 char *ps;
102 int pl;
103
104 pl = pattern->len;
105 ps = pattern->ptr.str;
106 while (pl > 0 && *ps == delim) {
107 pl--;
108 ps++;
109 }
110
111 while (pl > 0 && *(ps + pl - 1) == delim)
112 pl--;
113
114 if (pl > test->len)
115 return 0;
116
117 may_match = 1;
118 end = test->ptr + test->len - pl;
119 for (c = test->ptr; c <= end; c++) {
120 if (*c == '/' || *c == delim || *c == '?') {
121 may_match = 1;
122 continue;
123 }
124 if (may_match && (*c == *ps) &&
125 (strncmp(ps, c, pl) == 0) &&
126 (c == end || c[pl] == '/' || c[pl] == delim || c[pl] == '?'))
127 return 1;
128
129 may_match = 0;
130 }
131 return 0;
132}
133
134/* Checks that the pattern is included inside the tested string, but enclosed
135 * between slashes or at the beginning or end of the string. Slashes at the
136 * beginning or end of the pattern are ignored.
137 */
138int acl_match_dir(struct acl_test *test, struct acl_pattern *pattern)
139{
140 return match_word(test, pattern, '/');
141}
142
143/* Checks that the pattern is included inside the tested string, but enclosed
144 * between dots or at the beginning or end of the string. Dots at the beginning
145 * or end of the pattern are ignored.
146 */
147int acl_match_dom(struct acl_test *test, struct acl_pattern *pattern)
148{
149 return match_word(test, pattern, '.');
150}
151
152/* Checks that the integer in <test> is included between min and max */
153int acl_match_range(struct acl_test *test, struct acl_pattern *pattern)
154{
155 if ((pattern->val.range.min <= test->i) &&
156 (test->i <= pattern->val.range.max))
157 return 1;
158 return 0;
159}
160
161int acl_match_min(struct acl_test *test, struct acl_pattern *pattern)
162{
163 if (pattern->val.range.min <= test->i)
164 return 1;
165 return 0;
166}
167
168int acl_match_max(struct acl_test *test, struct acl_pattern *pattern)
169{
170 if (test->i <= pattern->val.range.max)
171 return 1;
172 return 0;
173}
174
Willy Tarreaua67fad92007-05-08 19:50:09 +0200175int acl_match_ip(struct acl_test *test, struct acl_pattern *pattern)
176{
177 struct in_addr *s;
178
179 if (test->i != AF_INET)
180 return 0;
181
182 s = (void *)test->ptr;
183 if (((s->s_addr ^ pattern->val.ipv4.addr.s_addr) & pattern->val.ipv4.mask.s_addr) == 0)
184 return 1;
185 return 0;
186}
187
Willy Tarreaua84d3742007-05-07 00:36:48 +0200188/* Parse a string. It is allocated and duplicated. */
189int acl_parse_str(const char *text, struct acl_pattern *pattern)
190{
191 int len;
192
193 len = strlen(text);
194
195 pattern->ptr.str = strdup(text);
196 if (!pattern->ptr.str)
197 return 0;
198 pattern->len = len;
199 return 1;
200}
201
202/* Parse an integer. It is put both in min and max. */
203int acl_parse_int(const char *text, struct acl_pattern *pattern)
204{
205 pattern->val.range.min = pattern->val.range.max = __str2ui(text);
206 return 1;
207}
208
209/* Parse a range of integers delimited by either ':' or '-'. If only one
210 * integer is read, it is set as both min and max.
211 */
212int acl_parse_range(const char *text, struct acl_pattern *pattern)
213{
214 unsigned int i, j, last;
215
216 last = i = 0;
217 while (1) {
218 j = (*text++);
219 if ((j == '-' || j == ':') && !last) {
220 last++;
221 pattern->val.range.min = i;
222 i = 0;
223 continue;
224 }
225 j -= '0';
226 if (j > 9)
227 // also catches the terminating zero
228 break;
229 i *= 10;
230 i += j;
231 }
232 if (!last)
233 pattern->val.range.min = i;
234 pattern->val.range.max = i;
235 return 1;
236}
237
Willy Tarreaua67fad92007-05-08 19:50:09 +0200238/* Parse an IP address and an optional mask in the form addr[/mask].
239 * The addr may either be an IPv4 address or a hostname. The mask
240 * may either be a dotted mask or a number of bits. Returns 1 if OK,
241 * otherwise 0.
242 */
243int acl_parse_ip(const char *text, struct acl_pattern *pattern)
244{
245 return str2net(text, &pattern->val.ipv4.addr, &pattern->val.ipv4.mask);
246}
247
Willy Tarreaua84d3742007-05-07 00:36:48 +0200248/*
249 * Registers the ACL keyword list <kwl> as a list of valid keywords for next
250 * parsing sessions.
251 */
252void acl_register_keywords(struct acl_kw_list *kwl)
253{
254 LIST_ADDQ(&acl_keywords.list, &kwl->list);
255}
256
257/*
258 * Unregisters the ACL keyword list <kwl> from the list of valid keywords.
259 */
260void acl_unregister_keywords(struct acl_kw_list *kwl)
261{
262 LIST_DEL(&kwl->list);
263 LIST_INIT(&kwl->list);
264}
265
266/* Return a pointer to the ACL <name> within the list starting at <head>, or
267 * NULL if not found.
268 */
269struct acl *find_acl_by_name(const char *name, struct list *head)
270{
271 struct acl *acl;
272 list_for_each_entry(acl, head, list) {
273 if (strcmp(acl->name, name) == 0)
274 return acl;
275 }
276 return NULL;
277}
278
279/* Return a pointer to the ACL keyword <kw>, or NULL if not found. Note that if
280 * <kw> contains an opening parenthesis, only the left part of it is checked.
281 */
282struct acl_keyword *find_acl_kw(const char *kw)
283{
284 int index;
285 const char *kwend;
286 struct acl_kw_list *kwl;
287
288 kwend = strchr(kw, '(');
289 if (!kwend)
290 kwend = kw + strlen(kw);
291
292 list_for_each_entry(kwl, &acl_keywords.list, list) {
293 for (index = 0; kwl->kw[index].kw != NULL; index++) {
294 if ((strncmp(kwl->kw[index].kw, kw, kwend - kw) == 0) &&
295 kwl->kw[index].kw[kwend-kw] == 0)
296 return &kwl->kw[index];
297 }
298 }
299 return NULL;
300}
301
302static void free_pattern(struct acl_pattern *pat)
303{
304 if (pat->ptr.ptr)
305 free(pat->ptr.ptr);
306 free(pat);
307}
308
309static void free_pattern_list(struct list *head)
310{
311 struct acl_pattern *pat, *tmp;
312 list_for_each_entry_safe(pat, tmp, head, list)
313 free_pattern(pat);
314}
315
316static struct acl_expr *prune_acl_expr(struct acl_expr *expr)
317{
318 free_pattern_list(&expr->patterns);
319 LIST_INIT(&expr->patterns);
320 if (expr->arg.str)
321 free(expr->arg.str);
322 expr->kw->use_cnt--;
323 return expr;
324}
325
326/* Parse an ACL expression starting at <args>[0], and return it.
327 * Right now, the only accepted syntax is :
328 * <subject> [<value>...]
329 */
330struct acl_expr *parse_acl_expr(const char **args)
331{
332 __label__ out_return, out_free_expr, out_free_pattern;
333 struct acl_expr *expr;
334 struct acl_keyword *aclkw;
335 struct acl_pattern *pattern;
336 const char *arg;
337
338 aclkw = find_acl_kw(args[0]);
339 if (!aclkw || !aclkw->parse)
340 goto out_return;
341
342 expr = (struct acl_expr *)calloc(1, sizeof(*expr));
343 if (!expr)
344 goto out_return;
345
346 expr->kw = aclkw;
347 aclkw->use_cnt++;
348 LIST_INIT(&expr->patterns);
349 expr->arg.str = NULL;
350
351 arg = strchr(args[0], '(');
352 if (arg != NULL) {
353 char *end, *arg2;
354 /* there is an argument in the form "subject(arg)" */
355 arg++;
356 end = strchr(arg, ')');
357 if (!end)
358 goto out_free_expr;
359 arg2 = (char *)calloc(1, end - arg + 1);
360 if (!arg2)
361 goto out_free_expr;
362 memcpy(arg2, arg, end - arg);
363 arg2[end-arg] = '\0';
364 expr->arg.str = arg2;
365 }
366
367 /* now parse all patterns */
368 args++;
369 while (**args) {
370 pattern = (struct acl_pattern *)calloc(1, sizeof(*pattern));
371 if (!pattern)
372 goto out_free_expr;
373 if (!aclkw->parse(*args, pattern))
374 goto out_free_pattern;
375 LIST_ADDQ(&expr->patterns, &pattern->list);
376 args++;
377 }
378
379 return expr;
380
381 out_free_pattern:
382 free_pattern(pattern);
383 out_free_expr:
384 prune_acl_expr(expr);
385 free(expr);
386 out_return:
387 return NULL;
388}
389
390/* Parse an ACL with the name starting at <args>[0], and with a list of already
391 * known ACLs in <acl>. If the ACL was not in the list, it will be added.
392 * A pointer to that ACL is returned.
393 *
394 * args syntax: <aclname> <acl_expr>
395 */
396struct acl *parse_acl(const char **args, struct list *known_acl)
397{
398 __label__ out_return, out_free_acl_expr, out_free_name;
399 struct acl *cur_acl;
400 struct acl_expr *acl_expr;
401 char *name;
402
403 acl_expr = parse_acl_expr(args + 1);
404 if (!acl_expr)
405 goto out_return;
406
407 cur_acl = find_acl_by_name(args[0], known_acl);
408 if (!cur_acl) {
409 name = strdup(args[0]);
410 if (!name)
411 goto out_free_acl_expr;
412 cur_acl = (struct acl *)calloc(1, sizeof(*cur_acl));
413 if (cur_acl == NULL)
414 goto out_free_name;
415
416 LIST_INIT(&cur_acl->expr);
417 LIST_ADDQ(known_acl, &cur_acl->list);
418 cur_acl->name = name;
419 }
420
421 LIST_ADDQ(&cur_acl->expr, &acl_expr->list);
422 return cur_acl;
423
424 out_free_name:
425 free(name);
426 out_free_acl_expr:
427 prune_acl_expr(acl_expr);
428 free(acl_expr);
429 out_return:
430 return NULL;
431}
432
433
434/* Purge everything in the acl_cond <cond>, then return <cond>. */
435struct acl_cond *prune_acl_cond(struct acl_cond *cond)
436{
437 struct acl_term_suite *suite, *tmp_suite;
438 struct acl_term *term, *tmp_term;
439
440 /* iterate through all term suites and free all terms and all suites */
441 list_for_each_entry_safe(suite, tmp_suite, &cond->suites, list) {
442 list_for_each_entry_safe(term, tmp_term, &suite->terms, list)
443 free(term);
444 free(suite);
445 }
446 return cond;
447}
448
449/* Parse an ACL condition starting at <args>[0], relying on a list of already
450 * known ACLs passed in <known_acl>. The new condition is returned (or NULL in
451 * case of low memory). Supports multiple conditions separated by "or".
452 */
453struct acl_cond *parse_acl_cond(const char **args, struct list *known_acl, int pol)
454{
455 __label__ out_return, out_free_suite, out_free_term;
456 int arg;
457 int neg = 0;
458 const char *word;
459 struct acl *cur_acl;
460 struct acl_term *cur_term;
461 struct acl_term_suite *cur_suite;
462 struct acl_cond *cond;
463
464 cond = (struct acl_cond *)calloc(1, sizeof(*cond));
465 if (cond == NULL)
466 goto out_return;
467
468 LIST_INIT(&cond->list);
469 LIST_INIT(&cond->suites);
470 cond->pol = pol;
471
472 cur_suite = NULL;
473 for (arg = 0; *args[arg]; arg++) {
474 word = args[arg];
475
476 /* remove as many exclamation marks as we can */
477 while (*word == '!') {
478 neg = !neg;
479 word++;
480 }
481
482 /* an empty word is allowed because we cannot force the user to
483 * always think about not leaving exclamation marks alone.
484 */
485 if (!*word)
486 continue;
487
488 if (strcasecmp(word, "or") == 0) {
489 /* new term suite */
490 cur_suite = NULL;
491 neg = 0;
492 continue;
493 }
494
495 /* search for <word> in the known ACL names */
496 cur_acl = find_acl_by_name(word, known_acl);
497 if (cur_acl == NULL)
498 goto out_free_suite;
499
500 cur_term = (struct acl_term *)calloc(1, sizeof(*cur_term));
501 if (cur_term == NULL)
502 goto out_free_suite;
503
504 cur_term->acl = cur_acl;
505 cur_term->neg = neg;
506
507 if (!cur_suite) {
508 cur_suite = (struct acl_term_suite *)calloc(1, sizeof(*cur_suite));
509 if (cur_term == NULL)
510 goto out_free_term;
511 LIST_INIT(&cur_suite->terms);
512 LIST_ADDQ(&cond->suites, &cur_suite->list);
513 }
514 LIST_ADDQ(&cur_suite->terms, &cur_term->list);
515 }
516
517 return cond;
518
519 out_free_term:
520 free(cur_term);
521 out_free_suite:
522 prune_acl_cond(cond);
523 free(cond);
524 out_return:
525 return NULL;
526}
527
528/* Execute condition <cond> and return 0 if test fails or 1 if test succeeds.
529 * This function only computes the condition, it does not apply the polarity
530 * required by IF/UNLESS, it's up to the caller to do this.
531 */
532int acl_exec_cond(struct acl_cond *cond, struct proxy *px, struct session *l4, void *l7)
533{
534 __label__ fetch_next;
535 struct acl_term_suite *suite;
536 struct acl_term *term;
537 struct acl_expr *expr;
538 struct acl *acl;
539 struct acl_pattern *pattern;
540 struct acl_test test;
541 int acl_res, pat_res, suite_res, cond_res;
542
543 /* we're doing a logical OR between conditions so we initialize to FAIL */
544 cond_res = ACL_PAT_FAIL;
545 list_for_each_entry(suite, &cond->suites, list) {
546 /* evaluate condition suite <suite>. We stop at the first term
547 * which does not return ACL_PAT_PASS.
548 */
549
550 /* we're doing a logical AND between terms, so we must set the
551 * initial value to PASS.
552 */
553 suite_res = ACL_PAT_PASS;
554 list_for_each_entry(term, &suite->terms, list) {
555 acl = term->acl;
556
557 /* FIXME: use cache !
558 * check acl->cache_idx for this.
559 */
560
561 /* ACL result not cached. Let's scan all the expressions
562 * and use the first one to match.
563 */
564 acl_res = ACL_PAT_FAIL;
565 list_for_each_entry(expr, &acl->expr, list) {
566 test.flags = test.len = 0;
567 fetch_next:
568 if (!expr->kw->fetch(px, l4, l7, expr->arg.str, &test))
569 continue;
570
571 /* apply all tests to this value */
572 list_for_each_entry(pattern, &expr->patterns, list) {
573 pat_res = expr->kw->match(&test, pattern);
574
575 if (pat_res & ACL_PAT_MISS) {
576 /* there is at least one test which might be worth retrying later. */
577 acl_res |= ACL_PAT_MISS;
578 continue;
579 } else if (pat_res & ACL_PAT_PASS) {
580 /* we found one ! */
581 acl_res |= ACL_PAT_PASS;
582 break;
583 }
584 }
585 /*
586 * OK now we have the result of this expression in expr_res.
587 * - we have the PASS bit set if at least one pattern matched ;
588 * - we have the MISS bit set if at least one pattern may match
589 * later so that we should not cache a failure ;
590 *
591 * Then if (PASS || !MISS) we can cache the result, and put
592 * (test.flags & ACL_TEST_F_VOLATILE) in the cache flags.
593 *
594 * FIXME: implement cache.
595 *
596 */
597
598 /* now we may have some cleanup to do */
599 if (test.flags & ACL_TEST_F_MUST_FREE) {
600 free(test.ptr);
601 test.len = 0;
602 }
603
604 if (acl_res & ACL_PAT_PASS)
605 break;
606
607 /* prepare to test another expression */
608 acl_res = ACL_PAT_FAIL;
609
610 if (test.flags & ACL_TEST_F_FETCH_MORE)
611 goto fetch_next;
612 }
613 /*
614 * Here we have the result of an ACL (cached or not).
615 * ACLs are combined, negated or not, to form conditions.
616 */
617
618 acl_res &= ACL_PAT_PASS;
619 if (term->neg)
620 acl_res ^= ACL_PAT_PASS;
621
622 suite_res &= acl_res;
623 if (!(suite_res & ACL_PAT_PASS))
624 break;
625 }
626 cond_res |= suite_res;
627 if (cond_res & ACL_PAT_PASS)
628 break;
629 }
630
631 return (cond_res & ACL_PAT_PASS) ? 1 : 0;
632}
633
634
635/************************************************************************/
636/* All supported keywords must be declared here. */
637/************************************************************************/
638
639/* Note: must not be declared <const> as its list will be overwritten */
640static struct acl_kw_list acl_kws = {{ },{
641#if 0
642 { "time", acl_parse_time, acl_fetch_time, acl_match_time },
643#endif
644 { NULL, NULL, NULL, NULL }
645}};
646
647
648__attribute__((constructor))
649static void __acl_init(void)
650{
651 acl_register_keywords(&acl_kws);
652}
653
654
655/*
656 * Local variables:
657 * c-indent-level: 8
658 * c-basic-offset: 8
659 * End:
660 */