#!/usr/bin/perl -Tw
# -T - Force "taint" checks to be turned on.
# -w -
Warn about anything which looks like bad code.

require 5.004;

package
KuangPlus;

### Begin user configurable options section

# directory in
which to find the other modules which are part of kuangplus,
# namely
engine.pl and safeops.pl. Also used as the directory in which the
#
"maxims" are expected if the "-m" option isn't used on the command
line.
$KuangPlus::kuang_home = "/home/jhoward/Kuangplus/new-work/wip";

###
End user configurable options section

###
### N.B. the order of
declaration is "use" first, followed in order by 
### global variables,
local "require" statements, command line argument
### processing,
subroutines declaration and then "MAIN" logic.

use Getopt::Long; # for
GetOptions function used for command line args parsing.
use strict;
use
Safe; # for compartmentalising maxims.

$ENV{PATH} = "/bin"; # Because
we're running in taint if for no other
reason.


push(@INC,$KuangPlus::kuang_home); # so we can find things under
taint and if we
                        # aren't being run from the
directory containing
                        # the supporting
utilities.
### load other modules
# the psuedo safe operations for maxims
to call.
require "$KuangPlus::kuang_home/safeops.pl";
# the "engine.pl"
file defines the "Engine" namespace.
require
"$KuangPlus::kuang_home/engine.pl";
### end of module loading

### usage
message
$KuangPlus::usage = "
usage: $0 [-d[=<level>]] [-v] [-h]
[-m=<pathname>]

$0 finds security concerns in the configuration of the
system it is run on.

    -h, --h, -help, --help 
	- Prints this message
and exits.

    -v, --v, -verbose, --verbose
	- verbose output. On finding
a successful exploit, any 
	information relevant to the exploit is
printed.

    -m=<dir>, --m=<dir>, -maximdir=<dir>, --maximdir=<dir>

- nominate the directory in which the \"maxims\" can be found. By

default this is the directory \"maxims\" in the same directory as

the \"kuangplus.pl\" script was invoked from.
        
    -d[=level],
--d[=level], -debug[=level], --debug[=level] 
	- Various debugging
information is available. The \"=level\" 
        argument indicates what
information is of interest. With no level
        specified, \"all\"
debugging information is printed. The following,
        \"-d=1\",
\"-d=.1\" and \"-d=1.1\", are all valid levels. The following 
        are
the attributes attached to the different levels:

	1 - main
.1 - file access
	2 - humanize()                          .2
- \@plan array access
	3 - addto()                             .3 -
miscellaneous messages
	4 - init_kuang()                        .5 -
\@known array access
	5 - evaluate_plans()                    .6 - rejected
rules
	6 - maxim files
	7 - command line argument handling
        8 -
print_success()
        9 - find_match()
        10 - safeops - stat()

11 - safeops - uname()
        12 - safeops - getpwent()
        13 -
safeops - getpwname()
        14 - safeops - getpwuid()
        15 -
safeops - general
        16 - rule_qc()
";
### end of usage message

###
initialise and set options, 
$KuangPlus::debug = ""; $KuangPlus::verbose =
""; $KuangPlus::help = "";
# Where to find maxims by
default
$KuangPlus::maxim_dir =
"$KuangPlus::kuang_home/maxims";

@KuangPlus::original_ARGV = @ARGV;
#
process command line arguments. @ARGV is cleaned out of all 
# arguments
unless the "--" is given to terminate command line 
# argument processing.
If no argument is given to an option 
# which can have an argument, such as
'-d', then 
# the corresponding variable, i.e. $KuangPlus::debug, is set to
0.
$KuangPlus::go_return = Getopt::Long::GetOptions(
		"debug|d:f",
\$KuangPlus::debug, 
		"verbose|v", \$KuangPlus::verbose, 
		"help|h",
\$KuangPlus::help,
                "maximdir|m=s",
\$KuangPlus::maxim_dir
		);

$KuangPlus::debug = "all" if (
$KuangPlus::debug eq "0" );
pdebug("Level $KuangPlus::debug debugging
turned on.", $KuangPlus::debug);
pdebug("Before processing, ARGV was
\"@KuangPlus::original_ARGV\"", 7.3);
pdebug("debug =
\"$KuangPlus::debug\", help = \"$KuangPlus::help\", verbose =
\"$KuangPlus::verbose\",", 7.3);
pdebug("GetOptions return is
\"$KuangPlus::go_return\", and ARGV is \"@ARGV\"", 7.3);

if ( "@ARGV" ne
"" || "$KuangPlus::help" eq "1" ) {
	print $KuangPlus::usage;
	exit
0;
}
### end of option handling

### define sub-routines
# humanize()
#
Re-presents a controlling operation in a human readable form i.e. an
#
operation like "w /etc/passwd" is re-presented as "overwrite file
#
/etc/passwd".
#
# Calls: KuangPlus::pdebug()
# Called by:
KuangPlus::print_success()
# Expects: a controlling operation i.e. an
operator object pair
# Returns: none.
# Side effects: causes message to be
printed to STDOUT.
# Global variables: none
# Private variables: $co_s,
$string, $leader, @co_a, $op, $ob, 
sub humanize {
    my($co_s) = $_[0];


my($string);
    my($leader) = "\t(verbose)";


pdebug("humanize($co_s)",2.3);

    my(@co_a) = split(' ',$co_s);
    while
( @co_a ) {
        my($op) = shift(@co_a); # change order & use "pop" to
get it printed in
        my($ob) = shift(@co_a); # event order rather than
backward chain.
        pdebug("humanize(), working with op: \"$op\" and
ob: \"$ob\"",2.3);
        if ( $op eq "u" ) {
            if ( $ob eq "-1"
) {
		$string .= "$leader you can get access to the system if\n";

} else {
                $string .= "$leader you can get access to userid
$ob if\n";
            }
        } elsif ( $op eq "g" ) {

$string .= "$leader you can get access to the gid $ob if\n";
        }
elsif ( $op eq "r" ) {
            $string .= "$leader you can replace file
$ob if\n";
        } elsif ( $op eq "w" ) {
            $string .= "$leader
you can overwrite file $ob if\n";
        } elsif ( $op eq "v" ) {

$string .= "$leader operating system is version $ob if\n";
        }
else {
            $string .= "$leader $op $ob (no translation)\n";
	}

}
    print "$string";
} # end of humanize()

# print_success() 
#
Prints out the successful "plan", breaking it up into rules and prior
#
knowledge.
# Calls: KuangPlus::pdebug(), KuangPlus::humanize()
# Called by:
main
# Expects: a string comprising a series of controlling operation
pairs.
# Returns: nothing
# Side Effects: prints to STDOUT
# External
references: $KuangPlus::verbose, %KuangPlus::plans,
#
%KuangPlus::known_facts
# Private variables: @elements, $offset, $index,
$leader, @co_a
sub print_success {
    my(@elements) = split('\s',$_[0]);


my($leader) = "\t";

    pdebug("print_success(@elements)",8.2);

print "\nSuccess: \"@elements\"\n";
    if ( $KuangPlus::verbose ) {

print "The verbose breakdown follows:\n";
        # The first co is the
goal, by definition
        print "$leader The goal is
\"@elements[0..1]\"\n";
        # The last co must be known or we couldn't
have got here. So take a copy
        # of it for later use

my($known) = $elements[$#elements - 1] . " " . $elements[$#elements];

# The rest we know were loaded in the plans array

my($offset,$index);
        my($co_s) .= shift(@elements)."
".shift(@elements);
        while ( @elements ) {
            $co_s .= "
".shift(@elements)." ".shift(@elements);
            pdebug("print_success:
Checking out how came \"$co_s\"",8.2);
            if (
defined($KuangPlus::plans{$co_s}) ) {
                print "$leader Plan:
\"$co_s\"\n$leader(".
                    $KuangPlus::plans{$co_s}.")\n";

humanize($co_s);
            } elsif ( my($match) =
find_regexp_plan($co_s) ) {
                print "$leader Regex Plan:
$match == $co_s\n$leader(" .

$KuangPlus::plans{$match}.")\n";
            } else {
                print
"Can't find \"$co_s\" in %plans ... this shouldn't happen.\n";

}
            $co_s =~ s/.*\s(\w+ [\w\/\.\*]+)/$1/;
        }
        print
"$leader Known:
\"$known\"\n$leader(".$KuangPlus::known_facts{$known}.")\n";
    }
} # end
of print_success()

# find_regexp_plan()
#       Given a controlling
operation pair, look for a regular expression
#       match in the
%KuangPlus::plans assoc. array.
# called by: print_success.
# expects: a co
pair
# returns: the plan match if found, 0 otherwise
# side effects: none
#
globals used: %KuangPlus::plans
sub find_regexp_plan {
    my($co_s) =
$_[0];

    foreach ( keys(%KuangPlus::plans) ) {

pdebug("find_regexp_plan: comparing \"$co_s\" and \"$_\"",9.3);
        if
( ( "$_" =~ "$co_s" ) || ( "$co_s" =~ "$_" ) ) {
            return $_;

}
    }
    return 0;
}

# pdebug()
# print debugging statemement if
necessary. Takes a string and a level as 
# arguments. The level should be
a real number. The whole part should 
# reflect the procedure the debugging
statement came from, the fractional 
# part should reflect what the
debugging statement refers to. If the
# debugging option is in place, and
the level matches the debugging level
# specified then this sub-routine
will cause the string to be printed. If
# the debugging option is only
specified with a "-d" and no level, then
# all debugging statements will be
printed.
#
# Calls: print()
# Called by: numerous procedures throughout
code.
# External variables used: $KuangPlus::debug
# Expects: A string.
#
Returns: None.
# Side effects: causes a string to be printed to STDOUT if
appropriate.
#
sub pdebug {
    my($string) = $_[0];
    my($level) =
$_[1];

    my($whole_level,$frac_level);

    # whole_level reflects where
the call came from i.e. procedure level
    ( $whole_level = $level ) =~
s/(\d+)\.\d+/$1/;
    # frac_level reflects what the call is doing. e.g.
manipulating the plan
    ( $frac_level = $level ) =~ s/\d+(\.\d+)/$1/;


    if ( $KuangPlus::debug eq "" ) {
        # check to see if debug is
set first, since this is the most 
	# likely case. Do nothing if it isn't.

} elsif ( $KuangPlus::debug eq $level || 
	    $KuangPlus::debug eq
$whole_level ||
	    $KuangPlus::debug eq $frac_level ||

$KuangPlus::debug eq "all" ) {
        print "debug($level): $string\n";

} else {
        # If the level of interest (in $KuangPlus::debug) doesn't

        # match the level of this message (in $level or its derivatives),

        # do nothing.
    }
    # If the level is '*' then always print
it, no matter what the 
    # debugging flag is set to.
    if ( "$level"
eq '*' ) {
        print "debug($level): $string\n";
    }
} # end of
pdebug()

# KuangPlus::rule_qc()
# Calls: 
# Called by:
# External
variables used:
# Expects: first arg is a string representing a plan.
Second arg is the
#    file from which it was loaded.
# Returns:
# Side
effects:
# Debug hooks used: major - 16.
sub KuangPlus::rule_qc {

my($new_plans_r) = $_[0];
    my($f_r) = $_[1];


my($plan,$rule,@els,$el0,$el1);

    foreach $rule ( keys(%$new_plans_r) )
{
        my($qc_failed) = 1;
        pdebug "rule_qc(): testing $rule
($$f_r) now ...", 16.3;
        (@els) = split('\s',$rule);
        if (
@els != 4 ) {
            KuangPlus::pdebug 
                "rule_qc():
ignoring incomplete rule:\n\t\"$rule\" ($$f_r)",
                16.6;

} else {
            while ( @els && $qc_failed ) {
                $el0
= shift(@els);
                $el1 = shift(@els);

KuangPlus::pdebug
                    "el0 is \"$el0\" and el1 is \"$el1\"
..",
                    16.3;
                if ( $el0 =~ /^[wr]$/ &&
$el1 =~ /^[\/\w-\.]+$/ ) {
                    # its a good w|r CO

if ( SafeOps::stat($el1) ) {

$KuangPlus::plans{$rule} = $$new_plans_r{$rule};

KuangPlus::pdebug "rule_qc(): Loading \"$rule\".", 16.2;

} else {
                        KuangPlus::pdebug 

"rule_qc(): \"$el1\" doesn't exist ($$f_r)", 

16.1;
                    }
                } elsif ( $el0 =~ /^[ug]$/
&& $el1 =~ /^(([\d-]+)|(\.\*))$/ ) {
                    # its a good u|g
C0
                    $KuangPlus::plans{$rule} = $$new_plans_r{$rule};

KuangPlus::pdebug "rule_qc(): Loading \"$rule\".", 16.2;
                }
elsif ( $el0 =~ /^v$/ && $el1 =~ /^[\w-\.]+/ ) {
                    # its
a good v C0
                    $KuangPlus::plans{$rule} =
$$new_plans_r{$rule};
                    KuangPlus::pdebug "rule_qc():
Loading \"$rule\".", 16.2;
                } else {

KuangPlus::pdebug 
                        "rule_qc(): invalid
rule:\n\t\"$rule\" ($$f_r)",
                        16.6; 

$qc_failed = 0;
                } # if
            } # while
        }
# if
    } # foreach 
} # end of KuangPlus::rule_qc()

### end of
sub-routine definitions.

####
## MAIN logic follows 
####

# Define what
we know:
# Generally known things -
$KuangPlus::known_facts{"u -1"} =
"Anybody has access.";
# Things about the platform we are running on
derived from uname() call
@KuangPlus::uname =
KuangPlus::uname();
$KuangPlus::known_facts{"v
$KuangPlus::uname[0]-$KuangPlus::uname[2]"} = 
    "From POSIX::uname()
call.";

if ( $KuangPlus::debug =~ /\.5/ ) {
    pdebug "Generating
\"known\" file for reference ...", 1.5;
    open(K,">known") or die "Can't
open file to dump known facts in";
    print K "# file generated at
".`date`."#\n";
    print K "#\n# These are the facts are prior
knowledge.\n#\n";
    print K "# uname:\n# @KuangPlus::uname\n#\n";

my($fact);
    foreach $fact (keys(%KuangPlus::known_facts)) {
	pdebug("I
know this: $fact($KuangPlus::known_facts{$fact})", 1.5);
	print K "$fact
($KuangPlus::known_facts{$fact})\n";
    }
    close(K);
}

pdebug "Ready
to load rules, maxim_dir is \"$KuangPlus::maxim_dir\"",
6.1;

opendir(Rules,"$KuangPlus::maxim_dir") or die "Can't open maxims
subdirectory";
my(@rule_files) = readdir(Rules);
closedir(Rules);

foreach
(@rule_files) {
    next if ( /(\.|\.\.)/ ); # skip current and parent
directories

    # The following line "untaints" the string we want to
require later. We
    # use the result of the search pattern as $1 in
setting $rule_file.
    # "\w" matches with a "Word character"
([a-zA-Z_0_9]).
    if ( /^(\w+)$/ ) {
        my($rule_file) =
"$KuangPlus::maxim_dir/$1";
        if ( -f $rule_file ) {
            #
variables shar'd to safe compartments
            %KuangPlus::new_plans =
();
            pdebug "loading \"$rule_file\" to generate plans ...",1.1;

$KuangPlus::cmpt = new Safe;

$KuangPlus::cmpt->share_from('KuangPlus',
                ['&pdebug',
'%new_plans']);

            # This little bit of code automatically loads
every sub-routine
            # loaded in the "SafeOps::" namespace.

my($routine,$fullname);
            foreach $routine
(keys(%SafeOps::)) {
                $fullname = "SafeOps::$routine";

if ( defined(&$fullname) ) {
                    pdebug "It
appears I should load \"\&$fullname\"\n", 15.3;

$KuangPlus::cmpt->share_from('SafeOps',['&'."$routine"]);

}
            }
                
            # This is how I first tried to
use load routines from SafeOps::
            #
$KuangPlus::cmpt->share_from('SafeOps', 
            #    ['&uname',
'&stat', '&getpwent', '&getpwnam', '&getpwuid']);


$KuangPlus::cmpt->rdo($rule_file);
            if ( "$@" ne "" ) {

KuangPlus::pdebug "Problem with rule \"$rule_file\": $@", '*';

} else {

KuangPlus::rule_qc(\%KuangPlus::new_plans,\$rule_file);
            }

KuangPlus::pdebug "finished loading \"$rule_file\".",1.1;

undef $KuangPlus::cmpt;
        } else {
            print STDERR "Skipping
irregular rule file \"$rule_file\"\n";
        }
    } else {
        print
STDERR "Bodgy file name \"$_\" skipped\n";
    }
}

pdebug "Prior to
invoking rule-engine, plans are: ".keys(%KuangPlus::plans),1.2;

if (
$KuangPlus::debug =~ /.2/ ) {
    pdebug "Generating \"plans\" file for
reference ...", 1.2;
    open(P,">plans") || die "Can't open plan file for
debugging dump of plans";
    print P "# file generated at
".`/bin/date`."#\n";
    print P "# Raw plans which will be chewed over by
the rule engine\n#\n";
    print P "# uname:\n# @KuangPlus::uname\n#\n";

my($i) = 0;
    my($p);
    foreach $p ( keys(%KuangPlus::plans) ) {

print P "$p\n";
        $i++;
    }
    print P "#\n# Summary: $i plans
in total.\n#\n";
    close(P);
}


# All thats left is to invoke
Engine::evaluate_plans to chew over the 
# "%plans" associative
array.
@KuangPlus::successful_exploits =
Engine::evaluate_plans(\%KuangPlus::plans);

if (
$#KuangPlus::successful_exploits == -1 ) {
    print "\nThere were no
exploits detected.\n\n";
} else {
    KuangPlus::pdebug
"Successful_exploits are ".

"\"@KuangPlus::successful_exploits\"\n", 3.0;
    my($exploit);
    foreach
$exploit ( @KuangPlus::successful_exploits ) {

print_success($exploit);
        print "\n";
    }
}

exit 0;

