Coverage Report

Created: 2025-12-05 06:31

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/frr/lib/command_match.c
Line
Count
Source
1
// SPDX-License-Identifier: GPL-2.0-or-later
2
/*
3
 * Input matching routines for CLI backend.
4
 *
5
 * --
6
 * Copyright (C) 2016 Cumulus Networks, Inc.
7
 */
8
9
#include <zebra.h>
10
11
#include "command_match.h"
12
#include "memory.h"
13
#include "asn.h"
14
15
8
DEFINE_MTYPE_STATIC(LIB, CMD_MATCHSTACK, "Command Match Stack");
16
8
17
8
#ifdef TRACE_MATCHER
18
8
#define TM 1
19
8
#else
20
8
#define TM 0
21
#endif
22
23
#define trace_matcher(...)                                                     \
24
0
  do {                                                                   \
25
0
    if (TM)                                                        \
26
0
      fprintf(stderr, __VA_ARGS__);                          \
27
0
  } while (0);
28
29
/* matcher helper prototypes */
30
static int add_nexthops(struct list *, struct graph_node *,
31
      struct graph_node **, size_t, bool);
32
33
static enum matcher_rv command_match_r(struct graph_node *, vector,
34
               unsigned int, struct graph_node **,
35
               struct list **);
36
37
static int score_precedence(enum cmd_token_type);
38
39
static enum match_type min_match_level(enum cmd_token_type);
40
41
static void del_arglist(struct list *);
42
43
static struct cmd_token *disambiguate_tokens(struct cmd_token *,
44
               struct cmd_token *, char *);
45
46
static struct list *disambiguate(struct list *, struct list *, vector,
47
         unsigned int);
48
49
int compare_completions(const void *, const void *);
50
51
/* token matcher prototypes */
52
static enum match_type match_token(struct cmd_token *, char *);
53
54
static enum match_type match_ipv4(const char *);
55
56
static enum match_type match_ipv4_prefix(const char *);
57
58
static enum match_type match_ipv6_prefix(const char *, bool);
59
60
static enum match_type match_range(struct cmd_token *, const char *);
61
62
static enum match_type match_word(struct cmd_token *, const char *);
63
64
static enum match_type match_variable(struct cmd_token *, const char *);
65
66
static enum match_type match_mac(const char *, bool);
67
68
static bool is_neg(vector vline, size_t idx)
69
0
{
70
0
  if (idx >= vector_active(vline) || !vector_slot(vline, idx))
71
0
    return false;
72
0
  return !strcmp(vector_slot(vline, idx), "no");
73
0
}
74
75
enum matcher_rv command_match(struct graph *cmdgraph, vector vline,
76
            struct list **argv, const struct cmd_element **el)
77
0
{
78
0
  struct graph_node *stack[CMD_ARGC_MAX];
79
0
  enum matcher_rv status;
80
0
  *argv = NULL;
81
82
  // prepend a dummy token to match that pesky start node
83
0
  vector vvline = vector_init(vline->alloced + 1);
84
0
  vector_set_index(vvline, 0, XSTRDUP(MTYPE_TMP, "dummy"));
85
0
  memcpy(vvline->index + 1, vline->index,
86
0
         sizeof(void *) * vline->alloced);
87
0
  vvline->active = vline->active + 1;
88
89
0
  struct graph_node *start = vector_slot(cmdgraph->nodes, 0);
90
0
  status = command_match_r(start, vvline, 0, stack, argv);
91
0
  if (status == MATCHER_OK) { // successful match
92
0
    struct listnode *head = listhead(*argv);
93
0
    struct listnode *tail = listtail(*argv);
94
95
0
    assert(head);
96
0
    assert(tail);
97
98
    // delete dummy start node
99
0
    cmd_token_del((struct cmd_token *)head->data);
100
0
    list_delete_node(*argv, head);
101
102
    // get cmd_element out of list tail
103
0
    *el = listgetdata(tail);
104
0
    list_delete_node(*argv, tail);
105
106
    // now argv is an ordered list of cmd_token matching the user
107
    // input, with each cmd_token->arg holding the corresponding
108
    // input
109
0
    assert(*el);
110
0
  } else if (*argv) {
111
0
    del_arglist(*argv);
112
0
    *argv = NULL;
113
0
  }
114
115
0
  if (!*el) {
116
0
    trace_matcher("No match\n");
117
0
  } else {
118
0
    trace_matcher("Matched command\n->string %s\n->desc %s\n",
119
0
            (*el)->string, (*el)->doc);
120
0
  }
121
122
  // free the leader token we alloc'd
123
0
  XFREE(MTYPE_TMP, vector_slot(vvline, 0));
124
  // free vector
125
0
  vector_free(vvline);
126
127
0
  return status;
128
0
}
129
130
/**
131
 * Builds an argument list given a DFA and a matching input line.
132
 *
133
 * First the function determines if the node it is passed matches the first
134
 * token of input. If it does not, it returns NULL (MATCHER_NO_MATCH). If it
135
 * does match, then it saves the input token as the head of an argument list.
136
 *
137
 * The next step is to see if there is further input in the input line. If
138
 * there is not, the current node's children are searched to see if any of them
139
 * are leaves (type END_TKN). If this is the case, then the bottom of the
140
 * recursion stack has been reached, the leaf is pushed onto the argument list,
141
 * the current node is pushed, and the resulting argument list is
142
 * returned (MATCHER_OK). If it is not the case, NULL is returned, indicating
143
 * that there is no match for the input along this path (MATCHER_INCOMPLETE).
144
 *
145
 * If there is further input, then the function recurses on each of the current
146
 * node's children, passing them the input line minus the token that was just
147
 * matched. For each child, the return value of the recursive call is
148
 * inspected. If it is null, then there is no match for the input along the
149
 * subgraph headed by that child. If it is not null, then there is at least one
150
 * input match in that subgraph (more on this in a moment).
151
 *
152
 * If a recursive call on a child returns a non-null value, then it has matched
153
 * the input given it on the subgraph that starts with that child. However, due
154
 * to the flexibility of the grammar, it is sometimes the case that two or more
155
 * child graphs match the same input (two or more of the recursive calls have
156
 * non-NULL return values). This is not a valid state, since only one true
157
 * match is possible. In order to resolve this conflict, the function keeps a
158
 * reference to the child node that most specifically matches the input. This
159
 * is done by assigning each node type a precedence. If a child is found to
160
 * match the remaining input, then the precedence values of the current
161
 * best-matching child and this new match are compared. The node with higher
162
 * precedence is kept, and the other match is discarded. Due to the recursive
163
 * nature of this function, it is only necessary to compare the precedence of
164
 * immediate children, since all subsequent children will already have been
165
 * disambiguated in this way.
166
 *
167
 * In the event that two children are found to match with the same precedence,
168
 * then the input is ambiguous for the passed cmd_element and NULL is returned.
169
 *
170
 * @param[in] start the start node.
171
 * @param[in] vline the vectorized input line.
172
 * @param[in] n the index of the first input token.
173
 * @return A linked list of n elements. The first n-1 elements are pointers to
174
 * struct cmd_token and represent the sequence of tokens matched by the input.
175
 * The ->arg field of each token points to a copy of the input matched on it.
176
 * The final nth element is a pointer to struct cmd_element, which is the
177
 * command that was matched.
178
 *
179
 * If no match was found, the return value is NULL.
180
 */
181
static enum matcher_rv command_match_r(struct graph_node *start, vector vline,
182
               unsigned int n,
183
               struct graph_node **stack,
184
               struct list **currbest)
185
0
{
186
0
  assert(n < vector_active(vline));
187
188
0
  enum matcher_rv status = MATCHER_NO_MATCH;
189
190
  // get the minimum match level that can count as a full match
191
0
  struct cmd_token *copy, *token = start->data;
192
0
  enum match_type minmatch = min_match_level(token->type);
193
194
  /* check history/stack of tokens
195
   * this disallows matching the same one more than once if there is a
196
   * circle in the graph (used for keyword arguments) */
197
0
  if (n == CMD_ARGC_MAX)
198
0
    return MATCHER_NO_MATCH;
199
0
  if (!token->allowrepeat)
200
0
    for (size_t s = 0; s < n; s++)
201
0
      if (stack[s] == start)
202
0
        return MATCHER_NO_MATCH;
203
204
  // get the current operating input token
205
0
  char *input_token = vector_slot(vline, n);
206
207
#ifdef TRACE_MATCHER
208
  fprintf(stdout, "\"%-20s\" matches \"%-30s\" ? ", input_token,
209
    token->text);
210
  enum match_type mt = match_token(token, input_token);
211
  fprintf(stdout, "type: %d ", token->type);
212
  fprintf(stdout, "min: %d - ", minmatch);
213
  switch (mt) {
214
  case trivial_match:
215
    fprintf(stdout, "trivial_match ");
216
    break;
217
  case no_match:
218
    fprintf(stdout, "no_match ");
219
    break;
220
  case partly_match:
221
    fprintf(stdout, "partly_match ");
222
    break;
223
  case exact_match:
224
    fprintf(stdout, "exact_match ");
225
    break;
226
  }
227
  if (mt >= minmatch)
228
    fprintf(stdout, " MATCH");
229
  fprintf(stdout, "\n");
230
#endif
231
232
  // if we don't match this node, die
233
0
  if (match_token(token, input_token) < minmatch)
234
0
    return MATCHER_NO_MATCH;
235
236
0
  stack[n] = start;
237
238
  // pointers for iterating linklist
239
0
  struct listnode *ln;
240
0
  struct graph_node *gn;
241
242
  // get all possible nexthops
243
0
  struct list *next = list_new();
244
0
  add_nexthops(next, start, NULL, 0, is_neg(vline, 1));
245
246
  // determine the best match
247
0
  for (ALL_LIST_ELEMENTS_RO(next, ln, gn)) {
248
    // if we've matched all input we're looking for END_TKN
249
0
    if (n + 1 == vector_active(vline)) {
250
0
      struct cmd_token *tok = gn->data;
251
0
      if (tok->type == END_TKN) {
252
        // if more than one END_TKN in the follow set
253
0
        if (*currbest) {
254
0
          status = MATCHER_AMBIGUOUS;
255
0
          break;
256
0
        } else {
257
0
          status = MATCHER_OK;
258
0
        }
259
0
        *currbest = list_new();
260
        // node should have one child node with the
261
        // element
262
0
        struct graph_node *leaf =
263
0
          vector_slot(gn->to, 0);
264
        // last node in the list will hold the
265
        // cmd_element; this is important because
266
        // list_delete() expects that all nodes have
267
        // the same data type, so when deleting this
268
        // list the last node must be manually deleted
269
0
        struct cmd_element *el = leaf->data;
270
0
        listnode_add(*currbest, el);
271
0
        (*currbest)->del =
272
0
          (void (*)(void *)) & cmd_token_del;
273
        // do not break immediately; continue walking
274
        // through the follow set to ensure that there
275
        // is exactly one END_TKN
276
0
      }
277
0
      continue;
278
0
    }
279
280
    // else recurse on candidate child node
281
0
    struct list *result = NULL;
282
0
    enum matcher_rv rstat =
283
0
      command_match_r(gn, vline, n + 1, stack, &result);
284
285
    // save the best match
286
0
    if (result && *currbest) {
287
      // pick the best of two matches
288
0
      struct list *newbest =
289
0
        disambiguate(*currbest, result, vline, n + 1);
290
291
      // current best and result are ambiguous
292
0
      if (!newbest)
293
0
        status = MATCHER_AMBIGUOUS;
294
      // current best is still the best, but ambiguous
295
0
      else if (newbest == *currbest
296
0
         && status == MATCHER_AMBIGUOUS)
297
0
        status = MATCHER_AMBIGUOUS;
298
      // result is better, but also ambiguous
299
0
      else if (newbest == result
300
0
         && rstat == MATCHER_AMBIGUOUS)
301
0
        status = MATCHER_AMBIGUOUS;
302
      // one or the other is superior and not ambiguous
303
0
      else
304
0
        status = MATCHER_OK;
305
306
      // delete the unnecessary result
307
0
      struct list *todelete =
308
0
        ((newbest && newbest == result) ? *currbest
309
0
                : result);
310
0
      del_arglist(todelete);
311
312
0
      *currbest = newbest ? newbest : *currbest;
313
0
    } else if (result) {
314
0
      status = rstat;
315
0
      *currbest = result;
316
0
    } else if (!*currbest) {
317
0
      status = MAX(rstat, status);
318
0
    }
319
0
  }
320
0
  if (*currbest) {
321
    // copy token, set arg and prepend to currbest
322
0
    token = start->data;
323
0
    copy = cmd_token_dup(token);
324
0
    copy->arg = XSTRDUP(MTYPE_CMD_ARG, input_token);
325
0
    listnode_add_before(*currbest, (*currbest)->head, copy);
326
0
  } else if (n + 1 == vector_active(vline) && status == MATCHER_NO_MATCH)
327
0
    status = MATCHER_INCOMPLETE;
328
329
  // cleanup
330
0
  list_delete(&next);
331
332
0
  return status;
333
0
}
334
335
static void stack_del(void *val)
336
0
{
337
0
  XFREE(MTYPE_CMD_MATCHSTACK, val);
338
0
}
339
340
enum matcher_rv command_complete(struct graph *graph, vector vline,
341
         struct list **completions)
342
0
{
343
  // pointer to next input token to match
344
0
  char *input_token;
345
0
  bool neg = is_neg(vline, 0);
346
347
0
  struct list *
348
0
    current =
349
0
           list_new(), // current nodes to match input token against
350
0
    *next = list_new(); // possible next hops after current input
351
            // token
352
0
  current->del = next->del = stack_del;
353
354
  // pointers used for iterating lists
355
0
  struct graph_node **gstack, **newstack;
356
0
  struct listnode *node;
357
358
  // add all children of start node to list
359
0
  struct graph_node *start = vector_slot(graph->nodes, 0);
360
0
  add_nexthops(next, start, &start, 0, neg);
361
362
0
  unsigned int idx;
363
0
  for (idx = 0; idx < vector_active(vline) && next->count > 0; idx++) {
364
0
    list_delete(&current);
365
0
    current = next;
366
0
    next = list_new();
367
0
    next->del = stack_del;
368
369
0
    input_token = vector_slot(vline, idx);
370
371
0
    int exact_match_exists = 0;
372
0
    for (ALL_LIST_ELEMENTS_RO(current, node, gstack))
373
0
      if (!exact_match_exists)
374
0
        exact_match_exists =
375
0
          (match_token(gstack[0]->data,
376
0
                 input_token)
377
0
           == exact_match);
378
0
      else
379
0
        break;
380
381
0
    for (ALL_LIST_ELEMENTS_RO(current, node, gstack)) {
382
0
      struct cmd_token *token = gstack[0]->data;
383
384
0
      if (token->attr & CMD_ATTR_HIDDEN)
385
0
        continue;
386
387
0
      enum match_type minmatch = min_match_level(token->type);
388
0
      trace_matcher("\"%s\" matches \"%s\" (%d) ? ",
389
0
              input_token, token->text, token->type);
390
391
0
      unsigned int last_token =
392
0
        (vector_active(vline) - 1 == idx);
393
0
      enum match_type matchtype =
394
0
        match_token(token, input_token);
395
0
      switch (matchtype) {
396
      // occurs when last token is whitespace
397
0
      case trivial_match:
398
0
        trace_matcher("trivial_match\n");
399
0
        assert(last_token);
400
0
        newstack = XMALLOC(MTYPE_CMD_MATCHSTACK,
401
0
               sizeof(struct graph_node *));
402
        /* we're not recursing here, just the first
403
         * element is OK */
404
0
        newstack[0] = gstack[0];
405
0
        listnode_add(next, newstack);
406
0
        break;
407
0
      case partly_match:
408
0
        trace_matcher("trivial_match\n");
409
0
        if (exact_match_exists && !last_token)
410
0
          break;
411
      /* fallthru */
412
0
      case exact_match:
413
0
        trace_matcher("exact_match\n");
414
0
        if (last_token) {
415
0
          newstack = XMALLOC(
416
0
            MTYPE_CMD_MATCHSTACK,
417
0
            sizeof(struct graph_node *));
418
          /* same as above, not recursing on this
419
           */
420
0
          newstack[0] = gstack[0];
421
0
          listnode_add(next, newstack);
422
0
        } else if (matchtype >= minmatch)
423
0
          add_nexthops(next, gstack[0], gstack,
424
0
                 idx + 1, neg);
425
0
        break;
426
0
      case no_match:
427
0
        trace_matcher("no_match\n");
428
0
        break;
429
0
      }
430
0
    }
431
0
  }
432
433
  /* Variable summary
434
   * -----------------------------------------------------------------
435
   * token    = last input token processed
436
   * idx      = index in `command` of last token processed
437
   * current  = set of all transitions from the previous input token
438
   * next     = set of all nodes reachable from all nodes in `matched`
439
   */
440
441
0
  enum matcher_rv mrv = idx == vector_active(vline) && next->count
442
0
              ? MATCHER_OK
443
0
              : MATCHER_NO_MATCH;
444
445
0
  *completions = NULL;
446
0
  if (!MATCHER_ERROR(mrv)) {
447
    // extract cmd_token into list
448
0
    *completions = list_new();
449
0
    for (ALL_LIST_ELEMENTS_RO(next, node, gstack)) {
450
0
      listnode_add(*completions, gstack[0]->data);
451
0
    }
452
0
  }
453
454
0
  list_delete(&current);
455
0
  list_delete(&next);
456
457
0
  return mrv;
458
0
}
459
460
/**
461
 * Adds all children that are reachable by one parser hop to the given list.
462
 * special tokens except END_TKN are treated as transparent.
463
 *
464
 * @param[in] list to add the nexthops to
465
 * @param[in] node to start calculating nexthops from
466
 * @param[in] stack listing previously visited nodes, if non-NULL.
467
 * @param[in] stackpos how many valid entries are in stack
468
 * @return the number of children added to the list
469
 *
470
 * NB: non-null "stack" means that new stacks will be added to "list" as
471
 * output, instead of direct node pointers!
472
 */
473
static int add_nexthops(struct list *list, struct graph_node *node,
474
      struct graph_node **stack, size_t stackpos, bool neg)
475
0
{
476
0
  int added = 0;
477
0
  struct graph_node *child;
478
0
  struct graph_node **nextstack;
479
0
  for (unsigned int i = 0; i < vector_active(node->to); i++) {
480
0
    child = vector_slot(node->to, i);
481
0
    size_t j;
482
0
    struct cmd_token *token = child->data;
483
0
    if (!token->allowrepeat && stack) {
484
0
      for (j = 0; j < stackpos; j++)
485
0
        if (child == stack[j])
486
0
          break;
487
0
      if (j != stackpos)
488
0
        continue;
489
0
    }
490
491
0
    if (token->type == NEG_ONLY_TKN && !neg)
492
0
      continue;
493
494
0
    if (token->type >= SPECIAL_TKN && token->type != END_TKN) {
495
0
      added +=
496
0
        add_nexthops(list, child, stack, stackpos, neg);
497
0
    } else {
498
0
      if (stack) {
499
0
        nextstack = XMALLOC(
500
0
          MTYPE_CMD_MATCHSTACK,
501
0
          (stackpos + 1)
502
0
            * sizeof(struct graph_node *));
503
0
        nextstack[0] = child;
504
0
        memcpy(nextstack + 1, stack,
505
0
               stackpos * sizeof(struct graph_node *));
506
507
0
        listnode_add(list, nextstack);
508
0
      } else
509
0
        listnode_add(list, child);
510
0
      added++;
511
0
    }
512
0
  }
513
514
0
  return added;
515
0
}
516
517
/**
518
 * Determines the node types for which a partial match may count as a full
519
 * match. Enables command abbrevations.
520
 *
521
 * @param[in] type node type
522
 * @return minimum match level needed to for a token to fully match
523
 */
524
static enum match_type min_match_level(enum cmd_token_type type)
525
0
{
526
0
  switch (type) {
527
  // anything matches a start node, for the sake of recursion
528
0
  case START_TKN:
529
0
    return no_match;
530
  // allowing words to partly match enables command abbreviation
531
0
  case WORD_TKN:
532
0
    return partly_match;
533
0
  case RANGE_TKN:
534
0
  case IPV4_TKN:
535
0
  case IPV4_PREFIX_TKN:
536
0
  case IPV6_TKN:
537
0
  case IPV6_PREFIX_TKN:
538
0
  case MAC_TKN:
539
0
  case MAC_PREFIX_TKN:
540
0
  case FORK_TKN:
541
0
  case JOIN_TKN:
542
0
  case END_TKN:
543
0
  case NEG_ONLY_TKN:
544
0
  case VARIABLE_TKN:
545
0
  case ASNUM_TKN:
546
0
    return exact_match;
547
0
  }
548
549
0
  assert(!"Reached end of function we should never hit");
550
0
}
551
552
/**
553
 * Assigns precedence scores to node types.
554
 *
555
 * @param[in] type node type to score
556
 * @return precedence score
557
 */
558
static int score_precedence(enum cmd_token_type type)
559
0
{
560
0
  switch (type) {
561
  // some of these are mutually exclusive, so they share
562
  // the same precedence value
563
0
  case IPV4_TKN:
564
0
  case IPV4_PREFIX_TKN:
565
0
  case IPV6_TKN:
566
0
  case IPV6_PREFIX_TKN:
567
0
  case MAC_TKN:
568
0
  case MAC_PREFIX_TKN:
569
0
  case ASNUM_TKN:
570
0
  case RANGE_TKN:
571
0
    return 2;
572
0
  case WORD_TKN:
573
0
    return 3;
574
0
  case VARIABLE_TKN:
575
0
    return 4;
576
0
  case JOIN_TKN:
577
0
  case START_TKN:
578
0
  case END_TKN:
579
0
  case NEG_ONLY_TKN:
580
0
  case SPECIAL_TKN:
581
0
    return 10;
582
0
  }
583
584
0
  assert(!"Reached end of function we should never hit");
585
0
}
586
587
/**
588
 * Picks the better of two possible matches for a token.
589
 *
590
 * @param[in] first candidate node matching token
591
 * @param[in] second candidate node matching token
592
 * @param[in] token the token being matched
593
 * @return the best-matching node, or NULL if the two are entirely ambiguous
594
 */
595
static struct cmd_token *disambiguate_tokens(struct cmd_token *first,
596
               struct cmd_token *second,
597
               char *input_token)
598
0
{
599
  // if the types are different, simply go off of type precedence
600
0
  if (first->type != second->type) {
601
0
    int firstprec = score_precedence(first->type);
602
0
    int secndprec = score_precedence(second->type);
603
0
    if (firstprec != secndprec)
604
0
      return firstprec < secndprec ? first : second;
605
0
    else
606
0
      return NULL;
607
0
  }
608
609
  // if they're the same, return the more exact match
610
0
  enum match_type fmtype = match_token(first, input_token);
611
0
  enum match_type smtype = match_token(second, input_token);
612
0
  if (fmtype != smtype)
613
0
    return fmtype > smtype ? first : second;
614
615
0
  return NULL;
616
0
}
617
618
/**
619
 * Picks the better of two possible matches for an input line.
620
 *
621
 * @param[in] first candidate list of cmd_token matching vline
622
 * @param[in] second candidate list of cmd_token matching vline
623
 * @param[in] vline the input line being matched
624
 * @param[in] n index into vline to start comparing at
625
 * @return the best-matching list, or NULL if the two are entirely ambiguous
626
 */
627
static struct list *disambiguate(struct list *first, struct list *second,
628
         vector vline, unsigned int n)
629
0
{
630
0
  assert(first != NULL);
631
0
  assert(second != NULL);
632
  // doesn't make sense for these to be inequal length
633
0
  assert(first->count == second->count);
634
0
  assert(first->count == vector_active(vline) - n + 1);
635
636
0
  struct listnode *fnode = listhead_unchecked(first),
637
0
      *snode = listhead_unchecked(second);
638
0
  struct cmd_token *ftok = listgetdata(fnode), *stok = listgetdata(snode),
639
0
       *best = NULL;
640
641
  // compare each token, if one matches better use that one
642
0
  for (unsigned int i = n; i < vector_active(vline); i++) {
643
0
    char *token = vector_slot(vline, i);
644
0
    if ((best = disambiguate_tokens(ftok, stok, token)))
645
0
      return best == ftok ? first : second;
646
0
    fnode = listnextnode(fnode);
647
0
    snode = listnextnode(snode);
648
0
    ftok = listgetdata(fnode);
649
0
    stok = listgetdata(snode);
650
0
  }
651
652
0
  return NULL;
653
0
}
654
655
/*
656
 * Deletion function for arglist.
657
 *
658
 * Since list->del for arglists expects all listnode->data to hold cmd_token,
659
 * but arglists have cmd_element as the data for the tail, this function
660
 * manually deletes the tail before deleting the rest of the list as usual.
661
 *
662
 * The cmd_element at the end is *not* a copy. It is the one and only.
663
 *
664
 * @param list the arglist to delete
665
 */
666
static void del_arglist(struct list *list)
667
0
{
668
  // manually delete last node
669
0
  struct listnode *tail = listtail(list);
670
0
  tail->data = NULL;
671
0
  list_delete_node(list, tail);
672
673
  // delete the rest of the list as usual
674
0
  list_delete(&list);
675
0
}
676
677
/*---------- token level matching functions ----------*/
678
679
static enum match_type match_token(struct cmd_token *token, char *input_token)
680
0
{
681
  // nothing trivially matches everything
682
0
  if (!input_token)
683
0
    return trivial_match;
684
685
0
  switch (token->type) {
686
0
  case WORD_TKN:
687
0
    return match_word(token, input_token);
688
0
  case IPV4_TKN:
689
0
    return match_ipv4(input_token);
690
0
  case IPV4_PREFIX_TKN:
691
0
    return match_ipv4_prefix(input_token);
692
0
  case IPV6_TKN:
693
0
    return match_ipv6_prefix(input_token, false);
694
0
  case IPV6_PREFIX_TKN:
695
0
    return match_ipv6_prefix(input_token, true);
696
0
  case RANGE_TKN:
697
0
    return match_range(token, input_token);
698
0
  case VARIABLE_TKN:
699
0
    return match_variable(token, input_token);
700
0
  case MAC_TKN:
701
0
    return match_mac(input_token, false);
702
0
  case MAC_PREFIX_TKN:
703
0
    return match_mac(input_token, true);
704
0
  case ASNUM_TKN:
705
0
    return asn_str2asn_match(input_token);
706
0
  case END_TKN:
707
0
  case FORK_TKN:
708
0
  case JOIN_TKN:
709
0
  case START_TKN:
710
0
  case NEG_ONLY_TKN:
711
0
    return no_match;
712
0
  }
713
714
0
  assert(!"Reached end of function we should never hit");
715
0
}
716
717
#define IPV4_ADDR_STR   "0123456789."
718
#define IPV4_PREFIX_STR "0123456789./"
719
720
static enum match_type match_ipv4(const char *str)
721
0
{
722
0
  const char *sp;
723
0
  int dots = 0, nums = 0;
724
0
  char buf[4];
725
726
0
  for (;;) {
727
0
    memset(buf, 0, sizeof(buf));
728
0
    sp = str;
729
0
    while (*str != '\0') {
730
0
      if (*str == '.') {
731
0
        if (dots >= 3)
732
0
          return no_match;
733
734
0
        if (*(str + 1) == '.')
735
0
          return no_match;
736
737
0
        if (*(str + 1) == '\0')
738
0
          return partly_match;
739
740
0
        dots++;
741
0
        break;
742
0
      }
743
0
      if (!isdigit((unsigned char)*str))
744
0
        return no_match;
745
746
0
      str++;
747
0
    }
748
749
0
    if (str - sp > 3)
750
0
      return no_match;
751
752
0
    memcpy(buf, sp, str - sp);
753
754
0
    int v = atoi(buf);
755
756
0
    if (v > 255)
757
0
      return no_match;
758
0
    if (v > 0 && buf[0] == '0')
759
0
      return no_match;
760
761
0
    nums++;
762
763
0
    if (*str == '\0')
764
0
      break;
765
766
0
    str++;
767
0
  }
768
769
0
  if (nums < 4)
770
0
    return partly_match;
771
772
0
  return exact_match;
773
0
}
774
775
static enum match_type match_ipv4_prefix(const char *str)
776
0
{
777
0
  const char *sp;
778
0
  int dots = 0;
779
0
  char buf[4];
780
781
0
  for (;;) {
782
0
    memset(buf, 0, sizeof(buf));
783
0
    sp = str;
784
0
    while (*str != '\0' && *str != '/') {
785
0
      if (*str == '.') {
786
0
        if (dots == 3)
787
0
          return no_match;
788
789
0
        if (*(str + 1) == '.' || *(str + 1) == '/')
790
0
          return no_match;
791
792
0
        if (*(str + 1) == '\0')
793
0
          return partly_match;
794
795
0
        dots++;
796
0
        break;
797
0
      }
798
799
0
      if (!isdigit((unsigned char)*str))
800
0
        return no_match;
801
802
0
      str++;
803
0
    }
804
805
0
    if (str - sp > 3)
806
0
      return no_match;
807
808
0
    memcpy(buf, sp, str - sp);
809
810
0
    int v = atoi(buf);
811
812
0
    if (v > 255)
813
0
      return no_match;
814
0
    if (v > 0 && buf[0] == '0')
815
0
      return no_match;
816
817
0
    if (dots == 3) {
818
0
      if (*str == '/') {
819
0
        if (*(str + 1) == '\0')
820
0
          return partly_match;
821
822
0
        str++;
823
0
        break;
824
0
      } else if (*str == '\0')
825
0
        return partly_match;
826
0
    }
827
828
0
    if (*str == '\0')
829
0
      return partly_match;
830
831
0
    str++;
832
0
  }
833
834
0
  sp = str;
835
0
  while (*str != '\0') {
836
0
    if (!isdigit((unsigned char)*str))
837
0
      return no_match;
838
839
0
    str++;
840
0
  }
841
842
0
  if (atoi(sp) > IPV4_MAX_BITLEN)
843
0
    return no_match;
844
845
0
  return exact_match;
846
0
}
847
848
0
#define IPV6_ADDR_STR   "0123456789abcdefABCDEF:."
849
0
#define IPV6_PREFIX_STR "0123456789abcdefABCDEF:./"
850
0
#define STATE_START     1
851
0
#define STATE_COLON     2
852
0
#define STATE_DOUBLE    3
853
0
#define STATE_ADDR      4
854
0
#define STATE_DOT       5
855
0
#define STATE_SLASH     6
856
0
#define STATE_MASK      7
857
858
static enum match_type match_ipv6_prefix(const char *str, bool prefix)
859
0
{
860
0
  int state = STATE_START;
861
0
  int colons = 0, nums = 0, double_colon = 0;
862
0
  int mask;
863
0
  const char *sp = NULL, *start = str;
864
0
  char *endptr = NULL;
865
866
0
  if (str == NULL)
867
0
    return partly_match;
868
869
0
  if (strspn(str, prefix ? IPV6_PREFIX_STR : IPV6_ADDR_STR)
870
0
      != strlen(str))
871
0
    return no_match;
872
873
0
  while (*str != '\0' && state != STATE_MASK) {
874
0
    switch (state) {
875
0
    case STATE_START:
876
0
      if (*str == ':') {
877
0
        if (*(str + 1) != ':' && *(str + 1) != '\0')
878
0
          return no_match;
879
0
        colons--;
880
0
        state = STATE_COLON;
881
0
      } else {
882
0
        sp = str;
883
0
        state = STATE_ADDR;
884
0
      }
885
886
0
      continue;
887
0
    case STATE_COLON:
888
0
      colons++;
889
0
      if (*(str + 1) == '/')
890
0
        return no_match;
891
0
      else if (*(str + 1) == ':')
892
0
        state = STATE_DOUBLE;
893
0
      else {
894
0
        sp = str + 1;
895
0
        state = STATE_ADDR;
896
0
      }
897
0
      break;
898
0
    case STATE_DOUBLE:
899
0
      if (double_colon)
900
0
        return no_match;
901
902
0
      if (*(str + 1) == ':')
903
0
        return no_match;
904
0
      else {
905
0
        if (*(str + 1) != '\0' && *(str + 1) != '/')
906
0
          colons++;
907
0
        sp = str + 1;
908
909
0
        if (*(str + 1) == '/')
910
0
          state = STATE_SLASH;
911
0
        else
912
0
          state = STATE_ADDR;
913
0
      }
914
915
0
      double_colon++;
916
0
      nums += 1;
917
0
      break;
918
0
    case STATE_ADDR:
919
0
      if (*(str + 1) == ':' || *(str + 1) == '.'
920
0
          || *(str + 1) == '\0' || *(str + 1) == '/') {
921
0
        if (str - sp > 3)
922
0
          return no_match;
923
924
0
        for (; sp <= str; sp++)
925
0
          if (*sp == '/')
926
0
            return no_match;
927
928
0
        nums++;
929
930
0
        if (*(str + 1) == ':')
931
0
          state = STATE_COLON;
932
0
        else if (*(str + 1) == '.') {
933
0
          if (colons || double_colon)
934
0
            state = STATE_DOT;
935
0
          else
936
0
            return no_match;
937
0
        } else if (*(str + 1) == '/')
938
0
          state = STATE_SLASH;
939
0
      }
940
0
      break;
941
0
    case STATE_DOT:
942
0
      state = STATE_ADDR;
943
0
      break;
944
0
    case STATE_SLASH:
945
0
      if (*(str + 1) == '\0')
946
0
        return partly_match;
947
948
0
      state = STATE_MASK;
949
0
      break;
950
0
    default:
951
0
      break;
952
0
    }
953
954
0
    if (nums > 11)
955
0
      return no_match;
956
957
0
    if (colons > 7)
958
0
      return no_match;
959
960
0
    str++;
961
0
  }
962
963
0
  if (!prefix) {
964
0
    struct sockaddr_in6 sin6_dummy;
965
0
    int ret = inet_pton(AF_INET6, start, &sin6_dummy.sin6_addr);
966
0
    return ret == 1 ? exact_match : partly_match;
967
0
  }
968
969
0
  if (state < STATE_MASK)
970
0
    return partly_match;
971
972
0
  mask = strtol(str, &endptr, 10);
973
0
  if (*endptr != '\0')
974
0
    return no_match;
975
976
0
  if (mask < 0 || mask > IPV6_MAX_BITLEN)
977
0
    return no_match;
978
979
0
  return exact_match;
980
0
}
981
982
static enum match_type match_range(struct cmd_token *token, const char *str)
983
0
{
984
0
  assert(token->type == RANGE_TKN);
985
986
0
  char *endptr = NULL;
987
0
  long long val;
988
989
0
  val = strtoll(str, &endptr, 10);
990
0
  if (*endptr != '\0')
991
0
    return no_match;
992
993
0
  if (val < token->min || val > token->max)
994
0
    return no_match;
995
0
  else
996
0
    return exact_match;
997
0
}
998
999
static enum match_type match_word(struct cmd_token *token, const char *word)
1000
0
{
1001
0
  assert(token->type == WORD_TKN);
1002
1003
  // if the passed token is 0 length, partly match
1004
0
  if (!strlen(word))
1005
0
    return partly_match;
1006
1007
  // if the passed token is strictly a prefix of the full word, partly
1008
  // match
1009
0
  if (strlen(word) < strlen(token->text))
1010
0
    return !strncmp(token->text, word, strlen(word)) ? partly_match
1011
0
                 : no_match;
1012
1013
  // if they are the same length and exactly equal, exact match
1014
0
  else if (strlen(word) == strlen(token->text))
1015
0
    return !strncmp(token->text, word, strlen(word)) ? exact_match
1016
0
                 : no_match;
1017
1018
0
  return no_match;
1019
0
}
1020
1021
static enum match_type match_variable(struct cmd_token *token, const char *word)
1022
0
{
1023
0
  assert(token->type == VARIABLE_TKN);
1024
0
  return exact_match;
1025
0
}
1026
1027
0
#define MAC_CHARS "ABCDEFabcdef0123456789:"
1028
1029
static enum match_type match_mac(const char *word, bool prefix)
1030
0
{
1031
  /* 6 2-digit hex numbers separated by 5 colons */
1032
0
  size_t mac_explen = 6 * 2 + 5;
1033
  /* '/' + 2-digit integer */
1034
0
  size_t mask_len = 1 + 2;
1035
0
  unsigned int i;
1036
0
  char *eptr;
1037
0
  unsigned int maskval;
1038
1039
  /* length check */
1040
0
  if (strlen(word) > mac_explen + (prefix ? mask_len : 0))
1041
0
    return no_match;
1042
1043
  /* address check */
1044
0
  for (i = 0; i < mac_explen; i++) {
1045
0
    if (word[i] == '\0' || !strchr(MAC_CHARS, word[i]))
1046
0
      break;
1047
0
    if (((i + 1) % 3 == 0) != (word[i] == ':'))
1048
0
      return no_match;
1049
0
  }
1050
1051
  /* incomplete address */
1052
0
  if (i < mac_explen && word[i] == '\0')
1053
0
    return partly_match;
1054
0
  else if (i < mac_explen)
1055
0
    return no_match;
1056
1057
  /* mask check */
1058
0
  if (prefix && word[i] == '/') {
1059
0
    if (word[++i] == '\0')
1060
0
      return partly_match;
1061
1062
0
    maskval = strtoul(&word[i], &eptr, 10);
1063
0
    if (*eptr != '\0' || maskval > 48)
1064
0
      return no_match;
1065
0
  } else if (prefix && word[i] == '\0') {
1066
0
    return partly_match;
1067
0
  } else if (prefix) {
1068
0
    return no_match;
1069
0
  }
1070
1071
0
  return exact_match;
1072
0
}