Commit Graph
142 Commits
Author SHA1 Message Date
sysopsandClaude Sonnet 5 1dfd8c18ac feat(PROJ-84): fix-subjects CLI-Backfill für kaputte MIME-Header-Betreffs
CLI-Subkommando "archivmail fix-subjects" korrigiert Bestandsmails mit
undekodiertem RFC-2047-Encoded-Word im Betreff (Folge des Fixes in
31d7113). Dry-Run per Default, echte Änderung nur mit --apply.

Original-EML im Storage bleibt unangetastet - nur Postgres emails.subject
wird korrigiert, danach Manticore-Reindex inkl. Erhalt von OCR-Text.
Jeder Lauf erzeugt einen Audit-Log-Eintrag (metadata_backfill).

Verifiziert auf 192.168.1.132: 543 Kandidaten, 541 reparierbar, 2 zu
Recht übersprungen (RFC-2047-Verletzung im Original). --apply noch
nicht ausgeführt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WapWkrQusDuBMhaN8WyuXB
2026-08-06 19:58:41 +02:00
sysopsandClaude Sonnet 5 c75352839c perf(PROJ-75): Partial Index gegen Seq-Scan in GetUnindexedMails
Der index-pending-Cron (PROJ-58) scannte bei jedem Lauf die komplette
emails-Tabelle, weil indexed_at unindiziert war — skaliert linear mit
Archivgröße. Partial Index auf (received_at DESC) WHERE indexed_at IS NULL
verifiziert auf 192.168.1.132: Seq Scan (13.3ms, 52895 Zeilen) → Index Scan
(0.086ms).

Restlicher PROJ-75-Audit (pgxpool-Tuning, reconciliation N+1) bewusst
zurückgestellt, siehe Feature-Spec.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019j28kGcaJAhBnrYX34hGdt
2026-08-05 14:55:13 +02:00
sysopsandClaude Sonnet 5 075afa005a fix(PROJ-74): Test-/Vet-Signatur-Drift beheben, go build/vet/test wieder grün
archivmail-export nutzte noch die alte storage.New(path string)-Signatur
statt storage.Config (fehlender Keyfile/Compress hätte Rohbytes statt
Klartext-EML exportiert). Toten Self-Assignment-Code in storage.go entfernt.
Testdateien (storage, audit, api, userstore, auth) an aktuelle Signaturen
angeglichen; auth-Tests liefen bisher gegen einen SQLite-Pfad statt Postgres-
DSN und wurden auf das TEST_DATABASE_URL-Schema-Isolationsmuster der übrigen
Pakete umgestellt. api_test.go las den Login-Token noch aus dem JSON-Body
statt aus dem httpOnly-Cookie (Auth-Contract-Drift).

TestParseMissingDate an tatsächliches Verhalten angepasst: der Parser lässt
das Datum bewusst als Zero-Value, der time.Now()-Fallback sitzt in der
Storage-Schicht — damit bleibt nachvollziehbar ob ein Datum aus der Mail
stammt oder vom Archiv gesetzt wurde (GoBD).

Verifiziert auf 192.168.1.132: go build/vet/test ./... komplett grün,
kein Skip (Postgres + Manticore erreichbar).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019j28kGcaJAhBnrYX34hGdt
2026-08-05 14:07:46 +02:00
sysopsandClaude Sonnet 5 88cdc3eb3e fix(PROJ-73): Upload-Job-Status bei Panic + nil-Guards nach GetByUsername
Upload-Job bleibt bei einem Panic im Verarbeitungspfad nicht mehr auf
"running" hängen, sondern zeigt "error" mit generischer Meldung (Panic-
Rohwert nur im Server-Log, ErrMsg geht ans Frontend und könnte sonst
Mail-Inhalt-Fragmente transportieren). Zusätzlich fail-closed nil-Guards
an allen 13 GetByUsername-Aufrufstellen in internal/api/, die das
Ergebnis bisher ungeprüft dereferenzierten.

Verifiziert auf 192.168.1.132: Build und go vet fehlerfrei für die
geänderten Dateien.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019j28kGcaJAhBnrYX34hGdt
2026-08-05 13:50:36 +02:00
sysopsandClaude Sonnet 5 798cb2817c fix: Crash-Robustheit + Performance in Backend und Frontend härten
Backend: recover() in allen langlebigen Goroutinen (neues internal/safego-
Paket), MIME-Multipart-Tiefenlimit gegen Stack-Overflow, IMAP-Zeilenlängen-
und FETCH-Result-Limits gegen OOM, MBOX-Buffer-Aliasing-Bug (Datenkorruption
beim Import), ungeprüfte Type Assertions abgesichert, Data Race im
API-Key-Rate-Limiter behoben, SMTP-Session-Panic führt jetzt zu 451-Retry
statt Prozessabsturz. Performance: O(n²)-String-Concat in Mailparser und
IMAP-Parser durch strings.Builder ersetzt.

Frontend: Error Boundaries für Root und Mail-Detailansicht ergänzt (gab es
vorher nicht), zahlreiche Guards gegen nil-Slices aus dem Backend-JSON die
sonst .map()/.length-Crashes/White-Screens auslösten, defekte JSON-Antworten
in api/core.ts abgefangen, zwei React-Key-Bugs bei löschbaren Listen
korrigiert.

Verifiziert auf 192.168.1.132: Build und Tests der geänderten Pakete
fehlerfrei, keine Regressionen gegenüber vorbestehendem Stand.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019j28kGcaJAhBnrYX34hGdt
2026-08-05 13:39:00 +02:00
sysops 2e952f111d docs(PROJ-72): Feature-Spec nachtragen + Audit-Log-Härtung für abgelehnte Privilege-Checks
Feature-Spec, INDEX.md und features.ts für PROJ-72 (Superadmin-Peer-Patch-Fix)
ergänzt inkl. QA-Ergebnisse. Zusätzlich: abgelehnte Privilege-Escalation-/
Tenant-Isolation-Versuche (403) werden jetzt als Success:false im Audit-Log
protokolliert (vorher nur erfolgreiche Updates) - Härtungspunkt aus QA-Runde.
2026-07-27 00:08:43 +02:00
sysops c580a7c05d fix(PROJ-72): Superadmin kann Passwort/Rolle von Superadmin-Peers nicht ändern
SEC-01 Privilegien-Check (roleLevel(target) >= roleLevel(sess)) blockte
Superadmin (höchstes Level) bei jedem Ziel-User mit gleichem Level -
inklusive anderer Superadmins. Passwort-Reset durch Superadmin schlug
dadurch fehl. Superadmin ist jetzt vom Peer-Level-Check ausgenommen,
da kein höheres Level existiert.
2026-07-26 21:50:19 +02:00
sysopsandClaude Sonnet 5 9db5eaa1e8 feat(PROJ-71): TLS-Pflicht (optional) für eingehenden SMTP-BCC-Kanal
fix: Audit-Log-Detailspalte per Tooltip statt hartem Truncate lesbar machen

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-08 11:43:18 +02:00
sysopsandClaude Sonnet 5 da79a56b3e feat(PROJ-70): Self-Service IMAP-Rückholung (Backend)
Neuer Opt-in-Endpunkt, mit dem User archivierte Mails per IMAP APPEND
zurück in ihr eigenes externes Postfach (INBOX) kopieren können. Das
Archiv selbst bleibt read-only (nur storage.Load(), kein Schreibzugriff
auf internal/imapserver).

- PATCH /api/auth/imap-restore: Opt-in-Flag umschalten, Aktivierung
  erfordert Passwort-Reverifikation.
- POST /api/mails/{id}/restore: lädt Mail lesend, prüft Mail- und
  Account-Ownership in restoreAccessAllowed() (PROJ-61-Muster), APPEND
  via neuer internal/imap/append.go, kein Admin-Override.
- Audit-Log-Eintrag (EventRestore) pro Versuch, erfolgreich und
  fehlgeschlagen.
- imap_restore_enabled-Spalte (default false) via idempotenter
  initSchema-Migration.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 00:36:44 +02:00
sysops 8564d7c11f feat: Versions-Spalte im Dienste-Tab für Superadmin
Bislang war die einzige Möglichkeit, die laufende Manticore-/PostgreSQL-/
Postfix-/nginx-Version zu sehen, SSH + <binary> --version. serviceVersion()
löst das best-effort pro Dienst auf (archivmail/-web: appVersion-Konstante,
manticore: searchd --version, postgresql: psql --version, postfix: postconf
mail_version, nginx: nginx -v). Fehler werden verschluckt (leerer String),
eine unbekannte Version darf den Dienst-Status nicht auf "Fehler" kippen.

manticore war bisher gar nicht in der Dienste-Whitelist (allowedServices) —
jetzt ergänzt, damit es überhaupt in der Liste auftaucht und
start/stop/restart wie die anderen Dienste möglich ist.
2026-07-05 19:12:38 +02:00
sysops a15fa37619 feat(PROJ-65): Physische Tenant-Trennung im Storage-Layer (Hardlink-Ordner)
Jeder Tenant bekommt ein eigenes Verzeichnis store/tenant_<id>/, das per
Hardlink auf die kanonische content-adressierte Datei zeigt — das bestehende
Cross-Tenant-Dedup-Modell (email_refs M:N, PROJ-32/37) bleibt dadurch
erhalten, kein Speicherplatz-Mehrverbrauch. Neues CLI-Subcommand
`archivmail migrate-tenant-dirs` zieht Bestandsdaten einmalig nach
(idempotent). Zusätzlich neuer Status-Check checkStoragePermissions
(warnt bei zu offenen store_path-Rechten, analog checkEncryption/PROJ-49).

DB-gestützte Zugriffskontrolle bleibt der maßgebliche Zugriffspfad im Code;
die Tenant-Ordner sind eine zusätzliche Defense-in-Depth-Ebene für manuelle
Dateisystem-Audits. Kein lokaler go build möglich, QA folgt auf Testserver.
2026-07-04 13:03:52 +02:00
sysopsandClaude Sonnet 5 4c92587b60 fix(PROJ-43): Dry-Run für from_addr/to_addr matcht bare Adressen statt <addr>-Form
dryRunCondition() erwartete faelschlich die Winkelklammer-Form "Name <addr>",
waehrend mail_from/mail_to die Adresse bare speichern - Dry-Run zeigte dadurch
immer 0 Treffer fuer Adress-Regeln, obwohl der Live-Matcher (routeBareAddr)
korrekt matcht. QA-Ergebnisse (Bug-1) in die Feature-Spec uebernommen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 11:57:21 +02:00
sysopsandClaude Sonnet 5 04e5b0f74a fix(security-audit): HTTPS/Port-Checks erkennen nftables Set-Syntax nicht
strings.Contains(nftStr, "dport 443") fand den Substring nicht, wenn
nftables mehrere Ports als Set ausgibt ("tcp dport { 80, 443 } accept" statt
"tcp dport 443 accept") — das ist bei archivmail-Installationen der
Normalfall (80+443 stehen zusammen in einer Regel). Dashboard zeigte
dadurch fälschlich "Kein HTTPS — Verbindungen unverschlüsselt", obwohl
Port 443 korrekt offen war (bestätigt auf 131 und 132).

Neue Helper-Funktion nftHasPort() per Regex erkennt beide Formen
(Einzelport und Set). Betrifft die Checks für HTTPS/443, Port 3000 und
Port 8080.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 00:02:20 +02:00
sysopsandClaude Sonnet 5 767373b206 feat(PROJ-46): E-Mail als primärer Login-Identifier für Tenant-User
Tenant-User (tenant_id IS NOT NULL) melden sich künftig per E-Mail an statt
per Username — behebt Verwechslungen wie im Support-Fall vom 2026-06-13
(Login schlug trotz Passwort-Reset fehl, weil E-Mail statt Username
verwendet wurde). Nicht-Tenant-User (Superadmin/System) können weiterhin
Username ODER E-Mail nutzen.

Neue Store.VerifyLogin() prüft erst per E-Mail (alle User), fällt dann auf
Username zurück (nur tenant_id IS NULL). VerifyPassword() bleibt für den
IMAP-Server-Login-Pfad (PROJ-26) unverändert. Bewusster Breaking Change für
Tenant-User, Datenqualität vorab geprüft (0 Kollisionen).

Security-Nachtrag: bcrypt-Dummy-Compare im "user not found"-Pfad ergänzt,
um Timing-basierte Identifier-Enumeration zu verhindern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-03 23:37:20 +02:00
sysopsandClaude Sonnet 5 99e4c1bbe4 fix(security): Fail-closed Tenant-Filter in v1-Suche + SMTP-Out-Key-Kette gehärtet
Security-Audit-Nachtrag (siehe PROJ-64):

- internal/api/v1_handlers.go: handleV1SearchMails fehlte der fail-closed
  Tenant-Post-Filter-Fallback für den Fall idxMgr==nil (gleiches Muster wie
  bereits in search_handlers.go). Aktuell nicht ausnutzbar, da idxMgr in
  main.go immer gesetzt wird, aber strukturelle Absicherung gegen künftige
  Regressionen (analog PROJ-55 BUG-1).
- internal/smtpoutconfig/store.go: Verschlüsselungsschlüssel wird jetzt aus
  dem HKDF-abgeleiteten aesKey gebildet statt aus dem rohen cfg.API.Secret,
  konsistent mit internal/ldapconfig und internal/imap/store.go (SEC-08).
  Verifiziert: smtp_out_config auf Produktiv (131) war leer, kein
  Breaking Change für bestehend gespeicherte Zugangsdaten.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-03 22:48:55 +02:00
sysopsandClaude Sonnet 5 be93614c9f feat(PROJ-52): Vollständigkeits-Reconciliation (Zähl-Report Mailserver vs. Archiv)
Täglicher Cron-Job (archivmail reconcile) berechnet pro Tenant/Quelle
(SMTP-Journal, IMAP-Konto, POP3-Konto, Datei-Import) archivierte Mail-Zahlen,
für IMAP zusätzlich einen Soll/Ist-Vergleich via UID-Tracking. Abweichungen
über Schwellenwert erzeugen Audit-Log-Warnung. Neue Admin-Dashboard-Kachel
"Vollständigkeits-Check" (letzte 7 Tage, Warn-Badge, CSV-Export).

Schließt die "teilweise erfüllt"-Lücke bei Vollständigkeit im
GoBD/DSGVO-Compliance-Check (VOI-Grundsatz 2).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-03 22:48:42 +02:00
sysopsandClaude Sonnet 5 b286352d07 fix(PROJ-64): Session-Invalidation bei Passwort-Change + Datei-Permissions gehärtet
Security-Audit deckte zwei Medium-Findings auf: JWTs blieben bis zu 8h nach
Passwort-Change/-Reset oder Admin-TOTP-Reset gültig (kein Session-Invalidation),
und archivierte Mails/Anhänge wurden mit 0644/0755 statt 0600/0700 geschrieben.

- users.tokens_valid_after (neue Spalte) wird bei SetPassword() und
  InvalidateTokensBefore() gesetzt; ValidateToken() lehnt JWTs mit iat davor ab.
- Admin-TOTP-Reset revoked jetzt aktive Sessions des Zielnutzers.
- Mail-/Attachment-Dateien und ihre Verzeichnisse nur noch für den
  archivmail-Service-Account lesbar.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-03 22:23:27 +02:00
sysopsandClaude Sonnet 4.6 0552ce49e2 fix(PROJ-50): Cross-Tenant-Lücke + Fehlerbehandlung in DSGVO-Handlern behoben
Bug-1: GetDSGVOMailMeta mit tenant_id-Filter (Defense-in-Depth an DB-Schicht).
Bug-2: Verwaiste open-Einträge bei Auswertungsfehler werden auf failed markiert.
Bug-3: Fehlgeschlagene Suchen werden im Audit-Log protokolliert.
Bug-6: Ungültige date_from/date_to-Eingaben liefern HTTP 400.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 18:17:25 +02:00
sysopsandClaude Sonnet 4.6 dcb88317ac fix(PROJ-63): Defense-in-Depth Tenant-Scope-Härtung der Admin-Endpunkte
tenantAccessAllowed()-Check in allen {id}-Handlern von tenant_handlers.go,
tenant_domain_handlers.go und tenant_logo_handlers.go ergänzt — No-op für
globale Admins, zweite Verteidigungslinie für hypothetische tenant-gebundene
Admin-Sessions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 14:37:39 +02:00
sysopsandClaude Sonnet 4.6 7c028601cf fix(PROJ-62): Cross-Tenant IDOR bei POP3-Konto-Löschung/-Import behoben (Sicherheitsbug)
handleDeletePop3 und handleStartPop3Import prüften nur Owner/Rollen-Level,
nicht den Tenant-Scope (anders als das korrekte IMAP-Pendant). Ein
domain_admin konnte dadurch POP3-Konten eines fremden Tenants löschen
oder deren Import anstoßen. Fix: tenantAccessAllowed(sess, acc.TenantID)
ergänzt, analog zum IMAP-Handler. Gefunden bei gezielter Nachsuche nach
Geschwister-Bugs zu PROJ-61.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 01:52:01 +02:00
sysopsandClaude Sonnet 4.6 363874767b fix(PROJ-61): Cross-Tenant Stored XSS via Mandanten-Logo behoben (Sicherheitsbug)
GET /api/tenants/{id}/logo prüfte nur die Authentifizierung, aber keinen
Tenant-Scope — jeder eingeloggte Nutzer konnte das Logo jedes beliebigen
Tenants lesen. Kombiniert mit dem bisher erlaubten SVG-Upload (kann
eingebettetes JavaScript enthalten) ergab das einen Cross-Tenant Stored-XSS:
ein domain_admin konnte ein bösartiges SVG als eigenes Logo hochladen und
Opfer aus beliebigen anderen Tenants per direktem Link darauf locken.

Fix: tenantAccessAllowed()-Scope-Check beim Logo-Lesepfad (analog PROJ-55),
SVG aus erlaubten Upload-Typen entfernt, X-Content-Type-Options: nosniff
als Defense-in-Depth ergänzt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 00:31:41 +02:00
sysopsandClaude Sonnet 4.6 5f63bfe8d4 chore(PROJ-60): Xapian-Legacy-Backend vollständig aus dem Code entfernt
Xapian war seit der Manticore-Migration (PROJ-30) nur noch ein
build-tag-gatetes, in Produktion nie genutztes Legacy-Backend.
internal/index/xapian.go, xapian_stub.go, xapian_wrapper.cpp/.h entfernt;
index.New() unterstützt jetzt nur noch "manticore". Default-Fallbacks in
Nebenwerkzeugen (archivmail-import/-export, cmd_export/cmd_reindex/...)
von "xapian" auf "manticore" korrigiert, Xapian-spezifische Tests entfernt,
Kommentare bereinigt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 00:12:06 +02:00
sysopsandClaude Sonnet 4.6 fae274f930 feat(PROJ-58): Indexierung + OCR optional als Cron-Batch statt Dauerbetrieb
Neues config.yml-Feld batch_mode (index/ocr, Default false = unverändertes
Verhalten). Bei batch_mode:true verarbeiten neue Cron-Jobs (index-pending,
ocr-reprocess) die Backlogs in größeren Abständen statt sofort bei jedem
Mail-Import, um Schreiblast auf der Festplatte zu glätten. Zeiten in
/etc/cron.d/archivmail frei anpassbar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 23:07:17 +02:00
sysopsandClaude Sonnet 4.6 76655f78a2 fix(PROJ-57): UTF-8-Encoding für Mails mit Nicht-UTF-8-Charset korrigieren
Der Mail-Parser ignorierte das charset-Parameter aus Content-Type und
interpretierte Bytes immer als UTF-8, wodurch iso-8859-1/windows-1252
kodierte Mails (z.B. mit Umlauten) als Mojibake gespeichert wurden.
Zusätzlich fehlte das Charset für die Manticore-MySQL-Verbindung und
der charset-Parameter im JSON-Response-Header.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 22:47:00 +02:00
sysopsandClaude Sonnet 4.6 b3ff8e6cf9 feat(PROJ-56c): Purge-Cron löscht nur explizit markierte Mails
Der automatische Purge-Cron darf nicht allein anhand des abgelaufenen
retain_until löschen — eine Mail muss zusätzlich von einem Admin im UI
zur Löschung markiert worden sein. Dafür: neue Spalten
marked_for_deletion(_by/_at) auf emails, Store.ListExpiredMarkedMailIDs()
(retain_until abgelaufen UND markiert) für den Cron-Pfad, und
Store.SetMarkedForDeletion() zum Setzen/Löschen der Markierung.

Neue Endpoints (domain_admin+, tenant-scoped):
- GET  /api/admin/retention/expired      Metadaten abgelaufener Mails
                                          (kein Body-Zugriff, SEC-29)
- PUT  /api/admin/mails/{id}/mark-deletion  Markierung setzen/entfernen,
                                          mit Audit-Log-Eintrag

RetentionTab.tsx zeigt abgelaufene Mails mit Checkbox zum Markieren.
Der bestehende manuelle "Jetzt löschen"-Button (/api/admin/purge) bleibt
unverändert und löscht weiterhin alle abgelaufenen Mails auf einen Klick —
nur der unbeaufsichtigte Cron-Job ist jetzt auf markierte Mails beschränkt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 16:40:18 +02:00
sysopsandClaude Sonnet 4.6 586af2478c feat(PROJ-56c): GoBD-Retention-Purge als Cron-Job + Delete()-Konsistenzfix
archivmail purge ist ein neuer CLI-Befehl, der Mails mit abgelaufener
retain_until löscht, aus dem Suchindex entfernt und pro Mail einen
Audit-Eintrag (mail_purged) schreibt — analog zu Pilers purge.sh, nachts
03:40 Uhr über deploy/cron.d/archivmail. Nur Mails mit explizit gesetztem
und abgelaufenem retain_until werden angefasst; ohne retain_until bleibt
alles unberührt, die Löschsperre (PROJ-34) greift weiterhin.

Beim Testen aufgedeckt: Store.Delete() entfernte die Datei vor dem
DB-Delete und verschluckte den Fehler, wenn email_refs/email_attachments
per Fremdschlüssel die Löschung blockierten — Ergebnis war ein DB-Eintrag
ohne zugehörige Datei. Jetzt läuft die DB-Löschung (inkl. abhängiger
Zeilen) zuerst in einer Transaktion, die Datei wird erst nach
erfolgreichem Commit entfernt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 16:00:41 +02:00
sysopsandClaude Sonnet 4.6 dba9939880 feat(PROJ-56): OCR-Pausenfenster per SIGHUP-Reload statt Restart
paused_hours konnte bisher nur über einen vollen Prozess-Restart geändert
werden, was SMTP/IMAP/API unnötig unterbricht. Worker.pausedHours ist jetzt
ein atomic.Pointer mit SetPausedHours(); SIGHUP liest config.yml neu und
aktualisiert nur die OCR-Pausenzeit im laufenden Prozess. Neue
deploy/cron.d/archivmail-ocr-pause(.sh) lässt Admins die Pausenzeiten direkt
in der Cron-Datei pflegen und löst per systemctl reload statt restart aus.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 14:46:13 +02:00
sysops 4dbf27cc1d feat(PROJ-56): Last-Entzerrung für OCR und IMAP-Sync
OCR-Worker pausieren optional in konfigurierbarem Zeitfenster
(paused_hours), Jobs bleiben pending statt verworfen zu werden.
IMAP-Scheduler verteilt Sync-Starts via deterministischem
Pro-Account-Jitter, um Lastspitzen bei vielen Postfächern mit
gleichem Intervall zu vermeiden. Beides per Config opt-out,
Default-Verhalten unverändert. Build + Smoke-Test auf 132 verifiziert.
2026-06-22 14:21:09 +02:00
sysops 1d27dc2d8b refactor: große API-Handler-Dateien in fokussierte Module aufteilen
Reine Code-Verschiebung, keine Logikänderung. Betroffen:
- ldap_tenants.go (994 Zeilen) -> ldap_tenants.go (Routing) + ldap_handlers.go
  + tenant_handlers.go + tenant_domain_handlers.go + tenant_logo_handlers.go
  + tenant_helpers.go
- import_handlers.go (621 Zeilen) -> imap_handlers.go + pop3_handlers.go
  + import_helpers.go
- admin_handlers.go (702 Zeilen) -> admin_users_handlers.go +
  admin_status_handlers.go + admin_services_handlers.go +
  admin_security_handlers.go

Lokal kein Go-Build möglich — Verifikation manuell per Funktions- und
Import-Abgleich Alt/Neu (alle Symbole exakt einmal vorhanden). Build
muss vor Deploy auf 192.168.1.131/132 bestätigt werden.
2026-06-21 23:38:57 +02:00
sysops 92e57431c3 fix(PROJ-55): Tenant-Isolation für Rolle "auditor" + Audit-Log korrigieren
Kritischer Sicherheitsbug: Auditoren mit zugewiesenem Tenant sahen Mails
und Audit-Log-Einträge anderer Tenants (DSGVO-relevant). auditor wird
jetzt analog zu domain_auditor pro Tenant gescoped, sofern tenant_id
gesetzt ist (Abwärtskompatibilität: ohne tenant_id bleibt der bisherige
globale Zugriff erhalten). Betrifft Mail-Suche, Mail-Detailzugriff,
Export, eDiscovery, Threads, OCR sowie das Audit-Log (DB + tamper-evidentes
Flat-File), inkl. Befüllung von tenant_id an allen Audit-Log-Schreibstellen.
2026-06-21 22:26:06 +02:00
sysops 776dee8a56 fix(PROJ-54): Listenansicht/Pagination für Rolle "user" korrigieren
Filtert die Mails des Nutzers (From/To/Cc/Bcc) jetzt serverseitig via
req.AnyAddress vor LIMIT/OFFSET im Index, statt wie bisher per
Post-Filter nach der Paginierung. total und totalPages stimmen damit
mit den tatsächlich sichtbaren Treffern überein.
2026-06-14 22:58:56 +02:00
sysops 472ba6a087 feat(PROJ-53): Konfigurierbare Listenanzahl pro Seite
- users.list_page_size (Default 25), PATCH /api/auth/preferences,
  Whitelist 25/50/100/200, Wert in login/me-Response
- Settings-UI mit Select, /search nutzt gespeicherte Seitengröße
- /api/search page_size serverseitig auf max. 500 gecappt

fix(PROJ-46): login_attempts-Migration nutzte s.db statt s.pool
(Backend kompilierte nicht)

feat(PROJ-50): DSGVO-Löschersuchen Backend (dsgvo_requests, Handler,
cc_addr/bcc_addr Indexerweiterung) — noch nicht QA'd/deployed
2026-06-14 22:25:02 +02:00
sysopsandClaude Sonnet 4.6 7ac0391205 fix(PROJ-51): retain_until_source nicht für Endbenutzer, WARN-Status in CLI
- search_handlers.go: retain_until_source wird nur noch an Rollen != user
  ausgegeben, um interne Archivierungsregel-IDs nicht an normale
  Endbenutzer zu exponieren
- cmd_status.go: archivmail status zeigt [WARN] statt [OK] wenn Detail
  mit "WARNUNG" beginnt (z.B. PROJ-51 Retention-Check); Exit-Code/r.OK
  bleibt unverändert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 21:05:07 +02:00
sysops 507dee6431 feat(PROJ-51): Aufbewahrungsfristen nach Dokumentenart (Retention-Kategorien)
Fuehrt archiving_rules ein (PROJ-43-Basis: Tabelle + CRUD-API + Admin-UI) und
erweitert die Retention-Logik (PROJ-34) um Regel-basierte Fristen, eine
globale Mindestfrist (min_retention_days) sowie Nachvollziehbarkeit der
Frist-Quelle (retain_until_source) in API und Mail-Detailansicht.
2026-06-13 20:48:16 +02:00
sysopsandClaude Sonnet 4.6 46096db802 feat(PROJ-49): Verschlüsselungspflicht at-rest sichtbar machen (Healthcheck & Warnung)
storage.loadKey() startet bei fehlendem/unlesbarem/ungültigem Keyfile
weiterhin unverschlüsselt (kein Hard-Fail), aber:
- einmalige WARN-Logzeile beim Start mit konkretem Grund
- neuer Healthcheck-Prüfpunkt "Encryption" in archivmail status
- Dashboard-API liefert encryption.enabled
- README: GoBD-Hinweis zu storage.keyfile

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 20:01:38 +02:00
sysopsandClaude Sonnet 4.6 7e4175923f feat(PROJ-48): Audit-Log Unveränderbarkeit (Trigger, append-only Logfile, Healthcheck)
DB-Trigger audit_log_immutable verhindert UPDATE/DELETE auf audit_log,
zusätzliches append-only JSON-Lines-Logfile (audit.log_path) als
tamper-evident Backup, neuer Healthcheck-Prüfpunkt in archivmail status.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 19:44:07 +02:00
sysops 501ee8f7ea fix(sec): Cross-Tenant-IDOR bei POP3-Konten schließen
Gleiches Muster wie bei IMAP (730099d): domain_admin konnte POP3-Konten
fremder Tenants auflisten, löschen und Importe/Progress fremder Tenants
ansehen, da pop3_accounts keine tenant_id hatte und Store.List() für
Admins ungefiltert alle Konten lieferte.

- pop3_accounts: neue Spalte tenant_id (ALTER TABLE ADD COLUMN IF NOT EXISTS)
- Store.List() filtert nach tenant_id, außer für superadmin
- Store.Create() setzt tenant_id beim Anlegen
- delete/start-import/progress prüfen zusätzlich tenantAccessAllowed()
2026-06-12 23:31:56 +02:00
sysops 730099d2aa fix(sec): Cross-Tenant-IDOR bei IMAP-Konten schließen
domain_admin sah und konnte IMAP-Konten (inkl. Credentials) fremder
Tenants auflisten, löschen, synchronisieren und umkonfigurieren, da
Store.List() für Admins ungefiltert alle Konten lieferte und die
Einzelhandler nur den Owner, nicht den Tenant prüften.

- Store.List() filtert jetzt nach tenant_id, außer für superadmin
- Store.Create() setzt tenant_id beim Anlegen
- Alle Einzelhandler (delete/start-import/progress/sync/update)
  prüfen zusätzlich tenantAccessAllowed()
2026-06-12 23:26:31 +02:00
sysopsandClaude Sonnet 4.6 d07e65021f fix(tenants): NULL-Scan-Fehler in tenantstore.List() beheben
ldap_url kommt via LEFT JOIN tenant_ldap und ist NULL für Mandanten
ohne LDAP-Konfiguration. Scan in *string schlug fehl ("cannot scan
NULL into *string") und ließ GET /api/admin/quotas mit 500 fehlschlagen
("Quota-Daten konnten nicht geladen werden").

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 22:38:28 +02:00
sysopsandClaude Sonnet 4.6 a3bab16f07 fix(imap): IMAP/POP3-Import in korrekten Tenant-Index schreiben
IMAP- und POP3-Importer haben Mails immer nur in emails_global
indexiert (TenantID nie gesetzt, idxMgr.Global() statt
ForTenant(tenantID)). Dadurch fehlten neue Mails ab dem letzten
Server-Neustart im Tenant-Index (Suche zeigte veraltete Ergebnisse).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 21:15:10 +02:00
sysopsandClaude Sonnet 4.6 f32f83ff8e fix(PROJ-28): Invite-Token Pflicht bei Signup — TOCTOU + Enumeration-Leak schließen
- Signup ohne Invite-Token gibt 400 zurück (war: optional)
- Use() statt Peek() vor User-Erstellung: verhindert TOCTOU bei parallelen
  Requests mit demselben Token und Enumeration via "Token noch gültig?"
- invite_used Audit-Eintrag ergänzt
- Doppeltes IsConfigured()-Check entfernt
- Frontend: ohne ?invite= im URL wird Formular nicht gerendert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 18:20:07 +02:00
sysopsandClaude Sonnet 4.6 15a5da33fd feat(PROJ-13): OpenAPI 3.0 Spec + GET /api/v1/docs Endpoint
Serves the static OpenAPI YAML via go:embed. Completes the last
open acceptance criterion for PROJ-13. PROJ-44 marked Deployed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 18:03:58 +02:00
sysopsandClaude Sonnet 4.6 fa9f77782c fix(sec): Authorization-Bypässe und Path-Traversal schließen, Xapian-Doku bereinigen
- SEC: requireMailAccess auf GET /api/threads/{threadID} — superadmin/domain_admin konnten Mail-Metadaten lesen
- SEC: requireMailAccess auf POST /api/export/ediscovery — superadmin/domain_admin konnten bis zu 10k EML exportieren
- SEC: V1-API user-role Keys müssen 'contact=' angeben — verhindert vollständige Tenant-Enumeration
- SEC: Domain-Regex-Validierung in handleCertACME vor filepath.Join und certbot-Aufruf
- docs: README und config.test.yml auf Manticore Search aktualisiert (kein Xapian mehr)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 23:55:31 +02:00
sysopsandClaude Sonnet 4.6 a1c4e59fff fix: Date-Parsing-Fallback für nicht-standard MTA-Datumsformate
mailparser: weitere Layouts (Timezone +02:00 mit Doppelpunkt, ohne Sekunden)
storage: GetReceivedAts() für Batch-Lookup von received_at
search_handlers: received_at als Fallback wenn pm.Date.IsZero()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 23:36:18 +02:00
sysopsandClaude Sonnet 4.6 726dd78f3a fix(PROJ-4): SMTP tenant routing: header fallback for BCC journaling
resolveTenant() now tries RCPT TO first, then falls back to parsing
To/Cc/From headers. Needed because BCC-journaled mails arrive with
RCPT TO = the archive's own BCC address, not the real recipient's domain.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 14:56:32 +02:00
sysopsandClaude Sonnet 4.6 799c828548 feat(PROJ-45): IMAP per-folder UID-tracking, UIDVALIDITY-check + reindex OCR protection
- scheduler.go: BUG-1 fix — preserve stored uid_validity when server returns 0
- scheduler.go: BUG-2 fix — replace inline switch with DecideResync() call
- scheduler.go: SetAuditLogger wired; imap_uidvalidity_reset audit event
- cmd_reindex.go: read existing attachment_text before IndexSync to prevent
  Manticore REPLACE INTO from wiping OCR text written by the OCR worker

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 14:56:28 +02:00
sysopsandClaude Sonnet 4.6 4151b6f8c5 feat(PROJ-45): IMAP Per-Folder UID-Tracking + UIDVALIDITY-Check
- FolderState: GetFolderState, UpsertFolderState, ListFolderStates, DecideResync
- syncFolder nutzt per-folder UID-Tracking statt globalem highest_uid
- UIDVALIDITY-Check loest automatisch Full-Resync aus
- imap_folder_state Tabelle in initSchema (CREATE TABLE IF NOT EXISTS)
- SetAuditLogger in main.go verdrahtet

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 10:49:14 +02:00
sysops 16013e8b66 fix(PROJ-44): OCR-Tenant-Routing nutzt kanonische DB-tenant_id
Strukturbug auf 132 gefunden: Tenant-User (Rolle user) sahen ihren
OCR-Text nicht, obwohl ocr_chars>0 in PostgreSQL stand. Ursache:

- OCR-Worker hat in den per Job.TenantID gewaehlten Index geschrieben.
  Beim Reprocess via CLI kam TenantID aus dem Submitter-Kontext und
  konnte vom in emails.tenant_id gespeicherten Wert abweichen.
- /ocr-text-Endpoint hat fuer die Index-Auswahl session.TenantID
  benutzt. Bei Admin/Auditor (nil Session-Tenant) wurde immer global
  gelesen, auch wenn die Mail einem Tenant gehoert.

Fix: Beide Stellen lesen jetzt die TenantID **immer** aus
storage.GetTenantForMail(emails.tenant_id) und routen den
Manticore-Index entsprechend. ACL-Check im Endpoint bleibt
unveraendert auf session.TenantID == mail.tenant_id — die
Tenant-Isolation wird nicht aufgeweicht.

Edge cases:
- Mail mit tenant_id NULL: GetTenantForMail liefert nil -> globaler
  Index (vorher und nachher gleich).
- DB-Fehler beim Lookup: faellt auf nil zurueck -> globaler Index,
  liefert leeren Text fuer Tenant-Mails -> 404. Safe (keine
  Querleckage zwischen Tenants).
2026-05-10 23:13:57 +02:00
sysopsandClaude Sonnet 4.6 5e1a51b028 fix(PROJ-44): SNIPPET via SELECT statt CALL SNIPPETS (Go MySQL-Treiber-Kompatibilitaet)
CALL SNIPPETS liefert einen anderen MySQL-Pakettyp als SELECT, den der
Go-Treiber (go-sql-driver/mysql) mit "malformed packet" ablehnt.
SELECT SNIPPET(text, query) FROM table ist die korrekte Alternative
fuer Manticore 25.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 22:53:27 +02:00
sysopsandClaude Sonnet 4.6 bb71ef2fd1 fix(PROJ-44): snippet + match_field in enrichedHit-Struct und JSON-Response verdrahten
Die enrichedHit-Struct in search_handlers.go fehlten die PROJ-44-Felder
Snippet und MatchField, sodass die vom Index berechneten Snippets in der
API-Response verworfen wurden.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 22:48:20 +02:00