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