text: /*	$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 "includes.h"
  
  #include <sys/types.h>
  #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);
  #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN
  		in->sin_len = sizeof(struct sockaddr_in);
  #endif
  		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);
  #ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN
  		in6->sin6_len = sizeof(struct sockaddr_in6);
  #endif
  		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_NODELOCAL(&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);
  }
  