/* *************************************************************
   RWhois Software

   Copyright (c) 1994 Scott Williamson and Mark Kosters
   Copyright (c) 1996-2000 Network Solutions, Inc.

   See the file LICENSE for conditions of use and distribution.
**************************************************************** */

/* This set of routines provides a (very) limited set of WHOIS (not
   RWhois) client routines, provided so that there is at least some
   support for following whois referrals.

   Note that since WHOIS is largely unstandardized, these routines are
   not guaranteed to work against all deployed WHOIS server.  In fact,
   it is not guaranteed to work with any of them. */

#include "whoisquery.h"
#include "rwhois.h"
#include "strutils.h"

char *whois_keyword_allowed_domains[] =
{
  "networksolutions.com",
  "internic.net",
  NULL
};

char *rwhois_keyword_list[] =
{
  "domain",
  "host",
  "contact",
  "network",
  "server",
  NULL
};

static int
is_keyword_ok_host(char *host)
{
  char *k;
  int  i = 0;

  while ((k = whois_keyword_allowed_domains[i]) != NULL) {
    if (strSTR(host, k)) {
      return TRUE;
    }
    i++;
  }

  return FALSE;
}

/* query can be one of the following forms, really:

   value (e.g, "example.com", "host-org", "ns21.worldnic.com")

   attribute = value (e.g., "domain-name=example.com", "tech-contact =
   host-org", etc.)

   keyword value (e.g., "domain netsol.com") keyword attribute = value

   plus, AND and OR clauses.

   We won't deal with multi clause rwhois queries.  We will just split
   into keyword, attribute name, and value */
static int
split_rwhois_non_keyword_fragment(char *frag, char **attr, char **value)
{
  char *p;

  /* we have attr = value (with or without whitespace) */
  if ((p = strchr(frag, '='))) {
    *p = '\0';
    *attr = frag;
    *value = p + 1;

    return RW_OK;
  }

  /* otherwise, we just have value */
  *value = frag;
  return RW_OK;
}

static int
split_rwhois_query(char *query, char **keyword, char **attr, char **value)
{
  char *p;
  int  i;

  assert(keyword);
  assert(attr);
  assert(value);

  if (NOT_STR_EXISTS(query)) {
    return RW_ERROR;
  }

  *keyword = NULL;
  *attr    = NULL;
  *value   = NULL;

  /* remove leading and trailing whitespace */
  trim(query);

  /* if there is no whitespace, then there is no keyword */
  if (count_spaces(query) <= 0) {
    return split_rwhois_non_keyword_fragment(query, attr, value);
  }

  /* we have whitespace, so there might be a keyword */

  /* change sequences of whitespace to one to simplify matters */
  compact_whitespace(query);

  i = 0;
  while ( rwhois_keyword_list[i] ) {

    /* we found a keyword */
    if (strSTR(query, rwhois_keyword_list[i]) && (p = strchr(query, ' '))) {
      *p = '\0'; p++;
      *keyword = query;

      return split_rwhois_non_keyword_fragment(p, attr, value);
    }

    i++;
  }

  /* no keyword */
  return split_rwhois_non_keyword_fragment(query, attr, value);
}

/* do some simplistic "corrections" to an rwhois query.
   Realistically, this should use regular expressions, or a full
   rwhois query parser. */
char *
rwhois_correct_whois_query(rw_server_struct *server,
                           char             *rwhois_query)
{
  char *keyword;
  char *attr;
  char *value;
  char *query_copy;
  char *query_result;
  int  query_result_len;

  assert(server);

  if (NOT_STR_EXISTS(rwhois_query)) {
    return NULL;
  }

  if (NOT_STR_EXISTS(server->host)) {
    return NULL;
  }

  query_copy = STRDUP(rwhois_query);

  if (split_rwhois_query(query_copy, &keyword, &attr, &value) < 0) {
    FREE(query_copy);
    return STRDUP(rwhois_query);
  }

  query_result_len = 0;
  /* if we allow keywords, then result query is "keyword value" */
  if (is_keyword_ok_host(server->host) &&
      STR_EXISTS(keyword) && STR_EXISTS(value)) {
    query_result_len = strlen(keyword) + strlen(value) + 2;
    query_result = (char *) CALLOC(1, query_result_len);
    strcpy(query_result, keyword);
    strcat(query_result, " ");
    strcat(query_result, value);

    FREE(query_copy);
    return query_result;
  }

  /* otherwise, result query is just "value" */
  if (STR_EXISTS(value)) {
    query_result = STRDUP(value);
    FREE(query_copy);
    return query_result;
  }

  FREE(query_copy);
  return NULL;
}

static rw_whois_result_struct *
read_whois_response(rw_server_struct   *server,
                    rw_response_struct *response)
{
  char                   line[MAXBUFFER];
  char                   *buffer;
  rw_whois_result_struct *result;
  size_t                 buffer_size;
  size_t                 buffer_len;
  size_t                 line_length;
  assert(server);
  assert(response);

  result = (rw_whois_result_struct *) CALLOC(1, sizeof(*result));
  buffer = (char *) CALLOC(1, MAXBUFFER);
  buffer_size = MAXBUFFER;
  buffer_len = 0;

  while ( (rwhois_read(server, line, sizeof(line))) > 0 ) {

    if (NOT_STR_EXISTS(line)) {
      continue;
    }

    if (strSTR(line, "Registrant:") && ( line[0] == 'R' || line[0] == 'r') ) {
      result->separator = STRDUP(line);
      if (buffer_len > 0) {
        result->disclaimer = buffer;
        buffer = (char *) CALLOC(1, MAXBUFFER);
        buffer_size = MAXBUFFER;
        buffer_len = 0;
      }

      continue;
    }

    line_length = strlen(line);
    /* see if we need to grow the buffer */
    if ( (buffer_len + line_length + 2) > buffer_size &&
         buffer_size < (5 * MAXBUFFER)) {
      buffer = (char *) REALLOC(buffer, buffer_size + MAXBUFFER);
      buffer_size += MAXBUFFER;
    }

    strcat(buffer, line);
    buffer_len += line_length;
    strcat(buffer, "\n");
    buffer_len++;
  }

  result->text = buffer;

  return result;
}

rw_whois_result_struct *
rwhois_query_whois_server(rw_server_struct   *server,
                          char               *rwhois_query,
                          rw_response_struct *response)
{
  rw_whois_result_struct *result;
  char                   *actual_query;

  assert(server);
  assert(response);

  if (NOT_STR_EXISTS(rwhois_query)) {
    rwhois_set_response(response, RW_ERROR, "Null query");
    return NULL;
  }
  /* open the connection to the whois server, if we haven't already */
  if (!rwhois_is_open(server)) {
    if (rwhois_whoisconnect(server, response) < 0) {
      return NULL;
    }
  }

  /* calulate the real query */
  actual_query = rwhois_correct_whois_query(server, rwhois_query);
  if (NOT_STR_EXISTS(actual_query)) {
    rwhois_set_response(response, RW_ERROR,
                        "Error transforming rwhois query to whois query");
    return NULL;
  }

  if (rwhois_write(server, actual_query, strlen(actual_query)) <= 0) {
    rwhois_set_response(response, RW_ERROR,
                        "Write error occured in whois query");
    FREE(actual_query);
    return NULL;
  }

  FREE(actual_query);
  result = read_whois_response(server, response);

  rwhois_close(server);

  return result;
}

rw_whois_result_struct *
rwhois_query_whois_referral(rw_referral_struct *referral,
                            char               *rwhois_query,
                            int                debug_level,
                            rw_response_struct *response)
{
  rw_server_struct whois_server;

  assert(response);
  assert(referral);

  if (NOT_STR_EXISTS(referral->scheme) || !STR_EQ(referral->scheme, "whois")) {
    return NULL;
  }
  memset(&whois_server, 0, sizeof(whois_server));

  whois_server.host = referral->host;
  whois_server.port = referral->port;
  whois_server.debug_level = debug_level;

  return rwhois_query_whois_server(&whois_server, rwhois_query, response);
}

int
destroy_rw_whois_result_struct(rw_whois_result_struct *res)
{
  if (!res) return TRUE;

  if (res->disclaimer) FREE(res->disclaimer);
  if (res->separator) FREE(res->separator);
  if (res->text) FREE(res->text);

  FREE(res);

  return TRUE;
}
