Aus Linux-Magazin 02/2006

Postfix Restriction Classes nutzen

© Martin Abegglen/www.twicepix.com

Postfix beherrscht weit mehr als das Standardrepertoire eines Mailservers. Im hier vorgestellten Szenario geht es um eine datenbankgestützte Konfiguration, die mittels Restriction Classes Benutzer in unterschiedlich privilegierte Gruppen aufteilt.

Die Spamflut überschwemmte den internen Mailverkehr nicht, wenn er seine eigenen Kanäle hätte. Die teuren Virenscanner-Lizenzen müssten eigentlich nicht für solche Anwender beschafft werden, die ohnehin nur interne Mails verfassen und empfangen. Zwei Überlegungen, die mögliche Gründe liefern könnten, um die Benutzer des Mailservers in zwei Gruppen zu teilen, von denen die einen nur interne Mails senden und empfangen sollen, während die anderen auch mit dem Rest der Welt kommunizieren dürfen. Der verbreitete Mailserver Postfix erlaubt durch sein System der so genannten Restriction Classes eine solche trickreiche Konfiguration.

Je nach Gesprächsverlauf

Restriktionen sind ein mächtiges Werkzeug, mit dem Postfix Entscheidungen in Abhängigkeit von Verlauf und Inhalt des SMTP-Dialogs treffen und Benutzer oder Gruppen von Benutzern entsprechend behandeln kann. Es gibt mehrere vordefinierte Restriktionen, etwa »smtpd_sender_restrictions« oder »smtpd_end_of_data_restrictions«. Daneben ist mit den Restriction Classes eine Art Behälter für eigene Regelsätze verfügbar.

Das hier vorgestellte Anwendungsbeispiel, das den Einsatz von Restriction Classes demonstriert, verwendet als Hauptkomponenten den Postfix-SMTP-Server in der Version 2.1.5 und den Cyrus-IMAP-Server 2.1.18. Die Userdaten nimmt eine MySQL-Datenbank auf. Die Administration erfolgt über Webcyradmin, eine PHP-Skriptsammlung, und PHP-MyAdmin, das es im Fehlerfall erlaubt, schnell die Inhalte der einzelnen MySQL-Tabellen zu überprüfen. Zum Betrieb des Mailsystems sind jedoch beide nicht erforderlich. Alle Komponenten installiert und konfiguriert der Admin wie unter [1] oder [3] beschrieben.

Datenbank vorbereiten

Nachdem die Skripte »insertuser_mysql.sql« und »create_mysql.sql« vom Webcyradmin die Grundstruktur der Datenbank generiert haben, gilt es, die Tabelle »accountuser« um das Feld »local-only« vom Typ »enum« mit den möglichen Werten »localonly« und »OK« zu erweitern. Dies kann entweder über PHP-Myadmin oder über den MySQL-Kommandozeilen-Client erfolgen.

Die zweite, ebenfalls noch zu erweiternde Tabelle ist »domain«. Hier wird lediglich eine Status-Spalte für Postfix ergänzt, die der Mailserver benötigt, wenn er bei einer Abfrage auf eine Heimatdomain trifft:

alter table accountuser add localonly enum('localonly','OK') NOT NULL default'localonly';
alter table domain add column status varchar(2) default 'OK';

MySQL-Zugriff

Im Gegensatz zu einer Standard-Postfix-Installation verwendet das hier diskutierte Beispiel keine Lookup-Tables in Form von Hashtabellen unter »/etc/postfix«, stattdessen bezieht der Mailserver die notwendigen Informationen direkt aus der MySQL-Datenbank. Die dafür installierte Postfix-Erweiterung erlaubt in der Konfigurationsdatei »/etc/postfix/main.cf« Lookup-Konstrukte, wie sie in Listing 2 aufgeführt sind.

Die einzelnen MySQL-CF-Dateien enthalten dabei die SQL-Befehle, die auszuführen sind, um die erforderlichen Informationen abzufragen. Sie sind in [1] dokumentiert. Die im Zusammenhang mit den Restriction Classes neu hinzugekommenen CF-Dateien erläutert Listing 1a-c. Es zeigt allerdings nur die für das jeweilige SQL-Statement erforderlichen Zeilen. Der hier nicht mit abgedruckte Kopf dieser Konfigurationsfiles ist aber identisch mit dem in den standardmäßig vorhandenen MySQL-CF-Dateien, beispielsweise »mysql-transport.cf«

Normalerweise behandelt Postfix alle zur eigenen Domain gehörenden User vollkommen gleich. In der Beispieldomain »mydomain.de« besteht jedoch im Gegensatz dazu die Anforderung, eine Usergruppe zu bilden, die ihren Kommunikationspartner frei wählen darf, wogegen es einer zweiten Gruppe nur erlaubt sein soll, ausschließlich mit Usern aus der eigenen Domain E-Mails auszutauschen. Um diese Unterscheidung durchzusetzen, kann man eine Postfix Restriction Class konfigurieren. Sie soll prüfen, ob eine zu verarbeitende Mailadresse in der Datenbank mit dem Attribut »localonly« versehen ist. Trifft dies zu, darf der Kommunikationspartner auch nur aus der eigenen Domain stammen.

User mit Restriktionen

Der Versuch, Benutzer in einer fremden Domain anzusprechen, führt dazu, dass Postfix bereits die Annahme der Mail verweigert. Zur Prüfung sammelt Postfix im SMTP-Dialog zunächst alle erforderlichen Angaben – für diesen Zweck also Sender- und Empfängeradresse. Danach ergeben die Konfigurationsklausel und die darüber ausgelöste SQL-Abfrage aus Listing 3, ob der fragliche Anwender einer Restriktion unterworfen ist.

Im hier vorgestellten Beispiel kann dies nur die Restriktion »localonly« sein, die durch »smtpd_restriction_classes = localonly« in »main.cf« deklariert ist. Fällt der überprüfte User unter die Restriktion »localonly«, regelt eine weitere Zeile aus »main.cf«, welche Prüfung daraufhin zu erfolgen hat: »localonly = check_recipient_access mysql:/etc/postfix/mysql-localdomains.cf, reject«.

Dieser Eintrag prüft, ob die Empfängeradresse in der lokalen Domain liegt (die Postfix über »mysql-localdomains.cf« ermittelt – siehe Listing 1b). Liefert die dort kodierte SQL-Anweisung »SELECT status FROM domain WHERE domain_name=\’ Prüfdomain\’;« ein OK, dann ist die Prüfung abgeschlossen und die Mail wird akzeptiert.

Liefert die SQL-Abfrage jedoch keine Treffer, weil die Zieldomain nicht die lokale Domain ist, dann führt Postfix den zweiten Teil der Restriction Class, nämlich die Aktion »reject« aus, wodurch die Annahme der Mail verweigert wird. In diesem Fall erfolgt anschließend keine weitere Prüfung mehr.

Fiel der Test auf einen lokalen Absender mit »localonly« negativ aus, kann es sich entweder nur um einen lokalen Absender ohne Restriktionen oder um einen externen Absender handeln. Die zweite Zeile der »smtpd_recipient_restrictions« mit dem Inhalt »check_sender_access mysql:/etc/postfix/mysql-localdomains.cf« prüft nun, ob es sich um einen lokalen Absender ohne Restriktion handelt. Dabei kommt wiederum die bereits besprochene Datenbankabfrage nach den lokalen Domains zum Tragen – diesmal wird sie jedoch für den Absender ausgeführt.

Listing 1a:
»/etc/postfix/mysql-restricted_senders.cf«

01 # the database name on the servers
02 dbname = mail
03 # the table name
04 table = accountuser,virtual
05 #
06 select_field = localonly
07 where_field = accountuser.username=virtual.username and alias

Listing 1b:
»/etc/postfix/mysql-localdomains.cf«

01 table = domain
02 #
03 select_field = status
04 where_field = domain_name

Listing 1c:
»/etc/postfix/mysql-restricted_recipients.cf«

01 table = accountuser
02 #
03 select_field = 'restricted'
04 where_field = username
05 additional_conditions = and localonly = 'localonly'

Listing 2: Ausschnitt aus
»/etc/postfix/main.cf«

01 mydestination = mysql:/etc/postfix/mysql-mydestination.cf
02 virtual_maps = mysql:/etc/postfix/mysql-virtual.cf
03 sender_canonical_maps = mysql:/etc/postfix/mysql-canonical.cf
04 relay_domains = mysql:/etc/postfix/mysql-relay.cf
05 transport_maps = mysql:/etc/postfix/mysql-transport.cf
06 smtpd_recipient_restrictions=check_sender_access
07 mysql:/etc/postfix/mysql-restricted_senders.cf,
08 check_sender_access mysql:/etc/postfix/mysql-localdomains.cf,
09 check_recipient_access mysql:/etc/postfix/mysql-restricted_recipients.cf,
10 localonly = check_recipient_access mysql:/etc/postfix/mysql-localdomains.cf,
11 reject

Nicht für alle

Für den Fall, dass der Absender zu einer lokalen Domain gehört, liefert diese Abfrage wieder ein OK und akzeptiert die Mail. Handelt es sich dagegen um einen externen Absender, dürfen nur lokale Nutzer ohne Restriktion Post von ihm empfangen. Die dritte Zeile aus »smtp_receipient_restrictions« (»check_recipient_access mysql:/etc/postfix/mysql-restricted_recipients.cf«) führt mittels »SELECT \’reject\’ FROM accountuser,virtual WHERE alias=Receipents_email AND localonly = \’localonly\’ AND accountuser.username=virtual.username;« eine weitere Abfrage aus, die nur für die User mit dem label »localonly« den Wert »reject« zurückliefert.

Dabei handelt es sich um einen kleinen SQL-Trick: Da weder in der Tabelle »accountuser« noch in der Tabelle »virtual« eine Spalte »reject« existiert, wird die Abfrage immer den Wert »reject« zurückliefern, wenn sich überhaupt ein Match ergab, also die Abfrage die gesuchte E-Mail zusammen mit dem Attribut »localonly« fand. Damit hat Postfix dann auch eine Dienstanweisung, die ihm vorgibt, was im Trefferfall zu tun ist: Die Annahme einer solchen E-Mail ist immer zu verweigern.

Kein offenes Relay

Falls diese Prüfung kein Ergebnis erbracht hat, also der Empfänger kein lokaler User mit Restriktionen ist, regelt die vierte und letzte Zeile der »smtpd_recipient_restrictions«, wie mit der E-Mail zu verfahren ist, und zwar: »reject_unauth_destination«. Dieser Ausdruck weist Postfix dazu an, alles, was nicht für lokale Empfänger bestimmt ist, abzulehnen. Da die Prüfung bis hierhin durchlief, ist lokaler Empfänger gleichbedeutend mit lokaler Empfänger ohne Restriktionen.

Geblockt werden an dieser Stelle also alle von externen Absendern an externe Empfänger adressierten Mails, die den Server kreuzen. Das verhindert, dass Postfix zum offenen Relay für Spammer wird. Die Abbildung 1 gibt den gesamten Ablauf der Entscheidungsfindung noch einmal übersichtlich in einem Flussdiagramm wieder.

Abbildung 1: Der Entscheidungsprozess in Abhängigkeit von der Sender-Empfänger-Kombination.

Abbildung 1: Der Entscheidungsprozess in Abhängigkeit von der Sender-Empfänger-Kombination.

Damit das Anlegen und spätere Editieren der Mailuser über das Webinterface mit oder ohne die Restriction Class erfolgen kann, sind noch ein paar Änderungen in den PHP-Skripten des Webcyradm erforderlich. Zunächst bekommt »newaccount.php« eine Checkbox eingefügt, durch die »localonly« später als Option beim Anlegen eines Users zur Verfügung steht. Dies kann beispielsweise gleich direkt unter dem »Confirm Password«-Textfeld sein:

<tr><td>local only</td><td> 
<input class="inputfield" name="localonly" type="checkbox" value="localonly" CHECKED>
</td></tr>

Abbildung 2 zeigt das zugehörige ergänzte Eingabeformular.

Abbildung 2: Das Eingabeformular wurde um die Checkbox »localonly« ergänzt.

Abbildung 2: Das Eingabeformular wurde um die Checkbox »localonly« ergänzt.

Oberfläche anpassen

Damit auch der richtige Wert (nämlich »localonly« oder »OK«) in der entsprechenden Variablen abgelegt wird, ist vor dem Aufbau der SQL-Anweisung noch eine Entscheidung fällig. Das nächste Statement zeigt die Vorbelegung der fraglichen Variablen:

if ($localonly=="localonly"){
// nop
} else {
   $localonly="OK";
}

Die letzte Änderung in dieser Datei erweitert das eigentliche SQL-Statement für die Übergabe der Werte in die Datenbank um das Feld »localonly«:

$query3="INSERT INTO accountuser (username, password, prefix, domain_name, localonly) VALUES ('" . $username . "','" . $password . "','" . $prefix . 
"','" . $domain . "','" . $localonly "')";

Damit der Administrator die Restriktion bei Bedarf auch nachträglich anpassen kann, sind noch ein paar kleine Änderungen in »editemail.php« erforderlich (siehe Listing 4). Last but not least muss er die beiden neu hinzugekommenen Variablen zusätzlich in die Datei »validate.inc.php« eintragen, damit das Skript ihre Inhalte nicht bei der nächsten Bearbeitung gleich wieder verwirft (siehe Listing 5). (jcb)

Listing 4: Erforderliche
Änderungen in »editemail.php«

     29    $localonly = $row['localonly'];
:
     33   if ($newlocalonly=="localonly") {
     34        $query = "UPDATE virtual,accountuser SET localonly='localonly', alias='$newalias@$domain', dest='$dest' WHERE alias='$alias' and accountuser.username=virtual.username";
     35    } else {
     36        $query = "UPDATE virtual,accountuser SET localonly='OK', alias='$newalias@$domain', dest='$dest' WHERE alias='$alias' and accountuser.username=virtual.username";
     37    }
     38 } else {
     39    if ($newlocalonly=="localonly") {
     40        $query = "UPDATE virtual,accountuser SET localonly='localonly', alias='$newalias', dest='$dest' WHERE alias='$alias' and accountuser.username=virtual.username";
     41    } else {
     42        $query = "UPDATE virtual,accountuser SET localonly='OK', alias='$newalias', dest='$dest' WHERE alias='$alias' and accountuser.username=virtual.username";
     43    }
:
    137       <td>
    138          <input class="inputfield"
    139                  type="checkbox"
    140                  name="newlocalonly" value="localonly"
    141                  <?php
    142                      if ($localonly!="OK") {
    143                         echo "checked";
    144                      }
    145                  ?>
    146       </td>

Listing 5:
»validate.inc.php«

01 $_get_vars = array(
02         'adminuser', 'newadminuser', 'admintype', 'newadmintype', 'newusername', 'type', 'newtype', 'domain', 'prefix', 'action', 'row_pos', 'username', 'password', 'new_password', 'confirm_password', 'quota', 'maxaccounts', 'newdomain', 'email', 'alias', 'dest', 'newalias', 'newdest', 'confirmed', 'cancel', 'searchstring', 'transport', 'tparam', 'mode', 'forwards',  'metoo', 'vacation_text', 'freenames', 'freeaddress', 'LANG', 'delete_catchall', 'resp_domain', 'newdomain', 'newlocalonly');
03 
04 $_post_vars = array(
05         'confirmed', 'action', 'domain', 'alias' , 'username', 'new_password', 'confirm_password', 'email', 'quota', 'password', 'newadminuser', 'newadmintype', 'newdomain', 'newtype', 'resp_domain', 'localonly');

Infos

[1] Heiner Lamprecht, “Postmoderne Mail: Postfix-Cyrus-MySQL”: Linux Magazin 12/04, S. 66

[2] Webcyradm: [http://www.web-cyradm.org]

[3] Installationsanleitung für Debian Sarge: [http://www.web-cyradm.org/pipermail/web-cyradm/2004-March/017191.html]

Der Autor

Kai Krebber arbeitet als Technical Consultant beim Systemhaus Krick in Bad Oeynhausen für Kunden Linux- und Firewall-Lösungen aus.

LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Nach oben