/*
 * qstat
 * by Steve Jankowski
 * steve@activesw.com
 * http://www.activesw.com/people/steve/qstat.html
 *
 * Thanks to Per Hammer for the OS/2 patches (per@mindbend.demon.co.uk)
 * Thanks to John Ross Hunt for the OpenVMS Alpha patches (bigboote@ais.net)
 * Thanks to Scott MacFiggen for the quicksort code (smf@activesw.com)
 *
 * Inspired by QuakePing by Len Norton
 *
 * Copyright 1996,1997,1998,1999 by Steve Jankowski
 *
 * Licensed under the Artistic License, see LICENSE.txt for license terms
 */

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>

#include "qstat.h"
#include "debug.h"

#ifndef _WIN32
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <netdb.h>
#endif

#ifdef _WIN32
 #include <winsock.h>
#endif

#ifdef __hpux
	#define STATIC    static
#else
	#define STATIC
#endif

#ifndef INADDR_NONE
	#define INADDR_NONE    ~0
#endif

typedef struct _cache_entry {
	unsigned long ipaddr;
	char *hostname[5];
} cache_entry;

static cache_entry *hcache;
static int n_entry;
static int max_entry;
static char *last_filename;
static int n_changes;

static void write_file(FILE *file);
static cache_entry *init_entry(unsigned long ipaddr, char *hostname,
    cache_entry *known);
static cache_entry *find_entry(unsigned long ipaddr);
static void free_entry(cache_entry *entry);
static cache_entry *validate_entry(cache_entry *entry);
static cache_entry *find_host_entry(char *hostname);
static void add_hostname(cache_entry *entry, const char *hostname);

int
hcache_open(char *filename, int update)
{
	FILE *file;
	char line[500], ipstr[500], hostname[500];
	char *l;
	int line_no, end;
	unsigned long ip1, ip2, ip3, ip4, ipaddr;
	cache_entry *entry;

	file = fopen(filename, update ? "r+" : "r");
	if (file == NULL) {
		if (errno == ENOENT) {
			debug(2, "Creating new host cache \"%s\"\n", filename);
			last_filename = filename;
			return (0);
		}
		perror(filename);
		return (-1);
	}
	last_filename = filename;

	for (line_no = 1; fgets(line, sizeof(line), file) != NULL; line_no++) {
		if (strlen(line) < 2) {
			continue;
		}
		if (line[strlen(line) - 1] != '\n') {
			printf("%d: line too long\n", line_no);
			continue;
		}
		l = line;
		while (isspace((unsigned char)*l)) {
			l++;
		}
		if ((*l == '#') || (*l == '\0')) {
			continue;
		}
		if (sscanf(l, "%s%n", ipstr, &end) != 1) {
			printf("%d: parse error\n", line_no);
			continue;
		}
		if (sscanf(ipstr, "%lu.%lu.%lu.%lu", &ip1, &ip2, &ip3, &ip4) != 4) {
			init_entry(0, ipstr, NULL);
			continue;
		}

		if ((ip1 & 0xffffff00) || (ip2 & 0xffffff00) ||
		    (ip3 & 0xffffff00) || (ip4 & 0xffffff00)) {
			printf("%d: invalid IP address \"%s\"\n", line_no, ipstr);
			continue;
		}
		ipaddr = (ip1 << 24) | (ip2 << 16) | (ip3 << 8) | ip4;

		entry = init_entry(ipaddr, NULL, NULL);
		while (1) {
			l += end;
			while (isspace((unsigned char)*l)) {
				l++;
			}
			if ((*l == '#') || (*l == '\0')) {
				break;
			}
			hostname[0] = '\0';
			if (sscanf(l, "%s%n", hostname, &end) != 1) {
				printf("%d: parse error\n", line_no);
				continue;
			}
			init_entry(ipaddr, hostname, entry);
		}
	}
	fclose(file);
	return (0);
}


STATIC cache_entry *
init_entry(unsigned long ipaddr, char *hostname, cache_entry *known)
{
	cache_entry *entry;
	int e = 0, h;

	if (n_entry == max_entry) {
		if (max_entry == 0) {
			max_entry = 50;
			hcache = (cache_entry *)malloc(sizeof(cache_entry) * max_entry * 2);
		} else {
			hcache = (cache_entry *)realloc(hcache,
				sizeof(cache_entry) * max_entry * 2);
		}
		memset(hcache + n_entry, 0, sizeof(cache_entry) * max_entry *
		    (n_entry == 0 ? 2 : 1));
		max_entry *= 2;
	}

	if (ipaddr == 0) {
		entry = find_host_entry(hostname);
		if (entry == NULL) {
			hcache[n_entry].hostname[0] = strdup(hostname);
			return (&hcache[n_entry++]);
		}
		return (entry);
	}

	if (known != NULL) {
		entry = known;
	} else {
		for (e = 0; e < n_entry; e++) {
			if (hcache[e].ipaddr == ipaddr) {
				break;
			}
		}
		entry = &hcache[e];
		entry->ipaddr = ipaddr;
	}

	if (hostname && (hostname[0] != '\0')) {
		for (h = 0; h < 5; h++) {
			if (entry->hostname[h] == NULL) {
				entry->hostname[h] = strdup(hostname);
				break;
			}
		}
	}
	if (e == n_entry) {
		n_entry++;
	}
	return (entry);
}


STATIC cache_entry *
find_host_entry(char *hostname)
{
	cache_entry *entry = &hcache[0];
	char **ehost;
	int e, h;
	char first = *hostname;

	for (e = 0; e < n_entry; e++, entry++) {
		ehost = &entry->hostname[0];
		for (h = 0; h < 5; h++, ehost++) {
			if (*ehost && (first == **ehost) && (strcmp(hostname, *ehost) == 0)) {
				return (entry);
			}
		}
	}
	return (NULL);
}


void
hcache_write_file(char *filename)
{
	FILE *file;

	if (filename != NULL) {
		file = fopen(filename, "w");
	} else {
		file = stdout;
	}
	if (file == NULL) {
		perror(filename);
		return;
	}
	write_file(file);
}


void
hcache_update_file()
{
	FILE *file;

	if ((last_filename == NULL) || (n_changes == 0)) {
		return;
	}

	file = fopen(last_filename, "w");
	if (file == NULL) {
		perror(last_filename);
		return;
	}
	write_file(file);
}


STATIC void
write_file(FILE *file)
{
	int e, h;

	for (e = 0; e < n_entry; e++) {
		unsigned long ipaddr = hcache[e].ipaddr;
		if (ipaddr == 0) {
			continue;
		}
		fprintf(file, "%lu.%lu.%lu.%lu", (ipaddr & 0xff000000) >> 24,
		    (ipaddr & 0xff0000) >> 16, (ipaddr & 0xff00) >> 8, ipaddr & 0xff);
		if (hcache[e].hostname[0]) {
			for (h = 0; h < 5; h++) {
				if (hcache[e].hostname[h] != NULL) {
					fprintf(file, "%c%s", h ? ' ' : '\t', hcache[e].hostname[h]);
				}
			}
		}
		fprintf(file, "\n");
	}
	fclose(file);
}


void
hcache_invalidate()
{
	int e;

	for (e = 0; e < n_entry; e++) {
		if (hcache[e].ipaddr != 0) {
			memset(&hcache[e].hostname[0], 0, sizeof(hcache[e].hostname));
		}
	}
}


void
hcache_validate()
{
	int e;
	char **alias;
	struct hostent *ent;
	unsigned long ipaddr;
	cache_entry *entry;

	for (e = 0; e < n_entry; e++) {
		fprintf(stderr, "\r%d / %d  validating ", e, n_entry);
		if (hcache[e].ipaddr != 0) {
			ipaddr = hcache[e].ipaddr;
			fprintf(stderr, "%lu.%lu.%lu.%lu", (ipaddr & 0xff000000) >> 24,
			    (ipaddr & 0xff0000) >> 16, (ipaddr & 0xff00) >> 8, ipaddr & 0xff);
			ipaddr = htonl(ipaddr);
			ent = gethostbyaddr((char *)&ipaddr, sizeof(unsigned long),
				AF_INET);
		} else if (hcache[e].hostname[0] != NULL) {
			fprintf(stderr, "%s", hcache[e].hostname[0]);
			ent = gethostbyname(hcache[e].hostname[0]);
			if (ent != NULL) {
				memcpy(&ipaddr, ent->h_addr_list[0], sizeof(ipaddr));
				ipaddr = ntohl(ipaddr);
				if ((entry = find_entry(ipaddr)) != NULL) {
					add_hostname(entry, hcache[e].hostname[0]);
					free_entry(&hcache[e]);
				} else {
					hcache[e].ipaddr = ipaddr;
				}
			}
		} else {
			continue;
		}
		if (ent == NULL) {
			continue;
		}

		if (ent->h_name && (ent->h_name[0] != '\0')) {
			add_hostname(&hcache[e], ent->h_name);
		}
		printf("h_name %s\n", ent->h_name ? ent->h_name : "NULL");
		alias = ent->h_aliases;
		while (*alias) {
			add_hostname(&hcache[e], *alias);
			printf("h_aliases %s\n", *alias);
			alias++;
		}
	}
}


STATIC cache_entry *
validate_entry(cache_entry *entry)
{
	struct hostent *ent;
	char **alias;
	cache_entry *tmp;
	unsigned long ipaddr;

	if (entry->ipaddr != 0) {
		ipaddr = htonl(entry->ipaddr);

/*	    fprintf( stderr, "%u.%u.%u.%u", (ipaddr&0xff000000)>>24,
 *      (ipaddr&0xff0000)>>16, (ipaddr&0xff00)>>8, ipaddr&0xff);
 */
		ent = gethostbyaddr((char *)&ipaddr, sizeof(unsigned long), AF_INET);
	} else if (entry->hostname[0] != NULL) {
/*      fprintf( stderr, "%s", entry->hostname[0]);
 */
		ent = gethostbyname(entry->hostname[0]);
		if (ent != NULL) {
			memcpy(&ipaddr, ent->h_addr_list[0], sizeof(ipaddr));
			ipaddr = ntohl(ipaddr);
			if ((tmp = find_entry(ipaddr)) != NULL) {
				add_hostname(tmp, entry->hostname[0]);
				free_entry(entry);
				entry = tmp;
			} else {
				entry->ipaddr = ipaddr;
			}
		}
	} else {
		return (NULL);
	}

	if (ent == NULL) {
		return (NULL);
	}

	if (ent->h_name && (ent->h_name[0] != '\0')) {
		add_hostname(entry, ent->h_name);
	}
	alias = ent->h_aliases;
	while (*alias) {
		add_hostname(entry, *alias);
		alias++;
	}
	return (entry);
}


unsigned long
hcache_lookup_hostname(char *hostname)
{
	cache_entry *entry;
	int e, h;

	debug(1, "looking up %s\n", hostname);
	for (e = 0; e < n_entry; e++) {
		for (h = 0; h < 5; h++) {
			if (hcache[e].hostname[h] &&
			    (strcmp(hostname, hcache[e].hostname[h]) == 0)) {
				return (hcache[e].ipaddr);
			}
		}
	}
	entry = init_entry(0, hostname, NULL);
	if (entry->ipaddr == 0) {
		debug(2, "validating %s\n", hostname);
		entry = validate_entry(entry);
		n_changes++;
	}
	if ((entry != NULL) && entry->ipaddr) {
		debug(2, "returning %lx\n", entry->ipaddr);
		return (entry->ipaddr);
	}

	return (INADDR_NONE);
}


char *
hcache_lookup_ipaddr(unsigned long ipaddr)
{
	cache_entry *entry;
	int e;

	for (e = 0; e < n_entry; e++) {
		if (hcache[e].ipaddr == ipaddr) {
			return (hcache[e].hostname[0]);
		}
	}
	entry = init_entry(ipaddr, 0, NULL);
	debug(1, "validating %lx\n", ipaddr);
	validate_entry(entry);
	n_changes++;
	return (entry ? entry->hostname[0] : NULL);
}


STATIC cache_entry *
find_entry(unsigned long ipaddr)
{
	int e;

	for (e = 0; e < n_entry; e++) {
		if (hcache[e].ipaddr == ipaddr) {
			return (&hcache[e]);
		}
	}
	return (NULL);
}


STATIC void
free_entry(cache_entry *entry)
{
	int h;

	for (h = 0; h < 5; h++) {
		if (entry->hostname[h] != NULL) {
			free(entry->hostname[h]);
		}
	}
	memset(entry, 0, sizeof(*entry));
}


STATIC void
add_hostname(cache_entry *entry, const char *hostname)
{
	int h;

	for (h = 0; h < 5; h++) {
		if (entry->hostname[h] == NULL) {
			break;
		}
		if (strcmp(entry->hostname[h], hostname) == 0) {
			return;
		}
	}
	if (h < 5) {
		entry->hostname[h] = strdup(hostname);
	}
}


/*
 * main(int argc, char *argv[])
 * {
 *  hcache_open( argv[1], 0);
 *  hcache_write(NULL);
 *
 *  hcache_invalidate();
 * printf( "invalidate\n");
 *  hcache_write(NULL);
 *  hcache_validate();
 * printf( "validate\n");
 *  hcache_write( "/tmp/qhcache.out");
 * }
 *
 */
