]> git.proxmox.com Git - mirror_frr.git/blob - lib/command_match.c
lib: Fix memory leak in ipv6_prefix_match
[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
20 * along with GNU Zebra; see the file COPYING. If not, write to the Free
21 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
22 * 02111-1307, USA.
23 */
24
25 #include <zebra.h>
26 #include "command_match.h"
27 #include "command_parse.h"
28 #include "grammar_sandbox.h"
29 #include "memory.h"
30
31 /* matcher helper prototypes */
32 static int
33 add_nexthops (struct list *, struct graph_node *);
34
35 static struct list *
36 command_match_r (struct graph_node *, vector, unsigned int);
37
38 static int
39 score_precedence (enum cmd_token_type_t);
40
41 static enum match_type
42 min_match_level (enum cmd_token_type_t);
43
44 static void
45 del_arglist (struct list *);
46
47 static struct cmd_token_t *
48 disambiguate_tokens (struct cmd_token_t *, struct cmd_token_t *, char *);
49
50 static struct list *
51 disambiguate (struct list *, struct list *, vector, unsigned int);
52
53 int
54 compare_completions (const void *, const void *);
55
56 /* token matcher prototypes */
57 static enum match_type
58 match_token (struct cmd_token_t *, char *);
59
60 static enum match_type
61 match_ipv4 (const char *);
62
63 static enum match_type
64 match_ipv4_prefix (const char *);
65
66 static enum match_type
67 match_ipv6 (const char *);
68
69 static enum match_type
70 match_ipv6_prefix (const char *);
71
72 static enum match_type
73 match_range (struct cmd_token_t *, const char *);
74
75 static enum match_type
76 match_word (struct cmd_token_t *, const char *);
77
78 static enum match_type
79 match_number (struct cmd_token_t *, const char *);
80
81 static enum match_type
82 match_variable (struct cmd_token_t *, const char *);
83
84 /* matching functions */
85 static enum matcher_rv matcher_rv;
86
87 enum matcher_rv
88 command_match (struct graph *cmdgraph,
89 vector vline,
90 struct list **argv,
91 struct cmd_element **el)
92 {
93 matcher_rv = MATCHER_NO_MATCH;
94
95 // prepend a dummy token to match that pesky start node
96 vector vvline = vector_init (vline->alloced + 1);
97 vector_set_index (vvline, 0, (void *) XSTRDUP (MTYPE_TMP, "dummy"));
98 memcpy (vvline->index + 1, vline->index, sizeof (void *) * vline->alloced);
99 vvline->active = vline->active + 1;
100
101 struct graph_node *start = vector_slot (cmdgraph->nodes, 0);
102 if ((*argv = command_match_r (start, vvline, 0))) // successful match
103 {
104 struct listnode *head = listhead (*argv);
105 struct listnode *tail = listtail (*argv);
106
107 // delete dummy start node
108 del_cmd_token ((struct cmd_token_t *) head->data);
109 list_delete_node (*argv, head);
110
111 // get cmd_element out of list tail
112 *el = listgetdata (tail);
113 list_delete_node (*argv, tail);
114
115 // now argv is an ordered list of cmd_token matching the user
116 // input, with each cmd_token->arg holding the corresponding input
117 assert (*el);
118 }
119
120 // free the leader token we alloc'd
121 XFREE (MTYPE_TMP, vector_slot (vvline, 0));
122 // free vector
123 vector_free (vvline);
124
125 return matcher_rv;
126 }
127
128 /**
129 * Builds an argument list given a DFA and a matching input line.
130 *
131 * First the function determines if the node it is passed matches the first
132 * token of input. If it does not, it returns NULL (MATCHER_NO_MATCH). If it
133 * does match, then it saves the input token as the head of an argument list.
134 *
135 * The next step is to see if there is further input in the input line. If
136 * there is not, the current node's children are searched to see if any of them
137 * are leaves (type END_TKN). If this is the case, then the bottom of the
138 * recursion stack has been reached, the leaf is pushed onto the argument list,
139 * the current node is pushed, and the resulting argument list is
140 * returned (MATCHER_OK). If it is not the case, NULL is returned, indicating
141 * that there is no match for the input along this path (MATCHER_INCOMPLETE).
142 *
143 * If there is further input, then the function recurses on each of the current
144 * node's children, passing them the input line minus the token that was just
145 * matched. For each child, the return value of the recursive call is
146 * inspected. If it is null, then there is no match for the input along the
147 * subgraph headed by that child. If it is not null, then there is at least one
148 * input match in that subgraph (more on this in a moment).
149 *
150 * If a recursive call on a child returns a non-null value, then it has matched
151 * the input given it on the subgraph that starts with that child. However, due
152 * to the flexibility of the grammar, it is sometimes the case that two or more
153 * child graphs match the same input (two or more of the recursive calls have
154 * non-NULL return values). This is not a valid state, since only one true
155 * match is possible. In order to resolve this conflict, the function keeps a
156 * reference to the child node that most specifically matches the input. This
157 * is done by assigning each node type a precedence. If a child is found to
158 * match the remaining input, then the precedence values of the current
159 * best-matching child and this new match are compared. The node with higher
160 * precedence is kept, and the other match is discarded. Due to the recursive
161 * nature of this function, it is only necessary to compare the precedence of
162 * immediate children, since all subsequent children will already have been
163 * disambiguated in this way.
164 *
165 * In the event that two children are found to match with the same precedence,
166 * then the input is ambiguous for the passed cmd_element and NULL is returned.
167 *
168 * The ultimate return value is an ordered linked list of nodes that comprise
169 * the best match for the command, each with their `arg` fields pointing to the
170 * matching token string.
171 *
172 * @param[in] start the start node.
173 * @param[in] vline the vectorized input line.
174 * @param[in] n the index of the first input token.
175 */
176 static struct list *
177 command_match_r (struct graph_node *start, vector vline, unsigned int n)
178 {
179 assert (n < vector_active (vline));
180
181 // get the minimum match level that can count as a full match
182 struct cmd_token_t *token = start->data;
183 enum match_type minmatch = min_match_level (token->type);
184
185 // get the current operating input token
186 char *input_token = vector_slot (vline, n);
187
188 // if we don't match this node, die
189 if (match_token (token, input_token) < minmatch)
190 return NULL;
191
192 // pointers for iterating linklist
193 struct listnode *ln;
194 struct graph_node *gn;
195
196 // get all possible nexthops
197 struct list *next = list_new();
198 add_nexthops (next, start);
199
200 // determine the best match
201 int ambiguous = 0;
202 struct list *currbest = NULL;
203 for (ALL_LIST_ELEMENTS_RO (next,ln,gn))
204 {
205 // if we've matched all input we're looking for END_TKN
206 if (n+1 == vector_active (vline))
207 {
208 struct cmd_token_t *tok = gn->data;
209 if (tok->type == END_TKN)
210 {
211 currbest = list_new();
212 // node should have one child node with the element
213 struct graph_node *leaf = vector_slot (gn->to, 0);
214 // last node in the list will hold the cmd_element;
215 // this is important because list_delete() expects
216 // that all nodes have the same data type, so when
217 // deleting this list the last node must be
218 // manually deleted
219 struct cmd_element *el = leaf->data;
220 listnode_add (currbest, copy_cmd_element (el));
221 currbest->del = (void (*)(void *)) &del_cmd_token;
222 break;
223 }
224 else continue;
225 }
226
227 // else recurse on candidate child node
228 struct list *result = command_match_r (gn, vline, n+1);
229
230 // save the best match
231 if (result && currbest)
232 {
233 // pick the best of two matches
234 struct list *newbest = disambiguate (currbest, result, vline, n+1);
235 // set ambiguity flag
236 ambiguous = !newbest || (ambiguous && newbest == currbest);
237 // delete the unnecessary result
238 struct list *todelete = ((newbest && newbest == result) ? currbest : result);
239 del_arglist (todelete);
240
241 currbest = newbest ? newbest : currbest;
242 }
243 else if (result)
244 currbest = result;
245 }
246
247 if (currbest)
248 {
249 if (ambiguous)
250 {
251 del_arglist (currbest);
252 currbest = NULL;
253 matcher_rv = MATCHER_AMBIGUOUS;
254 }
255 else
256 {
257 // copy token, set arg and prepend to currbest
258 struct cmd_token_t *token = start->data;
259 struct cmd_token_t *copy = copy_cmd_token (token);
260 copy->arg = XSTRDUP (MTYPE_CMD_TOKENS, input_token);
261 list_add_node_prev (currbest, currbest->head, copy);
262 matcher_rv = MATCHER_OK;
263 }
264 }
265 else if (n+1 == vector_active (vline) && matcher_rv == MATCHER_NO_MATCH)
266 matcher_rv = MATCHER_INCOMPLETE;
267
268 // cleanup
269 list_delete (next);
270
271 return currbest;
272 }
273
274 enum matcher_rv
275 command_complete (struct graph *graph,
276 vector vline,
277 struct list **completions)
278 {
279 // pointer to next input token to match
280 char *input_token;
281
282 struct list *current = list_new(), // current nodes to match input token against
283 *next = list_new(); // possible next hops after current input token
284
285 // pointers used for iterating lists
286 struct graph_node *gn;
287 struct listnode *node;
288
289 // add all children of start node to list
290 struct graph_node *start = vector_slot (graph->nodes, 0);
291 add_nexthops (next, start);
292
293 unsigned int idx;
294 for (idx = 0; idx < vector_active (vline) && next->count > 0; idx++)
295 {
296 list_delete (current);
297 current = next;
298 next = list_new();
299
300 input_token = vector_slot (vline, idx);
301
302 for (ALL_LIST_ELEMENTS_RO (current,node,gn))
303 {
304 struct cmd_token_t *token = gn->data;
305 switch (match_token (token, input_token))
306 {
307 case partly_match:
308 if (idx == vector_active (vline) - 1)
309 {
310 listnode_add (next, gn);
311 break;
312 }
313 case exact_match:
314 add_nexthops (next, gn);
315 break;
316 default:
317 break;
318 }
319 }
320 }
321
322 /* Variable summary
323 * -----------------------------------------------------------------
324 * token = last input token processed
325 * idx = index in `command` of last token processed
326 * current = set of all transitions from the previous input token
327 * next = set of all nodes reachable from all nodes in `matched`
328 */
329
330 matcher_rv =
331 idx == vector_active(vline) && next->count ?
332 MATCHER_OK :
333 MATCHER_NO_MATCH;
334
335 // extract cmd_token into list
336 *completions = list_new ();
337 for (ALL_LIST_ELEMENTS_RO (next,node,gn))
338 listnode_add (*completions, gn->data);
339
340 list_delete (current);
341 list_delete (next);
342
343 return matcher_rv;
344 }
345
346 /**
347 * Adds all children that are reachable by one parser hop to the given list.
348 * NUL_TKN, SELECTOR_TKN, and OPTION_TKN nodes are treated as transparent.
349 *
350 * @param[in] list to add the nexthops to
351 * @param[in] node to start calculating nexthops from
352 * @return the number of children added to the list
353 */
354 static int
355 add_nexthops (struct list *list, struct graph_node *node)
356 {
357 int added = 0;
358 struct graph_node *child;
359 for (unsigned int i = 0; i < vector_active (node->to); i++)
360 {
361 child = vector_slot (node->to, i);
362 struct cmd_token_t *token = child->data;
363 switch (token->type)
364 {
365 case OPTION_TKN:
366 case SELECTOR_TKN:
367 case NUL_TKN:
368 added += add_nexthops (list, child);
369 break;
370 default:
371 listnode_add (list, child);
372 added++;
373 }
374 }
375
376 return added;
377 }
378
379 /**
380 * Determines the node types for which a partial match may count as a full
381 * match. Enables command abbrevations.
382 *
383 * @param[in] type node type
384 * @return minimum match level needed to for a token to fully match
385 */
386 static enum match_type
387 min_match_level (enum cmd_token_type_t type)
388 {
389 switch (type)
390 {
391 // anything matches a start node, for the sake of recursion
392 case START_TKN:
393 return no_match;
394 // allowing words to partly match enables command abbreviation
395 case WORD_TKN:
396 return partly_match;
397 default:
398 return exact_match;
399 }
400 }
401
402 /**
403 * Assigns precedence scores to node types.
404 *
405 * @param[in] type node type to score
406 * @return precedence score
407 */
408 static int
409 score_precedence (enum cmd_token_type_t type)
410 {
411 switch (type)
412 {
413 // some of these are mutually exclusive, so they share
414 // the same precedence value
415 case IPV4_TKN:
416 case IPV4_PREFIX_TKN:
417 case IPV6_TKN:
418 case IPV6_PREFIX_TKN:
419 case NUMBER_TKN:
420 return 1;
421 case RANGE_TKN:
422 return 2;
423 case WORD_TKN:
424 return 3;
425 case VARIABLE_TKN:
426 return 4;
427 default:
428 return 10;
429 }
430 }
431
432 /**
433 * Picks the better of two possible matches for a token.
434 *
435 * @param[in] first candidate node matching token
436 * @param[in] second candidate node matching token
437 * @param[in] token the token being matched
438 * @return the best-matching node, or NULL if the two are entirely ambiguous
439 */
440 static struct cmd_token_t *
441 disambiguate_tokens (struct cmd_token_t *first,
442 struct cmd_token_t *second,
443 char *input_token)
444 {
445 // if the types are different, simply go off of type precedence
446 if (first->type != second->type)
447 {
448 int firstprec = score_precedence (first->type);
449 int secndprec = score_precedence (second->type);
450 if (firstprec != secndprec)
451 return firstprec < secndprec ? first : second;
452 else
453 return NULL;
454 }
455
456 // if they're the same, return the more exact match
457 enum match_type fmtype = match_token (first, input_token);
458 enum match_type smtype = match_token (second, input_token);
459 if (fmtype != smtype)
460 return fmtype > smtype ? first : second;
461
462 return NULL;
463 }
464
465 /**
466 * Picks the better of two possible matches for an input line.
467 *
468 * @param[in] first candidate list of cmd_token_t matching vline
469 * @param[in] second candidate list of cmd_token_t matching vline
470 * @param[in] vline the input line being matched
471 * @param[in] n index into vline to start comparing at
472 * @return the best-matching list, or NULL if the two are entirely ambiguous
473 */
474 static struct list *
475 disambiguate (struct list *first,
476 struct list *second,
477 vector vline,
478 unsigned int n)
479 {
480 // doesn't make sense for these to be inequal length
481 assert (first->count == second->count);
482 assert (first->count == vector_active (vline) - n+1);
483
484 struct listnode *fnode = listhead (first),
485 *snode = listhead (second);
486 struct cmd_token_t *ftok = listgetdata (fnode),
487 *stok = listgetdata (snode),
488 *best = NULL;
489
490 // compare each token, if one matches better use that one
491 for (unsigned int i = n; i < vector_active (vline); i++)
492 {
493 char *token = vector_slot(vline, i);
494 if ((best = disambiguate_tokens (ftok, stok, token)))
495 return best == ftok ? first : second;
496 fnode = listnextnode (fnode);
497 snode = listnextnode (snode);
498 ftok = listgetdata (fnode);
499 stok = listgetdata (snode);
500 }
501
502 return NULL;
503 }
504
505 /*
506 * Deletion function for arglist.
507 *
508 * Since list->del for arglists expects all listnode->data to hold cmd_token,
509 * but arglists have cmd_element as the data for the tail, this function
510 * manually deletes the tail before deleting the rest of the list as usual.
511 *
512 * @param list the arglist to delete
513 */
514 static void
515 del_arglist (struct list *list)
516 {
517 // manually delete last node
518 struct listnode *tail = listtail (list);
519 del_cmd_element (tail->data);
520 tail->data = NULL;
521 list_delete_node (list, tail);
522
523 // delete the rest of the list as usual
524 list_delete (list);
525 }
526
527 /*---------- token level matching functions ----------*/
528
529 static enum match_type
530 match_token (struct cmd_token_t *token, char *input_token)
531 {
532 switch (token->type) {
533 case WORD_TKN:
534 return match_word (token, input_token);
535 case IPV4_TKN:
536 return match_ipv4 (input_token);
537 case IPV4_PREFIX_TKN:
538 return match_ipv4_prefix (input_token);
539 case IPV6_TKN:
540 return match_ipv6 (input_token);
541 case IPV6_PREFIX_TKN:
542 return match_ipv6_prefix (input_token);
543 case RANGE_TKN:
544 return match_range (token, input_token);
545 case NUMBER_TKN:
546 return match_number (token, input_token);
547 case VARIABLE_TKN:
548 return match_variable (token, input_token);
549 case END_TKN:
550 default:
551 return no_match;
552 }
553 }
554
555 #define IPV4_ADDR_STR "0123456789."
556 #define IPV4_PREFIX_STR "0123456789./"
557
558 static enum match_type
559 match_ipv4 (const char *str)
560 {
561 const char *sp;
562 int dots = 0, nums = 0;
563 char buf[4];
564
565 if (str == NULL)
566 return partly_match;
567
568 for (;;)
569 {
570 memset (buf, 0, sizeof (buf));
571 sp = str;
572 while (*str != '\0')
573 {
574 if (*str == '.')
575 {
576 if (dots >= 3)
577 return no_match;
578
579 if (*(str + 1) == '.')
580 return no_match;
581
582 if (*(str + 1) == '\0')
583 return partly_match;
584
585 dots++;
586 break;
587 }
588 if (!isdigit ((int) *str))
589 return no_match;
590
591 str++;
592 }
593
594 if (str - sp > 3)
595 return no_match;
596
597 strncpy (buf, sp, str - sp);
598 if (atoi (buf) > 255)
599 return no_match;
600
601 nums++;
602
603 if (*str == '\0')
604 break;
605
606 str++;
607 }
608
609 if (nums < 4)
610 return partly_match;
611
612 return exact_match;
613 }
614
615 static enum match_type
616 match_ipv4_prefix (const char *str)
617 {
618 const char *sp;
619 int dots = 0;
620 char buf[4];
621
622 if (str == NULL)
623 return partly_match;
624
625 for (;;)
626 {
627 memset (buf, 0, sizeof (buf));
628 sp = str;
629 while (*str != '\0' && *str != '/')
630 {
631 if (*str == '.')
632 {
633 if (dots == 3)
634 return no_match;
635
636 if (*(str + 1) == '.' || *(str + 1) == '/')
637 return no_match;
638
639 if (*(str + 1) == '\0')
640 return partly_match;
641
642 dots++;
643 break;
644 }
645
646 if (!isdigit ((int) *str))
647 return no_match;
648
649 str++;
650 }
651
652 if (str - sp > 3)
653 return no_match;
654
655 strncpy (buf, sp, str - sp);
656 if (atoi (buf) > 255)
657 return no_match;
658
659 if (dots == 3)
660 {
661 if (*str == '/')
662 {
663 if (*(str + 1) == '\0')
664 return partly_match;
665
666 str++;
667 break;
668 }
669 else if (*str == '\0')
670 return partly_match;
671 }
672
673 if (*str == '\0')
674 return partly_match;
675
676 str++;
677 }
678
679 sp = str;
680 while (*str != '\0')
681 {
682 if (!isdigit ((int) *str))
683 return no_match;
684
685 str++;
686 }
687
688 if (atoi (sp) > 32)
689 return no_match;
690
691 return exact_match;
692 }
693
694 #ifdef HAVE_IPV6
695 #define IPV6_ADDR_STR "0123456789abcdefABCDEF:."
696 #define IPV6_PREFIX_STR "0123456789abcdefABCDEF:./"
697
698 static enum match_type
699 match_ipv6 (const char *str)
700 {
701 struct sockaddr_in6 sin6_dummy;
702 int ret;
703
704 if (str == NULL)
705 return partly_match;
706
707 if (strspn (str, IPV6_ADDR_STR) != strlen (str))
708 return no_match;
709
710 ret = inet_pton(AF_INET6, str, &sin6_dummy.sin6_addr);
711
712 if (ret == 1)
713 return exact_match;
714
715 return no_match;
716 }
717
718 static enum match_type
719 match_ipv6_prefix (const char *str)
720 {
721 struct sockaddr_in6 sin6_dummy;
722 const char *delim = "/\0";
723 char *tofree, *dupe, *prefix, *mask, *endptr;
724 int nmask = -1;
725
726 if (str == NULL)
727 return partly_match;
728
729 if (strspn (str, IPV6_PREFIX_STR) != strlen (str))
730 return no_match;
731
732 /* tokenize to prefix + mask */
733 tofree = dupe = XSTRDUP (MTYPE_TMP, str);
734 prefix = strsep (&dupe, delim);
735 mask = dupe;
736
737 /* validate prefix */
738 if (inet_pton (AF_INET6, prefix, &sin6_dummy.sin6_addr) != 1)
739 {
740 XFREE (MTYPE_TMP, tofree);
741 return no_match;
742 }
743
744 /* validate mask */
745 if (!mask)
746 {
747 XFREE (MTYPE_TMP, tofree);
748 return partly_match;
749 }
750
751 nmask = strtoimax (mask, &endptr, 10);
752 if (*endptr != '\0' || nmask < 0 || nmask > 128)
753 {
754 XFREE (MTYPE_TMP, tofree);
755 return no_match;
756 }
757
758 XFREE (MTYPE_TMP, tofree);
759 return exact_match;
760 }
761 #endif
762
763 static enum match_type
764 match_range (struct cmd_token_t *token, const char *str)
765 {
766 assert (token->type == RANGE_TKN);
767
768 char *endptr = NULL;
769 long long val;
770
771 if (str == NULL)
772 return 1;
773
774 val = strtoll (str, &endptr, 10);
775 if (*endptr != '\0')
776 return 0;
777
778 if (val < token->min || val > token->max)
779 return no_match;
780 else
781 return exact_match;
782 }
783
784 static enum match_type
785 match_word (struct cmd_token_t *token, const char *word)
786 {
787 assert (token->type == WORD_TKN);
788
789 // if the passed token is null or 0 length, partly match
790 if (!word || !strlen(word))
791 return partly_match;
792
793 // if the passed token is strictly a prefix of the full word, partly match
794 if (strlen (word) < strlen (token->text))
795 return !strncmp (token->text, word, strlen (word)) ?
796 partly_match :
797 no_match;
798
799 // if they are the same length and exactly equal, exact match
800 else if (strlen (word) == strlen (token->text))
801 return !strncmp (token->text, word, strlen (word)) ? exact_match : no_match;
802
803 return no_match;
804 }
805
806 static enum match_type
807 match_number (struct cmd_token_t *token, const char *word)
808 {
809 assert (token->type == NUMBER_TKN);
810
811 if (!strcmp ("\0", word)) return no_match;
812 char *endptr;
813 long long num = strtoll (word, &endptr, 10);
814 if (endptr != '\0') return no_match;
815 return num == token->value ? exact_match : no_match;
816 }
817
818 #define VARIABLE_ALPHABET \
819 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890:"
820
821 static enum match_type
822 match_variable (struct cmd_token_t *token, const char *word)
823 {
824 assert (token->type == VARIABLE_TKN);
825
826 return strlen (word) == strspn(word, VARIABLE_ALPHABET) ?
827 exact_match : no_match;
828 }