/*
 * $Id: netaddr.c,v 1.6 2009/04/15 16:21:40 c4chris Exp $
 */

#include "os.h"

/*
 * netaddr.c - routines for working with network addresses.
 *
 * David A. Curry				Jeffrey C. Mogul
 * Purdue University				Digital Equipment Corporation
 * Engineering Computer Network			Western Research Laboratory
 * 1285 Electrical Engineering Building		250 University Avenue
 * West Lafayette, IN 47907-1285		Palo Alto, CA 94301
 * davy@ecn.purdue.edu				mogul@decwrl.dec.com
 * 
 */

/*
 * TODO:
 * - replace the gethostbyname() functions with getaddrinfo()
 * - look at this broken strict aliasing warning:
 *   In function 'setup_ouraddrs':
 *   netaddr.c:338: warning:
 *     dereferencing pointer 'sin' does break strict-aliasing rules
 *   netaddr.c:322: note: initialized from here
 */

#include <sys/param.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#ifdef	SUNOS5
#include <sys/sockio.h>
#endif	/* SUNOS5 */
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/in.h>

#include "nfswatch.h"
#include "externs.h"

#define	MAXOURADDRS 32
ipaddrt	ouraddrs[MAXOURADDRS];

static void setup_ouraddrs(void);

/*
 * get_net_addrs - get network addresses of source and destination
 *		   hosts, along with official host names.
 */
void
get_net_addrs(void)
{
	register int n, i;
	register char **cp;
	struct hostent *hp;

	/*
	 * Look up the local host.
	 */
	if ((hp = gethostbyname(myhost)) == NULL) {
		(void) fprintf(stderr, "%s: %s: unknown host.\n", pname,
			myhost);
		finish(-1);
	}

	setup_ouraddrs();

	/*
	 * Save the official host name.
	 */
	(void) strcpy(myhost, hp->h_name);

	/*
	 * If one was specified, look up the destination host.
	 * Otherwise, we can use what we have.
	 */
	if (allflag) {
		(void) sprintf(dsthost, "all hosts");
	}
	else if (dstflag) {
		if ((hp = gethostbyname(dsthost)) == NULL) {
			(void) fprintf(stderr, "%s: %s: unknown host.\n", pname,
				dsthost);
			finish(-1);
		}

		/*
		 * Save the official host name.
		 */
		(void) strcpy(dsthost, hp->h_name);
	}
	else {
		/*
		 * Host name is the same as the local
		 * host.
		 */
		(void) strcpy(dsthost, myhost);
	}

	/* Setup the dst addrs we are interested in.  */
	n = 0;
	memset(dstaddrs, 0, MAXHOSTADDR * sizeof(ipaddrt));

	/* Add ouraddrs.  */
	for (i=0; (ouraddrs[i] != 0) && (i < MAXOURADDRS); i++) {
		dstaddrs[n++] = ouraddrs[i];
	}

	/*
	 * Copy destination host's network addresses.
	 */
	for (cp = hp->h_addr_list; *cp != NULL; cp++) {
		if (n >= MAXHOSTADDR)
			break;

		memcpy(&dstaddrs[n], *cp, hp->h_length);
		n++;
	}

	/*
	 * If they specified a server host, get its addresses.
	 */
	if (serverflag) {
		if ((hp = gethostbyname(serverhost)) == NULL) {
			fprintf(stderr, "%s: %s: unknown host.\n", pname,
				serverhost);
			finish(-1);
		}

		/*
		 * Save the official host name.
		 */
		(void) strcpy(serverhost, hp->h_name);

		/*
		 * Copy the server's network addresses.
		 */
		n = 0;
		memset(serveraddrs, 0, MAXHOSTADDR * sizeof(ipaddrt));

		for (cp = hp->h_addr_list; *cp != NULL; cp++) {
			if (n >= MAXHOSTADDR)
				break;

			memcpy(&serveraddrs[n], *cp, hp->h_length);
			n++;
		}
	}

	/*
	 * If they didn't specify a source host,
	 * we're done.
	 */
	if (!srcflag)
		return;

	/*
	 * Look up the source host.
	 */
	if ((hp = gethostbyname(srchost)) == NULL) {
		(void) fprintf(stderr, "%s: %s: unknown host.\n", pname,
			srchost);
		finish(-1);
	}

	/*
	 * Save the official host name.
	 */
	(void) strcpy(srchost, hp->h_name);

	/*
	 * Copy source host's network addresses.
	 */
	n = 0;
	memset(srcaddrs, 0, MAXHOSTADDR * sizeof(ipaddrt));

	for (cp = hp->h_addr_list; *cp != NULL; cp++) {
		if (n >= MAXHOSTADDR)
			break;

		memcpy(&srcaddrs[n], *cp, hp->h_length);
		n++;
	}
}

/*
 * want_packet - determine if we're interested in a packet by examining
 *		 its source and destination addresses.
 */
int
want_packet(ipaddrt src, ipaddrt dst)
{
	register int i, want;

	want = FALSE;
	thisdst = dst;

	/*
	 * Check that the source or destination is the server.
	 */
	if (serverflag) {
		for (i=0; (serveraddrs[i] != 0) && (i < MAXHOSTADDR); i++) {
			if (!bcmp((char *) &src, (char *) &serveraddrs[i],
				  sizeof(ipaddrt)) ||
			    !bcmp((char *) &dst, (char *) &serveraddrs[i],
				  sizeof(ipaddrt))) {
				want = TRUE;
				break;
			}
		}
		return(want);
	}
	 
	/*
	 * Any source or destination is okay.
	 */
	if (allflag) {
		return(TRUE);
	}

	/*
	 * Check source address first.
	 */
	if (srcflag) {
		for (i = 0; (srcaddrs[i] != 0) && (i < MAXHOSTADDR); i++) {
			if (!bcmp((char *) &src, (char *) &srcaddrs[i],
			    sizeof(ipaddrt))) {
				want = TRUE;
				break;
			}
		}

		/*
		 * If it's not from our source, we
		 * don't even need to check the destination.
		 */
		if (!want)
			return(FALSE);
	}

	want = FALSE;

	/*
	 * Check destination address.
	 */
	for (i = 0; (dstaddrs[i] != 0) && (i < MAXHOSTADDR); i++) {
		if (!bcmp((char *) &dst, (char *) &dstaddrs[i],
		    sizeof(ipaddrt))) {
			want = TRUE;
			break;
		}
	}

	return(want);
}

/*
 * to_self - determine if packet destination is the local host
 */
int
to_self(ipaddrt dst)
{
	register int i;

	/*
	 * Check if the destination is one of our addresses
	 */
	for (i=0; (ouraddrs[i] != 0) && (i < MAXOURADDRS); i++) {
	    if (ouraddrs[i] == dst) {
		return(1);
	    }
	}
	
	return(0);
}

/* Not all systems have IFF_LOOPBACK */
#ifdef IFF_LOOPBACK
#define ISLOOPBACK(p) ((p)->ifr_flags & IFF_LOOPBACK)
#else
#define ISLOOPBACK(p) (strcmp((p)->ifr_name, "lo0") == 0)
#endif

/*
 * Make a list of our possible IP addresses
 * A lot of the code in setup_ouraddrs() was borrowed from tcpdump/
 */
static void
setup_ouraddrs(void)
{
	struct ifreq ibuf[MAXOURADDRS], *ifrp, *ifend;
	struct ifconf ifc;
	int n;
	int fd;

	n = 0;
	memset(ouraddrs, 0, MAXOURADDRS * sizeof(ipaddrt));

	fd = socket(AF_INET, SOCK_DGRAM, 0);
	if (fd < 0) {
	    perror("socket");
	    finish(-1);
	}
	ifc.ifc_len = sizeof ibuf;
	ifc.ifc_buf = (caddr_t)ibuf;

	if (ioctl(fd, SIOCGIFCONF, (char *)&ifc) < 0 ||
			(unsigned int) ifc.ifc_len < sizeof(struct ifreq)) {
	    perror("SIOCGIFCONF");
	    finish(-1);
	}
	ifrp = ibuf;
	ifend = (struct ifreq *)((char *)ibuf + ifc.ifc_len);
	
	while (ifrp < ifend) {
		struct ifreq ifr;
		struct sockaddr_in *sin =
				(struct sockaddr_in *)&ifrp->ifr_addr;
		/*
		 * Need a temporary to preserve address info that is
		 * used below to locate the next entry.  (Otherwise,
		 * SIOCGIFFLAGS stomps over it because the requests
		 * are returned in a union.)
		 */
		memcpy(ifr.ifr_name, ifrp->ifr_name, sizeof(ifr.ifr_name));
		if (ioctl(fd, SIOCGIFFLAGS, (char *)&ifr) < 0) {
			(void)fprintf(stderr, "SIOCGIFFLAGS: ");
			perror(ifrp->ifr_name);
			finish(-1);
		}
		if ((ifr.ifr_flags & IFF_UP) && !ISLOOPBACK(&ifr)) {
		    if (n >= MAXOURADDRS)
			break;
		    ouraddrs[n] = sin->sin_addr.s_addr;
		    n++;
		}
#if BSD >= 199006
		n = ifrp->ifr_addr.sa_len + sizeof(ifrp->ifr_name);
		if (n < sizeof(*ifrp))
			++ifrp;
		else
			ifrp = (struct ifreq *)((char *)ifrp + n);
#else
		++ifrp;
#endif
	}
	close(fd);
}
