/* doscan - Denial Of Service Capable Auditing of Networks
 * Copyright (C) 2003, 2004 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 "half_duplex.h"
#include "opt.h"
#include "proto.h"
#include "results.h"
#include "rx.h"
#include "scan_tcp.h"
#include "scan_trigger.h"
#include "subnets.h"
#include "tcp_server.h"
#include "utils.h"

#include <arpa/inet.h>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <memory>
#include <netinet/in.h>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>

static bool proxy_start(subnets&);
static void proxy_open(scan_host_t *);

typedef enum { method_get, method_connect } method_type;
static method_type method;

static unsigned listen_port;

void
proto_http_proxy_register()
{
  proto_register ("http_proxy", proxy_start, proxy_open);
}

static void
proxy_open (scan_host_t *)
{
}

// Shared mark between client and server, to recognize connections.

static const std::string mark("doscan open proxy test");

class http_reply {
  const std::string& data;
  static const rx regexp_return_code;
  static const unsigned max_reply_size = 4000;

public:
  http_reply(const std::string& reply);

  static const unsigned max_header_size = 4000;

  typedef enum { yes, maybe, no} state;

  state return_code_available() const;
  state reply_available() const;

  unsigned return_code() const;
  bool success() const;

  std::string reply() const;
};

inline
http_reply::http_reply(const std::string& reply)
  : data(reply)
{
}

const rx http_reply::regexp_return_code("^HTTP/(\\d+\\.\\d+) (\\d\\d\\d)"
                                        "(?: |\r\n)");

inline http_reply::state
http_reply::return_code_available() const
{
  if (data.find("\r\n") == std::string::npos) {
    return maybe;
  }

  if (data.size() > max_reply_size) {
    return no;
  }

  if (regexp_return_code.exec(data)) {
    return yes;
  } else {
    return no;
  }
}

unsigned
http_reply::return_code() const
{
  rx::matches matches;

  if (regexp_return_code.exec(data, matches)) {
    return atoi(matches[2].data().c_str());
  } else {
    abort();
  }
}

inline bool
http_reply::success() const
{
  unsigned ret = return_code();
  return ret >= 200 && ret <= 299;
};

inline http_reply::state
http_reply::reply_available() const
{
  std::string::size_type pos = data.find("\r\n\r\n");

  if (pos != std::string::npos && data.size() >= pos + 4 + mark.size()) {
    return yes;
  } else {
    return maybe;
  }
}

std::string
http_reply::reply() const
{
  std::string::size_type pos = data.find("\r\n\r\n");

  if (pos == std::string::npos) {
    return std::string();
  } else {
    return data.substr(pos + 4);
  }
}

class http_client : public tcp_half_duplex_handler {
  virtual void ready(int state, int error, const std::string& data);

  void make_get_request(method_type, int& state, std::string&);

public:
  http_client(event_queue&, ipv4_t);
};

http_client::http_client(event_queue& q, ipv4_t host)
  : tcp_half_duplex_handler(q, host, opt_port)
{
  set_relative_timeout(opt_connect_timeout);
}

void
http_client::make_get_request(method_type method, int& state, std::string& req)
{
  // Obtain our address for the back-connect.

  sockaddr_in sa;
  socklen_t namelen = sizeof(sa);
  int result = getsockname(fd(), reinterpret_cast<sockaddr*>(&sa),
                           &namelen);
  if (result == -1) {
    fprintf(stderr, "%s: getpeername failed, error was: %s\n",
            opt_program, strerror(errno));
    exit(EXIT_FAILURE);
  }

  char buf[100];
  switch (method) {
  case method_get:
    // We use HTTP 1.0 so that we can omit the Host: header.

    sprintf(buf, "GET http://%s:%u/doscan-probe/%u HTTP/1.0\r\n\r\n",
            inet_ntoa(sa.sin_addr), listen_port,
            static_cast<unsigned>(time(0)));
    state = 20;
    break;

  case method_connect:
    sprintf(buf, "CONNECT %s:%u HTTP/1.0\r\n\r\n",
            inet_ntoa(sa.sin_addr), listen_port);
    state = 10;
    break;
  }
  req.assign(buf);
}

void
http_client::ready(int state, int error, const std::string& data)
{
  std::string request;

  if (error) {
    return;
  }

  switch (state) {
  case 0:
    if (get_error()) {
      return;
    }
    make_get_request(method, state, request);
    send(state, request);
    return;

  case 10:
    // Wait for CONNECT result.  If ready, send the GET request.

    {
      http_reply reply(data);

      switch (reply.return_code_available()) {
      case http_reply::yes:
        if (reply.success()) {
          // New send get request.
          make_get_request(method_get, state, request);
          send(state, request);
          return;
        } else {
          // Error code, we are not welcome.  Good!
          return;
        }

      case http_reply::no:
        results_add_unquoted(host(), "invalid return code: " + quote(data));
        break;

      case http_reply::maybe:
        request_more_data(state);
      }
    }

  case 20:
    // Connection is established.  Wait for the line with the return
    // code.
    {
      http_reply reply(data);

      switch (reply.return_code_available()) {
      case http_reply::yes:
        if (reply.success()) {
          goto process_reply;
        } else {
          // Error code, we are not welcome.  Good!
          return;
        }

      case http_reply::no:
        results_add_unquoted(host(), "invalid return code: " + quote(data));
        break;

      case http_reply::maybe:
        request_more_data(state);
      }
    }
    return;

  process_reply:
    state = 21;
  case 21:
    // Reply indicating success was encountered.  We have to check the
    // contents.
    {
      http_reply reply(data);

      switch (reply.reply_available()) {
      case http_reply::yes:
        if (reply.reply() == mark) {
          results_add_unquoted(host(), "open proxy");
        }
        return;

      case http_reply::maybe:
        request_more_data(state);
        return;

      case http_reply::no:
        results_add_unquoted(host(), "invalid header: " + quote(data));
        return;
      }
    }
  }

  abort();
}

class http_server : public half_duplex_handler
{
  ipv4_t host;                  // peer address

  static const unsigned max_request_size = 4000;
  static const rx regexp_request;

  virtual void ready();
  virtual void error(int);

  void maybe_request_more_data();
  void bad_request();
  void serve_request();

public:
  http_server(event_queue&, int fd, ipv4_t host, unsigned port);
};

inline
http_server::http_server(event_queue& q, int fd, ipv4_t h, unsigned)
  : half_duplex_handler(q, fd, true), host(h)
{
  set_relative_timeout(opt_connect_timeout);
}

const rx http_server::regexp_request("^GET (?:http://[0-9.]+(?::\\d+)?)?"
                                     "/([^ ]*) (HTTP/\\d+\\.\\d+)\r\n",
                                     PCRE_CASELESS);

void
http_server::ready()
{
  switch (state) {
  case 0:
    if (receive_buffer.find("\r\n") == std::string::npos) {
      maybe_request_more_data();
      return;
    }

    // We have received the first line, advance to the next state.
    state++;

  case 1:
    // Wait until the header is complete.
    {
      rx::matches matches;
      if (regexp_request.exec(receive_buffer, matches)) {
        if (receive_buffer.find("\r\n\r\n") != std::string::npos) {
          serve_request();
          return;
        } else {
          maybe_request_more_data();
          state++;
          return;
        }
      } else{
        bad_request();
        return;
      }
    }

  case 2:
    if (receive_buffer.find("\r\n\r\n") != std::string::npos) {
      serve_request();
      return;
    }
    maybe_request_more_data();
    return;

  case 99:
    return;
  }

  abort();
}

void
http_server::maybe_request_more_data()
{
  if (receive_buffer.size() > max_request_size) {
    bad_request();
  } else {
    request_more_data();
  }
}

void
http_server::bad_request()
{
  results_add_unquoted(host, RESULTS_ERROR_NOMATCH,
                       "invalid request to server: " + quote(receive_buffer));
  send("HTTP/1.1 400 Bad Request\r\n"
       "Connection: close\r\n"
       "Content-Type: application/text/plain\r\n"
       "\r\n"
       "Bad request\r\n");
  state = 99;
}

void
http_server::serve_request()
{
  send_buffer.assign("HTTP/1.0 200 OK\r\n"
                     "Connection: close\r\n"
                     "Content-Type: application/octet-stream\r\n"
                     "\r\n");
  send_buffer.append(mark);
  send();
  state = 99;
}

class trigger_handler : public scan_trigger::default_handler<http_client> {
  tcp_accept_handler* server;
  virtual void all_connected();
public:
  trigger_handler(tcp_accept_handler*);
};

trigger_handler::trigger_handler(tcp_accept_handler* s)
  : server(s)
{
}

void
trigger_handler::all_connected()
{
  server->terminate();
}


void
http_server::error(int)
{
  // FIXME: We should log the connection attempt.
}

static bool
proxy_start(subnets& nets)
{
  if (strcmp(opt_send, "GET") == 0) {
    method = method_get;
  } else if(strcmp(opt_send, "CONNECT") == 0) {
    method = method_connect;
  } else {
    fprintf (stderr, "%s: --send GET or --send CONNECT required\n",
             opt_program);
    exit (EXIT_FAILURE);
  }

  listen_port = atoi(opt_receive);
  if (listen_port == 0 || listen_port >= 65535) {
    fprintf (stderr, "%s: --receive PORT required, 0 < PORT < 65535\n",
             opt_program);
    exit (EXIT_FAILURE);
  }

  if (opt_banner_size) {
    fprintf (stderr, "%s: --banner is not supported by this module.\n",
             opt_program);
    exit (EXIT_FAILURE);
  }

  std::auto_ptr<event_queue> q(event_queue::create(opt_fd_count));
  trigger_handler th(new tcp_default_accept_handler<http_server>(*q,
                                                                 listen_port));
  scan_trigger t(*q, nets, th, opt_fd_count, opt_add_timeout, opt_add_burst);

  q->run();

  return false;
}
