alpha perl-cops, part 1 of 3
Dan Farmer
df at sei.cmu.edu
Fri May 17 14:46:08 AEST 1991
Submitted-by: df at 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 at 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 at 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 at 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 at 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 at 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 at 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 at 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 at 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('/', at 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, at 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 at cis.ohio-state.edu
# tjt Tim Tessin, tjt at 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
More information about the Alt.sources
mailing list