152 lines
4.5 KiB
Python
152 lines
4.5 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}')
|
|
|
|
if __name__ == '__main__':
|
|
# 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 |