diff options
Diffstat (limited to '')
-rw-r--r-- | src/util/myaddrinfo.c | 888 |
1 files changed, 888 insertions, 0 deletions
diff --git a/src/util/myaddrinfo.c b/src/util/myaddrinfo.c new file mode 100644 index 0000000..a52e076 --- /dev/null +++ b/src/util/myaddrinfo.c @@ -0,0 +1,888 @@ +/*++ +/* 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 +/*--*/ + +/* 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> + +/* 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; + + 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 + } + 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 + } + 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; + + /* + * Artefact 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 |