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