#include "port_before.h"

#include <syslog.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "port_after.h"

/* We define this here to make this fixed array size easy
 * to expand as needed. It should not really be done as an
 * array, but it will be sufficient for now.
 */
#define MAX_NT_IF 300
/*
 * Enumerate the TCP/IP interfaces on this machine.
 * Use WSAIoctl() to do this - note that this often
 * does not enumerate the loopback interface.
 *
 * It is the caller's responsibility to free elements
 * of IFData malloc'd by us.
 *
 * For a large number of Interfaces we have upped the array
 * from 20 to 300.  This is a workaround for now.  We should
 * really be dynamically figuring out how many Interfaces there
 * are and allocating the space required.  For now we have
 * hardcoded the number of possible interfaces and hope we don't
 * run out in case some is foolish enough to add too many addresses.
 * This should be fixed to check and retry.
 */
int GetInterfacesWSAIoctl(struct ifdata *IFData[], int *nextIndex, int maxIF)
{
    INTERFACE_INFO InterfaceList[MAX_NT_IF];
	SOCKET s;
    unsigned long bytesReturned, flags;
	int numInterfaces, i;
	int totalInterfaces;
    struct sockaddr_in* pAddress;
	struct in_addr addr, *dest;
	struct ifdata *ifd;
	BOOL ifNamed;

	ntPrintf(6, "Enumerating interfaces via WSAIoctl() - Loopback IF may be missed\n");

	s = socket(AF_INET, SOCK_DGRAM, 0);
    if (s == SOCKET_ERROR)
	{
		ntPrintf(1, "Failed to get a socket.  Error %d\n", GetLastError());
        return 0;
    }

    if (p_WSAIoctl(s, SIO_GET_INTERFACE_LIST, 0, 0, &InterfaceList, sizeof(InterfaceList), &bytesReturned, 0, 0) == SOCKET_ERROR)
	{
		ntPrintf(1, "Failed calling WSAIoctl:  Error %d\n", GetLastError());
        closesocket(s);
        return 0;
    }
    closesocket(s);

    numInterfaces = bytesReturned / sizeof(INTERFACE_INFO);
	totalInterfaces = numInterfaces;
	if (numInterfaces > maxIF) {
		ntPrintf(1, "Maximum Number of Interfaces exceeded. Found %d. Using first %d\n",
					numInterfaces, maxIF);
		numInterfaces = maxIF;
	}

	if(numInterfaces > 0)
	{
		for (i = totalInterfaces -1; i >= (totalInterfaces - numInterfaces); i--)
		{
			ifNamed = FALSE;

			ifd = (struct ifdata *)malloc(sizeof(struct ifdata));
			memset(ifd, 0, sizeof(struct ifdata));


			pAddress = (struct sockaddr_in*)&(InterfaceList[i].iiAddress);
			dest = &((struct sockaddr_in *)&ifd->if_addr)->sin_addr;
			memcpy(dest, &pAddress->sin_addr, sizeof(struct in_addr)); 
			ifd->if_addr.sa_family = AF_INET;

			pAddress = (struct sockaddr_in*)&(InterfaceList[i].iiBroadcastAddress);
			dest = &((struct sockaddr_in *)&ifd->if_broadaddr)->sin_addr;
			memcpy(dest, &pAddress->sin_addr, sizeof(struct in_addr)); 
			ifd->if_broadaddr.sa_family = AF_INET;

			flags = InterfaceList[i].iiFlags;
			if (flags & IFF_UP)
				ifd->if_flags |= IFF_UP;
			if (flags & IFF_POINTTOPOINT)
			{
				sprintf(ifd->if_name, "PPP Interface (interface %d)", numInterfaces -i);
				ifd->if_flags |= IFF_POINTTOPOINT;
				ifNamed = TRUE;
			}
			if (flags & IFF_LOOPBACK)
			{
				sprintf(ifd->if_name, "Loopback Interface (interface %d)", numInterfaces - i);
				ifd->if_flags |= IFF_LOOPBACK;
				ifNamed = TRUE;
			}
			if (flags & IFF_BROADCAST)
				ifd->if_flags |= IFF_BROADCAST;
			if (flags & IFF_MULTICAST)
				ifd->if_flags |= IFF_MULTICAST;

			if(ifNamed == FALSE)
			{
				sprintf(ifd->if_name, "TCP/IP Interface %d", numInterfaces - i);
			}

			IFData[*nextIndex] = ifd;
			*nextIndex = *nextIndex + 1;
		}
	}
	else
	{
		ntPrintf(1, "No interfaces found!\n");
	}
    return numInterfaces;
}

/*
 * This is the IPv6 version for getting the Interfaces.  It is kept separate
 * but follows the same methodology as the IPv4 version.
 */
int GetInterfaces6WSAIoctl(struct ifdata *IFData[], int *nextIndex, int maxIF)
{
    INTERFACE_INFO InterfaceList[MAX_NT_IF];
	SOCKET s;
    unsigned long bytesReturned, flags;
	int numInterfaces, i;
	int totalInterfaces;
    struct sockaddr_in6* pAddress;
	struct in_addr6 addr, *dest;
	struct ifdata *ifd;
	BOOL ifNamed;

	ntPrintf(6, "Enumerating IP v6 interfaces via WSAIoctl() - Loopback IF may be missed\n");

	s = socket(AF_INET6, SOCK_DGRAM, 0);
    if (s == SOCKET_ERROR)
	{
		ntPrintf(1, "IPv6 socket not available.  Error %d\n", GetLastError());
        return 0;
    }

    if (p_WSAIoctl(s, SIO_GET_INTERFACE_LIST, 0, 0, &InterfaceList, sizeof(InterfaceList), &bytesReturned, 0, 0) == SOCKET_ERROR)
	{
		ntPrintf(1, "Failed calling WSAIoctl for IP v6:  Error %d\n", GetLastError());
        closesocket(s);
        return 0;
    }
    closesocket(s);

    numInterfaces = bytesReturned / sizeof(INTERFACE_INFO);
	totalInterfaces = numInterfaces;
	if (numInterfaces > maxIF) {
		ntPrintf(1, "Maximum Number of IP v6 Interfaces exceeded. Found %d. Using first %d\n",
					numInterfaces, maxIF);
		numInterfaces = maxIF;
	}

	if(numInterfaces > 0)
	{
		for (i = totalInterfaces -1; i >= (totalInterfaces - numInterfaces); i--)
		{
			ifNamed = FALSE;

			ifd = (struct ifdata *)malloc(sizeof(struct ifdata));
			memset(ifd, 0, sizeof(struct ifdata));


			pAddress = (struct sockaddr_in6*)&(InterfaceList[i].iiAddress);
			dest = &((struct sockaddr_in6 *)&ifd->if_addr)->sin6_addr;
			memcpy(dest, &pAddress->sin6_addr, sizeof(struct in6_addr)); 
			ifd->if_addr.sa_family = AF_INET6;

			pAddress = (struct sockaddr_in6*)&(InterfaceList[i].iiBroadcastAddress);
			dest = &((struct sockaddr_in6 *)&ifd->if_broadaddr)->sin6_addr;
			memcpy(dest, &pAddress->sin6_addr, sizeof(struct in6_addr)); 
			ifd->if_broadaddr.sa_family = AF_INET6;

			flags = InterfaceList[i].iiFlags;
			if (flags & IFF_UP)
				ifd->if_flags |= IFF_UP;
			if (flags & IFF_POINTTOPOINT)
			{
				sprintf(ifd->if_name, "PPP Interface (interface %d)", numInterfaces - i);
				ifd->if_flags |= IFF_POINTTOPOINT;
				ifNamed = TRUE;
			}
			if (flags & IFF_LOOPBACK)
			{
				sprintf(ifd->if_name, "Loopback Interface (interface %d)", numInterfaces - i);
				ifd->if_flags |= IFF_LOOPBACK;
				ifNamed = TRUE;
			}
			if (flags & IFF_BROADCAST)
				ifd->if_flags |= IFF_BROADCAST;
			if (flags & IFF_MULTICAST)
				ifd->if_flags |= IFF_MULTICAST;

			if(ifNamed == FALSE)
			{
				sprintf(ifd->if_name, "TCP/IP Interface %d", numInterfaces - i);
			}

			IFData[*nextIndex] = ifd;
			*nextIndex = *nextIndex + 1;
		}
	}
	else
	{
		ntPrintf(1, "No IP v6 interfaces found!\n");
	}
    return numInterfaces;
}

int
isc_getifaddrs(struct ifaddrs **ifaddrp) {
	int anslen;
	int entries, count, nsin = 0, nsin6 = 0, i;
	struct ifaddrs *ifaddr = NULL, *last = NULL;
	struct sockaddr_in *sin;
	struct sockaddr_in6 *sin6;
	char *names;
	SOCKET s = SOCKET_ERROR;
	INTERFACE_INFO *List4 = NULL, *List6 = NULL;
	int nList4 = 100, nList6 = 100;
	unsigned long bytesReturned;
	size_t len;

	s = socket(AF_INET, SOCK_DGRAM, 0);
	if (s == SOCKET_ERROR) {
		ntPrintf(1, "Failed to get a socket.  Error %d\n",
			 GetLastError());
		goto try6;
	}
	do {
		len = sizeof(*List4) * nList4;
		List4 = malloc(len);
		if (List4 == NULL)
			goto cleanup;
		memset(List4, 0, len);
		if (p_WSAIoctl(s, SIO_GET_INTERFACE_LIST, NULL, 0, List4, len,
			       &bytesReturned, 0, NULL) == SOCKET_ERROR) {
			ntPrintf(1, "Failed calling WSAIoctl:  Error %d\n",
				 GetLastError());
			goto cleanup;
		}
		nsin = bytesReturned / sizeof(*List4);
		if (nsin < nList4)
			break;
		nList4 += 100;
		free(List4);
	} while (1);
	closesocket(s);
 try6:
#if 0
	/* NOT WORKING */
	s = socket(AF_INET6, SOCK_DGRAM, 0);
	if (s == SOCKET_ERROR) {
		ntPrintf(1, "Failed to get a socket.  Error %d\n",
			 GetLastError());
		goto getanswers;
	}
	do {
		len = sizeof(*List6) * nList6;
		List6 = malloc(len);
		if (List6 == NULL)
			goto cleanup;
		memset(List6, 0, len);
		if (p_WSAIoctl(s, SIO_GET_INTERFACE_LIST, NULL, 0, List6, len,
			       &bytesReturned, 0, NULL) == SOCKET_ERROR) {
			ntPrintf(1, "Failed calling WSAIoctl:  Error %d\n",
				 GetLastError());
			goto cleanup;
		}
		nsin6 = bytesReturned / sizeof(*List6);
		if (nsin6 < nList6)
			break;
		nList6 += 100;
		free(List6);
	} while (1);
	closesocket(s);
 getanswers:
#endif
	s = SOCKET_ERROR;
	ntPrintf(1, "nsin = %d, nsin6 = %d\n", nsin, nsin6);
	entries = nsin + nsin6;
	if (entries == 0) {
		free(List4);
		free(List6);
		*ifaddrp = NULL;
		return (0);
	}
	anslen = entries * sizeof(struct ifaddrs) +
		 nsin * 3 * sizeof(struct sockaddr_in) +
		 nsin6 * 3 * sizeof(struct sockaddr_in6) +
		 entries * IFNAMSIZ + 1;

	*ifaddrp = ifaddr = malloc(anslen);
	if (ifaddr == NULL)
		goto cleanup;
	sin = (struct sockaddr_in *)(ifaddr + entries);
	sin6 = (struct sockaddr_in6 *)(sin + (nsin * 3));
	names = (char *)(sin6 + (nsin6 * 3));

	for (i = 0; i < nsin6; i++) {
		memset(ifaddr, 0, sizeof(*ifaddr));
		ifaddr->ifa_next = ifaddr + 1;
		ifaddr->ifa_flags = List6[i].iiFlags;
		memcpy(sin6, &(List6[i].iiAddress), sizeof(*sin6));
		ifaddr->ifa_addr = (struct sockaddr*)sin6++;
		memcpy(sin6, &(List6[i].iiBroadcastAddress), sizeof(*sin6));
		if ((ifaddr->ifa_flags & IFF_POINTTOPOINT) != 0)
			ifaddr->ifa_dstaddr = (struct sockaddr*)sin6++;
		else
			ifaddr->ifa_broadaddr = (struct sockaddr*)sin6++;
		memcpy(sin6, &(List6[i].iiNetmask), sizeof(*sin6));
		ifaddr->ifa_netmask = (struct sockaddr *)sin6++;
		if ((ifaddr->ifa_flags & IFF_LOOPBACK) != 0)

			snprintf(names, IFNAMSIZ, "Loopback Interface %d", i);
		else
			snprintf(names, IFNAMSIZ, "TCP/IP Interface %d", i);
		ifaddr->ifa_name = names;
		names += strlen(names) + 1;
		last = ifaddr;
		ifaddr = ifaddr->ifa_next;
	}
	free(List6);
	for (i = 0; i < nsin; i++) {
		memset(ifaddr, 0, sizeof(*ifaddr));
		ifaddr->ifa_next = ifaddr + 1;
		ifaddr->ifa_flags = List4[i].iiFlags;
		memcpy(sin, &(List4[i].iiAddress), sizeof(*sin));
		ifaddr->ifa_addr = (struct sockaddr*)sin++;
		memcpy(sin, &(List4[i].iiBroadcastAddress), sizeof(*sin));
		if ((ifaddr->ifa_flags & IFF_POINTTOPOINT) != 0)
			ifaddr->ifa_dstaddr = (struct sockaddr*)sin++;
		else
			ifaddr->ifa_broadaddr = (struct sockaddr*)sin++;
		memcpy(sin, &(List4[i].iiNetmask), sizeof(*sin));
		ifaddr->ifa_netmask = (struct sockaddr *)sin++;
		if ((ifaddr->ifa_flags & IFF_LOOPBACK) != 0)
			snprintf(names, IFNAMSIZ, "Loopback Interface %d", i);
		else
			snprintf(names, IFNAMSIZ, "TCP/IP Interface %d", i);
		ifaddr->ifa_name = names;
		names += strlen(names) + 1;
		last = ifaddr;
		ifaddr = ifaddr->ifa_next;
	}
	free(List4);
	if (last != NULL)
		last->ifa_next = NULL;
	return (entries);

 cleanup:
	if (List4 != NULL)
		free(List4);
	if (List6 != NULL)
		free(List6);
	if (s != SOCKET_ERROR)
		closesocket(s);
	return (-1);
}

void
isc_freeifaddrs(struct ifaddrs *ifaddr) {
	free(ifaddr);
}