/*
 * ebtablesd.c, January 2005
 *
 * Author: Bart De Schuymer
 *
 * This program is free software; you can redistribute 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.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>
#include "include/ebtables_u.h"

#define OPT_KERNELDATA	0x800 /* Also defined in ebtables.c */

static struct ebt_u_replace replace[3];
#define OPEN_METHOD_FILE 1
#define OPEN_METHOD_KERNEL 2
static int open_method[3];
void ebt_early_init_once();

static void sigpipe_handler(int sig)
{
}
static void copy_table_names()
{
	strcpy(replace[0].name, "filter");
	strcpy(replace[1].name, "nat");
	strcpy(replace[2].name, "broute");
}

int main(int argc_, char *argv_[])
{
	char *argv[EBTD_ARGC_MAX], *args[4], name[] = "mkdir",
	     mkdir_option[] = "-p", mkdir_dir[] = EBTD_PIPE_DIR,
	     cmdline[EBTD_CMDLINE_MAXLN];
	int readfd, base = 0, offset = 0, n = 0, quotemode = 0;

	/* Make sure the pipe directory exists */
	args[0] = name;
	args[1] = mkdir_option;
	args[2] = mkdir_dir;
	args[3] = NULL;
	switch (fork()) {
	case 0:
		execvp(args[0], args);

		/* Not usually reached */
		exit(0);
	case -1:
		return -1;

	default: /* Parent */
		wait(NULL);
	}

	if (mkfifo(EBTD_PIPE, 0600) < 0 && errno != EEXIST) {
		printf("Error creating FIFO " EBTD_PIPE "\n");
		goto do_exit;
	}

	if ((readfd = open(EBTD_PIPE, O_RDONLY | O_NONBLOCK, 0)) == -1) {
		perror("open");
		goto do_exit;
	}

	if (signal(SIGPIPE, sigpipe_handler) == SIG_ERR) {
		perror("signal");
		goto do_exit;
	}

	ebt_silent = 1;

	copy_table_names();
	ebt_early_init_once();

	while (1) {
		int n2, i, argc, table_nr, ntot;

		/* base == 0 */
		ntot = read(readfd, cmdline+offset, EBTD_CMDLINE_MAXLN-offset-1);
		if (ntot <= 0)
			continue;
		ntot += offset;
continue_read:
		/* Put '\0' between arguments. */
		for (; offset < ntot; n++, offset++) {
			if (cmdline[offset] == '\"') {
				quotemode ^= 1;
				cmdline[offset] = '\0';
			} else if (!quotemode && cmdline[offset] == ' ') {
				cmdline[offset] = '\0';
			} else if (cmdline[offset] == '\n') {
				if (quotemode)
					ebt_print_error("ebtablesd: wrong number of \" delimiters");
				cmdline[offset] = '\0';
				break;
			}
		}
		if (n == 0) {
			if (offset == ntot) {
				/* The ntot bytes were parsed and ended with '\n' */
				base = 0;
				offset = 0;
				continue;
			}
			offset++;
			base = offset;
			n = 0;
			goto continue_read;
		}
		if (offset == ntot) { /* The ntot bytes were parsed but no complete rule is yet specified */
			if (base == 0) {
				ebt_print_error("ebtablesd: the maximum command line length is %d", EBTD_CMDLINE_MAXLN-1);
				goto write_msg;
			}
			memmove(cmdline, cmdline+base+offset, ntot-offset);
			offset -= base;
			offset++;
			base = 0;
			continue;
		}

		table_nr = 0;
		n2 = 0;
		argc = 0;
		while (n2 < n && argc < EBTD_ARGC_MAX) {
			if (*(cmdline + base + n2) == '\0') {
				n2++;
				continue;
			}
			argv[argc++] = cmdline + base + n2;
			n2 += strlen(cmdline + base + n2) + 1;
		}
		offset++; /* Move past the '\n' */
		base = offset;

		if (argc > EBTD_ARGC_MAX) {
			ebt_print_error("ebtablesd: maximum %d arguments "
			                "allowed", EBTD_ARGC_MAX - 1);
			goto write_msg;
		}
		if (argc == 1) {
			ebt_print_error("ebtablesd: no arguments");
			goto write_msg;
		}

		/* Parse the options */
		if (!strcmp(argv[1], "-t")) {
			if (argc < 3) {
				ebt_print_error("ebtablesd: -t but no table");
				goto write_msg;
			}
			for (i = 0; i < 3; i++)
				if (!strcmp(replace[i].name, argv[2]))
					break;
			if (i == 3) {
				ebt_print_error("ebtablesd: table '%s' was "
				                "not recognized", argv[2]);
				goto write_msg;
			}
			table_nr = i;
		} else if (!strcmp(argv[1], "free")) {
			if (argc != 3) {
				ebt_print_error("ebtablesd: command free "
				                "needs exactly one argument");
				goto write_msg;
			}
			for (i = 0; i < 3; i++)
				if (!strcmp(replace[i].name, argv[2]))
					break;
			if (i == 3) {
				ebt_print_error("ebtablesd: table '%s' was "
				                "not recognized", argv[2]);
				goto write_msg;
			}
			if (!(replace[i].flags & OPT_KERNELDATA)) {
				ebt_print_error("ebtablesd: table %s has not "
				                "been opened");
				goto write_msg;
			}
			ebt_cleanup_replace(&replace[i]);
			copy_table_names();
			replace[i].flags &= ~OPT_KERNELDATA;
			goto write_msg;
		} else if (!strcmp(argv[1], "open")) {
			if (argc != 3) {
				ebt_print_error("ebtablesd: command open "
				                "needs exactly one argument");
				goto write_msg;
			}

			for (i = 0; i < 3; i++)
				if (!strcmp(replace[i].name, argv[2]))
					break;
			if (i == 3) {
				ebt_print_error("ebtablesd: table '%s' was "
				                "not recognized", argv[2]);
				goto write_msg;
			}
			if (replace[i].flags & OPT_KERNELDATA) {
				ebt_print_error("ebtablesd: table %s needs to "
				                "be freed before it can be "
				                "opened");
				goto write_msg;
			}
			if (!ebt_get_kernel_table(&replace[i], 0)) {
				replace[i].flags |= OPT_KERNELDATA;
				open_method[i] = OPEN_METHOD_KERNEL;
			}
			goto write_msg;
		} else if (!strcmp(argv[1], "fopen")) {
			struct ebt_u_replace tmp;

			memset(&tmp, 0, sizeof(tmp));
			if (argc != 4) {
				ebt_print_error("ebtablesd: command fopen "
				                "needs exactly two arguments");
				goto write_msg;
			}

			for (i = 0; i < 3; i++)
				if (!strcmp(replace[i].name, argv[2]))
					break;
			if (i == 3) {
				ebt_print_error("ebtablesd: table '%s' was "
				                "not recognized", argv[2]);
				goto write_msg;
			}
			if (replace[i].flags & OPT_KERNELDATA) {
				ebt_print_error("ebtablesd: table %s needs to "
				                "be freed before it can be "
				                "opened");
				goto write_msg;
			}
			tmp.filename = (char *)malloc(strlen(argv[3]) + 1);
			if (!tmp.filename) {
				ebt_print_error("Out of memory");
				goto write_msg;
			}
			strcpy(tmp.filename, argv[3]);
			strcpy(tmp.name, "filter");
			tmp.command = 'L'; /* Make sure retrieve_from_file()
			                    * doesn't complain about wrong
			                    * table name */

			ebt_get_kernel_table(&tmp, 0);
			free(tmp.filename);
			tmp.filename = NULL;
			if (ebt_errormsg[0] != '\0')
				goto write_msg;

			if (strcmp(tmp.name, argv[2])) {
				ebt_print_error("ebtablesd: opened file with "
				                "wrong table name '%s'", tmp.name);
				ebt_cleanup_replace(&tmp);
				goto write_msg;
			}
			replace[i] = tmp;
			replace[i].command = '\0';
			replace[i].flags |= OPT_KERNELDATA;
			open_method[i] = OPEN_METHOD_FILE;
			goto write_msg;
		} else if (!strcmp(argv[1], "commit")) {
			if (argc != 3) {
				ebt_print_error("ebtablesd: command commit "
				                "needs exactly one argument");
				goto write_msg;
			}

			for (i = 0; i < 3; i++)
				if (!strcmp(replace[i].name, argv[2]))
					break;
			if (i == 3) {
				ebt_print_error("ebtablesd: table '%s' was "
				                "not recognized", argv[2]);
				goto write_msg;
			}
			if (!(replace[i].flags & OPT_KERNELDATA)) {
				ebt_print_error("ebtablesd: table %s has not "
				                "been opened");
				goto write_msg;
			}
			/* The counters from the kernel are useless if we 
			 * didn't start from a kernel table */
			if (open_method[i] == OPEN_METHOD_FILE)
				replace[i].num_counters = 0;
			ebt_deliver_table(&replace[i]);
			if (ebt_errormsg[0] == '\0' && open_method[i] == OPEN_METHOD_KERNEL)
				ebt_deliver_counters(&replace[i]);
			goto write_msg;
		} else if (!strcmp(argv[1], "fcommit")) {
			if (argc != 4) {
				ebt_print_error("ebtablesd: command commit "
				                "needs exactly two argument");
				goto write_msg;
			}

			for (i = 0; i < 3; i++)
				if (!strcmp(replace[i].name, argv[2]))
					break;
			if (i == 3) {
				ebt_print_error("ebtablesd: table '%s' was "
				                "not recognized", argv[2]);
				goto write_msg;
			}
			if (!(replace[i].flags & OPT_KERNELDATA)) {
				ebt_print_error("ebtablesd: table %s has not "
				                "been opened");
				goto write_msg;
			}
			replace[i].filename = (char *)malloc(strlen(argv[3]) + 1);
			if (!replace[i].filename) {
				ebt_print_error("Out of memory");
				goto write_msg;
			}
			strcpy(replace[i].filename, argv[3]);
			ebt_deliver_table(&replace[i]);
			if (ebt_errormsg[0] == '\0' && open_method[i] == OPEN_METHOD_KERNEL)
				ebt_deliver_counters(&replace[i]);
			free(replace[i].filename);
			replace[i].filename = NULL;
			goto write_msg;
		}else if (!strcmp(argv[1], "quit")) {
			if (argc != 2) {
				ebt_print_error("ebtablesd: command quit does "
				                "not take any arguments");
				goto write_msg;
			}
			break;
		}
		if (!(replace[table_nr].flags & OPT_KERNELDATA)) {
			ebt_print_error("ebtablesd: table %s has not been "
			                "opened", replace[table_nr].name);
			goto write_msg;
		}
		optind = 0; /* Setting optind = 1 causes serious annoyances */
		do_command(argc, argv, EXEC_STYLE_DAEMON, &replace[table_nr]);
		ebt_reinit_extensions();
write_msg:
#ifndef SILENT_DAEMON
		if (ebt_errormsg[0] != '\0')
			printf("%s.\n", ebt_errormsg);
#endif
		ebt_errormsg[0]= '\0';
		n = 0;
		goto continue_read;
	}
do_exit:
	unlink(EBTD_PIPE);
	
	return 0;
}
