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