335 lines
7.0 KiB
C
335 lines
7.0 KiB
C
/* $OpenBSD: parser.c,v 1.43 2021/06/14 17:58:15 eric Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 2013 Eric Faurot <eric@openbsd.org>
|
|
*
|
|
* 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 <sys/queue.h>
|
|
#include <sys/socket.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <net/if.h>
|
|
|
|
#include <arpa/inet.h>
|
|
#include <err.h>
|
|
#include <inttypes.h>
|
|
#include <limits.h>
|
|
#include <netdb.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#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], "<str>"))
|
|
tmp->type = P_STR;
|
|
else if (!strcmp(argv[i], "<int>"))
|
|
tmp->type = P_INT;
|
|
else if (!strcmp(argv[i], "<msgid>"))
|
|
tmp->type = P_MSGID;
|
|
else if (!strcmp(argv[i], "<evpid>"))
|
|
tmp->type = P_EVPID;
|
|
else if (!strcmp(argv[i], "<routeid>"))
|
|
tmp->type = P_ROUTEID;
|
|
else if (!strcmp(argv[i], "<addr>"))
|
|
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);
|
|
}
|