Bump the version to 2.1.0.
[mailpopbox.git] / smtp.go
1 // mailpopbox
2 // Copyright 2020 Blue Static <https://www.bluestatic.org>
3 // This program is free software licensed under the GNU General Public License,
4 // version 3.0. The full text of the license can be found in LICENSE.txt.
5 // SPDX-License-Identifier: GPL-3.0-only
6
7 package main
8
9 import (
10 "bytes"
11 "crypto/tls"
12 "fmt"
13 "net"
14 "net/mail"
15 "os"
16 "path"
17 "regexp"
18
19 "go.uber.org/zap"
20
21 "src.bluestatic.org/mailpopbox/smtp"
22 )
23
24 var sendAsSubject = regexp.MustCompile(`(?i)\[sendas:\s*([a-zA-Z0-9\.\-_]+)\]`)
25
26 func runSMTPServer(config Config, log *zap.Logger) <-chan ServerControlMessage {
27 server := smtpServer{
28 config: config,
29 controlChan: make(chan ServerControlMessage),
30 log: log.With(zap.String("server", "smtp")),
31 }
32 server.mta = smtp.NewDefaultMTA(&server, server.log)
33 go server.run()
34 return server.controlChan
35 }
36
37 type smtpServer struct {
38 config Config
39 tlsConfig *tls.Config
40
41 mta smtp.MTA
42
43 log *zap.Logger
44
45 controlChan chan ServerControlMessage
46 }
47
48 func (server *smtpServer) run() {
49 if !server.loadTLSConfig() {
50 return
51 }
52
53 addr := fmt.Sprintf(":%d", server.config.SMTPPort)
54 server.log.Info("starting server", zap.String("address", addr))
55
56 l, err := net.Listen("tcp", addr)
57 if err != nil {
58 server.log.Error("listen", zap.Error(err))
59 server.controlChan <- ServerControlFatalError
60 return
61 }
62
63 connChan := make(chan net.Conn)
64 go RunAcceptLoop(l, connChan, server.log)
65
66 reloadChan := CreateReloadSignal()
67
68 for {
69 select {
70 case <-reloadChan:
71 if !server.loadTLSConfig() {
72 return
73 }
74 case conn, ok := <-connChan:
75 if ok {
76 go smtp.AcceptConnection(conn, server, server.log)
77 } else {
78 break
79 }
80 }
81 }
82 }
83
84 func (server *smtpServer) loadTLSConfig() bool {
85 var err error
86 server.tlsConfig, err = server.config.GetTLSConfig()
87 if err != nil {
88 server.log.Error("failed to configure TLS", zap.Error(err))
89 server.controlChan <- ServerControlFatalError
90 return false
91 }
92 server.log.Info("loaded TLS config")
93 return true
94 }
95
96 func (server *smtpServer) Name() string {
97 return server.config.Hostname
98 }
99
100 func (server *smtpServer) TLSConfig() *tls.Config {
101 return server.tlsConfig
102 }
103
104 func (server *smtpServer) VerifyAddress(addr mail.Address) smtp.ReplyLine {
105 s := server.configForAddress(addr)
106 if s == nil {
107 return smtp.ReplyBadMailbox
108 }
109 for _, blocked := range s.BlockedAddresses {
110 if blocked == addr.Address {
111 return smtp.ReplyMailboxUnallowed
112 }
113 }
114 return smtp.ReplyOK
115 }
116
117 func (server *smtpServer) Authenticate(authz, authc, passwd string) bool {
118 authcAddr, err := mail.ParseAddress(authc)
119 if err != nil {
120 return false
121 }
122
123 authzAddr, err := mail.ParseAddress(authz)
124 if authz != "" && err != nil {
125 return false
126 }
127
128 domain := smtp.DomainForAddress(*authcAddr)
129 for _, s := range server.config.Servers {
130 if domain == s.Domain {
131 authOk := authc == MailboxAccount+s.Domain && passwd == s.MailboxPassword
132 if authzAddr != nil {
133 authOk = authOk && smtp.DomainForAddress(*authzAddr) == domain
134 }
135 return authOk
136 }
137 }
138 return false
139 }
140
141 func (server *smtpServer) DeliverMessage(en smtp.Envelope) *smtp.ReplyLine {
142 maildrop := server.maildropForAddress(en.RcptTo[0])
143 if maildrop == "" {
144 server.log.Error("faild to open maildrop to deliver message", zap.String("id", en.ID))
145 return &smtp.ReplyBadMailbox
146 }
147
148 f, err := os.Create(path.Join(maildrop, en.ID+".msg"))
149 if err != nil {
150 server.log.Error("failed to create message file", zap.String("id", en.ID), zap.Error(err))
151 return &smtp.ReplyBadMailbox
152 }
153
154 smtp.WriteEnvelopeForDelivery(f, en)
155 f.Close()
156 return nil
157 }
158
159 func (server *smtpServer) configForAddress(addr mail.Address) *Server {
160 domain := smtp.DomainForAddress(addr)
161 for _, s := range server.config.Servers {
162 if domain == s.Domain {
163 return &s
164 }
165 }
166 return nil
167 }
168
169 func (server *smtpServer) maildropForAddress(addr mail.Address) string {
170 s := server.configForAddress(addr)
171 if s != nil {
172 return s.MaildropPath
173 }
174 return ""
175 }
176
177 func (server *smtpServer) RelayMessage(en smtp.Envelope, authc string) {
178 go func() {
179 log := server.log.With(zap.String("id", en.ID))
180 server.handleSendAs(log, &en, authc)
181 server.mta.RelayMessage(en)
182 }()
183 }
184
185 func (server *smtpServer) handleSendAs(log *zap.Logger, en *smtp.Envelope, authc string) {
186 // Find the separator between the message header and body.
187 headerIdx := bytes.Index(en.Data, []byte("\n\n"))
188 if headerIdx == -1 {
189 log.Error("send-as: could not find headers index")
190 return
191 }
192
193 var buf bytes.Buffer
194
195 headers := bytes.SplitAfter(en.Data[:headerIdx], []byte("\n"))
196
197 var fromIdx, subjectIdx int
198 for i, header := range headers {
199 if bytes.HasPrefix(header, []byte("From:")) {
200 fromIdx = i
201 continue
202 }
203 if bytes.HasPrefix(header, []byte("Subject:")) {
204 subjectIdx = i
205 continue
206 }
207 }
208
209 if subjectIdx == -1 {
210 log.Error("send-as: could not find Subject header")
211 return
212 }
213 if fromIdx == -1 {
214 log.Error("send-as: could not find From header")
215 return
216 }
217
218 sendAs := sendAsSubject.FindSubmatchIndex(headers[subjectIdx])
219 if sendAs == nil {
220 // No send-as modification.
221 return
222 }
223
224 // Submatch 0 is the whole sendas magic. Submatch 1 is the address prefix.
225 sendAsUser := headers[subjectIdx][sendAs[2]:sendAs[3]]
226 sendAsAddress := string(sendAsUser) + "@" + smtp.DomainForAddressString(authc)
227
228 log.Info("handling send-as", zap.String("address", sendAsAddress))
229
230 for i, header := range headers {
231 if i == subjectIdx {
232 buf.Write(header[:sendAs[0]])
233 buf.Write(header[sendAs[1]:])
234 } else if i == fromIdx {
235 addressStart := bytes.LastIndexByte(header, byte('<'))
236 buf.Write(header[:addressStart+1])
237 buf.WriteString(sendAsAddress)
238 buf.WriteString(">\n")
239 } else {
240 buf.Write(header)
241 }
242 }
243
244 buf.Write(en.Data[headerIdx:])
245
246 en.Data = buf.Bytes()
247 en.MailFrom.Address = sendAsAddress
248 }