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