all repos — postern @ fc287763b9ce6b6f9823e11c0b1173da0446ebc5

Modern mail management

main.go (view raw)

  1package main
  2
  3import (
  4	"cmp"
  5	"crypto/tls"
  6	"log"
  7	"log/slog"
  8	"os"
  9	"path"
 10
 11	"postern/internal/cli"
 12	"postern/internal/db"
 13	"postern/internal/dkim"
 14	"postern/internal/imap"
 15	"postern/internal/inbound"
 16	"postern/internal/persistence"
 17	"postern/internal/policy"
 18	"postern/internal/submission"
 19)
 20
 21func main() {
 22	posternPersistenceDir := cmp.Or(os.Getenv("POSTERN_PERSISTENCE_DIR"), os.Getenv("STATE_DIRECTORY"))
 23	err := persistence.CheckDirWritable(posternPersistenceDir)
 24	if err != nil {
 25		log.Fatal("POSTERN_PERSISTENCE_DIR: ", err)
 26	}
 27	slog.Info("using postern persistence", "dir", posternPersistenceDir)
 28
 29	persistenceMasterkey := os.Getenv("POSTERN_MASTERKEY_FILE")
 30	if persistenceMasterkey == "" {
 31		persistenceMasterkey = path.Join(os.Getenv("CREDENTIALS_DIRECTORY"), "masterkey")
 32	}
 33	p, err := persistence.New(persistence.Config{
 34		BlobRoot:      path.Join(posternPersistenceDir, "blobs"),
 35		MasterkeyFile: persistenceMasterkey,
 36	})
 37	if err != nil {
 38		log.Fatal(err)
 39	}
 40
 41	posternDB, err := db.OpenDB(&db.Config{
 42		DBPath: path.Join(posternPersistenceDir, "meta.db"),
 43	})
 44	if err != nil {
 45		log.Fatal(err)
 46	}
 47	defer func(db *db.DB) {
 48		if db == nil {
 49			return
 50		}
 51		err := db.Close()
 52		if err != nil {
 53			log.Fatal(err)
 54		}
 55	}(posternDB)
 56
 57	engine, err := policy.NewEngine(path.Join(os.Getenv("CONFIGURATION_DIRECTORY"), "policy.star"), posternDB)
 58	if err != nil {
 59		log.Fatal(err)
 60	}
 61
 62	dkimSvc := dkim.New(&dkim.Config{
 63		DB:          posternDB,
 64		Persistence: p,
 65	})
 66
 67	listenAddrs := make(map[string]policy.ListenerConfig)
 68	for _, l := range engine.Listeners {
 69		listenAddrs[l.Role] = l
 70	}
 71
 72	services := make(map[string]cli.Service)
 73
 74	if listener, ok := listenAddrs["imap"]; ok {
 75		cert, err := tls.LoadX509KeyPair(listener.TLSCertFile, listener.TLSKeyFile)
 76		if err != nil {
 77			log.Fatal(err)
 78		}
 79
 80		tlsConfig := &tls.Config{
 81			Certificates: []tls.Certificate{cert},
 82		}
 83
 84		imapServer, err := imap.NewServer(&imap.Config{
 85			DB:           posternDB,
 86			Listen:       listener.Address.String(),
 87			Persistence:  p,
 88			TLSConfig:    tlsConfig,
 89			PolicyEngine: &engine,
 90		})
 91		if err != nil {
 92			log.Fatal(err)
 93		}
 94		services["imap"] = imapServer
 95	}
 96
 97	if listener, ok := listenAddrs["smtp_inbound"]; ok {
 98		cert, err := tls.LoadX509KeyPair(listener.TLSCertFile, listener.TLSKeyFile)
 99		if err != nil {
100			log.Fatal(err)
101		}
102
103		tlsConfig := &tls.Config{
104			Certificates: []tls.Certificate{cert},
105		}
106
107		receptionServer, err := inbound.NewServer(&inbound.Config{
108			DB:           posternDB,
109			Deliverer:    services["imap"].(inbound.MessageDeliverer),
110			Listen:       listener.Address.String(),
111			TLS:          tlsConfig,
112			PolicyEngine: &engine,
113		})
114		if err != nil {
115			log.Fatal(err)
116		}
117		services["smtp_inbound"] = receptionServer
118	}
119
120	if listener, ok := listenAddrs["smtp_submission"]; ok {
121		cert, err := tls.LoadX509KeyPair(listener.TLSCertFile, listener.TLSKeyFile)
122		if err != nil {
123			log.Fatal(err)
124		}
125
126		tlsConfig := &tls.Config{
127			Certificates: []tls.Certificate{cert},
128		}
129
130		submissionServer, err := submission.NewServer(&submission.Config{
131			DB:           posternDB,
132			DKIM:         dkimSvc,
133			Listen:       listener.Address.String(),
134			TLS:          tlsConfig,
135			PolicyEngine: &engine,
136		})
137		if err != nil {
138			log.Fatal(err)
139		}
140		services["smtp_submission"] = submissionServer
141	}
142
143	if listener, ok := listenAddrs["smtp_local_submission"]; ok {
144		// Check that address is loopback. Other addresses are not allowed for security reasons.
145		if !listener.Address.Addr().IsLoopback() {
146			log.Fatal("smtp_local_submission address is not a loopback address")
147		}
148
149		submissionServer, err := submission.NewServer(&submission.Config{
150			DB:                posternDB,
151			DKIM:              dkimSvc,
152			Listen:            listener.Address.String(),
153			PolicyEngine:      &engine,
154			IsLocalSubmission: true,
155		})
156		if err != nil {
157			log.Fatal(err)
158		}
159		services["smtp_local_submission"] = submissionServer
160	}
161
162	posternCLI, err := cli.New(&cli.Config{
163		DB:          posternDB,
164		Persistence: p,
165		DKIM:        dkimSvc,
166		Services:    services,
167	})
168	if err != nil {
169		log.Fatal(err)
170	}
171
172	if err := posternCLI.Execute(); err != nil {
173		os.Exit(1)
174	}
175}