/* LAT's Gabriel network probe (e.g., SATAN) detector.
 *
 * Module: Database of info on host records.
 * Author: Bob Baldwin.
 * Acknowledgements: Thanks to LLNL for the Courtney probe detector.
 */

#include "copyright.h"

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/time.h>
#ifndef BSDI2
#include <malloc.h>
#endif

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

/* Define this to enable debugging messages. */
#define HOSTREC_DEBUG



/* Describe the time a service was probed.
 * Multi-port services like X adn port scanning
 * are reduced to a single service by this abstraction.
 */
struct service_item 
{
	char	*service_name;	/* Symbolic or numberic. */
	time_t	last_probe_time;
};
typedef struct service_item service_item;
#define	SERVICE_X	"service_x"
#define SERVICE_SCAN	"service_port_scan"

char	*x_service_names[] = 
{
	"6000", "6001", "6002", 
	"6010", "6011", "6012",
	NULL
};

#define SCAN_MIN	610



/* The list of hosts we are tracking is held in
 * a single global list.
 * For now this is a doubly linked list that is
 * searched linearly.  It should be converted
 * to a balanced tree or other logN structure.
 */
List	hostrec_list = NULL;


/* Forward references. */
extern	char		*cannonical_service_name();
extern	service_item	*service_create();
extern	void		service_free();
extern	void		count_items();



/* Return a pointer to a string that might be a static area
 * that identifies the cannonical name for this service.
 *
 * For now, just convert port scanning and X-window probing.
 */
char *
cannonical_service_name(service_name)
    char	*service_name;
{
	char	**alternate_namep;
	char	*p;
	int	port;
	

	for (alternate_namep = x_service_names ;
	     *alternate_namep != NULL ;
	     alternate_namep++)
	{
		if (0 == strcasecmp(service_name, *alternate_namep))
			return(SERVICE_X);
	}

	/* Treat all other numeric ports above SCAN_MIN as a scan. */
	for (p = service_name ; *p != 0 ; p++)
	{	/* Return if the service contains non-digits. */
		if ((*p < '0') || ('9' < *p))
			return(service_name);
	}
	port = atoi(service_name);
	if (port > SCAN_MIN)
	{
		return(SERVICE_SCAN);
	}
	return(service_name);
}


/* Allocate and initialize a service item.
 */
service_item *
service_create(service_name, probe_time)
    char	*service_name;
    time_t	probe_time;
{
	service_item	*service;


	service = (service_item *)malloc(sizeof(service_item));
	service->service_name = strdup(cannonical_service_name(service_name));
	service->last_probe_time = probe_time;
	return(service);
}


/* Deallocate a service item.
 */
void
service_free(service)
    service_item	*service;
{
	free(service->service_name);
	free(service);
}


/* Callback routine that returns True 
 * if the last probe time for this service is
 * older than the given time.
 * If so, it also free's the service item.
 */
bool
service_free_old_probe(element, state)
    Obj		element;
    Obj		state;
{
	service_item	*service = (service_item *)element;
	time_t		stale_time = *((time_t *) state);
	

	if (service->last_probe_time < stale_time)
	{
		service_free(service);
		return True;
	}
	return False;
}


/* Callback routine that returns True if the service name matches.
 */
bool
service_name_equal(element, state)
    Obj		element;
    Obj		state;
{
	service_item	*service = (service_item *)element;
	char		*service_name = (char *) state;
	

	if (0 == strcasecmp(service->service_name,
			    service_name))
		return True;
	return False;
}


/* Callback routine to count the items in a list.
 */
void
count_items(element, counterp)
    Obj element;
    Obj counterp;
{
	int	count;


	count = *((int *) counterp);
	*((int *) counterp) = count + 1;
}


/* Allocate a host record from the heap
 * and initialize it.
 */
hostrec *
hostrec_create(host_name, service_name, probe_time)
    char	*host_name;
    char	*service_name;
    time_t	probe_time;
{
	hostrec		*hostrecp;
	List		service_list;
	service_item	*service_probe;
	char		*cannonical_name;
	

#ifdef HOSTREC_DEBUG
	(void)printf("Create host record(%s, %s, %ld)\n",
		     host_name, service_name, probe_time);
#endif
	hostrecp = (hostrec *) malloc(sizeof(hostrec));
	hostrecp->host_name = strdup(host_name);
	hostrecp->last_probe_time = probe_time;
	hostrecp->last_high_report_time = 0;
	hostrecp->last_low_report_time = 0;

	service_list = list_new();
	cannonical_name = cannonical_service_name(service_name);
	service_probe = service_create(cannonical_name, probe_time);
	list_push(service_list, service_probe);
	hostrecp->service_list = (Obj) service_list;
	
	return(hostrecp);
}


/* Deallocate a hostrec.
 */
void
hostrec_free(hostrecp)
    hostrec	*hostrecp;
{
	List		service_list;


	service_list = (List) hostrecp->service_list;
	list_free(service_list, service_free, NULL);
	free(hostrecp->host_name);
	free(hostrecp);
}


/* Return the count of different probe classes.
 */
int
hostrec_num_probes(hostrecp)
    hostrec	*hostrecp;
{
	int	num_probes = 0;
	
	
	list_traverse_fifo((List)hostrecp->service_list,
			   count_items, &num_probes);
	return(num_probes);
}


/* Add a host record to the database of host records.
 * Caller must make sure that the host is not already
 * in the list.
 */
void
hostrec_add(hostrecp)
    hostrec	*hostrecp;
{
	if (hostrec_list == NULL)
		hostrec_list = list_new();
	list_push(hostrec_list, (Obj) hostrecp);
}


/* Remove and deallocate a host record.
 */
void
hostrec_remove(hostrecp)
    hostrec	*hostrecp;

{
	if (hostrec_list == NULL)
		hostrec_list = list_new();
#ifdef HOSTREC_DEBUG
	(void)printf("Removing host %s\n",
		     hostrecp->host_name);
#endif
	hostrecp = (hostrec *) list_remove(hostrec_list, element_eq, hostrecp);
	hostrec_free(hostrecp);
}


/* Remove service probe records older than the given time.
 */
void
hostrec_remove_probes_older_than(hostrecp, stale_time)
    hostrec	*hostrecp;
    time_t	stale_time;
{
	(void)list_free_subset((List)hostrecp->service_list,
			       service_free_old_probe,
			       &stale_time);
}


/* Callback routine to locate a host record for
 * the given src_host. 
 */
bool
src_host_equal(element, state)
    Obj	element;
    Obj	state;
{
	hostrec	*hostrecp = (hostrec *) element;
	char	*src_host = (char *) state;
	

	if (0 == strcasecmp(hostrecp->host_name,
			    src_host))
	{
		return True;
	}
	return False;
}


/* Find a the hostrec for the given host
 * or return NULL.
 */
hostrec *
hostrec_lookup(src_host)
    char	*src_host;
{
	if (hostrec_list == NULL)
		hostrec_list = list_new();

	return(list_search(hostrec_list,
			   src_host_equal,
			   (Obj)src_host));
}


/* Update a host record that is already in the database.
 */
void
hostrec_update(hostrecp, service_name, probe_time)
    hostrec	*hostrecp;
    char	*service_name;
    time_t	probe_time;
{
	List		service_list;
	service_item	*service_probe;
	char		*cannonical_name;
	

	hostrecp->last_probe_time = probe_time;
	service_list = (List) hostrecp->service_list;
	cannonical_name = cannonical_service_name(service_name);
	service_probe = (service_item *)list_search(service_list,
						    service_name_equal,
						    cannonical_name);
	if (service_probe == NULL)
	{	/* Add a new service probe. */
#ifdef HOSTREC_DEBUG
		printf("new_probe('%s', '%s', %ld)\n",
		       hostrecp->host_name, cannonical_name, probe_time);
#endif	
		service_probe = service_create(cannonical_name, probe_time);
		list_push(service_list, service_probe);
		/* Make sure we check this record. */
		if (hostrec_num_probes(hostrecp) >= LOW_THRESHOLD)
		{
			last_check_time = 0;
		}
	} else
	{	/* Modify existing probe. */
		service_probe->last_probe_time = probe_time;
	}
}


/* Traverse the list of hostrecs invoking the
 * given function on each one with the given
 * state information.
 */
void
hostrec_traverse(probe_checker, state)
    void	(*probe_checker)();
    Obj		state;
{
	if (hostrec_list == NULL)
		hostrec_list = list_new();

	list_traverse_fifo(hostrec_list,
			   probe_checker,
			   state);
}
