/*
 * %A%
 *
 * ipfw: ip firewalling using the same command language that gatekeeper
 *       uses.
 *
 * usage:  ipfw [options] list		; list rules
 *	   ipfw [options] clear		; clear rules
 *         ipfw [options] insert	; insert rules (from stdin)
 *         ipfw [options] append	; append rules
 *         ipfw [options] delete	; delete rules
 *
 * options:
 *	-i	;input rules
 *	-o	;output rules
 *	-n	;don't resolve ip addresses
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <malloc.h>
#include <netdb.h>
#include <netinet/in.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <linux/icmp.h>
#include <linux/if.h>
#include <linux/ip_fw.h>
#include "parser.h"
#include "policies.h"

struct chainv4 *hard_fw[2], *soft_fw[2];

int filter = 0;	/* for list, delete, clear */
int rflag = 1;	/* attempt to resolve IP addresses */
int vflag = 0;	/* verbosity */

int append(int);
int clear(int);
int list(int);
int delete(int);
int insert(int);
int debug(int);

typedef int (*fn)(int);

struct cmdcmd {
    char *cmd;
    fn ftn;
    char *comment;
} cmds[] = {
    { "list",	list,   "list current filter" },
    { "clear",  clear,  "clear a filter"  },
    { "flush",  clear,  0/*alias for clear*/  },
    { "insert", insert, "insert rules into filters"  },
    { "append", append, "append rules to filters"  },
    { "add",    append, 0/*alias for append*/ },
    { "delete", delete, "delete rules from a filter" },
    { "debug",  debug,  0/* debugging */ }
};
#define NR_CMDS	(sizeof cmds / sizeof cmds[0])


void
usage()
{
    int i;

    fprintf(stderr, "usage: ipfw [-ion] [-f<file>] command\n"
		    "       command is:\n");
    for (i = 0; i < NR_CMDS; i++)
	if (cmds[i].comment)
	    fprintf(stderr, "%16s%-8s: %s\n", "", cmds[i].cmd, cmds[i].comment);
    exit(1);
}


int yyerror() { }

main(int argc, char **argv)
{
    int i;
    int opt;
    char *cmd;
    char *rule;
    int len;

    opterr = 1;
    while ( (opt=getopt(argc,argv,"iof:nv")) != EOF) {
	switch (opt) {
	case 'i':	filter = IN;	break;
	case 'o':	filter = OUT;	break;
	case 'f':	if (freopen(optarg, "r", stdin) == 0) {
			    perror(optarg);
			    exit(1);
			}
			break;
	case 'n':	rflag = 0;
			break;
	case 'v':	vflag++;
			break;
	default:	usage();
	}
    }
    argc -= optind;
    argv += optind;

    if (argc < 1)
	usage();

    cmd = argv[0];
    if (strcasecmp(cmd, "deny") == 0 || strcasecmp(cmd, "allow") == 0) {
	/* convert firewall rule prefix to ``add hard <pfx>'' */
	argv -= 2;
	argc += 2;
	argv[1] = "hard";
	cmd = "add";
    }
    else if (argc > 1) {
	if (strcasecmp(argv[1], "input") == 0) {
	    filter = IN;
	    ++argv, --argc;
	}
	else if (strcasecmp(argv[1], "output") == 0) {
	    filter = OUT;
	    ++argv, --argc;
	}
    }

    for (i=1,len=0; i < argc; ++i)
	len += 1+strlen(argv[i]);
    
    if (len > 0) {
	++len;
	if ( (rule = calloc(len, 1)) == 0) {
	    perror("calloc");
	    exit(1);
	}
	for (i=1; i < argc; ++i) {
	    if (i > 1) strcat(rule, " ");
	    strcat(rule, argv[i]);
	}
	strcat(rule, "\n");	/* need to terminate command with \n */
	yy_scan_string(rule);
    }

    for (i=0; i < NR_CMDS; i++)
	if (strcasecmp(cmd, cmds[i].cmd) == 0) {
	    (cmds[i].ftn)(filter);
	    exit(0);
	}
    usage();
}


int
getrule(FILE *f, int dir, struct chainv4 **rules)
{
    long srcip, srcmask, destip, destmask;
    struct chainv4 *rule = calloc(1, sizeof (struct chainv4));
    int flag;
    short nrsrc, nrdst;
    short destport;
    int c, rc;

#define CONSUME	while ((c = fgetc(f)) != EOF && c != '\n')

    if (rule == NULL) {
	fprintf(stderr, "ipfw: out of memory in getrule()\n");
	abort();
    }
    if ((rc=fscanf(f, "%lX / %lX -> %lX / %lX %*16s %*lX %hX %hu %hu %*hu %*hu",
		&srcip, &srcmask, &destip, &destmask,
		&flag, &nrsrc, &nrdst)) != 7) {
	if (!feof(f))
	    fprintf(stderr, "garbled status file: wanted 7, got %d\n", rc);
	return 0;
    }
    /* need to read the actual ports;  args 12 -21 are the port numbers */
    if (nrsrc != 0) {
	CONSUME;
	fprintf(stderr, "skipping rule with nrsrc=%d, nrdst=%d\n",
			nrsrc, nrdst);
	return 1;
    }
    destport = 0;
    while (nrdst-- > 0) {
	if (fscanf(f, " %hu", &destport) != 1) {
	    CONSUME;
	    fprintf(stderr, "garbled status file: couldn't read port\n");
	    return 0;
	}
	rule->port[rule->nrports++] = htons(destport);
    }

    CONSUME;

    rule->flag = flag & ~(IP_FW_F_KIND|IP_FW_F_ACCEPT);

    rule->srcaddr = htonl(srcip);
    rule->srcmask = htonl(srcmask);
    rule->dstaddr = htonl(destip);
    rule->dstmask = htonl(destmask);

    rule->action = (flag & IP_FW_F_ACCEPT) ? ALLOW : DENY;

    switch (flag & IP_FW_F_KIND) {
    case IP_FW_F_TCP:	rule->proto = TCP;	break;
    case IP_FW_F_UDP:	rule->proto = UDP;	break;
    case IP_FW_F_ICMP:	rule->proto = ICMP;	break;
    default:
    case IP_FW_F_ALL:	rule->proto = 0;	break;
    }

    rule->direction = dir;

    if (*rules == NULL) {
	rule->next = rule->prev = rule;
	*rules = rule;
    }
    else {
	rule->prev = (*rules)->prev;
	rule->next = (*rules);
	(*rules)->prev->next = rule;
	(*rules)->prev = rule;
    }
    return 1;
}

char *
fwinfo(int dir)
{
    switch (dir) {
    case OUT: return "/proc/net/ip_output";
    case IN:  return "/proc/net/ip_input";
    default:  return "/dev/null";
    }
}

void
fw_list(int dir)
{
    FILE *fwf;
    struct chainv4 *rules = 0, *p;

    if ( (fwf = fopen(fwinfo(dir), "r")) != NULL) {
	int c;
	while ( (c = fgetc(fwf)) != '\n' && c != EOF)
	    ;
	while (getrule(fwf, dir, &rules))
	    ;
	fclose(fwf);

	display(stdout, "hard", rules);
	if ( (p = rules) != 0) {
	    do {
		p = p->next;
		free(p->prev);
	    } while (p != rules);
	    rules = 0;
	}
    }
}

int
list(int dir)
{
    if (dir != IN)
	fw_list(OUT);
    if (dir != OUT)
	fw_list(IN);
}


clear(int dir)
{
    int f = fwopen(0);
    struct ip_fw fw;

    if (f != EOF) {
	memset(&fw, 0, sizeof fw);
	if (dir!=OUT)
	    fwcmd(f, IP_FW_FLUSH_IN, &fw, sizeof fw);
	if (dir!=IN)
	    fwcmd(f, IP_FW_FLUSH_OUT, &fw, sizeof fw);
	fwclose(f);
    }
}

insert(int dir)
{
    yyparse();
    fwent(1,hard_fw[0]);
    fwent(1,hard_fw[1]);
}

append(int dir)
{
    yyparse();
    fwent(2,hard_fw[0]);
    fwent(2,hard_fw[1]);
}

delete(int dir)
{
    yyparse();
    fwent(0,hard_fw[0]);
    fwent(0,hard_fw[1]);
}

debug(int i)
{
    yyparse();

    display(stderr, 0, hard_fw[!!(IN==IN)]);
    display(stderr, 0, hard_fw[!!(IN==OUT)]);
}
