/* Distributed Checksum Clearinghouse
 *
 * kludge replacement for getifaddrs(3) for systems that lack it
 *
 * Copyright (c) 2005 by Rhyolite Software
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE
 * BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 *
 * Rhyolite Software DCC 1.2.74-1.13 $Revision$
 */


/* let the configure script decide if this might work */
#ifndef TEST_DCC_GETIFADDRS
#include "dcc_config.h"
#endif

#ifndef USE_DCC_GETIFADDRS
/* global to suppress "defined but not used" warning */
char dcc_getifaddrs_unneeded[] = "dcc_getifaddrs() unneeded";

#else

#include <sys/types.h>
#define BSD_COMP			/* for SunOS */
#define _IO_TERMIOS_H			/* kludge for OpenUNIX */
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <unistd.h>
#include "dcc_defs.h"
#include "dcc_heap_debug.h"
#include "dcc_ifaddrs.h"

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

int
dcc_getifaddrs(struct ifaddrs **pif)
{
	struct ifconf ifc;
	int buf_size;
	char *buf;
	struct ifreq *ifrp, *ifrp_next, ifr_f;
	struct ifaddrs_all *result0, *result, *rp;
	int i, serrno, s;

	if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)
		return -1;

	/* get the list of interface names and addresses */
	buf_size = 1024;
	for (;;) {
		buf = dcc_malloc(buf_size);
		if (!buf) {
			serrno = errno;
			close(s);
			errno = serrno;
			return -1;
		}
		ifc.ifc_buf = buf;
		ifc.ifc_len = buf_size;
		memset(buf, 0, buf_size);
		if (0 > ioctl(s, SIOCGIFCONF, (char *)&ifc)
		    && errno != EINVAL) {
			serrno = errno;
			close(s);
			errno = serrno;
			*pif = 0;
			return -1;
		}
		if (ifc.ifc_len < buf_size && ifc.ifc_len != 0)
			break;
		dcc_free(buf);
		buf_size *= 2;
	}

	i = (ifc.ifc_len/sizeof(struct ifreq)) * sizeof(*result);
	result = (struct ifaddrs_all *)dcc_malloc(i);
	if (!result) {
		serrno = errno;
		dcc_free(buf);
		close(s);
		errno = serrno;
		*pif = 0;
		return -1;
	}
	memset(result, 0, i);
	result0 = result;
	*pif = &result->ifa;

	for (ifrp = ifc.ifc_req;
	     (void *)ifrp < (void *)&ifc.ifc_buf[ifc.ifc_len];
	     ifrp = ifrp_next) {
#ifdef DCC_HAVE_SA_LEN
		i = ifrp->ifr_addr.sa_len + sizeof(ifrp->ifr_name);
		if (i < (int)sizeof(*ifrp))
			ifrp_next = ifrp + 1;
		else
			ifrp_next = (struct ifreq *)((char *)ifrp + i);
#else
		ifrp_next = ifrp + 1;
#endif

		if (ifrp->ifr_addr.sa_family != AF_INET
#ifdef AF_INET6
		&& ifrp->ifr_addr.sa_family != AF_INET6
#endif
		    )
			continue;

		strncpy(ifr_f.ifr_name, ifrp->ifr_name, sizeof(ifr_f.ifr_name));
		if (0 > ioctl(s, SIOCGIFFLAGS, (char *)&ifr_f)) {
			if (errno == ENXIO)
				continue;
			serrno = errno;
			close(s);
			dcc_free(buf);
			dcc_free(*pif);
			*pif = 0;
			errno = serrno;
			return -1;
		}

		/* save the name only for debugging */
		strncpy(result->name, ifrp->ifr_name, sizeof(result->name));
		result->ifa.ifa_name = result->name;

		result->addr.sa = ifrp->ifr_addr;
		result->ifa.ifa_addr = &result->addr.sa;
		result->ifa.ifa_flags = ifr_f.ifr_flags;
		result->ifa.ifa_next = &(result+1)->ifa;

		/* skip duplicate addresses */
		rp = result0;
		for (;;) {
			if (rp >= result) {
				++result;
				break;
			}
			if (DCC_SU_SA_EQ(&rp->addr, &result->addr))
				break;
			++rp;
		}
	}

	if (&result->ifa == *pif) {
		*pif = 0;
	} else {
		(result-1)->ifa.ifa_next = 0;
	}
	dcc_free(buf);
	close(s);

	return 0;
}
#endif /* USE_DCC_GETIFADDRS */
