/*
** nbgethost.c for  in 
** 
** Made by 
** Login   <vianney@epita.fr>
** 
** Started on  Wed Sep  1 06:45:16 1999 
** Last update Thu Oct 28 20:20:24 1999 
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/signal.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <netdb.h>
#include <setjmp.h>
#include "pat.h"
#include "nbgethost.h"

t_boolean		nbgethost_active = TRUE;
t_u32			nbgethost_timeout = 1;
jmp_buf			nbgethost_jmp;
t_boolean		dns_response_ok;
t_dict			*inaddr_dict = NULL;
t_dict			*hostname_dict = NULL;

/* initializes the non-blocking gethostby*(3) stuff.
   Returns 0 if OK, might return various errors */
t_status		nbgethost_init(VOID_DECL)
{
  t_status		status;

  if ((inaddr_dict = dict_new(HASH_SMALL_BASE,
			      VEC_BASE,
			      dict_hash_code_public,
			      PAT_ALLOC_ALGORITHM,
			      PAT_ALLOC_PROC,
			      PAT_REALLOC_PROC,
			      PAT_FREE_PROC,
			      "nbgethost",
			      &status)) == NULL)
    return (status);
  if ((hostname_dict = dict_new(HASH_SMALL_BASE,
				VEC_BASE,
				dict_hash_code_public,
				PAT_ALLOC_ALGORITHM,
				PAT_ALLOC_PROC,
				PAT_REALLOC_PROC,
				PAT_FREE_PROC,
				"nbgethost",
				&status)) == NULL)
    {
      dict_delete(inaddr_dict);
      return (status);
    }
  return (0);
}

/* is a t_dict_walk_proc.
   It is used internally by nbgethost_destroy(3). */
t_status		nbgethost_destroy_inaddr_walk(he,unused)
t_hash_elt		*he;
VOID_PTR		unused;
{
  PAT_FREE_PROC(he->value,
		"nbgethost",
		"*:value");
  return (0);
}

/* destroys the non-blocking gethostby* stuff. */
VOID_FUNC		nbgethost_destroy(VOID_DECL)
{
  t_status		status;

  status = dict_walk(inaddr_dict,
		     (t_dict_walk_proc)nbgethost_destroy_inaddr_walk,
		     NULL);
  assert(status == 0);
  dict_delete(inaddr_dict);
  dict_str_delete(hostname_dict);
}

/* SIGALRM handler of the non-blocking gethostby* stuff. */
RETSIGTYPE		sigalrm(unused)
int			unused;
{
#ifdef DEBUG
  if (PAT_VERB(VERB_PAT_NBSTUFF))
    fprintf(stderr,"+nbstuff+ %s\n",
	    dns_response_ok?"dns reponse was ok":"timeout has expired");
#endif
  if (!dns_response_ok)
    {
      longjmp(nbgethost_jmp,1);
    }
}

/* Non-blocking version of gethostbyname(3).
   Returns a hostent structure or NULL. 
   
   Warning: It modifies the SIGARLM signal handler for the process.
   It is intended to use with care! */
struct hostent		*nbgethostbyname(name)
char			*name;		/* Host name */
{
  dns_response_ok = FALSE;
  if (nbgethost_active)
    {
      signal(SIGALRM,sigalrm);
      alarm(nbgethost_timeout);
    }
  if (setjmp(nbgethost_jmp) == 0)
    {
      struct hostent	*hostentry;
      
#ifdef DEBUG
      if (PAT_VERB(VERB_PAT_NBSTUFF))
	fprintf(stderr,"+nbstuff+ querying %s\n",name);
#endif
      hostentry = gethostbyname(name);
      dns_response_ok = TRUE;
      return (hostentry);
    }
  else
    return (NULL);
}

/* Non-blocking version of gethostbyaddr(3).
   Returns a hostent structure or NULL.
   
   Warning: It modifies the SIGARLM signal handler for the process.
   It is intended to use with care! */
struct hostent		*nbgethostbyaddr(addr,addrlen,family)
char			*addr;
int			addrlen;
int			family;
{
  dns_response_ok = FALSE;
  if (nbgethost_active)
    {
      signal(SIGALRM,sigalrm);
      alarm(nbgethost_timeout);
    }
  if (setjmp(nbgethost_jmp) == 0)
    {
      struct hostent	*hostentry;

#ifdef DEBUG
      if (PAT_VERB(VERB_PAT_NBSTUFF))
	fprintf(stderr,"+nbstuff+ querying %s\n",
		(family == AF_INET)?
		inet_ntoa(*((struct in_addr *)addr)):"unknown family");
#endif
      hostentry = gethostbyaddr(addr,addrlen,family);
      dns_response_ok = TRUE;
      return (hostentry);
    }
  else
    return (NULL);
}

/* Non-blocking version of inaddr_from_str(3).
   Returns 0 if OK. Check inaddr_from_str(3) for errors descriptions.
   
   Warning: It modifies the SIGARLM signal handler for the process.
   It is intended to use with care! */
t_status			inaddr_from_str_alarm(str,mba_inaddr,resolve)
char				*str;
struct in_addr			*mba_inaddr;
t_boolean			resolve;
{
  t_status			status;

  mba_inaddr->s_addr = inet_addr(str);
  if (mba_inaddr->s_addr == -1)
    {
      if (resolve)
	{
	  t_hash_elt		*he;
	  struct in_addr	*value;
	  
	  assert(inaddr_dict);
	  if (he = dict_get(inaddr_dict,str))
	    value = (struct in_addr *)(he->value);
	  else
	    {
	      struct in_addr	*nvalue;
	      struct hostent	*hostentry;
	  
	      if ((hostentry = nbgethostbyname(str)) == NULL)
		return (ERR_PAT_UNRESOLVABLE);
	      if (hostentry->h_addrtype != AF_INET)
		return (ERR_PAT_BAD_FAMILY);
	      if ((nvalue = PAT_ALLOC_PROC(sizeof (struct in_addr),
					   "nbgethost",
					   "inaddr_from_str_alarm:value",
					   &status)) == NULL)
		return (status);
	      FBCOPY(hostentry->h_addr_list[0],nvalue,hostentry->h_length);
	      if ((status = dict_override(inaddr_dict,
					  str,
					  (VOID_PTR)nvalue)) != 0)
		{
		  PAT_FREE_PROC(nvalue,
				  "nbgethost",
				  "*:value");
		  return (status);
		}
	      value = nvalue;
	    }
	  FBCOPY(value,mba_inaddr,sizeof (struct in_addr));
	}
      else
	return (ERR_PAT_MALFORMED);
    }
  return (0);
}

/* Non-blocking version of inaddr_to_str(3).
   Returns 0 if OK. Check inaddr_to_str(3) for errors descriptions.
   
   Warning: It modifies the SIGARLM signal handler for the process.
   It is intended to use with care! */
t_status			inaddr_to_str_alarm(mba_inaddr,
						    str,
						    max_len,
						    resolve)
struct in_addr			*mba_inaddr;
char				*str;
int				max_len;
t_boolean			resolve;
{
  t_status			status;

  if (resolve)
    {
      t_hash_elt		*he;
      char			*value;	
      
      assert(hostname_dict);
      if (he = dict_get(hostname_dict,inet_ntoa(*mba_inaddr)))
	value = (char *)(he->value);
      else
	{
	  struct hostent	*hostentry;
	  
	  if (hostentry = nbgethostbyaddr((char *)mba_inaddr,
					   sizeof (struct in_addr),
					   AF_INET))
	    value = hostentry->h_name;
	  else
	    value = inet_ntoa(*mba_inaddr);
	  if ((status = dict_str_override(hostname_dict,
					  inet_ntoa(*mba_inaddr),
					  value)) != 0)
	    return (status);
	}
      return (str_cat_str(str,max_len,value));
    }
  return (str_cat_str(str,max_len,inet_ntoa(*mba_inaddr)));
}

t_status		nbgethost_inaddr_get_choices_walk(he,vec_str)
t_hash_elt		*he;
t_vec			*vec_str;
{
  if (vec_str_index(vec_str,he->key) < 0)
    return (vec_str_add(vec_str,he->key));
  else
    return (0);
}

t_status		nbgethost_inaddr_get_choices(vec_str)
t_vec			*vec_str;
{
  return (dict_walk(inaddr_dict,
		    (t_dict_walk_proc)nbgethost_inaddr_get_choices_walk,
		    vec_str));
}

t_status		nbgethost_hostname_get_choices_walk(he,vec_str)
t_hash_elt		*he;
t_vec			*vec_str;
{
  if (vec_str_index(vec_str,(char *)he->value) < 0)
    return (vec_str_add(vec_str,(char *)he->value));
  else
    return (0);
}

t_status		nbgethost_hostname_get_choices(vec_str)
t_vec			*vec_str;
{
  return (dict_walk(hostname_dict,
		    (t_dict_walk_proc)nbgethost_hostname_get_choices_walk,
		    vec_str));
}

t_status		nbgethost_get_choices(vec_str)
t_vec			*vec_str;
{
  t_status		status;

  if ((status = nbgethost_inaddr_get_choices(vec_str)) != 0)
    return (status);
  return (nbgethost_hostname_get_choices(vec_str));
}
