package main import ( "cmp" "crypto/tls" "log" "log/slog" "os" "path" "postern/internal/cli" "postern/internal/db" "postern/internal/dkim" "postern/internal/imap" "postern/internal/inbound" "postern/internal/persistence" "postern/internal/policy" "postern/internal/submission" ) func main() { posternPersistenceDir := cmp.Or(os.Getenv("POSTERN_PERSISTENCE_DIR"), os.Getenv("STATE_DIRECTORY")) err := persistence.CheckDirWritable(posternPersistenceDir) if err != nil { log.Fatal("POSTERN_PERSISTENCE_DIR: ", err) } slog.Info("using postern persistence", "dir", posternPersistenceDir) persistenceMasterkey := os.Getenv("POSTERN_MASTERKEY_FILE") if persistenceMasterkey == "" { persistenceMasterkey = path.Join(os.Getenv("CREDENTIALS_DIRECTORY"), "masterkey") } p, err := persistence.New(persistence.Config{ BlobRoot: path.Join(posternPersistenceDir, "blobs"), MasterkeyFile: persistenceMasterkey, }) if err != nil { log.Fatal(err) } posternDB, err := db.OpenDB(&db.Config{ DBPath: path.Join(posternPersistenceDir, "meta.db"), }) if err != nil { log.Fatal(err) } defer func(db *db.DB) { if db == nil { return } err := db.Close() if err != nil { log.Fatal(err) } }(posternDB) engine, err := policy.NewEngine(path.Join(os.Getenv("CONFIGURATION_DIRECTORY"), "policy.star"), posternDB) if err != nil { log.Fatal(err) } dkimSvc := dkim.New(&dkim.Config{ DB: posternDB, Persistence: p, }) listenAddrs := make(map[string]policy.ListenerConfig) for _, l := range engine.Listeners { listenAddrs[l.Role] = l } services := make(map[string]cli.Service) if listener, ok := listenAddrs["imap"]; ok { cert, err := tls.LoadX509KeyPair(listener.TLSCertFile, listener.TLSKeyFile) if err != nil { log.Fatal(err) } tlsConfig := &tls.Config{ Certificates: []tls.Certificate{cert}, } imapServer, err := imap.NewServer(&imap.Config{ DB: posternDB, Listen: listener.Address.String(), Persistence: p, TLSConfig: tlsConfig, PolicyEngine: &engine, }) if err != nil { log.Fatal(err) } services["imap"] = imapServer } if listener, ok := listenAddrs["smtp_inbound"]; ok { cert, err := tls.LoadX509KeyPair(listener.TLSCertFile, listener.TLSKeyFile) if err != nil { log.Fatal(err) } tlsConfig := &tls.Config{ Certificates: []tls.Certificate{cert}, } receptionServer, err := inbound.NewServer(&inbound.Config{ DB: posternDB, Deliverer: services["imap"].(inbound.MessageDeliverer), Listen: listener.Address.String(), TLS: tlsConfig, PolicyEngine: &engine, }) if err != nil { log.Fatal(err) } services["smtp_inbound"] = receptionServer } if listener, ok := listenAddrs["smtp_submission"]; ok { cert, err := tls.LoadX509KeyPair(listener.TLSCertFile, listener.TLSKeyFile) if err != nil { log.Fatal(err) } tlsConfig := &tls.Config{ Certificates: []tls.Certificate{cert}, } submissionServer, err := submission.NewServer(&submission.Config{ DB: posternDB, DKIM: dkimSvc, Listen: listener.Address.String(), TLS: tlsConfig, PolicyEngine: &engine, }) if err != nil { log.Fatal(err) } services["smtp_submission"] = submissionServer } if listener, ok := listenAddrs["smtp_local_submission"]; ok { // Check that address is loopback. Other addresses are not allowed for security reasons. if !listener.Address.Addr().IsLoopback() { log.Fatal("smtp_local_submission address is not a loopback address") } submissionServer, err := submission.NewServer(&submission.Config{ DB: posternDB, DKIM: dkimSvc, Listen: listener.Address.String(), PolicyEngine: &engine, IsLocalSubmission: true, }) if err != nil { log.Fatal(err) } services["smtp_local_submission"] = submissionServer } posternCLI, err := cli.New(&cli.Config{ DB: posternDB, Persistence: p, DKIM: dkimSvc, Services: services, }) if err != nil { log.Fatal(err) } if err := posternCLI.Execute(); err != nil { os.Exit(1) } }