rm setup-permisssions, s3 deletion added

This commit is contained in:
Andreas Knuth 2025-03-16 11:10:51 +01:00
parent 2179d5110f
commit 044bb3a960
2 changed files with 119 additions and 116 deletions

View File

@ -3,18 +3,12 @@
S3 E-Mail Downloader Script S3 E-Mail Downloader Script
Dieses Script lädt E-Mails aus einem S3-Bucket herunter und speichert sie im 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: Nutzung:
python3 s3_email_downloader.py 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
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)
""" """
import boto3 import boto3
@ -24,11 +18,14 @@ import logging
import json import json
import re import re
import hashlib import hashlib
import sys
from pathlib import Path from pathlib import Path
from email.parser import BytesParser from email.parser import BytesParser
from email import policy from email import policy
from dotenv import load_dotenv from dotenv import load_dotenv
load_dotenv() # .env-Datei laden
# .env-Datei laden
load_dotenv()
# Logging konfigurieren # Logging konfigurieren
logging.basicConfig( logging.basicConfig(
@ -46,10 +43,18 @@ AWS_REGION = os.environ.get('AWS_REGION', 'us-east-2')
S3_BUCKET = os.environ.get('S3_BUCKET', '') S3_BUCKET = os.environ.get('S3_BUCKET', '')
EMAIL_PREFIX = os.environ.get('EMAIL_PREFIX', 'emails/') EMAIL_PREFIX = os.environ.get('EMAIL_PREFIX', 'emails/')
MAIL_DIR = os.environ.get('MAIL_DIR', './data/mail') # Pfad zum Docker-Volume 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-Datei für die Synchronisation
STATUS_FILE = Path('sync_status.json') 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(): def load_sync_status():
"""Lädt den letzten Synchronisationsstatus""" """Lädt den letzten Synchronisationsstatus"""
if STATUS_FILE.exists(): if STATUS_FILE.exists():
@ -90,6 +95,19 @@ def extract_email_address(address):
# Fallback # Fallback
return address.strip() 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): def get_maildir_path(to_address, mail_dir):
""" """
Ermittelt den Pfad im Maildir-Format basierend auf der Empfängeradresse 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: if '@' in email:
user, domain = email.split('@', 1) user, domain = email.split('@', 1)
else: else:
user, domain = email, 'bizmatch.net' user, domain = email, VALID_DOMAIN
# Pfad erstellen # Pfad erstellen
mail_dir_path = Path(mail_dir) 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)}") logger.error(f"Fehler beim Speichern der E-Mail {s3_key}: {str(e)}")
return False 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(): def main():
"""Hauptfunktion""" """Hauptfunktion"""
# Prüfen, ob die erforderlichen Umgebungsvariablen gesetzt sind # Prüfen, ob die erforderlichen Umgebungsvariablen gesetzt sind
@ -154,6 +203,9 @@ def main():
logger.info(f"S3 E-Mail Downloader gestartet. Bucket: {S3_BUCKET}, Präfix: {EMAIL_PREFIX}") 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"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: try:
# S3-Client initialisieren # S3-Client initialisieren
@ -162,83 +214,33 @@ def main():
# Letzten Synchronisationsstatus laden # Letzten Synchronisationsstatus laden
last_sync = load_sync_status() 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') paginator = s3.get_paginator('list_objects_v2')
pages = paginator.paginate(Bucket=S3_BUCKET, Prefix=EMAIL_PREFIX) pages = paginator.paginate(Bucket=S3_BUCKET, Prefix=EMAIL_PREFIX)
new_emails = 0 new_emails = 0
total_emails = 0
emails_to_delete = []
# Alle Seiten durchlaufen
for page in pages: for page in pages:
if 'Contents' not in page: if 'Contents' not in page:
logger.error(f"Keine 'Contents' in der Seite gefunden") continue
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']}")
for obj in page['Contents']: objects = page['Contents']
total_emails += len(objects)
for obj in objects:
key = obj['Key'] key = obj['Key']
if not key.endswith('/'): # Verzeichnisse überspringen
if key.endswith('/'):
continue continue
logger.info(f"Verarbeite Objekt mit Key: {key}")
# Prüfen, ob die E-Mail bereits synchronisiert wurde # Prüfen, ob die E-Mail bereits synchronisiert wurde
if key in last_sync: if key in last_sync:
logger.info(f"E-Mail {key} bereits synchronisiert - ÜBERSPRUNGEN") logger.debug(f"E-Mail {key} bereits synchronisiert - übersprungen")
continue 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: try:
# E-Mail aus S3 laden # E-Mail aus S3 laden
@ -246,33 +248,55 @@ def main():
email_content = response['Body'].read() email_content = response['Body'].read()
# Header parsen # Header parsen
headers = BytesParser(policy=policy.default).parsebytes(email_content, headersonly=True) try:
headers = BytesParser(policy=policy.default).parsebytes(email_content, headersonly=True)
to_address = headers.get('To', '')
message_id = headers.get('Message-ID', '')
# Empfänger-Adresse extrahieren # Prüfen, ob die Empfängeradresse gültig ist
to_address = headers.get('To', '') if is_valid_recipient(to_address):
message_id = headers.get('Message-ID', '') logger.info(f"Gültige E-Mail für: {to_address}")
# E-Mail speichern # E-Mail speichern
if store_email(email_content, to_address, message_id, key, MAIL_DIR): if store_email(email_content, to_address, message_id, key, MAIL_DIR):
# Status aktualisieren # Status aktualisieren
last_sync[key] = { last_sync[key] = {
'timestamp': time.time(), 'timestamp': time.time(),
'to': to_address, 'to': to_address,
'message_id': message_id 'message_id': message_id
} }
new_emails += 1 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: except Exception as e:
logger.error(f"Fehler bei der Verarbeitung der E-Mail {key}: {str(e)}") logger.error(f"Fehler bei der Verarbeitung der E-Mail {key}: {str(e)}")
# Status speichern # Abschließend den Status speichern
logger.info(f"Aktueller Status enthält {len(last_sync)} E-Mails")
save_sync_status(last_sync) 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: except Exception as e:
logger.error(f"Fehler: {str(e)}") logger.error(f"Fehler: {str(e)}")
import traceback
logger.error(traceback.format_exc())
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@ -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