/*
 * Xtables BPF extension
 *
 * Written by Willem de Bruijn (willemb@google.com)
 * Copyright Google, Inc. 2013
 * Licensed under the GNU General Public License version 2 (GPLv2)
*/

#include <linux/netfilter/xt_bpf.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <xtables.h>

#define BCODE_FILE_MAX_LEN_B	1024

enum {
	O_BCODE_STDIN = 0,
};

static void bpf_help(void)
{
	printf(
"bpf match options:\n"
"--bytecode <program>	: a bpf program as generated by\n"
"  `nfbpf_compiler RAW <filter>`\n");
}

static const struct xt_option_entry bpf_opts[] = {
	{.name = "bytecode", .id = O_BCODE_STDIN, .type = XTTYPE_STRING},
	XTOPT_TABLEEND,
};

static void bpf_parse_string(struct xt_option_call *cb, const char *bpf_program,
			     const char separator)
{
	struct xt_bpf_info *bi = (void *) cb->data;
	const char *token;
	char sp;
	int i;

	/* parse head: length. */
	if (sscanf(bpf_program, "%hu%c", &bi->bpf_program_num_elem, &sp) != 2 ||
		   sp != separator)
		xtables_error(PARAMETER_PROBLEM,
			      "bpf: error parsing program length");
	if (!bi->bpf_program_num_elem)
		xtables_error(PARAMETER_PROBLEM,
			      "bpf: illegal zero length program");
	if (bi->bpf_program_num_elem > XT_BPF_MAX_NUM_INSTR)
		xtables_error(PARAMETER_PROBLEM,
			      "bpf: number of instructions exceeds maximum");

	/* parse instructions. */
	i = 0;
	token = bpf_program;
	while ((token = strchr(token, separator)) && (++token)[0]) {
		if (i >= bi->bpf_program_num_elem)
			xtables_error(PARAMETER_PROBLEM,
				      "bpf: real program length exceeds"
				      " the encoded length parameter");
		if (sscanf(token, "%hu %hhu %hhu %u,",
			   &bi->bpf_program[i].code,
			   &bi->bpf_program[i].jt,
			   &bi->bpf_program[i].jf,
			   &bi->bpf_program[i].k) != 4)
			xtables_error(PARAMETER_PROBLEM,
				      "bpf: error at instr %d", i);
		i++;
	}

	if (i != bi->bpf_program_num_elem)
		xtables_error(PARAMETER_PROBLEM,
			      "bpf: parsed program length is less than the"
			      " encoded length parameter");
}

static void bpf_parse(struct xt_option_call *cb)
{
	xtables_option_parse(cb);
	switch (cb->entry->id) {
	case O_BCODE_STDIN:
		bpf_parse_string(cb, cb->arg, ',');
		break;
	default:
		xtables_error(PARAMETER_PROBLEM, "bpf: unknown option");
	}
}

static void bpf_print_code(const void *ip, const struct xt_entry_match *match)
{
	const struct xt_bpf_info *info = (void *) match->data;
	int i;

	for (i = 0; i < info->bpf_program_num_elem-1; i++)
		printf("%hu %hhu %hhu %u,", info->bpf_program[i].code,
					    info->bpf_program[i].jt,
					    info->bpf_program[i].jf,
					    info->bpf_program[i].k);

	printf("%hu %hhu %hhu %u", info->bpf_program[i].code,
				    info->bpf_program[i].jt,
				    info->bpf_program[i].jf,
				    info->bpf_program[i].k);
}

static void bpf_save(const void *ip, const struct xt_entry_match *match)
{
	const struct xt_bpf_info *info = (void *) match->data;

	printf(" --bytecode \"%hu,", info->bpf_program_num_elem);
	bpf_print_code(ip, match);
	printf("\"");
}

static void bpf_fcheck(struct xt_fcheck_call *cb)
{
	if (!(cb->xflags & (1 << O_BCODE_STDIN)))
		xtables_error(PARAMETER_PROBLEM,
			      "bpf: missing --bytecode parameter");
}

static void bpf_print(const void *ip, const struct xt_entry_match *match,
		      int numeric)
{
	printf("match bpf ");
	return bpf_print_code(ip, match);
}

static struct xtables_match bpf_match = {
	.family		= NFPROTO_UNSPEC,
	.name		= "bpf",
	.version	= XTABLES_VERSION,
	.size		= XT_ALIGN(sizeof(struct xt_bpf_info)),
	.userspacesize	= XT_ALIGN(offsetof(struct xt_bpf_info, filter)),
	.help		= bpf_help,
	.print		= bpf_print,
	.save		= bpf_save,
	.x6_parse	= bpf_parse,
	.x6_fcheck	= bpf_fcheck,
	.x6_options	= bpf_opts,
};

void _init(void)
{
	xtables_register_match(&bpf_match);
}