/* doscan - Denial Of Service Capable Auditing of Networks
 * Copyright (C) 2003 Florian Weimer
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "config.h"
#include "opt.h"
#include "results.h"

#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <netdb.h>
#include <netinet/in.h>
#include <string>
#include <sys/socket.h>

typedef struct {
  ticks_t timestamp;
  ipv4_t host;
  int error;
  const char *banner;
} result_t;

result_t *results = 0;
unsigned allocated;
unsigned used;

typedef struct format_t {
  void (*printer) (const struct format_t *, const result_t *);
  char *data;
} format_t;

format_t *format_list;

static void format_host (const format_t *, const result_t *);
static void format_host_name (const format_t *, const result_t *);
static void format_banner (const format_t *, const result_t *);
static void format_error (const format_t *, const result_t *);
static void format_errno (const format_t *, const result_t *);
static void format_time_relative (const format_t *, const result_t *);
static void format_time_local (const format_t *, const result_t *);
static void format_time_utc (const format_t *, const result_t *);
static void format_print_data (const format_t *, const result_t *);

static void grow_results (void);
static const char *quote (const char *, size_t);
static int result_cmp (const void *, const void *);
static void results_print_one (const result_t *);
static const char *error_as_string (int error);

static enum output_style {
  SORT_BY_HOST,
  UNSORTED
} output_style = SORT_BY_HOST;

void
results_style (const char *style)
{
  if (strcasecmp (style, "sort-by-host") == 0) {
    output_style = SORT_BY_HOST;
    return;
  }
  if (strcasecmp (style, "unsorted") == 0) {
    output_style = UNSORTED;
    return;
  }

  fprintf (stderr, "%s: unknown output style '%s'\n",
           opt_program, style);
  exit (EXIT_FAILURE);
}

void
results_format (const char *format)
{
  const char *p;
  format_t *f;
  /* Allocate the formatting descriptor array. */

  format_list = new format_t[strlen (format) + 1];

  p = format;
  f = format_list;
  while (*p) {
    if (*p == '%') {
      p++;
      switch (*p) {
      case '\0':
        fprintf (stderr, "%s: trailing '%%' in --output argument",
                 opt_program);
        exit (EXIT_FAILURE);
        break;

      case 'a':
        f->printer = format_host;
        f->data = 0;
        f++;
        break;

      case 'b':
        f->printer = format_banner;
        f->data = 0;
        f++;
        break;

      case 'e':
        f->printer = format_error;
        f->data = 0;
        f++;
        break;

      case 'E':
        f->printer = format_errno;
        f->data = 0;
        f++;
        break;

      case 'n':
        f->printer = format_host_name;
        f->data = 0;
        f++;
        break;

      case 'N':
        f->printer = format_print_data;
        f->data = strdup("\n");
        f++;
        break;

      case 'r':
        f->printer = format_time_relative;
        f->data = 0;
        f++;
        break;

      case 't':
        f->printer = format_time_local;
        f->data = 0;
        f++;
        break;

      case 'T':
        f->printer = format_time_utc;
        f->data = 0;
        f++;
        break;

      case '%':
        f->printer = format_print_data;
        f->data = strdup("%");
        f++;
        break;

      default:
        fprintf (stderr, "%s: illegal pattern '%%%c' in --output argument",
                 opt_program, *p);
        exit (EXIT_FAILURE);

      }

      p++;

    } else {
      /* Not a pattern, but a string. */

      const char *end = p;
      while ((*end != '\0') && (*end != '%')) {
        end++;
      }

      f->printer = format_print_data;
      f->data = strndup(p, end - p);

      f++;
      p = end;
    }
  }

  f->printer = format_print_data;
  f->data = strdup("\n");
  f++;
  f->printer = 0;
  f->data = 0;
}



void
results_add (ticks_t timestamp, ipv4_t host, int error, const char *data, size_t length)
{
  grow_results();
  results[used].timestamp = timestamp;
  results[used].host = host;
  results[used].error = error;
  if (data) {
    results[used].banner = quote (data, length);
  } else {
    results[used].banner = "";
  }

  if (output_style == UNSORTED) {
    results_print_one (results + used);
  }

  used++;
}

void
results_add (ipv4_t host, int error, const std::string& data)
{
  results_add (ticks_get_cached(), host, error, data.data(), data.size());
}

void
results_add (ipv4_t host, const std::string& data)
{
  results_add (ticks_get_cached(), host, 0, data.data(), data.size());
}

void
results_add_unquoted (ipv4_t host, int error, const std::string& msg)
{
  grow_results();
  results[used].timestamp = ticks_get_cached();
  results[used].host = host;
  results[used].error = error;
  results[used].banner = strdup(msg.c_str());

  if (output_style == UNSORTED) {
    results_print_one (results + used);
  }

  used++;
}

void
results_add_unquoted (ipv4_t host, const std::string& msg)
{
  results_add_unquoted(host, 0, msg);
}

void
results_print ()
{
  unsigned j;

  if (output_style == UNSORTED) {
    return;                     /* already printed */
  }

  qsort (results, used, sizeof (result_t), result_cmp);

  for (j = 0; j < used; j++) {
    results_print_one (results + j);
  }

}

unsigned
results_count (void)
{
  return used;
}

static int
result_cmp (const void *a, const void *b)
{
  result_t *left = (result_t *)a;
  result_t *right = (result_t *)b;

  if (left->host < right->host) {
    return -1;
  }
  if (left->host > right->host) {
    return 1;
  }
  return 0;
}

static void
grow_results (void)
{
  if (results == 0) {
    allocated = 256;
  } else {
    if (used < allocated) {
      /* Nothing to do, enough space. */
      return;
    }
    allocated *= 2;
  }

  results = static_cast<result_t*>(realloc (results,
                                            allocated * sizeof (result_t)));
}

static const char *
quote (const char *data, size_t length)
{
  const char *source;
  char *target, *result;
  char buffer[length * 4 + 1];
  unsigned ch;

  source = data;
  target = buffer;

  while (source < (data + length)) {
    ch = (unsigned char)*source;
    switch (ch) {
    case '\t':
      *(target++) = '\\';
      *(target++) = 't';
      break;

    case '\r':
      *(target++) = '\\';
      *(target++) = 'r';
      break;

    case '\n':
      *(target++) = '\\';
      *(target++) = 'n';
      break;

    case '\\':
      *(target++) = '\\';
      *(target++) = '\\';
      break;

    default:
      if ((ch < ' ') || (ch > '~')) {
        /* This assumes ASCII. */
        *(target++) = '\\';
        *(target++) = '0' + ((ch >> 6) & 7);
        *(target++) = '0' + ((ch >> 3) & 7);
        *(target++) = '0' + (ch & 7);
      } else {
        *(target++) = *source;
      }
    }
    source++;
  }
  *(target++) = 0;

  result = new char[target - buffer];
  memcpy (result, buffer, target - buffer);
  return result;
}

static void
results_print_one (const result_t *result)
{
  const format_t *f = format_list;

  while (f->printer) {
    f->printer (f, result);
    f++;
  }
}

static const char *
error_as_string (int error)
{
  switch (error) {
  case 0:
    return "";

#define X(e) case e: return #e
    X(EACCES);
    X(ECONNREFUSED);
    X(ECONNRESET);
    X(EHOSTUNREACH);
    X(ENETUNREACH);
    X(EPERM);
    X(EPIPE);
    X(ETIMEDOUT);
#undef X

  case RESULTS_ERROR_NOMATCH:
    return "no match";

  case RESULTS_ERROR_NODATA:
    return "no data";

  default:
    return "unknown";
  }
}

static void
format_host (const format_t *format, const result_t *result)
{
    ipv4_string_t a;

    ipv4_address_to_string (result->host, a);
    printf ("%s", a);
}

static void
format_host_name (const format_t *format, const result_t *result)
{
  struct in_addr in;
  struct hostent *he;

  in.s_addr = htonl (result->host);
#ifdef GETHOSTBYADDR_ACCEPTS_IN_ADDR
  he = gethostbyaddr (&in, sizeof (in), AF_INET);
#else
#ifdef GETHOSTBYADDR_ACCEPTS_CHAR
  he = gethostbyaddr (reinterpret_cast<char*>(&in), sizeof (in), AF_INET);
#else
#error Type of first argument to gethostbyaddr() is not known.
#endif
#endif

  if (he) {
    printf ("%s", he->h_name);
  } else {
    /* No address. */
    format_host (format, result);
  }
}

static void
format_banner (const format_t *format, const result_t *result)
{
  printf ("%s", result->banner);
}


static void
format_error (const format_t *format, const result_t *result)
{
  printf ("%s", error_as_string (result->error));
}

static void
format_errno (const format_t *format, const result_t *result)
{
  printf ("%d", result->error);
}

static void
format_time_relative (const format_t *format, const result_t *result)
{
  printf ("%u.%03u", result->timestamp / 1000, result->timestamp % 1000);
}

static void
format_time_local (const format_t *format, const result_t *result)
{
  ticks_string_t t;

  ticks_to_string_local (result->timestamp, t);
  printf ("%s", t);
}

static void
format_time_utc (const format_t *format, const result_t *result)
{
  ticks_string_t t;

  ticks_to_string_utc (result->timestamp, t);
  printf ("%s", t);
}

static void
format_print_data (const format_t *format, const result_t *result)
{
  printf ("%s", format->data);
}
