1package queue
2
3import (
4 "bytes"
5 "context"
6 "fmt"
7 "io"
8 "log/slog"
9 "net"
10 "os"
11 "time"
12
13 "github.com/mjl-/mox/dns"
14 "github.com/mjl-/mox/dsn"
15 "github.com/mjl-/mox/mlog"
16 "github.com/mjl-/mox/mox-"
17 "github.com/mjl-/mox/smtpclient"
18 "github.com/mjl-/mox/store"
19)
20
21// We won't be dialing remote servers. We just connect the smtp port of the first
22// ip in the "local" listener, with fallback to localhost:1025 for any destination
23// address and try to deliver. Our smtpserver uses a mocked dns resolver to give
24// spf/dkim a chance to pass.
25func deliverLocalserve(ctx context.Context, log mlog.Log, msgs []*Msg, backoff time.Duration) {
26 m0 := msgs[0]
27
28 addr := "localhost:1025"
29 l, ok := mox.Conf.Static.Listeners["local"]
30 if ok && l.SMTP.Enabled {
31 port := 1025
32 if l.SMTP.Port != 0 {
33 port = l.SMTP.Port
34 }
35 addr = net.JoinHostPort(l.IPs[0], fmt.Sprintf("%d", port))
36 }
37 var d net.Dialer
38 dialctx, dialcancel := context.WithTimeout(ctx, 30*time.Second)
39 defer dialcancel()
40 conn, err := d.DialContext(dialctx, "tcp", addr)
41 dialcancel()
42 if err != nil {
43 failMsgsDB(log, msgs, m0.DialedIPs, backoff, dsn.NameIP{}, err)
44 return
45 }
46 defer func() {
47 if conn != nil {
48 err = conn.Close()
49 log.Check(err, "closing connection")
50 }
51 }()
52
53 clientctx, clientcancel := context.WithTimeout(context.Background(), 60*time.Second)
54 defer clientcancel()
55 localhost := dns.Domain{ASCII: "localhost"}
56 client, err := smtpclient.New(clientctx, log.Logger, conn, smtpclient.TLSOpportunistic, false, localhost, localhost, smtpclient.Opts{})
57 clientcancel()
58 if err != nil {
59 failMsgsDB(log, msgs, m0.DialedIPs, backoff, dsn.NameIP{}, err)
60 return
61 }
62 conn = nil // Will be closed when closing client.
63 defer func() {
64 err := client.Close()
65 log.Check(err, "closing smtp client")
66 }()
67
68 var msgr io.ReadCloser
69 var size int64
70 if len(m0.DSNUTF8) > 0 {
71 msgr = io.NopCloser(bytes.NewReader(m0.DSNUTF8))
72 size = int64(len(m0.DSNUTF8))
73 } else {
74 size = m0.Size
75 p := m0.MessagePath()
76 f, err := os.Open(p)
77 if err != nil {
78 log.Errorx("opening message for delivery", err, slog.String("remote", addr), slog.String("path", p))
79 err = fmt.Errorf("opening message file: %v", err)
80 failMsgsDB(log, msgs, m0.DialedIPs, backoff, dsn.NameIP{}, err)
81 return
82 }
83 msgr = store.FileMsgReader(m0.MsgPrefix, f)
84 defer func() {
85 err := msgr.Close()
86 log.Check(err, "closing message after delivery attempt")
87 }()
88 }
89
90 deliverctx, delivercancel := context.WithTimeout(context.Background(), 60*time.Second)
91 defer delivercancel()
92 requireTLS := m0.RequireTLS != nil && *m0.RequireTLS
93 rcpts := make([]string, len(msgs))
94 for i, m := range msgs {
95 rcpts[i] = m.Recipient().String()
96 }
97 rcptErrs, err := client.DeliverMultiple(deliverctx, m0.Sender().String(), rcpts, size, msgr, m0.Has8bit, m0.SMTPUTF8, requireTLS)
98 delivercancel()
99 if err != nil {
100 log.Infox("smtp transaction for delivery failed", err)
101 }
102 processDeliveries(log, m0, msgs, addr, "localhost", backoff, rcptErrs, err)
103}
104