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 if server.maildropForAddress(addr) == "" {
106 return smtp.ReplyBadMailbox
107 }
108 return smtp.ReplyOK
109 }
110
111 func (server *smtpServer) Authenticate(authz, authc, passwd string) bool {
112 authcAddr, err := mail.ParseAddress(authc)
113 if err != nil {
114 return false
115 }
116
117 authzAddr, err := mail.ParseAddress(authz)
118 if authz != "" && err != nil {
119 return false
120 }
121
122 domain := smtp.DomainForAddress(*authcAddr)
123 for _, s := range server.config.Servers {
124 if domain == s.Domain {
125 authOk := authc == MailboxAccount+s.Domain && passwd == s.MailboxPassword
126 if authzAddr != nil {
127 authOk = authOk && smtp.DomainForAddress(*authzAddr) == domain
128 }
129 return authOk
130 }
131 }
132 return false
133 }
134
135 func (server *smtpServer) DeliverMessage(en smtp.Envelope) *smtp.ReplyLine {
136 maildrop := server.maildropForAddress(en.RcptTo[0])
137 if maildrop == "" {
138 server.log.Error("faild to open maildrop to deliver message", zap.String("id", en.ID))
139 return &smtp.ReplyBadMailbox
140 }
141
142 f, err := os.Create(path.Join(maildrop, en.ID+".msg"))
143 if err != nil {
144 server.log.Error("failed to create message file", zap.String("id", en.ID), zap.Error(err))
145 return &smtp.ReplyBadMailbox
146 }
147
148 smtp.WriteEnvelopeForDelivery(f, en)
149 f.Close()
150 return nil
151 }
152
153 func (server *smtpServer) maildropForAddress(addr mail.Address) string {
154 domain := smtp.DomainForAddress(addr)
155 for _, s := range server.config.Servers {
156 if domain == s.Domain {
157 return s.MaildropPath
158 }
159 }
160
161 return ""
162 }
163
164 func (server *smtpServer) RelayMessage(en smtp.Envelope, authc string) {
165 go func() {
166 log := server.log.With(zap.String("id", en.ID))
167 server.handleSendAs(log, &en, authc)
168 server.mta.RelayMessage(en)
169 }()
170 }
171
172 func (server *smtpServer) handleSendAs(log *zap.Logger, en *smtp.Envelope, authc string) {
173 // Find the separator between the message header and body.
174 headerIdx := bytes.Index(en.Data, []byte("\n\n"))
175 if headerIdx == -1 {
176 log.Error("send-as: could not find headers index")
177 return
178 }
179
180 var buf bytes.Buffer
181
182 headers := bytes.SplitAfter(en.Data[:headerIdx], []byte("\n"))
183
184 var fromIdx, subjectIdx int
185 for i, header := range headers {
186 if bytes.HasPrefix(header, []byte("From:")) {
187 fromIdx = i
188 continue
189 }
190 if bytes.HasPrefix(header, []byte("Subject:")) {
191 subjectIdx = i
192 continue
193 }
194 }
195
196 if subjectIdx == -1 {
197 log.Error("send-as: could not find Subject header")
198 return
199 }
200 if fromIdx == -1 {
201 log.Error("send-as: could not find From header")
202 return
203 }
204
205 sendAs := sendAsSubject.FindSubmatchIndex(headers[subjectIdx])
206 if sendAs == nil {
207 // No send-as modification.
208 return
209 }
210
211 // Submatch 0 is the whole sendas magic. Submatch 1 is the address prefix.
212 sendAsUser := headers[subjectIdx][sendAs[2]:sendAs[3]]
213 sendAsAddress := string(sendAsUser) + "@" + smtp.DomainForAddressString(authc)
214
215 log.Info("handling send-as", zap.String("address", sendAsAddress))
216
217 for i, header := range headers {
218 if i == subjectIdx {
219 buf.Write(header[:sendAs[0]])
220 buf.Write(header[sendAs[1]:])
221 } else if i == fromIdx {
222 addressStart := bytes.LastIndexByte(header, byte('<'))
223 buf.Write(header[:addressStart+1])
224 buf.WriteString(sendAsAddress)
225 buf.WriteString(">\n")
226 } else {
227 buf.Write(header)
228 }
229 }
230
231 buf.Write(en.Data[headerIdx:])
232
233 en.Data = buf.Bytes()
234 en.MailFrom.Address = sendAsAddress
235 }