You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
141 lines
4.3 KiB
141 lines
4.3 KiB
// Copyright (c) 2022 Proton Technologies AG |
|
// |
|
// This file is part of ProtonMail Bridge. |
|
// |
|
// ProtonMail Bridge is free software: you can redistribute it and/or modify |
|
// it under the terms of the GNU General Public License as published by |
|
// the Free Software Foundation, either version 3 of the License, or |
|
// (at your option) any later version. |
|
// |
|
// ProtonMail Bridge is distributed in the hope that it will be useful, |
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
// GNU General Public License for more details. |
|
// |
|
// You should have received a copy of the GNU General Public License |
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. |
|
|
|
package imap |
|
|
|
import ( |
|
"crypto/tls" |
|
"fmt" |
|
"io" |
|
"net" |
|
"strings" |
|
"time" |
|
|
|
"github.com/emersion/go-imap" |
|
imapappendlimit "github.com/emersion/go-imap-appendlimit" |
|
imapmove "github.com/emersion/go-imap-move" |
|
imapquota "github.com/emersion/go-imap-quota" |
|
imapunselect "github.com/emersion/go-imap-unselect" |
|
"github.com/emersion/go-imap/backend" |
|
imapserver "github.com/emersion/go-imap/server" |
|
"github.com/emersion/go-sasl" |
|
"github.com/ljanyst/peroxide/pkg/imap/idle" |
|
"github.com/ljanyst/peroxide/pkg/imap/uidplus" |
|
"github.com/ljanyst/peroxide/pkg/listener" |
|
"github.com/ljanyst/peroxide/pkg/serverutil" |
|
) |
|
|
|
// Server takes care of IMAP listening serving. It implements serverutil.Server. |
|
type Server struct { |
|
debugClient bool |
|
debugServer bool |
|
address string |
|
port int |
|
|
|
server *imapserver.Server |
|
controller serverutil.Controller |
|
} |
|
|
|
// NewIMAPServer constructs a new IMAP server configured with the given options. |
|
func NewIMAPServer( |
|
debugClient, debugServer bool, |
|
address string, |
|
port int, |
|
tls *tls.Config, |
|
imapBackend backend.Backend, |
|
eventListener listener.Listener, |
|
) *Server { |
|
server := &Server{ |
|
debugClient: debugClient, |
|
debugServer: debugServer, |
|
address: address, |
|
port: port, |
|
} |
|
|
|
server.server = newGoIMAPServer(tls, imapBackend, server.Address()) |
|
server.controller = serverutil.NewController(server, eventListener) |
|
return server |
|
} |
|
|
|
func newGoIMAPServer(tls *tls.Config, backend backend.Backend, address string) *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 |
|
|
|
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 { |
|
return err |
|
} |
|
|
|
ctx := conn.Context() |
|
ctx.State = imap.AuthenticatedState |
|
ctx.User = user |
|
return nil |
|
}) |
|
}) |
|
|
|
server.Enable( |
|
idle.NewExtension(), |
|
imapmove.NewExtension(), |
|
imapquota.NewExtension(), |
|
imapappendlimit.NewExtension(), |
|
imapunselect.NewExtension(), |
|
uidplus.NewExtension(), |
|
) |
|
|
|
return server |
|
} |
|
|
|
// ListenAndServe will run server and all monitors. |
|
func (s *Server) ListenAndServe() { s.controller.ListenAndServe() } |
|
|
|
// Close turns off server and monitors. |
|
func (s *Server) Close() { s.controller.Close() } |
|
|
|
// Implements serverutil.Server interface. |
|
|
|
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", s.address, s.port) } |
|
func (s *Server) TLSConfig() *tls.Config { return s.server.TLSConfig } |
|
|
|
func (s *Server) DebugServer() bool { return s.debugServer } |
|
func (s *Server) DebugClient() bool { return s.debugClient } |
|
|
|
func (s *Server) SetLoggers(localDebug, remoteDebug io.Writer) { |
|
s.server.Debug = imap.NewDebugWriter(localDebug, remoteDebug) |
|
} |
|
|
|
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 |
|
if connUser != nil && strings.EqualFold(connUser.Username(), address) { |
|
if err := conn.Close(); err != nil { |
|
log.WithError(err).Error("Failed to close the connection") |
|
} |
|
} |
|
}) |
|
} |
|
|
|
func (s *Server) Serve(listener net.Listener) error { return s.server.Serve(listener) } |
|
func (s *Server) StopServe() error { return s.server.Close() }
|
|
|