blob: 2912f082adada220e6ea805a42fa423de29401f3 [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
175/* Parse a string. It is allocated and duplicated. */
176int acl_parse_str(const char *text, struct acl_pattern *pattern)
177{
178 int len;
179
180 len = strlen(text);
181
182 pattern->ptr.str = strdup(text);
183 if (!pattern->ptr.str)
184 return 0;
185 pattern->len = len;
186 return 1;
187}
188
189/* Parse an integer. It is put both in min and max. */
190int acl_parse_int(const char *text, struct acl_pattern *pattern)
191{
192 pattern->val.range.min = pattern->val.range.max = __str2ui(text);
193 return 1;
194}
195
196/* Parse a range of integers delimited by either ':' or '-'. If only one
197 * integer is read, it is set as both min and max.
198 */
199int acl_parse_range(const char *text, struct acl_pattern *pattern)
200{
201 unsigned int i, j, last;
202
203 last = i = 0;
204 while (1) {
205 j = (*text++);
206 if ((j == '-' || j == ':') && !last) {
207 last++;
208 pattern->val.range.min = i;
209 i = 0;
210 continue;
211 }
212 j -= '0';
213 if (j > 9)
214 // also catches the terminating zero
215 break;
216 i *= 10;
217 i += j;
218 }
219 if (!last)
220 pattern->val.range.min = i;
221 pattern->val.range.max = i;
222 return 1;
223}
224
225/*
226 * Registers the ACL keyword list <kwl> as a list of valid keywords for next
227 * parsing sessions.
228 */
229void acl_register_keywords(struct acl_kw_list *kwl)
230{
231 LIST_ADDQ(&acl_keywords.list, &kwl->list);
232}
233
234/*
235 * Unregisters the ACL keyword list <kwl> from the list of valid keywords.
236 */
237void acl_unregister_keywords(struct acl_kw_list *kwl)
238{
239 LIST_DEL(&kwl->list);
240 LIST_INIT(&kwl->list);
241}
242
243/* Return a pointer to the ACL <name> within the list starting at <head>, or
244 * NULL if not found.
245 */
246struct acl *find_acl_by_name(const char *name, struct list *head)
247{
248 struct acl *acl;
249 list_for_each_entry(acl, head, list) {
250 if (strcmp(acl->name, name) == 0)
251 return acl;
252 }
253 return NULL;
254}
255
256/* Return a pointer to the ACL keyword <kw>, or NULL if not found. Note that if
257 * <kw> contains an opening parenthesis, only the left part of it is checked.
258 */
259struct acl_keyword *find_acl_kw(const char *kw)
260{
261 int index;
262 const char *kwend;
263 struct acl_kw_list *kwl;
264
265 kwend = strchr(kw, '(');
266 if (!kwend)
267 kwend = kw + strlen(kw);
268
269 list_for_each_entry(kwl, &acl_keywords.list, list) {
270 for (index = 0; kwl->kw[index].kw != NULL; index++) {
271 if ((strncmp(kwl->kw[index].kw, kw, kwend - kw) == 0) &&
272 kwl->kw[index].kw[kwend-kw] == 0)
273 return &kwl->kw[index];
274 }
275 }
276 return NULL;
277}
278
279static void free_pattern(struct acl_pattern *pat)
280{
281 if (pat->ptr.ptr)
282 free(pat->ptr.ptr);
283 free(pat);
284}
285
286static void free_pattern_list(struct list *head)
287{
288 struct acl_pattern *pat, *tmp;
289 list_for_each_entry_safe(pat, tmp, head, list)
290 free_pattern(pat);
291}
292
293static struct acl_expr *prune_acl_expr(struct acl_expr *expr)
294{
295 free_pattern_list(&expr->patterns);
296 LIST_INIT(&expr->patterns);
297 if (expr->arg.str)
298 free(expr->arg.str);
299 expr->kw->use_cnt--;
300 return expr;
301}
302
303/* Parse an ACL expression starting at <args>[0], and return it.
304 * Right now, the only accepted syntax is :
305 * <subject> [<value>...]
306 */
307struct acl_expr *parse_acl_expr(const char **args)
308{
309 __label__ out_return, out_free_expr, out_free_pattern;
310 struct acl_expr *expr;
311 struct acl_keyword *aclkw;
312 struct acl_pattern *pattern;
313 const char *arg;
314
315 aclkw = find_acl_kw(args[0]);
316 if (!aclkw || !aclkw->parse)
317 goto out_return;
318
319 expr = (struct acl_expr *)calloc(1, sizeof(*expr));
320 if (!expr)
321 goto out_return;
322
323 expr->kw = aclkw;
324 aclkw->use_cnt++;
325 LIST_INIT(&expr->patterns);
326 expr->arg.str = NULL;
327
328 arg = strchr(args[0], '(');
329 if (arg != NULL) {
330 char *end, *arg2;
331 /* there is an argument in the form "subject(arg)" */
332 arg++;
333 end = strchr(arg, ')');
334 if (!end)
335 goto out_free_expr;
336 arg2 = (char *)calloc(1, end - arg + 1);
337 if (!arg2)
338 goto out_free_expr;
339 memcpy(arg2, arg, end - arg);
340 arg2[end-arg] = '\0';
341 expr->arg.str = arg2;
342 }
343
344 /* now parse all patterns */
345 args++;
346 while (**args) {
347 pattern = (struct acl_pattern *)calloc(1, sizeof(*pattern));
348 if (!pattern)
349 goto out_free_expr;
350 if (!aclkw->parse(*args, pattern))
351 goto out_free_pattern;
352 LIST_ADDQ(&expr->patterns, &pattern->list);
353 args++;
354 }
355
356 return expr;
357
358 out_free_pattern:
359 free_pattern(pattern);
360 out_free_expr:
361 prune_acl_expr(expr);
362 free(expr);
363 out_return:
364 return NULL;
365}
366
367/* Parse an ACL with the name starting at <args>[0], and with a list of already
368 * known ACLs in <acl>. If the ACL was not in the list, it will be added.
369 * A pointer to that ACL is returned.
370 *
371 * args syntax: <aclname> <acl_expr>
372 */
373struct acl *parse_acl(const char **args, struct list *known_acl)
374{
375 __label__ out_return, out_free_acl_expr, out_free_name;
376 struct acl *cur_acl;
377 struct acl_expr *acl_expr;
378 char *name;
379
380 acl_expr = parse_acl_expr(args + 1);
381 if (!acl_expr)
382 goto out_return;
383
384 cur_acl = find_acl_by_name(args[0], known_acl);
385 if (!cur_acl) {
386 name = strdup(args[0]);
387 if (!name)
388 goto out_free_acl_expr;
389 cur_acl = (struct acl *)calloc(1, sizeof(*cur_acl));
390 if (cur_acl == NULL)
391 goto out_free_name;
392
393 LIST_INIT(&cur_acl->expr);
394 LIST_ADDQ(known_acl, &cur_acl->list);
395 cur_acl->name = name;
396 }
397
398 LIST_ADDQ(&cur_acl->expr, &acl_expr->list);
399 return cur_acl;
400
401 out_free_name:
402 free(name);
403 out_free_acl_expr:
404 prune_acl_expr(acl_expr);
405 free(acl_expr);
406 out_return:
407 return NULL;
408}
409
410
411/* Purge everything in the acl_cond <cond>, then return <cond>. */
412struct acl_cond *prune_acl_cond(struct acl_cond *cond)
413{
414 struct acl_term_suite *suite, *tmp_suite;
415 struct acl_term *term, *tmp_term;
416
417 /* iterate through all term suites and free all terms and all suites */
418 list_for_each_entry_safe(suite, tmp_suite, &cond->suites, list) {
419 list_for_each_entry_safe(term, tmp_term, &suite->terms, list)
420 free(term);
421 free(suite);
422 }
423 return cond;
424}
425
426/* Parse an ACL condition starting at <args>[0], relying on a list of already
427 * known ACLs passed in <known_acl>. The new condition is returned (or NULL in
428 * case of low memory). Supports multiple conditions separated by "or".
429 */
430struct acl_cond *parse_acl_cond(const char **args, struct list *known_acl, int pol)
431{
432 __label__ out_return, out_free_suite, out_free_term;
433 int arg;
434 int neg = 0;
435 const char *word;
436 struct acl *cur_acl;
437 struct acl_term *cur_term;
438 struct acl_term_suite *cur_suite;
439 struct acl_cond *cond;
440
441 cond = (struct acl_cond *)calloc(1, sizeof(*cond));
442 if (cond == NULL)
443 goto out_return;
444
445 LIST_INIT(&cond->list);
446 LIST_INIT(&cond->suites);
447 cond->pol = pol;
448
449 cur_suite = NULL;
450 for (arg = 0; *args[arg]; arg++) {
451 word = args[arg];
452
453 /* remove as many exclamation marks as we can */
454 while (*word == '!') {
455 neg = !neg;
456 word++;
457 }
458
459 /* an empty word is allowed because we cannot force the user to
460 * always think about not leaving exclamation marks alone.
461 */
462 if (!*word)
463 continue;
464
465 if (strcasecmp(word, "or") == 0) {
466 /* new term suite */
467 cur_suite = NULL;
468 neg = 0;
469 continue;
470 }
471
472 /* search for <word> in the known ACL names */
473 cur_acl = find_acl_by_name(word, known_acl);
474 if (cur_acl == NULL)
475 goto out_free_suite;
476
477 cur_term = (struct acl_term *)calloc(1, sizeof(*cur_term));
478 if (cur_term == NULL)
479 goto out_free_suite;
480
481 cur_term->acl = cur_acl;
482 cur_term->neg = neg;
483
484 if (!cur_suite) {
485 cur_suite = (struct acl_term_suite *)calloc(1, sizeof(*cur_suite));
486 if (cur_term == NULL)
487 goto out_free_term;
488 LIST_INIT(&cur_suite->terms);
489 LIST_ADDQ(&cond->suites, &cur_suite->list);
490 }
491 LIST_ADDQ(&cur_suite->terms, &cur_term->list);
492 }
493
494 return cond;
495
496 out_free_term:
497 free(cur_term);
498 out_free_suite:
499 prune_acl_cond(cond);
500 free(cond);
501 out_return:
502 return NULL;
503}
504
505/* Execute condition <cond> and return 0 if test fails or 1 if test succeeds.
506 * This function only computes the condition, it does not apply the polarity
507 * required by IF/UNLESS, it's up to the caller to do this.
508 */
509int acl_exec_cond(struct acl_cond *cond, struct proxy *px, struct session *l4, void *l7)
510{
511 __label__ fetch_next;
512 struct acl_term_suite *suite;
513 struct acl_term *term;
514 struct acl_expr *expr;
515 struct acl *acl;
516 struct acl_pattern *pattern;
517 struct acl_test test;
518 int acl_res, pat_res, suite_res, cond_res;
519
520 /* we're doing a logical OR between conditions so we initialize to FAIL */
521 cond_res = ACL_PAT_FAIL;
522 list_for_each_entry(suite, &cond->suites, list) {
523 /* evaluate condition suite <suite>. We stop at the first term
524 * which does not return ACL_PAT_PASS.
525 */
526
527 /* we're doing a logical AND between terms, so we must set the
528 * initial value to PASS.
529 */
530 suite_res = ACL_PAT_PASS;
531 list_for_each_entry(term, &suite->terms, list) {
532 acl = term->acl;
533
534 /* FIXME: use cache !
535 * check acl->cache_idx for this.
536 */
537
538 /* ACL result not cached. Let's scan all the expressions
539 * and use the first one to match.
540 */
541 acl_res = ACL_PAT_FAIL;
542 list_for_each_entry(expr, &acl->expr, list) {
543 test.flags = test.len = 0;
544 fetch_next:
545 if (!expr->kw->fetch(px, l4, l7, expr->arg.str, &test))
546 continue;
547
548 /* apply all tests to this value */
549 list_for_each_entry(pattern, &expr->patterns, list) {
550 pat_res = expr->kw->match(&test, pattern);
551
552 if (pat_res & ACL_PAT_MISS) {
553 /* there is at least one test which might be worth retrying later. */
554 acl_res |= ACL_PAT_MISS;
555 continue;
556 } else if (pat_res & ACL_PAT_PASS) {
557 /* we found one ! */
558 acl_res |= ACL_PAT_PASS;
559 break;
560 }
561 }
562 /*
563 * OK now we have the result of this expression in expr_res.
564 * - we have the PASS bit set if at least one pattern matched ;
565 * - we have the MISS bit set if at least one pattern may match
566 * later so that we should not cache a failure ;
567 *
568 * Then if (PASS || !MISS) we can cache the result, and put
569 * (test.flags & ACL_TEST_F_VOLATILE) in the cache flags.
570 *
571 * FIXME: implement cache.
572 *
573 */
574
575 /* now we may have some cleanup to do */
576 if (test.flags & ACL_TEST_F_MUST_FREE) {
577 free(test.ptr);
578 test.len = 0;
579 }
580
581 if (acl_res & ACL_PAT_PASS)
582 break;
583
584 /* prepare to test another expression */
585 acl_res = ACL_PAT_FAIL;
586
587 if (test.flags & ACL_TEST_F_FETCH_MORE)
588 goto fetch_next;
589 }
590 /*
591 * Here we have the result of an ACL (cached or not).
592 * ACLs are combined, negated or not, to form conditions.
593 */
594
595 acl_res &= ACL_PAT_PASS;
596 if (term->neg)
597 acl_res ^= ACL_PAT_PASS;
598
599 suite_res &= acl_res;
600 if (!(suite_res & ACL_PAT_PASS))
601 break;
602 }
603 cond_res |= suite_res;
604 if (cond_res & ACL_PAT_PASS)
605 break;
606 }
607
608 return (cond_res & ACL_PAT_PASS) ? 1 : 0;
609}
610
611
612/************************************************************************/
613/* All supported keywords must be declared here. */
614/************************************************************************/
615
616/* Note: must not be declared <const> as its list will be overwritten */
617static struct acl_kw_list acl_kws = {{ },{
618#if 0
619 { "time", acl_parse_time, acl_fetch_time, acl_match_time },
620#endif
621 { NULL, NULL, NULL, NULL }
622}};
623
624
625__attribute__((constructor))
626static void __acl_init(void)
627{
628 acl_register_keywords(&acl_kws);
629}
630
631
632/*
633 * Local variables:
634 * c-indent-level: 8
635 * c-basic-offset: 8
636 * End:
637 */