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