/*
 * Copyright (c) 2002, 2003, 2004 Niels Provos <provos@citi.umich.edu>
 * All rights reserved.
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <sys/param.h>
#include <sys/types.h>

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_SYS_IOCCOM_H
#include <sys/ioccom.h>
#endif
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/tree.h>
#include <sys/wait.h>
#include <sys/queue.h>

#include <pcap.h>

#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <signal.h>
#ifdef HAVE_TIME_H
#include <time.h>
#endif
#include <unistd.h>
#include <getopt.h>
#include <dnet.h>

#undef timeout_pending
#undef timeout_initialized

#include <event.h>

#include "honeyd.h"
#include "osfp.h"
#include "hsniff.h"
#include "hooks.h"
#include "interface.h"
#include "tagging.h"
#include "stats.h"
#include "debug.h"

int			honeyd_debug;

static int		hsniff_show_version;
static int		hsniff_show_usage;
static int		hsniff_useudp;
static uid_t		hsniff_uid = 32767;
static gid_t		hsniff_gid = 32767;

static struct option hsniff_long_opts[] = {
	{"version",     0, &hsniff_show_version, 1},
	{"help",        0, &hsniff_show_usage, 1},
	{0, 0, 0, 0}
};

struct tree tcpcons;

SPLAY_PROTOTYPE(tree, tuple, node, conhdr_compare);
SPLAY_GENERATE(tree, tuple, node, conhdr_compare);

void
usage(void)
{
	fprintf(stderr,
		"Usage: hsniff [OPTIONS] [net ...]\n\n"
		"where options include:\n"
		"  -d                     Do not daemonize, be verbose.\n"
		"  -V, --version          Print program version and exit.\n"
		"  -h, --help             Print this message and exit.\n"
	    );
	
	exit(1);
}

void
hsniff_settcp(struct tcp_track *con, struct ip_hdr *ip, struct tcp_hdr *tcp,
    int local)
{
	struct tuple *hdr = &con->conhdr;

	memset(hdr, 0, sizeof(struct tuple));
	hdr->ip_src = ip->ip_src;
	hdr->ip_dst = ip->ip_dst;
	hdr->sport = ntohs(tcp->th_sport);
	hdr->dport = ntohs(tcp->th_dport);
	hdr->type = SOCK_STREAM;
	hdr->local = local;

	TAILQ_INIT(&con->segments);
}

void
hsniff_setudp(struct udp_con *con, struct ip_hdr *ip, struct udp_hdr *udp,
    int local)
{
	struct tuple *hdr = &con->conhdr;

	memset(hdr, 0, sizeof(struct tuple));
	hdr->ip_src = ip->ip_src;
	hdr->ip_dst = ip->ip_dst;
	hdr->sport = ntohs(udp->uh_sport);
	hdr->dport = ntohs(udp->uh_dport);
	hdr->type = SOCK_DGRAM;
	hdr->local = local;
	con->softerrors = 0;
	con->cmd.pfd = -1;
	con->cmd.perrfd = -1;

	TAILQ_INIT(&con->incoming);
}

static void
syslog_init(int argc, char *argv[])
{
	int options, i;
	char buf[MAXPATHLEN];

#ifdef LOG_PERROR
	options = LOG_PERROR|LOG_PID|LOG_CONS;
#else
	options = LOG_PID|LOG_CONS;
#endif
	openlog("hsniff", options, LOG_DAEMON);	

	/* Create a string containing all the command line
	 * arguments and pass it to syslog:
	 */

	buf[0] = '\0';
	for (i = 1; i < argc; i++) {
		if (i > 1 && strlcat(buf, " ", sizeof(buf)) >= sizeof(buf))
			break;
		if (strlcat(buf, argv[i], sizeof(buf)) >= sizeof(buf))
			break;
	}

	syslog(LOG_NOTICE, "started with %s", buf);
}

void
hsniff_init(void)
{
	/* Initalize ongoing connection state */
	SPLAY_INIT(&tcpcons);
}

void
hsniff_exit(int status)
{
	interface_close_all();

	closelog();

	exit(status);
}

void
generic_timeout(struct event *ev, int seconds)
{
	struct timeval tv;

	timerclear(&tv);
	tv.tv_sec = seconds;
	evtimer_add(ev, &tv);
}

struct tcp_track *
tcp_track_new(struct ip_hdr *ip, struct tcp_hdr *tcp, int local)
{
	struct tcp_track *con;

	if ((con = calloc(1, sizeof(struct tcp_track))) == NULL) {
		syslog(LOG_WARNING, "calloc: %m");
		return (NULL);
	}

	hsniff_settcp(con, ip, tcp, local);
	evtimer_set(&con->timeout, hsniff_tcp_timeout, con);

	SPLAY_INSERT(tree, &tcpcons, &con->conhdr);

	TAILQ_INIT(&con->segments);
	return (con);
}

void
tcp_track_free(struct tcp_track *con)
{
	struct tcp_segment *seg;
	SPLAY_REMOVE(tree, &tcpcons, &con->conhdr);

	while ((seg = TAILQ_FIRST(&con->segments)) != NULL) {
		TAILQ_REMOVE(&con->segments, seg, next);
		free(seg);
	}

	hooks_dispatch(IP_PROTO_TCP, HD_INCOMING_STREAM, &con->conhdr,
	    NULL, 0);

	evtimer_del(&con->timeout);

	free(con);
}

void
hsniff_tcp_timeout(int fd, short event, void *arg)
{
	struct tcp_track *con = arg;

	syslog(LOG_DEBUG, "Expiring TCP %s (%p)",
	    honeyd_contoa(&con->conhdr), con);

	tcp_track_free(con);
}

void
tcp_insert(struct tcp_track *con, uint32_t th_seq, void *data, size_t dlen)
{
	struct tcp_segment *seg, *newseg;

	TAILQ_FOREACH(seg, &con->segments, next) {
		/* New packet before data that we have seen so far */
		if (TCP_SEQ_LEQ(th_seq + dlen, seg->seq)) {
			newseg = calloc(1, sizeof(struct tcp_segment));
			if (newseg == NULL)
				err(1, "%s: calloc", __func__);
			TAILQ_INSERT_BEFORE(seg, newseg, next);
			return;
		}

		/* Packet is a total duplicate */
		if (TCP_SEQ_LEQ(th_seq + dlen, seg->seq + seg->len))
			return;

		/* There is some overlap */
		if (TCP_SEQ_LT(th_seq, seg->seq + seg->len)) {
			uint32_t off;

			off = seg->seq + seg->len - th_seq;
			th_seq += off;
			data += off;
			dlen -= off;
		}
	}
}

void
tcp_drop_subsumed(struct tcp_track *con)
{
	struct tcp_segment *seg;
	uint32_t off;

	while ((seg = TAILQ_FIRST(&con->segments)) != NULL) {
		/* This segment is still in the future */
		if (TCP_SEQ_GT(seg->seq, con->snd_una))
			break;

		/* For all other segments, we can do some work */
		TAILQ_REMOVE(&con->segments, seg, next);

		/* The old segment is completely covered, so we can drop it */
		if (TCP_SEQ_LEQ(seg->seq + seg->len, con->snd_una)) {
			free(seg);
			continue;
		}

		if (seg->seq != con->snd_una) {
			/* The data overlaps */
			off = con->snd_una - seg->seq;
			seg->seq += off;
			seg->len -= off;
		}

		/*
		 * We are matching up a previously stored segment,
		 * so we can stream its content out.
		 */

		syslog(LOG_NOTICE, "Streaming: %s %u: %d",
		    honeyd_contoa(&con->conhdr), con->snd_una, seg->len);
		hooks_dispatch(IP_PROTO_TCP, HD_INCOMING_STREAM,
		    &con->conhdr, seg->data, seg->len);

		con->snd_una += seg->len;
		free(seg);
	}
}

void
tcp_recv_cb(u_char *pkt, u_short pktlen)
{
	struct ip_hdr *ip;
	struct tcp_hdr *tcp;
	struct tcp_track *con, tmp;
	uint32_t th_seq;
	uint16_t th_sum;
	size_t dlen;
	u_char *data;
	uint8_t tiflags;

	ip = (struct ip_hdr *)pkt;
	tcp = (struct tcp_hdr *)(pkt + (ip->ip_hl << 2));
	data = (u_char *)(pkt + (ip->ip_hl*4) + (tcp->th_off*4));
	
	if (pktlen < (ip->ip_hl << 2) + TCP_HDR_LEN)
		return;

	/* Check the checksum the brutal way, until libdnet supports */
	th_sum = tcp->th_sum;
	ip_checksum(ip, pktlen);
	if (th_sum != tcp->th_sum)
		return;

	th_seq = ntohl(tcp->th_seq);

	tiflags = tcp->th_flags;
	hsniff_settcp(&tmp, ip, tcp, 0);
	con = (struct tcp_track *)SPLAY_FIND(tree, &tcpcons, &tmp.conhdr);
	if (con == NULL) {
		/* Only create new connections upon seeing a SYN packet */
		if (!(tiflags & TH_SYN) || (tiflags & (TH_ACK|TH_RST)))
			return;

		/* Need to create new tcp connection tracker */
		if ((con = tcp_track_new(ip, tcp, 0)) == NULL)
			return;

		con->snd_una = th_seq + 1;
	}

	/* 
	 * We need to hear back from this connection every so often,
	 * or we are going to time it out.
	 */
	generic_timeout(&con->timeout, HSNIFF_CON_EXPIRE);

	hooks_dispatch(ip->ip_p, HD_INCOMING, &tmp.conhdr, pkt, pktlen);
	
	dlen = ntohs(ip->ip_len) - (ip->ip_hl << 2) - (tcp->th_off << 2);

	/*
	 * This is responsible for ignoring the initial syn packet,
	 * so checking for SYN further below is safe.
	 */
	if (TCP_SEQ_LEQ(th_seq + dlen, con->snd_una))
		return;

	/* There is some overlap */
	if (TCP_SEQ_LT(th_seq, con->snd_una)) {
		uint32_t off;

		off = con->snd_una - th_seq;
		th_seq += off;
		data += off;
		dlen -= off;
	}

	if (th_seq == con->snd_una) {
		/* Inform our listener about the new data */
		syslog(LOG_NOTICE, "Streaming: %s %u: %d",
		    honeyd_contoa(&con->conhdr), con->snd_una, dlen);
		hooks_dispatch(IP_PROTO_TCP, HD_INCOMING_STREAM, &con->conhdr,
		    data, dlen);
		con->snd_una = th_seq + dlen;
		
		tcp_drop_subsumed(con);
	} else {
		if (TCP_SEQ_GEQ(th_seq, con->snd_una + TCP_WIN_MAX) &&
		    TCP_SEQ_LEQ(th_seq, con->snd_una + TCP_WIN_MAX * 2)) {
			/*
			 * We probably lost some packets that are not going
			 * to be retransmitted.
			 */
			con->snd_una = th_seq - TCP_WIN_MAX;
			tcp_drop_subsumed(con);
		}

		/* Only insert data that is at most one window away */
		if (TCP_SEQ_LEQ(th_seq, con->snd_una + TCP_WIN_MAX))
			tcp_insert(con, th_seq, data, dlen);
	} 

	if (tiflags & (TH_RST|TH_FIN|TH_SYN)) {
		/* 
		 * Check if the conditions for closing this connection,
		 * have been met.
		 */
		if (TCP_SEQ_GEQ(th_seq, con->snd_una) &&
		    TCP_SEQ_LEQ(th_seq, con->snd_una + TCP_WIN_MAX)) {
			tcp_track_free(con);
			return;
		}
	}
}

void
udp_recv_cb(u_char *pkt, u_short pktlen)
{
	struct ip_hdr *ip = NULL;
	struct udp_hdr *udp;
	struct udp_con tmp;
	
	uint16_t uh_sum;
	u_char *data;
	u_int dlen;

	ip = (struct ip_hdr *)pkt;
	udp = (struct udp_hdr *)(pkt + (ip->ip_hl << 2));

	if (pktlen < (ip->ip_hl << 2) + UDP_HDR_LEN)
		return;

	hsniff_setudp(&tmp, ip, udp, 0);
	hooks_dispatch(ip->ip_p, HD_INCOMING, &tmp.conhdr, pkt, pktlen);

	data = (u_char *)(pkt + (ip->ip_hl*4) + UDP_HDR_LEN);
	dlen = ntohs(ip->ip_len) - (ip->ip_hl << 2) - UDP_HDR_LEN;
	if (dlen != (ntohs(udp->uh_ulen) - UDP_HDR_LEN))
		return;
	
	uh_sum = udp->uh_sum;
	if (uh_sum) {
		ip_checksum(ip, pktlen);
		if (uh_sum != udp->uh_sum)
			return;
	}

	hooks_dispatch(ip->ip_p, HD_INCOMING_STREAM, &tmp.conhdr, data, dlen);
}

void
hsniff_recv_cb(u_char *ag, const struct pcap_pkthdr *pkthdr, const u_char *pkt)
{
	const struct interface *inter = (const struct interface *)ag;
	struct ip_hdr *ip;
	u_short iplen;

	/* Everything below assumes that the packet is IPv4 */
	if (pkthdr->caplen < inter->if_dloff + IP_HDR_LEN)
		return;

	ip = (struct ip_hdr *)(pkt + inter->if_dloff);

	iplen = ntohs(ip->ip_len);
	if (pkthdr->caplen - inter->if_dloff < iplen)
		return;
	if (ip->ip_hl << 2 > iplen)
		return;
	if (ip->ip_hl << 2 < sizeof(struct ip_hdr))
		return;

	switch(ip->ip_p) {
	case IP_PROTO_TCP:
		tcp_recv_cb((u_char *)ip, iplen);
		break;
	case IP_PROTO_UDP:
		if (hsniff_useudp)
			udp_recv_cb((u_char *)ip, iplen);
		break;
	default:
		return;
	}
}

void
hsniff_signal(int fd, short what, void *arg)
{
	syslog(LOG_NOTICE, "exiting on signal %d", fd);
	hsniff_exit(0);
}

int
main(int argc, char *argv[])
{
	extern int interface_dopoll;
	struct event sigterm_ev, sigint_ev;
	char *dev[HSNIFF_MAX_INTERFACES];
	char **orig_argv;
	char *osfp = PATH_HONEYDDATA "/pf.os";
	struct addr stats_dst;
	u_short stats_port = 0;
	char *stats_username = NULL;
	char *stats_password = NULL;
	char filter[2048], line[128];
	int i, c, orig_argc, ninterfaces = 0;
	FILE *fp;

	orig_argc = argc;
	orig_argv = argv;
	while ((c = getopt_long(argc, argv, "VPUdc:i:u:g:f:0:h?",
				hsniff_long_opts, NULL)) != -1) {
		char *ep;
		switch (c) {
		case 'U':
			hsniff_useudp = 1;
			break;
		case 'c': {
			char line[1024], *p = line;
			char *address;
			char *strport;
			char *name;
			char *password;

			strlcpy(line, optarg, sizeof(line));

			if ((address = strsep(&p, ":")) == NULL)
				usage();
			if ((strport = strsep(&p, ":")) == NULL)
				usage();
			if ((name = strsep(&p, ":")) == NULL)
				usage();
			if ((password = strsep(&p, ":")) == NULL)
				usage();
			if (p != NULL && *p != '\0')
				usage();

			if (addr_pton(address, &stats_dst) == -1) {
				fprintf(stderr, "Bad destination address %s\n",
				    address);
				usage();
			}
			if ((stats_port = atoi(strport)) == 0) {
				fprintf(stderr, "Bad destination port %s\n",
				    strport);
				usage();
			}

			stats_username = strdup(name);
			stats_password = strdup(password);
		}
			break;
		case 'u':
			hsniff_uid = strtoul(optarg, &ep, 10);
			if (optarg[0] == '\0' || *ep != '\0') {
				fprintf(stderr, "Bad uid %s\n", optarg);
				usage();
			}
			break;
		case 'g':
			hsniff_gid = strtoul(optarg, &ep, 10);
			if (optarg[0] == '\0' || *ep != '\0') {
				fprintf(stderr, "Bad gid %s\n", optarg);
				usage();
			}
			break;
		case 'V':
			hsniff_show_version = 1;
			break;
		case 'P':
			interface_dopoll = 1;
			break;
		case 'd':
			honeyd_debug++;
			break;
		case 'i':
			if (ninterfaces >= HSNIFF_MAX_INTERFACES)
				errx(1, "Too many interfaces specified");
			dev[ninterfaces++] = optarg;
			break;
		case '0':
			osfp = optarg;
			break;
		case 0:
			/* long option handled -- skip this one. */
			break;
		default:
			usage();
			/* not reached */
		}
	}

	if (hsniff_show_version) {
		printf("Hsniff Version %s\n", VERSION);
		exit(0);
	}
	if (hsniff_show_usage) {
		usage();
		/* not reached */
	}

	argc -= optind;
	argv += optind;

	filter[0] = '\0';
	while (argc > 0) {
		struct addr addr;

		if (addr_pton(*argv, &addr) == -1)
			errx(1, "invalid address \"%s\"", *argv);

		if (strlen(filter) &&
		    strlcat(filter, " or ", sizeof(filter)) >= sizeof(filter))
			errx(1, "too many addresses; filter too long");

		if (addr.addr_bits == 32) {
			snprintf(line, sizeof(line), "(not src %s and dst %s)",
			    addr_ntoa(&addr), addr_ntoa(&addr));
			if (strlcat(filter, line, sizeof(filter)) >= 
			    sizeof(filter))
				errx(1, "too many address; filter too long");
		} else {
			snprintf(line, sizeof(line),
			    "(not src net %s and dst net %s)",
			    addr_ntoa(&addr), addr_ntoa(&addr));
			if (strlcat(filter, line, sizeof(filter)) >= 
			    sizeof(filter))
				errx(1, "too many address; filter too long");
		}
		argv++;
		argc--;
	}

	if (strlen(filter)) {
		extern char *interface_filter;

		interface_filter = filter;
	} else {
		fprintf(stderr, "no addresses specified\n");
		usage();
		/* NOTREACHED */
	}

	fprintf(stderr, "Hsniff V%s Copyright (c) 2004-2007 Niels Provos\n",
	    VERSION);

	/* disabled event methods that don't work with bpf */
	interface_prevent_init();

	event_init();

	syslog_init(orig_argc, orig_argv);

	/* Initialize Honeyd's callback hooks */
	hooks_init();

	tagging_init();
	interface_initialize(hsniff_recv_cb);

	if (stats_username == NULL)
		errx(1, "no username specified for stats reporting");

	stats_init();
	stats_init_collect(&stats_dst, stats_port,
	    stats_username, stats_password);

	/* PF OS fingerprints */
	if (honeyd_osfp_init(osfp) == -1)
		errx(1, "reading OS fingerprints failed");

	/* Initialize the specified interfaces */
	if (ninterfaces == 0)
		interface_init(NULL, argc, argc ? argv : NULL);
	else {
		for (i = 0; i < ninterfaces; i++)
			interface_init(dev[i], argc, argc ? argv : NULL);
	}

	/* Create PID file, we might not be able to remove it */
	unlink(HSNIFF_PIDFILE);
	if ((fp = fopen(HSNIFF_PIDFILE, "w")) == NULL)
		err(1, "fopen");

	/* Start Hsniff in the background if necessary */
	if (!honeyd_debug) {
		setlogmask(LOG_UPTO(LOG_INFO));
		
		fprintf(stderr, "Hsniff starting as background process\n");
		if (daemon(1, 0) < 0) {
			unlink(HSNIFF_PIDFILE);
			err(1, "daemon");
		}
	}
	
	fprintf(fp, "%d\n", getpid());
	fclose(fp);
	
	chmod(HSNIFF_PIDFILE, 0644);

	/* Drop privileges if we do not need them */
	droppriv(hsniff_uid, hsniff_gid);

	syslog(LOG_NOTICE,
	    "Demoting process privileges to uid %u, gid %u",
	    hsniff_uid, hsniff_gid);

	signal_set(&sigint_ev, SIGINT, hsniff_signal, NULL);
	signal_add(&sigint_ev, NULL);
	signal_set(&sigterm_ev, SIGTERM, hsniff_signal, NULL);
	signal_add(&sigterm_ev, NULL);

	event_dispatch();

	syslog(LOG_ERR, "Kqueue does not recognize bpf filedescriptor.");

	return (0);
}

/* Drop the privileges and verify that they got dropped */

#define SETERROR(x) do { \
	snprintf x; \
	strlcat(error, errline, sizeof(error)); \
} while (0)

void
droppriv(uid_t uid, gid_t gid)
{
	static char error[1024];
	static char errline[256];

	error[0] = '\0';

	/* Lower privileges */
#ifdef HAVE_SETGROUPS
	if (setgroups(1, &gid) == -1)
		SETERROR((errline, sizeof(errline),
			     "%s: setgroups(%d) failed\n", __func__, gid));
#endif
#ifdef HAVE_SETREGID
	if (setregid(gid, gid) == -1)
		SETERROR((errline, sizeof(errline),
			     "%s: setregid(%d) failed\n", __func__, gid));
#endif
	if (setegid(gid) == -1)
		SETERROR((errline, sizeof(errline),
			     "%s: setegid(%d) failed\n", __func__, gid));
	if (setgid(gid) == -1)
		SETERROR((errline, sizeof(errline), 
			     "%s: setgid(%d) failed\n", __func__, gid));
#ifdef HAVE_SETREUID
	if (setrugid(uid, uid) == -1)
		SETERROR((errline, sizeof(errline),
			     "%s: setreuid(%d) failed\n", __func__, uid));
#endif
#ifdef __OpenBSD__
	if (seteuid(uid) == -1)
		SETERROR((errline, sizeof(errline),
			     "%s: seteuid(%d) failed\n", __func__, gid));
#endif
	if (setuid(uid) == -1)
		SETERROR((errline, sizeof(errline),
			     "%s: setuid(%d) failed\n", __func__, gid));

	if (getgid() != gid || getegid() != gid) {
		SETERROR((errline, sizeof(errline),
			     "%s: could not set gid to %d", __func__, gid));
		goto error;
	}

	if (getuid() != uid || geteuid() != uid) {
		SETERROR((errline, sizeof(errline),
			     "%s: could not set uid to %d", __func__, uid));
		goto error;
	}

	/* Make really sure that we dropped them */
	if (uid != 0 && (setuid(0) != -1 || seteuid(0) != -1)) {
		SETERROR((errline, sizeof(errline),
			     "%s: did not successfully drop privilege",
			     __func__));
		goto error;
	}
	if (gid != 0 && (setgid(0) != -1 || setegid(0) != -1)) {
		SETERROR((errline, sizeof(errline),
			     "%s: did not successfully drop privilege",
			     __func__));
		goto error;
	}

	return;
 error:
	syslog(LOG_WARNING, error);
	errx(1, "%s: terminated", __func__);
}


syntax highlighted by Code2HTML, v. 0.9.1