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

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

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

#!/bin/sh
# This is alpha p-cops, a shell archive (produced by shar 3.49)
# To extract the files from this archive, save it to a file, remove
# everything above the "!/bin/sh" line above, and type "sh file_name".
#
# made 05/17/1991 04:38 UTC by df@death.cert.sei.cmu.edu
# Source directory /usr/users/df/COPS/perl
#
# existing files will NOT be overwritten unless -c is specified
#
# This is part 1 of a multipart archive                                    
# do not concatenate these parts, unpack them in order with /bin/sh        
#
# This shar contains:
# length  mode       name
# ------ ---------- ------------------------------------------
#   7064 -rwx------ p-cops.alpha/ftp.chk
#   1776 -rwx------ p-cops.alpha/get-cf
#   1292 -rwx------ p-cops.alpha/chk_strings
#   3559 -rwx------ p-cops.alpha/chk_strings.pl
#   2129 -rwx------ p-cops.alpha/cops.cf
#   2399 -rwx------ p-cops.alpha/cron.chk
#   6390 -rwx------ p-cops.alpha/cops
#    463 -rwx------ p-cops.alpha/fgrep.pl
#    413 -rwx------ p-cops.alpha/file_mode.pl
#    398 -rwx------ p-cops.alpha/file_owner.pl
#   2739 -rwx------ p-cops.alpha/dev.chk
#    902 -rwx------ p-cops.alpha/getopts.pl
#   2960 -rwx------ p-cops.alpha/glob.pl
#   5060 -rwx------ p-cops.alpha/group.chk
#    444 -rwx------ p-cops.alpha/hostname.pl
#   1591 -rwx------ p-cops.alpha/is_able.chk
#   1603 -rwx------ p-cops.alpha/is_able.lst
#   2835 -rwx------ p-cops.alpha/is_able.pl
#  14688 -rwx------ p-cops.alpha/kuang
#   4413 -rwx------ p-cops.alpha/misc.chk
#   6730 -rw------- p-cops.alpha/kuang.1
#  10420 -rwx------ p-cops.alpha/pass.cache.pl
#   6684 -rwx------ p-cops.alpha/pass.chk
#   5989 -rwx------ p-cops.alpha/passwd.chk
#    677 -rwx------ p-cops.alpha/pathconf.pl
#    644 -rwx------ p-cops.alpha/pathconf.sh
#   1014 -rwx------ p-cops.alpha/rc.chk
#   3296 -rwx------ p-cops.alpha/reconfig.pl
#   2048 -rwx------ p-cops.alpha/user.chk
#    653 -rwx------ p-cops.alpha/stat.pl
#    229 -rwx------ p-cops.alpha/suckline.pl
#   4477 -rwx------ p-cops.alpha/suid.chk
#   5959 -rwx------ p-cops.alpha/root.chk
#   4848 -rwx------ p-cops.alpha/README.perl
#   6206 -rwx------ p-cops.alpha/root.chk.old
#   9306 -rw------- p-cops.alpha/README.kuang
#      0 -rwx------ p-cops.alpha/suid.stop
#   9915 -rw------- p-cops.alpha/pwgrid.pl
#   6147 -rwx------ p-cops.alpha/root.chk.new
#   2768 -rw------- p-cops.alpha/rules.pl
#
if test -r _shar_seq_.tmp; then
	echo 'Must unpack archives in sequence!'
	echo Please unpack part `cat _shar_seq_.tmp` next
	exit 1
fi
# ============= p-cops.alpha/ftp.chk ==============
if test ! -d 'p-cops.alpha'; then
    echo 'x - creating directory p-cops.alpha'
    mkdir 'p-cops.alpha'
fi
if test -f 'p-cops.alpha/ftp.chk' -a X"$1" != X"-c"; then
	echo 'x - skipping p-cops.alpha/ftp.chk (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting p-cops.alpha/ftp.chk (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/ftp.chk' &&
#!/bin/sh  # need to mention perl here to avoid recursion
#
#  Usage: ftp.chk
#
#   This shell script checks to see if you've set up (mainly anonymous)
# ftp correctly.  There seems to be some different types of ftp's 
# around; for instance, some allow "chmod" -- and if the home dir is 
# owned by "ftp", you're toast.  So I've tried to err on the side of
# safety...
#
#   See the man page for a more detailed description, here's what this
# checks for:
#
# - User ftp exists in the password file.
# - root (or all root equivalents) are in ftpusers file.
# - Home directory for ftp should exist, and not be /
# - The ~ftp/etc/{passwd|group} should not be the same as the real ones.
# - Various critical files/directories should exist, and have correct
#   permissions and owners; variables "$primary" and "$secondary" can be set
# to whomever you want owning the files:
#
#  File/Dir          Perms           Owner      Other
#  =========         ======          ======     ======
#  ~ftp              non-w.w.        root
#           or
#  ~ftp              555             ftp	if no chmod command exists
#
#     All of these are ftp owned iff no chmod exists...
#
#  ~ftp/bin          non-w.w.        root/ftp
#  ~ftp/bin/ls       111             root/ftp
#  ~ftp/etc          non-w.w.        root/ftp
#  ~ftp/etc/passwd   non-w.w.        root/ftp   0 size or nonexistant
#  ~ftp/etc/group    non-w.w.        root/ftp   0 size or nonexistant
#  ~ftp/pub          non-w.w.        root/ftp
#  ~ftp/incoming     world-writable  root/ftp   This can be set to "pub"
#  ~ftp/.rhosts      non-w.w.        root       0 size, is optional
#  ~ftp/*            non-w.w.                   other dirs/files in ~ftp
#
#
#  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 'is_able.pl';
require 'file_mode.pl';
require 'glob.pl';
require 'fgrep.pl';
require 'pass.cache.pl';
require 'file_owner.pl';
require 'pathconf.pl';
X
$CMP="/bin/cmp" unless defined $CMP;
X
package ftp;
X
#   Primary and secondary owners of the ftp files/dirs; if you *don't* have
# chmod, you can probably change the secondary owner to "ftp".  If you have
# chmod in your ftp, definitely have secondary to some other account (root
# is fine for this.)
local($primary)="root" unless defined $primary;
local($secondary)="ftp" unless defined $secondary;
X
# some might have this as ftpd; is the account in /etc/passwd
local($ftpuid)="ftp";
X
# system files
local($ftpusers)="/etc/ftpusers";
local($passwd)="/etc/passwd" unless defined $PASSWD;
local($group)="/etc/group" unless defined $GROUP;
X
#   ftp's home:
$ftproot = &'uname2dir($ftpuid);
$anonymous = $ftproot ne '';
X
local($ftprhosts)="$ftproot/.rhosts";
local($ftpbin)="$ftproot/bin";
local($ftpls)="$ftpbin/ls";
local($ftpetc)="$ftproot/etc";
local($ftppasswd)="$ftpetc/passwd";
local($ftpgroup)="$ftpetc/group";
X
local($W) = 'Warning!   ' unless defined $W;
X
#   the pub/incoming stuff; by default, pub is *not* world writable, incoming
# is; if you want pub to be world writable, just change incoming to "pub"
local($incoming)="pub";
X
@crit_files=($ftpgroup,
X	     $ftppasswd,
X	     $ftpls);
X
if (-s $ftpusers) {
X    # check to see if root (or root equivalents) is in ftpusers file
X    @all_roots = split(" ", $'uid2names{0});
X    if (@all_roots ne "") {
X	for $i (@all_roots) {
X	    if (length($user2passwd{$i}) == 13 && ! &'fgrep($ftpusers, "^$i$")) {
X		print "Warning!  $i should be in $ftpusers!\n";
X	    }
X	}
X    }
}
X
#  do the anonymous ftp checking stuff now?
die unless $anonymous;
X
#   if the user ftp doesn't exist, no-anon stuff....
# if $TEST -z $ftproot -a "$anonymous" = "yes" ; then
X
die "${W}Need user $ftpuid for anonymous ftp to work!\n" if ($ftpuid eq "");
X
#   if the user ftp doesn't exist, no-anon stuff....
if (! -d $ftproot || $ftproot eq "") {
X    die "${W}Home directory for ftp doesn\'t exist!\n";
}
if ($ftproot eq "/") {
X    print "${W}$ftproot ftp\'s home directory should not be \"/\"!\n";
}
X
#   want to check all the critical files and directories for correct
# ownership.  Some versions of ftp don't need much of anything... no 
# etc directory or password/group files.
#   others need etc directory & password/group files.  Experiment.
#
push(@crit_files, $ftpbin, $ftpetc);
for $i (@crit_files) {
X    $owner = &'Owner($i);
X
X    if ($owner eq 'BOGUS') {
X	print "${W}Critical anon-ftp file $i is missing!\n";
X	next;
X    }
X
X    $owner = $'uid2names{$owner};
X
X    if ($owner ne $primary && $owner ne $secondary) {
X	print "${W}$i should be owned by $primary or $secondary, not $owner!\n";
X    }
}
X
#  Don't want the passwd and group files to be the real ones!
if (&'Owner($ftppasswd) ne 'BOGUS' &&
X    $passwd ne $ftppasswd && 
X    system "$CMP -s $passwd $ftppasswd") 
{
X    print "${W}$ftppasswd and $passwd are the same!\n";
}
X
if (&'Owner($ftpgroup) ne 'BOGUS' &&
X    $group ne $ftpgroup && 
X    system "$CMP -s $passwd $ftpgroup") 
{
X    print "${W}$ftpgroup and $group are the same!\n";
}
X
#   ftproot is special; if owned by root; should be !world writable;
# if owned by ftp, should be mode 555
X
if (&'Owner($ftproot) ne BOGUS) {
X    $owner = $'uid2names{&'Owner($ftproot)};
X    $perms=&'Mode($ftproot);
X    if ($owner ne $primary && $owner ne $secondary) {
X	print "${W}$ftproot should be owned by $primary or $secondary, not $owner!\n";
X    }
X
X    # ftp-root should not be world-writable:
X    &'is_able($ftproot, "w", "w");
X
X    # if ftp owns root-dir, then mode should be 555:
X    if ($owner eq $ftpuid && $perms ne 00555) {
X	print "${W}$ftproot should be mode 555!\n";
X    }
}
X
#
# check the .rhosts file:
if (-f $ftprhosts) {
X    if (-s $ftprhosts) {
X	print "${W}$ftprhosts should be be empty!\n";
X    }
X    $owner=$'uid2names{&'Owner($ftprhosts)};
X    if ($owner ne $primary && $owner ne $secondary) {
X	print "${W}$ftprhosts should be owned by $primary or $secondary!\n";
X    }
}
X
# finally, some permissions of miscellaneous files:
if (($perms=&'Mode($ftpls)) & 0666) {
X    printf "${W}Incorrect permissions (%04o) on $ftpls!\n", $perms;
}
X
if (($perms=&'Mode($ftppasswd)) & 0333) {
X    printf "${W}Incorrect permissions (%04o) on $ftppasswd!\n", $perms;
}
X
X
if (($perms=&'Mode($ftpgroup)) & 0333) {
X    printf "${W}Incorrect permissions (%04o) on $ftpgroup!\n", $perms;
}
X
#   Finally, the ~ftp/{pub|incoming|whatever} stuff:
opendir(FTPDIR, $ftproot) || die "can't opendir $ftproot: $!";
X
@all_dirs=grep(-d, readdir(FTPDIR));
X
local($is_able'silent) = 1;  
for $i (@all_dirs) {
X    if ($i ne $incoming && &'is_able($ftproot . "/$i", "w", "w")) {
X	print "${W}Anon-ftp directory $i is World Writable!\n";
X    }
}
X
1;
# end of script
SHAR_EOF
chmod 0700 p-cops.alpha/ftp.chk ||
echo 'restore of p-cops.alpha/ftp.chk failed'
Wc_c="`wc -c < 'p-cops.alpha/ftp.chk'`"
test 7064 -eq "$Wc_c" ||
	echo 'p-cops.alpha/ftp.chk: original size 7064, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= p-cops.alpha/get-cf ==============
if test -f 'p-cops.alpha/get-cf' -a X"$1" != X"-c"; then
	echo 'x - skipping p-cops.alpha/get-cf (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting p-cops.alpha/get-cf (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/get-cf' &&
#! /usr/local/bin/perl
X
@dot_files = (
X    ".login", ".logout", ".cshrc",			# csh, cshe or tcsh
X    ".profile",						# ksh, sh
X    ".env",						# ksh
X    ".alias", ".aliases",				# common for all shells
X    "user.ps", ".user.ps", "tools.ps", ".tools.ps",
X	"startup.ps", ".startup.ps",			# NeWS
X    ".mgrc",						# MGR
X    ".X11init", ".awmrc", ".twmrc", ".xinitrc",		# X11
X    ".emacs"						# emacs
);
X
%seen = {};
X
open(HOST, "/bin/hostname |") || die "can't get the hostname";
chop($hostname=<HOST>);
close(HOST);
X
user_loop:
X    for (($name,$passwd,$uid,$gid,$quota,$comment,$gcos,$dir,$shell) = getpwent();
X         $name ne "";
X         ($name,$passwd,$uid,$gid,$quota,$comment,$gcos,$dir,$shell) = getpwent()) {
X
X	#
X	# If the user has a home directory on this server, get the info 
X	# about the directory, his CF's and so on.
X	#
X	if ($dir =~ m,^/n/$hostname/,) {
X	    if (! -d $dir) {
X		printf(stderr "home directory '%s' for user '%s' doesn't exist.\n",
X			$dir,
X			$name);
X		next user_loop;
X	    }
X
X	    ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
X                    $atime,$mtime,$ctime,$blksize,$blocks)
X                        = stat(_);
X	    $mode = $mode & 07777;
X
X	    &spit_it_out("d", $uid, $gid, $mode, $dir);
X
X	    foreach $file (@dot_files) {
X		$path = "$dir/$file";
X
X		if (-f $path) {
X		    ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
X                        $atime,$mtime,$ctime,$blksize,$blocks)
X                            = stat(_);
X		    $mode = $mode & 07777;
X
X		    &spit_it_out("f", $uid, $gid, $mode, $dir);
X		}
X	    }
X	}
X    }
X
X
X
X
sub spit_it_out {
X    local($type, $uid, $gid, $mode, $name) = @_;
X
X    if (defined($seen{$name})) {
X	return;
X    }
X
X    printf("%s %d %d 0%o %s\n", $type, $uid, $gid, $mode, $name);
X    $seen{$name} = 1;
}
X
SHAR_EOF
chmod 0700 p-cops.alpha/get-cf ||
echo 'restore of p-cops.alpha/get-cf failed'
Wc_c="`wc -c < 'p-cops.alpha/get-cf'`"
test 1776 -eq "$Wc_c" ||
	echo 'p-cops.alpha/get-cf: original size 1776, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= p-cops.alpha/chk_strings ==============
if test -f 'p-cops.alpha/chk_strings' -a X"$1" != X"-c"; then
	echo 'x - skipping p-cops.alpha/chk_strings (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting p-cops.alpha/chk_strings (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/chk_strings' &&
#!/bin/sh  # need to mention perl here to avoid recursion
#
#  Usage: chk_strings filename
#
#  This will check pathnames inside executable files for writability,
# The big string "@ignores" is a list of files that are ignored by
# this; you can set it to whatever you want -- default is:
# '^/tmp/?' and '^/(var|usr)/tmp/?'
# 
#  No program root EVER runs should be show up here.
#
# 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
package main;
X
$| = 1;
X
require 'getopts.pl';
require 'chk_strings.pl';
X
die "Usage: $0 [-fr] file ...\n" unless &Getopts('rd') && @ARGV;
X
package chk_strings;
X
$debug = $'opt_d;
$recurse = $'opt_r;
@ignores = ( '^/tmp/?', '^/(var|usr)/tmp/?' )
X    unless defined @ignores;
X
#%paths = ();  # faster than local
X
for (@'ARGV) { 
X    (warn("$0: $_: $!\n"), next) unless -e;
X    &'chk_strings($_); 
} 
SHAR_EOF
chmod 0700 p-cops.alpha/chk_strings ||
echo 'restore of p-cops.alpha/chk_strings failed'
Wc_c="`wc -c < 'p-cops.alpha/chk_strings'`"
test 1292 -eq "$Wc_c" ||
	echo 'p-cops.alpha/chk_strings: original size 1292, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= p-cops.alpha/chk_strings.pl ==============
if test -f 'p-cops.alpha/chk_strings.pl' -a X"$1" != X"-c"; then
	echo 'x - skipping p-cops.alpha/chk_strings.pl (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting p-cops.alpha/chk_strings.pl (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/chk_strings.pl' &&
#
#  This is a big one.  Support routines to check for strings 
# that look like pathnames and make sure they're not writable.
# Will recurse if $recurse is set.  the shell version can't do
# this (yet).  call &ignore with list of regexps that you don't
# care about.  (or set @ignores)
#
# originally by Tom Christiansen <tchrist@convex.com>
# since hacked on by parties various and sundry.
X
require 'is_able.pl';
require 'file_mode.pl';
require 'pathconf.pl';
X
package chk_strings;
X
X
$'STRINGS = $'STRINGS || '/usr/ucb/strings';
X
for ( '/dev/null', '/dev/tty' ) {
X    $seen{$_}++;
} 
X
sub main'chk_strings {
X    local($ARGV) = @_;
X    local($_);
X    local($word);
X    local(*STRINGS);  # XXX: might run out of fd's on deep recursion!  -tchrist
X    local(%paths, $text); 
X    local($STRINGS) = "$'STRINGS $ARGV |";
X
X    &ignore(@ignores) if defined @ignores && !$already_ignored;
X
X    $STRINGS="< $ARGV", $text=1 if -T $ARGV;
X    print "Opening via: $STRINGS\n" if $debug;
X
X    open (STRINGS, $STRINGS); 
X    while (<STRINGS>) { 
X	next unless m#/#;   # was m#/.*/#;
#---------------------------------------------------------------------------
# Comments and modifications by Martin Foord (maf%dbsm.oz.au@munnari.oz.au).
X	#s/#.*$// if $text;  # strip out comments if -T file
X	# Comments start in the shell at the beginning of a word or at the
X	# beggining of a line
X	if ($text) {
X		s/\s+#.*$//;
X		s/^#.*$//;
X	}
X
X	# Get rid of semicolons, they can hang around on filenames ...
X	s/;//g;
#---------------------------------------------------------------------------
X
X	s/"([^"]*)"/ $1 /g;
X	s/'([^']*)'/ $1 /g;
X	# See my comments below on how to deal with this stuff ... (line 64).
X	#s/`([^`]*)`/ $1 /g;
X
X
X	s!([<>])\s+/!$1/!g;  # "> /foo" goes to ">foo";
X
X	s/=/ /g;  # warning -- mangled files with = in them
X	for $word (split) {
X	    if ($word =~ m#:/#) {
X		@paths{split(/:/, $word)} = ();
X	    } elsif ($word =~ m#^[<>]?/#) {
X		print "push $word\n" if $debug;
X		$paths{$word}++;
X	    }
X	}
X    }
X    close (STRINGS);
X    push(@files, $ARGV);
X
X    for (keys %paths) {
X	s/\)$//;
X	s/^\(//;
X	s#^/+#/#;
X	s#^(/.*)/$#$1#;	    # get rid of trailing slash
X
#---------------------------------------------------------------------------
# Comments and modifications by Martin Foord (maf%dbsm.oz.au@munnari.oz.au).
X	# It's best to evaluate what's in backquotes rather than remove them
X	# as in the substitution above, due to files which
X	# look like this /var/yp/`domainname` (eg in my /etc/rc.local).
X	s`\`(.+)\``$1`; # eval what's in backquotes.
X	chop if /\n$/;	# fang off \n if there ...
#---------------------------------------------------------------------------
X	next if &ignored($_);
X	s/^[<>]//;
X	next if $_ eq '';
X	next unless !$seen{$_}++ && -e && !-S _;
X	print "checking $_\n" if $debug;
X	if ($how = &'is_writable($_)) {
X	    print "Warning!  File $_ (inside ",
X			join(' inside ', reverse @files), ") is _World_ $how!\n";
X	} elsif ($recurse && (&'Mode($_) & 0111) && -f _) {
X	     print "recursing $_\n" if $debug;
X	     &'chk_strings($_);   
X	} 
X    }
X     pop(@files);
} 
X
sub ignore {
X    local($_);
X    local($prog);
X
X    $already_ignored = 1;
X
X    $prog = <<'EOCODE';
X
sub ignored {
X    local($return) = 1;
X    local($prog);
X    local($_) = @_;
X    {
EOCODE
X    for (@_) {
X	$prog .= "\tlast if m\201${_}\201;\n";
X    } 
X    $prog .= <<'EOCODE';
X	$return = 0;
X    }
X    print "$_ IGNORED\n" if $debug && $return;
X    $return;
}
EOCODE
X    
X    print $prog if $debug;
X    eval $prog;
X    die $@ if $@;
} 
X
sub ignored {}; # in case they never ignore anything
X
1;
SHAR_EOF
chmod 0700 p-cops.alpha/chk_strings.pl ||
echo 'restore of p-cops.alpha/chk_strings.pl failed'
Wc_c="`wc -c < 'p-cops.alpha/chk_strings.pl'`"
test 3559 -eq "$Wc_c" ||
	echo 'p-cops.alpha/chk_strings.pl: original size 3559, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= p-cops.alpha/cops.cf ==============
if test -f 'p-cops.alpha/cops.cf' -a X"$1" != X"-c"; then
	echo 'x - skipping p-cops.alpha/cops.cf (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting p-cops.alpha/cops.cf (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/cops.cf' &&
#
# COPS CONFIGURATION FILE
#
# put user variables here: anything beginning with /^\s*[$@%&]/ will be eval'd
# 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.
X
# COPS  main variables follow...
$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
$C2             = 0;		# Sun c2 security package
X
# these don't really work for pass.cache yet
$IGNORE_YP	= 0;
$PASSWD		= '/etc/passwd';
$GROUP		= '/etc/group';
X
###############################################################
# many things call &chk_strings (including {cron,misc,rc,root}.chk)
# the following two variable settings affects its behaviour...
# this one says to ignore warnings about paths matching these regexps
X
@chk_strings'ignores = ( '^/tmp/?', '^/(var|usr)/tmp/?' );
X
# this will take a bit longer, but can find dangerous things much better
# the shell cops can't do this.... sigh
X
$chk_strings'recurse = 0;
X
# if we want stat failure warnings from is_able...
X
$is_able'noisy = 0;  
X
###############################################################
# finally the checks to execute.  these can also all be run 
# stand-alone from the shell.
X
# first test the security of the root account
root.chk
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
# exaustive tests for things that shouldn't be readable/writable etc
is_able.chk
X
# check for insecuities in /etc/rc*
rc.chk
X
# and in cron
cron.chk
X
# some consistency and idiocy (null or trivial passwds) in /etc/passwd
passwd.chk
pass.chk
X
# consistency checks in /etc/group
group.chk
X
# make sure user accounts don't have trojanable dot files
user.chk
X
# check ftp security
$ftp'primary = "root";
$ftp'secondary = "ftp";
ftp.chk
X
# and anything else we forgot
X
# Sys V types use /etc/servers here:
$misc'inetd = "/etc/inetd";
misc.chk
X
# Kuang!
kuang
X
# SUID checking
# suid.chk
SHAR_EOF
chmod 0700 p-cops.alpha/cops.cf ||
echo 'restore of p-cops.alpha/cops.cf failed'
Wc_c="`wc -c < 'p-cops.alpha/cops.cf'`"
test 2129 -eq "$Wc_c" ||
	echo 'p-cops.alpha/cops.cf: original size 2129, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= p-cops.alpha/cron.chk ==============
if test -f 'p-cops.alpha/cron.chk' -a X"$1" != X"-c"; then
	echo 'x - skipping p-cops.alpha/cron.chk (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting p-cops.alpha/cron.chk (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/cron.chk' &&
#!/bin/sh  # need to mention perl here to avoid recursion
#
#  Usage: cron.chk.pl [-rd]
#
#  This checks pathnames and files inside the cron files /usr/lib/crontab
# for writability.
#
#  Mechanism:  The commands inside the file /usr/lib/crontab are executed
# by root.  This perl script uses chk_strings.pl for chking for writable
# files/dirs.
#
#  cron.chk.pl will try to find a file in /usr/lib/crontab first (bsd),
# and then if it isn't there, it will look in the any alternate
# possible locations next -- right now, /usr/spool/cron/crontab -- to
# see if a directory exists, and, if it does, it checks all the cron
# files in turn.
#
#  WARNING!
#
#  Spurious messages can occur; a more stringent method (if perhaps less
# careful of a check) would be to test just the 6th field, instead of
# all the fields after the fifth.  Also throwing away /tmp, etc. could
# be a mistake.
#
#  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
package main;
X
require 'getopts.pl';
require 'glob.pl';
require 'chk_strings.pl';
require 'pathconf.pl';
X
# should also add args to override default crontab locations
die "Usage: $0 [-rd]\n" unless &Getopts('rd') && !@ARGV;
X
$chk_strings'debug = $opt_d;
$chk_strings'recurse = $opt_r;
X
package cron_chk;
X
#  Possible location of crontab file:
$cron = "/usr/lib/crontab";
#  alternate reality locations of crontab file:
@alt_cron = ("/usr/spool/cron/crontabs");
X
if ( ! -s $cron) {
X    for (@alt_cron) {
X	# are there ever multiple crontab directories?
X	(@crons = &'glob("$_/*")), last if -d;
X    }
X    die "No crontabs?\n" if ! @crons;
}
@crons = ($cron) unless @crons;
X
# ignore /tmp /dev/null and tty stuff
# &'chk_strings ignores all of above
# STILL NEED to ignore stuff after `>'
#   when we add @ignore stuff to &'chk_strings
X
# finally, do the checking -- maybe for one, maybe for lots of cron-ites:
for (@crons) {
X    if (! -e) {
X	warn "$0: $_: $!\n";
X	next;
X    }
X    &'chk_strings($_);
}
X
1;
SHAR_EOF
chmod 0700 p-cops.alpha/cron.chk ||
echo 'restore of p-cops.alpha/cron.chk failed'
Wc_c="`wc -c < 'p-cops.alpha/cron.chk'`"
test 2399 -eq "$Wc_c" ||
	echo 'p-cops.alpha/cron.chk: original size 2399, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= p-cops.alpha/cops ==============
if test -f 'p-cops.alpha/cops' -a X"$1" != X"-c"; then
	echo 'x - skipping p-cops.alpha/cops (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting p-cops.alpha/cops (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/cops' &&
#!/bin/sh  # need to mention perl here to avoid recursion
#
#  Usage: cops [-v] [-c config file] [-s secure_dir] [architecture]
#
#  This will change into the $SECURE/architecture directory, suck lots
# of info and configuration stuff out of "cops.cf", and runs all of the
# security programs in that file.  If any of the programs find any 
# security problems, it either sends mail to everyone in the $SECURE_USERS
# list (see "cops.cf"), or saves the results in a file 
# $SECURE/architecture/hostname.  It then destroys all temporary files, 
# and exits the program.  Programs that are run (besides this one):
#
#	root.chk	dev.chk		group.chk
#	rc.chk		passwd.chk	is_able.chk
#	pass.chk 	user.chk	cron.chk
#	misc.chk	ftp.chk
#
#  This -v (verbose) flag prints out the name each program to 
# the results file as it is executed.  The -s and -c flags allow you
# to specify the $SECURE directory and $CONFIG file, respectively.
# 
# 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
######################################
# perl COPS main driver.
# tchrist@convex.com
######################################
X
# security sanity settings
#
$ENV{'IFS'} = '' if $ENV{'IFS'};
$ENV{'PATH'} = '/bin:/usr/bin:/usr/ucb';
$| = 1;
umask 077;
X
#
# Getopts stuff
$usage = "Usage: $0 [-v] [-c config_file] [-s secure_dir] architecture\n";
require 'getopts.pl';
# Process the command args; Either specify verbose or an alternate config file:
die $usage unless &Getopts('vc:s:');
X
if (defined($opt_v)) { $verbose = $opt_v;}
else { $verbose = 0; }
X
if (defined($opt_s)) { $SECURE = $LIBCOPS = $opt_s; }
else { $SECURE = $LIBCOPS = '.'; }
X
if (defined($opt_c)) { $CONFIG = $opt_c; }
else {$CONFIG = "$SECURE/cops.cf"; }
X
if (@ARGV > 1) {
X    die $usage;
} elsif (@ARGV == 1) {
X    $SECURE = shift;
X    die "Architecture directory $SECURE does not exist\n" unless -d $SECURE;
X    chdir($SECURE) || die "can't cd to $SECURE: $!";
X    exec './cops';
} 
X
# internal cops stuff needed
require "$LIBCOPS/pathconf.pl";
require "$LIBCOPS/is_able.pl";
X
chmod 0700, $SECURE;  
chdir ($SECURE) || die "Error -- Security directory $SECURE doesn't exist";
X
#  Read stuff to do from the config file
die "$0: Can't trust $CONFIG to reconfig!" 	if &'is_writable($CONFIG);
open CONFIG || die "can't open $CONFIG: $!";
X
&argh unless -s $CONFIG;
X
&init_result;
X
while (<CONFIG>) {
X    next if /^\s*#/;
X    next if /^\s*$/;
X
X    if (/^\s*[\$&\@\%]/) {  #  reset a config variable
X	s/#.*//;
X	eval;
X	warn "Bad config variable at line $. of $CONFIG:\n\t$_\t$@\n" if $@;
X	next;
X    } 
X
X    # must be a program to run
X    chop;
X    s/#.*//;
X    s/;$//;
X    @ARGV=split;
X    $program = shift;
X    if ($verbose) { print "\n******* $program *******\n\n"; }
X    &flush;
X    &run("$LIBCOPS/$program");
X    &flush;
X
} 
X
&save_result;
X
&argh unless $ran_something;
X
Xexit 0;
X
######################################################################
sub run {
X    local($module) = @_;
X    local($status);
X    local($0) = $module; # so it shows up in ps
X    local($!);
X
X
X    $ran_something++;
X
X    open(STDERR, $COPS_ERRORS ? ">&STDOUT" : ">/dev/null");
X
X    unless ($status = do $module) {
X	if ($@) {
X	    warn "cops: unexpected exit from $module:\n\t-> $@\n";
X	} elsif ($! != 0) {
X	    warn "cops: couldn't run $module: $!\n";
X	} else {
X	    warn "cops: $module returned $status\n";
X	} 
X    }
X
X    # hack for kuang, who doesn't write to STDOUT (yet!)
X    $SUCCESS = "$SECURE/Success";
X    if ($module =~ /^kuang/ && -e $SUCCESS) {
X	if (open SUCCESS) {
X	    print while <SUCCESS>;  # STDOUT is $REPORT
X	    close SUCCESS;
X	    unlink $SUCCESS;
X	} else {
X	    warn "can't open $SUCCESS: $!";
X	} 
X    } 
}
######################################################################
sub init_result {
X    $REPORT = "$SECURE/result.$$";  # global!
X    open (REPORT, ">$REPORT") || die "can't create $REPORT: $!";
X
X    # assume dups work
X    open (STDOUT, ">&REPORT");
X    open (SAVERR, ">&STDERR");
X    open (STDERR, ">&STDOUT");
X
X    ($sec, $min, $hour, $mday, $mon, $year,
X	$wday, $yday, $isdst) = localtime(time);
X
X    $name = sprintf("%s_%s_%s", $year + 1900, 
X	(Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec)[$mon],
X	$mday);
X
X    $host =  ( -x '/bin/hostname'   && `/bin/hostname` ) 
X	  || ( -x '/bin/uname'      && `/bin/uname -n` )
X	  || ( -x '/usr/bin/uuname' && `/usr/bin/uuname -l`)
X	  || 'Amnesiac!';
X
X    chop $host;
X    $host =~ s/\..*//;
X
X    print "ATTENTION:\nSecurity report for ", `date`;
X    print "\nfrom host $host, $name\n\n";
X    $report = $name;
X
X    &flush;
}
######################################################################
sub save_result {
X    open(STDERR, ">&SAVERR");
X
X    close REPORT || die "can't close $REPORT: $!";
X
X    $dir = "$SECURE/$host";
X    $report = $dir . "/" . $report;
X
X    mkdir($dir,0700) unless -d $dir;
X
X    if ($NMAIL) {
X	system "$MAIL $SECURE_USERS < $REPORT"
X	    unless $ONLY_DIFF && !&different($dir, $REPORT);
X    } else {
#	rename ($REPORT, $dir . "/" . $name) ||
#	    die "can't put $REPORT into $dir/$name: $!";
X	rename ($REPORT, $report) ||
X	    die "can't put $REPORT into $report: $!";
X    }
X    unlink $REPORT;
} 
X
######################################################################
sub different {
X    local($dir, $FILE1) = @_;
X    local($FILE2, $f1, $f2, $_);
X
X    open (LS, "$LS -t $dir |");
X    chop($FILE2 = <LS>);
X    close(LS); # snuff it out
X
X
X    return 1 if !$FILE2 || -s $FILE1 != -s $FILE2;
X
X    open FILE1 || die "can't open $FILE1: $!";
X    open FILE2 || die "can't open $FILE2: $!";
X
X    for (1..5) {
X	$_ = <FILE1>;
X	$_ = <FILE2>;
X    } 
X
X    while ( ($f1 = <FILE1>), ($f2 = <FILE2>) ) {
X	last if $f1 ne $f2;
X    } 
X
X    close FILE1;
X    close FILE2;
X
X    defined($f1) || defined($f2);
} 
X
######################################################################
sub flush {
X    local($old) = $|;
X    $| = 1;
X    print '';
X    $| = $old;
} 
X
sub argh {
X    die "Argh -- Can't find anything in $CONFIG\n";
}
SHAR_EOF
chmod 0700 p-cops.alpha/cops ||
echo 'restore of p-cops.alpha/cops failed'
Wc_c="`wc -c < 'p-cops.alpha/cops'`"
test 6390 -eq "$Wc_c" ||
	echo 'p-cops.alpha/cops: original size 6390, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= p-cops.alpha/fgrep.pl ==============
if test -f 'p-cops.alpha/fgrep.pl' -a X"$1" != X"-c"; then
	echo 'x - skipping p-cops.alpha/fgrep.pl (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting p-cops.alpha/fgrep.pl (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/fgrep.pl' &&
#
#  Just a quick perl fgrep...
#
package fgrep;
X
sub main'fgrep {
X    local($file, @exprs) = @_;
X    local(@list);
X
X    if (open file) {
X	$code = "while (<file>) {\n\tchop;\n";
X	for (@exprs) {
X	    $code .= "\tpush(\@list, \$_), next if m\201${_}\201;\n";
X	} 
X	$code .= "}\n";
X	warn "fgrep code is $code" if $debug;
X	eval $code;
X	warn "fgrep @exprs $file: $@\n" if $@;
X    } elsif ($debug) {
X	warn "main'fgrep: can't open $file: $!\n";
X    } 
X
X    @list;
} 
X
1;
SHAR_EOF
chmod 0700 p-cops.alpha/fgrep.pl ||
echo 'restore of p-cops.alpha/fgrep.pl failed'
Wc_c="`wc -c < 'p-cops.alpha/fgrep.pl'`"
test 463 -eq "$Wc_c" ||
	echo 'p-cops.alpha/fgrep.pl: original size 463, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= p-cops.alpha/file_mode.pl ==============
if test -f 'p-cops.alpha/file_mode.pl' -a X"$1" != X"-c"; then
	echo 'x - skipping p-cops.alpha/file_mode.pl (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting p-cops.alpha/file_mode.pl (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/file_mode.pl' &&
#
#  This retrieves a possibly cached mode on file.
# If it returns "BOGUS", it means that the stat failed.
#
# tchrist@convx.com
X
package main;
require 'stat.pl';
X
package file_mode;
X
sub main'Mode {
X    local($file) = @_;
X
X    if (!defined $modes{$file}) {
X       if (&'Stat($file)) {
X           $modes{$file} = $'st_mode;
X       } else {
X           $modes{$file} = 'BOGUS';
X       }
X    }
X    $modes{$file};
}
SHAR_EOF
chmod 0700 p-cops.alpha/file_mode.pl ||
echo 'restore of p-cops.alpha/file_mode.pl failed'
Wc_c="`wc -c < 'p-cops.alpha/file_mode.pl'`"
test 413 -eq "$Wc_c" ||
	echo 'p-cops.alpha/file_mode.pl: original size 413, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= p-cops.alpha/file_owner.pl ==============
if test -f 'p-cops.alpha/file_owner.pl' -a X"$1" != X"-c"; then
	echo 'x - skipping p-cops.alpha/file_owner.pl (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting p-cops.alpha/file_owner.pl (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/file_owner.pl' &&
#
#   This retrieves possibly cached owner of a file.
# If it returns "BOGUS", it means that the stat failed.
X
package main;
require 'stat.pl';
X
package file_owner;
X
sub main'Owner {
X    local($file) = @_;
X
X    if (!defined $owners{$file}) {
X       if (&'Stat($file)) {
X           $owners{$file} = $'st_uid;
X       } else {
X           $owners{$file} = 'BOGUS';
X       }
X    }
X    $owners{$file};
}
SHAR_EOF
chmod 0700 p-cops.alpha/file_owner.pl ||
echo 'restore of p-cops.alpha/file_owner.pl failed'
Wc_c="`wc -c < 'p-cops.alpha/file_owner.pl'`"
test 398 -eq "$Wc_c" ||
	echo 'p-cops.alpha/file_owner.pl: original size 398, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= p-cops.alpha/dev.chk ==============
if test -f 'p-cops.alpha/dev.chk' -a X"$1" != X"-c"; then
	echo 'x - skipping p-cops.alpha/dev.chk (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting p-cops.alpha/dev.chk (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/dev.chk' &&
#!/bin/sh  # need to mention perl here to avoid recursion
#
#  dev.chk [-g]
#
#   This shell script checks the permissions of all devs listed in the
# file /etc/fstab (the "mount" command would be a preferable way of
# getting the file system name, but the syntax of the output is variable
# from machine to machine), and flags them if they are readable by using
# the "is_readable" command.  It also checks for unrestricted NFS
# mountings.  By default, dev_check will flag devs only if world readable
# or writable.  The -g option tells it to print out devs that are also
# group readable/writable.
#   As an aside, the fact that NFS mounted dirs are world readable isn't
# a big deal, but they shouldn't be world writable.  So do two checks here,
# instead of one.
#
#  Two types of /etc/fstab formats I've seen so far:
#
# "old" --
#  spec:file:type:freq:passno:name:options
#      NFS are indicated by an "@"
#
# "new" --
#  fsname dir type opts freq passno
#      NFS are indicated by an ":"
#
#  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
#
#  dev.chk [-g]
# 
# tchrist@convx.com
#
X
require 'is_able.pl';
X
$MTAB    = '/etc/fstab' unless defined $MTAB;
$EXPORTS = '/etc/exports' unless defined $EXPORTS;
$TAB_STYLE = 'new' unless defined $TAB_STYLE;  # or 'old'  
X
&usage if @ARGV > 1;
X
sub usage { die "Usage: $0 [-g]\n"; }
X
if (@ARGV == 1) {
X    if ($ARGV[0] eq '-g') {
X	$group++;
X    } else {
X	&usage;
X    } 
} 
X
X
open MTAB || die "can't open $MTAB: $!";
X
while (<MTAB>) {
X    next if /^#/;
X    chop;
X    if ($TAB_STYLE eq 'new') {
X	($dev, $fs) = split;
X	next unless $fs;
X	if ($dev =~ /:/) {
X	    push(@nfs_devs, $fs);
X	} else {
X	    push(@local_devs, $dev);
X	} 
X    } else {
X	($dev, $fs) = split(/:/);
X	next unless $fs;
X	if ($dev =~ /@/) {
X	    push(@nfs_devs, $fs);
X	} else {
X	    push(@local_devs, $dev);
X	} 
X    } 
X
} 
X
if (open EXPORTS) {
X    while (<EXPORTS>) {
X	next if /^\s*#/;
X	next if /\S\s+\S/;
X	chop;
X	printf "Warning!  NFS file system $_ exported with no restrictions.\n";
X    } 
} 
X
# WARNING: we may hang if server down....
#
for (@nfs_devs, @local_devs) {
X    &is_able($_, 'w', 'w');
X    next unless $group;
X    &is_able($_, 'g', 'w');
} 
X
for (@local_devs) {
X    &is_able($_, 'w', 'r');
X    next unless $group;
X    &is_able($_, 'g', 'r');
} 
X
1;
SHAR_EOF
chmod 0700 p-cops.alpha/dev.chk ||
echo 'restore of p-cops.alpha/dev.chk failed'
Wc_c="`wc -c < 'p-cops.alpha/dev.chk'`"
test 2739 -eq "$Wc_c" ||
	echo 'p-cops.alpha/dev.chk: original size 2739, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= p-cops.alpha/getopts.pl ==============
if test -f 'p-cops.alpha/getopts.pl' -a X"$1" != X"-c"; then
	echo 'x - skipping p-cops.alpha/getopts.pl (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting p-cops.alpha/getopts.pl (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/getopts.pl' &&
;# getopts.pl - a better getopt.pl
X
;# Usage:
;#      do Getopts('a:bc');  # -a takes arg. -b & -c not. Sets opt_* as a
;#                           #  side effect.
X
sub Getopts {
X    local($argumentative) = @_;
X    local(@args,$_,$first,$rest,$errs);
X    local($[) = 0;
X
X    @args = split( / */, $argumentative );
X    while(($_ = $ARGV[0]) =~ /^-(.)(.*)/) {
X	($first,$rest) = ($1,$2);
X	$pos = index($argumentative,$first);
X	if($pos >= $[) {
X	    if($args[$pos+1] eq ':') {
X		shift(@ARGV);
X		if($rest eq '') {
X		    $rest = shift(@ARGV);
X		}
X		eval "\$opt_$first = \$rest;";
X	    }
X	    else {
X		eval "\$opt_$first = 1";
X		if($rest eq '') {
X		    shift(@ARGV);
X		}
X		else {
X		    $ARGV[0] = "-$rest";
X		}
X	    }
X	}
X	else {
X	    print STDERR "Unknown option: $first\n";
X	    ++$errs;
X	    if($rest ne '') {
X		$ARGV[0] = "-$rest";
X	    }
X	    else {
X		shift(@ARGV);
X	    }
X	}
X    }
X    $errs == 0;
}
X
1;
SHAR_EOF
chmod 0700 p-cops.alpha/getopts.pl ||
echo 'restore of p-cops.alpha/getopts.pl failed'
Wc_c="`wc -c < 'p-cops.alpha/getopts.pl'`"
test 902 -eq "$Wc_c" ||
	echo 'p-cops.alpha/getopts.pl: original size 902, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= p-cops.alpha/glob.pl ==============
if test -f 'p-cops.alpha/glob.pl' -a X"$1" != X"-c"; then
	echo 'x - skipping p-cops.alpha/glob.pl (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting p-cops.alpha/glob.pl (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/glob.pl' &&
#
#  This does shell or perl globbing without resorting
# to the shell -- we were having problems with the shell blowing
# up with extra long pathnames and lots of file names.  set $glob'debug 
# for trace information.
#
# tom christiansen <tchrist@convex.com>
X
package glob;
X
sub main'glob { 
X    local($expr) = @_;
X    local(@files);
X
X    $? = 0;
X    open(SAVERR, ">&STDERR"); close(STDERR);  # suppress args too long
X    @files = <${expr}>;
X    if ($?) {
X	print SAVERR "shell glob blew up on $expr\n" if $debug;
X	@files = &SHglob($expr);
X    }
X    open (STDERR, ">&SAVERR");
X    # if (@files == 1 && $files[0] eq $expr) { @files = ''; } # sh foo
X    @files;
}
X
sub main'SHglob {
X    local($expr) = @_;
X    local(@retlist) = ();
X    local($dir);
X
X    printf "SHglob: globbing $expr\n" if $debug;
X
X    $expr =~ s/([.{+\\])/\\$1/g;
X    $expr =~ s/\*/.*/g;
X    $expr =~ s/\?/./g;
X
X    for $dir (split(' ',$expr)) {
X	push(@retlist, &main'REglob($dir));
X    } 
X
X    return sort @retlist;
} 
X
sub main'REglob {
X    local($path) = @_;
X    local($_);
X    local(@retlist) = ();
X    local($root,$expr,$pos);
X    local($relative) = 0;
X    local(@dirs);
X    local($user);
X
X    $haveglobbed = 0;
X
X    @dirs = split(/\/+/, $path);
X
X    if ($dirs[$[] =~ m!~(.*)!) {
X	$dirs[$[] = &homedir($1);
X	return @retlist unless $dirs[$[];
X    } elsif ($dirs[$[] eq '') {
X	$dirs[$[] = '/' unless $dirs[$[] =~ m!^\.{1,2}$!;
X    } else {
X	unshift(@dirs, '.');
X	$relative = 1;
X    } 
X
X    printf "REglob: globbing %s\n", join('/',@dirs) if $debug;
X
X    @retlist = &expand(@dirs);
X
X    for (@retlist) {
X	if ($relative) {
X	    s!^\./!!o;
X	}
X	s!/{2,}!/!g;
X    } 
X
X    return sort @retlist;
}
X
sub expand {
X    local($dir, $thisdir, @rest) = @_;
X    local($nextdir);
X    local($_);
X    local(@retlist) = ();
X    local(*DIR);
X
X    unless ($haveglobbed || $thisdir =~ /([^\\]?)[?.*{[+\\]/ && $1 ne '\\') {
X	@retlist = ($thisdir);
X    } else {
X	unless (opendir(DIR,$dir)) {
X	    warn "glob: can't opendir $dir: $!\n" if $debug;
X	} else {
X		@retlist = grep(/^$thisdir$/,readdir(DIR));
X		@retlist = grep(!/^\./, @retlist) unless $thisdir =~ /^\\\./;
X		$haveglobbed++;
X	} 
X	closedir DIR;
X    } 
X
X    for (@retlist) {
X	$_ = $dir . '/' . $_;
X    }
X
X    if ($nextdir = shift @rest) {
X	local(@newlist) = ();
X	for (@retlist) {
X	    push(@newlist,&expand($_,$nextdir,@rest));
X	} 
X	@retlist = @newlist;
X    } 
X
X    return @retlist;
} 
X
sub homedir {
X    local($user) = @_;
X    local(@pwent);
X    # global %homedir
X
X    if (!$user) {
X	return $ENV{'HOME'} 		if $ENV{'HOME'};
X	($user = $ENV{'USER'})  	|| 
X	    ($user = getlogin) 		|| 
X	    (($user) = getpwnam($>));
X	warn "glob'homedir: who are you, user #$>?" unless $user;
X	return '/';
X    } 
X    unless (defined $homedir{$user}) {
X	if (@pwent = getpwnam($user)) {
X	    $homedir{$user} = $pwent[$#pwent - 1];
X	} else {
X	    warn "glob'homedir: who are you, user #$>?" unless $user;
X	    $homedir{$user} = '/';
X	}
X    }
X    return $homedir{$user};
} 
X
X
1;
SHAR_EOF
chmod 0700 p-cops.alpha/glob.pl ||
echo 'restore of p-cops.alpha/glob.pl failed'
Wc_c="`wc -c < 'p-cops.alpha/glob.pl'`"
test 2960 -eq "$Wc_c" ||
	echo 'p-cops.alpha/glob.pl: original size 2960, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= p-cops.alpha/group.chk ==============
if test -f 'p-cops.alpha/group.chk' -a X"$1" != X"-c"; then
	echo 'x - skipping p-cops.alpha/group.chk (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting p-cops.alpha/group.chk (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/group.chk' &&
#!/bin/sh  # need to mention perl here to avoid recursion
#
#   group.chk.pl
#
#  Check group file -- /etc/group -- for incorrect number of fields,
# duplicate groups, non-alphanumeric group names, and non-numeric group
# id's.
#
#   Mechanism:  Group.check uses awk to ensure that each line of the group
# has 4 fields, as well as examining each line for any duplicate groups or
# any duplicate user id's in a given group by using "sort -u" to ferret
# out any duplications.  It also checks to make sure that the password
# field (the second one) is a "*", meaning the group has no password (a
# group password is usually not necessary because each member listed on 
# the line has all the privilages that the group has.)  All results are
# echoed to standard output.  Finally it ensures that the group names
# are alphanumeric, that the group id's are numeric, and that there are
# no blank lines.  For yellow pages groups, it does the same checking,
# but in order to get a listing of all members of the groups, it does a
# "ypcat group".
#
#   The /etc/group file has a very specific format, making the task
# fairly simple.  Normally it has lines with 4 fields, each field
# separated by a colon (:).  The first field is the group name, the second
# field is the encrypted password (an asterix (*) means the group has no
# password, otherwise the first two characters are the salt), the third
# field is the group id number, and the fourth field is a list of user
# ids in the group.  If a line begins with a plus sign (+), it is a yellow
# pages entry.  See group(5) for more information.
#
# 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
# should get below config stuff from cops.cf file
package main;
X
die "Usage: $0\n" if @ARGV;
X
require 'pathconf.pl';
X
#   Used for Sun C2 security group file.  FALSE (default) will flag
# valid C2 group syntax as an error, TRUE attempts to validate it.
# Thanks to Pete Troxell for pointing this out.
#
# moved to cops.cf
X
$etc_group=$GROUP || '/etc/group';
X
package group_chk;
X
$yptmp = "./yptmp.$$";
X
# Testing $etc_group for potential problems....
open (Group, "< $'etc_group") || warn "$0: Can't open $'etc_group: $!\n";
&chk_group_file_format('Group');
close Group;
X
# Testing ypcat group for potential problems
$yp=0;
if (-s $'YPCAT && -x _) {
X    system("$'YPCAT group >$yptmp 2>/dev/null");  
X    if (-s $yptmp) {
X       open(YGroup, "$'YPCAT group |") || die "$0: Can't popen $'YPCAT: $!\n";
X       &chk_group_file_format('YGroup');
X       close(YGroup);
X       $yp=1;
X   }
}
X
# usage: &chk_group_file_format('Filehandle-name');
# skips over lines that begin with "+:"
# It really should check for correct yellow pages syntax....
#
# this routine checks lines read from a filehandle for potential format
# problems .. should be matching group(5)
#
# checks for duplicate users in a group as it reads the lines instead
# of after (as the original shell script does)
X
sub chk_group_file_format {
X    local($file) = @_;
X    local($W) = "Warning!  $file file,";
X
X    while (<$file>) {
X	next if /^\+:/;
X	print "$W line $., is blank\n" if /^\s*$/;
X	split(/:/);
X	$groups{$_[0]}++;   # keep track of dups
X	print "$W line $., does not have 4 fields: $_" if (@_ != 4);
X	print "$W line $., nonalphanumeric group name: $_"
X	    if $_[0] !~ /^[A-Za-z0-9-]+$/;
X	if ($_[1] && $_[1] ne '*') {
X	    if ( ! $C2 || $yp ) {
X		print "$W line $., group has password: $_"
X		    if length($_[1]) == 13;
X	    } else {
X		print "$W line $., group has invalid field for C2:\n$_"
X		    if $_[1] ne "#\$$_[0]";
X	    }
X	}
X	print "$W line $., nonnumeric group id: $_" if $_[2] !~ /^\d+$/;
X
X	# look for duplicate users in a group
X	# kinda ugly, but it works .. and I have too much other work right
X	# now to clean it up.  maybe later.. ;-)
X	chop($_[3]);	# down here, 'cos split gets rid of final null fields
X	@users = sort split(/\s*,\s*/, $_[3]);
X	# %users = # of times user is in group, $dup_user = duplicate found
X	undef %users;  $dup_user=0;
X	grep(!$users{$_}++, @users);
X	for (keys %users) {
X	    (print "Warning!  Group $_[0] has duplicate user(s):\n"),
X		$dup_user=1 if !$dup_user && $users{$_} > 1;
X	    print "$_ " if $users{$_} > 1;
X	}
X	print "\n" if $dup_user;
X
X    }
X    # find duplicate group names 
X    # not the best way, but it works..
X    # boy, this is ugly too .. but, not as bad as above.. :)
X    $dup_warned = 0;
X    for (sort keys %groups) {
X	(print "Warning!  Duplicate Group(s) found in $file:\n"), $dup_warned++
X	    if !$dup_warned && $groups{$_} > 1;
X	print "$_ " if $groups{$_} > 1;
X    }
X    print "\n" if $dup_warned;
}
X
unlink $yptmp;
X
1;
# end
SHAR_EOF
chmod 0700 p-cops.alpha/group.chk ||
echo 'restore of p-cops.alpha/group.chk failed'
Wc_c="`wc -c < 'p-cops.alpha/group.chk'`"
test 5060 -eq "$Wc_c" ||
	echo 'p-cops.alpha/group.chk: original size 5060, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= p-cops.alpha/hostname.pl ==============
if test -f 'p-cops.alpha/hostname.pl' -a X"$1" != X"-c"; then
	echo 'x - skipping p-cops.alpha/hostname.pl (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting p-cops.alpha/hostname.pl (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/hostname.pl' &&
#
# file: hostname.pl
# usage: $hostname = &'hostname;
#
# purpose: get hostname -- try method until we get an answer 
#	or return "Amnesiac!"
#
X
package hostname;
X
sub main'hostname {
X    if (!defined $hostname) {
X	$hostname =  ( -x '/bin/hostname'   && `/bin/hostname` ) 
X		  || ( -x '/bin/uname'      && `/bin/uname -n` )
X		  || ( -x '/usr/bin/uuname' && `/usr/bin/uuname -l`)
X		  || 'Amnesiac!';
X	chop $hostname;
X    }
X    $hostname;
}
X
1;
SHAR_EOF
chmod 0700 p-cops.alpha/hostname.pl ||
echo 'restore of p-cops.alpha/hostname.pl failed'
Wc_c="`wc -c < 'p-cops.alpha/hostname.pl'`"
test 444 -eq "$Wc_c" ||
	echo 'p-cops.alpha/hostname.pl: original size 444, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= p-cops.alpha/is_able.chk ==============
if test -f 'p-cops.alpha/is_able.chk' -a X"$1" != X"-c"; then
	echo 'x - skipping p-cops.alpha/is_able.chk (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting p-cops.alpha/is_able.chk (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/is_able.chk' &&
#!/bin/sh  # need to mention perl here to avoid recursion
#
#  is_able.chk
#
#   This shell script checks the permissions of all files and directories
# listed in the configuration file "is_able.lst", and prints warning messages
# according to the status of files.  You can specify world or group readability
# or writeability.  See the config file for the format of the configuration
# file.
#
#   Mechanism:  This shell script parses each line from the configure file
# and uses the "is_able.pl" program to check if any of
# the directories in question are writable by world/group.
#
# 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;
require 'is_able.pl';
require 'file_mode.pl';
require 'glob.pl';
X
if ($ARGV[0] eq '-d') {
X    shift;
X    $debug = $glob'debug = 1;  # maybe should turn off glob'debug afterwards
}
X
unshift (@ARGV, "is_able.lst" ) unless @ARGV;
X
while (<>) {
X    next if /^\s*#/;
X    split;
X    next unless @_ == 3;
X    ($file, $x, $y) = @_;
X    @files = $file =~ /[\[?*]/ ? &'glob($file) : ($file);
X    for $file (@files) {
X	print STDERR "is_able $file $x $y\n" if $debug;
X	&'is_able($file, $x, $y);
X    }
}
X
1;
SHAR_EOF
chmod 0700 p-cops.alpha/is_able.chk ||
echo 'restore of p-cops.alpha/is_able.chk failed'
Wc_c="`wc -c < 'p-cops.alpha/is_able.chk'`"
test 1591 -eq "$Wc_c" ||
	echo 'p-cops.alpha/is_able.chk: original size 1591, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= p-cops.alpha/is_able.lst ==============
if test -f 'p-cops.alpha/is_able.lst' -a X"$1" != X"-c"; then
	echo 'x - skipping p-cops.alpha/is_able.lst (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting p-cops.alpha/is_able.lst (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/is_able.lst' &&
#  This lists any/all sensitive files the administration wants to ensure
# non-read/writability of.  Comments are lines starting with a "#".
#
# USE FULL PATHNAMES!
#
#   Lines are of the format:
#
# /path/to/{dir|file}	World/Group	Read/Write/Both
#
# as above		{w|g}		{r|w|b}
#
/			w		w
/etc			w		w
/usr			w		w
/bin			w		w
/dev			w		w
/usr/bin		w		w
/usr/etc		w		w
/usr/adm		w		w
/usr/lib		w		w
/usr/spool		w		w
/usr/spool/mail		w		w
/usr/spool/news		w		w
/usr/spool/uucp		w		w
/usr/spool/at		w		w
/usr/local		w		w
/usr/local/bin		w		w
/usr/local/lib		w		w
/usr/users		w		w
/Mail			w		w
X
# some Un*x's put shadowpass stuff here:
/etc/security		w		r
X
# /.login /.profile /.cshrc /.rhosts
/.*			w		w
X
#   I think everything in /etc should be !world-writable, as a rule; but
# if you're selecting individual files, do at *least* these:
#   /etc/passwd /etc/group /etc/inittab /etc/rc /etc/rc.local /etc/rc.boot
#   /etc/hosts.equiv /etc/profile /etc/syslog.conf /etc/export /etc/utmp
#   /etc/wtmp
/etc/*			w		w
X
/bin/*			w		w
/usr/bin/*		w		w
/usr/etc/*		w		w
/usr/adm/*		w		w
/usr/lib/*		w		w
/usr/local/lib/*	w		w
/usr/local/bin/*	w		w
/usr/etc/yp*		w		w
/usr/etc/yp/*		w		w
X
# individual files:
/usr/lib/crontab	w		b
/usr/lib/aliases	w		w
/usr/lib/sendmail	w		w
/usr/spool/uucp/L.sys	w		b
X
#  NEVER want these readable!
/dev/kmem		w		b
/dev/mem		w		b
X
#   Optional List of assorted files that shouldn't be
# write/readable (mix 'n match; add to the list as desired):
/usr/adm/sulog		w		r
/.netrc			w		b
# HP-UX and others:
/etc/btmp		w		b
/etc/securetty		w		b
# Sun-fun
/dev/drum		w		b
/dev/nit		w		b
SHAR_EOF
chmod 0700 p-cops.alpha/is_able.lst ||
echo 'restore of p-cops.alpha/is_able.lst failed'
Wc_c="`wc -c < 'p-cops.alpha/is_able.lst'`"
test 1603 -eq "$Wc_c" ||
	echo 'p-cops.alpha/is_able.lst: original size 1603, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= p-cops.alpha/is_able.pl ==============
if test -f 'p-cops.alpha/is_able.pl' -a X"$1" != X"-c"; then
	echo 'x - skipping p-cops.alpha/is_able.pl (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting p-cops.alpha/is_able.pl (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/is_able.pl' &&
#
#  (This takes the place of the C program is_able.c, BTW.)
# 
#  is_able filename {w|g|s|S}       {r|w|B|b|s}
#      (world/group/SUID/SGID   read/write/{read&write}/{suid&write}/s[ug]id)
# 
#     The second arg of {r|w} determines whether a file is (group or world
#   depending on the first arg of {w|g}) writable/readable, or if it is
#   SUID/SGID (first arg, either s or S, respectively), and prints out a
#   short message to that effect.
# 
#  So:
#     is_able w w		# checks if world writable
#     is_able g r		# checks if group readable
#     is_able s s		# checks if SUID
#     is_able S b		# checks if world writable and SGID
X
package main;
require 'file_mode.pl';
X
package is_able;
X
# package statics
#
%wg = ( 
X	'w', 00006,
X	'g', 00060,
X	's', 04000,
X	'S', 02000,
X       );
X
%rwb= (
X	'r', 00044,
X	'w', 00022,
X	'B', 00066,
X	'b', 04022,
X	's', 06000,
X      );
X
$silent = 0;  # for suppressing diagnostic messages
X
X
sub main'is_able {
X    local($file, $wg, $rwb) = @_;
X
X    local ( 
X	   $mode, 			# file mode
X           $piece,			# 1 directory component
X	   @pieces, 			# all the pieces
X	   @dirs, 			# all the directories
X	   $p, 				# punctuation; (*) mean writable
X	   				#       due to writable parent
X	   $retval,			# true if vulnerable
X	   $[				# paranoia
X	  );
X
X    &usage, return undef	if @_ != 3 || $file eq '';
X
X    &usage, return undef	unless defined $wg{$wg} && defined $rwb{$rwb};
X
X    if (&'Mode($file) eq 'BOGUS' && $noisy) {
X	warn "is_able: can't stat $file: $!\n";
X	return undef;
X    }
X
X    $retval = 0;
X
X    if ($rwb{$rwb} & $rwb{'w'}) {
X	@pieces = split(m#/#, $file);
X	for ($i = 1; $i <= $#pieces; $i++) {
X	    push(@dirs, join('/', @pieces[0..$i]));
X	}
X    } else {
X	@dirs = ( $file );
X    } 
X
X    for $piece ( reverse @dirs ) {
X
X	next unless $mode = &'Mode($piece);
X	next if $mode eq 'BOGUS';
X
X	next unless $mode &= 07777 & $wg{$wg} & $rwb{$rwb};
X
X	$retval = 1;
X
X	$p = $piece eq $file ? '!' : '! (*)';
X
X	$parent_is_writable = $p eq '! (*)'; # for later
X
X	next if $silent; # for &is_writable
X
X	print "Warning!  $file is group readable$p\n"	if $mode & 00040; 
X	print "Warning!  $file is _World_ readable$p\n"	if $mode & 00004; 
X	print "Warning!  $file is group writable$p\n"	if $mode & 00020; 
X	print "Warning!  $file is _World_ writable$p\n"	if $mode & 00002; 
X	print "Warning!  $file is SUID!\n"		if $mode & 04000; 
X	print "Warning!  $file is SGID!\n"		if $mode & 02000; 
X
X	last if $piece ne $file;  # only complain on first writable parent
X    }
X    $retval;
}
X
sub main'is_writable {
X    local($silent) = 1;
X    &'is_able($_[0], 'w', 'w') 
X	? $parent_is_writable 
X	     ? "writable (*)"
X	     : "writable" 
X	: 0;
} 
X
sub main'is_readable {
X    local($silent) = 1;
X    &'is_able($_[0], 'w', 'r');
}
X
sub usage { 
X    warn <<EOF;
Usage: is_able file {w|g|S|s} {r|w|B|b|s}
X (not: is_able @_)
EOF
}
X
1;
SHAR_EOF
chmod 0700 p-cops.alpha/is_able.pl ||
echo 'restore of p-cops.alpha/is_able.pl failed'
Wc_c="`wc -c < 'p-cops.alpha/is_able.pl'`"
test 2835 -eq "$Wc_c" ||
	echo 'p-cops.alpha/is_able.pl: original size 2835, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= p-cops.alpha/kuang ==============
if test -f 'p-cops.alpha/kuang' -a X"$1" != X"-c"; then
	echo 'x - skipping p-cops.alpha/kuang (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting p-cops.alpha/kuang (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'p-cops.alpha/kuang' &&
#! /usr/local/bin/perl
X
#
# kuang - rule based analysis of Unix security
#
# Perl version by Steve Romig of the CIS department, The Ohio State
# University, October 1990. 
# 
# Based on the shell script version by Dan Farmer from his COPS
# package, which in turn is based on a shell version by Robert
# Baldwin. 
#
#-----------------------------------------------------------------------------
# Players:
#	romig	Steve Romig, romig@cis.ohio-state.edu
#	tjt	Tim Tessin, tjt@cirrus.com
#
# History:
# 4/25/91  tjt, romig	Various fixes to filewriters (better messages about 
#			permission problems) and don't update the DBM cache 
#			with local file info.
# 11/1/90  romig	Major rewrite - generic lists, nuking get_entry 
#			and put_entry, moved rules to separate file.
#
X
#
# Options
#
# -l		list uid's that can access the given target, directly
#		or indirectly
# -d		debug
# -v 		verbose
#
# -k file	load the list of known CO's
# -f file	preload file information from the named file.
# -p file	preload passwd info from the named file.
# -P		preload passwd info from ypcat + /etc/passwd
# -g group	preload group info from the named file.
# -G		preload group info from ypcat + /etc/group
# 
X
$options = "ldvk:p:g:f:PG";
$usage = "usage: kuang [-l] [-d] [-v] [-k known] [-f file] [-P] [-G] [-p passwd] [-g group] [u.username|g.groupname]\n";
X
$add_files_to_cache = 1;		# Whether to update the %files cache
X					# with local file info or not.
X
#
# Terminology:
#
#   An "op" is an operation, such as uid, gid, write, or replace. 
#   'uid' means to gain access to some uid, 'gid' means to gain access 
#   to some gid.  'write' and 'replace' refer to files - replace means
#   that we can delete a file and replace it with a new one somehow
#   (for example, if we could write the directory it is in).
#
#   An object is a uid, gid or pathname.  
#
#   A Controlling Operation (CO) is a (operation, object) pair
#   represented as "op object": "uid 216" (become uid 216) or "replace
#   /.rhosts" (replace file /.rhosts).  These are represented
#   internally as "c value", where "c" is a character representing an
SHAR_EOF
true || echo 'restore of p-cops.alpha/kuang failed'
fi
echo 'End of alpha p-cops part 1'
echo 'File p-cops.alpha/kuang is continued in part 2'
echo 2 > _shar_seq_.tmp
exit 0