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