13 "github.com/mjl-/mox/config"
14 "github.com/mjl-/mox/dns"
15 "github.com/mjl-/mox/dsn"
16 "github.com/mjl-/mox/mlog"
17 "github.com/mjl-/mox/mox-"
18 "github.com/mjl-/mox/sasl"
19 "github.com/mjl-/mox/smtp"
20 "github.com/mjl-/mox/smtpclient"
21 "github.com/mjl-/mox/store"
24// todo: reuse connection? do fewer concurrently (other than with direct delivery).
26// deliver via another SMTP server, e.g. relaying to a smart host, possibly
27// with authentication (submission).
28func deliverSubmit(cid int64, qlog *mlog.Log, resolver dns.Resolver, dialer smtpclient.Dialer, m Msg, backoff time.Duration, transportName string, transport *config.TransportSMTP, dialTLS bool, defaultPort int) {
29 // todo: configurable timeouts
31 port := transport.Port
36 tlsMode := smtpclient.TLSRequiredStartTLS
39 tlsMode = smtpclient.TLSImmediate
40 } else if transport.STARTTLSInsecureSkipVerify {
41 tlsMode = smtpclient.TLSOpportunistic
43 } else if transport.NoSTARTTLS {
44 tlsMode = smtpclient.TLSSkip
48 var deliveryResult string
54 metricDelivery.WithLabelValues(fmt.Sprintf("%d", m.Attempts), transportName, string(tlsMode), deliveryResult).Observe(float64(time.Since(start)) / float64(time.Second))
55 qlog.Debug("queue deliversubmit result", mlog.Field("host", transport.DNSHost), mlog.Field("port", port), mlog.Field("attempt", m.Attempts), mlog.Field("permanent", permanent), mlog.Field("secodeopt", secodeOpt), mlog.Field("errmsg", errmsg), mlog.Field("ok", success), mlog.Field("duration", time.Since(start)))
58 // todo: SMTP-DANE should be used when relaying on port 25.
61 // todo: for submission, understand SRV records, and even DANE.
63 // If submit was done with REQUIRETLS extension for SMTP, we must verify TLS
64 // certificates. If our submission connection is not configured that way, abort.
65 requireTLS := m.RequireTLS != nil && *m.RequireTLS
66 if requireTLS && (tlsMode != smtpclient.TLSRequiredStartTLS && tlsMode != smtpclient.TLSImmediate || !tlsPKIX) {
67 errmsg = fmt.Sprintf("transport %s: message requires verified tls but transport does not verify tls", transportName)
68 fail(qlog, m, backoff, true, dsn.NameIP{}, smtp.SePol7MissingReqTLS, errmsg)
72 dialctx, dialcancel := context.WithTimeout(context.Background(), 30*time.Second)
74 if m.DialedIPs == nil {
75 m.DialedIPs = map[string][]net.IP{}
77 _, _, _, ips, _, err := smtpclient.GatherIPs(dialctx, qlog, resolver, dns.IPDomain{Domain: transport.DNSHost}, m.DialedIPs)
80 if m.DialedIPs == nil {
81 m.DialedIPs = map[string][]net.IP{}
83 conn, _, err = smtpclient.Dial(dialctx, qlog, dialer, dns.IPDomain{Domain: transport.DNSHost}, ips, port, m.DialedIPs)
85 addr := net.JoinHostPort(transport.Host, fmt.Sprintf("%d", port))
90 case errors.Is(err, os.ErrDeadlineExceeded), errors.Is(err, context.DeadlineExceeded):
92 case errors.Is(err, context.Canceled):
97 metricConnection.WithLabelValues(result).Inc()
101 qlog.Check(err, "closing connection")
103 qlog.Errorx("dialing for submission", err, mlog.Field("remote", addr))
104 errmsg = fmt.Sprintf("transport %s: dialing %s for submission: %v", transportName, addr, err)
105 fail(qlog, m, backoff, false, dsn.NameIP{}, "", errmsg)
110 var auth []sasl.Client
111 if transport.Auth != nil {
113 for _, mech := range a.EffectiveMechanisms {
116 auth = append(auth, sasl.NewClientPlain(a.Username, a.Password))
118 auth = append(auth, sasl.NewClientCRAMMD5(a.Username, a.Password))
120 auth = append(auth, sasl.NewClientSCRAMSHA1(a.Username, a.Password))
121 case "SCRAM-SHA-256":
122 auth = append(auth, sasl.NewClientSCRAMSHA256(a.Username, a.Password))
124 // Should not happen.
125 qlog.Error("missing smtp authentication mechanisms implementation", mlog.Field("mechanism", mech))
126 errmsg = fmt.Sprintf("transport %s: authentication mechanisms %q not implemented", transportName, mech)
127 fail(qlog, m, backoff, false, dsn.NameIP{}, "", errmsg)
132 clientctx, clientcancel := context.WithTimeout(context.Background(), 60*time.Second)
134 opts := smtpclient.Opts{
136 RootCAs: mox.Conf.Static.TLS.CertPool,
138 client, err := smtpclient.New(clientctx, qlog, conn, tlsMode, tlsPKIX, mox.Conf.Static.HostnameDomain, transport.DNSHost, opts)
140 smtperr, ok := err.(smtpclient.Error)
141 var remoteMTA dsn.NameIP
143 remoteMTA.Name = transport.Host
145 qlog.Errorx("establishing smtp session for submission", err, mlog.Field("remote", addr))
146 errmsg = fmt.Sprintf("transport %s: establishing smtp session with %s for submission: %v", transportName, addr, err)
147 secodeOpt = smtperr.Secode
148 fail(qlog, m, backoff, false, remoteMTA, secodeOpt, errmsg)
152 err := client.Close()
153 qlog.Check(err, "closing smtp client after delivery")
157 var msgr io.ReadCloser
159 var req8bit, reqsmtputf8 bool
160 if len(m.DSNUTF8) > 0 && client.SupportsSMTPUTF8() {
161 msgr = io.NopCloser(bytes.NewReader(m.DSNUTF8))
163 size = int64(len(m.DSNUTF8))
165 req8bit = m.Has8bit // todo: not require this, but just try to submit?
171 qlog.Errorx("opening message for delivery", err, mlog.Field("remote", addr), mlog.Field("path", p))
172 errmsg = fmt.Sprintf("transport %s: opening message file for submission: %v", transportName, err)
173 fail(qlog, m, backoff, false, dsn.NameIP{}, "", errmsg)
176 msgr = store.FileMsgReader(m.MsgPrefix, f)
179 qlog.Check(err, "closing message after delivery attempt")
183 deliverctx, delivercancel := context.WithTimeout(context.Background(), time.Duration(60+size/(1024*1024))*time.Second)
184 defer delivercancel()
185 err = client.Deliver(deliverctx, m.Sender().String(), m.Recipient().String(), size, msgr, req8bit, reqsmtputf8, requireTLS)
187 qlog.Infox("delivery failed", err)
189 var cerr smtpclient.Error
192 deliveryResult = "ok"
194 case errors.Is(err, os.ErrDeadlineExceeded), errors.Is(err, context.DeadlineExceeded):
195 deliveryResult = "timeout"
196 case errors.Is(err, context.Canceled):
197 deliveryResult = "canceled"
198 case errors.As(err, &cerr):
199 deliveryResult = "temperror"
201 deliveryResult = "permerror"
204 deliveryResult = "error"
207 smtperr, ok := err.(smtpclient.Error)
208 var remoteMTA dsn.NameIP
210 remoteMTA.Name = transport.Host
212 qlog.Errorx("submitting email", err, mlog.Field("remote", addr))
213 permanent = smtperr.Permanent
214 secodeOpt = smtperr.Secode
215 errmsg = fmt.Sprintf("transport %s: submitting email to %s: %v", transportName, addr, err)
216 fail(qlog, m, backoff, permanent, remoteMTA, secodeOpt, errmsg)
219 qlog.Info("delivered from queue with transport")
220 if err := queueDelete(context.Background(), m.ID); err != nil {
221 qlog.Errorx("deleting message from queue after delivery", err)