/* *************************************************************
   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.
**************************************************************** */

#include "rwhois.h"
#include "rwhois_internal.h"
#include "strutils.h"
#include "whoisquery.h"/* for the minimal whois support */

/* note that SIG_IGN doesn't work on non-SysV systems. */
static
void set_sig_ignore(int signum)
{
  signal(signum, SIG_IGN);
}

static char *
connect_state_to_string(rw_connect_state_enum state)
{
  char *msg;

  switch (state)
  {
  case RW_CONN_BADADDR:
    msg = "Cannot resolve host";
    break;
  case RW_CONN_BADSOCK:
    msg = "Could not create socket";
    break;
  case RW_CONN_BADCON:
    msg = "Could not connect to host";
    break;
  case RW_CONN_BADMODE:
    msg = "Incorrect fcntl mode specified";
    break;
  case RW_CONN_NOTRW:
    msg = "Socket not read/writable";
    break;
  case RW_CONN_GOOD:
    msg = "";
    break;
  default:
    msg = "Unknown contact error.";
    break;
  }

  return msg;
}

static rw_connect_state_enum
contact(rw_server_struct *server)
{
  struct servent        *service = NULL;
  rw_connect_state_enum status   = RW_CONN_GOOD;

#ifdef HAVE_GETADDRINFO
  struct addrinfo hints, *res, *res2;
  char            port[50];
  int             error;

  memset(&hints, 0, sizeof(hints));
  hints.ai_family   = PF_UNSPEC;
  hints.ai_socktype = SOCK_STREAM;

  if (! server->port) {
    service = getservbyname(SERVICE, PROTOCOL);

    /* Connect to that port */
    if ( service == NULL ) {
      server->port = htons(NORMPORT);
    }
    else {
      server->port = service->s_port;
    }
  }

  sprintf(port, "%d", server->port );
  error = getaddrinfo(server->host, port, &hints, &res );

  if (server->debug_level > 0) {
    printf("Connecting to %s:%d\n", server->host, server->port);
  }

  if (error) return RW_CONN_BADADDR;

  res2 = res;
  for ( ; res; res = res->ai_next ) {
    server->sock = socket(res->ai_family, res->ai_socktype,
                          res->ai_protocol);
    if (server->sock < 0) {
      status = RW_CONN_BADSOCK;
      continue;
    }

    if (connect(server->sock, res->ai_addr, res->ai_addrlen ) < 0 ) {
      status = RW_CONN_BADCON;
      continue;
    }

    status = RW_CONN_GOOD;
    break;
  }

  freeaddrinfo(res2);

#else   /* HAVE_GETADDRINFO */
  struct sockaddr_in  sin;
  struct hostent      *host  = NULL;

  if (!(host = gethostbyname(server->host))) {
    unsigned long   hostaddr;

    hostaddr = inet_addr(server->host);
    if (!(host = gethostbyaddr((char *) &hostaddr, 4, AF_INET))) {
      status = RW_CONN_BADADDR;
    }
  }
  else {
    /* Create a socket  if ok continue on to contact server */
    if ((server->sock = socket( PF_INET, SOCK_STREAM, 0)) < 0) {
      status = RW_CONN_BADSOCK;
    }
    else {
      /* Bind to that socket */
      sin.sin_family = host->h_addrtype;
      /* lookup the port number for the rwhois server */
      memcpy (&sin.sin_addr,host->h_addr, host->h_length);

      if (!server->port) {
        service = getservbyname(SERVICE, PROTOCOL);

        /* Connect to that port */
        if ( service == NULL ) {
          sin.sin_port = htons(NORMPORT);
        }
        else {
          sin.sin_port = service->s_port;
        }
      }
      else {
        sin.sin_port = htons(server->port);
      }

      if (connect(server->sock, (struct sockaddr *) &sin,
                  sizeof (sin)) < 0) {
        status = RW_CONN_BADCON;
      }
    }
  }
#endif  /* HAVE_GETADDRINFO */

  return status;
}

static int
fetch_intial_banner(rw_server_struct *server)
{
  int fetch_ret;
  char buffer[MAXBUFFER];

  /* read the banner line.  RWhois insists that it is there, so if it
     isn't, this bad */
  fetch_ret = rwhois_read(server, buffer, sizeof(buffer));
  if (fetch_ret <= 0) {
    rwhois_close(server);
    return RW_ERROR;
  }

  if (!STRN_EQ(buffer, "%rwhois", 7)) {
    rwhois_close(server);
    return RW_ERROR;
  }

  server->version = get_version(buffer);
  get_capability(buffer, &(server->capability));

  return RW_OK;
}

/* This function establishes a connection between the server and the
   client. */
int
rwhois_connect(rw_server_struct   *server,
               char               *spec_version,
               char               *imp_version,
               char               *info_buffer,
               size_t             info_buffer_len,
               rw_response_struct *response)
{
  rw_connect_state_enum connect_status = RW_CONN_GOOD;
  rw_record_struct      *record        = NULL;
  char                  output_buffer[MAXBUFFER];

  assert(server);
  assert(response);
  assert(STR_EXISTS(spec_version));
  assert(STR_EXISTS(imp_version));

  if (NOT_STR_EXISTS(server->host)) {
    rwhois_set_response(response, RW_ERROR, "Cannot connect to null host");
    return RW_ERROR;
  }

  set_sig_ignore(SIGPIPE);

  if ((connect_status = contact(server)) != RW_CONN_GOOD) {
    rwhois_set_response(response, RW_ERROR,
                        connect_state_to_string(connect_status));
    return RW_ERROR;
  }

  /* read the banner; rwhois_fetch_record will fill the server record with
     information gleaned from the banner */
  if (fetch_intial_banner(server) != RW_OK) {
    rwhois_set_response(response, RW_ERROR,
                        "Could not parse initial RWhois banner.");
    return RW_ERROR;
  }

  /* if we are standard version 1.0 or 1.5, do the -rwhois response to
     the banner */
  if ( (server->version == VER_1_0) ||
       (server->version == VER_1_5) ) {
    sprintf(output_buffer, "-rwhois V-%s (NSI, Inc. V-%s)",
            spec_version, imp_version);

    /* send the -rwhois command */
    rwhois_command(server, output_buffer, response);

    if (response->status < RW_OK) {
      return response->status;
    }

    /* read the banner reply (again) */
    record = rwhois_fetch_record(server, response);

    /* if there was an info block... */
    if (record && record->text && info_buffer) {
      strncpy(info_buffer, record->text, info_buffer_len);
    }

    /* free the record, if one was allocated */
    destroy_rw_record_struct(record);

    /* if we got a good response.. */
    if (response->status >= RW_OK) {
      rwhois_set_response(response, RW_OK, "");
      return RW_OK;
    }
    /* otherwise, fall through */
  }

  /* if we were an unknown version, or we got a bad response from the
     '-rwhois' directive */
  rwhois_set_response(response, RW_ERROR, NULL);
  return RW_ERROR;
}



size_t
rwhois_prim_write(rw_server_struct *server, char *str, size_t n)
{
  size_t  nleft;
  ssize_t nwritten;
  char    *ptr;

  assert(server);
  assert(STR_EXISTS(str));

  if (! rwhois_is_open(server)) {
    return RW_ERROR;
  }

  ptr = str;
  nleft = n;

  while (nleft > 0) {
    if ( (nwritten = write(server->sock, ptr, nleft)) <= 0) {
      if (errno == EINTR) {
        nwritten = 0;
      } else {
        /* we have encountered a fatal write error, so close the
           connection */
        rwhois_close(server);
        return RW_ERROR;
      }
    }
    nleft -= nwritten;
    ptr += nwritten;
  }

  if (server->debug_level > 1) fputs(str, stdout);

  return n;
}

size_t
rwhois_write(rw_server_struct *server, char *str, size_t n)
{
  assert(server);

  if (server->debug_level > 1)fputs("C: ", stdout);

  if (rwhois_prim_write(server, str, n) < 0) {
    return RW_ERROR;
  }

  if (rwhois_prim_write(server, "\r\n", 2) < 0) {
    return RW_ERROR;
  }

  return n;
}

ssize_t
rwhois_prim_read(rw_server_struct *server, char *buffer, size_t maxlen)
{
  ssize_t n;
  ssize_t rc;
  char    *ptr;
  char    c;

  assert(server);
  assert(buffer);

  ptr   = buffer;
  for (n = 1; n < maxlen; n++) {
    if ( (rc = read(server->sock, &c, 1)) == 1) {
      /* we successfully read the character */
      *ptr++ = c;
      if (c == '\n') break; /* we hit EOL, so we are done */
    } else if (rc == 0) {
      if (n == 1) return 0; /* EOF; no data read at all */
      /* else */
      break; /* we've hit the end of the stream, so we are done for now */
    } else {
      if (errno == EINTR) continue; /* retry interrupts */
      if (errno == EPIPE) continue; /* try to get the rest of the recv buf */
      /* else */
      rwhois_close(server);
      return RW_ERROR;
    }
  }
  /* null terminate the string */
  *ptr = '\0';

  if (server->debug_level > 1) {
    fputs("S: ", stdout);
    fputs(buffer, stdout);
  }

  return n;
}


ssize_t
rwhois_read(rw_server_struct *server, char *buffer, size_t n)
{
  ssize_t r = rwhois_prim_read(server, buffer, n);

  if (r < 0) {
    return r;
  }

  rtrim(buffer);
  return r;
}

int
rwhois_close(rw_server_struct *server)
{
  if (server != NULL) {
    close(server->sock);
    server->sock = 0;
  }

  return RW_OK;
}

int
rwhois_is_open(rw_server_struct *server)
{
  if (!server) return FALSE;

  /* FIXME: should probably have a postive check for socket openness */
  if (server->sock != 0) return TRUE;

  return FALSE;
}

int
destroy_rw_server_struct(rw_server_struct *server)
{
  if (!server) return TRUE;

  if (server->host) free(server->host);
  free(server);

  return TRUE;
}

/* this is part of the whoisquery support.  It's prototype is in whoisquery.h */
int
rwhois_whoisconnect(rw_server_struct   *server,
                    rw_response_struct *response)
{
  rw_connect_state_enum connect_status = RW_CONN_GOOD;

  assert(server);
  assert(response);

  if (NOT_STR_EXISTS(server->host)) {
    rwhois_set_response(response, RW_ERROR, "Cannot connect to null host");
    return RW_ERROR;
  }

  if (server->port <= 0) {
    server->port = WHOIS_PORT;
  }
  set_sig_ignore(SIGPIPE);

  if ((connect_status = contact(server)) != RW_CONN_GOOD) {
    rwhois_set_response(response, RW_ERROR,
                        connect_state_to_string(connect_status));
    return RW_ERROR;
  }

  /* whois is so simple, that there isn't anything else to do */
  rwhois_set_response(response, RW_OK, "");
  return RW_OK;
}
