text: /*
   * m_ematch.c		Extended Matches
   *
   *		This program is free software; you can distribute it and/or
   *		modify it under the terms of the GNU General Public License
   *		as published by the Free Software Foundation; either version
   *		2 of the License, or (at your option) any later version.
   *
   * Authors:	Thomas Graf <tgraf@suug.ch>
   */
  
  #include <stdio.h>
  #include <stdlib.h>
  #include <unistd.h>
  #include <syslog.h>
  #include <fcntl.h>
  #include <sys/socket.h>
  #include <netinet/in.h>
  #include <arpa/inet.h>
  #include <string.h>
  #include <dlfcn.h>
  #include <stdarg.h>
  #include <errno.h>
  
  #include "utils.h"
  #include "tc_util.h"
  #include "m_ematch.h"
  
  #define EMATCH_MAP "/etc/iproute2/ematch_map"
  
  static struct ematch_util *ematch_list;
  
  /* export to bison parser */
  int ematch_argc;
  char **ematch_argv;
  char *ematch_err = NULL;
  struct ematch *ematch_root;
  
  static int begin_argc;
  static char **begin_argv;
  
  static inline void map_warning(int num, char *kind)
  {
  	fprintf(stderr,
  	    "Error: Unable to find ematch \"%s\" in %s\n" \
  	    "Please assign a unique ID to the ematch kind the suggested " \
  	    "entry is:\n" \
  	    "\t%d\t%s\n", 
  	    kind, EMATCH_MAP, num, kind);
  }
  
  static int lookup_map(__u16 num, char *dst, int len, const char *file)
  {
  	int err = -EINVAL;
  	char buf[512];
  	FILE *fd = fopen(file, "r");
  
  	if (fd == NULL)
  		return -errno;
  
  	while (fgets(buf, sizeof(buf), fd)) {
  		char namebuf[512], *p = buf;
  		int id;
  
  		while (*p == ' ' || *p == '\t')
  			p++;
  		if (*p == '#' || *p == '\n' || *p == 0)
  			continue;
  	
  		if (sscanf(p, "%d %s", &id, namebuf) != 2) {
  			fprintf(stderr, "ematch map %s corrupted at %s\n",
  			    file, p);
  			goto out;
  		}
  
  		if (id == num) {
  			if (dst)
  				strncpy(dst, namebuf, len - 1);
  			err = 0;
  			goto out;
  		}
  	}
  
  	err = -ENOENT;
  out:
  	fclose(fd);
  	return err;
  }
  
  static int lookup_map_id(char *kind, int *dst, const char *file)
  {
  	int err = -EINVAL;
  	char buf[512];
  	FILE *fd = fopen(file, "r");
  
  	if (fd == NULL)
  		return -errno;
  
  	while (fgets(buf, sizeof(buf), fd)) {
  		char namebuf[512], *p = buf;
  		int id;
  
  		while (*p == ' ' || *p == '\t')
  			p++;
  		if (*p == '#' || *p == '\n' || *p == 0)
  			continue;
  	
  		if (sscanf(p, "%d %s", &id, namebuf) != 2) {
  			fprintf(stderr, "ematch map %s corrupted at %s\n",
  			    file, p);
  			goto out;
  		}
  
  		if (!strcasecmp(namebuf, kind)) {
  			if (dst)
  				*dst = id;
  			err = 0;
  			goto out;
  		}
  	}
  
  	err = -ENOENT;
  	*dst = 0;
  out:
  	fclose(fd);
  	return err;
  }
  
  static struct ematch_util *get_ematch_kind(char *kind)
  {
  	static void *body;
  	void *dlh;
  	char buf[256];
  	struct ematch_util *e;
  
  	for (e = ematch_list; e; e = e->next) {
  		if (strcmp(e->kind, kind) == 0)
  			return e;
  	}
  
  	snprintf(buf, sizeof(buf), "em_%s.so", kind);
  	dlh = dlopen(buf, RTLD_LAZY);
  	if (dlh == NULL) {
  		dlh = body;
  		if (dlh == NULL) {
  			dlh = body = dlopen(NULL, RTLD_LAZY);
  			if (dlh == NULL)
  				return NULL;
  		}
  	}
  
  	snprintf(buf, sizeof(buf), "%s_ematch_util", kind);
  	e = dlsym(dlh, buf);
  	if (e == NULL)
  		return NULL;
  
  	e->next = ematch_list;
  	ematch_list = e;
  
  	return e;
  }
  
  static struct ematch_util *get_ematch_kind_num(__u16 kind)
  {
  	char name[32];
  
  	if (lookup_map(kind, name, sizeof(name), EMATCH_MAP) < 0)
  		return NULL;
  
  	return get_ematch_kind(name);
  
  	return NULL;
  }
  
  static int parse_tree(struct nlmsghdr *n, struct ematch *tree)
  {
  	int index = 1;
  	struct ematch *t;
  
  	for (t = tree; t; t = t->next) {
  		struct rtattr *tail = NLMSG_TAIL(n);
  		struct tcf_ematch_hdr hdr = {
  			.flags = t->relation
  		};
  
  		if (t->inverted)
  			hdr.flags |= TCF_EM_INVERT;
  		
  		addattr_l(n, MAX_MSG, index++, NULL, 0);
  
  		if (t->child) {
  			__u32 r = t->child_ref;
  			addraw_l(n, MAX_MSG, &hdr, sizeof(hdr));
  			addraw_l(n, MAX_MSG, &r, sizeof(r));
  		} else {
  			int num = 0, err;
  			char buf[64];
  			struct ematch_util *e;
  
  			if (t->args == NULL)
  				return -1;
  
  			strncpy(buf, (char*) t->args->data, sizeof(buf)-1);
  			e = get_ematch_kind(buf);
  			if (e == NULL) {
  				fprintf(stderr, "Unknown ematch \"%s\"\n",
  				    buf);
  				return -1;
  			}
  
  			err = lookup_map_id(buf, &num, EMATCH_MAP);
  			if (err < 0) {
  				if (err == -ENOENT)
  					map_warning(e->kind_num, buf);
  				return err;
  			}
  
  			hdr.kind = num;
  			if (e->parse_eopt(n, &hdr, t->args->next) < 0)
  				return -1;
  		}
  
  		tail->rta_len = (void*) NLMSG_TAIL(n) - (void*) tail;
  	}
  
  	return 0;
  }
  
  static int flatten_tree(struct ematch *head, struct ematch *tree)
  {
  	int i, count = 0;
  	struct ematch *t;
  
  	for (;;) {
  		count++;
  
  		if (tree->child) {
  			for (t = head; t->next; t = t->next);
  			t->next = tree->child;
  			count += flatten_tree(head, tree->child);
  		}
  
  		if (tree->relation == 0)
  			break;
  
  		tree = tree->next;
  	}
  
  	for (i = 0, t = head; t; t = t->next, i++)
  		t->index = i;
  
  	for (t = head; t; t = t->next)
  		if (t->child)
  			t->child_ref = t->child->index;
  
  	return count;
  }
  
  int em_parse_error(int err, struct bstr *args, struct bstr *carg,
  		   struct ematch_util *e, char *fmt, ...)
  {
  	va_list a;
  
  	va_start(a, fmt);
  	vfprintf(stderr, fmt, a);
  	va_end(a);
  
  	if (ematch_err)
  		fprintf(stderr, ": %s\n... ", ematch_err);
  	else
  		fprintf(stderr, "\n... ");
  
  	while (ematch_argc < begin_argc) {
  		if (ematch_argc == (begin_argc - 1))
  			fprintf(stderr, ">>%s<< ", *begin_argv);
  		else
  			fprintf(stderr, "%s ", *begin_argv);
  		begin_argv++;
  		begin_argc--;
  	}
  	
  	fprintf(stderr, "...\n");
  
  	if (args) {
  		fprintf(stderr, "... %s(", e->kind);
  		while (args) {
  			fprintf(stderr, "%s", args == carg ? ">>" : "");
  			bstr_print(stderr, args, 1);
  			fprintf(stderr, "%s%s", args == carg ? "<<" : "",
  			    args->next ? " " : "");
  			args = args->next;
  		}
  		fprintf(stderr, ")...\n");
  
  	}
  
  	if (e == NULL) {
  		fprintf(stderr,
  		    "Usage: EXPR\n" \
  		    "where: EXPR  := TERM [ { and | or } EXPR ]\n" \
  		    "       TERM  := [ not ] { MATCH | '(' EXPR ')' }\n" \
  		    "       MATCH := module '(' ARGS ')'\n" \
  		    "       ARGS := ARG1 ARG2 ...\n" \
  		    "\n" \
  		    "Example: a(x y) and not (b(x) or c(x y z))\n");
  	} else
  		e->print_usage(stderr);
  
  	return -err;
  }
  
  static inline void free_ematch_err(void)
  {
  	if (ematch_err) {
  		free(ematch_err);
  		ematch_err = NULL;
  	}
  }
  
  extern int ematch_parse(void);
  
  int parse_ematch(int *argc_p, char ***argv_p, int tca_id, struct nlmsghdr *n)
  {
  	begin_argc = ematch_argc = *argc_p;
  	begin_argv = ematch_argv = *argv_p;
  
  	if (ematch_parse()) {
  		int err = em_parse_error(EINVAL, NULL, NULL, NULL,
  		    "Parse error");
  		free_ematch_err();
  		return err;
  	}
  
  	free_ematch_err();
  
  	/* undo look ahead by parser */
  	ematch_argc++;
  	ematch_argv--;
  
  	if (ematch_root) {
  		struct rtattr *tail, *tail_list;
  
  		struct tcf_ematch_tree_hdr hdr = {
  			.nmatches = flatten_tree(ematch_root, ematch_root),
  			.progid = TCF_EM_PROG_TC
  		};
  
  		tail = NLMSG_TAIL(n);
  		addattr_l(n, MAX_MSG, tca_id, NULL, 0);
  		addattr_l(n, MAX_MSG, TCA_EMATCH_TREE_HDR, &hdr, sizeof(hdr));
  
  		tail_list = NLMSG_TAIL(n);
  		addattr_l(n, MAX_MSG, TCA_EMATCH_TREE_LIST, NULL, 0);
  
  		if (parse_tree(n, ematch_root) < 0)
  			return -1;
  
  		tail_list->rta_len = (void*) NLMSG_TAIL(n) - (void*) tail_list;
  		tail->rta_len = (void*) NLMSG_TAIL(n) - (void*) tail;
  	}
  
  	*argc_p = ematch_argc;
  	*argv_p = ematch_argv;
  
  	return 0;
  }
  
  static int print_ematch_seq(FILE *fd, struct rtattr **tb, int start,
  			    int prefix)
  {
  	int n, i = start;
  	struct tcf_ematch_hdr *hdr;
  	int dlen;
  	void *data;
  
  	for (;;) {
  		if (tb[i] == NULL)
  			return -1;
  
  		dlen = RTA_PAYLOAD(tb[i]) - sizeof(*hdr);
  		data = (void *) RTA_DATA(tb[i]) + sizeof(*hdr);
  
  		if (dlen < 0)
  			return -1;
  
  		hdr = RTA_DATA(tb[i]);
  
  		if (hdr->flags & TCF_EM_INVERT)
  			fprintf(fd, "NOT ");
  
  		if (hdr->kind == 0) {
  			__u32 ref;
  
  			if (dlen < sizeof(__u32))
  				return -1;
  
  			ref = *(__u32 *) data;
  			fprintf(fd, "(\n");
  			for (n = 0; n <= prefix; n++)
  				fprintf(fd, "  ");
  			if (print_ematch_seq(fd, tb, ref + 1, prefix + 1) < 0)
  				return -1;
  			for (n = 0; n < prefix; n++)
  				fprintf(fd, "  ");
  			fprintf(fd, ") ");
  
  		} else {
  			struct ematch_util *e;
  
  			e = get_ematch_kind_num(hdr->kind);
  			if (e == NULL)
  				fprintf(fd, "[unknown ematch %d]\n",
  				    hdr->kind);
  			else {
  				fprintf(fd, "%s(", e->kind);
  				if (e->print_eopt(fd, hdr, data, dlen) < 0)
  					return -1;
  				fprintf(fd, ")\n");
  			}
  			if (hdr->flags & TCF_EM_REL_MASK)
  				for (n = 0; n < prefix; n++)
  					fprintf(fd, "  ");
  		}
  
  		switch (hdr->flags & TCF_EM_REL_MASK) {
  			case TCF_EM_REL_AND:
  				fprintf(fd, "AND ");
  				break;
  
  			case TCF_EM_REL_OR:
  				fprintf(fd, "OR ");
  				break;
  
  			default:
  				return 0;
  		}
  
  		i++;
  	}
  	
  	return 0;
  }
  
  static int print_ematch_list(FILE *fd, struct tcf_ematch_tree_hdr *hdr,
  			     struct rtattr *rta)
  {
  	int err = -1;
  	struct rtattr **tb;
  
  	tb = malloc((hdr->nmatches + 1) * sizeof(struct rtattr *));
  	if (tb == NULL)
  		return -1;
  
  	if (parse_rtattr_nested(tb, hdr->nmatches, rta) < 0)
  		goto errout;
  
  	fprintf(fd, "\n  ");
  	if (print_ematch_seq(fd, tb, 1, 1) < 0)
  		goto errout;
  
  	err = 0;
  errout:
  	free(tb);
  	return err;
  }
  
  int print_ematch(FILE *fd, const struct rtattr *rta)
  {
  	struct rtattr *tb[TCA_EMATCH_TREE_MAX+1];
  	struct tcf_ematch_tree_hdr *hdr;
  
  	if (parse_rtattr_nested(tb, TCA_EMATCH_TREE_MAX, rta) < 0)
  		return -1;
  
  	if (tb[TCA_EMATCH_TREE_HDR] == NULL) {
  		fprintf(stderr, "Missing ematch tree header\n");
  		return -1;
  	}
  
  	if (tb[TCA_EMATCH_TREE_LIST] == NULL) {
  		fprintf(stderr, "Missing ematch tree list\n");
  		return -1;
  	}
  
  	if (RTA_PAYLOAD(tb[TCA_EMATCH_TREE_HDR]) < sizeof(*hdr)) {
  		fprintf(stderr, "Ematch tree header size mismatch\n");
  		return -1;
  	}
  
  	hdr = RTA_DATA(tb[TCA_EMATCH_TREE_HDR]);
  
  	return print_ematch_list(fd, hdr, tb[TCA_EMATCH_TREE_LIST]);
  }
  