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