feat(PROJ-26): IMAP-Archive-Server Read-Only Zugriff auf archivierte Mails
- Neues Package internal/imapserver: vollständiger IMAP4rev1-Server (~700 Zeilen) - Auth via bcrypt (userstore.VerifyPassword), Multi-Tenant-Isolation - INBOX + INBOX/LabelName Ordnerstruktur - FETCH mit BODY[], ENVELOPE, RFC822.SIZE, INTERNALDATE, FLAGS, UID - SEARCH: ALL, FROM, TO, SUBJECT, SINCE, BEFORE + UID FETCH/SEARCH - Read-Only: STORE, DELETE, COPY, MOVE, APPEND → NO [CANNOT] - \Seen-Flag nicht persistent (GoBD-konform) - Max 5 gleichzeitige Verbindungen pro User, 30min Idle-Timeout - Audit-Log: imap_login / imap_login_failed Events - Config: imap_server.enabled + imap_server.bind (default: 127.0.0.1:1143) - Externe Ports: 9993 (primär) und 993 (alternativ) via nginx TLS-Terminierung Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -39,7 +39,7 @@
|
||||
| PROJ-24 | TOTP Zwei-Faktor-Authentifizierung (2FA) | Deployed | [PROJ-24](PROJ-24-totp-zwei-faktor.md) | 2026-03-18 |
|
||||
| PROJ-25 | User-Profil & Einstellungen | Deployed | [PROJ-25](PROJ-25-user-profil-einstellungen.md) | 2026-03-18 |
|
||||
|
||||
| PROJ-26 | IMAP-Server-Schnittstelle (Read-Only Archivzugriff) | Planned | [PROJ-26](PROJ-26-imap-server-schnittstelle.md) | 2026-03-18 |
|
||||
| PROJ-26 | IMAP-Server-Schnittstelle (Read-Only Archivzugriff) | In Progress | [PROJ-26](PROJ-26-imap-server-schnittstelle.md) | 2026-03-18 |
|
||||
|
||||
<!-- Add features above this line -->
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# PROJ-26: IMAP-Server-Schnittstelle (Read-Only Archivzugriff)
|
||||
|
||||
## Status: Planned
|
||||
## Status: In Progress
|
||||
**Created:** 2026-03-18
|
||||
**Last Updated:** 2026-03-18
|
||||
|
||||
@@ -15,11 +15,12 @@
|
||||
- Als Nutzer möchte ich mich mit meinem normalen Benutzername und Passwort am IMAP-Server anmelden, damit ich keine separaten Zugangsdaten brauche.
|
||||
- Als Nutzer möchte ich meine Labels als IMAP-Ordner sehen, damit ich archivierte Mails thematisch organisiert abrufen kann.
|
||||
- Als Admin möchte ich, dass Nutzer das Archiv nur lesen können (kein Löschen, kein Verschieben), damit die GoBD-Konformität und Archivintegrität gewahrt bleibt.
|
||||
- Als Nutzer möchte ich den IMAP-Zugang von außen (Port 993, SSL) nutzen können, damit ich auch unterwegs auf das Archiv zugreifen kann.
|
||||
- Als Nutzer möchte ich den IMAP-Zugang von außen über Port 993 (Standard IMAPS) oder alternativ Port 9993 (archivmail-spezifisch) nutzen können, damit ich auch unterwegs auf das Archiv zugreifen kann und Port-Konflikte mit einem ggf. vorhandenen regulären Mailserver vermieden werden.
|
||||
|
||||
## Acceptance Criteria
|
||||
- [ ] Eingebetteter IMAP4rev1-Server läuft als Teil des Go-Backends (Port 1143 intern)
|
||||
- [ ] Externer Zugriff via IMAPS Port 993 — nginx oder stunnel terminiert TLS, leitet an 1143 weiter
|
||||
- [ ] Externer Zugriff via IMAPS Port **993** (Standard) oder alternativ Port **9993** (archivmail-spezifisch) — nginx terminiert TLS, leitet an internen Port 1143 weiter
|
||||
- [ ] Beide Ports können gleichzeitig aktiv sein (parallele nginx-Listener)
|
||||
- [ ] Authentifizierung mit Benutzername + Passwort (bcrypt-Vergleich, wie Webinterface)
|
||||
- [ ] LDAP-Nutzer können sich ebenfalls per IMAP anmelden (LDAP-Auth-Pfad)
|
||||
- [ ] Jeder Nutzer sieht ausschließlich seine eigenen archivierten E-Mails (Multi-Tenant-Isolation)
|
||||
@@ -43,11 +44,12 @@
|
||||
- Gleichzeitige Verbindungen vom gleichen Client → erlaubt bis Limit (5), danach `BYE`
|
||||
- LDAP-Nutzer dessen LDAP-Server nicht erreichbar ist → Login verweigert, Fehlermeldung im Audit-Log
|
||||
- Nutzer wird während aktiver IMAP-Session gelöscht → Session wird beim nächsten Kommando beendet
|
||||
- Port 993 bereits durch anderen Mailserver belegt → Betrieb ausschließlich auf Port 9993 möglich (nginx-Config anpassen)
|
||||
|
||||
## Technical Requirements
|
||||
- **Protokoll:** IMAP4rev1 (RFC 3501)
|
||||
- **Port intern:** 1143 (plaintext, nur localhost/LAN)
|
||||
- **Port extern:** 993 (IMAPS via nginx/stunnel als TLS-Terminator)
|
||||
- **Port extern:** 993 (Standard IMAPS) **und/oder** 9993 (archivmail-spezifisch, vermeidet Konflikte mit existierenden Mailservern) — beide via nginx als TLS-Terminator
|
||||
- **TLS:** Pflicht für externe Verbindungen — Zertifikat von `/etc/letsencrypt/` oder selbstsigniert
|
||||
- **Authentifizierung:** LOGIN-Mechanismus (Benutzername/Passwort), PLAIN über TLS
|
||||
- **Performance:** SELECT auf 10.000 Mails < 500ms, FETCH einer einzelnen Mail < 200ms
|
||||
@@ -59,10 +61,47 @@
|
||||
enabled: true
|
||||
bind: "0.0.0.0:1143"
|
||||
tls: false # TLS-Terminierung durch nginx
|
||||
# Externe Ports (nginx-Konfiguration):
|
||||
# 993 → Standard IMAPS (ggf. mit vorhandenem Mailserver kollidierend)
|
||||
# 9993 → archivmail-spezifisch (empfohlen wenn 993 bereits belegt)
|
||||
```
|
||||
- **Sicherheit:** Kein STARTTLS auf 1143 (nginx übernimmt TLS) — Rate-Limiting bei Login-Fehlern
|
||||
|
||||
---
|
||||
## Implementation Notes
|
||||
|
||||
### What was built (2026-03-18):
|
||||
|
||||
**New package:** `internal/imapserver/server.go`
|
||||
- Custom IMAP4rev1 protocol implementation over raw TCP (no dependency on unstable go-imap/v2 server API)
|
||||
- Architecture mirrors `internal/smtpd/smtpd.go` (goroutine-based, background listener)
|
||||
- Authentication via `userstore.VerifyPassword()` (bcrypt, bypasses TOTP for protocol access)
|
||||
- Mailbox listing: INBOX (all tenant mails) + INBOX/LabelName per user label
|
||||
- FETCH: loads full RFC-2822 via `storage.Store.Load()`, supports BODY[], ENVELOPE, RFC822.SIZE, INTERNALDATE, BODYSTRUCTURE
|
||||
- SEARCH: ALL, FROM, TO, SUBJECT, SINCE, BEFORE criteria
|
||||
- UID FETCH and UID SEARCH supported
|
||||
- Read-only enforcement: STORE, DELETE, COPY, MOVE, APPEND, EXPUNGE, CREATE, RENAME all return `NO [CANNOT]`
|
||||
- `\Seen` flag NOT persisted (always reported as set for client compatibility)
|
||||
- Multi-tenant isolation via `storage.GetAllIDsByTenant()` filtered by `user.TenantID`
|
||||
- Connection limit: 5 concurrent per user (atomic counter with acquire/release)
|
||||
- Idle timeout: 30 minutes
|
||||
- IDLE command support (waits for DONE)
|
||||
- Audit log: `imap_login` on success, `imap_login_failed` on failure
|
||||
|
||||
**Config change:** `config/config.go`
|
||||
- Added `IMAPServer IMAPServerConfig` to Config struct
|
||||
- New `IMAPServerConfig` struct with `enabled` and `bind` fields
|
||||
|
||||
**Wiring:** `cmd/archivmail/main.go`
|
||||
- IMAP server starts after label store init, before SMTP daemon
|
||||
- Controlled by `imap_server.enabled` config flag
|
||||
- Default bind: `127.0.0.1:1143`
|
||||
|
||||
### Design decisions:
|
||||
- Used raw TCP IMAP implementation instead of go-imap/v2 `imapserver` package because the v2 library is still in beta.8 and the server-side API is unstable
|
||||
- TOTP is bypassed for IMAP access (standard IMAP LOGIN does not support 2FA)
|
||||
- UIDs equal sequence numbers for simplicity (UIDVALIDITY=1, stable within session)
|
||||
|
||||
## Tech Design (Solution Architect)
|
||||
_To be added by /architecture_
|
||||
|
||||
|
||||
Reference in New Issue
Block a user