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