GODT-1187: Remove IMAP/SMTP blocking when no internet.

This commit is contained in:
Jakub
2021-05-28 09:59:31 +02:00
committed by Jakub Cuth
parent f0ee82fdd2
commit 21dcac9fac
10 changed files with 604 additions and 321 deletions

View File

@ -23,13 +23,11 @@ import (
"io"
"net"
"strings"
"sync/atomic"
"time"
imapid "github.com/ProtonMail/go-imap-id"
"github.com/ProtonMail/proton-bridge/internal/bridge"
"github.com/ProtonMail/proton-bridge/internal/config/useragent"
"github.com/ProtonMail/proton-bridge/internal/events"
"github.com/ProtonMail/proton-bridge/internal/imap/id"
"github.com/ProtonMail/proton-bridge/internal/imap/idle"
"github.com/ProtonMail/proton-bridge/internal/imap/uidplus"
@ -43,43 +41,58 @@ import (
"github.com/emersion/go-imap/backend"
imapserver "github.com/emersion/go-imap/server"
"github.com/emersion/go-sasl"
"github.com/sirupsen/logrus"
)
type imapServer struct {
panicHandler panicHandler
server *imapserver.Server
userAgent *useragent.UserAgent
eventListener listener.Listener
debugClient bool
debugServer bool
port int
isRunning atomic.Value
// Server takes care of IMAP listening serving. It implements serverutil.Server.
type Server struct {
panicHandler panicHandler
userAgent *useragent.UserAgent
debugClient bool
debugServer bool
port int
server *imapserver.Server
controller serverutil.Controller
}
// NewIMAPServer constructs a new IMAP server configured with the given options.
func NewIMAPServer(panicHandler panicHandler, debugClient, debugServer bool, port int, tls *tls.Config, imapBackend backend.Backend, userAgent *useragent.UserAgent, eventListener listener.Listener) *imapServer { // nolint[golint]
s := imapserver.New(imapBackend)
s.Addr = fmt.Sprintf("%v:%v", bridge.Host, port)
s.TLSConfig = tls
s.AllowInsecureAuth = true
s.ErrorLog = newServerErrorLogger("server-imap")
s.AutoLogout = 30 * time.Minute
if debugServer {
fmt.Println("THE LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA")
log.Warning("================================================")
log.Warning("THIS LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA")
log.Warning("================================================")
func NewIMAPServer(
panicHandler panicHandler,
debugClient, debugServer bool,
port int,
tls *tls.Config,
imapBackend backend.Backend,
userAgent *useragent.UserAgent,
eventListener listener.Listener,
) *Server {
server := &Server{
panicHandler: panicHandler,
userAgent: userAgent,
debugClient: debugClient,
debugServer: debugServer,
port: port,
}
server.server = newGoIMAPServer(tls, imapBackend, server.Address(), userAgent)
server.controller = serverutil.NewController(server, eventListener)
return server
}
func newGoIMAPServer(tls *tls.Config, backend backend.Backend, address string, userAgent *useragent.UserAgent) *imapserver.Server {
server := imapserver.New(backend)
server.TLSConfig = tls
server.AllowInsecureAuth = true
server.ErrorLog = serverutil.NewServerErrorLogger(serverutil.IMAP)
server.AutoLogout = 30 * time.Minute
server.Addr = address
serverID := imapid.ID{
imapid.FieldName: "ProtonMail Bridge",
imapid.FieldVendor: "Proton Technologies AG",
imapid.FieldSupportURL: "https://protonmail.com/support",
}
s.EnableAuth(sasl.Login, func(conn imapserver.Conn) sasl.Server {
server.EnableAuth(sasl.Login, func(conn imapserver.Conn) sasl.Server {
return sasl.NewLoginServer(func(address, password string) error {
user, err := conn.Server().Backend.Login(nil, address, password)
if err != nil {
@ -93,7 +106,7 @@ func NewIMAPServer(panicHandler panicHandler, debugClient, debugServer bool, por
})
})
s.Enable(
server.Enable(
idle.NewExtension(),
imapmove.NewExtension(),
id.NewExtension(serverID, userAgent),
@ -103,87 +116,35 @@ func NewIMAPServer(panicHandler panicHandler, debugClient, debugServer bool, por
uidplus.NewExtension(),
)
server := &imapServer{
panicHandler: panicHandler,
server: s,
userAgent: userAgent,
eventListener: eventListener,
debugClient: debugClient,
debugServer: debugServer,
port: port,
}
server.isRunning.Store(false)
return server
}
func (s *imapServer) HandlePanic() { s.panicHandler.HandlePanic() }
func (s *imapServer) IsRunning() bool { return s.isRunning.Load().(bool) }
func (s *imapServer) Port() int { return s.port }
// ListenAndServe will run server and all monitors.
func (s *Server) ListenAndServe() { s.controller.ListenAndServe() }
// ListenAndServe starts the server and keeps it on based on internet
// availability.
func (s *imapServer) ListenAndServe() {
serverutil.ListenAndServe(s, s.eventListener)
}
// Close turns off server and monitors.
func (s *Server) Close() { s.controller.Close() }
// ListenRetryAndServe will start listener. If port is occupied it will try
// again after coolDown time. Once listener is OK it will serve.
func (s *imapServer) ListenRetryAndServe(retries int, retryAfter time.Duration) {
if s.IsRunning() {
return
}
s.isRunning.Store(true)
// Implements serverutil.Server interface.
l := log.WithField("address", s.server.Addr)
l.Info("IMAP server is starting")
listener, err := net.Listen("tcp", s.server.Addr)
if err != nil {
s.isRunning.Store(false)
if retries > 0 {
l.WithError(err).WithField("retries", retries).Warn("IMAP listener failed")
time.Sleep(retryAfter)
s.ListenRetryAndServe(retries-1, retryAfter)
return
}
func (Server) Protocol() serverutil.Protocol { return serverutil.IMAP }
func (s *Server) UseSSL() bool { return false }
func (s *Server) Address() string { return fmt.Sprintf("%s:%d", bridge.Host, s.port) }
func (s *Server) TLSConfig() *tls.Config { return s.server.TLSConfig }
func (s *Server) HandlePanic() { s.panicHandler.HandlePanic() }
l.WithError(err).Error("IMAP listener failed")
s.eventListener.Emit(events.ErrorEvent, "IMAP failed: "+err.Error())
return
}
func (s *Server) DebugServer() bool { return s.debugServer }
func (s *Server) DebugClient() bool { return s.debugClient }
err = s.server.Serve(&connListener{
Listener: listener,
server: s,
userAgent: s.userAgent,
})
// Serve returns error every time, even after closing the server.
// User shouldn't be notified about error if server shouldn't be running,
// but it should in case it was not closed by `s.Close()`.
if err != nil && s.IsRunning() {
s.isRunning.Store(false)
l.WithError(err).Error("IMAP server failed")
s.eventListener.Emit(events.ErrorEvent, "IMAP failed: "+err.Error())
return
}
defer s.server.Close() //nolint[errcheck]
func (s *Server) SetLoggers(localDebug, remoteDebug io.Writer) {
s.server.Debug = imap.NewDebugWriter(localDebug, remoteDebug)
l.Info("IMAP server stopped")
}
// Stops the server.
func (s *imapServer) Close() {
if !s.IsRunning() {
return
}
s.isRunning.Store(false)
log.Info("Closing IMAP server")
if err := s.server.Close(); err != nil {
log.WithError(err).Error("Failed to close the connection")
if !s.userAgent.HasClient() {
s.userAgent.SetClient("UnknownClient", "0.0.1")
}
}
func (s *imapServer) DisconnectUser(address string) {
func (s *Server) DisconnectUser(address string) {
log.Info("Disconnecting all open IMAP connections for ", address)
s.server.ForEachConn(func(conn imapserver.Conn) {
connUser := conn.Context().User
@ -195,60 +156,5 @@ func (s *imapServer) DisconnectUser(address string) {
})
}
// connListener sets debug loggers on server containing fields with local
// and remote addresses right after new connection is accepted.
type connListener struct {
net.Listener
server *imapServer
userAgent *useragent.UserAgent
}
func (l *connListener) Accept() (net.Conn, error) {
conn, err := l.Listener.Accept()
if err == nil && (l.server.debugServer || l.server.debugClient) {
debugLog := log
if addr := conn.LocalAddr(); addr != nil {
debugLog = debugLog.WithField("loc", addr.String())
}
if addr := conn.RemoteAddr(); addr != nil {
debugLog = debugLog.WithField("rem", addr.String())
}
var localDebug, remoteDebug io.Writer
if l.server.debugServer {
localDebug = debugLog.WithField("pkg", "imap/server").WriterLevel(logrus.DebugLevel)
}
if l.server.debugClient {
remoteDebug = debugLog.WithField("pkg", "imap/client").WriterLevel(logrus.DebugLevel)
}
l.server.server.Debug = imap.NewDebugWriter(localDebug, remoteDebug)
}
if !l.userAgent.HasClient() {
l.userAgent.SetClient("UnknownClient", "0.0.1")
}
return conn, err
}
// serverErrorLogger implements go-imap/logger interface.
type serverErrorLogger struct {
tag string
}
func newServerErrorLogger(tag string) *serverErrorLogger {
return &serverErrorLogger{tag}
}
func (s *serverErrorLogger) Printf(format string, args ...interface{}) {
err := fmt.Sprintf(format, args...)
log.WithField("pkg", s.tag).Error(err)
}
func (s *serverErrorLogger) Println(args ...interface{}) {
err := fmt.Sprintln(args...)
log.WithField("pkg", s.tag).Error(err)
}
func (s *Server) Serve(listener net.Listener) error { return s.server.Serve(listener) }
func (s *Server) StopServe() error { return s.server.Close() }