295 lines
7.4 KiB
Perl
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);
|
|
}
|