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 if server.maildropForAddress(addr) == "" {
106 return smtp.ReplyBadMailbox
111 func (server *smtpServer) Authenticate(authz, authc, passwd string) bool {
112 authcAddr, err := mail.ParseAddress(authc)
117 authzAddr, err := mail.ParseAddress(authz)
118 if authz != "" && err != nil {
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
135 func (server *smtpServer) DeliverMessage(en smtp.Envelope) *smtp.ReplyLine {
136 maildrop := server.maildropForAddress(en.RcptTo[0])
138 server.log.Error("faild to open maildrop to deliver message", zap.String("id", en.ID))
139 return &smtp.ReplyBadMailbox
142 f, err := os.Create(path.Join(maildrop, en.ID+".msg"))
144 server.log.Error("failed to create message file", zap.String("id", en.ID), zap.Error(err))
145 return &smtp.ReplyBadMailbox
148 smtp.WriteEnvelopeForDelivery(f, en)
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
164 func (server *smtpServer) RelayMessage(en smtp.Envelope, authc string) {
166 log := server.log.With(zap.String("id", en.ID))
167 server.handleSendAs(log, &en, authc)
168 server.mta.RelayMessage(en)
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"))
176 log.Error("send-as: could not find headers index")
182 headers := bytes.SplitAfter(en.Data[:headerIdx], []byte("\n"))
184 var fromIdx, subjectIdx int
185 for i, header := range headers {
186 if bytes.HasPrefix(header, []byte("From:")) {
190 if bytes.HasPrefix(header, []byte("Subject:")) {
196 if subjectIdx == -1 {
197 log.Error("send-as: could not find Subject header")
201 log.Error("send-as: could not find From header")
205 sendAs := sendAsSubject.FindSubmatchIndex(headers[subjectIdx])
207 // No send-as modification.
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)
215 log.Info("handling send-as", zap.String("address", sendAsAddress))
217 for i, header := range headers {
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")
231 buf.Write(en.Data[headerIdx:])
233 en.Data = buf.Bytes()
234 en.MailFrom.Address = sendAsAddress