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