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