#include "loc.h"

/* $Id: loc.c,v 1.1 2008/02/15 01:47:15 marka Exp $ */

/* Global variables */

short rr_errno;

/*
   Prints the actual usage
 */
void
usage ()
{
  (void) fprintf (stderr,
		  "Usage: %s: [-v] [-d nnn] hostname\n", progname);
  exit (2);
}

/*
   Panics
 */
void
panic (message)
     char *message;
{
  (void) fprintf (stderr,
		  "%s: %s\n", progname, message);
  exit (2);
}

/*
   ** IN_ADDR_ARPA -- Convert dotted quad string to reverse in-addr.arpa
   ** ------------------------------------------------------------------
   **
   **   Returns:
   **           Pointer to appropriate reverse in-addr.arpa name
   **           with trailing dot to force absolute domain name.
   **           NULL in case of invalid dotted quad input string.
 */

#ifndef ARPA_ROOT
#define ARPA_ROOT "in-addr.arpa"
#endif

char *
in_addr_arpa (dottedquad)
     char *dottedquad;		/* input string with dotted quad */
{
  static char addrbuf[4 * 4 + sizeof (ARPA_ROOT) + 2];
  unsigned int a[4];
  register int n;

  n = sscanf (dottedquad, "%u.%u.%u.%u", &a[0], &a[1], &a[2], &a[3]);
  switch (n)
    {
    case 4:
      (void) sprintf (addrbuf, "%u.%u.%u.%u.%s.",
	     a[3] & 0xff, a[2] & 0xff, a[1] & 0xff, a[0] & 0xff, ARPA_ROOT);
      break;

    case 3:
      (void) sprintf (addrbuf, "%u.%u.%u.%s.",
		      a[2] & 0xff, a[1] & 0xff, a[0] & 0xff, ARPA_ROOT);
      break;

    case 2:
      (void) sprintf (addrbuf, "%u.%u.%s.",
		      a[1] & 0xff, a[0] & 0xff, ARPA_ROOT);
      break;

    case 1:
      (void) sprintf (addrbuf, "%u.%s.",
		      a[0] & 0xff, ARPA_ROOT);
      break;

    default:
      return (NULL);
    }

  while (--n >= 0)
    if (a[n] > 255)
      return (NULL);

  return (addrbuf);
}

/* 
   Returns a human-readable version of the LOC information or
   NULL if it failed. Argument is a name (of a network or a machine)
   and a boolean telling is it is a network name or a machine name.
 */
char *
getlocbyname (name, is_network)
     const char *name;
     short is_network;
{
  char *result;
  struct list_in_addr *list, *p;
  result = findRR (name, T_LOC);
  if (result != NULL)
    {
      if (debug >= 2)
	printf ("LOC record found for the name %s\n", name);
      return result;
    }
  else
    {
      if (!is_network)
	{
	  list = findA (name);
	  if (debug >= 2)
	    printf ("No LOC record found for the name %s, trying addresses\n", name);
	  if (list != NULL)
	    {
	      for (p = list; p != NULL; p = p->next)
		{
		  if (debug >= 2)
		    printf ("Trying address %s\n", inet_ntoa (p->addr));
		  result = getlocbyaddr (p->addr, NULL);
		  if (result != NULL)
		    return result;
		}
	      return NULL;
	    }
	  else
	    {
	      if (debug >= 2)
		printf ("   No A record found for %s\n", name);
	      return NULL;
	    }
	}
      else
	{
	  if (debug >= 2)
	    printf ("No LOC record found for the network name %s\n", name);
	  return NULL;
	}
    }
}

/* 
   Returns a human-readable version of the LOC information or
   NULL if it failed. Argument is an IP address.
 */
char *
getlocbyaddr (addr, mask)
     const struct in_addr addr;
     const struct in_addr *mask;
{
  struct in_addr netaddr;
  u_int32_t a;
  struct in_addr themask;
  char *text_addr, *text_mask;

  if (mask == NULL)
    {
      themask.s_addr = (u_int32_t) 0;
    }
  else
    {
      themask = *mask;
    }

  text_addr = (char *) malloc (256);
  text_mask = (char *) malloc (256);
  strcpy (text_addr, inet_ntoa (addr));
  strcpy (text_mask, inet_ntoa (themask));

  if (debug >= 2)
    printf ("Testing address %s/%s\n", text_addr, text_mask);
  if (mask == NULL)
    {
      a = ntohl (addr.s_addr);
      if (IN_CLASSA (a))
	{
	  netaddr.s_addr = htonl (a & IN_CLASSA_NET);
	  themask.s_addr = htonl(IN_CLASSA_NET);
	}
      else if (IN_CLASSB (a))
	{
	  netaddr.s_addr = htonl (a & IN_CLASSB_NET);
	  themask.s_addr = htonl(IN_CLASSB_NET);
	}
      else if (IN_CLASSC (a))
	{
	  netaddr.s_addr = htonl (a & IN_CLASSC_NET);
	  themask.s_addr = htonl(IN_CLASSC_NET);
	}
      else
	{
	  /* Error */
	  return NULL;
	}
      return getlocbynet (in_addr_arpa (inet_ntoa (netaddr)), addr, &themask);
    }
  else
    {
      netaddr.s_addr = addr.s_addr & themask.s_addr;
      return getlocbynet (in_addr_arpa (inet_ntoa (netaddr)), addr, mask);
    }
}

/* 
   Returns a human-readable LOC.
   Argument is a network name in the 0.z.y.x.in-addr.arpa format
   and the original address
 */
char *
getlocbynet (name, addr, mask)
     char *name;
     struct in_addr addr;
     struct in_addr *mask;
{
  char *network;
  char *result;
  struct list_in_addr *list;
  struct in_addr newmask;
  u_int32_t a;
  char newname[4 * 4 + sizeof (ARPA_ROOT) + 2];
  
  if (debug >= 2)
    printf ("Testing network %s with mask %s\n", name, inet_ntoa(*mask));
  
  /* Check if this network has an A RR */
  list = findA (name);
  if (list != NULL)
    {
      /* Yes, it does. This A record will be used as the
       * new mask for recursion if it is longer than
       * the actual mask. */
      if (mask != NULL && mask->s_addr < list->addr.s_addr)
	{
	  /* compute the new arguments for recursion 
	   * - compute the new network by applying the new mask
	   *   to the address and get the in_addr_arpa representation
	   *   of it.
	   * - the address remains unchanged
	   * - the new mask is the one given in the A record
	   */
	  a = ntohl(addr.s_addr);        /* start from host address */
	  a &= ntohl(list->addr.s_addr); /* apply new mask */
	  newname[sizeof newname - 1] = 0;
	  strncpy(
		 newname,
		 in_addr_arpa(inet_ntoa(inet_makeaddr(a, 0))),
		 sizeof newname);
	  newmask = inet_makeaddr(ntohl(list->addr.s_addr), 0);
	  result = getlocbynet (newname, addr, &newmask);
	  if (result != NULL)
	    {
	      return result;
	    }
	}
      /* couldn't find a LOC. Fall through and try with name */
    }

  /* Check if this network has a name */
  network = findRR (name, T_PTR);
  if (network == NULL)
    {
      if (debug >= 2)
	printf ("No name for network %s\n", name);
      return NULL;
    }
  else
    {
      return getlocbyname (network, TRUE);
    }
}

/* 
   The code for these two functions is stolen from the examples in Liu and Albitz
   book "DNS and BIND" (O'Reilly).
 */

/****************************************************************
 * skipName -- This routine skips over a domain name.  If the   *
 *     domain name expansion fails, it crashes.                 *
 *     dn_skipname() is probably not on your manual             *
 *     page; it is similar to dn_expand() except that it just   *
 *     skips over the name.  dn_skipname() is in res_comp.c if  *
 *     you need to find it.                                     *
 ****************************************************************/
int
skipName (cp, endOfMsg)
     u_char *cp;
     u_char *endOfMsg;
{
  int n;

  if ((n = dn_skipname (cp, endOfMsg)) < 0)
    {
      panic ("dn_skipname failed\n");
    }
  return (n);
}

/****************************************************************
 * skipToData -- This routine advances the cp pointer to the    *
 *     start of the resource record data portion.  On the way,  *
 *     it fills in the type, class, ttl, and data length        *
 ****************************************************************/
int
skipToData (cp, type, class, ttl, dlen, endOfMsg)
     u_char *cp;
     u_short *type;
     u_short *class;
     u_int32_t *ttl;
     u_short *dlen;
     u_char *endOfMsg;
{
  u_char *tmp_cp = cp;		/* temporary version of cp */

  /* Skip the domain name; it matches the name we looked up */
  tmp_cp += skipName (tmp_cp, endOfMsg);

  /*
   * Grab the type, class, and ttl.  GETSHORT and GETLONG
   * are macros defined in arpa/nameser.h.
   */
  GETSHORT (*type, tmp_cp);
  GETSHORT (*class, tmp_cp);
  GETLONG (*ttl, tmp_cp);
  GETSHORT (*dlen, tmp_cp);

  return (tmp_cp - cp);
}


/* 
   Returns a human-readable version of a DNS RR (resource record)
   associated with the name 'domain'.
   If it does not find, ir returns NULL and sets rr_errno to explain why.

   The code for this function is stolen from the examples in Liu and Albitz
   book "DNS and BIND" (O'Reilly).
 */
char *
findRR (domain, requested_type)
     char *domain;
     int requested_type;
{
  char *result, *message;

  union
    {
      HEADER hdr;		/* defined in resolv.h */
      u_char buf[PACKETSZ];	/* defined in arpa/nameser.h */
    }
  response;			/* response buffers */
short found = 0;  
int responseLen;		/* buffer length */

  u_char *cp;			/* character pointer to parse DNS packet */
  u_char *endOfMsg;		/* need to know the end of the message */
  u_short class;		/* classes defined in arpa/nameser.h */
  u_short type;			/* types defined in arpa/nameser.h */
  u_int32_t ttl;		/* resource record time to live */
  u_short dlen;			/* size of resource record data */

  int i, count, dup;		/* misc variables */

  char *ptrList[1];
  int ptrNum = 0;
  struct in_addr addr;

  result = (char *) malloc (256);
  message = (char *) malloc (256);
  /* 
   * Look up the records for the given domain name.
   * We expect the domain to be a fully qualified name, so
   * we use res_query().  If we wanted the resolver search 
   * algorithm, we would have used res_search() instead.
   */
  if ((responseLen =
       res_query (domain,	/* the domain we care about   */
		  C_IN,		/* Internet class records     */
		  requested_type,	/* Look up name server records */
		  (u_char *) & response,	/*response buffer */
		  sizeof (response)))	/*buffer size    */
      < 0)
    {				/*If negative    */
      rr_errno = h_errno;
      return NULL;
    }

  /*
   * Keep track of the end of the message so we don't 
   * pass it while parsing the response.  responseLen is 
   * the value returned by res_query.
   */
  endOfMsg = response.buf + responseLen;

  /*
   * Set a pointer to the start of the question section, 
   * which begins immediately AFTER the header.
   */
  cp = response.buf + sizeof (HEADER);

  /*
   * Skip over the whole question section.  The question 
   * section is comprised of a name, a type, and a class.  
   * QFIXEDSZ (defined in arpa/nameser.h) is the size of 
   * the type and class portions, which is fixed.  Therefore, 
   * we can skip the question section by skipping the 
   * name (at the beginning) and then advancing QFIXEDSZ.
   * After this calculation, cp points to the start of the 
   * answer section, which is a list of NS records.
   */
  cp += skipName (cp, endOfMsg) + QFIXEDSZ;

  count = ntohs (response.hdr.ancount) +
    ntohs (response.hdr.nscount);
  while ((--count >= 0)		/* still more records     */
	 && (cp < endOfMsg))
    {				/* still inside the packet */


      /* Skip to the data portion of the resource record */
      cp += skipToData (cp, &type, &class, &ttl, &dlen, endOfMsg);

      if (type == requested_type)
	{
	  switch (requested_type)
	    {
	    case (T_LOC):
	      loc_ntoa (cp, result);
	      return result;
	      break;
	    case (T_PTR):
	      ptrList[ptrNum] = (char *) malloc (MAXDNAME);
	      if (ptrList[ptrNum] == NULL)
		{
		  panic ("Malloc failed");
		}

	      if (dn_expand (response.buf,	/* Start of the packet   */
			     endOfMsg,	/* End of the packet     */
			     cp,	/* Position in the packet */
			     (char *) ptrList[ptrNum],	/* Result    */
			     MAXDNAME)	/* size of ptrList buffer */
		  < 0)
		{		/* Negative: error    */
		  panic ("dn_expand failed");
		}

	      /*
	       * Check the name we've just unpacked and add it to 
	       * the list if it is not a duplicate.
	       * If it is a duplicate, just ignore it.
	       */
	      for (i = 0, dup = 0; (i < ptrNum) && !dup; i++)
		dup = !strcasecmp (ptrList[i], ptrList[ptrNum]);
	      if (dup)
		free (ptrList[ptrNum]);
	      else
		ptrNum++;
	      strcpy (result, ptrList[0]);
	      return result;
	      break;
	    case (T_A):
	      bcopy ((char *) cp, (char *) &addr, INADDRSZ);
	      strcat (result, " ");
	      strcat (result, inet_ntoa (addr));
	      found = 1;
	      break;
	    default:
	      sprintf (message, "Unexpected type %u", requested_type);
	      panic (message);
	    }
	}

      /* Advance the pointer over the resource record data */
      cp += dlen;

    }				/* end of while */
  if (found) 
  return result;
else
return NULL;
}

struct list_in_addr *
findA (domain)
     char *domain;
{

  struct list_in_addr *result, *end;

  union
    {
      HEADER hdr;		/* defined in resolv.h */
      u_char buf[PACKETSZ];	/* defined in arpa/nameser.h */
    }
  response;			/* response buffers */
  int responseLen;		/* buffer length */

  u_char *cp;			/* character pointer to parse DNS packet */
  u_char *endOfMsg;		/* need to know the end of the message */
  u_short class;		/* classes defined in arpa/nameser.h */
  u_short type;			/* types defined in arpa/nameser.h */
  u_int32_t ttl;		/* resource record time to live */
  u_short dlen;			/* size of resource record data */

  int count;			/* misc variables */

  struct in_addr addr;

  end = NULL;
  result = NULL;

  /* 
   * Look up the records for the given domain name.
   * We expect the domain to be a fully qualified name, so
   * we use res_query().  If we wanted the resolver search 
   * algorithm, we would have used res_search() instead.
   */
  if ((responseLen =
       res_query (domain,	/* the domain we care about   */
		  C_IN,		/* Internet class records     */
		  T_A,
		  (u_char *) & response,	/*response buffer */
		  sizeof (response)))	/*buffer size    */
      < 0)
    {				/*If negative    */
      rr_errno = h_errno;
      return NULL;
    }

  /*
   * Keep track of the end of the message so we don't 
   * pass it while parsing the response.  responseLen is 
   * the value returned by res_query.
   */
  endOfMsg = response.buf + responseLen;

  /*
   * Set a pointer to the start of the question section, 
   * which begins immediately AFTER the header.
   */
  cp = response.buf + sizeof (HEADER);

  /*
   * Skip over the whole question section.  The question 
   * section is comprised of a name, a type, and a class.  
   * QFIXEDSZ (defined in arpa/nameser.h) is the size of 
   * the type and class portions, which is fixed.  Therefore, 
   * we can skip the question section by skipping the 
   * name (at the beginning) and then advancing QFIXEDSZ.
   * After this calculation, cp points to the start of the 
   * answer section, which is a list of NS records.
   */
  cp += skipName (cp, endOfMsg) + QFIXEDSZ;

  count = ntohs (response.hdr.ancount) +
    ntohs (response.hdr.nscount);
  while ((--count >= 0)		/* still more records     */
	 && (cp < endOfMsg))
    {				/* still inside the packet */


      /* Skip to the data portion of the resource record */
      cp += skipToData (cp, &type, &class, &ttl, &dlen, endOfMsg);

      if (type == T_A)
	{
	  bcopy ((char *) cp, (char *) &addr, INADDRSZ);
	  if (end == NULL)
	    {
	      result = (void *) malloc (sizeof (struct list_in_addr));
	      result->addr = addr;
	      result->next = NULL;
	      end = result;
	    }
	  else
	    {
	      end->next = (void *) malloc (sizeof (struct list_in_addr));
	      end = end->next;
	      end->addr = addr;
	      end->next = NULL;
	    }
	}

      /* Advance the pointer over the resource record data */
      cp += dlen;

    }				/* end of while */
  return result;
}
