/*
 * RISC OS resolver abstraction code
 *
 *  Joseph Heenan, 2000
 *
 * $Id: dnslib,v 1.12 2003/10/20 20:39:24 ijeffray Exp $
 */

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>

#include "kernel.h"
#include "swis.h"

#include "sys/types.h"
#include "inetlib.h"
#include "netinet/in.h"

#include "arpa/inet.h" /* inet_aton */
#include "netdb.h" /* gethostname */
#include "sys/errno.h"
#include "sys/socket.h" /* AF_INET */

#include "dnslib.h"

#ifndef Resolver_GetHost
#define Resolver_GetHost 0x46001
#endif /* Resolver_GetHost */

struct dns_s
{
  dns_status_t status;
  char *hostname;
  struct hostent *hostent;
  struct hostent ourhostent; /* only used when user enters quad dotted ip */
  struct in_addr *inaddrs[2];
  struct in_addr inaddr;
};

/* This only needs to be here for platforms which don't have strdup... */
static char *strdup(const char *string)
{
    char *newString;

    newString = malloc(strlen(string) + 1);

    if (newString != NULL)
    {
        strcpy(newString, string);
    }

    return newString;
}

dns_t *dns_gethostbyname(const char *hostname)
{
  dns_t *dns;

  dns = calloc(1, sizeof(struct dns_s));
  if (!dns)
    goto failed;

  dns->status = dns_init;

  dns->hostname = strdup(hostname);
  if (!dns->hostname)
    goto failed;

  return dns;

failed:
  free(dns);

  return NULL;
}


void dns_dispose(dns_t *dns)
{
  assert(dns);

  if (dns)
  {
    free(dns->hostname);
    free(dns);
  }
}



dns_status_t dns_check(dns_t *dns)
{
  _kernel_oserror *e;
  int status;

  assert(dns);

  switch (dns->status)
  {
    case dns_init:
    {
      char swiname[32];

      if (inet_aton(dns->hostname, &dns->inaddr) == 1)
      {
        /* setup a hostent structure containing result */
        struct hostent host = {
            NULL, NULL, AF_INET, sizeof(struct in_addr), NULL
        };
        dns->hostent = &dns->ourhostent;
        memcpy(dns->hostent, &host, sizeof host);
        dns->hostent->h_addr_list = (char **) &dns->inaddrs;
        dns->hostent->h_addr_list[0] = (char *) &dns->inaddr;
        dns->hostent->h_addr_list[1] = NULL;
        /* valid ip address given */
        dns->status = dns_complete_success;
        break;
      }

      /* check if resolver module is present */
      if (_swix(OS_SWINumberToString, _INR(0,2), Resolver_GetHost,  swiname,
                sizeof(swiname)-1) == NULL)
      {
        if (strcmp(swiname, "Resolver_GetHost") == 0)
        {
          /* Resolver is present and is the one that's got the resolver swi
           * chunk */
          dns->status = dns_inprogress;
        }
        else
        {
          /* Not present, fallback to simple gethostbyname() */
          dns->status = dns_singletask;
        }
      }
      else
      {
        /* swi lookup failed, fallback to simple gethostbyname() */
        dns->status = dns_singletask;
      }
      break;
    }

    case dns_inprogress:
      e = _swix(Resolver_GetHost, _IN(0)|_OUTR(0,1), dns->hostname, &status, &dns->hostent);
      if (e && e->errnum == 486)
      {
        /* Bad SWI, fallback to singletasking lookup */
        dns->status = dns_singletask;
      }
      else if (e)
      {
        /* error from swi */
        dns->status = dns_complete_failure;
      }
      else if (status != EINPROGRESS)
      {
        /* finished */
        if (dns->hostent)
          dns->status = dns_complete_success;
        else
          dns->status = dns_complete_failure;
      }
      else
      {
        /* it's still resolving */
      }
      break;

    case dns_singletask:
      /* Bad SWI, fallback to singletasking lookup */
      dns->hostent = gethostbyname(dns->hostname);
      if (dns->hostent)
        dns->status = dns_complete_success;
      else
        dns->status = dns_complete_failure;
      break;

    case dns_complete_failure:
    case dns_complete_success:
      /* finished, do nothing */
      break;

    default:
      /* attempt to catch random crashes which look like they happen here */
      printf("Error: Unknown dns status - %d\n", dns->status);
      abort();
  }

  return dns->status;
}



struct hostent *dns_getanswer(dns_t *dns)
{
  assert(dns);

  if (dns->status != dns_complete_success)
    return NULL;

  return dns->hostent;
}
