/*
 * $Id: gethostnamadr.c,v 1.7 1993/09/08 14:26:47 too Exp $
 *
 * Last modified: Wed Sep  8 16:50:00 1993 too
 *
 * HISTORY
 * $Log: gethostnamadr.c,v $
 * Revision 1.7  1993/09/08  14:26:47  too
 * Fixed getanswer (from gethostbyaddr) further. Now alias and addr lists
 * are NULL terminated. question is no longer copied. Removed that
 * unnecessary aligment by accident.
 *
 * Revision 1.6  1993/08/20  17:26:34  too
 * Fixed bug in getanswer that caused gethostbyaddr() to return
 * hostname w/ garpage at the end.
 * Added some comments w/ wponders
 * now answer buffer is aligned before host addresses are written.
 * this is unnecessary though.
 * getanswer still writes the question to the answer buffer. This will
 * be fixed later since it doesn't affect functionality
 *
 * Revision 1.5  1993/06/07  12:37:20  too
 * Changed inet_ntoa, netdatabase functions and WaitSelect() use
 * separate buffers for their dynamic buffers
 *
 * Revision 1.4  1993/06/04  11:16:15  jraja
 * Fixes for first public release.
 *
 * Revision 1.3  1993/06/03  20:22:24  too
 * Added variable usens. Fixed some bsd_free memory pointer arguments.
 * Fixed ptr aligment in makehostent
 *
 * Revision 1.2  1993/06/03  07:20:57  too
 * FIxed bugs with pointer arithmetic and destination addressess
 * and data lengths
 *
 * Revision 1.1  1993/06/01  16:35:59  too
 * Initial revision
 *
 *
 */
/*
 * Copyright (c) 1985, 1988 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#if defined(LIBC_SCCS) && !defined(lint)
static char sccsid[] = "@(#)gethostnamadr.c	6.45 (Berkeley) 2/24/91";
#endif /* LIBC_SCCS and not lint */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/socket.h>
#include <sys/malloc.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <ctype.h>
#include <errno.h>

#include <api/arpa_nameser.h>
#include <api/resolv.h>
#include <kern/amiga_includes.h>
#include <api/amiga_api.h>
#include <api/amiga_libcallentry.h>
#include <api/amiga_raf.h>
#include <api/allocdatabuffer.h>     
#include <kern/amiga_subr.h>

#include <api/gethtbynamadr.h>     /* prototypes (NO MORE BUGS HERE) */
     
#define	MAXALIASES	35
#define	MAXADDRS	35

#if PACKETSZ > 1024
#define	MAXPACKET	PACKETSZ
#else
#define	MAXPACKET	1024
#endif

typedef union {
    HEADER hdr;
    u_char buf[MAXPACKET];
} querybuf;

typedef union {
    long al;
    char ac;
} align;

/*
 * macro for getting error value from another library base function
 * ( which is called directly here )
 */

#define errno libPtr->errnoPtr[libPtr->errnoSize - 1]

/*
 * hostent structure in SocketBase
 */
#define HOSTENT ((struct hostent *)libPtr->hostents.db_Addr)

/*
 * longword align given pointer (i.e. divides by 4)
 */
#define ALIGN(p) (((u_int)(p) + (sizeof(long) - 1)) &~ (sizeof (long) -1))

#define MAXALIASES	35
#define MAXADDRS	35  
  
typedef char hostbuf_t[512];

struct hoststruct {
  char * host_aliases[MAXALIASES + 1];
  char * h_addr_ptrs[MAXADDRS + 1];
  short host_alias_count;
  short h_addr_count;
  struct hostent host;
  hostbuf_t hostbuf;
};

static struct hostent * makehostent(struct SocketBase * libPtr,
				    struct hoststruct * HS,
				    char * ptr);

extern int h_errno;
LONG usens = 1;

static char *
 getanswer(struct SocketBase * libPtr, querybuf *answer,int anslen, int iquery,
	    struct hoststruct * HS)
{
  register HEADER *hp;
  register u_char *cp; /* pointer to traverse in 'answer' */
  register int n;
  u_char *eom;
  int buflen = sizeof HS->hostbuf;
  char *bp; /* bp -- answer buffer pointer */
  int type, class, ancount, qdcount;
  int haveanswer, getclass = C_ANY;
  char **ap, **hap;
  
  eom = answer->buf + anslen;
  /*
   * find first satisfactory answer
   */
  hp = &answer->hdr;
  ancount = ntohs(hp->ancount); /* how many answers returned from nameserver */
  qdcount = ntohs(hp->qdcount); /* how many questions in nameserver query */

  /*
   *  bp, points to start of buffer space where new resolved answer is to 
   *  be written. the bp is moved to next free space. Initially it is
   *  set below, to start of buffer allocated for it-
   */
  bp = HS->hostbuf;
  /*
   * address cp to start of nameserver answers (after static sized header)
   */
  cp = answer->buf + sizeof(HEADER);

  /*
   * Any questions asked..hmm this should always be the case
   */
  if (qdcount) {
#if 0			/* added by too 8.Sep.1993: skipping strange parts */
    /*
     * gethostbyaddr uses inverse query...
     */
    if (iquery) {
      if ((n = dn_expand((u_char *)answer->buf,
			 (u_char *)eom, (u_char *)cp, (u_char *)bp,
			 buflen)) < 0) {
	h_errno = NO_RECOVERY;
	return NULL;
      }
      cp += n + QFIXEDSZ;
      /*
       * Hostname in final hostent structure is written here in case
       * of gethostbyaddr. (from question section ???)
       */
      HS->host.h_name = bp;
      n = strlen(bp) + 1;
      bp += n;
      buflen -= n;
    }
    else
#endif	/* 0 */		/* 8Sep93: now code below skips all question strings */
      /*
       * here is normal query (gethostbyname). skipping query section
       * hmm, wondering why is it originally implemented as 2
       * __dn_skipname function calls ?
       */
      cp += __dn_skipname(cp, eom) + QFIXEDSZ;
    while (--qdcount > 0)
      cp += __dn_skipname(cp, eom) + QFIXEDSZ;
  }
  else if (iquery) {
    /*
     * no questions and inverse query :o
     */
    if (hp->aa)
      h_errno = HOST_NOT_FOUND;
    else
      h_errno = TRY_AGAIN;
    return NULL;
  }
  ap = HS->host_aliases;
  HS->host_alias_count = 1; /* there is always NULL as last pointer */
  hap = HS->h_addr_ptrs;
  HS->h_addr_count = 1; /* there is always NULL as last pointer */
  
  haveanswer = 0;
  while (--ancount >= 0 && cp < eom) {
    if ((n = dn_expand((u_char *)answer->buf, (u_char *)eom,
		       (u_char *)cp, (u_char *)bp, buflen)) < 0)
      break;
    cp += n;
    /*
     * Type and class are type and class of answer in returned resource
     * record. see arpa[_/]nameserver.h for more information.
     */
    type = _getshort(cp); 
    cp += sizeof(u_short);
    class = _getshort(cp);
    cp += sizeof(u_short) + sizeof(u_long);
    n = _getshort(cp);
    cp += sizeof(u_short);
    
    if (type == T_CNAME) {   /* canonical name (add alias names)*/
      cp += n;
      if (HS->host_alias_count >= MAXALIASES)
	continue;
      *ap++ = bp;
      HS->host_alias_count++;
      n = strlen(bp) + 1;
      bp += n;
      buflen -= n;
      continue;
    }
    if (iquery && type == T_PTR) {  /* domain name pointer (get domain
				       name and return) */
      if ((n = dn_expand((u_char *)answer->buf,
			 (u_char *)eom, (u_char *)cp, (u_char *)bp,
			 buflen)) < 0) {
	cp += n;
	continue;
      }
      cp += n;
      HS->host.h_name = bp;   /* well, rewrites name pointer if there were
				 returned questions also... */
      haveanswer = 1;
      bp+= (strlen(bp) + 1);
      break;
    }
    if (iquery || type != T_A)  {
      /*
       * here is strange answer from nameserver: inverse query should have
       * been handled earlyer and there should not be any other types
       * left than "host address"
       */
#ifdef RES_DEBUG
      printf("unexpected answer type %d, size %d\n",
	     type, n);
#endif
      cp += n;
      continue;
    }
    if (haveanswer) {
      /*
       * Here if one host address answer is already returned (rather odd...)
       */
      if (n != HS->host.h_length) {
	cp += n;
	continue;
      }
      if (class != getclass) {
	cp += n;
	continue;
      }
    }
    else {
      /*
       * Fill in host address data and comparing info for next cycle (if any)
       */
      HS->host.h_length = n;
      getclass = class;
      HS->host.h_addrtype = (class == C_IN) ?
	AF_INET : AF_UNSPEC;
      if (!iquery) {
	/*
	 * if not inverse query and haveanswer = 0 host name is first in
	 * bp pointed buffer. (rather strange if answer already returned
	 * and new addresses are to be added since aren't in that case
	 * also names returned or is it inconsistent or have i missed
	 * something ?
	 */
	int n1;

	HS->host.h_name = bp;
	n1 = strlen(bp) + 1;
	bp += n1;
	buflen -= n1;
      }
    }

/*    bp = (char *)ALIGN(bp); /* align answer buffer for next host address */
		
    if (HS->host.h_length >= buflen) {
#ifdef RES_DEBUG
      printf("size (%d) too big\n", host->h_length);
#endif
      break;
    }
    /*
     * Fill next host address in address list
     */
    bcopy(cp, *hap++ = bp, n);
    HS->h_addr_count++;
    bp += n;
    buflen -= n;
    cp += n;
    haveanswer++;
  } /* while (--ancount ...) */
  
  if (haveanswer) {
    *ap = NULL;
    *hap = NULL;
    return bp;
  }
  else {
    h_errno = TRY_AGAIN;
    return NULL;
  }
}

struct hostent * SAVEDS RAF2 (_gethostbyname,
			      struct SocketBase *,	libPtr,	a6,
			      const char *,		name,	a0)
#if 0
{
#endif  
  querybuf *buf;
  int n;
  char * ptr;
  extern int inet_aton(const char *name, struct in_addr *ia);
  struct hoststruct * HS = NULL;
  struct hostent * anshost = NULL;
  
  CHECK_TASK2();
  
  /*
   * check if name consists only dots and digits.
   */
  if (isdigit(name[0])) {
    struct in_addr inaddr;
    u_long * lptr;
    
    if (!inet_aton(name, &inaddr)) {
      writeErrnoValue(libPtr, 0);
      return NULL;
    }
    
    if (allocDataBuffer(&libPtr->hostents,
			sizeof (struct hostent) + 28) == FALSE) {
      writeErrnoValue(libPtr, ENOMEM);
      return NULL;
    }
    HOSTENT->h_addrtype = AF_INET;
    HOSTENT->h_length = sizeof (struct in_addr);
    lptr = (u_long *)(HOSTENT + 1);
    *lptr++ = inaddr.s_addr;
    *(u_long **)(lptr) = lptr - 1;
    HOSTENT->h_addr_list = (char **)lptr;
    *++lptr = NULL;
    HOSTENT->h_aliases = (char **)lptr;
    HOSTENT->h_name = strcpy((char *)++lptr, name);
    
    return HOSTENT;
  }
  /*
   * Search local database (first) is usens not FIRST
   */
  if (usens != 1)
    if ((anshost =_gethtbyname(libPtr, name)) != NULL || usens == 0)
      return anshost;
  /*
   * Here if usens is FIRST or host not in local database and usens is SECOND
   */
  if ((HS = bsd_malloc(sizeof (querybuf) + sizeof (struct hoststruct),
		       M_TEMP, M_WAITOK)) == NULL) {
    writeErrnoValue(libPtr, ENOMEM);
    return NULL;
  }
  buf = (querybuf *)(HS + 1);
  
  ObtainSemaphore(&res_lock);
  n = res_search(libPtr, name, C_IN, T_A, buf->buf, sizeof (querybuf));
  ReleaseSemaphore(&res_lock);
  if (n >= 0) {
    ptr = getanswer(libPtr, buf, n, 0, HS);
    if (ptr != NULL) {
      if ((anshost = makehostent(libPtr, HS, ptr)) != NULL) {
	anshost->h_addrtype = HS->host.h_addrtype;
	anshost->h_length = HS->host.h_length;
      }    
    }
  }
  else {
#ifdef RES_DEBUG
    printf("res_search failed\n");
#endif
    /*
     * If usens is FIRST and host not found using resolver.
     */
    if (usens != 2)
      anshost =_gethtbyname(libPtr, name);
  }
  if (HS)
    bsd_free(HS, M_TEMP);
  return anshost;
  
}

struct hostent * SAVEDS RAF4 (_gethostbyaddr,
			      struct SocketBase *,	libPtr,	a6,
			      const char *,		addr,	a0,
			      int,			len,	d0,
			      int,			type,	d1)
#if 0
{
#endif  
  querybuf * buf;
  int n;
  char * ptr;
  struct hoststruct * HS = NULL;
  char * qbuf;
  struct hostent * anshost = NULL;
  
  CHECK_TASK2();
  
  if (type != AF_INET)
    return ((struct hostent *) NULL);
  
  /*
   * Search local database (first) is usens not FIRST
   */
  if (usens != 1)
    if ((anshost =_gethtbyaddr(libPtr, addr, len, type)) != NULL || usens == 0)
      return anshost;

  /*
   * Here if usens is FIRST or host not in local database and usens is SECOND
   */
  if ((HS = bsd_malloc(sizeof (querybuf) + MAXDNAME + 1 +
		       sizeof (struct hoststruct),  M_TEMP, M_WAITOK))
      == NULL) {
    writeErrnoValue(libPtr, ENOMEM);
    return NULL;
  }
  buf = (querybuf *)(HS + 1);
  qbuf = (caddr_t)(buf + 1);
  
  (void)sprintf(qbuf, "%lu.%lu.%lu.%lu.in-addr.arpa",
		((unsigned)addr[3] & 0xff),
		((unsigned)addr[2] & 0xff),
		((unsigned)addr[1] & 0xff),
		((unsigned)addr[0] & 0xff));
  ObtainSemaphore(&res_lock);
  n = res_query(libPtr, qbuf, C_IN, T_PTR, (char *)buf, sizeof (querybuf));
  ReleaseSemaphore(&res_lock);

  if (n >= 0) {
    ptr = getanswer(libPtr, buf, n, 1, HS);
    if (ptr != NULL) {
      if (HS->h_addr_count == 1) {
	HS->h_addr_count++;
	bcopy(addr, ptr, len);
	HS->h_addr_ptrs[0] = ptr;
	ptr += len;
      }      
      else
	bcopy(addr, &HS->h_addr_ptrs[0], len);
      HS->h_addr_ptrs[1] = NULL;
      if ((anshost = makehostent(libPtr, HS, ptr)) != NULL) {
	anshost->h_addrtype = type;
	anshost->h_length = len;
      }
    }
  }
  else {
#ifdef RES_DEBUG
    printf("res_query failed\n");
#endif
    /*
     * If usens is FIRST and host not found using resolver.
     */
    if (usens != 2)
      anshost = _gethtbyaddr(libPtr, addr, len, type);
  }
  if (HS)
    bsd_free(HS, M_TEMP);
  return anshost;
}

static struct hostent * makehostent(struct SocketBase * libPtr,
				    struct hoststruct * HS,
				    char * ptr)
{
  int n, i;

  i = (caddr_t)ALIGN(ptr) - (caddr_t)&HS->hostbuf;
  n = i + sizeof (struct hostent) + HS->h_addr_count * sizeof (char *) +
    HS->host_alias_count * sizeof (char *);
    
  if (allocDataBuffer(&libPtr->hostents, n) == FALSE) {
    writeErrnoValue(libPtr, ENOMEM);    
    return NULL;
  }
  /*
   * copy ent data to user buffer (pointers will be set later)
   */
  bcopy(HS->hostbuf, (caddr_t)(HOSTENT + 1), i);

  /*
   * how much to add to old pointers
   */
  n = (caddr_t)HOSTENT + sizeof(struct hostent) - (caddr_t)&HS->hostbuf;
  
  /*
   * fill vital fields in user hostent structure
   */
  HOSTENT->h_name = HS->host.h_name + n;

  HOSTENT->h_aliases = (char **)((char *)(HOSTENT + 1) + i);
  for (i = 0; HS->host_aliases[i]; i++)
    HOSTENT->h_aliases[i] = HS->host_aliases[i] + n;
  HOSTENT->h_aliases[i++] = NULL;

  HOSTENT->h_addr_list = HOSTENT->h_aliases + i;
  for (i = 0; HS->h_addr_ptrs[i]; i++)
    HOSTENT->h_addr_list[i] = HS->h_addr_ptrs[i] + n;
  HOSTENT->h_addr_list[i] = NULL;

  return HOSTENT;
}
