postfix-quota ist ein kleines Perl Script welches einen Dienst zur Abfrage von Mailboxgrössen für Postfix zur Verfügung stellt. Dabei öffnet das Script TCP-Sockets und lauscht auf Anfragen von Postfix. Das Script beantwortet Anfragen mit einem ACTION Code für Postfix. Ist die Quota in Ordnung oder tritt ein Fehler auf, dann kommt als ACTION ein DUNNO. Ist der User overquota dann wird Postfix angewiesen die Mail zu verweigern. Bis zu 200% Overquota mittels temporärem 450-er Fehler und ab 200% mit einem harten 550-er Fehler.
Das Script setzt Perl und ein paar Module/Libraries voraus. Zudem mysql, da die erlaubte Quote pro User aus einer Mysql-Tabelle abgefragt wird
#!/usr/bin/perl 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 { #Konfigution fuer den TCP Socket #User/Gruppe muessen auf basedir Leserechte haben 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>) { # Request zerlegen am Leerzeichen # den hinteren Teil (Mailadresse) an @ zerlegen chomp($line); my @parts = split(' ',$line); my @values = split('@',$parts[1]); # Fehlerhafter Request DUNNO zurueckgeben, damit postfix 'nichts macht' if(!defined $parts[0] || $parts[0] ne "get" || !defined $parts[1] || !defined $values[0] || !defined $values[1]) { print STDOUT "200 DUNNO\n"; next; } # $user und $domain my $user = $values[0]; my $domain = $values[1]; trim($user); trim($domain); # Maximale Groesse der Mailbox my $sqlsize = quotaFromDB("$user\@$domain"); # sofort "abbrechen" wenn quotaFromDB() einen Fehler oder den Wert 0 produziert # mit dem quota Wert 0 in der DB kann man also einen User von quotas ausnehmen if (defined $sqlsize && $sqlsize == 0) { print STDOUT "200 DUNNO\n"; next; } # Pfad zur Usermailbox erstellen und an checksize uebergeben my $dirsize = checksize($self->{basedir} . $domain. '/'. $user); if (defined $dirsize && defined $sqlsize) { # syslog Meldung in mail.log # da jede Pruefung geloggt wird habe ich es aus # Performance-Gruenden auskommentiert # syslog("info","Checking %s maildir size: define=%s, diskusage=%s", "$user\@$domain", format_byte($sqlsize), format_byte($dirsize)); # Mailbox ist Overquota if ( $dirsize > $sqlsize ) { # Prozentuale Belegung der Mailbox berechnen my $usage = (100 * $dirsize) / $sqlsize; $usage = sprintf("%.1f",$usage); # $sqlsize und $dirsize werden fuer die Textausgabe # duch format_byte() formatiert $sqlsize = format_byte($sqlsize); $dirsize = format_byte($dirsize); syslog("info","%s maildir overquota size: define=%s, diskusage=%s", "$user\@$domain", $sqlsize, $dirsize); # Meldung an den Client (postfix) geben dass die Mailbox voll ist # damit wird postfix eine eingehende Mail verweigern # bis 200% overquota wird "nur" ein temporaerer Fehler ausgegeben # damit der Client es spaeter nochmals probiert # ab 200% overquota wird ein harter SMTP Fehler ausgegeben # damit muss der Client diese Zustellung als gescheitert betrachten und eine Fehlermeldung an den Absender schicken 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 { # Kein Overquota drum DUNNO print STDOUT "200 DUNNO\n"; next; } } else { # Soll quasi als default Antwort dienen und # sicherstellen das postfix nicht eine Anweisung zum Blockieren bekommt print STDOUT "200 DUNNO\n"; next; } } } # liest die Quota des Users aus der mysql-DB # gibt den Wert aus der DB an den Aufrufer zurueck 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; } } # Entfernt Leerzeichen vom Anfang umd vom Ende des Strings sub trim{ $_[0]=~s/^\s+//; $_[0]=~s/\s+$//; return; } # Prueft wieviel Platz eine Mailbox im Dateisystem belegt sub checksize { my $diruser = $_[0]; trim($diruser); # effektive Groesse des Verzeichnisses ermitteln # "du" ist wesentlich schneller als "find" # -b ist wichtig damit "du" die Groesse in bytes und nicht in kbytes zurueckgibt my @size = split(' ',`du -sb $diruser`); if (defined $size[0]) { my $size = sprintf("%u",$size[0]); return $size; } return undef; } # Formatiert eine Zahl 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;