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

/* This is a simple HTTP version string collector, intended as sample
   code for the TCP engine code. */

#include "config.h"
#include "engine_tcp.h"
#include "opt.h"
#include "proto.h"
#include "results.h"
#include "scan.h"
#include "utils.h"

#include <cstdio>

static bool http_start (subnets&);
static void http_open (scan_host_t *);
static void http_send_request (scan_host_t *);
static void http_receive_reply (scan_host_t *);
static void http_finish (scan_host_t *, const char *buffer, unsigned size);

static char *request_buffer;
static unsigned request_size;

static pcre *end_regexp;
static pcre *server_regexp;

void
proto_http_register (void)
{
  proto_register ("http", http_start, http_open);
}

static bool
http_start (subnets&)
{
  const char *errptr;
  int erroffset;

  if (opt_banner_size == 0) {
    opt_banner_size = 4000;
  }

  if (opt_send && (opt_send[0] != '\0')) {
    string_dequote (opt_send, &request_buffer, &request_size, "--send option");
  } else {
    string_dequote ("GET / HTTP/1.0\\r\\n\\r\\n",
                    &request_buffer, &request_size, "--send option");
  }

  if (opt_receive && (opt_receive[0] != '\0')) {
    fprintf (stderr, "%s: http protocol module does not support --receive\n",
             opt_program);
    exit (EXIT_FAILURE);
  }

  end_regexp
    = pcre_compile ("(.*?)\r\n\r\n",
                    PCRE_ANCHORED | PCRE_DOLLAR_ENDONLY | PCRE_DOTALL,
                    &errptr, &erroffset, 0);
  server_regexp
    = pcre_compile (".*\r\nServer:[\t ]*(.*?)[\t ]*\r\n",
                    PCRE_ANCHORED | PCRE_DOLLAR_ENDONLY | PCRE_DOTALL,
                    &errptr, &erroffset, 0);
  if (! (end_regexp && server_regexp)) {
    fprintf (stderr, "%s: fatal PCRE error\n",
             opt_program);
    exit (EXIT_FAILURE);
  }

  return true;
}

static void
http_open (scan_host_t *s)
{
  if (!s->state) {
    s->state = malloc (sizeof (engine_tcp_t));
  }
  engine_tcp_open (s, http_send_request, http_receive_reply);
}

static void
http_send_request (scan_host_t *s)
{
  engine_tcp_send (s, request_buffer, request_size, ENGINE_TCP_NO_COPY,
                   http_receive_reply);
}

static void
http_receive_reply (scan_host_t *s)
{
  engine_tcp_receive_until_match (s, end_regexp, 0, opt_banner_size,
                                  http_finish);
}

static void
http_finish (scan_host_t *s, const char *buffer, unsigned size)
{
  int ovector[6];
  int rc;
  unsigned header_length;

  /* First determine the end of the header. */

  rc = pcre_exec (end_regexp, 0, buffer, size, 0, 0, ovector, 6);

  if (rc != 2) {
    fprintf (stderr, "%s: internal error %d in http protocol module\n",
             opt_program, rc);
    exit (EXIT_FAILURE);
  }
  if (ovector[2] != 0) {
    fprintf (stderr, "%s: internal match error in http protocol module\n",
             opt_program);
    exit (EXIT_FAILURE);
  }
  header_length = ovector[3];

  /* Then look for the Server: string in the header. */

  rc = pcre_exec (server_regexp, 0, buffer, header_length,
                  0, 0, ovector, 6);
  switch (rc) {
  case 2:
    results_add (ticks_get_cached (), s->host, 0,
                 buffer + ovector[2], ovector[3] - ovector[2]);
    break;

  case PCRE_ERROR_NOMATCH:
    results_add (ticks_get_cached (), s->host, RESULTS_ERROR_NODATA,
                 buffer, header_length);
    break;

  default:
    fprintf (stderr, "%s: internal PCRE in http protocol module\n",
             opt_program);
    exit (EXIT_FAILURE);
  }

  /* No more pending requests, connection is closed automatically. */
}
