#!/usr/bin/perl -w package esmith; use strict; use esmith::ConfigDB; use esmith::util; use esmith::db; use Text::Template; sub getUserQuotas(); sub alertUsersOverQuota($$); sub sendAdminSummary($$$$); sub toMB ($); sub toMBNoDecimalPlaces ($); sub toKB ($); my $conf = esmith::ConfigDB->open_ro; =for testing is( system("$^X -cw $Original_File 2>&1"), 0, 'it compiles' ); =head1 Automated quota reporting This script iterates through each user account on the system and sends an email report to each user when they are over quota. It sends a combined summary report to the administrator. =cut #---------------------------------------------------------------------- # Set qmail environment variables so that we get decent looking mail # headers (From, Return-Path) #---------------------------------------------------------------------- my $domain = $conf->get_value("DomainName") || 'localhost'; my ($quotaOffenders, $quotaUsers, $quotalessUsers) = getUserQuotas(); alertUsersOverQuota($conf, $quotaOffenders); sendAdminSummary($conf, $quotaOffenders, $quotaUsers, $quotalessUsers); =head2 getUserQuotas() For each user on the system, determines their current disk usage, soft and hard quota, and grace period remaining. It returns three data structures containing users whom are over quota, users whom are under quota, and those with no quotas: %hash = { 'user1' => { 'hardQuota', 'softQuota', 'username', 'usage', 'fullname', 'gracePeriod' }, . . . . 'userX' => { 'hardQuota', 'softQuota', 'username', 'usage', 'fullname', 'gracePeriod' } }; =cut sub getUserQuotas() { use Quota; use esmith::AccountsDB; my $adb = esmith::AccountsDB->open_ro(); my %usersOverQuota; my %usersUnderQuota; my %usersWithNoQuota; # Make $dev arg for quota query calls my $dev = Quota::getqcarg('/home/e-smith/files'); #------------------------------------------------------------ # Loop through all user accounts and collect stats. #------------------------------------------------------------ foreach my $user ($adb->users) { my $uid = getpwnam($user->key); unless ($uid) { warn("Could not get uid for user \"" . $user->key ."\""); next; } my ($bc, $bs, $bh, $bt, $ic, $is, $ih, $it) = Quota::query($dev, $uid); if( !defined $bc ) # Quota::query failed { warn "Cannot query quota for '" . $user->key . "' on '$dev': ", Quota::strerr(), "\n"; next; } my $name = $user->prop('FirstName') . " " . $user->prop('LastName'); #------------------------------------------------------------ # Collect user stats if over quota. # Everyone over quota goes into %usersOverQuota # Everyone under quota goes into %usersUnderQuota # Everyone else, goes into %usersWithNoQuota. #------------------------------------------------------------ if ( ($bs == 0) && ($bh == 0) ) { # User has no quota # Add user to data structure. ${usersWithNoQuota{$user->key}} = { username => $user->key, fullname => $name, usage => toMB($bc), softQuota => toMB($bs), hardQuota => toMB($bh), gracePeriod => $bt }; } elsif ( (($bc > $bh) && ($bh > 0)) || (($bc > $bs) && ($bs > 0)) ) { # User is over quota # gracePeriod is 0 if over hard limit # Add user to data structure. ${usersOverQuota{$user->key}} = { username => $user->key, fullname => $name, usage => toMB($bc), softQuota => toMB($bs), hardQuota => toMB($bh), gracePeriod => $bt }; } else { # User is within quota # Add user to data structure. ${usersUnderQuota{$user->key}} = { username => $user->key, fullname => $name, usage => toMB($bc), softQuota => toMB($bs), hardQuota => toMB($bh), gracePeriod => $bt }; } } return \%usersOverQuota, \%usersUnderQuota, \%usersWithNoQuota; } =head2 alertUsersOverQuota($) This function iterates through all users who are over quota (contained in data structure above), and sends email to each one. It doesn't bother sending mail to those users who have exceeded their hard quota, since they probably won't be able to receive e-mail, unless there's either a delegate mail server defined, or their mail spool is elsewhere. =cut sub alertUsersOverQuota($$) { my ($conf, $usersOverQuota) = @_; return unless keys %$usersOverQuota; foreach my $user (keys %$usersOverQuota) { #------------------------------------------------------------------ # Don't send email to users who are over their hard quota # They likely won't be able to receive it. # Plus, they've already had 7 days of warnings. #------------------------------------------------------------------ next if ( ($usersOverQuota->{$user}{usage} > $usersOverQuota->{$user}{hardQuota}) and ($usersOverQuota->{$user}{hardQuota} != 0) ); my $templates = '/etc/e-smith/templates'; my $source = '/usr/lib/e-smith-quota/userOverQuota.tmpl'; # Use templates-custom version by preference if it exists -f "${templates}-custom${source}" and $templates .= "-custom"; my $t = new Text::Template(TYPE => 'FILE', SOURCE => "${templates}${source}"); open(QMAIL, "|/var/qmail/bin/qmail-inject -fdo-not-reply\@$domain $user") || die "Could not send mail via qmail-inject!\n"; print QMAIL $t->fill_in( HASH => { conf => \$conf, data => $usersOverQuota->{$user} }); close QMAIL; } } =head2 sendAdminSummary($$$$) Generates an email summary of users and their quota statuses. It takes as arguments a reference to a tied configuration database hash, and three data structures (format above) containing: - users over quota - users under quota - users without quotas. At the moment, it only emails those users who are over quota to the administrator. =cut sub sendAdminSummary($$$$) { my ($conf, $usersOverQuota, $usersUnderQuota, $usersWithNoQuota) = @_; return unless keys %$usersOverQuota; my $templates = '/etc/e-smith/templates'; my $source = '/usr/lib/e-smith-quota/adminQuotaSummary.tmpl'; # Use templates-custom version by preference if it exists -f "${templates}-custom${source}" and $templates .= "-custom"; my $t = new Text::Template(TYPE => 'FILE', SOURCE => "${templates}${source}"); open(QMAIL, "|/var/qmail/bin/qmail-inject -fdo-not-reply\@$domain admin") || die "Could not send mail via qmail-inject!\n"; print QMAIL $t->fill_in( HASH => { conf => \$conf, usersOverQuota => $usersOverQuota, usersUnderQuota => $usersUnderQuota }); close QMAIL; } =head2 toMB($) Takes a number as input, and output the number divided by 1024, but also formatted to two decimal places. A helper function to convert kilobytes to megabytes. =cut #---------------------------------------------------------------------- # Helper function to convert kilobytes to megabytes. #---------------------------------------------------------------------- sub toMB ($) { my ($kb) = @_; return sprintf("%.2f", $kb / 1024); }