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
21 "src.bluestatic.org/mailpopbox/smtp"
24 var sendAsSubject = regexp.MustCompile(`(?i)\[sendas:\s*([a-zA-Z0-9\.\-_]+)\]`)
26 func runSMTPServer(config Config, log *zap.Logger) <-chan ServerControlMessage {
29 controlChan: make(chan ServerControlMessage),
30 log: log.With(zap.String("server", "smtp")),
32 server.mta = smtp.NewDefaultMTA(&server, server.log)
34 return server.controlChan
37 type smtpServer struct {
45 controlChan chan ServerControlMessage
48 func (server *smtpServer) run() {
49 if !server.loadTLSConfig() {
53 addr := fmt.Sprintf(":%d", server.config.SMTPPort)
54 server.log.Info("starting server", zap.String("address", addr))
56 l, err := net.Listen("tcp", addr)
58 server.log.Error("listen", zap.Error(err))
59 server.controlChan <- ServerControlFatalError
63 connChan := make(chan net.Conn)
64 go RunAcceptLoop(l, connChan, server.log)
66 reloadChan := CreateReloadSignal()
71 if !server.loadTLSConfig() {
74 case conn, ok := <-connChan:
76 go smtp.AcceptConnection(conn, server, server.log)
84 func (server *smtpServer) loadTLSConfig() bool {
86 server.tlsConfig, err = server.config.GetTLSConfig()
88 server.log.Error("failed to configure TLS", zap.Error(err))
89 server.controlChan <- ServerControlFatalError
92 server.log.Info("loaded TLS config")
96 func (server *smtpServer) Name() string {
97 return server.config.Hostname
100 func (server *smtpServer) TLSConfig() *tls.Config {
101 return server.tlsConfig
104 func (server *smtpServer) VerifyAddress(addr mail.Address) smtp.ReplyLine {
105 s := server.configForAddress(addr)
107 return smtp.ReplyBadMailbox
109 for _, blocked := range s.BlockedAddresses {
110 if blocked == addr.Address {
111 return smtp.ReplyMailboxUnallowed
117 func (server *smtpServer) Authenticate(authz, authc, passwd string) bool {
118 authcAddr, err := mail.ParseAddress(authc)
123 authzAddr, err := mail.ParseAddress(authz)
124 if authz != "" && err != nil {
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
141 func (server *smtpServer) DeliverMessage(en smtp.Envelope) *smtp.ReplyLine {
142 maildrop := server.maildropForAddress(en.RcptTo[0])
144 server.log.Error("faild to open maildrop to deliver message", zap.String("id", en.ID))
145 return &smtp.ReplyBadMailbox
148 f, err := os.Create(path.Join(maildrop, en.ID+".msg"))
150 server.log.Error("failed to create message file", zap.String("id", en.ID), zap.Error(err))
151 return &smtp.ReplyBadMailbox
154 smtp.WriteEnvelopeForDelivery(f, en)
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 {
169 func (server *smtpServer) maildropForAddress(addr mail.Address) string {
170 s := server.configForAddress(addr)
172 return s.MaildropPath
177 func (server *smtpServer) RelayMessage(en smtp.Envelope, authc string) {
179 log := server.log.With(zap.String("id", en.ID))
180 server.handleSendAs(log, &en, authc)
181 server.mta.RelayMessage(en)
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"))
189 log.Error("send-as: could not find headers index")
195 headers := bytes.SplitAfter(en.Data[:headerIdx], []byte("\n"))
197 var fromIdx, subjectIdx int
198 for i, header := range headers {
199 if bytes.HasPrefix(header, []byte("From:")) {
203 if bytes.HasPrefix(header, []byte("Subject:")) {
209 if subjectIdx == -1 {
210 log.Error("send-as: could not find Subject header")
214 log.Error("send-as: could not find From header")
218 sendAs := sendAsSubject.FindSubmatchIndex(headers[subjectIdx])
220 // No send-as modification.
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)
228 log.Info("handling send-as", zap.String("address", sendAsAddress))
230 for i, header := range headers {
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")
244 buf.Write(en.Data[headerIdx:])
246 en.Data = buf.Bytes()
247 en.MailFrom.Address = sendAsAddress