I've written a script which is designed to find all files not owned by either an existing user or group. However, despite having created a test user and then removing it leaving behind its /home
directory, the script is not finding it. Clearly I have an error in the script's logic. I just can't find it.
#!/usr/bin/perl
# Directives which establish our execution environment
use warnings;
use strict;
use File::Find;
no warnings 'File::Find';
no warnings 'uninitialized';
# Variables used throughout the script
my $OUTDIR = "/var/log/tivoli/";
my $MTAB = "/etc/mtab";
my $PERMFILE = "orphan_files.txt";
my $TMPFILE = "orphan_files.tmp";
my $ROOT = "/";
my(@devNum, @uidNums, @gidNums);
# 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 HDD mountpoints
while (<MT>) {
if ($_ =~ /ext[34]/) {
my @line = split;
push(@mounts, $line[1]);
}
}
close MT;
# Build an array of each mountpoint's device number for future comparison
foreach (@mounts) {
my @stats = stat($_);
push(@devNum, $stats[0]);
print $_ . ": " . $stats[0] . "\n";
}
# Build an array of the existing UIDs on the system
while((my($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell)) = getpwent()) {
push(@uidNums, $uid);
}
# Build an array of existing GIDs on the system
while((my($name, $passwd, $gid, $members)) = getgrent()){
push(@gidNums, $gid);
}
# Create a regex to compare file device numbers to.
my $devRegex = do {
chomp @devNum;
local $" = '|';
qr/@devNum/;
};
# Create a regex to compare file UIDs to.
my $uidRegex = do {
chomp @uidNums;
local $" = '|';
qr/@uidNums/;
};
# Create a regex to compare file GIDs to.
my $gidRegex = do {
chomp @gidNums;
local $" = '|';
qr/@gidNums/;
};
print $gidRegex . "\n";
# Create the output file path if it doesn't already exist.
mkdir "${OUTDIR}" or die "Cannot execute mkdir on ${OUTDIR}, $!" unless (-d "${OUTDIR}");
# Create our filehandle for writing our findings
open ORPHFILE, ">${OUTDIR}${TMPFILE}" or die "Cannot open ${OUTDIR}${TMPFILE}, $!";
foreach (@mounts) {
# The anonymous subroutine which is executed by File::Find
find sub {
my @fileStats = stat($File::Find::name);
# Is it in a basic directory, ...
return if $File::Find::dir =~ /sys|proc|dev/;
# ...an actual file vs. a link, directory, pipe, etc, ...
return unless -f;
# ...local, ...
return unless $fileStats[0] =~ $devRegex;
# ...and unowned? If so write it to the output file
if (($fileStats[4] !~ $uidRegex) || ($fileStats[5] !~ $gidRegex)) {
print $File::Find::name . " UID: " . $fileStats[4] . "\n";
print $File::Find::name . " GID: " . $fileStats[5] . "\n";
print ORPHFILE "$File::Find::name\n";
}
}, $_;
}
close ORPHFILE;
# If no world-writable files have been found ${TMPFILE} should be zero-size;
# Delete it so Tivoli won't alert
if (-z "${OUTDIR}${TMPFILE}") {
unlink "${OUTDIR}${TMPFILE}";
} else {
rename("${OUTDIR}${TMPFILE}","${OUTDIR}${PERMFILE}") or die "Cannot rename file ${OUTDIR}${TMPFILE}, $!";
}
The test user's home directory showing ownership (or lack thereof):
drwx------ 2 20000 20000 4096 Apr 9 19:59 test
The regex for comparing a files GID to those existing on the system:
(?-xism:0|1|2|3|4|5|6|7|8|9|10|12|14|15|20|30|39|40|50|54|63|99|100|81|22|35|19|69|32|173|11|33|18|48|68|38|499|76|90|89|156|157|158|67|77|74|177|72|21|501|502|10000|10001|10002|10004|10005|10006|5001|5002|5005|5003|10007|10008|10009|10012|10514|47|51|6000|88|5998)
What am I missing with my logic?