#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdarg.h>
#include <string.h>
#include <fcntl.h>
#include <getopt.h>
#include <iptables.h>
#include <xtables.h>

#include <netinet/ether.h>

#include <linux/netfilter_bridge.h>
#include <linux/netfilter/nf_tables.h>
#include <libiptc/libxtc.h>

#include "xshared.h"
#include "xtables-multi.h"
#include "nft-bridge.h"
#include "nft.h"
#include "nft-shared.h"
/*
 * From include/ebtables_u.h
 */
#define EXEC_STYLE_PRG    0
#define EXEC_STYLE_DAEMON 1

#define ebt_check_option2(flags, mask) EBT_CHECK_OPTION(flags, mask)

extern int ebt_invert;

static int ebt_check_inverse2(const char option[], int argc, char **argv)
{
	if (!option)
		return ebt_invert;
	if (strcmp(option, "!") == 0) {
		if (ebt_invert == 1)
			xtables_error(PARAMETER_PROBLEM,
				      "Double use of '!' not allowed");
		if (optind >= argc)
			optarg = NULL;
		else
			optarg = argv[optind];
		optind++;
		ebt_invert = 1;
		return 1;
	}
	return ebt_invert;
}

/*
 * Glue code to use libxtables
 */
static int parse_rule_number(const char *rule)
{
	unsigned int rule_nr;

	if (!xtables_strtoui(rule, NULL, &rule_nr, 1, INT_MAX))
		xtables_error(PARAMETER_PROBLEM,
			      "Invalid rule number `%s'", rule);

	return rule_nr;
}

static int get_current_chain(const char *chain)
{
	if (strcmp(chain, "PREROUTING") == 0)
		return NF_BR_PRE_ROUTING;
	else if (strcmp(chain, "INPUT") == 0)
		return NF_BR_LOCAL_IN;
	else if (strcmp(chain, "FORWARD") == 0)
		return NF_BR_FORWARD;
	else if (strcmp(chain, "OUTPUT") == 0)
		return NF_BR_LOCAL_OUT;
	else if (strcmp(chain, "POSTROUTING") == 0)
		return NF_BR_POST_ROUTING;

	return -1;
}

/*
 * The original ebtables parser
 */

/* Checks whether a command has already been specified */
#define OPT_COMMANDS (flags & OPT_COMMAND || flags & OPT_ZERO)

#define OPT_COMMAND	0x01
#define OPT_TABLE	0x02
#define OPT_IN		0x04
#define OPT_OUT		0x08
#define OPT_JUMP	0x10
#define OPT_PROTOCOL	0x20
#define OPT_SOURCE	0x40
#define OPT_DEST	0x80
#define OPT_ZERO	0x100
#define OPT_LOGICALIN	0x200
#define OPT_LOGICALOUT	0x400
#define OPT_COUNT	0x1000 /* This value is also defined in libebtc.c */

/* Default command line options. Do not mess around with the already
 * assigned numbers unless you know what you are doing */
extern struct option ebt_original_options[];
extern struct xtables_globals ebtables_globals;
#define opts ebtables_globals.opts
#define prog_name ebtables_globals.program_name
#define prog_vers ebtables_globals.program_version

static void print_help(void)
{
	fprintf(stderr, "%s: Translate ebtables command to nft syntax\n"
			"no side effects occur, the translated command is written "
			"to standard output.\n"
			"A '#' followed by input means no translation "
			"is available.\n", prog_name);
	exit(0);
}

static int parse_rule_range(const char *argv, int *rule_nr, int *rule_nr_end)
{
	char *colon = strchr(argv, ':'), *buffer;

	if (colon) {
		*colon = '\0';
		if (*(colon + 1) == '\0')
			*rule_nr_end = -1; /* Until the last rule */
		else {
			*rule_nr_end = strtol(colon + 1, &buffer, 10);
			if (*buffer != '\0' || *rule_nr_end == 0)
				return -1;
		}
	}
	if (colon == argv)
		*rule_nr = 1; /* Beginning with the first rule */
	else {
		*rule_nr = strtol(argv, &buffer, 10);
		if (*buffer != '\0' || *rule_nr == 0)
			return -1;
	}
	if (!colon)
		*rule_nr_end = *rule_nr;
	return 0;
}

static void ebtables_parse_interface(const char *arg, char *vianame)
{
	unsigned char mask[IFNAMSIZ];
	char *c;

	xtables_parse_interface(arg, vianame, mask);

	if ((c = strchr(vianame, '+'))) {
		if (*(c + 1) != '\0')
			xtables_error(PARAMETER_PROBLEM,
				      "Spurious characters after '+' wildcard");
	}
}

static void print_ebt_cmd(int argc, char *argv[])
{
	int i;

	printf("# ");
	for (i = 1; i < argc; i++)
		printf("%s ", argv[i]);

	printf("\n");
}

static int nft_rule_eb_xlate_add(struct nft_handle *h, const struct nft_xt_cmd_parse *p,
				 const struct iptables_command_state *cs, bool append)
{
	struct xt_xlate *xl = xt_xlate_alloc(10240);
	int ret;

	if (append) {
		xt_xlate_add(xl, "add rule bridge %s %s ", p->table, p->chain);
	} else {
		xt_xlate_add(xl, "insert rule bridge %s %s ", p->table, p->chain);
	}

	ret = h->ops->xlate(cs, xl);
	if (ret)
		printf("%s\n", xt_xlate_get(xl));

	xt_xlate_free(xl);
	return ret;
}

/* We use exec_style instead of #ifdef's because ebtables.so is a shared object. */
static int do_commandeb_xlate(struct nft_handle *h, int argc, char *argv[], char **table)
{
	char *buffer;
	int c, i;
	int rule_nr = 0;
	int rule_nr_end = 0;
	int ret = 0;
	unsigned int flags = 0;
	struct iptables_command_state cs = {
		.argv		= argv,
		.eb.bitmask	= EBT_NOPROTO,
	};
	char command = 'h';
	const char *chain = NULL;
	int exec_style = EXEC_STYLE_PRG;
	int selected_chain = -1;
	struct xtables_rule_match *xtrm_i;
	struct ebt_match *match;
	struct nft_xt_cmd_parse p = {
		.table          = *table,
        };

	/* prevent getopt to spoil our error reporting */
	opterr = false;

	printf("nft ");
	/* Getopt saves the day */
	while ((c = getopt_long(argc, argv,
	   "-A:D:I:N:E:X::L::Z::F::P:Vhi:o:j:c:p:s:d:t:M:", opts, NULL)) != -1) {
		cs.c = c;
		cs.invert = ebt_invert;
		switch (c) {
		case 'A': /* Add a rule */
		case 'D': /* Delete a rule */
		case 'P': /* Define policy */
		case 'I': /* Insert a rule */
		case 'N': /* Make a user defined chain */
		case 'E': /* Rename chain */
		case 'X': /* Delete chain */
			/* We allow -N chainname -P policy */
			/* XXX: Not in ebtables-compat */
			if (command == 'N' && c == 'P') {
				command = c;
				optind--; /* No table specified */
				break;
			}
			if (OPT_COMMANDS)
				xtables_error(PARAMETER_PROBLEM,
					      "Multiple commands are not allowed");
			command = c;
			chain = optarg;
			selected_chain = get_current_chain(chain);
			p.chain = chain;
			flags |= OPT_COMMAND;

			if (c == 'N') {
				printf("add chain bridge %s %s\n", p.table, p.chain);
				ret = 1;
				break;
			} else if (c == 'X') {
				printf("delete chain bridge %s %s\n", p.table, p.chain);
				ret = 1;
				break;
			}

			if (c == 'E') {
				break;
			} else if (c == 'D' && optind < argc && (argv[optind][0] != '-' || (argv[optind][1] >= '0' && argv[optind][1] <= '9'))) {
				if (optind != argc - 1)
					xtables_error(PARAMETER_PROBLEM,
							 "No extra options allowed with -D start_nr[:end_nr]");
				if (parse_rule_range(argv[optind], &rule_nr, &rule_nr_end))
					xtables_error(PARAMETER_PROBLEM,
							 "Problem with the specified rule number(s) '%s'", argv[optind]);
				optind++;
			} else if (c == 'I') {
				if (optind >= argc || (argv[optind][0] == '-' && (argv[optind][1] < '0' || argv[optind][1] > '9')))
					rule_nr = 1;
				else {
					rule_nr = parse_rule_number(argv[optind]);
					optind++;
				}
				p.rulenum = rule_nr;
			} else if (c == 'P') {
				break;
			}
			break;
		case 'L': /* List */
			printf("list table bridge %s\n", p.table);
			ret = 1;
			break;
		case 'F': /* Flush */
			if (p.chain) {
				printf("flush chain bridge %s %s\n", p.table, p.chain);
			} else {
				printf("flush table bridge %s\n", p.table);
			}
			ret = 1;
			break;
		case 'Z': /* Zero counters */
			if (c == 'Z') {
				if ((flags & OPT_ZERO) || (flags & OPT_COMMAND && command != 'L'))
print_zero:
					xtables_error(PARAMETER_PROBLEM,
						      "Command -Z only allowed together with command -L");
				flags |= OPT_ZERO;
			} else {
				if (flags & OPT_COMMAND)
					xtables_error(PARAMETER_PROBLEM,
						      "Multiple commands are not allowed");
				command = c;
				flags |= OPT_COMMAND;
				if (flags & OPT_ZERO && c != 'L')
					goto print_zero;
			}
			break;
		case 'V': /* Version */
			if (OPT_COMMANDS)
				xtables_error(PARAMETER_PROBLEM,
					      "Multiple commands are not allowed");
			if (exec_style == EXEC_STYLE_DAEMON)
				xtables_error(PARAMETER_PROBLEM,
					      "%s %s\n", prog_name, prog_vers);
			printf("%s %s\n", prog_name, prog_vers);
			exit(0);
		case 'h':
			if (OPT_COMMANDS)
				xtables_error(PARAMETER_PROBLEM,
					      "Multiple commands are not allowed");
			print_help();
			break;
		case 't': /* Table */
			if (OPT_COMMANDS)
				xtables_error(PARAMETER_PROBLEM,
					      "Please put the -t option first");
			ebt_check_option2(&flags, OPT_TABLE);
			if (strlen(optarg) > EBT_TABLE_MAXNAMELEN - 1)
				xtables_error(PARAMETER_PROBLEM,
					      "Table name length cannot exceed %d characters",
					      EBT_TABLE_MAXNAMELEN - 1);
			*table = optarg;
			p.table = optarg;
			break;
		case 'i': /* Input interface */
		case 2  : /* Logical input interface */
		case 'o': /* Output interface */
		case 3  : /* Logical output interface */
		case 'j': /* Target */
		case 'p': /* Net family protocol */
		case 's': /* Source mac */
		case 'd': /* Destination mac */
		case 'c': /* Set counters */
			if (!OPT_COMMANDS)
				xtables_error(PARAMETER_PROBLEM,
					      "No command specified");
			if (command != 'A' && command != 'D' && command != 'I')
				xtables_error(PARAMETER_PROBLEM,
					      "Command and option do not match");
			if (c == 'i') {
				ebt_check_option2(&flags, OPT_IN);
				if (selected_chain > 2 && selected_chain < NF_BR_BROUTING)
					xtables_error(PARAMETER_PROBLEM,
						      "Use -i only in INPUT, FORWARD, PREROUTING and BROUTING chains");
				if (ebt_check_inverse2(optarg, argc, argv))
					cs.eb.invflags |= EBT_IIN;

				ebtables_parse_interface(optarg, cs.eb.in);
				break;
			} else if (c == 2) {
				ebt_check_option2(&flags, OPT_LOGICALIN);
				if (selected_chain > 2 && selected_chain < NF_BR_BROUTING)
					xtables_error(PARAMETER_PROBLEM,
						      "Use --logical-in only in INPUT, FORWARD, PREROUTING and BROUTING chains");
				if (ebt_check_inverse2(optarg, argc, argv))
					cs.eb.invflags |= EBT_ILOGICALIN;

				ebtables_parse_interface(optarg, cs.eb.logical_in);
				break;
			} else if (c == 'o') {
				ebt_check_option2(&flags, OPT_OUT);
				if (selected_chain < 2 || selected_chain == NF_BR_BROUTING)
					xtables_error(PARAMETER_PROBLEM,
						      "Use -o only in OUTPUT, FORWARD and POSTROUTING chains");
				if (ebt_check_inverse2(optarg, argc, argv))
					cs.eb.invflags |= EBT_IOUT;

				ebtables_parse_interface(optarg, cs.eb.out);
				break;
			} else if (c == 3) {
				ebt_check_option2(&flags, OPT_LOGICALOUT);
				if (selected_chain < 2 || selected_chain == NF_BR_BROUTING)
					xtables_error(PARAMETER_PROBLEM,
						      "Use --logical-out only in OUTPUT, FORWARD and POSTROUTING chains");
				if (ebt_check_inverse2(optarg, argc, argv))
					cs.eb.invflags |= EBT_ILOGICALOUT;

				ebtables_parse_interface(optarg, cs.eb.logical_out);
				break;
			} else if (c == 'j') {
				ebt_check_option2(&flags, OPT_JUMP);
				command_jump(&cs, optarg);
				break;
			} else if (c == 's') {
				ebt_check_option2(&flags, OPT_SOURCE);
				if (ebt_check_inverse2(optarg, argc, argv))
					cs.eb.invflags |= EBT_ISOURCE;

				if (xtables_parse_mac_and_mask(optarg,
							       cs.eb.sourcemac,
							       cs.eb.sourcemsk))
					xtables_error(PARAMETER_PROBLEM, "Problem with specified source mac '%s'", optarg);
				cs.eb.bitmask |= EBT_SOURCEMAC;
				break;
			} else if (c == 'd') {
				ebt_check_option2(&flags, OPT_DEST);
				if (ebt_check_inverse2(optarg, argc, argv))
					cs.eb.invflags |= EBT_IDEST;

				if (xtables_parse_mac_and_mask(optarg,
							       cs.eb.destmac,
							       cs.eb.destmsk))
					xtables_error(PARAMETER_PROBLEM, "Problem with specified destination mac '%s'", optarg);
				cs.eb.bitmask |= EBT_DESTMAC;
				break;
			} else if (c == 'c') {
				ebt_check_option2(&flags, OPT_COUNT);
				if (ebt_check_inverse2(optarg, argc, argv))
					xtables_error(PARAMETER_PROBLEM,
						      "Unexpected '!' after -c");
				if (optind >= argc || optarg[0] == '-' || argv[optind][0] == '-')
					xtables_error(PARAMETER_PROBLEM,
						      "Option -c needs 2 arguments");

				cs.counters.pcnt = strtoull(optarg, &buffer, 10);
				if (*buffer != '\0')
					xtables_error(PARAMETER_PROBLEM,
						      "Packet counter '%s' invalid",
						      optarg);
				cs.counters.bcnt = strtoull(argv[optind], &buffer, 10);
				if (*buffer != '\0')
					xtables_error(PARAMETER_PROBLEM,
						      "Packet counter '%s' invalid",
						      argv[optind]);
				optind++;
				break;
			}
			ebt_check_option2(&flags, OPT_PROTOCOL);
			if (ebt_check_inverse2(optarg, argc, argv))
				cs.eb.invflags |= EBT_IPROTO;

			cs.eb.bitmask &= ~((unsigned int)EBT_NOPROTO);
			i = strtol(optarg, &buffer, 16);
			if (*buffer == '\0' && (i < 0 || i > 0xFFFF))
				xtables_error(PARAMETER_PROBLEM,
					      "Problem with the specified protocol");
			if (*buffer != '\0') {
				struct xt_ethertypeent *ent;

				if (!strcasecmp(optarg, "LENGTH")) {
					cs.eb.bitmask |= EBT_802_3;
					break;
				}
				ent = xtables_getethertypebyname(optarg);
				if (!ent)
					xtables_error(PARAMETER_PROBLEM,
						      "Problem with the specified Ethernet protocol '%s', perhaps "XT_PATH_ETHERTYPES " is missing", optarg);
				cs.eb.ethproto = ent->e_ethertype;
			} else
				cs.eb.ethproto = i;

			if (cs.eb.ethproto < 0x0600)
				xtables_error(PARAMETER_PROBLEM,
					      "Sorry, protocols have values above or equal to 0x0600");
			break;
		case 4  : /* Lc */
			ebt_check_option2(&flags, LIST_C);
			if (command != 'L')
				xtables_error(PARAMETER_PROBLEM,
					      "Use --Lc with -L");
			flags |= LIST_C;
			break;
		case 5  : /* Ln */
			ebt_check_option2(&flags, LIST_N);
			if (command != 'L')
				xtables_error(PARAMETER_PROBLEM,
					      "Use --Ln with -L");
			if (flags & LIST_X)
				xtables_error(PARAMETER_PROBLEM,
					      "--Lx is not compatible with --Ln");
			flags |= LIST_N;
			break;
		case 6  : /* Lx */
			ebt_check_option2(&flags, LIST_X);
			if (command != 'L')
				xtables_error(PARAMETER_PROBLEM,
					      "Use --Lx with -L");
			if (flags & LIST_N)
				xtables_error(PARAMETER_PROBLEM,
					      "--Lx is not compatible with --Ln");
			flags |= LIST_X;
			break;
		case 12 : /* Lmac2 */
			ebt_check_option2(&flags, LIST_MAC2);
			if (command != 'L')
				xtables_error(PARAMETER_PROBLEM,
					       "Use --Lmac2 with -L");
			flags |= LIST_MAC2;
			break;
		case 1 :
			if (!strcmp(optarg, "!"))
				ebt_check_inverse2(optarg, argc, argv);
			else
				xtables_error(PARAMETER_PROBLEM,
					      "Bad argument : '%s'", optarg);
			/* ebt_ebt_check_inverse2() did optind++ */
			optind--;
			continue;
		default:
			ebt_check_inverse2(optarg, argc, argv);

			if (ebt_command_default(&cs))
				xtables_error(PARAMETER_PROBLEM,
					      "Unknown argument: '%s'",
					      argv[optind - 1]);

			if (command != 'A' && command != 'I' &&
			    command != 'D')
				xtables_error(PARAMETER_PROBLEM,
					      "Extensions only for -A, -I, -D");
		}
		ebt_invert = 0;
	}

	/* Do the final checks */
	if (command == 'A' || command == 'I' || command == 'D') {
		for (xtrm_i = cs.matches; xtrm_i; xtrm_i = xtrm_i->next)
			xtables_option_mfcall(xtrm_i->match);

		for (match = cs.match_list; match; match = match->next) {
			if (match->ismatch)
				continue;

			xtables_option_tfcall(match->u.watcher);
		}

		if (cs.target != NULL)
			xtables_option_tfcall(cs.target);
	}

	cs.eb.ethproto = htons(cs.eb.ethproto);

	if (command == 'P') {
		return 0;
	} else if (command == 'A') {
		ret = nft_rule_eb_xlate_add(h, &p, &cs, true);
		if (!ret)
			print_ebt_cmd(argc, argv);
	} else if (command == 'I') {
		ret = nft_rule_eb_xlate_add(h, &p, &cs, false);
		if (!ret)
			print_ebt_cmd(argc, argv);
	}

	ebt_cs_clean(&cs);
	return ret;
}

static int dummy_compat_rev(const char *name, uint8_t rev, int opt)
{
	return 1;
}

int xtables_eb_xlate_main(int argc, char *argv[])
{
	int ret;
	char *table = "filter";
	struct nft_handle h;

	nft_init_eb(&h, argv[0]);
	ebtables_globals.compat_rev = dummy_compat_rev;

	ret = do_commandeb_xlate(&h, argc, argv, &table);
	if (!ret)
		fprintf(stderr, "Translation not implemented\n");

	exit(!ret);
}

