Bump the version to 2.1.0.
[mailpopbox.git] / pop3.go
diff --git a/pop3.go b/pop3.go
index 53c4ebc18a6c7e3b666796605f397bb28761b216..a03c1b81c181a0965e11c00780ed9294392ac52d 100644 (file)
--- a/pop3.go
+++ b/pop3.go
@@ -1,3 +1,9 @@
+// mailpopbox
+// Copyright 2020 Blue Static <https://www.bluestatic.org>
+// This program is free software licensed under the GNU General Public License,
+// version 3.0. The full text of the license can be found in LICENSE.txt.
+// SPDX-License-Identifier: GPL-3.0-only
+
 package main
 
 import (
@@ -10,40 +16,69 @@ import (
        "os"
        "path"
 
-       "github.com/uber-go/zap"
+       "go.uber.org/zap"
 
        "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)
@@ -57,20 +92,10 @@ func (server *pop3Server) run() {
        }
        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 {
@@ -79,7 +104,7 @@ func (server *pop3Server) Name() string {
 
 func (server *pop3Server) OpenMailbox(user, pass string) (pop3.Mailbox, error) {
        for _, s := range server.config.Servers {
-               if user == "mailbox@"+s.Domain && pass == s.MailboxPassword {
+               if user == MailboxAccount+s.Domain && pass == s.MailboxPassword {
                        return server.openMailbox(s.MaildropPath)
                }
        }
@@ -89,8 +114,8 @@ func (server *pop3Server) OpenMailbox(user, pass string) (pop3.Mailbox, error) {
 func (server *pop3Server) openMailbox(maildrop string) (*mailbox, error) {
        files, err := ioutil.ReadDir(maildrop)
        if err != nil {
-               // TODO: hide error, log instead
-               return nil, err
+               server.log.Error("failed read maildrop dir", zap.String("dir", maildrop), zap.Error(err))
+               return nil, errors.New("error opening maildrop")
        }
 
        mb := &mailbox{
@@ -152,7 +177,7 @@ func (mb *mailbox) ListMessages() ([]pop3.Message, error) {
 }
 
 func (mb *mailbox) GetMessage(id int) pop3.Message {
-       if id > len(mb.messages) {
+       if id == 0 || id > len(mb.messages) {
                return nil
        }
        return &mb.messages[id-1]