feat(PROJ-28): FQDN + smtp_out Konfiguration

- 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 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-03-31 09:51:41 +02:00
parent 8d0f685fc9
commit 27040ffcb1
5 changed files with 53 additions and 25 deletions
+5
View File
@@ -209,6 +209,7 @@ func main() {
// PROJ-26: IMAP Archive Server (read-only access for IMAP clients) // PROJ-26: IMAP Archive Server (read-only access for IMAP clients)
if cfg.IMAPServer.Enabled { if cfg.IMAPServer.Enabled {
cfg.IMAPServer.FQDN = cfg.Server.FQDN
imapSrv := imapserver.New(cfg.IMAPServer, mailStore, users, labelSt, audlog, authMgr, logger, tenantSt) imapSrv := imapserver.New(cfg.IMAPServer, mailStore, users, labelSt, audlog, authMgr, logger, tenantSt)
if err := imapSrv.Start(); err != nil { if err := imapSrv.Start(); err != nil {
logger.Error("IMAP server failed to start", "err", err) logger.Error("IMAP server failed to start", "err", err)
@@ -226,6 +227,10 @@ func main() {
if cfg.SMTP.Bind == "" { if cfg.SMTP.Bind == "" {
cfg.SMTP.Bind = fmt.Sprintf(":%d", cfg.Server.SMTPPort) 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 := smtpd.New(cfg.SMTP, mailStore, logger)
smtpDaemon.SetIndexCallback(func(raw []byte, id string) { smtpDaemon.SetIndexCallback(func(raw []byte, id string) {
// Look up the tenant_id for this email from DB metadata. // Look up the tenant_id for this email from DB metadata.
+2 -2
View File
@@ -13,8 +13,8 @@ const AppVersion = "0.9.1"
// MINOR: Neue Funktionen, Bugfixes, Security-Patches // MINOR: Neue Funktionen, Bugfixes, Security-Patches
var Modules = map[string]string{ var Modules = map[string]string{
"storage": "1.7", // PROJ-33 MailWithUID, GetMailsWithUID, GetMailsByRecipient "storage": "1.7", // PROJ-33 MailWithUID, GetMailsWithUID, GetMailsByRecipient
"smtpd": "1.2", // IP-Allowlist fail-closed, Domain→Tenant-Routing "smtpd": "1.3", // PROJ-28 FQDN-Fallback für EHLO-Banner
"imapserver": "1.2", // PROJ-33 UID-Stabilität, shared/personal IMAP-Modus "imapserver": "1.3", // PROJ-28 FQDN-Greeting (RFC 3501)
"auth": "1.3", // JWT, bcrypt cost 12, TOTP "auth": "1.3", // JWT, bcrypt cost 12, TOTP
"audit": "1.1", // PostgreSQL append-only, QueryFilter "audit": "1.1", // PostgreSQL append-only, QueryFilter
"index": "1.0", // Xapian-Wrapper, Async-Worker, Tenant-Index "index": "1.0", // Xapian-Wrapper, Async-Worker, Tenant-Index
+16 -3
View File
@@ -25,6 +25,7 @@ type Config struct {
Storage StorageConfig `yaml:"storage"` Storage StorageConfig `yaml:"storage"`
Database DatabaseConfig `yaml:"database"` Database DatabaseConfig `yaml:"database"`
SMTP SMTPConfig `yaml:"smtp"` SMTP SMTPConfig `yaml:"smtp"`
SMTPOut SMTPOutConfig `yaml:"smtp_out"`
API APIConfig `yaml:"api"` API APIConfig `yaml:"api"`
Index IndexConfig `yaml:"index"` Index IndexConfig `yaml:"index"`
Audit AuditConfig `yaml:"audit"` Audit AuditConfig `yaml:"audit"`
@@ -35,15 +36,27 @@ type Config struct {
// IMAPServerConfig holds settings for the embedded read-only IMAP archive server. // IMAPServerConfig holds settings for the embedded read-only IMAP archive server.
type IMAPServerConfig struct { type IMAPServerConfig struct {
Enabled bool `yaml:"enabled"` 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 TLSCert string `yaml:"tls_cert"` // path to PEM certificate; if set, TLS is enabled
TLSKey string `yaml:"tls_key"` // path to PEM private key 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. // ServerConfig holds port settings for the main services.
type ServerConfig struct { type ServerConfig struct {
APIPort int `yaml:"api_port"` FQDN string `yaml:"fqdn"` // Fully Qualified Domain Name — used in SMTP EHLO, IMAP greeting, and generated links
SMTPPort int `yaml:"smtp_port"` 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 <noreply@firma.de>"
} }
// StorageConfig holds file system paths for email storage. // StorageConfig holds file system paths for email storage.
+6 -2
View File
@@ -213,8 +213,12 @@ func (s *Server) handleConnection(conn net.Conn) {
state: stateNotAuth, state: stateNotAuth,
} }
// Send greeting // Send greeting — use FQDN if configured (RFC 3501 §7.1)
sess.writeResponse("* OK archivmail IMAP4rev1 Read-Only Archive Server ready") fqdn := s.cfg.FQDN
if fqdn == "" {
fqdn = "archivmail"
}
sess.writeResponse("* OK " + fqdn + " IMAP4rev1 Read-Only Archive ready")
for { for {
// Reset idle timeout // Reset idle timeout
+24 -18
View File
@@ -23,24 +23,30 @@ export function Navbar({ username, role }: NavbarProps) {
> >
archivmail archivmail
</Link> </Link>
<Link {role !== "superadmin" && (
href="/search" <Link
className="text-sm text-muted-foreground hover:text-foreground transition-colors" href="/search"
> className="text-sm text-muted-foreground hover:text-foreground transition-colors"
Suche >
</Link> Suche
<Link </Link>
href="/imap" )}
className="text-sm text-muted-foreground hover:text-foreground transition-colors" {role !== "superadmin" && (
> <Link
IMAP Import href="/imap"
</Link> className="text-sm text-muted-foreground hover:text-foreground transition-colors"
<Link >
href="/pop3" IMAP Import
className="text-sm text-muted-foreground hover:text-foreground transition-colors" </Link>
> )}
POP3 Import {role !== "superadmin" && (
</Link> <Link
href="/pop3"
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
>
POP3 Import
</Link>
)}
{(role === "admin" || role === "domain_admin" || role === "superadmin") && ( {(role === "admin" || role === "domain_admin" || role === "superadmin") && (
<Link <Link
href="/admin" href="/admin"