#!/usr/bin/perl -w use Socket; use Getopt::Std; use strict; ############################################################################## # Configuration ############################################################################## # Maximum time to trust cached data. my $maxkeep = 60; ############################################################################## # End of Configuration ############################################################################## # First, we set upt the environment, write a pid file and check that we're not # already running. my $sockname = "/var/run/mailquotad.sock"; my $pidfile = "/var/run/mailquotad.pid"; my $debugmode = 0; # Did we get run with the -d (debug) flag? our $opt_d; getopts('d:'); if ($opt_d) { # This debug mode allows you to run your own checks against the maildir # quota checker. $debugmode = 1; checkquota($opt_d); } else { if (open(F, "<$pidfile")) { my $running = ; close(F); chop($running); die "Another mailquotad is running (pid = $running)\n" if (-e "/proc/$running"); } # Daemonize so that we run in the background. demonize_me(); # do the main bit. main(); } sub main { # change our name $0 = "mailquotad"; # This is our three hashes representing our cache. I could make it a hash of # arrays but I'm being lazy for this prototype. my %quotainfo; my %quotafiletime; my %quotachecktime; # make the socket and listen to it. socket(UNIX, PF_UNIX, SOCK_STREAM, 0) || die "socket: $!"; unlink($sockname); bind(UNIX, sockaddr_un($sockname)) || die "bind: $!"; chmod(0666, $sockname); listen(UNIX, SOMAXCONN) || die "listen: $!"; # Record our PID. open(F, ">$pidfile") || die "Can't write $pidfile: $!\n"; print F "$$\n"; close(F); # ignore PIPEs $SIG{'PIPE'} = sub { }; while (1) { my $maildirpath; my $quotastatus; # read the socket, expecting a path. accept(C, UNIX); sysread(C, $maildirpath, 1024); chomp $maildirpath; # check to see if we have looked at this path before. if ($quotainfo{$maildirpath}) { # We have looked at this path before. See how long it was since the last # time we checked, and if it is greater than $maxkeep secs, we potentially # refetch the quota if (time() - $quotachecktime{$maildirpath} > $maxkeep) { # Last time we checked, it was more than $maxkeep sec ago, so lets # stat the maildirsize file. my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat("$maildirpath/maildirsize"); if ($mtime > $quotafiletime{$maildirpath}) { # The maildirsize file has been modified, lets recalc # the current mailbox size. $quotastatus = checkquota($maildirpath); $quotainfo{$maildirpath} = $quotastatus; $quotafiletime{$maildirpath} = $mtime; $quotachecktime{$maildirpath} = time(); } else { # There has not been any modification to the maildirsize # file, so lets use the cached value. $quotastatus = $quotainfo{$maildirpath}; } } else { # Its been less than $maxkeep seconds since we last fetched the # maildir size, lets just use the cached information. We do # this so that if the mailbox is getting a lot of mail all at once, $quotastatus = $quotainfo{$maildirpath}; } } else { # No information for this path before. my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat("$maildirpath/maildirsize"); $quotastatus = checkquota($maildirpath); $quotainfo{$maildirpath} = $quotastatus; $quotafiletime{$maildirpath} = $mtime; $quotachecktime{$maildirpath} = time(); } syswrite(C, $quotastatus); close(C); } } sub checkquota { my ($maildir) = @_; my $size_quota = 0; my $size = 0; my $filecount_quota = 0; my $filecount = 0; if ($debugmode) { print "Checking quota for $maildir\n"} if (open MAILDIRSIZE, "<$maildir/maildirsize") { my $first = ; if ($first) { # we make an assumption here that the first line is in # the format 576S,0C $first =~ m/([0-9]+)[Ss],([0-9]+)[Cc]/; $size_quota = $1; $filecount_quota = $2; while (my $line = ) { chomp($line); my ($bytes, $files) = split ' ', $line; $size += $bytes; $filecount += $files; } } close MAILDIRSIZE; } if ($debugmode) { print "Filesize Quota: $size_quota. Current size: $size.\n"} if ($debugmode) { print "Filecount Quota: $filecount_quota. Current size: $filecount.\n"} # If the quota is set to 0 for bytes/count, we ignore it. if ($size_quota == 0) { $size = 0 }; if ($filecount_quota == 0) { $filecount = 0 }; if ($size > $size_quota || $filecount > $filecount_quota) { if ($debugmode) { print "$maildir is over quota.\n"} return "yes"; } else { if ($debugmode) { print "$maildir is not over quota.\n"} return "no"; } } sub demonize_me { defined (my $pid = fork) or die "Can't fork: $!"; if ($pid) { exit; } else { require 'POSIX.pm'; &POSIX::setsid or die "Can't start a new session: $!"; open STDOUT,'>/dev/null' or die "ERROR: Redirecting STDOUT to /dev/null: $!"; open STDERR,'>/dev/null' or die "ERROR: Redirecting STDERR to /dev/null: $!"; open STDIN, '