/* LAT's Gabriel network probe (e.g., SATAN) detector.
 *
 * Module: Top level of client probe monitor.
 * Author: Dr. Robert W. Baldwin.
 */

/* To Do:
 * zzz Due to buffering in the pipe, it can take
 *     some time before an attack is noticed.
 * zzz CHECK_INTERVAL should be able to be arg.
 * zzz The network device name should be a parameter
 *     so things work right on hosts with two interfaces.
 *
 * added hacks for BSDI 2.0 (using TCPDUMP and friends)
 * Dennis S. Breckenridge dennis@nebulus.net
 */

#include "copyright.h"

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/time.h>
#include <syslog.h>

#include "gabriel_client.h"
#include "hostrec.h"


/* Define this to enable debugging messages. */
#define GABRIEL_CLIENT_DEBUG
/* #define GABRIEL_PROBE_DEBUG */


/* Forward references. */
extern	void	do_solaris1();
extern	void	parse_sol1_pkt();
extern	void	do_solaris2();
extern	void	parse_sol2_pkt();
extern	void	do_bsdi();
extern	void	parse_bsdi_pkt();
extern	void	record_probe();
extern	void	check_threshold();
extern	void	probe_checker();
extern	void	log_probing();
extern	void	gabriel_gethostname();


char	*program_name;
hostrec	*myhostrecp;
char	hostname[MAXHOSTNAMELEN+1];
time_t	last_check_time = 0;
time_t	last_heartbeat_time = 0;


/* Top-level of the Gabriel network probe detector.
 * Use netfind or snoop to gather a signature for
 * a network prober like SATAN.
 */
int
main(argc, argv)
    int		argc;
    char	*argv[];
{
	char	*suffix;


	/* Set the program name. */
        program_name = strrchr(argv[0], '/');
	if (program_name == NULL)
		program_name = argv[0];
	else
		program_name++;

	/* Must be root to run this. */
	if (getuid() != 0)
	{
		(void)printf("You must be root to run %s\n",
			     program_name);
		exit(1);
	}

	/* Setup information for a heatbeat. */
	gabriel_gethostname(hostname, MAXHOSTNAMELEN);
	myhostrecp = hostrec_create(hostname, GABRIEL_IDENT, 0);

	/* Figure out if we are running solaris 1 or 2. or BSDI 2.0 */
	suffix = strrchr(program_name, '.');
	if (suffix == NULL)
	{
		(void)printf("%s: Missing program suffix.\n",
			     program_name);
		exit(1);
	}

	fclose(stderr);
	if (0 == strcmp(suffix, ".sol1"))
	{
		while (1)
		{
			do_solaris1(argc, argv);
		}
	} else if (0 == strcmp(suffix, ".sol2"))
	{
		while (1)
		{
			do_solaris2(argc, argv);
		}
	} else if (0 == strcmp(suffix, ".bsdi"))
	{
		while (1)
		{
			do_bsdi(argc, argv);
		}
	} else 
	{
		(void)printf("%s: Unacceptable program suffix.\n",
			     program_name);
		exit(1);
	}
	return(0);
}


/* Look for network probe signature on Solaris 1.
 */
void
do_solaris1(argc, argv)
    int		argc;
    char	*argv[];
{
	FILE	*pkt_filter;
	char	linebuf[LINEBUF_SIZE];
	char	*src_host, *service; /* Ptrs within linebuf. */
	

	/* Keep lint happy. */
	(void) argc;
	(void) argv;

	pkt_filter = popen(SOL1_POPEN_ARG, "r");
	if (pkt_filter == NULL)
	{
		(void)printf("%s: Cannot spawn packet filter.\n",
			     program_name);
		perror("/usr/etc/etherfind");
		exit(1);
	}
	
	while (1)
	{
		/* Due to buffering on the pipe, packet info */	
		/* will come through in bursts. */
		if (NULL == fgets(linebuf, LINEBUF_SIZE, pkt_filter))
		{
			(void)printf("%s: The packet filter terminated.\n",
				     program_name);
			pclose(pkt_filter);
			return;
		}
		parse_sol1_pkt(linebuf, &src_host, &service);
		record_probe(src_host, service);
		check_threshold();
	}
}

/* Look for network probe signature on BSDI 2.0
 */
void
do_bsdi(argc, argv)
    int		argc;
    char	*argv[];
{
	FILE	*pkt_filter;
	char	linebuf[LINEBUF_SIZE];
	char	cmdbuf[LINEBUF_SIZE];
	char	*src_host, *service; /* Ptrs within linebuf. */
	

	/* Keep lint happy. */
	(void) argc;
	(void) argv;

	strcpy(linebuf, BSD_ATTACK_INTERFACE);

	if(argc > 2) {
		(void)printf("Usage: %s [interface]\n", argv[0]);
		(void)printf("Where [interface] is the tcpdump interface.\n");
		(void)printf("The packet filter terminated.\n");
		exit (1);
	}
	if(argc > 1) {
		(void)strcpy(linebuf, argv[1]);
	}
	sprintf(cmdbuf, "%s %s %s", BSDI_POPEN_ARG1, linebuf, BSDI_POPEN_ARG2);
	pkt_filter = popen(cmdbuf, "r");
	if (pkt_filter == NULL)
	{
		(void)printf("%s: Cannot spawn packet filter.\n",
			     program_name);
		perror("/usr/sbin/tcpdump");
		exit(1);
	}
	
	while (1)
	{
		/* Due to buffering on the pipe, packet info */	
		/* will come through in bursts. */
		if (NULL == fgets(linebuf, LINEBUF_SIZE, pkt_filter))
		{
			(void)printf("%s: The packet filter terminated.\n",
				     program_name);
			pclose(pkt_filter);
			return;
		}
		parse_bsdi_pkt(linebuf, &src_host, &service);
		record_probe(src_host, service);
		check_threshold();
	}
}


/* Look for network probe signature on Solaris 2.
 */
void
do_solaris2(argc, argv)
    int		argc;
    char	*argv[];
{
	FILE	*pkt_filter;
	char	linebuf[LINEBUF_SIZE];
	char	*src_host, *service; /* Ptrs within linebuf. */
	

	/* Keep lint happy. */
	(void) argc;
	(void) argv;

	pkt_filter = popen(SOL2_POPEN_ARG, "r");
	if (pkt_filter == NULL)
	{
		(void)printf("%s: Cannot spawn packet filter.\n",
			     program_name);
		perror("/usr/etc/etherfind");
		exit(1);
	}
	
	while (1)
	{
		if (NULL == fgets(linebuf, LINEBUF_SIZE, pkt_filter))
		{
			(void)printf("%s: The packet filter terminated.\n",
				     program_name);
			pclose(pkt_filter);
			return;
		}
		parse_sol2_pkt(linebuf, &src_host, &service);
		record_probe(src_host, service);
		check_threshold();
	}
}


/* Parse a packet filter line to extract
 * the source host and service being probed.
 * For now we do not care about the destination host.
 * The linebuf is modified by adding nulls and
 * the src_hostp and servicep pointers are set to
 * the appropriate places within linebuf.
 * The results might be symbolic ("telnet") or
 * numeric ("23") ascii strings.
 * If the line is inappropriate, src_hostp is set to NULL.
 * ICMP from nebulus.net to skyros.ucs.ubc.ca echo 64 data bytes
 * ICMP from nebulus.net to skyros.ucs.ubc.ca echo 64 data bytes
 * TCP from nebulus.net.1873 to skyros.ucs.ubc.ca.tcpmux seq 29AC714B, SYN,  window 8192, <mss 1460> <3> <3> 
 * TCP from nebulus.net.1874 to skyros.ucs.ubc.ca.11000 seq 2B0EB12D, SYN,  window 8192, <mss 1460> <3> <3> 
 */
void
parse_sol1_pkt(linebuf, src_hostp, servicep)
    char	*linebuf;
    char	**src_hostp;	/* Returned */
    char	**servicep;	/* Returned */
{
	char	*host;
	char	*p, *rest;
	int	i;
	

	/* Handle bogus lines. */
	*src_hostp = "";
	*servicep = "";
	if ((linebuf[0] == 0) ||
	    (  (0 != strncmp(linebuf, SOL1_START1, sizeof(SOL1_START1)-1))
	     &&(0 != strncmp(linebuf, SOL1_START2, sizeof(SOL1_START2)-1))
	     &&(0 != strncmp(linebuf, SOL1_START3, sizeof(SOL1_START3)-1))))
	{
		return;
	}
	/* Host name is the first part of the third word.
	 * This would all be easy to do in perl, but one of
	 * the design rules is to avoid perl.
	 */
	for (host = linebuf, i = 2 ; i > 0 ; i--)
	{
		for ( ; *host != ' ' ; host++)
		{
			if (*host == 0)
				return;
		}
		host++;
	}

	/* Strip the source port number from host name.
	 * ICMP packets do not have source port numbers.
	 */
	/* Advance p to the end of the host field. */
	for (p = host ; *p != ' ' ; p++)
	{
		if (*p == 0)
			return;
	}
	rest = &(p[1]);
	if (0 != strncmp(linebuf, "ICMP", 4))
	{	/* Skip back to the last dot and change it to a null. */
		for ( ; *p != '.' ; p--)
		{
			if (*p == 0)
				return;
		}
	}
	*p++ = 0;

	if (0 == strncmp(linebuf, "ICMP", 4))
	{
		p = SERVICE_PING;
	} else
	{
		/* The service is last part of the fifth word.
		 */
		/* Advance p over two more spaces. */
		p = rest;
		for (i = 2 ; i > 0 ; i--)
		{
			for ( ; *p != ' ' ; p++)
			{
				if (*p == 0)
					return;
			}
			p++;
		}
		*--p = 0;		/* Terminate the service. */
		/* Backup to just after the last dot. */
		for ( p-- ; *p != '.' ; p--)
		{
			if (*p == 0)
				return;
		}
		p++;
	}
	
	*src_hostp = host;
	*servicep = p;
}


/* Parse a packet filter line to extract
 * the source host and service being probed.
 * For now we do not care about the destination host.
 * The linebuf is modified by adding nulls and
 * the src_hostp and servicep pointers are set to
 * the appropriate places within linebuf.
 * The results might be symbolic ("telnet") or
 * numeric ("23") ascii strings.
 * If the line is inappropriate, src_hostp is set to NULL.
 *
 */

void
parse_bsdi_pkt(linebuf, src_hostp, servicep)
    char	*linebuf;
    char	**src_hostp;	/* Returned */
    char	**servicep;	/* Returned */
{
	char	*host, *host_end;
	char	*service;
	char	*p, *rest;
	int	i;
	

	/* Handle bogus lines. */
	*src_hostp = "";
	*servicep = "";

	host_end = strstr(linebuf, BSDI_PATTERN);

	if ((linebuf[0] == 0) ||
	    (host_end == NULL))
	{
		return;
	}

	/* Advance p to the end of the host field. */
	host = linebuf;

	for (p = host ; *p != ' ' ; p++)
	{
		if (*p == 0)
			return;
	}
	rest = &(p[1]);
	if(strstr(rest, "icmp:") == NULL) 
	{	/* Skip back to the last dot and change it to a null. */
		for (; *p != '.' ; p--)
		{
			if (*p == 0)
				return;
		}
	}
	*p++ = 0;

	/* Advance to the packet type information.
	 * Skip over pattern, non-spaces, and then spaces.
	 */
	for (p = &(host_end[strlen(BSDI_PATTERN)]) ; *p != ' ' ; p++)
	{
		if (*p == 0)
			return;
	}
	
	
	/* Advance p to the end of the service field. */

	for (; *p != ' ' ; p++)
	{
		if (*p == 0)
			return;
	}

	if(strstr(p, "icmp:") != NULL) 
	{
		p = SERVICE_PING;
	} else {
		/* Advance p over three more spaces. */
		p = rest;
		for (i = 2 ; i > 0 ; i--)
		{
			for ( ; *p != ' ' ; p++)
			{
				if (*p == 0)
					return;
			}
			p++;
		}
		p--;			/* back up over the colon */
		*--p = 0;		/* Terminate the service. */
		/* Backup to just after the last dot. */
		for ( p-- ; *p != '.' ; p--)
		{
			if (*p == 0)
				return;
		}
		p++;
	}

	*src_hostp = host;
	*servicep = p;
}



/* Parse a packet filter line to extract
 * the source host and service being probed.
 * For now we do not care about the destination host.
 * The linebuf is modified by adding nulls and
 * the src_hostp and servicep pointers are set to
 * the appropriate places within linebuf.
 * The results might be symbolic ("telnet") or
 * numeric ("23") ascii strings.
 * If the line is inappropriate, src_hostp is set to NULL.
 */
void
parse_sol2_pkt(linebuf, src_hostp, servicep)
    char	*linebuf;
    char	**src_hostp;	/* Returned */
    char	**servicep;	/* Returned */
{
	char	*host, *host_end;
	char	*service;
	char	*p;
	int	i;
	

	/* Handle bogus lines. */
	*src_hostp = "";
	*servicep = "";
	host_end = strstr(linebuf, SOL2_PATTERN);
	if ((linebuf[0] == 0) ||
	    (host_end == NULL))
	{
		return;
	}
	/* Source host name is the word before the SOL2_PATTERN.
	 * Backup to white space.
	 */
	*host_end = 0;	/* Terminate and backup. */
	p = host_end;
	/* host_end now points to last letter of host name. */
	for (p-- ; *p != ' ' ; p--)
	{
		if (*p == 0)
			return;
	}
	host = ++p;		/* Mark begining of host_name. */

	/* Advance to the packet type information.
	 * Skip over pattern, non-spaces, and then spaces.
	 */
	for (p = &(host_end[strlen(SOL2_PATTERN)]) ; *p != ' ' ; p++)
	{
		if (*p == 0)
			return;
	}
	for ( ; (*p == ' ') || (*p == '\t') ; p++)
	{
		if (*p == 0)
			return;
	}

	/* In general, the service is the next word, but
	 * TCP and UDP services that are not named need
	 * to be replaced with their port numbers.
	 */
	/* Advance p to the end of the service field. */
	for (service = p ; *p != ' ' ; p++)
	{
		if (*p == 0)
			return;
	}
	*p++ = 0;
	/* If the next word is "D=nnn", then use nnn as the service name. */
	if (0 == strncmp(p, "D=", 2))
	{
		p = &(p[2]);
		for (service = p ; *p != ' ' ; p++)
		{
			if (*p == 0)
				return;
		}
		*p++ = 0;
	}

	*src_hostp = host;
	*servicep = service;
}


/* Record a probe in the database. 
 * For each source host track the most recent event
 * of any kind and track the most recent event of each kind.
 *
 * For simplicities sake we use the current time to
 * mark each event even though it may have happened
 * in the past due to buffering delays in the pipe.
 */
void
record_probe(src_host, service)
    char	*src_host;
    char	*service;
{
	time_t	now;
	hostrec	*hostrecp;
	

	if ((src_host == NULL) || (*src_host == 0))
	{
#ifdef GABRIEL_PROBE_DEBUG
		(void)printf("Pkt info line skipped.\n");
#endif
		return;
	}
	
#ifdef GABRIEL_PROBE_DEBUG
	(void)printf("record_probe from '%s' of '%s'\n",
		     src_host, service);
#endif
	
	now = time((time_t *)NULL);
	hostrecp = hostrec_lookup(src_host);
	if (hostrecp == NULL)
	{
		/* If none, add the record and we're done. */
		hostrecp = hostrec_create(src_host, service, now);
		hostrec_add(hostrecp);
		return;
	}
	/* Update host record. */
	hostrec_update(hostrecp, service, now);
}


/* Check the probe thresholds.
 * Periodically check the host records for violations.
 * The real work is done in probe_checker().
 */
void
check_threshold()
{
	time_t		now;
    

	/* Skip the check if we did one recently. */
	now = time((time_t *)NULL);
	if (last_heartbeat_time < (now - HEARTBEAT_INTERVAL))
	{
		last_heartbeat_time = now;
		log_probing(myhostrecp, 0);
	}
	if (last_check_time > (now - CHECK_INTERVAL))
	{
		return;
	}
	last_check_time = now;
	
	hostrec_traverse(probe_checker, (void *) &now);
}


/* This routine is passed each host record and
 * it reports excessive probing and it removes
 * inactive host records.
 */
void
probe_checker(hostrecp, state)
    hostrec	*hostrecp;
    void	*state;
{
	time_t	now;
	int	num_probes;
	

	/* Remove and skip inactive host records. */
	now = *((time_t *) state);
	if (hostrecp->last_probe_time < (now - INACTIVE_INTERVAL))
	{
		hostrec_remove(hostrecp);
		return;
	}

	/* Remove stale probes. */
	hostrec_remove_probes_older_than(hostrecp, (now - INACTIVE_INTERVAL));

	/* Check the number of probes. */
	num_probes = hostrec_num_probes(hostrecp);
#ifdef GABRIEL_CLIENT_DEBUG
	(void)printf("Check finds %d probe%s from '%s'\n",
		     num_probes, (num_probes == 1) ? "" : "s",
		     hostrecp->host_name);
#endif
	if (num_probes >= LOW_THRESHOLD)
	{
		log_probing(hostrecp, num_probes);
	}
}


/* Log excessive probing.
 * If the num_probes is zero, this routine
 * sends a heartbeat message.
 */
void
log_probing(hostrecp, num_probes)
    hostrec	*hostrecp;
    int		num_probes;
{
	int	level, priority;
	time_t	now;
	bool	sendlog;
	
	
	sendlog = False;
	now = time((time_t) NULL);
	if (num_probes >= HIGH_THRESHOLD)
	{
		level = 3;
		priority = LOG_NOTICE;
		if (hostrecp->last_high_report_time <
		    (now - REPORT_INTERVAL))
		{
			hostrecp->last_high_report_time = now;
			sendlog = True;
		}
	} else if (num_probes >= LOW_THRESHOLD)
	{
		level = 2;
		priority = LOG_NOTICE;
		if (hostrecp->last_low_report_time <
		    (now - REPORT_INTERVAL))
		{
			hostrecp->last_low_report_time = now;
			sendlog = True;
		}
	} else  if (num_probes == 0)
	{
		level = 1;
		priority = LOG_INFO;
		sendlog = True;
	}
	if (sendlog)
	{
#ifdef GABRIEL_CLIENT_DEBUG
		(void)printf("Logging: %d %s\n",
			     level, hostrecp->host_name);
#endif
		openlog(GABRIEL_IDENT, GABRIEL_LOG_OPTS, GABRIEL_FACILITY);
		syslog(priority, "%d %s",
		       level, hostrecp->host_name);
		closelog();
	}
}

    
/*
 * A wrapper that sets hostname to the name of the
 * machine.
 */
void
gabriel_gethostname(hostname, hostname_len)
    char       *hostname;
    int		hostname_len;
{
	
#ifdef __sol1__
	(void) gethostname(hostname, hostname_len);
#endif /* __sol1__ */

#ifdef BSDI2
	(void) gethostname(hostname, hostname_len);
#endif /* BSDI2 */


#ifdef __sol2__
#include <sys/utsname.h>

	struct utsname *name;

	name = ( struct utsname *)calloc(1,sizeof(struct  utsname));

        if (!uname(name)) {
        	strcpy(hostname, "UNKNOWN");
        }

        strcpy(hostname, name->nodename);

        free(name);
#endif /* __sol2__ */
}
