| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| #include <zebra.h> |
| |
| #include "command_match.h" |
| #include "memory.h" |
| |
| DEFINE_MTYPE_STATIC(LIB, CMD_MATCHSTACK, "Command Match Stack") |
| |
| #ifdef TRACE_MATCHER |
| #define TM 1 |
| #else |
| #define TM 0 |
| #endif |
| |
| #define trace_matcher(...) \ |
| do { \ |
| if (TM) \ |
| fprintf(stderr, __VA_ARGS__); \ |
| } while (0); |
| |
| |
| static int add_nexthops(struct list *, struct graph_node *, |
| struct graph_node **, size_t); |
| |
| static enum matcher_rv command_match_r(struct graph_node *, vector, |
| unsigned int, struct graph_node **, |
| struct list **); |
| |
| static int score_precedence(enum cmd_token_type); |
| |
| static enum match_type min_match_level(enum cmd_token_type); |
| |
| static void del_arglist(struct list *); |
| |
| static struct cmd_token *disambiguate_tokens(struct cmd_token *, |
| struct cmd_token *, char *); |
| |
| static struct list *disambiguate(struct list *, struct list *, vector, |
| unsigned int); |
| |
| int compare_completions(const void *, const void *); |
| |
| |
| static enum match_type match_token(struct cmd_token *, char *); |
| |
| static enum match_type match_ipv4(const char *); |
| |
| static enum match_type match_ipv4_prefix(const char *); |
| |
| static enum match_type match_ipv6_prefix(const char *, bool); |
| |
| static enum match_type match_range(struct cmd_token *, const char *); |
| |
| static enum match_type match_word(struct cmd_token *, const char *); |
| |
| static enum match_type match_variable(struct cmd_token *, const char *); |
| |
| static enum match_type match_mac(const char *, bool); |
| |
| enum matcher_rv command_match(struct graph *cmdgraph, vector vline, |
| struct list **argv, const struct cmd_element **el) |
| { |
| struct graph_node *stack[CMD_ARGC_MAX]; |
| enum matcher_rv status; |
| *argv = NULL; |
| |
| |
| vector vvline = vector_init(vline->alloced + 1); |
| vector_set_index(vvline, 0, XSTRDUP(MTYPE_TMP, "dummy")); |
| memcpy(vvline->index + 1, vline->index, |
| sizeof(void *) * vline->alloced); |
| vvline->active = vline->active + 1; |
| |
| struct graph_node *start = vector_slot(cmdgraph->nodes, 0); |
| status = command_match_r(start, vvline, 0, stack, argv); |
| if (status == MATCHER_OK) { |
| struct listnode *head = listhead(*argv); |
| struct listnode *tail = listtail(*argv); |
| |
| assert(head); |
| assert(tail); |
| |
| |
| cmd_token_del((struct cmd_token *)head->data); |
| list_delete_node(*argv, head); |
| |
| |
| *el = listgetdata(tail); |
| list_delete_node(*argv, tail); |
| |
| |
| |
| |
| assert(*el); |
| } else if (*argv) { |
| del_arglist(*argv); |
| *argv = NULL; |
| } |
| |
| if (!*el) { |
| trace_matcher("No match\n"); |
| } else { |
| trace_matcher("Matched command\n->string %s\n->desc %s\n", |
| (*el)->string, (*el)->doc); |
| } |
| |
| |
| XFREE(MTYPE_TMP, vector_slot(vvline, 0)); |
| |
| vector_free(vvline); |
| |
| return status; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static enum matcher_rv command_match_r(struct graph_node *start, vector vline, |
| unsigned int n, |
| struct graph_node **stack, |
| struct list **currbest) |
| { |
| assert(n < vector_active(vline)); |
| |
| enum matcher_rv status = MATCHER_NO_MATCH; |
| |
| |
| struct cmd_token *copy, *token = start->data; |
| enum match_type minmatch = min_match_level(token->type); |
| |
| |
| |
| |
| if (n == CMD_ARGC_MAX) |
| return MATCHER_NO_MATCH; |
| if (!token->allowrepeat) |
| for (size_t s = 0; s < n; s++) |
| if (stack[s] == start) |
| return MATCHER_NO_MATCH; |
| |
| |
| char *input_token = vector_slot(vline, n); |
| |
| #ifdef TRACE_MATCHER |
| fprintf(stdout, "\"%-20s\" matches \"%-30s\" ? ", input_token, |
| token->text); |
| enum match_type mt = match_token(token, input_token); |
| fprintf(stdout, "type: %d ", token->type); |
| fprintf(stdout, "min: %d - ", minmatch); |
| switch (mt) { |
| case trivial_match: |
| fprintf(stdout, "trivial_match "); |
| break; |
| case no_match: |
| fprintf(stdout, "no_match "); |
| break; |
| case partly_match: |
| fprintf(stdout, "partly_match "); |
| break; |
| case exact_match: |
| fprintf(stdout, "exact_match "); |
| break; |
| } |
| if (mt >= minmatch) |
| fprintf(stdout, " MATCH"); |
| fprintf(stdout, "\n"); |
| #endif |
| |
| |
| if (match_token(token, input_token) < minmatch) |
| return MATCHER_NO_MATCH; |
| |
| stack[n] = start; |
| |
| |
| struct listnode *ln; |
| struct graph_node *gn; |
| |
| |
| struct list *next = list_new(); |
| add_nexthops(next, start, NULL, 0); |
| |
| |
| for (ALL_LIST_ELEMENTS_RO(next, ln, gn)) { |
| |
| if (n + 1 == vector_active(vline)) { |
| struct cmd_token *tok = gn->data; |
| if (tok->type == END_TKN) { |
| |
| if (*currbest) { |
| status = MATCHER_AMBIGUOUS; |
| break; |
| } else { |
| status = MATCHER_OK; |
| } |
| *currbest = list_new(); |
| |
| |
| struct graph_node *leaf = |
| vector_slot(gn->to, 0); |
| |
| |
| |
| |
| |
| struct cmd_element *el = leaf->data; |
| listnode_add(*currbest, el); |
| (*currbest)->del = |
| (void (*)(void *)) & cmd_token_del; |
| |
| |
| |
| } |
| continue; |
| } |
| |
| |
| struct list *result = NULL; |
| enum matcher_rv rstat = |
| command_match_r(gn, vline, n + 1, stack, &result); |
| |
| |
| if (result && *currbest) { |
| |
| struct list *newbest = |
| disambiguate(*currbest, result, vline, n + 1); |
| |
| |
| if (!newbest) |
| status = MATCHER_AMBIGUOUS; |
| |
| else if (newbest == *currbest |
| && status == MATCHER_AMBIGUOUS) |
| status = MATCHER_AMBIGUOUS; |
| |
| else if (newbest == result |
| && rstat == MATCHER_AMBIGUOUS) |
| status = MATCHER_AMBIGUOUS; |
| |
| else |
| status = MATCHER_OK; |
| |
| |
| struct list *todelete = |
| ((newbest && newbest == result) ? *currbest |
| : result); |
| del_arglist(todelete); |
| |
| *currbest = newbest ? newbest : *currbest; |
| } else if (result) { |
| status = rstat; |
| *currbest = result; |
| } else if (!*currbest) { |
| status = MAX(rstat, status); |
| } |
| } |
| if (*currbest) { |
| |
| token = start->data; |
| copy = cmd_token_dup(token); |
| copy->arg = XSTRDUP(MTYPE_CMD_ARG, input_token); |
| listnode_add_before(*currbest, (*currbest)->head, copy); |
| } else if (n + 1 == vector_active(vline) && status == MATCHER_NO_MATCH) |
| status = MATCHER_INCOMPLETE; |
| |
| |
| list_delete(&next); |
| |
| return status; |
| } |
| |
| static void stack_del(void *val) |
| { |
| XFREE(MTYPE_CMD_MATCHSTACK, val); |
| } |
| |
| enum matcher_rv command_complete(struct graph *graph, vector vline, |
| struct list **completions) |
| { |
| |
| char *input_token; |
| |
| struct list * |
| current = |
| list_new(), // current nodes to match input token against |
| *next = list_new(); |
| |
| current->del = next->del = stack_del; |
| |
| |
| struct graph_node **gstack, **newstack; |
| struct listnode *node; |
| |
| |
| struct graph_node *start = vector_slot(graph->nodes, 0); |
| add_nexthops(next, start, &start, 0); |
| |
| unsigned int idx; |
| for (idx = 0; idx < vector_active(vline) && next->count > 0; idx++) { |
| list_delete(¤t); |
| current = next; |
| next = list_new(); |
| next->del = stack_del; |
| |
| input_token = vector_slot(vline, idx); |
| |
| int exact_match_exists = 0; |
| for (ALL_LIST_ELEMENTS_RO(current, node, gstack)) |
| if (!exact_match_exists) |
| exact_match_exists = |
| (match_token(gstack[0]->data, |
| input_token) |
| == exact_match); |
| else |
| break; |
| |
| for (ALL_LIST_ELEMENTS_RO(current, node, gstack)) { |
| struct cmd_token *token = gstack[0]->data; |
| |
| if (token->attr == CMD_ATTR_HIDDEN |
| || token->attr == CMD_ATTR_DEPRECATED) |
| continue; |
| |
| enum match_type minmatch = min_match_level(token->type); |
| trace_matcher("\"%s\" matches \"%s\" (%d) ? ", |
| input_token, token->text, token->type); |
| |
| unsigned int last_token = |
| (vector_active(vline) - 1 == idx); |
| enum match_type matchtype = |
| match_token(token, input_token); |
| switch (matchtype) { |
| |
| case trivial_match: |
| trace_matcher("trivial_match\n"); |
| assert(last_token); |
| newstack = XMALLOC(MTYPE_CMD_MATCHSTACK, |
| sizeof(struct graph_node *)); |
| |
| |
| newstack[0] = gstack[0]; |
| listnode_add(next, newstack); |
| break; |
| case partly_match: |
| trace_matcher("trivial_match\n"); |
| if (exact_match_exists && !last_token) |
| break; |
| |
| case exact_match: |
| trace_matcher("exact_match\n"); |
| if (last_token) { |
| newstack = XMALLOC( |
| MTYPE_CMD_MATCHSTACK, |
| sizeof(struct graph_node *)); |
| |
| |
| newstack[0] = gstack[0]; |
| listnode_add(next, newstack); |
| } else if (matchtype >= minmatch) |
| add_nexthops(next, gstack[0], gstack, |
| idx + 1); |
| break; |
| default: |
| trace_matcher("no_match\n"); |
| break; |
| } |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| enum matcher_rv mrv = idx == vector_active(vline) && next->count |
| ? MATCHER_OK |
| : MATCHER_NO_MATCH; |
| |
| *completions = NULL; |
| if (!MATCHER_ERROR(mrv)) { |
| |
| *completions = list_new(); |
| for (ALL_LIST_ELEMENTS_RO(next, node, gstack)) { |
| listnode_add(*completions, gstack[0]->data); |
| } |
| } |
| |
| list_delete(¤t); |
| list_delete(&next); |
| |
| return mrv; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static int add_nexthops(struct list *list, struct graph_node *node, |
| struct graph_node **stack, size_t stackpos) |
| { |
| int added = 0; |
| struct graph_node *child; |
| struct graph_node **nextstack; |
| for (unsigned int i = 0; i < vector_active(node->to); i++) { |
| child = vector_slot(node->to, i); |
| size_t j; |
| struct cmd_token *token = child->data; |
| if (!token->allowrepeat && stack) { |
| for (j = 0; j < stackpos; j++) |
| if (child == stack[j]) |
| break; |
| if (j != stackpos) |
| continue; |
| } |
| if (token->type >= SPECIAL_TKN && token->type != END_TKN) { |
| added += add_nexthops(list, child, stack, stackpos); |
| } else { |
| if (stack) { |
| nextstack = XMALLOC( |
| MTYPE_CMD_MATCHSTACK, |
| (stackpos + 1) |
| * sizeof(struct graph_node *)); |
| nextstack[0] = child; |
| memcpy(nextstack + 1, stack, |
| stackpos * sizeof(struct graph_node *)); |
| |
| listnode_add(list, nextstack); |
| } else |
| listnode_add(list, child); |
| added++; |
| } |
| } |
| |
| return added; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| static enum match_type min_match_level(enum cmd_token_type type) |
| { |
| switch (type) { |
| |
| case START_TKN: |
| return no_match; |
| |
| case WORD_TKN: |
| return partly_match; |
| default: |
| return exact_match; |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| static int score_precedence(enum cmd_token_type type) |
| { |
| switch (type) { |
| |
| |
| case IPV4_TKN: |
| case IPV4_PREFIX_TKN: |
| case IPV6_TKN: |
| case IPV6_PREFIX_TKN: |
| case MAC_TKN: |
| case MAC_PREFIX_TKN: |
| case RANGE_TKN: |
| return 2; |
| case WORD_TKN: |
| return 3; |
| case VARIABLE_TKN: |
| return 4; |
| default: |
| return 10; |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static struct cmd_token *disambiguate_tokens(struct cmd_token *first, |
| struct cmd_token *second, |
| char *input_token) |
| { |
| |
| if (first->type != second->type) { |
| int firstprec = score_precedence(first->type); |
| int secndprec = score_precedence(second->type); |
| if (firstprec != secndprec) |
| return firstprec < secndprec ? first : second; |
| else |
| return NULL; |
| } |
| |
| |
| enum match_type fmtype = match_token(first, input_token); |
| enum match_type smtype = match_token(second, input_token); |
| if (fmtype != smtype) |
| return fmtype > smtype ? first : second; |
| |
| return NULL; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static struct list *disambiguate(struct list *first, struct list *second, |
| vector vline, unsigned int n) |
| { |
| assert(first != NULL); |
| assert(second != NULL); |
| |
| assert(first->count == second->count); |
| assert(first->count == vector_active(vline) - n + 1); |
| |
| struct listnode *fnode = listhead_unchecked(first), |
| *snode = listhead_unchecked(second); |
| struct cmd_token *ftok = listgetdata(fnode), *stok = listgetdata(snode), |
| *best = NULL; |
| |
| |
| for (unsigned int i = n; i < vector_active(vline); i++) { |
| char *token = vector_slot(vline, i); |
| if ((best = disambiguate_tokens(ftok, stok, token))) |
| return best == ftok ? first : second; |
| fnode = listnextnode(fnode); |
| snode = listnextnode(snode); |
| ftok = listgetdata(fnode); |
| stok = listgetdata(snode); |
| } |
| |
| return NULL; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static void del_arglist(struct list *list) |
| { |
| |
| struct listnode *tail = listtail(list); |
| tail->data = NULL; |
| list_delete_node(list, tail); |
| |
| |
| list_delete(&list); |
| } |
| |
| |
| |
| static enum match_type match_token(struct cmd_token *token, char *input_token) |
| { |
| |
| if (!input_token) |
| return trivial_match; |
| |
| switch (token->type) { |
| case WORD_TKN: |
| return match_word(token, input_token); |
| case IPV4_TKN: |
| return match_ipv4(input_token); |
| case IPV4_PREFIX_TKN: |
| return match_ipv4_prefix(input_token); |
| case IPV6_TKN: |
| return match_ipv6_prefix(input_token, false); |
| case IPV6_PREFIX_TKN: |
| return match_ipv6_prefix(input_token, true); |
| case RANGE_TKN: |
| return match_range(token, input_token); |
| case VARIABLE_TKN: |
| return match_variable(token, input_token); |
| case MAC_TKN: |
| return match_mac(input_token, false); |
| case MAC_PREFIX_TKN: |
| return match_mac(input_token, true); |
| case END_TKN: |
| default: |
| return no_match; |
| } |
| } |
| |
| #define IPV4_ADDR_STR "0123456789." |
| #define IPV4_PREFIX_STR "0123456789./" |
| |
| static enum match_type match_ipv4(const char *str) |
| { |
| const char *sp; |
| int dots = 0, nums = 0; |
| char buf[4]; |
| |
| for (;;) { |
| memset(buf, 0, sizeof(buf)); |
| sp = str; |
| while (*str != '\0') { |
| if (*str == '.') { |
| if (dots >= 3) |
| return no_match; |
| |
| if (*(str + 1) == '.') |
| return no_match; |
| |
| if (*(str + 1) == '\0') |
| return partly_match; |
| |
| dots++; |
| break; |
| } |
| if (!isdigit((unsigned char)*str)) |
| return no_match; |
| |
| str++; |
| } |
| |
| if (str - sp > 3) |
| return no_match; |
| |
| memcpy(buf, sp, str - sp); |
| |
| int v = atoi(buf); |
| |
| if (v > 255) |
| return no_match; |
| if (v > 0 && buf[0] == '0') |
| return no_match; |
| |
| nums++; |
| |
| if (*str == '\0') |
| break; |
| |
| str++; |
| } |
| |
| if (nums < 4) |
| return partly_match; |
| |
| return exact_match; |
| } |
| |
| static enum match_type match_ipv4_prefix(const char *str) |
| { |
| const char *sp; |
| int dots = 0; |
| char buf[4]; |
| |
| for (;;) { |
| memset(buf, 0, sizeof(buf)); |
| sp = str; |
| while (*str != '\0' && *str != '/') { |
| if (*str == '.') { |
| if (dots == 3) |
| return no_match; |
| |
| if (*(str + 1) == '.' || *(str + 1) == '/') |
| return no_match; |
| |
| if (*(str + 1) == '\0') |
| return partly_match; |
| |
| dots++; |
| break; |
| } |
| |
| if (!isdigit((unsigned char)*str)) |
| return no_match; |
| |
| str++; |
| } |
| |
| if (str - sp > 3) |
| return no_match; |
| |
| memcpy(buf, sp, str - sp); |
| |
| int v = atoi(buf); |
| |
| if (v > 255) |
| return no_match; |
| if (v > 0 && buf[0] == '0') |
| return no_match; |
| |
| if (dots == 3) { |
| if (*str == '/') { |
| if (*(str + 1) == '\0') |
| return partly_match; |
| |
| str++; |
| break; |
| } else if (*str == '\0') |
| return partly_match; |
| } |
| |
| if (*str == '\0') |
| return partly_match; |
| |
| str++; |
| } |
| |
| sp = str; |
| while (*str != '\0') { |
| if (!isdigit((unsigned char)*str)) |
| return no_match; |
| |
| str++; |
| } |
| |
| if (atoi(sp) > 32) |
| return no_match; |
| |
| return exact_match; |
| } |
| |
| |
| #define IPV6_ADDR_STR "0123456789abcdefABCDEF:." |
| #define IPV6_PREFIX_STR "0123456789abcdefABCDEF:./" |
| #define STATE_START 1 |
| #define STATE_COLON 2 |
| #define STATE_DOUBLE 3 |
| #define STATE_ADDR 4 |
| #define STATE_DOT 5 |
| #define STATE_SLASH 6 |
| #define STATE_MASK 7 |
| |
| static enum match_type match_ipv6_prefix(const char *str, bool prefix) |
| { |
| int state = STATE_START; |
| int colons = 0, nums = 0, double_colon = 0; |
| int mask; |
| const char *sp = NULL, *start = str; |
| char *endptr = NULL; |
| |
| if (str == NULL) |
| return partly_match; |
| |
| if (strspn(str, prefix ? IPV6_PREFIX_STR : IPV6_ADDR_STR) |
| != strlen(str)) |
| return no_match; |
| |
| while (*str != '\0' && state != STATE_MASK) { |
| switch (state) { |
| case STATE_START: |
| if (*str == ':') { |
| if (*(str + 1) != ':' && *(str + 1) != '\0') |
| return no_match; |
| colons--; |
| state = STATE_COLON; |
| } else { |
| sp = str; |
| state = STATE_ADDR; |
| } |
| |
| continue; |
| case STATE_COLON: |
| colons++; |
| if (*(str + 1) == '/') |
| return no_match; |
| else if (*(str + 1) == ':') |
| state = STATE_DOUBLE; |
| else { |
| sp = str + 1; |
| state = STATE_ADDR; |
| } |
| break; |
| case STATE_DOUBLE: |
| if (double_colon) |
| return no_match; |
| |
| if (*(str + 1) == ':') |
| return no_match; |
| else { |
| if (*(str + 1) != '\0' && *(str + 1) != '/') |
| colons++; |
| sp = str + 1; |
| |
| if (*(str + 1) == '/') |
| state = STATE_SLASH; |
| else |
| state = STATE_ADDR; |
| } |
| |
| double_colon++; |
| nums += 1; |
| break; |
| case STATE_ADDR: |
| if (*(str + 1) == ':' || *(str + 1) == '.' |
| || *(str + 1) == '\0' || *(str + 1) == '/') { |
| if (str - sp > 3) |
| return no_match; |
| |
| for (; sp <= str; sp++) |
| if (*sp == '/') |
| return no_match; |
| |
| nums++; |
| |
| if (*(str + 1) == ':') |
| state = STATE_COLON; |
| else if (*(str + 1) == '.') { |
| if (colons || double_colon) |
| state = STATE_DOT; |
| else |
| return no_match; |
| } else if (*(str + 1) == '/') |
| state = STATE_SLASH; |
| } |
| break; |
| case STATE_DOT: |
| state = STATE_ADDR; |
| break; |
| case STATE_SLASH: |
| if (*(str + 1) == '\0') |
| return partly_match; |
| |
| state = STATE_MASK; |
| break; |
| default: |
| break; |
| } |
| |
| if (nums > 11) |
| return no_match; |
| |
| if (colons > 7) |
| return no_match; |
| |
| str++; |
| } |
| |
| if (!prefix) { |
| struct sockaddr_in6 sin6_dummy; |
| int ret = inet_pton(AF_INET6, start, &sin6_dummy.sin6_addr); |
| return ret == 1 ? exact_match : partly_match; |
| } |
| |
| if (state < STATE_MASK) |
| return partly_match; |
| |
| mask = strtol(str, &endptr, 10); |
| if (*endptr != '\0') |
| return no_match; |
| |
| if (mask < 0 || mask > 128) |
| return no_match; |
| |
| return exact_match; |
| } |
| |
| static enum match_type match_range(struct cmd_token *token, const char *str) |
| { |
| assert(token->type == RANGE_TKN); |
| |
| char *endptr = NULL; |
| long long val; |
| |
| val = strtoll(str, &endptr, 10); |
| if (*endptr != '\0') |
| return no_match; |
| |
| if (val < token->min || val > token->max) |
| return no_match; |
| else |
| return exact_match; |
| } |
| |
| static enum match_type match_word(struct cmd_token *token, const char *word) |
| { |
| assert(token->type == WORD_TKN); |
| |
| |
| if (!strlen(word)) |
| return partly_match; |
| |
| |
| |
| if (strlen(word) < strlen(token->text)) |
| return !strncmp(token->text, word, strlen(word)) ? partly_match |
| : no_match; |
| |
| |
| else if (strlen(word) == strlen(token->text)) |
| return !strncmp(token->text, word, strlen(word)) ? exact_match |
| : no_match; |
| |
| return no_match; |
| } |
| |
| static enum match_type match_variable(struct cmd_token *token, const char *word) |
| { |
| assert(token->type == VARIABLE_TKN); |
| return exact_match; |
| } |
| |
| #define MAC_CHARS "ABCDEFabcdef0123456789:" |
| |
| static enum match_type match_mac(const char *word, bool prefix) |
| { |
| |
| size_t mac_explen = 6 * 2 + 5; |
| |
| size_t mask_len = 1 + 2; |
| unsigned int i; |
| char *eptr; |
| unsigned int maskval; |
| |
| |
| if (strlen(word) > mac_explen + (prefix ? mask_len : 0)) |
| return no_match; |
| |
| |
| for (i = 0; i < mac_explen; i++) { |
| if (word[i] == '\0' || !strchr(MAC_CHARS, word[i])) |
| break; |
| if (((i + 1) % 3 == 0) != (word[i] == ':')) |
| return no_match; |
| } |
| |
| |
| if (i < mac_explen && word[i] == '\0') |
| return partly_match; |
| else if (i < mac_explen) |
| return no_match; |
| |
| |
| if (prefix && word[i] == '/') { |
| if (word[++i] == '\0') |
| return partly_match; |
| |
| maskval = strtoul(&word[i], &eptr, 10); |
| if (*eptr != '\0' || maskval > 48) |
| return no_match; |
| } else if (prefix && word[i] == '\0') { |
| return partly_match; |
| } else if (prefix) { |
| return no_match; |
| } |
| |
| return exact_match; |
| } |