/*++
/* NAME
/*	myaddrinfo 3
/* SUMMARY
/*	addrinfo encapsulation and emulation
/* SYNOPSIS
/*	#include <myaddrinfo.h>
/*
/*	#define MAI_V4ADDR_BITS ...
/*	#define MAI_V6ADDR_BITS ...
/*	#define MAI_V4ADDR_BYTES ...
/*	#define MAI_V6ADDR_BYTES ...
/*
/*	typedef struct { char buf[....]; } MAI_HOSTNAME_STR;
/*	typedef struct { char buf[....]; } MAI_HOSTADDR_STR;
/*	typedef struct { char buf[....]; } MAI_SERVNAME_STR;
/*	typedef struct { char buf[....]; } MAI_SERVPORT_STR;
/*
/*	int	hostname_to_sockaddr(hostname, service, socktype, result)
/*	const char *hostname;
/*	const char *service;
/*	int	socktype;
/*	struct addrinfo **result;
/*
/*	int	hostname_to_sockaddr_pf(hostname, pf, service, socktype, result)
/*	const char *hostname;
/*	int	pf;
/*	const char *service;
/*	int	socktype;
/*	struct addrinfo **result;
/*
/*	int	hostaddr_to_sockaddr(hostaddr, service, socktype, result)
/*	const char *hostaddr;
/*	const char *service;
/*	int	socktype;
/*	struct addrinfo **result;
/*
/*	int	sockaddr_to_hostaddr(sa, salen, hostaddr, portnum, socktype)
/*	const struct sockaddr *sa;
/*	SOCKADDR_SIZE salen;
/*	MAI_HOSTADDR_STR *hostaddr;
/*	MAI_SERVPORT_STR *portnum;
/*	int	socktype;
/*
/*	int	sockaddr_to_hostname(sa, salen, hostname, service, socktype)
/*	const struct sockaddr *sa;
/*	SOCKADDR_SIZE salen;
/*	MAI_HOSTNAME_STR *hostname;
/*	MAI_SERVNAME_STR *service;
/*	int	socktype;
/*
/*	const char *MAI_STRERROR(error)
/*	int	error;
/* DESCRIPTION
/*	This module provides a simplified user interface to the
/*	getaddrinfo(3) and getnameinfo(3) routines (which provide
/*	a unified interface to manipulate IPv4 and IPv6 socket
/*	address structures).
/*
/*	On systems without getaddrinfo(3) and getnameinfo(3) support,
/*	emulation for IPv4 only can be enabled by defining
/*	EMULATE_IPV4_ADDRINFO.
/*
/*	hostname_to_sockaddr() looks up the binary addresses for
/*	the specified symbolic hostname or numeric address.  The
/*	result should be destroyed with freeaddrinfo(). A null host
/*	pointer converts to the null host address.
/*
/*	hostname_to_sockaddr_pf() is an extended interface that
/*	provides a protocol family override.
/*
/*	hostaddr_to_sockaddr() converts a printable network address
/*	into the corresponding binary form.  The result should be
/*	destroyed with freeaddrinfo(). A null host pointer converts
/*	to the null host address.
/*
/*	sockaddr_to_hostaddr() converts a binary network address
/*	into printable form. The result buffers should be large
/*	enough to hold the printable address or port including the
/*	null terminator.
/*	This function strips off the IPv6 datalink suffix.
/*
/*	sockaddr_to_hostname() converts a binary network address
/*	into a hostname or service.  The result buffer should be
/*	large enough to hold the hostname or service including the
/*	null terminator. This routine rejects malformed hostnames
/*	or numeric hostnames and pretends that the lookup failed.
/*
/*	MAI_STRERROR() is an unsafe macro (it evaluates the argument
/*	multiple times) that invokes strerror() or gai_strerror()
/*	as appropriate.
/*
/*	This module exports the following constants that should be
/*	user for storage allocation of name or address information:
/* .IP MAI_V4ADDR_BITS
/* .IP MAI_V6ADDR_BITS
/* .IP MAI_V4ADDR_BYTES
/* .IP MAI_V6ADDR_BYTES
/*	The number of bits or bytes needed to store a binary
/*	IPv4 or IPv6 network address.
/* .PP
/*	The types MAI_HOST{NAME,ADDR}_STR and MAI_SERV{NAME,PORT}_STR
/*	implement buffers for the storage of the string representations
/*	of symbolic or numerical hosts or services. Do not use
/*	buffer types other than the ones that are expected here,
/*	or things will blow up with buffer overflow problems.
/*
/*	Arguments:
/* .IP hostname
/*	On input to hostname_to_sockaddr(), a numeric or symbolic
/*	hostname, or a null pointer (meaning the wild-card listen
/*	address).  On output from sockaddr_to_hostname(), storage
/*	for the result hostname, or a null pointer.
/* .IP pf
/*	Protocol type: PF_UNSPEC (meaning: use any protocol that is
/*	available), PF_INET, or PF_INET6.  This argument is ignored
/*	in EMULATE_IPV4_ADDRINFO mode.
/* .IP hostaddr
/*	On input to hostaddr_to_sockaddr(), a numeric hostname,
/*	or a null pointer (meaning the wild-card listen address).
/*	On output from sockaddr_to_hostaddr(), storage for the
/*	result hostaddress, or a null pointer.
/* .IP service
/*	On input to hostname/addr_to_sockaddr(), a numeric or
/*	symbolic service name, or a null pointer in which case the
/*	socktype argument is ignored.  On output from
/*	sockaddr_to_hostname/addr(), storage for the result service
/*	name, or a null pointer.
/* .IP portnum
/*	Storage for the result service port number, or a null pointer.
/* .IP socktype
/*	Socket type: SOCK_STREAM, SOCK_DGRAM, etc. This argument is
/*	ignored when no service or port are specified.
/* .IP sa
/*	Protocol-independent socket address structure.
/* .IP salen
/*	Protocol-dependent socket address structure size in bytes.
/* SEE ALSO
/*	getaddrinfo(3), getnameinfo(3), freeaddrinfo(3), gai_strerror(3)
/* DIAGNOSTICS
/*	All routines either return 0 upon success, or an error code
/*	that is compatible with gai_strerror().
/*
/*	On systems where addrinfo support is emulated by Postfix,
/*	some out-of-memory errors are not reported to the caller,
/*	but are handled by mymalloc().
/* BUGS
/*	The IPv4-only emulation code does not support requests that
/*	specify a service but no socket type. It returns an error
/*	indication, instead of enumerating all the possible answers.
/*
/*	The hostname/addr_to_sockaddr() routines should accept a
/*	list of address families that the caller is interested in,
/*	and they should return only information of those types.
/*
/*	Unfortunately, it is not possible to remove unwanted address
/*	family results from hostname_to_sockaddr(), because we
/*	don't know how the system library routine getaddrinfo()
/*	allocates memory.  For example, getaddrinfo() could save
/*	space by referencing the same string object from multiple
/*	addrinfo structures; or it could allocate a string object
/*	and the addrinfo structure as one memory block.
/*
/*	We could get around this by copying getaddrinfo() results
/*	to our own private data structures, but that would only
/*	make an already expensive API even more expensive.
/*
/*	A better workaround is to return a vector of addrinfo
/*	pointers to the elements that contain only the elements
/*	that the caller is interested in. The pointer to the
/*	original getaddrinfo() result can be hidden at the end
/*	after the null terminator, or before the first element.
/* LICENSE
/* .ad
/* .fi
/*	The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/*	Wietse Venema
/*	IBM T.J. Watson Research
/*	P.O. Box 704
/*	Yorktown Heights, NY 10598, USA
/*
/*	Wietse Venema
/*	Google, Inc.
/*	111 8th Avenue
/*	New York, NY 10011, USA
/*--*/

/* System library. */

#include <sys_defs.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>			/* sprintf() */

/* Utility library. */

#include <mymalloc.h>
#include <valid_hostname.h>
#include <sock_addr.h>
#include <stringops.h>
#include <msg.h>
#include <inet_proto.h>
#include <myaddrinfo.h>
#include <split_at.h>
#include <known_tcp_ports.h>

/* Application-specific. */

 /*
  * Use an old trick to save some space: allocate space for two objects in
  * one. In Postfix we often use this trick for structures that have an array
  * of things at the end.
  */
struct ipv4addrinfo {
    struct addrinfo info;
    struct sockaddr_in sin;
};

 /*
  * When we're not interested in service ports, we must pick a socket type
  * otherwise getaddrinfo() will give us duplicate results: one set for TCP,
  * and another set for UDP. For consistency, we'll use the same default
  * socket type for the results from emulation mode.
  */
#define MAI_SOCKTYPE	SOCK_STREAM	/* getaddrinfo() query */

#ifdef EMULATE_IPV4_ADDRINFO

/* clone_ipv4addrinfo - clone ipv4addrinfo structure */

static struct ipv4addrinfo *clone_ipv4addrinfo(struct ipv4addrinfo * tp)
{
    struct ipv4addrinfo *ip;

    ip = (struct ipv4addrinfo *) mymalloc(sizeof(*ip));
    *ip = *tp;
    ip->info.ai_addr = (struct sockaddr *) &(ip->sin);
    return (ip);
}

/* init_ipv4addrinfo - initialize an ipv4addrinfo structure */

static void init_ipv4addrinfo(struct ipv4addrinfo * ip, int socktype)
{

    /*
     * Portability: null pointers aren't necessarily all-zero bits, so we
     * make explicit assignments to all the pointers that we're aware of.
     */
    memset((void *) ip, 0, sizeof(*ip));
    ip->info.ai_family = PF_INET;
    ip->info.ai_socktype = socktype;
    ip->info.ai_protocol = 0;			/* XXX */
    ip->info.ai_addrlen = sizeof(ip->sin);
    ip->info.ai_canonname = 0;
    ip->info.ai_addr = (struct sockaddr *) &(ip->sin);
    ip->info.ai_next = 0;
    ip->sin.sin_family = AF_INET;
#ifdef HAS_SA_LEN
    ip->sin.sin_len = sizeof(ip->sin);
#endif
}

/* find_service - translate numeric or symbolic service name */

static int find_service(const char *service, int socktype)
{
    struct servent *sp;
    const char *proto;
    unsigned port;

    service = filter_known_tcp_port(service);
    if (alldig(service)) {
	port = atoi(service);
	return (port < 65536 ? htons(port) : -1);
    }
    if (socktype == SOCK_STREAM) {
	proto = "tcp";
    } else if (socktype == SOCK_DGRAM) {
	proto = "udp";
    } else {
	return (-1);
    }
    if ((sp = getservbyname(service, proto)) != 0) {
	return (sp->s_port);
    } else {
	return (-1);
    }
}

#endif

/* hostname_to_sockaddr_pf - hostname to binary address form */

int     hostname_to_sockaddr_pf(const char *hostname, int pf,
				        const char *service, int socktype,
				        struct addrinfo ** res)
{
#ifdef EMULATE_IPV4_ADDRINFO

    /*
     * Emulated getaddrinfo(3) version.
     */
    static struct ipv4addrinfo template;
    struct ipv4addrinfo *ip;
    struct ipv4addrinfo *prev;
    struct in_addr addr;
    struct hostent *hp;
    char  **name_list;
    int     port;

    /*
     * Validate the service.
     */
    if (service) {
	if ((port = find_service(service, socktype)) < 0)
	    return (EAI_SERVICE);
    } else {
	port = 0;
	socktype = MAI_SOCKTYPE;
    }

    /*
     * No host means INADDR_ANY.
     */
    if (hostname == 0) {
	ip = (struct ipv4addrinfo *) mymalloc(sizeof(*ip));
	init_ipv4addrinfo(ip, socktype);
	ip->sin.sin_addr.s_addr = INADDR_ANY;
	ip->sin.sin_port = port;
	*res = &(ip->info);
	return (0);
    }

    /*
     * Numeric host.
     */
    if (inet_pton(AF_INET, hostname, (void *) &addr) == 1) {
	ip = (struct ipv4addrinfo *) mymalloc(sizeof(*ip));
	init_ipv4addrinfo(ip, socktype);
	ip->sin.sin_addr = addr;
	ip->sin.sin_port = port;
	*res = &(ip->info);
	return (0);
    }

    /*
     * Look up the IPv4 address list.
     */
    if ((hp = gethostbyname(hostname)) == 0)
	return (h_errno == TRY_AGAIN ? EAI_AGAIN : EAI_NODATA);
    if (hp->h_addrtype != AF_INET
	|| hp->h_length != sizeof(template.sin.sin_addr))
	return (EAI_NODATA);

    /*
     * Initialize the result template.
     */
    if (template.info.ai_addrlen == 0)
	init_ipv4addrinfo(&template, socktype);

    /*
     * Copy the address information into an addrinfo structure.
     */
    prev = &template;
    for (name_list = hp->h_addr_list; name_list[0]; name_list++) {
	ip = clone_ipv4addrinfo(prev);
	ip->sin.sin_addr = IN_ADDR(name_list[0]);
	ip->sin.sin_port = port;
	if (prev == &template)
	    *res = &(ip->info);
	else
	    prev->info.ai_next = &(ip->info);
	prev = ip;
    }
    return (0);
#else

    /*
     * Native getaddrinfo(3) version.
     * 
     * XXX Wild-card listener issues.
     * 
     * With most IPv4 plus IPv6 systems, an IPv6 wild-card listener also listens
     * on the IPv4 wild-card address. Connections from IPv4 clients appear as
     * IPv4-in-IPv6 addresses; when Postfix support for IPv4 is turned on,
     * Postfix automatically maps these embedded addresses to their original
     * IPv4 form. So everything seems to be fine.
     * 
     * However, some applications prefer to use separate listener sockets for
     * IPv4 and IPv6. The Postfix IPv6 patch provided such an example. And
     * this is where things become tricky. On many systems the IPv6 and IPv4
     * wild-card listeners cannot coexist. When one is already active, the
     * other fails with EADDRINUSE. Solaris 9, however, will automagically
     * "do the right thing" and allow both listeners to coexist.
     * 
     * Recent systems have the IPV6_V6ONLY feature (RFC 3493), which tells the
     * system that we really mean IPv6 when we say IPv6. This allows us to
     * set up separate wild-card listener sockets for IPv4 and IPv6. So
     * everything seems to be fine again.
     * 
     * The following workaround disables the wild-card IPv4 listener when
     * IPV6_V6ONLY is unavailable. This is necessary for some Linux versions,
     * but is not needed for Solaris 9 (which allows IPv4 and IPv6 wild-card
     * listeners to coexist). Solaris 10 beta already has IPV6_V6ONLY.
     * 
     * XXX This workaround obviously breaks if we want to support protocols in
     * addition to IPv6 and IPv4, but it is needed only until IPv6
     * implementations catch up with RFC 3493. A nicer fix is to filter the
     * getaddrinfo() result, and to return a vector of addrinfo pointers to
     * only those types of elements that the caller has expressed interested
     * in.
     * 
     * XXX Vanilla AIX 5.1 getaddrinfo() does not support a null hostname with
     * AI_PASSIVE. And since we don't know how getaddrinfo() manages its
     * memory we can't bypass it for this special case, or freeaddrinfo()
     * might blow up. Instead we turn off IPV6_V6ONLY in inet_listen(), and
     * supply a protocol-dependent hard-coded string value to getaddrinfo()
     * below, so that it will convert into the appropriate wild-card address.
     * 
     * XXX AIX 5.[1-3] getaddrinfo() may return a non-null port when a null
     * service argument is specified.
     */
    struct addrinfo hints;
    int     err;

    memset((void *) &hints, 0, sizeof(hints));
    hints.ai_family = (pf != PF_UNSPEC) ? pf : inet_proto_info()->ai_family;
    hints.ai_socktype = service ? socktype : MAI_SOCKTYPE;
    if (!hostname) {
	hints.ai_flags = AI_PASSIVE;
#if !defined(IPV6_V6ONLY) || defined(BROKEN_AI_PASSIVE_NULL_HOST)
	switch (hints.ai_family) {
	case PF_UNSPEC:
	    hints.ai_family = PF_INET6;
#ifdef BROKEN_AI_PASSIVE_NULL_HOST
	case PF_INET6:
	    hostname = "::";
	    break;
	case PF_INET:
	    hostname = "0.0.0.0";
	    break;
#endif
	}
#endif
    }
    if (service) {
	service = filter_known_tcp_port(service);
	if (alldig(service))
	    hints.ai_flags |= AI_NUMERICSERV;
    }
    err = getaddrinfo(hostname, service, &hints, res);
#if defined(BROKEN_AI_NULL_SERVICE)
    if (service == 0 && err == 0) {
	struct addrinfo *r;
	unsigned short *portp;

	for (r = *res; r != 0; r = r->ai_next)
	    if (*(portp = SOCK_ADDR_PORTP(r->ai_addr)) != 0)
		*portp = 0;
    }
#endif
    return (err);
#endif
}

/* hostaddr_to_sockaddr - printable address to binary address form */

int     hostaddr_to_sockaddr(const char *hostaddr, const char *service,
			             int socktype, struct addrinfo ** res)
{
#ifdef EMULATE_IPV4_ADDRINFO

    /*
     * Emulated getaddrinfo(3) version.
     */
    struct ipv4addrinfo *ip;
    struct in_addr addr;
    int     port;

    /*
     * Validate the service.
     */
    if (service) {
	if ((port = find_service(service, socktype)) < 0)
	    return (EAI_SERVICE);
    } else {
	port = 0;
	socktype = MAI_SOCKTYPE;
    }

    /*
     * No host means INADDR_ANY.
     */
    if (hostaddr == 0) {
	ip = (struct ipv4addrinfo *) mymalloc(sizeof(*ip));
	init_ipv4addrinfo(ip, socktype);
	ip->sin.sin_addr.s_addr = INADDR_ANY;
	ip->sin.sin_port = port;
	*res = &(ip->info);
	return (0);
    }

    /*
     * Deal with bad address forms.
     */
    switch (inet_pton(AF_INET, hostaddr, (void *) &addr)) {
    case 1:					/* Success */
	break;
    default:					/* Unparsable */
	return (EAI_NONAME);
    case -1:					/* See errno */
	return (EAI_SYSTEM);
    }

    /*
     * Initialize the result structure.
     */
    ip = (struct ipv4addrinfo *) mymalloc(sizeof(*ip));
    init_ipv4addrinfo(ip, socktype);

    /*
     * And copy the result.
     */
    ip->sin.sin_addr = addr;
    ip->sin.sin_port = port;
    *res = &(ip->info);

    return (0);
#else

    /*
     * Native getaddrinfo(3) version. See comments in hostname_to_sockaddr().
     * 
     * XXX Vanilla AIX 5.1 getaddrinfo() returns multiple results when
     * converting a printable ipv4 or ipv6 address to socket address with
     * ai_family=PF_UNSPEC, ai_flags=AI_NUMERICHOST, ai_socktype=SOCK_STREAM,
     * ai_protocol=0 or IPPROTO_TCP, and service=0. The workaround is to
     * ignore all but the first result.
     * 
     * XXX AIX 5.[1-3] getaddrinfo() may return a non-null port when a null
     * service argument is specified.
     */
    struct addrinfo hints;
    int     err;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = inet_proto_info()->ai_family;
    hints.ai_socktype = service ? socktype : MAI_SOCKTYPE;
    hints.ai_flags = AI_NUMERICHOST;
    if (!hostaddr) {
	hints.ai_flags |= AI_PASSIVE;
#if !defined(IPV6_V6ONLY) || defined(BROKEN_AI_PASSIVE_NULL_HOST)
	switch (hints.ai_family) {
	case PF_UNSPEC:
	    hints.ai_family = PF_INET6;
#ifdef BROKEN_AI_PASSIVE_NULL_HOST
	case PF_INET6:
	    hostaddr = "::";
	    break;
	case PF_INET:
	    hostaddr = "0.0.0.0";
	    break;
#endif
	}
#endif
    }
    if (service) {
	service = filter_known_tcp_port(service);
	if (alldig(service))
	    hints.ai_flags |= AI_NUMERICSERV;
    }
    err = getaddrinfo(hostaddr, service, &hints, res);
#if defined(BROKEN_AI_NULL_SERVICE)
    if (service == 0 && err == 0) {
	struct addrinfo *r;
	unsigned short *portp;

	for (r = *res; r != 0; r = r->ai_next)
	    if (*(portp = SOCK_ADDR_PORTP(r->ai_addr)) != 0)
		*portp = 0;
    }
#endif
    return (err);
#endif
}

/* sockaddr_to_hostaddr - binary address to printable address form */

int     sockaddr_to_hostaddr(const struct sockaddr *sa, SOCKADDR_SIZE salen,
			             MAI_HOSTADDR_STR *hostaddr,
			             MAI_SERVPORT_STR *portnum,
			             int unused_socktype)
{
#ifdef EMULATE_IPV4_ADDRINFO
    char    portbuf[sizeof("65535")];
    ssize_t len;

    /*
     * Emulated getnameinfo(3) version. The buffer length includes the space
     * for the null terminator.
     */
    if (sa->sa_family != AF_INET) {
	errno = EAFNOSUPPORT;
	return (EAI_SYSTEM);
    }
    if (hostaddr != 0) {
	if (inet_ntop(AF_INET, (void *) &(SOCK_ADDR_IN_ADDR(sa)),
		      hostaddr->buf, sizeof(hostaddr->buf)) == 0)
	    return (EAI_SYSTEM);
    }
    if (portnum != 0) {
	sprintf(portbuf, "%d", ntohs(SOCK_ADDR_IN_PORT(sa)) & 0xffff);
	if ((len = strlen(portbuf)) >= sizeof(portnum->buf)) {
	    errno = ENOSPC;
	    return (EAI_SYSTEM);
	}
	memcpy(portnum->buf, portbuf, len + 1);
    }
    return (0);
#else
    int     ret;

    /*
     * Native getnameinfo(3) version.
     */
    ret = getnameinfo(sa, salen,
		      hostaddr ? hostaddr->buf : (char *) 0,
		      hostaddr ? sizeof(hostaddr->buf) : 0,
		      portnum ? portnum->buf : (char *) 0,
		      portnum ? sizeof(portnum->buf) : 0,
		      NI_NUMERICHOST | NI_NUMERICSERV);
    if (hostaddr != 0 && ret == 0 && sa->sa_family == AF_INET6)
	(void) split_at(hostaddr->buf, '%');
    return (ret);
#endif
}

/* sockaddr_to_hostname - binary address to printable hostname */

int     sockaddr_to_hostname(const struct sockaddr *sa, SOCKADDR_SIZE salen,
			             MAI_HOSTNAME_STR *hostname,
			             MAI_SERVNAME_STR *service,
			             int socktype)
{
#ifdef EMULATE_IPV4_ADDRINFO

    /*
     * Emulated getnameinfo(3) version.
     */
    struct hostent *hp;
    struct servent *sp;
    size_t  len;

    /*
     * Sanity check.
     */
    if (sa->sa_family != AF_INET)
	return (EAI_NODATA);

    /*
     * Look up the host name.
     */
    if (hostname != 0) {
	if ((hp = gethostbyaddr((char *) &(SOCK_ADDR_IN_ADDR(sa)),
				sizeof(SOCK_ADDR_IN_ADDR(sa)),
				AF_INET)) == 0)
	    return (h_errno == TRY_AGAIN ? EAI_AGAIN : EAI_NONAME);

	/*
	 * Save the result. The buffer length includes the space for the null
	 * terminator. Hostname sanity checks are at the end of this
	 * function.
	 */
	if ((len = strlen(hp->h_name)) >= sizeof(hostname->buf)) {
	    errno = ENOSPC;
	    return (EAI_SYSTEM);
	}
	memcpy(hostname->buf, hp->h_name, len + 1);
    }

    /*
     * Look up the service.
     */
    if (service != 0) {
	if ((sp = getservbyport(ntohs(SOCK_ADDR_IN_PORT(sa)),
			      socktype == SOCK_DGRAM ? "udp" : "tcp")) == 0)
	    return (EAI_NONAME);

	/*
	 * Save the result. The buffer length includes the space for the null
	 * terminator.
	 */
	if ((len = strlen(sp->s_name)) >= sizeof(service->buf)) {
	    errno = ENOSPC;
	    return (EAI_SYSTEM);
	}
	memcpy(service->buf, sp->s_name, len + 1);
    }
#else

    /*
     * Native getnameinfo(3) version.
     */
    int     err;

    err = getnameinfo(sa, salen,
		      hostname ? hostname->buf : (char *) 0,
		      hostname ? sizeof(hostname->buf) : 0,
		      service ? service->buf : (char *) 0,
		      service ? sizeof(service->buf) : 0,
		      socktype == SOCK_DGRAM ?
		      NI_NAMEREQD | NI_DGRAM : NI_NAMEREQD);
    if (err != 0)
	return (err);
#endif

    /*
     * Hostname sanity checks.
     */
    if (hostname != 0) {
	if (valid_hostaddr(hostname->buf, DONT_GRIPE)) {
	    msg_warn("numeric hostname: %s", hostname->buf);
	    return (EAI_NONAME);
	}
	if (!valid_hostname(hostname->buf, DO_GRIPE))
	    return (EAI_NONAME);
    }
    return (0);
}

/* myaddrinfo_control - fine control */

void    myaddrinfo_control(int name,...)
{
    const char *myname = "myaddrinfo_control";
    va_list ap;

    for (va_start(ap, name); name != 0; name = va_arg(ap, int)) {
	switch (name) {
	default:
	    msg_panic("%s: bad name %d", myname, name);
	}
    }
    va_end(ap);
}

#ifdef EMULATE_IPV4_ADDRINFO

/* freeaddrinfo - release storage */

void    freeaddrinfo(struct addrinfo * ai)
{
    struct addrinfo *ap;
    struct addrinfo *next;

    /*
     * Artifact of implementation: tolerate a null pointer argument.
     */
    for (ap = ai; ap != 0; ap = next) {
	next = ap->ai_next;
	if (ap->ai_canonname)
	    myfree(ap->ai_canonname);
	/* ap->ai_addr is allocated within this memory block */
	myfree((void *) ap);
    }
}

static char *ai_errlist[] = {
    "Success",
    "Address family for hostname not supported",	/* EAI_ADDRFAMILY */
    "Temporary failure in name resolution",	/* EAI_AGAIN	 */
    "Invalid value for ai_flags",	/* EAI_BADFLAGS   */
    "Non-recoverable failure in name resolution",	/* EAI_FAIL	 */
    "ai_family not supported",		/* EAI_FAMILY     */
    "Memory allocation failure",	/* EAI_MEMORY     */
    "No address associated with hostname",	/* EAI_NODATA     */
    "hostname nor servname provided, or not known",	/* EAI_NONAME     */
    "service name not supported for ai_socktype",	/* EAI_SERVICE    */
    "ai_socktype not supported",	/* EAI_SOCKTYPE   */
    "System error returned in errno",	/* EAI_SYSTEM     */
    "Invalid value for hints",		/* EAI_BADHINTS   */
    "Resolved protocol is unknown",	/* EAI_PROTOCOL   */
    "Unknown error",			/* EAI_MAX	  */
};

/* gai_strerror - error number to string */

char   *gai_strerror(int ecode)
{

    /*
     * Note: EAI_SYSTEM errors are not automatically handed over to
     * strerror(). The application decides.
     */
    if (ecode < 0 || ecode > EAI_MAX)
	ecode = EAI_MAX;
    return (ai_errlist[ecode]);
}

#endif

#ifdef TEST

 /*
  * A test program that takes some info from the command line and runs it
  * forward and backward through the above conversion routines.
  */
#include <stdlib.h>
#include <msg.h>
#include <vstream.h>
#include <msg_vstream.h>

static int compare_family(const void *a, const void *b)
{
    struct addrinfo *resa = *(struct addrinfo **) a;
    struct addrinfo *resb = *(struct addrinfo **) b;

    return (resa->ai_family - resb->ai_family);
}

int     main(int argc, char **argv)
{
    struct addrinfo *info;
    struct addrinfo *ip;
    struct addrinfo **resv;
    MAI_HOSTNAME_STR host;
    MAI_HOSTADDR_STR addr;
    size_t  len, n;
    int     err;

    msg_vstream_init(argv[0], VSTREAM_ERR);

    if (argc != 4)
	msg_fatal("usage: %s protocols hostname hostaddress", argv[0]);

    inet_proto_init(argv[0], argv[1]);

    msg_info("=== hostname %s ===", argv[2]);

    if ((err = hostname_to_sockaddr(argv[2], (char *) 0, 0, &info)) != 0) {
	msg_info("hostname_to_sockaddr(%s): %s",
	  argv[2], err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err));
    } else {
	for (len = 0, ip = info; ip != 0; ip = ip->ai_next)
	    len += 1;
	resv = (struct addrinfo **) mymalloc(len * sizeof(*resv));
	for (len = 0, ip = info; ip != 0; ip = ip->ai_next)
	    resv[len++] = ip;
	qsort((void *) resv, len, sizeof(*resv), compare_family);
	for (n = 0; n < len; n++) {
	    ip = resv[n];
	    if ((err = sockaddr_to_hostaddr(ip->ai_addr, ip->ai_addrlen, &addr,
					 (MAI_SERVPORT_STR *) 0, 0)) != 0) {
		msg_info("sockaddr_to_hostaddr: %s",
		   err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err));
		continue;
	    }
	    msg_info("%s -> family=%d sock=%d proto=%d %s", argv[2],
		 ip->ai_family, ip->ai_socktype, ip->ai_protocol, addr.buf);
	    if ((err = sockaddr_to_hostname(ip->ai_addr, ip->ai_addrlen, &host,
					 (MAI_SERVNAME_STR *) 0, 0)) != 0) {
		msg_info("sockaddr_to_hostname: %s",
		   err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err));
		continue;
	    }
	    msg_info("%s -> %s", addr.buf, host.buf);
	}
	freeaddrinfo(info);
	myfree((void *) resv);
    }

    msg_info("=== host address %s ===", argv[3]);

    if ((err = hostaddr_to_sockaddr(argv[3], (char *) 0, 0, &ip)) != 0) {
	msg_info("hostaddr_to_sockaddr(%s): %s",
	  argv[3], err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err));
    } else {
	if ((err = sockaddr_to_hostaddr(ip->ai_addr, ip->ai_addrlen, &addr,
					(MAI_SERVPORT_STR *) 0, 0)) != 0) {
	    msg_info("sockaddr_to_hostaddr: %s",
		   err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err));
	} else {
	    msg_info("%s -> family=%d sock=%d proto=%d %s", argv[3],
		 ip->ai_family, ip->ai_socktype, ip->ai_protocol, addr.buf);
	    if ((err = sockaddr_to_hostname(ip->ai_addr, ip->ai_addrlen, &host,
					 (MAI_SERVNAME_STR *) 0, 0)) != 0) {
		msg_info("sockaddr_to_hostname: %s",
		   err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err));
	    } else
		msg_info("%s -> %s", addr.buf, host.buf);
	    freeaddrinfo(ip);
	}
    }
    exit(0);
}

#endif
