Coverage Report

Created: 2026-04-03 06:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/haproxy/src/acl.c
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
 */