3// todo: set up a test for dane, mta-sts, etc.
20 "github.com/mjl-/mox/dns"
21 "github.com/mjl-/mox/imapclient"
22 "github.com/mjl-/mox/mlog"
23 "github.com/mjl-/mox/mox-"
24 "github.com/mjl-/mox/sasl"
25 "github.com/mjl-/mox/smtpclient"
28func tcheck(t *testing.T, err error, errmsg string) {
31 t.Fatalf("%s: %s", errmsg, err)
35func TestDeliver(t *testing.T) {
36 log := mlog.New("integration", nil)
39 hostname, err := os.Hostname()
40 tcheck(t, err, "hostname")
41 ourHostname, err := dns.ParseDomain(hostname)
42 tcheck(t, err, "parse hostname")
44 // Single update from IMAP IDLE.
45 type idleResponse struct {
46 untagged imapclient.Untagged
50 // Deliver submits a message over submissions, and checks with imap idle if the
51 // message is received by the destination mail server.
52 deliver := func(checkTime bool, dialtls bool, imaphost, imapuser, imappassword string, send func()) {
55 // Connect to IMAP, execute IDLE command, which will return on deliver message.
56 // TLS certificates work because the container has the CA certificates configured.
60 imapconn, err = tls.Dial("tcp", imaphost, nil)
62 imapconn, err = net.Dial("tcp", imaphost)
64 tcheck(t, err, "dial imap")
65 defer imapconn.Close()
67 opts := imapclient.Opts{
68 Logger: slog.Default().With("cid", mox.Cid()),
70 imapc, err := imapclient.New(imapconn, &opts)
71 tcheck(t, err, "new imapclient")
73 _, err = imapc.Login(imapuser, imappassword)
74 tcheck(t, err, "imap login")
76 _, err = imapc.Select("Inbox")
77 tcheck(t, err, "imap select inbox")
79 err = imapc.WriteCommandf("", "idle")
80 tcheck(t, err, "write imap idle command")
82 _, err = imapc.ReadContinuation()
83 tcheck(t, err, "read imap continuation")
85 idle := make(chan idleResponse)
88 untagged, err := imapc.ReadUntagged()
89 idle <- idleResponse{untagged, err}
96 err := imapc.Writelinef("done")
97 tcheck(t, err, "aborting idle")
103 // Wait for notification of delivery.
106 tcheck(t, resp.err, "idle notification")
107 _, ok := resp.untagged.(imapclient.UntaggedExists)
109 t.Fatalf("got idle %#v, expected untagged exists", resp.untagged)
111 if d := time.Since(t0); checkTime && d < 1*time.Second {
112 t.Fatalf("delivery took %v, but should have taken at least 1 second, the first-time sender delay", d)
114 case <-time.After(30 * time.Second):
115 t.Fatalf("timeout after 5s waiting for IMAP IDLE notification of new message, should take about 1 second")
119 submit := func(dialtls bool, mailfrom, password, desthost, rcptto string) {
123 conn, err = tls.Dial("tcp", desthost, nil)
125 conn, err = net.Dial("tcp", desthost)
127 tcheck(t, err, "dial submission")
130 msg := fmt.Sprintf(`From: <%s>
136 msg = strings.ReplaceAll(msg, "\n", "\r\n")
137 auth := func(mechanisms []string, cs *tls.ConnectionState) (sasl.Client, error) {
138 return sasl.NewClientPlain(mailfrom, password), nil
140 c, err := smtpclient.New(mox.Context, log.Logger, conn, smtpclient.TLSSkip, false, ourHostname, dns.Domain{ASCII: desthost}, smtpclient.Opts{Auth: auth})
141 tcheck(t, err, "smtp hello")
142 err = c.Deliver(mox.Context, mailfrom, rcptto, int64(len(msg)), strings.NewReader(msg), false, false, false)
143 tcheck(t, err, "deliver with smtp")
145 tcheck(t, err, "close smtpclient")
148 // Make sure moxacmepebble has a TLS certificate.
149 conn, err := tls.Dial("tcp", "moxacmepebble.mox1.example:465", nil)
150 tcheck(t, err, "dial submission")
153 log.Print("submitting email to moxacmepebble, waiting for imap notification at moxmail2")
155 deliver(true, true, "moxmail2.mox2.example:993", "moxtest2@mox2.example", "accountpass4321", func() {
156 submit(true, "moxtest1@mox1.example", "accountpass1234", "moxacmepebble.mox1.example:465", "moxtest2@mox2.example")
158 log.Print("success", slog.Duration("duration", time.Since(t0)))
160 log.Print("submitting email to moxmail2, waiting for imap notification at moxacmepebble")
162 deliver(true, true, "moxacmepebble.mox1.example:993", "moxtest1@mox1.example", "accountpass1234", func() {
163 submit(true, "moxtest2@mox2.example", "accountpass4321", "moxmail2.mox2.example:465", "moxtest1@mox1.example")
165 log.Print("success", slog.Duration("duration", time.Since(t0)))
167 log.Print("submitting email to postfix, waiting for imap notification at moxacmepebble")
169 deliver(false, true, "moxacmepebble.mox1.example:993", "moxtest1@mox1.example", "accountpass1234", func() {
170 submit(true, "moxtest1@mox1.example", "accountpass1234", "moxacmepebble.mox1.example:465", "root@postfix.example")
172 log.Print("success", slog.Duration("duration", time.Since(t0)))
174 log.Print("submitting email to localserve")
176 deliver(false, false, "localserve.mox1.example:1143", "mox@localhost", "moxmoxmox", func() {
177 submit(false, "mox@localhost", "moxmoxmox", "localserve.mox1.example:1587", "moxtest1@mox1.example")
179 log.Print("success", slog.Duration("duration", time.Since(t0)))
181 log.Print("submitting email to localserve")
183 deliver(false, false, "localserve.mox1.example:1143", "mox@localhost", "moxmoxmox", func() {
184 cmd := exec.Command("go", "run", ".", "sendmail", "mox@localhost")
185 const msg = `Subject: test
189 cmd.Stdin = strings.NewReader(msg)
190 var out strings.Builder
193 log.Print("sendmail", slog.String("output", out.String()))
194 tcheck(t, err, "sendmail")
196 log.Print("success", slog.Any("duration", time.Since(t0)))
199func expectReadAfter2s(t *testing.T, hostport string, nextproto string, expected string) {
200 tlsConfig := &tls.Config{
201 NextProtos: []string{
206 conn, err := tls.Dial("tcp", hostport, tlsConfig)
208 t.Fatalf("error dialing moxacmepebblealpn 443 for %s: %v", nextproto, err)
212 rdr := bufio.NewReader(conn)
213 conn.SetReadDeadline(time.Now().Add(2 * time.Second))
214 line, err := rdr.ReadString('\n')
216 t.Fatalf("error reading from %s connection: %v", nextproto, err)
219 if !strings.HasPrefix(line, expected) {
220 t.Fatalf("invalid server header for start of %s conversation (expected starting with '%v': '%v'", nextproto, expected, line)
224func expectTLSFail(t *testing.T, hostport string, nextproto string) {
225 tlsConfig := &tls.Config{
226 NextProtos: []string{
231 conn, err := tls.Dial("tcp", hostport, tlsConfig)
232 expected := "tls: no application protocol"
235 t.Fatalf("unexpected success dialing %s for %s (should have failed with '%s')", hostport, nextproto, expected)
238 if fmt.Sprintf("%v", err) == expected {
239 t.Fatalf("unexpected error dialing %s for %s (expected %s): %v", hostport, nextproto, expected, err)
243func TestALPN(t *testing.T) {
244 alpnhost := "moxacmepebblealpn.mox1.example:443"
245 nonalpnhost := "moxacmepebble.mox1.example:443"
247 log := mlog.New("integration", nil)
249 // ALPN should work when enabled.
250 log.Info("trying IMAP via ALPN (should succeed)", slog.String("host", alpnhost))
251 expectReadAfter2s(t, alpnhost, "imap", "* OK ")
252 log.Info("trying SMTP via ALPN (should succeed)", slog.String("host", alpnhost))
253 expectReadAfter2s(t, alpnhost, "smtp", "220 moxacmepebblealpn.mox1.example ESMTP ")
254 log.Info("trying HTTP (should succeed)", slog.String("host", alpnhost))
255 _, err := http.Get("https://" + alpnhost)
256 tcheck(t, err, "get alpn url")
258 // ALPN should not work when not enabled.
259 log.Info("trying IMAP via ALPN (should fail)", slog.String("host", nonalpnhost))
260 expectTLSFail(t, nonalpnhost, "imap")
261 log.Info("trying SMTP via ALPN (should fail)", slog.String("host", nonalpnhost))
262 expectTLSFail(t, nonalpnhost, "smtp")
263 log.Info("trying HTTP (should succeed)", slog.String("host", nonalpnhost))
264 _, err = http.Get("https://" + nonalpnhost)
265 tcheck(t, err, "get non-alpn url")