From 27040ffcb180a70edd96538833bd9c118dee7ede Mon Sep 17 00:00:00 2001 From: sysops Date: Tue, 31 Mar 2026 09:51:41 +0200 Subject: [PATCH] feat(PROJ-28): FQDN + smtp_out Konfiguration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - config: server.fqdn, SMTPOutConfig, smtp_out Abschnitt - config: IMAPServerConfig.FQDN (runtime-gesetzt) - main: FQDN-Fallback für SMTP EHLO, FQDN an IMAP-Server - imapserver: Greeting nutzt server.fqdn (RFC 3501) - smtpd/imapserver Modulversion 1.3 fix: Superadmin sieht keine Suche/IMAP/POP3 Nav-Links - Navbar: Suche, IMAP Import, POP3 Import nur für non-superadmin - Superadmin landet direkt auf Admin-Dashboard Co-Authored-By: Claude Sonnet 4.6 --- cmd/archivmail/main.go | 5 +++++ cmd/archivmail/version.go | 4 ++-- config/config.go | 19 +++++++++++++--- internal/imapserver/server.go | 8 +++++-- src/components/navbar.tsx | 42 ++++++++++++++++++++--------------- 5 files changed, 53 insertions(+), 25 deletions(-) diff --git a/cmd/archivmail/main.go b/cmd/archivmail/main.go index acdda4f..08b4579 100644 --- a/cmd/archivmail/main.go +++ b/cmd/archivmail/main.go @@ -209,6 +209,7 @@ func main() { // PROJ-26: IMAP Archive Server (read-only access for IMAP clients) if cfg.IMAPServer.Enabled { + cfg.IMAPServer.FQDN = cfg.Server.FQDN imapSrv := imapserver.New(cfg.IMAPServer, mailStore, users, labelSt, audlog, authMgr, logger, tenantSt) if err := imapSrv.Start(); err != nil { logger.Error("IMAP server failed to start", "err", err) @@ -226,6 +227,10 @@ func main() { if cfg.SMTP.Bind == "" { cfg.SMTP.Bind = fmt.Sprintf(":%d", cfg.Server.SMTPPort) } + // PROJ-28: FQDN fallback for SMTP EHLO banner + if cfg.SMTP.Domain == "" && cfg.Server.FQDN != "" { + cfg.SMTP.Domain = cfg.Server.FQDN + } smtpDaemon := smtpd.New(cfg.SMTP, mailStore, logger) smtpDaemon.SetIndexCallback(func(raw []byte, id string) { // Look up the tenant_id for this email from DB metadata. diff --git a/cmd/archivmail/version.go b/cmd/archivmail/version.go index f6b7c61..1318f11 100644 --- a/cmd/archivmail/version.go +++ b/cmd/archivmail/version.go @@ -13,8 +13,8 @@ const AppVersion = "0.9.1" // MINOR: Neue Funktionen, Bugfixes, Security-Patches var Modules = map[string]string{ "storage": "1.7", // PROJ-33 MailWithUID, GetMailsWithUID, GetMailsByRecipient - "smtpd": "1.2", // IP-Allowlist fail-closed, Domain→Tenant-Routing - "imapserver": "1.2", // PROJ-33 UID-Stabilität, shared/personal IMAP-Modus + "smtpd": "1.3", // PROJ-28 FQDN-Fallback für EHLO-Banner + "imapserver": "1.3", // PROJ-28 FQDN-Greeting (RFC 3501) "auth": "1.3", // JWT, bcrypt cost 12, TOTP "audit": "1.1", // PostgreSQL append-only, QueryFilter "index": "1.0", // Xapian-Wrapper, Async-Worker, Tenant-Index diff --git a/config/config.go b/config/config.go index 1ec1e4e..d58ebc2 100644 --- a/config/config.go +++ b/config/config.go @@ -25,6 +25,7 @@ type Config struct { Storage StorageConfig `yaml:"storage"` Database DatabaseConfig `yaml:"database"` SMTP SMTPConfig `yaml:"smtp"` + SMTPOut SMTPOutConfig `yaml:"smtp_out"` API APIConfig `yaml:"api"` Index IndexConfig `yaml:"index"` Audit AuditConfig `yaml:"audit"` @@ -35,15 +36,27 @@ type Config struct { // IMAPServerConfig holds settings for the embedded read-only IMAP archive server. type IMAPServerConfig struct { Enabled bool `yaml:"enabled"` - Bind string `yaml:"bind"` // plain: ":1143", TLS: ":993" + Bind string `yaml:"bind"` // plain: ":1143", TLS: ":993" TLSCert string `yaml:"tls_cert"` // path to PEM certificate; if set, TLS is enabled TLSKey string `yaml:"tls_key"` // path to PEM private key + FQDN string `yaml:"-"` // set at runtime from server.fqdn } // ServerConfig holds port settings for the main services. type ServerConfig struct { - APIPort int `yaml:"api_port"` - SMTPPort int `yaml:"smtp_port"` + FQDN string `yaml:"fqdn"` // Fully Qualified Domain Name — used in SMTP EHLO, IMAP greeting, and generated links + APIPort int `yaml:"api_port"` + SMTPPort int `yaml:"smtp_port"` +} + +// SMTPOutConfig holds settings for outgoing email (password reset, invitations). +type SMTPOutConfig struct { + Host string `yaml:"host"` + Port int `yaml:"port"` + User string `yaml:"user"` + Password string `yaml:"password"` + TLS bool `yaml:"tls"` + From string `yaml:"from"` // e.g. "archivmail " } // StorageConfig holds file system paths for email storage. diff --git a/internal/imapserver/server.go b/internal/imapserver/server.go index 2540ccb..395e438 100644 --- a/internal/imapserver/server.go +++ b/internal/imapserver/server.go @@ -213,8 +213,12 @@ func (s *Server) handleConnection(conn net.Conn) { state: stateNotAuth, } - // Send greeting - sess.writeResponse("* OK archivmail IMAP4rev1 Read-Only Archive Server ready") + // Send greeting — use FQDN if configured (RFC 3501 §7.1) + fqdn := s.cfg.FQDN + if fqdn == "" { + fqdn = "archivmail" + } + sess.writeResponse("* OK " + fqdn + " IMAP4rev1 Read-Only Archive ready") for { // Reset idle timeout diff --git a/src/components/navbar.tsx b/src/components/navbar.tsx index 3bcfd9e..7c3397e 100644 --- a/src/components/navbar.tsx +++ b/src/components/navbar.tsx @@ -23,24 +23,30 @@ export function Navbar({ username, role }: NavbarProps) { > archivmail - - Suche - - - IMAP Import - - - POP3 Import - + {role !== "superadmin" && ( + + Suche + + )} + {role !== "superadmin" && ( + + IMAP Import + + )} + {role !== "superadmin" && ( + + POP3 Import + + )} {(role === "admin" || role === "domain_admin" || role === "superadmin") && (