proton-bridge/internal/bridge/bridge.go

420 lines
10 KiB
Go
Raw Normal View History

// Package bridge implements the Bridge, which acts as the backend to the UI.
package bridge
import (
2022-08-26 15:00:21 +00:00
"context"
"crypto/tls"
"fmt"
2022-08-26 15:00:21 +00:00
"net"
"net/http"
"sync"
"time"
2020-05-25 13:34:18 +00:00
"github.com/Masterminds/semver/v3"
2022-08-26 15:00:21 +00:00
"github.com/ProtonMail/gluon"
"github.com/ProtonMail/gluon/watcher"
2022-05-31 13:54:04 +00:00
"github.com/ProtonMail/proton-bridge/v2/internal/constants"
2022-08-26 15:00:21 +00:00
"github.com/ProtonMail/proton-bridge/v2/internal/events"
"github.com/ProtonMail/proton-bridge/v2/internal/focus"
"github.com/ProtonMail/proton-bridge/v2/internal/user"
"github.com/ProtonMail/proton-bridge/v2/internal/vault"
"github.com/bradenaw/juniper/xslices"
"github.com/emersion/go-smtp"
"github.com/go-resty/resty/v2"
"github.com/sirupsen/logrus"
"gitlab.protontech.ch/go/liteapi"
)
2022-08-26 15:00:21 +00:00
type Bridge struct {
// vault holds bridge-specific data, such as preferences and known users (authorized or not).
vault *vault.Vault
2022-08-26 15:00:21 +00:00
// users holds authorized users.
users map[string]*user.User
2022-08-26 15:00:21 +00:00
// api manages user API clients.
api *liteapi.Manager
proxyCtl ProxyController
identifier Identifier
2022-08-26 15:00:21 +00:00
// watchers holds all registered event watchers.
watchers []*watcher.Watcher[events.Event]
watchersLock sync.RWMutex
2022-08-26 15:00:21 +00:00
// tlsConfig holds the bridge TLS config used by the IMAP and SMTP servers.
tlsConfig *tls.Config
2022-08-26 15:00:21 +00:00
// imapServer is the bridge's IMAP server.
imapServer *gluon.Server
imapListener net.Listener
2022-08-26 15:00:21 +00:00
// smtpServer is the bridge's SMTP server.
smtpServer *smtp.Server
smtpBackend *smtpBackend
2022-08-26 15:00:21 +00:00
// updater is the bridge's updater.
updater Updater
curVersion *semver.Version
updateCheckCh chan struct{}
2020-05-25 13:34:18 +00:00
2022-08-26 15:00:21 +00:00
// focusService is used to raise the bridge window when needed.
focusService *focus.Service
2020-05-25 13:34:18 +00:00
2022-08-26 15:00:21 +00:00
// autostarter is the bridge's autostarter.
autostarter Autostarter
2022-08-26 15:00:21 +00:00
// locator is the bridge's locator.
locator Locator
2020-05-25 13:34:18 +00:00
2022-08-26 15:00:21 +00:00
// errors contains errors encountered during startup.
errors []error
// These control the bridge's IMAP and SMTP logging behaviour.
logIMAPClient bool
logIMAPServer bool
logSMTP bool
// stopCh is used to stop ongoing goroutines when the bridge is closed.
stopCh chan struct{}
2020-05-25 13:34:18 +00:00
}
2022-08-26 15:00:21 +00:00
// New creates a new bridge.
func New(
locator Locator, // the locator to provide paths to store data
vault *vault.Vault, // the bridge's encrypted data store
2022-10-11 17:40:28 +00:00
autostarter Autostarter, // the autostarter to manage autostart settings
updater Updater, // the updater to fetch and install updates
curVersion *semver.Version, // the current version of the bridge
apiURL string, // the URL of the API to use
cookieJar http.CookieJar, // the cookie jar to use
identifier Identifier, // the identifier to keep track of the user agent
tlsReporter TLSReporter, // the TLS reporter to report TLS errors
roundTripper http.RoundTripper, // the round tripper to use for API requests
proxyCtl ProxyController, // the DoH controller
2022-10-11 17:40:28 +00:00
logIMAPClient, logIMAPServer bool, // whether to log IMAP client/server activity
logSMTP bool, // whether to log SMTP activity
2022-08-26 15:00:21 +00:00
) (*Bridge, error) {
api := liteapi.New(
liteapi.WithHostURL(apiURL),
liteapi.WithAppVersion(constants.AppVersion(curVersion.Original())),
2022-08-26 15:00:21 +00:00
liteapi.WithCookieJar(cookieJar),
liteapi.WithTransport(roundTripper),
2022-08-26 15:00:21 +00:00
)
2022-08-26 15:00:21 +00:00
tlsConfig, err := loadTLSConfig(vault)
if err != nil {
return nil, fmt.Errorf("failed to load TLS config: %w", err)
2020-05-25 13:34:18 +00:00
}
gluonDir, err := getGluonDir(vault)
if err != nil {
return nil, fmt.Errorf("failed to get Gluon directory: %w", err)
}
2022-08-26 15:00:21 +00:00
smtpBackend, err := newSMTPBackend()
if err != nil {
return nil, fmt.Errorf("failed to create SMTP backend: %w", err)
}
imapServer, err := newIMAPServer(gluonDir, curVersion, tlsConfig, logIMAPClient, logIMAPServer)
if err != nil {
return nil, fmt.Errorf("failed to create IMAP server: %w", err)
}
2022-08-26 15:00:21 +00:00
focusService, err := focus.NewService()
if err != nil {
return nil, fmt.Errorf("failed to create focus service: %w", err)
}
bridge := newBridge(
2022-10-11 17:40:28 +00:00
locator,
vault,
2022-10-11 17:40:28 +00:00
autostarter,
updater,
curVersion,
api,
identifier,
2022-10-11 17:40:28 +00:00
proxyCtl,
tlsConfig,
imapServer,
smtpBackend,
focusService,
logIMAPClient,
logIMAPServer,
logSMTP,
)
if err := bridge.init(tlsReporter); err != nil {
return nil, fmt.Errorf("failed to initialize bridge: %w", err)
}
return bridge, nil
}
func newBridge(
2022-10-11 17:40:28 +00:00
locator Locator,
vault *vault.Vault,
2022-10-11 17:40:28 +00:00
autostarter Autostarter,
updater Updater,
curVersion *semver.Version,
api *liteapi.Manager,
identifier Identifier,
2022-10-11 17:40:28 +00:00
proxyCtl ProxyController,
tlsConfig *tls.Config,
imapServer *gluon.Server,
smtpBackend *smtpBackend,
focusService *focus.Service,
logIMAPClient, logIMAPServer, logSMTP bool,
) *Bridge {
return &Bridge{
2022-08-26 15:00:21 +00:00
vault: vault,
users: make(map[string]*user.User),
api: api,
proxyCtl: proxyCtl,
identifier: identifier,
2022-08-26 15:00:21 +00:00
tlsConfig: tlsConfig,
imapServer: imapServer,
smtpServer: newSMTPServer(smtpBackend, tlsConfig, logSMTP),
2022-08-26 15:00:21 +00:00
smtpBackend: smtpBackend,
updater: updater,
curVersion: curVersion,
updateCheckCh: make(chan struct{}, 1),
focusService: focusService,
autostarter: autostarter,
locator: locator,
logIMAPClient: logIMAPClient,
logIMAPServer: logIMAPServer,
logSMTP: logSMTP,
stopCh: make(chan struct{}),
}
}
func (bridge *Bridge) init(tlsReporter TLSReporter) error {
if bridge.vault.GetProxyAllowed() {
bridge.proxyCtl.AllowProxy()
} else {
bridge.proxyCtl.DisallowProxy()
}
bridge.api.AddStatusObserver(func(status liteapi.Status) {
switch {
case status == liteapi.StatusUp:
go bridge.onStatusUp()
case status == liteapi.StatusDown:
go bridge.onStatusDown()
}
2022-08-26 15:00:21 +00:00
})
bridge.api.AddErrorHandler(liteapi.AppVersionBadCode, func() {
2022-08-26 15:00:21 +00:00
bridge.publish(events.UpdateForced{})
})
bridge.api.AddPreRequestHook(func(_ *resty.Client, req *resty.Request) error {
2022-08-26 15:00:21 +00:00
req.SetHeader("User-Agent", bridge.identifier.GetUserAgent())
return nil
})
if err := bridge.loadUsers(); err != nil {
return fmt.Errorf("failed to load users: %w", err)
}
2022-08-26 15:00:21 +00:00
go func() {
for range tlsReporter.GetTLSIssueCh() {
bridge.publish(events.TLSIssue{})
}
2022-08-26 15:00:21 +00:00
}()
go func() {
for range bridge.focusService.GetRaiseCh() {
2022-08-26 15:00:21 +00:00
bridge.publish(events.Raise{})
}
}()
go func() {
for event := range bridge.imapServer.AddWatcher() {
2022-08-26 15:00:21 +00:00
bridge.handleIMAPEvent(event)
}
}()
if err := bridge.serveIMAP(); err != nil {
bridge.PushError(ErrServeIMAP)
}
2022-08-26 15:00:21 +00:00
if err := bridge.serveSMTP(); err != nil {
bridge.PushError(ErrServeSMTP)
}
2022-08-26 15:00:21 +00:00
if err := bridge.watchForUpdates(); err != nil {
bridge.PushError(ErrWatchUpdates)
}
return nil
}
2022-08-26 15:00:21 +00:00
// GetEvents returns a channel of events of the given type.
// If no types are supplied, all events are returned.
func (bridge *Bridge) GetEvents(ofType ...events.Event) (<-chan events.Event, func()) {
newWatcher := bridge.addWatcher(ofType...)
return newWatcher.GetChannel(), func() { bridge.remWatcher(newWatcher) }
}
2022-08-26 15:00:21 +00:00
func (bridge *Bridge) FactoryReset(ctx context.Context) error {
panic("TODO")
}
2022-08-26 15:00:21 +00:00
func (bridge *Bridge) PushError(err error) {
bridge.errors = append(bridge.errors, err)
}
2022-08-26 15:00:21 +00:00
func (bridge *Bridge) GetErrors() []error {
return bridge.errors
}
2022-08-26 15:00:21 +00:00
func (bridge *Bridge) Close(ctx context.Context) error {
// Stop ongoing operations such as connectivity checks.
close(bridge.stopCh)
2022-09-28 09:29:33 +00:00
2022-08-26 15:00:21 +00:00
// Close the IMAP server.
if err := bridge.closeIMAP(ctx); err != nil {
logrus.WithError(err).Error("Failed to close IMAP server")
}
2022-08-26 15:00:21 +00:00
// Close the SMTP server.
if err := bridge.closeSMTP(); err != nil {
logrus.WithError(err).Error("Failed to close SMTP server")
}
2022-08-26 15:00:21 +00:00
// Close all users.
for _, user := range bridge.users {
if err := user.Close(); err != nil {
2022-08-26 15:00:21 +00:00
logrus.WithError(err).Error("Failed to close user")
}
}
2022-08-26 15:00:21 +00:00
// Close the focus service.
bridge.focusService.Close()
// Save the last version of bridge that was run.
if err := bridge.vault.SetLastVersion(bridge.curVersion); err != nil {
logrus.WithError(err).Error("Failed to save last version")
}
return nil
}
2022-08-26 15:00:21 +00:00
func (bridge *Bridge) publish(event events.Event) {
bridge.watchersLock.RLock()
defer bridge.watchersLock.RUnlock()
for _, watcher := range bridge.watchers {
if watcher.IsWatching(event) {
if ok := watcher.Send(event); !ok {
logrus.WithField("event", event).Warn("Failed to send event to watcher")
}
}
}
}
2022-08-26 15:00:21 +00:00
func (bridge *Bridge) addWatcher(ofType ...events.Event) *watcher.Watcher[events.Event] {
bridge.watchersLock.Lock()
defer bridge.watchersLock.Unlock()
2022-08-26 15:00:21 +00:00
newWatcher := watcher.New(ofType...)
2022-08-26 15:00:21 +00:00
bridge.watchers = append(bridge.watchers, newWatcher)
return newWatcher
}
2022-08-26 15:00:21 +00:00
func (bridge *Bridge) remWatcher(oldWatcher *watcher.Watcher[events.Event]) {
bridge.watchersLock.Lock()
defer bridge.watchersLock.Unlock()
bridge.watchers = xslices.Filter(bridge.watchers, func(other *watcher.Watcher[events.Event]) bool {
return other != oldWatcher
})
}
func (bridge *Bridge) onStatusUp() {
bridge.publish(events.ConnStatusUp{})
if err := bridge.loadUsers(); err != nil {
logrus.WithError(err).Error("Failed to load users")
}
}
func (bridge *Bridge) onStatusDown() {
bridge.publish(events.ConnStatusDown{})
upCh, done := bridge.GetEvents(events.ConnStatusUp{})
defer done()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
backoff := time.Second
for {
select {
case <-upCh:
return
case <-bridge.stopCh:
return
case <-time.After(backoff):
if err := bridge.api.Ping(ctx); err != nil {
logrus.WithError(err).Debug("Failed to ping API")
}
}
if backoff < 30*time.Second {
backoff *= 2
}
}
}
2022-08-26 15:00:21 +00:00
func loadTLSConfig(vault *vault.Vault) (*tls.Config, error) {
cert, err := tls.X509KeyPair(vault.GetBridgeTLSCert(), vault.GetBridgeTLSKey())
if err != nil {
return nil, err
}
// TODO: Do we have to set MinVersion to tls.VersionTLS12?
2022-08-26 15:00:21 +00:00
return &tls.Config{
Certificates: []tls.Certificate{cert},
}, nil
}
2022-08-26 15:00:21 +00:00
func newListener(port int, useTLS bool, tlsConfig *tls.Config) (net.Listener, error) {
if useTLS {
tlsListener, err := tls.Listen("tcp", fmt.Sprintf(":%v", port), tlsConfig)
if err != nil {
return nil, err
}
2021-12-09 10:50:42 +00:00
2022-08-26 15:00:21 +00:00
return tlsListener, nil
}
2022-08-26 15:00:21 +00:00
netListener, err := net.Listen("tcp", fmt.Sprintf(":%v", port))
if err != nil {
return nil, err
}
2022-08-26 15:00:21 +00:00
return netListener, nil
}