/* Nessus
 * Copyright (C) 1998 Renaud Deraison
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *
 * Attack.c : 
 *  launch the plugins, and manages the multithreading 
 *
 */
 
#include <includes.h>

#include "attack.h"
#include "log.h"
#include "hostloop.h"
#include "sighand.h"
#include "rules.h"
#include "auth.h"
#include "threads.h"
#include "comm.h" 
#include "utils.h"
#include "preferences.h"
#include "ntp.h"
#ifndef INADDR_NONE
#define INADDR_NONE 0xffffffff
#endif

/*
 * Setup an arglist from a given hostname
 */
struct arglist * 
attack_init_hostinfos(hostname)
     char * hostname;
{
  struct arglist * hostinfos;
  struct arglist * ports;
  struct in_addr *p_addr;
  
  p_addr = emalloc(sizeof(struct in_addr));
  *p_addr = nn_resolve(hostname);
  if((p_addr->s_addr == INADDR_NONE) || (p_addr->s_addr == INADDR_ANY))
    {
      efree(&p_addr);
      return(NULL);
    }
  hostinfos = emalloc(sizeof(struct arglist));
  arg_add_value(hostinfos, "FQDN", ARG_STRING, strlen(hostname), hostname);
  arg_add_value(hostinfos, "IP", ARG_PTR, sizeof(struct in_addr), p_addr);
  ports = emalloc(sizeof(struct arglist));
  arg_add_value(hostinfos, "PORTS", ARG_ARGLIST, sizeof(struct arglist), ports);
  return(hostinfos);
}


/*
 * This function attacks *one* host
 */
void 
attack_host_thread(globals, hostinfos, plugins, hostname, port_range, hosts_list)
     struct arglist * globals;
     struct arglist * hostinfos;
     struct arglist * plugins;
     char * hostname;
     char * port_range;
     struct arglist * hosts_list;
{ 
  int num_plugs = 0;
  int cur_plug = 1;
  struct arglist * new_args;
  struct arglist * scanner = arg_get_value(globals, "scanners");
  struct arglist * key = emalloc(sizeof(struct arglist));
  ext_library_t ptr;
  plugin_run_t f_run = NULL;
  
  arg_add_value(key, "hosts", ARG_ARGLIST, -1, hosts_list);
  num_plugs = get_active_plugins_number(plugins);
  
  /* do the portscan(s) */
  while(scanner && scanner->next)
  {
   if(plug_get_launch(scanner->value))
   {
    new_args = arg_get_value(scanner->value, "plugin_args");
    arg_add_value(new_args, "globals", ARG_ARGLIST, -1, globals);
    arg_add_value(new_args, "hostinfos", ARG_ARGLIST, -1, hostinfos);
    arg_add_value(new_args, "port_range", ARG_STRING, strlen(port_range), port_range);
    /*
     if(arg_get_value(new_args, "LIBPCAP"))lib
    */
    ptr = LOAD_LIBRARY(arg_get_value(scanner->value, "full_name"));
    f_run = (plugin_run_t)LOAD_FUNCTION(ptr, "plugin_run");
    if(!f_run)f_run = (plugin_run_t)LOAD_FUNCTION(ptr, "_plugin_run");
    log_write("user %s : launching %s against %s\n", (char *)arg_get_value(globals, "user"),
					scanner->name, hostname);
    if(f_run)(*f_run)(new_args); 
    else log_write("Could not find the 'plugin_run' entry point in %s", plugins->name);
   }
   scanner = scanner->next;
 }
 
 
  /* launch the plugins */
  while(plugins && plugins->next)
    {
     
      struct arglist * args;
      nthread_t current_plugin;
   
      
      ptr = LOAD_LIBRARY(arg_get_value(plugins->value, "full_name"));
      f_run = (plugin_run_t)LOAD_FUNCTION(ptr, "plugin_run");
      if(!f_run)f_run = (plugin_run_t)LOAD_FUNCTION(ptr, "_plugin_run");
      if(!f_run)
      {
       log_write("Could not find the 'plugin_run' entry point in %s", plugins->name);
       plugins = plugins->next;
       continue;
      }
      
      if(plug_get_launch(plugins->value)) /* can we launch it ? */
	{
	 struct arglist * preferences = arg_get_value(globals,"preferences");
         int pip[2];
         int timeout;
         pipe(pip);
         fcntl(pip[0], F_SETFL, O_NONBLOCK);
	 args = arg_get_value(plugins->value, "plugin_args");
         arg_add_value(args, "HOSTNAME", ARG_ARGLIST, -1, hostinfos);
	 log_write("user %s : launching %s against %s\n", (char *)arg_get_value(globals, "user"),
					plugins->name, hostname);
         arg_set_value(args, "preferences", -1, preferences);
         arg_add_value(args, "pipe", ARG_INT, sizeof(int), (void *)pip[1]);
         arg_add_value(args, "key", ARG_ARGLIST, -1, key);
         timeout = (int)arg_get_value(args, "TIMEOUT");
	 comm_send_status(globals, hostname, "attack", cur_plug++, num_plugs);
	 current_plugin = create_thread(f_run, args);
         if(!timeout)timeout = PLUGIN_TIMEOUT;
	 thread_timeout(current_plugin, timeout, pip[0], key);
         close(pip[0]);
         close(pip[1]);
	}
      CLOSE_LIBRARY(ptr);
      plugins = plugins->next;
    }
}

/* 
 * Start an attack : set up the host infos and launch the
 * attack thread
 */
void
attack_start(args)
  struct arglist * args;
{
 struct arglist * globals = arg_get_value(args, "globals");
 char * hostname = arg_get_value(args, "hostname");
 struct arglist * plugs = arg_get_value(args, "plugins");
 char * port_range = arg_get_value(args, "port_range");
 struct arglist * hosts_list = arg_get_value(args, "hosts_list");
 struct arglist * hostinfos;
 arg_free(args);
 if(!(hostinfos = attack_init_hostinfos(hostname)))
   auth_printf(globals, "SERVER <|> ERROR <|> %s : host unknown <|> SERVER\n", 
	       hostname);
 else
   {
    struct arglist * new_plugs = emalloc(sizeof(struct arglist));
    arg_dup(new_plugs, plugs);
    attack_host_thread(globals, hostinfos, new_plugs, hostname, port_range, hosts_list);
   }
 EXIT(0);
}

/*
 * This is the main function for attacking
 * a whole network
 */
int 
attack_host(globals)
    struct arglist * globals;
{
  int max_threads;
  struct arglist * tplugs;
  int num_tested = 0;
  int host_pending;
  int num_threads = 0;
  char * hostname;
  char * port_range;
  int max_hosts;
  int hl_flags;
  int subnet_class;
  void * hls_data;
  int global_socket = (int)arg_get_value(globals, "global_socket");
  struct arglist * preferences = arg_get_value(globals, "preferences");
  struct arglist * plugins = arg_get_value(globals, "plugins");
  struct arglist * hosts_list = emalloc(sizeof(struct arglist));
  int ntp_version = (int)arg_get_value(globals, "ntp_version");
  struct nessusd_threads * threads;
  
  
  hostname = arg_get_value(preferences, "TARGET");
  max_hosts = atoi(arg_get_value(preferences, "max_hosts"));
  port_range = arg_get_value(preferences, "port_range");
  /*
   * Initialization of the attack
   */
  tplugs = sort_plugins_by_type(plugins); /* sort the plugins by category   */
  
  
  if(ntp_version > NTP_10)send_plugin_order(globals, tplugs);
  hl_flags = preferences_get_host_expansion(preferences);
  subnet_class = preferences_get_subnet_class(preferences);
#ifndef NESSUSNT 
  if(geteuid() && (hl_flags & HL_PING))
    {
     auth_printf(globals, "SERVER <|> ERROR <|> The server is not root. This is needed \
for sending a ping to the remote hosts <|> SERVER\n");
     comm_terminate(globals);
     shutdown(global_socket, 2);
     EXIT(0);
     }
#endif
  hls_data = hls_new_session(hostname, hl_flags, subnet_class);   /* init. the hostloop lib  */
  max_threads = get_max_thread_number(preferences);
  log_write("user %s starts a new attack against %s\n",
			 (char *)arg_get_value(globals, "user"), hostname);
  threads = nessusd_threads_init();
  arg_add_value(globals, "threads", ARG_PTR, -1, threads);
  /*
   * Start the attack !
   */
  while(hostname && (num_tested != max_hosts))
    {
      nthread_t pid;
      host_pending = 0 ;
      if(pTEST(get_hostname_attributes(globals, hostname))) /* do we have the right to test this
						               host ? */
	{
	  if(num_threads < max_threads)
	    {
             struct arglist * arglist = emalloc(sizeof(struct arglist));
             arg_add_value(hosts_list, hostname, ARG_INT, -1, (void *)1);
             arg_add_value(arglist, "globals", ARG_ARGLIST, -1, globals);
             arg_add_value(arglist, "hostname", ARG_STRING, -1, hostname);
             arg_add_value(arglist, "plugins", ARG_ARGLIST, -1, tplugs);
             arg_add_value(arglist, "port_range", ARG_STRING, -1, port_range);
             arg_add_value(arglist, "hosts_list", ARG_ARGLIST, -1, hosts_list);
	     pid = create_thread(attack_start, arglist);
	     nessusd_thread_register(&threads, hostname, pid);
             arg_set_value(globals, "threads", -1, threads);
             num_threads++;
	     log_write("user %s : testing %s\n", (char *)arg_get_value(globals, "user"), hostname);
	    }
	  else 
	    {
              /* 
               * There are too many threads at this time... We
               * have to wait until one of them finishes
               */
	      host_pending = 1; /* means that we must NOT update the hostname */
             
	      while(num_threads >= max_threads)
                {
                check_client_input(globals);
                threads = arg_get_value(globals, "threads");
		num_threads = wait_for_a_thread(&threads);
                arg_set_value(globals, "threads", -1, threads);
                }
	    }
	}
      if(!host_pending){
        /*
         * since hls_get_next_host() may take a little time, 
         * we set up a thread dedicated to listenning to our 
         * socket so that we can catch the user input...
         */
        nthread_t check_client_thread = create_thread(check_client_input_thread, globals);
        num_tested++;
       	hostname = hls_get_next_host(hls_data); 
        TERMINATE_THREAD(check_client_thread); 
#ifndef NESSUSNT
        /* resync the threads table -- not necessary under NT 
           nor under UNIX with the pthreads. But I don't like the pthreads
	 */
        wait_for_a_thread(&threads);
        arg_set_value(globals, "threads", -1, threads);
#endif
      }
    }
  /*
   * Every host is being tested... We have to wait for the threads
   * to terminate
   */
  
  while(wait_for_a_thread(&threads)){
        arg_set_value(globals, "threads", -1, threads);
     	check_client_input(globals);
        threads = arg_get_value(globals, "threads");
        }
  
  /*
   * Clean up the temporary plugin list in memory
   */
  arg_free(tplugs);
  return(0);
}

   
 
