#!/usr/local/bin/perl $age = 10; ## how many map records to keep... $isbsd = 1; ## SYSV=0 BSD=1 $mount = "/sbin/mount"; if ($isbsd) { $mapdir = "/var/log"; } else { $mapdir = "/var/adm"; } #$cksum = "/bin/cksum"; $cksum = "/sbin/md5"; $md5 = 1; $mapfile = "$mapdir/setuid.map"; $msgfile = "$mapdir/setuid.msg"; chomp($hostname = `hostname`); $debug = 0; while ($#ARGV > -1) { $_ = shift(@ARGV); if (/^-d/) { $debug = 1; } } $sendmail = "/usr/sbin/sendmail -t"; $sendfrom = "root\@$hostname"; $sendto = "username\@domain.com"; # perform security checks $ENV{PATH} = "/bin:/usr/bin:/sbin:/usr/sbin"; %pwmap = (); $pwmax = 0; %grmap = (); $grmax = 0; umask(027); %mounts = (); for $mount (split(/\n/, `mount`)) { if ($isbsd) { ($dev, $on, $mp, @ignore) = split(/\s+/, $mount); } else { ($mp, $on, $dev, @ignore) = split(/\s+/, $mount); } $debug && print "mounts{$mp} = $dev\n"; if ($dev =~ /^\/dev\//) { $mounts{$mp} = 1; } else { $mounts{$mp} = 0; } } %newmap = (); for $mp (sort keys(%mounts)) { if ($mounts{$mp}) { $debug && print "Checking $mp\n"; search_dir($mp, 'check_file'); } } $msg = ""; if (! -f $mapfile) { &writemap(\%newmap, $mapfile); } else { %oldmap = (); &loadmap(\%oldmap, $mapfile); @oldfiles = (); @newfiles = (); @different = (); for $k (keys %oldmap) { if (!exists($newmap{$k})) { push(@oldfiles, $k); } } for $k (keys %newmap) { if (!exists($oldmap{$k})) { push(@newfiles, $k); } else { if ($oldmap{$k} ne $newmap{$k}) { push(@different, $k); } } } ## houston, we have a problem if ($#oldfiles > -1 || $#newfiles > -1 || $#different > -1) { if (!$hostname) { chomp($hostname = `hostname`); } &mappwnames(); &getsizes(); print < -1) { $msg .= "Files which are no longer setuid (or no longer exist):\n\n$title"; for $f (sort @oldfiles) { &listfile($f, $oldmap{$f}); } } $msg .= "\n"; if ($#newfiles > -1) { $msg .= "Files which are were not setuid before:\n\n"; $msg .= $title; for $f (sort @newfiles) { &listfile($f, $newmap{$f}); } } $msg .= "\n"; if ($#different > -1) { $msg .= "Files which have differences from previous check:\n\n"; $msg .= $title; for $f (sort @different) { &cmpfile($f); } } ## rotate files for $x (1 .. $age) { $x = $age - $x; $y = $x + 1; if (-f "$mapfile.$x") { rename("$mapfile.$x", "$mapfile.$y"); rename("$msgfile.$x", "$msgfile.$y"); } } rename($mapfile, "$mapfile.0"); rename($msgfile, "$msgfile.0"); &writemap(\%newmap, $mapfile); } } if (length($msg)) { $msghdr = <$msgfile") || die("open(>$msgfile): $!\n"); print MSG $msghdr; print MSG $msg; close(MSG); } exit(0); ###################################################################### sub getsizes { $pwmax = 0; $grmax = 0; $szmax = 0; $uidmax = 0; $gidmax = 0; for $k (@oldfiles, keys %newmap) { $d = $newmap{$k} || $oldmap{$k}; ($mode, $uid, $gid, $mtime, $size, $chk) = split(/\s+/, $d); &checkmax($uid, \$uidmax); &checkmax($gid, \$gidmax); &checkmax($grmap{$gid}, \$grmax); &checkmax($pwmap{$uid}, \$pwmax); &checkmax($size, \$szmax); } } sub checkmax { my ($s, $r) = @_; if (length($s) > $$r) { $$r = length($s); } } sub cmpfile { my ($f) = @_; &_listfile("< ", $f, $oldmap{$f}); &_listfile("> ", $f, $newmap{$f}); } sub listfile { &_listfile(" ", @_); } sub _listfile { my ($p, $f, $d) = @_; ($mode, $uid, $gid, $mtime, $size, $chk) = split(/\s+/, $d); $msg .= sprintf("$p %4s $f:\n ", $mode); $msg .= sprintf("%${pwmax}s:%-${grmax}s%9d %${szmax}d $chk\n", "$pwmap{$uid}($uid)", "$grmap{$gid}($gid)", $mtime, $size); } sub writemap { my ($map, $file) = @_; open(OUT, ">$file") || die("open(>$file): $!\n"); for $f (sort keys %$map) { print OUT "$f $$map{$f}\n"; } close(OUT); } sub loadmap { my ($dict, $file) = @_; open(IN, $file) || die("open($file): $!\n"); while () { if (!/^([^\s]+)\s(.*)$/) { die("$file: Bad format at line $.\n"); } $$dict{$1} = $2; } close(IN); } sub check_file { my ($fname, $fstat) = @_; if (-x _ && (-u _ || -g _)) { ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks) = stat(_); $mode = $mode & 0x00000fff; ## we only want the file perms if ($md5) { ($ign, $file, $ign, $chk) = split(/\s+/, `$cksum $fname`); } else { ($chk, $oct, $file) = split(/\s+/, `$cksum $fname`); } $data = sprintf("%04o $uid $gid $mtime $size $chk", $mode); $newmap{$fname} = $data; } } sub search_dir { my ($dir, $func) = @_; my $f, $ftype; my @files; my @sbuf; $debug && print "opendir($dir)\n"; if (!opendir(D, $dir)) { print STDERR "opendir($dir): $!\n"; return 0; } @files = readdir(D); closedir(D); for $f (@files) { ($f eq "." || $f eq "..") && next; ## special case if ($dir eq "/") { $fn = "/$f"; } else { $fn = "$dir/$f"; } if (-l $fn) { next; } elsif (exists($mounts{$fn})) { $debug && print "skipping $fn\n"; next; } ## follow directories elsif (-d _) { my $tfn = $fn; search_dir($fn, $func); } else { &$func($fn, _); } } } sub mappwnames { %pwmap = (); $pwmax = 0; %grmap = (); $grmax = 0; while (($name,$passwd,$uid,$gid) = getpwent()) { if (exists($pwmap{$uid})) { next; } $pwmap{$uid} = $name; if (length($name) > $pwmax) { $pwmax = length($name); } } while (($name,$passwd,$gid,$members) = getgrent()) { if (exists($grmap{$gid})) { next; } $grmap{$gid} = $name; if (length($name) > $grmax) { $grmax = length($name); } } }