email-amazon/DMS/sync_dynamodb_to_sieve.py

168 lines
5.0 KiB
Python

#!/usr/bin/env python3
"""
sync_dynamodb_to_sieve.py - Sync DynamoDB rules to Dovecot Sieve
"""
import boto3
import os
from pathlib import Path
import json
import time
from datetime import datetime
try:
from croniter import croniter
except ImportError:
print("Bitte 'croniter' via pip installieren!")
exit(1)
# Config
REGION = 'us-east-2'
TABLE = 'email-rules'
VMAIL_BASE = '/var/mail'
dynamodb = boto3.resource('dynamodb', region_name=REGION)
table = dynamodb.Table(TABLE)
def generate_sieve(email, rules):
"""Generate Sieve script from DynamoDB rules"""
lines = ['require ["copy","vacation","variables"];', '']
# Skip if already processed by worker
lines.extend([
'# Skip if already processed by worker',
'if header :contains "X-SES-Worker-Processed" "" {',
' keep;',
' stop;',
'}',
''
])
# Forwards
forwards = rules.get('forwards', [])
if forwards:
lines.append('# rule:[forward]')
for fwd in forwards:
lines.append(f'redirect :copy "{fwd}";')
lines.append('')
# OOO
if rules.get('ooo_active'):
msg = rules.get('ooo_message', 'I am away')
content_type = rules.get('ooo_content_type', 'text')
lines.append('# rule:[reply]')
if content_type == 'html':
lines.extend([
f'vacation :days 1 :from "{email}" :mime text:',
'Content-Type: text/html; charset=utf-8',
'',
msg,
'.',
';' # <--- HIER WAR DER FEHLER
])
else:
# Sicherheitshalber JSON dump für escaping von Anführungszeichen nutzen
safe_msg = json.dumps(msg, ensure_ascii=False)
lines.append(f'vacation :days 1 :from "{email}" {safe_msg};')
return '\n'.join(lines) + '\n'
def sync():
"""Sync all rules from DynamoDB to Sieve"""
response = table.scan()
for item in response.get('Items', []):
email = item['email_address']
domain = email.split('@')[1]
user = email.split('@')[0]
# Path: /var/mail/domain.de/user/.dovecot.sieve
mailbox_dir = Path(VMAIL_BASE) / domain / user
# Skip if mailbox doesn't exist
if not mailbox_dir.exists():
print(f'⚠ Skipped {email} (mailbox not found)')
continue
sieve_path = mailbox_dir / '.dovecot.sieve'
# Generate & write
script = generate_sieve(email, item)
sieve_path.write_text(script)
# Compile
os.system(f'sievec {sieve_path}')
# Copy to sieve dir
sieve_dir = mailbox_dir / 'sieve'
sieve_dir.mkdir(exist_ok=True)
managed_script = sieve_dir / 'default.sieve'
managed_script.write_text(script)
os.system(f'sievec {managed_script}')
# Ownership
os.system(f'chown -R docker:docker {sieve_dir}')
# Aktivieren mit doveadm sieve put
os.system(f'doveadm sieve put -u {email} -a default {managed_script}')
print(f'{email}')
def wait_for_dovecot():
"""Wartet, bis der Dovecot Auth Socket verfügbar ist."""
# Der Pfad zum Socket, über den doveadm kommuniziert
socket_path = '/var/run/dovecot/auth-userdb'
print("⏳ Warte auf Dovecot Start...")
while not os.path.exists(socket_path):
print(f" ... Socket {socket_path} noch nicht da. Schlafe 5s.")
time.sleep(5)
print("✅ Dovecot ist bereit!")
if __name__ == '__main__':
# 1. Erst warten, bis Dovecot da ist, sonst hagelt es Fehler beim Start
wait_for_dovecot()
# Pfad zur Cron-Definition (nur der String, z.B. "*/5 * * * *")
CRON_FILE = '/etc/sieve-schedule'
# Fallback, falls Datei fehlt
cron_string = "*/5 * * * *"
if os.path.exists(CRON_FILE):
with open(CRON_FILE, 'r') as f:
# Kommentare entfernen und String holen
content = f.read().strip()
if content and not content.startswith('#'):
cron_string = content
print(f"DynamoDB Sieve Sync gestartet. Zeitplan: {cron_string}")
print(f"AWS Region: {os.environ.get('AWS_DEFAULT_REGION', 'nicht gesetzt')}") # Debug Check
# Initialer Lauf beim Start? (Optional, hier auskommentiert)
sync()
# Iterator erstellen
base_time = datetime.now()
iter = croniter(cron_string, base_time)
while True:
# Den nächsten Zeitpunkt berechnen
next_run = iter.get_next(datetime)
now = datetime.now()
sleep_seconds = (next_run - now).total_seconds()
if sleep_seconds > 0:
# Warten bis zum nächsten Slot
time.sleep(sleep_seconds)
try:
print(f"[{datetime.now()}] Starte Sync...")
sync()
except Exception as e:
print(f"Fehler beim Sync: {e}")
# Wichtig: Bei Fehler nicht abstürzen, sondern weitermachen
pass