diff --git a/dovecot/s3_email_downloader.py b/dovecot/s3_email_downloader.py index dee080e..cd54673 100644 --- a/dovecot/s3_email_downloader.py +++ b/dovecot/s3_email_downloader.py @@ -3,18 +3,12 @@ S3 E-Mail Downloader Script Dieses Script lädt E-Mails aus einem S3-Bucket herunter und speichert sie im -Maildir-Format für Dovecot. Es berücksichtigt die Docker-Container-Umgebung. +Maildir-Format für Dovecot. Es filtert nach bestimmten Domains und Benutzernamen +und kann nicht passende E-Mails löschen. -Verwendung: - python3 s3_email_downloader.py - -Umgebungsvariablen: - AWS_ACCESS_KEY_ID: AWS Zugriffsschlüssel - AWS_SECRET_ACCESS_KEY: AWS geheimer Zugriffsschlüssel - AWS_REGION: AWS-Region (Standard: us-east-2) - S3_BUCKET: S3-Bucket-Name - EMAIL_PREFIX: Präfix für E-Mails im S3-Bucket (Standard: emails/) - MAIL_DIR: Lokales Verzeichnis für E-Mails (Standard: ./data/mail) +Nutzung: + python3 s3_email_downloader.py # Mit Bestätigungsabfrage für Löschungen + python3 s3_email_downloader.py y # Ohne Bestätigungsabfrage für Löschungen """ import boto3 @@ -24,11 +18,14 @@ import logging import json import re import hashlib +import sys from pathlib import Path from email.parser import BytesParser from email import policy from dotenv import load_dotenv -load_dotenv() # .env-Datei laden + +# .env-Datei laden +load_dotenv() # Logging konfigurieren logging.basicConfig( @@ -46,10 +43,18 @@ AWS_REGION = os.environ.get('AWS_REGION', 'us-east-2') S3_BUCKET = os.environ.get('S3_BUCKET', '') EMAIL_PREFIX = os.environ.get('EMAIL_PREFIX', 'emails/') MAIL_DIR = os.environ.get('MAIL_DIR', './data/mail') # Pfad zum Docker-Volume +VALID_DOMAIN = os.environ.get('VALID_DOMAIN', 'bizmatch.net') +VALID_USERNAMES = os.environ.get('VALID_USERNAMES', 'accounting,info,sales,support,test1,test2,test3') + +# Umwandlung der komma-getrennten Liste in ein Set für schnellere Suche +VALID_USERNAMES_SET = set(VALID_USERNAMES.split(',')) # Status-Datei für die Synchronisation STATUS_FILE = Path('sync_status.json') +# Prüfen, ob automatische Löschung aktiviert ist +AUTO_DELETE = len(sys.argv) > 1 and sys.argv[1].lower() == 'y' + def load_sync_status(): """Lädt den letzten Synchronisationsstatus""" if STATUS_FILE.exists(): @@ -90,6 +95,19 @@ def extract_email_address(address): # Fallback return address.strip() +def is_valid_recipient(to_address): + """Prüft, ob die Empfängeradresse gültig ist (passende Domain und Username)""" + email = extract_email_address(to_address) + + # E-Mail-Adresse aufteilen + if '@' not in email: + return False + + username, domain = email.split('@', 1) + + # Domain und Username prüfen + return domain.lower() == VALID_DOMAIN.lower() and username.lower() in VALID_USERNAMES_SET + def get_maildir_path(to_address, mail_dir): """ Ermittelt den Pfad im Maildir-Format basierend auf der Empfängeradresse @@ -101,7 +119,7 @@ def get_maildir_path(to_address, mail_dir): if '@' in email: user, domain = email.split('@', 1) else: - user, domain = email, 'bizmatch.net' + user, domain = email, VALID_DOMAIN # Pfad erstellen mail_dir_path = Path(mail_dir) @@ -145,6 +163,37 @@ def store_email(email_content, to_address, message_id, s3_key, mail_dir): logger.error(f"Fehler beim Speichern der E-Mail {s3_key}: {str(e)}") return False +def delete_s3_emails(s3, emails_to_delete): + """Löscht E-Mails aus dem S3-Bucket""" + if not emails_to_delete: + return 0 + + if not AUTO_DELETE: + # Bestätigung einholen + print(f"\nFolgende {len(emails_to_delete)} E-Mails werden gelöscht:") + for i, key in enumerate(emails_to_delete[:10]): + print(f" - {key}") + + if len(emails_to_delete) > 10: + print(f" ... und {len(emails_to_delete) - 10} weitere") + + confirmation = input("\nMöchten Sie diese E-Mails wirklich löschen? (j/n): ") + if confirmation.lower() not in ['j', 'ja', 'y', 'yes']: + logger.info("Löschung abgebrochen") + return 0 + + # Löschung durchführen + deleted_count = 0 + for key in emails_to_delete: + try: + s3.delete_object(Bucket=S3_BUCKET, Key=key) + logger.info(f"E-Mail gelöscht: {key}") + deleted_count += 1 + except Exception as e: + logger.error(f"Fehler beim Löschen der E-Mail {key}: {str(e)}") + + return deleted_count + def main(): """Hauptfunktion""" # Prüfen, ob die erforderlichen Umgebungsvariablen gesetzt sind @@ -154,7 +203,10 @@ def main(): logger.info(f"S3 E-Mail Downloader gestartet. Bucket: {S3_BUCKET}, Präfix: {EMAIL_PREFIX}") logger.info(f"E-Mails werden nach {MAIL_DIR} heruntergeladen") - + logger.info(f"Gültige Domain: {VALID_DOMAIN}") + logger.info(f"Gültige Usernames: {VALID_USERNAMES}") + logger.info(f"Automatisches Löschen: {'Ja' if AUTO_DELETE else 'Nein (mit Bestätigung)'}") + try: # S3-Client initialisieren s3 = boto3.client('s3', region_name=AWS_REGION) @@ -162,83 +214,33 @@ def main(): # Letzten Synchronisationsstatus laden last_sync = load_sync_status() - # Alle E-Mails im Bucket auflisten + # Um alle E-Mails zu verarbeiten, müssen wir den Paginator verwenden paginator = s3.get_paginator('list_objects_v2') pages = paginator.paginate(Bucket=S3_BUCKET, Prefix=EMAIL_PREFIX) new_emails = 0 + total_emails = 0 + emails_to_delete = [] + # Alle Seiten durchlaufen for page in pages: if 'Contents' not in page: - logger.error(f"Keine 'Contents' in der Seite gefunden") - else: - logger.info(f"Gefunden: {len(page['Contents'])} Objekte in der Seite") - # Ersten 5 Objekte anzeigen - for i, obj in enumerate(page['Contents'][:5]): - logger.info(f"Objekt {i}: {obj['Key']}") + continue - for obj in page['Contents']: + objects = page['Contents'] + total_emails += len(objects) + + for obj in objects: key = obj['Key'] - if not key.endswith('/'): + # Verzeichnisse überspringen + if key.endswith('/'): continue - logger.info(f"Verarbeite Objekt mit Key: {key}") - # Prüfen, ob die E-Mail bereits synchronisiert wurde if key in last_sync: - logger.info(f"E-Mail {key} bereits synchronisiert - ÜBERSPRUNGEN") + logger.debug(f"E-Mail {key} bereits synchronisiert - übersprungen") continue - else: - logger.info(f"E-Mail {key} wird heruntergeladen...") - - - try: - # E-Mail aus S3 laden - logger.info(f"Versuche, E-Mail aus S3 zu laden: {key}") - response = s3.get_object(Bucket=S3_BUCKET, Key=key) - email_content = response['Body'].read() - logger.info(f"E-Mail erfolgreich geladen, Größe: {len(email_content)} Bytes") - - # Debug: Erste 100 Bytes anzeigen - logger.info(f"E-Mail-Anfang: {email_content[:100]}") - - # Header parsen - headers = BytesParser(policy=policy.default).parsebytes(email_content, headersonly=True) - - # Empfänger-Adresse extrahieren - to_address = headers.get('To', '') - message_id = headers.get('Message-ID', '') - logger.info(f"Empfänger: '{to_address}', Message-ID: '{message_id}'") - - # Überprüfe MAIL_DIR - logger.info(f"MAIL_DIR ist: {MAIL_DIR}") - mail_dir_path = Path(MAIL_DIR) - logger.info(f"MAIL_DIR existiert: {mail_dir_path.exists()}, ist Verzeichnis: {mail_dir_path.is_dir() if mail_dir_path.exists() else 'N/A'}") - - # E-Mail speichern - result = store_email(email_content, to_address, message_id, key, MAIL_DIR) - logger.info(f"Ergebnis des Speicherns: {result}") - - if result: - # Status aktualisieren - last_sync[key] = { - 'timestamp': time.time(), - 'to': to_address, - 'message_id': message_id - } - new_emails += 1 - logger.info(f"E-Mail {key} erfolgreich synchronisiert") - else: - logger.error(f"E-Mail {key} konnte nicht gespeichert werden") - - except Exception as e: - logger.error(f"Fehler bei der Verarbeitung der E-Mail {key}: {str(e)}") - import traceback - logger.error(traceback.format_exc()) - - - try: # E-Mail aus S3 laden @@ -246,33 +248,55 @@ def main(): email_content = response['Body'].read() # Header parsen - headers = BytesParser(policy=policy.default).parsebytes(email_content, headersonly=True) - - # Empfänger-Adresse extrahieren - to_address = headers.get('To', '') - message_id = headers.get('Message-ID', '') - - # E-Mail speichern - if store_email(email_content, to_address, message_id, key, MAIL_DIR): - # Status aktualisieren - last_sync[key] = { - 'timestamp': time.time(), - 'to': to_address, - 'message_id': message_id - } - new_emails += 1 - + try: + headers = BytesParser(policy=policy.default).parsebytes(email_content, headersonly=True) + to_address = headers.get('To', '') + message_id = headers.get('Message-ID', '') + + # Prüfen, ob die Empfängeradresse gültig ist + if is_valid_recipient(to_address): + logger.info(f"Gültige E-Mail für: {to_address}") + + # E-Mail speichern + if store_email(email_content, to_address, message_id, key, MAIL_DIR): + # Status aktualisieren + last_sync[key] = { + 'timestamp': time.time(), + 'to': to_address, + 'message_id': message_id + } + new_emails += 1 + logger.info(f"E-Mail {key} erfolgreich synchronisiert ({new_emails})") + + # Status nach jeweils 10 E-Mails speichern + if new_emails % 10 == 0: + save_sync_status(last_sync) + logger.info(f"Zwischenspeicherung: {new_emails} neue E-Mails bisher") + else: + logger.info(f"Ungültige Empfängeradresse: {to_address} für {key}") + emails_to_delete.append(key) + + except Exception as e: + logger.error(f"Fehler beim Parsen der E-Mail-Header {key}: {str(e)}") + emails_to_delete.append(key) + except Exception as e: logger.error(f"Fehler bei der Verarbeitung der E-Mail {key}: {str(e)}") - # Status speichern - logger.info(f"Aktueller Status enthält {len(last_sync)} E-Mails") + # Abschließend den Status speichern save_sync_status(last_sync) - logger.info(f"{new_emails} neue E-Mails heruntergeladen") + # Ungültige E-Mails löschen + if emails_to_delete: + deleted_count = delete_s3_emails(s3, emails_to_delete) + logger.info(f"Insgesamt {deleted_count} von {len(emails_to_delete)} ungültigen E-Mails gelöscht") + + logger.info(f"Verarbeitung abgeschlossen. Insgesamt {total_emails} E-Mails gefunden, {new_emails} neue heruntergeladen.") except Exception as e: logger.error(f"Fehler: {str(e)}") + import traceback + logger.error(traceback.format_exc()) if __name__ == "__main__": main() \ No newline at end of file diff --git a/dovecot/setup-permissions.sh b/dovecot/setup-permissions.sh deleted file mode 100644 index a564f35..0000000 --- a/dovecot/setup-permissions.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -# Dieses Skript richtet die Verzeichnisstruktur und Berechtigungen für Dovecot ein - -# Erstellen der Verzeichnisstruktur -mkdir -p /var/mail/domains/bizmatch.net/info -mkdir -p /var/mail/user1 -mkdir -p /var/mail/user2 - -# Festlegen der richtigen Besitzer und Berechtigungen -chown -R 1001:1000 /var/mail/domains/bizmatch.net/info -chown -R 1000:1000 /var/mail/user1 -chown -R 1001:1000 /var/mail/user2 - -# Setzen der Berechtigungen (wichtig für den dovecot Zugriff) -chmod -R 0770 /var/mail - -# Ausgabe zur Bestätigung -echo "Verzeichnisse und Berechtigungen wurden korrekt eingerichtet." -echo "Verzeichnisstruktur:" -ls -la /var/mail -ls -la /var/mail/domains/bizmatch.net \ No newline at end of file