src/usr.sbin/smtpd/parser.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, &param[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);
}