/*
 * trigger.c
 *
 * Copyright (c) 2000 Dug Song <dugsong@monkey.org>
 *
 * $Id: trigger.c,v 1.21 2001/03/15 08:33:05 dugsong Exp $
 */

#include "config.h"

#include <sys/types.h>
#include <sys/uio.h>
#include <netinet/in.h>

#include <stdlib.h>
#include <err.h>
#include <libnet.h>
#include <nids.h>

#include "decode.h"
#include "magic.h"
#include "options.h"
#include "pathnames.h"
#include "record.h"
#include "tcp_raw.h"
#include "trigger.h"

struct trigger {
	int num;
	struct decode *decode;
};

static struct trigger	ip_triggers[32];
static struct trigger	udp_triggers[512];
static struct trigger	tcp_triggers[512];
static struct trigger	rpc_triggers[32];

static u_int		ip_cnt = 0;
static u_int		udp_cnt = 0;
static u_int		tcp_cnt = 0;
static u_int		rpc_cnt = 0;

static char		obuf[4096];

static int
trigger_compare(const void *a, const void *b)
{
	struct trigger *p, *q;

	q = (struct trigger *)a;
	p = (struct trigger *)b;
	
	if (p->num < q->num) {
		return (-1);
	}
	else if (p->num > q->num) {
		return (1);
	}
	return (0);
}

int
trigger_set_ip(int num, char *name)
{
	struct trigger *t, tr;

	tr.num = num;
	
	if ((tr.decode = getdecodebyname(name)) == NULL) {
		warnx("trigger_set_ip: unknown decode: %s", name);
		return (0);
	}
	t = (struct trigger *) bsearch(&tr, &ip_triggers, ip_cnt,
				       sizeof(tr), trigger_compare);
	if (t != NULL) {
		if (Opt_debug)
			warnx("trigger_set_ip: proto %d already set", num);
		return (0);
	}
	if (ip_cnt == sizeof(ip_triggers) / sizeof(tr)) {
		warnx("trigger_set_ip: ip_triggers full");
		return (0);
	}
	ip_triggers[ip_cnt++] = tr;
	
	qsort(&ip_triggers, ip_cnt, sizeof(tr), trigger_compare);
	
	if (Opt_debug)
		warnx("trigger_set_ip: proto %d -> %s", num, name);
	
	return (1);
}

int
trigger_set_udp(int num, char *name)
{
	struct trigger *t, tr;
	
	tr.num = num;
	
	if ((tr.decode = getdecodebyname(name)) == NULL) {
		warnx("trigger_set_udp: unknown decode: %s", name);
		return (0);
	}
	t = (struct trigger *) bsearch(&tr, &udp_triggers, udp_cnt,
				       sizeof(tr), trigger_compare);
	if (t != NULL) {
		if (Opt_debug)
			warnx("trigger_set_udp: port %d already set", num);
		return (0);
	}
	if (udp_cnt == sizeof(udp_triggers) / sizeof(tr)) {
		warnx("trigger_set_udp: udp_triggers full");
		return (0);
	}
	udp_triggers[udp_cnt++] = tr;
	
	qsort(&udp_triggers, udp_cnt, sizeof(tr), trigger_compare);
	
	if (Opt_debug)
		warnx("trigger_set_udp: port %d -> %s", num, name);

	return (1);
}

int
trigger_set_tcp(int num, char *name)
{
	struct trigger *t, tr;
	
	tr.num = num;
	
	if ((tr.decode = getdecodebyname(name)) == NULL) {
		warnx("trigger_set_tcp: unknown decode: %s", name);
		return (0);
	}
	t = (struct trigger *) bsearch(&tr, &tcp_triggers, tcp_cnt,
				       sizeof(tr), trigger_compare);
	if (t != NULL) {
		if (Opt_debug)
			warnx("trigger_set_tcp: port %d already set", num);
		return (0);
	}
	if (tcp_cnt == sizeof(tcp_triggers) / sizeof(tr)) {
		warnx("trigger_set_tcp: tcp_triggers full");
		return (0);
	}
	tcp_triggers[tcp_cnt++] = tr;
	
	qsort(&tcp_triggers, tcp_cnt, sizeof(tr), trigger_compare);
	
	if (Opt_debug)
		warnx("trigger_set_tcp: port %d -> %s", num, name);
	
	return (1);
}

int
trigger_set_rpc(int num, char *name)
{
	struct trigger *t, tr;

	tr.num = num;
	
	if ((tr.decode = getdecodebyname(name)) == NULL) {
		warnx("trigger_set_tcp: unknown decode: %s", name);
		return (0);
	}
	t = (struct trigger *) bsearch(&tr, &rpc_triggers, rpc_cnt,
				       sizeof(tr), trigger_compare);
	if (t != NULL) {
		if (Opt_debug)
			warnx("trigger_set_rpc: RPC program %d already set",
			      num);
		return (0);
	}
	if (rpc_cnt == sizeof(rpc_triggers) / sizeof(tr)) {
		warnx("trigger_set_rpc: rpc_triggers full");
		return (0);
	}
	rpc_triggers[rpc_cnt++] = tr;
	
	qsort(&rpc_triggers, rpc_cnt, sizeof(tr), trigger_compare);
	
	if (Opt_debug)
		warnx("trigger_set_rpc: program %d -> %s", num, name);
	
	return (1);
}

static int
trigger_set(char *proto, int num, char *name)
{
	if (strcasecmp(proto, "ip") == 0) {
		return (trigger_set_ip(num, name));
	}
	else if (strcasecmp(proto, "udp") == 0) {
		return (trigger_set_udp(num, name));
	}
	else if (strcasecmp(proto, "tcp") == 0) {
		return (trigger_set_tcp(num, name));
	}
	else if (strcasecmp(proto, "rpc") == 0) {
		return (trigger_set_rpc(num, name));
	}
	else warnx("trigger_set: unknown protocol %s", proto);

	return (0);
}

static struct trigger *
trigger_set_magic(int proto, int num, u_char *buf, int len)
{
	struct trigger *t, tr;
	char *name;

	if ((name = magic_match(buf, len)) == NULL)
		return (NULL);

	t = NULL;
	tr.num = num;

	if (proto == IPPROTO_UDP) {
		trigger_set_udp(num, name);
		if (strcmp(name, "portmap") == 0 ||	/* XXX - hack */
		    strcmp(name, "mountd") == 0 ||
		    strcmp(name, "yppasswd") == 0) {
			trigger_set_udp(0 - num, name);
		}
		t = (struct trigger *) bsearch(&tr, &udp_triggers, udp_cnt,
					       sizeof(tr), trigger_compare);
	}
	else if (proto == IPPROTO_TCP) {
		trigger_set_tcp(num, name);
		if (strcmp(name, "portmap") == 0 ||	/* XXX - hack */
		    strcmp(name, "mountd") == 0 ||
		    strcmp(name, "yppasswd") == 0) {
			trigger_set_tcp(0 - num, name);
		}
		t = (struct trigger *) bsearch(&tr, &tcp_triggers, tcp_cnt,
					       sizeof(tr), trigger_compare);
	}
	return (t);
}

void
trigger_dump(void)
{
	FILE *f;
	int i;

	if ((f = fopen(DSNIFF_SERVICES, "w")) == NULL) {
		warn("trigger_dump: couldn't open " DSNIFF_SERVICES);
		return;
	}
	fprintf(f, "# $Id: trigger.c,v 1.21 2001/03/15 08:33:05 dugsong Exp $\n"
		"#\n# Network services, dsniff style\n#\n");
	
	for (i = 0; i < ip_cnt; i++) {
		fprintf(f, "%s\t\t%d/ip\n", ip_triggers[i].decode->dc_name,
			ip_triggers[i].num);
	}
	for (i = 0; i < udp_cnt; i++) {
		fprintf(f, "%s\t\t%d/udp\n", udp_triggers[i].decode->dc_name,
			udp_triggers[i].num);
	}
	for (i = 0; i < tcp_cnt; i++) {
		fprintf(f, "%s\t\t%d/tcp\n", tcp_triggers[i].decode->dc_name,
			tcp_triggers[i].num);
	}
	for (i = 0; i < rpc_cnt; i++) {
		fprintf(f, "%s\t\t%d/rpc\n", rpc_triggers[i].decode->dc_name,
			rpc_triggers[i].num);
	}
	fclose(f);
}
	
void
trigger_ip(struct libnet_ip_hdr *ip)
{
	struct trigger *t, tr;
	u_char *buf;
	int len;

	tr.num = ip->ip_p;
	
	t = (struct trigger *) bsearch(&tr, &ip_triggers, ip_cnt,
				       sizeof(tr), trigger_compare);

	if (t == NULL)
		return;
	
	buf = (u_char *)ip + (ip->ip_hl * 4);
	len = ntohs(ip->ip_len) - (ip->ip_hl * 4);
	
	if (Opt_debug)
		warnx("trigger_ip: decoding proto %d as %s",
		      tr.num, t->decode->dc_name);
	
	if ((len = t->decode->dc_func(buf, len, obuf, sizeof(obuf))) > 0) {
		record(ip->ip_src.s_addr, ip->ip_dst.s_addr, ip->ip_p,
		       0, 0, t->decode->dc_name, obuf, len);
	}		
}

/* libnids needs a nids_register_udp()... */
void
trigger_udp(struct libnet_ip_hdr *ip)
{
	struct trigger *t, tr;
	struct libnet_udp_hdr *udp;
	u_char *buf;
	int len, ip_hl = ip->ip_hl * 4;
	
	len = ntohs(ip->ip_len) - ip_hl;
	
	if (ip->ip_p != IPPROTO_UDP || len < sizeof(*udp))
		return;

	buf = (u_char *)ip + ip_hl;
	udp = (struct libnet_udp_hdr *)buf;
	
	if (len != ntohs(udp->uh_ulen))
		return;
	
	buf += sizeof(*udp);
	len -= sizeof(*udp);
	
	tr.num = ntohs(udp->uh_dport);
	t = (struct trigger *) bsearch(&tr, &udp_triggers, udp_cnt,
				       sizeof(tr), trigger_compare);
	if (t == NULL) {
		tr.num = 0 - (int) ntohs(udp->uh_sport);
		t = (struct trigger *) bsearch(&tr, &udp_triggers, udp_cnt,
					       sizeof(tr), trigger_compare);
		if (t == NULL && Opt_magic)
			t = trigger_set_magic(IPPROTO_UDP,
					      ntohs(udp->uh_dport), buf, len);
		if (t == NULL)
			return;
	}
	if (Opt_debug)
		warnx("trigger_udp: decoding port %d as %s",
		      tr.num, t->decode->dc_name);
	
	if ((len = t->decode->dc_func(buf, len, obuf, sizeof(obuf))) > 0) {
		record(ip->ip_src.s_addr, ip->ip_dst.s_addr, IPPROTO_UDP,
		       ntohs(udp->uh_sport), ntohs(udp->uh_dport),
		       t->decode->dc_name, obuf, len);
	}
}

static void
trigger_tcp_half(struct tuple4 *addr, struct half_stream *hs,
		 struct trigger *t)
{
	u_char *buf;
	int len;

	buf = hs->data;
	len = hs->count - hs->offset;

	if (len == 0 || buf == NULL)
		return;
	
	if (hs->bufsize > len)
		buf[len] = '\0';

	if (t == NULL && Opt_magic)
		t = trigger_set_magic(IPPROTO_TCP, addr->dest, buf, len);

	if (t != NULL) {
		if (Opt_debug)
			warnx("trigger_tcp: decoding port %d as %s",
			      addr->dest, t->decode->dc_name);
		
		if ((len = t->decode->dc_func(buf, len,
					      obuf, sizeof(obuf))) > 0) {
			record(addr->saddr, addr->daddr, IPPROTO_TCP,
			       addr->source, addr->dest, t->decode->dc_name,
			       obuf, len);
		}
	}
	hs->collect = 0;
}

void
trigger_tcp(struct tcp_stream *ts, void **conn_save)
{
	struct trigger *ct, *st, tr;
	
	tr.num = ts->addr.dest;
	ct = (struct trigger *) bsearch(&tr, &tcp_triggers, tcp_cnt,
					sizeof(tr), trigger_compare);
	
	tr.num = 0 - (int) ts->addr.dest;
	st = (struct trigger *) bsearch(&tr, &tcp_triggers, tcp_cnt,
					sizeof(tr), trigger_compare);
	
	switch (ts->nids_state) {
		
	case NIDS_JUST_EST:
		if (ct != NULL || Opt_magic) {
			ts->server.collect = 1;
		}
		if (st != NULL) {
			ts->client.collect = 1;
		}
		break;
		
	case NIDS_DATA:
		if ((ct != NULL || Opt_magic) && ts->server.count_new) {
			if (ts->server.count - ts->server.offset >=
			    Opt_snaplen) {
				trigger_tcp_half(&ts->addr, &ts->server, ct);
			}
			else nids_discard(ts, 0);
		}
		else if (st != NULL && ts->client.count_new) {
			if (ts->client.count - ts->client.offset >=
			    Opt_snaplen) {
				trigger_tcp_half(&ts->addr, &ts->client, st);
			}
			else nids_discard(ts, 0);
		}
		break;
		
	default:
		if ((ct != NULL || Opt_magic) && ts->server.count > 0) {
			trigger_tcp_half(&ts->addr, &ts->server, ct);
		}
		if (st != NULL && ts->client.count > 0) {
			trigger_tcp_half(&ts->addr, &ts->client, st);
		}
		break;
	}
}

void
trigger_tcp_raw(struct libnet_ip_hdr *ip)
{
	struct trigger *t, tr;
	struct libnet_tcp_hdr *tcp;
	struct iovec *iov;
	int len, ip_hl = ip->ip_hl * 4;
	
	len = ntohs(ip->ip_len) - ip_hl;

	if (ip->ip_p != IPPROTO_TCP || len < sizeof(*tcp))
		return;

	tcp = (struct libnet_tcp_hdr *)((u_char *)ip + ip_hl);
	
	tr.num = ntohs(tcp->th_dport);
	
	t = (struct trigger *) bsearch(&tr, &tcp_triggers, tcp_cnt,
				       sizeof(tr), trigger_compare);
	if (t == NULL) {
		tr.num = 0 - (int) ntohs(tcp->th_sport);
		t = (struct trigger *) bsearch(&tr, &tcp_triggers, tcp_cnt,
					       sizeof(tr), trigger_compare);
		if (t == NULL && !Opt_magic)
			return;
	}
	if ((iov = tcp_raw_input(ip, tcp, len)) == NULL)
		return;

	if (t == NULL && Opt_magic)
		t = trigger_set_magic(IPPROTO_TCP, ntohs(tcp->th_dport),
				      iov->iov_base, iov->iov_len);

	if (t != NULL) {
		if (Opt_debug)
			warnx("trigger_tcp_raw: decoding port %d as %s",
			      tr.num, t->decode->dc_name);
		
		len = t->decode->dc_func(iov->iov_base, iov->iov_len,
					 obuf, sizeof(obuf));
		
		if (len > 0) {
			record(ip->ip_src.s_addr, ip->ip_dst.s_addr,
			       IPPROTO_TCP, ntohs(tcp->th_sport),
			       ntohs(tcp->th_dport), t->decode->dc_name,
			       obuf, len);
		}
	}
	free(iov->iov_base);
	free(iov);
}

static void
trigger_tcp_raw_callback(in_addr_t src, in_addr_t dst,
			 u_short sport, u_short dport,
			 u_char *buf, int len)
{
	struct trigger *t, tr;

	tr.num = dport;

	t = (struct trigger *) bsearch(&tr, &tcp_triggers, tcp_cnt,
				       sizeof(tr), trigger_compare);
	if (t == NULL && Opt_magic)
		t = trigger_set_magic(IPPROTO_TCP, dport, buf, len);

	if (t != NULL) {
		if (Opt_debug)
			warnx("trigger_tcp_raw_timeout: "
			      "decoding port %d as %s",
			      tr.num, t->decode->dc_name);
		
		if ((len = t->decode->dc_func(buf, len,
					      obuf, sizeof(obuf))) > 0) {
			record(src, dst, IPPROTO_TCP, sport, dport,
			       t->decode->dc_name, obuf, len);
		}
	}
}

void
trigger_tcp_raw_timeout(int signal)
{
	tcp_raw_timeout(TRIGGER_TCP_RAW_TIMEOUT, trigger_tcp_raw_callback);
	alarm(TRIGGER_TCP_RAW_TIMEOUT);
}

void
trigger_rpc(int program, int proto, int port)
{
	struct trigger *t, tr;

	tr.num = program;

	t = (struct trigger *) bsearch(&tr, &rpc_triggers, rpc_cnt,
				       sizeof(tr), trigger_compare);
	if (t == NULL)
		return;

	if (proto == IPPROTO_UDP) {
		trigger_set_udp(port, t->decode->dc_name);
	}
	else if (proto == IPPROTO_TCP) {
		trigger_set_tcp(port, t->decode->dc_name);
	}
}
	
void
trigger_init_magic(char *filename)
{
	magic_init(filename);
}

void
trigger_init_list(char *list)
{
	char *name, *port, *proto = NULL;

	while ((name = strsep(&list, ",")) != NULL) {
		if ((port = strsep(&name, "/")) == NULL ||
		    (proto = strsep(&name, "=")) == NULL) {
			errx(1, "trigger_init_list: parse error");
		}
		trigger_set(proto, atoi(port), name);
	}
}

void
trigger_init_services(char *services)
{
	FILE *f;
	char *name, *port, *proto, line[1024];
	
	if ((f = fopen(services, "r")) == NULL)
		errx(1, "couldn't open %s", services);
	
	while (fgets(line, sizeof(line), f) != NULL) {
		if (line[0] == '#' || line[0] == '\n')
			continue;
		
		if ((name = strtok(line, " \t")) == NULL ||
		    (port = strtok(NULL, " \t/")) == NULL ||
		    (proto = strtok(NULL, " \t#\n")) == NULL) {
			continue;
		}
		trigger_set(proto, atoi(port), name);
	}
	fclose(f);
}

