# perl/todo.pl
# add_todo($record) add one record to the new todo list.
# save_todos($path) saves the old todos to the named file.
# drop_old_todos($host) forget everything we know about a host.
# merge_todos($path) merge with in-core tables.
# version 2, Mon Mar 20 19:47:07 1995, last mod by wietse
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, $threads, $pid, $child);

while (sizeof(*new_todos) > 0) {
	%temp_todos = %new_todos;
	%new_todos = ();
	$threads = 0;
	%children = ();
	for $key (keys %temp_todos) {
		($target, $tool, $args) = split(/\|/, $temp_todos{$key}, 3);
		if ($target =~ /^[\d\.]+\/$/) {
		    # target is a network address
		    next unless $attack_proximate_subnets;
		} else {
		    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;

			    # if not multitasking, just run the todo
			    if ($maximum_threads <= 1) {
				# Update host last access time.
				&set_host_time($target);
				&run_next_todo($target, $tool, $args);
			    } else {
				while ($threads >= $maximum_threads) {

				    # wait for a thread to terminate
				    $pid = wait;
				    if ($children{$pid} > 0) {
					$threads--;
					$children{$pid} = 0;
				    }
				}

				# fork a new thread for this todo
				&set_host_time($target);
				if (($pid = fork()) == 0) {
				    &run_next_todo($target, $tool, $args);
				    exit;
				}
				$threads++;
				$children{$pid} = 1;
			    }
			    last;
			}
		}
	}
	# wait for the remaining threads to finish
	while ($threads > 0) {
		$pid = wait;
		if ($children{$pid} > 0) {
		    $threads--;
		    $children{$pid} = 0;
		}
	}

	# harvest the output files from all the threads (if multitasking)
	if ($maximum_threads > 1) {
	    for $child (keys %children) {
		if (open (TEMPFACTS, "facts.$child")) {
			while (<TEMPFACTS>) {
			    # if the program starved, give it
			    # another chance, running alone */
			    if (/^(.+)\|(.+)\|u\|\|\|\|\|program did not run \((.*)\)$/) {
				&set_host_time($1);
				&run_next_todo($1,$2,$3,1);
			    } else {
				chop;
				&add_fact($_);
			    }
			}
			close(TEMPFACTS);
			unlink "facts.$child";
		}
	    }
	}
}
}

#
# 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, $network);

	for $key (keys %old_todos) {
		($target, $tool, $args) = split(/\|/, $key);
		delete $old_todos{$key} if $target eq $host;
		# delete associated network address
		# (s.k. 8/00)
		$network = &get_network($host) . "/";
		delete $old_todos{$key} if $target eq $network;
	}
}

#
# 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, $alone) = @_;
local($text, $ttl);

if ($tool =~ /^([^\. ]+)/) { $timeout_var = $1 . "_timeout"; }
$timeout_var =~ s/-/_/;
@all_timeouts = ($short_timeout, $med_timeout, $long_timeout);
$ttl = (defined(${$timeout_var}) ? ${$timeout_var} : $all_timeouts[$timeout]);

$command = "bin/$tool $args $target";
$fflag="";
if ($firewall_flag) {
   $fflag="FW";
   if ($tool =~ /tcpscan.saint/) {
         $ttl=$fw_tcp_scan;
   }
}
if ($attack_level =~ "3") {
   $fflag="$fflag.PLUS";
}
$command="$command $fflag";


# Damn the torpedoes!
die "Can't run $tool\n" unless &open_cmd(TOOL, $ttl, $command);

# if not multitasking, just add each facts from tool
if ($maximum_threads <= 1 || $alone) {
    $begun = 1;
    while (<TOOL>) {
	chop;
	&add_fact($_) unless /^BEGIN$/;
	}
}

# if multitasking, create an output file from each tool
else {
    $begun = 0;
    open(TEMPFACTS, "> facts.$$");
    while (<TOOL>) {
	if (/^BEGIN$/) {
	    $begun = 1;
	} else {
	    print TEMPFACTS;
	}
    }
    close (TEMPFACTS);
}

close(TOOL);

# Did we fly like the mighty turkey or soar like an...?
# If the former, assume that we need to output an error record...
if ($?) {
	# based on exit value, decide what happened:
	if ($? == $timeout_kill) {
	    if ($begun) {
		$text = "program timed out";
	    } else {
		$text = "program did not run ($args)";
	    }
	}
	elsif ($? > 0 && $? < $timeout_kill) {
		$text = "internal program error $?";
	}
	else { $text = "unknown error #$?"; }
 
	if ($maximum_threads <= 1 || $alone) {
		&add_fact("$target|$tool|u|||||$text");
	} else {
		open(TEMPFACTS, ">> facts.$$");
		print TEMPFACTS "$target|$tool|u|||||$text\n";
		close(TEMPFACTS);
	}

}
}

1;
