#include <netdb.h>
#include <arpa/inet.h>
#include <nss_dbdefs.h>
#include <netinet/in.h>
#include <sys/debug.h>
#include <sys/socket.h>
#include <string.h>
#include <strings.h>
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <stdlib.h>
#include <libintl.h>
#include <net/if.h>
#define ai2sin(x) ((struct sockaddr_in *)((x)->ai_addr))
#define ai2sin6(x) ((struct sockaddr_in6 *)((x)->ai_addr))
#define HOST_BROADCAST "255.255.255.255"
#define GAIV_DEFAULT 0
#define GAIV_XPG6 1
const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT;
const struct in6_addr in6addr_loopback = IN6ADDR_LOOPBACK_INIT;
#define AI_MASK (AI_PASSIVE | AI_CANONNAME | AI_NUMERICHOST \
| AI_ADDRCONFIG | AI_NUMERICSERV | AI_V4MAPPED | AI_ALL)
#define ANY 0
#define AI_ADDRINFO 0x8000
typedef struct {
int si_socktype;
int si_protocol;
ushort_t si_port;
} spinfo_t;
static int get_addr(int, const char *, struct addrinfo *,
struct addrinfo *, spinfo_t *, uint_t, int);
static uint_t getscopeidfromzone(const struct sockaddr_in6 *,
const char *, uint32_t *);
static void servtype(const char *, int *, int *);
static boolean_t str_isnumber(const char *);
static int
_getaddrinfo(const char *hostname, const char *servname,
const struct addrinfo *hints, struct addrinfo **res, int version)
{
struct addrinfo *cur;
struct addrinfo *aip;
struct addrinfo ai;
int error;
uint_t i;
#define SPINFO_SIZE 3
spinfo_t spinfo[SPINFO_SIZE];
uint_t spidx = 0;
#define SP_ADDX(type, proto, port) \
do { \
ASSERT3U(spidx, <, SPINFO_SIZE); \
spinfo[spidx].si_socktype = (type); \
spinfo[spidx].si_protocol = (proto); \
spinfo[spidx].si_port = (port); \
spidx++; \
} while (0)
#define SP_ADD(sp) \
do { \
int _type, _proto; \
servtype((sp)->s_proto, &_type, &_proto); \
SP_ADDX(_type, _proto, (sp)->s_port); \
} while (0)
*res = NULL;
if (hostname == NULL && servname == NULL)
return (EAI_NONAME);
cur = &ai;
aip = &ai;
if (hints == NULL) {
aip->ai_flags = 0;
aip->ai_family = PF_UNSPEC;
aip->ai_socktype = ANY;
aip->ai_protocol = ANY;
} else {
(void) memcpy(aip, hints, sizeof (*aip));
if (hints->ai_flags != 0 && (hints->ai_flags & ~AI_MASK))
return (EAI_BADFLAGS);
if ((hostname == NULL || *hostname == '\0') &&
(hints->ai_flags & AI_CANONNAME)) {
return (EAI_BADFLAGS);
}
if (hints->ai_family != PF_UNSPEC &&
hints->ai_family != PF_INET &&
hints->ai_family != PF_INET6) {
return (EAI_FAMILY);
}
switch (aip->ai_socktype) {
case ANY:
switch (aip->ai_protocol) {
case ANY:
break;
case IPPROTO_UDP:
aip->ai_socktype = SOCK_DGRAM;
break;
case IPPROTO_TCP:
case IPPROTO_SCTP:
aip->ai_socktype = SOCK_STREAM;
break;
default:
aip->ai_socktype = SOCK_RAW;
break;
}
break;
case SOCK_RAW:
break;
case SOCK_SEQPACKET:
if (aip->ai_protocol == ANY)
aip->ai_protocol = IPPROTO_SCTP;
break;
case SOCK_DGRAM:
aip->ai_protocol = IPPROTO_UDP;
break;
case SOCK_STREAM:
if (aip->ai_protocol == ANY)
aip->ai_protocol = IPPROTO_TCP;
break;
default:
return (EAI_SOCKTYPE);
}
}
aip->ai_addrlen = 0;
aip->ai_canonname = NULL;
aip->ai_addr = NULL;
aip->ai_next = NULL;
#ifdef __sparcv9
aip->_ai_pad = 0;
#endif
if (servname != NULL) {
struct servent result;
int bufsize = 128;
char *buf = NULL;
struct servent *sp;
const char *proto = NULL;
switch (aip->ai_socktype) {
case ANY:
case SOCK_RAW:
proto = NULL;
break;
case SOCK_DGRAM:
proto = "udp";
break;
case SOCK_STREAM:
switch (aip->ai_protocol) {
case ANY:
case IPPROTO_TCP:
default:
proto = "tcp";
break;
case IPPROTO_SCTP:
proto = "sctp";
break;
}
break;
case SOCK_SEQPACKET:
switch (aip->ai_protocol) {
case ANY:
default:
proto = "sctp";
break;
}
break;
}
if (str_isnumber(servname)) {
ushort_t port = htons(atoi(servname));
if (aip->ai_socktype == ANY) {
SP_ADDX(SOCK_STREAM, IPPROTO_TCP, port);
SP_ADDX(SOCK_DGRAM, IPPROTO_UDP, port);
SP_ADDX(SOCK_STREAM, IPPROTO_SCTP, port);
} else {
SP_ADDX(aip->ai_socktype, aip->ai_protocol,
port);
}
} else if (aip->ai_flags & AI_NUMERICSERV) {
return (EAI_NONAME);
} else {
do {
buf = reallocf(buf, bufsize);
if (buf == NULL)
return (EAI_MEMORY);
sp = getservbyname_r(servname, proto, &result,
buf, bufsize);
if (sp == NULL && errno != ERANGE) {
free(buf);
return (EAI_SERVICE);
}
bufsize *= 2;
} while (sp == NULL);
if (aip->ai_socktype != ANY) {
SP_ADDX(aip->ai_socktype, aip->ai_protocol,
sp->s_port);
} else {
SP_ADD(sp);
}
}
free(buf);
if (spidx == 0)
return (EAI_SERVICE);
} else {
SP_ADDX(aip->ai_socktype, aip->ai_protocol, 0);
}
error = get_addr(aip->ai_family, hostname, aip, cur,
spinfo, spidx, version);
if (error != 0) {
if (aip->ai_next != NULL)
freeaddrinfo(aip->ai_next);
return (error);
}
*res = aip->ai_next;
return (0);
}
#undef SP_ADD
#undef SP_ADDX
int
getaddrinfo(const char *hostname, const char *servname,
const struct addrinfo *hints, struct addrinfo **res)
{
return (_getaddrinfo(hostname, servname, hints, res, GAIV_DEFAULT));
}
int
__xnet_getaddrinfo(const char *hostname, const char *servname,
const struct addrinfo *hints, struct addrinfo **res)
{
return (_getaddrinfo(hostname, servname, hints, res, GAIV_XPG6));
}
static int
add_address4(struct addrinfo *aip, struct addrinfo **cur,
struct in_addr *addr, const char *canonname, spinfo_t *info)
{
struct addrinfo *nai;
int addrlen;
nai = malloc(sizeof (struct addrinfo));
if (nai == NULL)
return (EAI_MEMORY);
*nai = *aip;
nai->ai_next = NULL;
addrlen = sizeof (struct sockaddr_in);
nai->ai_addr = malloc(addrlen);
if (nai->ai_addr == NULL) {
freeaddrinfo(nai);
return (EAI_MEMORY);
}
bzero(nai->ai_addr, addrlen);
nai->ai_addrlen = addrlen;
nai->ai_family = PF_INET;
(void) memcpy(&ai2sin(nai)->sin_addr, addr, sizeof (struct in_addr));
nai->ai_canonname = NULL;
if ((nai->ai_flags & AI_CANONNAME) && canonname != NULL) {
canonname = strdup(canonname);
if (canonname == NULL) {
freeaddrinfo(nai);
return (EAI_MEMORY);
}
nai->ai_canonname = (char *)canonname;
}
ai2sin(nai)->sin_family = PF_INET;
ai2sin(nai)->sin_port = info->si_port;
nai->ai_socktype = info->si_socktype;
nai->ai_protocol = info->si_protocol;
(*cur)->ai_next = nai;
*cur = nai;
return (0);
}
static int
add_address6(struct addrinfo *aip, struct addrinfo **cur,
struct in6_addr *addr, const char *zonestr, const char *canonname,
spinfo_t *info)
{
struct addrinfo *nai;
int addrlen;
nai = malloc(sizeof (struct addrinfo));
if (nai == NULL)
return (EAI_MEMORY);
*nai = *aip;
nai->ai_next = NULL;
addrlen = sizeof (struct sockaddr_in6);
nai->ai_addr = malloc(addrlen);
if (nai->ai_addr == NULL) {
freeaddrinfo(nai);
return (EAI_MEMORY);
}
bzero(nai->ai_addr, addrlen);
nai->ai_addrlen = addrlen;
nai->ai_family = PF_INET6;
(void) memcpy(ai2sin6(nai)->sin6_addr.s6_addr,
&addr->s6_addr, sizeof (struct in6_addr));
nai->ai_canonname = NULL;
if ((nai->ai_flags & AI_CANONNAME) && canonname != NULL) {
canonname = strdup(canonname);
if (canonname == NULL) {
freeaddrinfo(nai);
return (EAI_MEMORY);
}
nai->ai_canonname = (char *)canonname;
}
ai2sin6(nai)->sin6_family = PF_INET6;
ai2sin6(nai)->sin6_port = info->si_port;
nai->ai_socktype = info->si_socktype;
nai->ai_protocol = info->si_protocol;
if (zonestr != NULL) {
int err = getscopeidfromzone(ai2sin6(nai), zonestr,
&ai2sin6(nai)->sin6_scope_id);
if (err != 0) {
freeaddrinfo(nai);
return (err);
}
} else {
ai2sin6(nai)->sin6_scope_id = 0;
}
(*cur)->ai_next = nai;
*cur = nai;
return (0);
}
static int
get_addr(int family, const char *hostname, struct addrinfo *aip,
struct addrinfo *cur, spinfo_t *ports, uint_t nport, int version)
{
struct hostent *hp;
char _hostname[MAXHOSTNAMELEN];
int errnum;
boolean_t firsttime = B_TRUE;
char *zonestr = NULL;
uint_t i;
if (hostname == NULL) {
const char *canon = "loopback";
errnum = 0;
if (family != PF_INET) {
struct in6_addr v6addr;
if (aip->ai_flags & AI_PASSIVE) {
(void) memcpy(&v6addr.s6_addr,
in6addr_any.s6_addr,
sizeof (struct in6_addr));
canon = NULL;
} else {
(void) memcpy(&v6addr.s6_addr,
in6addr_loopback.s6_addr,
sizeof (struct in6_addr));
}
for (i = 0; i < nport; i++) {
errnum = add_address6(aip, &cur, &v6addr, NULL,
canon, &ports[i]);
canon = NULL;
if (errnum != 0)
break;
}
}
if (errnum == 0 && family != PF_INET6) {
struct in_addr addr;
if (aip->ai_flags & AI_PASSIVE) {
addr.s_addr = INADDR_ANY;
canon = NULL;
} else {
addr.s_addr = htonl(INADDR_LOOPBACK);
}
for (i = 0; i < nport; i++) {
errnum = add_address4(aip, &cur, &addr, canon,
&ports[i]);
canon = NULL;
if (errnum != 0)
break;
}
}
return (errnum);
}
if ((zonestr = strchr(hostname, '%')) != NULL) {
if ((zonestr - hostname) + 1 > sizeof (_hostname))
return (EAI_MEMORY);
(void) strlcpy(_hostname, hostname, (zonestr - hostname) + 1);
++zonestr;
if (*zonestr == '\0' || strlen(zonestr) > LIFNAMSIZ)
return (EAI_NONAME);
} else {
size_t hlen = sizeof (_hostname);
if (strlcpy(_hostname, hostname, hlen) >= hlen)
return (EAI_MEMORY);
}
if (aip->ai_flags & AI_NUMERICHOST) {
struct in6_addr v6addr;
if (!(inet_addr(_hostname) != ((in_addr_t)-1) ||
strcmp(_hostname, HOST_BROADCAST) == 0 ||
inet_pton(AF_INET6, _hostname, &v6addr) > 0)) {
return (EAI_NONAME);
}
}
if (family == PF_UNSPEC) {
hp = getipnodebyname(_hostname, AF_INET6,
AI_ALL | aip->ai_flags | AI_V4MAPPED | AI_ADDRINFO,
&errnum);
} else {
hp = getipnodebyname(_hostname, family, aip->ai_flags, &errnum);
}
if (hp == NULL) {
switch (errnum) {
case HOST_NOT_FOUND:
return (EAI_NONAME);
case TRY_AGAIN:
return (EAI_AGAIN);
case NO_RECOVERY:
return (EAI_FAIL);
case NO_ADDRESS:
if (version == GAIV_XPG6)
return (EAI_NONAME);
return (EAI_NODATA);
default:
return (EAI_SYSTEM);
}
}
for (i = 0; hp->h_addr_list[i]; i++) {
boolean_t create_v6_addrinfo = B_TRUE;
struct in_addr v4addr;
struct in6_addr v6addr;
uint_t j;
if (hp->h_addrtype == AF_INET6) {
struct in6_addr *v6addrp;
v6addrp = (struct in6_addr *)hp->h_addr_list[i];
if (!(aip->ai_flags & AI_V4MAPPED) &&
IN6_IS_ADDR_V4MAPPED(v6addrp)) {
create_v6_addrinfo = B_FALSE;
IN6_V4MAPPED_TO_INADDR(v6addrp, &v4addr);
} else {
(void) memcpy(&v6addr.s6_addr,
hp->h_addr_list[i],
sizeof (struct in6_addr));
}
} else if (hp->h_addrtype == AF_INET) {
create_v6_addrinfo = B_FALSE;
(void) memcpy(&v4addr.s_addr, hp->h_addr_list[i],
sizeof (struct in_addr));
} else {
return (EAI_SYSTEM);
}
for (j = 0; j < nport; j++) {
if (create_v6_addrinfo) {
errnum = add_address6(aip, &cur, &v6addr,
zonestr, firsttime ? hp->h_name : NULL,
&ports[j]);
} else {
errnum = add_address4(aip, &cur, &v4addr,
firsttime ? hp->h_name : NULL,
&ports[j]);
}
firsttime = B_FALSE;
if (errnum != 0) {
freehostent(hp);
return (errnum);
}
}
}
freehostent(hp);
return (0);
}
static uint_t
getscopeidfromzone(const struct sockaddr_in6 *sa, const char *zone,
uint32_t *sin6_scope_id)
{
const in6_addr_t *addr = &sa->sin6_addr;
ulong_t ul_scope_id;
char *endp;
if (IN6_IS_ADDR_LINKSCOPE(addr)) {
if ((*sin6_scope_id = if_nametoindex(zone)) != 0) {
return (0);
} else {
if ((ul_scope_id = strtoul(zone, &endp, 10)) != 0) {
if (*endp != '\0') {
return (EAI_NONAME);
}
*sin6_scope_id =
(uint32_t)(ul_scope_id & 0xffffffffUL);
} else {
return (EAI_NONAME);
}
}
} else {
return (EAI_NONAME);
}
return (0);
}
void
freeaddrinfo(struct addrinfo *ai)
{
struct addrinfo *next;
do {
next = ai->ai_next;
free(ai->ai_canonname);
free(ai->ai_addr);
free(ai);
ai = next;
} while (ai != NULL);
}
static void
servtype(const char *tag, int *type, int *proto)
{
*type = *proto = 0;
if (strcmp(tag, "udp") == 0) {
*type = SOCK_DGRAM;
*proto = IPPROTO_UDP;
} else if (strcmp(tag, "tcp") == 0) {
*type = SOCK_STREAM;
*proto = IPPROTO_TCP;
} else if (strcmp(tag, "sctp") == 0) {
*type = SOCK_STREAM;
*proto = IPPROTO_SCTP;
}
}
static boolean_t
str_isnumber(const char *p)
{
char *q = (char *)p;
while (*q != '\0') {
if (!isdigit(*q))
return (B_FALSE);
q++;
}
return (B_TRUE);
}
static const char *gai_errlist[] = {
"name translation error 0 (no error)",
"specified address family not supported",
"temporary name resolution failure",
"invalid flags",
"non-recoverable name resolution failure",
"specified address family not supported",
"memory allocation failure",
"no address for the specified node name",
"node name or service name not known",
"service name not available for the specified socket type",
"specified socket type not supported",
"system error",
};
static int gai_nerr = { sizeof (gai_errlist)/sizeof (gai_errlist[0]) };
const char *
gai_strerror(int ecode)
{
if (ecode < 0)
return (dgettext(TEXT_DOMAIN,
"name translation internal error"));
else if (ecode < gai_nerr)
return (dgettext(TEXT_DOMAIN, gai_errlist[ecode]));
return (dgettext(TEXT_DOMAIN, "unknown name translation error"));
}