/*
 * %CopyrightBegin%
 * 
 * Copyright Ericsson AB 1997-2009. All Rights Reserved.
 * 
 * The contents of this file are subject to the Erlang Public License,
 * Version 1.1, (the "License"); you may not use this file except in
 * compliance with the License. You should have received a copy of the
 * Erlang Public License along with this software. If not, it can be
 * retrieved online at http://www.erlang.org/.
 * 
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * %CopyrightEnd%
 */
/* 
 * Interface functions to different versions of gethostbyname
 */

#ifdef VXWORKS
#include <vxWorks.h>
#include <stdio.h>
#include <semLib.h>
#include <hostLib.h>
#include <resolvLib.h>
#include <string.h>
#include <sys/socket.h>
#include <errno.h>
#include <symLib.h>
#include <sysSymTbl.h>

#elif __WIN32__
#include <winsock2.h>
#include <windows.h>
#include <winbase.h>

#else /* unix of some kind */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/param.h>
#endif 

/* common to all platforms */
#include "eidef.h"
#include "ei_resolve.h"
#include "ei_locking.h"

#ifdef HAVE_GETHOSTBYNAME_R

void ei_init_resolve(void)
{
    return;			/* Do nothing */
}

#else /* !HAVE_GETHOSTBYNAME_R */

/* we have our own in that case */

/* Make sure this semaphore has been initialized somewhere first. This
 * should probably be done from 'erl'_init() but we do it in the first
 * call to gethostbyname_r() or gethostbyaddr_r().
 */
/* FIXME we don't want globals here, but maybe ok? */
#ifdef _REENTRANT
static ei_mutex_t *ei_gethost_sem = NULL;
#endif /* _REENTRANT */
static int ei_resolve_initialized = 0;
#ifndef __WIN32__
int h_errno;
#endif

#ifdef DEBUG
#define DEBUGF(X) fprintf X
#else
#define DEBUGF(X) /* Nothing */
#endif

#ifdef VXWORKS
/* FIXME problem for threaded ? */
static struct hostent *(*sens_gethostbyname)(const char *name,
					     char *, int) = NULL;
static struct hostent *(*sens_gethostbyaddr)(const char *addr,
					     char *, int) = NULL;
#endif

#ifdef VXWORKS
static int verify_dns_configuration(void);
#endif

/*
 * If we find SENS resolver, use the functions found there, i.e.
 * resolvGetHostByName() and resolvGetHostByAddr(). Otherwise we use
 * our own, which are just wrappers around hostGetByName() and
 * hostGetByAddr(). Here we look up the functions.
 */
void ei_init_resolve(void)
{

#ifdef VXWORKS
  void *sym;
  SYM_TYPE symtype;

  if (symFindByName(sysSymTbl,"resolvGetHostByName",
		    (char **)&sym,&symtype) == OK && 
      verify_dns_configuration()) {
      sens_gethostbyname = sym;
      DEBUGF((stderr,"found SENS resolver - using it for gethostbyname()\n"));
      if (symFindByName(sysSymTbl,"resolvGetHostByAddr",
			(char **)&sym,&symtype) == OK) {
	  sens_gethostbyaddr = sym;
	  DEBUGF((stderr,"found SENS resolver - "
		  "using it for gethostbyaddr()\n"));
      }
      else {
	  DEBUGF((stderr,"SENS resolver not found - "
		  "using default gethostbyaddr()\n"));
      }
  }
  else {
      DEBUGF((stderr,"SENS resolver not found - "
	      "using default gethostbyname()\n"));
  }
#endif /* VXWORKS */

#ifdef _REENTRANT
  ei_gethost_sem = ei_mutex_create();
#endif /* _REENTRANT */

  ei_resolve_initialized = 1;
}

#ifdef VXWORKS
/*
** Function to verify the DNS configuration on VwXorks SENS.
** Actually configures to a default value if unconfigured...
*/
static int verify_dns_configuration(void) 
{
    /* FIXME problem for threaded ? */ 
    static char resolv_params[sizeof(RESOLV_PARAMS_S)];
    void (*rpg)(char *);
    STATUS (*rps)(char *);
    SYM_TYPE dummy;
    int get_result, set_result;

    get_result = symFindByName(sysSymTbl,"resolvParamsGet", (char **) &rpg, &dummy);
    set_result = symFindByName(sysSymTbl,"resolvParamsSet", (char **) &rps, &dummy);

    if (!(get_result == OK &&
	  set_result == OK))
	return -1;
    (*rpg)(resolv_params);
    if (*resolv_params == '\0') {
	/* It exists, but is not configured, ei_connect would fail
	   if we left it this way... The best we can do is to configure
	   it to use the local host database on the card, as a fallback */
	*resolv_params = (char) 1;
	fprintf(stderr,"Trying to fix up DNS configuration.\n");
	if (((*rps)(resolv_params)) != OK)
	  return -1;
    }
    return 0;
}

#endif    

/* 
 * Copy the contents of one struct hostent to another, i.e. don't just
 * copy the pointers, copy all the data and create new pointers, etc.
 * User must provide a secondary buffer to which the host data can be copied.
 *
 * Returns 0 on success or -1 if buffer is too small for host data 
*/

/* a couple of helpers
 * align: increment buf until it is dword-aligned, reduce len by same amount.
 * advance:  increment buf by n bytes, reduce len by same amount .
 */
#if defined SIZEOF_VOID_P
#define ALIGNBYTES (SIZEOF_VOID_P - 1)
#else
#define ALIGNBYTES (sizeof(void*) - 1)
#endif
#define align_buf(buf,len) for (;(((unsigned)buf) & ALIGNBYTES); (buf)++, len--)
#define advance_buf(buf,len,n) ((buf)+=(n),(len)-=(n))

/* "and now the tricky part..." */
static int copy_hostent(struct hostent *dest, const struct hostent *src, char *buffer, int buflen)
{
  char **pptr;
  int len;
  char **src_aliases   = NULL;
  char **src_addr_list = NULL;

  /* fix initial buffer alignment */
  align_buf(buffer, buflen);

  /* copy the data into our buffer... */
  /* first the easy ones! */
  dest->h_length = src->h_length;
  dest->h_addrtype = src->h_addrtype;

  /* h_name */
  dest->h_name = buffer;
  len = strlen(src->h_name);
  if (buflen < len+1) return -1;
  memmove((char *)dest->h_name,src->h_name,len);
  buffer[len] = (char)0;
  advance_buf(buffer,buflen,len+1);

  /* traverse alias list, collecting the pointers */
  align_buf(buffer, buflen);
  pptr = (char **)buffer;
  dest->h_aliases = pptr;          /* save head of pointer array */
  
  src_aliases = src->h_aliases;
  
  while(*(src_aliases)) {
    if (buflen < sizeof(*pptr)) return -1;
    *pptr = src_aliases;
    advance_buf(buffer,buflen,sizeof(*pptr));
    src_aliases++;
    pptr++;
  }  
  if (buflen < sizeof(*pptr)) return -1;
  *pptr = NULL;
  advance_buf(buffer,buflen,sizeof(*pptr));

  /* go back to saved position & transfer the alias data */
  pptr = dest->h_aliases;
  while (*pptr) {
    len = strlen(*pptr);
    if (buflen < len+1) return -1;
    memmove(buffer,*pptr,len);     /* copy data to local buffer */
    buffer[len] = (char)0;
    *pptr = buffer;                /* point to own copy now */
    advance_buf(buffer,buflen,len+1);
    pptr++;
  }

  /* traverse address list, collecting the pointers */
  align_buf(buffer, buflen);
  pptr = (char **)buffer;
  dest->h_addr_list = pptr;        /* save head of pointer array */
  
  src_addr_list = src->h_addr_list;
  
  while(*(src_addr_list)) {
    if (buflen < sizeof(*pptr)) return -1;
    *pptr = *src_addr_list;
    advance_buf(buffer,buflen,sizeof(*pptr));
    src_addr_list++;
    pptr++;
  }  
  if (buflen < sizeof(*pptr)) return -1;
  *pptr = NULL;
  advance_buf(buffer,buflen,sizeof(*pptr));

  /* go back to saved position & transfer the addresses */
  /* align_buf(buffer, buflen); */
  pptr = dest->h_addr_list;
  while (*pptr) {
    len = src->h_length;
    if (buflen < len+1) return -1;
    memmove(buffer,*pptr,len);     /* copy data to local buffer */
    buffer[len]=(char)0;           /* not sure if termination is necessary */
    *pptr = buffer;                /* point to own copy now */
    advance_buf(buffer,buflen,len+1);
    pptr++;
  }

  if (buflen < 0) return -1;
  return 0;
}

/* This function is a pseudo-reentrant version of gethostbyname(). It
 * uses locks to serialize the call to the regular (non-reentrant)
 * gethostbyname() and then copies the data into the user-provided
 * buffers. It's not pretty but it works. 
 *
 * name - name of host to look up
 * hostp - user-supplied structure for returning host entry
 * buffer - user-supplied buffer: storage for the copied host data
 * buflen - length of user-supplied buffer
 * h_errnop - buffer for return status
 *
 * Returns values as for gethostbyname(). Additionally, sets
 * errno=ERANGE and returns NULL if buffer is too small for host data. 
 */

static struct hostent *my_gethostbyname_r(const char *name, 
					  struct hostent *hostp, 
					  char *buffer, 
					  int buflen, 
					  int *h_errnop)
{
  struct hostent dest;
  struct hostent *src;
  struct hostent *rval = NULL;

  /* FIXME this should have been done in 'erl'_init()? */
  if (!ei_resolve_initialized) ei_init_resolve(); 

#ifdef _REENTRANT
  /* === BEGIN critical section === */
  if (ei_mutex_lock(ei_gethost_sem,0) != 0) {
    *h_errnop = NO_RECOVERY;
    return NULL;
  }
#endif /* _REENTRANT */

  /* lookup the data */
  if ((src = ei_gethostbyname(name))) {
    /* copy to caller's buffer */
    if (!copy_hostent(&dest,src,buffer,buflen)) {
      /* success */ 
      *hostp = dest;               
      *h_errnop = 0;
      rval = hostp;
    }
    
    else {
      /* failure - buffer size */
#ifdef __WIN32__
      SetLastError(ERROR_INSUFFICIENT_BUFFER);
#else
      errno = ERANGE;
#endif
      *h_errnop = 0;
    }
  }
  
  else {
    /* failure - lookup */
#ifdef __WIN32__
    *h_errnop = WSAGetLastError();
#else
    *h_errnop = h_errno;
#endif
  }

#ifdef _REENTRANT
  /* === END critical section === */
  ei_mutex_unlock(ei_gethost_sem);
#endif /* _REENTRANT */
  return rval;
}

static struct hostent *my_gethostbyaddr_r(const char *addr,
					  int length, 
					  int type, 
					  struct hostent *hostp,
					  char *buffer,  
					  int buflen, 
					  int *h_errnop)
{
  struct hostent dest;
  struct hostent *src;
  struct hostent *rval = NULL;

  /* FIXME this should have been done in 'erl'_init()? */
  if (!ei_resolve_initialized) ei_init_resolve();

#ifdef _REENTRANT
  /* === BEGIN critical section === */
  if (ei_mutex_lock(ei_gethost_sem,0) != 0) {
    *h_errnop = NO_RECOVERY;
    return NULL;
  }
#endif /* _REENTRANT */

  /* lookup the data */
  if ((src = ei_gethostbyaddr(addr,length,type))) {
    /* copy to caller's buffer */
    if (!copy_hostent(&dest,src,buffer,buflen)) {
      /* success */
      *hostp = dest;                
      *h_errnop = 0;
      rval = hostp;
    }

    else {
      /* failure - buffer size */
#ifdef __WIN32__
      SetLastError(ERROR_INSUFFICIENT_BUFFER);
#else
      errno = ERANGE;
#endif
      *h_errnop = 0;
    }
  }

  else {
    /* failure - lookup */
#ifdef __WIN32__
    *h_errnop = WSAGetLastError();
#else
    *h_errnop = h_errno;
#endif
  }


#ifdef _REENTRANT
  /* === END critical section === */
  ei_mutex_unlock(ei_gethost_sem);
#endif /* _REENTRANT */
  return rval;
}


#endif /* !HAVE_GETHOSTBYNAME_R */


#ifdef __WIN32__
struct hostent *ei_gethostbyname(const char *name)
{
    return gethostbyname(name);
}

struct hostent *ei_gethostbyaddr(const char *addr, int len, int type)
{
    return gethostbyaddr(addr, len, type);
}

#elif VXWORKS


/* these are a couple of substitutes for the real thing when we run on
 * stock vxworks (i.e. no sens).
 *
 * len and type are ignored, but we make up some reasonable values and
 * insert them
 */
static struct hostent *my_gethostbyname(const char *name)
{
  /* FIXME problem for threaded ? */
  static struct hostent h;
  static char hostname[EI_MAXHOSTNAMELEN+1];
  static char *aliases[1] = {NULL};
  static char *addrp[2] = {NULL,NULL};
  static unsigned long addr = 0;

  strcpy(hostname,name);
  if ((addr = (unsigned long)hostGetByName(hostname)) == ERROR) {
    h_errno = HOST_NOT_FOUND;
    return NULL;
  }

  h_errno = 0;
  h.h_name = hostname;
  h.h_aliases = aliases;
  h.h_length = 4;
  h.h_addrtype = AF_INET;
  addrp[0] = (char *)&addr;
  h.h_addr_list = addrp;
  
  return &h;
}

static struct hostent *my_gethostbyaddr(const char *addr, int len, int type)
{
  /* FIXME problem for threaded ? */
  static struct hostent h;
  static char hostname[EI_MAXHOSTNAMELEN+1];
  static char *aliases[1] = { NULL };
  static unsigned long inaddr;
  static char *addrp[2] = {(char *)&inaddr, NULL};

  memmove(&inaddr,addr,sizeof(inaddr));
  
  if ((hostGetByAddr(inaddr,hostname)) == ERROR) {
    h_errno = HOST_NOT_FOUND;
    return NULL;
  }
  
  h_errno = 0;
  h.h_name = hostname;
  h.h_aliases = aliases;
  h.h_length = 4;
  h.h_addrtype = AF_INET;
  h.h_addr_list = addrp;

  return &h;
}

/* use sens functions for these, if found. */
struct hostent *ei_gethostbyname(const char *name)
{
  struct hostent *h = NULL;
  
  if (!sens_gethostbyname) {
    h = my_gethostbyname(name);
  }
  else {
    /* FIXME problem for threaded ? */
    static char buf[1024];
    h = sens_gethostbyname(name,buf,1024);
  }

  return h;
}

struct hostent *ei_gethostbyaddr(const char *addr, int len, int type)
{
  struct hostent *h = NULL;
  
  if (!sens_gethostbyaddr) { 
    h = my_gethostbyaddr(addr,len,type);
  }
  else {
    /* FIXME problem for threaded ? */
    static char buf[1024];
    h = sens_gethostbyaddr(addr,buf,1024);
  }

  return h;
}

struct hostent *ei_gethostbyaddr_r(const char *addr,
				int length, 
				int type, 
				struct hostent *hostp,
				char *buffer,  
				int buflen, 
				int *h_errnop)
{
  struct hostent *h = NULL;
  
  /* use own func if sens function not available */
  if (!sens_gethostbyaddr) {
    h = my_gethostbyaddr_r(addr,length,type,hostp,buffer,buflen,h_errnop);
  }
  else {
    if (!(h = sens_gethostbyaddr(addr,buffer,buflen))) {
      /* sens returns status via errno */
      *h_errnop = errno; 
    }
    else {
      *hostp = *h;
      *h_errnop = 0;
    }
  }
      
  return h;
}

struct hostent *ei_gethostbyname_r(const char *name, 
				    struct hostent *hostp, 
				    char *buffer, 
				    int buflen, 
				    int *h_errnop)
{
  struct hostent *h = NULL;
  
  /* use own func if sens function not available */
  if (!sens_gethostbyname) {
    h = my_gethostbyname_r(name,hostp,buffer,buflen,h_errnop);
  }
  else {
    if (!(h = sens_gethostbyname(name,buffer,buflen))) {
      /* sens returns status via errno */
      *h_errnop = errno; 
    }
    else {
      *hostp = *h;
      *h_errnop = 0;
    }
  }
      
  return h;
}

#else /* unix of some kind */

struct hostent *ei_gethostbyname(const char *name)
{
    return gethostbyname(name);
}

struct hostent *ei_gethostbyaddr(const char *addr, int len, int type)
{
    return gethostbyaddr(addr, len, type);
}

struct hostent *ei_gethostbyaddr_r(const char *addr,
				int length, 
				int type, 
				struct hostent *hostp,
				char *buffer,  
				int buflen, 
				int *h_errnop)
{
#if (EI_THREADS == false)
  /* threads disabled, no need to call reentrant function */
  return gethostbyaddr(addr, length, type); 
#else
#ifndef HAVE_GETHOSTBYNAME_R
  return my_gethostbyaddr_r(addr,length,type,hostp,buffer,buflen,h_errnop);
#else
#if (defined(__GLIBC__) || (__FreeBSD_version >= 602000))
  struct hostent *result;

  gethostbyaddr_r(addr, length, type, hostp, buffer, buflen, &result,
		h_errnop);

  return result;
#else
  return gethostbyaddr_r(addr,length,type,hostp,buffer,buflen,h_errnop);
#endif
#endif
#endif
}

struct hostent *ei_gethostbyname_r(const char *name, 
				    struct hostent *hostp, 
				    char *buffer, 
				    int buflen, 
				    int *h_errnop)
{
#ifndef _REENTRANT
  /* threads disabled, no need to call reentrant function */
  return gethostbyname(name);
#else
#ifndef HAVE_GETHOSTBYNAME_R
  return my_gethostbyname_r(name,hostp,buffer,buflen,h_errnop);
#else
#if (defined(__GLIBC__) || (__FreeBSD_version >= 602000))
  struct hostent *result;

  gethostbyname_r(name, hostp, buffer, buflen, &result, h_errnop);

  return result;
#else
  return gethostbyname_r(name,hostp,buffer,buflen,h_errnop);
#endif
#endif
#endif
}

#endif /* vxworks, win, unix */

