/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <net/if.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <netinet/ip.h>
#include <netdb.h>
#include <string.h>
#include <signal.h>
#include <setjmp.h>
#include <arpa/inet.h>
#include "snoop.h"

static sigjmp_buf nisjmp;

#define	MAXHASH 1024  /* must be a power of 2 */

#define	SEPARATORS " \t\n"

struct hostdata {
	struct hostdata	*h_next;
	char		*h_hostname;
	int		h_pktsout;
	int		h_pktsin;
};

struct hostdata4 {
	struct hostdata4	*h4_next;
	char		*h4_hostname;
	int		h4_pktsout;
	int		h4_pktsin;
	struct in_addr	h4_addr;
};

struct hostdata6 {
	struct hostdata6	*h6_next;
	char		*h6_hostname;
	int		h6_pktsout;
	int		h6_pktsin;
	struct in6_addr	h6_addr;
};

static struct hostdata *addhost(int, const void *, const char *, char **);

static struct hostdata4 *h_table4[MAXHASH];
static struct hostdata6 *h_table6[MAXHASH];

#define	iphash(e)  ((e) & (MAXHASH-1))

/* ARGSUSED */
static void
wakeup(int n)
{
	siglongjmp(nisjmp, 1);
}

extern char *inet_ntoa();

static struct hostdata *
iplookup(struct in_addr ipaddr)
{
	register struct hostdata4 *h;
	struct hostent *hp = NULL;
	struct netent *np;
	int error_num;
	struct hostdata *retval;

	for (h = h_table4[iphash(ipaddr.s_addr)]; h; h = h->h4_next) {
		if (h->h4_addr.s_addr == ipaddr.s_addr)
			return ((struct hostdata *)h);
	}

	/* not found.  Put it in */

	if (ipaddr.s_addr == htonl(INADDR_BROADCAST))
		return (addhost(AF_INET, &ipaddr, "BROADCAST", NULL));
	if (ipaddr.s_addr == htonl(INADDR_ANY))
		return (addhost(AF_INET, &ipaddr, "OLD-BROADCAST", NULL));

	/*
	 * Set an alarm here so we don't get held up by
	 * an unresponsive name server.
	 * Give it 3 sec to do its work.
	 */
	if (! rflg && sigsetjmp(nisjmp, 1) == 0) {
		(void) snoop_alarm(3, wakeup);
		hp = getipnodebyaddr((char *)&ipaddr, sizeof (int),
		    AF_INET, &error_num);
		if (hp == NULL && inet_lnaof(ipaddr) == 0) {
			np = getnetbyaddr(inet_netof(ipaddr), AF_INET);
			if (np)
				return (addhost(AF_INET, &ipaddr, np->n_name,
				    np->n_aliases));
		}
		(void) snoop_alarm(0, wakeup);
	}

	retval = addhost(AF_INET, &ipaddr,
	    hp ? hp->h_name : inet_ntoa(ipaddr),
	    hp ? hp->h_aliases : NULL);
	if (hp != NULL)
		freehostent(hp);
	return (retval);
}

static struct hostdata *
ip6lookup(const struct in6_addr *ip6addr)
{
	struct hostdata6 *h;
	struct hostent *hp = NULL;
	int error_num;
	char addrstr[INET6_ADDRSTRLEN];
	char *addname;
	struct hostdata *retval;

	for (h = h_table6[iphash(((uint32_t *)ip6addr)[3])]; h;
	    h = h->h6_next) {
		if (IN6_ARE_ADDR_EQUAL(&h->h6_addr, ip6addr))
			return ((struct hostdata *)h);
	}

	/* not in the hash table, put it in */
	if (IN6_IS_ADDR_UNSPECIFIED(ip6addr))
		return (addhost(AF_INET6, ip6addr, "UNSPECIFIED", NULL));

	/*
	 * Set an alarm here so we don't get held up by
	 * an unresponsive name server.
	 * Give it 3 sec to do its work.
	 */
	if (! rflg && sigsetjmp(nisjmp, 1) == 0) {
		(void) snoop_alarm(3, wakeup);
		hp = getipnodebyaddr(ip6addr, sizeof (struct in6_addr),
		    AF_INET6, &error_num);
		(void) snoop_alarm(0, wakeup);
	} else {
		hp = NULL;
	}

	if (hp != NULL)
		addname = hp->h_name;
	else {
		(void) inet_ntop(AF_INET6, ip6addr, addrstr, INET6_ADDRSTRLEN);
		addname = addrstr;
	}

	retval = addhost(AF_INET6, ip6addr, addname, hp ? hp->h_aliases : NULL);
	if (hp != NULL)
		freehostent(hp);
	return (retval);
}

static struct hostdata *
addhost(int family, const void *ipaddr, const char *name, char **aliases)
{
	struct hostdata **hp, *n = NULL;
	extern FILE *namefile;
	int hashval;
	static char aname[128];
	char *np;
	static struct hostdata h;
	int ind;

	switch (family) {
	case AF_INET:
		n = (struct hostdata *)malloc(sizeof (struct hostdata4));
		if (n == NULL)
			goto alloc_failed;

		memset(n, 0, sizeof (struct hostdata4));
		n->h_hostname = strdup(name);
		if (n->h_hostname == NULL)
			goto alloc_failed;

		((struct hostdata4 *)n)->h4_addr =
		    *(const struct in_addr *)ipaddr;
		hashval = ((struct in_addr *)ipaddr)->s_addr;
		hp = (struct hostdata **)&h_table4[iphash(hashval)];
		break;
	case AF_INET6:
		n = (struct hostdata *)malloc(sizeof (struct hostdata6));
		if (n == NULL)
			goto alloc_failed;

		memset(n, 0, sizeof (struct hostdata6));
		n->h_hostname = strdup(name);
		if (n->h_hostname == NULL)
			goto alloc_failed;

		memcpy(&((struct hostdata6 *)n)->h6_addr, ipaddr,
		    sizeof (struct in6_addr));
		hashval = ((const int *)ipaddr)[3];
		hp = (struct hostdata **)&h_table6[iphash(hashval)];
		break;
	default:
		fprintf(stderr, "snoop: ERROR: Unknown address family: %d",
		    family);
		exit(1);
	}

	n->h_next = *hp;
	*hp = n;

	if (namefile != NULL) {
		if (family == AF_INET) {
			np = inet_ntoa(*(const struct in_addr *)ipaddr);
			if (np) {
				(void) fprintf(namefile, "%s\t%s", np, name);
				if (aliases) {
					for (ind = 0;
					    aliases[ind] != NULL;
					    ind++) {
						(void) fprintf(namefile, " %s",
						    aliases[ind]);
					}
				}
				(void) fprintf(namefile, "\n");
			}
		} else if (family == AF_INET6) {
			np = (char *)inet_ntop(AF_INET6, (void *)ipaddr, aname,
			    sizeof (aname));
			if (np) {
				(void) fprintf(namefile, "%s\t%s", np, name);
				if (aliases) {
					for (ind = 0;
					    aliases[ind] != NULL;
					    ind++) {
						(void) fprintf(namefile, " %s",
						    aliases[ind]);
					}
				}
				(void) fprintf(namefile, "\n");
			}
		} else {
			(void) fprintf(stderr, "addhost: unknown family %d\n",
			    family);
		}
	}
	return (n);

alloc_failed:
	if (n)
		free(n);
	(void) fprintf(stderr, "addhost: no mem\n");

	aname[0] = '\0';
	memset(&h, 0, sizeof (struct hostdata));
	h.h_hostname = aname;
	return (&h);
}

char *
addrtoname(int family, const void *ipaddr)
{
	switch (family) {
	case AF_INET:
		return (iplookup(*(const struct in_addr *)ipaddr)->h_hostname);
	case AF_INET6:
		return (ip6lookup((const struct in6_addr *)ipaddr)->h_hostname);
	}
	(void) fprintf(stderr, "snoop: ERROR: unknown address family: %d\n",
	    family);
	exit(1);
	/* NOTREACHED */
}

void
load_names(fname)
	char *fname;
{
	char buf[1024];
	char *addr, *name, *alias;
	FILE *f;
	unsigned int addrv4;
	struct in6_addr addrv6;
	int family;
	void *naddr;

	(void) fprintf(stderr, "Loading name file %s\n", fname);
	f = fopen(fname, "r");
	if (f == NULL) {
		perror(fname);
		return;
	}

	while (fgets(buf, 1024, f) != NULL) {
		addr = strtok(buf, SEPARATORS);
		if (addr == NULL || *addr == '#')
			continue;
		if (inet_pton(AF_INET6, addr, (void *)&addrv6) == 1) {
			family = AF_INET6;
			naddr = (void *)&addrv6;
		} else if ((addrv4 = inet_addr(addr)) != (ulong_t)-1) {
			family = AF_INET;
			naddr = (void *)&addrv4;
		}
		name = strtok(NULL, SEPARATORS);
		if (name == NULL)
			continue;
		while ((alias = strtok(NULL, SEPARATORS)) != NULL &&
		    (*alias != '#')) {
			(void) addhost(family, naddr, alias, NULL);
		}
		(void) addhost(family, naddr, name, NULL);
		/* Note: certain addresses such as broadcast are skipped */
	}

	(void) fclose(f);
}

/*
 * lgetipnodebyname: looks up hostname in cached address data. This allows
 * filtering on hostnames from the .names file to work properly, and
 * avoids name clashes between domains. Note that only the first of the
 * ipv4, ipv6, or v4mapped address will be returned, because the
 * cache does not contain information on multi-homed hosts.
 */
/*ARGSUSED*/
struct hostent *
lgetipnodebyname(const char *name, int af, int flags, int *error_num)
{
	int i;
	struct hostdata4 *h;
	struct hostdata6 *h6;
	static struct hostent he;		/* host entry */
	static struct in6_addr h46_addr[MAXADDRS];	/* v4mapped address */
	static char h_name[MAXHOSTNAMELEN];	/* hostname */
	static char *list[MAXADDRS];		/* addr_list array */
	struct hostent *hp = &he;
	int ind;

	(void) memset((char *)hp, 0, sizeof (struct hostent));
	hp->h_name = h_name;
	h_name[0] = '\0';
	strcpy(h_name, name);

	hp->h_addrtype = AF_INET6;

	hp->h_addr_list = list;
	for (i = 0; i < MAXADDRS; i++)
		hp->h_addr_list[i] = NULL;
	ind = 0;

	/* ipv6 lookup */
	if (af == AF_INET6) {
		hp->h_length = sizeof (struct in6_addr);
		for (i = 0; i < MAXHASH; i++) {
			for (h6 = h_table6[i]; h6; h6 = h6->h6_next) {
				if (strcmp(name, h6->h6_hostname) == 0) {
					if (ind >= MAXADDRS - 1) {
						/* too many addresses */
						return (hp);
					}
					/* found ipv6 addr */
					hp->h_addr_list[ind] =
					    (char *)&h6->h6_addr;
					ind++;
				}
			}
		}
	}
	/* ipv4 or v4mapped lookup */
	if (af == AF_INET || (flags & AI_ALL)) {
		for (i = 0; i < MAXHASH; i++) {
			for (h = h_table4[i]; h; h = h->h4_next) {
				if (strcmp(name, h->h4_hostname) == 0) {
					if (ind >= MAXADDRS - 1) {
						/* too many addresses */
						return (hp);
					}
					if (af == AF_INET) {
						/* found ipv4 addr */
						hp->h_addrtype = AF_INET;
						hp->h_length =
						    sizeof (struct in_addr);
						hp->h_addr_list[ind] =
						    (char *)&h->h4_addr;
						ind++;
					} else {
						/* found v4mapped addr */
						hp->h_length =
						    sizeof (struct in6_addr);
						hp->h_addr_list[ind] =
						    (char *)&h46_addr[ind];
						IN6_INADDR_TO_V4MAPPED(
						    &h->h4_addr,
						    &h46_addr[ind]);
						ind++;
					}
				}
			}
		}
	}
	return (ind > 0 ? hp : NULL);
}