mirror of
https://blitiri.com.ar/repos/chasquid
synced 2025-12-29 16:37:19 +00:00
Support submission (directly) over TLS (submissions/smtps/port 465)
This patch adds support for TLS-wrapped submission connections. Instead of clients establishing a connection over plain text and then using STARTTLS to switch over a TLS connection, this new mode allows the clients to connect directly over TLS, like it's done in HTTPS. This is not an official standard yet, but it's reasonably common in practice, and provides some advantages over the traditional submission port. The default port is 465, commonly used for this; chasquid defaults to systemd file descriptor passing as for the other protocols (for now).
This commit is contained in:
@@ -55,12 +55,31 @@ var (
|
||||
|
||||
// Mode for a socket (listening or connection).
|
||||
// We keep them distinct, as policies can differ between them.
|
||||
type SocketMode string
|
||||
type SocketMode struct {
|
||||
// Is this mode submission?
|
||||
IsSubmission bool
|
||||
|
||||
// Is this mode TLS-wrapped? That means that we don't use STARTTLS, the
|
||||
// connection is directly established over TLS (like HTTPS).
|
||||
TLS bool
|
||||
}
|
||||
|
||||
func (mode SocketMode) String() string {
|
||||
s := "SMTP"
|
||||
if mode.IsSubmission {
|
||||
s = "submission"
|
||||
}
|
||||
if mode.TLS {
|
||||
s += "+TLS"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Valid socket modes.
|
||||
const (
|
||||
ModeSMTP SocketMode = "SMTP"
|
||||
ModeSubmission SocketMode = "submission"
|
||||
var (
|
||||
ModeSMTP = SocketMode{IsSubmission: false, TLS: false}
|
||||
ModeSubmission = SocketMode{IsSubmission: true, TLS: false}
|
||||
ModeSubmissionTLS = SocketMode{IsSubmission: true, TLS: true}
|
||||
)
|
||||
|
||||
// Incoming SMTP connection.
|
||||
@@ -137,6 +156,7 @@ func (c *Conn) Handle() {
|
||||
|
||||
c.tr = trace.New("SMTP.Conn", c.conn.RemoteAddr().String())
|
||||
defer c.tr.Finish()
|
||||
c.tr.Debugf("Connected, mode: %s", c.mode)
|
||||
|
||||
c.tc.PrintfLine("220 %s ESMTP chasquid", c.hostname)
|
||||
|
||||
@@ -314,7 +334,7 @@ func (c *Conn) MAIL(params string) (code int, msg string) {
|
||||
if !strings.HasPrefix(strings.ToLower(params), "from:") {
|
||||
return 500, "unknown command"
|
||||
}
|
||||
if c.mode == ModeSubmission && !c.completedAuth {
|
||||
if c.mode.IsSubmission && !c.completedAuth {
|
||||
return 550, "mail to submission port must be authenticated"
|
||||
}
|
||||
|
||||
|
||||
@@ -199,6 +199,11 @@ func (s *Server) ListenAndServe() {
|
||||
}
|
||||
|
||||
func (s *Server) serve(l net.Listener, mode SocketMode) {
|
||||
// If this mode is expected to be TLS-wrapped, make it so.
|
||||
if mode.TLS {
|
||||
l = tls.NewListener(l, s.tlsConfig)
|
||||
}
|
||||
|
||||
for {
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
@@ -213,6 +218,7 @@ func (s *Server) serve(l net.Listener, mode SocketMode) {
|
||||
tc: textproto.NewConn(conn),
|
||||
mode: mode,
|
||||
tlsConfig: s.tlsConfig,
|
||||
onTLS: mode.TLS,
|
||||
userDBs: s.userDBs,
|
||||
aliasesR: s.aliasesR,
|
||||
localDomains: s.localDomains,
|
||||
|
||||
@@ -34,8 +34,9 @@ var (
|
||||
// Server addresses.
|
||||
// We default to internal ones, but may get overridden via flags.
|
||||
// TODO: Don't hard-code the default.
|
||||
smtpAddr = "127.0.0.1:13444"
|
||||
submissionAddr = "127.0.0.1:13999"
|
||||
smtpAddr = "127.0.0.1:13444"
|
||||
submissionAddr = "127.0.0.1:13999"
|
||||
submissionTLSAddr = "127.0.0.1:13777"
|
||||
|
||||
// TLS configuration to use in the clients.
|
||||
// Will contain the generated server certificate as root CA.
|
||||
@@ -46,14 +47,28 @@ var (
|
||||
// === Tests ===
|
||||
//
|
||||
|
||||
func mustDial(tb testing.TB, mode SocketMode, useTLS bool) *smtp.Client {
|
||||
func mustDial(tb testing.TB, mode SocketMode, startTLS bool) *smtp.Client {
|
||||
addr := ""
|
||||
if mode == ModeSMTP {
|
||||
switch mode {
|
||||
case ModeSMTP:
|
||||
addr = smtpAddr
|
||||
} else {
|
||||
case ModeSubmission:
|
||||
addr = submissionAddr
|
||||
case ModeSubmissionTLS:
|
||||
addr = submissionTLSAddr
|
||||
}
|
||||
c, err := smtp.Dial(addr)
|
||||
|
||||
var err error
|
||||
var conn net.Conn
|
||||
if mode.TLS {
|
||||
conn, err = tls.Dial("tcp", addr, tlsConfig)
|
||||
} else {
|
||||
conn, err = net.Dial("tcp", addr)
|
||||
}
|
||||
if err != nil {
|
||||
tb.Fatalf("(net||tls).Dial: %v", err)
|
||||
}
|
||||
c, err := smtp.NewClient(conn, "127.0.0.1")
|
||||
if err != nil {
|
||||
tb.Fatalf("smtp.Dial: %v", err)
|
||||
}
|
||||
@@ -62,7 +77,7 @@ func mustDial(tb testing.TB, mode SocketMode, useTLS bool) *smtp.Client {
|
||||
tb.Fatalf("c.Hello: %v", err)
|
||||
}
|
||||
|
||||
if useTLS {
|
||||
if startTLS {
|
||||
if ok, _ := c.Extension("STARTTLS"); !ok {
|
||||
tb.Fatalf("STARTTLS not advertised in EHLO")
|
||||
}
|
||||
@@ -153,6 +168,14 @@ func TestSubmissionWithoutAuth(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthOnTLS(t *testing.T) {
|
||||
c := mustDial(t, ModeSubmissionTLS, false)
|
||||
defer c.Close()
|
||||
|
||||
auth := smtp.PlainAuth("", "testuser@localhost", "testpasswd", "127.0.0.1")
|
||||
sendEmailWithAuth(t, c, auth)
|
||||
}
|
||||
|
||||
func TestAuthOnSMTP(t *testing.T) {
|
||||
c := mustDial(t, ModeSMTP, true)
|
||||
defer c.Close()
|
||||
@@ -285,6 +308,16 @@ func TestRepeatedStartTLS(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Test that STARTTLS fails on a TLS connection.
|
||||
func TestStartTLSOnTLS(t *testing.T) {
|
||||
c := mustDial(t, ModeSubmissionTLS, false)
|
||||
defer c.Close()
|
||||
|
||||
if err := c.StartTLS(tlsConfig); err == nil {
|
||||
t.Errorf("STARTTLS did not fail as expected")
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// === Benchmarks ===
|
||||
//
|
||||
@@ -440,6 +473,7 @@ func realMain(m *testing.M) int {
|
||||
s.AddCerts(tmpDir+"/cert.pem", tmpDir+"/key.pem")
|
||||
s.AddAddr(smtpAddr, ModeSMTP)
|
||||
s.AddAddr(submissionAddr, ModeSubmission)
|
||||
s.AddAddr(submissionTLSAddr, ModeSubmissionTLS)
|
||||
|
||||
localC := &courier.Procmail{}
|
||||
remoteC := &courier.SMTP{}
|
||||
|
||||
Reference in New Issue
Block a user