/*
 * Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * Portions Copyright (c) 1999 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>
#include <sys/types.h>
#include <stdint.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <rpc/types.h>
#include <rpc/xdr.h>
#include <mach/mach.h>
#include <servers/bootstrap.h>
#include <ifaddrs.h>
#include "lu_utils.h"
#include "netdb_async.h"

#define SOCK_UNSPEC 0
#define IPPROTO_UNSPEC 0

#define LONG_STRING_LENGTH 8192
#define _LU_MAXLUSTRLEN 256
#define LU_QBUF_SIZE 8192

#define MAX_LOOKUP_ATTEMPTS 10

#define INET_NTOP_AF_INET_OFFSET 4
#define INET_NTOP_AF_INET6_OFFSET 8

extern mach_port_t _lookupd_port();

static int gai_proc = -1;
static int gni_proc = -1;

static int32_t supported_family[] =
{
	PF_UNSPEC,
	PF_INET,
	PF_INET6
};
static int32_t supported_family_count = 3;

static int32_t supported_socket[] =
{
	SOCK_RAW,
	SOCK_UNSPEC,
	SOCK_DGRAM,
	SOCK_STREAM
};
static int32_t supported_socket_count = 4;

static int32_t supported_protocol[] =
{
	IPPROTO_UNSPEC,
	IPPROTO_ICMPV6,
	IPPROTO_UDP,
	IPPROTO_TCP
};
static int32_t supported_protocol_count = 4;

static int32_t supported_socket_protocol_pair[] =
{
	SOCK_RAW,    IPPROTO_UNSPEC,
	SOCK_RAW,    IPPROTO_UDP,
	SOCK_RAW,    IPPROTO_TCP,
	SOCK_RAW,    IPPROTO_ICMPV6,
	SOCK_UNSPEC, IPPROTO_UNSPEC,
	SOCK_UNSPEC, IPPROTO_UDP,
	SOCK_UNSPEC, IPPROTO_TCP,
	SOCK_UNSPEC, IPPROTO_ICMPV6,
	SOCK_DGRAM,  IPPROTO_UNSPEC,
	SOCK_DGRAM,  IPPROTO_UDP,
	SOCK_STREAM, IPPROTO_UNSPEC,
	SOCK_STREAM, IPPROTO_TCP
};
static int32_t supported_socket_protocol_pair_count = 12;

static int
gai_family_type_check(int32_t f)
{
	int32_t i;
	
	for (i = 0; i < supported_family_count; i++)
	{
		if (f == supported_family[i]) return 0;
	}

	return 1;
}

static int
gai_socket_type_check(int32_t s)
{
	int32_t i;
	
	for (i = 0; i < supported_socket_count; i++)
	{
		if (s == supported_socket[i]) return 0;
	}

	return 1;
}

static int
gai_protocol_type_check(int32_t p)
{
	int32_t i;
	
	for (i = 0; i < supported_protocol_count; i++)
	{
		if (p == supported_protocol[i]) return 0;
	}

	return 1;
}

static int
gai_socket_protocol_type_check(int32_t s, int32_t p)
{
	int32_t i, j, ss, sp;
	
	for (i = 0, j = 0; i < supported_socket_protocol_pair_count; i++, j+=2)
	{
		ss = supported_socket_protocol_pair[j];
		sp = supported_socket_protocol_pair[j+1];
		if ((s == ss) && (p == sp)) return 0;
	}

	return 1;
}

const char *
gai_strerror(int32_t err)
{
	switch (err)
	{
		case EAI_ADDRFAMILY: return "Address family for nodename not supported";
		case EAI_AGAIN: return "Temporary failure in name resolution";
		case EAI_BADFLAGS:	return "Invalid value for ai_flags";
		case EAI_FAIL: return "Non-recoverable failure in name resolution";
		case EAI_FAMILY: return "ai_family not supported";
		case EAI_MEMORY: return "Memory allocation failure";
		case EAI_NODATA: return "No address associated with nodename";
		case EAI_NONAME: return "nodename nor servname provided, or not known";
		case EAI_SERVICE: return "servname not supported for ai_socktype";
		case EAI_SOCKTYPE: return "ai_socktype not supported";
		case EAI_SYSTEM: return "System error";
		case EAI_BADHINTS: return "Bad hints";
		case EAI_PROTOCOL: return "ai_protocol not supported";
	}

	return "Unknown error";
}

static void
append_addrinfo(struct addrinfo **l, struct addrinfo *a)
{
	struct addrinfo *x;

	if (l == NULL) return;
	if (a == NULL) return;

	if (*l == NULL)
	{
		*l = a;
		return;
	}

	x = *l;

	if (a->ai_family == PF_INET6)
	{
		if (x->ai_family == PF_INET)
		{
			*l = a;
			a->ai_next = x;
			return;
		}

		while ((x->ai_next != NULL) && (x->ai_next->ai_family != PF_INET)) x = x->ai_next;
		a->ai_next = x->ai_next;
		x->ai_next = a;
	}
	else
	{
		while (x->ai_next != NULL) x = x->ai_next;
		a->ai_next = NULL;
		x->ai_next = a;
	}
}

static int
encode_kv(XDR *x, const char *k, const char *v)
{
	int32_t n = 1;

	if (!xdr_string(x, (char **)&k, _LU_MAXLUSTRLEN)) return 1;
	if (!xdr_int(x, &n)) return 1;
	if (!xdr_string(x, (char **)&v, _LU_MAXLUSTRLEN)) return 1;

	return 0;
}

void
freeaddrinfo(struct addrinfo *a)
{
	struct addrinfo *next;

	while (a != NULL)
	{
		next = a->ai_next;
		if (a->ai_addr != NULL) free(a->ai_addr);
		if (a->ai_canonname != NULL) free(a->ai_canonname);
		free(a);
		a = next;
	}
}

static struct addrinfo *
new_addrinfo_v4(int32_t flags, int32_t sock, int32_t proto, uint16_t port, struct in_addr addr, uint32_t iface, char *cname)
{
	struct addrinfo *a;
	struct sockaddr_in *sa;
	int32_t len;

	a = (struct addrinfo *)calloc(1, sizeof(struct addrinfo));
	if (a == NULL) return NULL;

	a->ai_next = NULL;

	a->ai_flags = flags;
	a->ai_family = PF_INET;
	a->ai_socktype = sock;
	a->ai_protocol = proto;

	a->ai_addrlen = sizeof(struct sockaddr_in);

	sa = (struct sockaddr_in *)calloc(1, a->ai_addrlen);
	if (sa == NULL)
	{
		free(a);
		return NULL;
	}

	sa->sin_len = a->ai_addrlen;
	sa->sin_family = PF_INET;
	sa->sin_port = htons(port);
	sa->sin_addr = addr;

	/* Kludge: Jam the interface number into sin_zero. */
	memmove(sa->sin_zero, &iface, sizeof(uint32_t));

	a->ai_addr = (struct sockaddr *)sa;

	if (cname != NULL)
	{
		len = strlen(cname) + 1;
		a->ai_canonname = malloc(len);
		memmove(a->ai_canonname, cname, len);
	}

	return a;
}

static struct addrinfo *
new_addrinfo_v6(int32_t flags, int32_t sock, int32_t proto, uint16_t port, struct in6_addr addr, uint32_t scopeid, char *cname)
{
	struct addrinfo *a;
	struct sockaddr_in6 *sa;
	int32_t len;

	a = (struct addrinfo *)calloc(1, sizeof(struct addrinfo));
	if (a == NULL) return NULL;

	a->ai_next = NULL;

	a->ai_flags = flags;
	a->ai_family = PF_INET6;
	a->ai_socktype = sock;
	a->ai_protocol = proto;

	a->ai_addrlen = sizeof(struct sockaddr_in6);

	sa = (struct sockaddr_in6 *)calloc(1, a->ai_addrlen);
	if (sa == NULL)
	{
		free(a);
		return NULL;
	}

	sa->sin6_len = a->ai_addrlen;
	sa->sin6_family = PF_INET6;
	sa->sin6_port = htons(port);
	sa->sin6_addr = addr;
	sa->sin6_scope_id = scopeid;
	a->ai_addr = (struct sockaddr *)sa;

	if (cname != NULL)
	{
		len = strlen(cname) + 1;
		a->ai_canonname = malloc(len);
		memmove(a->ai_canonname, cname, len);
	}

	return a;
}

/*
 * getaddrinfo support in lookupd
 * Input dict may contain the following
 *
 * name: nodename
 * service: servname
 * protocol: [IPPROTO_UNSPEC] | IPPROTO_UDP | IPPROTO_TCP
 * socktype: [SOCK_UNSPEC] | SOCK_DGRAM | SOCK_STREAM
 * family: [PF_UNSPEC] | PF_INET | PF_INET6
 * canonname: [0] | 1
 * passive: [0] | 1
 * numerichost: [0] | 1
 *
 * Output dictionary may contain the following
 * All values are encoded as strings.
 *
 * flags: unsigned long
 * family: unsigned long
 * socktype: unsigned long
 * protocol: unsigned long
 * port: unsigned long
 * address: char *
 * scopeid: unsigned long
 * canonname: char *
 *
 */

static struct addrinfo *
gai_lookupd_process_dictionary(XDR *inxdr)
{
	int32_t i, nkeys, nvals;
	char *key, *val;
	uint32_t flags, family, socktype, protocol, longport, scopeid;
	uint16_t port;
	char *addr, *canonname;
	struct addrinfo *a;
	struct in_addr a4;
	struct in6_addr a6;

	flags = 0;
	family = PF_UNSPEC;
	socktype = SOCK_UNSPEC;
	protocol = IPPROTO_UNSPEC;
	port = 0;
	scopeid = 0;
	addr = NULL;
	canonname = NULL;

	if (!xdr_int(inxdr, &nkeys)) return NULL;

	for (i = 0; i < nkeys; i++)
	{
		key = NULL;
		val = NULL;

		if (!xdr_string(inxdr, &key, LONG_STRING_LENGTH)) return NULL;
		if (!xdr_int(inxdr, &nvals)) 
		{
			free(key);
			return NULL;
		}

		if (nvals != 1)
		{
			free(key);
			return NULL;
		}

		if (!xdr_string(inxdr, &val, LONG_STRING_LENGTH))
		{
			free(key);
			return NULL;
		}

		if (!strcmp(key, "flags"))
		{
			flags = atoi(val);
		}
		else if (!strcmp(key, "family")) 
		{
			family = atoi(val);
		}
		else if (!strcmp(key, "socktype"))
		{
			socktype = atoi(val);
		}
		else if (!strcmp(key, "protocol"))
		{
			protocol = atoi(val);
		}
		else if (!strcmp(key, "port"))
		{
			longport = atoi(val);
			port = longport;
		}
		else if (!strcmp(key, "scopeid"))
		{
			scopeid = atoi(val);
		}
		else if (!strcmp(key, "address")) addr = strdup(val);
		else if (!strcmp(key, "canonname")) canonname = strdup(val);
		free(key);
		free(val);
	}

	if (family == PF_UNSPEC)
	{
		if (addr != NULL) free(addr);
		if (canonname != NULL) free(canonname);
		return NULL;
	}

	a = NULL;
	if (family == PF_INET)
	{
		inet_aton(addr, &a4);
		a = new_addrinfo_v4(flags, socktype, protocol, port, a4, scopeid, canonname);
	}
	else if (family == PF_INET6)
	{
		inet_pton(AF_INET6, addr, &a6);
		a = new_addrinfo_v6(flags, socktype, protocol, port, a6, scopeid, canonname);
	}

	if (addr != NULL) free(addr);
	if (canonname != NULL) free(canonname);

	return a;
}

static int
gai_make_query(const char *nodename, const char *servname, const struct addrinfo *hints, char *buf, uint32_t *len)
{
	int32_t numerichost, family, proto, socktype, canonname, passive, parallel;
	uint32_t na;
	XDR outxdr;
	char str[64], *cname;

	numerichost = 0;
	family = PF_UNSPEC;
	proto = IPPROTO_UNSPEC;
	socktype = SOCK_UNSPEC;
	canonname = 0;
	passive = 0;
	parallel = 0;
	cname = NULL;

	if (hints != NULL)
	{
		family = hints->ai_family;
		if (hints->ai_flags & AI_NUMERICHOST) numerichost = 1;
		if (hints->ai_flags & AI_CANONNAME) canonname = 1;
		if (hints->ai_flags & AI_PASSIVE) passive = 1;
		if (hints->ai_flags & AI_PARALLEL) parallel = 1;

		proto = hints->ai_protocol;
		if (hints->ai_socktype == SOCK_DGRAM)
		{
			socktype = SOCK_DGRAM;
			proto = IPPROTO_UDP;
		}
		if (hints->ai_socktype == SOCK_STREAM)
		{
			socktype = SOCK_STREAM;
			proto = IPPROTO_TCP;
		}
	}

	xdrmem_create(&outxdr, buf, *len, XDR_ENCODE);

	/* Attribute count */
	na = 0;
	if (nodename != NULL) na++;
	if (servname != NULL) na++;
	if (proto != IPPROTO_UNSPEC) na++;
	if (socktype != SOCK_UNSPEC) na++;
	if (family != PF_UNSPEC) na++;
	if (canonname != 0) na++;
	if (passive != 0) na++;
	if (parallel != 0) na++;
	if (numerichost != 0) na++;

	if (!xdr_int(&outxdr, (int32_t *)&na))
	{
		xdr_destroy(&outxdr);
		return EAI_SYSTEM;
	}

	if (nodename != NULL)
	{
		if (encode_kv(&outxdr, "name", nodename) != 0)
		{
			xdr_destroy(&outxdr);
			return EAI_SYSTEM;
		}
	}

	if (servname != NULL)
	{
		if (encode_kv(&outxdr, "service", servname) != 0)
		{
			xdr_destroy(&outxdr);
			return EAI_SYSTEM;
		}
	}

	if (proto != IPPROTO_UNSPEC)
	{
		snprintf(str, 64, "%u", proto);
		if (encode_kv(&outxdr, "protocol", str) != 0)
		{
			xdr_destroy(&outxdr);
			return EAI_SYSTEM;
		}
	}
	
	if (socktype != SOCK_UNSPEC)
	{
		snprintf(str, 64, "%u", socktype);
		if (encode_kv(&outxdr, "socktype", str) != 0)
		{
			xdr_destroy(&outxdr);
			return EAI_SYSTEM;
		}
	}
	
	if (family != PF_UNSPEC)
	{
		snprintf(str, 64, "%u", family);
		if (encode_kv(&outxdr, "family", str) != 0)
		{
			xdr_destroy(&outxdr);
			return EAI_SYSTEM;
		}
	}
	
	if (canonname != 0)
	{
		if (encode_kv(&outxdr, "canonname", "1") != 0)
		{
			xdr_destroy(&outxdr);
			return EAI_SYSTEM;
		}
	}
	
	if (passive != 0)
	{
		if (encode_kv(&outxdr, "passive", "1") != 0)
		{
			xdr_destroy(&outxdr);
			return EAI_SYSTEM;
		}
	}
	
	if (parallel != 0)
	{
		if (encode_kv(&outxdr, "parallel", "1") != 0)
		{
			xdr_destroy(&outxdr);
			return EAI_SYSTEM;
		}
	}
	
	if (numerichost != 0)
	{
		if (encode_kv(&outxdr, "numerichost", "1") != 0)
		{
			xdr_destroy(&outxdr);
			return EAI_SYSTEM;
		}
	}

	*len = xdr_getpos(&outxdr);

	xdr_destroy(&outxdr);
	
	return 0;
}

static int32_t
is_a_number(const char *s)
{
	int32_t i, len;

	if (s == NULL) return 0;

	len = strlen(s);
	for (i = 0; i < len; i++)
	{
		if (isdigit(s[i]) == 0) return 0;
	}

	return 1;
}

int
gai_files(const char *nodename, const char *servname, const struct addrinfo *hints, struct addrinfo **res)
{
	int32_t i, numericserv, numerichost, family, proto, wantv4, wantv6;
	int16_t port;
	struct servent *s;
	struct hostent *h;
	char *protoname, *loopv4, *loopv6;
	struct in_addr a4;
	struct in6_addr a6;
	struct addrinfo *a;

	numericserv = 0;
	if (servname != NULL) numericserv = is_a_number(servname);

	family = PF_UNSPEC;
	if (hints != NULL) family = hints->ai_family;

	numerichost = 0;

	if (nodename == NULL)
	{
		numerichost = 1;

		loopv4 = "127.0.0.1";
		loopv6 = "0:0:0:0:0:0:0:1";

		if ((hints != NULL) && ((hints->ai_flags & AI_PASSIVE) == 1))
		{
			loopv4 = "0.0.0.0";
			loopv6 = "0:0:0:0:0:0:0:0";
		}

		if ((family == PF_UNSPEC) || (family == PF_INET))
		{
			inet_pton(AF_INET, loopv4, &a4);
		}

		if ((family == PF_UNSPEC) || (family == PF_INET6))
		{
			inet_pton(AF_INET6, loopv6, &a6);
		}
	}
	else
	{
		if ((family == PF_UNSPEC) || (family == PF_INET))
		{
			numerichost = inet_pton(AF_INET, nodename, &a4);
			if ((numerichost == 1) && (family == PF_UNSPEC)) family = PF_INET;
		}

		if ((family == PF_UNSPEC) || (family == PF_INET6))
		{
			numerichost = inet_pton(AF_INET6, nodename, &a6);
			if ((numerichost == 1) && (family == PF_UNSPEC)) family = PF_INET6;
		}
	}

	wantv4 = 1;
	wantv6 = 1;
	if (family == PF_INET6) wantv4 = 0;
	if (family == PF_INET) wantv6 = 0;

	proto = IPPROTO_UNSPEC;
	protoname = NULL;

	if (hints != NULL)
	{
		proto = hints->ai_protocol;
		if (proto == IPPROTO_UNSPEC)
		{
			if (hints->ai_socktype == SOCK_DGRAM) proto = IPPROTO_UDP;
			else if (hints->ai_socktype == SOCK_STREAM) proto = IPPROTO_TCP;
		}
	}

	if (proto == IPPROTO_UDP) protoname = "udp";
	else if (proto == IPPROTO_TCP) protoname = "tcp";

	s = NULL;
	port = 0;

	if (numericserv != 0)
	{
		port = htons(atoi(servname));
	}
	else if (servname != NULL)
	{
		s = getservbyname(servname, protoname);
		if (s != NULL) port = s->s_port;
	}

	/* new_addrinfo_v4 and new_addrinfo_v6 expect port in host byte order */
	port = ntohs(port);

	if (numerichost != 0)
	{
		if (wantv4 == 1)
		{
			if ((proto == IPPROTO_UNSPEC) || (proto == IPPROTO_UDP))
			{
				a = new_addrinfo_v4(0, SOCK_DGRAM, IPPROTO_UDP, port, a4, 0, NULL);
				append_addrinfo(res, a);
			}

			if ((proto == IPPROTO_UNSPEC) || (proto == IPPROTO_TCP))
			{
				a = new_addrinfo_v4(0, SOCK_STREAM, IPPROTO_TCP, port, a4, 0, NULL);
				append_addrinfo(res, a);
			}
		}

		if (wantv6 == 1)
		{
			if ((proto == IPPROTO_UNSPEC) || (proto == IPPROTO_UDP))
			{
				a = new_addrinfo_v6(0, SOCK_DGRAM, IPPROTO_UDP, port, a6, 0, NULL);
				append_addrinfo(res, a);
			}

			if ((proto == IPPROTO_UNSPEC) || (proto == IPPROTO_TCP))
			{
				a = new_addrinfo_v6(0, SOCK_STREAM, IPPROTO_TCP, port, a6, 0, NULL);
				append_addrinfo(res, a);
			}
		}

		return 0;
	}

	if (wantv4 == 1)
	{
		h = gethostbyname(nodename);
		if (h == NULL) return 0;

		for (i = 0; h->h_addr_list[i] != 0; i++)
		{
			memmove((void *)&a4.s_addr, h->h_addr_list[i], h->h_length);

			if ((proto == IPPROTO_UNSPEC) || (proto == IPPROTO_UDP))
			{
				a = new_addrinfo_v4(0, SOCK_DGRAM, IPPROTO_UDP, port, a4, 0, NULL);
				append_addrinfo(res, a);
			}

			if ((proto == IPPROTO_UNSPEC) || (proto == IPPROTO_TCP))
			{
				a = new_addrinfo_v4(0, SOCK_STREAM, IPPROTO_TCP, port, a4, 0, NULL);
				append_addrinfo(res, a);
			}
		}
	}

	return 0;
}

static int
gai_lookupd(const char *nodename, const char *servname, const struct addrinfo *hints, struct addrinfo **res)
{
	uint32_t n, i, qlen, rlen;
	XDR inxdr;
	char qbuf[LU_QBUF_SIZE];
	char *rbuf;
	char *cname;
	mach_port_t server_port;
	kern_return_t status;
	struct addrinfo *a;

	server_port = MACH_PORT_NULL;
	if (_lu_running()) server_port = _lookupd_port(0);
	if (server_port == MACH_PORT_NULL)
	{
		/* lookupd isn't available - fall back to the flat files */
		return gai_files(nodename, servname, hints, res);
	}

	if (gai_proc < 0)
	{
		status = _lookup_link(server_port, "getaddrinfo", &gai_proc);
		if (status != KERN_SUCCESS) return EAI_SYSTEM;
	}

	qlen = LU_QBUF_SIZE;
	i = gai_make_query(nodename, servname, hints, qbuf, &qlen);
	if (i != 0) return EAI_SYSTEM;

	qlen /= BYTES_PER_XDR_UNIT;

	rbuf = NULL;

	status = _lookup_all(server_port, gai_proc, (unit *)qbuf, qlen, &rbuf, &rlen);
	if (status != KERN_SUCCESS) return EAI_NODATA;

	rlen *= BYTES_PER_XDR_UNIT;

	xdrmem_create(&inxdr, rbuf, rlen, XDR_DECODE);

	if (!xdr_int(&inxdr, (int32_t *)&n))
	{
		xdr_destroy(&inxdr);
		return EAI_SYSTEM;
	}

	cname = NULL;
	for (i = 0; i < n; i++)
	{
		a = gai_lookupd_process_dictionary(&inxdr);
		if ((cname == NULL) && (a->ai_canonname != NULL)) cname = a->ai_canonname;
		append_addrinfo(res, a);
	}

	xdr_destroy(&inxdr);
	if (rbuf != NULL) vm_deallocate(mach_task_self(), (vm_address_t)rbuf, rlen);

	if ((cname != NULL) && (res != NULL) && (res[0] != NULL) && (res[0]->ai_canonname == NULL))
	{
		res[0]->ai_canonname = strdup(cname);
	}

	return 0;
}

static int
gai_checkhints(const struct addrinfo *hints)
{
	if (hints == NULL) return 0;
	if (hints->ai_addrlen != 0) return EAI_BADHINTS;
	if (hints->ai_canonname != NULL) return EAI_BADHINTS;
	if (hints->ai_addr != NULL) return EAI_BADHINTS;
	if (hints->ai_next != NULL) return EAI_BADHINTS;

	/* Check for supported protocol family */
	if (gai_family_type_check(hints->ai_family) != 0) return EAI_FAMILY;

	/* Check for supported socket */
	if (gai_socket_type_check(hints->ai_socktype) != 0) return EAI_BADHINTS;

	/* Check for supported protocol */
	if (gai_protocol_type_check(hints->ai_protocol) != 0) return EAI_BADHINTS;

	/* Check that socket type is compatible with protocol */
	if (gai_socket_protocol_type_check(hints->ai_socktype, hints->ai_protocol) != 0) return EAI_BADHINTS;

	return 0;
}

int
getaddrinfo(const char * __restrict nodename, const char * __restrict servname, const struct addrinfo * __restrict hints, struct addrinfo ** __restrict res)
{
	int32_t status, nodenull, servnull;

	if (res == NULL) return 0;
	*res = NULL;

	/* Check input */
	nodenull = 0;
	if ((nodename == NULL) || (nodename[0] == '\0')) nodenull = 1;

	servnull = 0;
	if ((servname == NULL) || (servname[0] == '\0')) servnull = 1;
	
	if ((nodenull == 1) && (servnull == 1)) return EAI_NONAME;
	status = gai_checkhints(hints);
	if (status != 0) return status;

	if (nodenull == 1) status = gai_lookupd(NULL, servname, hints, res);
	else if (servnull == 1) status = gai_lookupd(nodename, NULL, hints, res);
	else status = gai_lookupd(nodename, servname, hints, res);

	if ((status == 0) && (*res == NULL)) status = EAI_NODATA;

	return status;
}

int32_t
getaddrinfo_async_start(mach_port_t *p, const char *nodename, const char *servname, const struct addrinfo *hints, getaddrinfo_async_callback callback, void *context)
{
	int32_t status;
	uint32_t i, qlen;
	char qbuf[LU_QBUF_SIZE];
	mach_port_t server_port;

	*p = MACH_PORT_NULL;

	if ((nodename == NULL) && (servname == NULL)) return EAI_NONAME;

	status = gai_checkhints(hints);
	if (status != 0) return EAI_BADHINTS;

	server_port = MACH_PORT_NULL;
	if (_lu_running()) server_port = _lookupd_port(0);
	if (server_port == MACH_PORT_NULL) return EAI_SYSTEM;

	if (gai_proc < 0)
	{
		status = _lookup_link(server_port, "getaddrinfo", &gai_proc);
		if (status != KERN_SUCCESS) return EAI_SYSTEM;
	}

	qlen = LU_QBUF_SIZE;
	i = gai_make_query(nodename, servname, hints, qbuf, &qlen);
	if (i != 0) return EAI_SYSTEM;

	qlen /= BYTES_PER_XDR_UNIT;

	return lu_async_start(p, gai_proc, qbuf, qlen, (void *)callback, context);
}

int32_t
getaddrinfo_async_send(mach_port_t *p, const char *nodename, const char *servname, const struct addrinfo *hints)
{
	return getaddrinfo_async_start(p, nodename, servname, hints, NULL, NULL);
}

static int
gai_extract_data(char *buf, uint32_t len, struct addrinfo **res)
{
	XDR xdr;
	uint32_t i, n;
	char *cname;
	struct addrinfo *a;

	*res = NULL;

	if (buf == NULL) return EAI_NODATA;
	if (len == 0) return EAI_NODATA;

	xdrmem_create(&xdr, buf, len, XDR_DECODE);

	if (!xdr_int(&xdr, (int32_t *)&n))
	{
		xdr_destroy(&xdr);
		return EAI_SYSTEM;
	}

	cname = NULL;
	for (i = 0; i < n; i++)
	{
		a = gai_lookupd_process_dictionary(&xdr);
		if (a == NULL) break;

		if ((cname == NULL) && (a->ai_canonname != NULL)) cname = a->ai_canonname;
		append_addrinfo(res, a);
	}

	xdr_destroy(&xdr);

	if ((cname != NULL) && (res != NULL) && (res[0] != NULL) && (res[0]->ai_canonname == NULL))
	{
		res[0]->ai_canonname = strdup(cname);
	}

	if (*res == NULL) return EAI_NODATA;
	return 0;
}

int32_t
getaddrinfo_async_receive(mach_port_t p, struct addrinfo **res)
{
	kern_return_t status;
	char *buf;
	uint32_t len;

	if (res == NULL) return 0;
	*res = NULL;

	buf = NULL;
	len = 0;

	status = lu_async_receive(p, &buf, &len);
	if (status < 0) return EAI_FAIL;

	status = gai_extract_data(buf, len, res);
	if (buf != NULL) vm_deallocate(mach_task_self(), (vm_address_t)buf, len);
	if (status != 0) return status;

	if (*res == NULL) return EAI_NODATA;

	return 0;
}

int32_t
getaddrinfo_async_handle_reply(void *msg)
{
	getaddrinfo_async_callback callback;
	void *context;
	char *buf;
	uint32_t len;
	int status;
	struct addrinfo *res;

	callback = (getaddrinfo_async_callback)NULL;
	context = NULL;
	buf = NULL;
	len = 0;
	res = NULL;

	status = lu_async_handle_reply(msg, &buf, &len, (void **)&callback, &context);
	if (status != KERN_SUCCESS)
	{
		if (status == MIG_REPLY_MISMATCH) return 0;
		if (callback != NULL) callback(EAI_NODATA, NULL, context);
		return EAI_NODATA;
	}

	status = gai_extract_data(buf, len, &res);
	if (buf != NULL) vm_deallocate(mach_task_self(), (vm_address_t)buf, len);
	if (status != 0)
	{
		if (callback != NULL) callback(status, NULL, context);
		return status;
	}

	if (res == NULL)
	{
		callback(EAI_NODATA, NULL, context);
		return EAI_NODATA;
	}

	callback(0, res, context);
	return 0;
}

/*
 * getnameinfo
 */
 
/*
 * getnameinfo support in lookupd
 * Input dict may contain the following
 *
 * ip_address: node address
 * ipv6_address: node address
 * port: service number
 * protocol: [tcp] | udp
 * fqdn: [1] | 0
 * numerichost: [0] | 1
 * name_required: [0] | 1
 * numericserv: [0] | 1
 *
 * Output dictionary may contain the following
 * All values are encoded as strings.
 *
 * name: char *
 * service: char *
 */

static int
gni_lookupd_process_dictionary(XDR *inxdr, char **host, char **serv)
{
	int32_t i, j, nkeys, nvals, status;
	char *key, **vals;

	if ((host == NULL) || (serv == NULL)) return EAI_SYSTEM;

	if (!xdr_int(inxdr, &nkeys)) return EAI_SYSTEM;

	*host = NULL;
	*serv = NULL;

	for (i = 0; i < nkeys; i++)
	{
		key = NULL;
		vals = NULL;
		nvals = 0;
		
		status = _lu_xdr_attribute(inxdr, &key, &vals, (uint32_t *)&nvals);
		if (status < 0) return EAI_SYSTEM;

		if (nvals == 0)
		{
			free(key);
			continue;
		}

		if ((*host == NULL) && (!strcmp("name", key)))
		{
			*host = vals[0];
			for (j = 1; j < nvals; j++) free(vals[j]);
		}

		else if ((*serv == NULL) && (!strcmp(key, "service")))
		{
			*serv = vals[0];
			for (j = 1; j < nvals; j++) free(vals[j]);
		}

		if (key != NULL) free(key);
		free(vals);
	}

	return 0;
}

static int
gni_make_query(const struct sockaddr *sa, size_t salen, int wanthost, int wantserv, int flags, char *buf, uint32_t *len)
{
	XDR outxdr;
	uint16_t port;
	char str[_LU_MAXLUSTRLEN], *key, ifname[IF_NAMESIZE];
	uint32_t a4, ifnum, offset, na, proto, fqdn, numerichost, numericserv, name_req, isll, issl;
	struct sockaddr_in6 *s6;

	if (sa == NULL) return EAI_FAIL;
	if (sa->sa_len != salen) return EAI_FAMILY;

	proto = IPPROTO_TCP;
	fqdn = 1;
	numerichost = 0;
	numericserv = 0;
	name_req = 0;
	isll = 0;
	issl = 0;

	offset = INET_NTOP_AF_INET_OFFSET;
	key = "ip_address";
	port = 0;

	if (sa->sa_family == PF_INET)
	{
		a4 = ntohl(((const struct sockaddr_in *)sa)->sin_addr.s_addr);
		if (IN_MULTICAST(a4) || IN_EXPERIMENTAL(a4)) flags |= NI_NUMERICHOST;
		a4 >>= IN_CLASSA_NSHIFT;
		if (a4 == 0) flags |= NI_NUMERICHOST;

		port = ((struct sockaddr_in *)sa)->sin_port;
	}
	else if (sa->sa_family == PF_INET6)
	{
		s6 = (struct sockaddr_in6 *)sa;
		switch (s6->sin6_addr.s6_addr[0])
		{
			case 0x00:
				if (IN6_IS_ADDR_V4MAPPED(&s6->sin6_addr))
				{
				}
				else if (IN6_IS_ADDR_LOOPBACK(&s6->sin6_addr))
				{
				}
				else
				{
					flags |= NI_NUMERICHOST;
				}
				break;
			default:
				if (IN6_IS_ADDR_LINKLOCAL(&s6->sin6_addr))
				{
					isll = 1;
				}
				else if (IN6_IS_ADDR_SITELOCAL(&s6->sin6_addr))
				{
					issl = 1;
				}
				else if (IN6_IS_ADDR_MULTICAST(&s6->sin6_addr))
				{
					flags |= NI_NUMERICHOST;
				}
				break;
		}

		if ((isll != 0) || (issl != 0))
		{
			ifnum = s6->sin6_addr.__u6_addr.__u6_addr16[1];
			if (ifnum == 0) ifnum = s6->sin6_scope_id;
			else if ((s6->sin6_scope_id != 0) && (ifnum != s6->sin6_scope_id)) return EAI_FAIL;
			
			s6->sin6_addr.__u6_addr.__u6_addr16[1] = 0;
			s6->sin6_scope_id = ifnum;
			if ((ifnum != 0) && (flags & NI_NUMERICHOST)) flags |= NI_WITHSCOPEID;
		}

		offset = INET_NTOP_AF_INET6_OFFSET;
		key = "ipv6_address";
		port = s6->sin6_port;
	}
	else
	{
		return EAI_FAMILY;
	}

	na = 0;

	if (wanthost != 0) na++;
	if (wantserv != 0) na++;

	if (flags & NI_NOFQDN)
	{
		fqdn = 0;
		na++;
	}

	if (flags & NI_NUMERICHOST)
	{
		numerichost = 1;
		na++;
	}

	if (flags & NI_NUMERICSERV)
	{
		numericserv = 1;
		na++;
	}

	if (flags & NI_NAMEREQD)
	{
		name_req = 1;
		na++;
	}

	if (flags & NI_DGRAM)
	{
		proto = IPPROTO_UDP;
		na++;
	}

	xdrmem_create(&outxdr, buf, *len, XDR_ENCODE);

	if (!xdr_int(&outxdr, (int32_t *)&na))
	{
		xdr_destroy(&outxdr);
		return EAI_SYSTEM;
	}

	if (wanthost != 0)
	{
		inet_ntop(sa->sa_family, (char *)(sa) + offset, str, _LU_MAXLUSTRLEN);

		if ((flags & NI_WITHSCOPEID) && (sa->sa_family == AF_INET6))
		{
			ifnum = ((struct sockaddr_in6 *)sa)->sin6_scope_id;
			if (if_indextoname(ifnum, ifname) != NULL)
			{
				strcat(str, "%");
				strcat(str, ifname);
			}
		}

		if (encode_kv(&outxdr, key, str) != 0)
		{
			xdr_destroy(&outxdr);
			return EAI_SYSTEM;
		}
	}

	if (wantserv != 0)
	{
		snprintf(str, _LU_MAXLUSTRLEN, "%hu", port);
		if (encode_kv(&outxdr, "port", str) != 0)
		{
			xdr_destroy(&outxdr);
			return EAI_SYSTEM;
		}
	}

	if (proto == IPPROTO_UDP)
	{
		if (encode_kv(&outxdr, "protocol", "udp") != 0)
		{
			xdr_destroy(&outxdr);
			return EAI_SYSTEM;
		}
	}

	if (fqdn == 0)
	{
		if (encode_kv(&outxdr, "fqdn", "0") != 0)
		{
			xdr_destroy(&outxdr);
			return EAI_SYSTEM;
		}
	}

	if (numerichost == 1)
	{
		if (encode_kv(&outxdr, "numerichost", "1") != 0)
		{
			xdr_destroy(&outxdr);
			return EAI_SYSTEM;
		}
	}

	if (numericserv == 1)
	{
		if (encode_kv(&outxdr, "numericserv", "1") != 0)
		{
			xdr_destroy(&outxdr);
			return EAI_SYSTEM;
		}
	}

	if (name_req == 1)
	{
		if (encode_kv(&outxdr, "name_required", "1") != 0)
		{
			xdr_destroy(&outxdr);
			return EAI_SYSTEM;
		}
	}

	*len = xdr_getpos(&outxdr);

	xdr_destroy(&outxdr);
	
	return 0;
}

int
getnameinfo(const struct sockaddr * __restrict sa, socklen_t salen, char * __restrict host, socklen_t hostlen, char * __restrict serv, socklen_t servlen, int flags)
{
	uint32_t n, i, qlen, rlen;
	uint32_t ifnum;
	int wanth, wants, isll, issl;
	XDR inxdr;
	char qbuf[LU_QBUF_SIZE], ifname[IF_NAMESIZE];
	char *rbuf, *hval, *sval;
	mach_port_t server_port;
	kern_return_t status;
	struct sockaddr_in *s4;
	struct sockaddr_in6 *s6;

	/* Check input */
	if (sa == NULL) return EAI_FAIL;

	isll = 0;
	issl = 0;
	ifnum = 0;

	if (sa->sa_family == AF_INET6)
	{
		s6 = (struct sockaddr_in6 *)sa;

		if (IN6_IS_ADDR_LINKLOCAL(&s6->sin6_addr)) isll = 1;
		if (IN6_IS_ADDR_SITELOCAL(&s6->sin6_addr)) issl = 1;

		/*
		 * Link-local and site-local IPv6 addresses may have a scope id 
		 * in s6->sin6_addr.__u6_addr.__u6_addr16[1] as well as in s6->sin6_scope_id.
		 * If they are both non-zero, they must be equal.
		 * We zero s6->sin6_addr.__u6_addr.__u6_addr16[1] and set s6->sin6_scope_id.
		 */
		if ((isll != 0) || (issl != 0))
		{
			ifnum = s6->sin6_addr.__u6_addr.__u6_addr16[1];
			if (ifnum == 0) ifnum = s6->sin6_scope_id;
			else if ((s6->sin6_scope_id != 0) && (ifnum != s6->sin6_scope_id)) return EAI_FAIL;

			s6->sin6_addr.__u6_addr.__u6_addr16[1] = 0;
			s6->sin6_scope_id = ifnum;
		}

		/* V4 mapped and compat addresses are converted to plain V4 */
		if ((IN6_IS_ADDR_V4MAPPED(&s6->sin6_addr)) || (IN6_IS_ADDR_V4COMPAT(&s6->sin6_addr)))
		{
			s4 = (struct sockaddr_in *)calloc(1, sizeof(struct sockaddr_in));
			s4->sin_len = sizeof(struct sockaddr_in);
			s4->sin_family = AF_INET;
			s4->sin_port = s6->sin6_port;
			memcpy(&(s4->sin_addr.s_addr), &(s6->sin6_addr.s6_addr[12]), 4);

			i = getnameinfo((const struct sockaddr *)s4, s4->sin_len, host, hostlen, serv, servlen, flags);
			free(s4);
			return i;
		}
	}
		
	wanth = 0;
	if ((host != NULL) && (hostlen != 0)) wanth = 1;

	wants = 0;
	if ((serv != NULL) && (servlen != 0)) wants = 1;

	if ((wanth == 0) && (wants == 0)) return 0;

	/*
	 * Special cases handled by the library
	 */
	if ((wanth == 1) && (flags & NI_NUMERICHOST))
	{
		i = INET_NTOP_AF_INET_OFFSET;
		if (sa->sa_family == PF_INET6) i = INET_NTOP_AF_INET6_OFFSET;
		if (inet_ntop(sa->sa_family, (char *)(sa) + i, host, hostlen) == NULL) return EAI_FAIL;

		if (((isll != 0) || (issl != 0)) && (ifnum != 0))
		{
			/* append interface name */
			if (if_indextoname(ifnum, ifname) != NULL)
			{
				strcat(host, "%");
				strcat(host, ifname);
			}
		}

		if (wants == 0) return 0;
	}

	if ((wants == 1) && (flags & NI_NUMERICSERV))
	{
		if (sa->sa_family == PF_INET)
		{
			s4 = (struct sockaddr_in *)sa;
			n = snprintf(serv, servlen, "%hu", ntohs(s4->sin_port));
			if (n >= servlen) return EAI_FAIL;
		}
		else if (sa->sa_family == PF_INET6)
		{
			s6 = (struct sockaddr_in6 *)sa;
			n = snprintf(serv, servlen, "%hu", ntohs(s6->sin6_port));
			if (n >= servlen) return EAI_FAIL;
		}
		else return EAI_FAMILY;

		if (wanth == 0) return 0;
	}

	if ((wanth == 1) && (flags & NI_NUMERICHOST) && (wants == 1) && (flags & NI_NUMERICSERV)) return 0;

	/*
	 * Ask lookupd
	 */
	server_port = MACH_PORT_NULL;
	if (_lu_running()) server_port = _lookupd_port(0);
	if (server_port == MACH_PORT_NULL) return EAI_SYSTEM;

	if (gni_proc < 0)
	{
		status = _lookup_link(server_port, "getnameinfo", &gni_proc);
		if (status != KERN_SUCCESS) return EAI_SYSTEM;
	}

	qlen = LU_QBUF_SIZE;
	i = gni_make_query(sa, salen, wanth, wants, flags, qbuf, &qlen);
	if (i != 0) return i;

	qlen /= BYTES_PER_XDR_UNIT;

	rbuf = NULL;

	status = _lookup_all(server_port, gni_proc, (unit *)qbuf, qlen, &rbuf, &rlen);
	if (status != KERN_SUCCESS) return EAI_NONAME;

	rlen *= BYTES_PER_XDR_UNIT;

	xdrmem_create(&inxdr, rbuf, rlen, XDR_DECODE);

	if (!xdr_int(&inxdr, (int32_t *)&n))
	{
		xdr_destroy(&inxdr);
		return EAI_SYSTEM;
	}

	if (n != 1)
	{
		xdr_destroy(&inxdr);
		return EAI_NONAME;
	}

	hval = NULL;
	sval = NULL;

	i = gni_lookupd_process_dictionary(&inxdr, &hval, &sval);

	xdr_destroy(&inxdr);
	if (rbuf != NULL) vm_deallocate(mach_task_self(), (vm_address_t)rbuf, rlen);

	if (i != 0) return i;

	i = 0;
	if (hval != NULL) i = strlen(hval) + 1;
	if ((host != NULL) && (hostlen != 0) && (i != 0))
	{
		if (i > hostlen) return EAI_FAIL;
		memcpy(host, hval, i);
		free(hval);
	}

	i = 0;
	if (sval != NULL) i = strlen(sval) + 1;
	if ((serv != NULL) && (servlen != 0) && (i != 0))
	{
		if (i > servlen) return EAI_FAIL;
		memcpy(serv, sval, i);
		free(sval);
	}

	return 0;
}

int32_t
getnameinfo_async_start(mach_port_t *p, const struct sockaddr *sa, size_t salen, int flags, getnameinfo_async_callback callback, void *context)
{
	uint32_t i, qlen;
	char qbuf[LU_QBUF_SIZE];
	mach_port_t server_port;
	kern_return_t status;

	/* Check input */
	if (sa == NULL) return EAI_FAIL;

	server_port = MACH_PORT_NULL;
	if (_lu_running()) server_port = _lookupd_port(0);
	if (server_port == MACH_PORT_NULL) return EAI_SYSTEM;

	if (gni_proc < 0)
	{
		status = _lookup_link(server_port, "getnameinfo", &gni_proc);
		if (status != KERN_SUCCESS) return EAI_SYSTEM;
	}

	qlen = LU_QBUF_SIZE;
	i = gni_make_query(sa, salen, 1, 1, flags, qbuf, &qlen);
	if (i != 0) return i;

	qlen /= BYTES_PER_XDR_UNIT;

	return lu_async_start(p, gni_proc, qbuf, qlen, (void *)callback, context);
}

int32_t
getnameinfo_async_send(mach_port_t *p, const struct sockaddr *sa, size_t salen, int flags)
{
	return getnameinfo_async_start(p, sa, salen, flags, NULL, NULL);
}

static int
gni_extract_data(char *buf, uint32_t len, char **host, char **serv)
{
	XDR xdr;
	uint32_t n;
	int i;

	*host = NULL;
	*serv = NULL;

	if (buf == NULL) return EAI_NODATA;
	if (len == 0) return EAI_NODATA;

	xdrmem_create(&xdr, buf, len, XDR_DECODE);

	if (!xdr_int(&xdr, (int32_t *)&n))
	{
		xdr_destroy(&xdr);
		return EAI_SYSTEM;
	}

	if (n != 1)
	{
		xdr_destroy(&xdr);
		return EAI_NONAME;
	}

	i = gni_lookupd_process_dictionary(&xdr, host, serv);
	xdr_destroy(&xdr);
	return i;
}

int32_t
getnameinfo_async_receive(mach_port_t p, char **host, char **serv)
{
	kern_return_t status;
	char *buf;
	uint32_t len;

	buf = NULL;
	len = 0;

	status = lu_async_receive(p, &buf, &len);
	if (status < 0) return EAI_FAIL;

	status = gni_extract_data(buf, len, host, serv);
	if (buf != NULL) vm_deallocate(mach_task_self(), (vm_address_t)buf, len);
	if (status != 0) return status;

	return 0;
}

int32_t
getnameinfo_async_handle_reply(void *msg)
{
	getnameinfo_async_callback callback;
	void *context;
	char *buf, *hval, *sval;
	uint32_t len;
	int status;

	callback = (getnameinfo_async_callback)NULL;
	context = NULL;
	buf = NULL;
	len = 0;

	status = lu_async_handle_reply(msg, &buf, &len, (void **)&callback, &context);
	if (status != KERN_SUCCESS)
	{
		if (status == MIG_REPLY_MISMATCH) return 0;
		if (callback != NULL) callback(EAI_NONAME, NULL, NULL, context);
		return EAI_NONAME;
	}

	hval = NULL;
	sval = NULL;

	status = gni_extract_data(buf, len, &hval, &sval);
	if (buf != NULL) vm_deallocate(mach_task_self(), (vm_address_t)buf, len);
	if (status != 0)
	{
		if (callback != NULL) callback(status, NULL, NULL, context);
		return status;
	}

	callback(0, hval, sval, context);
	return 0;
}
