/*
 * Copyright (C) 2006  Internet Systems Consortium, Inc. ("ISC")
 *
 * 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 ISC DISCLAIMS ALL WARRANTIES WITH
 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS.  IN NO EVENT SHALL ISC 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.
 */

/*
 * This module uses Windows lookup facilities to get the address information
 * wherever it resides. This avoids calling the Internet standard funcction
 * gethostbyname and gives us more control over the results since the
 * Microsoft implementation seems to return the wrong error code for some
 * conditions.
 */

#include <config.h>
#include <ws2tcpip.h>
#include <nspapi.h>
#include <svcguid.h>
#include <ntp_rfc2553.h>
#include <malloc.h>
#include <ntp_stdlib.h>
#include <syslog.h>

/*
 * Set this Macro to force retries even if it fails
 * the lookup
 */
#ifdef FORCE_DNSRETRY

#undef EAI_NONAME
#define EAI_NONAME EAI_AGAIN

#endif

typedef struct hostent hostent_t;

int ReturnCode(int errcode)
{
	int retcode;

	switch (errcode)
	{
	case 0:
		return (0);
	case WSAEINVAL:
		return (EAI_BADFLAGS);
	case WSANO_DATA:
		return (EAI_NONAME);
	case WSANOTINITIALISED:
	case WSASERVICE_NOT_FOUND:
		return (EAI_FAIL);
	case WSA_NOT_ENOUGH_MEMORY:
		return (EAI_MEMORY);
	default:
		return (EAI_FAIL);
	}
}

int
AddToAddresses(char **Addresses, int *cnt, CSADDR_INFO *csaddr)
{

	int csize;
	struct in_addr *sinaddr;
	char *addr;
	struct in_addr *addr_list;
	struct sockaddr_in *sin;
	sin = (struct sockaddr_in *) csaddr->RemoteAddr.lpSockaddr;
	if (*Addresses != NULL)
	{
		csize = _msize(*Addresses);
		addr_list = realloc(*Addresses, csize + sizeof(struct in_addr));
	}
	else
	{
		csize = 0;
		addr_list = malloc(sizeof(struct in_addr));
	}
	addr = (char *) addr_list;
	sinaddr = &((struct in_addr*) addr)[(*cnt)];
	memset(sinaddr, 0, sizeof(sinaddr));
	memcpy(sinaddr, &sin->sin_addr, sizeof(struct in_addr));
	
	(*cnt)++;
	*Addresses = (char *) addr_list;
	return 0;
}

int
DNSlookup_name(
	const char *name,
	int ai_family,
	struct hostent **Addresses
)
{
	char buffer[sizeof(WSAQUERYSET) + 2048];
	WSAQUERYSET query;
	struct hostent *addr = NULL;
	char *bufaddr = NULL;
	char ** addrlist = &bufaddr;
	int addrcnt = 0;
	WSAQUERYSET *results = (WSAQUERYSET *) buffer;
	GUID	HostnameGUID = SVCID_INET_HOSTADDRBYNAME;
	HANDLE	handle;
	DWORD dwLength;
	int err = 0;
	int retcode = 0;
	int errcode = 0;
	DWORD i;

	/*
	 * First we must create a query set
	 */
	memset(&query, 0, sizeof(query));
	query.dwSize = sizeof(query);
	query.lpszServiceInstanceName = (char *)name;
	query.dwNameSpace = NS_DNS;
	query.lpServiceClassId = &HostnameGUID;

	err = WSALookupServiceBegin(&query,
                                 LUP_RETURN_NAME | LUP_RETURN_BLOB | LUP_RETURN_ADDR,
                                 &handle);

	if(err == SOCKET_ERROR)
	{
		/*
		 * Convert the error code and return
		 */
		return (ReturnCode(WSAGetLastError()));
	}

	/*
	 * Initially none
	 * Change if we get something
	 */
	retcode = EAI_NONAME;
	dwLength = sizeof(buffer);

	while(err == 0)		/* Drop out when error */
	{
		memset(&buffer, 0, dwLength);
		err = WSALookupServiceNext(
			                handle,
			                0,
			                &dwLength,
			                results);
		errcode = WSAGetLastError();
		if (results->dwNumberOfCsAddrs > 0)
		{
			if (addr == NULL)
			{
				addr = (struct hostent *) malloc(sizeof(struct hostent));
				memset(addr, 0, sizeof(struct hostent));
				addr->h_addrtype = (short) results->lpcsaBuffer->iSocketType;
				addr->h_length = sizeof(struct in_addr); /* Only passing back the address */
				addrlist = malloc(sizeof(char *));
				*addrlist = NULL;
			}
			for (i = 0; i < results->dwNumberOfCsAddrs; i++)
			{
				AddToAddresses(addrlist, &addrcnt, &results->lpcsaBuffer[i]);
			}
		}

	}
	if (addr != NULL)
	{
		addr->h_name = (char *) name;
		addr->h_addr_list = addrlist;
		retcode = 0;
		*Addresses = addr;
	}
	else
	{
#ifdef FORCE_DNSRETRY
		/*
		 * We do this or the error would never be logged
		 */
		if (errcode == WSANO_DATA)
			msyslog(LOG_ERR, "Address not found for %s", name);
#endif
		retcode = ReturnCode(errcode);
	}
	WSALookupServiceEnd(handle);
	return (retcode);
}

