forked from Silverfish/proton-bridge
feat(GODT-2814): Standalone Server Manager
Convert ServerManger into a standalone service so that it can become a self contained module.
This commit is contained in:
730
internal/services/imapsmtpserver/service.go
Normal file
730
internal/services/imapsmtpserver/service.go
Normal file
@ -0,0 +1,730 @@
|
||||
// Copyright (c) 2023 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail 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.
|
||||
//
|
||||
// Proton Mail 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package imapsmtpserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/ProtonMail/gluon"
|
||||
"github.com/ProtonMail/gluon/async"
|
||||
"github.com/ProtonMail/gluon/connector"
|
||||
"github.com/ProtonMail/gluon/imap"
|
||||
"github.com/ProtonMail/gluon/logging"
|
||||
"github.com/ProtonMail/gluon/reporter"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapservice"
|
||||
bridgesmtp "github.com/ProtonMail/proton-bridge/v3/internal/services/smtp"
|
||||
"github.com/ProtonMail/proton-bridge/v3/pkg/cpc"
|
||||
"github.com/emersion/go-smtp"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Service manages the IMAP & SMTP servers and their listeners.
|
||||
type Service struct {
|
||||
requests *cpc.CPC
|
||||
|
||||
imapServer *gluon.Server
|
||||
imapListener net.Listener
|
||||
|
||||
smtpServer *smtp.Server
|
||||
smtpListener net.Listener
|
||||
smtpAccounts *bridgesmtp.Accounts
|
||||
|
||||
smtpSettings SMTPSettingsProvider
|
||||
imapSettings IMAPSettingsProvider
|
||||
eventPublisher events.EventPublisher
|
||||
panicHandler async.PanicHandler
|
||||
reporter reporter.Reporter
|
||||
|
||||
loadedUserCount int
|
||||
log *logrus.Entry
|
||||
tasks *async.Group
|
||||
|
||||
uidValidityGenerator imap.UIDValidityGenerator
|
||||
telemetry Telemetry
|
||||
}
|
||||
|
||||
func NewService(
|
||||
ctx context.Context,
|
||||
smtpSettings SMTPSettingsProvider,
|
||||
imapSettings IMAPSettingsProvider,
|
||||
eventPublisher events.EventPublisher,
|
||||
panicHandler async.PanicHandler,
|
||||
reporter reporter.Reporter,
|
||||
uidValidityGenerator imap.UIDValidityGenerator,
|
||||
telemetry Telemetry,
|
||||
) *Service {
|
||||
return &Service{
|
||||
requests: cpc.NewCPC(),
|
||||
smtpAccounts: bridgesmtp.NewAccounts(),
|
||||
|
||||
panicHandler: panicHandler,
|
||||
reporter: reporter,
|
||||
smtpSettings: smtpSettings,
|
||||
imapSettings: imapSettings,
|
||||
eventPublisher: eventPublisher,
|
||||
log: logrus.WithField("service", "server-manager"),
|
||||
tasks: async.NewGroup(ctx, panicHandler),
|
||||
uidValidityGenerator: uidValidityGenerator,
|
||||
telemetry: telemetry,
|
||||
}
|
||||
}
|
||||
|
||||
func (sm *Service) Init(ctx context.Context, group *async.Group, subscription events.Subscription) error {
|
||||
imapServer, err := sm.createIMAPServer(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
smtpServer := sm.createSMTPServer()
|
||||
|
||||
sm.imapServer = imapServer
|
||||
sm.smtpServer = smtpServer
|
||||
|
||||
group.Once(func(ctx context.Context) {
|
||||
logging.DoAnnotated(ctx, func(ctx context.Context) {
|
||||
sm.run(ctx, subscription)
|
||||
}, logging.Labels{
|
||||
"service": "server-manager",
|
||||
})
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sm *Service) CloseServers(ctx context.Context) error {
|
||||
defer sm.requests.Close()
|
||||
_, err := sm.requests.Send(ctx, &smRequestClose{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (sm *Service) RestartIMAP(ctx context.Context) error {
|
||||
_, err := sm.requests.Send(ctx, &smRequestRestartIMAP{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (sm *Service) RestartSMTP(ctx context.Context) error {
|
||||
_, err := sm.requests.Send(ctx, &smRequestRestartSMTP{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (sm *Service) AddIMAPUser(
|
||||
ctx context.Context,
|
||||
connector connector.Connector,
|
||||
addrID string,
|
||||
idProvider imapservice.GluonIDProvider,
|
||||
syncStateProvider imapservice.SyncStateProvider,
|
||||
) error {
|
||||
_, err := sm.requests.Send(ctx, &smRequestAddIMAPUser{
|
||||
connector: connector,
|
||||
addrID: addrID,
|
||||
idProvider: idProvider,
|
||||
syncStateProvider: syncStateProvider,
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (sm *Service) SetGluonDir(ctx context.Context, gluonDir string) error {
|
||||
_, err := sm.requests.Send(ctx, &smRequestSetGluonDir{
|
||||
dir: gluonDir,
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (sm *Service) RemoveIMAPUser(ctx context.Context, deleteData bool, provider imapservice.GluonIDProvider, addrID ...string) error {
|
||||
_, err := sm.requests.Send(ctx, &smRequestRemoveIMAPUser{
|
||||
withData: deleteData,
|
||||
addrID: addrID,
|
||||
idProvider: provider,
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (sm *Service) AddSMTPAccount(ctx context.Context, service *bridgesmtp.Service) error {
|
||||
_, err := sm.requests.Send(ctx, &smRequestAddSMTPAccount{account: service})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (sm *Service) RemoveSMTPAccount(ctx context.Context, service *bridgesmtp.Service) error {
|
||||
_, err := sm.requests.Send(ctx, &smRequestRemoveSMTPAccount{account: service})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (sm *Service) run(ctx context.Context, subscription events.Subscription) {
|
||||
eventSub := subscription.Add()
|
||||
defer subscription.Remove(eventSub)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
sm.handleClose(ctx)
|
||||
return
|
||||
|
||||
case evt := <-eventSub.GetChannel():
|
||||
switch evt.(type) {
|
||||
case events.ConnStatusDown:
|
||||
logrus.Info("Server Manager, network down stopping listeners")
|
||||
if err := sm.closeSMTPServer(ctx); err != nil {
|
||||
logrus.WithError(err).Error("Failed to close SMTP server")
|
||||
}
|
||||
|
||||
if err := sm.stopIMAPListener(ctx); err != nil {
|
||||
logrus.WithError(err)
|
||||
}
|
||||
case events.ConnStatusUp:
|
||||
logrus.Info("Server Manager, network up starting listeners")
|
||||
sm.handleLoadedUserCountChange(ctx)
|
||||
}
|
||||
|
||||
case request, ok := <-sm.requests.ReceiveCh():
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
switch r := request.Value().(type) {
|
||||
case *smRequestClose:
|
||||
sm.handleClose(ctx)
|
||||
request.Reply(ctx, nil, nil)
|
||||
return
|
||||
|
||||
case *smRequestRestartSMTP:
|
||||
err := sm.restartSMTP(ctx)
|
||||
request.Reply(ctx, nil, err)
|
||||
|
||||
case *smRequestRestartIMAP:
|
||||
err := sm.restartIMAP(ctx)
|
||||
request.Reply(ctx, nil, err)
|
||||
|
||||
case *smRequestAddIMAPUser:
|
||||
err := sm.handleAddIMAPUser(ctx, r.connector, r.addrID, r.idProvider, r.syncStateProvider)
|
||||
request.Reply(ctx, nil, err)
|
||||
if err == nil {
|
||||
sm.handleLoadedUserCountChange(ctx)
|
||||
}
|
||||
|
||||
case *smRequestRemoveIMAPUser:
|
||||
err := sm.handleRemoveIMAPUser(ctx, r.withData, r.idProvider, r.addrID...)
|
||||
request.Reply(ctx, nil, err)
|
||||
if err == nil {
|
||||
sm.handleLoadedUserCountChange(ctx)
|
||||
}
|
||||
|
||||
case *smRequestSetGluonDir:
|
||||
err := sm.handleSetGluonDir(ctx, r.dir)
|
||||
request.Reply(ctx, nil, err)
|
||||
|
||||
case *smRequestAddSMTPAccount:
|
||||
logrus.WithField("user", r.account.UserID()).Debug("Adding SMTP Account")
|
||||
sm.smtpAccounts.AddAccount(r.account)
|
||||
request.Reply(ctx, nil, nil)
|
||||
|
||||
case *smRequestRemoveSMTPAccount:
|
||||
logrus.WithField("user", r.account.UserID()).Debug("Removing SMTP Account")
|
||||
sm.smtpAccounts.RemoveAccount(r.account)
|
||||
request.Reply(ctx, nil, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sm *Service) handleLoadedUserCountChange(ctx context.Context) {
|
||||
logrus.Infof("Validating Listener State %v", sm.loadedUserCount)
|
||||
if sm.shouldStartServers() {
|
||||
if sm.imapListener == nil {
|
||||
if err := sm.serveIMAP(ctx); err != nil {
|
||||
logrus.WithError(err).Error("Failed to start IMAP server")
|
||||
}
|
||||
}
|
||||
|
||||
if sm.smtpListener == nil {
|
||||
if err := sm.restartSMTP(ctx); err != nil {
|
||||
logrus.WithError(err).Error("Failed to start SMTP server")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if sm.imapListener != nil {
|
||||
if err := sm.stopIMAPListener(ctx); err != nil {
|
||||
logrus.WithError(err).Error("Failed to stop IMAP server")
|
||||
}
|
||||
}
|
||||
|
||||
if sm.smtpListener != nil {
|
||||
if err := sm.closeSMTPServer(ctx); err != nil {
|
||||
logrus.WithError(err).Error("Failed to stop SMTP server")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sm *Service) handleClose(ctx context.Context) {
|
||||
sm.tasks.Cancel()
|
||||
|
||||
// Close the IMAP server.
|
||||
if err := sm.closeIMAPServer(ctx); err != nil {
|
||||
logrus.WithError(err).Error("Failed to close IMAP server")
|
||||
}
|
||||
|
||||
// Close the SMTP server.
|
||||
if err := sm.closeSMTPServer(ctx); err != nil {
|
||||
logrus.WithError(err).Error("Failed to close SMTP server")
|
||||
}
|
||||
}
|
||||
|
||||
func (sm *Service) handleAddIMAPUser(ctx context.Context,
|
||||
connector connector.Connector,
|
||||
addrID string,
|
||||
idProvider imapservice.GluonIDProvider,
|
||||
syncStateProvider imapservice.SyncStateProvider,
|
||||
) error {
|
||||
// Due to the many different error exits, performer user count change at this stage rather we split the incrementing
|
||||
// of users from the logic.
|
||||
err := sm.handleAddIMAPUserImpl(ctx, connector, addrID, idProvider, syncStateProvider)
|
||||
if err == nil {
|
||||
sm.loadedUserCount++
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (sm *Service) handleAddIMAPUserImpl(ctx context.Context,
|
||||
connector connector.Connector,
|
||||
addrID string,
|
||||
idProvider imapservice.GluonIDProvider,
|
||||
syncStateProvider imapservice.SyncStateProvider,
|
||||
) error {
|
||||
if sm.imapServer == nil {
|
||||
return fmt.Errorf("no imap server instance running")
|
||||
}
|
||||
|
||||
log := logrus.WithFields(logrus.Fields{
|
||||
"addrID": addrID,
|
||||
})
|
||||
log.Info("Adding user to imap server")
|
||||
|
||||
if gluonID, ok := idProvider.GetGluonID(addrID); ok {
|
||||
log.WithField("gluonID", gluonID).Info("Loading existing IMAP user")
|
||||
|
||||
// Load the user, checking whether the DB was newly created.
|
||||
isNew, err := sm.imapServer.LoadUser(ctx, connector, gluonID, idProvider.GluonKey())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load IMAP user: %w", err)
|
||||
}
|
||||
|
||||
if isNew {
|
||||
// If the DB was newly created, clear the sync status; gluon's DB was not found.
|
||||
logrus.Warn("IMAP user DB was newly created, clearing sync status")
|
||||
|
||||
// Remove the user from IMAP so we can clear the sync status.
|
||||
if err := sm.imapServer.RemoveUser(ctx, gluonID, false); err != nil {
|
||||
return fmt.Errorf("failed to remove IMAP user: %w", err)
|
||||
}
|
||||
|
||||
// Clear the sync status -- we need to resync all messages.
|
||||
if err := syncStateProvider.ClearSyncStatus(); err != nil {
|
||||
return fmt.Errorf("failed to clear sync status: %w", err)
|
||||
}
|
||||
|
||||
// Add the user back to the IMAP server.
|
||||
if isNew, err := sm.imapServer.LoadUser(ctx, connector, gluonID, idProvider.GluonKey()); err != nil {
|
||||
return fmt.Errorf("failed to add IMAP user: %w", err)
|
||||
} else if isNew {
|
||||
panic("IMAP user should already have a database")
|
||||
}
|
||||
} else if status := syncStateProvider.GetSyncStatus(); !status.HasLabels {
|
||||
// Otherwise, the DB already exists -- if the labels are not yet synced, we need to re-create the DB.
|
||||
if err := sm.imapServer.RemoveUser(ctx, gluonID, true); err != nil {
|
||||
return fmt.Errorf("failed to remove old IMAP user: %w", err)
|
||||
}
|
||||
|
||||
if err := idProvider.RemoveGluonID(addrID, gluonID); err != nil {
|
||||
return fmt.Errorf("failed to remove old IMAP user ID: %w", err)
|
||||
}
|
||||
|
||||
gluonID, err := sm.imapServer.AddUser(ctx, connector, idProvider.GluonKey())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add IMAP user: %w", err)
|
||||
}
|
||||
|
||||
if err := idProvider.SetGluonID(addrID, gluonID); err != nil {
|
||||
return fmt.Errorf("failed to set IMAP user ID: %w", err)
|
||||
}
|
||||
|
||||
log.WithField("gluonID", gluonID).Info("Re-created IMAP user")
|
||||
}
|
||||
} else {
|
||||
log.Info("Creating new IMAP user")
|
||||
|
||||
gluonID, err := sm.imapServer.AddUser(ctx, connector, idProvider.GluonKey())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add IMAP user: %w", err)
|
||||
}
|
||||
|
||||
if err := idProvider.SetGluonID(addrID, gluonID); err != nil {
|
||||
return fmt.Errorf("failed to set IMAP user ID: %w", err)
|
||||
}
|
||||
|
||||
log.WithField("gluonID", gluonID).Info("Created new IMAP user")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sm *Service) handleRemoveIMAPUser(ctx context.Context, withData bool, idProvider imapservice.GluonIDProvider, addrIDs ...string) error {
|
||||
if sm.imapServer == nil {
|
||||
return fmt.Errorf("no imap server instance running")
|
||||
}
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"withData": withData,
|
||||
"addresses": addrIDs,
|
||||
}).Debug("Removing IMAP user")
|
||||
|
||||
for _, addrID := range addrIDs {
|
||||
gluonID, ok := idProvider.GetGluonID(addrID)
|
||||
if !ok {
|
||||
logrus.Warnf("Could not find Gluon ID for addrID %v", addrID)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := sm.imapServer.RemoveUser(ctx, gluonID, withData); err != nil {
|
||||
return fmt.Errorf("failed to remove IMAP user: %w", err)
|
||||
}
|
||||
|
||||
if withData {
|
||||
if err := idProvider.RemoveGluonID(addrID, gluonID); err != nil {
|
||||
return fmt.Errorf("failed to remove IMAP user ID: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
sm.loadedUserCount--
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sm *Service) createIMAPServer(ctx context.Context) (*gluon.Server, error) {
|
||||
gluonDataDir, err := sm.imapSettings.DataDirectory()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get Gluon Database directory: %w", err)
|
||||
}
|
||||
|
||||
server, err := newIMAPServer(
|
||||
sm.imapSettings.CacheDirectory(),
|
||||
gluonDataDir,
|
||||
sm.imapSettings.Version(),
|
||||
sm.imapSettings.TLSConfig(),
|
||||
sm.reporter,
|
||||
sm.imapSettings.LogClient(),
|
||||
sm.imapSettings.LogServer(),
|
||||
sm.imapSettings.EventPublisher(),
|
||||
sm.tasks,
|
||||
sm.uidValidityGenerator,
|
||||
sm.panicHandler,
|
||||
)
|
||||
if err == nil {
|
||||
sm.eventPublisher.PublishEvent(ctx, events.IMAPServerCreated{})
|
||||
}
|
||||
|
||||
return server, err
|
||||
}
|
||||
|
||||
func (sm *Service) createSMTPServer() *smtp.Server {
|
||||
return newSMTPServer(sm.smtpAccounts, sm.smtpSettings)
|
||||
}
|
||||
|
||||
func (sm *Service) closeSMTPServer(ctx context.Context) error {
|
||||
// We close the listener ourselves even though it's also closed by smtpServer.Close().
|
||||
// This is because smtpServer.Serve() is called in a separate goroutine and might be executed
|
||||
// after we've already closed the server. However, go-smtp has a bug; it blocks on the listener
|
||||
// even after the server has been closed. So we close the listener ourselves to unblock it.
|
||||
|
||||
if sm.smtpListener != nil {
|
||||
logrus.Info("Closing SMTP Listener")
|
||||
if err := sm.smtpListener.Close(); err != nil {
|
||||
return fmt.Errorf("failed to close SMTP listener: %w", err)
|
||||
}
|
||||
|
||||
sm.smtpListener = nil
|
||||
}
|
||||
|
||||
if sm.smtpServer != nil {
|
||||
logrus.Info("Closing SMTP server")
|
||||
if err := sm.smtpServer.Close(); err != nil {
|
||||
logrus.WithError(err).Debug("Failed to close SMTP server (expected -- we close the listener ourselves)")
|
||||
}
|
||||
|
||||
sm.smtpServer = nil
|
||||
|
||||
sm.eventPublisher.PublishEvent(ctx, events.SMTPServerStopped{})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sm *Service) closeIMAPServer(ctx context.Context) error {
|
||||
if sm.imapListener != nil {
|
||||
logrus.Info("Closing IMAP Listener")
|
||||
|
||||
if err := sm.imapListener.Close(); err != nil {
|
||||
return fmt.Errorf("failed to close IMAP listener: %w", err)
|
||||
}
|
||||
|
||||
sm.imapListener = nil
|
||||
|
||||
sm.eventPublisher.PublishEvent(ctx, events.IMAPServerStopped{})
|
||||
}
|
||||
|
||||
if sm.imapServer != nil {
|
||||
logrus.Info("Closing IMAP server")
|
||||
if err := sm.imapServer.Close(ctx); err != nil {
|
||||
return fmt.Errorf("failed to close IMAP server: %w", err)
|
||||
}
|
||||
|
||||
sm.imapServer = nil
|
||||
|
||||
sm.eventPublisher.PublishEvent(ctx, events.IMAPServerClosed{})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sm *Service) restartIMAP(ctx context.Context) error {
|
||||
logrus.Info("Restarting IMAP server")
|
||||
|
||||
if sm.imapListener != nil {
|
||||
if err := sm.imapListener.Close(); err != nil {
|
||||
return fmt.Errorf("failed to close IMAP listener: %w", err)
|
||||
}
|
||||
|
||||
sm.imapListener = nil
|
||||
|
||||
sm.eventPublisher.PublishEvent(ctx, events.IMAPServerStopped{})
|
||||
}
|
||||
|
||||
if sm.shouldStartServers() {
|
||||
return sm.serveIMAP(ctx)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sm *Service) restartSMTP(ctx context.Context) error {
|
||||
logrus.Info("Restarting SMTP server")
|
||||
|
||||
if err := sm.closeSMTPServer(ctx); err != nil {
|
||||
return fmt.Errorf("failed to close SMTP: %w", err)
|
||||
}
|
||||
|
||||
sm.eventPublisher.PublishEvent(ctx, events.SMTPServerStopped{})
|
||||
|
||||
sm.smtpServer = newSMTPServer(sm.smtpAccounts, sm.smtpSettings)
|
||||
|
||||
if sm.shouldStartServers() {
|
||||
return sm.serveSMTP(ctx)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sm *Service) serveSMTP(ctx context.Context) error {
|
||||
port, err := func() (int, error) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"port": sm.smtpSettings.Port(),
|
||||
"ssl": sm.smtpSettings.UseSSL(),
|
||||
}).Info("Starting SMTP server")
|
||||
|
||||
smtpListener, err := newListener(sm.smtpSettings.Port(), sm.smtpSettings.UseSSL(), sm.smtpSettings.TLSConfig())
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to create SMTP listener: %w", err)
|
||||
}
|
||||
|
||||
sm.smtpListener = smtpListener
|
||||
|
||||
sm.tasks.Once(func(context.Context) {
|
||||
if err := sm.smtpServer.Serve(smtpListener); err != nil {
|
||||
logrus.WithError(err).Info("SMTP server stopped")
|
||||
}
|
||||
})
|
||||
|
||||
if err := sm.smtpSettings.SetPort(getPort(smtpListener.Addr())); err != nil {
|
||||
return 0, fmt.Errorf("failed to store SMTP port in vault: %w", err)
|
||||
}
|
||||
|
||||
return getPort(smtpListener.Addr()), nil
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
sm.eventPublisher.PublishEvent(ctx, events.SMTPServerError{
|
||||
Error: err,
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
sm.eventPublisher.PublishEvent(ctx, events.SMTPServerReady{
|
||||
Port: port,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sm *Service) serveIMAP(ctx context.Context) error {
|
||||
port, err := func() (int, error) {
|
||||
if sm.imapServer == nil {
|
||||
return 0, fmt.Errorf("no IMAP server instance running")
|
||||
}
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"port": sm.imapSettings.Port(),
|
||||
"ssl": sm.imapSettings.UseSSL(),
|
||||
}).Info("Starting IMAP server")
|
||||
|
||||
imapListener, err := newListener(sm.imapSettings.Port(), sm.imapSettings.UseSSL(), sm.imapSettings.TLSConfig())
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to create IMAP listener: %w", err)
|
||||
}
|
||||
|
||||
sm.imapListener = imapListener
|
||||
|
||||
if err := sm.imapServer.Serve(ctx, sm.imapListener); err != nil {
|
||||
return 0, fmt.Errorf("failed to serve IMAP: %w", err)
|
||||
}
|
||||
|
||||
if err := sm.imapSettings.SetPort(getPort(imapListener.Addr())); err != nil {
|
||||
return 0, fmt.Errorf("failed to store IMAP port in vault: %w", err)
|
||||
}
|
||||
|
||||
return getPort(imapListener.Addr()), nil
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
sm.eventPublisher.PublishEvent(ctx, events.IMAPServerError{
|
||||
Error: err,
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
sm.eventPublisher.PublishEvent(ctx, events.IMAPServerReady{
|
||||
Port: port,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sm *Service) stopIMAPListener(ctx context.Context) error {
|
||||
logrus.Info("Stopping IMAP listener")
|
||||
if sm.imapListener != nil {
|
||||
if err := sm.imapListener.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sm.imapListener = nil
|
||||
|
||||
sm.eventPublisher.PublishEvent(ctx, events.IMAPServerStopped{})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sm *Service) handleSetGluonDir(ctx context.Context, newGluonDir string) error {
|
||||
currentGluonDir := sm.imapSettings.CacheDirectory()
|
||||
newGluonDir = filepath.Join(newGluonDir, "gluon")
|
||||
if newGluonDir == currentGluonDir {
|
||||
return fmt.Errorf("new gluon dir is the same as the old one")
|
||||
}
|
||||
|
||||
if err := sm.closeIMAPServer(ctx); err != nil {
|
||||
return fmt.Errorf("failed to close IMAP: %w", err)
|
||||
}
|
||||
|
||||
sm.loadedUserCount = 0
|
||||
|
||||
if err := moveGluonCacheDir(sm.imapSettings, currentGluonDir, newGluonDir); err != nil {
|
||||
logrus.WithError(err).Error("failed to move GluonCacheDir")
|
||||
|
||||
if err := sm.imapSettings.SetCacheDirectory(currentGluonDir); err != nil {
|
||||
return fmt.Errorf("failed to revert GluonCacheDir: %w", err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
sm.telemetry.SetCacheLocation(newGluonDir)
|
||||
|
||||
imapServer, err := sm.createIMAPServer(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create new IMAP server: %w", err)
|
||||
}
|
||||
|
||||
sm.imapServer = imapServer
|
||||
|
||||
if sm.shouldStartServers() {
|
||||
if err := sm.serveIMAP(ctx); err != nil {
|
||||
return fmt.Errorf("failed to serve IMAP: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sm *Service) shouldStartServers() bool {
|
||||
return sm.loadedUserCount >= 1
|
||||
}
|
||||
|
||||
type smRequestClose struct{}
|
||||
|
||||
type smRequestRestartIMAP struct{}
|
||||
|
||||
type smRequestRestartSMTP struct{}
|
||||
|
||||
type smRequestAddIMAPUser struct {
|
||||
connector connector.Connector
|
||||
addrID string
|
||||
idProvider imapservice.GluonIDProvider
|
||||
syncStateProvider imapservice.SyncStateProvider
|
||||
}
|
||||
|
||||
type smRequestRemoveIMAPUser struct {
|
||||
withData bool
|
||||
addrID []string
|
||||
idProvider imapservice.GluonIDProvider
|
||||
}
|
||||
|
||||
type smRequestSetGluonDir struct {
|
||||
dir string
|
||||
}
|
||||
|
||||
type smRequestAddSMTPAccount struct {
|
||||
account *bridgesmtp.Service
|
||||
}
|
||||
|
||||
type smRequestRemoveSMTPAccount struct {
|
||||
account *bridgesmtp.Service
|
||||
}
|
||||
Reference in New Issue
Block a user