[alt.sources] cops 4 of 8

df@sei.cmu.edu (Dan Farmer) (01/08/91)

#!/bin/sh
# This is part 04 of cops
# ============= cops/init_kuang ==============
if test ! -d 'cops'; then
    echo 'x - creating directory cops'
    mkdir 'cops'
fi
if test -f 'cops/init_kuang' -a X"$1" != X"-c"; then
	echo 'x - skipping cops/init_kuang (File already exists)'
else
echo 'x - extracting cops/init_kuang (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'cops/init_kuang' &&
X# /* Copyright 1985 Robert W. Baldwin */
X# /* Copyright 1986 Robert W. Baldwin */
X###############################################
X# Kuang: Rule based computer security checker.
X###############################################
X
XCAT=/bin/cat
XECHO=/bin/echo
X
X#
X# Initialization.
X#
X./clearfiles
X#
X# First setup what we have access to.
X# The uids.k file must include the user 'OTHER' meaning the world access bits.
X# Add any other UIDs accessible to the attacker (e.g., ftp, daemon).
X#
X# Directly accessible user IDs.
X$CAT >uids.k <<END
XOTHER
XEND
X#
X# Directly accessible group IDs.
X# This usually includes a group like 'users', which most users are in.
X#
X$CAT >gids.k <<END
XEND
X#
X# Setup the primary goal(s).
X#
X$ECHO Setting up goal						#>/dev/tty
X./addto uids root DO ANYTHING
SHAR_EOF
chmod 0700 cops/init_kuang ||
echo 'restore of cops/init_kuang failed'
Wc_c="`wc -c < 'cops/init_kuang'`"
test 773 -eq "$Wc_c" ||
	echo 'cops/init_kuang: original size 773, current size' "$Wc_c"
fi
# ============= cops/is_able.chk ==============
if test -f 'cops/is_able.chk' -a X"$1" != X"-c"; then
	echo 'x - skipping cops/is_able.chk (File already exists)'
else
echo 'x - extracting cops/is_able.chk (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'cops/is_able.chk' &&
X:
X#
X#  is_able.chk
X#
X#   This shell script checks the permissions of all files and directories
X# listed in the configuration file "is_able.lst", and prints warning messages
X# according to the status of files.  You can specify world or group readability
X# or writeability.  See the config file for the format of the configuration
X# file.
X#
X#   Mechanism:  This shell script parses each line from the configure file
X# and uses the "is_{read|write}able" program to check if any of
X# the directories in question are writable by world/group.  All results
X# are written to standard output.
X#
XTEST=/bin/test
XECHO=/bin/echo
XAWK=/bin/awk
XSED=/bin/sed
X
Xconfig_file=is_able.lst
X
Xif $TEST ! -f "$config_file" ; then
X	$ECHO "Config file $config_file doesn't exist!"
X	exit
X	fi
X
X#  Read from $dir_list (e.g. "is.chk.lst") what files/dirs to check.
X#
X# Comments are lines starting with a "#".
X#
X# /path/to/{dir|file}   World/Group     Read/Write/Both
X# as above              {W|w|G|g}       {R|r|W|w|B|b}
X#
X{
X$AWK '/^#/ {next;} \
X	{ world=group=read=write=both=0; \
X	# need 3 fields, or format error
X	if (NF != 3) next; \
X	if ($2 != "W" && $2 != "w" && $2 != "G" && $2 != "g") next; \
X	if ($3!="R"&&$3!="r"&&$3!="W"&&$3!="w"&&$3!="B"&&$3!="b") next; \
X	for (f=1;f < NF; f++) printf("%s ", $f); \
X	print $NF;
X	}' $config_file
X} |
Xwhile read targets
X	do
X	#   Use sed, 'cause awk lets me down (line too long) -- then realize
X	# I should have used sed anyway.  Lazy bum.
X	foo=`echo "$targets" | $SED 's/\(.*\)....$/\1/'`
X	args=`echo $targets | $SED 's/.*\(...\)$/\1/'`
X	for f in $foo
X		do
X#		echo $f $args
X		./is_able $f $args
X		done
X	done
X
X# end of script
SHAR_EOF
chmod 0700 cops/is_able.chk ||
echo 'restore of cops/is_able.chk failed'
Wc_c="`wc -c < 'cops/is_able.chk'`"
test 1637 -eq "$Wc_c" ||
	echo 'cops/is_able.chk: original size 1637, current size' "$Wc_c"
fi
# ============= cops/is_able.lst ==============
if test -f 'cops/is_able.lst' -a X"$1" != X"-c"; then
	echo 'x - skipping cops/is_able.lst (File already exists)'
else
echo 'x - extracting cops/is_able.lst (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'cops/is_able.lst' &&
X#  This lists any/all sensitive files the administration wants to ensure
X# non-read/writability of.  Comments are lines starting with a "#".
X#
X#   Lines are of the format:
X#
X# /path/to/{dir|file}	World/Group	Read/Write/Both
X#
X# as above		{w|g}		{r|w|b}
X#
X/			w		w
X/etc			w		w
X/usr			w		w
X/bin			w		w
X/usr/bin		w		w
X/usr/etc		w		w
X/usr/adm		w		w
X/usr/lib		w		w
X/usr/spool		w		w
X/usr/spool/mail		w		w
X/usr/spool/news		w		w
X/usr/spool/uucp		w		w
X/usr/spool/at		w		w
X/usr/local		w		w
X/usr/local/bin		w		w
X/usr/local/lib		w		w
X/usr/users		w		w
X/Mail			w		w
X
X# some Un*x's put shadowpass stuff here:
X/etc/security		w		r
X
X# /.login /.profile /.cshrc /.rhosts
X/.*			w		w
X
X#   I think everything in /etc should be !world-writable, as a rule; but
X# if you're selecting individual files, do at *least* these:
X#   /etc/passwd /etc/group /etc/inittab /etc/rc /etc/rc.local /etc/rc.boot
X#   /etc/hosts.equiv /etc/profile /etc/syslog.conf /etc/export /etc/utmp
X#   /etc/wtmp
X/etc/*			w		w
X
X/bin/*			w		w
X/usr/bin/*		w		w
X/usr/etc/*		w		w
X/usr/adm/*		w		w
X/usr/lib/*		w		w
X/usr/local/lib/*	w		w
X/usr/local/bin/*	w		w
X/usr/etc/yp*		w		w
X
X# individual files:
X/usr/lib/crontab	w		b
X/usr/lib/aliases	w		w
X/usr/lib/sendmail	w		w
X/usr/spool/uucp/L.sys	w		b
X
X#  NEVER want these readable!
X/dev/kmem		w		b
X/dev/mem		w		b
X
X#   Optional List of assorted files that shouldn't be
X# write/readable (mix 'n match; add to the list as desired):
X/usr/adm/sulog		w		r
X/.netrc			w		b
X# HP-UX and others:
X/etc/btmp		w		b
X/etc/securetty		w		b
X# Sun-fun
X/dev/drum		w		b
X/dev/nit		w		b
SHAR_EOF
chmod 0600 cops/is_able.lst ||
echo 'restore of cops/is_able.lst failed'
Wc_c="`wc -c < 'cops/is_able.lst'`"
test 1547 -eq "$Wc_c" ||
	echo 'cops/is_able.lst: original size 1547, current size' "$Wc_c"
fi
# ============= cops/kuang ==============
if test -f 'cops/kuang' -a X"$1" != X"-c"; then
	echo 'x - skipping cops/kuang (File already exists)'
else
echo 'x - extracting cops/kuang (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'cops/kuang' &&
X:
X# /* Copyright 1985 Robert W. Baldwin */
X# /* Copyright 1986 Robert W. Baldwin */
X#
X# Jan 1990, Ported to bourne shell from Csh.  Dan Farmer
X#
X#   Took out some comments, combined four of Bob's shell
X# scripts into one (the target script remains separate for
X# easy editing of targets.)  More or less a straight line
X# for line translation; a rewrite that goes for speed will
X# come later.  Maybe just rewrite it in C.  Yeah, that's it....
X
X###############################################
X# Kuang: Rule based computer security checker.
X###############################################
X
X# commands used....
XSH=/bin/sh
XMV=/bin/mv
XTEST=/bin/test
XECHO=/bin/echo
XAWK=/bin/awk
XRM=/bin/rm
X
X# Initialization.
X$SH ./init_kuang
X
X# Main loop
X#
X$ECHO Starting main loop                        #>/dev/tty
Xwhile $TEST -f uids.n -o -f gids.n -o -f files.n
X    do
X    if $TEST -f uids.n ; then
X        $MV uids.n uids.x
X
X# Process a list of uids from stdin.
X# Usage: douids username comments
X    $ECHO Called douids                        #>/dev/tty
X    i=1
X    while $TEST "1"
X        do
X        nextuid=`$AWK '{if (NR=="'$i'") print $0}' uids.x`
X        i=`expr $i + 1`
X
X        if $TEST -z "$nextuid"  ; then
X            break;
X	    fi
X
X            user=`$ECHO $nextuid | $AWK '{print $1}'`
X
X        $ECHO "   " User $user                    #>/dev/tty
X
X# Rules mapping uids to files.
X#
X        next=`$ECHO $nextuid | $AWK '{for (i=2;i<=NF;i++) printf("%s ", $i)}'`
X        ./addto files /etc/passwd replace grant $user $next
X        ./addto files /usr/lib/aliases replace trojan $user $next
X
X#   hsh = home sweet home = home directory of $user
X        hsh=`./tilde $user`
X
X        if $TEST -f $hsh/.rhosts ;  then
X            ./addto files $hsh/.rhosts write grant $user $next
X        fi
X
X        if $TEST -f $hsh/.login ;  then
X            ./addto files $hsh/.login replace trojan $user $next
X        fi
X
X        if $TEST -f $hsh/.cshrc ;  then
X            ./addto files $hsh/.cshrc replace trojan $user $next
X        fi
X
X        if $TEST -f $hsh/.profile ;  then
X            ./addto files $hsh/.profile replace trojan $user $next
X        fi
X
X        if $TEST "$user" = "root" ;  then
X	    if $TEST -f /usr/lib/crontab ; then
X               ./addto files /usr/lib/crontab replace create supershell $next
X	    else
X               ./addto files /usr/spool/cron/crontabs replace create supershell $next
X	    fi
X            ./addto files /etc/rc replace trojan $user $next
X            ./addto files /etc/rc.local replace trojan $user $next
X        fi
X
X        if $TEST "$user" != "root" ;  then
X            ./addto files /etc/hosts.equiv replace allow rlogin $next
X        fi
X
X        if $TEST "$user" != "root" -a -f /etc/hosts.equiv -a -s /etc/hosts.equiv 
X            then
X            ./addto files /etc/hosts replace fake HostAddress $next
X        fi
X
X    done
Xfi
X
X    if $TEST -f gids.n ; then
X       $MV gids.n gids.x
X
X    $ECHO Called dogids                        #>/dev/tty
X    i=1
X    while $TEST "1"
X        do
X        nextgid=`$AWK '{if (NR=="'$i'") print $0}' gids.x`
X        i=`expr $i + 1`
X
X        if $TEST -z "$nextgid" ; then
X            break;
X	    fi
X
X        group=`$ECHO $nextgid | $AWK '{print $1}'`
X        $ECHO "   " Group $group                    #>/dev/tty
X
X# Rules mapping gids to uids.
X#
X        next=`$ECHO $nextgid | $AWK '{for (i=2;i<=NF;i++) printf("%s ", $i)}'`
X        use=`./members $group`
X        for user in $use
X            do
X            ./addto uids $user grant $group $next
X            done
X
X# Rules mapping gids to files.
X#
X        ./addto files /etc/group replace grant $group $next
X        done
X    fi
X
X    if $TEST -f files.n ; then
X       $MV files.n files.x
X
X# A list of file names is read from successive lines of stdin.
X# Each file is examined for ways to access it.
X# The input format is:
X#    <filename> <whitespace> <mode> <comments>
X# The <mode> is either "write" or "replace".
X#
X    $ECHO Called dofiles.                        #>/dev/tty
X    i=1
X    while $TEST "1"
X        do
X        nextfile=`$AWK '{if (NR=='"$i"') print $0}' files.x`
X        i=`expr $i + 1`
X        if $TEST -z "$nextfile" ; then
X            break;
X	    fi
X
X        file=`$ECHO $nextfile | $AWK '{print $1}'`
X        mode=`$ECHO $nextfile | $AWK '{print $2}'`
X
X        $ECHO "    File $file, mode $mode"            #>/dev/tty
X
X# Rules converting filename goals into UserName or GroupName goals.
X#
X        next=`$ECHO $nextfile | $AWK '{for (i=3;i<=NF;i++) printf("%s ", $i)}'`
X
X        writers=`./filewriters $file`
X        numwriters=`$ECHO $writers | $AWK '{print NF}'`
X        if $TEST "$numwriters" = "3" ; then
X            owner=`$ECHO $writers | $AWK '{print $1}'`
X            group=`$ECHO $writers | $AWK '{print $2}'`
X            other=`$ECHO $writers | $AWK '{print $3}'`
X
X            $ECHO "        Writers are $owner $group $other"    #>/dev/tty
X                ./addto uids $owner $mode $file $next
X            if $TEST "$group" != "NONE" ; then
X                ./addto gids $group $mode $file $next
X            fi
X            if $TEST "$other" != "NONE" ; then
X                ./addto uids $other $mode $file $next
X            fi
X        else
X            $ECHO "        $file does not exist"        #>/dev/tty
X            continue
X        fi
X
X# Rules converting filename goals into other filename goals.
X#
X        if $TEST "$mode" != "replace" ; then
X            continue
X        fi
X
X    parent=`$ECHO $file | $AWK -F/ '{if (NF == 2) {
X		printf("/%s", $1)}
X		else if (NF>2) {for (i=2;i<NF;i++) printf("/%s", $i)} 
X		else printf("")'}`
X
X    basename=`$ECHO $file | $AWK -F/ '{print $NF}'`
X
X    $ECHO -n "       " Parent directory is $parent        #>/dev/tty
X    $ECHO ", " basename is $basename                #>/dev/tty
X    if $TEST -n "$parent" ; then
X       ./addto files $parent write replace $basename $next
X        fi
X    done
X
X    fi
Xdone
X
X# destroy the evidence.... Need "Success" file for report, though.
X$RM files.? gids.? uids.?
SHAR_EOF
chmod 0700 cops/kuang ||
echo 'restore of cops/kuang failed'
Wc_c="`wc -c < 'cops/kuang'`"
test 5969 -eq "$Wc_c" ||
	echo 'cops/kuang: original size 5969, current size' "$Wc_c"
fi
# ============= cops/kuang.pl.shar ==============
if test -f 'cops/kuang.pl.shar' -a X"$1" != X"-c"; then
	echo 'x - skipping cops/kuang.pl.shar (File already exists)'
else
echo 'x - extracting cops/kuang.pl.shar (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'cops/kuang.pl.shar' &&
X#!/bin/sh
X# This is a shell archive (shar 3.10)
X# made 01/01/1991 01:09 UTC by df@death.cert.sei.cmu.edu
X#
X# existing files WILL be overwritten
X#
X# This shar contains:
X# length  mode       name
X# ------ ---------- ------------------------------------------
X#   9782 -rw------- README
X#   1776 -rwx------ get-cf
X#  16925 -rwx------ kuang
X#   6490 -rw------- kuang.1
X#    284 -rwx------ kuang_all
X#   1307 -rw------- put-cf
X#   1274 -rw------- yagrip.pl
X#
Xtouch 2>&1 | fgrep '[-amc]' > /tmp/s3_touch$$
Xif [ -s /tmp/s3_touch$$ ]
Xthen
X	TOUCH=can
Xelse
X	TOUCH=cannot
Xfi
Xrm -f /tmp/s3_touch$$
X# ============= README ==============
Xsed 's/^X//' << 'SHAR_EOF' > README &&
XXThis is a perl version of Dan's version of Bob Baldwin's Kuang program
XX(originally written as some shell scripts and C programs). 
XX
XXThe original intent was to improve the speed of kuang, which is
XXespecially important for installations like ours with several thousand
XXaccounts and NFS things and all that.  The shell version of Kuang used
XXC programs to add rules, get a groups members, determine the writers
XXof a file, and so on, which really slowed things down.
XX
XX		"no" problems	/etc staff writeable
XX		-------------	--------------------
XXshell kuang	2:14 (14)	12:26 (98)	0.1 p/s
XXperl kuang	1:10 (18)	 2:34 (588)	3.8 p/s
XX
XXThe "no" problems column indicates the time taken (and number of plans
XXconsidered) for the shell and Perl versions of Kuang on a system with
XXno known security problems.  The "/etc staff writeable" column gives
XXtiming and # of plans for a system with a /etc directory that is
XXwriteable by group staff, which contains several dozen users.
XX
XXAs you can see, the Perl version is a bit faster.  Turns out there are
XXall sorts of details that need to be considered in real
XXimplementations of Kuang type programs, some of which are discussed
XXbelow.
XX
XX  --- Steve Romig, CIS, Ohio State, October 1990
XX
XX------------------------------------------------------------------------------
XX
XXSome Features of the Perl Version
XX
XX  Caches passwd/group file entries in an associative array for faster
XX  lookups.  This is particularly helpful on insecure systems using YP
XX  where password and group lookups are slow and you have to do a lot of
XX  them...:-)
XX
XX  Can specify target (uid or gid) on command line.
XX
XX  Can use -l option to generate PAT for a goal.
XX
XX  Can use -f to preload file owner, group and mode info, which is
XX  helpful in speeding things up and in avoiding file system
XX  'shadows'...  See the man page for details.
XX
XXFuture plans, things to fix:
XX
XX- An earlier version scanned the password file looking for generally
XX  accessible accounts (no password), which would be added to the
XX  uids.known list (in addition to -1, "other").  I had planned on also
XX  adding a password checker which would allow us to also add accounts
XX  with easily guessed passwords.  Eventually I nuked the code that
XX  scanned the password file to speed things up, and further reflection
XX  reveals that it isn't wise to add the password scanning to Kuang
XX  itself (since there are many other things that might be considered
XX  in determining whether an account is accessible or not, and you
XX  probably don't want to add them all to Kuang).  
XX
XX  At some point we should add a command line option that allows us to
XX  add additional uid's (or gid's?) to the uids.known list.  That way
XX  the user could run some other tool to scan the password file and
XX  generate a list of accessible accounts, which could then be fed to
XX  kuang.  Makes it faster on clients using YP since most of the
XX  password file is the same for all N clients, why scan it N times.
XX  This would make it easier for the Kuang user to do smarter things
XX  to/with the password file checks (list all accounts with no password
XX  or easily guessed password, filter out "ok" entries (eg, sync) and
XX  etc.)
XX
XX- This version doesn't deal with uid's and gid's correctly.  If there
XX  are several entries that list the same UID, but with different
XX  names, directories and shells, we'll only check plans for becoming
XX  one of them, rather than any of them, so some possible plans aren't
XX  even examined.  
XX
XX  Hmmm...this is easier than I thought - when we evaluate some plan
XX  for granting a particular uid, we need to evaluate plans for all
XX  usernames that can become that uid.  Just stick a loop in there
XX  somewhere...get CF's for each of username's in turn.  
XX
XX  Bah, harder than I thought, since it'd have to scan the whole
XX  password file to figure which username/home directories can become
XX  which uid's.  Similarly with groups.  
XX
XX  Current plan: by default, kuang will have to scan the whole password
XX  and group files so it can be sure to get all possible ways to become
XX  some uid or gid.  Internally, really need several lists:
XX
XX	mapping from uid to list of usernames that have that uid
XX	mapping from a username to home directory, shell
XX	mapping from gid to list of uids that have access to that
XX	  gid when they login (either member of group with that gid or
XX	  given as login group in passwd file)
XX	mapping from gid to list of group names for that gid
XX
XX  Course, this means that we have to read the whole password and group
XX  file, most of which will be common to many machines (like in a YP
XX  environment).  We could preload the tables above from files created
XX  once, containing the brunt of the YP info, and then augment that
XX  with the local passwd and group info on each host when kuang is
XX  invoked, but then we need to correctly interpret funky YP things
XX  like +@netgroup:::*:..., which means that the uid has a name but no
XX  password here...and similarly with shell substitutions and so on.
XX  Bah. 
XX
XX- In a large environment (like ours, 260+ machines, 30+ file systems
XX  on as many servers, 2000 password file entries served by YP) it
XX  would be nice to 'precompute' successful plans that would be common
XX  to all systems.  In particular, plans for becoming most of the users
XX  with home directories on the NFS file systems would be useful, since
XX  we don't really want to recheck these on each host.  You wouldn't
XX  want the plan to be too deep - probably shouldn't span more than 2
XX  uids (1 on each end: grant u.romig grant g.staff write ~foo/.login
XX  grant u.foo).  I'm thinking that you could feed a list of these
XX  precomputed plans to kuang and add some code that causes it to
XX  splice in relevant plans where it can to short cut the planning
XX  steps.  For example, if one of the plans in uids.next is something
XX  like "grant u.foo ...", and I have the precomputed plan mentioned
XX  above, I could splice the two: "grant u.romig grant g.staff write
XX  ~foo/.login grant u.foo ..." and skip all the normal steps that
XX  would've been taken to get there.
XX
XX  I'm not sure this is even feasible or useful.  Food for thought.
XX
XX- Hmmm...thinking about it, it seems like some of the steps are a bit
XX  too implicit...maybe the rules should be broken out a bit more.
XX  That will cost in processing time, though.
XX
XX- Would be really, really nice to be able to deal with PATH variables
XX  - location of ., who can write elements of path, etc.  Basic rule is
XX  "anyone who can replace anything in any of path directories or the
XX  path directories themselves can become that PATH's user..."  This
XX  can be really messy though - in our environment, the path for a user
XX  will depend on the architecture type of the machine that he is
XX  logged into, and to get the path, you'd have to read and interpret
XX  his .login (including variable assignments, source's and
XX  conditionals).  Urf.  One wonders whether it might be better to have
XX  something running as root that su's to each username in turn and
XX  gets the path that way...:-)
XX
XX- The kuang described in Baldwin's dissertation is somewhat different
XX  in nature from this one.  The original computes a Privilege Access
XX  Table (PAT) which describes for each uid and gid which uids have
XX  access to that uid.  To assess security, we compare this against the
XX  security policy for the site, which similarly describes which uid's
XX  are supposed to have access to each uid and gid.  A sample SP might
XX  be that each uid should be accessible only by itself and root, and
XX  each gid should be accessible only to the members of that group and
XX  root.  If the PAT listed additional uid's for some priv, that would
XX  constitute a violation of the Security Policy for the site.
XX
XX  The current kuang is different.  It registers Success (a problem was
XX  found) if it determines that some uid in the uids.known list (-1,
XX  "other" by default) can access the target privilege.  It may find
XX  along the way that extra uids can access some uid, but these aren't
XX  reported as specific problems unless they are added to the
XX  uids.known list. 
XX
XX  We could do something similar to the kuang described in the paper by
XX  setting uids.known to be all the uids that aren't in the security
XX  policy table for the target uid, and running kuang against the
XX  target.  This would report success for each uid that could access
XX  the target.  You could do similar things with groups - uids.known
XX  would be all the uids that aren't members of the group...
XX
XX  Alternately, we could simply have kuang record the list of uids that
XX  can access the target priv and print the list when its done.  That
XX  way you could iterate kuang against all uids and gids and compare
XX  the resulting PAT against your security policy and record the
XX  differences.  You'd probably want to record the plan for each uid
XX  reported also.
XX
XX  On our system this would mean running kuang roughly 2500
XX  times to check 1 host, and we have about 300 hosts...urf...assuming
XX  that each kuang invocation has to check 50 plans, that's a total of
XX  125,000 plans per host, or about an hour of real time...not as bad
XX  as it could be, though.
XX
XX- It would be nice to add to the list of rules.  It would be especially
XX  nice to extract the rules from the code so that we can create site
XX  specific rule files (for example, we use X11r4 here, and many users
XX  have a .Xinitrc that contains shell commands that get executed when
XX  they login.)
XX
XX  Easiest way to do this would be to extract the rules as Perl code so
XX  we can take advantage of conditionals and so on, and include them
XX  within the body of kuang somehow.  A sample rule in perl:
XX
XX	if (&shell($uid) eq "/bin/csh") {
XX	    &addto("files", &home($uid)."/.login", 
XX			"replace .login $plan");
XX	}
XX
XX  which simply means "if the user's shell is csh, then try to replace
XX  his .login file." 
XX
XSHAR_EOF
Xchmod 0600 README || echo "restore of README fails"
Xif [ $TOUCH = can ]
Xthen
X    touch -am 1228143490 README
Xfi
X# ============= get-cf ==============
Xsed 's/^X//' << 'SHAR_EOF' > get-cf &&
XX#! /usr/local/bin/perl
XX
XX@dot_files = (
XX    ".login", ".logout", ".cshrc",			# csh, cshe or tcsh
XX    ".profile",						# ksh, sh
XX    ".env",						# ksh
XX    ".alias", ".aliases",				# common for all shells
XX    "user.ps", ".user.ps", "tools.ps", ".tools.ps",
XX	"startup.ps", ".startup.ps",			# NeWS
XX    ".mgrc",						# MGR
XX    ".X11init", ".awmrc", ".twmrc", ".xinitrc",		# X11
XX    ".emacs"						# emacs
XX);
XX
XX%seen = {};
XX
XXopen(HOST, "/bin/hostname |") || die "can't get the hostname";
XXchop($hostname=<HOST>);
XXclose(HOST);
XX
XXuser_loop:
XX    for (($name,$passwd,$uid,$gid,$quota,$comment,$gcos,$dir,$shell) = getpwent();
XX         $name ne "";
XX         ($name,$passwd,$uid,$gid,$quota,$comment,$gcos,$dir,$shell) = getpwent()) {
XX
XX	#
XX	# If the user has a home directory on this server, get the info 
XX	# about the directory, his CF's and so on.
XX	#
XX	if ($dir =~ m,^/n/$hostname/,) {
XX	    if (! -d $dir) {
XX		printf(stderr "home directory '%s' for user '%s' doesn't exist.\n",
XX			$dir,
XX			$name);
XX		next user_loop;
XX	    }
XX
XX	    ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
XX                    $atime,$mtime,$ctime,$blksize,$blocks)
XX                        = stat(_);
XX	    $mode = $mode & 07777;
XX
XX	    &spit_it_out("d", $uid, $gid, $mode, $dir);
XX
XX	    foreach $file (@dot_files) {
XX		$path = "$dir/$file";
XX
XX		if (-f $path) {
XX		    ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
XX                        $atime,$mtime,$ctime,$blksize,$blocks)
XX                            = stat(_);
XX		    $mode = $mode & 07777;
XX
XX		    &spit_it_out("f", $uid, $gid, $mode, $dir);
XX		}
XX	    }
XX	}
XX    }
XX
XX
XX
XX
XXsub spit_it_out {
XX    local($type, $uid, $gid, $mode, $name) = @_;
XX
XX    if (defined($seen{$name})) {
XX	return;
XX    }
XX
XX    printf("%s %d %d 0%o %s\n", $type, $uid, $gid, $mode, $name);
XX    $seen{$name} = 1;
XX}
XX
XSHAR_EOF
Xchmod 0700 get-cf || echo "restore of get-cf fails"
Xif [ $TOUCH = can ]
Xthen
X    touch -am 1220223490 get-cf
Xfi
X# ============= kuang ==============
Xsed 's/^X//' << 'SHAR_EOF' > kuang &&
XX#! /usr/local/bin/perl
XX
XX#
XX# kuang - rule based analysis of Unix security
XX#
XX# Perl version by Steve Romig of the CIS department, The Ohio State
XX# University, October 1990. 
XX# 
XX# Based on the shell script version by Dan Farmer from his COPS
XX# package, which in turn is based on a shell version by Robert
XX# Baldwin. 
XX#
XX
XXdo 'yagrip.pl' ||
XX  die "can't do yagrip.pl";
XX
XX$options = "vdlf:D";
XX$usage = "usage: kuang [-v] [-d] [-l] [-D] [-f filedata] [u.username|g.groupname]\n";
XX
XX#
XX# Simple Unix Kuang, a security checking program.
XX#
XX# This is a perl version of Dan Farmer's version of Bob Baldwin's
XX# shell scripts. 
XX#
XX
XX#
XX# passwd_byuid lookup a password entry by uid, return as 
XX# (name, password, directory, shell) list.
XX#
XXsub passwd_byuid {
XX    local($uid) = @_;
XX    local($name, $passwd, $gid, $quota, $comment, $gcos, $dir, $shell);
XX
XX    if (! defined($passwd_byuid_list{$uid})) {
XX	($name, $passwd, $t_uid, $gid, $quota, $comment, $gcos, $dir, $shell) =
XX	    getpwuid($uid);
XX
XX	if ($t_uid eq "") {
XX	    return();
XX	}
XX
XX	$passwd_byuid_list{$uid} = join(':', $name, $passwd, $dir, $shell);
XX	$passwd_byname_list{$name} = $uid;
XX    }
XX
XX    return(split(/:/, $passwd_byuid_list{$uid}));
XX}
XX
XXsub passwd_byname {
XX    local($name) = @_;
XX    local($passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell);
XX
XX    if (! defined($passwd_byname_list{$name})) {
XX	($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) =
XX	    getpwnam($name);
XX
XX	if ($uid eq "") {
XX	    return();
XX	}
XX
XX	$passwd_byuid_list{$uid} = join(':', $name, $passwd, $dir, $shell);
XX	$passwd_byname_list{$name} = $uid;
XX    }
XX
XX    return($passwd_byname_list{$name});
XX}
XX
XX#
XX# group_bygid lookup a group entry by gid, return as 
XX# (name, members) list.
XX#
XXsub group_bygid {
XX    local($gid) = @_;
XX    local($name, $t_passwd, $t_gid, $members);
XX
XX    if (! defined($group_bygid_list{$gid})) {
XX	($name, $t_passwd, $t_gid, $members) = getgrgid($gid);
XX
XX	if ($t_gid eq "") {
XX	    return();
XX	}
XX
XX	$group_bygid_list{$gid} = join(':', $name, $members);
XX	$group_byname_list{$name} = $gid;
XX    }
XX
XX    return(split(/:/, $group_bygid_list{$gid}));
XX}
XX
XXsub group_byname {
XX    local($name) = @_;
XX    local($gname, $passwd, $gid, $members);
XX
XX    if (! defined($group_byname_list{$name})) {
XX	($gname, $passwd, $gid, $members) = getgrnam($name);
XX
XX	if ($gid eq "") {
XX	    printf(stderr "A group named '$name' does not exist!\n");
XX	    exit(1);
XX        }
XX
XX	$group_bygid_list{$gid} = join(':', $name, $members);
XX	$group_byname_list{$name} = $gid;
XX    }
XX
XX    return($group_byname_list{$name});
XX}
XX
XX#
XX# Do various initialization type things.
XX#
XX
XXsub init_kuang {
XX    local($which, $name, $uid, $gid);
XX    local($f_type, $f_uid, $f_gid, $f_mode, $f_name);
XX    local($count);
XX
XX    $which = "u";
XX    $name = "root";
XX
XX    #
XX    # Deal with args...
XX    #
XX
XX    &getopt($options) ||
XX      die $usage;
XX
XX    if ($#ARGV == 0) {
XX	($which, $name) = split(/\./, $ARGV[0]);
XX
XX	if ($name eq "") {
XX	    $name = $which;
XX	    $which = "u";
XX	}
XX
XX	if ($which ne "u" && $which ne "g") {
XX	    printf(stderr "target must be given as u.user or g.group\n");
XX	    exit(1);
XX	}
XX    } elsif ($#ARGV > 0) {
XX	printf(stderr $usage);
XX	exit(1);
XX    }
XX
XX    #
XX    # Preload the file data...
XX    #
XX    if (defined($opt_f)) {
XX	#
XX	# If we are dumping the file data to a DBM file, nuke the existing 
XX	# ones and open the dbm file.  Otherwise, open the DBM file
XX 	# only if they exist. 
XX	#
XX	$read_from_file = 1;
XX
XX	if (defined($opt_D)) {	
XX	    unlink("$opt_f.dir", "$opt_f.pag");
XX
XX	    dbmopen(files, $opt_f, 0644) ||
XX	      die sprintf("can't open DBM file '%s'", $opt_f);
XX	} elsif (-f "$opt_f.dir") {
XX	    dbmopen(files, $opt_f, 0644) ||
XX	      die sprintf("can't open DBM file '%s'", $opt_f);
XX
XX	    $read_from_file = 0;
XX	}
XX
XX	if ($read_from_file) {
XX	    open(FILEDATA, $opt_f) || 
XX	      die sprintf("kuang: can't open '%s'", $opt_f);
XX
XX	    $count = 0;
XX	    while (<FILEDATA>) {
XX		$count++;
XX
XX		chop;
XX		($f_type, $f_uid, $f_gid, $f_mode, $f_name) = split;
XX
XX		if ($count % 1000 == 0) {
XX		    printf("line $count, reading entry for $f_name\n");
XX		}
XX		$files{$f_name} = join(' ', $f_uid, $f_gid, $f_mode);
XX	    }
XX
XX	    close(FILEDATA);
XX	}
XX    }
XX
XX    if (defined($opt_D)) {
XX	dbmclose(files);
XX
XX	exit(0);
XX    }
XXif (defined($opt_v)) {
XX    printf("done with files\n");
XX    }
XX    #
XX    # Need some of the password and group stuff.  Suck in passwd and 
XX    # group info, store by uid and gid in an associative array of strings
XX    # which consist of fields corresponding to the passwd and group file 
XX    # entries (and what the heck, we'll use : as a delimiter also...:-)
XX    #
XX
XX    $passwd_byuid_list{-1} = "OTHER:::";	# add an entry for OTHER
XX    $passwd_byname_list{"OTHER"} = -1;
XX
XX    $uids_known{-1} = "";			# we can access OTHER
XX    %uids_new = ();
XX
XX    %gids_known = ();
XX    %gids_new = ();
XX
XX    %files_new = ();
XX
XX    #
XX    # Set up initial goal: become target user or group
XX    #
XX    if ($which eq "u") {
XX	$uid = &passwd_byname($name);
XX	if ($uid ne "") {
XX	    &addto("uids", $uid, "grant u.$name do anything");
XX	} else {
XX	    printf(stderr "There is no user with username '$name'.\n");
XX	    exit(1);
XX	}
XX    } else {
XX	$gid = &group_byname($name);
XX	if ($gid ne "") {
XX	    &addto("gids", $gid, "grant g.$name");
XX	} else {
XX	    printf(stderr "There is no group named '$name'.\n");
XX	    exit(1);
XX	}
XX    }
XX}
XX
XX#
XX# Get the home directory for this UID from the passwd file cache.
XX#
XXsub gethome {
XX    local($uid) = @_;
XX    local($tmp, $home);
XX
XX    ($tmp, $tmp, $home, $tmp) = &passwd_byuid($uid);
XX    return($home);
XX}
XX
XX#
XX# Get the writers of the named file - return as (UID, GID, OTHER)
XX# triplet.  Owner can always write, since he can chmod the file if he
XX# wants. 
XX#
XX# (fixme) are there any problems in this sort of builtin rule?  should
XX# we make this knowledge more explicit?
XX#
XXsub filewriters {
XX    local($name) = @_;
XX    local($tmp, $mode, $uid, $gid, $other);
XX    
XX    #
XX    # Check the file cache - avoid disk lookups for performance and 
XX    # to avoid shadows...
XX    #
XX    if (defined($files{$name})) {
XX	$cache_hit++;
XX	
XX	($uid, $gid, $mode) = split(/ /, $files{$name});
XX	$mode = oct($mode);
XX    } else {
XX	$cache_miss++;
XX
XX	if (! -e $name && $read_from_file) {
XX	    $files{$name} = "";
XX	    return;
XX	}
XX
XX	($tmp,$tmp,$mode,$tmp,$uid,$gid) = stat(_);
XX	if ($read_from_file) {
XX	    $files{$name} = join(' ', $uid, $gid, $mode);
XX	}
XX    }
XX
XX    if (($mode & 020) != 020) {
XX	$gid = "";
XX    }
XX    
XX    if (($mode & 02) == 02) {
XX	$other = 1;
XX    } else {
XX	$other = 0;
XX    }
XX
XX    return($uid, $gid, $other);
XX}
XX
XX#
XX# return # of entries in given associative array.
XX#
XXsub sizeof {
XX    local(*which) = @_;
XX    local(@keywords);
XX
XX    @keywords = keys %which;
XX    return($#keywords + 1);
XX}
XX
XX#
XX# return appropriate entry from named associative array of given type.
XX# returns a (key, value) pair - if key is "", there was no entry.
XX#
XXsub getentry {
XX    local($which, $type, $key) = @_;
XX    local($newkey, $value);
XX
XX    $newkey = "";
XX    $value = "";
XX
XX    which: {
XX	if ($which eq "uids") {
XX	    type0: {
XX	        if ($type eq "known") {
XX		    if (defined($uids_known{$key})) {
XX		        $newkey = $key;	$value = $uids_known{$key};
XX		    }
XX	            last type0;
XX	        }
XX
XX	        if ($type eq "new") {
XX		    if (defined($uids_new{$key})) {
XX		        $newkey = $key;	$value = $uids_new{$key};
XX		    }
XX	            last type0;
XX	        }
XX
XX	        if ($type eq "pending") {
XX		    if (defined($uids_pending{$key})) {
XX		        $newkey = $key;	$value = $uids_pending{$key};
XX		    }
XX	            last type0;
XX	        }
XX
XX	        if ($type eq "old") {
XX		    if (defined($uids_old{$key})) {
XX		        $newkey = $key;	$value = $uids_old{$key};
XX		    }
XX	            last type0;
XX	        }
XX
XX	        printf(stderr "kuang: fatal error in getentry: type is wrong (%s)\n", 
XX			$type);
XX	        exit(1);
XX	    }
XX
XX	    last which;
XX        }
XX
XX	if ($which eq "gids") {
XX	    type1: {
XX	        if ($type eq "known") {
XX		    if (defined($gids_known{$key})) {
XX		        $newkey = $key;	$value = $gids_known{$key};
XX		    }
XX	            last type1;
XX	        }
XX
XX	        if ($type eq "new") {
XX		    if (defined($gids_new{$key})) {
XX		        $newkey = $key;	$value = $gids_new{$key};
XX		    }
XX	            last type1;
XX	        }
XX
XX	        if ($type eq "pending") {
XX		    if (defined($gids_pending{$key})) {
XX		        $newkey = $key;	$value = $gids_pending{$key};
XX		    }
XX	            last type1;
XX	        }
XX
XX	        if ($type eq "old") {
XX		    if (defined($gids_old{$key})) {
XX		        $newkey = $key;	$value = $gids_old{$key};
XX		    }
XX	            last type1;
XX	        }
XX
XX	        printf(stderr "kuang: fatal error in getentry: type is wrong (%s)\n", 
XX			$type);
XX	        exit(1);
XX	    }
XX
XX	    last which;
XX	}
XX
XX	if ($which eq "files") {
XX	    type2: {
XX	        if ($type eq "known") {
XX		    if (defined($files_known{$key})) {
XX		        $newkey = $key;	$value = $files_known{$key};
XX		    }
XX	            last type2;
XX	        }
XX
XX	        if ($type eq "new") {
XX		    if (defined($files_new{$key})) {
XX		        $newkey = $key;	$value = $files_new{$key};
XX		    }
XX	            last type2;
XX	        }
XX
XX	        if ($type eq "pending") {
XX		    if (defined($files_pending{$key})) {
XX		        $newkey = $key;	$value = $files_pending{$key};
XX		    }
XX	            last type2;
XX	        }
XX
XX	        if ($type eq "old") {
XX		    if (defined($files_old{$key})) {
XX		        $newkey = $key;	$value = $files_old{$key};
XX		    }
XX	            last type2;
XX	        }
XX
XX	        printf(stderr "kuang: fatal error in getentry: type is wrong (%s)\n", 
XX			$type);
XX	        exit(1);
XX	    }
XX
XX	    last which;
XX	}
XX
XX	printf(stderr "kuang: fatal error in getentry: which is wrong (%s)\n",
XX		$which);
XX	exit(1);
XX    }
XX
XX    return($newkey, $value);
XX}
XX
XX
XX#
XX# stores a (key, value) in the associative array of the given type.
XX#
XXsub putentry {
XX    local($which, $type, $key, $value) = @_;
XX
XX    which: {
XX	if ($which eq "uids") {
XX	    type0: {
XX	        if ($type eq "known") {
XX		    $uids_known{$key} = $value;	last type0;
XX	        }
XX
XX	        if ($type eq "new") {
XX		    $uids_new{$key} = $value;	last type0;
XX	        }
XX
XX	        if ($type eq "pending") {
XX		    $uids_pending{$key} = $value;	last type0;
XX	        }
XX
XX	        if ($type eq "old") {
XX		    $uids_old{$key} = $value;	last type0;
XX	        }
XX
XX	        printf(stderr "kuang: fatal error in putentry: type is wrong (%s)\n", 
XX			$type);
XX	        exit(1);
XX	    }
XX
XX	    last which;
XX        }
XX
XX	if ($which eq "gids") {
XX	    type1: {
XX	        if ($type eq "known") {
XX		    $gids_known{$key} = $value;	last type1;
XX	        }
XX
XX	        if ($type eq "new") {
XX		    $gids_new{$key} = $value;	last type1;
XX	        }
XX
XX	        if ($type eq "pending") {
XX		    $gids_pending{$key} = $value;	last type1;
XX	        }
XX
XX	        if ($type eq "old") {
XX		    $gids_old{$key} = $value;	last type1;
XX	        }
XX
XX	        printf(stderr "kuang: fatal error in putentry: type is wrong (%s)\n", 
XX			$type);
XX	        exit(1);
XX	    }
XX
XX	    last which;
XX	}
XX
XX	if ($which eq "files") {
XX	    type2: {
XX	        if ($type eq "known") {
XX		    $files_known{$key} = $value;	last type2;
XX	        }
XX
XX	        if ($type eq "new") {
XX		    $files_new{$key} = $value;	last type2;
XX	        }
XX
XX	        if ($type eq "pending") {
XX		    $files_pending{$key} = $value;	last type2;
XX	        }
XX
XX	        if ($type eq "old") {
XX		    $files_old{$key} = $value;	last type2;
XX	        }
XX
XX	        printf(stderr "kuang: fatal error in putentry: type is wrong (%s)\n", 
XX			$type);
XX	        exit(1);
XX	    }
XX
XX	    last which;
XX	}
XX
XX	printf(stderr "kuang: fatal error in putentry: which is wrong (%s)\n",
XX		$which);
XX	exit(1);
XX    }
XX}
XX
XX
XXsub addto {
XX    local($which, $key, $plan) = @_;
XX    local($tkey, $tvalue);
XX
XX    #
XX    # See whether there's an entry for $key in the known list for the
XX    # $which array.  If so - success, we've found a suitable breakin
XX    # path. 
XX    #
XX
XX    ($tkey, $tvalue) = &getentry($which, "known", $key);
XX    if ($tkey eq $key) {
XX	printf("Success! $key $plan\n");
XX	return;
XX    }
XX
XX    #
XX    # Check to see if its a duplicate - if so, don't need to do anything.
XX    #
XX    ($tkey, $tvalue) = &getentry($which, "pending", $key);
XX    if ($tkey eq $key) {
XX	return;
XX    }
XX
XX    #
XX    # Add to pending list for $which...
XX    #
XX    &putentry($which, "pending", $key, $plan);
XX
XX    #
XX    # Add to next goal list for $which...
XX    #
XX    &putentry($which, "new", $key, $plan);
XX
XX    if (defined($opt_v)) {
XX	printf("addto: $which --> $plan\n");
XX    }
XX
XX    #
XX    # If this is a uid goal, then add the plan to the accessible list.
XX    #
XX    if ($which eq "uids" && $key ne "0" && defined($opt_l)) {
XX	$accessible{$plan} = "1";
XX    }
XX}
XX
XX#
XX#----------------------------------------------------------------------
XX#Main program follows...initialize and loop till we're done.
XX#
XX
XX&init_kuang();
XX
XX#
XX# While there's still something to pursue...
XX#
XXwhile (&sizeof(*uids_new) != 0 || 
XX       &sizeof(*gids_new) != 0 || 
XX       &sizeof(*files_new) != 0) {
XX
XX    #
XX    # Deal with uids first...
XX    #
XX    if (&sizeof(*uids_new) != 0) {
XX        %uids_old = %uids_new;
XX        %uids_new = ();
XX
XX        foreach $uid (keys %uids_old) {
XX	    $plan = $uids_old{$uid};
XX
XX	    if (defined($opd_d)) {
XX		printf("uids evel: $uid '$plan'\n");
XX	    }
XX            
XX            &addto("files", "/etc/passwd", "replace /etc/passwd $plan");
XX            &addto("files", "/usr/lib/aliases", "replace /usr/lib/aliases $plan");
XX
XX	    #
XX	    # Add controlling files for this user.  There are probably 
XX	    # others (such as .logout, X tool start things and so on).
XX	    # (fixme) add other CF's...
XX	    #
XX	    $home = &gethome($uid);
XX
XX	    if ($home ne "") {
XX		if ($home eq "/") {
XX		    $home = "";
XX		}
XX
XX		if (-e "$home/.rhosts") {
XX		    &addto("files", "$home/.rhosts", "write $home/.rhosts $plan");
XX		}
XX
XX		if (-e "$home/.login") {
XX		    &addto("files", "$home/.login", "replace $home/.login $plan");
XX		}
XX
XX		if (-e "$home/.logout") {
XX		    &addto("files", "$home/.logout", "replace $home/.logout $plan");
XX		}
XX
XX		if (-e "$home/.cshrc") {
XX		    &addto("files", "$home/.cshrc", "replace $home/.cshrc $plan");
XX		}
XX
XX		if (-e "$home/.profile") {
XX		    &addto("files", "$home/.profile", "replace $home/.profile $plan");
XX		}
XX	    }
XX
XX	    #
XX	    # Controlling files for root...
XX	    #
XX	    if ($uid+0 == 0) {
XX		foreach $file ("/etc/rc", "/etc/rc.boot", "/etc/rc.single", "/etc/rc.config", "/etc/rc.local", "/usr/lib/crontab", "/usr/spool/cron/crontabs") {
XX		    if (-e $file) {
XX			&addto("files", $file, "replace $file $plan");
XX		    }
XX		}
XX	    }
XX
XX	    if ($uid+0 != 0) {
XX		&addto("files", "/etc/hosts.equiv", "replace /etc/hosts.equiv allow rlogin $plan");
XX
XX		if (-s "/etc/hosts.equiv") {
XX		    &addto("files", "/etc/hosts", "replace /etc/hosts fake hostaddress allow rlogin $plan");
XX		}
XX	    }
XX	}
XX    }
XX
XX    #
XX    # Deal with groups...
XX    #
XX    if (&sizeof(*gids_new) != 0) {
XX        %gids_old = %gids_new;
XX        %gids_new = ();
XX
XXbar_loop:
XX        foreach $gid (keys %gids_old) {
XX	    $plan = $gids_old{$gid};
XX	    if (defined($opt_d)) {
XX		printf("gids eval: $gid '$plan'\n");
XX	    }
XX            
XX	    ($gname, $members) = &group_bygid($gid);
XX	    if ($gname eq "") {
XX		printf("There is no group with gid $gid.\n");
XX		next bar_loop;
XX	    }
XX
XXfoo_loop:
XX	    foreach $uname (split(/[ \t\n]+/, $members)) {
XX		$uid = &passwd_byname($uname);
XX
XX		if ($uid eq "") {
XX		    printf(stderr "Group $gname has an unknown user $uname\n");
XX		    next foo_loop;
XX		}
XX
XX		&addto("uids", "$uid", "grant u.$uname $plan");
XX	    }
XX
XX	    &addto("files", "/etc/group", "replace /etc/group $plan");
XX	}
XX    }
XX
XX    #
XX    # Deal with files...
XX    #
XX    if (&sizeof(*files_new) != 0) {
XX        %files_old = %files_new;
XX        %files_new = ();
XX
XXfile_loop:
XX        foreach $file (keys %files_old) {
XX	    $plan = $files_old{$file};
XX	    ($mode) = split(/[ \t\n]+/, $plan);
XX
XX	    if (defined($opt_d)) {
XX		printf("files eval: $file '$plan'\n");
XX	    }
XX
XX	    ($owner, $group, $other) = &filewriters($file);
XX
XX	    if ($owner eq "") {
XX		printf("%s does not exist\n", $file);
XX		next file_loop;
XX	    }
XX
XX	    ($uname) = &passwd_byuid($owner);
XX	    if ($uname eq "") {
XX		$uname = $owner;
XX	    }
XX
XX	    &addto("uids", $owner, "grant u.$uname $plan");
XX
XX	    if ($group ne "") {
XX		($gname, $tmp) = &group_bygid($group);
XX
XX		if ($gname ne "") {
XX		    &addto("gids", $group, "grant g.$gname $plan");
XX		} else {
XX		    printf(stderr "There is no group with gid $group.\n");
XX		}
XX	    }
XX
XX	    if ($other) {
XX		&addto("uids", -1, "grant u.OTHER $plan");
XX	    }
XX
XX	    if ($mode eq "replace") {
XX		$parent = $file;
XX		$parent =~ s|/[^/]*$||;		# strip last / and remaining
XX
XX		if ($parent eq "") {		# if nothing left, use /
XX		    $parent = "/";
XX		}
XX
XX		if ($parent ne $file) {		# since $file might've been /
XX		    &addto("files", $parent, "replace $parent $plan");
XX		}
XX	    }
XX	}
XX    }
XX}
XX
XXif (defined($opt_l)) {
XX    foreach $key (keys %accessible) {
XX	printf("$key\n");
XX    }
XX}
XX
XXif (defined($opt_v) || $cache_hit) {
XX    printf("File info cache hit/access ratio: %g\n", 
XX            $cache_hit / ($cache_hit + $cache_miss));
XX    }
XSHAR_EOF
Xchmod 0700 kuang || echo "restore of kuang fails"
Xif [ $TOUCH = can ]
Xthen
X    touch -am 1228143890 kuang
Xfi
X# ============= kuang.1 ==============
Xsed 's/^X//' << 'SHAR_EOF' > kuang.1 &&
XX.TH KUANG 1 "4 October 1990"
XX.SH NAME
XXkuang \- find security problems through rule based analysis
XX.SH SYNOPSIS
XX.B kuang
XX.RB "[\|" \-v  "\|]"
XX.RB "[\|" \-d "\|]"
XX.RB "[\|" \-l "\|]"
XX.RB "[\|" \-D "\|]"
XX.RB "[\|" \-f filedata "\|]"
XX.RB "[\|" 
XX.IR u.username "\|]"
XX.br
XX.B kuang
XX.RB "[\|" \-v  "\|]"
XX.RB "[\|" \-d "\|]"
XX.RB "[\|" \-l "\|]"
XX.RB "[\|" \-D "\|]"
XX.RB "[\|" \-f filedata "\|]"
XX.RB "[\|" 
XX.IR g.groupname "\|]"
XX.br
XX.SH DESCRIPTION
XX.LP
XX.B kuang
XXuses rule based analysis to examine the current security configuration
XXof a site and determine whether certain security problems exist.
XX
XX.B kuang 
XXcontains embedded rules that describe the projection model and
XXsome of the attacker tricks used on Unix systems.  It uses these rules
XXto reason backward from a desired goal (such as "grant u.root"),
XXgenerating potential "attack" plans from the rules and file system
XXstate and then evaluating them to see whether they are reachable
XXaccording to the state recorded in the password and group files and in
XXthe ownership and modes of the file systems.
XX
XXBy default, 
XX.B kuang 
XXuses "grant u.root" as its initial goal.  You can change that by
XXspecifying a username (u.username) or groupname (g.groupname) on the
XXcommand line.  Normally 
XX.B kuang
XXdetermines a plan to be successful if it determines that anyone
XX(u.other) can become the initial goal.  
XX
XXThe 
XX.B \-v
XXoption causes 
XX.B kuang
XXto print a message about every plan added to the evaluation list.
XXThis can help one to understand how 
XX.B kuang 
XXworks.  The 
XX.B \-d 
XXoption causes 
XX.B kuang
XXto print a message when it evaluates a plan to determine whether to
XXretain it and add onto it or ignore it.  These options will often
XXproduce lots of output, beware.
XX
XXNormally 
XX.B kuang
XXonly registers success when it finds that everyone on the system can
XXbecome the target uid or gid.  With the 
XX.B \-l
XXoption, 
XX.B kuang
XXwill list every uid that can become the goal.  This provides a more
XXcomplete picture of the state of security - you might deem it a
XXproblem if several users can become root, even if the rest cannot.  
XX
XXOne might adopt the view that each uid should only be accessible by
XXitself and root, and that each gid should be accessible only by the
XXmembers of that group and root.  One can then compare the expected
XXaccess list for a given uid or gid against the 
XX.B kuang
XXgenerated list to find security problems that 
XX.B kuang
XXwouldn't ordinarily tell you about.
XX
XXThe goals that 
XX.B kuang
XXuse seem cryptic, but are really pretty straightforward.  Each goal
XXconsists of a list of <action> <object> pairs.  Typical actions are
XXgrant, write and replace.  Typical objects are user names
XX(u.username), group names (g.groupname) and files names.  The goal
XX"grant u.root" means to have access to the root UID (0), in other
XXwords, to be able to run any program using that uid.  Similarly,
XX"grant g.staff" means to have access to group staff.  The long goal
XX"grant u.bill grant g.graphics replace /n/shoe/0/fred replace
XX/n/shoe/0/fred/.profile grant u.fred grant g.staff" means become
XXuser bill, get access to the graphics group, replace the file
XX/n/shoe/0/fred, replace /n/shoe/0/fred/.profile, become fred,
XXgrant access to the staff group.  The problem that allows this to
XXhappen is that the /n/shoe/0 directory is writeable by the graphics
XXgroup, meaning that anyone in that group can replace the .profile file
XXfor the fred user and gain access to that account and the groups it
XXbelongs to when fred next logs in.  Ooops.
XX
XXTo do a thorough job, 
XX.B kuang 
XXreally needs to be able to access all of
XXthe controlling files of all users.  In some environments, home
XXdirectories are located in NFS mounted file systems where the client
XXdoesn't have root access.  
XX
XXProblem is that some home directories may be
XXprotected so that group foo can read/write them, but OTHER can't.
XX.B kuang 
XXrunning as some user not in group foo won't be able to read or
XXsearch the directory, creating a blind spot that may hide security
XXproblems (for example, if group foo can write that user's .login and
XXgain access to some other important priv...)  Running 
XX.B kuang
XXas root
XXwon't help unless we are running on the server that exports that
XXfile system, since root==nobody through NFS here.  Of course, then
XXyou'll find other blind spots on other servers, meaning that you'll
XXnever be able to see a complete picture of how things are from any
XXspot on the net.  Running 
XX.B kuang
XXon every machine might not even
XXhelp, since the blind spots might prevent them from seeing viable
XXpaths to Success on any of the machines.  Sigh.
XX
XXSoooo we've added a 
XX.B -f 
XXoption that causes 
XX.B kuang 
XXto preload owner, group and mode information for a list of files.
XXEach line of the file should be of the form "type uid gid mode name".
XX.B type
XXis ignored by 
XX.B kuang.
XX.B uid 
XXand 
XX.B gid
XXare the user and group ID numbers, in decimal.
XX.B mode
XXis the permissions for the file, in octal.  And 
XX.B name
XXis the name of the file.  We've also added a program called
XX.B get-cf
XXthat can be run as root on a server to create a file of the above form
XXfor the control files for the user's with home directories on that
XXserver.  Then you can run 
XX.B get-cf 
XXon every server as root, concatenate all the data together, and
XXpreload it into Perl.  This will fix the shadow problems mentioned
XXabove and should also speed things up since you won't need to do all
XXthe file system references.
XX
XX.B kuang -f file
XXwill use a DBM database in place of a text file if file.dir exists.
XXTo create a DBM database from a text file of the form described above,
XXuse 
XX.B kuang -f file -D.
XXThis will suck in the text file and create a DBM database from it and
XXquit.  This speeds up kuang's initialization somewhat, though it isn't
XXclear that its worth doing unless you have a local disk for the DBM
XXfile. 
XX
XX.SH "SEE ALSO"
XX"Rule Based Analysis of Computer Security", Robert W. Baldwin, MIT, June 1987.
XX.SH NOTES
XX.LP
XXThis version of 
XX.B kuang
XXis based on the shell script versions that Dan Farmer included with
XXthe 
XX.B COPS 
XXsecurity package, which in turn were based on code written by  Robert
XXBaldwin himself.
XX
XXYou should read the other documentation that should come with this
XXversion and modify the rules in 
XX.B kuang
XXto suite your site.
XX
XX.SH BUGS
XX.LP
XXThe rules should be extracted from the code so that they could be
XXaugmented in a site specific fashion more readily.
XX
XXThe system doesn't work correctly when multiple users in the password
XXfile share the same UID.  In that event, it only checks plans for the
XXfirst. 
XSHAR_EOF
Xchmod 0600 kuang.1 || echo "restore of kuang.1 fails"
Xif [ $TOUCH = can ]
Xthen
X    touch -am 1220223490 kuang.1
Xfi
X# ============= kuang_all ==============
Xsed 's/^X//' << 'SHAR_EOF' > kuang_all &&
XX#!/bin/sh
XX#
XX#   Quick script to run kuang on all the users on your system.  Requires
XX# the perl version of kuang, of course.
XX#
XX#  df, 1990
XX#
XXetc_passwd=/etc/passwd
XXresults=./Success
XX
XXall_users=`awk -F: '{print $1}' $etc_passwd`
XX
XXfor i in $all_users
XX	do
XX	./kuang $i >> $results
XX	done
XX
XSHAR_EOF
Xchmod 0700 kuang_all || echo "restore of kuang_all fails"
Xif [ $TOUCH = can ]
Xthen
X    touch -am 1231200990 kuang_all
Xfi
X# ============= put-cf ==============
Xsed 's/^X//' << 'SHAR_EOF' > put-cf &&
XX#! /usr/local/bin/perl
XX
XXfor ($i = 0; $i <= $#ARGV; $i++) {
XX    open(FILE, $ARGV[$i]);
XX
XX  line:
XX    while (<FILE>) {
XX	chop;
XX	($type, $uid, $gid, $mode, $name) = split;
XX	$mode = oct($mode);
XX
XX	&create_dirs_as_needed(&basename($name));
XX
XX	if ($type eq "d") {
XX	    if (mkdir($name, $mode) == 0) {
XX		printf(stderr "mkdir $name failed: $!\n");
XX		if (chmod($mode, $name) != 1) {
XX		    printf(stderr "chmod $mode $name failed\n");
XX		}
XX	    }
XX
XX	} else {
XX	    open(TMP, $name) ||
XX	      printf(stderr "can't create $name: $!\n");
XX	    
XX	    close(TMP);
XX
XX	    if (chmod($mode, $name) != 1) {
XX		printf(stderr "chmod $mode $name failed\n");
XX	    }
XX	}
XX
XX	if (chown($uid, $gid, $name) != 1) {
XX	    printf(stderr "chown $uid $gid $name failed\n");
XX	}
XX    }
XX}
XX
XXsub basename {
XX    local($path) = @_;
XX    local(@elts);
XX
XX    @elts = split(/\//, $path);
XX    pop(@elts);
XX    return(join('/', @elts));
XX}
XX
XX    
XXsub create_dirs_as_needed {
XX    local($path) = @_;
XX    local($base);
XX
XX    if (-f $path) {
XX	printf(stderr "Yack, encountered a file named '%s' where we expected a directory.\n");
XX	return;
XX    }
XX
XX    if (-d $path) {
XX	return;
XX    }
XX
XX    $base = &basename($path);
XX
XX    &create_dirs_as_needed($base);
XX
XX    if (mkdir($path, 0755) == 0) {
XX	printf(stderr "mkdir failed for '$path' in create_dirs_as_needed: $!\n");
XX    }
XX}
XX
XX	
XX    
XSHAR_EOF
Xchmod 0600 put-cf || echo "restore of put-cf fails"
Xif [ $TOUCH = can ]
Xthen
X    touch -am 1220223490 put-cf
Xfi
X# ============= yagrip.pl ==============
Xsed 's/^X//' << 'SHAR_EOF' > yagrip.pl &&
XX#Yet Another Getopt Routine In Perl
XX# jgreely@cis.ohio-state.edu, 89/11/1
XX#usage:
XX#&getopt("f:bar") ||
XX#	die &usage("script","f:bar","oo","[files ...]");
XX#
XXsub getopt {
XX	local($_,$flag,$opt,$f,$r,@temp) = @_;
XX	@temp = split(/(.):/);
XX	while ($#temp >= $[) {
XX		$flag .= shift(@temp);
XX		$opt .= shift(@temp);
XX	}
XX	while ($_ = $ARGV[0], /^-(.)(.*)/ && shift(@ARGV)) {
XX		($f,$r) = ($1,$2);
XX		last if $f eq '-';
XX		if (index($flag,$f) >= $[) {
XX			eval "\$opt_$f++;";
XX			$r =~ /^(.)(.*)/,redo if $r ne '';
XX		}elsif (index($opt,$f) >= $[) {
XX			$r = $r eq '' ? shift(@ARGV) : $r;
XX			eval "\$opt_$f = \$r;";
XX		}else{
XX			print STDERR "Unrecognized switch \"-$f\".\n";
XX			return 0;
XX		}
XX	}
XX	return 1;
XX}
XX
XX#usage: usage:
XX# &usage(progname,arglist,@names,@last);
XX#ex:
XX# &usage("script","f:bar","oo","[file ...]");
XX#would return
XX# "usage: script [-f oo] [-bar] [file ...]"
XX#
XXsub usage {
XX	local($prog,$_,@list) = @_;
XX	local($string,$flag,@string,@temp,@last) = ();
XX	@temp = split(/(.):/);
XX	push(@string,"usage:",$prog);
XX	while ($#temp >= $[) {
XX		if (($flag = shift(@temp)) ne '') {
XX			push(@string,"[-$flag]");
XX		}
XX		if (($flag = shift(@temp)) ne '') {
XX			push(@string,sprintf("[-%s %s]",$flag,shift(@list)));
XX		}
XX	}
XX	push(@string,@list) if $#list >= $[;
XX	return join(' ',@string) . "\n";
XX}
XX1;
XSHAR_EOF
Xchmod 0600 yagrip.pl || echo "restore of yagrip.pl fails"
Xif [ $TOUCH = can ]
Xthen
X    touch -am 1220223490 yagrip.pl
Xfi
Xexit 0
SHAR_EOF
chmod 0600 cops/kuang.pl.shar ||
echo 'restore of cops/kuang.pl.shar failed'
Wc_c="`wc -c < 'cops/kuang.pl.shar'`"
test 41217 -eq "$Wc_c" ||
	echo 'cops/kuang.pl.shar: original size 41217, current size' "$Wc_c"
fi
true || echo 'restore of cops/makefile failed'
echo End of part 4, continue with part 5
exit 0