/*
 * getif.c : get an interface structure
 */

#ifdef  __STDC__
#define P(args) args
#else
#define P(args) ()
#endif

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>	/* struct in_addr */
#include <arpa/inet.h>  /* inet_ntoa */

#include "defs.h"

#ifdef HAVE_STROPTS_H
#include <stropts.h>
#endif

#include <errno.h>
#include <assert.h>

#include "defaults.h"
#include "bootpd.h"
#include "getif.h"
#include "report.h"
#include "bootp.h"	/* struct bootp, required by utils.h */
#include "dhcpd.h"	/* struct client_info, required by dhcp.h */
#include "dhcp.h"	/* struct opts, required by utils.h */
#include "utils.h"	/* smalloc() */
#include "my_syslog.h"


static struct interface_addrs *interfaces; 	/* head of linked list of interfaces we will build */

static int nmatch P((u_char *ca, u_char *cb));

/* initialize the 'interfaces' linked list */
/* Based closely on get_ifi_info() in W. Richard Stevens' "UNIX Network Programming: Volume 1", 
   Second Edition, p. 429, modified to support platforms needing SYS_SOCKET_IOCTLS_USE_STREAMS */
void
init_interfaces(sockfd)
	int sockfd;
{
	int len, lastlen, rc;
	char *ptr, *buf;
	struct ifreq *ifr, ifrcopy;
	struct sockaddr_in *sip;
	struct interface_addrs *new_interface;	/* malloc new ones in here */
	struct interface_addrs **interfaceNext;	/* points to where we link in next interface */
	struct ifconf ifconf;				/* holds list of interfaces as returned by ioctl SIOCGIFCONF  */
#ifdef SYS_SOCKET_IOCTLS_USE_STREAMS
	/* On systems that don't support SIOCxxx ioctls on socket descriptors, we will issue
	   I_STR ioctls instead.  We use ioc to hold args/result, and copy any values we need 
       out of it to where we would expect to find them. */
	struct strioctl ioc;
#endif

	/* issue SIOCGIFCONF to get buf populated with all the struct ifreq's  */
	/* Since we don't know how many interfaces there are, we allocate some space for the buffer,
	   then issue the request in a loop, stopping when we have a succesful return and the returned
	   data length is the same as the previous attempt.  */
	lastlen = 0;
	len = 100 * sizeof(struct ifreq);	/* initial buffer size guess */
	for ( ; ; ) {
		buf = smalloc(len);
#ifdef SYS_SOCKET_IOCTLS_USE_STREAMS
		ioc.ic_cmd = SIOCGIFCONF;
		ioc.ic_timout = 0;
		ioc.ic_len = len;
		ioc.ic_dp = (char *) buf;
		rc = ioctl(sockfd, I_STR, (char *) &ioc);
		ifconf.ifc_len = ioc.ic_len;
		ifconf.ifc_req = (void *) ioc.ic_dp;
#else /* SYS_SOCKET_IOCTLS_USE_STREAMS */
		ifconf.ifc_len = len;
		ifconf.ifc_buf = buf;
		rc = ioctl(sockfd, SIOCGIFCONF, (void *)&ifconf);
#endif /* SYS_SOCKET_IOCTLS_USE_STREAMS */
		if (rc < 0) {
			/* EINVAL is an expected error return on some systems, indicating a buffer too small; 
			   any other error return is presumably serious.  Something's also wrong if we get an
			   error return *after* a previous succesful call (which last lastlen>0). */
			if (errno != EINVAL || lastlen != 0) {
				report(LOG_ERR, "ioctl(SIOCGIFCONF): %s", get_network_errmsg());
				exit(1);
			} /* else we know buffer was too small, so we'll just try again with larger buffer */
		} else { 
			/* no error return, but some systems just truncate the data and return 0.  
			   So we're only done if we got *something* back and 
			   we've previousy gotten the same datalength with a smaller buffer. */
			if (ifconf.ifc_len &&(ifconf.ifc_len == lastlen))
				break;		/* success, len has not changed */
			lastlen = ifconf.ifc_len;
		}
		len += 10 * sizeof(struct ifreq);		/* increment for next try */
		free(buf);
	}

	interfaces = NULL;	/* init the linked list */
	interfaceNext = &interfaces;

	for (ptr = buf; ptr < buf + ifconf.ifc_len; ) {	/* walk through the struct ifreq's in buf */
													/* increment of ptr is done separately below */
		ifr = (struct ifreq *) ptr;
	
		/* determine 'len', the length of the current ifreq, so we can increment over it when done */
#ifdef STRUCT_SOCKADDR_HAS_SA_LEN
		len = max(sizeof(struct sockaddr), ifr->ifr_addr.sa_len);
#else
		len = sizeof(struct sockaddr);	/* we're assuming IPv4 */
#endif

		/* increment ptr to next interface for next time through the loop */
		ptr += sizeof(ifr->ifr_name) + len;

		ifrcopy = *ifr;	/* copy so we can modify ifrcopy without trashing ifr */
		/* get interface flags */
#ifdef SYS_SOCKET_IOCTLS_USE_STREAMS
		ioc.ic_cmd = SIOCGIFFLAGS;
		ioc.ic_timout = 0;
		ioc.ic_len = sizeof(ifrcopy);
		ioc.ic_dp = (char *) &ifrcopy;
		rc = ioctl(sockfd, I_STR, (char *) &ioc);
#else /* SYS_SOCKET_IOCTLS_USE_STREAMS */
		rc = ioctl(sockfd, SIOCGIFFLAGS, (void *) &ifrcopy);
#endif /* SYS_SOCKET_IOCTLS_USE_STREAMS */
		if (rc < 0) {
			report(LOG_ERR, "ioctl(SIOCGIFFLAGS): %s", get_errmsg());
			exit(1);
		}
		/* skip interfaces which are not UP, are LOOPBACK, do not support BROADCAST, or are POINTOPOINT */
		if ( !(ifrcopy.ifr_flags & IFF_UP) || (ifrcopy.ifr_flags & IFF_LOOPBACK) ||
	     	!(ifrcopy.ifr_flags & IFF_BROADCAST) || (ifrcopy.ifr_flags & IFF_POINTOPOINT) ) {
			if (debug > 3)
				report(LOG_INFO,"interface %s, skipping", ifrcopy.ifr_name);
			continue;
		}

		/* skip interfaces which are not IPv4.  We have to examine ifr (which we retrieved earlier), not ifrcopy. */
		if (ifr->ifr_addr.sa_family != AF_INET) {
			if (debug > 3)
				report(LOG_INFO,"interface %s, skipping (not IPv4)", ifr->ifr_name);
			continue;	/* we only support IPv4 interfaces */
		}

		/* at this point we've decided to record this interface */

		new_interface = (struct interface_addrs *) smalloc(sizeof(struct interface_addrs)); /* space for new interface */
		*interfaceNext = new_interface;		/* make previous point to it */
		interfaceNext = &new_interface->next;	/* where we'll link in next interface, if any */

		/* copy IP address.  It should still be in ifr. */
		sip = (struct sockaddr_in *) &ifr->ifr_addr;
		bcopy((char *) &sip->sin_addr, (char *) &new_interface->ip_address.s_addr, sizeof(struct in_addr));
		if (debug > 3)
			report(LOG_INFO,"interface %s, IP address: %s", ifr->ifr_name, inet_ntoa(new_interface->ip_address));

		/* get broadcast address */
#ifdef SYS_SOCKET_IOCTLS_USE_STREAMS
		ioc.ic_cmd = SIOCGIFBRDADDR;
		ioc.ic_timout = 0;
		ioc.ic_len = sizeof(ifrcopy);
		ioc.ic_dp = (char *) &ifrcopy;
		rc = ioctl(sockfd, I_STR, (char *) &ioc);
#else /* SYS_SOCKET_IOCTLS_USE_STREAMS */
		rc = ioctl(sockfd, SIOCGIFBRDADDR, (void *) &ifrcopy);
#endif /* SYS_SOCKET_IOCTLS_USE_STREAMS */
		if (rc < 0) {
			report(LOG_ERR, "ioctl(SIOCGIFBRDADDR): %s", get_errmsg());
			exit(1);
		}
		sip = (struct sockaddr_in *) &ifrcopy.ifr_broadaddr;
		bcopy((char *) &sip->sin_addr, (char *) &new_interface->ip_broadcast_address.s_addr, sizeof(struct in_addr));
		if (debug > 3)
			report(LOG_INFO,"interface %s, IP broadcast address: %s", ifr->ifr_name, inet_ntoa(new_interface->ip_broadcast_address));
	
		/* get netmask */
#ifdef SYS_SOCKET_IOCTLS_USE_STREAMS
		ioc.ic_cmd = SIOCGIFNETMASK;
		ioc.ic_timout = 0;
		ioc.ic_len = sizeof(ifrcopy);
		ioc.ic_dp = (char *) &ifrcopy;
		rc = ioctl(sockfd, I_STR, (char *) &ioc);
#else /* SYS_SOCKET_IOCTLS_USE_STREAMS */
		rc = ioctl(sockfd, SIOCGIFNETMASK, (void *) &ifrcopy);
#endif /* SYS_SOCKET_IOCTLS_USE_STREAMS */
		if (rc < 0) {
			report(LOG_ERR, "ioctl(SIOCGIFNETMASK): %s", get_errmsg());
			exit(1);
		}
		sip = (struct sockaddr_in *) &ifrcopy.ifr_addr;
		bcopy((char *) &sip->sin_addr, (char *) &new_interface->ip_netmask.s_addr, sizeof(struct in_addr));
		if (debug > 3)
			report(LOG_INFO,"interface %s, IP netmask: %s", ifr->ifr_name, inet_ntoa(new_interface->ip_netmask));
	
		/* compute the subnet address */
		new_interface->ip_subnet_address.s_addr = new_interface->ip_address.s_addr & new_interface->ip_netmask.s_addr;
		if (debug > 3)
			report(LOG_INFO,"interface %s, IP subnet address: %s", ifr->ifr_name, inet_ntoa(new_interface->ip_subnet_address));

		/* go on to next interface */
	}

	*interfaceNext = NULL;	/* end of list */
	free(buf);	 /* we're done with ifconf */
}

#define MINMATCH 7
/* Return a struct in_addr * to one of our own interface's IP addresses that is the best
   "best match" for addrp.  This is defined as the interface's IP address which has the
   largest number of identical bits (counting from the left) to addrp.  If no interface
   matches at least the first MINMATCH bits, we return NULL ptr.   If there's a tie
   for the best interface, we end up returning the "last" one, in the order the OS
   returns the interface list to us.  Note that we return a ptr to
   static storage, so cannot be called twice in the same expr. */
struct in_addr * 
getif(addrp)
	struct in_addr *addrp;
{
	static struct in_addr bestmatch;
	int maxmatch, m;
	struct interface_addrs *interface_ptr;

	/* if no address was supplied, just return NULL. */
	if (!addrp)
		return (struct in_addr *) NULL;

	maxmatch = MINMATCH;
	for (interface_ptr = interfaces; interface_ptr != NULL; interface_ptr = interface_ptr->next) {
		m = nmatch((u_char *) &(addrp->s_addr), (u_char *) &interface_ptr->ip_address.s_addr);
		if (m > maxmatch) {
			maxmatch = m;
			bestmatch.s_addr = interface_ptr->ip_address.s_addr;
		}
	}
	
	if (maxmatch > MINMATCH)
		return &bestmatch;
	else
		return (struct in_addr *) NULL;
}


/*
 * Return the number of leading bits matching in the
 * internet addresses supplied.
 */
static int
nmatch(ca, cb)
	u_char *ca, *cb;			/* ptrs to IP address, network order */
{
	u_int m = 0;				/* count of matching bits */
	u_int n = 4;				/* bytes left, then bitmask */

	/* Count matching bytes. */
	while (n && (*ca == *cb)) {
		ca++;
		cb++;
		m += 8;
		n--;
	}
	/* Now count matching bits. */
	if (n) {
		n = 0x80;
		while (n && ((*ca & n) == (*cb & n))) {
			m++;
			n >>= 1;
		}
	}
	return (m);
}

/*
 * Return true if ipaddress is on a network directly attached to this server.
 * Note that if any of the local wires has multiple IP networks running on it,
 * then is_local() will only be accurate if this server has been configured
 * with a virtual interface (secondary IP address) on each of the additional
 * networks.  Otherwise, is_local() will report those networks as non-local.
 */
boolean 
is_local(ipaddress)
	struct in_addr * ipaddress;
{
	struct interface_addrs *interface_ptr;

	for (interface_ptr = interfaces; interface_ptr != NULL; interface_ptr = interface_ptr->next) {
		if (interface_ptr->ip_subnet_address.s_addr == (interface_ptr->ip_netmask.s_addr & ipaddress->s_addr))
			return TRUE;
	}
	return FALSE;
}

/*
 * Return true if ipaddress is one of my ip addresses
 */
boolean 
is_my_ip_addr(ipaddress)
	struct in_addr * ipaddress;
{
	struct interface_addrs *interface_ptr;

	for (interface_ptr = interfaces; interface_ptr != NULL; interface_ptr = interface_ptr->next) {
		if (interface_ptr->ip_address.s_addr == ipaddress->s_addr) return TRUE;
	}
	return FALSE;
}
