Line | Count | Source |
1 | | /* |
2 | | * ACL management functions. |
3 | | * |
4 | | * Copyright 2000-2013 Willy Tarreau <w@1wt.eu> |
5 | | * |
6 | | * This program is free software; you can redistribute it and/or |
7 | | * modify it under the terms of the GNU General Public License |
8 | | * as published by the Free Software Foundation; either version |
9 | | * 2 of the License, or (at your option) any later version. |
10 | | * |
11 | | */ |
12 | | |
13 | | #include <ctype.h> |
14 | | #include <stdio.h> |
15 | | #include <string.h> |
16 | | |
17 | | #include <import/ebsttree.h> |
18 | | |
19 | | #include <haproxy/acl.h> |
20 | | #include <haproxy/api.h> |
21 | | #include <haproxy/arg.h> |
22 | | #include <haproxy/auth.h> |
23 | | #include <haproxy/errors.h> |
24 | | #include <haproxy/global.h> |
25 | | #include <haproxy/list.h> |
26 | | #include <haproxy/pattern.h> |
27 | | #include <haproxy/proxy-t.h> |
28 | | #include <haproxy/sample.h> |
29 | | #include <haproxy/stick_table.h> |
30 | | #include <haproxy/tools.h> |
31 | | #include <haproxy/cfgparse.h> |
32 | | |
33 | | /* List head of all known ACL keywords */ |
34 | | static struct acl_kw_list acl_keywords = { |
35 | | .list = LIST_HEAD_INIT(acl_keywords.list) |
36 | | }; |
37 | | |
38 | | /* input values are 0 or 3, output is the same */ |
39 | | static inline enum acl_test_res pat2acl(struct pattern *pat) |
40 | 0 | { |
41 | 0 | if (pat) |
42 | 0 | return ACL_TEST_PASS; |
43 | 0 | else |
44 | 0 | return ACL_TEST_FAIL; |
45 | 0 | } |
46 | | |
47 | | /* |
48 | | * Registers the ACL keyword list <kwl> as a list of valid keywords for next |
49 | | * parsing sessions. |
50 | | */ |
51 | | void acl_register_keywords(struct acl_kw_list *kwl) |
52 | 0 | { |
53 | 0 | LIST_APPEND(&acl_keywords.list, &kwl->list); |
54 | 0 | } |
55 | | |
56 | | /* |
57 | | * Unregisters the ACL keyword list <kwl> from the list of valid keywords. |
58 | | */ |
59 | | void acl_unregister_keywords(struct acl_kw_list *kwl) |
60 | 0 | { |
61 | 0 | LIST_DELETE(&kwl->list); |
62 | 0 | LIST_INIT(&kwl->list); |
63 | 0 | } |
64 | | |
65 | | /* Return a pointer to the ACL <name> within the list starting at <head>, or |
66 | | * NULL if not found. |
67 | | */ |
68 | | struct acl *find_acl_by_name(const char *name, struct list *head) |
69 | 0 | { |
70 | 0 | struct acl *acl; |
71 | 0 | list_for_each_entry(acl, head, list) { |
72 | 0 | if (strcmp(acl->name, name) == 0) |
73 | 0 | return acl; |
74 | 0 | } |
75 | 0 | return NULL; |
76 | 0 | } |
77 | | |
78 | | /* Return a pointer to the ACL keyword <kw>, or NULL if not found. Note that if |
79 | | * <kw> contains an opening parenthesis or a comma, only the left part of it is |
80 | | * checked. |
81 | | */ |
82 | | struct acl_keyword *find_acl_kw(const char *kw) |
83 | 0 | { |
84 | 0 | int index; |
85 | 0 | const char *kwend; |
86 | 0 | struct acl_kw_list *kwl; |
87 | |
|
88 | 0 | kwend = kw; |
89 | 0 | while (is_idchar(*kwend)) |
90 | 0 | kwend++; |
91 | |
|
92 | 0 | list_for_each_entry(kwl, &acl_keywords.list, list) { |
93 | 0 | for (index = 0; kwl->kw[index].kw != NULL; index++) { |
94 | 0 | if ((strncmp(kwl->kw[index].kw, kw, kwend - kw) == 0) && |
95 | 0 | kwl->kw[index].kw[kwend-kw] == 0) |
96 | 0 | return &kwl->kw[index]; |
97 | 0 | } |
98 | 0 | } |
99 | 0 | return NULL; |
100 | 0 | } |
101 | | |
102 | | static struct acl_expr *prune_acl_expr(struct acl_expr *expr) |
103 | 0 | { |
104 | 0 | struct arg *arg; |
105 | |
|
106 | 0 | pattern_prune(&expr->pat); |
107 | |
|
108 | 0 | for (arg = expr->smp->arg_p; arg; arg++) { |
109 | 0 | if (arg->type == ARGT_STOP) |
110 | 0 | break; |
111 | 0 | if (arg->type == ARGT_STR || arg->unresolved) { |
112 | 0 | chunk_destroy(&arg->data.str); |
113 | 0 | arg->unresolved = 0; |
114 | 0 | } |
115 | 0 | } |
116 | |
|
117 | 0 | release_sample_expr(expr->smp); |
118 | |
|
119 | 0 | return expr; |
120 | 0 | } |
121 | | |
122 | | /* Parse an ACL expression starting at <args>[0], and return it. If <err> is |
123 | | * not NULL, it will be filled with a pointer to an error message in case of |
124 | | * error. This pointer must be freeable or NULL. <al> is an arg_list serving |
125 | | * as a list head to report missing dependencies. It may be NULL if such |
126 | | * dependencies are not allowed. |
127 | | * |
128 | | * Right now, the only accepted syntax is : |
129 | | * <subject> [<value>...] |
130 | | */ |
131 | | struct acl_expr *parse_acl_expr(const char **args, char **err, struct arg_list *al, |
132 | | const char *file, int line) |
133 | 0 | { |
134 | 0 | __label__ out_return, out_free_expr; |
135 | 0 | struct acl_expr *expr; |
136 | 0 | struct acl_keyword *aclkw; |
137 | 0 | int refflags, patflags; |
138 | 0 | const char *arg; |
139 | 0 | struct sample_expr *smp = NULL; |
140 | 0 | int idx = 0; |
141 | 0 | char *ckw = NULL; |
142 | 0 | const char *endt; |
143 | 0 | int cur_type; |
144 | 0 | int nbargs; |
145 | 0 | int operator = STD_OP_EQ; |
146 | 0 | int op; |
147 | 0 | int contain_colon, have_dot; |
148 | 0 | const char *dot; |
149 | 0 | signed long long value, minor; |
150 | | /* The following buffer contain two numbers, a ':' separator and the final \0. */ |
151 | 0 | char buffer[NB_LLMAX_STR + 1 + NB_LLMAX_STR + 1]; |
152 | 0 | int is_loaded, match_forced; |
153 | 0 | int unique_id; |
154 | 0 | char *error; |
155 | 0 | struct pat_ref *ref; |
156 | 0 | struct pattern_expr *pattern_expr; |
157 | 0 | int load_as_map = 0; |
158 | 0 | int acl_conv_found = 0; |
159 | | |
160 | | /* First, we look for an ACL keyword. And if we don't find one, then |
161 | | * we look for a sample fetch expression starting with a sample fetch |
162 | | * keyword. |
163 | | */ |
164 | |
|
165 | 0 | if (al) { |
166 | 0 | al->ctx = ARGC_ACL; // to report errors while resolving args late |
167 | 0 | al->conv = NULL; |
168 | 0 | } |
169 | |
|
170 | 0 | aclkw = find_acl_kw(args[0]); |
171 | 0 | if (aclkw) { |
172 | | /* OK we have a real ACL keyword */ |
173 | |
|
174 | 0 | if (al) |
175 | 0 | al->kw = aclkw->kw; |
176 | | |
177 | | /* build new sample expression for this ACL */ |
178 | 0 | smp = calloc(1, sizeof(*smp)); |
179 | 0 | if (!smp) { |
180 | 0 | memprintf(err, "out of memory when parsing ACL expression"); |
181 | 0 | goto out_return; |
182 | 0 | } |
183 | 0 | LIST_INIT(&(smp->conv_exprs)); |
184 | 0 | smp->fetch = aclkw->smp; |
185 | 0 | smp->arg_p = empty_arg_list; |
186 | | |
187 | | /* look for the beginning of the subject arguments */ |
188 | 0 | for (arg = args[0]; is_idchar(*arg); arg++) |
189 | 0 | ; |
190 | | |
191 | | /* At this point, we have : |
192 | | * - args[0] : beginning of the keyword |
193 | | * - arg : end of the keyword, first character not part of keyword |
194 | | */ |
195 | 0 | nbargs = make_arg_list(arg, -1, smp->fetch->arg_mask, &smp->arg_p, |
196 | 0 | err, &endt, NULL, al); |
197 | 0 | if (nbargs < 0) { |
198 | | /* note that make_arg_list will have set <err> here */ |
199 | 0 | memprintf(err, "ACL keyword '%s' : %s", aclkw->kw, *err); |
200 | 0 | goto out_free_smp; |
201 | 0 | } |
202 | | |
203 | 0 | if (!smp->arg_p) { |
204 | 0 | smp->arg_p = empty_arg_list; |
205 | 0 | } |
206 | 0 | else if (smp->fetch->val_args && !smp->fetch->val_args(smp->arg_p, err)) { |
207 | | /* invalid keyword argument, error must have been |
208 | | * set by val_args(). |
209 | | */ |
210 | 0 | memprintf(err, "in argument to '%s', %s", aclkw->kw, *err); |
211 | 0 | goto out_free_smp; |
212 | 0 | } |
213 | | |
214 | | /* look for the beginning of the converters list. Those directly attached |
215 | | * to the ACL keyword are found just after the comma. |
216 | | * If we find any converter, then we don't use the ACL keyword's match |
217 | | * anymore but the one related to the converter's output type. |
218 | | */ |
219 | 0 | if (!sample_parse_expr_cnv((char **)args, NULL, NULL, err, al, file, line, smp, endt)) { |
220 | 0 | if (err) |
221 | 0 | memprintf(err, "ACL keyword '%s' : %s", aclkw->kw, *err); |
222 | 0 | goto out_free_smp; |
223 | 0 | } |
224 | 0 | acl_conv_found = !LIST_ISEMPTY(&smp->conv_exprs); |
225 | 0 | } |
226 | 0 | else { |
227 | | /* This is not an ACL keyword, so we hope this is a sample fetch |
228 | | * keyword that we're going to transparently use as an ACL. If |
229 | | * so, we retrieve a completely parsed expression with args and |
230 | | * convs already done. |
231 | | */ |
232 | 0 | smp = sample_parse_expr((char **)args, &idx, file, line, err, al, NULL); |
233 | 0 | if (!smp) { |
234 | 0 | memprintf(err, "%s in ACL expression '%s'", *err, *args); |
235 | 0 | goto out_return; |
236 | 0 | } |
237 | 0 | } |
238 | | |
239 | | /* get last effective output type for smp */ |
240 | 0 | cur_type = smp_expr_output_type(smp); |
241 | |
|
242 | 0 | expr = calloc(1, sizeof(*expr)); |
243 | 0 | if (!expr) { |
244 | 0 | memprintf(err, "out of memory when parsing ACL expression"); |
245 | 0 | goto out_free_smp; |
246 | 0 | } |
247 | | |
248 | 0 | pattern_init_head(&expr->pat); |
249 | |
|
250 | 0 | expr->pat.expect_type = cur_type; |
251 | 0 | expr->smp = smp; |
252 | 0 | expr->kw = smp->fetch->kw; |
253 | 0 | smp = NULL; /* don't free it anymore */ |
254 | |
|
255 | 0 | if (aclkw && !acl_conv_found) { |
256 | 0 | expr->kw = aclkw->kw; |
257 | 0 | expr->pat.parse = aclkw->parse ? aclkw->parse : pat_parse_fcts[aclkw->match_type]; |
258 | 0 | expr->pat.index = aclkw->index ? aclkw->index : pat_index_fcts[aclkw->match_type]; |
259 | 0 | expr->pat.match = aclkw->match ? aclkw->match : pat_match_fcts[aclkw->match_type]; |
260 | 0 | expr->pat.prune = aclkw->prune ? aclkw->prune : pat_prune_fcts[aclkw->match_type]; |
261 | 0 | } |
262 | |
|
263 | 0 | if (!expr->pat.parse) { |
264 | | /* Parse/index/match functions depend on the expression type, |
265 | | * so we have to map them now. Some types can be automatically |
266 | | * converted. |
267 | | */ |
268 | 0 | switch (cur_type) { |
269 | 0 | case SMP_T_BOOL: |
270 | 0 | expr->pat.parse = pat_parse_fcts[PAT_MATCH_BOOL]; |
271 | 0 | expr->pat.index = pat_index_fcts[PAT_MATCH_BOOL]; |
272 | 0 | expr->pat.match = pat_match_fcts[PAT_MATCH_BOOL]; |
273 | 0 | expr->pat.prune = pat_prune_fcts[PAT_MATCH_BOOL]; |
274 | 0 | expr->pat.expect_type = pat_match_types[PAT_MATCH_BOOL]; |
275 | 0 | break; |
276 | 0 | case SMP_T_SINT: |
277 | 0 | expr->pat.parse = pat_parse_fcts[PAT_MATCH_INT]; |
278 | 0 | expr->pat.index = pat_index_fcts[PAT_MATCH_INT]; |
279 | 0 | expr->pat.match = pat_match_fcts[PAT_MATCH_INT]; |
280 | 0 | expr->pat.prune = pat_prune_fcts[PAT_MATCH_INT]; |
281 | 0 | expr->pat.expect_type = pat_match_types[PAT_MATCH_INT]; |
282 | 0 | break; |
283 | 0 | case SMP_T_ADDR: |
284 | 0 | case SMP_T_IPV4: |
285 | 0 | case SMP_T_IPV6: |
286 | 0 | expr->pat.parse = pat_parse_fcts[PAT_MATCH_IP]; |
287 | 0 | expr->pat.index = pat_index_fcts[PAT_MATCH_IP]; |
288 | 0 | expr->pat.match = pat_match_fcts[PAT_MATCH_IP]; |
289 | 0 | expr->pat.prune = pat_prune_fcts[PAT_MATCH_IP]; |
290 | 0 | expr->pat.expect_type = pat_match_types[PAT_MATCH_IP]; |
291 | 0 | break; |
292 | 0 | case SMP_T_STR: |
293 | 0 | expr->pat.parse = pat_parse_fcts[PAT_MATCH_STR]; |
294 | 0 | expr->pat.index = pat_index_fcts[PAT_MATCH_STR]; |
295 | 0 | expr->pat.match = pat_match_fcts[PAT_MATCH_STR]; |
296 | 0 | expr->pat.prune = pat_prune_fcts[PAT_MATCH_STR]; |
297 | 0 | expr->pat.expect_type = pat_match_types[PAT_MATCH_STR]; |
298 | 0 | break; |
299 | 0 | } |
300 | 0 | } |
301 | | |
302 | | /* Additional check to protect against common mistakes */ |
303 | 0 | if (expr->pat.parse && cur_type != SMP_T_BOOL && !*args[1]) { |
304 | 0 | ha_warning("parsing acl keyword '%s' :\n" |
305 | 0 | " no pattern to match against were provided, so this ACL will never match.\n" |
306 | 0 | " If this is what you intended, please add '--' to get rid of this warning.\n" |
307 | 0 | " If you intended to match only for existence, please use '-m found'.\n" |
308 | 0 | " If you wanted to force an int to match as a bool, please use '-m bool'.\n" |
309 | 0 | "\n", |
310 | 0 | args[0]); |
311 | 0 | } |
312 | |
|
313 | 0 | args++; |
314 | | |
315 | | /* check for options before patterns. Supported options are : |
316 | | * -i : ignore case for all patterns by default |
317 | | * -f : read patterns from those files |
318 | | * -m : force matching method (must be used before -f) |
319 | | * -M : load the file as map file |
320 | | * -u : force the unique id of the acl |
321 | | * -- : everything after this is not an option |
322 | | */ |
323 | 0 | refflags = PAT_REF_ACL; |
324 | 0 | patflags = 0; |
325 | 0 | is_loaded = 0; |
326 | 0 | match_forced = 0; |
327 | 0 | unique_id = -1; |
328 | 0 | while (**args == '-') { |
329 | 0 | if (strcmp(*args, "-i") == 0) |
330 | 0 | patflags |= PAT_MF_IGNORE_CASE; |
331 | 0 | else if (strcmp(*args, "-n") == 0) |
332 | 0 | patflags |= PAT_MF_NO_DNS; |
333 | 0 | else if (strcmp(*args, "-u") == 0) { |
334 | 0 | unique_id = strtol(args[1], &error, 10); |
335 | 0 | if (*error != '\0') { |
336 | 0 | memprintf(err, "the argument of -u must be an integer"); |
337 | 0 | goto out_free_expr; |
338 | 0 | } |
339 | | |
340 | | /* Check if this id is really unique. */ |
341 | 0 | if (pat_ref_lookupid(unique_id)) { |
342 | 0 | memprintf(err, "the id is already used"); |
343 | 0 | goto out_free_expr; |
344 | 0 | } |
345 | | |
346 | 0 | args++; |
347 | 0 | } |
348 | 0 | else if (strcmp(*args, "-f") == 0) { |
349 | 0 | if (!expr->pat.parse) { |
350 | 0 | memprintf(err, "matching method must be specified first (using '-m') when using a sample fetch of this type ('%s')", expr->kw); |
351 | 0 | goto out_free_expr; |
352 | 0 | } |
353 | | |
354 | 0 | if (!pattern_read_from_file(&expr->pat, refflags, args[1], patflags, load_as_map, err, file, line)) |
355 | 0 | goto out_free_expr; |
356 | 0 | is_loaded = 1; |
357 | 0 | args++; |
358 | 0 | } |
359 | 0 | else if (strcmp(*args, "-m") == 0) { |
360 | 0 | int idx; |
361 | |
|
362 | 0 | if (is_loaded) { |
363 | 0 | memprintf(err, "'-m' must only be specified before patterns and files in parsing ACL expression"); |
364 | 0 | goto out_free_expr; |
365 | 0 | } |
366 | 0 | if (match_forced) { |
367 | 0 | memprintf(err, "only one explicit matching method can be defined with '-m' parameter." |
368 | 0 | " if migrating from an old version, just keep the last one"); |
369 | 0 | goto out_free_expr; |
370 | 0 | } |
371 | | |
372 | 0 | idx = pat_find_match_name(args[1]); |
373 | 0 | if (idx < 0) { |
374 | 0 | memprintf(err, "unknown matching method '%s' when parsing ACL expression", args[1]); |
375 | 0 | goto out_free_expr; |
376 | 0 | } |
377 | | |
378 | | /* Note: -m found is always valid, bool/int are compatible, str/bin/reg/len are compatible */ |
379 | 0 | if (idx != PAT_MATCH_FOUND && !sample_casts[cur_type][pat_match_types[idx]]) { |
380 | 0 | memprintf(err, "matching method '%s' cannot be used with fetch keyword '%s'", args[1], expr->kw); |
381 | 0 | goto out_free_expr; |
382 | 0 | } |
383 | 0 | expr->pat.parse = pat_parse_fcts[idx]; |
384 | 0 | expr->pat.index = pat_index_fcts[idx]; |
385 | 0 | expr->pat.match = pat_match_fcts[idx]; |
386 | 0 | expr->pat.prune = pat_prune_fcts[idx]; |
387 | 0 | expr->pat.expect_type = pat_match_types[idx]; |
388 | 0 | match_forced = 1; |
389 | 0 | args++; |
390 | 0 | } |
391 | 0 | else if (strcmp(*args, "-M") == 0) { |
392 | 0 | refflags |= PAT_REF_MAP; |
393 | 0 | load_as_map = 1; |
394 | 0 | } |
395 | 0 | else if (strcmp(*args, "--") == 0) { |
396 | 0 | args++; |
397 | 0 | break; |
398 | 0 | } |
399 | 0 | else { |
400 | 0 | memprintf(err, "'%s' is not a valid ACL option. Please use '--' before any pattern beginning with a '-'", args[0]); |
401 | 0 | goto out_free_expr; |
402 | 0 | break; |
403 | 0 | } |
404 | 0 | args++; |
405 | 0 | } |
406 | | |
407 | 0 | if (!expr->pat.parse) { |
408 | 0 | memprintf(err, "matching method must be specified first (using '-m') when using a sample fetch of this type ('%s')", expr->kw); |
409 | 0 | goto out_free_expr; |
410 | 0 | } |
411 | | |
412 | 0 | if (aclkw) { |
413 | 0 | if (((aclkw->match_type == PAT_MATCH_BEG || aclkw->match_type == PAT_MATCH_DIR || aclkw->match_type == PAT_MATCH_DOM || |
414 | 0 | aclkw->match_type == PAT_MATCH_DOM || aclkw->match_type == PAT_MATCH_END || aclkw->match_type == PAT_MATCH_LEN || |
415 | 0 | aclkw->match_type == PAT_MATCH_REG || aclkw->match_type == PAT_MATCH_SUB) && |
416 | 0 | expr->pat.match != pat_match_fcts[aclkw->match_type]) || |
417 | 0 | (aclkw->match && expr->pat.match != aclkw->match)) |
418 | 0 | ha_warning("parsing [%s:%d] : original matching method '%s' was overwritten and will not be applied as expected.\n", |
419 | 0 | file, line, aclkw->kw); |
420 | 0 | } |
421 | | |
422 | | /* Create displayed reference */ |
423 | 0 | snprintf(trash.area, trash.size, "acl '%s' file '%s' line %d", |
424 | 0 | expr->kw, file, line); |
425 | 0 | trash.area[trash.size - 1] = '\0'; |
426 | | |
427 | | /* Create new pattern reference. */ |
428 | 0 | ref = pat_ref_newid(unique_id, trash.area, PAT_REF_ACL); |
429 | 0 | if (!ref) { |
430 | 0 | memprintf(err, "memory error"); |
431 | 0 | goto out_free_expr; |
432 | 0 | } |
433 | | |
434 | | /* Create new pattern expression associated to this reference. */ |
435 | 0 | pattern_expr = pattern_new_expr(&expr->pat, ref, patflags, err, NULL); |
436 | 0 | if (!pattern_expr) |
437 | 0 | goto out_free_expr; |
438 | | |
439 | | /* now parse all patterns */ |
440 | 0 | while (**args) { |
441 | 0 | arg = *args; |
442 | | |
443 | | /* Compatibility layer. Each pattern can parse only one string per pattern, |
444 | | * but the pat_parser_int() and pat_parse_dotted_ver() parsers were need |
445 | | * optionally two operators. The first operator is the match method: eq, |
446 | | * le, lt, ge and gt. pat_parse_int() and pat_parse_dotted_ver() functions |
447 | | * can have a compatibility syntax based on ranges: |
448 | | * |
449 | | * pat_parse_int(): |
450 | | * |
451 | | * "eq x" -> "x" or "x:x" |
452 | | * "le x" -> ":x" |
453 | | * "lt x" -> ":y" (with y = x - 1) |
454 | | * "ge x" -> "x:" |
455 | | * "gt x" -> "y:" (with y = x + 1) |
456 | | * |
457 | | * pat_parse_dotted_ver(): |
458 | | * |
459 | | * "eq x.y" -> "x.y" or "x.y:x.y" |
460 | | * "le x.y" -> ":x.y" |
461 | | * "lt x.y" -> ":w.z" (with w.z = x.y - 1) |
462 | | * "ge x.y" -> "x.y:" |
463 | | * "gt x.y" -> "w.z:" (with w.z = x.y + 1) |
464 | | * |
465 | | * If y is not present, assume that is "0". |
466 | | * |
467 | | * The syntax eq, le, lt, ge and gt are proper to the acl syntax. The |
468 | | * following block of code detect the operator, and rewrite each value |
469 | | * in parsable string. |
470 | | */ |
471 | 0 | if (expr->pat.parse == pat_parse_int || |
472 | 0 | expr->pat.parse == pat_parse_dotted_ver) { |
473 | | /* Check for operator. If the argument is operator, memorise it and |
474 | | * continue to the next argument. |
475 | | */ |
476 | 0 | op = get_std_op(arg); |
477 | 0 | if (op != -1) { |
478 | 0 | operator = op; |
479 | 0 | args++; |
480 | 0 | continue; |
481 | 0 | } |
482 | | |
483 | | /* Check if the pattern contain ':' or '-' character. */ |
484 | 0 | contain_colon = (strchr(arg, ':') || strchr(arg, '-')); |
485 | | |
486 | | /* If the pattern contain ':' or '-' character, give it to the parser as is. |
487 | | * If no contain ':' and operator is STD_OP_EQ, give it to the parser as is. |
488 | | * In other case, try to convert the value according with the operator. |
489 | | */ |
490 | 0 | if (!contain_colon && operator != STD_OP_EQ) { |
491 | | /* Search '.' separator. */ |
492 | 0 | dot = strchr(arg, '.'); |
493 | 0 | if (!dot) { |
494 | 0 | have_dot = 0; |
495 | 0 | minor = 0; |
496 | 0 | dot = arg + strlen(arg); |
497 | 0 | } |
498 | 0 | else |
499 | 0 | have_dot = 1; |
500 | | |
501 | | /* convert the integer minor part for the pat_parse_dotted_ver() function. */ |
502 | 0 | if (expr->pat.parse == pat_parse_dotted_ver && have_dot) { |
503 | 0 | if (strl2llrc(dot+1, strlen(dot+1), &minor) != 0) { |
504 | 0 | memprintf(err, "'%s' is neither a number nor a supported operator", arg); |
505 | 0 | goto out_free_expr; |
506 | 0 | } |
507 | 0 | if (minor >= 65536) { |
508 | 0 | memprintf(err, "'%s' contains too large a minor value", arg); |
509 | 0 | goto out_free_expr; |
510 | 0 | } |
511 | 0 | } |
512 | | |
513 | | /* convert the integer value for the pat_parse_int() function, and the |
514 | | * integer major part for the pat_parse_dotted_ver() function. |
515 | | */ |
516 | 0 | if (strl2llrc(arg, dot - arg, &value) != 0) { |
517 | 0 | memprintf(err, "'%s' is neither a number nor a supported operator", arg); |
518 | 0 | goto out_free_expr; |
519 | 0 | } |
520 | 0 | if (expr->pat.parse == pat_parse_dotted_ver) { |
521 | 0 | if (value >= 65536) { |
522 | 0 | memprintf(err, "'%s' contains too large a major value", arg); |
523 | 0 | goto out_free_expr; |
524 | 0 | } |
525 | 0 | value = (value << 16) | (minor & 0xffff); |
526 | 0 | } |
527 | | |
528 | 0 | switch (operator) { |
529 | | |
530 | 0 | case STD_OP_EQ: /* this case is not possible. */ |
531 | 0 | memprintf(err, "internal error"); |
532 | 0 | goto out_free_expr; |
533 | | |
534 | 0 | case STD_OP_GT: |
535 | 0 | value++; /* gt = ge + 1 */ |
536 | 0 | __fallthrough; |
537 | |
|
538 | 0 | case STD_OP_GE: |
539 | 0 | if (expr->pat.parse == pat_parse_int) |
540 | 0 | snprintf(buffer, NB_LLMAX_STR+NB_LLMAX_STR+2, "%lld:", value); |
541 | 0 | else |
542 | 0 | snprintf(buffer, NB_LLMAX_STR+NB_LLMAX_STR+2, "%lld.%lld:", |
543 | 0 | value >> 16, value & 0xffff); |
544 | 0 | arg = buffer; |
545 | 0 | break; |
546 | | |
547 | 0 | case STD_OP_LT: |
548 | 0 | value--; /* lt = le - 1 */ |
549 | 0 | __fallthrough; |
550 | |
|
551 | 0 | case STD_OP_LE: |
552 | 0 | if (expr->pat.parse == pat_parse_int) |
553 | 0 | snprintf(buffer, NB_LLMAX_STR+NB_LLMAX_STR+2, ":%lld", value); |
554 | 0 | else |
555 | 0 | snprintf(buffer, NB_LLMAX_STR+NB_LLMAX_STR+2, ":%lld.%lld", |
556 | 0 | value >> 16, value & 0xffff); |
557 | 0 | arg = buffer; |
558 | 0 | break; |
559 | 0 | } |
560 | 0 | } |
561 | 0 | } |
562 | | |
563 | | /* Add sample to the reference, and try to compile it fior each pattern |
564 | | * using this value. |
565 | | */ |
566 | 0 | if (!pat_ref_add(ref, arg, NULL, err)) |
567 | 0 | goto out_free_expr; |
568 | | |
569 | 0 | if (global.mode & MODE_DIAG) { |
570 | 0 | if (strcmp(arg, "&&") == 0 || strcmp(arg, "and") == 0 || |
571 | 0 | strcmp(arg, "||") == 0 || strcmp(arg, "or") == 0) |
572 | 0 | ha_diag_warning("parsing [%s:%d] : pattern '%s' looks like a failed attempt at using an operator inside a pattern list\n", file, line, arg); |
573 | 0 | else if (strcmp(arg, "#") == 0 || strcmp(arg, "//") == 0) |
574 | 0 | ha_diag_warning("parsing [%s:%d] : pattern '%s' looks like a failed attempt at commenting an end of line\n", file, line, arg); |
575 | 0 | else if (find_acl_kw(arg)) |
576 | 0 | ha_diag_warning("parsing [%s:%d] : pattern '%s' suspiciously looks like a known acl keyword\n", file, line, arg); |
577 | 0 | else { |
578 | 0 | const char *begw = arg, *endw; |
579 | |
|
580 | 0 | for (endw = begw; is_idchar(*endw); endw++) |
581 | 0 | ; |
582 | |
|
583 | 0 | if (endw != begw && find_sample_fetch(begw, endw - begw)) |
584 | 0 | ha_diag_warning("parsing [%s:%d] : pattern '%s' suspiciously looks like a known sample fetch keyword\n", file, line, arg); |
585 | 0 | } |
586 | 0 | } |
587 | 0 | args++; |
588 | 0 | } |
589 | | |
590 | 0 | return expr; |
591 | | |
592 | 0 | out_free_expr: |
593 | 0 | prune_acl_expr(expr); |
594 | 0 | free(expr); |
595 | 0 | out_free_smp: |
596 | 0 | free(ckw); |
597 | 0 | free(smp); |
598 | 0 | out_return: |
599 | 0 | return NULL; |
600 | 0 | } |
601 | | |
602 | | /* Purge everything in the acl <acl>, then return <acl>. */ |
603 | 0 | struct acl *prune_acl(struct acl *acl) { |
604 | |
|
605 | 0 | struct acl_expr *expr, *exprb; |
606 | |
|
607 | 0 | free(acl->name); |
608 | |
|
609 | 0 | list_for_each_entry_safe(expr, exprb, &acl->expr, list) { |
610 | 0 | LIST_DELETE(&expr->list); |
611 | 0 | prune_acl_expr(expr); |
612 | 0 | free(expr); |
613 | 0 | } |
614 | |
|
615 | 0 | return acl; |
616 | 0 | } |
617 | | |
618 | | /* Walk the ACL tree, following nested acl() sample fetches, for no more than |
619 | | * max_recurse evaluations. Returns -1 if a recursive loop is detected, 0 if |
620 | | * the max_recurse was reached, otherwise the number of max_recurse left. |
621 | | */ |
622 | | static int parse_acl_recurse(struct acl *acl, struct acl_expr *expr, int max_recurse) |
623 | 0 | { |
624 | 0 | struct acl_term *term; |
625 | 0 | struct acl_sample *sample; |
626 | |
|
627 | 0 | if (strcmp(expr->smp->fetch->kw, "acl") != 0) |
628 | 0 | return max_recurse; |
629 | | |
630 | 0 | if (--max_recurse <= 0) |
631 | 0 | return 0; |
632 | | |
633 | 0 | sample = (struct acl_sample *)expr->smp->arg_p->data.ptr; |
634 | 0 | list_for_each_entry(term, &sample->suite.terms, list) { |
635 | 0 | if (term->acl == acl) |
636 | 0 | return -1; |
637 | 0 | list_for_each_entry(expr, &term->acl->expr, list) { |
638 | 0 | max_recurse = parse_acl_recurse(acl, expr, max_recurse); |
639 | 0 | if (max_recurse <= 0) |
640 | 0 | return max_recurse; |
641 | 0 | } |
642 | 0 | } |
643 | | |
644 | 0 | return max_recurse; |
645 | 0 | } |
646 | | |
647 | | /* Parse an ACL with the name starting at <args>[0], and with a list of already |
648 | | * known ACLs in <acl>. If the ACL was not in the list, it will be added. |
649 | | * A pointer to that ACL is returned. If the ACL has an empty name, then it's |
650 | | * an anonymous one and it won't be merged with any other one. If <err> is not |
651 | | * NULL, it will be filled with an appropriate error. This pointer must be |
652 | | * freeable or NULL. <al> is the arg_list serving as a head for unresolved |
653 | | * dependencies. It may be NULL if such dependencies are not allowed. |
654 | | * |
655 | | * args syntax: <aclname> <acl_expr> |
656 | | */ |
657 | | struct acl *parse_acl(const char **args, struct list *known_acl, char **err, struct arg_list *al, |
658 | | const char *file, int line) |
659 | 0 | { |
660 | 0 | __label__ out_return, out_free_acl_expr, out_free_name; |
661 | 0 | struct acl *cur_acl; |
662 | 0 | struct acl_expr *acl_expr; |
663 | 0 | char *name; |
664 | 0 | const char *pos; |
665 | |
|
666 | 0 | if (**args && (pos = invalid_char(*args))) { |
667 | 0 | memprintf(err, "invalid character in ACL name : '%c'", *pos); |
668 | 0 | goto out_return; |
669 | 0 | } |
670 | | |
671 | 0 | acl_expr = parse_acl_expr(args + 1, err, al, file, line); |
672 | 0 | if (!acl_expr) { |
673 | | /* parse_acl_expr will have filled <err> here */ |
674 | 0 | goto out_return; |
675 | 0 | } |
676 | | |
677 | | /* Check for args beginning with an opening parenthesis just after the |
678 | | * subject, as this is almost certainly a typo. Right now we can only |
679 | | * emit a warning, so let's do so. |
680 | | */ |
681 | 0 | if (!strchr(args[1], '(') && *args[2] == '(') |
682 | 0 | ha_warning("parsing acl '%s' :\n" |
683 | 0 | " matching '%s' for pattern '%s' is likely a mistake and probably\n" |
684 | 0 | " not what you want. Maybe you need to remove the extraneous space before '('.\n" |
685 | 0 | " If you are really sure this is not an error, please insert '--' between the\n" |
686 | 0 | " match and the pattern to make this warning message disappear.\n", |
687 | 0 | args[0], args[1], args[2]); |
688 | |
|
689 | 0 | if (*args[0]) |
690 | 0 | cur_acl = find_acl_by_name(args[0], known_acl); |
691 | 0 | else |
692 | 0 | cur_acl = NULL; |
693 | |
|
694 | 0 | if (cur_acl) { |
695 | 0 | int ret = parse_acl_recurse(cur_acl, acl_expr, ACL_MAX_RECURSE); |
696 | 0 | if (ret <= 0) { |
697 | 0 | if (ret < 0) |
698 | 0 | memprintf(err, "have a recursive loop"); |
699 | 0 | else |
700 | 0 | memprintf(err, "too deep acl() tree"); |
701 | 0 | goto out_free_acl_expr; |
702 | 0 | } |
703 | 0 | } else { |
704 | 0 | name = strdup(args[0]); |
705 | 0 | if (!name) { |
706 | 0 | memprintf(err, "out of memory when parsing ACL"); |
707 | 0 | goto out_free_acl_expr; |
708 | 0 | } |
709 | 0 | cur_acl = calloc(1, sizeof(*cur_acl)); |
710 | 0 | if (cur_acl == NULL) { |
711 | 0 | memprintf(err, "out of memory when parsing ACL"); |
712 | 0 | goto out_free_name; |
713 | 0 | } |
714 | | |
715 | 0 | LIST_INIT(&cur_acl->expr); |
716 | 0 | LIST_APPEND(known_acl, &cur_acl->list); |
717 | 0 | cur_acl->name = name; |
718 | 0 | } |
719 | | |
720 | | /* We want to know what features the ACL needs (typically HTTP parsing), |
721 | | * and where it may be used. If an ACL relies on multiple matches, it is |
722 | | * OK if at least one of them may match in the context where it is used. |
723 | | */ |
724 | 0 | cur_acl->use |= acl_expr->smp->fetch->use; |
725 | 0 | cur_acl->val |= acl_expr->smp->fetch->val; |
726 | 0 | LIST_APPEND(&cur_acl->expr, &acl_expr->list); |
727 | 0 | return cur_acl; |
728 | | |
729 | 0 | out_free_name: |
730 | 0 | free(name); |
731 | 0 | out_free_acl_expr: |
732 | 0 | prune_acl_expr(acl_expr); |
733 | 0 | free(acl_expr); |
734 | 0 | out_return: |
735 | 0 | return NULL; |
736 | 0 | } |
737 | | |
738 | | /* Some useful ACLs provided by default. Only those used are allocated. */ |
739 | | |
740 | | const struct { |
741 | | const char *name; |
742 | | const char *expr[4]; /* put enough for longest expression */ |
743 | | } default_acl_list[] = { |
744 | | { .name = "TRUE", .expr = {"always_true",""}}, |
745 | | { .name = "FALSE", .expr = {"always_false",""}}, |
746 | | { .name = "LOCALHOST", .expr = {"src","127.0.0.1/8","::1",""}}, |
747 | | { .name = "HTTP", .expr = {"req.proto_http",""}}, |
748 | | { .name = "HTTP_1.0", .expr = {"req.ver","1.0",""}}, |
749 | | { .name = "HTTP_1.1", .expr = {"req.ver","1.1",""}}, |
750 | | { .name = "HTTP_2.0", .expr = {"req.ver","2.0",""}}, |
751 | | { .name = "HTTP_3.0", .expr = {"req.ver","3.0",""}}, |
752 | | { .name = "METH_CONNECT", .expr = {"method","CONNECT",""}}, |
753 | | { .name = "METH_DELETE", .expr = {"method","DELETE",""}}, |
754 | | { .name = "METH_GET", .expr = {"method","GET","HEAD",""}}, |
755 | | { .name = "METH_HEAD", .expr = {"method","HEAD",""}}, |
756 | | { .name = "METH_OPTIONS", .expr = {"method","OPTIONS",""}}, |
757 | | { .name = "METH_POST", .expr = {"method","POST",""}}, |
758 | | { .name = "METH_PUT", .expr = {"method","PUT",""}}, |
759 | | { .name = "METH_TRACE", .expr = {"method","TRACE",""}}, |
760 | | { .name = "HTTP_URL_ABS", .expr = {"url_reg","^[^/:]*://",""}}, |
761 | | { .name = "HTTP_URL_SLASH", .expr = {"url_beg","/",""}}, |
762 | | { .name = "HTTP_URL_STAR", .expr = {"url","*",""}}, |
763 | | { .name = "HTTP_CONTENT", .expr = {"req.hdr_val(content-length)","gt","0",""}}, |
764 | | { .name = "RDP_COOKIE", .expr = {"req.rdp_cookie_cnt","gt","0",""}}, |
765 | | { .name = "REQ_CONTENT", .expr = {"req.len","gt","0",""}}, |
766 | | { .name = "WAIT_END", .expr = {"wait_end",""}}, |
767 | | { .name = NULL, .expr = {""}} |
768 | | }; |
769 | | |
770 | | /* Find a default ACL from the default_acl list, compile it and return it. |
771 | | * If the ACL is not found, NULL is returned. In theory, it cannot fail, |
772 | | * except when default ACLs are broken, in which case it will return NULL. |
773 | | * If <known_acl> is not NULL, the ACL will be queued at its tail. If <err> is |
774 | | * not NULL, it will be filled with an error message if an error occurs. This |
775 | | * pointer must be freeable or NULL. <al> is an arg_list serving as a list head |
776 | | * to report missing dependencies. It may be NULL if such dependencies are not |
777 | | * allowed. |
778 | | */ |
779 | | struct acl *find_acl_default(const char *acl_name, struct list *known_acl, |
780 | | char **err, struct arg_list *al, |
781 | | const char *file, int line) |
782 | 0 | { |
783 | 0 | __label__ out_return, out_free_acl_expr, out_free_name; |
784 | 0 | struct acl *cur_acl; |
785 | 0 | struct acl_expr *acl_expr; |
786 | 0 | char *name; |
787 | 0 | int index; |
788 | |
|
789 | 0 | for (index = 0; default_acl_list[index].name != NULL; index++) { |
790 | 0 | if (strcmp(acl_name, default_acl_list[index].name) == 0) |
791 | 0 | break; |
792 | 0 | } |
793 | |
|
794 | 0 | if (default_acl_list[index].name == NULL) { |
795 | 0 | memprintf(err, "no such ACL : '%s'", acl_name); |
796 | 0 | return NULL; |
797 | 0 | } |
798 | | |
799 | 0 | acl_expr = parse_acl_expr((const char **)default_acl_list[index].expr, err, al, file, line); |
800 | 0 | if (!acl_expr) { |
801 | | /* parse_acl_expr must have filled err here */ |
802 | 0 | goto out_return; |
803 | 0 | } |
804 | | |
805 | 0 | name = strdup(acl_name); |
806 | 0 | if (!name) { |
807 | 0 | memprintf(err, "out of memory when building default ACL '%s'", acl_name); |
808 | 0 | goto out_free_acl_expr; |
809 | 0 | } |
810 | | |
811 | 0 | cur_acl = calloc(1, sizeof(*cur_acl)); |
812 | 0 | if (cur_acl == NULL) { |
813 | 0 | memprintf(err, "out of memory when building default ACL '%s'", acl_name); |
814 | 0 | goto out_free_name; |
815 | 0 | } |
816 | | |
817 | 0 | cur_acl->name = name; |
818 | 0 | cur_acl->use |= acl_expr->smp->fetch->use; |
819 | 0 | cur_acl->val |= acl_expr->smp->fetch->val; |
820 | 0 | LIST_INIT(&cur_acl->expr); |
821 | 0 | LIST_APPEND(&cur_acl->expr, &acl_expr->list); |
822 | 0 | if (known_acl) |
823 | 0 | LIST_APPEND(known_acl, &cur_acl->list); |
824 | |
|
825 | 0 | return cur_acl; |
826 | | |
827 | 0 | out_free_name: |
828 | 0 | free(name); |
829 | 0 | out_free_acl_expr: |
830 | 0 | prune_acl_expr(acl_expr); |
831 | 0 | free(acl_expr); |
832 | 0 | out_return: |
833 | 0 | return NULL; |
834 | 0 | } |
835 | | |
836 | | /* Parse an ACL condition starting at <args>[0], relying on a list of already |
837 | | * known ACLs passed in <known_acl>. The new condition is returned (or NULL in |
838 | | * case of low memory). Supports multiple conditions separated by "or". If |
839 | | * <err> is not NULL, it will be filled with a pointer to an error message in |
840 | | * case of error, that the caller is responsible for freeing. The initial |
841 | | * location must either be freeable or NULL. The list <al> serves as a list head |
842 | | * for unresolved dependencies. It may be NULL if such dependencies are not |
843 | | * allowed. |
844 | | */ |
845 | | struct acl_cond *parse_acl_cond(const char **args, struct list *known_acl, |
846 | | enum acl_cond_pol pol, char **err, struct arg_list *al, |
847 | | const char *file, int line) |
848 | 0 | { |
849 | 0 | __label__ out_return, out_free_suite, out_free_term; |
850 | 0 | int arg, neg; |
851 | 0 | const char *word; |
852 | 0 | struct acl *cur_acl; |
853 | 0 | struct acl_term *cur_term; |
854 | 0 | struct acl_term_suite *cur_suite; |
855 | 0 | struct acl_cond *cond; |
856 | 0 | unsigned int suite_val; |
857 | |
|
858 | 0 | cond = calloc(1, sizeof(*cond)); |
859 | 0 | if (cond == NULL) { |
860 | 0 | memprintf(err, "out of memory when parsing condition"); |
861 | 0 | goto out_return; |
862 | 0 | } |
863 | | |
864 | 0 | LIST_INIT(&cond->list); |
865 | 0 | LIST_INIT(&cond->suites); |
866 | 0 | cond->pol = pol; |
867 | 0 | cond->val = 0; |
868 | |
|
869 | 0 | cur_suite = NULL; |
870 | 0 | suite_val = ~0U; |
871 | 0 | neg = 0; |
872 | 0 | for (arg = 0; *args[arg]; arg++) { |
873 | 0 | word = args[arg]; |
874 | | |
875 | | /* remove as many exclamation marks as we can */ |
876 | 0 | while (*word == '!') { |
877 | 0 | neg = !neg; |
878 | 0 | word++; |
879 | 0 | } |
880 | | |
881 | | /* an empty word is allowed because we cannot force the user to |
882 | | * always think about not leaving exclamation marks alone. |
883 | | */ |
884 | 0 | if (!*word) |
885 | 0 | continue; |
886 | | |
887 | 0 | if (strcasecmp(word, "or") == 0 || strcmp(word, "||") == 0) { |
888 | | /* new term suite */ |
889 | 0 | cond->val |= suite_val; |
890 | 0 | suite_val = ~0U; |
891 | 0 | cur_suite = NULL; |
892 | 0 | neg = 0; |
893 | 0 | continue; |
894 | 0 | } |
895 | | |
896 | 0 | if (strcmp(word, "{") == 0) { |
897 | | /* we may have a complete ACL expression between two braces, |
898 | | * find the last one. |
899 | | */ |
900 | 0 | int arg_end = arg + 1; |
901 | 0 | const char **args_new; |
902 | |
|
903 | 0 | while (*args[arg_end] && strcmp(args[arg_end], "}") != 0) |
904 | 0 | arg_end++; |
905 | |
|
906 | 0 | if (!*args[arg_end]) { |
907 | 0 | memprintf(err, "missing closing '}' in condition"); |
908 | 0 | goto out_free_suite; |
909 | 0 | } |
910 | | |
911 | 0 | args_new = calloc(1, (arg_end - arg + 1) * sizeof(*args_new)); |
912 | 0 | if (!args_new) { |
913 | 0 | memprintf(err, "out of memory when parsing condition"); |
914 | 0 | goto out_free_suite; |
915 | 0 | } |
916 | | |
917 | 0 | args_new[0] = ""; |
918 | 0 | memcpy(args_new + 1, args + arg + 1, (arg_end - arg) * sizeof(*args_new)); |
919 | 0 | args_new[arg_end - arg] = ""; |
920 | 0 | cur_acl = parse_acl(args_new, known_acl, err, al, file, line); |
921 | 0 | free(args_new); |
922 | |
|
923 | 0 | if (!cur_acl) { |
924 | | /* note that parse_acl() must have filled <err> here */ |
925 | 0 | goto out_free_suite; |
926 | 0 | } |
927 | 0 | arg = arg_end; |
928 | 0 | } |
929 | 0 | else { |
930 | | /* search for <word> in the known ACL names. If we do not find |
931 | | * it, let's look for it in the default ACLs, and if found, add |
932 | | * it to the list of ACLs of this proxy. This makes it possible |
933 | | * to override them. |
934 | | */ |
935 | 0 | cur_acl = find_acl_by_name(word, known_acl); |
936 | 0 | if (cur_acl == NULL) { |
937 | 0 | cur_acl = find_acl_default(word, known_acl, err, al, file, line); |
938 | 0 | if (cur_acl == NULL) { |
939 | | /* note that find_acl_default() must have filled <err> here */ |
940 | 0 | goto out_free_suite; |
941 | 0 | } |
942 | 0 | } |
943 | 0 | } |
944 | | |
945 | 0 | cur_term = calloc(1, sizeof(*cur_term)); |
946 | 0 | if (cur_term == NULL) { |
947 | 0 | memprintf(err, "out of memory when parsing condition"); |
948 | 0 | goto out_free_suite; |
949 | 0 | } |
950 | | |
951 | 0 | cur_term->acl = cur_acl; |
952 | 0 | cur_term->neg = neg; |
953 | | |
954 | | /* Here it is a bit complex. The acl_term_suite is a conjunction |
955 | | * of many terms. It may only be used if all of its terms are |
956 | | * usable at the same time. So the suite's validity domain is an |
957 | | * AND between all ACL keywords' ones. But, the global condition |
958 | | * is valid if at least one term suite is OK. So it's an OR between |
959 | | * all of their validity domains. We could emit a warning as soon |
960 | | * as suite_val is null because it means that the last ACL is not |
961 | | * compatible with the previous ones. Let's remain simple for now. |
962 | | */ |
963 | 0 | cond->use |= cur_acl->use; |
964 | 0 | suite_val &= cur_acl->val; |
965 | |
|
966 | 0 | if (!cur_suite) { |
967 | 0 | cur_suite = calloc(1, sizeof(*cur_suite)); |
968 | 0 | if (cur_suite == NULL) { |
969 | 0 | memprintf(err, "out of memory when parsing condition"); |
970 | 0 | goto out_free_term; |
971 | 0 | } |
972 | 0 | LIST_INIT(&cur_suite->terms); |
973 | 0 | LIST_APPEND(&cond->suites, &cur_suite->list); |
974 | 0 | } |
975 | 0 | LIST_APPEND(&cur_suite->terms, &cur_term->list); |
976 | 0 | neg = 0; |
977 | 0 | } |
978 | | |
979 | 0 | cond->val |= suite_val; |
980 | 0 | return cond; |
981 | | |
982 | 0 | out_free_term: |
983 | 0 | free(cur_term); |
984 | 0 | out_free_suite: |
985 | 0 | free_acl_cond(cond); |
986 | 0 | out_return: |
987 | 0 | return NULL; |
988 | 0 | } |
989 | | |
990 | | /* Builds an ACL condition starting at the if/unless keyword. The complete |
991 | | * condition is returned. NULL is returned in case of error or if the first |
992 | | * word is neither "if" nor "unless". It automatically sets the file name and |
993 | | * the line number in the condition for better error reporting, and sets the |
994 | | * HTTP initialization requirements in the proxy. If <err> is not NULL, it will |
995 | | * be filled with a pointer to an error message in case of error, that the |
996 | | * caller is responsible for freeing. The initial location must either be |
997 | | * freeable or NULL. |
998 | | */ |
999 | | struct acl_cond *build_acl_cond(const char *file, int line, struct list *known_acl, |
1000 | | struct proxy *px, const char **args, char **err) |
1001 | 0 | { |
1002 | 0 | enum acl_cond_pol pol = ACL_COND_NONE; |
1003 | 0 | struct acl_cond *cond = NULL; |
1004 | |
|
1005 | 0 | if (err) |
1006 | 0 | *err = NULL; |
1007 | |
|
1008 | 0 | if (strcmp(*args, "if") == 0) { |
1009 | 0 | pol = ACL_COND_IF; |
1010 | 0 | args++; |
1011 | 0 | } |
1012 | 0 | else if (strcmp(*args, "unless") == 0) { |
1013 | 0 | pol = ACL_COND_UNLESS; |
1014 | 0 | args++; |
1015 | 0 | } |
1016 | 0 | else { |
1017 | 0 | memprintf(err, "conditions must start with either 'if' or 'unless'"); |
1018 | 0 | return NULL; |
1019 | 0 | } |
1020 | | |
1021 | 0 | cond = parse_acl_cond(args, known_acl, pol, err, &px->conf.args, file, line); |
1022 | 0 | if (!cond) { |
1023 | | /* note that parse_acl_cond must have filled <err> here */ |
1024 | 0 | return NULL; |
1025 | 0 | } |
1026 | | |
1027 | 0 | cond->file = file; |
1028 | 0 | cond->line = line; |
1029 | 0 | px->http_needed |= !!(cond->use & SMP_USE_HTTP_ANY); |
1030 | 0 | return cond; |
1031 | 0 | } |
1032 | | |
1033 | | /* Execute condition <cond> and return either ACL_TEST_FAIL, ACL_TEST_MISS or |
1034 | | * ACL_TEST_PASS depending on the test results. ACL_TEST_MISS may only be |
1035 | | * returned if <opt> does not contain SMP_OPT_FINAL, indicating that incomplete |
1036 | | * data is being examined. The function automatically sets SMP_OPT_ITERATE. This |
1037 | | * function only computes the condition, it does not apply the polarity required |
1038 | | * by IF/UNLESS, it's up to the caller to do this using something like this : |
1039 | | * |
1040 | | * res = acl_pass(res); |
1041 | | * if (res == ACL_TEST_MISS) |
1042 | | * return 0; |
1043 | | * if (cond->pol == ACL_COND_UNLESS) |
1044 | | * res = !res; |
1045 | | */ |
1046 | | enum acl_test_res acl_exec_cond(struct acl_cond *cond, struct proxy *px, struct session *sess, struct stream *strm, unsigned int opt) |
1047 | 0 | { |
1048 | 0 | __label__ fetch_next; |
1049 | 0 | struct acl_term_suite *suite; |
1050 | 0 | struct acl_term *term; |
1051 | 0 | struct acl_expr *expr; |
1052 | 0 | struct acl *acl; |
1053 | 0 | struct sample smp; |
1054 | 0 | enum acl_test_res acl_res, suite_res, cond_res; |
1055 | | |
1056 | | /* ACLs are iterated over all values, so let's always set the flag to |
1057 | | * indicate this to the fetch functions. |
1058 | | */ |
1059 | 0 | opt |= SMP_OPT_ITERATE; |
1060 | | |
1061 | | /* We're doing a logical OR between conditions so we initialize to FAIL. |
1062 | | * The MISS status is propagated down from the suites. |
1063 | | */ |
1064 | 0 | cond_res = ACL_TEST_FAIL; |
1065 | 0 | list_for_each_entry(suite, &cond->suites, list) { |
1066 | | /* Evaluate condition suite <suite>. We stop at the first term |
1067 | | * which returns ACL_TEST_FAIL. The MISS status is still propagated |
1068 | | * in case of uncertainty in the result. |
1069 | | */ |
1070 | | |
1071 | | /* we're doing a logical AND between terms, so we must set the |
1072 | | * initial value to PASS. |
1073 | | */ |
1074 | 0 | suite_res = ACL_TEST_PASS; |
1075 | 0 | list_for_each_entry(term, &suite->terms, list) { |
1076 | 0 | acl = term->acl; |
1077 | | |
1078 | | /* FIXME: use cache ! |
1079 | | * check acl->cache_idx for this. |
1080 | | */ |
1081 | | |
1082 | | /* ACL result not cached. Let's scan all the expressions |
1083 | | * and use the first one to match. |
1084 | | */ |
1085 | 0 | acl_res = ACL_TEST_FAIL; |
1086 | 0 | list_for_each_entry(expr, &acl->expr, list) { |
1087 | | /* we need to reset context and flags */ |
1088 | 0 | memset(&smp, 0, sizeof(smp)); |
1089 | 0 | fetch_next: |
1090 | 0 | if (!sample_process(px, sess, strm, opt, expr->smp, &smp)) { |
1091 | | /* maybe we could not fetch because of missing data */ |
1092 | 0 | if (smp.flags & SMP_F_MAY_CHANGE && !(opt & SMP_OPT_FINAL)) |
1093 | 0 | acl_res |= ACL_TEST_MISS; |
1094 | 0 | continue; |
1095 | 0 | } |
1096 | | |
1097 | 0 | acl_res |= pat2acl(pattern_exec_match(&expr->pat, &smp, 0)); |
1098 | | /* |
1099 | | * OK now acl_res holds the result of this expression |
1100 | | * as one of ACL_TEST_FAIL, ACL_TEST_MISS or ACL_TEST_PASS. |
1101 | | * |
1102 | | * Then if (!MISS) we can cache the result, and put |
1103 | | * (smp.flags & SMP_F_VOLATILE) in the cache flags. |
1104 | | * |
1105 | | * FIXME: implement cache. |
1106 | | * |
1107 | | */ |
1108 | | |
1109 | | /* we're ORing these terms, so a single PASS is enough */ |
1110 | 0 | if (acl_res == ACL_TEST_PASS) |
1111 | 0 | break; |
1112 | | |
1113 | 0 | if (smp.flags & SMP_F_NOT_LAST) |
1114 | 0 | goto fetch_next; |
1115 | | |
1116 | | /* sometimes we know the fetched data is subject to change |
1117 | | * later and give another chance for a new match (eg: request |
1118 | | * size, time, ...) |
1119 | | */ |
1120 | 0 | if (smp.flags & SMP_F_MAY_CHANGE && !(opt & SMP_OPT_FINAL)) |
1121 | 0 | acl_res |= ACL_TEST_MISS; |
1122 | 0 | } |
1123 | | /* |
1124 | | * Here we have the result of an ACL (cached or not). |
1125 | | * ACLs are combined, negated or not, to form conditions. |
1126 | | */ |
1127 | | |
1128 | 0 | if (term->neg) |
1129 | 0 | acl_res = acl_neg(acl_res); |
1130 | |
|
1131 | 0 | suite_res &= acl_res; |
1132 | | |
1133 | | /* we're ANDing these terms, so a single FAIL or MISS is enough */ |
1134 | 0 | if (suite_res != ACL_TEST_PASS) |
1135 | 0 | break; |
1136 | 0 | } |
1137 | 0 | cond_res |= suite_res; |
1138 | | |
1139 | | /* we're ORing these terms, so a single PASS is enough */ |
1140 | 0 | if (cond_res == ACL_TEST_PASS) |
1141 | 0 | break; |
1142 | 0 | } |
1143 | 0 | return cond_res; |
1144 | 0 | } |
1145 | | |
1146 | | /* Returns a pointer to the first ACL conflicting with usage at place <where> |
1147 | | * which is one of the SMP_VAL_* bits indicating a check place, or NULL if |
1148 | | * no conflict is found. Only full conflicts are detected (ACL is not usable). |
1149 | | * Use the next function to check for useless keywords. |
1150 | | */ |
1151 | | const struct acl *acl_cond_conflicts(const struct acl_cond *cond, unsigned int where) |
1152 | 0 | { |
1153 | 0 | struct acl_term_suite *suite; |
1154 | 0 | struct acl_term *term; |
1155 | 0 | struct acl *acl; |
1156 | |
|
1157 | 0 | list_for_each_entry(suite, &cond->suites, list) { |
1158 | 0 | list_for_each_entry(term, &suite->terms, list) { |
1159 | 0 | acl = term->acl; |
1160 | 0 | if (!(acl->val & where)) |
1161 | 0 | return acl; |
1162 | 0 | } |
1163 | 0 | } |
1164 | 0 | return NULL; |
1165 | 0 | } |
1166 | | |
1167 | | /* Returns a pointer to the first ACL and its first keyword to conflict with |
1168 | | * usage at place <where> which is one of the SMP_VAL_* bits indicating a check |
1169 | | * place. Returns true if a conflict is found, with <acl> and <kw> set (if non |
1170 | | * null), or false if not conflict is found. The first useless keyword is |
1171 | | * returned. |
1172 | | */ |
1173 | | int acl_cond_kw_conflicts(const struct acl_cond *cond, unsigned int where, struct acl const **acl, char const **kw) |
1174 | 0 | { |
1175 | 0 | struct acl_term_suite *suite; |
1176 | 0 | struct acl_term *term; |
1177 | 0 | struct acl_expr *expr; |
1178 | |
|
1179 | 0 | list_for_each_entry(suite, &cond->suites, list) { |
1180 | 0 | list_for_each_entry(term, &suite->terms, list) { |
1181 | 0 | list_for_each_entry(expr, &term->acl->expr, list) { |
1182 | 0 | if (!(expr->smp->fetch->val & where)) { |
1183 | 0 | if (acl) |
1184 | 0 | *acl = term->acl; |
1185 | 0 | if (kw) |
1186 | 0 | *kw = expr->kw; |
1187 | 0 | return 1; |
1188 | 0 | } |
1189 | 0 | } |
1190 | 0 | } |
1191 | 0 | } |
1192 | 0 | return 0; |
1193 | 0 | } |
1194 | | |
1195 | | /* |
1196 | | * Find targets for userlist and groups in acl. Function returns the number |
1197 | | * of errors or OK if everything is fine. It must be called only once sample |
1198 | | * fetch arguments have been resolved (after smp_resolve_args()). |
1199 | | */ |
1200 | | int acl_find_targets(struct proxy *p) |
1201 | 0 | { |
1202 | |
|
1203 | 0 | struct acl *acl; |
1204 | 0 | struct acl_expr *expr; |
1205 | 0 | struct pattern_list *pattern; |
1206 | 0 | int cfgerr = 0; |
1207 | 0 | struct pattern_expr_list *pexp; |
1208 | |
|
1209 | 0 | list_for_each_entry(acl, &p->acl, list) { |
1210 | 0 | list_for_each_entry(expr, &acl->expr, list) { |
1211 | 0 | if (strcmp(expr->kw, "http_auth_group") == 0) { |
1212 | | /* Note: the ARGT_USR argument may only have been resolved earlier |
1213 | | * by smp_resolve_args(). |
1214 | | */ |
1215 | 0 | if (expr->smp->arg_p->unresolved) { |
1216 | 0 | ha_alert("Internal bug in proxy %s: %sacl %s %s() makes use of unresolved userlist '%s'. Please report this.\n", |
1217 | 0 | p->id, *acl->name ? "" : "anonymous ", acl->name, expr->kw, |
1218 | 0 | expr->smp->arg_p->data.str.area); |
1219 | 0 | cfgerr++; |
1220 | 0 | continue; |
1221 | 0 | } |
1222 | | |
1223 | 0 | if (LIST_ISEMPTY(&expr->pat.head)) { |
1224 | 0 | ha_alert("proxy %s: acl %s %s(): no groups specified.\n", |
1225 | 0 | p->id, acl->name, expr->kw); |
1226 | 0 | cfgerr++; |
1227 | 0 | continue; |
1228 | 0 | } |
1229 | | |
1230 | | /* For each pattern, check if the group exists. */ |
1231 | 0 | list_for_each_entry(pexp, &expr->pat.head, list) { |
1232 | 0 | if (LIST_ISEMPTY(&pexp->expr->patterns)) { |
1233 | 0 | ha_alert("proxy %s: acl %s %s(): no groups specified.\n", |
1234 | 0 | p->id, acl->name, expr->kw); |
1235 | 0 | cfgerr++; |
1236 | 0 | continue; |
1237 | 0 | } |
1238 | | |
1239 | 0 | list_for_each_entry(pattern, &pexp->expr->patterns, list) { |
1240 | | /* this keyword only has one argument */ |
1241 | 0 | if (!check_group(expr->smp->arg_p->data.usr, pattern->pat.ptr.str)) { |
1242 | 0 | ha_alert("proxy %s: acl %s %s(): invalid group '%s'.\n", |
1243 | 0 | p->id, acl->name, expr->kw, pattern->pat.ptr.str); |
1244 | 0 | cfgerr++; |
1245 | 0 | } |
1246 | 0 | } |
1247 | 0 | } |
1248 | 0 | } |
1249 | 0 | } |
1250 | 0 | } |
1251 | |
|
1252 | 0 | return cfgerr; |
1253 | 0 | } |
1254 | | |
1255 | | /* initializes ACLs by resolving the sample fetch names they rely upon. |
1256 | | * Returns 0 on success, otherwise an error. |
1257 | | */ |
1258 | | int init_acl() |
1259 | 0 | { |
1260 | 0 | int err = 0; |
1261 | 0 | int index; |
1262 | 0 | const char *name; |
1263 | 0 | struct acl_kw_list *kwl; |
1264 | 0 | struct sample_fetch *smp; |
1265 | |
|
1266 | 0 | list_for_each_entry(kwl, &acl_keywords.list, list) { |
1267 | 0 | for (index = 0; kwl->kw[index].kw != NULL; index++) { |
1268 | 0 | name = kwl->kw[index].fetch_kw; |
1269 | 0 | if (!name) |
1270 | 0 | name = kwl->kw[index].kw; |
1271 | |
|
1272 | 0 | smp = find_sample_fetch(name, strlen(name)); |
1273 | 0 | if (!smp) { |
1274 | 0 | ha_alert("Critical internal error: ACL keyword '%s' relies on sample fetch '%s' which was not registered!\n", |
1275 | 0 | kwl->kw[index].kw, name); |
1276 | 0 | err++; |
1277 | 0 | continue; |
1278 | 0 | } |
1279 | 0 | kwl->kw[index].smp = smp; |
1280 | 0 | } |
1281 | 0 | } |
1282 | 0 | return err; |
1283 | 0 | } |
1284 | | |
1285 | | /* dump known ACL keywords on stdout */ |
1286 | | void acl_dump_kwd(void) |
1287 | 0 | { |
1288 | 0 | struct acl_kw_list *kwl; |
1289 | 0 | const struct acl_keyword *kwp, *kw; |
1290 | 0 | const char *name; |
1291 | 0 | int index; |
1292 | |
|
1293 | 0 | for (kw = kwp = NULL;; kwp = kw) { |
1294 | 0 | list_for_each_entry(kwl, &acl_keywords.list, list) { |
1295 | 0 | for (index = 0; kwl->kw[index].kw != NULL; index++) { |
1296 | 0 | if (strordered(kwp ? kwp->kw : NULL, |
1297 | 0 | kwl->kw[index].kw, |
1298 | 0 | kw != kwp ? kw->kw : NULL)) |
1299 | 0 | kw = &kwl->kw[index]; |
1300 | 0 | } |
1301 | 0 | } |
1302 | |
|
1303 | 0 | if (kw == kwp) |
1304 | 0 | break; |
1305 | | |
1306 | 0 | name = kw->fetch_kw; |
1307 | 0 | if (!name) |
1308 | 0 | name = kw->kw; |
1309 | |
|
1310 | 0 | printf("%s = %s -m %s\n", kw->kw, name, pat_match_names[kw->match_type]); |
1311 | 0 | } |
1312 | 0 | } |
1313 | | |
1314 | | /* Purge everything in the acl_cond <cond>, then free <cond> */ |
1315 | | void free_acl_cond(struct acl_cond *cond) |
1316 | 0 | { |
1317 | 0 | struct acl_term_suite *suite, *suiteb; |
1318 | 0 | struct acl_term *term, *termb; |
1319 | |
|
1320 | 0 | if (!cond) |
1321 | 0 | return; |
1322 | | |
1323 | 0 | list_for_each_entry_safe(suite, suiteb, &cond->suites, list) { |
1324 | 0 | list_for_each_entry_safe(term, termb, &suite->terms, list) { |
1325 | 0 | LIST_DELETE(&term->list); |
1326 | 0 | free(term); |
1327 | 0 | } |
1328 | 0 | LIST_DELETE(&suite->list); |
1329 | 0 | free(suite); |
1330 | 0 | } |
1331 | |
|
1332 | 0 | free(cond); |
1333 | 0 | } |
1334 | | |
1335 | | |
1336 | | static int smp_fetch_acl(const struct arg *args, struct sample *smp, const char *kw, void *private) |
1337 | 0 | { |
1338 | 0 | struct acl_sample *acl_sample = (struct acl_sample *)args->data.ptr; |
1339 | 0 | enum acl_test_res ret; |
1340 | |
|
1341 | 0 | ret = acl_exec_cond(&acl_sample->cond, smp->px, smp->sess, smp->strm, smp->opt); |
1342 | 0 | if (ret == ACL_TEST_MISS) |
1343 | 0 | return 0; |
1344 | 0 | smp->data.u.sint = ret == ACL_TEST_PASS; |
1345 | 0 | smp->data.type = SMP_T_BOOL; |
1346 | 0 | return 1; |
1347 | 0 | } |
1348 | | |
1349 | | int smp_fetch_acl_parse(struct arg *args, char **err_msg) |
1350 | 0 | { |
1351 | 0 | struct acl_sample *acl_sample; |
1352 | 0 | char *name; |
1353 | 0 | int i; |
1354 | |
|
1355 | 0 | for (i = 0; args[i].type != ARGT_STOP; i++) |
1356 | 0 | ; |
1357 | 0 | acl_sample = calloc(1, sizeof(struct acl_sample) + sizeof(struct acl_term) * i); |
1358 | 0 | if (unlikely(!acl_sample)) { |
1359 | 0 | memprintf(err_msg, "out of memory when parsing ACL expression"); |
1360 | 0 | return 0; |
1361 | 0 | } |
1362 | 0 | LIST_INIT(&acl_sample->suite.terms); |
1363 | 0 | LIST_INIT(&acl_sample->cond.suites); |
1364 | 0 | LIST_APPEND(&acl_sample->cond.suites, &acl_sample->suite.list); |
1365 | 0 | acl_sample->cond.val = ~0U; // the keyword is valid everywhere for now. |
1366 | |
|
1367 | 0 | args->data.ptr = acl_sample; |
1368 | |
|
1369 | 0 | for (i = 0; args[i].type != ARGT_STOP; i++) { |
1370 | 0 | name = args[i].data.str.area; |
1371 | 0 | if (name[0] == '!') { |
1372 | 0 | acl_sample->terms[i].neg = 1; |
1373 | 0 | name++; |
1374 | 0 | } |
1375 | | |
1376 | |
|
1377 | 0 | if ( |
1378 | 0 | !(acl_sample->terms[i].acl = find_acl_by_name(name, &curproxy->acl)) && |
1379 | 0 | !(acl_sample->terms[i].acl = find_acl_default(name, &curproxy->acl, err_msg, NULL, NULL, 0)) |
1380 | 0 | ) { |
1381 | 0 | memprintf(err_msg, "ACL '%s' not found", name); |
1382 | 0 | goto err; |
1383 | 0 | } |
1384 | | |
1385 | 0 | acl_sample->cond.use |= acl_sample->terms[i].acl->use; |
1386 | 0 | acl_sample->cond.val &= acl_sample->terms[i].acl->val; |
1387 | |
|
1388 | 0 | LIST_APPEND(&acl_sample->suite.terms, &acl_sample->terms[i].list); |
1389 | 0 | } |
1390 | | |
1391 | 0 | return 1; |
1392 | | |
1393 | 0 | err: |
1394 | 0 | free(acl_sample); |
1395 | 0 | return 0; |
1396 | 0 | } |
1397 | | |
1398 | | /************************************************************************/ |
1399 | | /* All supported sample and ACL keywords must be declared here. */ |
1400 | | /************************************************************************/ |
1401 | | |
1402 | | /* Note: must not be declared <const> as its list will be overwritten. |
1403 | | * Please take care of keeping this list alphabetically sorted. |
1404 | | */ |
1405 | | static struct acl_kw_list acl_kws = {ILH, { |
1406 | | { /* END */ }, |
1407 | | }}; |
1408 | | |
1409 | | INITCALL1(STG_REGISTER, acl_register_keywords, &acl_kws); |
1410 | | |
1411 | | static struct sample_fetch_kw_list smp_kws = {ILH, { |
1412 | | { "acl", smp_fetch_acl, ARG12(1,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR), smp_fetch_acl_parse, SMP_T_BOOL, SMP_USE_CONST }, |
1413 | | { /* END */ }, |
1414 | | }}; |
1415 | | |
1416 | | INITCALL1(STG_REGISTER, sample_register_fetches, &smp_kws); |
1417 | | |
1418 | | /* |
1419 | | * Local variables: |
1420 | | * c-indent-level: 8 |
1421 | | * c-basic-offset: 8 |
1422 | | * End: |
1423 | | */ |