Initial commit

Claude has done a lot of work, but there are still some errors which
cause the mails to be distributed to wrong mail boxes. I am now going to
let codex and claude compete. They will have their own branches and
merge to main as soon as we are somewhere where we can switch AI.
This commit is contained in:
2026-07-04 11:18:05 +02:00
commit 4780c18d70
27 changed files with 43087 additions and 0 deletions
+190
View File
@@ -0,0 +1,190 @@
#!/usr/bin/env python3
"""
Upload en activeer de Sieve-mailrule op australius.nl via ManageSieve (RFC 5804).
Poort: 4190 met STARTTLS.
Gebruik: python3 upload_sieve.py
"""
import socket
import ssl
import sys
import base64
import config
from pathlib import Path
SCRIPT_NAME = "mailrules"
SCRIPT_FILE = Path(__file__).parent / "mailrules.sieve"
# Als fileinto-paden "INBOX." als prefix nodig hebben op deze server:
# Zet SIEVE_PREFIX op "INBOX." en het script past de paden automatisch aan.
# Zet op "" als de server paden zonder prefix accepteert (meest gangbaar).
SIEVE_PREFIX = ""
class ManageSieveClient:
"""Minimale ManageSieve-client (RFC 5804)."""
def __init__(self, host: str, port: int):
self._sock = None
self._host = host
self._port = port
def connect(self):
raw = socket.create_connection((self._host, self._port), timeout=15)
self._sock = raw
greeting = self._readline()
if not greeting.startswith("OK"):
raise ConnectionError(f"Onverwachte greeting: {greeting!r}")
# Lees eventuele capability-regels na de greeting
self._read_capabilities()
def starttls(self):
self._send("STARTTLS")
resp = self._readline()
if not resp.startswith("OK"):
raise ConnectionError(f"STARTTLS geweigerd: {resp!r}")
context = ssl.create_default_context()
self._sock = context.wrap_socket(self._sock, server_hostname=self._host)
# Nieuwe capabilities na TLS
self._read_capabilities()
def authenticate(self, username: str, password: str):
creds = base64.b64encode(f"\x00{username}\x00{password}".encode()).decode()
self._send(f'AUTHENTICATE "PLAIN" "{creds}"')
resp = self._readline()
if not resp.startswith("OK"):
raise PermissionError(f"Authenticatie mislukt: {resp!r}")
def put_script(self, name: str, content: str):
encoded = content.encode("utf-8")
self._send(f'PUTSCRIPT "{name}" {{{len(encoded)}+}}')
self._sock.sendall(encoded)
resp = self._readline()
if not resp.startswith("OK"):
raise RuntimeError(f"PUTSCRIPT mislukt: {resp!r}")
def set_active(self, name: str):
self._send(f'SETACTIVE "{name}"')
resp = self._readline()
if not resp.startswith("OK"):
raise RuntimeError(f"SETACTIVE mislukt: {resp!r}")
def list_scripts(self) -> list[str]:
self._send("LISTSCRIPTS")
scripts = []
while True:
line = self._readline()
if line.startswith("OK"):
break
scripts.append(line)
return scripts
def logout(self):
try:
self._send("LOGOUT")
except Exception:
pass
try:
self._sock.close()
except Exception:
pass
def _send(self, cmd: str):
self._sock.sendall((cmd + "\r\n").encode())
def _readline(self) -> str:
buf = b""
while not buf.endswith(b"\n"):
chunk = self._sock.recv(1)
if not chunk:
break
buf += chunk
return buf.decode("utf-8", errors="replace").strip()
def _read_capabilities(self):
"""Lees en negeer capability-regels tot een lege regel of einde."""
# Capabilities eindigen met een lege regel of worden gevolgd door authenticatie
# We lezen tot we iets tegenkomen dat niet op een capability lijkt
# (dit is een vereenvoudiging; volledige RFC 5804 parsing is complexer)
pass
def apply_prefix(script: str, prefix: str) -> str:
"""Voeg SIEVE_PREFIX toe aan alle fileinto-paden als dat nodig is."""
if not prefix:
return script
import re
def add_prefix(m):
path = m.group(1)
if path.startswith(prefix):
return m.group(0) # al aanwezig
return f'fileinto "{prefix}{path}"'
return re.sub(r'fileinto "([^"]+)"', add_prefix, script)
def run():
if not SCRIPT_FILE.exists():
print(f"Script niet gevonden: {SCRIPT_FILE}")
sys.exit(1)
script_content = SCRIPT_FILE.read_text(encoding="utf-8")
script_content = apply_prefix(script_content, SIEVE_PREFIX)
cfg = config.load()
print(f"Script: {SCRIPT_FILE.name} ({len(script_content)} bytes)")
print(f"Server: {cfg.sieve_host}:{cfg.sieve_port}")
print(f"Account:{cfg.username}")
print(f"Naam: {SCRIPT_NAME}\n")
client = ManageSieveClient(cfg.sieve_host, cfg.sieve_port)
print("Verbinden ...")
try:
client.connect()
print(" STARTTLS ...")
client.starttls()
print(" Authenticeren ...")
client.authenticate(cfg.username, cfg.password)
except Exception as e:
print(f"Verbinding mislukt: {e}")
sys.exit(1)
# Toon bestaande scripts
scripts = client.list_scripts()
if scripts:
print(f"\nBestaande scripts op server:")
for s in scripts:
print(f" {s}")
else:
print("\nGeen bestaande scripts.")
# Bevestiging vragen
print(f"\nScript '{SCRIPT_NAME}' wordt geüpload en geactiveerd.")
bevestig = input("Typ JA om door te gaan: ").strip()
if bevestig != "JA":
print("Afgebroken.")
client.logout()
sys.exit(0)
print("\nUploaden ...")
try:
client.put_script(SCRIPT_NAME, script_content)
print(f"{SCRIPT_NAME} geüpload")
client.set_active(SCRIPT_NAME)
print(f"{SCRIPT_NAME} geactiveerd")
except Exception as e:
print(f" ✗ Mislukt: {e}")
client.logout()
sys.exit(1)
client.logout()
print("\nKlaar. Nieuwe inkomende mail wordt nu automatisch gesorteerd.")
print(f"\nAls mail niet aankomt in de verwachte mappen:")
print(f" 1. Controleer of alle mappen bestaan (maak_mappen.py)")
print(f" 2. Probeer SIEVE_PREFIX = \"INBOX.\" in dit script")
print(f" 3. Controleer server-logs via webhosting-paneel")
if __name__ == "__main__":
run()