/*
 * support/export/hostname.c
 *
 * Functions for hostname.
 *
 */

#include "config.h"

/*
#define TEST
*/

#include <string.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <xlog.h>
#ifdef TEST
#define xmalloc malloc
#else
#include "xmalloc.h"
#include "misc.h"
#endif

#define ALIGNMENT	sizeof (char *)

static int
align (int len, int al)
{
  int i;
  i = len % al;
  if (i)
    len += al - i;
  return len;
}

struct hostent *
get_hostent (const char *addr, int len, int type)
{
  struct hostent *cp;
  int len_ent;
  const char *name;
  int len_name;
  int num_aliases = 1;
  int len_aliases = sizeof (char *);
  int num_addr_list = 1;
  int len_addr_list = sizeof (char *);
  int pos;
  struct in_addr *ipv4;

  switch (type)
    {
    case AF_INET:
      ipv4 = (struct in_addr *) addr;
      name = inet_ntoa (*ipv4);
      break;

    default:
      return NULL;
    }

  len_ent = align (sizeof (*cp), ALIGNMENT);
  len_name = align (strlen (name) + 1, ALIGNMENT);

  num_addr_list++;
  len_addr_list += align (len, ALIGNMENT) + sizeof (char *);

  cp = (struct hostent *) xmalloc (len_ent + len_name + len_aliases
				   + len_addr_list);

  cp->h_addrtype = type;
  cp->h_length = len;
  pos = len_ent;
  cp->h_name = (char *) &(((char *) cp) [pos]);
  strcpy (cp->h_name, name);

  pos += len_name;
  cp->h_aliases = (char **) &(((char *) cp) [pos]);
  pos += num_aliases * sizeof (char *);
  cp->h_aliases [0] = NULL;

  pos = len_ent + len_name + len_aliases;
  cp->h_addr_list = (char **) &(((char *) cp) [pos]);
  pos += num_addr_list * sizeof (char *);
  cp->h_addr_list [0] = (char *) &(((char *) cp) [pos]);
  memcpy (cp->h_addr_list [0], addr, cp->h_length);
  pos += align (cp->h_length, ALIGNMENT);
  cp->h_addr_list [1] = NULL;

  return cp;
}

struct hostent *
hostent_dup (struct hostent *hp)
{
  int len_ent = align (sizeof (*hp), ALIGNMENT);
  int len_name = align (strlen (hp->h_name) + 1, ALIGNMENT);
  int num_aliases = 1;
  int len_aliases = sizeof (char *);
  int num_addr_list = 1;
  int len_addr_list = sizeof (char *);
  int pos, i;
  char **sp;
  struct hostent *cp;

  for (sp = hp->h_aliases; sp && *sp; sp++)
    {
      num_aliases++;
      len_aliases += align (strlen (*sp) + 1, ALIGNMENT)
		     + sizeof (char *);
    }

  for (sp = hp->h_addr_list; *sp; sp++)
    {
      num_addr_list++;
      len_addr_list += align (hp->h_length, ALIGNMENT)
		       + sizeof (char *);
    }
  
  cp = (struct hostent *) xmalloc (len_ent + len_name + len_aliases
				   + len_addr_list);

  *cp = *hp;
  pos = len_ent;
  cp->h_name = (char *) &(((char *) cp) [pos]);
  strcpy (cp->h_name, hp->h_name);

  pos += len_name;
  cp->h_aliases = (char **) &(((char *) cp) [pos]);
  pos += num_aliases * sizeof (char *);
  for (sp = hp->h_aliases, i = 0; i < num_aliases; i++, sp++)
    if (sp && *sp)
      {
	cp->h_aliases [i] = (char *) &(((char *) cp) [pos]);
	strcpy (cp->h_aliases [i], *sp);
	pos += align (strlen (*sp) + 1, ALIGNMENT);
      }
    else
      cp->h_aliases [i] = NULL;

  pos = len_ent + len_name + len_aliases;
  cp->h_addr_list = (char **) &(((char *) cp) [pos]);
  pos += num_addr_list * sizeof (char *);
  for (sp = hp->h_addr_list, i = 0; i < num_addr_list; i++, sp++)
    if (*sp)
      {
	cp->h_addr_list [i] = (char *) &(((char *) cp) [pos]);
	memcpy (cp->h_addr_list [i], *sp, hp->h_length);
	pos += align (hp->h_length, ALIGNMENT);
      }
    else
      cp->h_addr_list [i] = *sp;

  return cp;
}

static int
is_hostname(const char *sp)
{
  if (*sp == '\0' || *sp == '@')
    return 0;

  for (; *sp; sp++)
    {
      if (*sp == '*' || *sp == '?' || *sp == '[' || *sp == '/')
	return 0;
      if (*sp == '\\' && sp[1])
	sp++;
    }

  return 1;
}

int
matchhostname (const char *h1, const char *h2)
{
  struct hostent *hp1, *hp2;
  int status;

  if (strcasecmp (h1, h2) == 0)
    return 1;

  if (!is_hostname (h1) || !is_hostname (h2))
    return 0;

  hp1 = gethostbyname (h1);
  if (hp1 == NULL)
    return 0;

  hp1 = hostent_dup (hp1);

  hp2 = gethostbyname (h2);
  if (hp2)
    {
      if (strcasecmp (hp1->h_name, hp2->h_name) == 0)
	status = 1;
      else
	{
	  char **ap1, **ap2;

	  status = 0;
	  for (ap1 = hp1->h_addr_list; *ap1 && status == 0; ap1++)
	    for (ap2 = hp2->h_addr_list; *ap2; ap2++)
	      if (memcmp (*ap1, *ap2, sizeof (struct in_addr)) == 0)
		{
		  status = 1;
		  break;
		}
	}
    }
  else
    status = 0;

  free (hp1);
  return status;
}


/* Map IP to hostname, and then map back to addr to make sure it is a
 * reliable hostname 
 */
struct hostent *
get_reliable_hostbyaddr(const char *addr, int len, int type)
{
	struct hostent *hp = NULL;

	struct hostent *reverse;
	struct hostent *forward;
	char **sp;

	reverse = gethostbyaddr (addr, len, type);
	if (!reverse)
		return NULL;

	/* must make sure the hostent is authorative. */

	reverse = hostent_dup (reverse);
	forward = gethostbyname (reverse->h_name);

	if (forward) {
		/* now make sure the "addr" is in the list */
		for (sp = forward->h_addr_list ; *sp ; sp++) {
			if (memcmp (*sp, addr, forward->h_length) == 0)
				break;
		}

		if (*sp) {
			/* it's valid */
			hp = hostent_dup (forward);
		}
		else {
			/* it was a FAKE */
			xlog (L_WARNING, "Fake hostname %s for %s - forward lookup doesn't match reverse",
			      reverse->h_name, inet_ntoa(*(struct in_addr*)addr));
		}
	}
	else {
		/* never heard of it. misconfigured DNS? */
		xlog (L_WARNING, "Fake hostname %s for %s - forward lookup doesn't exist",
		      reverse->h_name, inet_ntoa(*(struct in_addr*)addr));
	}

	free (reverse);
	return hp;
}


#ifdef TEST
void
print_host (struct hostent *hp)
{
  char **sp;

  if (hp)
    {
      printf ("official hostname: %s\n", hp->h_name); 
      printf ("aliases:\n");
      for (sp = hp->h_aliases; *sp; sp++)
	printf ("  %s\n", *sp);
      printf ("IP addresses:\n");
      for (sp = hp->h_addr_list; *sp; sp++)
	printf ("  %s\n", inet_ntoa (*(struct in_addr *) *sp));
    }
  else
    printf ("Not host information\n");
}

int
main (int argc, char **argv)
{
  struct hostent *hp = gethostbyname (argv [1]);
  struct hostent *cp;
  struct in_addr addr;

  print_host (hp);

  if (hp)
    {
      cp = hostent_dup (hp);
      print_host (cp);
      free (cp);
    }
  printf ("127.0.0.1 == %s: %d\n", argv [1],
	  matchhostname ("127.0.0.1", argv [1]));
  addr.s_addr = inet_addr(argv [2]);
  printf ("%s\n", inet_ntoa (addr));
  cp = get_hostent ((const char *)&addr, sizeof(addr), AF_INET);
  print_host (cp);
  return 0;
}
#endif
