Move smtp.RelayMessage into a new smtp.MTA interface.
[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 "crypto/tls"
11 "fmt"
12 "net"
13 "net/mail"
14 "os"
15 "path"
16
17 "go.uber.org/zap"
18
19 "src.bluestatic.org/mailpopbox/smtp"
20 )
21
22 func runSMTPServer(config Config, log *zap.Logger) <-chan ServerControlMessage {
23 server := smtpServer{
24 config: config,
25 controlChan: make(chan ServerControlMessage),
26 log: log.With(zap.String("server", "smtp")),
27 }
28 server.mta = smtp.NewDefaultMTA(&server, server.log)
29 go server.run()
30 return server.controlChan
31 }
32
33 type smtpServer struct {
34 config Config
35 tlsConfig *tls.Config
36
37 mta smtp.MTA
38
39 log *zap.Logger
40
41 controlChan chan ServerControlMessage
42 }
43
44 func (server *smtpServer) run() {
45 if !server.loadTLSConfig() {
46 return
47 }
48
49 addr := fmt.Sprintf(":%d", server.config.SMTPPort)
50 server.log.Info("starting server", zap.String("address", addr))
51
52 l, err := net.Listen("tcp", addr)
53 if err != nil {
54 server.log.Error("listen", zap.Error(err))
55 server.controlChan <- ServerControlFatalError
56 return
57 }
58
59 connChan := make(chan net.Conn)
60 go RunAcceptLoop(l, connChan, server.log)
61
62 reloadChan := CreateReloadSignal()
63
64 for {
65 select {
66 case <-reloadChan:
67 if !server.loadTLSConfig() {
68 return
69 }
70 case conn, ok := <-connChan:
71 if ok {
72 go smtp.AcceptConnection(conn, server, server.log)
73 } else {
74 break
75 }
76 }
77 }
78 }
79
80 func (server *smtpServer) loadTLSConfig() bool {
81 var err error
82 server.tlsConfig, err = server.config.GetTLSConfig()
83 if err != nil {
84 server.log.Error("failed to configure TLS", zap.Error(err))
85 server.controlChan <- ServerControlFatalError
86 return false
87 }
88 server.log.Info("loaded TLS config")
89 return true
90 }
91
92 func (server *smtpServer) Name() string {
93 return server.config.Hostname
94 }
95
96 func (server *smtpServer) TLSConfig() *tls.Config {
97 return server.tlsConfig
98 }
99
100 func (server *smtpServer) VerifyAddress(addr mail.Address) smtp.ReplyLine {
101 if server.maildropForAddress(addr) == "" {
102 return smtp.ReplyBadMailbox
103 }
104 return smtp.ReplyOK
105 }
106
107 func (server *smtpServer) Authenticate(authz, authc, passwd string) bool {
108 authcAddr, err := mail.ParseAddress(authc)
109 if err != nil {
110 return false
111 }
112
113 authzAddr, err := mail.ParseAddress(authz)
114 if authz != "" && err != nil {
115 return false
116 }
117
118 domain := smtp.DomainForAddress(*authcAddr)
119 for _, s := range server.config.Servers {
120 if domain == s.Domain {
121 authOk := authc == MailboxAccount+s.Domain && passwd == s.MailboxPassword
122 if authzAddr != nil {
123 authOk = authOk && smtp.DomainForAddress(*authzAddr) == domain
124 }
125 return authOk
126 }
127 }
128 return false
129 }
130
131 func (server *smtpServer) DeliverMessage(en smtp.Envelope) *smtp.ReplyLine {
132 maildrop := server.maildropForAddress(en.RcptTo[0])
133 if maildrop == "" {
134 server.log.Error("faild to open maildrop to deliver message", zap.String("id", en.ID))
135 return &smtp.ReplyBadMailbox
136 }
137
138 f, err := os.Create(path.Join(maildrop, en.ID+".msg"))
139 if err != nil {
140 server.log.Error("failed to create message file", zap.String("id", en.ID), zap.Error(err))
141 return &smtp.ReplyBadMailbox
142 }
143
144 smtp.WriteEnvelopeForDelivery(f, en)
145 f.Close()
146 return nil
147 }
148
149 func (server *smtpServer) RelayMessage(en smtp.Envelope) {
150 go server.mta.RelayMessage(en)
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 }