#include <sys/types.h>
#include <sys/socket.h>
#include <sys/param.h>
#if LOCAL
#include <sys/utsname.h>
#include <sys/un.h>
#endif /* LOCAL */
#include <netinet/in.h>
#ifdef INET6
#include <netinet6/in6.h>
#endif /* INET6 */
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <errno.h>

#include "support.h"

#ifndef AF_LOCAL
#define AF_LOCAL AF_UNIX
#endif /* AF_LOCAL */

#if HOSTTABLE
struct hostent *_addr2hostname_hosts(const char *, int, int);
#endif /* HOSTTABLE */

int nrl_getnameinfo(const struct sockaddr *sa, size_t addrlen, char *host,
  size_t hostlen, char *serv, size_t servlen, int flags)
{
  char buffer[MAXHOSTNAMELEN];
  int serrno = errno;

#if SALEN
  if (sa->sa_len != addrlen)
    return -1;
#endif /* SALEN */

  if (host && hostlen) {
    switch(sa->sa_family) {
#if LOCAL
    case AF_LOCAL: 
      if (flags & 1)
        strcpy(buffer, "localhost");
      else {
        struct utsname utsname;
        if (uname(&utsname))
          return -1;
        strcpy(buffer, utsname.nodename);
      };
      flags |= 4;
      break;
#endif /* LOCAL */
    case AF_INET: {
      struct hostent *h = NULL;

#if HOSTTABLE
      if (!(flags & 1) && !h && (h = _addr2hostname_hosts((void *)&(((struct sockaddr_in *)sa)->sin_addr), sizeof(struct in_addr), AF_INET)))
	strncpy(buffer, h->h_name, sizeof(buffer));
#endif /* HOSTTABLE */

#if RESOLVER
      if (!(flags & 1) && !h && (h = gethostbyaddr((void *)&(((struct sockaddr_in *)sa)->sin_addr), sizeof(struct in_addr), AF_INET)))
	strncpy(buffer, h->h_name, sizeof(buffer));
#endif /* RESOLVER */

      if (!h)
	if (inet_ntop(AF_INET, (void *)&(((struct sockaddr_in *)sa)->sin_addr), buffer, sizeof(buffer)))
	  flags |= 4;
	else
	  return -1;
      }
      break;
#ifdef INET6
    case AF_INET6: {
      struct hostent *h = NULL;

#if HOSTTABLE
      if (!(flags & 1) && !h && (h = _addr2hostname_hosts((void *)&(((struct sockaddr_in6 *)sa)->sin6_addr), sizeof(struct in6_addr), AF_INET6)))
	strncpy(buffer, h->h_name, sizeof(buffer));
#endif /* HOSTTABLE */

#if RESOLVER
      if (!(flags & 1) && !h && (h = gethostbyaddr((void *)&(((struct sockaddr_in6 *)sa)->sin6_addr), sizeof(struct in6_addr), AF_INET6)))
	strncpy(buffer, h->h_name, sizeof(buffer));
#endif /* RESOLVER */

      if (!h)
	if (inet_ntop(AF_INET6, (void *)&(((struct sockaddr_in6 *)sa)->sin6_addr), buffer, sizeof(buffer)))
	  flags |= 4;
	else
	  return -1;
      }
      break;
#endif /* INET6 */
    default:
      return -1;
    }
    if (!(flags & 5)) {
      char *c;
      if ((c = nrl_domainname()) && (c = strstr(buffer, c)) && (c != buffer) && (*(--c) == '.'))
        *c = 0;
    }
    strncpy(host, buffer, hostlen);
    host[hostlen-1] = 0;
  }

  if (serv && servlen) {
    switch(sa->sa_family) {
#if LOCAL
    case AF_LOCAL:
      strncpy(serv, ((struct sockaddr_un *)sa)->sun_path, servlen);
      break;
#endif /* LOCAL */
    case AF_INET:
#ifdef INET6
    case AF_INET6:
#endif /* INET6 */
      {
	struct servent *s = NULL;

	if (!(flags & 2) && (s = getservbyport(((struct sockaddr_in *)sa)->sin_port, "tcp")))
	  strncpy(serv, s->s_name, servlen);

        if (!s) {
	  sprintf(buffer, "%d", ntohs(((struct sockaddr_in *)sa)->sin_port));
	  strncpy(serv, buffer, servlen);
	}

        serv[servlen-1] = 0;
      }
      break;
    default:
      return -1;
    }
  }

  errno = serrno;
  return 0;
}

int getnameinfo(const struct sockaddr *sa, size_t addrlen, char *host,
  size_t hostlen, char *serv, size_t servlen)
{
  return nrl_getnameinfo(sa, addrlen, host, hostlen, serv, servlen, 0);
}
