Updates for the latest zap.
[mailpopbox.git] / smtp / relay.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 smtp
8
9 import (
10 "crypto/tls"
11 "net"
12 "net/smtp"
13
14 "go.uber.org/zap"
15 )
16
17 func RelayMessage(server Server, env Envelope, log *zap.Logger) {
18 for _, rcptTo := range env.RcptTo {
19 sendLog := log.With(zap.String("address", rcptTo.Address))
20
21 domain := DomainForAddress(rcptTo)
22 mx, err := net.LookupMX(domain)
23 if err != nil || len(mx) < 1 {
24 sendLog.Error("failed to lookup MX records",
25 zap.Error(err))
26 deliverRelayFailure(env, err)
27 return
28 }
29 host := mx[0].Host + ":25"
30 relayMessageToHost(server, env, sendLog, rcptTo.Address, host)
31 }
32 }
33
34 func relayMessageToHost(server Server, env Envelope, log *zap.Logger, to, host string) {
35 from := env.MailFrom.Address
36
37 c, err := smtp.Dial(host)
38 if err != nil {
39 // TODO - retry, or look at other MX records
40 log.Error("failed to dial host",
41 zap.String("host", host),
42 zap.Error(err))
43 deliverRelayFailure(env, err)
44 return
45 }
46 defer c.Quit()
47
48 log = log.With(zap.String("host", host))
49
50 if err = c.Hello(server.Name()); err != nil {
51 log.Error("failed to HELO", zap.Error(err))
52 deliverRelayFailure(env, err)
53 return
54 }
55
56 if hasTls, _ := c.Extension("STARTTLS"); hasTls {
57 config := &tls.Config{ServerName: host}
58 if err = c.StartTLS(config); err != nil {
59 log.Error("failed to STARTTLS", zap.Error(err))
60 deliverRelayFailure(env, err)
61 return
62 }
63 }
64
65 if err = c.Mail(from); err != nil {
66 log.Error("failed MAIL FROM", zap.Error(err))
67 deliverRelayFailure(env, err)
68 return
69 }
70
71 if err = c.Rcpt(to); err != nil {
72 log.Error("failed to RCPT TO", zap.Error(err))
73 deliverRelayFailure(env, err)
74 return
75 }
76
77 wc, err := c.Data()
78 if err != nil {
79 log.Error("failed to DATA", zap.Error(err))
80 deliverRelayFailure(env, err)
81 return
82 }
83
84 _, err = wc.Write(env.Data)
85 if err != nil {
86 wc.Close()
87 log.Error("failed to write DATA", zap.Error(err))
88 deliverRelayFailure(env, err)
89 return
90 }
91
92 if err = wc.Close(); err != nil {
93 log.Error("failed to close DATA", zap.Error(err))
94 deliverRelayFailure(env, err)
95 return
96 }
97 }
98
99 func deliverRelayFailure(env Envelope, err error) {
100 // TODO: constructo a delivery status notification
101 }