Archive

Kategorien

Postfix Mailbox Quota

postfix-quota

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#!/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;

Leave a Reply

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre lang="" line="" escaped="" cssfile="">

  

  

  

eighteen − three =

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahren Sie mehr darüber, wie Ihre Kommentardaten verarbeitet werden .