diff --git a/Makefile b/Makefile index 12de8bebe..3f5dfa7a4 100644 --- a/Makefile +++ b/Makefile @@ -217,7 +217,7 @@ OBJS = src/haproxy.o src/list.o src/chtbl.o src/hashpjw.o src/base64.o \ src/time.o src/fd.o src/regex.o src/cfgparse.o src/server.o \ src/checks.o src/queue.o src/capture.o src/client.o src/proxy.o \ src/proto_http.o src/stream_sock.o src/appsession.o src/backend.o \ - src/session.o src/hdr_idx.o src/ev_select.o + src/session.o src/hdr_idx.o src/ev_select.o src/acl.o haproxy: $(OBJS) $(OPT_OBJS) $(LD) $(LDFLAGS) -o $@ $^ $(LIBS) diff --git a/Makefile.bsd b/Makefile.bsd index d125c9051..55c5a5668 100644 --- a/Makefile.bsd +++ b/Makefile.bsd @@ -88,7 +88,7 @@ OBJS = src/haproxy.o src/list.o src/chtbl.o src/hashpjw.o src/base64.o \ src/checks.o src/queue.o src/capture.o src/client.o src/proxy.o \ src/proto_http.o src/stream_sock.o src/appsession.o src/backend.o \ src/session.o src/hdr_idx.o src/ev_select.o src/ev_poll.o \ - src/ev_kqueue.o + src/ev_kqueue.o src/acl.o all: haproxy diff --git a/Makefile.osx b/Makefile.osx index 4b028715f..3666a88fd 100644 --- a/Makefile.osx +++ b/Makefile.osx @@ -87,7 +87,7 @@ OBJS = src/haproxy.o src/list.o src/chtbl.o src/hashpjw.o src/base64.o \ src/time.o src/fd.o src/regex.o src/cfgparse.o src/server.o \ src/checks.o src/queue.o src/capture.o src/client.o src/proxy.o \ src/proto_http.o src/stream_sock.o src/appsession.o src/backend.o \ - src/session.o src/hdr_idx.o src/ev_select.o src/ev_poll.o + src/session.o src/hdr_idx.o src/ev_select.o src/ev_poll.o src/acl.o all: haproxy diff --git a/include/proto/acl.h b/include/proto/acl.h new file mode 100644 index 000000000..19642b419 --- /dev/null +++ b/include/proto/acl.h @@ -0,0 +1,149 @@ +/* + include/proto/acl.h + This file provides interface definitions for ACL manipulation. + + Copyright (C) 2000-2007 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _PROTO_ACL_H +#define _PROTO_ACL_H + +#include +#include + +/* + * FIXME: we need destructor functions too ! + */ + + +/* Return a pointer to the ACL within the list starting at , or + * NULL if not found. + */ +struct acl *find_acl_by_name(const char *name, struct list *head); + +/* Return a pointer to the ACL keyword within the list starting at , + * or NULL if not found. Note that if contains an opening parenthesis, + * only the left part of it is checked. + */ +struct acl_keyword *find_acl_kw(const char *kw); + +/* Parse an ACL expression starting at [0], and return it. + * Right now, the only accepted syntax is : + * [...] + */ +struct acl_expr *parse_acl_expr(const char **args); + +/* Parse an ACL with the name starting at [0], and with a list of already + * known ACLs in . If the ACL was not in the list, it will be added. + * A pointer to that ACL is returned. + * + * args syntax: + */ +struct acl *parse_acl(const char **args, struct list *known_acl); + +/* Purge everything in the acl_cond , then return . */ +struct acl_cond *prune_acl_cond(struct acl_cond *cond); + +/* Parse an ACL condition starting at [0], relying on a list of already + * known ACLs passed in . The new condition is returned (or NULL in + * case of low memory). Supports multiple conditions separated by "or". + */ +struct acl_cond *parse_acl_cond(const char **args, struct list *known_acl, int pol); + +/* Execute condition and return 0 if test fails or 1 if test succeeds. + * This function only computes the condition, it does not apply the polarity + * required by IF/UNLESS, it's up to the caller to do this. + */ +int acl_exec_cond(struct acl_cond *cond, struct proxy *px, struct session *l4, void *l7); + +/* Return a pointer to the ACL within the list starting at , or + * NULL if not found. + */ +struct acl *find_acl_by_name(const char *name, struct list *head); + +/* + * Registers the ACL keyword list as a list of valid keywords for next + * parsing sessions. + */ +void acl_register_keywords(struct acl_kw_list *kwl); + +/* + * Unregisters the ACL keyword list from the list of valid keywords. + */ +void acl_unregister_keywords(struct acl_kw_list *kwl); + + +/* + * + * The following functions are general purpose ACL matching functions. + * + */ + + +/* This one always returns 1 because its only purpose is to check that the + * value is present, which is already checked by getval(). + */ +int acl_match_pst(struct acl_test *test, struct acl_pattern *pattern); + +/* NB: For two strings to be identical, it is required that their lengths match */ +int acl_match_str(struct acl_test *test, struct acl_pattern *pattern); + +/* Checks that the integer in is included between min and max */ +int acl_match_range(struct acl_test *test, struct acl_pattern *pattern); +int acl_match_min(struct acl_test *test, struct acl_pattern *pattern); +int acl_match_max(struct acl_test *test, struct acl_pattern *pattern); + +/* Parse an integer. It is put both in min and max. */ +int acl_parse_int(const char *text, struct acl_pattern *pattern); + +/* Parse a range of integers delimited by either ':' or '-'. If only one + * integer is read, it is set as both min and max. + */ +int acl_parse_range(const char *text, struct acl_pattern *pattern); + +/* Parse a string. It is allocated and duplicated. */ +int acl_parse_str(const char *text, struct acl_pattern *pattern); + +/* Checks that the pattern matches the end of the tested string. */ +int acl_match_end(struct acl_test *test, struct acl_pattern *pattern); + +/* Checks that the pattern matches the beginning of the tested string. */ +int acl_match_beg(struct acl_test *test, struct acl_pattern *pattern); + +/* Checks that the pattern is included inside the tested string. */ +int acl_match_sub(struct acl_test *test, struct acl_pattern *pattern); + +/* Checks that the pattern is included inside the tested string, but enclosed + * between slashes or at the beginning or end of the string. Slashes at the + * beginning or end of the pattern are ignored. + */ +int acl_match_dir(struct acl_test *test, struct acl_pattern *pattern); + +/* Checks that the pattern is included inside the tested string, but enclosed + * between dots or at the beginning or end of the string. Dots at the beginning + * or end of the pattern are ignored. + */ +int acl_match_dom(struct acl_test *test, struct acl_pattern *pattern); + +#endif /* _PROTO_ACL_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/types/acl.h b/include/types/acl.h new file mode 100644 index 000000000..229ea6335 --- /dev/null +++ b/include/types/acl.h @@ -0,0 +1,181 @@ +/* + include/types/acl.h + This file provides structures and types for ACLs. + + Copyright (C) 2000-2007 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _TYPES_ACL_H +#define _TYPES_ACL_H + +#include +#include +#include + +#include +#include + + +/* pattern matching function result */ +enum { + ACL_PAT_FAIL = 0, /* test failed */ + ACL_PAT_PASS = (1 << 0), /* test passed */ + ACL_PAT_MISS = (1 << 1), /* failed because of missing info (do not cache) */ +}; + +/* Condition polarity. It makes it easier for any option to choose between + * IF/UNLESS if it can store that information within the condition itself. + */ +enum { + ACL_COND_NONE, /* no polarity set yet */ + ACL_COND_IF, /* positive condition (after 'if') */ + ACL_COND_UNLESS, /* negative condition (after 'unless') */ +}; + +/* possible flags for intermediate test values. The flags are maintained + * across consecutive fetches for a same entry (eg: parse all req lines). + */ +enum { + ACL_TEST_F_READ_ONLY = 1 << 0, /* test data are read-only */ + ACL_TEST_F_MUST_FREE = 1 << 1, /* test data must be freed after end of evaluation */ + ACL_TEST_F_VOL_TEST = 1 << 2, /* result must not survive longer than the test (eg: time) */ + ACL_TEST_F_VOL_HDR = 1 << 3, /* result sensitive to changes in headers */ + ACL_TEST_F_VOL_1ST = 1 << 4, /* result sensitive to changes in first line (eg: URI) */ + ACL_TEST_F_VOL_TXN = 1 << 5, /* result sensitive to new transaction (eg: persist) */ + ACL_TEST_F_VOL_SESS = 1 << 6, /* result sensitive to new session (eg: IP) */ + ACL_TEST_F_VOLATILE = (1<<2)|(1<<3)|(1<<4)|(1<<5)|(1<<6), + ACL_TEST_F_FETCH_MORE = 1 << 7, /* if test does not match, retry with next entry */ +}; + +/* How to store a time range and the valid days in 29 bits */ +struct acl_time { + int dow:7; /* 1 bit per day of week: 0-6 */ + int h1:5, m1:6; /* 0..24:0..60. Use 0:0 for all day. */ + int h2:5, m2:6; /* 0..24:0..60. Use 24:0 for all day. */ +}; + +/* The acl will be linked to from the proxy where it is declared */ +struct acl_pattern { + struct list list; /* chaining */ + union { + int i; /* integer value */ + struct { int min, max; } range; /* integer range */ + struct sockaddr_in ipv4; /* IPv4 address */ + struct acl_time time; /* valid hours and days */ + } val; /* direct value */ + union { + void *ptr; /* any data */ + char *str; /* any string */ + regex_t *reg; /* a compiled regex */ + } ptr; /* indirect values, allocated */ + int len; /* data length when required */ +}; + +/* The structure exchanged between an acl_fetch_* function responsible for + * retrieving a value, and an acl_match_* function responsible for testing it. + */ +struct acl_test { + int i; /* integer value */ + char *ptr; /* pointer to beginning of value */ + int len; /* length of value at ptr, otherwise ignored */ + int flags; /* ACL_TEST_F_* set to 0 on first call */ + union { /* fetch_* functions context for any purpose */ + void *p; + int i; + } ctx; +}; + + +/* + * ACL keyword: Associates keywords with parsers, methods to retrieve the value and testers. + */ + +/* some dummy declarations to silent the compiler */ +struct proxy; +struct session; + +struct acl_keyword { + const char *kw; + int (*parse)(const char *text, struct acl_pattern *pattern); + int (*fetch)(struct proxy *px, struct session *l4, void *l7, void *arg, struct acl_test *test); + int (*match)(struct acl_test *test, struct acl_pattern *pattern); + int use_cnt; +}; + +/* + * A keyword list. It is a NULL-terminated array of keywords. It embeds a + * struct list in order to be linked to other lists, allowing it to easily + * be declared where it is needed, and linked without duplicating data nor + * allocating memory. + */ +struct acl_kw_list { + struct list list; + struct acl_keyword kw[VAR_ARRAY]; +}; + +/* + * Description of an ACL expression. + * It contains a subject and a set of patterns to test against it. + * - the function get() is called to retrieve the subject from the + * current session or transaction and build a test. + * - the function test() is called to evaluate the test based on the + * available patterns and return ACL_PAT_* + * Both of those functions are available through the keyword. + */ +struct acl_expr { + struct list list; /* chaining */ + struct acl_keyword *kw; /* back-reference to the keyword */ + union { /* optional argument of the subject (eg: header or cookie name) */ + char *str; + } arg; + struct list patterns; /* list of acl_patterns */ +}; + +struct acl { + struct list list; /* chaining */ + char *name; /* acl name */ + struct list expr; /* list of acl_exprs */ + int cache_idx; /* ACL index in cache */ +}; + +/* the condition will be linked to from an action in a proxy */ +struct acl_term { + struct list list; /* chaining */ + struct acl *acl; /* acl pointed to by this term */ + int neg; /* 1 if the ACL result must be negated */ +}; + +struct acl_term_suite { + struct list list; /* chaining of term suites */ + struct list terms; /* list of acl_terms */ +}; + +struct acl_cond { + struct list list; /* Some specific tests may use multiple conditions */ + struct list suites; /* list of acl_term_suites */ + int pol; /* polarity: ACL_COND_IF / ACL_COND_UNLESS */ +}; + + +#endif /* _TYPES_ACL_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/acl.c b/src/acl.c new file mode 100644 index 000000000..2912f082a --- /dev/null +++ b/src/acl.c @@ -0,0 +1,637 @@ +/* + * ACL management functions. + * + * Copyright 2000-2007 Willy Tarreau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include + +/* List head of all known ACL keywords */ +static struct acl_kw_list acl_keywords = { + .list = LIST_HEAD_INIT(acl_keywords.list) +}; + + +/* This one always returns 1 because its only purpose is to check that the + * value is present, which is already checked by getval(). + */ +int acl_match_pst(struct acl_test *test, struct acl_pattern *pattern) +{ + return 1; +} + +/* NB: For two strings to be identical, it is required that their lengths match */ +int acl_match_str(struct acl_test *test, struct acl_pattern *pattern) +{ + if (pattern->len != test->len) + return 0; + if (strncmp(pattern->ptr.str, test->ptr, test->len) == 0) + return 1; + return 0; +} + +/* Checks that the pattern matches the beginning of the tested string. */ +int acl_match_beg(struct acl_test *test, struct acl_pattern *pattern) +{ + if (pattern->len > test->len) + return 0; + if (strncmp(pattern->ptr.str, test->ptr, pattern->len) != 0) + return 0; + return 1; +} + +/* Checks that the pattern matches the end of the tested string. */ +int acl_match_end(struct acl_test *test, struct acl_pattern *pattern) +{ + if (pattern->len > test->len) + return 0; + if (strncmp(pattern->ptr.str, test->ptr + test->len - pattern->len, pattern->len) != 0) + return 0; + return 1; +} + +/* Checks that the pattern is included inside the tested string. + * NB: Suboptimal, should be rewritten using a Boyer-Moore method. + */ +int acl_match_sub(struct acl_test *test, struct acl_pattern *pattern) +{ + char *end; + char *c; + + if (pattern->len > test->len) + return 0; + + end = test->ptr + test->len - pattern->len; + for (c = test->ptr; c <= end; c++) { + if (*c != *pattern->ptr.str) + continue; + if (strncmp(pattern->ptr.str, c, pattern->len) == 0) + return 1; + } + return 0; +} + +/* This one is used by other real functions. It checks that the pattern is + * included inside the tested string, but enclosed between the specified + * delimitor, or a '/' or a '?' or at the beginning or end of the string. + * The delimitor is stripped at the beginning or end of the pattern are + * ignored. + */ +static int match_word(struct acl_test *test, struct acl_pattern *pattern, char delim) +{ + int may_match; + char *c, *end; + char *ps; + int pl; + + pl = pattern->len; + ps = pattern->ptr.str; + while (pl > 0 && *ps == delim) { + pl--; + ps++; + } + + while (pl > 0 && *(ps + pl - 1) == delim) + pl--; + + if (pl > test->len) + return 0; + + may_match = 1; + end = test->ptr + test->len - pl; + for (c = test->ptr; c <= end; c++) { + if (*c == '/' || *c == delim || *c == '?') { + may_match = 1; + continue; + } + if (may_match && (*c == *ps) && + (strncmp(ps, c, pl) == 0) && + (c == end || c[pl] == '/' || c[pl] == delim || c[pl] == '?')) + return 1; + + may_match = 0; + } + return 0; +} + +/* Checks that the pattern is included inside the tested string, but enclosed + * between slashes or at the beginning or end of the string. Slashes at the + * beginning or end of the pattern are ignored. + */ +int acl_match_dir(struct acl_test *test, struct acl_pattern *pattern) +{ + return match_word(test, pattern, '/'); +} + +/* Checks that the pattern is included inside the tested string, but enclosed + * between dots or at the beginning or end of the string. Dots at the beginning + * or end of the pattern are ignored. + */ +int acl_match_dom(struct acl_test *test, struct acl_pattern *pattern) +{ + return match_word(test, pattern, '.'); +} + +/* Checks that the integer in is included between min and max */ +int acl_match_range(struct acl_test *test, struct acl_pattern *pattern) +{ + if ((pattern->val.range.min <= test->i) && + (test->i <= pattern->val.range.max)) + return 1; + return 0; +} + +int acl_match_min(struct acl_test *test, struct acl_pattern *pattern) +{ + if (pattern->val.range.min <= test->i) + return 1; + return 0; +} + +int acl_match_max(struct acl_test *test, struct acl_pattern *pattern) +{ + if (test->i <= pattern->val.range.max) + return 1; + return 0; +} + +/* Parse a string. It is allocated and duplicated. */ +int acl_parse_str(const char *text, struct acl_pattern *pattern) +{ + int len; + + len = strlen(text); + + pattern->ptr.str = strdup(text); + if (!pattern->ptr.str) + return 0; + pattern->len = len; + return 1; +} + +/* Parse an integer. It is put both in min and max. */ +int acl_parse_int(const char *text, struct acl_pattern *pattern) +{ + pattern->val.range.min = pattern->val.range.max = __str2ui(text); + return 1; +} + +/* Parse a range of integers delimited by either ':' or '-'. If only one + * integer is read, it is set as both min and max. + */ +int acl_parse_range(const char *text, struct acl_pattern *pattern) +{ + unsigned int i, j, last; + + last = i = 0; + while (1) { + j = (*text++); + if ((j == '-' || j == ':') && !last) { + last++; + pattern->val.range.min = i; + i = 0; + continue; + } + j -= '0'; + if (j > 9) + // also catches the terminating zero + break; + i *= 10; + i += j; + } + if (!last) + pattern->val.range.min = i; + pattern->val.range.max = i; + return 1; +} + +/* + * Registers the ACL keyword list as a list of valid keywords for next + * parsing sessions. + */ +void acl_register_keywords(struct acl_kw_list *kwl) +{ + LIST_ADDQ(&acl_keywords.list, &kwl->list); +} + +/* + * Unregisters the ACL keyword list from the list of valid keywords. + */ +void acl_unregister_keywords(struct acl_kw_list *kwl) +{ + LIST_DEL(&kwl->list); + LIST_INIT(&kwl->list); +} + +/* Return a pointer to the ACL within the list starting at , or + * NULL if not found. + */ +struct acl *find_acl_by_name(const char *name, struct list *head) +{ + struct acl *acl; + list_for_each_entry(acl, head, list) { + if (strcmp(acl->name, name) == 0) + return acl; + } + return NULL; +} + +/* Return a pointer to the ACL keyword , or NULL if not found. Note that if + * contains an opening parenthesis, only the left part of it is checked. + */ +struct acl_keyword *find_acl_kw(const char *kw) +{ + int index; + const char *kwend; + struct acl_kw_list *kwl; + + kwend = strchr(kw, '('); + if (!kwend) + kwend = kw + strlen(kw); + + list_for_each_entry(kwl, &acl_keywords.list, list) { + for (index = 0; kwl->kw[index].kw != NULL; index++) { + if ((strncmp(kwl->kw[index].kw, kw, kwend - kw) == 0) && + kwl->kw[index].kw[kwend-kw] == 0) + return &kwl->kw[index]; + } + } + return NULL; +} + +static void free_pattern(struct acl_pattern *pat) +{ + if (pat->ptr.ptr) + free(pat->ptr.ptr); + free(pat); +} + +static void free_pattern_list(struct list *head) +{ + struct acl_pattern *pat, *tmp; + list_for_each_entry_safe(pat, tmp, head, list) + free_pattern(pat); +} + +static struct acl_expr *prune_acl_expr(struct acl_expr *expr) +{ + free_pattern_list(&expr->patterns); + LIST_INIT(&expr->patterns); + if (expr->arg.str) + free(expr->arg.str); + expr->kw->use_cnt--; + return expr; +} + +/* Parse an ACL expression starting at [0], and return it. + * Right now, the only accepted syntax is : + * [...] + */ +struct acl_expr *parse_acl_expr(const char **args) +{ + __label__ out_return, out_free_expr, out_free_pattern; + struct acl_expr *expr; + struct acl_keyword *aclkw; + struct acl_pattern *pattern; + const char *arg; + + aclkw = find_acl_kw(args[0]); + if (!aclkw || !aclkw->parse) + goto out_return; + + expr = (struct acl_expr *)calloc(1, sizeof(*expr)); + if (!expr) + goto out_return; + + expr->kw = aclkw; + aclkw->use_cnt++; + LIST_INIT(&expr->patterns); + expr->arg.str = NULL; + + arg = strchr(args[0], '('); + if (arg != NULL) { + char *end, *arg2; + /* there is an argument in the form "subject(arg)" */ + arg++; + end = strchr(arg, ')'); + if (!end) + goto out_free_expr; + arg2 = (char *)calloc(1, end - arg + 1); + if (!arg2) + goto out_free_expr; + memcpy(arg2, arg, end - arg); + arg2[end-arg] = '\0'; + expr->arg.str = arg2; + } + + /* now parse all patterns */ + args++; + while (**args) { + pattern = (struct acl_pattern *)calloc(1, sizeof(*pattern)); + if (!pattern) + goto out_free_expr; + if (!aclkw->parse(*args, pattern)) + goto out_free_pattern; + LIST_ADDQ(&expr->patterns, &pattern->list); + args++; + } + + return expr; + + out_free_pattern: + free_pattern(pattern); + out_free_expr: + prune_acl_expr(expr); + free(expr); + out_return: + return NULL; +} + +/* Parse an ACL with the name starting at [0], and with a list of already + * known ACLs in . If the ACL was not in the list, it will be added. + * A pointer to that ACL is returned. + * + * args syntax: + */ +struct acl *parse_acl(const char **args, struct list *known_acl) +{ + __label__ out_return, out_free_acl_expr, out_free_name; + struct acl *cur_acl; + struct acl_expr *acl_expr; + char *name; + + acl_expr = parse_acl_expr(args + 1); + if (!acl_expr) + goto out_return; + + cur_acl = find_acl_by_name(args[0], known_acl); + if (!cur_acl) { + name = strdup(args[0]); + if (!name) + goto out_free_acl_expr; + cur_acl = (struct acl *)calloc(1, sizeof(*cur_acl)); + if (cur_acl == NULL) + goto out_free_name; + + LIST_INIT(&cur_acl->expr); + LIST_ADDQ(known_acl, &cur_acl->list); + cur_acl->name = name; + } + + LIST_ADDQ(&cur_acl->expr, &acl_expr->list); + return cur_acl; + + out_free_name: + free(name); + out_free_acl_expr: + prune_acl_expr(acl_expr); + free(acl_expr); + out_return: + return NULL; +} + + +/* Purge everything in the acl_cond , then return . */ +struct acl_cond *prune_acl_cond(struct acl_cond *cond) +{ + struct acl_term_suite *suite, *tmp_suite; + struct acl_term *term, *tmp_term; + + /* iterate through all term suites and free all terms and all suites */ + list_for_each_entry_safe(suite, tmp_suite, &cond->suites, list) { + list_for_each_entry_safe(term, tmp_term, &suite->terms, list) + free(term); + free(suite); + } + return cond; +} + +/* Parse an ACL condition starting at [0], relying on a list of already + * known ACLs passed in . The new condition is returned (or NULL in + * case of low memory). Supports multiple conditions separated by "or". + */ +struct acl_cond *parse_acl_cond(const char **args, struct list *known_acl, int pol) +{ + __label__ out_return, out_free_suite, out_free_term; + int arg; + int neg = 0; + const char *word; + struct acl *cur_acl; + struct acl_term *cur_term; + struct acl_term_suite *cur_suite; + struct acl_cond *cond; + + cond = (struct acl_cond *)calloc(1, sizeof(*cond)); + if (cond == NULL) + goto out_return; + + LIST_INIT(&cond->list); + LIST_INIT(&cond->suites); + cond->pol = pol; + + cur_suite = NULL; + for (arg = 0; *args[arg]; arg++) { + word = args[arg]; + + /* remove as many exclamation marks as we can */ + while (*word == '!') { + neg = !neg; + word++; + } + + /* an empty word is allowed because we cannot force the user to + * always think about not leaving exclamation marks alone. + */ + if (!*word) + continue; + + if (strcasecmp(word, "or") == 0) { + /* new term suite */ + cur_suite = NULL; + neg = 0; + continue; + } + + /* search for in the known ACL names */ + cur_acl = find_acl_by_name(word, known_acl); + if (cur_acl == NULL) + goto out_free_suite; + + cur_term = (struct acl_term *)calloc(1, sizeof(*cur_term)); + if (cur_term == NULL) + goto out_free_suite; + + cur_term->acl = cur_acl; + cur_term->neg = neg; + + if (!cur_suite) { + cur_suite = (struct acl_term_suite *)calloc(1, sizeof(*cur_suite)); + if (cur_term == NULL) + goto out_free_term; + LIST_INIT(&cur_suite->terms); + LIST_ADDQ(&cond->suites, &cur_suite->list); + } + LIST_ADDQ(&cur_suite->terms, &cur_term->list); + } + + return cond; + + out_free_term: + free(cur_term); + out_free_suite: + prune_acl_cond(cond); + free(cond); + out_return: + return NULL; +} + +/* Execute condition and return 0 if test fails or 1 if test succeeds. + * This function only computes the condition, it does not apply the polarity + * required by IF/UNLESS, it's up to the caller to do this. + */ +int acl_exec_cond(struct acl_cond *cond, struct proxy *px, struct session *l4, void *l7) +{ + __label__ fetch_next; + struct acl_term_suite *suite; + struct acl_term *term; + struct acl_expr *expr; + struct acl *acl; + struct acl_pattern *pattern; + struct acl_test test; + int acl_res, pat_res, suite_res, cond_res; + + /* we're doing a logical OR between conditions so we initialize to FAIL */ + cond_res = ACL_PAT_FAIL; + list_for_each_entry(suite, &cond->suites, list) { + /* evaluate condition suite . We stop at the first term + * which does not return ACL_PAT_PASS. + */ + + /* we're doing a logical AND between terms, so we must set the + * initial value to PASS. + */ + suite_res = ACL_PAT_PASS; + list_for_each_entry(term, &suite->terms, list) { + acl = term->acl; + + /* FIXME: use cache ! + * check acl->cache_idx for this. + */ + + /* ACL result not cached. Let's scan all the expressions + * and use the first one to match. + */ + acl_res = ACL_PAT_FAIL; + list_for_each_entry(expr, &acl->expr, list) { + test.flags = test.len = 0; + fetch_next: + if (!expr->kw->fetch(px, l4, l7, expr->arg.str, &test)) + continue; + + /* apply all tests to this value */ + list_for_each_entry(pattern, &expr->patterns, list) { + pat_res = expr->kw->match(&test, pattern); + + if (pat_res & ACL_PAT_MISS) { + /* there is at least one test which might be worth retrying later. */ + acl_res |= ACL_PAT_MISS; + continue; + } else if (pat_res & ACL_PAT_PASS) { + /* we found one ! */ + acl_res |= ACL_PAT_PASS; + break; + } + } + /* + * OK now we have the result of this expression in expr_res. + * - we have the PASS bit set if at least one pattern matched ; + * - we have the MISS bit set if at least one pattern may match + * later so that we should not cache a failure ; + * + * Then if (PASS || !MISS) we can cache the result, and put + * (test.flags & ACL_TEST_F_VOLATILE) in the cache flags. + * + * FIXME: implement cache. + * + */ + + /* now we may have some cleanup to do */ + if (test.flags & ACL_TEST_F_MUST_FREE) { + free(test.ptr); + test.len = 0; + } + + if (acl_res & ACL_PAT_PASS) + break; + + /* prepare to test another expression */ + acl_res = ACL_PAT_FAIL; + + if (test.flags & ACL_TEST_F_FETCH_MORE) + goto fetch_next; + } + /* + * Here we have the result of an ACL (cached or not). + * ACLs are combined, negated or not, to form conditions. + */ + + acl_res &= ACL_PAT_PASS; + if (term->neg) + acl_res ^= ACL_PAT_PASS; + + suite_res &= acl_res; + if (!(suite_res & ACL_PAT_PASS)) + break; + } + cond_res |= suite_res; + if (cond_res & ACL_PAT_PASS) + break; + } + + return (cond_res & ACL_PAT_PASS) ? 1 : 0; +} + + +/************************************************************************/ +/* All supported keywords must be declared here. */ +/************************************************************************/ + +/* Note: must not be declared as its list will be overwritten */ +static struct acl_kw_list acl_kws = {{ },{ +#if 0 + { "time", acl_parse_time, acl_fetch_time, acl_match_time }, +#endif + { NULL, NULL, NULL, NULL } +}}; + + +__attribute__((constructor)) +static void __acl_init(void) +{ + acl_register_keywords(&acl_kws); +} + + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */