#!/usr/local/bin/perl

# Auto hacker script. You specify a host and the script will follow
# login/nis/nfs relationships in order to locate possible points of
# entrance.  By default, it operates on pre-collected host information.
# Specify the -u option to collect data on hosts that haven't been
# examined yet.

do 'yagrip.pl' ||
  die "can't do yagrip.pl";

$options = "bd:k:lt:u:v";
$usage = "usage: attack [-b] [-d datadir] [-k host] [-l] [-t timeout] [-u depth] [-v] [user@]host\
    -b		break root if we get stuck\
    -d dir	where host info is kept\
    -k host	assume host is compromised\
    -l		display undecided paths\
    -t time	set timeout for real-time lookups\
    -u depth	probe new hosts in real time, limit plans to <depth> hosts\
    -v		verbose mode\n";

#
# Defaults for the -d and -u options. By default, the host data is kept
# in files named hosts/fully.qualified.host.name, and only plans up to
# three levels deep are considered when hosts are being probed in real
# time.
#
$data_dir = "rip_data";
$depth_limit = 3;

#
# addto - add plan to list
#
sub addto {
    local($host, $privs, $plan) = @_;

    #
    # If this host is in the list of known hosts we have found a path to
    # break in.
    #
    if (defined($hosts_known{$host})) {
	print "Plan:\n$plan\n\n";
	return;
    }
    $key = "$privs@$host";
    #
    # Check to see if it's a duplicate - if so, don't need to do anything.
    #
    if (defined($targets_pending{$key})) {
	return;
    }
    #
    # Add to pending list 
    #
    $targets_pending{$key} = $plan;

    #
    # Add to next plan list
    #
    $targets_new{$key} = $plan;

    if (defined($opt_v)) {
	printf "addto: $key --> $plan\n";
    }
}

#
# Initializations...
#
sub init_attack {

    %targets_new = ();

    #
    # Deal with args...
    #

    (&getopt($options) && ($#ARGV == 0)) ||
      die $usage;

    if ($ARGV[0] =~ /@/) {
	($privs, $host) = split(/@/, $ARGV[0]);
    } else {
	$privs = 2;
	$host = $ARGV[0];
    }
    &addto($host, $privs, "grant $privs@$host.");

    if (defined($opt_d)) {
	$data_dir = $opt_d;
    }
    #
    # Limit search when probing hosts in real time.
    #
    if (defined($opt_u)) {
	$depth_limit = $opt_u;
    } else {
	$depth_limit = -1;
    }
    if (defined($opt_k)) {
	$hosts_known{$opt_k} = 1;
    }
    if (defined($opt_t)) {
	$rip_flags="$rip_flags -t $opt_t";
    }

    #
    # All per-host data is kept below a separate directory.
    #
    foreach $path ("", "/hosts", "/grant") {
	if (! -d "$data_dir$path" && !mkdir("$data_dir$path", 0755)) {
	    die "Unable to initialize the $data_dir$path directory. Giving up."
	}
    }
    #
    # Set up the initial known goal. When some plan is accessible for 
    # known host we have found a path to break in.
    #
    $hosts_known{"anyhost"} = 1;
}

#
#----------------------------------------------------------------------
#Main program follows...initialize and loop till we're done.
#

&init_attack();
$depth = 0;

#
# While there's still something to pursue...
#
while($depth != $depth_limit && &sizeof(*targets_new) != 0) {
    %targets_old = %targets_new;
    %targets_new = ();

    foreach $target (keys %targets_old) {
	$plan = $targets_old{$target};
	($privs, $host) = split(/@/, $target);
	if (defined($opt_v)) {
	    printf "eval[$depth]: $privs@$host `$plan'\n";
	}
	$host_data = "$data_dir/hosts/$host";
	if (! -f $host_data && defined($opt_u)) {
	    system("./rip_host $rip_flags $host >>$host_data");
	}
	if (! -s $host_data) {
	    if (defined($opt_l)) {
		print "Undecided:\n$plan\n\n";
	    }
	} else {
	    $grant_data = "$data_dir/grant_dst/$host";
	    if (-s $grant_data) {
		open(RIP_DATA, $grant_data);
	    loop:
		while (<RIP_DATA>) {
		    chop;
		    ($post_privs, $pre_host, $pre_privs, $why) = split(/[|]/);
		    #
		    # Avoid visiting the same host more than once.
		    #
		    if (index($plan, $pre_host) != -1) {
			next loop;
		    }
		    #
		    # For the time being, ignore resricted NFS exports.
		    #
		    if ($why =~ /exports/ && $pre_host ne "anyhost") {
			next loop;
		    }
		    #
		    # Get more specific (2->username) if we have to.
		    #
		    if ($privs !~ /[0-9]/ && $post_privs == 2) {
			$post_privs = $privs;
			if ($pre_privs == 2 && $pre_host != "anyhost") {
			    $pre_privs = $privs;
			}
		    }
		    #
		    # Pursue this rule if it gives access to $privs@$host.
		    # Otherwise, if we have permission to break root, try
		    # that, but not too often.
		    #
		    if (($privs == 2)			# No privs needed
		    || ($privs == 1 && $post_privs eq 0)	# More privilege
		    || ($privs eq $post_privs)) {		# Same privilege
			&addto($pre_host, $pre_privs, 
			    "grant $pre_privs@$pre_host ($why).\n$plan");
		    } elsif (defined($opt_b) 
			&& $privs == 0 && index($plan, "FORCE") == -1) {
			$targets_pending{"$post_privs@$host"} = "occupied";
			$t_plan = "grant $post_privs@$host FORCE $privs.\n$plan";
			&addto($pre_host, $pre_privs,
			    "grant $pre_privs@$pre_host ($why).\n$t_plan");
		    }
		}
		close(RIP_DATA);
	    }
	}
    }
    $depth++;
}

sub sizeof {
    local(*which) = @_;
    local(@keywords);

    @keywords = keys %which;
    return($#keywords + 1);
}
