e-smith-quota/root/sbin/e-smith/warnquota

295 lines
7.4 KiB
Perl

#!/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);
}