/*-
 * Copyright (c) 2001-2005 Christian S.J. Peron
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
#ifndef lint
static const char rcsid[] =
        "@(#) $Header: /usr/cvs/ipex/ipex.c,v 1.51 2005/05/18 17:32:38 modulus Exp $";
#endif
#include <ipex_includes.h>
#define READ_TIMEOUT 1000
#define NO_PRINT 0x7f

static char **protocols;
static char **tcp_ports;
static char **udp_ports;

/*
 * lookup table, the function pointer in delegate will initialize by
 * retrieving the * ip_p section of the IP header and using it to
 * cross reference it's arbitrary handler in this table.
 */
static struct proto_printers {
	u_char	pp_proto;	/* ip protocol */
	int	pp_hlen;	/* required header length */
	int (*pp_printer)(u_char *,  struct pcap_pkthdr *, u_char *);
} p_funcs[] = {
	{ IPPROTO_TCP,		24,	print_tcp },
	{ IPPROTO_ICMP,		4,	print_icmp },
	{ IPPROTO_UDP,		8,	print_udp },
	{ NO_PRINT,		0,	generic_print },
	{ 0,			0,	NULL }
};

pcap_t *pd = NULL;
pcap_dumper_t *pdmper;
Heap	*conqueue;

static int
offline(void)
{
	char ebuf[32];

	pd = pcap_open_offline(opts.rflag, ebuf);
	if (pd == NULL)
		errx(EX_DATAERR, "fatal: pcap_open_offline: failed to "
		    "init pcap: %s", ebuf);
}

int
main(int argc, char *argv [])
{
	char ebuf[PCAP_ERRBUF_SIZE + 32];
	int fg = 1, ch, cnt, snaplen = 65535;
	glob_t g;
	FILE *cmdfp;
	char *cmdbuf, *fname, *rem;
	u_char *pargs;
	static struct in_addr localnet, netmask;
	struct bpf_program bpfcode;
	struct pcap_stat pstat;
	struct dump_info info;
	pcap_handler printer;
	struct stat sb;
 
	cmdbuf = NULL;
	memset(&opts, 0, sizeof(opts));
	opts.thiszone = gmt2local(0);
	opts.qflag = 1;
	while ((ch = getopt(argc, argv, "a:c:df:e:hi:lnpqr:t:u:vw:xB:C:DE:F:HLNOP:\
	    RST:X")) != -1)
		switch(ch) {
		case 'a':
			if (strncasecmp(optarg, "all", 4) == 0) {
				g.gl_offs=1;
				glob("*.dmp*", 0, NULL, &g);
				for(cnt = 0; cnt < g.gl_pathc; cnt++) {
					fname = chomp(g.gl_pathv[cnt]);
					disassemble_fname(fname);
				}
			} else {
				fname = chomp(optarg);
				disassemble_fname(fname);
			}
			exit(EX_OK);
			break; /* NOT REACHED */
		case 'c':
			opts.cflag = strtoul(optarg, &rem, 0);
			if (*rem || rem == optarg || opts.cflag > LONG_MAX ||
				!opts.cflag)
				errx(EX_USAGE, "invalid packet count" \
				    " specification.");
			break;
		case 'd':
			opts.dflag++;
			break;
		case 'e':
			opts.eflag = optarg;
			readtimespec(opts.eflag);
			break;
		case 'f':
			opts.fflag = optarg;
			if (stat(opts.fflag, &sb) < 0)
				err(1, "stat failed");
			cmdbuf = malloc(sb.st_size);
			if (cmdbuf == NULL)
				err(1, "malloc failed");
			if ((cmdfp = fopen(opts.fflag, "r")) == NULL)
				errx(EX_NOINPUT, "unable to open rules file.");
			logic_load(cmdbuf, cmdfp);
			fclose(cmdfp);
			break;
		case 'h':
			usage(NULL);
			exit(EX_USAGE);
			break;
			/* NOT REACHED */
		case 'i':
			opts.iflag = optarg;
			break;
		case 'l':
			setvbuf(stdout, NULL, _IOLBF, 0);
			break;
		case 'n':
			opts.nflag++;
			break;
		case 'p':
			opts.pflag++;
			break;
		case 'q':
			opts.qflag = 0;
			break;
		case 'r':
			opts.rflag = optarg;
			break;
		case 't':
			opts.tflag = strtoul(optarg, &rem, 0);
			if (*rem || rem == optarg || opts.uflag > LONG_MAX ||
			    !opts.tflag)
				errx(EX_USAGE, "invalid timeout specification.");
			break;
		case 'u':
			opts.uflag = strtoul(optarg, &rem, 0);
			if (*rem || rem == optarg || opts.uflag > LONG_MAX ||
			    !opts.uflag)
				errx(EX_USAGE, "invalid UID specification.");
			break;
		case 'v':
			printf("%s\n", _VERSION);
			exit(0);
			break;
		case 'w':
			opts.wflag = optarg;
			break;
		case 'x':
			opts.xflag++;
			break;
		case 'B':
			opts.Bflag = optarg;
			ln_pattern(opts.Bflag);
			break;
		case 'C':
			opts.Cflag = parseCflag(optarg);
			if (!opts.Cflag)
				errx(1, "size specification out of bounds");
			break;
		case 'D':
			opts.Dflag++;
			break;
		case 'E':
			opts.Eflag = optarg;
			break;
		case 'H':
			/* If we are just processing packet headers then
			 * there is no point is capturing the entire frame
			 * off the wire. Set the snaplen to 100.
			 */
			snaplen = 100;
			opts.Hflag++;
			break;
		case 'L':
			opts.Lflag++;
			break;
		case 'O':
			opts.Oflag = 1;
			break;
		case 'R':
			opts.Rflag++;
			break;
		case 'P':
#if !defined __POWERPC__ && !defined linux
			opts.Pflag = optarg;
			scan_descriptor_tables(opts.Pflag);
			cmdbuf = (char *)compile_logic_list();
#else
			errx(EX_USAGE, "-P not yet supported on this system.");
#endif
			break;
		case 'T':
			opts.Tflag = atoi(optarg);
			break;
		default:
			usage(NULL);
			exit(EX_USAGE);
		}
	argv += optind;
	argc -= optind;
	if (!opts.iflag && !opts.rflag)
       		if (!(opts.iflag = pcap_lookupdev(ebuf)))
			errx(EX_USAGE, "fatal: pcap_lookupdev: %s", ebuf);
	handle_signals();
	if (!opts.pflag)
		init_tabs();
	if (!opts.fflag && !opts.Pflag) {
		cmdbuf = retrive_pcap_expr(argv);
		if (cmdbuf == NULL)
			cmdbuf = " ";
	}
	if (opts.rflag != NULL)
		offline();
	else {
		pd = pcap_open_live(opts.iflag, snaplen, opts.qflag,
		    READ_TIMEOUT, ebuf);
		if (pd == NULL)
			errx(EX_DATAERR, "fatal: pcap_open_live: %s", ebuf);
	}
#if (!defined(linux))
	if (opts.Rflag && !opts.wflag)
		if (ioctl(pd->fd, BIOCIMMEDIATE, &fg) < 0)
			errx(EX_OSERR, "fatal: ioctl(BIOCIMMEDIATE) failed.");
#endif
	if (opts.wflag) {
		pdmper = pcap_dump_open(pd, opts.wflag);
		if (pdmper == NULL)
			errx(EX_NOINPUT, "fatal: pcap_dump_open: %s",
	 		    pcap_geterr(pd));
		info.WFileName = opts.wflag;
		info.pd = pd;
		info.p = pdmper;
	}
	fprintf(stderr, "ipex: reading packets from %s\n",
	    opts.rflag ? opts.rflag : opts.iflag);
	if (!opts.rflag) {
		if (pcap_lookupnet(opts.iflag, &localnet.s_addr,
		    &netmask.s_addr, ebuf) < 0) {
			memset(&localnet, 0, sizeof(localnet));
			memset(&netmask, 0, sizeof(netmask));
			fprintf(stderr,"warning: pcap_lookupnet failed\n");
		}
	}
	if (pcap_compile(pd, &bpfcode, cmdbuf, opts.Oflag,
		netmask.s_addr) < 0)
		errx(EX_DATAERR, "fatal: pcap_compile: %s", pcap_geterr(pd));
	if (pcap_setfilter(pd, &bpfcode) < 0)
		errx(EX_DATAERR, "fatal: pcap_setfilter() failed");
	if ((opts.loffset = datalink_lookup_offset(pd)) == -1)
		errx(EX_UNAVAILABLE, "fatal: unsupported interface type");
	printer = (pcap_handler)delegate;
	pargs = (caddr_t)&info;
	pcap_loop(pd, opts.cflag, printer, pargs);
	if (!opts.rflag) {
		pcap_stats(pd, &pstat);
		fprintf(stderr, "\nsession statistics: %d packets"
		    "received, %d dropped\n", pstat.ps_recv, pstat.ps_drop);
	}
	pcap_close(pd);
	return (0);
}

void 
cleanup(void)
{
	struct pcap_stat pstat;

	pcap_stats(pd, &pstat);
	fprintf(stderr, "\ninterrupted: session stats: %d packets received, "
	    "%d dropped\n", pstat.ps_recv, pstat.ps_drop);
	free(protocols);
	free(tcp_ports);
	free(udp_ports);
	flush_ns_cache();
	if (opts.Tflag)
		heap_destory(conqueue);
	pcap_close(pd);
	exit(0);
}

struct proto_printers *
get_proto_printer(u_char proto, int caplen)
{
	struct proto_printers *p;

	for (p = p_funcs; p->pp_printer; p++)
		if (proto == p->pp_proto) {
			/*
			 * extra check here to make sure we have enough
			 * data from the wire to align the proper protocol
			 * header later.
			 */
			if (caplen >= p->pp_hlen)
				return (p);
			else
				break;
		}
	return (NULL);
}

int
datalink_lookup_offset(pcap_t *p)
{

	switch(pcap_datalink(p)) {
	case DLT_EN10MB:
	case DLT_IEEE802:
		return(ETHHDR_SIZE);
	case DLT_FDDI:
		return(FDDIHDR_SIZE);
	case DLT_SLIP:
		return(SLIPHDR_SIZE);
	case DLT_PPP:
		return(PPPHDR_SIZE);
	case DLT_RAW:
		return(RAWHDR_SIZE);
#ifdef HAVE_NET_IF_PFLOG_H
	case DLT_PFLOG:
		return(PFLOG_HDRLEN);
#endif
	case DLT_NULL:
		return(LOOPHDR_SIZE);
	default:
		break;
	}
	return(-1);	/* unknown datalink layer type */
}

pcap_handler
delegate(u_char *pargs, struct pcap_pkthdr *h, u_char *p)
{
	struct ip *ip;
	struct proto_printers *pp, pr;
	int match;
	char block[65535];
	struct offset off;

	if (opts.Eflag) {
		memset(&off, 0, sizeof(off));
		memcpy(&block[0], p, h->len);
		rettext(&block[0], h->len);
		block[h->len] = 0;
		if (!grep(opts.Eflag, &block[0], &off))
			return (NULL);

	}
	if (opts.eflag) {
		match = ((h->ts.tv_sec >= dsp->lower) &&
		    (h->ts.tv_sec <= dsp->upper));
		if (!match)
			return (NULL);
	}
	if (opts.Bflag != NULL)
		if (!detectpattern(p, h->len))
			return (NULL);
	if (opts.Tflag) {
		handle_sessions(h, p);
		/* XXX should we be printing the sessions? */
		return (NULL);
   	}
	if (opts.tflag) {
		statetrack(pargs, h, p);
		return (NULL);
	}
	if (opts.wflag) {
		dump_and_trunc(pargs, h, p);
		return (NULL);
	}
	ip = (struct ip *)(p + opts.loffset);
	pp = get_proto_printer(ip->ip_p, h->caplen);
	if (pp == NULL) {
		pp = &pr;
		pp->pp_printer = generic_print;
	}
	pp->pp_printer(NULL, h, p);
	if (opts.Hflag)
		return (NULL);
	if (opts.xflag)
		ascii_print_with_offset(p, h->len, 0, 1);
	else
		text_dump(p, h->len, &off);
	return (NULL);
}

int
usage(char *base)
{
	fprintf(stderr,
	"usage: ipex [-hvnqbHxTLGdO] [-f config] [-r pattern] [-o outfile]\n"
	"            [-c count] [-i interface] [-u uid] [-P op=arg]\n"
	"	    [-t time] [-w file] [-F file] [expression] [-B file]\n");
	return (1);
}

/* Service lookup routines were snagged from IP Filter's ipmon. */
void
init_tabs(void)
{
	struct protoent *p;
	struct  servent *s;
	char *name, **tab;
	int port;

	if (protocols != NULL) {
		free(protocols);
		protocols = NULL;
	}
	protocols = memalloc(256 * sizeof(*protocols));
	memset(protocols, 0, 256 * sizeof(*protocols));
	setprotoent(1);
	while ((p = getprotoent()) != NULL)
		if (p->p_proto >= 0 && p->p_proto <= 255 && p->p_name != NULL)
			protocols[p->p_proto] = strdup(p->p_name);
	endprotoent();
	if (udp_ports != NULL) {
		free(udp_ports);
		udp_ports = NULL;
	}
	udp_ports = memalloc(65536 * sizeof(*udp_ports));
	if (udp_ports != NULL)
		memset(udp_ports, 0, 65536 * sizeof(*udp_ports));
	if (tcp_ports != NULL) {
		free(tcp_ports);
		tcp_ports = NULL;
	}
	tcp_ports = memalloc(65536 * sizeof(*tcp_ports));
	if (tcp_ports != NULL)
		memset(tcp_ports, 0, 65536 * sizeof(*tcp_ports));
	setservent(1);
	while ((s = getservent()) != NULL) {
		if (s->s_proto == NULL)
			continue;
		else if (!strcmp(s->s_proto, "tcp")) {
			port = ntohs(s->s_port);
			name = s->s_name;
			tab = tcp_ports;
		} else if (!strcmp(s->s_proto, "udp")) {
			port = ntohs(s->s_port);
			name = s->s_name;
			tab = udp_ports;
		} else
			continue;
		if ((port < 0 || port > 65535) || (name == NULL))
			continue;
		tab[port] = strdup(name);
	}
	return;
}

char *
getproto(u_int p)
{
	static char pnum[4];
	char *s;

	p &= 0xff;
	s = protocols ? protocols[p] : NULL;
	if (s == NULL) {
		sprintf(pnum, "%u", p);
		s = pnum;
	}
	return (s);
}

char *
portname(int res, char *proto, u_int port)
{
	static char pname[8];
	char *s;

	port = ntohs(port);
	port &= 0xffff;
	if (opts.pflag) {
		sprintf(pname, "%u", port);
		return (&pname[0]);
	}
	s = NULL;
	if (!strcmp(proto, "tcp"))
		s = tcp_ports[port];
	else if (!strcmp(proto, "udp"))
		s = udp_ports[port];
	if (s == NULL) {
		sprintf(pname, "%u", port);
		s = pname;
	}
	return (s);
}


syntax highlighted by Code2HTML, v. 0.9.1