2016-07-19 23:14:27 +02:00
|
|
|
#include <zebra.h>
|
|
|
|
#include "memory.h"
|
2016-07-21 23:38:03 +02:00
|
|
|
#include "vector.h"
|
2016-07-19 23:14:27 +02:00
|
|
|
#include "command_match.h"
|
|
|
|
|
2016-07-21 23:38:03 +02:00
|
|
|
static enum match_type
|
|
|
|
match_token (struct graph_node *node, char *token, enum filter_type filter)
|
|
|
|
{
|
|
|
|
switch (node->type) {
|
|
|
|
case WORD_GN:
|
|
|
|
return cmd_word_match (node, filter, token);
|
|
|
|
case IPV4_GN:
|
|
|
|
return cmd_ipv4_match (token);
|
|
|
|
case IPV4_PREFIX_GN:
|
|
|
|
return cmd_ipv4_prefix_match (token);
|
|
|
|
case IPV6_GN:
|
|
|
|
return cmd_ipv6_match (token);
|
|
|
|
case IPV6_PREFIX_GN:
|
|
|
|
return cmd_ipv6_prefix_match (token);
|
|
|
|
case RANGE_GN:
|
|
|
|
return cmd_range_match (node, token);
|
|
|
|
case NUMBER_GN:
|
|
|
|
return node->value == atoi(token);
|
|
|
|
case VARIABLE_GN:
|
|
|
|
default:
|
|
|
|
return no_match;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Breaking up string into each command piece. I assume given
|
|
|
|
character is separated by a space character. Return value is a
|
|
|
|
vector which includes char ** data element. */
|
|
|
|
static vector
|
|
|
|
cmd_make_strvec (const char *string)
|
|
|
|
{
|
|
|
|
const char *cp, *start;
|
|
|
|
char *token;
|
|
|
|
int strlen;
|
|
|
|
vector strvec;
|
|
|
|
|
|
|
|
if (string == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
cp = string;
|
|
|
|
|
|
|
|
/* Skip white spaces. */
|
|
|
|
while (isspace ((int) *cp) && *cp != '\0')
|
|
|
|
cp++;
|
|
|
|
|
|
|
|
/* Return if there is only white spaces */
|
|
|
|
if (*cp == '\0')
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (*cp == '!' || *cp == '#')
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/* Prepare return vector. */
|
|
|
|
strvec = vector_init (VECTOR_MIN_SIZE);
|
|
|
|
|
|
|
|
/* Copy each command piece and set into vector. */
|
|
|
|
while (1)
|
|
|
|
{
|
|
|
|
start = cp;
|
|
|
|
while (!(isspace ((int) *cp) || *cp == '\r' || *cp == '\n') &&
|
|
|
|
*cp != '\0')
|
|
|
|
cp++;
|
|
|
|
strlen = cp - start;
|
|
|
|
token = XMALLOC (MTYPE_STRVEC, strlen + 1);
|
|
|
|
memcpy (token, start, strlen);
|
|
|
|
*(token + strlen) = '\0';
|
|
|
|
vector_set (strvec, token);
|
|
|
|
|
|
|
|
while ((isspace ((int) *cp) || *cp == '\n' || *cp == '\r') &&
|
|
|
|
*cp != '\0')
|
|
|
|
cp++;
|
|
|
|
|
|
|
|
if (*cp == '\0')
|
|
|
|
return strvec;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds all children that are reachable by one parser hop
|
|
|
|
* to the given list. NUL_GN, SELECTOR_GN, and OPTION_GN
|
|
|
|
* nodes are treated as though their children are attached
|
|
|
|
* to their parent.
|
|
|
|
*
|
|
|
|
* @param[out] l the list to add the children to
|
|
|
|
* @param[in] node the node to get the children of
|
|
|
|
* @return the number of children added to the list
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
add_nexthops(struct list *l, struct graph_node *node)
|
|
|
|
{
|
|
|
|
int added = 0;
|
|
|
|
struct graph_node *child;
|
|
|
|
for (unsigned int i = 0; i < vector_active(node->children); i++)
|
|
|
|
{
|
|
|
|
child = vector_slot(node->children, i);
|
|
|
|
if (child->type == OPTION_GN || child->type == SELECTOR_GN || child->type == NUL_GN)
|
|
|
|
added += add_nexthops(l, child);
|
|
|
|
else {
|
|
|
|
listnode_add(l, child);
|
|
|
|
added++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return added;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Compiles matches or next-hops for a given line of user input.
|
|
|
|
*
|
|
|
|
* Given a string of input and a start node for a matching DFA, runs the input
|
|
|
|
* against the DFA until the input is exhausted, there are no possible
|
|
|
|
* transitions, or both.
|
|
|
|
* If there are no further state transitions available, one of two scenarios is possible:
|
|
|
|
* - The end of input has been reached. This indicates a valid command.
|
|
|
|
* - The end of input has not yet been reached. The input does not match any command.
|
|
|
|
* If there are further transitions available, one of two scenarios is possible:
|
|
|
|
* - The current input is a valid prefix to a longer command
|
|
|
|
* - The current input matches a command
|
|
|
|
* - The current input matches a command, and is also a valid prefix to a longer command
|
|
|
|
*
|
|
|
|
* Any other states indicate a programming error.
|
|
|
|
*
|
|
|
|
* @param[in] start the start node of the DFA to match against
|
|
|
|
* @param[in] filter the filtering method
|
|
|
|
* @param[in] input the input string
|
|
|
|
* @return an array with two lists. The first list is
|
|
|
|
*/
|
|
|
|
struct list**
|
2016-07-19 23:14:27 +02:00
|
|
|
match_command (struct graph_node *start, enum filter_type filter, const char *input)
|
|
|
|
{
|
2016-07-21 23:38:03 +02:00
|
|
|
// break command
|
|
|
|
vector command = cmd_make_strvec (input);
|
|
|
|
|
|
|
|
// pointer to next input token to match
|
|
|
|
char *token;
|
|
|
|
|
|
|
|
struct list *current = list_new(), // current nodes to match input token against
|
|
|
|
*matched = list_new(), // current nodes that match the input token
|
|
|
|
*next = list_new(); // possible next hops to current input token
|
|
|
|
|
|
|
|
// pointers used for iterating lists
|
|
|
|
struct graph_node *cnode;
|
|
|
|
struct listnode *node;
|
|
|
|
|
|
|
|
// add all children of start node to list
|
|
|
|
add_nexthops(next, start);
|
|
|
|
|
|
|
|
unsigned int idx;
|
|
|
|
for (idx = 0; idx < vector_active(command) && next->count > 0; idx++)
|
|
|
|
{
|
|
|
|
list_free (current);
|
|
|
|
current = next;
|
|
|
|
next = list_new();
|
|
|
|
|
|
|
|
token = vector_slot(command, idx);
|
|
|
|
|
|
|
|
list_delete_all_node(matched);
|
|
|
|
|
|
|
|
for (ALL_LIST_ELEMENTS_RO(current,node,cnode))
|
|
|
|
{
|
|
|
|
if (match_token(cnode, token, filter) == exact_match) {
|
|
|
|
listnode_add(matched, cnode);
|
|
|
|
add_nexthops(next, cnode);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Variable summary
|
|
|
|
* -----------------------------------------------------------------
|
|
|
|
* token = last input token processed
|
|
|
|
* idx = index in `command` of last token processed
|
|
|
|
* current = set of all transitions from the previous input token
|
|
|
|
* matched = set of all nodes reachable with current input
|
|
|
|
* next = set of all nodes reachable from all nodes in `matched`
|
|
|
|
*/
|
|
|
|
|
|
|
|
struct list **result = malloc( 2 * sizeof(struct list *) );
|
|
|
|
result[0] = matched;
|
|
|
|
result[1] = next;
|
|
|
|
|
|
|
|
return result;
|
2016-07-19 23:14:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#define IPV4_ADDR_STR "0123456789."
|
|
|
|
#define IPV4_PREFIX_STR "0123456789./"
|
|
|
|
|
|
|
|
enum match_type
|
|
|
|
cmd_ipv4_match (const char *str)
|
|
|
|
{
|
|
|
|
struct sockaddr_in sin_dummy;
|
|
|
|
|
|
|
|
if (str == NULL)
|
|
|
|
return partly_match;
|
|
|
|
|
|
|
|
if (strspn (str, IPV4_ADDR_STR) != strlen (str))
|
|
|
|
return no_match;
|
|
|
|
|
|
|
|
if (inet_pton(AF_INET, str, &sin_dummy.sin_addr) != 1)
|
|
|
|
return no_match;
|
|
|
|
|
|
|
|
return exact_match;
|
|
|
|
}
|
|
|
|
|
|
|
|
enum match_type
|
|
|
|
cmd_ipv4_prefix_match (const char *str)
|
|
|
|
{
|
|
|
|
struct sockaddr_in sin_dummy;
|
|
|
|
const char *delim = "/\0";
|
|
|
|
char *dupe, *prefix, *mask, *context, *endptr;
|
|
|
|
int nmask = -1;
|
|
|
|
|
|
|
|
if (str == NULL)
|
|
|
|
return partly_match;
|
|
|
|
|
|
|
|
if (strspn (str, IPV4_PREFIX_STR) != strlen (str))
|
|
|
|
return no_match;
|
|
|
|
|
|
|
|
/* tokenize to address + mask */
|
|
|
|
dupe = XMALLOC(MTYPE_TMP, strlen(str)+1);
|
|
|
|
strncpy(dupe, str, strlen(str)+1);
|
|
|
|
prefix = strtok_r(dupe, delim, &context);
|
|
|
|
mask = strtok_r(NULL, delim, &context);
|
|
|
|
|
|
|
|
if (!mask)
|
|
|
|
return partly_match;
|
|
|
|
|
|
|
|
/* validate prefix */
|
|
|
|
if (inet_pton(AF_INET, prefix, &sin_dummy.sin_addr) != 1)
|
|
|
|
return no_match;
|
|
|
|
|
|
|
|
/* validate mask */
|
|
|
|
nmask = strtol (mask, &endptr, 10);
|
|
|
|
if (*endptr != '\0' || nmask < 0 || nmask > 32)
|
|
|
|
return no_match;
|
|
|
|
|
|
|
|
XFREE(MTYPE_TMP, dupe);
|
|
|
|
|
|
|
|
return exact_match;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define IPV6_ADDR_STR "0123456789abcdefABCDEF:."
|
|
|
|
#define IPV6_PREFIX_STR "0123456789abcdefABCDEF:./"
|
|
|
|
|
|
|
|
#ifdef HAVE_IPV6
|
|
|
|
enum match_type
|
|
|
|
cmd_ipv6_match (const char *str)
|
|
|
|
{
|
|
|
|
struct sockaddr_in6 sin6_dummy;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (str == NULL)
|
|
|
|
return partly_match;
|
|
|
|
|
|
|
|
if (strspn (str, IPV6_ADDR_STR) != strlen (str))
|
|
|
|
return no_match;
|
|
|
|
|
|
|
|
ret = inet_pton(AF_INET6, str, &sin6_dummy.sin6_addr);
|
|
|
|
|
|
|
|
if (ret == 1)
|
|
|
|
return exact_match;
|
|
|
|
|
|
|
|
return no_match;
|
|
|
|
}
|
|
|
|
|
|
|
|
enum match_type
|
|
|
|
cmd_ipv6_prefix_match (const char *str)
|
|
|
|
{
|
|
|
|
struct sockaddr_in6 sin6_dummy;
|
|
|
|
const char *delim = "/\0";
|
|
|
|
char *dupe, *prefix, *mask, *context, *endptr;
|
|
|
|
int nmask = -1;
|
|
|
|
|
|
|
|
if (str == NULL)
|
|
|
|
return partly_match;
|
|
|
|
|
|
|
|
if (strspn (str, IPV6_PREFIX_STR) != strlen (str))
|
|
|
|
return no_match;
|
|
|
|
|
|
|
|
/* tokenize to address + mask */
|
|
|
|
dupe = XMALLOC(MTYPE_TMP, strlen(str)+1);
|
|
|
|
strncpy(dupe, str, strlen(str)+1);
|
|
|
|
prefix = strtok_r(dupe, delim, &context);
|
|
|
|
mask = strtok_r(NULL, delim, &context);
|
|
|
|
|
|
|
|
if (!mask)
|
|
|
|
return partly_match;
|
|
|
|
|
|
|
|
/* validate prefix */
|
|
|
|
if (inet_pton(AF_INET6, prefix, &sin6_dummy.sin6_addr) != 1)
|
|
|
|
return no_match;
|
|
|
|
|
|
|
|
/* validate mask */
|
|
|
|
nmask = strtol (mask, &endptr, 10);
|
|
|
|
if (*endptr != '\0' || nmask < 0 || nmask > 128)
|
|
|
|
return no_match;
|
|
|
|
|
|
|
|
XFREE(MTYPE_TMP, dupe);
|
|
|
|
|
|
|
|
return exact_match;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
enum match_type
|
|
|
|
cmd_range_match (struct graph_node *rangenode, const char *str)
|
|
|
|
{
|
|
|
|
char *endptr = NULL;
|
|
|
|
signed long long val;
|
|
|
|
|
|
|
|
if (str == NULL)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
val = strtoll (str, &endptr, 10);
|
|
|
|
if (*endptr != '\0')
|
|
|
|
return 0;
|
|
|
|
val = llabs(val);
|
|
|
|
|
|
|
|
if (val < rangenode->min || val > rangenode->max)
|
|
|
|
return no_match;
|
|
|
|
else
|
|
|
|
return exact_match;
|
|
|
|
}
|
|
|
|
|
|
|
|
enum match_type
|
|
|
|
cmd_word_match(struct graph_node *wordnode,
|
|
|
|
enum filter_type filter,
|
|
|
|
const char *word)
|
|
|
|
{
|
|
|
|
if (filter == FILTER_RELAXED)
|
|
|
|
if (!word || !strlen(word))
|
|
|
|
return partly_match;
|
|
|
|
|
|
|
|
if (!word)
|
|
|
|
return no_match;
|
|
|
|
|
|
|
|
if (filter == FILTER_RELAXED && !strncmp(wordnode->text, word, strlen(word)))
|
|
|
|
{
|
|
|
|
if (!strcmp(wordnode->text, word))
|
|
|
|
return exact_match;
|
|
|
|
return partly_match;
|
|
|
|
}
|
|
|
|
if (filter == FILTER_STRICT && !strcmp(wordnode->text, word))
|
|
|
|
return exact_match;
|
|
|
|
|
|
|
|
return no_match;
|
|
|
|
}
|