git » chasquid » commit 213bc63

Support submission (directly) over TLS (submissions/smtps/port 465)

author Alberto Bertogli
2017-04-01 11:47:46 UTC
committer Alberto Bertogli
2017-04-10 18:59:04 UTC
parent c4255933bdededc91a2080d3952fbd9fceabcd2d

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).

chasquid.go +2 -0
etc/systemd/system/chasquid-submission_tls.socket +11 -0
etc/systemd/system/chasquid.service +3 -1
internal/config/config.go +3 -0
internal/config/config.pb.go +34 -28
internal/config/config.proto +12 -7
internal/smtpsrv/conn.go +25 -5
internal/smtpsrv/server.go +6 -0
internal/smtpsrv/server_test.go +41 -7
test/t-01-simple_local/config/chasquid.conf +1 -0
test/t-01-simple_local/msmtprc +4 -0
test/t-01-simple_local/run.sh +5 -0
test/t-02-exim/config/chasquid.conf +1 -0
test/t-03-queue_persistency/config/chasquid.conf +1 -0
test/t-04-aliases/config/chasquid.conf +1 -0
test/t-05-null_address/config/chasquid.conf +1 -0
test/t-06-idna/A/chasquid.conf +1 -0
test/t-06-idna/B/chasquid.conf +1 -0
test/t-07-smtputf8/config/chasquid.conf +1 -0
test/t-09-loop/A/chasquid.conf +1 -0
test/t-09-loop/B/chasquid.conf +1 -0
test/t-10-hooks/config/chasquid.conf +1 -0

diff --git a/chasquid.go b/chasquid.go
index 640850d..e9a3b22 100644
--- a/chasquid.go
+++ b/chasquid.go
@@ -161,6 +161,8 @@ func main() {
 		systemdLs["smtp"], smtpsrv.ModeSMTP)
 	loadAddresses(s, conf.SubmissionAddress,
 		systemdLs["submission"], smtpsrv.ModeSubmission)
+	loadAddresses(s, conf.SubmissionOverTlsAddress,
+		systemdLs["submission_tls"], smtpsrv.ModeSubmissionTLS)
 
 	s.ListenAndServe()
 }
diff --git a/etc/systemd/system/chasquid-submission_tls.socket b/etc/systemd/system/chasquid-submission_tls.socket
new file mode 100644
index 0000000..3d8e0c9
--- /dev/null
+++ b/etc/systemd/system/chasquid-submission_tls.socket
@@ -0,0 +1,11 @@
+[Unit]
+Description=chasquid mail daemon (submission over TLS sockets)
+
+[Socket]
+ListenStream=465
+FileDescriptorName=submission_tls
+Service=chasquid.service
+
+[Install]
+WantedBy=chasquid.target
+
diff --git a/etc/systemd/system/chasquid.service b/etc/systemd/system/chasquid.service
index b44ade2..4c98ea3 100644
--- a/etc/systemd/system/chasquid.service
+++ b/etc/systemd/system/chasquid.service
@@ -1,6 +1,8 @@
 [Unit]
 Description=chasquid mail daemon (service)
-Requires=chasquid-smtp.socket chasquid-submission.socket
+Requires=chasquid-smtp.socket \
+	chasquid-submission.socket \
+	chasquid-submission_tls.socket
 
 [Service]
 ExecStart=/usr/local/bin/chasquid \
diff --git a/internal/config/config.go b/internal/config/config.go
index ef00a61..e6f261e 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -50,6 +50,9 @@ func Load(path string) (*Config, error) {
 	if len(c.SubmissionAddress) == 0 {
 		c.SubmissionAddress = append(c.SubmissionAddress, "systemd")
 	}
+	if len(c.SubmissionOverTlsAddress) == 0 {
+		c.SubmissionOverTlsAddress = append(c.SubmissionOverTlsAddress, "systemd")
+	}
 
 	if c.MailDeliveryAgentBin == "" {
 		c.MailDeliveryAgentBin = "maildrop"
diff --git a/internal/config/config.pb.go b/internal/config/config.pb.go
index 5e9c3af..ef42025 100644
--- a/internal/config/config.pb.go
+++ b/internal/config/config.pb.go
@@ -44,16 +44,20 @@ type Config struct {
 	// Default: "systemd", which means systemd passes sockets to us.
 	// systemd sockets must be named with "FileDescriptorName=submission".
 	SubmissionAddress []string `protobuf:"bytes,4,rep,name=submission_address,json=submissionAddress" json:"submission_address,omitempty"`
+	// Addresses to listen on for submission-over-TLS (usually port 465).
+	// Default: "systemd", which means systemd passes sockets to us.
+	// systemd sockets must be named with "FileDescriptorName=submission_tls".
+	SubmissionOverTlsAddress []string `protobuf:"bytes,5,rep,name=submission_over_tls_address,json=submissionOverTlsAddress" json:"submission_over_tls_address,omitempty"`
 	// Address for the monitoring http server.
 	// Do NOT expose this to the public internet.
 	// Default: no monitoring http server.
-	MonitoringAddress string `protobuf:"bytes,5,opt,name=monitoring_address,json=monitoringAddress" json:"monitoring_address,omitempty"`
+	MonitoringAddress string `protobuf:"bytes,6,opt,name=monitoring_address,json=monitoringAddress" json:"monitoring_address,omitempty"`
 	// Mail delivery agent (MDA, also known as LDA) to use.
 	// This should point to the binary to use to deliver email to local users.
 	// The content of the email will be passed via stdin.
 	// If it exits unsuccessfully, we assume the mail was not delivered.
 	// Default: "maildrop".
-	MailDeliveryAgentBin string `protobuf:"bytes,6,opt,name=mail_delivery_agent_bin,json=mailDeliveryAgentBin" json:"mail_delivery_agent_bin,omitempty"`
+	MailDeliveryAgentBin string `protobuf:"bytes,7,opt,name=mail_delivery_agent_bin,json=mailDeliveryAgentBin" json:"mail_delivery_agent_bin,omitempty"`
 	// Command line arguments for the mail delivery agent. One per argument.
 	// Some replacements will be done.
 	// On an email sent from marsnik@mars to venera@venus:
@@ -66,26 +70,26 @@ type Config struct {
 	//
 	// Default: "-f", "%from%", "-d", "%to_user%"  (adequate for procmail
 	// and maildrop).
-	MailDeliveryAgentArgs []string `protobuf:"bytes,7,rep,name=mail_delivery_agent_args,json=mailDeliveryAgentArgs" json:"mail_delivery_agent_args,omitempty"`
+	MailDeliveryAgentArgs []string `protobuf:"bytes,8,rep,name=mail_delivery_agent_args,json=mailDeliveryAgentArgs" json:"mail_delivery_agent_args,omitempty"`
 	// Directory where we store our persistent data.
 	// Default: "/var/lib/chasquid"
-	DataDir string `protobuf:"bytes,8,opt,name=data_dir,json=dataDir" json:"data_dir,omitempty"`
+	DataDir string `protobuf:"bytes,9,opt,name=data_dir,json=dataDir" json:"data_dir,omitempty"`
 	// Suffix separator, to perform suffix removal of local users.
 	// For example, if you set this to "-+", email to local user
 	// "user-blah" and "user+blah" will be delivered to "user".
 	// Including "+" is strongly encouraged, as it is assumed for email
 	// forwarding.
 	// Default: "+".
-	SuffixSeparators string `protobuf:"bytes,9,opt,name=suffix_separators,json=suffixSeparators" json:"suffix_separators,omitempty"`
+	SuffixSeparators string `protobuf:"bytes,10,opt,name=suffix_separators,json=suffixSeparators" json:"suffix_separators,omitempty"`
 	// Characters to drop from the user part on local emails.
 	// For example, if you set this to "._", email to local user
 	// "u.se_r" will be delivered to "user".
 	// Default: ".".
-	DropCharacters string `protobuf:"bytes,10,opt,name=drop_characters,json=dropCharacters" json:"drop_characters,omitempty"`
+	DropCharacters string `protobuf:"bytes,11,opt,name=drop_characters,json=dropCharacters" json:"drop_characters,omitempty"`
 	// Path where to write the mail log to.
 	// If "<syslog>", log using the syslog (at MAIL|INFO priority).
 	// Default: <syslog>
-	MailLogPath string `protobuf:"bytes,11,opt,name=mail_log_path,json=mailLogPath" json:"mail_log_path,omitempty"`
+	MailLogPath string `protobuf:"bytes,12,opt,name=mail_log_path,json=mailLogPath" json:"mail_log_path,omitempty"`
 }
 
 func (m *Config) Reset()                    { *m = Config{} }
@@ -100,25 +104,27 @@ func init() {
 func init() { proto.RegisterFile("config.proto", fileDescriptor0) }
 
 var fileDescriptor0 = []byte{
-	// 320 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0x91, 0xcd, 0x4e, 0x02, 0x31,
-	0x10, 0x80, 0x83, 0xab, 0xfc, 0x14, 0x50, 0x68, 0x34, 0x56, 0x4f, 0xc8, 0x05, 0x12, 0xa3, 0x17,
-	0x63, 0x3c, 0x23, 0x1c, 0x35, 0x31, 0xf0, 0x00, 0xcd, 0x2c, 0x5b, 0xba, 0x93, 0xd0, 0x76, 0xd3,
-	0x29, 0x06, 0x79, 0x4d, 0x5f, 0xc8, 0x6c, 0xd1, 0xc5, 0x44, 0x8f, 0xf3, 0x7d, 0xdf, 0x64, 0x27,
-	0x5b, 0xd6, 0x59, 0x3a, 0xbb, 0x42, 0x7d, 0x5f, 0x78, 0x17, 0xdc, 0xf0, 0x33, 0x61, 0xf5, 0x69,
-	0x04, 0xfc, 0x9a, 0x35, 0x73, 0x47, 0xc1, 0x82, 0x51, 0xa2, 0x36, 0xa8, 0x8d, 0x5b, 0xf3, 0x6a,
-	0xe6, 0x23, 0xd6, 0x33, 0xb0, 0x95, 0x19, 0x04, 0x90, 0x84, 0x3b, 0x25, 0x4d, 0x2a, 0x8e, 0x06,
-	0xb5, 0x71, 0x32, 0xef, 0x1a, 0xd8, 0xce, 0x20, 0xc0, 0x02, 0x77, 0xea, 0x35, 0xe5, 0x37, 0xac,
-	0x43, 0x26, 0x14, 0x12, 0xb2, 0xcc, 0x2b, 0x22, 0x91, 0x0c, 0x92, 0x71, 0x6b, 0xde, 0x2e, 0xd9,
-	0x64, 0x8f, 0xf8, 0x1d, 0xe3, 0xb4, 0x49, 0x0d, 0x12, 0xa1, 0xb3, 0x55, 0x78, 0x1c, 0xc3, 0xfe,
-	0xc1, 0xfc, 0xca, 0x8d, 0xb3, 0x18, 0x9c, 0x47, 0xab, 0xab, 0xfc, 0x24, 0x1e, 0xd8, 0x3f, 0x98,
-	0x9f, 0xfc, 0x91, 0x5d, 0x1a, 0xc0, 0xb5, 0xcc, 0xd4, 0x1a, 0xdf, 0x95, 0xff, 0x90, 0xa0, 0x95,
-	0x0d, 0x32, 0x45, 0x2b, 0xea, 0x71, 0xe7, 0xbc, 0xd4, 0xb3, 0x6f, 0x3b, 0x29, 0xe5, 0x33, 0x5a,
-	0xfe, 0xc4, 0xc4, 0x7f, 0x6b, 0xe0, 0x35, 0x89, 0x46, 0x3c, 0xed, 0xe2, 0xcf, 0xde, 0xc4, 0x6b,
-	0xe2, 0x57, 0xac, 0x19, 0xff, 0x4a, 0x86, 0x5e, 0x34, 0xe3, 0x07, 0x1a, 0xe5, 0x3c, 0x43, 0xcf,
-	0x6f, 0x59, 0x9f, 0x36, 0xab, 0x15, 0x6e, 0x25, 0xa9, 0x02, 0x3c, 0x04, 0xe7, 0x49, 0xb4, 0x62,
-	0xd3, 0xdb, 0x8b, 0x45, 0xc5, 0xf9, 0x88, 0x9d, 0x65, 0xde, 0x15, 0x72, 0x99, 0x83, 0x87, 0x65,
-	0x50, 0x9e, 0x04, 0x8b, 0xe9, 0x69, 0x89, 0xa7, 0x15, 0xe5, 0x43, 0xd6, 0x8d, 0x97, 0xae, 0x9d,
-	0x96, 0x05, 0x84, 0x5c, 0xb4, 0x63, 0xd6, 0x2e, 0xe1, 0x8b, 0xd3, 0x6f, 0x10, 0xf2, 0xb4, 0x1e,
-	0x1f, 0xf7, 0xe1, 0x2b, 0x00, 0x00, 0xff, 0xff, 0x04, 0x0f, 0xa4, 0xff, 0xec, 0x01, 0x00, 0x00,
+	// 351 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0x92, 0x4d, 0x4f, 0x22, 0x41,
+	0x10, 0x86, 0xc3, 0xc2, 0xf2, 0xd1, 0xc0, 0x2e, 0x74, 0x76, 0xb3, 0xbd, 0x7a, 0x41, 0x2e, 0x90,
+	0x18, 0xbd, 0x18, 0xe3, 0xc9, 0x03, 0xc2, 0x51, 0xa3, 0x01, 0xef, 0x9d, 0x1a, 0xa6, 0x99, 0xe9,
+	0x64, 0xba, 0x7b, 0x52, 0xd5, 0x10, 0xe4, 0x67, 0xf8, 0x8b, 0xcd, 0x34, 0x32, 0x90, 0xe8, 0xb1,
+	0x9e, 0xf7, 0x79, 0x53, 0x35, 0x93, 0x66, 0x9d, 0xa5, 0xb3, 0x2b, 0x9d, 0x5c, 0xe7, 0xe8, 0xbc,
+	0x1b, 0xbe, 0xd7, 0x58, 0x7d, 0x1a, 0x00, 0x3f, 0x63, 0xcd, 0xd4, 0x91, 0xb7, 0x60, 0x94, 0xa8,
+	0x0c, 0x2a, 0xe3, 0xd6, 0xbc, 0x9c, 0xf9, 0x88, 0xf5, 0x0c, 0x6c, 0x65, 0x0c, 0x1e, 0x24, 0xe9,
+	0x9d, 0x92, 0x26, 0x12, 0x3f, 0x06, 0x95, 0x71, 0x75, 0xde, 0x35, 0xb0, 0x9d, 0x81, 0x87, 0x85,
+	0xde, 0xa9, 0xa7, 0x88, 0x5f, 0xb0, 0x0e, 0x19, 0x9f, 0x4b, 0x88, 0x63, 0x54, 0x44, 0xa2, 0x3a,
+	0xa8, 0x8e, 0x5b, 0xf3, 0x76, 0xc1, 0x26, 0x7b, 0xc4, 0xaf, 0x18, 0xa7, 0x75, 0x64, 0x34, 0x91,
+	0x76, 0xb6, 0x14, 0x6b, 0x41, 0xec, 0x1f, 0x93, 0x83, 0x7e, 0xcf, 0xce, 0x4f, 0x74, 0xb7, 0x51,
+	0x28, 0x7d, 0x46, 0x65, 0xef, 0x67, 0xe8, 0x89, 0xa3, 0xf2, 0xbc, 0x51, 0xf8, 0x9a, 0xd1, 0xc9,
+	0x36, 0xe3, 0xac, 0xf6, 0x0e, 0xb5, 0x4d, 0xca, 0x56, 0x3d, 0x7c, 0x5f, 0xff, 0x98, 0x1c, 0xf4,
+	0x5b, 0xf6, 0xcf, 0x80, 0xce, 0x64, 0xac, 0x32, 0xbd, 0x51, 0xf8, 0x26, 0x21, 0x51, 0xd6, 0xcb,
+	0x48, 0x5b, 0xd1, 0x08, 0x9d, 0x3f, 0x45, 0x3c, 0xfb, 0x4c, 0x27, 0x45, 0xf8, 0xa0, 0x2d, 0xbf,
+	0x63, 0xe2, 0xbb, 0x1a, 0x60, 0x42, 0xa2, 0x19, 0x2e, 0xfc, 0xfb, 0xa5, 0x37, 0xc1, 0x84, 0xf8,
+	0x7f, 0xd6, 0x0c, 0x3f, 0x35, 0xd6, 0x28, 0x5a, 0x61, 0x41, 0xa3, 0x98, 0x67, 0x1a, 0xf9, 0x25,
+	0xeb, 0xd3, 0x7a, 0xb5, 0xd2, 0x5b, 0x49, 0x2a, 0x07, 0x04, 0xef, 0x90, 0x04, 0x0b, 0x4e, 0x6f,
+	0x1f, 0x2c, 0x4a, 0xce, 0x47, 0xec, 0x77, 0x8c, 0x2e, 0x97, 0xcb, 0x14, 0x10, 0x96, 0x5e, 0x21,
+	0x89, 0x76, 0x50, 0x7f, 0x15, 0x78, 0x5a, 0x52, 0x3e, 0x64, 0xdd, 0x70, 0x69, 0xe6, 0x12, 0x99,
+	0x83, 0x4f, 0x45, 0x27, 0x68, 0xed, 0x02, 0x3e, 0xba, 0xe4, 0x05, 0x7c, 0x1a, 0xd5, 0xc3, 0xdb,
+	0xb8, 0xf9, 0x08, 0x00, 0x00, 0xff, 0xff, 0xfb, 0x6f, 0x6c, 0xff, 0x2b, 0x02, 0x00, 0x00,
 }
diff --git a/internal/config/config.proto b/internal/config/config.proto
index a119c1e..298e496 100644
--- a/internal/config/config.proto
+++ b/internal/config/config.proto
@@ -21,17 +21,22 @@ message Config {
 	// systemd sockets must be named with "FileDescriptorName=submission".
 	repeated string submission_address = 4;
 
+	// Addresses to listen on for submission-over-TLS (usually port 465).
+	// Default: "systemd", which means systemd passes sockets to us.
+	// systemd sockets must be named with "FileDescriptorName=submission_tls".
+	repeated string submission_over_tls_address = 5;
+
 	// Address for the monitoring http server.
 	// Do NOT expose this to the public internet.
 	// Default: no monitoring http server.
-	string monitoring_address = 5;
+	string monitoring_address = 6;
 
 	// Mail delivery agent (MDA, also known as LDA) to use.
 	// This should point to the binary to use to deliver email to local users.
 	// The content of the email will be passed via stdin.
 	// If it exits unsuccessfully, we assume the mail was not delivered.
 	// Default: "maildrop".
-	string mail_delivery_agent_bin = 6;
+	string mail_delivery_agent_bin = 7;
 
 	// Command line arguments for the mail delivery agent. One per argument.
 	// Some replacements will be done.
@@ -45,11 +50,11 @@ message Config {
 	//
 	// Default: "-f", "%from%", "-d", "%to_user%"  (adequate for procmail
 	// and maildrop).
-	repeated string mail_delivery_agent_args = 7;
+	repeated string mail_delivery_agent_args = 8;
 
 	// Directory where we store our persistent data.
 	// Default: "/var/lib/chasquid"
-	string data_dir = 8;
+	string data_dir = 9;
 
 	// Suffix separator, to perform suffix removal of local users.
 	// For example, if you set this to "-+", email to local user
@@ -57,17 +62,17 @@ message Config {
 	// Including "+" is strongly encouraged, as it is assumed for email
 	// forwarding.
 	// Default: "+".
-	string suffix_separators = 9;
+	string suffix_separators = 10;
 
 	// Characters to drop from the user part on local emails.
 	// For example, if you set this to "._", email to local user
 	// "u.se_r" will be delivered to "user".
 	// Default: ".".
-	string drop_characters = 10;
+	string drop_characters = 11;
 
 	// Path where to write the mail log to.
 	// If "<syslog>", log using the syslog (at MAIL|INFO priority).
 	// Default: <syslog>
-	string mail_log_path = 11;
+	string mail_log_path = 12;
 }
 
diff --git a/internal/smtpsrv/conn.go b/internal/smtpsrv/conn.go
index 4950322..c347cea 100644
--- a/internal/smtpsrv/conn.go
+++ b/internal/smtpsrv/conn.go
@@ -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"
 	}
 
diff --git a/internal/smtpsrv/server.go b/internal/smtpsrv/server.go
index 3e9cb92..025eddd 100644
--- a/internal/smtpsrv/server.go
+++ b/internal/smtpsrv/server.go
@@ -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,
diff --git a/internal/smtpsrv/server_test.go b/internal/smtpsrv/server_test.go
index 08d4512..d37cdd8 100644
--- a/internal/smtpsrv/server_test.go
+++ b/internal/smtpsrv/server_test.go
@@ -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
+	}
+
+	var err error
+	var conn net.Conn
+	if mode.TLS {
+		conn, err = tls.Dial("tcp", addr, tlsConfig)
+	} else {
+		conn, err = net.Dial("tcp", addr)
 	}
-	c, err := smtp.Dial(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{}
diff --git a/test/t-01-simple_local/config/chasquid.conf b/test/t-01-simple_local/config/chasquid.conf
index 2da8942..cf76e8a 100644
--- a/test/t-01-simple_local/config/chasquid.conf
+++ b/test/t-01-simple_local/config/chasquid.conf
@@ -1,5 +1,6 @@
 smtp_address: ":1025"
 submission_address: ":1587"
+submission_over_tls_address: ":1465"
 monitoring_address: ":1099"
 
 mail_delivery_agent_bin: "test-mda"
diff --git a/test/t-01-simple_local/msmtprc b/test/t-01-simple_local/msmtprc
index acb28d1..eed8751 100644
--- a/test/t-01-simple_local/msmtprc
+++ b/test/t-01-simple_local/msmtprc
@@ -15,6 +15,10 @@ password secretpassword
 account smtpport : default
 port 1025
 
+account subm_tls : default
+port 1465
+tls_starttls off
+
 account baduser : default
 user unknownuser@testserver
 password secretpassword
diff --git a/test/t-01-simple_local/run.sh b/test/t-01-simple_local/run.sh
index 678349a..a4e30f0 100755
--- a/test/t-01-simple_local/run.sh
+++ b/test/t-01-simple_local/run.sh
@@ -31,6 +31,11 @@ if ! run_msmtp -a smtpport someone@testserver < content 2> /dev/null; then
 	fail "failed auth on the SMTP port"
 fi
 
+# Check deliver over the submission-over-TLS port.
+if ! run_msmtp -a subm_tls someone@testserver < content 2> /dev/null; then
+	fail "failed submission over TLS"
+fi
+
 if run_msmtp nobody@testserver < content 2> /dev/null; then
 	fail "successfuly sent an email to a non-existent user"
 fi
diff --git a/test/t-02-exim/config/chasquid.conf b/test/t-02-exim/config/chasquid.conf
index 2da8942..cf76e8a 100644
--- a/test/t-02-exim/config/chasquid.conf
+++ b/test/t-02-exim/config/chasquid.conf
@@ -1,5 +1,6 @@
 smtp_address: ":1025"
 submission_address: ":1587"
+submission_over_tls_address: ":1465"
 monitoring_address: ":1099"
 
 mail_delivery_agent_bin: "test-mda"
diff --git a/test/t-03-queue_persistency/config/chasquid.conf b/test/t-03-queue_persistency/config/chasquid.conf
index 2da8942..cf76e8a 100644
--- a/test/t-03-queue_persistency/config/chasquid.conf
+++ b/test/t-03-queue_persistency/config/chasquid.conf
@@ -1,5 +1,6 @@
 smtp_address: ":1025"
 submission_address: ":1587"
+submission_over_tls_address: ":1465"
 monitoring_address: ":1099"
 
 mail_delivery_agent_bin: "test-mda"
diff --git a/test/t-04-aliases/config/chasquid.conf b/test/t-04-aliases/config/chasquid.conf
index bb91f39..bc5b35c 100644
--- a/test/t-04-aliases/config/chasquid.conf
+++ b/test/t-04-aliases/config/chasquid.conf
@@ -1,5 +1,6 @@
 smtp_address: ":1025"
 submission_address: ":1587"
+submission_over_tls_address: ":1465"
 monitoring_address: ":1099"
 
 mail_delivery_agent_bin: "test-mda"
diff --git a/test/t-05-null_address/config/chasquid.conf b/test/t-05-null_address/config/chasquid.conf
index b3011f9..9a35641 100644
--- a/test/t-05-null_address/config/chasquid.conf
+++ b/test/t-05-null_address/config/chasquid.conf
@@ -2,6 +2,7 @@ hostname: "testserver"
 
 smtp_address: ":1025"
 submission_address: ":1587"
+submission_over_tls_address: ":1465"
 monitoring_address: ":1099"
 
 mail_delivery_agent_bin: "test-mda"
diff --git a/test/t-06-idna/A/chasquid.conf b/test/t-06-idna/A/chasquid.conf
index 6f0db35..5b54c64 100644
--- a/test/t-06-idna/A/chasquid.conf
+++ b/test/t-06-idna/A/chasquid.conf
@@ -1,5 +1,6 @@
 smtp_address: ":1025"
 submission_address: ":1587"
+submission_over_tls_address: ":1465"
 monitoring_address: ":1099"
 
 mail_delivery_agent_bin: "test-mda"
diff --git a/test/t-06-idna/B/chasquid.conf b/test/t-06-idna/B/chasquid.conf
index ef7a0da..e572536 100644
--- a/test/t-06-idna/B/chasquid.conf
+++ b/test/t-06-idna/B/chasquid.conf
@@ -1,5 +1,6 @@
 smtp_address: ":2025"
 submission_address: ":2587"
+submission_over_tls_address: ":2465"
 monitoring_address: ":2099"
 
 mail_delivery_agent_bin: "test-mda"
diff --git a/test/t-07-smtputf8/config/chasquid.conf b/test/t-07-smtputf8/config/chasquid.conf
index 2da8942..cf76e8a 100644
--- a/test/t-07-smtputf8/config/chasquid.conf
+++ b/test/t-07-smtputf8/config/chasquid.conf
@@ -1,5 +1,6 @@
 smtp_address: ":1025"
 submission_address: ":1587"
+submission_over_tls_address: ":1465"
 monitoring_address: ":1099"
 
 mail_delivery_agent_bin: "test-mda"
diff --git a/test/t-09-loop/A/chasquid.conf b/test/t-09-loop/A/chasquid.conf
index 6f0db35..5b54c64 100644
--- a/test/t-09-loop/A/chasquid.conf
+++ b/test/t-09-loop/A/chasquid.conf
@@ -1,5 +1,6 @@
 smtp_address: ":1025"
 submission_address: ":1587"
+submission_over_tls_address: ":1465"
 monitoring_address: ":1099"
 
 mail_delivery_agent_bin: "test-mda"
diff --git a/test/t-09-loop/B/chasquid.conf b/test/t-09-loop/B/chasquid.conf
index ef7a0da..e572536 100644
--- a/test/t-09-loop/B/chasquid.conf
+++ b/test/t-09-loop/B/chasquid.conf
@@ -1,5 +1,6 @@
 smtp_address: ":2025"
 submission_address: ":2587"
+submission_over_tls_address: ":2465"
 monitoring_address: ":2099"
 
 mail_delivery_agent_bin: "test-mda"
diff --git a/test/t-10-hooks/config/chasquid.conf b/test/t-10-hooks/config/chasquid.conf
index 2da8942..cf76e8a 100644
--- a/test/t-10-hooks/config/chasquid.conf
+++ b/test/t-10-hooks/config/chasquid.conf
@@ -1,5 +1,6 @@
 smtp_address: ":1025"
 submission_address: ":1587"
+submission_over_tls_address: ":1465"
 monitoring_address: ":1099"
 
 mail_delivery_agent_bin: "test-mda"