#include <config.h>
#ifndef HAVE_GETADDRINFO
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <netdb.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#ifdef NEED_RESOLV_H
# include <arpa/nameser.h>
# include <resolv.h>
#endif
#include <sudo_compat.h>
#include <compat/getaddrinfo.h>
#ifndef HAVE_DECL_H_ERRNO
extern int h_errno;
#endif
#ifndef HOST_NOT_FOUND
# define HOST_NOT_FOUND 1
# define TRY_AGAIN 2
# define NO_RECOVERY 3
# define NO_DATA 4
#endif
#ifndef NETDB_INTERNAL
# define NETDB_INTERNAL -1
#endif
#ifdef TESTING
# define gai_strerror test_gai_strerror
# define freeaddrinfo test_freeaddrinfo
# define getaddrinfo test_getaddrinfo
const char *test_gai_strerror(int);
void test_freeaddrinfo(struct addrinfo *);
int test_getaddrinfo(const char *, const char *, const struct addrinfo *,
struct addrinfo **);
#endif
#ifdef TESTING
# if AI_NUMERICSERV == 0
# undef AI_NUMERICSERV
# define AI_NUMERICSERV 0x0080
# endif
# if AI_NUMERICHOST == 0
# undef AI_NUMERICHOST
# define AI_NUMERICHOST 0x0100
# endif
#endif
#ifdef TESTING
# ifdef HAVE_GETADDRINFO
# define AI_INTERNAL_ALL 0x04ff
# else
# define AI_INTERNAL_ALL 0x01ff
# endif
#else
# define AI_INTERNAL_ALL 0x007f
#endif
static const char * const gai_errors[] = {
"Host name lookup failure",
"Invalid flag value",
"Unknown server error",
"Unsupported address family",
"Memory allocation failure",
"Host unknown or not given",
"Service not supported for socket",
"Unsupported socket type",
"System error",
"Supplied buffer too small",
};
#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN
# define sin_set_length(s) ((s)->sin_len = sizeof(struct sockaddr_in))
#else
# define sin_set_length(s)
#endif
const char *
sudo_gai_strerror(int ecode)
{
if (ecode < 1 || (size_t) ecode > nitems(gai_errors))
return "Unknown error";
else
return gai_errors[ecode - 1];
}
void
sudo_freeaddrinfo(struct addrinfo *ai)
{
struct addrinfo *next;
while (ai != NULL) {
next = ai->ai_next;
if (ai->ai_addr != NULL)
free(ai->ai_addr);
if (ai->ai_canonname != NULL)
free(ai->ai_canonname);
free(ai);
ai = next;
}
}
static struct addrinfo *
gai_addrinfo_new(int socktype, const char *canonical, struct in_addr addr,
unsigned short port)
{
struct addrinfo *ai;
ai = malloc(sizeof(*ai));
if (ai == NULL)
return NULL;
ai->ai_addr = malloc(sizeof(struct sockaddr_in));
if (ai->ai_addr == NULL) {
free(ai);
return NULL;
}
ai->ai_next = NULL;
if (canonical == NULL)
ai->ai_canonname = NULL;
else {
ai->ai_canonname = strdup(canonical);
if (ai->ai_canonname == NULL) {
freeaddrinfo(ai);
return NULL;
}
}
memset(ai->ai_addr, 0, sizeof(struct sockaddr_in));
ai->ai_flags = 0;
ai->ai_family = AF_INET;
ai->ai_socktype = socktype;
ai->ai_protocol = (socktype == SOCK_DGRAM) ? IPPROTO_UDP : IPPROTO_TCP;
ai->ai_addrlen = sizeof(struct sockaddr_in);
((struct sockaddr_in *) ai->ai_addr)->sin_family = AF_INET;
((struct sockaddr_in *) ai->ai_addr)->sin_addr = addr;
((struct sockaddr_in *) ai->ai_addr)->sin_port = htons(port);
sin_set_length((struct sockaddr_in *) ai->ai_addr);
return ai;
}
static int
gai_service(const char *servname, int flags, int *type, unsigned short *port)
{
struct servent *servent;
const char *protocol;
const char *errstr;
unsigned short value;
value = sudo_strtonum(servname, 0, USHRT_MAX, &errstr);
if (errstr == NULL) {
*port = value;
} else if (errno == ERANGE) {
return EAI_SERVICE;
} else {
if (flags & AI_NUMERICSERV)
return EAI_NONAME;
if (*type != 0)
protocol = (*type == SOCK_DGRAM) ? "udp" : "tcp";
else
protocol = NULL;
servent = getservbyname(servname, protocol);
if (servent == NULL)
return EAI_NONAME;
if (strcmp(servent->s_proto, "udp") == 0)
*type = SOCK_DGRAM;
else if (strcmp(servent->s_proto, "tcp") == 0)
*type = SOCK_STREAM;
else
return EAI_SERVICE;
*port = htons(servent->s_port);
}
return 0;
}
static int
gai_lookup(const char *nodename, int flags, int socktype, unsigned short port,
struct addrinfo **res)
{
struct addrinfo *ai, *first, *prev;
struct in_addr addr;
struct hostent *host;
const char *canonical;
size_t i;
if (inet_pton(AF_INET, nodename, &addr)) {
canonical = (flags & AI_CANONNAME) ? nodename : NULL;
ai = gai_addrinfo_new(socktype, canonical, addr, port);
if (ai == NULL)
return EAI_MEMORY;
*res = ai;
return 0;
} else {
if (flags & AI_NUMERICHOST)
return EAI_NONAME;
host = gethostbyname(nodename);
if (host == NULL)
switch (h_errno) {
case HOST_NOT_FOUND:
return EAI_NONAME;
case TRY_AGAIN:
case NO_DATA:
return EAI_AGAIN;
case NO_RECOVERY:
return EAI_FAIL;
case NETDB_INTERNAL:
default:
return EAI_SYSTEM;
}
if (host->h_addr_list[0] == NULL)
return EAI_FAIL;
canonical = (flags & AI_CANONNAME)
? ((host->h_name != NULL) ? host->h_name : nodename)
: NULL;
first = NULL;
prev = NULL;
for (i = 0; host->h_addr_list[i] != NULL; i++) {
if (host->h_length != sizeof(addr)) {
freeaddrinfo(first);
return EAI_FAIL;
}
memcpy(&addr, host->h_addr_list[i], sizeof(addr));
ai = gai_addrinfo_new(socktype, canonical, addr, port);
if (ai == NULL) {
freeaddrinfo(first);
return EAI_MEMORY;
}
if (first == NULL) {
first = ai;
prev = ai;
} else {
prev->ai_next = ai;
prev = ai;
}
}
*res = first;
return 0;
}
}
int
sudo_getaddrinfo(const char *nodename, const char *servname,
const struct addrinfo *hints, struct addrinfo **res)
{
struct addrinfo *ai;
struct in_addr addr;
int flags, socktype, status;
unsigned short port;
if (hints != NULL) {
flags = hints->ai_flags;
socktype = hints->ai_socktype;
if ((flags & AI_INTERNAL_ALL) != flags)
return EAI_BADFLAGS;
if (hints->ai_family != AF_UNSPEC && hints->ai_family != AF_INET)
return EAI_FAMILY;
if (socktype != 0 && socktype != SOCK_STREAM && socktype != SOCK_DGRAM)
return EAI_SOCKTYPE;
if (hints->ai_protocol != 0) {
int protocol = hints->ai_protocol;
if (protocol != IPPROTO_TCP && protocol != IPPROTO_UDP)
return EAI_SOCKTYPE;
}
} else {
flags = 0;
socktype = 0;
}
if (servname == NULL)
port = 0;
else {
status = gai_service(servname, flags, &socktype, &port);
if (status != 0)
return status;
}
if (nodename != NULL)
return gai_lookup(nodename, flags, socktype, port, res);
else {
if (servname == NULL)
return EAI_NONAME;
if ((flags & AI_PASSIVE) == AI_PASSIVE)
addr.s_addr = INADDR_ANY;
else
addr.s_addr = htonl(0x7f000001UL);
ai = gai_addrinfo_new(socktype, NULL, addr, port);
if (ai == NULL)
return EAI_MEMORY;
*res = ai;
return 0;
}
}
#endif