[alt.sources] alpha perl-cops, 3 of 3

df@sei.cmu.edu (Dan Farmer) (05/17/91)

Submitted-by: df@death.cert.sei.cmu.edu
Archive-name: alpha p-cops/part03

#!/bin/sh
# this is apcops.03 (part 3 of alpha p-cops)
# do not concatenate these parts, unpack them in order with /bin/sh
# file p-cops.alpha/user.chk continued
#
if test ! -r _shar_seq_.tmp; then
	echo 'Please unpack part 1 first!'
	exit 1
fi
(read Scheck
 if test "$Scheck" != 3; then
	echo Please unpack part "$Scheck" next!
	exit 1
 else
	exit 0
 fi
) < _shar_seq_.tmp || exit 1
if test ! -f _shar_wnt_.tmp; then
	echo 'x - still skipping p-cops.alpha/user.chk'
else
echo 'x - continuing file p-cops.alpha/user.chk'
sed 's/^X//' << 'SHAR_EOF' >> 'p-cops.alpha/user.chk' &&
# even if invoked as an sh or csh or foosh script.
# notice we don't use full path cause we don't
# know where the user has perl on their system.
#
eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' 
& eval 'exec perl -S $0 $argv:q'
X    if $running_under_some_stupid_shell_instead_of_perl;
X
#
# check for writable files in all user's homes
#
require "pass.cache.pl";
require "is_able.pl";
X
# files checked for:
@ftable = ("rhosts", "profile", "login", "logout", "cshrc",
X	   "bashrc", "kshrc", "tcshrc", "netrc", "forward", "dbxinit",
X	   "distfile", "exrc", "emacsrc", "remote", "mh_profile",
X	   "xinitrc", "xsession", "Xdefaults", "Xresources", "rninit");
X
@readables = ("netrc", "rhosts");
X
local(%done);
X
for $i (keys %uname2dir) {
X    $dir = $uname2dir{$i};
X    #   I don't want to hear about every file in their home dir, if 
X    # is WW, but still need to check the .netrc file for readability...
X    next if $done{$dir}++;
X    if (-e $dir) { 
X        if (&is_able($dir, "w", "w")) {
X            for $r (@readables) {
X                if (-s "$dir/.$r") {
X                    &is_able("$dir/.$r", "w", "r");
X		}
X	    }
X            next;
X	}
X        for $file (@ftable) {
X            $foo_file = $dir . "/.$file";
X            if (-e $foo_file) {
X                &is_able($foo_file, "w", "w");
X                for $r (@readables) {
X                    if ($file eq $r && -s $foo_file) {
X                        &is_able($foo_file, "w", "r");
X		    }
X		}
X	    }
X	}
X    }
}
X
1;
SHAR_EOF
echo 'File p-cops.alpha/user.chk is complete' &&
chmod 0700 p-cops.alpha/user.chk ||
echo 'restore of p-cops.alpha/user.chk failed'
Wc_c="`wc -c < 'p-cops.alpha/user.chk'`"
test 2048 -eq "$Wc_c" ||
	echo 'p-cops.alpha/user.chk: original size 2048, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= p-cops.alpha/stat.pl ==============
if test -f 'p-cops.alpha/stat.pl' -a X"$1" != X"-c"; then
	echo 'x - skipping p-cops.alpha/stat.pl (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting p-cops.alpha/stat.pl (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/stat.pl' &&
;# $Header: stat.pl,v 3.0.1.1 90/08/09 04:01:34 lwall Locked $
;# Usage:
;#	require 'stat.pl';
;#	@ary = stat(foo);
;#	$st_dev = @ary[$ST_DEV];
;#
$ST_DEV =	0 + $[;
$ST_INO =	1 + $[;
$ST_MODE =	2 + $[;
$ST_NLINK =	3 + $[;
$ST_UID =	4 + $[;
$ST_GID =	5 + $[;
$ST_RDEV =	6 + $[;
$ST_SIZE =	7 + $[;
$ST_ATIME =	8 + $[;
$ST_MTIME =	9 + $[;
$ST_CTIME =	10 + $[;
$ST_BLKSIZE =	11 + $[;
$ST_BLOCKS =	12 + $[;
X
;# Usage:
;#	require 'stat.pl';
;#	do Stat('foo');		# sets st_* as a side effect
;#
sub Stat {
X    ($st_dev,$st_ino,$st_mode,$st_nlink,$st_uid,$st_gid,$st_rdev,$st_size,
X	$st_atime,$st_mtime,$st_ctime,$st_blksize,$st_blocks) = stat(shift(@_));
}
X
1;
SHAR_EOF
chmod 0700 p-cops.alpha/stat.pl ||
echo 'restore of p-cops.alpha/stat.pl failed'
Wc_c="`wc -c < 'p-cops.alpha/stat.pl'`"
test 653 -eq "$Wc_c" ||
	echo 'p-cops.alpha/stat.pl: original size 653, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= p-cops.alpha/suckline.pl ==============
if test -f 'p-cops.alpha/suckline.pl' -a X"$1" != X"-c"; then
	echo 'x - skipping p-cops.alpha/suckline.pl (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting p-cops.alpha/suckline.pl (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/suckline.pl' &&
#
#  As title implies... :-)
#
sub main'suckline {
X    local($file, $_) = @_;
#   local($package) = caller;
X
#   $file =~ s/^([^']+)$/$package'$1/; 
X    {
X	if (s/\\\n?$//) {
X	    $_ .= <$file>;
X	    redo;
X	}
X    } 
X    $_;
}
X
1;
SHAR_EOF
chmod 0700 p-cops.alpha/suckline.pl ||
echo 'restore of p-cops.alpha/suckline.pl failed'
Wc_c="`wc -c < 'p-cops.alpha/suckline.pl'`"
test 229 -eq "$Wc_c" ||
	echo 'p-cops.alpha/suckline.pl: original size 229, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= p-cops.alpha/suid.chk ==============
if test -f 'p-cops.alpha/suid.chk' -a X"$1" != X"-c"; then
	echo 'x - skipping p-cops.alpha/suid.chk (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting p-cops.alpha/suid.chk (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/suid.chk' &&
#!/bin/sh  # need to mention perl here to avoid recursion
#
#  Usage: suid.chk [-n] [-s secure_dir] [search_starting_directory]
#
#   Shell script intended to be run periodically by cron in order
#   to spot changes in files with the suid or sgid bits set.
#
#	suid.chk	840919		Prentiss Riddle
#
#     This changes into the $SECURE directory first, then 
#   uses find(1) to search the directories in $SEARCH for all
#   files with the 4000 or 2000 permission bits set.  $STOP is a file
#   containing "ls -gildsa" output for known setuid or setgid programs.
#   Any additions or changes to this list represent potential security
#   problems, so they are reported.
#
#  Modified 8/15/89, Dan Farmer:
#	Just changed the program/doc names and some of the temp
#  files to make it fit in with the rest of the programs....
#  Modified 12/26/90, df
#       Now flags SUID shell scripts and world writeable SUID files, too.
#
#  Rewritten in perl, 1/17/91, df
#  Major hacks by tchrist 5/14/91
#
# NOTE:
#   If you know where perl is and your system groks #!, put its
# pathname at the top to make this a tad faster.
#
# the following magic is from the perl man page
# and should work to get us to run with perl 
# even if invoked as an sh or csh or foosh script.
# notice we don't use full path cause we don't
# know where the user has perl on their system.
#
eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' 
& eval 'exec perl -S $0 $argv:q'
X    if $running_under_some_stupid_shell_instead_of_perl;
X
require "hostname.pl";
require "is_able.pl";
require "file_owner.pl";
require "pathconf.pl";
require "chk_strings.pl";
require "pass.cache.pl";
package suid_chk; # name space protection
$debug=0;
X
#
# Getopts stuff
$usage = "Usage: $0 [-n] [-s secure_dir] [starting_directory]\n";
require 'getopts.pl';
# Process the command args; Either specify verbose or an alternate config file:
die $usage unless &Getopts('ns:');
X
$suid_dir = $'SECURE || '.';
if (defined($opt_s)) { $suid_dir = $opt_s; }
X
# Do NFS stuff?  Yes unless opt:
if (defined($opt_n)) { $skip_nfs = $opt_n; }
else { $skip_nfs = 0; }
X
$STOP="$suid_dir/suid.stop";
$TEMPOLD="$suid_dir/fsold$$";
$TEMPCUR="$suid_dir/fscur$$";
$TEMPNEW="$suid_dir/fsnew$$";
$TEMPGON="$suid_dir/fsgon$$";
$TEMPM="$suid_dir/fsm$$";
X
if (@ARGV > 1) { die $usage; }
elsif (@ARGV == 1) { $start_dir = shift; }
X
# these may be terribly rash assumptions....
$start_dir="/" unless defined $start_dir;
$find_can_ls = 1 unless defined $find_can_ls;
X
$NONFS = '\( -type f -fstype nfs -prune \) -o' if $skip_nfs;  
$find_ls = $find_can_ls ? '-ls' : "-exec $'LS -gilds {} \\;";
X
die "Error -- Security directory $suid_dir doesn't exist\n" unless -d $suid_dir;
unless (-d $suid_dir) {
X	mkdir($suid_dir, 0700) || die "can't mkdir $suid_dir: $!";
X	} 
chdir  $suid_dir || die "can't chdir $suid_dir: $!";
X
# find the setuid programs and sort
&run("$'FIND $start_dir $NONFS -type f \\( -perm -4000 -o -perm -2000 \\) $find_ls | $'SORT > $TEMPCUR");
X
# compare with the sorted stop list
# create stop file if needed
if (! -f $STOP) { open(S,">$STOP"); close(S); }
X
&run("$'SORT <$STOP >$TEMPOLD");
&run("$'COMM -13 $TEMPOLD $TEMPCUR | $'SORT +8 >$TEMPNEW");
&run("$'COMM -23 $TEMPOLD $TEMPCUR | $'SORT +8 >$TEMPGON");
X
local($is_able'silent) = 1;
local($chk_strings'recurse) = 0 unless defined $chk_strings'recurse;
X
# report changes
if (-s $TEMPNEW || -s $TEMPGON) {
X    if (-s $TEMPNEW) {
X	die "Can't open $TEMPNEW: $!" unless open TEMPNEW;
X	while (<TEMPNEW>) {
X	    ($file) = /(\S+)$/;
X
X	    # don't want SUID files to be world writable!
X	    # although *reasonable* systems clear the bit on write
X	    print "Warning!  SUID file $file is _World_ writable!\n" 
X		if &'is_able ($file, "w", "w"); 
X		    
X	    if (-r $file && -f _ && -T $file) {
X		print "Warning!  ", &'Owner($file) ? '' : 'ROOT-owned ', 
X		    "SUID file $file is a non-binary, executable file!\n";
X	    }
X
X	    &'chk_strings($file) if -r _;
X	}
X	close TEMPNEW;
X    }
X
X    if (-s $TEMPNEW) {
X	die "Can't reopen $TEMPNEW: $!" unless open TEMPNEW;
X	print "\nThese files are newly setuid/setgid:\n\n";
X	print while <TEMPNEW>;
X    }
X
X    if (-s $TEMPGON) {
X	die "Can't reopen $TEMPGON: $!" unless open TEMPGON;
X	print "\nThese files are no longer setuid/setgid:\n\n";
X	print while <TEMPGON>;
X    }
X
}
X
unlink $TEMPOLD, $TEMPCUR, $TEMPNEW, $TEMPGON;
X
sub run {
X    print "running: $_[0]\n" if $debug;
X    system $_[0];
X    warn "command $_[0] returned $?" if $?;
} 
X
#  end it all....
X
1;
SHAR_EOF
chmod 0700 p-cops.alpha/suid.chk ||
echo 'restore of p-cops.alpha/suid.chk failed'
Wc_c="`wc -c < 'p-cops.alpha/suid.chk'`"
test 4477 -eq "$Wc_c" ||
	echo 'p-cops.alpha/suid.chk: original size 4477, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= p-cops.alpha/root.chk ==============
if test -f 'p-cops.alpha/root.chk' -a X"$1" != X"-c"; then
	echo 'x - skipping p-cops.alpha/root.chk (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting p-cops.alpha/root.chk (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/root.chk' &&
#!/bin/sh  # need to mention perl here to avoid recursion
#
#  Usage: root.chk
#
#  This script checks pathnames inside root's startup files for 
# writability, improper umask settings (world writable), non-root
# entries in /.rhosts, writable binaries in root's path,
# and to ensure that root is in /etc/ftpuser.
#
# Also check for a single "+" in /etc/hosts.equiv (world is trusted),
# and that /bin, /etc and certain key files are root owned, so that you
# can't, say, rcp from a host.equived machine and blow over the password
# file... this may or may not be bad, decide for yourself.
# Startup files are /.login /.cshrc /.profile
#
#  Mechanism:  These files contain paths and filenames that are stripped
# out using "grep".  These strings are then processed by the "is_able"
# program to see if they are world writable.  Strings of the form:
#
#	path=(/bin /usr/bin .)
#		and
#	PATH=/bin:/usr/bin:.:
#
# are checked  to ensure that "." is not in the path.  All
# results are echoed to standard output.  In addition, some effort was
# put into parsing out paths with multiple lines; e.g. ending in "\",
# and continuing on the next line.  Also, all executable files and 
# directories in there are checked for writability as well.
#
#  For umask stuff, simply grep for umask in startup files, and check
# umask value.  For /etc/ftpuser, simple grep to check if root is in
# the file.  For /etc/hosts.equiv, just check to see if "+" is alone
# on a line by awking it.
#
#  NOTE:
# if you know where perl is and your system groks #!, put its
# pathname at the top to make this a tad faster.
#
# the following magic is from the perl man page
# and should work to get us to run with perl 
# even if invoked as an sh or csh or foosh script.
# notice we don't use full path cause we don't
# know where the user has perl on their system.
#
eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' 
& eval 'exec perl -S $0 $argv:q'
X    if $running_under_some_stupid_shell_instead_of_perl;
X
# rewritten in perl by tchrist@convex.com
# 
X
# root startup/important files
X
require 'file_owner.pl';
require 'fgrep.pl';
require 'suckline.pl';
require 'is_able.pl';
require 'chk_strings.pl';
require 'glob.pl';
X
package root_chk;
X
# use -a true if you care about non-executables
# in root's path
X
$ARGV[0] eq '-a' && ($all_files++, shift);
X
die "usage: root.chk [-a]\n" if @ARGV;
X
$W = 'Warning! ';
X
$cshrc	= '/.cshrc';
$profile= '/.profile';
$rhosts = '/.rhosts';
X
$| = 1;
X
@big_files= ('/.login', '/.cshrc', '/.profile', '/.logout' );
X
# root should own *at least* these, + $big_files; you can check for all files
# in /bin & /etc, or just the directories (the default.)
# root_files="/bin /bin/* /etc /etc/* $big_files $rhosts"
@root_files= ('/bin','/etc',@big_files,$rhosts,'/etc/passwd','/etc/group');
X
# misc important stuff
$ftp='/etc/ftpusers';
$equiv='/etc/hosts.equiv';
X
#   should't have anyone but root owning /bin or /etc files/directories
# In case some of the critical files don't exist (/.rhost), toss away error
# messages
X
if (@bad_files = grep (-e && &'Owner($_), @root_files)) {
X    print "$W  Root does not own the following file(s):\n";
X    print "\t@bad_files\n";
} 
X
local($chk_strings'recurse) = 1 unless defined $chk_strings'recurse;
X
for $file (@big_files) {
X    open file || next;
X
X    &'chk_strings($file);
X
X    # check for group or other writable umask
X    while (<file>) {
X	next if /^\s*#/;
X	next unless /umask\s*(\d+)/;
X	next unless ~oct($1) & 022;
X	print "$W root's umask set to $1 in $file\n";
X    } 
} 
X
print "$W $ftp exists and root is not in it\n" 
X    if -e $ftp && !&'fgrep($ftp,'root');
X
print "$W A \"+\" entry exists in $equiv!\n" if &'fgrep($equiv, '^\+$');
X
if (open rhosts) {
X    while (<rhosts>) {
X	next unless /\S+\s+(\S+)/ && $1 ne 'root';
X	print "$W Non-root entry in $rhosts! $1\n";
X    }
} 
close(rhosts);
X
undef @rootpath;
X
# checking paths...
#
# Get the root paths from $csh.
X
if (open(CSHRC, $cshrc)) {
X    $path = '';
X    while (<CSHRC>) {
X	next if /^\s*#/;
X	chop unless /\\$/;
X	if (/set\s+path\s*=/) {
X	    $_ = &'suckline($cshrc, $_);
X	    s/.*set\s+path\s*=\s*//;
X	    s/\((.*)\)/$1/;
X	    s/#.*/./;
X	    @tmppath = grep($_ ne '', split(' '));
X	    for (@tmppath) { $whence{$_} .= " " . $cshrc; } 
X	    push(@rootpath, @tmppath);
X	} 
X    } 
X    close(CSHRC);
} 
X
if (open login) {
X    $path = '';
X    while (<cshrc>) {
X	next if /^\s*#/;
X	chop unless /\\$/;
X	if (/set\s+path\s*=/) {
X	    $_ = &'suckline('login', $_);
X	    s/.*set\s+path\s*=\s*//;
X	    s/\((.*)\)/$1/;
X	    s/#.*/./;
X	    @tmppath = grep($_ ne '', split(' '));
X	    for (@tmppath) { $whence{$_} .= " " . $login; } 
X	    push(@rootpath, @tmppath);
X	} 
X    } 
X    close(login);
}
X
if (open profile) {
X    $path = '';
X    while (<profile>) {
X	next if /^\s*#/;
X	chop unless /\\$/;
X	if (/PATH=/) {
X	    $_ = &'suckline('profile', $_);
X	    s/.*PATH=//;
X	    s/#.*//;
X	    @tmppath = split(/:/);
X	    for (@tmppath) { $whence{$_} .= " " . $profile; } 
X	    push(@rootpath, @tmppath);
X	} 
X    } 
X    close(profile);
} 
X
for (keys %whence) {
X    $whence{$_} =~ s/^ //;
X    $whence{$_} =~ s/ / and /g;
} 
X
undef %seen;
grep($seen{$_}++, @rootpath);
X
$is_able'silent = 1;
for (keys %seen) {
X    if (!-e && $_ ne ".") {
X	print "$W path component $_ in $whence{$_} doesn't exist!\n";
X	next;
X    } 
X
X    if (/^\.?$/) {  # null -> dot
X	print "$W \".\" (or current directory) is in root's path in $whence{$_}!\n";
X    } elsif (&'is_writable($_)) {
X	print "$W Directory $_ is _World_ writable and in root's path in $whence{$_}!\n";
X	next;
X    }
X
X    foreach $file (&'glob("$_/*")) {
X	# can't just check -x here, as that depends on current user
X	$is_executable = -f $file && (&'Mode($file) & 0111);
X	if (($all_files || $is_executable) && 
X		    ($how = &'is_writable($file, 'w', 'w'))) {
X	    print "$W _World_ $how ",
X		    $is_executable ? 'executable' : 'file',
X		" $file in root path component $_ from $whence{$_}!\n";
X	} 
X    }
} 
X
$is_able'silent = 0;
X
1;
SHAR_EOF
chmod 0700 p-cops.alpha/root.chk ||
echo 'restore of p-cops.alpha/root.chk failed'
Wc_c="`wc -c < 'p-cops.alpha/root.chk'`"
test 5959 -eq "$Wc_c" ||
	echo 'p-cops.alpha/root.chk: original size 5959, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= p-cops.alpha/README.perl ==============
if test -f 'p-cops.alpha/README.perl' -a X"$1" != X"-c"; then
	echo 'x - skipping p-cops.alpha/README.perl (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting p-cops.alpha/README.perl (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/README.perl' &&
X
**ALPHA ALERT**ALPHA ALERT**ALPHA ALERT**ALPHA ALERT**ALPHA ALERT**
X
(Warning!  This is the alpha version of p-cops, or perl cops... beta
will be forthcoming shortly...)
X
X  This will attempt to familiarize you with the perl version of cops.  If
you have never used cops before, you can get the complete documentation,
along with shell/C code, via anon-ftp, at cert.sei.cmu.edu, ~ftp/pub/cops/1.02.
Ok; the main difference with this version is that (besides being written
in perl) there is a config file -- "cops.cf", that is used to control the
cruft inside all of the modules.  No more muss, no more fuss.  I'll try
to go over the main ones here.  The only important thing you have to set in
the "cops" main file (or via the "-s" flag) is the secure directory, which 
by default is "."; this is where cops will look for the config file and all
the programs.  Also, if something is flagged as world-writable, and the file
itself is not writable, but the parent directory is, then there will be an
asterix after the warning (e.g. /usr/foo/bar is World Writable! (*).)  
Finally, the suid.chk program, like all the rest of the programs, is meant 
to be run as a part of "cops"; it's output will go to stdout if run 
standalone, or either get mailed or saved to the result file if run under 
cops.  That's a bit different than the old version.  Depending on comments,
I'll either keep it this way, or put in some more options.
X
X  As said in the config file -- "cops.cf" (a "#" sign denotes comments):
X
# anything beginning with /^\s*[$@%&]/ will be eval'd
X
X  In general, you can put variables and programs that will be run inside
the config file.  Variables look startlingly like they do in normal perl
(look at the "PROGRAMS" section below for more on running programs); e.g.:
X
$NMAIL 		= 0; 		# send mail instead of generating reports
$ONLY_DIFF 	= 0;  		# only send diff from last time
$SECURE_USERS   = "root"; 	# user to receive mailed report
X
X  Setting something to "0" (without quotes is fine) generally means that 
the option is not used.  "1" (or non-zero values, if you feel gutsy) is 
used for a positive/true/whatever value.  The variables in general should
be very similar to their normal cops counterparts; in this case, setting
NMAIL to 1 would mean to mail info to the user listed in SECURE_USERS.
If ONLY_DIFF is 1, it will only mail reports if change has occurred.
X
X  In general, variables in package main are for cops itself, whereas
those with package qualifiers are for a particular chk routine or
for auxiliary routines.  For instance, the following lines:
X
# this one says to ignore warnings about paths matching these regexps
@chk_strings'ignores = ( '^/tmp/?', '^/(var|usr)/tmp/?' );
X
X  "chk_strings" is a routine that checks for writable programs within other
programs, usually executed by root, such as /etc/rc and crontab.  This line
says to ignore any files that start with a "/tmp", "/var/tmp", or "/usr/tmp".
If you have a file or set of files that always are returning writable that
are inside your rc and cron files, then you can put exceptions here.  One
possibility is that you don't care about files created by other programs,
so that anything after a ">" should be ignored.  You might add something
like '>.*' to ignore files like "/usr/bar/snowcone", in a line like
"/foo/bar/command > /usr/bar/snowcone".
X
X  Next, there is a nifty option, that does recursive searching inside the
files chk_strings looks at.  This is neat... get it working by setting this
to 1:
X
$chk_strings'recurse = 1;
X
X  So, if you have a line like this in /etc/rc:
X
/usr/bin/foo > /dev/console
X
X  It will examine "/usr/bin/foo" for programs inside of it -- and it will
keep going until it has exhausted all possibilities.  So you can get warning
messages like:
X
Warning!  File /foo/bar (inside /usr/local/X11R4/bin/X inside /usr/local/X11R4
/bin/xdm inside /etc/rc.local) is _World_ writable!
X
X  Fun stuff.  No one can hide, now...
X
PROGRAMS
=========
X
X  Running a program within cops is easy; you just have the program with
any options by itself on a line.  Semi-colons are not welcome here.
E.g.:
X
# first test the security of the root account
root.chk
X
X  Some variables specific to the various programs are here as well, e.g.:
X
# now of the various devices.  -g means to check group writability, too
$MTAB    = '/etc/fstab';
$EXPORTS = '/etc/exports';
$TAB_STYLE = 'new';
dev.chk -g 
X
X  This is specifying the export files, etc., and saying that you should
use the "new" format style in the exports file.  Ultrix, etc. uses the
old style.  Suid.chk eats up time -- consider the "-n" flag for systems
that have big NFS mounted disks.  And that's it -- the rest should be very 
similar to the old cops, and theoretically, should give you similar or
the same results.
X
X  Good luck!  Send bugs, flames, etc. to df@cert.sei.cmu.edu
X
X -- dan
SHAR_EOF
chmod 0700 p-cops.alpha/README.perl ||
echo 'restore of p-cops.alpha/README.perl failed'
Wc_c="`wc -c < 'p-cops.alpha/README.perl'`"
test 4848 -eq "$Wc_c" ||
	echo 'p-cops.alpha/README.perl: original size 4848, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= p-cops.alpha/root.chk.old ==============
if test -f 'p-cops.alpha/root.chk.old' -a X"$1" != X"-c"; then
	echo 'x - skipping p-cops.alpha/root.chk.old (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting p-cops.alpha/root.chk.old (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/root.chk.old' &&
#!/bin/sh  # need to mention perl here to avoid recursion
#
#  Usage: root.chk
#
#  This script checks pathnames inside root's startup files for 
# writability, improper umask settings (world writable), non-root
# entries in /.rhosts, writable binaries in root's path,
# and to ensure that root is in /etc/ftpuser.
#
# Also check for a single "+" in /etc/hosts.equiv (world is trusted),
# and that /bin, /etc and certain key files are root owned, so that you
# can't, say, rcp from a host.equived machine and blow over the password
# file... this may or may not be bad, decide for yourself.
# Startup files are /.login /.cshrc /.profile
#
#  Mechanism:  These files contain paths and filenames that are stripped
# out using "grep".  These strings are then processed by the "is_able"
# program to see if they are world writable.  Strings of the form:
#
#	path=(/bin /usr/bin .)
#		and
#	PATH=/bin:/usr/bin:.:
#
# are checked  to ensure that "." is not in the path.  All
# results are echoed to standard output.  In addition, some effort was
# put into parsing out paths with multiple lines; e.g. ending in "\",
# and continuing on the next line.  Also, all executable files and 
# directories in there are checked for writability as well.
#
#  For umask stuff, simply grep for umask in startup files, and check
# umask value.  For /etc/ftpuser, simple grep to check if root is in
# the file.  For /etc/hosts.equiv, just check to see if "+" is alone
# on a line by awking it.
#
#  NOTE:
# if you know where perl is and your system groks #!, put its
# pathname at the top to make this a tad faster.
#
# the following magic is from the perl man page
# and should work to get us to run with perl 
# even if invoked as an sh or csh or foosh script.
# notice we don't use full path cause we don't
# know where the user has perl on their system.
#
eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' 
& eval 'exec perl -S $0 $argv:q'
X    if $running_under_some_stupid_shell_instead_of_perl;
X
# rewritten in perl by tchrist@convex.com
# 
X
# root startup/important files
X
require 'file_owner.pl';
require 'fgrep.pl';
require 'suckline.pl';
require 'is_able.pl';
require 'chk_strings.pl';
require 'glob.pl';
X
package root_chk;
X
# use -a true if you care about non-executables
# in root's path
X
$ARGV[0] eq '-a' && ($all_files++, shift);
X
die "usage: root.chk [-a]\n" if @ARGV;
X
$W = 'Warning! ';
X
$cshrc	= '/.cshrc';
$profile= '/.profile';
$rhosts = '/.rhosts';
X
$| = 1;
X
@big_files= ('/.login', '/.cshrc', '/.profile', '/.logout' );
X
# root should own *at least* these, + $big_files; you can check for all files
# in /bin & /etc, or just the directories (the default.)
# root_files="/bin /bin/* /etc /etc/* $big_files $rhosts"
@root_files= ('/bin','/etc',@big_files,$rhosts,'/etc/passwd','/etc/group');
X
# misc important stuff
$ftp='/etc/ftpusers';
$equiv='/etc/hosts.equiv';
X
#   should't have anyone but root owning /bin or /etc files/directories
# In case some of the critical files don't exist (/.rhost), toss away error
# messages
X
if (@bad_files = grep (-e && &'Owner($_), @root_files)) {
X    print "$W  Root does not own the following file(s):\n";
X    print "\t@bad_files\n";
} 
X
local($chk_strings'recurse) = 1 unless defined $chk_strings'recurse;
X
for $file (@big_files) {
X    open file || next;
X
X    &'chk_strings($file);
X
X    # check for group or other writable umask
X    while (<file>) {
X	next if /^\s*#/;
X	next unless /umask\s*(\d+)/;
X	next unless ~oct($1) & 022;
X	print "$W root's umask set to $1 in $file\n";
X    } 
} 
X
print "$W $ftp exists and root is not in it\n" 
X    if -e $ftp && !&'fgrep($ftp,'root');
X
print "$W A \"+\" entry exists in $equiv!\n" if &'fgrep($equiv, '^\+$');
X
if (open rhosts) {
X    while (<rhosts>) {
X	next unless /\S+\s+(\S+)/ && $1 ne 'root';
X	print "$W Non-root entry in $rhosts! $1\n";
X    }
} 
close(rhosts);
X
undef @rootpath;
X
# checking paths...
#
# Get the root paths from $csh.
X
if (open $cshrc) {
X    $path = '';
X    while (<cshrc>) {
X	next if /^\s*#/;
X	chop unless /\\$/;
X	if (/set\s+path\s*=/) {
print "FOO-3: @rootpath , $_\n";
X	    $_ = &'suckline(cshrc, $_);
print "FOO-2: @rootpath , $_\n";
X	    s/.*set\s+path\s*=\s*//;
X	    s/\((.*)\)/$1/;
X	    s/#.*/./;
X	    @tmppath = grep($_ ne '', split(' '));
print "FOO-1: @rootpath , $_\n";
X	    for (@tmppath) { $whence{$_} .= " " . $cshrc; } 
X	    push(@rootpath, @tmppath);
print "FOO-0: @rootpath , $_\n";
X	} 
X    } 
X    close(cshrc);
} 
X
print "FOO0: @rootpath\n";
X
if (open login) {
X    $path = '';
X    while (<cshrc>) {
X	next if /^\s*#/;
X	chop unless /\\$/;
X	if (/set\s+path\s*=/) {
X	    $_ = &'suckline('login', $_);
X	    s/.*set\s+path\s*=\s*//;
X	    s/\((.*)\)/$1/;
X	    s/#.*/./;
X	    @tmppath = grep($_ ne '', split(' '));
X	    for (@tmppath) { $whence{$_} .= " " . $login; } 
X	    push(@rootpath, @tmppath);
X	} 
X    } 
X    close(login);
}
X
print "FOO1: @rootpath\n";
X
if (open profile) {
X    $path = '';
X    while (<profile>) {
X	next if /^\s*#/;
X	chop unless /\\$/;
X	if (/PATH=/) {
X	    $_ = &'suckline('profile', $_);
X	    s/.*PATH=//;
X	    s/#.*//;
X	    @tmppath = split(/:/);
X	    for (@tmppath) { $whence{$_} .= " " . $profile; } 
X	    push(@rootpath, @tmppath);
X	} 
X    } 
X    close(profile);
} 
X
print "FOO2: @rootpath\n";
X
for (keys %whence) {
X    $whence{$_} =~ s/^ //;
X    $whence{$_} =~ s/ / and /g;
} 
X
undef %seen;
grep($seen{$_}++, @rootpath);
X
$is_able'silent = 1;
for (keys %seen) {
X	print "WAK: $_\n";
X    if (!-e && $_ ne ".") {
X	print "$W path component $_ in $whence{$_} doesn't exist!\n";
X	next;
X    } 
X
X    if (/^\.?$/) {  # null -> dot
X	print "$W \".\" (or current directory) is in root's path in $whence{$_}!\n";
X    } elsif (&'is_writable($_)) {
X	print "$W Directory $_ is _World_ writable and in root's path in $whence{$_}!\n";
X	next;
X    }
X
X    foreach $file (&'glob("$_/*")) {
X	print "BAR: $_\n";
X	# can't just check -x here, as that depends on current user
X	$is_executable = -f $file && (&'Mode($file) & 0111);
X	if (($all_files || $is_executable) && 
X		    ($how = &'is_writable($file, 'w', 'w'))) {
X	    print "$W _World_ $how ",
X		    $is_executable ? 'executable' : 'file',
X		" $file in root path component $_ from $whence{$_}!\n";
X	} 
X    }
} 
X
$is_able'silent = 0;
X
1;
SHAR_EOF
chmod 0700 p-cops.alpha/root.chk.old ||
echo 'restore of p-cops.alpha/root.chk.old failed'
Wc_c="`wc -c < 'p-cops.alpha/root.chk.old'`"
test 6206 -eq "$Wc_c" ||
	echo 'p-cops.alpha/root.chk.old: original size 6206, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= p-cops.alpha/README.kuang ==============
if test -f 'p-cops.alpha/README.kuang' -a X"$1" != X"-c"; then
	echo 'x - skipping p-cops.alpha/README.kuang (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting p-cops.alpha/README.kuang (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/README.kuang' &&
This is a perl version of Dan's version of Bob Baldwin's Kuang program
(originally written as some shell scripts and C programs). 
X
The original intent was to improve the speed of kuang, which is
especially important for installations like ours with several thousand
accounts and NFS things and all that.  The shell version of Kuang used
C programs to add rules, get a groups members, determine the writers
of a file, and so on, which really slowed things down.
X
X		"no" problems	/etc staff writeable
X		-------------	--------------------
shell kuang	2:14 (14)	12:26 (98)	0.1 p/s
perl kuang	1:10 (18)	 2:34 (588)	3.8 p/s
X
--- Steve Romig, CIS, Ohio State, October 1990
X
------------------------------------------------------------------------------
X
Some Features
---- --------
X
X  Caches passwd/group file entries in an associative array for faster
X  lookups.  This is particularly helpful on insecure systems using YP
X  where password and group lookups are slow and you have to do alot of
X  them...:-)
X
X  Can specify target (uid or gid) on command line.
X
X  Can use -l option to generate PAT for a goal.
X
X  Can use -f to preload file owner, group and mode info, which is
X  helpful in speeding things up and in avoiding file system
X  'shadows'...  See the man page for details.
X
Future plans, things to fix:
----------------------------
X
- In a large environment (like ours, 260+ machines, 30+ file systems
X  on as many servers, 2000 password file entries served by YP) it
X  would be nice to 'precompute' successful plans that would be common
X  to all systems.  In particular, plans for becoming most of the users
X  with home directories on the NFS file systems would be useful, since
X  we don't really want to recheck these on each host.  You wouldn't
X  want the plan to be too deep - probably shouldn't span more than 2
X  uids (1 on each end: grant u.romig grant g.staff write ~foo/.login
X  grant u.foo).  I'm thinking that you could feed a list of these
X  precomputed plans to kuang and add some code that causes it to
X  splice in relevent plans where it can to short cut the planning
X  steps.  For example, if one of the plans in uids.next is something
X  like "grant u.foo ...", and I have the precomputed plan mentioned
X  above, I could splice the two: "grant u.romig grant g.staff write
X  ~foo/.login grant u.foo ..." and skip all the normal steps that
X  would've been taken to get there.
X
- Hmmm...thinking about it, it seems like some of the steps are a bit
X  too implicit...maybe the rules should be broken out a bit more.
X  That will cost in processing time, though.
X
- Would be really, really nice to be able to deal with PATH variables
X  - location of ., who can write elements of path, etc.  Basic rule is
X  "anyone who can replace anything in any of path directories or the
X  path directories themselves can become that PATH's user..."  This
X  can be really messy though - in our environment, the path for a user
X  will depend on the architecture type of the machine that he is
X  logged into, and to get the path, you'd have to read and interpret
X  his .login (including variable assignments, source's and
X  conditionals).  Urf.  One wonders whether it might be better to have
X  something running as root that su's to each username in turn and
X  gets the path that way...
X
- ignore plans that start with "uid root", unless that's the only element - root
X  can get to anything, and hopefully nothing can get to root...?
X
- remove duplicate names from uid2names and gid2names...
X
- with ~/.login world writeable - only follows group path, but not OTHER.
X
- add plans to asseccible list.
X
Done
----
X
- Need to find all plans that lead to compromise, not just a plan.
X
- An earlier version scanned the password file looking for generally
X  accesible accounts (no password), which would be added to the
X  uids.known list (in addition to -1, "other").  I had planned on also
X  adding a password checker which would allow us to also add accounts
X  with easily guessed passwords.  Eventually I nuked the code that
X  scanned the password file to speed things up, and further reflection
X  reveals that it isn't wise to add the password scanning to kuang
X  itself.  At some point we should add a comand line option that
X  allows us to add additional uid's (or gid's?) to the uids.known
X  list.  That way the user could run some other tool to scan the
X  password file and generate a list of accessible accounts, which
X  could then be fed to kuang.  Makes it faster on clients using YP
X  since most of the password file is the same for all N clients, why
X  scan it N times.  Means that user can do smarter things to/with the
X  password file checks (list all accounts with no password or easily
X  guessed password, filter out "ok" entries (eg, sync) and etc.)
X
- We aren't dealing with uid's and gid's correctly.  If there are
X  several entries that list the same UID, but with different names,
X  directories and shells, we'll only check plans for becoming one of
X  them, rather than any of them.  Hmmm...this is easier than I
X  thought, when we evaluate some plan for granting a particular uid,
X  we need to evaluate plans for all usernames that can become that
X  uid.  Just stick a loop in there somewhere...get CF's for each of
X  username's in turn.  Bah, harder than I thought, since it'd have to
X  scan the whole password file to figure which username/home directories
X  can become which uid's.  Similarly with groups.
X
X  Current plan: by default, kuang will have to scan the whole password
X  and group files so it can be sure to get all possible ways to become
X  some uid or gid.  Internally, really need several lists:
X
X	mapping from uid to list of usernames that have that uid
X	mapping from a username to home directory, shell
X	mapping from gid to list of uids that have access to that
X	  gid when they login (either member of group with that gid or
X	  given as login group in passwd file)
X	mapping from gid to list of group names for that gid
X
X  Course, this means that we have to read the whole password and group
X  file, most of which will be common to many machines (like in a YP
X  environment).  We could preload the tables above from files created
X  once, containing the brunt of the YP info, and then augment that
X  withthe local passwd and group info on each host when kuang is
X  invoked, but then we need to correctly interpret funky YP things
X  like +@netgroup:::*:..., which means that the uid has a name but no
X  password here...and similarly with shell substitutions and so on.
X  Bah. 
X
- The kuang described in Baldwin's dissertation is somewhat different
X  in nature from this one.  The original computes a Privilege Access
X  Table (PAT) which describes for each uid and gid which uids have
X  access to that uid.  To assess security, we compare this against the
X  security policy for the site, which similarly describes which uid's
X  are supposed to have access to each uid and gid.  A sample SP might
X  be that each uid should be accessible only by itself and root, and
X  each gid should be accessible only to the members of that group and
X  root.  If the PAT listed additional uid's for some priv, that would
X  constitute a violation of the Security Policy for the site.
X
X  The current kuang is different.  It registers Success (a problem was
X  found) if it determines that some uid in the uids.known list (-1,
X  "other" by default) can access the target privilege.  It may find
X  along the way that extra uids can access some uid, but these aren't
X  reported as specific problems unless they are added to the
X  uids.known list. 
X
X  We could do something similar to the kuang described in the paper by
X  setting uids.known to be all the uids that aren't in the security
X  policy table for the target uid, and running kuang against the
X  target.  This would report success for each uid that could access
X  the target.  You could do similar things with groups - uids.known
X  would be all the uids that aren't members of the group...
X
X  Alternately, we could simply have kuang record the list of uids that
X  can access the target priv and print the list when its done.  That
X  way you could iterate kuang against all uids and gids and compare
X  the resulting PAT against your security policy and record the
X  differences.  You'd probably want to record the plan for each uid
X  reported also.
X
X  On our system this would mean running kuang roughly 2500
X  times to check 1 host, and we have about 300 hosts...urf...assuming
X  that each kuang invocation has to check 50 plans, that's a total of
X  125,000 plans per host, or about an hour of real time...not as bad
X  as it could be, though.
X
- It would be nice to add to the list of rules.  It would be especialy
X  nice to extract the rules from the code so that we can create site
X  specific rule files (for example, we use X11r4 here, and many users
X  have a .Xinitrc that contains shell commands that get executed when
X  they login.)
X
X  Easiest way to do this would be to extract the rules as Perl code so
X  we can take advantage of conditionals and so on, and include them
X  within the body of kuang somehow.  A sample rule in perl:
X
X	if (&shell($uid) eq "/bin/csh") {
X	    &addto("files", &home($uid)."/.login", 
X			"replace .login $plan");
X	}
X
X  which simply means "if the user's shell is csh, then try to replace
X  his .login file." 
SHAR_EOF
chmod 0600 p-cops.alpha/README.kuang ||
echo 'restore of p-cops.alpha/README.kuang failed'
Wc_c="`wc -c < 'p-cops.alpha/README.kuang'`"
test 9306 -eq "$Wc_c" ||
	echo 'p-cops.alpha/README.kuang: original size 9306, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= p-cops.alpha/suid.stop ==============
if test -f 'p-cops.alpha/suid.stop' -a X"$1" != X"-c"; then
	echo 'x - skipping p-cops.alpha/suid.stop (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting p-cops.alpha/suid.stop (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/suid.stop' &&
SHAR_EOF
chmod 0700 p-cops.alpha/suid.stop ||
echo 'restore of p-cops.alpha/suid.stop failed'
Wc_c="`wc -c < 'p-cops.alpha/suid.stop'`"
test 0 -eq "$Wc_c" ||
	echo 'p-cops.alpha/suid.stop: original size 0, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= p-cops.alpha/pwgrid.pl ==============
if test -f 'p-cops.alpha/pwgrid.pl' -a X"$1" != X"-c"; then
	echo 'x - skipping p-cops.alpha/pwgrid.pl (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting p-cops.alpha/pwgrid.pl (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/pwgrid.pl' &&
# Routines for reading and caching user and group information.
#
# Provides a bunch of routines and a bunch of arrays.  Routines 
# (and their usage):
#
#    load_passwd_info($use_getent, $file_name)
#
#	loads user information into the %uname* and %uid* arrays 
#	(see below).  
#
#	If $use_getent is non-zero:
#	    get the info via repeated 'getpwent' calls.  This can be
#	    *slow* on some hosts, especially if they are running as a
#	    YP (NIS) client.
#	If $use_getent is 0:
#	    if $file_name is "", then get the info from reading the 
#	    results of "ypcat passwd" and from /etc/passwd.  Otherwise, 
#	    read the named file.  The file should be in passwd(5) 
#	    format.
#
#    load_group_info($use_gentent, $file_name)
#
#	is similar to load_passwd_info.
#
# Information is stored in several convenient associative arrays:
#
#   %uname2shell	Assoc array, indexed by user name, value is 
#			shell for that user name.
#
#   %uname2dir		Assoc array, indexed by user name, value is
#			home directory for that user name.
#
#   %uname2uid		Assoc array, indexed by name, value is uid for 
#			that uid.
#			
#   %uname2passwd	Assoc array, indexed by name, value is password
#			for that user name.
#
#   %uid2names		Assoc array, indexed by uid, value is list of
#			user names with that uid, in form "name name
#			name...". 
#
#   %gid2members	Assoc array, indexed by gid, value is list of
#			group members in form "name name name..."
#
#   %gname2gid		Assoc array, indexed by group name, value is
#			matching gid.
#
#   %gid2names		Assoc array, indexed by gid, value is the
#			list of group names with that gid in form 
#			"name name name...".
#
# You can also use routines named the same as the arrays - pass the index 
# as the arg, get back the value.  If you use this, get{gr|pw}{uid|gid|nam} 
# will be used to lookup entries that aren't found in the cache.
#
# To be done:
#    probably ought to add routines to deal with full names.
#    maybe there ought to be some anal-retentive checking of password 
#	and group entries.
#    probably ought to cache get{pw|gr}{nam|uid|gid} lookups also.
#    probably ought to avoid overwriting existing entries (eg, duplicate 
#       names in password file would collide in the tables that are 
#	indexed by name).
#
# Disclaimer:
#    If you use YP and you use netgroup entries such as 
#	+@servers::::::
#	+:*:::::/usr/local/utils/messages
#    then loading the password file in with &load_passwd_info(0) will get 
#    you mostly correct YP stuff *except* that it won't do the password and 
#    shell substitutions as you'd expect.  You might want to use 
#    &load_passwd_info(1) instead to use getpwent calls to do the lookups, 
#    which would be more correct.
#
X
%uname2shell = ();
%uname2dir = ();
%uname2uid = ();
%uname2passwd = ();
%uid2names = ();
%gid2members = ();
%gname2gid = ();
%gid2names = ();
X
if (! defined($DOMAINNAME)) {
X    $DOMAINNAME = "/bin/domainname";
}
if (! defined($YPCAT)) {
X    $YPCAT = "/bin/ypcat";
}
X
$passwd_loaded = 0;		# flags to use to avoid reloading everything
$group_loaded = 0;		# unnecessarily...
X
#
# We provide routines for getting values from the data structures as well.
# These are named after the data structures they cache their data in.  Note 
# that they will all generate password and group file lookups via getpw* 
# and getgr* if they can't find info in the cache, so they will work
# "right" even if load_passwd_info and load_group_info aren't called to 
# preload the caches.
#
# I should point out, however, that if you don't call load_*_info to preload
# the cache, uid2names, gid2names and gid2members *will not* be complete, since 
# you must read the entire password and group files to get a complete picture.
# This might be acceptable in some cases, so you can skip the load_*_info
# calls if you know what you are doing...
#
sub uname2shell {
X    local($key) = @_;
X
X    if (! defined($uname2shell{$key})) {
X	&add_pw_info(getpwnam($key));
X    }
X
X    return($uname2shell{$key});
}
X
sub uname2dir {
X    local($key) = @_;
X    local(@pw_info);
X
X    if (! defined($uname2dir{$key})) {
X	&add_pw_info(getpwnam($key));
X    }
X
X    return($uname2dir{$key});
}
X
sub uname2uid {
X    local($key) = @_;
X    local(@pw_info);
X
X    if (! defined($uname2uid{$key})) {
X	&add_pw_info(getpwnam($key));
X    }
X
X    return($uname2uid{$key});
}
X
sub uname2passwd {
X    local($key) = @_;
X    local(@pw_info);
X
X    if (! defined($uname2passwd{$key})) {
X	&add_pw_info(getpwnam($key));
X    }
X
X    return($uname2passwd{$key});
}
X
sub uid2names {
X    local($key) = @_;
X    local(@pw_info);
X
X    if (! defined($uid2names{$key})) {
X	&add_pw_info(getpwuid($key));
X    }
X
X    return($uid2names{$key});
}
X
sub gid2members {
X    local($key) = @_;
X    local(@gr_info);
X
X    if (! defined($gid2members{$key})) {
X	&add_gr_info(getgrgid($key));
X    }
X
X    return($gid2members{$key});
}
X
sub gname2gid {
X    local($key) = @_;
X    local(@gr_info);
X
X    if (! defined($gname2gid{$key})) {
X	&add_gr_info(getgrnam($key));
X    }
X
X    return($gname2gid{$key});
}
X
sub gid2names {
X    local($key) = @_;
X    local(@gr_info);
X
X    if (! defined($gid2names{$key})) {
X	&add_gr_info(getgrgid($key));
X    }
X
X    return($gid2names{$key});
}
X
#
# Update user information for the user named $name.  We cache the password, 
# uid, login group, home directory and shell.
#
X
sub add_pw_info {
X    local($name, $passwd, $uid, $gid) = @_;
X    local($dir, $shell);
X
#
# Ugh!  argh...yech...sigh.  If we use getpwent, we get back 9 elts, 
# if we parse /etc/passwd directly we get 7.  Pick off the last 2 and 
# assume that they are the $directory and $shell.  
#
X    $dir = $_[$#_ - 1];
X    $shell = $_[$#_];
X
X    if ($name ne "") {
X	$uname2shell{$name} = $shell;
X	$uname2dir{$name} = $dir;
X	$uname2uid{$name} = $uid;
X	$uname2passwd{$name} = $passwd;
X
X	if ($gid ne "") {
X	    # fixme: should probably check for duplicates...sigh
X
X	    if (defined($gid2members{$gid})) {
X		$gid2members{$gid} .= " $name";
X	    } else {
X		$gid2members{$gid} = $name;
X	    }
X	}
X
X	if ($uid ne "") {
X	    if (defined($uid2names{$uid})) {
X		$uid2names{$uid} .= " $name";
X	    } else {
X		$uid2names{$uid} = $name;
X	    }
X	}
X    }
}
X
#
# Update group information for the group named $name.  We cache the gid 
# and the list of group members.
#
X
sub add_gr_info {
X    local($name, $passwd, $gid, $members) = @_;
X
X    if ($name ne "") {
X	$gname2gid{$name} = $gid;
X
X	if ($gid ne "") {
X	    if (defined($gid2names)) {
X		$gid2names{$gid} .= " $name";
X	    } else {
X		$gid2names{$gid} = $name;
X	    }
X
X	    # fixme: should probably check for duplicates
X
X	    $members = join(' ', split(/[, \t]+/, $members));
X
X	    if (defined($gid2members{$gid})) {
X		$gid2members{$gid} .= " " . $members;
X	    } else {
X		$gid2members{$gid} = $members;
X	    }
X	}
X    }
}
X
#
# We need to suck in the entire group and password files so that we can 
# make the %uid2names, %gid2members and %gid2names lists complete.  Otherwise,
# we would just read the entries as needed with getpw* and cache the results.
# Sigh.
#
# There are several ways that we might find the info.  If $use_getent is 1, 
# then we just use getpwent and getgrent calls to read the info in.
#
# That isn't real efficient if you are using YP (especially on a YP client), so
# if $use_getent is 0, we can use ypcat to get a copy of the passwd and
# group maps in a fairly efficient manner.  If we do this we have to also read
# the local /etc/{passwd,group} files to complete our information.  If we aren't 
# using YP, we just read the local pasword and group files.
#
sub load_passwd_info {
X    local($use_getent, $file_name) = @_;
X    local(@pw_info);
X
X    if ($passwd_loaded) {
X	return;
X    }
X
X    $passwd_loaded = 1;
X
X    if ($use_getent) {
X	#
X	# Use getpwent to get the info from the system, and add_pw_info to 
X	# cache it.
X	#
X	while ((@pw_info = getpwent()) != 0) {
X	    &add_pw_info(@pw_info);
X	}
X
X	endpwent();
X
X	return();
X    } elsif ($file_name eq "") {
X	chop($has_yp = `$DOMAINNAME`);
X	if ($has_yp) {
X	    #
X	    # If we have YP (NIS), then use ypcat to get the stuff from the 
X	    # map.
X	    #
X	    open(FILE, "$YPCAT passwd|") ||
X	      die "can't 'ypcat passwd'";
X	    while (<FILE>) {
X		chop;
X		&add_pw_info(split(/:/));
X	    }
X	    close(FILE);
X	}
X
X	#
X	# We have to read /etc/passwd no matter what...
X	#
X	$file_name = "/etc/passwd";
X    }
X
X    open(FILE, $file_name) ||
X      die "can't open $file_name";
X
X    while (<FILE>) {
X	chop;
X	    
X	if ($_ !~ /^\+/) {
X	    &add_pw_info(split(/:/));
X	}
X
X	# fixme: if the name matches +@name, then this is a wierd 
X	# netgroup thing, and we aren't dealing with it right.  might want
X	# to warn the poor user...suggest that he use the use_getent 
X	# method instead.
X    }
X
X    close(FILE);
}
X
sub load_group_info {
X    local($use_getent, $file_name) = @_;
X    local(@gr_info);
X
X    if ($group_loaded) {
X	return;
X    }
X
X    $group_loaded = 1;
X
X    if ($use_getent) {
X	#
X	# Use getgrent to get the info from the system, and add_gr_info to 
X	# cache it.
X	#
X	while ((@gr_info = getgrent()) != 0) {
X	    &add_gr_info(@gr_info);
X	}
X
X	endgrent();
X
X	return();
X    } elsif ($file_name eq "") {
X	chop($has_yp = `$DOMAINNAME`);
X	if ($has_yp) {
X	    #
X	    # If we have YP (NIS), then use ypcat to get the stuff from the 
X	    # map.
X	    #
X	    open(FILE, "$YPCAT group|") ||
X	      die "can't 'ypcat group'";
X	    while (<FILE>) {
X		chop;
X		&add_gr_info(split(/:/));
X	    }
X	    close(FILE);
X	}
X
X	#
X	# We have to read /etc/group no matter what...
X	#
X	$file_name = "/etc/group";
X    }
X
X    open(FILE, $file_name) ||
X      die "can't open $file_name";
X
X    while (<FILE>) {
X	chop;
X	if ($_ !~ /^\+/) {
X	    &add_gr_info(split(/:/));
X	}
X
X	# fixme: if the name matches +@name, then this is a wierd 
X	# netgroup thing, and we aren't dealing with it right.  might want
X	# to warn the poor user...suggest that he use the use_getent 
X	# method instead.
X    }
X
X    close(FILE);
}
X
1;
X
SHAR_EOF
chmod 0600 p-cops.alpha/pwgrid.pl ||
echo 'restore of p-cops.alpha/pwgrid.pl failed'
Wc_c="`wc -c < 'p-cops.alpha/pwgrid.pl'`"
test 9915 -eq "$Wc_c" ||
	echo 'p-cops.alpha/pwgrid.pl: original size 9915, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= p-cops.alpha/root.chk.new ==============
if test -f 'p-cops.alpha/root.chk.new' -a X"$1" != X"-c"; then
	echo 'x - skipping p-cops.alpha/root.chk.new (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting p-cops.alpha/root.chk.new (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/root.chk.new' &&
#!/bin/sh  # need to mention perl here to avoid recursion
#
#  Usage: root.chk
#
#  This script checks pathnames inside root's startup files for 
# writability, improper umask settings (world writable), non-root
# entries in /.rhosts, writable binaries in root's path,
# and to ensure that root is in /etc/ftpuser.
#
# Also check for a single "+" in /etc/hosts.equiv (world is trusted),
# and that /bin, /etc and certain key files are root owned, so that you
# can't, say, rcp from a host.equived machine and blow over the password
# file... this may or may not be bad, decide for yourself.
# Startup files are /.login /.cshrc /.profile
#
#  Mechanism:  These files contain paths and filenames that are stripped
# out using "grep".  These strings are then processed by the "is_able"
# program to see if they are world writable.  Strings of the form:
#
#	path=(/bin /usr/bin .)
#		and
#	PATH=/bin:/usr/bin:.:
#
# are checked  to ensure that "." is not in the path.  All
# results are echoed to standard output.  In addition, some effort was
# put into parsing out paths with multiple lines; e.g. ending in "\",
# and continuing on the next line.  Also, all executable files and 
# directories in there are checked for writability as well.
#
#  For umask stuff, simply grep for umask in startup files, and check
# umask value.  For /etc/ftpuser, simple grep to check if root is in
# the file.  For /etc/hosts.equiv, just check to see if "+" is alone
# on a line by awking it.
#
#  NOTE:
# if you know where perl is and your system groks #!, put its
# pathname at the top to make this a tad faster.
#
# the following magic is from the perl man page
# and should work to get us to run with perl 
# even if invoked as an sh or csh or foosh script.
# notice we don't use full path cause we don't
# know where the user has perl on their system.
#
eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' 
& eval 'exec perl -S $0 $argv:q'
X    if $running_under_some_stupid_shell_instead_of_perl;
X
# rewritten in perl by tchrist@convex.com
# 
X
# root startup/important files
X
require 'file_owner.pl';
require 'fgrep.pl';
require 'suckline.pl';
require 'is_able.pl';
require 'chk_strings.pl';
require 'glob.pl';
X
package root_chk;
X
# use -a true if you care about non-executables
# in root's path
X
$ARGV[0] eq '-a' && ($all_files++, shift);
X
die "usage: root.chk [-a]\n" if @ARGV;
X
$W = 'Warning! ';
X
$cshrc	= '/.cshrc';
$profile= '/.profile';
$rhosts = '/.rhosts';
X
$| = 1;
X
@big_files= ('/.login', '/.cshrc', '/.profile', '/.logout' );
X
# root should own *at least* these, + $big_files; you can check for all files
# in /bin & /etc, or just the directories (the default.)
# root_files="/bin /bin/* /etc /etc/* $big_files $rhosts"
@root_files= ('/bin','/etc',@big_files,$rhosts,'/etc/passwd','/etc/group');
X
# misc important stuff
$ftp='/etc/ftpusers';
$equiv='/etc/hosts.equiv';
X
#   should't have anyone but root owning /bin or /etc files/directories
# In case some of the critical files don't exist (/.rhost), toss away error
# messages
X
if (@bad_files = grep (-e && &'Owner($_), @root_files)) {
X    print "$W  Root does not own the following file(s):\n";
X    print "\t@bad_files\n";
} 
X
local($chk_strings'recurse) = 1 unless defined $chk_strings'recurse;
X
for $file (@big_files) {
X    open file || next;
X
X    &'chk_strings($file);
X
X    # check for group or other writable umask
X    while (<file>) {
X	next if /^\s*#/;
X	next unless /umask\s*(\d+)/;
X	next unless ~oct($1) & 022;
X	print "$W root's umask set to $1 in $file\n";
X    } 
} 
X
print "$W $ftp exists and root is not in it\n" 
X    if -e $ftp && !&'fgrep($ftp,'root');
X
print "$W A \"+\" entry exists in $equiv!\n" if &'fgrep($equiv, '^\+$');
X
if (open rhosts) {
X    while (<rhosts>) {
X	next unless /\S+\s+(\S+)/ && $1 ne 'root';
X	print "$W Non-root entry in $rhosts! $1\n";
X    }
} 
close(rhosts);
X
undef @rootpath;
X
# checking paths...
#
# Get the root paths from $csh.
X
print "FOO-4:\n";
if (open(CSHRC, $cshrc)) {
X    $path = '';
X    while (<CSHRC>) {
X	print "FOO-3: $_\n";
X	next if /^\s*#/;
X	chop unless /\\$/;
X	if (/set\s+path\s*=/) {
X	  print "FOO-2: $_\n";
X	    $_ = &'suckline($cshrc, $_);
X	    s/.*set\s+path\s*=\s*//;
X	    s/\((.*)\)/$1/;
X	    s/#.*/./;
X	    @tmppath = grep($_ ne '', split(' '));
X	    for (@tmppath) { $whence{$_} .= " " . $cshrc; } 
X	    push(@rootpath, @tmppath);
X	} 
X    } 
X    close(CSHRC);
} 
X
print "FOO0: @rootpath\n";
X
if (open login) {
X    $path = '';
X    while (<cshrc>) {
X	next if /^\s*#/;
X	chop unless /\\$/;
X	if (/set\s+path\s*=/) {
X	    $_ = &'suckline('login', $_);
X	    s/.*set\s+path\s*=\s*//;
X	    s/\((.*)\)/$1/;
X	    s/#.*/./;
X	    @tmppath = grep($_ ne '', split(' '));
X	    for (@tmppath) { $whence{$_} .= " " . $login; } 
X	    push(@rootpath, @tmppath);
X	} 
X    } 
X    close(login);
}
X
print "FOO1: @rootpath\n";
X
if (open profile) {
X    $path = '';
X    while (<profile>) {
X	next if /^\s*#/;
X	chop unless /\\$/;
X	if (/PATH=/) {
X	    $_ = &'suckline('profile', $_);
X	    s/.*PATH=//;
X	    s/#.*//;
X	    @tmppath = split(/:/);
X	    for (@tmppath) { $whence{$_} .= " " . $profile; } 
X	    push(@rootpath, @tmppath);
X	} 
X    } 
X    close(profile);
} 
X
print "FOO2: @rootpath\n";
X
for (keys %whence) {
X    $whence{$_} =~ s/^ //;
X    $whence{$_} =~ s/ / and /g;
} 
X
undef %seen;
grep($seen{$_}++, @rootpath);
X
$is_able'silent = 1;
for (keys %seen) {
X	print "WAK: $_\n";
X    if (!-e && $_ ne ".") {
X	print "$W path component $_ in $whence{$_} doesn't exist!\n";
X	next;
X    } 
X
X    if (/^\.?$/) {  # null -> dot
X	print "$W \".\" (or current directory) is in root's path in $whence{$_}!\n";
X    } elsif (&'is_writable($_)) {
X	print "$W Directory $_ is _World_ writable and in root's path in $whence{$_}!\n";
X	next;
X    }
X
X    foreach $file (&'glob("$_/*")) {
X	print "BAR: $_\n";
X	# can't just check -x here, as that depends on current user
X	$is_executable = -f $file && (&'Mode($file) & 0111);
X	if (($all_files || $is_executable) && 
X		    ($how = &'is_writable($file, 'w', 'w'))) {
X	    print "$W _World_ $how ",
X		    $is_executable ? 'executable' : 'file',
X		" $file in root path component $_ from $whence{$_}!\n";
X	} 
X    }
} 
X
$is_able'silent = 0;
X
1;
SHAR_EOF
chmod 0700 p-cops.alpha/root.chk.new ||
echo 'restore of p-cops.alpha/root.chk.new failed'
Wc_c="`wc -c < 'p-cops.alpha/root.chk.new'`"
test 6147 -eq "$Wc_c" ||
	echo 'p-cops.alpha/root.chk.new: original size 6147, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= p-cops.alpha/rules.pl ==============
if test -f 'p-cops.alpha/rules.pl' -a X"$1" != X"-c"; then
	echo 'x - skipping p-cops.alpha/rules.pl (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting p-cops.alpha/rules.pl (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/rules.pl' &&
sub apply_rules {
X    local($op, $value, @plan) = @_;
X
X    printf("eval($op $value): %s\n", &ascii_plan(@plan)) if $opt_d;
X
X    #
X    # apply UID attack rules...
X    #
X    if ($op eq "u") {
X	#
X	# If we can replace /etc/passwd or /usr/lib/aliases, we can grant 
X	# any uid. 
X	#
X	&addto("r", "/etc/passwd", @plan);
X        &addto("r", "/usr/lib/aliases", @plan);
X        &addto("r", "/etc/aliases", @plan);
X
X	#
X	# Check CF's for all usernames with this uid.
X	#
uname_loop:
X    foreach $uname (split(/ /, $uid2names{$value})) {
X	    $home = $uname2dir{$uname};
X
X	    next uname_loop unless $home;
X
X	    if ($home eq "/") {
X		$home = "";
X	    }
X	    &addto("r", "$home/.rhosts", @plan);
X	    &addto("r", "$home/.login", @plan);
X	    &addto("r", "$home/.logout", @plan);
X	    &addto("r", "$home/.cshrc", @plan);
X	    &addto("r", "$home/.profile", @plan);
X	}
X
X	#
X	# Controlling files for root...
X	#
X	@rootlist = ( 
X		"/etc/rc", "/etc/rc.boot", "/etc/rc.single", 
X		"/etc/rc.config", "/etc/rc.local", "/usr/lib/crontab",
X		"/usr/spool/cron/crontabs",
X		);
X
X	if ($value eq "0") {
X	    foreach $file (@rootlist) {
X		    &addto("r", $file, @plan);
X	    }
X	    # Experimental!
X	    # you can remove this if desired - tjt
X	    #do "rc.prog";
X	}
X
X	#
X	# Other CFs for non-root folks...
X	#
X	if ($value ne "0") {
X	    &addto("r", "/etc/hosts.equiv", @plan);
X	    if (-s "/etc/hosts.equiv") {
X		&addto("r", "/etc/hosts", @plan);
X	    }
X	}
X
X    #
X    # Plans for attacking GIDs...
X    #
X    } elsif ($op eq "g") {	# apply gid attack rules
X
X	#
X	# If we can replace /etc/group we can become any group
X	#				  
X        &addto("r", "/etc/group", @plan);
X
X	#
X	# If we can grant any member of a group we can grant that group
X	#
member_loop:
X	foreach $uname (split(/ /, $gid2members{$value})) {
X	    if (! defined($uname2uid{$uname})) {
X		printf(stderr "group '%s' member '%s' doesn't exist.\n",
X			$value,
X			$uname);
X		next member_loop;
X	    }
X
X	    &addto("u", $uname2uid{$uname}, @plan);
X	}
X
X    #
X    # Plans for attacking files...
X    #
X
X    } elsif ($op eq "r" || $op eq "w") {
X
X        ($owner, $group, $other) = &filewriters($value);
X
X	&addto("u", $owner, @plan) if ($owner ne "");
X	&addto("g", $group, @plan) if ($group ne "");
X	&addto("u", "-1", @plan) if ($other);
X
X	#
X	# If the goal is to replace the file, check the parent directory...
X	#
X	if ($op eq "r") {
X	    $parent = $value;
X	    $parent =~ s#/[^/]*$##;     # strip last / and remaining stuff
X
X	    if ($parent eq "") {
X		$parent = "/";
X	    }
X
X	    if ($parent ne $value) {
X		&addto("r", $parent, @plan);
X	    }
X	}
X
X    } else {			# wow, bad $type of object!
X	printf(stderr "kuang: bad op in apply_rules!\n");
X	printf(stderr "op '%s' value '%s' plan '%s'\n",
X		$op,
X		$value,
X		&ascii_plan(@plan));
X	exit(1);
X    }
}
X
1;
X
SHAR_EOF
chmod 0600 p-cops.alpha/rules.pl ||
echo 'restore of p-cops.alpha/rules.pl failed'
Wc_c="`wc -c < 'p-cops.alpha/rules.pl'`"
test 2768 -eq "$Wc_c" ||
	echo 'p-cops.alpha/rules.pl: original size 2768, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
rm -f _shar_seq_.tmp
echo You have unpacked the last part
exit 0