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:
@@ -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.
|
||||||
|
|||||||
@@ -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
@@ -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.
|
||||||
|
|||||||
@@ -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
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user