/*
 * (C)opyright 1995-1998 Darren Reed.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that this notice is preserved and due credit is given
 * to the original author and the contributors.
 *
 *   The author of this software makes no garuntee about the
 * performance of this package or its suitability to fulfill any purpose.
 *
 */
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <stdlib.h>
#include <netdb.h>
#include <string.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/ip_icmp.h>
#ifndef	linux
#include <netinet/ip_var.h>
#include <netinet/tcpip.h>
#endif
#include "ip_compat.h"
#ifdef	linux
#include <linux/sockios.h>
#include "tcpip.h"
#endif
#include "ipsd.h"

#ifndef	lint
static const char sccsid[] = "@(#)ipsd.c	1.3 12/3/95 (C)1995 Darren Reed";
static const char rcsid[] = "@(#)$Id: ipsd.c,v 2.0.2.4.2.1 1998/11/22 01:51:08 darrenr Exp $";
#endif

extern	char	*optarg;
extern	int	optind;

#ifdef	linux
char	default_device[] = "eth0";
#else
# ifdef	sun
char	default_device[] = "le0";
# else
#  ifdef	ultrix
char	default_device[] = "ln0";
#  else
char	default_device[] = "lan0";
#  endif
# endif
#endif

#define	NPORTS	21

u_short	defports[NPORTS] = {
		  7,   9,  20,  21,  23,  25,  53,  69,  79, 111,
		123, 161, 162, 512, 513, 514, 515, 520, 540, 6000, 0
	};

ipsd_t	*iphits[NPORTS];
int	writes = 0;


int	ipcmp(sh1, sh2)
sdhit_t	*sh1, *sh2;
{
	return sh1->sh_ip.s_addr - sh2->sh_ip.s_addr;
}


/*
 * Check to see if we've already received a packet from this host for this
 * port.
 */
int	findhit(ihp, src, dport)
ipsd_t	*ihp;
struct	in_addr	src;
u_short	dport;
{
	int	i, j, k;
	sdhit_t	*sh;

	sh = NULL;

	if (ihp->sd_sz == 4) {
		for (i = 0, sh = ihp->sd_hit; i < ihp->sd_cnt; i++, sh++)
			if (src.s_addr == sh->sh_ip.s_addr)
				return 1;
	} else {
		for (i = ihp->sd_cnt / 2, j = (i / 2) - 1; j >= 0; j--) {
			k = ihp->sd_hit[i].sh_ip.s_addr - src.s_addr;
			if (!k)
				return 1;
			else if (k < 0)
				i -= j;
			else
				i += j;
		}
	}
	return 0;
}


/*
 * Search for port number amongst the sorted array of targets we're
 * interested in.
 */
int	detect(ip, tcp)
ip_t	*ip;
tcphdr_t	*tcp;
{
	ipsd_t	*ihp;
	sdhit_t	*sh;
	int	i, j, k;

	for (i = 10, j = 4; j >= 0; j--) {
		k = tcp->th_dport - defports[i];
		if (!k) {
			ihp = iphits[i];
			if (findhit(ihp, ip->ip_src, tcp->th_dport))
				return 0;
			sh = ihp->sd_hit + ihp->sd_cnt;
			sh->sh_date = time(NULL);
			sh->sh_ip.s_addr = ip->ip_src.s_addr;
			if (++ihp->sd_cnt == ihp->sd_sz)
			{
				ihp->sd_sz += 8;
				sh = realloc(sh, ihp->sd_sz * sizeof(*sh));
				ihp->sd_hit = sh;
			}
			qsort(sh, ihp->sd_cnt, sizeof(*sh), ipcmp);
			return 0;
		}
		if (k < 0)
			i -= j;
		else
			i += j;
	}
	return -1;
}


/*
 * Allocate initial storage for hosts
 */
setuphits()
{
	int	i;

	for (i = 0; i < NPORTS; i++) {
		if (iphits[i]) {
			if (iphits[i]->sd_hit)
				free(iphits[i]->sd_hit);
			free(iphits[i]);
		}
		iphits[i] = (ipsd_t *)malloc(sizeof(ipsd_t));
		iphits[i]->sd_port = defports[i];
		iphits[i]->sd_cnt = 0;
		iphits[i]->sd_sz = 4;
		iphits[i]->sd_hit = (sdhit_t *)malloc(sizeof(sdhit_t) * 4);
	}
}


/*
 * cleanup exits
 */
waiter()
{
	wait(0);
}


/*
 * Write statistics out to a file
 */
writestats(nwrites)
int	nwrites;
{
	ipsd_t	**ipsd, *ips;
	char	fname[32];
	int	i, fd;

	(void) sprintf(fname, "/var/log/ipsd/ipsd-hits.%d", nwrites);
	fd = open(fname, O_RDWR|O_CREAT|O_TRUNC|O_EXCL, 0644);
	for (i = 0, ipsd = iphits; i < NPORTS; i++, ipsd++) {
		ips = *ipsd;
		if (ips->sd_cnt) {
			write(fd, ips, sizeof(ipsd_t));
			write(fd, ips->sd_hit, sizeof(sdhit_t) * ips->sd_sz);
		}
	}
	(void) close(fd);
	exit(0);
}


void writenow()
{
	signal(SIGCHLD, waiter);
	switch (fork())
	{
	case 0 :
		writestats(writes);
		exit(0);
	case -1 :
		perror("vfork");
		break;
	default :
		writes++;
		setuphits();
		break;
	}
}


void	usage(prog)
char	*prog;
{
	fprintf(stderr, "Usage: %s [-d device]\n", prog);
	exit(1);
}


void detecthits(fd, writecount)
int fd, writecount;
{
	struct	in_addr	ip;
	int	hits = 0;

	while (1) {
		hits += readloop(fd, ip);
		if (hits > writecount) {
			writenow();
			hits = 0;
		}
	}
}


main(argc, argv)
int	argc;
char	*argv[];
{
	char	*name =  argv[0], *dev = NULL;
	int	fd, writeafter = 10000, angelic = 0, c;

	while ((c = getopt(argc, argv, "ad:n:")) != -1)
		switch (c)
		{
		case 'a' :
			angelic = 1;
			break;
		case 'd' :
			dev = optarg;
			break;
		case 'n' :
			writeafter = atoi(optarg);
			break;
		default :
			fprintf(stderr, "Unknown option \"%c\"\n", c);
			usage(name);
		}

	bzero(iphits, sizeof(iphits));
	setuphits();

	if (!dev)
		dev = default_device;
	printf("Device:  %s\n", dev);
	fd = initdevice(dev, 60);

	if (!angelic) {
		switch (fork())
		{
		case 0 :
			(void) close(0);
			(void) close(1);
			(void) close(2);
			(void) setpgrp(0, getpgrp());
			(void) setsid();
			break;
		case -1:
			perror("fork");
			exit(-1);
		default:
			exit(0);
		}
	}
	signal(SIGUSR1, writenow);
	detecthits(fd, writeafter);
}
