/* $OpenBSD: parser.c,v 1.42 2020/01/06 11:02:38 gilles Exp $ */ /* * Copyright (c) 2013 Eric Faurot * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "parser.h" uint64_t text_to_evpid(const char *); uint32_t text_to_msgid(const char *); struct node { int type; const char *token; struct node *parent; TAILQ_ENTRY(node) entry; TAILQ_HEAD(, node) children; int (*cmd)(int, struct parameter*); }; static struct node *root; static int text_to_sockaddr(struct sockaddr *, int, const char *); #define ARGVMAX 64 int cmd_install(const char *pattern, int (*cmd)(int, struct parameter*)) { struct node *node, *tmp; char *s, *str, *argv[ARGVMAX], **ap; int i, n; /* Tokenize */ str = s = strdup(pattern); if (str == NULL) err(1, "strdup"); n = 0; for (ap = argv; n < ARGVMAX && (*ap = strsep(&str, " \t")) != NULL;) { if (**ap != '\0') { ap++; n++; } } *ap = NULL; if (root == NULL) { root = calloc(1, sizeof (*root)); TAILQ_INIT(&root->children); } node = root; for (i = 0; i < n; i++) { TAILQ_FOREACH(tmp, &node->children, entry) { if (!strcmp(tmp->token, argv[i])) { node = tmp; break; } } if (tmp == NULL) { tmp = calloc(1, sizeof (*tmp)); TAILQ_INIT(&tmp->children); if (!strcmp(argv[i], "")) tmp->type = P_STR; else if (!strcmp(argv[i], "")) tmp->type = P_INT; else if (!strcmp(argv[i], "")) tmp->type = P_MSGID; else if (!strcmp(argv[i], "")) tmp->type = P_EVPID; else if (!strcmp(argv[i], "")) tmp->type = P_ROUTEID; else if (!strcmp(argv[i], "")) tmp->type = P_ADDR; else tmp->type = P_TOKEN; tmp->token = strdup(argv[i]); tmp->parent = node; TAILQ_INSERT_TAIL(&node->children, tmp, entry); node = tmp; } } if (node->cmd) errx(1, "duplicate pattern: %s", pattern); node->cmd = cmd; free(s); return (n); } static int cmd_check(const char *str, struct node *node, struct parameter *res) { const char *e; switch (node->type) { case P_TOKEN: if (!strcmp(str, node->token)) return (1); return (0); case P_STR: res->u.u_str = str; return (1); case P_INT: res->u.u_int = strtonum(str, INT_MIN, INT_MAX, &e); if (e) return (0); return (1); case P_MSGID: if (strlen(str) != 8) return (0); res->u.u_msgid = text_to_msgid(str); if (res->u.u_msgid == 0) return (0); return (1); case P_EVPID: if (strlen(str) != 16) return (0); res->u.u_evpid = text_to_evpid(str); if (res->u.u_evpid == 0) return (0); return (1); case P_ROUTEID: res->u.u_routeid = strtonum(str, 1, LLONG_MAX, &e); if (e) return (0); return (1); case P_ADDR: if (text_to_sockaddr((struct sockaddr *)&res->u.u_ss, PF_UNSPEC, str) == 0) return (1); return (0); default: errx(1, "bad token type: %d", node->type); return (0); } } int cmd_run(int argc, char **argv) { struct parameter param[ARGVMAX]; struct node *node, *tmp, *stack[ARGVMAX], *best; int i, j, np; node = root; np = 0; for (i = 0; i < argc; i++) { TAILQ_FOREACH(tmp, &node->children, entry) { if (cmd_check(argv[i], tmp, ¶m[np])) { stack[i] = tmp; node = tmp; param[np].type = node->type; if (node->type != P_TOKEN) np++; break; } } if (tmp == NULL) { best = NULL; TAILQ_FOREACH(tmp, &node->children, entry) { if (tmp->type != P_TOKEN) continue; if (strstr(tmp->token, argv[i]) != tmp->token) continue; if (best) goto fail; best = tmp; } if (best == NULL) goto fail; stack[i] = best; node = best; param[np].type = node->type; if (node->type != P_TOKEN) np++; } } if (node->cmd == NULL) goto fail; return (node->cmd(np, np ? param : NULL)); fail: if (TAILQ_FIRST(&node->children) == NULL) { fprintf(stderr, "invalid command\n"); return (-1); } fprintf(stderr, "possibilities are:\n"); TAILQ_FOREACH(tmp, &node->children, entry) { for (j = 0; j < i; j++) fprintf(stderr, "%s%s", j?" ":"", stack[j]->token); fprintf(stderr, "%s%s\n", i?" ":"", tmp->token); } return (-1); } int cmd_show_params(int argc, struct parameter *argv) { int i; for (i = 0; i < argc; i++) { switch(argv[i].type) { case P_STR: printf(" str:\"%s\"", argv[i].u.u_str); break; case P_INT: printf(" int:%d", argv[i].u.u_int); break; case P_MSGID: printf(" msgid:%08"PRIx32, argv[i].u.u_msgid); break; case P_EVPID: printf(" evpid:%016"PRIx64, argv[i].u.u_evpid); break; case P_ROUTEID: printf(" routeid:%016"PRIx64, argv[i].u.u_routeid); break; default: printf(" ???:%d", argv[i].type); } } printf ("\n"); return (1); } static int text_to_sockaddr(struct sockaddr *sa, int family, const char *str) { struct in_addr ina; struct in6_addr in6a; struct sockaddr_in *in; struct sockaddr_in6 *in6; char *cp, *str2; const char *errstr; switch (family) { case PF_UNSPEC: if (text_to_sockaddr(sa, PF_INET, str) == 0) return (0); return text_to_sockaddr(sa, PF_INET6, str); case PF_INET: if (inet_pton(PF_INET, str, &ina) != 1) return (-1); in = (struct sockaddr_in *)sa; memset(in, 0, sizeof *in); in->sin_len = sizeof(struct sockaddr_in); in->sin_family = PF_INET; in->sin_addr.s_addr = ina.s_addr; return (0); case PF_INET6: cp = strchr(str, SCOPE_DELIMITER); if (cp) { str2 = strdup(str); if (str2 == NULL) return (-1); str2[cp - str] = '\0'; if (inet_pton(PF_INET6, str2, &in6a) != 1) { free(str2); return (-1); } cp++; free(str2); } else if (inet_pton(PF_INET6, str, &in6a) != 1) return (-1); in6 = (struct sockaddr_in6 *)sa; memset(in6, 0, sizeof *in6); in6->sin6_len = sizeof(struct sockaddr_in6); in6->sin6_family = PF_INET6; in6->sin6_addr = in6a; if (cp == NULL) return (0); if (IN6_IS_ADDR_LINKLOCAL(&in6a) || IN6_IS_ADDR_MC_LINKLOCAL(&in6a) || IN6_IS_ADDR_MC_INTFACELOCAL(&in6a)) if ((in6->sin6_scope_id = if_nametoindex(cp))) return (0); in6->sin6_scope_id = strtonum(cp, 0, UINT32_MAX, &errstr); if (errstr) return (-1); return (0); default: break; } return (-1); }