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}