]>
src.bluestatic.org Git - mailpopbox.git/blob - smtp.go
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