Over at Stack Overflow I asked how to scan a local filesystem ignoring all remote mount points. I only really wanted a push in the right direction and received it. After clacking away on my keyboard I think I might have what I what I need. I ask for an evaluation to determine if it fulfills the requirements:
- Scan every local directory for world-writable files ignoring "special" directories (/sys, /proc, /dev)
- Ignore directories
- Ignore files on any remote filesystems which may be mounted
- Ignore any files in an "exclude" list
I'm quite certain it could be drastically optimized and any suggestions are welcome as are any suggestions for a better algorithm. However, rewriting the script is secondary to performing the task properly.
I've tested it and I'm fairly confident that I've created what I want. I just need some extra eyes to confirm.
#!/usr/bin/perl
# Directives which establish our execution environment
use warnings;
use strict;
use Fcntl ':mode';
use File::Find;
no warnings 'File::Find';
no warnings 'uninitialized';
# Variables used throughout the script
my $DIR = "/var/log/tivoli/";
my $MTAB = "/etc/mtab";
my $PERMFILE = "world_writable_w_files.txt";
my $TMPFILE = "world_writable_files.tmp";
my $EXCLUDE = "/usr/local/etc/world_writable_excludes.txt";
my $ROOT = "/";
my @devNum;
# Create an array of the file stats for "/"
my @rootStats = stat("${ROOT}");
# Compile a list of mountpoints that need to be scanned
my @mounts;
open MT, "<${MTAB}" or die "Cannot open ${MTAB}, $!";
# We only want the local mountpoints
while (<MT>) {
if ($_ =~ /ext[34]/) {
my @line = split;
push(@mounts, $line[1]);
}
}
close MT;
# Read in the list of excluded files and create a regex from them
my $regExcld = do {
open XCLD, "<${EXCLUDE}" or die "Cannot open ${EXCLUDE}, $!\n";
my @ignore = <XCLD>;
chomp @ignore;
local $" = '|';
qr/@ignore/;
};
# Create a regex containing mountpoint dev numbers to compare file device numbers to.
my $devRegex = do {
chomp @devNum;
local $" = '|';
qr/@devNum/;
};
# Create the output file path if it doesn't already exist.
mkdir("${DIR}" or die "Cannot execute mkdir on ${DIR}, $!") unless (-d "${DIR}");
# Create our filehandle for writing our findings
open WWFILE, ">${DIR}${TMPFILE}" or die "Cannot open ${DIR}${TMPFILE}, $!";
foreach (@mounts) {
# The anonymous subroutine which is executed by File::Find
find sub {
# Is it in a basic (non-special) directory, ...
return if $File::Find::dir =~ /sys|proc|dev/;
# ...a regular file, ...
return unless -f;
# ...local, ...
my @dirStats = stat($File::Find::name);
return unless $dirStats[0] =~ $devRegex;
# ...and world writable?
return unless (((stat)[2] & S_IWUSR) && ((stat)[2] & S_IWGRP) && ((stat)[2] & S_IWOTH));
# Add the file to the list of found world writable files unless it is
# in the list if exclusions
print(WWFILE, "$File::Find::name\n") unless ($File::Find::name =~ $regExcld);
}, $_;
}
close WWFILE;
# If no world-writable files have been found ${TMPFILE} should be zero-size;
# Delete it so Tivoli won't alert
if (-z "${DIR}${TMPFILE}") {
unlink "${DIR}${TMPFILE}";
} else {
rename("${DIR}${TMPFILE}","${DIR}${PERMFILE}") or die "Cannot rename file ${DIR}${TMPFILE}, $!";
}
open my $fh, '<', $file or die "$file: $!";
5) Instead of generating complex regexes (which you haven't anchored, by the way, so they will probably match more than you want), use hashes: (continue) \$\endgroup\$my %ignore = map { $_ => 1 } @array;
6) Don't callfind
in a loop, just call it once with@mounts
as the second argument. 7) Write a separatewanted
sub and pass it tofind
to improve readability. 8) Don't userename
as it's not portable. Instead usemove
from File::Copy. \$\endgroup\$find
with@mounts
as the second argument how do you mean? Would it be}, $_, @mounts;
? For anchoring the regexes (If I stick with that rather than mapping to a hash) would this be correct:qr/^@devNum$/;
? I'm not sure I know how to use a separatewanted
sub. \$\endgroup\$