Benutzer-Werkzeuge

Action disabled: source

postfix-quota

postfix-quota is a little perl script which offers a service for mailbox-size checking to postfix. The script listens as a daemon on a TCP port and serves requests from postfix. The script answers with a ACTION code for postfix (like DUNNO). If the mailbox is over quota a 450 error is returned until the mailbox is 200% over limit. If more than 200% over limit the script returns a 550 (hard error) to postfix.

The script requires some modules/libraries. Furthermore mysql is expected as the quota is per user is requested from a mysql table

#!/usr/bin/perl
 
# load necessary stuff
use strict;
use warnings;
use DBI;
use DBD::mysql;
use Sys::Syslog qw(:DEFAULT setlogsock);
use base qw(Net::Server::PreFork);
#
# Initalize and open syslog.
#
openlog('postfix-quota','pid','mail');
 
__PACKAGE__->run;
exit;
###
sub configure_hook {
        # config for tcp socket
        # user/group MUST have read access on $self->{basedir}
        my $self = shift;
        $self->{server}->{port}     = '127.0.0.1:20028';
        $self->{server}->{user}     = 'vmail';
        $self->{server}->{group}    = 'vmail';
        $self->{server}->{pid_file} = '/tmp/size.pid';
        $self->{server}->{setsid}   = 1;
        $self->{basedir}            = "/home/vmail/";
}
 
### process the request
sub process_request {
        my $self = shift;
        while(my $line = <STDIN>) {
                # split request on whitespace
                # split the later part (mailaddress) on @
                chomp($line);
                my @parts = split(' ',$line);
                my @values = split('@',$parts[1]);
                # non-valid request received, return DUNNO
                if(!defined $parts[0] || $parts[0] ne "get" || !defined $parts[1] || !defined $values[0] || !defined $values[1])        {
                        print STDOUT "200 DUNNO\n";
                        next;
                }
                # $user and $domain
                my $user = $values[0];
                my $domain = $values[1];
                trim($user);
                trim($domain);
                # max size of mailbox
                my $sqlsize = quotaFromDB("$user\@$domain");
                # instant DUNNO if quotaFromDB() returns an error OR mailbox size 0
                # with 0 as mailbox size, users can be whitelisted from postfix-quota
                if (defined $sqlsize && $sqlsize == 0) {
                        print STDOUT "200 DUNNO\n";
                        next;
                }
                # create path to users mailbox and call checkSize() upon that path
                my $dirsize = checksize($self->{basedir} . $domain. '/'. $user);
                if (defined $dirsize && defined $sqlsize) {
                        # syslog message to mail.log
                        # as every action is logged better keep it commented out to avoid performance issues or huge maillogs
                        # syslog("info","Checking %s maildir size: define=%s, diskusage=%s", "$user\@$domain", format_byte($sqlsize), format_byte($dirsize));
                        # mailbox is overquota
                        if ( $dirsize > $sqlsize ) {
                                # compute relative usage of mailbox
                                my $usage = (100 * $dirsize) / $sqlsize;
                                $usage = sprintf("%.1f",$usage);
                                # $sqlsize and $dirsize formated for output by format_byte()
                                $sqlsize = format_byte($sqlsize);
                                $dirsize = format_byte($dirsize);
                                syslog("info","%s maildir overquota size: define=%s, diskusage=%s", "$user\@$domain", $sqlsize, $dirsize);
                                # return to client that the mailbox is full
                                # so postfix will deny mail for this user
                                # up to 200% overquota a 450 error is returned, this tells the sending client
                                # to try it again later. From 200% overquota the mail is rejected by a 550 hard error.
                                # in this case the client must NOT try it again and has to send an error notification to the origin sender
                                if ( $usage < 200 ) {
                                        print STDOUT "200 452 4.2.2 Mailbox full!! mailbox size: allowed=$sqlsize, used=$dirsize, usage=$usage%\n";
                                        next;
                                } else {
                                        print STDOUT "200 550 5.7.1 Mailbox full!! mailbox size: allowed=$sqlsize, used=$dirsize, usage=$usage%\n";
                                        next;
                                }
                        } else {
                                # NO overquota -> DUNNO
                                print STDOUT "200 DUNNO\n";
                                next;
                        }
                } else {
                        # default answer DUNNO should ensure that postfix does not 
                        # receive block action due to an error
                        print STDOUT "200 DUNNO\n";
                        next;
                }
        }
}
 
# get the quota for the user from mysql
# returns the quota value
sub quotaFromDB {
        my $user = $_[0];
        my $sqlresult;
        trim($user);
        my $dbh = DBI->connect('DBI:mysql:postfix-quota:localhost', 'MYSQLUSER', 'MYSQLPASSWD', { RaiseError => 1 });
        my $sth = $dbh->prepare(qq{SELECT quota FROM mailbox WHERE username='$user'});
        $sth->execute();
        while (my @row = $sth->fetchrow_array) {
                $sqlresult = $row[0];
        }
        $sth->finish();
        $dbh->disconnect;
        if ($sqlresult >= 0 ) {
                return $sqlresult;
        } else {
                return undef;
        }
}
 
 
# removes whitespaces from start and tail of this string
sub trim{
        $_[0]=~s/^\s+//;
        $_[0]=~s/\s+$//;
        return;
}
# checks how much space is used in the filesystem by a mailbox
sub checksize {
        my $diruser = $_[0];
        trim($diruser);
        # get dirsize from filesystem
        # "du" is much faster than "find"
        # -b is important as it forces  "du" to return the size in bytes and not kbytes
        my @size = split(' ',`du -sb $diruser`);
        if (defined $size[0]) {
                my $size = sprintf("%u",$size[0]);
                return $size;
        }
        return undef;
}
# formats a byte number to more readable fashion
sub format_byte {
        my $number = shift;
        if($number >= 1000000000000){
                return sprintf("%.2f TB", $number / 1000000000000);
        }elsif($number >= 1000000000){
                return sprintf("%.2f GB", $number / 1000000000);
        }elsif($number >= 1000000){
                return sprintf("%.2f MB", $number / 1000000);
        }elsif($number >= 1000){
                return sprintf("%.2f KB", $number / 1000);
        }else{
                return sprintf("%.2f Bytes", $number);
        }
}
 
1;
Melden Sie sich an, um einen Kommentar zu erstellen.

Seiten-Werkzeuge