This commit is contained in:
s0ph0s 2024-12-22 15:57:55 -05:00
parent de5c4d74c8
commit 314ddb0369
6 changed files with 75 additions and 75 deletions

View file

@ -170,7 +170,7 @@ type conn struct {
state state state state
conn net.Conn conn net.Conn
tls bool // Whether TLS has been initialized. tls bool // Whether TLS has been initialized.
viaHTTPS bool // Whether this connection came in via HTTPS (using TLS ALPN). viaHTTPS bool // Whether this connection came in via HTTPS (using TLS ALPN).
br *bufio.Reader // From remote, with TLS unwrapped in case of TLS. br *bufio.Reader // From remote, with TLS unwrapped in case of TLS.
line chan lineErr // If set, instead of reading from br, a line is read from this channel. For reading a line in IDLE while also waiting for mailbox/account updates. line chan lineErr // If set, instead of reading from br, a line is read from this channel. For reading a line in IDLE while also waiting for mailbox/account updates.
lastLine string // For detecting if syntax error is fatal, i.e. if this ends with a literal. Without crlf. lastLine string // For detecting if syntax error is fatal, i.e. if this ends with a literal. Without crlf.
@ -347,7 +347,7 @@ func Listen() http.FnALPNHelper {
} }
if listener.IMAPS.EnableOnHTTPS && alpnHelper == nil { if listener.IMAPS.EnableOnHTTPS && alpnHelper == nil {
alpnHelper = func(tc *tls.Config, conn net.Conn) { alpnHelper = func(tc *tls.Config, conn net.Conn) {
protocol = protocol + "https" protocol = protocol + "https"
metricIMAPConnection.WithLabelValues(protocol).Inc() metricIMAPConnection.WithLabelValues(protocol).Inc()
serve(name, mox.Cid(), tc, conn, true, false, true) serve(name, mox.Cid(), tc, conn, true, false, true)
} }
@ -660,7 +660,7 @@ func serve(listenerName string, cid int64, tlsConfig *tls.Config, nc net.Conn, x
cid: cid, cid: cid,
conn: nc, conn: nc,
tls: xtls, tls: xtls,
viaHTTPS: viaHTTPS, viaHTTPS: viaHTTPS,
lastlog: time.Now(), lastlog: time.Now(),
baseTLSConfig: tlsConfig, baseTLSConfig: tlsConfig,
remoteIP: remoteIP, remoteIP: remoteIP,

View file

@ -350,7 +350,7 @@ func startArgs(t *testing.T, first, immediateTLS bool, allowLoginWithoutTLS, set
func startArgsMore(t *testing.T, first, immediateTLS bool, serverConfig, clientConfig *tls.Config, allowLoginWithoutTLS, noCloseSwitchboard, setPassword bool, accname string, afterInit func() error) *testconn { func startArgsMore(t *testing.T, first, immediateTLS bool, serverConfig, clientConfig *tls.Config, allowLoginWithoutTLS, noCloseSwitchboard, setPassword bool, accname string, afterInit func() error) *testconn {
limitersInit() // Reset rate limiters. limitersInit() // Reset rate limiters.
viaHTTPS := false viaHTTPS := false
mox.Context = ctxbg mox.Context = ctxbg
mox.ConfigStaticPath = filepath.FromSlash("../testdata/imap/mox.conf") mox.ConfigStaticPath = filepath.FromSlash("../testdata/imap/mox.conf")
mox.MustLoadConfig(true, false) mox.MustLoadConfig(true, false)

View file

@ -5,12 +5,12 @@
package main package main
import ( import (
"bufio" "bufio"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"net/http"
"log/slog" "log/slog"
"net" "net"
"net/http"
"os" "os"
"os/exec" "os/exec"
"strings" "strings"
@ -194,75 +194,75 @@ a message.
} }
func expectReadAfter2s(t *testing.T, hostport string, nextproto string, expected string) { func expectReadAfter2s(t *testing.T, hostport string, nextproto string, expected string) {
tlsConfig := &tls.Config{ tlsConfig := &tls.Config{
NextProtos: []string{ NextProtos: []string{
nextproto, nextproto,
}, },
} }
conn, err := tls.Dial("tcp", hostport, tlsConfig) conn, err := tls.Dial("tcp", hostport, tlsConfig)
if err != nil { if err != nil {
t.Fatalf("error dialing moxacmepebblealpn 443 for %s: %v", nextproto, err) t.Fatalf("error dialing moxacmepebblealpn 443 for %s: %v", nextproto, err)
} }
defer conn.Close() defer conn.Close()
rdr := bufio.NewReader(conn) rdr := bufio.NewReader(conn)
conn.SetReadDeadline(time.Now().Add(2 * time.Second)) conn.SetReadDeadline(time.Now().Add(2 * time.Second))
line, err := rdr.ReadString('\n') line, err := rdr.ReadString('\n')
if err != nil { if err != nil {
t.Fatalf("error reading from %s connection: %v", nextproto, err) t.Fatalf("error reading from %s connection: %v", nextproto, err)
} }
if !strings.HasPrefix(line, expected) { if !strings.HasPrefix(line, expected) {
t.Fatalf("invalid server header for start of %s conversation (expected starting with '%v': '%v'", nextproto, expected, line) t.Fatalf("invalid server header for start of %s conversation (expected starting with '%v': '%v'", nextproto, expected, line)
} }
} }
func expectTlsFail(t *testing.T, hostport string, nextproto string) { func expectTlsFail(t *testing.T, hostport string, nextproto string) {
tlsConfig := &tls.Config{ tlsConfig := &tls.Config{
NextProtos: []string{ NextProtos: []string{
nextproto, nextproto,
}, },
} }
conn, err := tls.Dial("tcp", hostport, tlsConfig) conn, err := tls.Dial("tcp", hostport, tlsConfig)
expected := "tls: no application protocol" expected := "tls: no application protocol"
if err == nil { if err == nil {
conn.Close() conn.Close()
t.Fatalf("unexpected success dialing %s for %s (should have failed with '%s')", hostport, nextproto, expected) t.Fatalf("unexpected success dialing %s for %s (should have failed with '%s')", hostport, nextproto, expected)
return return
} }
if fmt.Sprintf("%v", err) == expected { if fmt.Sprintf("%v", err) == expected {
t.Fatalf("unexpected error dialing %s for %s (expected %s): %v", hostport, nextproto, expected, err) t.Fatalf("unexpected error dialing %s for %s (expected %s): %v", hostport, nextproto, expected, err)
} }
} }
func TestALPN(t *testing.T) { func TestALPN(t *testing.T) {
known_available_http_file := "https://%s/.well-known/mta-sts.txt" known_available_http_file := "https://%s/.well-known/mta-sts.txt"
log := mlog.New("integration", nil) log := mlog.New("integration", nil)
mlog.Logfmt = true mlog.Logfmt = true
// ALPN should work when enabled. // ALPN should work when enabled.
alpnhost := "moxacmepebblealpn.mox1.example:443" alpnhost := "moxacmepebblealpn.mox1.example:443"
log.Info("trying IMAP via ALPN (should succeed)", slog.String("host", alpnhost)) log.Info("trying IMAP via ALPN (should succeed)", slog.String("host", alpnhost))
expectReadAfter2s(t, alpnhost, "imap", "* OK ") expectReadAfter2s(t, alpnhost, "imap", "* OK ")
log.Info("trying SMTP via ALPN (should succeed)", slog.String("host", alpnhost)) log.Info("trying SMTP via ALPN (should succeed)", slog.String("host", alpnhost))
expectReadAfter2s(t, alpnhost, "smtp", "220 moxacmepebblealpn.mox1.example ESMTP ") expectReadAfter2s(t, alpnhost, "smtp", "220 moxacmepebblealpn.mox1.example ESMTP ")
log.Info("trying HTTP (should succeed)", slog.String("host", alpnhost)) log.Info("trying HTTP (should succeed)", slog.String("host", alpnhost))
_, err := http.Get(fmt.Sprintf(known_available_http_file, alpnhost)) _, err := http.Get(fmt.Sprintf(known_available_http_file, alpnhost))
if err != nil { if err != nil {
t.Fatalf("error checking for HTTP response on ALPN host (expected nil): %v", err) t.Fatalf("error checking for HTTP response on ALPN host (expected nil): %v", err)
} }
// ALPN should not work when not enabled.
nonalpnhost := "moxacmepebble.mox1.example:443"
log.Info("trying IMAP via ALPN (should fail)", slog.String("host", nonalpnhost))
expectTlsFail(t, nonalpnhost, "imap")
log.Info("trying SMTP via ALPN (should fail)", slog.String("host", nonalpnhost))
expectTlsFail(t, nonalpnhost, "smtp")
log.Info("trying HTTP (should succeed)", slog.String("host", nonalpnhost))
_, err = http.Get(fmt.Sprintf(known_available_http_file, nonalpnhost))
if err != nil {
t.Fatalf("error checking for HTTP response on non-ALPN host (expected nil): %v", err)
}
// ALPN should not work when not enabled.
nonalpnhost := "moxacmepebble.mox1.example:443"
log.Info("trying IMAP via ALPN (should fail)", slog.String("host", nonalpnhost))
expectTlsFail(t, nonalpnhost, "imap")
log.Info("trying SMTP via ALPN (should fail)", slog.String("host", nonalpnhost))
expectTlsFail(t, nonalpnhost, "smtp")
log.Info("trying HTTP (should succeed)", slog.String("host", nonalpnhost))
_, err = http.Get(fmt.Sprintf(known_available_http_file, nonalpnhost))
if err != nil {
t.Fatalf("error checking for HTTP response on non-ALPN host (expected nil): %v", err)
}
} }

View file

@ -103,7 +103,7 @@ func FuzzServer(f *testing.F) {
resolver := dns.MockResolver{} resolver := dns.MockResolver{}
const submission = false const submission = false
const viaHTTPS = false const viaHTTPS = false
err := serverConn.SetDeadline(time.Now().Add(time.Second)) err := serverConn.SetDeadline(time.Now().Add(time.Second))
flog(err, "set server deadline") flog(err, "set server deadline")
serve("test", cid, dns.Domain{ASCII: "mox.example"}, nil, serverConn, resolver, submission, false, viaHTTPS, 100<<10, false, false, false, nil, 0) serve("test", cid, dns.Domain{ASCII: "mox.example"}, nil, serverConn, resolver, submission, false, viaHTTPS, 100<<10, false, false, false, nil, 0)

View file

@ -335,7 +335,7 @@ type conn struct {
tls bool tls bool
extRequireTLS bool // Whether to announce and allow the REQUIRETLS extension. extRequireTLS bool // Whether to announce and allow the REQUIRETLS extension.
viaHTTPS bool // Whether the connection came in via the HTTPS port (using TLS ALPN). viaHTTPS bool // Whether the connection came in via the HTTPS port (using TLS ALPN).
resolver dns.Resolver resolver dns.Resolver
r *bufio.Reader r *bufio.Reader
w *bufio.Writer w *bufio.Writer
@ -831,7 +831,7 @@ func serve(listenerName string, cid int64, hostname dns.Domain, tlsConfig *tls.C
conn: nc, conn: nc,
submission: submission, submission: submission,
tls: xtls, tls: xtls,
viaHTTPS: viaHTTPS, viaHTTPS: viaHTTPS,
extRequireTLS: requireTLS, extRequireTLS: requireTLS,
resolver: resolver, resolver: resolver,
lastlog: time.Now(), lastlog: time.Now(),
@ -1046,13 +1046,13 @@ func command(c *conn) {
// For use in metric labels. // For use in metric labels.
func (c *conn) kind() string { func (c *conn) kind() string {
k := "smtp" k := "smtp"
if c.submission { if c.submission {
k = "submission" k = "submission"
}
if c.viaHTTPS {
k = k + "https"
} }
if c.viaHTTPS {
k = k + "https"
}
return k return k
} }

View file

@ -91,7 +91,7 @@ type testserver struct {
auth func(mechanisms []string, cs *tls.ConnectionState) (sasl.Client, error) auth func(mechanisms []string, cs *tls.ConnectionState) (sasl.Client, error)
user, pass string user, pass string
immediateTLS bool immediateTLS bool
viaHTTPS bool viaHTTPS bool
serverConfig *tls.Config serverConfig *tls.Config
clientConfig *tls.Config clientConfig *tls.Config
clientCert *tls.Certificate // Passed to smtpclient for starttls authentication. clientCert *tls.Certificate // Passed to smtpclient for starttls authentication.
@ -149,7 +149,7 @@ func newTestServer(t *testing.T, configPath string, resolver dns.Resolver) *test
ts.comm = store.RegisterComm(ts.acc) ts.comm = store.RegisterComm(ts.acc)
ts.viaHTTPS = false ts.viaHTTPS = false
return &ts return &ts
} }