#
#  (http://www.porcupine.org/satan).
#
#
# add_todo($record) add one record to the new todo list.
# process_todos() iterates over the new todo list and generates new facts.
# save_todos($path) saves the old todos to the named file.
# drop_old_todos($host) forget everything we know about a host.
# read_todos($path) load tables from file.
# merge_todos($path) merge with in-core tables.
#
# version 2, Mon Mar 20 19:47:07 1995, last mod by wietse
# version 3, Wed Aug 23 18:00:00 2000, last mod by toddr
#
require 'perl/shell.pl';

#
# Add one probe to the new todo list.
#

sub add_todo {
local($host, $tool, $args) = @_;
local($record);

$record = "$host|$tool|$args";

if (!exists($old_todos{$record}) && !exists($new_todos{$record})) { 
	$new_todos{$record} = $record; 
	print "Add-todo: $record\n" if $debug;
	}
}

#
# Add one probe to the new todo list, ignore args when looking for dups.
#
sub add_todo_ignore_args {
local($host, $tool, $args) = @_;
local($key, $record);

$key = "$host|$tool";
$record = "$host|$tool|$args";

if (!exists($old_todos{$key})) { 
	$new_todos{$key} = $record; 
	print "Add-todo: $key ($args)\n" if $debug;
	}
}

#
# Iterate over the new todo list until nothing new shows up. Skip tools
# that we aren't supposed to run at this attack level.
#
sub process_todos {
local($key,%temp_todos,$allowed_tools);
local($target, $tool, $args, $level, $probe);
$processes = 0;
while (sizeof(*new_todos) > 0) {
	%temp_todos = %new_todos;
	%new_todos = ();
	for $key (keys %temp_todos) {
		($target, $tool, $args) = split(/\|/, $temp_todos{$key}, 3);
		next unless exists($all_hosts{$target});
		$level = $all_hosts{$target}{'attack'};
		next unless $level >= 0;
		for $probe (@{$all_attacks[$level]}) {
			if ($tool eq $probe || "$tool?" eq $probe || $probe eq "*?") {
				$old_todos{$key} = 1;
# 
# Work area for multi processing where
#   concurrent processes are defined in the command line or config/sara.cf
# 
                $concurrent_processes=$ENV{CONCURRENT_PROC};
                if ($concurrent_processes <=1) {
                   &set_host_time($target);
                   &run_next_todo($target, $tool, $args, "fact.$$");
                   &save_fact($$);
                } else {
                  $processes = $processes + 1;
                  while ($processes > $concurrent_processes) {
                    $pid = wait;
                    if ($pid > 0) {
                      $processes = $processes - 1;
                     &save_fact($pid);
                     }
                   }
                   &set_host_time($target);
                   if (($pid = fork()) == 0) {
                     &run_next_todo($target, $tool, $args, "fact.$$");
                     exit;
                    }
                }

                last;
              }
            }
         }
      }
      print "Waiting for all processes to complete\n" if $debug;
      while (($pid = wait) >= 1) {
         &save_fact($pid);
      }
}
#
# Save old todo list to file.
#
sub save_todos {
local($path) = @_;

open(TODO, ">$path") || die "cannot save old todo list to $path: $!";
for $key (keys %old_todos) {
	print TODO "$key\n";
	}
close(TODO);
}

#
# Reset todo tables and derivatives
#
sub clear_todos {
%new_todos = ();
%old_todos = ();
}

#
# Drop old entries on a specific host.
#
sub drop_old_todos {
	local($host) = @_;
	local($key, $target, $tool, $args);

	for $key (keys %old_todos) {
		($target, $tool, $args) = split(/\|/, $key);
		delete $old_todos{$key} if $target eq $host;
	}
}

#
# Read old todo list from file.
#
sub read_todos {
local($path) = @_;

&clear_todos();
&merge_todos($path);
}

#
# Merge old todo list with in-core table.
#
sub merge_todos {
local($path) = @_;

open(TODO, $path) || die "cannot read old todo list from $path: $!";
print "Reading old todo list from $path...\n" if $debug;
while (<TODO>) {
	chop;
	$old_todos{$_} = 1;
	}
close(TODO);
}

#
# Run a tool and collect its output.
#
sub run_next_todo
{
local($target, $tool, $args, $fact_file) = @_;
local($text, $ttl);
$ttl = (defined($timeouts{$tool}) ? $timeouts{$tool} : $timeout);

$command = "bin/$tool $args $target";
# Update host last access time.
#
# Damn the torpedoes!
die "Can't run $tool\n" unless &open_cmd(TOOL, $ttl, $command);
$cmd_status = $?;
open (FACT, "> $fact_file");
while (<TOOL>) {
  chomp;
  print FACT "$_\n";
}
close(TOOL);
close(FACT);

# Assume that we need to output an error record...
if ($cmd_status > 0) {
	# based on exit value, decide what happened:
	if ($cmd_status == $timeout_kill[0]) {$text = "program timed out";}
	elsif ($cmd_status == $timeout_kill[1]) {$text = "program timed out";}
        elsif ($cmd_status == $timeout_kill[2]) {$text = "program timed out";}
        elsif ($cmd_status == $timeout_kill[3]) {$text = "program timed out";}
	elsif ($cmd_status > 0 && $cmd_status < $timeout_kill) 
	{ $text = "internal program error $cmd_status";
		}
	else { $text = "unknown error #$cmd_status"; }
        open(FACT, ">> $fact_file");
	print FACT "$target|$tool|u|||||$text\n";
        close(FACT);
	}
return 1;
}
sub save_fact {
  local($pid) = @_;
   open (FACT, "fact.$pid");
   while (<FACT>) {
     chomp;
     &add_fact($_);
   }
   close(FACT);
   unlink("fact.$pid");
}
1;
