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