/*
 *  This file is part of the KDE libraries
 *  Copyright (C) 2000,2001 Thiago Macieira <thiago.macieira@kdemail.net>
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public License
 *  along with this library; see the file COPYING.LIB.  If not, write to
 *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 *  Boston, MA 02110-1301, USA.
 **/

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <arpa/inet.h>

#include <QtCore/QBool>

#include <kdebug.h>

// This is so that, if addrinfo is defined, it doesn't clobber our definition
// It might be defined in the few cases in which we are replacing the system's
// broken getaddrinfo
#include <netdb.h>

#include <config.h>
#include <config-network.h>
#include "klocale.h"

#ifndef IN6_IS_ADDR_V4MAPPED
#define NEED_IN6_TESTS
#endif
#undef CLOBBER_IN6
#include "netsupp.h" //krazy:exclude=includes (netsupp.h not installed; KDE3 compat code)

#if defined(__hpux) || defined(_HPUX_SOURCE)
extern int h_errno;
#endif

#if !defined(kde_sockaddr_in6)
/*
 * kde_sockaddr_in6 might have got defined even though we #undef'ed
 * CLOBBER_IN6. This happens when we are compiling under --enable-final.
 * However, in that case, if it was defined, that's because ksockaddr.cpp
 * had it defined because sockaddr_in6 didn't exist, and so sockaddr_in6
 * exists and is our kde_sockaddr_in6
 */
# define sockaddr_in6	kde_sockaddr_in6
# define in6_addr	kde_in6_addr
#endif

#ifdef offsetof
#undef offsetof
#endif
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

/*
 * These constants tell the flags in KDE::resolverFlags
 * The user could (but shouldn't) test the variable to know what kind of
 * resolution is supported
 */
#define KRF_KNOWS_AF_INET6		0x01	/* if present, the code knows about AF_INET6 */
#define KRF_USING_OWN_GETADDRINFO	0x02	/* if present, we are using our own getaddrinfo */
#define KRF_USING_OWN_INET_NTOP		0x04	/* if present, we are using our own inet_ntop */
#define KRF_USING_OWN_INET_PTON		0x08	/* if present, we are using our own inet_pton */
#define KRF_CAN_RESOLVE_UNIX		0x100	/* if present, the resolver can resolve Unix sockets */
#define KRF_CAN_RESOLVE_IPV4		0x200	/* if present, the resolver can resolve to IPv4 */
#define KRF_CAN_RESOLVE_IPV6		0x400	/* if present, the resolver can resolve to IPv6 */


static void dofreeaddrinfo(struct addrinfo *ai)
{
  while (ai)
    {
      struct addrinfo *ai2 = ai;
      if (ai->ai_canonname != NULL)
	free(ai->ai_canonname);

      if (ai->ai_addr != NULL)
	free(ai->ai_addr);

      ai = ai->ai_next;
      free(ai2);
    }
}

void kde_freeaddrinfo(struct kde_addrinfo *ai)
{
  if (ai->origin == KAI_LOCALUNIX)
    {
      struct addrinfo *p, *last = NULL;
      /* We've added one AF_UNIX socket in here, to the
       * tail of the linked list. We have to find it */
      for (p = ai->data; p; p = p->ai_next)
	{
	  if (p->ai_family == AF_UNIX)
	    {
	      if (last)
		{
		  last->ai_next = NULL;
		  freeaddrinfo(ai->data);
		}
	      dofreeaddrinfo(p);
	      break;
	    }
	  last = p;
	}
    }
  else
    freeaddrinfo(ai->data);

  free(ai);
}

static struct addrinfo*
make_unix(const char *name, const char *serv)
{
  const char *buf;
  struct addrinfo *p;
  struct sockaddr_un *_sun;
  int len;

  p = (addrinfo*)malloc(sizeof(*p));
  if (p == NULL)
    return NULL;
  memset(p, 0, sizeof(*p));

  if (name != NULL)
    buf = name;
  else
    buf = serv;

  // Calculate length of the binary representation
  len = strlen(buf) + offsetof(struct sockaddr_un, sun_path) + 1;
  if (*buf != '/')
    len += 5;			// strlen("/tmp/");

  _sun = (sockaddr_un*)malloc(len);
  if (_sun == NULL)
    {
      // Oops
      free(p);
      return NULL;
    }

  _sun->sun_family = AF_UNIX;
# ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
  _sun->sun_len = len;
# endif
  if (*buf == '/')
    *_sun->sun_path = '\0';	// empty it
  else
    strcpy(_sun->sun_path, "/tmp/");
  strcat(_sun->sun_path, buf);

  // Set the addrinfo
  p->ai_family = AF_UNIX;
  p->ai_addrlen = len;
  p->ai_addr = (sockaddr*)_sun;
  p->ai_canonname = strdup(buf);

  return p;
}

// Ugh. I hate #ifdefs
// Anyways, here's what this does:
// KDE_IPV6_LOOKUP_MODE != 1, this function doesn't exist
// AF_INET6 not defined, we say there is no IPv6 stack
// otherwise, we try to create a socket.
// returns: 1 for IPv6 stack available, 2 for not available
#if defined(KDE_IPV6_LOOKUP_MODE) && KDE_IPV6_LOOKUP_MODE == 1
static int check_ipv6_stack()
{
# ifndef AF_INET6
  return 2;			// how can we check?
# else
  if (!qgetenv("KDE_NO_IPV6").isEmpty())
     return 2;
  int fd = ::socket(AF_INET6, SOCK_STREAM, 0);
  if (fd == -1)
     return 2;

  ::close(fd);
  return 1;
# endif
}
#endif


/*
 * Reason for using this function: kde_getaddrinfo
 *
 * I decided to add this wrapper function for getaddrinfo
 * and have this be called by KExtendedSocket instead of
 * the real getaddrinfo so that we can make sure that the
 * behavior is the desired one.
 *
 * Currently, the only "undesired" behavior is getaddrinfo
 * not returning PF_UNIX sockets in some implementations.
 *
 * getaddrinfo and family are defined in POSIX 1003.1g
 * (Protocol Independent Interfaces) and in RFC 2553
 * (Basic Socket Interface for IPv6). Whereas the RFC is ambiguosly
 * vague whether this family of functions should return Internet
 * sockets only or not, the name of the POSIX draft says
 * otherwise: it should be independent of protocol.
 *
 * So, my interpretation is that they should return every
 * kind of socket available and known and that's how I
 * designed KExtendedSocket on top of it.
 *
 * That's why there's this wrapper, to make sure PF_UNIX
 * sockets are returned when expected.
 */

int kde_getaddrinfo(const char *name, const char *service,
		    const struct addrinfo* hint,
		    struct kde_addrinfo** result)
{
  struct kde_addrinfo* res;
  struct addrinfo* p;
  int err = EAI_SERVICE;
#if defined(KDE_IPV6_LOOKUP_MODE) && KDE_IPV6_LOOKUP_MODE == 1
  // mode 1: do a check on whether we have an IPv6 stack
  static int ipv6_stack = 0;	// 0: unknown, 1: yes, 2: no
#endif

  // allocate memory for results
  res = (kde_addrinfo*)malloc(sizeof(*res));
  if (res == NULL)
    return EAI_MEMORY;
  res->data = NULL;
  res->origin = KAI_SYSTEM;	// at first, it'll be only system data

  struct addrinfo* last = NULL;

  // Skip the getaddrinfo call and the ipv6 check for a UNIX socket.
  if (hint && (hint->ai_family == PF_UNIX))
  {
     if (service == NULL || *service == '\0')
       goto out;		// can't be Unix if no service was requested

     // Unix sockets must be localhost
     // That is, either name is NULL or, if it's not, it must be empty,
     // "*" or "localhost"
     if (name != NULL && !(name[0] == '\0' || (name[0] == '*' && name[1] == '\0') ||
		strcmp("localhost", name) == 0))
       goto out;		// isn't localhost

     goto do_unix;
  }

#if defined(KDE_IPV6_LOOKUP_MODE) && KDE_IPV6_LOOKUP_MODE != 0
# if KDE_IPV6_LOOKUP_MODE == 1
  // mode 1: do a check on whether we have an IPv6 stack
  if (ipv6_stack == 0)
    ipv6_stack = check_ipv6_stack();

  if (ipv6_stack == 2)
    {
# endif
      // here we have modes 1 and 2 (no lookups)
      // this is shared code
      struct addrinfo our_hint;
      if (hint != NULL)
	{
	  memcpy(&our_hint, hint, sizeof(our_hint));
	  if (our_hint.ai_family == AF_UNSPEC)
	    our_hint.ai_family = AF_INET;
	}
      else
	{
	  memset(&our_hint, 0, sizeof(our_hint));
	  our_hint.ai_family = AF_INET;
	}

      // do the actual resolution
      err = getaddrinfo(name, service, &our_hint, &res->data);
# if KDE_IPV6_LOOKUP_MODE == 1
    }
  else
# endif
#endif
#if defined(KDE_IPV6_LOOKUP_MODE) && KDE_IPV6_LOOKUP_MODE != 2
      // do the IPV6 resolution
      err = getaddrinfo(name, service, hint, &res->data);
#endif

  // Now we have to check whether the user could want a Unix socket

  if (service == NULL || *service == '\0')
    goto out;			// can't be Unix if no service was requested

  // Unix sockets must be localhost
  // That is, either name is NULL or, if it's not, it must be empty,
  // "*" or "localhost"
  if (name != NULL && !(name[0] == '\0' || (name[0] == '*' && name[1] == '\0') ||
			strcmp("localhost", name) == 0))
    goto out;			// isn't localhost

  // Unix sockets can only be returned if the user asked for a PF_UNSPEC
  // or PF_UNIX socket type or gave us a NULL hint
  if (hint != NULL && (hint->ai_family != PF_UNSPEC && hint->ai_family != PF_UNIX))
    goto out;			// user doesn't want Unix

  // If we got here, then it means that the user might be expecting Unix
  // sockets. The user wants a local socket, with a non-null service and
  // has told us that they accept PF_UNIX sockets
  // Check whether the system implementation returned Unix
  if (err == 0)
    for (p = res->data; p; p = p->ai_next)
      {
	last = p;			// we have to find out which one is last anyways
	if (p->ai_family == AF_UNIX)
	  // there is an Unix node
	  goto out;
      }

 do_unix:
  // So, give the user a PF_UNIX socket
  p = make_unix(NULL, service);
  if (p == NULL)
    {
      err = EAI_MEMORY;
      goto out;
    }
  if (hint != NULL)
    p->ai_socktype = hint->ai_socktype;
  if (p->ai_socktype == 0)
    p->ai_socktype = SOCK_STREAM; // default

  if (last)
    last->ai_next = p;
  else
    res->data = p;
  res->origin = KAI_LOCALUNIX;
  *result = res;
  return 0;

 out:
  if (res->data != NULL)
      freeaddrinfo(res->data);
  free(res);
  return err;
}

#if defined(HAVE_GETADDRINFO) && !defined(HAVE_BROKEN_GETADDRINFO)

#define KRF_getaddrinfo		0
#define KRF_resolver		0

#else  // !defined(HAVE_GETADDRINFO) || defined(HAVE_BROKEN_GETADDRINFO)

#define KRF_getaddrinfo			KRF_USING_OWN_GETADDRINFO
#define KRF_resolver			KRF_CAN_RESOLVE_UNIX | KRF_CAN_RESOLVE_IPV4

/*
 * No getaddrinfo() in this system.
 * We shall provide our own
 */

/** TODO
 * Try and use gethostbyname2_r before gethostbyname2 and gethostbyname
 */
static int inet_lookup(const char *name, int portnum, int protonum,
		       struct addrinfo *p, const struct addrinfo *hint,
		       struct addrinfo** result)
{
  struct addrinfo *q;
  struct hostent *h;
  struct sockaddr **psa = NULL;
  int len;

  // TODO
  // Currently, this never resolves IPv6 (need gethostbyname2, etc.)
# ifdef AF_INET6
  if (hint->ai_family == AF_INET6)
    {
      if (p != NULL)
	{
	  *result = p;
	  return 0;
	}
      return EAI_FAIL;
    }
# endif

  q = (addrinfo*)malloc(sizeof(*q));
  if (q == NULL)
    {
      freeaddrinfo(p);
      return EAI_MEMORY;
    }

  h = gethostbyname(name);
  if (h == NULL)
    {
      if (p != NULL)
	{
	  // There already is a suitable result
	  *result = p;
	  return 0;
	}

      switch (h_errno)
	{
	case HOST_NOT_FOUND:
	  return EAI_NONAME;
	case TRY_AGAIN:
	  return EAI_AGAIN;
	case NO_RECOVERY:
	  return EAI_FAIL;
	case NO_ADDRESS:
	  return EAI_NODATA;
	default:
	  // EH!?
	  return EAI_FAIL;
	}
    }

  // convert the hostent to addrinfo
  if (h->h_addrtype == AF_INET && (hint->ai_family == AF_INET || hint->ai_family == AF_UNSPEC))
    len = sizeof(struct sockaddr_in);
# ifdef AF_INET6
  else if (h->h_addrtype == AF_INET6 && (hint->ai_family == AF_INET6 ||
					 hint->ai_family == AF_UNSPEC))
    len = sizeof(struct sockaddr_in6);
# endif
  else
    {
      // We don't know what to do with these addresses
      // Or gethostbyname returned information we don't want
      if (p != NULL)
	{
	  *result = p;
	  return 0;
	}
      return EAI_NODATA;
    }

  q->ai_flags = 0;
  q->ai_family = h->h_addrtype;
  q->ai_socktype = hint->ai_socktype;
  q->ai_protocol = protonum;
  q->ai_addrlen = len;

  q->ai_addr = (sockaddr*)malloc(len);
  if (q->ai_addr == NULL)
    {
      free(q);
      freeaddrinfo(p);
      return EAI_MEMORY;
    }
  if (h->h_addrtype == AF_INET)
    {
      struct sockaddr_in *sin = (sockaddr_in*)q->ai_addr;
      sin->sin_family = AF_INET;
# ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
      sin->sin_len = sizeof(*sin);
# endif
      sin->sin_port = portnum;
      memcpy(&sin->sin_addr, h->h_addr, h->h_length);
    }
# ifdef AF_INET6
  else if (h->h_addrtype == AF_INET6)
    {
      struct sockaddr_in6 *sin6 = (sockaddr_in6*)q->ai_addr;
      sin6->sin6_family = AF_INET6;
#  ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
      sin6->sin6_len = sizeof(*sin6);
#  endif
      sin6->sin6_port = portnum;
      sin6->sin6_flowinfo = 0;
      memcpy(&sin6->sin6_addr, h->h_addr, h->h_length);
      sin6->sin6_scope_id = 0;
    }
# endif

  if (hint->ai_flags & AI_CANONNAME)
    q->ai_canonname = strdup(h->h_name);
  else
    q->ai_canonname = NULL;

  q->ai_next = p;
  p = q;

  // cycle through the rest of the hosts;
  for (psa = (sockaddr**)h->h_addr_list + 1; *psa; psa++)
    {
      q = (addrinfo*)malloc(sizeof(*q));
      if (q == NULL)
	{
	  freeaddrinfo(p);
	  return EAI_MEMORY;
	}
      memcpy(q, p, sizeof(*q));

      q->ai_addr = (sockaddr*)malloc(h->h_length);
      if (q->ai_addr == NULL)
	{
	  freeaddrinfo(p);
	  free(q);
	  return EAI_MEMORY;
	}
      if (h->h_addrtype == AF_INET)
	{
	  struct sockaddr_in *sin = (sockaddr_in*)q->ai_addr;
	  sin->sin_family = AF_INET;
# ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
	  sin->sin_len = sizeof(*sin);
# endif
	  sin->sin_port = portnum;
	  memcpy(&sin->sin_addr, *psa, h->h_length);
	}
# ifdef AF_INET6
      else if (h->h_addrtype == AF_INET6)
	{
	  struct sockaddr_in6 *sin6 = (sockaddr_in6*)q->ai_addr;
	  sin6->sin6_family = AF_INET6;
#  ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
	  sin6->sin6_len = sizeof(*sin6);
#  endif
	  sin6->sin6_port = portnum;
	  sin6->sin6_flowinfo = 0;
	  memcpy(&sin6->sin6_addr, *psa, h->h_length);
	  sin6->sin6_scope_id = 0;
	}
# endif

      if (q->ai_canonname != NULL)
	q->ai_canonname = strdup(q->ai_canonname);

      q->ai_next = p;
      p = q;
    }

  *result = p;
  return 0;			// Whew! Success!
}

static int make_inet(const char *name, int portnum, int protonum, struct addrinfo *p,
		     const struct addrinfo *hint, struct addrinfo** result)
{
  struct addrinfo *q;

  do
    {
      // This 'do' is here just so that we can 'break' out of it

      if (name != NULL)
	{
	  // first, try to use inet_pton before resolving
	  // it will catch IP addresses given without having to go to lookup
	  struct sockaddr_in *sin;
	  struct in_addr in;
# ifdef AF_INET6
	  struct sockaddr_in6 *sin6;
	  struct in6_addr in6;

	  if (hint->ai_family == AF_INET6 || (hint->ai_family == AF_UNSPEC &&
					      strchr(name, ':') != NULL))
	    {
	      // yes, this is IPv6
	      if (inet_pton(AF_INET6, name, &in6) != 1)
		{
		  if (hint->ai_flags & AI_NUMERICHOST)
		    {
		      freeaddrinfo(p);
		      return EAI_FAIL;
		    }
		  break;	// not a numeric host
		}

	      sin6 = (sockaddr_in6*)malloc(sizeof(*sin6));
	      if (sin6 == NULL)
		{
		  freeaddrinfo(p);
		  return EAI_MEMORY;
		}
	      memcpy(&sin6->sin6_addr, &in6, sizeof(in6));

	      if (strchr(name, '%') != NULL)
		{
		  errno = 0;
		  sin6->sin6_scope_id = strtoul(strchr(name, '%') + 1, NULL, 10);
		  if (errno != 0)
		    sin6->sin6_scope_id = 0; // no interface
		}

	      q = (addrinfo*)malloc(sizeof(*q));
	      if (q == NULL)
		{
		  freeaddrinfo(p);
		  free(sin6);
		  return EAI_MEMORY;
		}

	      sin6->sin6_family = AF_INET6;
#  ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
	      sin6->sin6_len = sizeof(*sin6);
#  endif
	      sin6->sin6_port = portnum;
	      sin6->sin6_flowinfo = 0;

	      q->ai_flags = 0;
	      q->ai_family = AF_INET6;
	      q->ai_socktype = hint->ai_socktype;
	      q->ai_protocol = protonum;
	      q->ai_addrlen = sizeof(*sin6);
	      q->ai_canonname = NULL;
	      q->ai_addr = (sockaddr*)sin6;
	      q->ai_next = p;

	      *result = q;
	      return 0;		// success!
	    }
# endif // AF_INET6

	  if (hint->ai_family == AF_INET || hint->ai_family == AF_UNSPEC)
	    {
	      // This has to be IPv4
	      if (inet_pton(AF_INET, name, &in) != 1)
		{
		  if (hint->ai_flags & AI_NUMERICHOST)
		    {
		      freeaddrinfo(p);
		      return EAI_FAIL;	// invalid, I guess
		    }
		  break;	// not a numeric host, do lookup
		}

	      sin = (sockaddr_in*)malloc(sizeof(*sin));
	      if (sin == NULL)
		{
		  freeaddrinfo(p);
		  return EAI_MEMORY;
		}

	      q = (addrinfo*)malloc(sizeof(*q));
	      if (q == NULL)
		{
		  freeaddrinfo(p);
		  free(sin);
		  return EAI_MEMORY;
		}

	      sin->sin_family = AF_INET;
# ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
	      sin->sin_len = sizeof(*sin);
# endif
	      sin->sin_port = portnum;
	      sin->sin_addr = in;

	      q->ai_flags = 0;
	      q->ai_family = AF_INET;
	      q->ai_socktype = hint->ai_socktype;
	      q->ai_protocol = protonum;
	      q->ai_addrlen = sizeof(*sin);
	      q->ai_canonname = NULL;
	      q->ai_addr = (sockaddr*)sin;
	      q->ai_next = p;
	      *result = q;
	      return 0;
	    }

	  // Eh, what!?
	  // One of the two above has to have matched
	  kError() << "I wasn't supposed to get here!";
	}
    } while (false);

  // This means localhost
  if (name == NULL)
    {
      struct sockaddr_in *sin = (sockaddr_in*)malloc(sizeof(*sin));
# ifdef AF_INET6
      struct sockaddr_in6 *sin6;
# endif

      if (hint->ai_family == AF_INET || hint->ai_family == AF_UNSPEC)
	{
	  if (sin == NULL)
	    {
	      free(sin);
	      freeaddrinfo(p);
	      return EAI_MEMORY;
	    }

	  // Do IPv4 first
	  q = (addrinfo*)malloc(sizeof(*q));
	  if (q == NULL)
	    {
	      free(sin);
	      freeaddrinfo(p);
	      return EAI_MEMORY;
	    }

	  sin->sin_family = AF_INET;
# ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
	  sin->sin_len = sizeof(*sin);
# endif
	  sin->sin_port = portnum;
	  if (hint->ai_flags & AI_PASSIVE)
	    *(quint32*)&sin->sin_addr = INADDR_ANY;
	  else
	    *(quint32*)&sin->sin_addr = htonl(INADDR_LOOPBACK);
	  q->ai_flags = 0;
	  q->ai_family = AF_INET;
	  q->ai_socktype = hint->ai_socktype;
	  q->ai_protocol = protonum;
	  q->ai_addrlen = sizeof(*sin);
	  q->ai_canonname = NULL;
	  q->ai_addr = (sockaddr*)sin;
	  q->ai_next = p;
	  p = q;
	}

# ifdef AF_INET6
      // Try now IPv6

      if (hint->ai_family == AF_INET6 || hint->ai_family == AF_UNSPEC)
	{
	  sin6 = (sockaddr_in6*)malloc(sizeof(*sin6));
	  q = (addrinfo*)malloc(sizeof(*q));
	  if (q == NULL || sin6 == NULL)
	    {
	      free(sin6);
	      free(q);
	      freeaddrinfo(p);
	      return EAI_MEMORY;
	    }

	  sin6->sin6_family = AF_INET6;
#  ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
	  sin6->sin6_len = sizeof(*sin6);
#  endif
	  sin6->sin6_port = portnum;
	  sin6->sin6_flowinfo = 0;
	  sin6->sin6_scope_id = 0;

      // We don't want to use in6addr_loopback and in6addr_any
	  memset(&sin6->sin6_addr, 0, sizeof(sin6->sin6_addr));
	  if ((hint->ai_flags & AI_PASSIVE) == 0)
	    ((char*)&sin6->sin6_addr)[15] = 1;

	  q->ai_flags = 0;
	  q->ai_family = AF_INET6;
	  q->ai_socktype = hint->ai_socktype;
	  q->ai_protocol = protonum;
	  q->ai_addrlen = sizeof(*sin6);
	  q->ai_canonname = NULL;
	  q->ai_addr = (sockaddr*)sin6;
	  q->ai_next = p;
	  p = q;
	}

# endif // AF_INET6

      *result = p;
      return 0;			// success!
    }

  return inet_lookup(name, portnum, protonum, p, hint, result);
}


int getaddrinfo(const char *name, const char *serv,
		const struct addrinfo* hint,
		struct addrinfo** result)
{
  unsigned short portnum;	// remember to store in network byte order
  int protonum = IPPROTO_TCP;
  const char *proto = "tcp";
  struct addrinfo *p = NULL;

  // Sanity checks:
  if (hint == NULL || result == NULL)
    return EAI_BADFLAGS;
  if (hint->ai_family != AF_UNSPEC && hint->ai_family != AF_UNIX &&
      hint->ai_family != AF_INET
# ifdef AF_INET6
      && hint->ai_family != AF_INET6
# endif
      )
    return EAI_FAMILY;
  if (hint->ai_socktype != 0 && hint->ai_socktype != SOCK_STREAM &&
      hint->ai_socktype != SOCK_DGRAM)
    return EAI_SOCKTYPE;

  // Treat hostname of "*" as NULL, which means localhost
  if (name != NULL && ((*name == '*' && name[1] == '\0') || *name == '\0'))
    name = NULL;
  // Treat service of "*" as NULL, which I guess means no port (0)
  if (serv != NULL && ((*serv == '*' && serv[1] == '\0') || *serv == '\0'))
    serv = NULL;

  if (name == NULL && serv == NULL) // what the hell do you want?
    return EAI_NONAME;

  // This is just to make it easier
  if (name != NULL && strcmp(name, "localhost") == 0)
     name = NULL;

  // First, check for a Unix socket
  // family must be either AF_UNIX or AF_UNSPEC
  // either of name or serv must be set, the other must be NULL or empty
  if (hint->ai_family == AF_UNIX || hint->ai_family == AF_UNSPEC)
    {
      if (name != NULL && serv != NULL)
	{
	  // This is not allowed
	  if (hint->ai_family == AF_UNIX)
	    return EAI_BADFLAGS;
	}
      else
	{
	  p = make_unix(name, serv);
	  if (p == NULL)
	    return EAI_MEMORY;

	  p->ai_socktype = hint->ai_socktype;
	  // If the name/service started with a slash, then this *IS*
	  // only a Unix socket. Return.
	  if (hint->ai_family == AF_UNIX || ((name != NULL && *name == '/') ||
					      (serv != NULL && *serv == '/')))
	    {
	      *result = p;
	      return 0;		// successful lookup
	    }
	}
    }

  // Lookup the service name, if required
  if (serv != NULL)
    {
      char *tail;
      struct servent *sent;

      portnum = htons((unsigned)strtoul(serv, &tail, 10));
      if (*tail != '\0')
	{
	  // not a number. We have to do the lookup
	  if (hint->ai_socktype == SOCK_DGRAM)
	    {
	      proto = "udp";
	      protonum = IPPROTO_UDP;
	    }

	  sent = getservbyname(serv, proto);
	  if (sent == NULL)	// no service?
	    {
	      if (p == NULL)
		return EAI_NONAME;
	      else
		return 0;	// a Unix socket available
	    }

	  portnum = sent->s_port;
	}
    }
  else
    portnum = 0;		// no port number

  return make_inet(name, portnum, protonum, p, hint, result);
}

void freeaddrinfo(struct addrinfo *p)
{
  dofreeaddrinfo(p);
}

#ifndef HAVE_GAI_STRERROR_PROTO
char *gai_strerror(int errorcode)
{
  static const char messages[] =
  {
    I18N_NOOP("no error")"\0"	// 0
    I18N_NOOP("address family for nodename not supported")"\0" // EAI_ADDRFAMILY
    I18N_NOOP("temporary failure in name resolution")"\0"	// EAI_AGAIN
    I18N_NOOP("invalid value for 'ai_flags'")"\0"	// EAI_BADFLAGS
    I18N_NOOP("non-recoverable failure in name resolution")"\0" // EAI_FAIL
    I18N_NOOP("'ai_family' not supported")"\0"	// EAI_FAMILY
    I18N_NOOP("memory allocation failure")"\0"	// EAI_MEMORY
    I18N_NOOP("no address associated with nodename")"\0"	// EAI_NODATA
    I18N_NOOP("name or service not known")"\0"	// EAI_NONAME
    I18N_NOOP("servname not supported for ai_socktype")"\0" // EAI_SERVICE
    I18N_NOOP("'ai_socktype' not supported")"\0"	// EAI_SOCKTYPE
    I18N_NOOP("system error")"\0"			// EAI_SYSTEM
    "\0"
  };

  static const int messages_indices[] =
  {
      0,    9,   51,   88,  117,  160,  186,  212,
      248,  274,  313,  341,    0
  };

  Q_ASSERT(sizeof(messages_indices)/sizeof(messages_indices[0]) >= EAI_SYSTEM);
  if (errorcode > EAI_SYSTEM || errorcode < 0)
    return NULL;

  static char buffer[200];
  strcpy(buffer, i18n(messages + messages_indices[errorcode]).toLocal8Bit());
  return buffer;
}
#endif

static void findport(unsigned short port, char *serv, size_t servlen, int flags)
{
  if (serv == NULL)
    return;

  if ((flags & NI_NUMERICSERV) == 0)
    {
      struct servent *sent;
      sent = getservbyport(ntohs(port), flags & NI_DGRAM ? "udp" : "tcp");
      if (sent != NULL && servlen > strlen(sent->s_name))
	{
	  strcpy(serv, sent->s_name);
	  return;
	}
    }

  qsnprintf(serv, servlen, "%u", ntohs(port));
}

int getnameinfo(const struct sockaddr *sa, kde_socklen_t salen,
		char *host, size_t hostlen, char *serv, size_t servlen,
		int flags)
{
  union
    {
      const sockaddr *sa;
      const sockaddr_un *_sun;
      const sockaddr_in *sin;
      const sockaddr_in6 *sin6;
  } s;

  if ((host == NULL || hostlen == 0) && (serv == NULL || servlen == 0))
    return 1;

  s.sa = sa;
  if (s.sa->sa_family == AF_UNIX)
    {
      if (salen < offsetof(struct sockaddr_un, sun_path) + strlen(s._sun->sun_path) + 1)
	return 1;		// invalid socket

      if (servlen && serv != NULL)
	*serv = '\0';
      if (host != NULL && hostlen > strlen(s._sun->sun_path))
	strcpy(host, s._sun->sun_path);

      return 0;
    }
  else if (s.sa->sa_family == AF_INET)
    {
      if (salen < offsetof(struct sockaddr_in, sin_addr) + sizeof(s.sin->sin_addr))
	return 1;		// invalid socket

      if (flags & NI_NUMERICHOST)
	inet_ntop(AF_INET, &s.sin->sin_addr, host, hostlen);
      else
	{
	  // have to do lookup
	  struct hostent *h = gethostbyaddr((const char*)&s.sin->sin_addr, sizeof(s.sin->sin_addr),
					    AF_INET);
	  if (h == NULL && flags & NI_NAMEREQD)
	    return 1;
	  else if (h == NULL)
	    inet_ntop(AF_INET, &s.sin->sin_addr, host, hostlen);
	  else if (host != NULL && hostlen > strlen(h->h_name))
	    strcpy(host, h->h_name);
	  else
	    return 1;		// error
	}

      findport(s.sin->sin_port, serv, servlen, flags);
    }
# ifdef AF_INET6
  else if (s.sa->sa_family == AF_INET6)
    {
      if (salen < offsetof(struct sockaddr_in6, sin6_addr) + sizeof(s.sin6->sin6_addr))
	return 1;		// invalid socket

      if (flags & NI_NUMERICHOST)
	inet_ntop(AF_INET6, &s.sin6->sin6_addr, host, hostlen);
      else
	{
	  // have to do lookup
	  struct hostent *h = gethostbyaddr((const char*)&s.sin->sin_addr, sizeof(s.sin->sin_addr),
					    AF_INET6);
	  if (h == NULL && flags & NI_NAMEREQD)
	    return 1;
	  else if (h == NULL)
	    inet_ntop(AF_INET6, &s.sin6->sin6_addr, host, hostlen);
	  else if (host != NULL && hostlen > strlen(h->h_name))
	    strcpy(host, h->h_name);
	  else
	    return 1;		// error
	}

      findport(s.sin6->sin6_port, serv, servlen, flags);
    }
# endif // AF_INET6

  return 1;			// invalid family
}

#endif // HAVE_GETADDRINFO

#ifndef HAVE_INET_NTOP

#define KRF_inet_ntop	KRF_USING_OWN_INET_NTOP

static void add_dwords(char *buf, quint16 *dw, int count)
{
  int i = 1;
  sprintf(buf + strlen(buf), "%x", ntohs(dw[0]));
  while (--count)
    sprintf(buf + strlen(buf), ":%x", ntohs(dw[i++]));
}

const char* inet_ntop(int af, const void *cp, char *buf, size_t len)
{
  char buf2[sizeof "1234:5678:9abc:def0:1234:5678:255.255.255.255" + 1];
  quint8 *data = (quint8*)cp;

  if (af == AF_INET)
    {
      sprintf(buf2, "%u.%u.%u.%u", data[0], data[1], data[2], data[3]);

      if (len > strlen(buf2))
	{
	  strcpy(buf, buf2);
	  return buf;
	}

      errno = ENOSPC;
      return NULL;		// failed
    }

# ifdef AF_INET6
  if (af == AF_INET6)
    {
      quint16 *p = (quint16*)data;
      quint16 *longest = NULL, *cur = NULL;
      int longest_length = 0, cur_length;
      int i;

      if (KDE_IN6_IS_ADDR_V4MAPPED(p) || KDE_IN6_IS_ADDR_V4COMPAT(p))
	sprintf(buf2, "::%s%u.%u.%u.%u",
		KDE_IN6_IS_ADDR_V4MAPPED(p) ? "ffff:" : "",
		buf[12], buf[13], buf[14], buf[15]);
      else
	{
	  // find the longest sequence of zeroes
	  for (i = 0; i < 8; --i)
	    if (cur == NULL && p[i] == 0)
	      {
		// a zero, start the sequence
		cur = p + i;
		cur_length = 1;
	      }
	    else if (cur != NULL && p[i] == 0)
	      // part of the sequence
	      cur_length++;
	    else if (cur != NULL && p[i] != 0)
	      {
		// end of the sequence
		if (cur_length > longest_length)
		  {
		    longest_length = cur_length;
		    longest = cur;
		  }
		cur = NULL;		// restart sequence
	      }
	  if (cur != NULL && cur_length > longest_length)
	    {
	      longest_length = cur_length;
	      longest = cur;
	    }

	  if (longest_length > 1)
	    {
	      // We have a candidate
	      buf2[0] = '\0';
	      if (longest != p)
		add_dwords(buf2, p, longest - p);
	      strcat(buf2, "::");
	      if (longest + longest_length < p + 8)
		add_dwords(buf2, longest + longest_length, 8 - (longest - p) - longest_length);
	    }
	  else
	    {
	      // Nope, no candidate
	      buf2[0] = '\0';
	      add_dwords(buf2, p, 8);
	    }
	}

      if (strlen(buf2) < len)
	{
	  strcpy(buf, buf2);
	  return buf;
	}

      errno = ENOSPC;
      return NULL;
    }
# endif

  errno = EAFNOSUPPORT;
  return NULL;			// a family we don't know about
}

#else	// HAVE_INET_NTOP

#define KRF_inet_ntop		0

#endif	// HAVE_INET_NTOP

#ifndef HAVE_INET_PTON

#define KRF_inet_pton		KRF_USING_OWN_INET_PTON
int inet_pton(int af, const char *cp, void *buf)
{
  if (af == AF_INET)
    {
      // Piece of cake
      unsigned p[4];
      unsigned char *q = (unsigned char*)buf;
      if (sscanf(cp, "%u.%u.%u.%u", p, p + 1, p + 2, p + 3) != 4)
	return 0;

      if (p[0] > 0xff || p[1] > 0xff || p[2] > 0xff || p[3] > 0xff)
	return 0;

      q[0] = p[0];
      q[1] = p[1];
      q[2] = p[2];
      q[3] = p[3];

      return 1;
    }

# ifdef AF_INET6
  else if (af == AF_INET6)
    {
      quint16 addr[8];
      const char *p = cp;
      int n = 0, start = 8;
      bool has_v4 = strchr(p, '.') != NULL;

      memset(addr, 0, sizeof(addr));

      if (*p == '\0' || p[1] == '\0')
	return 0;		// less than 2 chars is not valid

      if (*p == ':' && p[1] == ':')
	{
	  start = 0;
	  p += 2;
	}
      while (*p)
	{
	  if (has_v4 && inet_pton(AF_INET, p, addr + n) != 0)
	    {
	      // successful v4 convertion
	      addr[n] = ntohs(addr[n]);
	      n++;
	      addr[n] = ntohs(addr[n]);
	      n++;
	      break;
	    }
	  if (sscanf(p, "%hx", addr + n++) != 1)
	    return 0;

	  while (*p && *p != ':')
	    p++;
	  if (!*p)
	    break;
	  p++;

	  if (*p == ':')	// another ':'?
	    {
	      if (start != 8)
		return 0;	// two :: were found
	      start = n;
	      p++;
	    }
	}

      // if start is not 8, then a "::" was found at word 'start'
      // n is the number of converted words
      // n == 8 means everything was converted and no moving is necessary
      // n < 8 means that we have to move n - start words 8 - n words to the right
      if (start == 8 && n != 8)
	return 0;		// bad conversion
      memmove(addr + start + (8 - n), addr + start, (n - start) * sizeof(quint16));
      memset(addr + start, 0, (8 - n) * sizeof(quint16));

      // check the byte order
      // The compiler should optimize this out in big endian machines
      if (htons(0x1234) != 0x1234)
	for (n = 0; n < 8; ++n)
	  addr[n] = htons(addr[n]);

      memcpy(buf, addr, sizeof(addr));
      return 1;
    }
# endif

  errno = EAFNOSUPPORT;
  return -1;			// unknown family
}

#else  // HAVE_INET_PTON

#define KRF_inet_pton		0

#endif // HAVE_INET_PTON

#ifdef AF_INET6
# define KRF_afinet6	KRF_KNOWS_AF_INET6
#else
# define KRF_afinet6	0
#endif

namespace KDE
{
  /** @internal */
  extern const int resolverFlags = KRF_getaddrinfo | KRF_resolver | KRF_afinet6 | KRF_inet_ntop | KRF_inet_pton;
}
