/*
 * Copyright (c) 2002 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * Portions Copyright (c) 2002 Apple Computer, Inc. All Rights
 * Reserved. This file contains Original Code and/or Modifications of
 * Original Code as defined in and that are subject to the Apple Public
 * Source License Version 1.1 (the "License"). You may not use this file
 * except in compliance with the License. Please obtain a copy of the
 * License at http://www.apple.com/publicsource and read it before using
 * this file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON- INFRINGEMENT. Please see the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * @APPLE_LICENSE_HEADER_END@
 */

#include <netdb.h>
/* async gethostbyXXX function prototypes */
#include <netdb_async.h>
#include <pthread.h>
#include <stdlib.h>
#include <mach/mach.h>
#include <netinfo/_lu_types.h>
#include <netinfo/lookup.h>
#include <rpc/types.h>
#include <rpc/xdr.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <libkern/OSByteOrder.h>

#include "lu_host.h"
#include "lu_utils.h"

extern mach_port_t _lu_port;
extern int _lu_running(void);

extern int h_errno;

#define msgh_request_port msgh_remote_port
#define msgh_reply_port msgh_local_port


typedef union
{
	gethostbyaddr_async_callback hostAddr;
	gethostbyname_async_callback hostName;
	getipnodebyaddr_async_callback nodeAddr;
	getipnodebyname_async_callback nodeName;
} a_request_callout_t;

typedef struct a_requests
{
	struct a_requests *next;
	int retry;
	struct
	{
		int proc;
		ooline_data data;
		unsigned int dataLen;
		int want;
	} request;
	mach_port_t replyPort;
	a_request_callout_t callout;
	void *context;
	struct hostent *hent;		/* if reply known in XXX_start() */
} a_requests_t;

static a_requests_t *a_requests = NULL;
static pthread_mutex_t a_requests_lock = PTHREAD_MUTEX_INITIALIZER;

#define MAX_LOOKUP_ATTEMPTS 10

static kern_return_t
_lookup_all_tx(mach_port_t server, int proc, ooline_data indata, mach_msg_type_number_t indataCnt, mach_port_t *replyPort)
{
	typedef struct
	{
		mach_msg_header_t Head;
		NDR_record_t NDR;
		int proc;
		mach_msg_type_number_t indataCnt;
		unit indata[4096];
	} Request;

	Request In;
	register Request *InP = &In;
	mach_msg_return_t mr;
	unsigned int msgh_size;

	if (indataCnt > 4096) return MIG_ARRAY_TOO_LARGE;

	if (*replyPort == MACH_PORT_NULL)
	{
		mr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, replyPort);
		if (mr != KERN_SUCCESS) return mr;
	}

	msgh_size = (sizeof(Request) - 16384) + ((4 * indataCnt));
	InP->Head.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE);
//	InP->Head.msgh_size = msgh_size;	/* msgh_size passed as argument */
	InP->Head.msgh_request_port = server;
	InP->Head.msgh_reply_port = *replyPort;
	InP->Head.msgh_id = 4241776;
	InP->NDR = NDR_record;
	InP->proc = proc;
	InP->indataCnt = indataCnt;
	memcpy((char *)InP->indata, (const char *)indata, 4 * indataCnt);

	mr = mach_msg(&InP->Head,	/* msg */
		MACH_SEND_MSG,		/* options */
		msgh_size,		/* send_size */
		0,			/* rcv_size */
		MACH_PORT_NULL,		/* rcv_name */
		MACH_MSG_TIMEOUT_NONE,	/* timeout */
		MACH_PORT_NULL);	/* notify */

	switch (mr)
	{
		case MACH_MSG_SUCCESS:
			mr = KERN_SUCCESS;
			break;
		case MACH_SEND_INVALID_REPLY :
			(void)mach_port_mod_refs(mach_task_self(), *replyPort, MACH_PORT_RIGHT_RECEIVE, -1);
			*replyPort = MACH_PORT_NULL;
			break;
		default:
			break;
	}

	return mr;
}

static kern_return_t
_lookup_all_rx(void *msg, ooline_data *outdata, mach_msg_type_number_t *outdataCnt, security_token_t *token)
{
	typedef struct
	{
		mach_msg_header_t Head;
		mach_msg_body_t msgh_body;
		mach_msg_ool_descriptor_t outdata;
		NDR_record_t NDR;
		mach_msg_type_number_t outdataCnt;
		mach_msg_format_0_trailer_t trailer;
	} Reply;

	/*
	 * typedef struct {
	 * mach_msg_header_t Head;
	 * NDR_record_t NDR;
	 * kern_return_t RetCode;
	 * } mig_reply_error_t;
	 */

	register Reply *OutP = msg;
	mach_msg_format_0_trailer_t *TrailerP;
	boolean_t msgh_simple;

	if (OutP->Head.msgh_id != (4241776 + 100))
	{
		if (OutP->Head.msgh_id == MACH_NOTIFY_SEND_ONCE) return MIG_SERVER_DIED;
		else return MIG_REPLY_MISMATCH;
	}

	msgh_simple = !(OutP->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX);

	TrailerP = (mach_msg_format_0_trailer_t *)((vm_offset_t)OutP + round_msg(OutP->Head.msgh_size));
	if (TrailerP->msgh_trailer_type != MACH_MSG_TRAILER_FORMAT_0) return MIG_TRAILER_ERROR;

	if (OutP->NDR.int_rep != NDR_record.int_rep)
	{
		if (msgh_simple)
		{
			((mig_reply_error_t *)OutP)->RetCode = OSReadSwapInt32(&(((mig_reply_error_t *)OutP)->RetCode), 0);
		}
		else
		{
			OutP->outdataCnt = OSReadSwapInt32(&(OutP->outdataCnt), 0);
		}
	}

	if (msgh_simple && ((mig_reply_error_t *)OutP)->RetCode != KERN_SUCCESS) return ((mig_reply_error_t *)OutP)->RetCode;

	*outdata = (ooline_data)(OutP->outdata.address);
	*outdataCnt = OutP->outdataCnt;

	*token = TrailerP->msgh_sender;

	return KERN_SUCCESS;
}

static a_requests_t *
request_extract(mach_port_t port)
{
	a_requests_t *request0, *request;

	pthread_mutex_lock(&a_requests_lock);

	request0 = NULL;
	request = a_requests;

	while (request != NULL)
	{
		if (port == request->replyPort)
		{
			/* request found, remove from list */
			if (request0 != NULL)
			{
				request0->next = request->next;
			}
			else
			{
				a_requests = request->next;
			}

			break;
		}
		else
		{
			/* not this request, skip to next */
			request0 = request;
			request = request->next;
		}
	}

	pthread_mutex_unlock(&a_requests_lock);

	return request;
}

static void
request_queue(a_requests_t *request)
{
	pthread_mutex_lock(&a_requests_lock);

	request->next = a_requests;
	a_requests = request;

	pthread_mutex_unlock(&a_requests_lock);

	return;
}

static boolean_t
sendCannedReply(a_requests_t *request, int *error)
{
	/*
	 * typedef struct {
	 * mach_msg_header_t Head;
	 * NDR_record_t NDR;
	 * kern_return_t RetCode;
	 * } mig_reply_error_t;
	 */

	mig_reply_error_t Out;
	register mig_reply_error_t *OutP = &Out;
	kern_return_t kr;
	mach_msg_return_t mr;
	unsigned int msgh_size;

	mach_port_t sendPort;
	mach_msg_type_name_t sendType;

	/*
	 * allocate reply port
	 */
	kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &request->replyPort);
	if (kr != KERN_SUCCESS)
	{
		*error = NO_RECOVERY;
		return FALSE;
	}

	kr = mach_port_extract_right(mach_task_self(), request->replyPort, MACH_MSG_TYPE_MAKE_SEND_ONCE, &sendPort, &sendType);
	if (kr != KERN_SUCCESS)
	{
		(void)mach_port_destroy(mach_task_self(), request->replyPort);
		request->replyPort = MACH_PORT_NULL;
		*error = NO_RECOVERY;
		return FALSE;
	}

	/*
	 * queue reply message
	 */
	msgh_size = sizeof(Out);
	OutP->Head.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND_ONCE, 0);
//	OutP->Head.msgh_size = msgh_size;	/* msgh_size passed as argument */
	OutP->Head.msgh_request_port = sendPort;
	OutP->Head.msgh_reply_port = MACH_PORT_NULL;
	OutP->Head.msgh_id = 4241776 + 100;
	OutP->RetCode = MIG_REMOTE_ERROR;
	OutP->NDR = NDR_record;

	mr = mach_msg(&OutP->Head, MACH_SEND_MSG, msgh_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
	if (mr != MACH_MSG_SUCCESS)
	{
		if (mr == MACH_SEND_INVALID_REPLY)
		{
			(void)mach_port_destroy(mach_task_self(), request->replyPort);
			request->replyPort = MACH_PORT_NULL;
		}

		*error = NO_RECOVERY;
		return FALSE;
	}

	return TRUE;
}

static void
_async_cancel(mach_port_t port)
{
	a_requests_t *request;

	request = request_extract(port);
	if (request)
	{
		(void)mach_port_mod_refs(mach_task_self(), request->replyPort, MACH_PORT_RIGHT_RECEIVE, -1);
		if (request->request.data) free(request->request.data);
		if (request->hent) freehostent(request->hent);
		free(request);
	}

	return;
}

static mach_port_t
_gethostbyaddr_async_start(const char *addr, int len, int type, a_request_callout_t callout, void *context, int *error)
{
	void *address;
	int proc;
	a_requests_t *request;
	int want;
	static int proc4 = -1;
	struct in_addr *v4addr;
	static int proc6 = -1;
	struct in6_addr *v6addr;

	want = WANT_A4_ONLY;
	if (type == AF_INET6) want = WANT_A6_ONLY;

	if ((type == AF_INET6) && (len == 16) && (is_a4_mapped((const char *)addr) || is_a4_compat((const char *)addr)))
	{
		addr += 12;
		len = 4;
		type = AF_INET;
		want = WANT_MAPPED_A4_ONLY;
	}

	switch (type)
	{
		case AF_INET:
		{
			if (proc4 < 0)
			{
				if (_lookup_link(_lu_port, "gethostbyaddr", &proc4) != KERN_SUCCESS)
				{
					*error = NO_RECOVERY;
					return MACH_PORT_NULL;
				}
			}

			if (len != sizeof(struct in_addr))
			{
				*error = NO_RECOVERY;
				return MACH_PORT_NULL;
			}

			v4addr = malloc(len);
			memmove(v4addr, addr, len);
			v4addr->s_addr = htonl(v4addr->s_addr);

			address = (void *)v4addr;
			proc = proc4;
			break;
		}

		case AF_INET6:
		{
			if (proc6 < 0)
			{
				if (_lookup_link(_lu_port, "getipv6nodebyaddr", &proc6) != KERN_SUCCESS)
				{
					*error = NO_RECOVERY;
					return MACH_PORT_NULL;
				}
			}

			if (len != sizeof(struct in6_addr))
			{
				*error = NO_RECOVERY;
				return MACH_PORT_NULL;
			}

			v6addr = malloc(len);
			memmove(v6addr, addr, len);
			v6addr->__u6_addr.__u6_addr32[0] = htonl(v6addr->__u6_addr.__u6_addr32[0]);
			v6addr->__u6_addr.__u6_addr32[1] = htonl(v6addr->__u6_addr.__u6_addr32[1]);
			v6addr->__u6_addr.__u6_addr32[2] = htonl(v6addr->__u6_addr.__u6_addr32[2]);
			v6addr->__u6_addr.__u6_addr32[3] = htonl(v6addr->__u6_addr.__u6_addr32[3]);

			address = (void *)v6addr;
			proc = proc6;
			break;
		}

		default:
		*error = NO_RECOVERY;
		return MACH_PORT_NULL;
	}

	request = malloc(sizeof(a_requests_t));
	request->next = NULL;
	request->retry = MAX_LOOKUP_ATTEMPTS;
	request->request.proc = proc;
	request->request.data = (ooline_data)address;
	request->request.dataLen = len / BYTES_PER_XDR_UNIT;
	request->request.want = want;
	request->replyPort = MACH_PORT_NULL;
	request->callout = callout;
	request->context = context;
	request->hent = NULL;

	/*
	 * allocate reply port, send query to lookupd
	 */
	if (_lookup_all_tx(_lu_port, request->request.proc, request->request.data, request->request.dataLen, &request->replyPort) == KERN_SUCCESS)
	{
		request_queue(request);
	}
	else
	{
		if (request->request.data) free(request->request.data);
		free(request);
		*error = NO_RECOVERY;
		return MACH_PORT_NULL;
	}

	return request->replyPort;
}

static boolean_t
_gethostbyaddr_async_handleReply(void *replyMsg, a_requests_t **requestP, struct hostent **he, int *error)
{
	int count;
	ooline_data data;
	unsigned int datalen;
	XDR inxdr;
	mach_msg_header_t *msg = (mach_msg_header_t *)replyMsg;
	a_requests_t *request;
	kern_return_t status;
	security_token_t token;

	request = request_extract(msg->msgh_local_port);
	if (!request)
	{
		/* excuse me, what happenned to the request info? */
		return FALSE;
	}

	*requestP = request;
	*he = NULL;
	*error = 0;

	/* unpack the reply */
	status = _lookup_all_rx(replyMsg, &data, &datalen, &token);
	switch (status)
	{
		case KERN_SUCCESS:
			break;

		case MIG_SERVER_DIED:
			if (--request->retry > 0)
			{
				/* retry the request */
				if (_lookup_all_tx(_lu_port, request->request.proc, request->request.data, request->request.dataLen, &request->replyPort) == KERN_SUCCESS)
				{
					request_queue(request);
					return FALSE;
				}
			}
			/* fall through */

		default:
			*error = HOST_NOT_FOUND;
			return TRUE;
	}

	datalen *= BYTES_PER_XDR_UNIT;

	if (token.val[0] != 0)
	{
		vm_deallocate(mach_task_self(), (vm_address_t)data, datalen);
		*error = NO_RECOVERY;
		return TRUE;
	}

	xdrmem_create(&inxdr, data, datalen, XDR_DECODE);

	count = 0;
	if (!xdr_int(&inxdr, &count))
	{
		xdr_destroy(&inxdr);
		vm_deallocate(mach_task_self(), (vm_address_t)data, datalen);
		*error = NO_RECOVERY;
		return TRUE;
	}

	if (count == 0)
	{
		xdr_destroy(&inxdr);
		vm_deallocate(mach_task_self(), (vm_address_t)data, datalen);
		*error = HOST_NOT_FOUND;
		*he = NULL;
		return TRUE;
	}

	*he = extract_host(&inxdr, request->request.want, error);
	xdr_destroy(&inxdr);
	vm_deallocate(mach_task_self(), (vm_address_t)data, datalen);
	return TRUE;
}

mach_port_t
gethostbyaddr_async_start(const char *addr, int len, int type, gethostbyaddr_async_callback callout, void *context)
{
	a_request_callout_t cb;
	mach_port_t mp;

	if (!_lu_running())
	{
		h_errno = NO_RECOVERY;
		return MACH_PORT_NULL;
	}

	cb.hostAddr = callout;
	mp = _gethostbyaddr_async_start(addr, len, type, cb, context, &h_errno);
	return mp;
}

void
gethostbyaddr_async_cancel(mach_port_t port)
{
	_async_cancel(port);
	return;
}

void
gethostbyaddr_async_handleReply(void *replyMsg)
{
	int error = 0;
	struct hostent *he = NULL;
	a_requests_t *request = NULL;

	if (_gethostbyaddr_async_handleReply(replyMsg, &request, &he, &error))
	{
		/* if we have an answer to provide */
		h_errno = error;
		(request->callout.hostAddr)(he, request->context);

		(void)mach_port_mod_refs(mach_task_self(), request->replyPort, MACH_PORT_RIGHT_RECEIVE, -1);
		if (request->request.data) free(request->request.data);
		free(request);
		if (he != NULL) freehostent(he);
	}

	return;
}

mach_port_t
getipnodebyaddr_async_start(const void *addr, size_t len, int af, int *error, getipnodebyaddr_async_callback callout, void *context)
{
	a_request_callout_t cb;
	mach_port_t mp;

	if (!_lu_running())
	{
		*error = NO_RECOVERY;
		return MACH_PORT_NULL;
	}

	cb.nodeAddr = callout;
	mp = _gethostbyaddr_async_start(addr, len, af, cb, context, error);
	return mp;
}

void
getipnodebyaddr_async_cancel(mach_port_t port)
{
	_async_cancel(port);
	return;
}

void
getipnodebyaddr_async_handleReply(void *replyMsg)
{
	int error = 0;
	struct hostent *he = NULL;
	a_requests_t *request = NULL;

	if (_gethostbyaddr_async_handleReply(replyMsg, &request, &he, &error))
	{
		/* if we have an answer to provide */
		(request->callout.nodeAddr)(he, error, request->context);

		(void)mach_port_mod_refs(mach_task_self(), request->replyPort, MACH_PORT_RIGHT_RECEIVE, -1);
		if (request->request.data) free(request->request.data);
		free(request);
		/*
		 * Note: it is up to the callback function to call
		 * freehostent().
		 */
	}

	return;
}

static mach_port_t
_gethostbyname_async_start(const char *name, int want, int *error, a_request_callout_t callout, void *context)
{
	int af;
	boolean_t is_addr = FALSE;
	mach_port_t mp = MACH_PORT_NULL;
	XDR outxdr;
	static int proc;
	a_requests_t *request;
	static int proc4 = -1;
	static int proc6 = -1;
	struct in_addr v4addr;
	struct in6_addr v6addr;

	if ((name == NULL) || (name[0] == '\0'))
	{
		*error = NO_DATA;
		return MACH_PORT_NULL;
	}

	af = (want == WANT_A4_ONLY) ? AF_INET: AF_INET6;

	if ((af == AF_INET) || (want == WANT_MAPPED_A4_ONLY))
	{
		if (proc4 < 0)
		{
			if (_lookup_link(_lu_port, "gethostbyname", &proc4) != KERN_SUCCESS)
			{
				*error = NO_RECOVERY;
				return MACH_PORT_NULL;
			}
		}
		proc = proc4;
	}
	else /* if (af == AF_INET6) */
	{
		if (proc6 < 0)
		{
			if (_lookup_link(_lu_port, "getipv6nodebyname", &proc6) != KERN_SUCCESS)
			{
				*error = NO_RECOVERY;
				return MACH_PORT_NULL;
			}
		}
		proc = proc6;
	}

	request = malloc(sizeof(a_requests_t));
	request->next = NULL;
	request->retry = MAX_LOOKUP_ATTEMPTS;
	request->request.proc = proc;
	request->request.data = NULL;
	request->request.dataLen = 0;
	request->request.want = want;
	request->replyPort = MACH_PORT_NULL;
	request->callout = callout;
	request->context = context;
	request->hent = NULL;

	switch (af)
	{
		case AF_INET:
		{
			memset(&v4addr, 0, sizeof(struct in_addr));
			if (inet_aton(name, &v4addr) == 1)
			{
				/* return a fake hostent */
				request->hent = fake_hostent(name, v4addr);
				is_addr = TRUE;
			}
			break;
		}

		case AF_INET6:
		{
			memset(&v6addr, 0, sizeof(struct in6_addr));
			if (inet_pton(af, name, &v6addr) == 1)
			{
				/* return a fake hostent */
				request->hent = fake_hostent6(name, v6addr);
				is_addr = TRUE;
				break;
			} 

			memset(&v4addr, 0, sizeof(struct in_addr));
			if (inet_aton(name, &v4addr) == 1)
			{
				if (want == WANT_A4_ONLY)
				{
					free(request);
					*error = HOST_NOT_FOUND;
					return MACH_PORT_NULL;
				}

				v6addr.__u6_addr.__u6_addr32[0] = 0x00000000;
				v6addr.__u6_addr.__u6_addr32[1] = 0x00000000;
				v6addr.__u6_addr.__u6_addr32[2] = htonl(0x0000ffff);
				memmove(&(v6addr.__u6_addr.__u6_addr32[3]), &(v4addr.s_addr), sizeof(struct in_addr));

				/* return a fake hostent */
				request->hent = fake_hostent6(name, v6addr);
				is_addr = TRUE;
			}
			break;
		}

		default:
		free(request);
		*error = NO_RECOVERY;
		return MACH_PORT_NULL;
	}

	if (is_addr)
	{
		/*
		 * queue reply message
		 */
		if (sendCannedReply(request, error))
		{
			request_queue(request);
			return request->replyPort;
		}
		else
		{
			freehostent(request->hent);
			free(request);
			return MACH_PORT_NULL;
		}
	}

	request->request.dataLen = _LU_MAXLUSTRLEN + BYTES_PER_XDR_UNIT;
	request->request.data = malloc(request->request.dataLen);

	xdrmem_create(&outxdr, request->request.data, request->request.dataLen, XDR_ENCODE);
	if (!xdr__lu_string(&outxdr, (_lu_string *)&name))
	{
		xdr_destroy(&outxdr);
		free(request->request.data);
		free(request);
		*error = NO_RECOVERY;
		return MACH_PORT_NULL;
	}

	request->request.dataLen = xdr_getpos(&outxdr) / BYTES_PER_XDR_UNIT;

	/*
	 * allocate reply port, send query to lookupd
	 */
	if (_lookup_all_tx(_lu_port, request->request.proc, request->request.data, request->request.dataLen, &request->replyPort) == KERN_SUCCESS)
	{
		request_queue(request);
		mp = request->replyPort;
	}
	else
	{
		free(request->request.data);
		free(request);
		*error = NO_RECOVERY;
		mp = MACH_PORT_NULL;
	}

	xdr_destroy(&outxdr);
	return mp;
}

static boolean_t
_gethostbyname_async_handleReply(void *replyMsg, a_requests_t **requestP, struct hostent **he, int *error)
{
	int count;
	unsigned int datalen;
	XDR inxdr;
	ooline_data data;
	mach_msg_header_t *msg = (mach_msg_header_t *)replyMsg;
	a_requests_t *request;
	kern_return_t status;
	security_token_t token;
	int want;

	request = request_extract(msg->msgh_local_port);
	if (!request)
	{
		/* excuse me, what happenned to the request info? */
		return FALSE;
	}

	*requestP = request;
	*he = NULL;
	*error = 0;

	if (request->hent)
	{
		/*
		 * if the reply was already available when the
		 * request was made
		 */
		*he = request->hent;
		return TRUE;
	}

	/* unpack the reply */
	status = _lookup_all_rx(replyMsg, &data, &datalen, &token);
	switch (status)
	{
		case KERN_SUCCESS:
			break;

		case MIG_SERVER_DIED:
			if (--request->retry > 0)
			{
				/*
				 * retry the request
				 */
				if (_lookup_all_tx(_lu_port, request->request.proc, request->request.data, request->request.dataLen, &request->replyPort) == KERN_SUCCESS)
				{
					request_queue(request);
					return FALSE;
				}
			}
			/* fall through */

		default:
			*error = HOST_NOT_FOUND;
			return TRUE;
	}

	datalen *= BYTES_PER_XDR_UNIT;

	if (token.val[0] != 0)
	{
		vm_deallocate(mach_task_self(), (vm_address_t)data, datalen);
		*error = NO_RECOVERY;
		return TRUE;
	}

	xdrmem_create(&inxdr, data, datalen, XDR_DECODE);

	count = 0;
	if (!xdr_int(&inxdr, &count))
	{
		xdr_destroy(&inxdr);
		vm_deallocate(mach_task_self(), (vm_address_t)data, datalen);
		*error = NO_RECOVERY;
		return TRUE;
	}

	if (count == 0)
	{
		xdr_destroy(&inxdr);
		vm_deallocate(mach_task_self(), (vm_address_t)data, datalen);
		*error = HOST_NOT_FOUND;
		return TRUE;
	}

	want = request->request.want;
	if (want == WANT_A6_OR_MAPPED_A4_IF_NO_A6) want = WANT_A6_ONLY;

	*he = extract_host(&inxdr, want, error);
	xdr_destroy(&inxdr);
	vm_deallocate(mach_task_self(), (vm_address_t)data, datalen);
	return TRUE;
}

mach_port_t
gethostbyname_async_start(const char *name, gethostbyname_async_callback callout, void *context)
{
	a_request_callout_t cb;
	int error;
	mach_port_t mp = MACH_PORT_NULL;

	if (!_lu_running())
	{
		h_errno = NO_RECOVERY;
		return MACH_PORT_NULL;
	}

	cb.hostName = callout;
	mp = _gethostbyname_async_start(name, WANT_A4_ONLY, &error, cb, context);
	if (mp == MACH_PORT_NULL)
	{
		h_errno = error;
	}

	return mp;
}

void
gethostbyname_async_cancel(mach_port_t port)
{
	_async_cancel(port);
	return;
}

void
gethostbyname_async_handleReply(void *replyMsg)
{
	int error;
	struct hostent *he;
	a_requests_t *request;

	if (_gethostbyname_async_handleReply(replyMsg, &request, &he, &error))
	{
		/* if we have an answer to provide */
		h_errno = error;
		(request->callout.hostAddr)(he, request->context);

		(void)mach_port_mod_refs(mach_task_self(), request->replyPort, MACH_PORT_RIGHT_RECEIVE, -1);
		if (request->request.data) free(request->request.data);
		free(request);
		if (he != NULL) freehostent(he);
	}

	return;
}

mach_port_t
getipnodebyname_async_start(const char *name, int af, int flags, int *error, getipnodebyname_async_callback callout, void *context)
{
	a_request_callout_t cb;
	int if4 = 0;
	int if6 = 0;
	mach_port_t mp = MACH_PORT_NULL;
	int want = WANT_A4_ONLY;
	struct ifaddrs *ifa, *ifap;

	if (!_lu_running())
	{
		h_errno = NO_RECOVERY;
		return MACH_PORT_NULL;
	}

	/*
	 * IF AI_ADDRCONFIG is set, we need to know what interface flavors we really have.
	 */
	if (flags & AI_ADDRCONFIG)
	{
		if (getifaddrs(&ifa) < 0)
		{
			*error = NO_RECOVERY;
			return MACH_PORT_NULL;
		}

		for (ifap = ifa; ifap != NULL; ifap = ifap->ifa_next)
		{
			if (ifap->ifa_addr == NULL) continue;
			if ((ifap->ifa_flags & IFF_UP) == 0) continue;
			if (ifap->ifa_addr->sa_family == AF_INET)
			{
				if4++;
			}
			else if (ifap->ifa_addr->sa_family == AF_INET6)
			{
				if6++;
			}
		}

		freeifaddrs(ifa);

		/* Bail out if there are no interfaces */
		if ((if4 == 0) && (if6 == 0))
		{
			*error = NO_ADDRESS;
			return MACH_PORT_NULL;
		}
	}

	/*
	 * Figure out what we want.
	 * If user asked for AF_INET, we only want V4 addresses.
	 */
	switch (af)
	{
		case AF_INET:
		{
			want = WANT_A4_ONLY;
			if ((flags & AI_ADDRCONFIG) && (if4 == 0))
			{
				*error = NO_ADDRESS;
				return MACH_PORT_NULL;
			}
		}
		break;

		case AF_INET6:
		{
			want = WANT_A6_ONLY;
			if (flags & (AI_V4MAPPED|AI_V4MAPPED_CFG))
			{
				if (flags & AI_ALL)
				{
					want = WANT_A6_PLUS_MAPPED_A4;
				}
				else
				{
					want = WANT_A6_OR_MAPPED_A4_IF_NO_A6;
				}
			}
			else
			{
				if ((flags & AI_ADDRCONFIG) && (if6 == 0))
				{
					*error = NO_ADDRESS;
					return MACH_PORT_NULL;
				}
			}
		}
		break;
	}

	cb.nodeName = callout;
	mp = _gethostbyname_async_start(name, want, &h_errno, cb, context);
	return mp;
}

void
getipnodebyname_async_cancel(mach_port_t port)
{
	_async_cancel(port);
	return;
}

void
getipnodebyname_async_handleReply(void *replyMsg)
{
	int error = 0;
	struct hostent *he = NULL;
	a_requests_t *request = NULL;
	static int proc4 = -1;

	if (_gethostbyname_async_handleReply(replyMsg, &request, &he, &error))
	{
		/*
		 * we have an answer to provide
		 */
		if ((he == NULL) && (error == HOST_NOT_FOUND) && ((request->request.want == WANT_A6_PLUS_MAPPED_A4) || (request->request.want == WANT_A6_OR_MAPPED_A4_IF_NO_A6)))
		{
			/*
			 * no host found (yet), if requested we send a
			 * followup query to lookupd.
			 */
			if (proc4 < 0)
			{
				if (_lookup_link(_lu_port, "gethostbyname", &proc4) != KERN_SUCCESS)
				{
					error = NO_RECOVERY;
					goto answer;
				}
			}

			request->request.proc = proc4;
			request->request.want = WANT_MAPPED_A4_ONLY;
			if (_lookup_all_tx(_lu_port, request->request.proc, request->request.data, request->request.dataLen, &request->replyPort) == KERN_SUCCESS)
			{
				request_queue(request);
				return;
			}
			else
			{
				error = NO_RECOVERY;
			}
		}

answer:
		(request->callout.nodeName)(he, error, request->context);
		(void)mach_port_mod_refs(mach_task_self(), request->replyPort, MACH_PORT_RIGHT_RECEIVE, -1);
		if (request->request.data != NULL) free(request->request.data);
		free(request);
		/*
		 * Note: it is up to the callback function to call
		 * freehostent().
		 */
	}

	return;
}
