mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2025-02-26 15:40:32 +00:00
In make_arg_list() function, unresolved dependencies are pushed in an argument list to be resolved later, during the configuration validity check. It is now possible to forbid such unresolved dependencies by omitting <al> parameter (setting it to NULL). It is usefull when the parsing context is not the same than the running context or when the parsing context is lost after the startup stage. For instance, an argument may be defined in defaults section during parsing and executed in a frontend/backend section.
1350 lines
39 KiB
C
1350 lines
39 KiB
C
/*
|
|
* ACL management functions.
|
|
*
|
|
* Copyright 2000-2013 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 <ctype.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <import/ebsttree.h>
|
|
|
|
#include <haproxy/acl.h>
|
|
#include <haproxy/api.h>
|
|
#include <haproxy/arg.h>
|
|
#include <haproxy/auth.h>
|
|
#include <haproxy/errors.h>
|
|
#include <haproxy/global.h>
|
|
#include <haproxy/list.h>
|
|
#include <haproxy/pattern.h>
|
|
#include <haproxy/proxy-t.h>
|
|
#include <haproxy/sample.h>
|
|
#include <haproxy/stick_table.h>
|
|
#include <haproxy/tools.h>
|
|
|
|
/* List head of all known ACL keywords */
|
|
static struct acl_kw_list acl_keywords = {
|
|
.list = LIST_HEAD_INIT(acl_keywords.list)
|
|
};
|
|
|
|
/* input values are 0 or 3, output is the same */
|
|
static inline enum acl_test_res pat2acl(struct pattern *pat)
|
|
{
|
|
if (pat)
|
|
return ACL_TEST_PASS;
|
|
else
|
|
return ACL_TEST_FAIL;
|
|
}
|
|
|
|
/*
|
|
* 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_APPEND(&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_DELETE(&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 or a comma, 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 = kw;
|
|
while (is_idchar(*kwend))
|
|
kwend++;
|
|
|
|
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 struct acl_expr *prune_acl_expr(struct acl_expr *expr)
|
|
{
|
|
struct arg *arg;
|
|
|
|
pattern_prune(&expr->pat);
|
|
|
|
for (arg = expr->smp->arg_p; arg; arg++) {
|
|
if (arg->type == ARGT_STOP)
|
|
break;
|
|
if (arg->type == ARGT_STR || arg->unresolved) {
|
|
chunk_destroy(&arg->data.str);
|
|
arg->unresolved = 0;
|
|
}
|
|
}
|
|
|
|
release_sample_expr(expr->smp);
|
|
|
|
return expr;
|
|
}
|
|
|
|
/* Parse an ACL expression starting at <args>[0], and return it. If <err> is
|
|
* not NULL, it will be filled with a pointer to an error message in case of
|
|
* error. This pointer must be freeable or NULL. <al> is an arg_list serving
|
|
* as a list head to report missing dependencies. It may be NULL if such
|
|
* dependencies are not allowed.
|
|
*
|
|
* Right now, the only accepted syntax is :
|
|
* <subject> [<value>...]
|
|
*/
|
|
struct acl_expr *parse_acl_expr(const char **args, char **err, struct arg_list *al,
|
|
const char *file, int line)
|
|
{
|
|
__label__ out_return, out_free_expr;
|
|
struct acl_expr *expr;
|
|
struct acl_keyword *aclkw;
|
|
int refflags, patflags;
|
|
const char *arg;
|
|
struct sample_expr *smp = NULL;
|
|
int idx = 0;
|
|
char *ckw = NULL;
|
|
const char *begw;
|
|
const char *endw;
|
|
const char *endt;
|
|
int cur_type;
|
|
int nbargs;
|
|
int operator = STD_OP_EQ;
|
|
int op;
|
|
int contain_colon, have_dot;
|
|
const char *dot;
|
|
signed long long value, minor;
|
|
/* The following buffer contain two numbers, a ':' separator and the final \0. */
|
|
char buffer[NB_LLMAX_STR + 1 + NB_LLMAX_STR + 1];
|
|
int is_loaded;
|
|
int unique_id;
|
|
char *error;
|
|
struct pat_ref *ref;
|
|
struct pattern_expr *pattern_expr;
|
|
int load_as_map = 0;
|
|
int acl_conv_found = 0;
|
|
|
|
/* First, we look for an ACL keyword. And if we don't find one, then
|
|
* we look for a sample fetch expression starting with a sample fetch
|
|
* keyword.
|
|
*/
|
|
|
|
if (al) {
|
|
al->ctx = ARGC_ACL; // to report errors while resolving args late
|
|
al->kw = *args;
|
|
al->conv = NULL;
|
|
}
|
|
|
|
aclkw = find_acl_kw(args[0]);
|
|
if (aclkw) {
|
|
/* OK we have a real ACL keyword */
|
|
|
|
/* build new sample expression for this ACL */
|
|
smp = calloc(1, sizeof(*smp));
|
|
if (!smp) {
|
|
memprintf(err, "out of memory when parsing ACL expression");
|
|
goto out_return;
|
|
}
|
|
LIST_INIT(&(smp->conv_exprs));
|
|
smp->fetch = aclkw->smp;
|
|
smp->arg_p = empty_arg_list;
|
|
|
|
/* look for the beginning of the subject arguments */
|
|
for (arg = args[0]; is_idchar(*arg); arg++)
|
|
;
|
|
|
|
/* At this point, we have :
|
|
* - args[0] : beginning of the keyword
|
|
* - arg : end of the keyword, first character not part of keyword
|
|
*/
|
|
nbargs = make_arg_list(arg, -1, smp->fetch->arg_mask, &smp->arg_p,
|
|
err, &endt, NULL, al);
|
|
if (nbargs < 0) {
|
|
/* note that make_arg_list will have set <err> here */
|
|
memprintf(err, "ACL keyword '%s' : %s", aclkw->kw, *err);
|
|
goto out_free_smp;
|
|
}
|
|
|
|
if (!smp->arg_p) {
|
|
smp->arg_p = empty_arg_list;
|
|
}
|
|
else if (smp->fetch->val_args && !smp->fetch->val_args(smp->arg_p, err)) {
|
|
/* invalid keyword argument, error must have been
|
|
* set by val_args().
|
|
*/
|
|
memprintf(err, "in argument to '%s', %s", aclkw->kw, *err);
|
|
goto out_free_smp;
|
|
}
|
|
arg = endt;
|
|
|
|
/* look for the beginning of the converters list. Those directly attached
|
|
* to the ACL keyword are found just after <arg> which points to the comma.
|
|
* If we find any converter, then we don't use the ACL keyword's match
|
|
* anymore but the one related to the converter's output type.
|
|
*/
|
|
cur_type = smp->fetch->out_type;
|
|
while (*arg) {
|
|
struct sample_conv *conv;
|
|
struct sample_conv_expr *conv_expr;
|
|
int err_arg;
|
|
int argcnt;
|
|
|
|
if (*arg && *arg != ',') {
|
|
if (ckw)
|
|
memprintf(err, "ACL keyword '%s' : missing comma after converter '%s'.",
|
|
aclkw->kw, ckw);
|
|
else
|
|
memprintf(err, "ACL keyword '%s' : missing comma after fetch keyword.",
|
|
aclkw->kw);
|
|
goto out_free_smp;
|
|
}
|
|
|
|
/* FIXME: how long should we support such idiocies ? Maybe we
|
|
* should already warn ?
|
|
*/
|
|
while (*arg == ',') /* then trailing commas */
|
|
arg++;
|
|
|
|
begw = arg; /* start of converter keyword */
|
|
|
|
if (!*begw)
|
|
/* none ? end of converters */
|
|
break;
|
|
|
|
for (endw = begw; is_idchar(*endw); endw++)
|
|
;
|
|
|
|
free(ckw);
|
|
ckw = my_strndup(begw, endw - begw);
|
|
|
|
conv = find_sample_conv(begw, endw - begw);
|
|
if (!conv) {
|
|
/* Unknown converter method */
|
|
memprintf(err, "ACL keyword '%s' : unknown converter '%s'.",
|
|
aclkw->kw, ckw);
|
|
goto out_free_smp;
|
|
}
|
|
|
|
arg = endw;
|
|
|
|
if (conv->in_type >= SMP_TYPES || conv->out_type >= SMP_TYPES) {
|
|
memprintf(err, "ACL keyword '%s' : returns type of converter '%s' is unknown.",
|
|
aclkw->kw, ckw);
|
|
goto out_free_smp;
|
|
}
|
|
|
|
/* If impossible type conversion */
|
|
if (!sample_casts[cur_type][conv->in_type]) {
|
|
memprintf(err, "ACL keyword '%s' : converter '%s' cannot be applied.",
|
|
aclkw->kw, ckw);
|
|
goto out_free_smp;
|
|
}
|
|
|
|
cur_type = conv->out_type;
|
|
conv_expr = calloc(1, sizeof(*conv_expr));
|
|
if (!conv_expr)
|
|
goto out_free_smp;
|
|
|
|
LIST_APPEND(&(smp->conv_exprs), &(conv_expr->list));
|
|
conv_expr->conv = conv;
|
|
acl_conv_found = 1;
|
|
|
|
if (al) {
|
|
al->kw = smp->fetch->kw;
|
|
al->conv = conv_expr->conv->kw;
|
|
}
|
|
argcnt = make_arg_list(endw, -1, conv->arg_mask, &conv_expr->arg_p, err, &arg, &err_arg, al);
|
|
if (argcnt < 0) {
|
|
memprintf(err, "ACL keyword '%s' : invalid arg %d in converter '%s' : %s.",
|
|
aclkw->kw, err_arg+1, ckw, *err);
|
|
goto out_free_smp;
|
|
}
|
|
|
|
if (argcnt && !conv->arg_mask) {
|
|
memprintf(err, "converter '%s' does not support any args", ckw);
|
|
goto out_free_smp;
|
|
}
|
|
|
|
if (!conv_expr->arg_p)
|
|
conv_expr->arg_p = empty_arg_list;
|
|
|
|
if (conv->val_args && !conv->val_args(conv_expr->arg_p, conv, file, line, err)) {
|
|
memprintf(err, "ACL keyword '%s' : invalid args in converter '%s' : %s.",
|
|
aclkw->kw, ckw, *err);
|
|
goto out_free_smp;
|
|
}
|
|
}
|
|
ha_free(&ckw);
|
|
}
|
|
else {
|
|
/* This is not an ACL keyword, so we hope this is a sample fetch
|
|
* keyword that we're going to transparently use as an ACL. If
|
|
* so, we retrieve a completely parsed expression with args and
|
|
* convs already done.
|
|
*/
|
|
smp = sample_parse_expr((char **)args, &idx, file, line, err, al, NULL);
|
|
if (!smp) {
|
|
memprintf(err, "%s in ACL expression '%s'", *err, *args);
|
|
goto out_return;
|
|
}
|
|
cur_type = smp_expr_output_type(smp);
|
|
}
|
|
|
|
expr = calloc(1, sizeof(*expr));
|
|
if (!expr) {
|
|
memprintf(err, "out of memory when parsing ACL expression");
|
|
goto out_free_smp;
|
|
}
|
|
|
|
pattern_init_head(&expr->pat);
|
|
|
|
expr->pat.expect_type = cur_type;
|
|
expr->smp = smp;
|
|
expr->kw = smp->fetch->kw;
|
|
smp = NULL; /* don't free it anymore */
|
|
|
|
if (aclkw && !acl_conv_found) {
|
|
expr->kw = aclkw->kw;
|
|
expr->pat.parse = aclkw->parse ? aclkw->parse : pat_parse_fcts[aclkw->match_type];
|
|
expr->pat.index = aclkw->index ? aclkw->index : pat_index_fcts[aclkw->match_type];
|
|
expr->pat.match = aclkw->match ? aclkw->match : pat_match_fcts[aclkw->match_type];
|
|
expr->pat.prune = aclkw->prune ? aclkw->prune : pat_prune_fcts[aclkw->match_type];
|
|
}
|
|
|
|
if (!expr->pat.parse) {
|
|
/* Parse/index/match functions depend on the expression type,
|
|
* so we have to map them now. Some types can be automatically
|
|
* converted.
|
|
*/
|
|
switch (cur_type) {
|
|
case SMP_T_BOOL:
|
|
expr->pat.parse = pat_parse_fcts[PAT_MATCH_BOOL];
|
|
expr->pat.index = pat_index_fcts[PAT_MATCH_BOOL];
|
|
expr->pat.match = pat_match_fcts[PAT_MATCH_BOOL];
|
|
expr->pat.prune = pat_prune_fcts[PAT_MATCH_BOOL];
|
|
expr->pat.expect_type = pat_match_types[PAT_MATCH_BOOL];
|
|
break;
|
|
case SMP_T_SINT:
|
|
expr->pat.parse = pat_parse_fcts[PAT_MATCH_INT];
|
|
expr->pat.index = pat_index_fcts[PAT_MATCH_INT];
|
|
expr->pat.match = pat_match_fcts[PAT_MATCH_INT];
|
|
expr->pat.prune = pat_prune_fcts[PAT_MATCH_INT];
|
|
expr->pat.expect_type = pat_match_types[PAT_MATCH_INT];
|
|
break;
|
|
case SMP_T_ADDR:
|
|
case SMP_T_IPV4:
|
|
case SMP_T_IPV6:
|
|
expr->pat.parse = pat_parse_fcts[PAT_MATCH_IP];
|
|
expr->pat.index = pat_index_fcts[PAT_MATCH_IP];
|
|
expr->pat.match = pat_match_fcts[PAT_MATCH_IP];
|
|
expr->pat.prune = pat_prune_fcts[PAT_MATCH_IP];
|
|
expr->pat.expect_type = pat_match_types[PAT_MATCH_IP];
|
|
break;
|
|
case SMP_T_STR:
|
|
expr->pat.parse = pat_parse_fcts[PAT_MATCH_STR];
|
|
expr->pat.index = pat_index_fcts[PAT_MATCH_STR];
|
|
expr->pat.match = pat_match_fcts[PAT_MATCH_STR];
|
|
expr->pat.prune = pat_prune_fcts[PAT_MATCH_STR];
|
|
expr->pat.expect_type = pat_match_types[PAT_MATCH_STR];
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Additional check to protect against common mistakes */
|
|
if (expr->pat.parse && cur_type != SMP_T_BOOL && !*args[1]) {
|
|
ha_warning("parsing acl keyword '%s' :\n"
|
|
" no pattern to match against were provided, so this ACL will never match.\n"
|
|
" If this is what you intended, please add '--' to get rid of this warning.\n"
|
|
" If you intended to match only for existence, please use '-m found'.\n"
|
|
" If you wanted to force an int to match as a bool, please use '-m bool'.\n"
|
|
"\n",
|
|
args[0]);
|
|
}
|
|
|
|
args++;
|
|
|
|
/* check for options before patterns. Supported options are :
|
|
* -i : ignore case for all patterns by default
|
|
* -f : read patterns from those files
|
|
* -m : force matching method (must be used before -f)
|
|
* -M : load the file as map file
|
|
* -u : force the unique id of the acl
|
|
* -- : everything after this is not an option
|
|
*/
|
|
refflags = PAT_REF_ACL;
|
|
patflags = 0;
|
|
is_loaded = 0;
|
|
unique_id = -1;
|
|
while (**args == '-') {
|
|
if (strcmp(*args, "-i") == 0)
|
|
patflags |= PAT_MF_IGNORE_CASE;
|
|
else if (strcmp(*args, "-n") == 0)
|
|
patflags |= PAT_MF_NO_DNS;
|
|
else if (strcmp(*args, "-u") == 0) {
|
|
unique_id = strtol(args[1], &error, 10);
|
|
if (*error != '\0') {
|
|
memprintf(err, "the argument of -u must be an integer");
|
|
goto out_free_expr;
|
|
}
|
|
|
|
/* Check if this id is really unique. */
|
|
if (pat_ref_lookupid(unique_id)) {
|
|
memprintf(err, "the id is already used");
|
|
goto out_free_expr;
|
|
}
|
|
|
|
args++;
|
|
}
|
|
else if (strcmp(*args, "-f") == 0) {
|
|
if (!expr->pat.parse) {
|
|
memprintf(err, "matching method must be specified first (using '-m') when using a sample fetch of this type ('%s')", expr->kw);
|
|
goto out_free_expr;
|
|
}
|
|
|
|
if (!pattern_read_from_file(&expr->pat, refflags, args[1], patflags, load_as_map, err, file, line))
|
|
goto out_free_expr;
|
|
is_loaded = 1;
|
|
args++;
|
|
}
|
|
else if (strcmp(*args, "-m") == 0) {
|
|
int idx;
|
|
|
|
if (is_loaded) {
|
|
memprintf(err, "'-m' must only be specified before patterns and files in parsing ACL expression");
|
|
goto out_free_expr;
|
|
}
|
|
|
|
idx = pat_find_match_name(args[1]);
|
|
if (idx < 0) {
|
|
memprintf(err, "unknown matching method '%s' when parsing ACL expression", args[1]);
|
|
goto out_free_expr;
|
|
}
|
|
|
|
/* Note: -m found is always valid, bool/int are compatible, str/bin/reg/len are compatible */
|
|
if (idx != PAT_MATCH_FOUND && !sample_casts[cur_type][pat_match_types[idx]]) {
|
|
memprintf(err, "matching method '%s' cannot be used with fetch keyword '%s'", args[1], expr->kw);
|
|
goto out_free_expr;
|
|
}
|
|
expr->pat.parse = pat_parse_fcts[idx];
|
|
expr->pat.index = pat_index_fcts[idx];
|
|
expr->pat.match = pat_match_fcts[idx];
|
|
expr->pat.prune = pat_prune_fcts[idx];
|
|
expr->pat.expect_type = pat_match_types[idx];
|
|
args++;
|
|
}
|
|
else if (strcmp(*args, "-M") == 0) {
|
|
refflags |= PAT_REF_MAP;
|
|
load_as_map = 1;
|
|
}
|
|
else if (strcmp(*args, "--") == 0) {
|
|
args++;
|
|
break;
|
|
}
|
|
else {
|
|
memprintf(err, "'%s' is not a valid ACL option. Please use '--' before any pattern beginning with a '-'", args[0]);
|
|
goto out_free_expr;
|
|
break;
|
|
}
|
|
args++;
|
|
}
|
|
|
|
if (!expr->pat.parse) {
|
|
memprintf(err, "matching method must be specified first (using '-m') when using a sample fetch of this type ('%s')", expr->kw);
|
|
goto out_free_expr;
|
|
}
|
|
|
|
/* Create displayed reference */
|
|
snprintf(trash.area, trash.size, "acl '%s' file '%s' line %d",
|
|
expr->kw, file, line);
|
|
trash.area[trash.size - 1] = '\0';
|
|
|
|
/* Create new pattern reference. */
|
|
ref = pat_ref_newid(unique_id, trash.area, PAT_REF_ACL);
|
|
if (!ref) {
|
|
memprintf(err, "memory error");
|
|
goto out_free_expr;
|
|
}
|
|
|
|
/* Create new pattern expression associated to this reference. */
|
|
pattern_expr = pattern_new_expr(&expr->pat, ref, patflags, err, NULL);
|
|
if (!pattern_expr)
|
|
goto out_free_expr;
|
|
|
|
/* now parse all patterns */
|
|
while (**args) {
|
|
arg = *args;
|
|
|
|
/* Compatibility layer. Each pattern can parse only one string per pattern,
|
|
* but the pat_parser_int() and pat_parse_dotted_ver() parsers were need
|
|
* optionally two operators. The first operator is the match method: eq,
|
|
* le, lt, ge and gt. pat_parse_int() and pat_parse_dotted_ver() functions
|
|
* can have a compatibility syntax based on ranges:
|
|
*
|
|
* pat_parse_int():
|
|
*
|
|
* "eq x" -> "x" or "x:x"
|
|
* "le x" -> ":x"
|
|
* "lt x" -> ":y" (with y = x - 1)
|
|
* "ge x" -> "x:"
|
|
* "gt x" -> "y:" (with y = x + 1)
|
|
*
|
|
* pat_parse_dotted_ver():
|
|
*
|
|
* "eq x.y" -> "x.y" or "x.y:x.y"
|
|
* "le x.y" -> ":x.y"
|
|
* "lt x.y" -> ":w.z" (with w.z = x.y - 1)
|
|
* "ge x.y" -> "x.y:"
|
|
* "gt x.y" -> "w.z:" (with w.z = x.y + 1)
|
|
*
|
|
* If y is not present, assume that is "0".
|
|
*
|
|
* The syntax eq, le, lt, ge and gt are proper to the acl syntax. The
|
|
* following block of code detect the operator, and rewrite each value
|
|
* in parsable string.
|
|
*/
|
|
if (expr->pat.parse == pat_parse_int ||
|
|
expr->pat.parse == pat_parse_dotted_ver) {
|
|
/* Check for operator. If the argument is operator, memorise it and
|
|
* continue to the next argument.
|
|
*/
|
|
op = get_std_op(arg);
|
|
if (op != -1) {
|
|
operator = op;
|
|
args++;
|
|
continue;
|
|
}
|
|
|
|
/* Check if the pattern contain ':' or '-' character. */
|
|
contain_colon = (strchr(arg, ':') || strchr(arg, '-'));
|
|
|
|
/* If the pattern contain ':' or '-' character, give it to the parser as is.
|
|
* If no contain ':' and operator is STD_OP_EQ, give it to the parser as is.
|
|
* In other case, try to convert the value according with the operator.
|
|
*/
|
|
if (!contain_colon && operator != STD_OP_EQ) {
|
|
/* Search '.' separator. */
|
|
dot = strchr(arg, '.');
|
|
if (!dot) {
|
|
have_dot = 0;
|
|
minor = 0;
|
|
dot = arg + strlen(arg);
|
|
}
|
|
else
|
|
have_dot = 1;
|
|
|
|
/* convert the integer minor part for the pat_parse_dotted_ver() function. */
|
|
if (expr->pat.parse == pat_parse_dotted_ver && have_dot) {
|
|
if (strl2llrc(dot+1, strlen(dot+1), &minor) != 0) {
|
|
memprintf(err, "'%s' is neither a number nor a supported operator", arg);
|
|
goto out_free_expr;
|
|
}
|
|
if (minor >= 65536) {
|
|
memprintf(err, "'%s' contains too large a minor value", arg);
|
|
goto out_free_expr;
|
|
}
|
|
}
|
|
|
|
/* convert the integer value for the pat_parse_int() function, and the
|
|
* integer major part for the pat_parse_dotted_ver() function.
|
|
*/
|
|
if (strl2llrc(arg, dot - arg, &value) != 0) {
|
|
memprintf(err, "'%s' is neither a number nor a supported operator", arg);
|
|
goto out_free_expr;
|
|
}
|
|
if (expr->pat.parse == pat_parse_dotted_ver) {
|
|
if (value >= 65536) {
|
|
memprintf(err, "'%s' contains too large a major value", arg);
|
|
goto out_free_expr;
|
|
}
|
|
value = (value << 16) | (minor & 0xffff);
|
|
}
|
|
|
|
switch (operator) {
|
|
|
|
case STD_OP_EQ: /* this case is not possible. */
|
|
memprintf(err, "internal error");
|
|
goto out_free_expr;
|
|
|
|
case STD_OP_GT:
|
|
value++; /* gt = ge + 1 */
|
|
/* fall through */
|
|
|
|
case STD_OP_GE:
|
|
if (expr->pat.parse == pat_parse_int)
|
|
snprintf(buffer, NB_LLMAX_STR+NB_LLMAX_STR+2, "%lld:", value);
|
|
else
|
|
snprintf(buffer, NB_LLMAX_STR+NB_LLMAX_STR+2, "%lld.%lld:",
|
|
value >> 16, value & 0xffff);
|
|
arg = buffer;
|
|
break;
|
|
|
|
case STD_OP_LT:
|
|
value--; /* lt = le - 1 */
|
|
/* fall through */
|
|
|
|
case STD_OP_LE:
|
|
if (expr->pat.parse == pat_parse_int)
|
|
snprintf(buffer, NB_LLMAX_STR+NB_LLMAX_STR+2, ":%lld", value);
|
|
else
|
|
snprintf(buffer, NB_LLMAX_STR+NB_LLMAX_STR+2, ":%lld.%lld",
|
|
value >> 16, value & 0xffff);
|
|
arg = buffer;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Add sample to the reference, and try to compile it fior each pattern
|
|
* using this value.
|
|
*/
|
|
if (!pat_ref_add(ref, arg, NULL, err))
|
|
goto out_free_expr;
|
|
args++;
|
|
}
|
|
|
|
return expr;
|
|
|
|
out_free_expr:
|
|
prune_acl_expr(expr);
|
|
free(expr);
|
|
out_free_smp:
|
|
free(ckw);
|
|
free(smp);
|
|
out_return:
|
|
return NULL;
|
|
}
|
|
|
|
/* Purge everything in the acl <acl>, then return <acl>. */
|
|
struct acl *prune_acl(struct acl *acl) {
|
|
|
|
struct acl_expr *expr, *exprb;
|
|
|
|
free(acl->name);
|
|
|
|
list_for_each_entry_safe(expr, exprb, &acl->expr, list) {
|
|
LIST_DELETE(&expr->list);
|
|
prune_acl_expr(expr);
|
|
free(expr);
|
|
}
|
|
|
|
return acl;
|
|
}
|
|
|
|
/* 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. If the ACL has an empty name, then it's
|
|
* an anonymous one and it won't be merged with any other one. If <err> is not
|
|
* NULL, it will be filled with an appropriate error. This pointer must be
|
|
* freeable or NULL. <al> is the arg_list serving as a head for unresolved
|
|
* dependencies. It may be NULL if such dependencies are not allowed.
|
|
*
|
|
* args syntax: <aclname> <acl_expr>
|
|
*/
|
|
struct acl *parse_acl(const char **args, struct list *known_acl, char **err, struct arg_list *al,
|
|
const char *file, int line)
|
|
{
|
|
__label__ out_return, out_free_acl_expr, out_free_name;
|
|
struct acl *cur_acl;
|
|
struct acl_expr *acl_expr;
|
|
char *name;
|
|
const char *pos;
|
|
|
|
if (**args && (pos = invalid_char(*args))) {
|
|
memprintf(err, "invalid character in ACL name : '%c'", *pos);
|
|
goto out_return;
|
|
}
|
|
|
|
acl_expr = parse_acl_expr(args + 1, err, al, file, line);
|
|
if (!acl_expr) {
|
|
/* parse_acl_expr will have filled <err> here */
|
|
goto out_return;
|
|
}
|
|
|
|
/* Check for args beginning with an opening parenthesis just after the
|
|
* subject, as this is almost certainly a typo. Right now we can only
|
|
* emit a warning, so let's do so.
|
|
*/
|
|
if (!strchr(args[1], '(') && *args[2] == '(')
|
|
ha_warning("parsing acl '%s' :\n"
|
|
" matching '%s' for pattern '%s' is likely a mistake and probably\n"
|
|
" not what you want. Maybe you need to remove the extraneous space before '('.\n"
|
|
" If you are really sure this is not an error, please insert '--' between the\n"
|
|
" match and the pattern to make this warning message disappear.\n",
|
|
args[0], args[1], args[2]);
|
|
|
|
if (*args[0])
|
|
cur_acl = find_acl_by_name(args[0], known_acl);
|
|
else
|
|
cur_acl = NULL;
|
|
|
|
if (!cur_acl) {
|
|
name = strdup(args[0]);
|
|
if (!name) {
|
|
memprintf(err, "out of memory when parsing ACL");
|
|
goto out_free_acl_expr;
|
|
}
|
|
cur_acl = calloc(1, sizeof(*cur_acl));
|
|
if (cur_acl == NULL) {
|
|
memprintf(err, "out of memory when parsing ACL");
|
|
goto out_free_name;
|
|
}
|
|
|
|
LIST_INIT(&cur_acl->expr);
|
|
LIST_APPEND(known_acl, &cur_acl->list);
|
|
cur_acl->name = name;
|
|
}
|
|
|
|
/* We want to know what features the ACL needs (typically HTTP parsing),
|
|
* and where it may be used. If an ACL relies on multiple matches, it is
|
|
* OK if at least one of them may match in the context where it is used.
|
|
*/
|
|
cur_acl->use |= acl_expr->smp->fetch->use;
|
|
cur_acl->val |= acl_expr->smp->fetch->val;
|
|
LIST_APPEND(&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;
|
|
}
|
|
|
|
/* Some useful ACLs provided by default. Only those used are allocated. */
|
|
|
|
const struct {
|
|
const char *name;
|
|
const char *expr[4]; /* put enough for longest expression */
|
|
} default_acl_list[] = {
|
|
{ .name = "TRUE", .expr = {"always_true",""}},
|
|
{ .name = "FALSE", .expr = {"always_false",""}},
|
|
{ .name = "LOCALHOST", .expr = {"src","127.0.0.1/8",""}},
|
|
{ .name = "HTTP", .expr = {"req.proto_http",""}},
|
|
{ .name = "HTTP_1.0", .expr = {"req.ver","1.0",""}},
|
|
{ .name = "HTTP_1.1", .expr = {"req.ver","1.1",""}},
|
|
{ .name = "HTTP_2.0", .expr = {"req.ver","2.0",""}},
|
|
{ .name = "METH_CONNECT", .expr = {"method","CONNECT",""}},
|
|
{ .name = "METH_DELETE", .expr = {"method","DELETE",""}},
|
|
{ .name = "METH_GET", .expr = {"method","GET","HEAD",""}},
|
|
{ .name = "METH_HEAD", .expr = {"method","HEAD",""}},
|
|
{ .name = "METH_OPTIONS", .expr = {"method","OPTIONS",""}},
|
|
{ .name = "METH_POST", .expr = {"method","POST",""}},
|
|
{ .name = "METH_PUT", .expr = {"method","PUT",""}},
|
|
{ .name = "METH_TRACE", .expr = {"method","TRACE",""}},
|
|
{ .name = "HTTP_URL_ABS", .expr = {"url_reg","^[^/:]*://",""}},
|
|
{ .name = "HTTP_URL_SLASH", .expr = {"url_beg","/",""}},
|
|
{ .name = "HTTP_URL_STAR", .expr = {"url","*",""}},
|
|
{ .name = "HTTP_CONTENT", .expr = {"req.hdr_val(content-length)","gt","0",""}},
|
|
{ .name = "RDP_COOKIE", .expr = {"req.rdp_cookie_cnt","gt","0",""}},
|
|
{ .name = "REQ_CONTENT", .expr = {"req.len","gt","0",""}},
|
|
{ .name = "WAIT_END", .expr = {"wait_end",""}},
|
|
{ .name = NULL, .expr = {""}}
|
|
};
|
|
|
|
/* Find a default ACL from the default_acl list, compile it and return it.
|
|
* If the ACL is not found, NULL is returned. In theory, it cannot fail,
|
|
* except when default ACLs are broken, in which case it will return NULL.
|
|
* If <known_acl> is not NULL, the ACL will be queued at its tail. If <err> is
|
|
* not NULL, it will be filled with an error message if an error occurs. This
|
|
* pointer must be freeable or NULL. <al> is an arg_list serving as a list head
|
|
* to report missing dependencies. It may be NULL if such dependencies are not
|
|
* allowed.
|
|
*/
|
|
static struct acl *find_acl_default(const char *acl_name, struct list *known_acl,
|
|
char **err, struct arg_list *al,
|
|
const char *file, int line)
|
|
{
|
|
__label__ out_return, out_free_acl_expr, out_free_name;
|
|
struct acl *cur_acl;
|
|
struct acl_expr *acl_expr;
|
|
char *name;
|
|
int index;
|
|
|
|
for (index = 0; default_acl_list[index].name != NULL; index++) {
|
|
if (strcmp(acl_name, default_acl_list[index].name) == 0)
|
|
break;
|
|
}
|
|
|
|
if (default_acl_list[index].name == NULL) {
|
|
memprintf(err, "no such ACL : '%s'", acl_name);
|
|
return NULL;
|
|
}
|
|
|
|
acl_expr = parse_acl_expr((const char **)default_acl_list[index].expr, err, al, file, line);
|
|
if (!acl_expr) {
|
|
/* parse_acl_expr must have filled err here */
|
|
goto out_return;
|
|
}
|
|
|
|
name = strdup(acl_name);
|
|
if (!name) {
|
|
memprintf(err, "out of memory when building default ACL '%s'", acl_name);
|
|
goto out_free_acl_expr;
|
|
}
|
|
|
|
cur_acl = calloc(1, sizeof(*cur_acl));
|
|
if (cur_acl == NULL) {
|
|
memprintf(err, "out of memory when building default ACL '%s'", acl_name);
|
|
goto out_free_name;
|
|
}
|
|
|
|
cur_acl->name = name;
|
|
cur_acl->use |= acl_expr->smp->fetch->use;
|
|
cur_acl->val |= acl_expr->smp->fetch->val;
|
|
LIST_INIT(&cur_acl->expr);
|
|
LIST_APPEND(&cur_acl->expr, &acl_expr->list);
|
|
if (known_acl)
|
|
LIST_APPEND(known_acl, &cur_acl->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". If
|
|
* <err> is not NULL, it will be filled with a pointer to an error message in
|
|
* case of error, that the caller is responsible for freeing. The initial
|
|
* location must either be freeable or NULL. The list <al> serves as a list head
|
|
* for unresolved dependencies. It may be NULL if such dependencies are not
|
|
* allowed.
|
|
*/
|
|
struct acl_cond *parse_acl_cond(const char **args, struct list *known_acl,
|
|
enum acl_cond_pol pol, char **err, struct arg_list *al,
|
|
const char *file, int line)
|
|
{
|
|
__label__ out_return, out_free_suite, out_free_term;
|
|
int arg, neg;
|
|
const char *word;
|
|
struct acl *cur_acl;
|
|
struct acl_term *cur_term;
|
|
struct acl_term_suite *cur_suite;
|
|
struct acl_cond *cond;
|
|
unsigned int suite_val;
|
|
|
|
cond = calloc(1, sizeof(*cond));
|
|
if (cond == NULL) {
|
|
memprintf(err, "out of memory when parsing condition");
|
|
goto out_return;
|
|
}
|
|
|
|
LIST_INIT(&cond->list);
|
|
LIST_INIT(&cond->suites);
|
|
cond->pol = pol;
|
|
cond->val = 0;
|
|
|
|
cur_suite = NULL;
|
|
suite_val = ~0U;
|
|
neg = 0;
|
|
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 || strcmp(word, "||") == 0) {
|
|
/* new term suite */
|
|
cond->val |= suite_val;
|
|
suite_val = ~0U;
|
|
cur_suite = NULL;
|
|
neg = 0;
|
|
continue;
|
|
}
|
|
|
|
if (strcmp(word, "{") == 0) {
|
|
/* we may have a complete ACL expression between two braces,
|
|
* find the last one.
|
|
*/
|
|
int arg_end = arg + 1;
|
|
const char **args_new;
|
|
|
|
while (*args[arg_end] && strcmp(args[arg_end], "}") != 0)
|
|
arg_end++;
|
|
|
|
if (!*args[arg_end]) {
|
|
memprintf(err, "missing closing '}' in condition");
|
|
goto out_free_suite;
|
|
}
|
|
|
|
args_new = calloc(1, (arg_end - arg + 1) * sizeof(*args_new));
|
|
if (!args_new) {
|
|
memprintf(err, "out of memory when parsing condition");
|
|
goto out_free_suite;
|
|
}
|
|
|
|
args_new[0] = "";
|
|
memcpy(args_new + 1, args + arg + 1, (arg_end - arg) * sizeof(*args_new));
|
|
args_new[arg_end - arg] = "";
|
|
cur_acl = parse_acl(args_new, known_acl, err, al, file, line);
|
|
free(args_new);
|
|
|
|
if (!cur_acl) {
|
|
/* note that parse_acl() must have filled <err> here */
|
|
goto out_free_suite;
|
|
}
|
|
arg = arg_end;
|
|
}
|
|
else {
|
|
/* search for <word> in the known ACL names. If we do not find
|
|
* it, let's look for it in the default ACLs, and if found, add
|
|
* it to the list of ACLs of this proxy. This makes it possible
|
|
* to override them.
|
|
*/
|
|
cur_acl = find_acl_by_name(word, known_acl);
|
|
if (cur_acl == NULL) {
|
|
cur_acl = find_acl_default(word, known_acl, err, al, file, line);
|
|
if (cur_acl == NULL) {
|
|
/* note that find_acl_default() must have filled <err> here */
|
|
goto out_free_suite;
|
|
}
|
|
}
|
|
}
|
|
|
|
cur_term = calloc(1, sizeof(*cur_term));
|
|
if (cur_term == NULL) {
|
|
memprintf(err, "out of memory when parsing condition");
|
|
goto out_free_suite;
|
|
}
|
|
|
|
cur_term->acl = cur_acl;
|
|
cur_term->neg = neg;
|
|
|
|
/* Here it is a bit complex. The acl_term_suite is a conjunction
|
|
* of many terms. It may only be used if all of its terms are
|
|
* usable at the same time. So the suite's validity domain is an
|
|
* AND between all ACL keywords' ones. But, the global condition
|
|
* is valid if at least one term suite is OK. So it's an OR between
|
|
* all of their validity domains. We could emit a warning as soon
|
|
* as suite_val is null because it means that the last ACL is not
|
|
* compatible with the previous ones. Let's remain simple for now.
|
|
*/
|
|
cond->use |= cur_acl->use;
|
|
suite_val &= cur_acl->val;
|
|
|
|
if (!cur_suite) {
|
|
cur_suite = calloc(1, sizeof(*cur_suite));
|
|
if (cur_suite == NULL) {
|
|
memprintf(err, "out of memory when parsing condition");
|
|
goto out_free_term;
|
|
}
|
|
LIST_INIT(&cur_suite->terms);
|
|
LIST_APPEND(&cond->suites, &cur_suite->list);
|
|
}
|
|
LIST_APPEND(&cur_suite->terms, &cur_term->list);
|
|
neg = 0;
|
|
}
|
|
|
|
cond->val |= suite_val;
|
|
return cond;
|
|
|
|
out_free_term:
|
|
free(cur_term);
|
|
out_free_suite:
|
|
prune_acl_cond(cond);
|
|
free(cond);
|
|
out_return:
|
|
return NULL;
|
|
}
|
|
|
|
/* Builds an ACL condition starting at the if/unless keyword. The complete
|
|
* condition is returned. NULL is returned in case of error or if the first
|
|
* word is neither "if" nor "unless". It automatically sets the file name and
|
|
* the line number in the condition for better error reporting, and sets the
|
|
* HTTP intiailization requirements in the proxy. If <err> is not NULL, it will
|
|
* be filled with a pointer to an error message in case of error, that the
|
|
* caller is responsible for freeing. The initial location must either be
|
|
* freeable or NULL.
|
|
*/
|
|
struct acl_cond *build_acl_cond(const char *file, int line, struct list *known_acl,
|
|
struct proxy *px, const char **args, char **err)
|
|
{
|
|
enum acl_cond_pol pol = ACL_COND_NONE;
|
|
struct acl_cond *cond = NULL;
|
|
|
|
if (err)
|
|
*err = NULL;
|
|
|
|
if (strcmp(*args, "if") == 0) {
|
|
pol = ACL_COND_IF;
|
|
args++;
|
|
}
|
|
else if (strcmp(*args, "unless") == 0) {
|
|
pol = ACL_COND_UNLESS;
|
|
args++;
|
|
}
|
|
else {
|
|
memprintf(err, "conditions must start with either 'if' or 'unless'");
|
|
return NULL;
|
|
}
|
|
|
|
cond = parse_acl_cond(args, known_acl, pol, err, &px->conf.args, file, line);
|
|
if (!cond) {
|
|
/* note that parse_acl_cond must have filled <err> here */
|
|
return NULL;
|
|
}
|
|
|
|
cond->file = file;
|
|
cond->line = line;
|
|
px->http_needed |= !!(cond->use & SMP_USE_HTTP_ANY);
|
|
return cond;
|
|
}
|
|
|
|
/* Execute condition <cond> and return either ACL_TEST_FAIL, ACL_TEST_MISS or
|
|
* ACL_TEST_PASS depending on the test results. ACL_TEST_MISS may only be
|
|
* returned if <opt> does not contain SMP_OPT_FINAL, indicating that incomplete
|
|
* data is being examined. The function automatically sets SMP_OPT_ITERATE. 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 using something like this :
|
|
*
|
|
* res = acl_pass(res);
|
|
* if (res == ACL_TEST_MISS)
|
|
* return 0;
|
|
* if (cond->pol == ACL_COND_UNLESS)
|
|
* res = !res;
|
|
*/
|
|
enum acl_test_res acl_exec_cond(struct acl_cond *cond, struct proxy *px, struct session *sess, struct stream *strm, unsigned int opt)
|
|
{
|
|
__label__ fetch_next;
|
|
struct acl_term_suite *suite;
|
|
struct acl_term *term;
|
|
struct acl_expr *expr;
|
|
struct acl *acl;
|
|
struct sample smp;
|
|
enum acl_test_res acl_res, suite_res, cond_res;
|
|
|
|
/* ACLs are iterated over all values, so let's always set the flag to
|
|
* indicate this to the fetch functions.
|
|
*/
|
|
opt |= SMP_OPT_ITERATE;
|
|
|
|
/* We're doing a logical OR between conditions so we initialize to FAIL.
|
|
* The MISS status is propagated down from the suites.
|
|
*/
|
|
cond_res = ACL_TEST_FAIL;
|
|
list_for_each_entry(suite, &cond->suites, list) {
|
|
/* Evaluate condition suite <suite>. We stop at the first term
|
|
* which returns ACL_TEST_FAIL. The MISS status is still propagated
|
|
* in case of uncertainty in the result.
|
|
*/
|
|
|
|
/* we're doing a logical AND between terms, so we must set the
|
|
* initial value to PASS.
|
|
*/
|
|
suite_res = ACL_TEST_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_TEST_FAIL;
|
|
list_for_each_entry(expr, &acl->expr, list) {
|
|
/* we need to reset context and flags */
|
|
memset(&smp, 0, sizeof(smp));
|
|
fetch_next:
|
|
if (!sample_process(px, sess, strm, opt, expr->smp, &smp)) {
|
|
/* maybe we could not fetch because of missing data */
|
|
if (smp.flags & SMP_F_MAY_CHANGE && !(opt & SMP_OPT_FINAL))
|
|
acl_res |= ACL_TEST_MISS;
|
|
continue;
|
|
}
|
|
|
|
acl_res |= pat2acl(pattern_exec_match(&expr->pat, &smp, 0));
|
|
/*
|
|
* OK now acl_res holds the result of this expression
|
|
* as one of ACL_TEST_FAIL, ACL_TEST_MISS or ACL_TEST_PASS.
|
|
*
|
|
* Then if (!MISS) we can cache the result, and put
|
|
* (smp.flags & SMP_F_VOLATILE) in the cache flags.
|
|
*
|
|
* FIXME: implement cache.
|
|
*
|
|
*/
|
|
|
|
/* we're ORing these terms, so a single PASS is enough */
|
|
if (acl_res == ACL_TEST_PASS)
|
|
break;
|
|
|
|
if (smp.flags & SMP_F_NOT_LAST)
|
|
goto fetch_next;
|
|
|
|
/* sometimes we know the fetched data is subject to change
|
|
* later and give another chance for a new match (eg: request
|
|
* size, time, ...)
|
|
*/
|
|
if (smp.flags & SMP_F_MAY_CHANGE && !(opt & SMP_OPT_FINAL))
|
|
acl_res |= ACL_TEST_MISS;
|
|
}
|
|
/*
|
|
* Here we have the result of an ACL (cached or not).
|
|
* ACLs are combined, negated or not, to form conditions.
|
|
*/
|
|
|
|
if (term->neg)
|
|
acl_res = acl_neg(acl_res);
|
|
|
|
suite_res &= acl_res;
|
|
|
|
/* we're ANDing these terms, so a single FAIL or MISS is enough */
|
|
if (suite_res != ACL_TEST_PASS)
|
|
break;
|
|
}
|
|
cond_res |= suite_res;
|
|
|
|
/* we're ORing these terms, so a single PASS is enough */
|
|
if (cond_res == ACL_TEST_PASS)
|
|
break;
|
|
}
|
|
return cond_res;
|
|
}
|
|
|
|
/* Returns a pointer to the first ACL conflicting with usage at place <where>
|
|
* which is one of the SMP_VAL_* bits indicating a check place, or NULL if
|
|
* no conflict is found. Only full conflicts are detected (ACL is not usable).
|
|
* Use the next function to check for useless keywords.
|
|
*/
|
|
const struct acl *acl_cond_conflicts(const struct acl_cond *cond, unsigned int where)
|
|
{
|
|
struct acl_term_suite *suite;
|
|
struct acl_term *term;
|
|
struct acl *acl;
|
|
|
|
list_for_each_entry(suite, &cond->suites, list) {
|
|
list_for_each_entry(term, &suite->terms, list) {
|
|
acl = term->acl;
|
|
if (!(acl->val & where))
|
|
return acl;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Returns a pointer to the first ACL and its first keyword to conflict with
|
|
* usage at place <where> which is one of the SMP_VAL_* bits indicating a check
|
|
* place. Returns true if a conflict is found, with <acl> and <kw> set (if non
|
|
* null), or false if not conflict is found. The first useless keyword is
|
|
* returned.
|
|
*/
|
|
int acl_cond_kw_conflicts(const struct acl_cond *cond, unsigned int where, struct acl const **acl, char const **kw)
|
|
{
|
|
struct acl_term_suite *suite;
|
|
struct acl_term *term;
|
|
struct acl_expr *expr;
|
|
|
|
list_for_each_entry(suite, &cond->suites, list) {
|
|
list_for_each_entry(term, &suite->terms, list) {
|
|
list_for_each_entry(expr, &term->acl->expr, list) {
|
|
if (!(expr->smp->fetch->val & where)) {
|
|
if (acl)
|
|
*acl = term->acl;
|
|
if (kw)
|
|
*kw = expr->kw;
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Find targets for userlist and groups in acl. Function returns the number
|
|
* of errors or OK if everything is fine. It must be called only once sample
|
|
* fetch arguments have been resolved (after smp_resolve_args()).
|
|
*/
|
|
int acl_find_targets(struct proxy *p)
|
|
{
|
|
|
|
struct acl *acl;
|
|
struct acl_expr *expr;
|
|
struct pattern_list *pattern;
|
|
int cfgerr = 0;
|
|
struct pattern_expr_list *pexp;
|
|
|
|
list_for_each_entry(acl, &p->acl, list) {
|
|
list_for_each_entry(expr, &acl->expr, list) {
|
|
if (strcmp(expr->kw, "http_auth_group") == 0) {
|
|
/* Note: the ARGT_USR argument may only have been resolved earlier
|
|
* by smp_resolve_args().
|
|
*/
|
|
if (expr->smp->arg_p->unresolved) {
|
|
ha_alert("Internal bug in proxy %s: %sacl %s %s() makes use of unresolved userlist '%s'. Please report this.\n",
|
|
p->id, *acl->name ? "" : "anonymous ", acl->name, expr->kw,
|
|
expr->smp->arg_p->data.str.area);
|
|
cfgerr++;
|
|
continue;
|
|
}
|
|
|
|
if (LIST_ISEMPTY(&expr->pat.head)) {
|
|
ha_alert("proxy %s: acl %s %s(): no groups specified.\n",
|
|
p->id, acl->name, expr->kw);
|
|
cfgerr++;
|
|
continue;
|
|
}
|
|
|
|
/* For each pattern, check if the group exists. */
|
|
list_for_each_entry(pexp, &expr->pat.head, list) {
|
|
if (LIST_ISEMPTY(&pexp->expr->patterns)) {
|
|
ha_alert("proxy %s: acl %s %s(): no groups specified.\n",
|
|
p->id, acl->name, expr->kw);
|
|
cfgerr++;
|
|
continue;
|
|
}
|
|
|
|
list_for_each_entry(pattern, &pexp->expr->patterns, list) {
|
|
/* this keyword only has one argument */
|
|
if (!check_group(expr->smp->arg_p->data.usr, pattern->pat.ptr.str)) {
|
|
ha_alert("proxy %s: acl %s %s(): invalid group '%s'.\n",
|
|
p->id, acl->name, expr->kw, pattern->pat.ptr.str);
|
|
cfgerr++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return cfgerr;
|
|
}
|
|
|
|
/* initializes ACLs by resolving the sample fetch names they rely upon.
|
|
* Returns 0 on success, otherwise an error.
|
|
*/
|
|
int init_acl()
|
|
{
|
|
int err = 0;
|
|
int index;
|
|
const char *name;
|
|
struct acl_kw_list *kwl;
|
|
struct sample_fetch *smp;
|
|
|
|
list_for_each_entry(kwl, &acl_keywords.list, list) {
|
|
for (index = 0; kwl->kw[index].kw != NULL; index++) {
|
|
name = kwl->kw[index].fetch_kw;
|
|
if (!name)
|
|
name = kwl->kw[index].kw;
|
|
|
|
smp = find_sample_fetch(name, strlen(name));
|
|
if (!smp) {
|
|
ha_alert("Critical internal error: ACL keyword '%s' relies on sample fetch '%s' which was not registered!\n",
|
|
kwl->kw[index].kw, name);
|
|
err++;
|
|
continue;
|
|
}
|
|
kwl->kw[index].smp = smp;
|
|
}
|
|
}
|
|
return err;
|
|
}
|
|
|
|
void free_acl_cond(struct acl_cond *cond)
|
|
{
|
|
struct acl_term_suite *suite, *suiteb;
|
|
struct acl_term *term, *termb;
|
|
|
|
if (!cond)
|
|
return;
|
|
|
|
list_for_each_entry_safe(suite, suiteb, &cond->suites, list) {
|
|
list_for_each_entry_safe(term, termb, &suite->terms, list) {
|
|
LIST_DELETE(&term->list);
|
|
free(term);
|
|
}
|
|
LIST_DELETE(&suite->list);
|
|
free(suite);
|
|
}
|
|
|
|
free(cond);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* All supported sample and ACL keywords must be declared here. */
|
|
/************************************************************************/
|
|
|
|
/* Note: must not be declared <const> as its list will be overwritten.
|
|
* Please take care of keeping this list alphabetically sorted.
|
|
*/
|
|
static struct acl_kw_list acl_kws = {ILH, {
|
|
{ /* END */ },
|
|
}};
|
|
|
|
INITCALL1(STG_REGISTER, acl_register_keywords, &acl_kws);
|
|
|
|
/*
|
|
* Local variables:
|
|
* c-indent-level: 8
|
|
* c-basic-offset: 8
|
|
* End:
|
|
*/
|