#  This software was derived from SATAN 1.1.1 by Dan Farmer and Wietse Venema
#  (http://www.porcupine.org/satan).
#
#
# get_proximity(target) returns proximity by hostname.
#
# new_target(target, proximity) figures out what (still) needs to be done.
#
# save_all_hosts(path) saves the all_hosts table to file.
#
# read_all_hosts(path) reads the all_hosts table from file.
#
# drop_all_hosts(host) forget this host.
#
# merge_all_hosts(path) merge with in-core tables.
#
# set_host_time(host) set host access time (UTC).
#
# get_host_time(host) get host access time (UTC).
#
# version 3, Mon Mar 20 19:43:59 1995, last mod by wietse
#

require 'perl/policy-engine.pl';
require 'perl/subnets.pl';
require 'perl/domains.pl';
require 'perl/get_host.pl';
require 'perl/shell.pl';
require 'perl/misc.pl';

$ping_timeout = 10 * $huge_timeout;
#
# Get proximity for this host.
#
sub get_proximity
{
	local($target) = @_;

	return 100 unless defined($all_hosts{$target});
	return $all_hosts{$target}{'proximity'};
}

#
# Set up a list of primary targets. Primaries are special. They may be
# expanded. After all the primaries are known we may have to do something
# special, like forgetting everything we know about them.
#
sub add_primary_target {
	local($target) = @_;

	# Do we expand or do we scan a host.
	if ($target =~ /^\d+\.\d+\.\d+$/ || $attack_proximate_subnets) {
		print "Add-primary: expand $target...\n" if $debug;
		&target_acquisition($target, 0, $attack_level);
	} else {
		print "Add-primary: $target\n" if $debug;
		&new_target($target, 0);
	}
}

#
# Forget everything we know about primary targets (well, almost everything).
# We must re-read the data base, or memory would be polluted with stale
# inferences.
#
sub fix_primary_targets {
	local(%temp_targets);

	if ($primaries_deleted) {
		print "Primaries being rescanned, rebuilding tables.\n" if $debug;
		%temp_targets = %new_targets;
		&save_sara_data();
		&read_sara_data();
		%new_targets = %temp_targets;
		$primaries_deleted = 0;
	}
}

#
# Take a new potential target and let it cool down. This saves lots of
# duplicate work.
#
sub new_target
{
	local($target, $proximity) = @_;

	return unless $target;

	# Make sure all current primaries are rescanned, not the hosts that
	# were primaries during some earlier scan.
	if ($proximity == 0) {
		$primaries_deleted = defined($all_hosts{$target});
		drop_old_todos($target);
		drop_old_facts($target);
	}

	# Toss circular paths.
	if (defined($all_hosts{$target})
	    && $all_hosts{$target}{'proximity'} < $proximity) {
		return;
	}
	if (exists($new_targets{$target}) 
	    && $new_targets{$target} <= $proximity) {
		return;
	}

	# Keep primary, keep shortest path.
	if ($proximity == 0 || !exists($new_targets{$target}) 
	    || !defined($all_hosts{$target}) 
	    || $all_hosts{$target}{'proximity'} > $proximity) {
		$new_targets{$target} = $proximity;
		print "Add-target: $target prox $proximity\n" if $debug;
	}
}

#
# Probe targets.
#
sub process_targets
{
local($target, $proximity, $level, %temp_targets, %probe_targets);

while(sizeof(*new_targets) > 0) {
	%temp_targets = %new_targets;
	%new_targets = ();
	%probe_targets = ();

	# Generate probes according to policy restrictions.
	for $target (keys %temp_targets) {
		$proximity = $temp_targets{$target};

		# Instantiate new host.
		if (!defined($all_hosts{$target})) {
			&add_new_host($target, $proximity);
			}

		# Proximity may have changed.
		if ($all_hosts{$target}{'proximity'} > $proximity) {
			$all_hosts{$target}{'proximity'} = $proximity;
			}

		# Skip attack and target expansion if this host is off-limits.
		if (($level = &policy_engine($target, $proximity)) < 0) {
			print "process_targets: skip $target...\n" if $debug;
			next;
			}

		# Attack level contents may have changed.
		$all_hosts{$target}{'attack'} = $level;
		$check_alive_list .= "$target "
			if !defined($host_is_alive{$target});
		$probe_targets{$target} = $level;
		}
	}

	# Parallelize liveness checks.
	&check_alive();

	# Generate the probes.
	for $target (keys %probe_targets) {
		print "process_targets: probe $target...\n" if $debug;
		&target_attack($target, $probe_targets{$target});
	}
}

#
# Add one target to the %all_hosts list. Set proximity, IP, preliminary
# attack level, preliminary expansion flag.
#
sub add_new_host
{
local($target, $proximity) = @_;
$all_hosts{$target}{'proximity'} = $proximity;
$all_hosts{$target}{'attack'} = -1;
$all_hosts{$target}{'expand'} = 0;
$subnet_flag = 0;
$domain_flag = 0;
}

#
# ping a list of hosts and find out what hosts are alive.
#
sub check_alive {
	local ($host);
	if ($check_alive_list eq "") {
		return;
	}
	print "Check-pulse: $check_alive_list\n" if $debug;

	#
	# Fping or bust.
	#
        %all_targets = ();
        for $host (split(/\s+/, $check_alive_list)) {
          $ip = &get_host_addr($host);
          $all_targets{$ip}{'NAME'}   = $host;
          $all_targets{$ip}{'FPING'}  = "";
          $all_targets{$ip}{'FWPING'} = "";
          $mac = &ip2mac($ip);
          if ($mac =~ /\:/) {
            $all_targets{$ip}{'MAC'}   = $mac;
          }
        }
        &open_cmd(FPING, $long_timeout, "$FPING $check_alive_list") 
                || die "cannot run $FPING: $!\n" if $check_alive_list;
        while(<FPING>) {
                ($host, $result) = /(\S+)\s+(.*)$/;
                if (/(\S+) is alive/) {
                  $ip = &get_host_addr($host);
                  $all_targets{$ip}{'NAME'}  = $host;
                  $all_targets{$ip}{'FPING'} = $result;
                } elsif ($debug && /(\S+) is unreachable/) {
                  print "Failed ping - $host\n";
                }
        }
        close(FPING);

        &open_cmd(FPING, $long_timeout, "$FWPING $check_alive_list")
                || die "cannot run $FWPING: $!\n"  if $check_alive_list;
        while(<FPING>) {
          ($host, $result) = /(\S+)\s+(.*)$/;
          if (/(\S+) is alive/) {
             $ip = &get_host_addr($host);
             $all_targets{$ip}{'FWPING'} = $result;
          } 
        }
        $check_alive_list = "";
        close(FPING);
        foreach $ip (sort keys %all_targets) {
           print "Failed fwping - $all_targets{$ip}{'NAME'}\n" if ((! $all_targets{$ip}{'FWPING'}) && $debug);
        }
        $check_alive_list = "";
        foreach $ip (keys %all_targets) {
          $name   = $all_targets{$ip}{'NAME'};
          $fping  = $all_targets{$ip}{'FPING'};
          $fwping = $all_targets{$ip}{'FWPING'};
          $status ="";
          $status = "alive" if ($fping && $fwping);
          $status = "fw-0" if (!$fping && $fwping);
          $status = "fw-1" if ($fping && !$fwping);
          $status = "fw-2" if ($fwping =~ /firewall/);
          $status = "fw-3" if (!$status && $mac =~ /[0-9A-F]+\:/i);
          $all_hosts{$name}{'IP'}  = $ip;
          $all_hosts{$name}{'MAC'} = $mac;
          $all_hosts{$name}{'FW'}  = $status;
          if ($status) {
            $host_is_alive{$name} = 1;
            $live_hosts++;
          }
        }
}

#
# target attack; assign attacks, throw into todo queue
#
sub target_attack
{
local($target, $level) = @_;

if ($host_is_alive{$target}) {
	print "Prox: $all_hosts{$target}{'proximity'}\n" if $debug;
	print "AL  : $all_hosts{$target}{'attack'}\n" if $debug;
	print "ALC : $all_hosts{$parent}{'attack'}\n" if $parent && $debug;
        $ip  = &get_host_addr($target);
        $mac = &ip2mac($ip);
        $mac = "" if ($mac =~ /-/);
        $all_hosts{$target}{'MAC'} = $mac;
        $firewall_flag = $all_hosts{$target}{'FW'};
        $_level = $level;
        $_level = 7 if ($level =~ /[23]/ && $firewall_flag =~ /fw-[123]/);
	for (@{$all_attacks[$_level]}) {
		&add_todo($target, $_, "") if ! /\?/;
		}
	}
}

#
# target acq; get subnets
#
sub target_acquisition
{
local($target, $proximity, $level) = @_;
local($targets_found);
# Expand and then collect. Pass results through new_target() for
# consistent handling of constraints and policies.
$ret = &open_cmd (TARGETS, $ping_timeout, "$GET_TARGETS $target");
while (<TARGETS>) {
	chomp;
        ($target,$ip, $mac, $fw) = split(/\|/);
        $all_hosts{$target}{'IP'}  = $ip;
        $all_hosts{$target}{'MAC'} = $mac;
        $all_hosts{$target}{'FW'}  = $fw;
	$all_hosts{$target}{'proximity'} = $proximity; 
	$all_hosts{$target}{'expand'} = 1;
        $all_hosts{$target}{'attack'} = -1;
	$host_is_alive{$target} = 1;
	$live_hosts++;
	&new_target($target, $proximity);
	$targets_found++;
	}
  close(TARGETS);
  if ($ret < 1 || $targets_found < 1) {
    print "$GET_TARGETS failed - unable to expand subnet $target\n"; 
    return 0;
  }
}

#
# Save the %all_hosts array
#
sub save_all_hosts {
local($all_hosts_file) = @_;
local($record, $host, $ip, $mac, $fw, $proximity, $level, $expand, $time);
local @buffer, $mymac;
open(MAC, "results/$sara_data/.mac_list");
@buffer = <MAC>;
close (MAC);

open(ALL_HOSTS, ">$all_hosts_file") ||
	die "Can't open $all_hosts_file (cache for all-hosts) for writing\n";

for $host (keys %all_hosts) {
	$ip = $all_hosts{$host}{'IP'};
	$mac = $all_hosts{$host}{'MAC'};
	$fw  = $all_hosts{$host}{'FW'};
	$proximity = $all_hosts{$host}{'proximity'};
	$level = $all_hosts{$host}{'attack'};
	$expand = $all_hosts{$host}{'expand'};
	$time = $all_hosts{$host}{'time'};
        for (@buffer) {
             if (/$host/) {
               ($dum, $mymac) = split(/\|/);
	       $mymac =~ s/\n//;
              if ( $mymac !~ /00\:00\:00\:00\:00/) {
                 $mac = $mymac;
	         $all_hosts{$host}{'MAC'} = $mac;
               }
	       last;
             }
        }
	$record = "$host|$ip|$mac|$fw|$proximity|$level|$expand|$time";
	print ALL_HOSTS "$record\n";
	for (@buffer) {
	   if (/$host/) {
	         ($dum, $mymac) = split(/\|/);
                 if ( $mymac !~ /00\:00\:00\:00\:00/) {
	           $mac = $mymac;
               }
	   }  
	}   
	# print "save_all_hosts: $record\n" if $debug;
	}
close(ALL_HOSTS);
 
}

#
# Reset host info and derivatives
#
sub clear_all_hosts {
	%all_hosts = ();
	%new_targets = ();
	&clear_subnet_info();
	&clear_domain_info();
}

#
# suck in the all-hosts array from file
#
sub read_all_hosts {
	local($all_hosts_file) = @_;

	&clear_all_hosts();
	&merge_all_hosts($all_hosts_file);
}

#
# merge the in-core all-hosts array with one from file
#
sub merge_all_hosts {
local($all_hosts_file) = @_;
local($count, $host, $ip, $mac, $fw, $proximity, $level, $time);

open(ALL_HOSTS, $all_hosts_file) ||
	die "Can't open $all_hosts_file (cache for all-hosts) for reading\n";
	print "Reading all hosts info from $all_hosts_file...\n" if $debug;

while (<ALL_HOSTS>) {
	chop;
	$count = ($host, $ip, $mac, $fw, $proximity, $level, $expand, $time) = split(/\|/);
	if ($count != 8) {
		warn "corrupted $all_hosts_file: $_\n";
		next;
		}
	if (!defined($all_hosts{$host}{'proximity'})
	    || $all_hosts{$host}{'proximity'} > $proximity) {
		$all_hosts{$host}{'IP'}  = $ip;
		$all_hosts{$host}{'MAC'} = $mac;
		$all_hosts{$host}{'FW'}  = $fw;
		$all_hosts{$host}{'proximity'} = $proximity;
		$all_hosts{$host}{'attack'} = $level;
		$all_hosts{$host}{'expand'} = $expand;
		$all_hosts{$host}{'time'} = $time;
		}
	}
close(ALL_HOSTS);
}

#
# set the last access time for this host. Use numerical form for easy sorting.
#
sub set_host_time {
	local($host) = @_;
        do_delays();
	$all_hosts{$host}{'time'} = time();
}

#
# get the last access time for this host. Use numerical form for easy sorting.
#
sub get_host_time {
	local($host) = @_;

	return $all_hosts{$host}{'time'};
}
1;
