mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2024-12-27 07:02:11 +00:00
[MAJOR] new framework for generic ACL support
This framework offers all other subsystems the ability to register ACL matching criteria. Some generic matching functions are already provided. Others will come soon and the framework shall evolve.
This commit is contained in:
parent
14c8aac63b
commit
a84d374367
2
Makefile
2
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/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/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/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)
|
haproxy: $(OBJS) $(OPT_OBJS)
|
||||||
$(LD) $(LDFLAGS) -o $@ $^ $(LIBS)
|
$(LD) $(LDFLAGS) -o $@ $^ $(LIBS)
|
||||||
|
@ -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/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/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/ev_kqueue.o
|
src/ev_kqueue.o src/acl.o
|
||||||
|
|
||||||
all: haproxy
|
all: haproxy
|
||||||
|
|
||||||
|
@ -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/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/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/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
|
all: haproxy
|
||||||
|
|
||||||
|
149
include/proto/acl.h
Normal file
149
include/proto/acl.h
Normal file
@ -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 <common/config.h>
|
||||||
|
#include <types/acl.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FIXME: we need destructor functions too !
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/* Return a pointer to the ACL <name> within the list starting at <head>, or
|
||||||
|
* NULL if not found.
|
||||||
|
*/
|
||||||
|
struct acl *find_acl_by_name(const char *name, struct list *head);
|
||||||
|
|
||||||
|
/* Return a pointer to the ACL keyword <kw> within the list starting at <head>,
|
||||||
|
* or NULL if not found. Note that if <kw> 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 <args>[0], and return it.
|
||||||
|
* Right now, the only accepted syntax is :
|
||||||
|
* <subject> [<value>...]
|
||||||
|
*/
|
||||||
|
struct acl_expr *parse_acl_expr(const char **args);
|
||||||
|
|
||||||
|
/* Parse an ACL with the name starting at <args>[0], and with a list of already
|
||||||
|
* known ACLs in <acl>. If the ACL was not in the list, it will be added.
|
||||||
|
* A pointer to that ACL is returned.
|
||||||
|
*
|
||||||
|
* args syntax: <aclname> <acl_expr>
|
||||||
|
*/
|
||||||
|
struct acl *parse_acl(const char **args, struct list *known_acl);
|
||||||
|
|
||||||
|
/* Purge everything in the acl_cond <cond>, then return <cond>. */
|
||||||
|
struct acl_cond *prune_acl_cond(struct acl_cond *cond);
|
||||||
|
|
||||||
|
/* Parse an ACL condition starting at <args>[0], relying on a list of already
|
||||||
|
* known ACLs passed in <known_acl>. 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 <cond> 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 <name> within the list starting at <head>, or
|
||||||
|
* NULL if not found.
|
||||||
|
*/
|
||||||
|
struct acl *find_acl_by_name(const char *name, struct list *head);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Registers the ACL keyword list <kwl> as a list of valid keywords for next
|
||||||
|
* parsing sessions.
|
||||||
|
*/
|
||||||
|
void acl_register_keywords(struct acl_kw_list *kwl);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Unregisters the ACL keyword list <kwl> 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 <test> 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:
|
||||||
|
*/
|
181
include/types/acl.h
Normal file
181
include/types/acl.h
Normal file
@ -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 <common/compat.h>
|
||||||
|
#include <common/config.h>
|
||||||
|
#include <common/mini-clist.h>
|
||||||
|
|
||||||
|
#include <types/proxy.h>
|
||||||
|
#include <types/session.h>
|
||||||
|
|
||||||
|
|
||||||
|
/* 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:
|
||||||
|
*/
|
637
src/acl.c
Normal file
637
src/acl.c
Normal file
@ -0,0 +1,637 @@
|
|||||||
|
/*
|
||||||
|
* ACL management functions.
|
||||||
|
*
|
||||||
|
* Copyright 2000-2007 Willy Tarreau <w@1wt.eu>
|
||||||
|
*
|
||||||
|
* 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 <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <common/config.h>
|
||||||
|
#include <common/mini-clist.h>
|
||||||
|
#include <common/standard.h>
|
||||||
|
|
||||||
|
#include <proto/acl.h>
|
||||||
|
|
||||||
|
#include <types/acl.h>
|
||||||
|
#include <types/proxy.h>
|
||||||
|
#include <types/session.h>
|
||||||
|
|
||||||
|
/* 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 <test> 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 <kwl> 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 <kwl> 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 <name> within the list starting at <head>, 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 <kw>, or NULL if not found. Note that if
|
||||||
|
* <kw> 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 <args>[0], and return it.
|
||||||
|
* Right now, the only accepted syntax is :
|
||||||
|
* <subject> [<value>...]
|
||||||
|
*/
|
||||||
|
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 <args>[0], and with a list of already
|
||||||
|
* known ACLs in <acl>. If the ACL was not in the list, it will be added.
|
||||||
|
* A pointer to that ACL is returned.
|
||||||
|
*
|
||||||
|
* args syntax: <aclname> <acl_expr>
|
||||||
|
*/
|
||||||
|
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 <cond>, then return <cond>. */
|
||||||
|
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 <args>[0], relying on a list of already
|
||||||
|
* known ACLs passed in <known_acl>. 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 <word> 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 <cond> 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 <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 <const> 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:
|
||||||
|
*/
|
Loading…
Reference in New Issue
Block a user