git » chasquid » commit 941eb93

Distinguish between SMTP and submission ports

author Alberto Bertogli
2016-09-12 02:47:36 UTC
committer Alberto Bertogli
2016-10-09 23:50:24 UTC
parent 69222284e077cf4b70ebe045a3a001686bbe1d97

Distinguish between SMTP and submission ports

We want to be able to distinguish between connections for SMTP and connections
for submission, so we can make different policy decisions.

To do that, we first make the configuration aware of the different kinds of
addresses. This is done in this patch in a backwards-incompatible way, but at
this point in time it is ok to do so.

Then, we extend systemd's socket passing library to support socket naming, so
we can tell the different sockets apart. This is done via the
LISTEN_FDNAMES/FileDescriptorName mechanism.

And finally we make the server and connection types aware of the socket mode.

chasquid.go +57 -33
chasquid_test.go +1 -1
internal/config/config.go +7 -3
internal/config/config.pb.go +21 -17
internal/config/config.proto +11 -5
internal/config/config_test.go +11 -7
internal/systemd/systemd.go +22 -7
internal/systemd/systemd_test.go +36 -15
test/t-01-simple_local/config/chasquid.conf +2 -1
test/t-02-exim/config/chasquid.conf +2 -1

diff --git a/chasquid.go b/chasquid.go
index dd85229..7c93b6c 100644
--- a/chasquid.go
+++ b/chasquid.go
@@ -94,30 +94,37 @@ func main() {
 	// as a remote domain (for loops, alias resolutions, etc.).
 	s.AddDomain("localhost")
 
+	// Load the addresses and listeners.
+	systemdLs, err := systemd.Listeners()
+	if err != nil {
+		glog.Fatalf("Error getting systemd listeners: %v", err)
+	}
+
+	loadAddresses(s, conf.SmtpAddress, systemdLs["smtp"], ModeSMTP)
+	loadAddresses(s, conf.SubmissionAddress, systemdLs["submission"], ModeSubmission)
+
+	s.ListenAndServe()
+}
+
+func loadAddresses(srv *Server, addrs []string, ls []net.Listener, mode SocketMode) {
 	// Load addresses.
 	acount := 0
-	for _, addr := range conf.Address {
+	for _, addr := range addrs {
 		// The "systemd" address indicates we get listeners via systemd.
 		if addr == "systemd" {
-			ls, err := systemd.Listeners()
-			if err != nil {
-				glog.Fatalf("Error getting listeners via systemd: %v", err)
-			}
-			s.AddListeners(ls)
+			srv.AddListeners(ls, mode)
 			acount += len(ls)
 		} else {
-			s.AddAddr(addr)
+			srv.AddAddr(addr, mode)
 			acount++
 		}
 	}
 
 	if acount == 0 {
-		glog.Errorf("No addresses/listeners configured")
-		glog.Errorf("If using systemd, check that you started chasquid.socket")
+		glog.Errorf("No %v addresses/listeners", mode)
+		glog.Errorf("If using systemd, check that you named the sockets")
 		glog.Fatalf("Exiting")
 	}
-
-	s.ListenAndServe()
 }
 
 // Helper to load a single domain configuration into the server.
@@ -160,6 +167,15 @@ func setupSignalHandling() {
 	}()
 }
 
+// Mode for a socket (listening or connection).
+// We keep them distinct, as policies can differ between them.
+type SocketMode string
+
+const (
+	ModeSMTP       SocketMode = "SMTP"
+	ModeSubmission SocketMode = "Submission"
+)
+
 type Server struct {
 	// Main hostname, used for display only.
 	Hostname string
@@ -171,10 +187,10 @@ type Server struct {
 	certs, keys []string
 
 	// Addresses.
-	addrs []string
+	addrs map[SocketMode][]string
 
 	// Listeners (that came via systemd).
-	listeners []net.Listener
+	listeners map[SocketMode][]net.Listener
 
 	// TLS config.
 	tlsConfig *tls.Config
@@ -197,6 +213,8 @@ type Server struct {
 
 func NewServer() *Server {
 	return &Server{
+		addrs:          map[SocketMode][]string{},
+		listeners:      map[SocketMode][]net.Listener{},
 		connTimeout:    20 * time.Minute,
 		commandTimeout: 1 * time.Minute,
 		localDomains:   &set.String{},
@@ -209,12 +227,12 @@ func (s *Server) AddCerts(cert, key string) {
 	s.keys = append(s.keys, key)
 }
 
-func (s *Server) AddAddr(a string) {
-	s.addrs = append(s.addrs, a)
+func (s *Server) AddAddr(a string, m SocketMode) {
+	s.addrs[m] = append(s.addrs[m], a)
 }
 
-func (s *Server) AddListeners(ls []net.Listener) {
-	s.listeners = append(s.listeners, ls...)
+func (s *Server) AddListeners(ls []net.Listener, m SocketMode) {
+	s.listeners[m] = append(s.listeners[m], ls...)
 }
 
 func (s *Server) AddDomain(d string) {
@@ -256,26 +274,28 @@ func (s *Server) ListenAndServe() {
 	s.queue = queue.New(
 		&courier.Procmail{}, &courier.SMTP{}, s.localDomains)
 
-	for _, addr := range s.addrs {
-		// Listen.
-		l, err := net.Listen("tcp", addr)
-		if err != nil {
-			glog.Fatalf("Error listening: %v", err)
-		}
-		defer l.Close()
+	for m, addrs := range s.addrs {
+		for _, addr := range addrs {
+			// Listen.
+			l, err := net.Listen("tcp", addr)
+			if err != nil {
+				glog.Fatalf("Error listening: %v", err)
+			}
 
-		glog.Infof("Server listening on %s", addr)
+			glog.Infof("Server listening on %s (%v)", addr, m)
 
-		// Serve.
-		go s.serve(l)
+			// Serve.
+			go s.serve(l, m)
+		}
 	}
 
-	for _, l := range s.listeners {
-		defer l.Close()
-		glog.Infof("Server listening on %s (via systemd)", l.Addr())
+	for m, ls := range s.listeners {
+		for _, l := range ls {
+			glog.Infof("Server listening on %s (%v, via systemd)", l.Addr(), m)
 
-		// Serve.
-		go s.serve(l)
+			// Serve.
+			go s.serve(l, m)
+		}
 	}
 
 	// Never return. If the serve goroutines have problems, they will abort
@@ -285,7 +305,7 @@ func (s *Server) ListenAndServe() {
 	}
 }
 
-func (s *Server) serve(l net.Listener) {
+func (s *Server) serve(l net.Listener, mode SocketMode) {
 	for {
 		conn, err := l.Accept()
 		if err != nil {
@@ -297,6 +317,7 @@ func (s *Server) serve(l net.Listener) {
 			maxDataSize:    s.MaxDataSize,
 			netconn:        conn,
 			tc:             textproto.NewConn(conn),
+			mode:           mode,
 			tlsConfig:      s.tlsConfig,
 			userDBs:        s.userDBs,
 			deadline:       time.Now().Add(s.connTimeout),
@@ -305,6 +326,8 @@ func (s *Server) serve(l net.Listener) {
 		}
 		go sc.Handle()
 	}
+
+	l.Close()
 }
 
 type Conn struct {
@@ -317,6 +340,7 @@ type Conn struct {
 	// Connection information.
 	netconn net.Conn
 	tc      *textproto.Conn
+	mode    SocketMode
 
 	// System configuration.
 	config *config.Config
diff --git a/chasquid_test.go b/chasquid_test.go
index d17770a..14f494c 100644
--- a/chasquid_test.go
+++ b/chasquid_test.go
@@ -379,7 +379,7 @@ func realMain(m *testing.M) int {
 		s.Hostname = "localhost"
 		s.MaxDataSize = 50 * 1024 * 1025
 		s.AddCerts(tmpDir+"/cert.pem", tmpDir+"/key.pem")
-		s.AddAddr(srvAddr)
+		s.AddAddr(srvAddr, ModeSMTP)
 
 		udb := userdb.New("/dev/null")
 		udb.AddUser("testuser", "testpasswd")
diff --git a/internal/config/config.go b/internal/config/config.go
index 0b48a69..f60400e 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -43,8 +43,11 @@ func Load(path string) (*Config, error) {
 		c.MaxDataSizeMb = 50
 	}
 
-	if len(c.Address) == 0 {
-		c.Address = append(c.Address, "systemd")
+	if len(c.SmtpAddress) == 0 {
+		c.SmtpAddress = append(c.SmtpAddress, "systemd")
+	}
+	if len(c.SubmissionAddress) == 0 {
+		c.SubmissionAddress = append(c.SubmissionAddress, "systemd")
 	}
 
 	if c.MailDeliveryAgentBin == "" {
@@ -63,7 +66,8 @@ func logConfig(c *Config) {
 	glog.Infof("Configuration:")
 	glog.Infof("  Hostname: %q", c.Hostname)
 	glog.Infof("  Max data size (MB): %d", c.MaxDataSizeMb)
-	glog.Infof("  Addresses: %v", c.Address)
+	glog.Infof("  SMTP Addresses: %v", c.SmtpAddress)
+	glog.Infof("  Submission Addresses: %v", c.SubmissionAddress)
 	glog.Infof("  Monitoring address: %s", c.MonitoringAddress)
 	glog.Infof("  MDA: %s %v", c.MailDeliveryAgentBin, c.MailDeliveryAgentArgs)
 }
diff --git a/internal/config/config.pb.go b/internal/config/config.pb.go
index ba208c0..bd6bf18 100644
--- a/internal/config/config.pb.go
+++ b/internal/config/config.pb.go
@@ -36,24 +36,27 @@ type Config struct {
 	// Maximum email size, in megabytes.
 	// Default: 50.
 	MaxDataSizeMb int64 `protobuf:"varint,2,opt,name=max_data_size_mb" json:"max_data_size_mb,omitempty"`
-	// Addresses to listen on.
+	// Addresses to listen on for SMTP (usually port 25).
 	// Default: "systemd", which means systemd passes sockets to us.
-	Address []string `protobuf:"bytes,3,rep,name=address" json:"address,omitempty"`
+	SmtpAddress []string `protobuf:"bytes,3,rep,name=smtp_address" json:"smtp_address,omitempty"`
+	// Addresses to listen on for submission (usually port 587).
+	// Default: "systemd", which means systemd passes sockets to us.
+	SubmissionAddress []string `protobuf:"bytes,4,rep,name=submission_address" json:"submission_address,omitempty"`
 	// Address for the monitoring http server.
 	// Default: no monitoring http server.
-	MonitoringAddress string `protobuf:"bytes,4,opt,name=monitoring_address" json:"monitoring_address,omitempty"`
+	MonitoringAddress string `protobuf:"bytes,5,opt,name=monitoring_address" 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: "procmail".
-	MailDeliveryAgentBin string `protobuf:"bytes,5,opt,name=mail_delivery_agent_bin" json:"mail_delivery_agent_bin,omitempty"`
+	MailDeliveryAgentBin string `protobuf:"bytes,6,opt,name=mail_delivery_agent_bin" json:"mail_delivery_agent_bin,omitempty"`
 	// Command line arguments for the mail delivery agent. One per argument.
 	// Some replacements will be done:
 	//  - "%user%"   -> local user (anything before the @)
 	//  - "%domain%" -> domain (anything after the @)
 	// Default: "-d", "%user"  (adequate for procmail)
-	MailDeliveryAgentArgs []string `protobuf:"bytes,6,rep,name=mail_delivery_agent_args" json:"mail_delivery_agent_args,omitempty"`
+	MailDeliveryAgentArgs []string `protobuf:"bytes,7,rep,name=mail_delivery_agent_args" json:"mail_delivery_agent_args,omitempty"`
 }
 
 func (m *Config) Reset()                    { *m = Config{} }
@@ -68,16 +71,17 @@ func init() {
 func init() { proto.RegisterFile("config.proto", fileDescriptor0) }
 
 var fileDescriptor0 = []byte{
-	// 169 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0x8e, 0x41, 0xca, 0xc2, 0x30,
-	0x10, 0x46, 0xe9, 0xdf, 0xdf, 0xaa, 0x83, 0x60, 0xc9, 0xc6, 0xc1, 0x8d, 0xc5, 0x95, 0x2b, 0x37,
-	0x1e, 0xc1, 0x83, 0x0c, 0x53, 0x13, 0xe3, 0x40, 0x93, 0x48, 0x12, 0x44, 0x3d, 0x8f, 0x07, 0xb5,
-	0x06, 0xdc, 0xb9, 0xfd, 0xde, 0xe3, 0xf1, 0xc1, 0xe2, 0x14, 0xfc, 0x59, 0xec, 0xfe, 0x1a, 0x43,
-	0x0e, 0xdb, 0x57, 0x05, 0xcd, 0xb1, 0x0c, 0xaa, 0x85, 0xd9, 0x25, 0xa4, 0xec, 0xd9, 0x19, 0xac,
-	0xba, 0x6a, 0x37, 0x57, 0x08, 0xad, 0xe3, 0x3b, 0x69, 0xce, 0x4c, 0x49, 0x9e, 0x86, 0x5c, 0x8f,
-	0x7f, 0x23, 0xa9, 0xd5, 0x12, 0xa6, 0xac, 0x75, 0x34, 0x29, 0x61, 0xdd, 0xd5, 0xa3, 0xba, 0x06,
-	0xe5, 0x82, 0x97, 0x1c, 0xa2, 0x78, 0x4b, 0x5f, 0xf6, 0x5f, 0x32, 0x1b, 0x58, 0x39, 0x96, 0x81,
-	0xb4, 0x19, 0xe4, 0x66, 0xe2, 0x83, 0xd8, 0x1a, 0x9f, 0xa9, 0x17, 0x8f, 0x93, 0x22, 0x74, 0x80,
-	0xbf, 0x04, 0x8e, 0x36, 0x61, 0xf3, 0xc9, 0xf7, 0x4d, 0x79, 0x7b, 0x78, 0x07, 0x00, 0x00, 0xff,
-	0xff, 0xa3, 0xe4, 0x58, 0xd3, 0xbd, 0x00, 0x00, 0x00,
+	// 186 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0xcf, 0xc1, 0x8a, 0xc2, 0x30,
+	0x10, 0x80, 0x61, 0xba, 0xdd, 0xed, 0xee, 0x0e, 0x3d, 0x94, 0xb0, 0xb0, 0xc1, 0x8b, 0xc5, 0x93,
+	0x27, 0x2f, 0x3e, 0x82, 0x0f, 0x12, 0x26, 0x26, 0xc6, 0x81, 0x26, 0x29, 0x99, 0x28, 0xea, 0xfb,
+	0xf9, 0x5e, 0xd6, 0x80, 0x3d, 0x79, 0x9d, 0xef, 0x67, 0x86, 0x81, 0x76, 0x1f, 0xc3, 0x81, 0xdc,
+	0x66, 0x4c, 0x31, 0xc7, 0xd5, 0xbd, 0x82, 0x66, 0x57, 0x06, 0xa2, 0x83, 0x9f, 0x63, 0xe4, 0x1c,
+	0xd0, 0x5b, 0x59, 0xf5, 0xd5, 0xfa, 0x57, 0x48, 0xe8, 0x3c, 0x5e, 0x94, 0xc1, 0x8c, 0x8a, 0xe9,
+	0x66, 0x95, 0xd7, 0xf2, 0x63, 0x92, 0x5a, 0xfc, 0x41, 0xcb, 0x3e, 0x8f, 0x0a, 0x8d, 0x49, 0x96,
+	0x59, 0xd6, 0x7d, 0x3d, 0xf5, 0x0b, 0x10, 0x7c, 0xd2, 0x9e, 0x98, 0x29, 0x86, 0xd9, 0x3e, 0x5f,
+	0xe6, 0x63, 0xa0, 0x1c, 0x13, 0x05, 0x37, 0xdb, 0x57, 0xb9, 0xb3, 0x84, 0x7f, 0x8f, 0x34, 0x28,
+	0x63, 0x07, 0x3a, 0xdb, 0x74, 0x55, 0xe8, 0x6c, 0xc8, 0x4a, 0x53, 0x90, 0x4d, 0x09, 0x7a, 0x90,
+	0xef, 0x02, 0x4c, 0x8e, 0xe5, 0xf7, 0x73, 0xbd, 0x6e, 0xca, 0x3b, 0xdb, 0x47, 0x00, 0x00, 0x00,
+	0xff, 0xff, 0x75, 0x30, 0x88, 0xe6, 0xde, 0x00, 0x00, 0x00,
 }
diff --git a/internal/config/config.proto b/internal/config/config.proto
index 507c06d..493dbef 100644
--- a/internal/config/config.proto
+++ b/internal/config/config.proto
@@ -11,26 +11,32 @@ message Config {
 	// Default: 50.
 	int64 max_data_size_mb = 2;
 
-	// Addresses to listen on.
+	// Addresses to listen on for SMTP (usually port 25).
 	// Default: "systemd", which means systemd passes sockets to us.
-	repeated string address = 3;
+	// systemd sockets must be named with "FileDescriptorName=smtp".
+	repeated string smtp_address = 3;
+
+	// Addresses to listen on for submission (usually port 587).
+	// Default: "systemd", which means systemd passes sockets to us.
+	// systemd sockets must be named with "FileDescriptorName=submission".
+	repeated string submission_address = 4;
 
 	// Address for the monitoring http server.
 	// Default: no monitoring http server.
-	string monitoring_address = 4;
+	string monitoring_address = 5;
 
 	// 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: "procmail".
-	string mail_delivery_agent_bin = 5;
+	string mail_delivery_agent_bin = 6;
 
 	// Command line arguments for the mail delivery agent. One per argument.
 	// Some replacements will be done:
 	//  - "%user%"   -> local user (anything before the @)
 	//  - "%domain%" -> domain (anything after the @)
 	// Default: "-d", "%user"  (adequate for procmail)
-	repeated string mail_delivery_agent_args = 6;
+	repeated string mail_delivery_agent_args = 7;
 }
 
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
index 6abdc47..790c2a0 100644
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -40,8 +40,12 @@ func TestEmptyConfig(t *testing.T) {
 		t.Errorf("max data size != 50: %d", c.MaxDataSizeMb)
 	}
 
-	if len(c.Address) != 1 || c.Address[0] != "systemd" {
-		t.Errorf("unexpected address default: %v", c.Address)
+	if len(c.SmtpAddress) != 1 || c.SmtpAddress[0] != "systemd" {
+		t.Errorf("unexpected address default: %v", c.SmtpAddress)
+	}
+
+	if len(c.SubmissionAddress) != 1 || c.SubmissionAddress[0] != "systemd" {
+		t.Errorf("unexpected address default: %v", c.SubmissionAddress)
 	}
 
 	if c.MonitoringAddress != "" {
@@ -53,8 +57,8 @@ func TestEmptyConfig(t *testing.T) {
 func TestFullConfig(t *testing.T) {
 	confStr := `
 		hostname: "joust"
-		address: ":1234"
-		address: ":5678"
+		smtp_address: ":1234"
+		smtp_address: ":5678"
 		monitoring_address: ":1111"
 		max_data_size_mb: 26
 	`
@@ -75,9 +79,9 @@ func TestFullConfig(t *testing.T) {
 		t.Errorf("max data size != 26: %d", c.MaxDataSizeMb)
 	}
 
-	if len(c.Address) != 2 ||
-		c.Address[0] != ":1234" || c.Address[1] != ":5678" {
-		t.Errorf("different address: %v", c.Address)
+	if len(c.SmtpAddress) != 2 ||
+		c.SmtpAddress[0] != ":1234" || c.SmtpAddress[1] != ":5678" {
+		t.Errorf("different address: %v", c.SmtpAddress)
 	}
 
 	if c.MonitoringAddress != ":1111" {
diff --git a/internal/systemd/systemd.go b/internal/systemd/systemd.go
index 4cbaf5e..75f2686 100644
--- a/internal/systemd/systemd.go
+++ b/internal/systemd/systemd.go
@@ -7,6 +7,7 @@ import (
 	"net"
 	"os"
 	"strconv"
+	"strings"
 	"syscall"
 )
 
@@ -21,10 +22,12 @@ var (
 
 // Listeners creates a slice net.Listener from the file descriptors passed
 // by systemd, via the LISTEN_FDS environment variable.
-// See sd_listen_fds(3) for more details.
-func Listeners() ([]net.Listener, error) {
+// See sd_listen_fds(3) and sd_listen_fds_with_names(3) for more details.
+func Listeners() (map[string][]net.Listener, error) {
 	pidStr := os.Getenv("LISTEN_PID")
 	nfdsStr := os.Getenv("LISTEN_FDS")
+	fdNamesStr := os.Getenv("LISTEN_FDNAMES")
+	fdNames := strings.Split(fdNamesStr, ":")
 
 	// Nothing to do if the variables are not set.
 	if pidStr == "" || nfdsStr == "" {
@@ -45,26 +48,38 @@ func Listeners() ([]net.Listener, error) {
 			"error reading $LISTEN_FDS=%q: %v", nfdsStr, err)
 	}
 
-	listeners := []net.Listener{}
+	// We should have as many names as we have descriptors.
+	// Note that if we have no descriptors, fdNames will be [""] (due to how
+	// strings.Split works), so we consider that special case.
+	if nfds > 0 && (fdNamesStr == "" || len(fdNames) != nfds) {
+		return nil, fmt.Errorf(
+			"Incorrect LISTEN_FDNAMES, have you set FileDescriptorName?")
+	}
 
-	for fd := firstFD; fd < firstFD+nfds; fd++ {
+	listeners := map[string][]net.Listener{}
+
+	for i := 0; i < nfds; i++ {
+		fd := firstFD + i
 		// We don't want childs to inherit these file descriptors.
 		syscall.CloseOnExec(fd)
 
-		name := fmt.Sprintf("[systemd-fd-%d]", fd)
-		lis, err := net.FileListener(os.NewFile(uintptr(fd), name))
+		name := fdNames[i]
+
+		sysName := fmt.Sprintf("[systemd-fd-%d-%v]", fd, name)
+		lis, err := net.FileListener(os.NewFile(uintptr(fd), sysName))
 		if err != nil {
 			return nil, fmt.Errorf(
 				"Error making listener out of fd %d: %v", fd, err)
 		}
 
-		listeners = append(listeners, lis)
+		listeners[name] = append(listeners[name], lis)
 	}
 
 	// Remove them from the environment, to prevent accidental reuse (by
 	// us or children processes).
 	os.Unsetenv("LISTEN_PID")
 	os.Unsetenv("LISTEN_FDS")
+	os.Unsetenv("LISTEN_FDNAMES")
 
 	return listeners, nil
 }
diff --git a/internal/systemd/systemd_test.go b/internal/systemd/systemd_test.go
index 34bca4b..226b124 100644
--- a/internal/systemd/systemd_test.go
+++ b/internal/systemd/systemd_test.go
@@ -5,12 +5,14 @@ import (
 	"net"
 	"os"
 	"strconv"
+	"strings"
 	"testing"
 )
 
-func setenv(pid, fds string) {
+func setenv(pid, fds string, names ...string) {
 	os.Setenv("LISTEN_PID", pid)
 	os.Setenv("LISTEN_FDS", fds)
+	os.Setenv("LISTEN_FDNAMES", strings.Join(names, ":"))
 }
 
 func TestEmptyEnvironment(t *testing.T) {
@@ -30,16 +32,26 @@ func TestEmptyEnvironment(t *testing.T) {
 }
 
 func TestBadEnvironment(t *testing.T) {
+	// Create a listener so we have something to reference.
+	l := newListener(t)
+	firstFD = listenerFd(t, l)
+
 	ourPID := strconv.Itoa(os.Getpid())
-	cases := []struct{ pid, fds string }{
-		{"a", "4"},
-		{ourPID, "a"},
+	cases := []struct {
+		pid, fds string
+		names    []string
+	}{
+		{"a", "1", []string{"name"}},              // Invalid PID.
+		{ourPID, "a", []string{"name"}},           // Invalid number of fds.
+		{"1", "1", []string{"name"}},              // PID != ourselves.
+		{ourPID, "1", []string{"name1", "name2"}}, // Too many names.
+		{ourPID, "1", []string{}},                 // Not enough names.
 	}
 	for _, c := range cases {
-		setenv(c.pid, c.fds)
+		setenv(c.pid, c.fds, c.names...)
 
 		if ls, err := Listeners(); err == nil {
-			t.Logf("Case: LISTEN_PID=%q  LISTEN_FDS=%q", c.pid, c.fds)
+			t.Logf("Case: LISTEN_PID=%q  LISTEN_FDS=%q LISTEN_FDNAMES=%q", c.pid, c.fds, c.names)
 			t.Errorf("Unexpected result: %v // %v", ls, err)
 		}
 	}
@@ -98,13 +110,15 @@ func TestOneSocket(t *testing.T) {
 	l := newListener(t)
 	firstFD = listenerFd(t, l)
 
-	setenv(strconv.Itoa(os.Getpid()), "1")
+	setenv(strconv.Itoa(os.Getpid()), "1", "name")
 
-	ls, err := Listeners()
-	if err != nil || len(ls) != 1 {
-		t.Fatalf("Got an invalid result: %v // %v", ls, err)
+	lsMap, err := Listeners()
+	if err != nil || len(lsMap) != 1 {
+		t.Fatalf("Got an invalid result: %v // %v", lsMap, err)
 	}
 
+	ls := lsMap["name"]
+
 	if !sameAddr(ls[0].Addr(), l.Addr()) {
 		t.Errorf("Listener 0 address mismatch, expected %#v, got %#v",
 			l.Addr(), ls[0].Addr())
@@ -134,11 +148,16 @@ func TestManySockets(t *testing.T) {
 
 	firstFD = f0
 
-	setenv(strconv.Itoa(os.Getpid()), "2")
+	setenv(strconv.Itoa(os.Getpid()), "2", "name1", "name2")
 
-	ls, err := Listeners()
-	if err != nil || len(ls) != 2 {
-		t.Fatalf("Got an invalid result: %v // %v", ls, err)
+	lsMap, err := Listeners()
+	if err != nil || len(lsMap) != 2 {
+		t.Fatalf("Got an invalid result: %v // %v", lsMap, err)
+	}
+
+	ls := []net.Listener{
+		lsMap["name1"][0],
+		lsMap["name2"][0],
 	}
 
 	if !sameAddr(ls[0].Addr(), l0.Addr()) {
@@ -151,7 +170,9 @@ func TestManySockets(t *testing.T) {
 			l1.Addr(), ls[1].Addr())
 	}
 
-	if os.Getenv("LISTEN_PID") != "" || os.Getenv("LISTEN_FDS") != "" {
+	if os.Getenv("LISTEN_PID") != "" ||
+		os.Getenv("LISTEN_FDS") != "" ||
+		os.Getenv("LISTEN_FDNAMES") != "" {
 		t.Errorf("Failed to reset the environment")
 	}
 }
diff --git a/test/t-01-simple_local/config/chasquid.conf b/test/t-01-simple_local/config/chasquid.conf
index 1302399..5c36cb4 100644
--- a/test/t-01-simple_local/config/chasquid.conf
+++ b/test/t-01-simple_local/config/chasquid.conf
@@ -1,4 +1,5 @@
-address: ":1025"
+smtp_address: ":1025"
+submission_address: ":1587"
 monitoring_address: ":1099"
 
 mail_delivery_agent_bin: "test-mda"
diff --git a/test/t-02-exim/config/chasquid.conf b/test/t-02-exim/config/chasquid.conf
index 1302399..5c36cb4 100644
--- a/test/t-02-exim/config/chasquid.conf
+++ b/test/t-02-exim/config/chasquid.conf
@@ -1,4 +1,5 @@
-address: ":1025"
+smtp_address: ":1025"
+submission_address: ":1587"
 monitoring_address: ":1099"
 
 mail_delivery_agent_bin: "test-mda"