Wer einen eigenen Mailserver betreibt, kennt das Problem: Über die Jahre sammeln sich E-Mail-Adressen an. Projekt-Postfächer, die nicht mehr genutzt werden, Konten von ehemaligen Nutzenden oder einfach „Leichen“, die man beim letzten Serverumzug mitgeschleppt hat.
Warum sollte man diese Konten überhaupt löschen? Es geht um weit mehr als nur ein paar Megabyte Speicherplatz.
Warum „Aufräumen“ eine Sicherheitsmaßnahme ist
Ein sauberes System ist ein sicheres System. Inaktive E-Mail-Konten bergen verschiedene Risiken:
- Sicherheitsrisiko: Jedes existierende Konto ist ein potenzielles Einfallstor für Brute-Force-Angriffe. Ein Konto, das niemand aktiv nutzt, wird oft nicht auf verdächtige Aktivitäten überwacht.
- Speicherplatz & Backup-Effizienz: Auch wenn Textnachrichten klein sind, blähen jahrelang gesammelte Anhänge die Backups unnötig auf. Kürzere Backup-Zeiten und weniger Speicherbedarf sind sehr sinvolle Ziele.
- Datenschutz (DSGVO): Nach dem Prinzip der Datensparsamkeit sollten Konten, die keinen Zweck mehr erfüllen, gelöscht werden.
- Reputation des Mailservers: Falls ein altes Konto gehackt und für Spam missbraucht wird, landet die IP deines Servers schnell auf Blacklists.
Was ist eigentlich Froxlor?
Froxlor ist ein schlankes, Open-Source-Server-Management-Panel (ähnlich wie cPanel oder Plesk), das man unter Linux betreiben kann. Es wurde von erfahrenen Systemadministratoren entwickelt, um die Verwaltung von Webhosting-Ressourcen (Domains, E-Mails, Datenbanken) zu vereinfachen, ohne dabei den Server mit unnötigem Overhead zu belasten.
Da Froxlor alle E-Mail-Benutzer in einer MySQL-Datenbank speichert, können wir diese Informationen wunderbar nutzen, um sie mit den System-Logs abzugleichen.
Das Analyse-Skript: Login-Check über alle Logfiles
Das folgende Bash-Skript automatisiert die Suche. Es liest alle E-Mail-Adressen aus der Froxlor-Datenbank aus und durchsucht die Mail-Logs (/var/log/mail.log) inklusive der rotierten und komprimierten Archive (.gz). So erfährt man auf einen Blick, wer sich im letzten Monat (oder solange die Logs zurückreichen) eingeloggt hat.
Das Skript
#!/bin/bash
# Konfiguration
LISTE="/tmp/email-lastlogin.txt"
LOGS="/var/log/mail.log*"
# --- 1. Zeitraum-Analyse über ALLE Dateien ---
# Wir suchen die älteste Datei (höchste Nummer) für den Startzeitpunkt
OLDEST_LOG=$(ls -1 /var/log/mail.log.*.gz 2>/dev/null | sort -V | tail -n 1)
CURRENT_LOG="/var/log/mail.log"
# Falls keine alten Logs da sind, nehmen wir das aktuelle
if [ -z "$OLDEST_LOG" ]; then OLDEST_LOG=$CURRENT_LOG; fi
START_DATE=$(zgrep . "$OLDEST_LOG" | head -n 1 | cut -c 1-15)
END_DATE=$(tail -n 1 "$CURRENT_LOG" | cut -c 1-15)
echo "============================================================================="
echo "MAIL-LOGIN ANALYSE"
echo "Zeitraum: $START_DATE bis $END_DATE"
echo "============================================================================="
# --- 2. E-Mail-Liste aus Froxlor holen ---
echo "Hole E-Mail-Liste aus der Froxlor-Datenbank..."
# Hinweis: Das Passwort wird interaktiv abgefragt
mysql -u root -p -e "SELECT email FROM froxlor.mail_users;" --batch --skip-column-names > "$LISTE"
echo "-----------------------------------------------------------------------------"
# --- 3. Abgleich mit den Logs ---
echo "Prüfung der letzten Logins (bitte Geduld)..."
echo ""
while read -r email; do
[[ -z "$email" || "$email" == "email" ]] && continue
# zgrep durchsucht Text und Gzip-Archive gleichzeitig
# -i: ignoriert Groß/Kleinschreibung, -h: unterdrückt Dateinamen-Ausgabe
LAST_LOGIN=$(zgrep -ih "$email" $LOGS | grep -i "Login" | tail -n 1)
if [ -z "$LAST_LOGIN" ]; then
echo "[ ] $email: KEIN LOGIN im Zeitraum gefunden."
else
# Extrahiert das Datum (ersten 15 Zeichen)
DATE=$(echo "$LAST_LOGIN" | cut -c 1-15)
echo "[X] $email: Letzter Login am $DATE"
fi
done < "$LISTE"
echo "-----------------------------------------------------------------------------"
echo "Analyse abgeschlossen."
Wie man das Skript nutzt
- Speichern: Kopiere den Code in eine Datei, z.B.
check_mail_logins.sh. - Rechte vergeben: Mache das Skript ausführbar:
chmod +x check_mail_logins.sh. - Ausführen: Starte es mit
sudo ./check_mail_logins.sh. Du wirst nach deinem MySQL-Root-Passwort gefragt.
Wie geht man mit dem Ergebnis um?
Adressen, die mit [ ] markiert sind, haben sich im gesamten Log-Zeitraum nicht eingeloggt. Das ist die „To-Do-Liste“. Vor dem Löschen, empfiehlt es sich, die entsprechenden Nutzer zu kontaktieren oder das Postfach zunächst nur für den Login zu sperren, und die Löschung der Daten (E-Mails im Postfach) für einen späteren Zeitpunkt zu avisieren.
Automatisierung über Cron
Um das Skript in einem Cronjob zu nutzen oder einfach den Komfort zu erhöhen, empfiehlt es sich, die MySQL-Zugangsdaten in eine separate Datei auszulagern. Wir nutzen dafür das Standardformat der .my.cnf, auf die nur der Root-Benutzer Zugriff haben sollte.
Vorbereitung
Man erstellt beispielsweise eine Datei /root/.froxlor_db.cnf, mit folgendem Inhalt:
[client]
user=root
password=DEIN_PASSWORT
Und sichert die Datei ab: chmod 600 /root/.froxlor_db.cnf
Das erweiterte Skript für die Nutzung mit Cron
#!/bin/bash
# --- KONFIGURATION ---
# Pfad zur Datei mit den MySQL-Zugangsdaten (Sicherheit!)
DB_CONFIG="/root/.froxlor_db.cnf"
LISTE="/tmp/email-lastlogin.txt"
LOGS="/var/log/mail.log*"
# Überprüfung, ob die Config-Datei existiert
if [ ! -f "$DB_CONFIG" ]; then
echo "Fehler: $DB_CONFIG nicht gefunden! Bitte erstellen für automatischen Login."
exit 1
fi
# --- 1. ZEITRAUM-ANALYSE ---
# Höchste Nummer der .gz Archive finden (älteste Daten)
OLDEST_LOG=$(ls -1 /var/log/mail.log.*.gz 2>/dev/null | sort -V | tail -n 1)
CURRENT_LOG="/var/log/mail.log"
# Falls keine Archiv-Logs vorhanden sind, nur das aktuelle nutzen
[[ -z "$OLDEST_LOG" ]] && OLDEST_LOG=$CURRENT_LOG
START_DATE=$(zgrep . "$OLDEST_LOG" | head -n 1 | cut -c 1-15)
END_DATE=$(tail -n 1 "$CURRENT_LOG" | cut -c 1-15)
echo "============================================================================="
echo "MAIL-LOGIN ANALYSE FÜR FROXLOR"
echo "Zeitraum: $START_DATE bis $END_DATE"
echo "============================================================================="
# --- 2. E-MAIL-LISTE AUS DB EXTRAHIEREN ---
echo "Extrahiere aktive Konten aus Froxlor..."
# Nutzt die --defaults-extra-file Option für passwortlosen Login
mysql --defaults-extra-file="$DB_CONFIG" -e "SELECT email FROM froxlor.mail_users;" --batch --skip-column-names > "$LISTE"
echo "Erfolgreich eingelesen."
echo "-----------------------------------------------------------------------------"
# --- 3. LOG-CHECK ---
echo "Prüfung läuft (dies durchsucht auch komprimierte Archive)..."
echo ""
while read -r email; do
# Überspringe leere Zeilen
[[ -z "$email" ]] && continue
# Suche nach dem aktuellsten Login-Zeitstempel
# -i: Case-insensitive, -h: Dateinamen in der Ausgabe unterdrücken
LAST_LOGIN=$(zgrep -ih "$email" $LOGS | grep -i "Login" | tail -n 1)
if [ -z "$LAST_LOGIN" ]; then
echo "[ ] $email: KEIN LOGIN gefunden (seit $START_DATE)"
else
# Datum aus dem Log-String extrahieren
DATE=$(echo "$LAST_LOGIN" | cut -c 1-15)
echo "[X] $email: Letzter Login am $DATE"
fi
done < "$LISTE"
echo "-----------------------------------------------------------------------------"
echo "Analyse abgeschlossen. Ergebnisse unter $LISTE gespeichert."
Vorteile dieser Lösung:
- Automatisierung: Man kann das Skript jetzt als Cronjob laufen lassen, ohne dass es bei der Passwortabfrage stehen bleibt (z.b. Falls man daraus ein separates Log oder eine Informatione-E-Mail erstellen will).
- Sicherheit: Passwörter stehen nicht im Klartext im Skript (wo sie jeder mit Leserechten sehen könnte), sondern in einer geschützten Systemdatei.
- Fehlertoleranz: Das Skript prüft nun, ob überhaupt Log-Archive existieren, bevor es versucht, das Startdatum zu lesen.
Weitere Ideen
Man kann die Liste /tmp/email_lastlogin.txt auch mit grep "[ ]" filtern, um sofort eine Liste aller „toten“ Konten zu erhalten, die man dann weiter bearbeiten kann.
„Mail-Audit“ Skript (mit HTML-Export)
#!/bin/bash
# --- KONFIGURATION ---
DB_CONFIG="/root/.froxlor_db.cnf"
LISTE="/tmp/email_lastlogin.txt"
HTML_OUT="/var/www/html/mail_audit.html" # Pfad zu deinem Web-Ordner
LOGS="/var/log/mail.log*"
# Überprüfung der Config-Datei
if [ ! -f "$DB_CONFIG" ]; then
echo "Fehler: $DB_CONFIG nicht gefunden!"
exit 1
fi
# --- 1. ZEITRAUM-ANALYSE ---
OLDEST_LOG=$(ls -1 /var/log/mail.log.*.gz 2>/dev/null | sort -V | tail -n 1)
CURRENT_LOG="/var/log/mail.log"
[[ -z "$OLDEST_LOG" ]] && OLDEST_LOG=$CURRENT_LOG
START_DATE=$(zgrep . "$OLDEST_LOG" | head -n 1 | cut -c 1-15)
END_DATE=$(tail -n 1 "$CURRENT_LOG" | cut -c 1-15)
# --- 2. HTML HEADER ERSTELLEN ---
cat <<EOF > "$HTML_OUT"
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Mail-Audit: Froxlor Login-Check</title>
<style>
body { font-family: sans-serif; margin: 40px; background: #f4f4f9; }
table { width: 100%; border-collapse: collapse; background: white; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }
th { background-color: #005f87; color: white; }
tr:hover { background-color: #f1f1f1; }
.active { color: #27ae60; font-weight: bold; }
.inactive { color: #c0392b; font-weight: bold; }
.header-info { margin-bottom: 20px; padding: 15px; background: #e8eff2; border-left: 5px solid #005f87; }
</style>
</head>
<body>
<h1>E-Mail Konto Analyse</h1>
<div class="header-info">
<strong>Analyse-Zeitraum:</strong> $START_DATE bis $END_DATE <br>
<strong>Geprüfte Logs:</strong> $LOGS
</div>
<table>
<tr>
<th>Status</th>
<th>E-Mail Adresse</th>
<th>Letzter Login</th>
</tr>
EOF
# --- 3. DATEN VERARBEITEN ---
echo "Extrahiere Daten und erstelle HTML..."
mysql --defaults-extra-file="$DB_CONFIG" -e "SELECT email FROM froxlor.mail_users;" --batch --skip-column-names > "$LISTE"
while read -r email; do
[[ -z "$email" ]] && continue
LAST_LOGIN=$(zgrep -ih "$email" $LOGS | grep -i "Login" | tail -n 1)
if [ -z "$LAST_LOGIN" ]; then
echo "<tr><td class='inactive'>[ INAKTIV ]</td><td>$email</td><td>Kein Login gefunden</td></tr>" >> "$HTML_OUT"
else
DATE=$(echo "$LAST_LOGIN" | cut -c 1-15)
echo "<tr><td class='active'>[ AKTIV ]</td><td>$email</td><td>$DATE</td></tr>" >> "$HTML_OUT"
fi
done < "$LISTE"
# --- 4. HTML ABSCHLIESSEN ---
echo " </table>
<p><small>Generiert am: $(date)</small></p>
</body>
</html>" >> "$HTML_OUT"
echo "Fertig! Die Analyse ist unter $HTML_OUT verfügbar."
Visualisierung
Wenn man die Ergebnisse nicht nur in der Konsole sehen will, kann man die Ausgabe direkt in eine HTML-Datei umwandeln. Das Skript erzeugt eine Tabelle, in der inaktive Konten rot markiert werden. So hat man eine gute Übersicht.
Die Datei kann beispielsweise einfach im Web-Verzeichnis (z. B. unter /var/www/html/audit.html) abgelegt werden, ggf. sollte man den Ordner mit einem .htaccess Passwort schützen, und hat dann ein kleines Dashboard für die Mail-Hygiene.
Versand per E-Mail
Vollautomatisch: Den Bericht monatlich im Postfach
Durch die Kombination aus dem Skript und einem Cronjob erhält man einmal im Monat eine übersichtliche E-Mail.
Da die HTML-Header direkt mitgesendet werden, erscheint der Bericht direkt als formatierte Tabelle im E-Mail-Programm.
Um das Skript so zu erweitern, dass es die HTML-Ausgabe direkt per E-Mail versendet, nutzt man am besten das Tool mutt oder mailx. Da eine HTML-E-Mail spezielle Header benötigt, damit sie im Mail-Client (Thunderbird, Gmail,..) auch als formatierte Tabelle und nicht als Quellcode angezeigt wird, passen wir das Ende des Skripts an.
Mail-Tool installieren
sudo apt-get install bsd-mailx
Ergänzung (Versand-Logik)
Dieser Block wird am Ende des bestehenden Skripts eingebaut und arbeitet dann denMailversand ab (nachdem die HTML-Datei fertig geschrieben wurde):
# --- 5. E-MAIL VERSAND ---
EMPFAENGER="deine-email@beispiel.de"
BETREFF="Monatlicher Mail-Audit: Froxlor Login-Bericht ($(date +%m/%Y))"
echo "Sende HTML-Bericht an $EMPFAENGER..."
# Versand als HTML-E-Mail
# Wir nutzen 'mail' mit dem Header für HTML-Content
(
echo "Subject: $BETREFF"
echo "MIME-Version: 1.0"
echo "Content-Type: text/html; charset=utf-8"
echo "To: $EMPFAENGER"
echo ""
cat "$HTML_OUT"
) | /usr/sbin/sendmail -t
echo "E-Mail wurde versendet."
Automatisierung per Cronjob
Damit man sich nicht persönlich darum kümmern muss, richten wir einen Cronjob ein. Dieser sorgt dafür, dass das Skript automatisch am 1. jedes Monats um Mitternacht ausgeführt wird.
- Crontab öffnen als Root:
sudo crontab -e - Ganz unten folgende Zeile hinzufügen (Pfade ggf. anpassen):
0 0 1 * * /pfad/zu/deinem/skript.sh > /dev/null 2>&10 0= 00:00 Uhr1= am 1. Tag des Monats* *= jeden Monat, jeden Wochentag
Regelmäßige Bereinigungen können auf diesem Weg zu einem festen Bestandteil der Server-Hygiene werden. Die visuelle Aufbereitung der Daten bietet eine praktische Entscheidungsgrundlage, bevor endgültige Löschungen vorgenommen werden.