#!/usr/bin/perl -s $pwdcmd = '/bin/pwd'; $user = 'www'; $group = 'other'; @dflts = ('/l/www'); @avoid = ('/l/picons/db', '/l/aicons', '/l/doc/java/tutorial'); $SymLinksIfOwnerMatch = 1; # web/bin/ftw - file tree walker for FollowSymLinks # Steve Kinzler, kinzler@cs.indiana.edu, May 96 # some code borrowed and adapted from /usr/local/lib/perl/find.pl # http://www.cs.indiana.edu/~kinzler/home.html#webadm $usage = "usage: $0 [ -l ] [ -s ] [ -a | -v ] [ -d ] [ -n ] [ -p ] [ directory ... ] -l list directory tree as it is walked -s walk directories in sorted order -a don't prune avoided directories -v report directories avoided -d report directories skipped as done under another name -n report \"no such\" warnings -p report permission warnings This should be run as root, though access testing is for user $user and group $group. The walk method is " . (($SymLinksIfOwnerMatch) ? 'SymLinksIfOwnerMatch' : 'FollowSymLinks') . ". Default directories are " . join(', ', @dflts) . " Avoided directories are " . join(', ', @avoid) . "\n"; die $usage if $h; warn "$0: WARNING, cannot walk entire tree (not running as root)\n" unless $> == 0; (@ent = getpwnam($user)) || die "$0: cannot get pwent for $user ($!)\n"; $auid = $ent[2]; (@ent = getgrnam($group)) || die "$0: cannot get grent for $group ($!)\n"; $agid = $ent[2]; chop($cwd = `$pwdcmd 2> /dev/null`); die "$0: cannot determine cwd ($!)\n" if $? || $cwd eq ''; &auid(); unless ($a) { foreach (@avoid) { warn("$0: cannot chdir $_ to avoid it ($!)\n"), next unless chdir $_; &ruid(); chop($pwd = `$pwdcmd 2> /dev/null`); warn("$0: not avoiding $dir, cannot run `$pwdcmd` ($!)\n"), &auid(), next if $? || $pwd eq ''; $avoid{$pwd} = $_; chdir $cwd || die "$0: cannot chdir $cwd again ($!)\n"; &auid(); } } &find((@ARGV) ? @ARGV : @dflts); ############################################################################### # optimization: set $stat = 1 if _ contains results of stat($_) sub wanted { local(@lstat, $luid, @stat, $uid); return unless $SymLinksIfOwnerMatch; warn("$0: cannot lstat $name ($!)\n"), $prune = 1, return unless @lstat = lstat($_); if (-l _) { $luid = $lstat[4]; &ewarn("$!", "cannot stat $name"), $prune = 1, return unless @stat = stat($_); ($stat, $uid) = (1, $stat[4]); return if $luid == $uid; ($luid, $uid) = (&uname($luid), &uname($uid)); warn "$0: misown $name ($luid -> $uid ", readlink $_, ")\n"; $prune = 1; } } sub uname { local($uid) = @_; local($uname, @ent); return $unames{$uid} if defined $unames{$uid}; $uname = (@ent = getpwuid($uid)) ? $ent[0] : $uid; $unames{$uid} = $uname; return $uname; } ############################################################################### sub find { foreach $topdir (@_) { $topdir =~ s,/+,/,g; $topdir =~ s,/$,,; warn("$0: cannot stat $topdir ($!)\n"), next unless stat($topdir); if (-d _) { unless (chdir $topdir) { warn "$0: cannot chdir $topdir ($!)\n"; } else { ($dir, $_) = ($topdir, '.'); $name = $topdir; &wanted(); &finddir($topdir, 0); } } else { ($dir, $_) = ('.', $topdir) unless ($dir, $_) = $topdir =~ m,^(.*/)(.*)$,; $name = $topdir; (chdir $dir) ? &wanted() : warn "$0: cannot chdir $dir ($!)\n"; } &ruid(); chdir $cwd || die "$0: cannot chdir $cwd again ($!)\n"; &auid(); } } sub finddir { local($dir, $lev) = @_; local($pwd, $name); &ruid(); chop($pwd = `$pwdcmd 2> /dev/null`); warn("$0: skipping $dir, cannot run `$pwdcmd` ($!)\n"), &auid(), return if $? || $pwd eq ''; $v && warn("$0: avoid $dir (same as $diddir{$pwd} == $pwd)\n"), &auid(), return if ! $a && defined $avoid{$pwd}; $d && warn("$0: skip $dir (did as $diddir{$pwd} == $pwd)\n"), &auid(), return if defined $diddir{$pwd}; $diddir{$pwd} = $dir; warn("$0: cannot open $dir ($!)\n"), &auid(), return unless opendir(DIR, '.'); local(@filenames) = readdir(DIR); warn("$0: cannot read $dir ($!)\n"), closedir DIR, &auid(), return unless @filenames; closedir DIR; &auid(); @filenames = sort @filenames if $s; print ' ' x $lev, "$dir (", $#filenames - 1, ")\n" if $l; for (@filenames) { next if $_ eq '.' || $_ eq '..'; $stat = $prune = 0; $name = "$dir/$_"; &wanted(); next if $prune; &ewarn("$!", "cannot stat $name"), next unless $stat || stat($_); next unless -d _; &ewarn("$!", "cannot chdir $name"), next unless chdir $_; &finddir($name, $lev + 1); chdir(($dir =~ m,^/,) ? $dir : "$cwd/$dir") || die "$0: cannot chdir $dir again ($!)\n"; } } ############################################################################### sub ruid { $> = 0; $) = 0; } sub auid { $) = $agid; $> = $auid; } sub ewarn { local($bang) = shift; return if ! $p && $bang =~ /permission/i; return if ! $n && $bang =~ /\bno\s+such/i; warn "$0: @_ ($bang)\n"; }