/*
%%% copyright-cmetz-96
This software is Copyright 1996-1997 by Craig Metz, All Rights Reserved.
The Inner Net License Version 2 applies to this software.
You should have received a copy of the license with this software. If
you didn't get a copy, you may request one from <license@inner.net>.

*/
/* getaddrinfo() v1.26 */

/*
   I'd like to thank Matti Aarnio for finding some bugs in this code and
   sending me patches, as well as everyone else who has contributed to this
   code (by reporting bugs, suggesting improvements, and commented on its
   behavior and proposed changes).
*/

/* To do what POSIX says, even when it's broken, define: */
/* #define BROKEN_LIKE_POSIX 1 */
/* Note: real apps will break if you define this, while nothing other than a
   conformance test suite should have a problem with it undefined. */

/* If your C runtime library provides the POSIX p1003.1g D6.6 bit types
   of the form u?int(16|32)_t, define: */
/* #define HAVE_POSIX1G_TYPES 1 */
/* Note: this implementation tries to guess what the correct values are for
   your compiler+processor combination but might not always get it right. */

#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#if LOCAL
#include <stdio.h>
#include <string.h>
#include <sys/utsname.h>
#include <sys/un.h>
#endif /* LOCAL */
#include <netinet/in.h>
#include <netdb.h>
#if RESOLVER
#include <arpa/nameser.h>
#include <resolv.h>
#endif /* RESOLVER */

#ifndef AF_LOCAL
#define AF_LOCAL AF_UNIX
#endif /* AF_LOCAL */
#ifndef PF_LOCAL
#define PF_LOCAL PF_UNIX
#endif /* PF_LOCAL */
#ifndef UNIX_PATH_MAX
#define UNIX_PATH_MAX 108
#endif /* UNIX_PATH_MAX */

#if !HAVE_POSIX1G_TYPES
#if (~0UL) == 0xffffffff
#define int16_t short
#define uint16_t unsigned short
#define int32_t long
#define uint32_t unsigned long
#else /* (~0UL) == 0xffffffff */
#if (~0UL) == 0xffffffffffffffff
#define int16_t short
#define uint16_t unsigned short
#define int32_t int
#define uint32_t unsigned int
#else /* (~0UL) == 0xffffffffffffffff */
#error Neither 32 bit nor 64 bit word size detected.
#error You need to define the bit types manually.
#endif /* (~0UL) == 0xffffffffffffffff */
#endif /* (~0UL) == 0xffffffff */
#endif /* !HAVE_POSIX1G_TYPES */

#if defined(INET6) && !defined(AF_INET6)
#error Without a definition of AF_INET6, this system cannot support IPv6
#error addresses.
#endif /* defined(INET6) && !defined(AF_INET6) */

#if INET6
#ifndef T_AAAA
#define T_AAAA 28
#endif /* T_AAAA */
#endif /* INET6 */

#define GAIH_OKIFUNSPEC 0x0100
#define GAIH_EAI        ~(GAIH_OKIFUNSPEC)

static struct addrinfo nullreq =
{ 0, PF_UNSPEC, 0, 0, 0, NULL, NULL, NULL };

struct gaih_service {
  char *name;
  int num;
};

struct gaih_servtuple {
  struct gaih_servtuple *next;
  int socktype;
  int protocol;
  int port;
};

static struct gaih_servtuple nullserv = {
  NULL, 0, 0, 0
};

struct gaih_addrtuple {
  struct gaih_addrtuple *next;
  int family;
  char addr[16];
  char *cname;
};

struct gaih_typeproto {
  int socktype;
  int protocol;
  char *name;
};

#if HOSTTABLE
static int hosttable_lookup_addr(const char *name, const struct addrinfo *req, struct gaih_addrtuple **pat)
{
  FILE *f;
  char buffer[1024];
  char *c, *c2;
  int rval = 1;
  char *prevcname = NULL;

  if (!(f = fopen("/etc/hosts", "r")))
    return -EAI_SYSTEM;

  while(fgets(buffer, sizeof(buffer), f)) {
    if (c = strchr(buffer, '#'))
      *c = 0;

    c = buffer;
    while(*c && !isspace(*c)) c++;
    if (!*c)
      continue;

    *(c++) = 0;

    while(*c && isspace(*c)) c++;
    if (!*c)
      continue;

    if (!(c2 = strstr(c, name)))
      continue;

    if (*(c2 - 1) && !isspace(*(c2 - 1)))
      continue;

    c2 += strlen(name);
    if (*c2 && !isspace(*c2))
      continue;

    c2 = c;
    while(*c2 && !isspace(*c2)) c2++;
    if (!*c2)
      continue;
    *c2 = 0;

    if (!*pat) {
      if (!(*pat = malloc(sizeof(struct gaih_addrtuple))))
	return -EAI_MEMORY;
    };

    memset(*pat, 0, sizeof(struct gaih_addrtuple));

    if (!req->ai_family || (req->ai_family == AF_INET))
      if (inet_pton(AF_INET, buffer, (*pat)->addr) > 0) {
	(*pat)->family = AF_INET;
	goto build;
      };

#if INET6
    if (!req->ai_family || (req->ai_family == AF_INET6))
      if (inet_pton(AF_INET6, buffer, (*pat)->addr) > 0) {
	(*pat)->family = AF_INET6;
	goto build;
      };
#endif /* INET6 */

    continue;

build:
    if (req->ai_flags & AI_CANONNAME)
      if (prevcname && !strcmp(prevcname, c))
	(*pat)->cname = prevcname;
      else
	prevcname = (*pat)->cname = strdup(c);

    pat = &((*pat)->next);

    rval = 0;
  };

  fclose(f);
  return rval;
};
#endif /* HOSTTABLE */

#if RESOLVER
#define RRHEADER_SZ 10

int resolver_lookup_addr(const char *name, int type, const struct addrinfo *req, struct gaih_addrtuple **pat)
{
  char answer[PACKETSZ];
  int answerlen;
  char dn[MAXDNAME];
  char *prevcname = NULL;
  void *p, *ep;
  int answers, i, j;
  uint16_t rtype, rclass;

  if ((answerlen = res_search(name, C_IN, type, answer, sizeof(answer))) < 0) {
    switch(h_errno) {
    case NETDB_INTERNAL:
      return -EAI_SYSTEM;
    case HOST_NOT_FOUND:
      return 1;
    case TRY_AGAIN:
      return -EAI_AGAIN; /* XXX */
    case NO_RECOVERY:
      return -EAI_FAIL;
    case NO_DATA:
      return 1;
    default:
      return -EAI_FAIL;
    };
  };

  p = answer;
  ep = answer + answerlen;
  
  if (answerlen < RRHEADER_SZ)
    return -EAI_FAIL;
  {
    HEADER *h = (HEADER *)p;
    if (!h->qr || (h->opcode != QUERY) || (h->qdcount != htons(1)) || !h->ancount)
      return -EAI_FAIL;
    answers = ntohs(h->ancount);
  };
  p += sizeof(HEADER);

  if ((i = dn_expand(answer, ep, p, dn, sizeof(dn))) < 0)
    return -EAI_FAIL;
  p += i;

  if (p + 2*sizeof(uint16_t) >= ep)
    return -EAI_FAIL;

  GETSHORT(rtype, p);
  GETSHORT(rclass, p);

  if ((rtype != type) || (rclass != C_IN))
    return -EAI_FAIL;

  while(answers--) {
    if ((i = dn_expand(answer, ep, p, dn, sizeof(dn))) < 0)
      return -EAI_FAIL;
    p += i;

    if (p + RRHEADER_SZ >= ep)
      return -EAI_FAIL;

    GETSHORT(rtype, p);
    GETSHORT(rclass, p);
    p += sizeof(uint32_t);
    if (rclass != C_IN)
      return -EAI_FAIL;
    GETSHORT(rclass, p);
    i = rclass;

    if (p + i > ep)
      return -EAI_FAIL;

    if (rtype == type) {
      while(*pat)
	pat = &((*pat)->next);

      if (!(*pat = malloc(sizeof(struct gaih_addrtuple))))
	return -EAI_MEMORY;

      memset(*pat, 0, sizeof(struct gaih_addrtuple));
      
      switch(type) {
        case T_A:
	  if (i != sizeof(struct in_addr))
	    return -EAI_FAIL;
	  (*pat)->family = AF_INET;
	  break;
#if INET6
        case T_AAAA:
	  if (i != sizeof(struct in6_addr))
	    return -EAI_FAIL;
	  (*pat)->family = AF_INET6;
	  break;
#endif /* INET6 */
        default:
	  return -EAI_FAIL;
      };

      memcpy((*pat)->addr, p, i);
    
      if (req->ai_flags & AI_CANONNAME)
	if (prevcname && !strcmp(prevcname, dn))
	  (*pat)->cname = prevcname;
	else
	  prevcname = (*pat)->cname = strdup(dn);
    };
    p += i;
  };

  return 0;
};
#endif /* RESOLVER */

#if LOCAL
static int gaih_local(const char *name, const struct gaih_service *service,
		     const struct addrinfo *req, struct addrinfo **pai)
{
  struct utsname utsname;

  if (name || (req->ai_flags & AI_CANONNAME))
    if (uname(&utsname))
      return -EAI_SYSTEM;
  if (name) {
    if (strcmp(name, "localhost") && strcmp(name, "local") && strcmp(name, "unix") && strcmp(name, utsname.nodename))
      return (GAIH_OKIFUNSPEC | -EAI_NONAME);
  };

  if (!(*pai = malloc(sizeof(struct addrinfo) + sizeof(struct sockaddr_un) + ((req->ai_flags & AI_CANONNAME) ? (strlen(utsname.nodename) + 1): 0))))
    return -EAI_MEMORY;

  (*pai)->ai_next = NULL;
  (*pai)->ai_flags = req->ai_flags;
  (*pai)->ai_family = AF_LOCAL;
  (*pai)->ai_socktype = req->ai_socktype ? req->ai_socktype : SOCK_STREAM;
  (*pai)->ai_protocol = req->ai_protocol;
  (*pai)->ai_addrlen = sizeof(struct sockaddr_un);
  (*pai)->ai_addr = (void *)(*pai) + sizeof(struct addrinfo);
#if SALEN
  ((struct sockaddr_un *)(*pai)->ai_addr)->sun_len = sizeof(struct sockaddr_un);
#endif /* SALEN */
  ((struct sockaddr_un *)(*pai)->ai_addr)->sun_family = AF_LOCAL;
  memset(((struct sockaddr_un *)(*pai)->ai_addr)->sun_path, 0, UNIX_PATH_MAX);
  if (service) {
    char *c;
    if (c = strchr(service->name, '/')) {
      if (strlen(service->name) >= sizeof(((struct sockaddr_un *)(*pai)->ai_addr)->sun_path))
        return (GAIH_OKIFUNSPEC | -EAI_SERVICE);
      strcpy(((struct sockaddr_un *)(*pai)->ai_addr)->sun_path, service->name);
    } else {
      if (strlen(P_tmpdir "/") + 1 + strlen(service->name) >= sizeof(((struct sockaddr_un *)(*pai)->ai_addr)->sun_path))
        return (GAIH_OKIFUNSPEC | -EAI_SERVICE);
      strcpy(((struct sockaddr_un *)(*pai)->ai_addr)->sun_path, P_tmpdir "/");
      strcat(((struct sockaddr_un *)(*pai)->ai_addr)->sun_path, service->name);
    };
  } else {
    if (!tmpnam(((struct sockaddr_un *)(*pai)->ai_addr)->sun_path))
      return (GAIH_OKIFUNSPEC | -EAI_SYSTEM);
  };
  if (req->ai_flags & AI_CANONNAME)
    strcpy((*pai)->ai_canonname = (char *)(*pai) + sizeof(struct addrinfo) + sizeof(struct sockaddr_un), utsname.nodename);
  else
    (*pai)->ai_canonname = NULL;
  return 0;
};
#endif /* LOCAL */

static struct gaih_typeproto gaih_inet_typeproto[] = {
  { 0, 0, NULL },
  { SOCK_STREAM, IPPROTO_TCP, "tcp" },
  { SOCK_DGRAM, IPPROTO_UDP, "udp" },
  { 0, 0, NULL }
};

static int gaih_inet_serv(char *servicename, struct gaih_typeproto *tp, struct gaih_servtuple **st)
{
  struct servent *s;

  if (!(s = getservbyname(servicename, tp->name)))
    return (GAIH_OKIFUNSPEC | -EAI_SERVICE);

  if (!(*st = malloc(sizeof(struct gaih_servtuple))))
    return -EAI_MEMORY;

  (*st)->next = NULL;
  (*st)->socktype = tp->socktype;
  (*st)->protocol = tp->protocol;
  (*st)->port = s->s_port;

  return 0;
}

static int gaih_inet(const char *name, const struct gaih_service *service,
		     const struct addrinfo *req, struct addrinfo **pai)
{
  struct hostent *h = NULL;
  struct gaih_typeproto *tp = gaih_inet_typeproto;
  struct gaih_servtuple *st = &nullserv;
  struct gaih_addrtuple *at = NULL;
  int i;

  if (req->ai_protocol || req->ai_socktype) {
    for (tp++; tp->name &&
	  ((req->ai_socktype != tp->socktype) || !req->ai_socktype) && 
	  ((req->ai_protocol != tp->protocol) || !req->ai_protocol); tp++);
    if (!tp->name)
      if (req->ai_socktype)
	return (GAIH_OKIFUNSPEC | -EAI_SOCKTYPE);
      else
	return (GAIH_OKIFUNSPEC | -EAI_SERVICE);
  }

  if (service) {
    if (service->num < 0) {
      if (tp->name) {
	if (i = gaih_inet_serv(service->name, tp, &st))
	  return i;
      } else {
	struct gaih_servtuple **pst = &st;
	for (tp++; tp->name; tp++) {
	  if (i = gaih_inet_serv(service->name, tp, pst)) {
	    if (i & GAIH_OKIFUNSPEC)
	      continue;
	    goto ret;
	  }
	  pst = &((*pst)->next);
	}
	if (st == &nullserv) {
	  i = (GAIH_OKIFUNSPEC | -EAI_SERVICE);
	  goto ret;
	}
      }
    } else {
      if (!(st = malloc(sizeof(struct gaih_servtuple))))
	return -EAI_MEMORY;

      st->next = NULL;
      st->socktype = tp->socktype;
      st->protocol = tp->protocol;
      st->port = htons(service->num);
    }
  }

  if (!name) {
    if (!(at = malloc(sizeof(struct gaih_addrtuple)))) {
      i = -EAI_MEMORY;
      goto ret;
    };

    memset(at, 0, sizeof(struct gaih_addrtuple));

#if INET6
    if (!(at->next = malloc(sizeof(struct gaih_addrtuple)))) {
      i = -EAI_MEMORY;
      goto ret;
    };

    at->family = AF_INET6;

    memset(at->next, 0, sizeof(struct gaih_addrtuple));
    at->next->family = AF_INET;
#else /* INET6 */
    at->family = AF_INET;
#endif /* INET6 */

    goto build;
  };

  if (!req->ai_family || (req->ai_family == AF_INET)) {
    struct in_addr in_addr;
    if (inet_pton(AF_INET, name, &in_addr) > 0) {
      if (!(at = malloc(sizeof(struct gaih_addrtuple))))
	return -EAI_MEMORY;
      
      memset(at, 0, sizeof(struct gaih_addrtuple));
      
      at->family = AF_INET;
      memcpy(at->addr, &in_addr, sizeof(struct in_addr));
      goto build;
    };
  };

#if INET6
  if (!req->ai_family || (req->ai_family == AF_INET6)) {
    struct in6_addr in6_addr;
    if (inet_pton(AF_INET6, name, &in6_addr) > 0) {
      if (!(at = malloc(sizeof(struct gaih_addrtuple))))
	return -EAI_MEMORY;
      
      memset(at, 0, sizeof(struct gaih_addrtuple));
      
      at->family = AF_INET6;
      memcpy(at->addr, &in6_addr, sizeof(struct in6_addr));
      goto build;
    };
  };
#endif /* INET6 */

  if (!(req->ai_flags & AI_NONAME)) {
#if HOSTTABLE
    if ((i = hosttable_lookup_addr(name, req, &at)) < 0)
      goto ret;

    if (!i)
      goto build;
#endif /* HOSTTABLE */

#if RESOLVER
#if INET6
    if (!req->ai_family || (req->ai_family == AF_INET6))
      if ((i = resolver_lookup_addr(name, T_AAAA, req, &at)) < 0)
	goto ret;
#endif /* INET6 */
    if (!req->ai_family || (req->ai_family == AF_INET))
      if ((i = resolver_lookup_addr(name, T_A, req, &at)) < 0)
	goto ret;

    if (!i)
      goto build;
#endif /* RESOLVER */
  };

  if (!at)
    return (GAIH_OKIFUNSPEC | -EAI_NONAME);

build:
  {
    char *prevcname = NULL;
    struct gaih_servtuple *st2;
    struct gaih_addrtuple *at2 = at;
    int j;

    while(at2) {
      if (req->ai_flags & AI_CANONNAME) {
	if (at2->cname)
	  j = strlen(at2->cname) + 1;
	else
	  if (name)
	    j = strlen(name) + 1;
	  else
	    j = 2;
      } else
	j = 0;

#if INET6
      if (at2->family == AF_INET6)
	i = sizeof(struct sockaddr_in6);
      else
#endif /* INET6 */
	i = sizeof(struct sockaddr_in);

      st2 = st;
      while(st2) {
	if (!(*pai = malloc(sizeof(struct addrinfo) + i + j))) {
	  i = -EAI_MEMORY;
	  goto ret;
	}
	memset(*pai, 0, sizeof(struct addrinfo) + i + j);

	(*pai)->ai_flags = req->ai_flags;
	(*pai)->ai_family = at2->family;
	(*pai)->ai_socktype = st2->socktype;
	(*pai)->ai_protocol = st2->protocol;
	(*pai)->ai_addrlen = i;
	(*pai)->ai_addr = (void *)(*pai) + sizeof(struct addrinfo);
#if SALEN
	((struct sockaddr_in *)(*pai)->ai_addr)->sin_len = i;
#endif /* SALEN */
	((struct sockaddr_in *)(*pai)->ai_addr)->sin_family = at2->family;
	((struct sockaddr_in *)(*pai)->ai_addr)->sin_port = st2->port;

#if INET6
	if (at2->family == AF_INET6)
	  memcpy(&((struct sockaddr_in6 *)(*pai)->ai_addr)->sin6_addr, at2->addr, sizeof(struct in6_addr));
	else
#endif /* INET6 */
	  memcpy(&((struct sockaddr_in *)(*pai)->ai_addr)->sin_addr, at2->addr, sizeof(struct in_addr));

	if (j) {
	  (*pai)->ai_canonname = (void *)(*pai) + sizeof(struct addrinfo) + i;
	  if (at2->cname) {
	    strcpy((*pai)->ai_canonname, at2->cname);
	    if (prevcname != at2->cname) {
	      if (prevcname)
		free(prevcname);
	      prevcname = at2->cname;
	    };
	  } else
	    strcpy((*pai)->ai_canonname, name ? name : "*");
	};

	pai = &((*pai)->ai_next);

	st2 = st2->next;
      };
      at2 = at2->next;
    };
  };

  i = 0;

ret:
  if (st != &nullserv) {
    struct gaih_servtuple *st2 = st;
    while(st) {
      st2 = st->next;
      free(st);
      st = st2;
    }
  }
  if (at) {
    struct gaih_addrtuple *at2 = at;
    while(at) {
      at2 = at->next;
      free(at);
      at = at2;
    }
  }
  return i;
}

struct gaih {
  int family;
  int (*gaih)(const char *name, const struct gaih_service *service,
	      const struct addrinfo *req, struct addrinfo **pai);
};

static struct gaih gaih[] = {
#if INET6
  { PF_INET6, gaih_inet },
#endif /* INET6 */
  { PF_INET, gaih_inet },
#if LOCAL
  { PF_LOCAL, gaih_local },
#endif /* LOCAL */
  { PF_UNSPEC, NULL }
};

int getaddrinfo(const char *name, const char *service,
		const struct addrinfo *req, struct addrinfo **pai)
{
  int i, j = 0;
  struct addrinfo *p = NULL, **end;
  struct gaih *g = gaih, *pg = NULL;
  struct gaih_service gaih_service, *pservice;

  if (name && (name[0] == '*') && !name[1])
    name = NULL;

  if (service && (service[0] == '*') && !service[1])
    service = NULL;

#if BROKEN_LIKE_POSIX
  if (!name && !service)
    return EAI_NONAME;
#endif /* BROKEN_LIKE_POSIX */

  if (!req)
    req = &nullreq;

  if (req->ai_flags & ~3)
    return EAI_BADFLAGS;

#if BROKEN_LIKE_POSIX
  if ((req->ai_flags & AI_CANONNAME) && !name)
    return EAI_BADFLAGS;
#endif /* BROKEN_LIKE_POSIX */

  if (service && *service) {
    char *c;
    gaih_service.num = strtoul(gaih_service.name = (void *)service, &c, 10);
    if (*c) {
      gaih_service.num = -1;
    }
#if BROKEN_LIKE_POSIX
      else
        if (!req->ai_socktype)
          return EAI_SERVICE;
#endif /* BROKEN_LIKE_POSIX */
    pservice = &gaih_service;
  } else
    pservice = NULL;

  if (pai)
    end = &p;
  else
    end = NULL;

  while(g->gaih) {
    if ((req->ai_family == g->family) || !req->ai_family) {
      j++;
      if (!((pg && (pg->gaih == g->gaih)))) {
	pg = g;
	if (i = g->gaih(name, pservice, req, end)) {
	  if (!req->ai_family && (i & GAIH_OKIFUNSPEC))
	    continue;
	  goto gaih_err;
	}
	if (end)
          while(*end) end = &((*end)->ai_next);
      }
    }
    g++;
  }

  if (!j)
    return EAI_FAMILY;

  if (p) {
    *pai = p;
    return 0;
  }

  if (!pai && !i)
    return 0;

gaih_err:
  if (p)
    freeaddrinfo(p);

  if (i)
    return -(i & GAIH_EAI);

  return EAI_NONAME;
}

void freeaddrinfo(struct addrinfo *ai)
{
  struct addrinfo *p;

  while(ai) {
    p = ai;
    ai = ai->ai_next;
    free((void *)p);
  }
}
