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

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

static bool ident_start (subnets&);
static void ident_open (scan_host_t *);

void
proto_ident_check_register (void)
{
  proto_register ("ident_check", ident_start, ident_open);
}

static void ident_open (scan_host_t *)
{
}

class proto_ident_check : public tcp_half_duplex_handler {
  unsigned local_port;

  static const rx regexp_response;

public:
  proto_ident_check(event_queue&, ipv4_t);

  virtual void ready(int state, int error, const std::string& data);
};

const rx proto_ident_check::regexp_response("^[ ]*(\\d+) *, *(\\d+) *"
                                            ": *(?:USERID|ERROR) *:",
                                            PCRE_CASELESS);

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

void
proto_ident_check::ready(int state, int error, const std::string& data)
{
  if (error && state > 0) {
    results_add(host(), error, "error after connect");
    return;
  }

  switch (state) {
  case 0:
    // Just got connected.
    {
      // Check for pending connection error.  Only record the error if
      // the connection has been reset.

      int error = get_error();
      if (error) {
        if (error == ECONNRESET) {
          results_add(host(), error, "error during connect");
        }
        return;
      }

      // Construct the reply.  We need the port number on our side, so
      // we fetch it using getsockname().

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

      local_port = htons(sa.sin_port);
      char request[50];
      sprintf(request, "%u , %u\r\n", port(), local_port);
      send(state + 1, request);
      return;
    }

  case 1:
    // Process the reply.
    {
      unsigned len = data.size();
      if (data.size() < 2) {
        request_more_data(state);
        return;
      }

      if (data[len - 2] != '\r' || data[len - 1] != '\n') {
        // Do not retrieve too much data.
        if (data.size() < 1000) {
          request_more_data(state);
          return;
        } else {
          results_add(host(), RESULTS_ERROR_NOMATCH, data);
          return;
        }
      } else {
        // We have at least a line terminated by CRLF.
        std::string::size_type pos = data.find("\r\n");
        if (pos != len - 2) {
          // Multiple lines in reply.
          results_add(host(), RESULTS_ERROR_NOMATCH, data);
          return;
        }

        rx::matches matches;
        bool match = regexp_response.exec(data, matches);
        if (!match
            || (static_cast<unsigned>(atoi(matches[1].data().c_str()))
                != port())
            || (static_cast<unsigned>(atoi(matches[2].data().c_str()))
                != local_port)) {
          results_add(host(), RESULTS_ERROR_NOMATCH, data);
          return;
        }

        // Reply is genuine, log only in verbose mode.
        if (opt_verbose) {
          results_add(host(), data);
        }
        return;
      }
    }
  }

  abort();
}

static
bool ident_start(subnets& n)
{
  if (opt_receive && opt_receive[0]) {
    fprintf (stderr, "%s: --receive is not supported by this module\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);
  }

  if (opt_send && (opt_send[0] != 0)) {
    fprintf (stderr, "%s: --send is not supported by this module.\n",
             opt_program);
    exit (EXIT_FAILURE);
  }

  std::auto_ptr<event_queue> q(event_queue::create(opt_fd_count));
  scan_trigger::default_handler<proto_ident_check> th;
  scan_trigger t(*q, n, th, opt_fd_count, opt_add_timeout, opt_add_burst);

  q->run();
  return false;
}
