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 imapc, err := imapclient.New(mox.Cid(), imapconn, false)
68 tcheck(t, err, "new imapclient")
70 _, _, err = imapc.Login(imapuser, imappassword)
71 tcheck(t, err, "imap login")
73 _, _, err = imapc.Select("Inbox")
74 tcheck(t, err, "imap select inbox")
76 err = imapc.Commandf("", "idle")
77 tcheck(t, err, "write imap idle command")
79 _, _, _, err = imapc.ReadContinuation()
80 tcheck(t, err, "read imap continuation")
82 idle := make(chan idleResponse)
85 untagged, err := imapc.ReadUntagged()
86 idle <- idleResponse{untagged, err}
93 err := imapc.Writelinef("done")
94 tcheck(t, err, "aborting idle")
100 // Wait for notification of delivery.
103 tcheck(t, resp.err, "idle notification")
104 _, ok := resp.untagged.(imapclient.UntaggedExists)
106 t.Fatalf("got idle %#v, expected untagged exists", resp.untagged)
108 if d := time.Since(t0); checkTime && d < 1*time.Second {
109 t.Fatalf("delivery took %v, but should have taken at least 1 second, the first-time sender delay", d)
111 case <-time.After(30 * time.Second):
112 t.Fatalf("timeout after 5s waiting for IMAP IDLE notification of new message, should take about 1 second")
116 submit := func(dialtls bool, mailfrom, password, desthost, rcptto string) {
120 conn, err = tls.Dial("tcp", desthost, nil)
122 conn, err = net.Dial("tcp", desthost)
124 tcheck(t, err, "dial submission")
127 msg := fmt.Sprintf(`From: <%s>
133 msg = strings.ReplaceAll(msg, "\n", "\r\n")
134 auth := func(mechanisms []string, cs *tls.ConnectionState) (sasl.Client, error) {
135 return sasl.NewClientPlain(mailfrom, password), nil
137 c, err := smtpclient.New(mox.Context, log.Logger, conn, smtpclient.TLSSkip, false, ourHostname, dns.Domain{ASCII: desthost}, smtpclient.Opts{Auth: auth})
138 tcheck(t, err, "smtp hello")
139 err = c.Deliver(mox.Context, mailfrom, rcptto, int64(len(msg)), strings.NewReader(msg), false, false, false)
140 tcheck(t, err, "deliver with smtp")
142 tcheck(t, err, "close smtpclient")
145 // Make sure moxacmepebble has a TLS certificate.
146 conn, err := tls.Dial("tcp", "moxacmepebble.mox1.example:465", nil)
147 tcheck(t, err, "dial submission")
150 log.Print("submitting email to moxacmepebble, waiting for imap notification at moxmail2")
152 deliver(true, true, "moxmail2.mox2.example:993", "moxtest2@mox2.example", "accountpass4321", func() {
153 submit(true, "moxtest1@mox1.example", "accountpass1234", "moxacmepebble.mox1.example:465", "moxtest2@mox2.example")
155 log.Print("success", slog.Duration("duration", time.Since(t0)))
157 log.Print("submitting email to moxmail2, waiting for imap notification at moxacmepebble")
159 deliver(true, true, "moxacmepebble.mox1.example:993", "moxtest1@mox1.example", "accountpass1234", func() {
160 submit(true, "moxtest2@mox2.example", "accountpass4321", "moxmail2.mox2.example:465", "moxtest1@mox1.example")
162 log.Print("success", slog.Duration("duration", time.Since(t0)))
164 log.Print("submitting email to postfix, waiting for imap notification at moxacmepebble")
166 deliver(false, true, "moxacmepebble.mox1.example:993", "moxtest1@mox1.example", "accountpass1234", func() {
167 submit(true, "moxtest1@mox1.example", "accountpass1234", "moxacmepebble.mox1.example:465", "root@postfix.example")
169 log.Print("success", slog.Duration("duration", time.Since(t0)))
171 log.Print("submitting email to localserve")
173 deliver(false, false, "localserve.mox1.example:1143", "mox@localhost", "moxmoxmox", func() {
174 submit(false, "mox@localhost", "moxmoxmox", "localserve.mox1.example:1587", "moxtest1@mox1.example")
176 log.Print("success", slog.Duration("duration", time.Since(t0)))
178 log.Print("submitting email to localserve")
180 deliver(false, false, "localserve.mox1.example:1143", "mox@localhost", "moxmoxmox", func() {
181 cmd := exec.Command("go", "run", ".", "sendmail", "mox@localhost")
182 const msg = `Subject: test
186 cmd.Stdin = strings.NewReader(msg)
187 var out strings.Builder
190 log.Print("sendmail", slog.String("output", out.String()))
191 tcheck(t, err, "sendmail")
193 log.Print("success", slog.Any("duration", time.Since(t0)))
196func expectReadAfter2s(t *testing.T, hostport string, nextproto string, expected string) {
197 tlsConfig := &tls.Config{
198 NextProtos: []string{
203 conn, err := tls.Dial("tcp", hostport, tlsConfig)
205 t.Fatalf("error dialing moxacmepebblealpn 443 for %s: %v", nextproto, err)
209 rdr := bufio.NewReader(conn)
210 conn.SetReadDeadline(time.Now().Add(2 * time.Second))
211 line, err := rdr.ReadString('\n')
213 t.Fatalf("error reading from %s connection: %v", nextproto, err)
216 if !strings.HasPrefix(line, expected) {
217 t.Fatalf("invalid server header for start of %s conversation (expected starting with '%v': '%v'", nextproto, expected, line)
221func expectTLSFail(t *testing.T, hostport string, nextproto string) {
222 tlsConfig := &tls.Config{
223 NextProtos: []string{
228 conn, err := tls.Dial("tcp", hostport, tlsConfig)
229 expected := "tls: no application protocol"
232 t.Fatalf("unexpected success dialing %s for %s (should have failed with '%s')", hostport, nextproto, expected)
235 if fmt.Sprintf("%v", err) == expected {
236 t.Fatalf("unexpected error dialing %s for %s (expected %s): %v", hostport, nextproto, expected, err)
240func TestALPN(t *testing.T) {
241 alpnhost := "moxacmepebblealpn.mox1.example:443"
242 nonalpnhost := "moxacmepebble.mox1.example:443"
244 log := mlog.New("integration", nil)
246 // ALPN should work when enabled.
247 log.Info("trying IMAP via ALPN (should succeed)", slog.String("host", alpnhost))
248 expectReadAfter2s(t, alpnhost, "imap", "* OK ")
249 log.Info("trying SMTP via ALPN (should succeed)", slog.String("host", alpnhost))
250 expectReadAfter2s(t, alpnhost, "smtp", "220 moxacmepebblealpn.mox1.example ESMTP ")
251 log.Info("trying HTTP (should succeed)", slog.String("host", alpnhost))
252 _, err := http.Get("https://" + alpnhost)
253 tcheck(t, err, "get alpn url")
255 // ALPN should not work when not enabled.
256 log.Info("trying IMAP via ALPN (should fail)", slog.String("host", nonalpnhost))
257 expectTLSFail(t, nonalpnhost, "imap")
258 log.Info("trying SMTP via ALPN (should fail)", slog.String("host", nonalpnhost))
259 expectTLSFail(t, nonalpnhost, "smtp")
260 log.Info("trying HTTP (should succeed)", slog.String("host", nonalpnhost))
261 _, err = http.Get("https://" + nonalpnhost)
262 tcheck(t, err, "get non-alpn url")