mirror of
https://github.com/mjl-/mox.git
synced 2024-12-26 08:23:48 +03:00
d1b87cdb0d
since we are now at go1.21 as minimum.
192 lines
5.9 KiB
Go
192 lines
5.9 KiB
Go
//go:build integration
|
|
|
|
// todo: set up a test for dane, mta-sts, etc.
|
|
|
|
package main
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"fmt"
|
|
"log/slog"
|
|
"net"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/mjl-/mox/dns"
|
|
"github.com/mjl-/mox/imapclient"
|
|
"github.com/mjl-/mox/mlog"
|
|
"github.com/mjl-/mox/mox-"
|
|
"github.com/mjl-/mox/sasl"
|
|
"github.com/mjl-/mox/smtpclient"
|
|
)
|
|
|
|
func tcheck(t *testing.T, err error, errmsg string) {
|
|
if err != nil {
|
|
t.Helper()
|
|
t.Fatalf("%s: %s", errmsg, err)
|
|
}
|
|
}
|
|
|
|
func TestDeliver(t *testing.T) {
|
|
log := mlog.New("integration", nil)
|
|
mlog.Logfmt = true
|
|
|
|
hostname, err := os.Hostname()
|
|
tcheck(t, err, "hostname")
|
|
ourHostname, err := dns.ParseDomain(hostname)
|
|
tcheck(t, err, "parse hostname")
|
|
|
|
// Single update from IMAP IDLE.
|
|
type idleResponse struct {
|
|
untagged imapclient.Untagged
|
|
err error
|
|
}
|
|
|
|
// Deliver submits a message over submissions, and checks with imap idle if the
|
|
// message is received by the destination mail server.
|
|
deliver := func(checkTime bool, dialtls bool, imaphost, imapuser, imappassword string, send func()) {
|
|
t.Helper()
|
|
|
|
// Connect to IMAP, execute IDLE command, which will return on deliver message.
|
|
// TLS certificates work because the container has the CA certificates configured.
|
|
var imapconn net.Conn
|
|
var err error
|
|
if dialtls {
|
|
imapconn, err = tls.Dial("tcp", imaphost, nil)
|
|
} else {
|
|
imapconn, err = net.Dial("tcp", imaphost)
|
|
}
|
|
tcheck(t, err, "dial imap")
|
|
defer imapconn.Close()
|
|
|
|
imapc, err := imapclient.New(imapconn, false)
|
|
tcheck(t, err, "new imapclient")
|
|
|
|
_, _, err = imapc.Login(imapuser, imappassword)
|
|
tcheck(t, err, "imap login")
|
|
|
|
_, _, err = imapc.Select("Inbox")
|
|
tcheck(t, err, "imap select inbox")
|
|
|
|
err = imapc.Commandf("", "idle")
|
|
tcheck(t, err, "write imap idle command")
|
|
|
|
_, _, _, err = imapc.ReadContinuation()
|
|
tcheck(t, err, "read imap continuation")
|
|
|
|
idle := make(chan idleResponse)
|
|
go func() {
|
|
for {
|
|
untagged, err := imapc.ReadUntagged()
|
|
idle <- idleResponse{untagged, err}
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
defer func() {
|
|
err := imapc.Writelinef("done")
|
|
tcheck(t, err, "aborting idle")
|
|
}()
|
|
|
|
t0 := time.Now()
|
|
send()
|
|
|
|
// Wait for notification of delivery.
|
|
select {
|
|
case resp := <-idle:
|
|
tcheck(t, resp.err, "idle notification")
|
|
_, ok := resp.untagged.(imapclient.UntaggedExists)
|
|
if !ok {
|
|
t.Fatalf("got idle %#v, expected untagged exists", resp.untagged)
|
|
}
|
|
if d := time.Since(t0); checkTime && d < 1*time.Second {
|
|
t.Fatalf("delivery took %v, but should have taken at least 1 second, the first-time sender delay", d)
|
|
}
|
|
case <-time.After(30 * time.Second):
|
|
t.Fatalf("timeout after 5s waiting for IMAP IDLE notification of new message, should take about 1 second")
|
|
}
|
|
}
|
|
|
|
submit := func(dialtls bool, mailfrom, password, desthost, rcptto string) {
|
|
var conn net.Conn
|
|
var err error
|
|
if dialtls {
|
|
conn, err = tls.Dial("tcp", desthost, nil)
|
|
} else {
|
|
conn, err = net.Dial("tcp", desthost)
|
|
}
|
|
tcheck(t, err, "dial submission")
|
|
defer conn.Close()
|
|
|
|
msg := fmt.Sprintf(`From: <%s>
|
|
To: <%s>
|
|
Subject: test message
|
|
|
|
This is the message.
|
|
`, mailfrom, rcptto)
|
|
msg = strings.ReplaceAll(msg, "\n", "\r\n")
|
|
auth := func(mechanisms []string, cs *tls.ConnectionState) (sasl.Client, error) {
|
|
return sasl.NewClientPlain(mailfrom, password), nil
|
|
}
|
|
c, err := smtpclient.New(mox.Context, log.Logger, conn, smtpclient.TLSSkip, false, ourHostname, dns.Domain{ASCII: desthost}, smtpclient.Opts{Auth: auth})
|
|
tcheck(t, err, "smtp hello")
|
|
err = c.Deliver(mox.Context, mailfrom, rcptto, int64(len(msg)), strings.NewReader(msg), false, false, false)
|
|
tcheck(t, err, "deliver with smtp")
|
|
err = c.Close()
|
|
tcheck(t, err, "close smtpclient")
|
|
}
|
|
|
|
// Make sure moxacmepebble has a TLS certificate.
|
|
conn, err := tls.Dial("tcp", "moxacmepebble.mox1.example:465", nil)
|
|
tcheck(t, err, "dial submission")
|
|
defer conn.Close()
|
|
|
|
log.Print("submitting email to moxacmepebble, waiting for imap notification at moxmail2")
|
|
t0 := time.Now()
|
|
deliver(true, true, "moxmail2.mox2.example:993", "moxtest2@mox2.example", "accountpass4321", func() {
|
|
submit(true, "moxtest1@mox1.example", "accountpass1234", "moxacmepebble.mox1.example:465", "moxtest2@mox2.example")
|
|
})
|
|
log.Print("success", slog.Duration("duration", time.Since(t0)))
|
|
|
|
log.Print("submitting email to moxmail2, waiting for imap notification at moxacmepebble")
|
|
t0 = time.Now()
|
|
deliver(true, true, "moxacmepebble.mox1.example:993", "moxtest1@mox1.example", "accountpass1234", func() {
|
|
submit(true, "moxtest2@mox2.example", "accountpass4321", "moxmail2.mox2.example:465", "moxtest1@mox1.example")
|
|
})
|
|
log.Print("success", slog.Duration("duration", time.Since(t0)))
|
|
|
|
log.Print("submitting email to postfix, waiting for imap notification at moxacmepebble")
|
|
t0 = time.Now()
|
|
deliver(false, true, "moxacmepebble.mox1.example:993", "moxtest1@mox1.example", "accountpass1234", func() {
|
|
submit(true, "moxtest1@mox1.example", "accountpass1234", "moxacmepebble.mox1.example:465", "root@postfix.example")
|
|
})
|
|
log.Print("success", slog.Duration("duration", time.Since(t0)))
|
|
|
|
log.Print("submitting email to localserve")
|
|
t0 = time.Now()
|
|
deliver(false, false, "localserve.mox1.example:1143", "mox@localhost", "moxmoxmox", func() {
|
|
submit(false, "mox@localhost", "moxmoxmox", "localserve.mox1.example:1587", "moxtest1@mox1.example")
|
|
})
|
|
log.Print("success", slog.Duration("duration", time.Since(t0)))
|
|
|
|
log.Print("submitting email to localserve")
|
|
t0 = time.Now()
|
|
deliver(false, false, "localserve.mox1.example:1143", "mox@localhost", "moxmoxmox", func() {
|
|
cmd := exec.Command("go", "run", ".", "sendmail", "mox@localhost")
|
|
const msg = `Subject: test
|
|
|
|
a message.
|
|
`
|
|
cmd.Stdin = strings.NewReader(msg)
|
|
var out strings.Builder
|
|
cmd.Stdout = &out
|
|
err := cmd.Run()
|
|
log.Print("sendmail", slog.String("output", out.String()))
|
|
tcheck(t, err, "sendmail")
|
|
})
|
|
log.Print("success", slog.Any("duration", time.Since(t0)))
|
|
}
|