- Breaks out running an Accept loop into a goroutine.
- Changes how the servers communicate back to the main event loop,
using an enum instead of an error object.
catch-all delivery server for an MX domain. All messages that it receives are deposited into a
single mailbox, which can then be accessed using the POP3 protocol.
+## TLS Support
+
+TLS is recommended in production environments. To facilitate live-reloading of certificates, you can
+send a running instance SIGHUP.
+
## RFCs
This server implements the following RFCs:
pop3 := runPOP3Server(config, log)
smtp := runSMTPServer(config, log)
- select {
- case err := <-pop3:
- fmt.Println(err)
- case err := <-smtp:
- fmt.Println(err)
+ for {
+ select {
+ case cm := <-pop3:
+ if cm == ServerControlRestart {
+ pop3 = runPOP3Server(config, log)
+ } else {
+ break
+ }
+ case <-smtp:
+ // smtp never reloads.
+ break
+ }
}
}
"src.bluestatic.org/mailpopbox/pop3"
)
-func runPOP3Server(config Config, log zap.Logger) <-chan error {
+func runPOP3Server(config Config, log zap.Logger) <-chan ServerControlMessage {
server := pop3Server{
- config: config,
- rc: make(chan error),
- log: log.With(zap.String("server", "pop3")),
+ config: config,
+ controlChan: make(chan ServerControlMessage),
+ log: log.With(zap.String("server", "pop3")),
}
go server.run()
- return server.rc
+ return server.controlChan
}
type pop3Server struct {
- config Config
- rc chan error
- log zap.Logger
+ config Config
+ controlChan chan ServerControlMessage
+ log zap.Logger
}
func (server *pop3Server) run() {
for _, s := range server.config.Servers {
if err := os.Mkdir(s.MaildropPath, 0700); err != nil && !os.IsExist(err) {
server.log.Error("failed to open maildrop", zap.Error(err))
- server.rc <- err
+ server.controlChan <- ServerControlFatalError
}
}
+ l, err := server.newListener()
+ if err != nil {
+ server.controlChan <- ServerControlFatalError
+ return
+ }
+
+ connChan := make(chan net.Conn)
+ go RunAcceptLoop(l, connChan, server.log)
+
+ reloadChan := CreateReloadSignal()
+
+ for {
+ select {
+ case <-reloadChan:
+ server.log.Info("restarting server")
+ l.Close()
+ server.controlChan <- ServerControlRestart
+ break
+ case conn, ok := <-connChan:
+ if ok {
+ go pop3.AcceptConnection(conn, server, server.log)
+ } else {
+ server.controlChan <- ServerControlFatalError
+ break
+ }
+ }
+ }
+}
+
+func (server *pop3Server) newListener() (net.Listener, error) {
tlsConfig, err := server.config.GetTLSConfig()
if err != nil {
server.log.Error("failed to configure TLS", zap.Error(err))
- server.rc <- err
- return
+ return nil, err
}
addr := fmt.Sprintf(":%d", server.config.POP3Port)
}
if err != nil {
server.log.Error("listen", zap.Error(err))
- server.rc <- err
- return
+ return nil, err
}
- for {
- conn, err := l.Accept()
- if err != nil {
- server.log.Error("accept", zap.Error(err))
- server.rc <- err
- break
- }
-
- go pop3.AcceptConnection(conn, server, server.log)
- }
+ return l, nil
}
func (server *pop3Server) Name() string {
--- /dev/null
+package main
+
+import (
+ "net"
+ "os"
+ "os/signal"
+ "syscall"
+
+ "github.com/uber-go/zap"
+)
+
+type ServerControlMessage int
+
+const (
+ ServerControlFatalError ServerControlMessage = iota
+ ServerControlRestart
+)
+
+func RunAcceptLoop(l net.Listener, c chan<- net.Conn, log zap.Logger) {
+ for {
+ conn, err := l.Accept()
+ if err != nil {
+ log.Error("accept", zap.Error(err))
+ close(c)
+ return
+ }
+
+ c <- conn
+ }
+}
+
+func CreateReloadSignal() <-chan os.Signal {
+ reloadChan := make(chan os.Signal, 1)
+ signal.Notify(reloadChan, syscall.SIGHUP)
+ return reloadChan
+}
"src.bluestatic.org/mailpopbox/smtp"
)
-func runSMTPServer(config Config, log zap.Logger) <-chan error {
+func runSMTPServer(config Config, log zap.Logger) <-chan ServerControlMessage {
server := smtpServer{
- config: config,
- rc: make(chan error),
- log: log.With(zap.String("server", "smtp")),
+ config: config,
+ controlChan: make(chan ServerControlMessage),
+ log: log.With(zap.String("server", "smtp")),
}
go server.run()
- return server.rc
+ return server.controlChan
}
type smtpServer struct {
log zap.Logger
- rc chan error
+ controlChan chan ServerControlMessage
}
func (server *smtpServer) run() {
- var err error
- server.tlsConfig, err = server.config.GetTLSConfig()
- if err != nil {
- server.log.Error("failed to configure TLS", zap.Error(err))
- server.rc <- err
+ if !server.loadTLSConfig() {
+ return
}
addr := fmt.Sprintf(":%d", server.config.SMTPPort)
l, err := net.Listen("tcp", addr)
if err != nil {
server.log.Error("listen", zap.Error(err))
- server.rc <- err
+ server.controlChan <- ServerControlFatalError
return
}
+ connChan := make(chan net.Conn)
+ go RunAcceptLoop(l, connChan, server.log)
+
+ reloadChan := CreateReloadSignal()
+
for {
- conn, err := l.Accept()
- if err != nil {
- server.log.Error("accept", zap.Error(err))
- server.rc <- err
- return
+ select {
+ case <-reloadChan:
+ if !server.loadTLSConfig() {
+ return
+ }
+ case conn, ok := <-connChan:
+ if ok {
+ go smtp.AcceptConnection(conn, server, server.log)
+ } else {
+ break
+ }
}
+ }
+}
- go smtp.AcceptConnection(conn, server, server.log)
+func (server *smtpServer) loadTLSConfig() bool {
+ var err error
+ server.tlsConfig, err = server.config.GetTLSConfig()
+ if err != nil {
+ server.log.Error("failed to configure TLS", zap.Error(err))
+ server.controlChan <- ServerControlFatalError
+ return false
}
+ server.log.Info("loaded TLS config")
+ return true
}
func (server *smtpServer) Name() string {