GODT-1779: Remove go-imap

This commit is contained in:
James Houlahan
2022-08-26 17:00:21 +02:00
parent 3b0bc1ca15
commit 39433fe707
593 changed files with 12725 additions and 91626 deletions

File diff suppressed because it is too large Load Diff

View File

@ -72,7 +72,6 @@ service Bridge {
rpc IsAutomaticUpdateOn(google.protobuf.Empty) returns (google.protobuf.BoolValue);
// cache
rpc IsCacheOnDiskEnabled (google.protobuf.Empty) returns (google.protobuf.BoolValue);
rpc DiskCachePath(google.protobuf.Empty) returns (google.protobuf.StringValue);
rpc ChangeLocalCache(ChangeLocalCacheRequest) returns (google.protobuf.Empty);
@ -160,7 +159,6 @@ message LoginAbortRequest {
// Cache on disk related message
//**********************************************************
message ChangeLocalCacheRequest {
bool enableDiskCache = 1;
string diskCachePath = 2;
}

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc v3.21.3
// - protoc v3.21.7
// source: bridge.proto
package grpc
@ -64,7 +64,6 @@ type BridgeClient interface {
SetIsAutomaticUpdateOn(ctx context.Context, in *wrapperspb.BoolValue, opts ...grpc.CallOption) (*emptypb.Empty, error)
IsAutomaticUpdateOn(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.BoolValue, error)
// cache
IsCacheOnDiskEnabled(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.BoolValue, error)
DiskCachePath(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.StringValue, error)
ChangeLocalCache(ctx context.Context, in *ChangeLocalCacheRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
// mail
@ -425,15 +424,6 @@ func (c *bridgeClient) IsAutomaticUpdateOn(ctx context.Context, in *emptypb.Empt
return out, nil
}
func (c *bridgeClient) IsCacheOnDiskEnabled(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.BoolValue, error) {
out := new(wrapperspb.BoolValue)
err := c.cc.Invoke(ctx, "/grpc.Bridge/IsCacheOnDiskEnabled", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bridgeClient) DiskCachePath(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.StringValue, error) {
out := new(wrapperspb.StringValue)
err := c.cc.Invoke(ctx, "/grpc.Bridge/DiskCachePath", in, out, opts...)
@ -699,7 +689,6 @@ type BridgeServer interface {
SetIsAutomaticUpdateOn(context.Context, *wrapperspb.BoolValue) (*emptypb.Empty, error)
IsAutomaticUpdateOn(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error)
// cache
IsCacheOnDiskEnabled(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error)
DiskCachePath(context.Context, *emptypb.Empty) (*wrapperspb.StringValue, error)
ChangeLocalCache(context.Context, *ChangeLocalCacheRequest) (*emptypb.Empty, error)
// mail
@ -841,9 +830,6 @@ func (UnimplementedBridgeServer) SetIsAutomaticUpdateOn(context.Context, *wrappe
func (UnimplementedBridgeServer) IsAutomaticUpdateOn(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error) {
return nil, status.Errorf(codes.Unimplemented, "method IsAutomaticUpdateOn not implemented")
}
func (UnimplementedBridgeServer) IsCacheOnDiskEnabled(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error) {
return nil, status.Errorf(codes.Unimplemented, "method IsCacheOnDiskEnabled not implemented")
}
func (UnimplementedBridgeServer) DiskCachePath(context.Context, *emptypb.Empty) (*wrapperspb.StringValue, error) {
return nil, status.Errorf(codes.Unimplemented, "method DiskCachePath not implemented")
}
@ -1571,24 +1557,6 @@ func _Bridge_IsAutomaticUpdateOn_Handler(srv interface{}, ctx context.Context, d
return interceptor(ctx, in, info, handler)
}
func _Bridge_IsCacheOnDiskEnabled_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(emptypb.Empty)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BridgeServer).IsCacheOnDiskEnabled(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/grpc.Bridge/IsCacheOnDiskEnabled",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BridgeServer).IsCacheOnDiskEnabled(ctx, req.(*emptypb.Empty))
}
return interceptor(ctx, in, info, handler)
}
func _Bridge_DiskCachePath_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(emptypb.Empty)
if err := dec(in); err != nil {
@ -2139,10 +2107,6 @@ var Bridge_ServiceDesc = grpc.ServiceDesc{
MethodName: "IsAutomaticUpdateOn",
Handler: _Bridge_IsAutomaticUpdateOn_Handler,
},
{
MethodName: "IsCacheOnDiskEnabled",
Handler: _Bridge_IsCacheOnDiskEnabled_Handler,
},
{
MethodName: "DiskCachePath",
Handler: _Bridge_DiskCachePath_Handler,

View File

@ -0,0 +1,32 @@
// Copyright (c) 2022 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 grpc
import "github.com/bradenaw/juniper/xslices"
// isInternetStatus returns true iff the event is InternetStatus.
func (x *StreamEvent) isInternetStatus() bool {
appEvent := x.GetApp()
return (appEvent != nil) && (appEvent.GetInternetStatus() != nil)
}
// filterOutInternetStatusEvents return a copy of the events list where all internet connection events have been removed.
func filterOutInternetStatusEvents(events []*StreamEvent) []*StreamEvent {
return xslices.Filter(events, func(event *StreamEvent) bool { return !event.isInternetStatus() })
}

View File

@ -21,34 +21,29 @@ package grpc
import (
"context"
cryptotls "crypto/tls"
"crypto/tls"
"errors"
"fmt"
"net"
"path/filepath"
"strings"
"sync"
"time"
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
"github.com/ProtonMail/proton-bridge/v2/internal/config/tls"
"github.com/ProtonMail/proton-bridge/v2/internal/certs"
"github.com/ProtonMail/proton-bridge/v2/internal/crash"
"github.com/ProtonMail/proton-bridge/v2/internal/events"
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/types"
"github.com/ProtonMail/proton-bridge/v2/internal/locations"
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
"github.com/ProtonMail/proton-bridge/v2/internal/users"
"github.com/ProtonMail/proton-bridge/v2/pkg/keychain"
"github.com/ProtonMail/proton-bridge/v2/pkg/listener"
"github.com/ProtonMail/proton-bridge/v2/pkg/pmapi"
"github.com/ProtonMail/proton-bridge/v2/internal/vault"
"github.com/ProtonMail/proton-bridge/v2/pkg/restarter"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"gitlab.protontech.ch/go/liteapi"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
codes "google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
status "google.golang.org/grpc/status"
)
const (
@ -59,6 +54,7 @@ const (
// Service is the RPC service struct.
type Service struct { // nolint:structcheck
UnimplementedBridgeServer
grpcServer *grpc.Server // the gGRPC server
listener net.Listener
eventStreamCh chan *StreamEvent
@ -66,99 +62,87 @@ type Service struct { // nolint:structcheck
eventQueue []*StreamEvent
eventQueueMutex sync.Mutex
panicHandler types.PanicHandler
eventListener listener.Listener
updater types.Updater
updateCheckMutex sync.Mutex
bridge types.Bridger
restarter types.Restarter
showOnStartup bool
authClient pmapi.Client
auth *pmapi.Auth
password []byte
newVersionInfo updater.VersionInfo
panicHandler *crash.Handler
restarter *restarter.Restarter
bridge *bridge.Bridge
newVersionInfo updater.VersionInfo
log *logrus.Entry
initializing sync.WaitGroup
initializationDone sync.Once
firstTimeAutostart sync.Once
locations *locations.Locations
token string
pemCert string
showOnStartup bool
}
// NewService returns a new instance of the service.
func NewService(
showOnStartup bool,
panicHandler types.PanicHandler,
eventListener listener.Listener,
updater types.Updater,
bridge types.Bridger,
restarter types.Restarter,
panicHandler *crash.Handler,
restarter *restarter.Restarter,
locations *locations.Locations,
) *Service {
s := Service{
UnimplementedBridgeServer: UnimplementedBridgeServer{},
panicHandler: panicHandler,
eventListener: eventListener,
updater: updater,
bridge: bridge,
restarter: restarter,
showOnStartup: showOnStartup,
bridge *bridge.Bridge,
showOnStartup bool,
) (*Service, error) {
tlsConfig, certPEM, err := newTLSConfig()
if err != nil {
logrus.WithError(err).Panic("Could not generate gRPC TLS config")
}
listener, err := net.Listen("tcp", "127.0.0.1:0") // Port should be provided by the OS.
if err != nil {
logrus.WithError(err).Panic("Could not create gRPC listener")
}
token := uuid.NewString()
if path, err := saveGRPCServerConfigFile(locations, listener, token, certPEM); err != nil {
logrus.WithError(err).WithField("path", path).Panic("Could not write gRPC service config file")
} else {
logrus.WithField("path", path).Info("Successfully saved gRPC service config file")
}
s := &Service{
grpcServer: grpc.NewServer(
grpc.Creds(credentials.NewTLS(tlsConfig)),
grpc.UnaryInterceptor(newUnaryTokenValidator(token)),
grpc.StreamInterceptor(newStreamTokenValidator(token)),
),
listener: listener,
panicHandler: panicHandler,
restarter: restarter,
bridge: bridge,
log: logrus.WithField("pkg", "grpc"),
initializing: sync.WaitGroup{},
initializationDone: sync.Once{},
firstTimeAutostart: sync.Once{},
locations: locations,
token: uuid.NewString(),
showOnStartup: showOnStartup,
}
// Initializing.Done is only called sync.Once. Please keep the increment
// set to 1
// Initializing.Done is only called sync.Once. Please keep the increment set to 1
s.initializing.Add(1)
tlsConfig, pemCert, err := s.generateTLSConfig()
if err != nil {
s.log.WithError(err).Panic("Could not generate gRPC TLS config")
}
s.pemCert = string(pemCert)
// Initialize the autostart.
s.initAutostart()
s.grpcServer = grpc.NewServer(
grpc.Creds(credentials.NewTLS(tlsConfig)),
grpc.UnaryInterceptor(s.validateUnaryServerToken),
grpc.StreamInterceptor(s.validateStreamServerToken),
)
RegisterBridgeServer(s.grpcServer, &s)
s.listener, err = net.Listen("tcp", "127.0.0.1:0") // Port 0 means that the port is randomly picked by the system.
if err != nil {
s.log.WithError(err).Panic("Could not create gRPC listener")
}
if path, err := s.saveGRPCServerConfigFile(); err != nil {
s.log.WithError(err).WithField("path", path).Panic("Could not write gRPC service config file")
} else {
s.log.WithField("path", path).Info("Successfully saved gRPC service config file")
}
// Register the gRPC service implementation.
RegisterBridgeServer(s.grpcServer, s)
s.log.Info("gRPC server listening on ", s.listener.Addr())
return &s
return s, nil
}
// GODT-1507 Windows: autostart needs to be created after Qt is initialized.
// GODT-1206: if preferences file says it should be on enable it here.
// TO-DO GODT-1681 Autostart needs to be properly implement for gRPC approach.
func (s *Service) initAutostart() {
// GODT-1507 Windows: autostart needs to be created after Qt is initialized.
// GODT-1206: if preferences file says it should be on enable it here.
// TO-DO GODT-1681 Autostart needs to be properly implement for gRPC approach.
s.firstTimeAutostart.Do(func() {
shouldAutostartBeOn := s.bridge.GetBool(settings.AutostartKey)
if s.bridge.IsFirstStart() || shouldAutostartBeOn {
if err := s.bridge.EnableAutostart(); err != nil {
shouldAutostartBeOn := s.bridge.GetAutostart()
if s.bridge.GetFirstStart() || shouldAutostartBeOn {
if err := s.bridge.SetAutostart(true); err != nil {
s.log.WithField("prefs", shouldAutostartBeOn).WithError(err).Error("Failed to enable first autostart")
}
return
@ -168,7 +152,7 @@ func (s *Service) initAutostart() {
func (s *Service) Loop() error {
defer func() {
s.bridge.SetBool(settings.FirstStartGUIKey, false)
_ = s.bridge.SetFirstStartGUI(false)
}()
go func() {
@ -179,7 +163,7 @@ func (s *Service) Loop() error {
s.log.Info("Starting gRPC server")
if err := s.grpcServer.Serve(s.listener); err != nil {
s.log.WithError(err).Error("Error serving gRPC")
s.log.WithError(err).Error("Failed to serve gRPC")
return err
}
@ -212,140 +196,59 @@ func (s *Service) WaitUntilFrontendIsReady() {
s.initializing.Wait()
}
func (s *Service) watchEvents() { // nolint:funlen
if s.bridge.HasError(bridge.ErrLocalCacheUnavailable) {
_ = s.SendEvent(NewCacheErrorEvent(CacheErrorType_CACHE_UNAVAILABLE_ERROR))
}
func (s *Service) watchEvents() {
eventCh, done := s.bridge.GetEvents()
defer done()
errorCh := s.eventListener.ProvideChannel(events.ErrorEvent)
credentialsErrorCh := s.eventListener.ProvideChannel(events.CredentialsErrorEvent)
noActiveKeyForRecipientCh := s.eventListener.ProvideChannel(events.NoActiveKeyForRecipientEvent)
internetConnChangedCh := s.eventListener.ProvideChannel(events.InternetConnChangedEvent)
secondInstanceCh := s.eventListener.ProvideChannel(events.SecondInstanceEvent)
restartBridgeCh := s.eventListener.ProvideChannel(events.RestartBridgeEvent)
addressChangedCh := s.eventListener.ProvideChannel(events.AddressChangedEvent)
addressChangedLogoutCh := s.eventListener.ProvideChannel(events.AddressChangedLogoutEvent)
logoutCh := s.eventListener.ProvideChannel(events.LogoutEvent)
updateApplicationCh := s.eventListener.ProvideChannel(events.UpgradeApplicationEvent)
userChangedCh := s.eventListener.ProvideChannel(events.UserRefreshEvent)
certIssue := s.eventListener.ProvideChannel(events.TLSCertIssue)
// we forward events to the GUI/frontend via the gRPC event stream.
for {
select {
case errorDetails := <-errorCh:
if strings.Contains(errorDetails, "IMAP failed") {
_ = s.SendEvent(NewMailSettingsErrorEvent(MailSettingsErrorType_IMAP_PORT_ISSUE))
}
if strings.Contains(errorDetails, "SMTP failed") {
_ = s.SendEvent(NewMailSettingsErrorEvent(MailSettingsErrorType_SMTP_PORT_ISSUE))
}
case reason := <-credentialsErrorCh:
if reason == keychain.ErrMacKeychainRebuild.Error() {
_ = s.SendEvent(NewKeychainRebuildKeychainEvent())
continue
}
// TODO: Better error events.
for _, err := range s.bridge.GetErrors() {
switch {
case errors.Is(err, vault.ErrCorrupt):
_ = s.SendEvent(NewKeychainHasNoKeychainEvent())
case email := <-noActiveKeyForRecipientCh:
_ = s.SendEvent(NewMailNoActiveKeyForRecipientEvent(email))
case stat := <-internetConnChangedCh:
if stat == events.InternetOff {
_ = s.SendEvent(NewInternetStatusEvent(false))
}
if stat == events.InternetOn {
_ = s.SendEvent(NewInternetStatusEvent(true))
}
case <-secondInstanceCh:
_ = s.SendEvent(NewShowMainWindowEvent())
case <-restartBridgeCh:
_, _ = s.Restart(
metadata.AppendToOutgoingContext(context.Background(), serverTokenMetadataKey, s.token),
&emptypb.Empty{},
)
case address := <-addressChangedCh:
_ = s.SendEvent(NewMailAddressChangeEvent(address))
case address := <-addressChangedLogoutCh:
_ = s.SendEvent(NewMailAddressChangeLogoutEvent(address))
case userID := <-logoutCh:
user, err := s.bridge.GetUserInfo(userID)
if err != nil {
return
}
_ = s.SendEvent(NewUserDisconnectedEvent(user.Username))
case <-updateApplicationCh:
s.updateForce()
case userID := <-userChangedCh:
_ = s.SendEvent(NewUserChangedEvent(userID))
case <-certIssue:
_ = s.SendEvent(NewMailApiCertIssue())
case errors.Is(err, vault.ErrInsecure):
_ = s.SendEvent(NewKeychainHasNoKeychainEvent())
case errors.Is(err, bridge.ErrServeIMAP):
_ = s.SendEvent(NewMailSettingsErrorEvent(MailSettingsErrorType_IMAP_PORT_ISSUE))
case errors.Is(err, bridge.ErrServeSMTP):
_ = s.SendEvent(NewMailSettingsErrorEvent(MailSettingsErrorType_SMTP_PORT_ISSUE))
}
}
}
func (s *Service) loginAbort() {
s.loginClean()
}
for event := range eventCh {
switch event := event.(type) {
case events.ConnStatus:
_ = s.SendEvent(NewInternetStatusEvent(event.Status == liteapi.StatusUp))
func (s *Service) loginClean() {
s.auth = nil
s.authClient = nil
for i := range s.password {
s.password[i] = '\x00'
}
s.password = s.password[0:0]
}
case events.Raise:
_ = s.SendEvent(NewShowMainWindowEvent())
func (s *Service) finishLogin() {
defer s.loginClean()
case events.UserAddressCreated:
_ = s.SendEvent(NewMailAddressChangeEvent(event.Address))
if len(s.password) == 0 || s.auth == nil || s.authClient == nil {
s.log.
WithField("hasPass", len(s.password) != 0).
WithField("hasAuth", s.auth != nil).
WithField("hasClient", s.authClient != nil).
Error("Finish login: authentication incomplete")
case events.UserAddressChanged:
_ = s.SendEvent(NewMailAddressChangeEvent(event.Address))
_ = s.SendEvent(NewLoginError(LoginErrorType_TWO_PASSWORDS_ABORT, "Missing authentication, try again."))
return
}
case events.UserAddressDeleted:
_ = s.SendEvent(NewMailAddressChangeLogoutEvent(event.Address))
done := make(chan string)
s.eventListener.Add(events.UserChangeDone, done)
defer s.eventListener.Remove(events.UserChangeDone, done)
case events.UserChanged:
_ = s.SendEvent(NewUserChangedEvent(event.UserID))
userID, err := s.bridge.FinishLogin(s.authClient, s.auth, s.password)
if err != nil && err != users.ErrUserAlreadyConnected {
s.log.WithError(err).Errorf("Finish login failed")
_ = s.SendEvent(NewLoginError(LoginErrorType_TWO_PASSWORDS_ABORT, err.Error()))
return
}
// The user changed should be triggered by FinishLogin, but it is not
// guaranteed when this is going to happen. Therefor we should wait
// until we receive the signal from userChanged function.
s.waitForUserChangeDone(done, userID)
s.log.WithField("userID", userID).Debug("Login finished")
_ = s.SendEvent(NewLoginFinishedEvent(userID))
if err == users.ErrUserAlreadyConnected {
s.log.WithError(err).Error("User already logged in")
_ = s.SendEvent(NewLoginAlreadyLoggedInEvent(userID))
}
}
func (s *Service) waitForUserChangeDone(done <-chan string, userID string) {
for {
select {
case changedID := <-done:
if changedID == userID {
return
case events.UserDeauth:
if user, err := s.bridge.GetUserInfo(event.UserID); err != nil {
s.log.WithError(err).Error("Failed to get user info")
} else {
_ = s.SendEvent(NewUserDisconnectedEvent(user.Username))
}
case <-time.After(2 * time.Second):
s.log.WithField("ID", userID).Warning("Login finished but user not added within 2 seconds")
return
case events.TLSIssue:
_ = s.SendEvent(NewMailApiCertIssue())
case events.UpdateForced:
panic("TODO")
}
}
}
@ -354,103 +257,46 @@ func (s *Service) triggerReset() {
defer func() {
_ = s.SendEvent(NewResetFinishedEvent())
}()
s.bridge.FactoryReset()
if err := s.bridge.FactoryReset(context.Background()); err != nil {
s.log.WithError(err).Error("Failed to reset")
}
}
func (s *Service) checkUpdate() {
version, err := s.updater.Check()
func newTLSConfig() (*tls.Config, []byte, error) {
template, err := certs.NewTLSTemplate()
if err != nil {
s.log.WithError(err).Error("An error occurred while checking for updates")
s.SetVersion(updater.VersionInfo{})
return
}
s.SetVersion(version)
}
func (s *Service) updateForce() {
s.updateCheckMutex.Lock()
defer s.updateCheckMutex.Unlock()
s.checkUpdate()
_ = s.SendEvent(NewUpdateForceEvent(s.newVersionInfo.Version.String()))
}
func (s *Service) checkUpdateAndNotify(isReqFromUser bool) {
s.updateCheckMutex.Lock()
defer func() {
s.updateCheckMutex.Unlock()
_ = s.SendEvent(NewUpdateCheckFinishedEvent())
}()
s.checkUpdate()
version := s.newVersionInfo
if (version.Version == nil) || (version.Version.String() == "") {
if isReqFromUser {
_ = s.SendEvent(NewUpdateErrorEvent(UpdateErrorType_UPDATE_MANUAL_ERROR))
}
return
}
if !s.updater.IsUpdateApplicable(s.newVersionInfo) {
s.log.Info("No need to update")
if isReqFromUser {
_ = s.SendEvent(NewUpdateIsLatestVersionEvent())
}
} else if isReqFromUser {
s.NotifyManualUpdate(s.newVersionInfo, s.updater.CanInstall(s.newVersionInfo))
}
}
func (s *Service) installUpdate() {
s.updateCheckMutex.Lock()
defer s.updateCheckMutex.Unlock()
if !s.updater.CanInstall(s.newVersionInfo) {
s.log.Warning("Skipping update installation, current version too old")
_ = s.SendEvent(NewUpdateErrorEvent(UpdateErrorType_UPDATE_MANUAL_ERROR))
return
return nil, nil, fmt.Errorf("failed to create TLS template: %w", err)
}
if err := s.updater.InstallUpdate(s.newVersionInfo); err != nil {
if errors.Cause(err) == updater.ErrDownloadVerify {
s.log.WithError(err).Warning("Skipping update installation due to temporary error")
} else {
s.log.WithError(err).Error("The update couldn't be installed")
_ = s.SendEvent(NewUpdateErrorEvent(UpdateErrorType_UPDATE_MANUAL_ERROR))
}
return
}
_ = s.SendEvent(NewUpdateSilentRestartNeededEvent())
}
func (s *Service) generateTLSConfig() (tlsConfig *cryptotls.Config, pemCert []byte, err error) {
pemCert, pemKey, err := tls.NewPEMKeyPair()
certPEM, keyPEM, err := certs.GenerateCert(template)
if err != nil {
return nil, nil, errors.New("Could not get TLS config")
return nil, nil, fmt.Errorf("failed to generate cert: %w", err)
}
tlsConfig, err = tls.GetConfigFromPEMKeyPair(pemCert, pemKey)
cert, err := tls.X509KeyPair(certPEM, keyPEM)
if err != nil {
return nil, nil, errors.New("Could not get TLS config")
return nil, nil, fmt.Errorf("failed to load cert: %w", err)
}
tlsConfig.ClientAuth = cryptotls.NoClientCert // skip client auth if the certificate allow it.
return tlsConfig, pemCert, nil
return &tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.NoClientCert,
}, certPEM, nil
}
func (s *Service) saveGRPCServerConfigFile() (string, error) {
address, ok := s.listener.Addr().(*net.TCPAddr)
func saveGRPCServerConfigFile(locations *locations.Locations, listener net.Listener, token string, certPEM []byte) (string, error) {
address, ok := listener.Addr().(*net.TCPAddr)
if !ok {
return "", fmt.Errorf("could not retrieve gRPC service listener address")
}
sc := config{
Port: address.Port,
Cert: s.pemCert,
Token: s.token,
Cert: string(certPEM),
Token: token,
}
settingsPath, err := s.locations.ProvideSettingsPath()
settingsPath, err := locations.ProvideSettingsPath()
if err != nil {
return "", err
}
@ -461,7 +307,7 @@ func (s *Service) saveGRPCServerConfigFile() (string, error) {
}
// validateServerToken verify that the server token provided by the client is valid.
func (s *Service) validateServerToken(ctx context.Context) error {
func validateServerToken(ctx context.Context, wantToken string) error {
values, ok := metadata.FromIncomingContext(ctx)
if !ok {
return status.Error(codes.Unauthenticated, "missing server token")
@ -476,40 +322,31 @@ func (s *Service) validateServerToken(ctx context.Context) error {
return status.Error(codes.Unauthenticated, "more than one server token was provided")
}
if token[0] != s.token {
if token[0] != wantToken {
return status.Error(codes.Unauthenticated, "invalid server token")
}
return nil
}
// validateUnaryServerToken check the server token for every unary gRPC call.
func (s *Service) validateUnaryServerToken(
ctx context.Context,
req interface{},
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (resp interface{}, err error) {
if err := s.validateServerToken(ctx); err != nil {
return nil, err
}
// newUnaryTokenValidator checks the server token for every unary gRPC call.
func newUnaryTokenValidator(wantToken string) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
if err := validateServerToken(ctx, wantToken); err != nil {
return nil, err
}
return handler(ctx, req)
return handler(ctx, req)
}
}
// validateStreamServerToken check the server token for every gRPC stream request.
func (s *Service) validateStreamServerToken(
srv interface{},
ss grpc.ServerStream,
info *grpc.StreamServerInfo,
handler grpc.StreamHandler,
) error {
logEntry := s.log.WithField("FullMethod", info.FullMethod)
// newStreamTokenValidator checks the server token for every gRPC stream request.
func newStreamTokenValidator(wantToken string) grpc.StreamServerInterceptor {
return func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
if err := validateServerToken(stream.Context(), wantToken); err != nil {
return err
}
if err := s.validateServerToken(ss.Context()); err != nil {
logEntry.WithError(err).Error("Stream validator failed")
return err
return handler(srv, stream)
}
return handler(srv, ss)
}

View File

@ -23,15 +23,13 @@ import (
"runtime"
"github.com/Masterminds/semver/v3"
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
"github.com/ProtonMail/proton-bridge/v2/internal/constants"
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/theme"
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
"github.com/ProtonMail/proton-bridge/v2/pkg/keychain"
"github.com/ProtonMail/proton-bridge/v2/pkg/pmapi"
"github.com/ProtonMail/proton-bridge/v2/pkg/ports"
"github.com/sirupsen/logrus"
"golang.org/x/exp/maps"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
@ -116,7 +114,7 @@ func (s *Service) Quit(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empt
func (s *Service) Restart(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) {
s.log.Debug("Restart")
s.restarter.SetToRestart()
s.restarter.Set(true, false)
return s.Quit(ctx, empty)
}
@ -129,25 +127,19 @@ func (s *Service) ShowOnStartup(ctx context.Context, _ *emptypb.Empty) (*wrapper
func (s *Service) ShowSplashScreen(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.BoolValue, error) {
s.log.Debug("ShowSplashScreen")
if s.bridge.IsFirstStart() {
return wrapperspb.Bool(false), nil
}
ver, err := semver.NewVersion(s.bridge.GetLastVersion())
if err != nil {
s.log.WithError(err).WithField("last", s.bridge.GetLastVersion()).Debug("Cannot parse last version")
if s.bridge.GetFirstStart() {
return wrapperspb.Bool(false), nil
}
// Current splash screen contains update on rebranding. Therefore, it
// should be shown only if the last used version was less than 2.2.0.
return wrapperspb.Bool(ver.LessThan(semver.MustParse("2.2.0"))), nil
return wrapperspb.Bool(s.bridge.GetLastVersion().LessThan(semver.MustParse("2.2.0"))), nil
}
func (s *Service) IsFirstGuiStart(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.BoolValue, error) {
s.log.Debug("IsFirstGuiStart")
return wrapperspb.Bool(s.bridge.GetBool(settings.FirstStartGUIKey)), nil
return wrapperspb.Bool(s.bridge.GetFirstStartGUI()), nil
}
func (s *Service) SetIsAutostartOn(ctx context.Context, isOn *wrapperspb.BoolValue) (*emptypb.Empty, error) {
@ -155,22 +147,16 @@ func (s *Service) SetIsAutostartOn(ctx context.Context, isOn *wrapperspb.BoolVal
defer func() { _ = s.SendEvent(NewToggleAutostartFinishedEvent()) }()
if isOn.Value == s.bridge.IsAutostartEnabled() {
if isOn.Value == s.bridge.GetAutostart() {
s.initAutostart()
return &emptypb.Empty{}, nil
}
var err error
if isOn.Value {
err = s.bridge.EnableAutostart()
} else {
err = s.bridge.DisableAutostart()
}
s.initAutostart()
if err != nil {
if err := s.bridge.SetAutostart(isOn.Value); err != nil {
s.log.WithField("makeItEnabled", isOn.Value).WithError(err).Error("Autostart change failed")
return nil, status.Errorf(codes.Internal, "failed to set autostart: %v", err)
}
return &emptypb.Empty{}, nil
@ -179,7 +165,7 @@ func (s *Service) SetIsAutostartOn(ctx context.Context, isOn *wrapperspb.BoolVal
func (s *Service) IsAutostartOn(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.BoolValue, error) {
s.log.Debug("IsAutostartOn")
return wrapperspb.Bool(s.bridge.IsAutostartEnabled()), nil
return wrapperspb.Bool(s.bridge.GetAutostart()), nil
}
func (s *Service) SetIsBetaEnabled(ctx context.Context, isEnabled *wrapperspb.BoolValue) (*emptypb.Empty, error) {
@ -190,8 +176,10 @@ func (s *Service) SetIsBetaEnabled(ctx context.Context, isEnabled *wrapperspb.Bo
channel = updater.EarlyChannel
}
s.bridge.SetUpdateChannel(channel)
s.checkUpdate()
if err := s.bridge.SetUpdateChannel(channel); err != nil {
s.log.WithError(err).Error("Failed to set update channel")
return nil, status.Errorf(codes.Internal, "failed to set update channel: %v", err)
}
return &emptypb.Empty{}, nil
}
@ -205,7 +193,10 @@ func (s *Service) IsBetaEnabled(ctx context.Context, _ *emptypb.Empty) (*wrapper
func (s *Service) SetIsAllMailVisible(ctx context.Context, isVisible *wrapperspb.BoolValue) (*emptypb.Empty, error) {
s.log.WithField("isVisible", isVisible.Value).Debug("SetIsAllMailVisible")
s.bridge.SetIsAllMailVisible(isVisible.Value)
if err := s.bridge.SetShowAllMail(isVisible.Value); err != nil {
s.log.WithError(err).Error("Failed to set show all mail")
return nil, status.Errorf(codes.Internal, "failed to set show all mail: %v", err)
}
return &emptypb.Empty{}, nil
}
@ -213,7 +204,7 @@ func (s *Service) SetIsAllMailVisible(ctx context.Context, isVisible *wrapperspb
func (s *Service) IsAllMailVisible(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.BoolValue, error) {
s.log.Debug("IsAllMailVisible")
return wrapperspb.Bool(s.bridge.IsAllMailVisible()), nil
return wrapperspb.Bool(s.bridge.GetShowAllMail()), nil
}
func (s *Service) GoOs(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.StringValue, error) {
@ -241,7 +232,7 @@ func (s *Service) Version(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.St
func (s *Service) LogsPath(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.StringValue, error) {
s.log.Debug("LogsPath")
path, err := s.bridge.ProvideLogsPath()
path, err := s.bridge.GetLogsPath()
if err != nil {
s.log.WithError(err).Error("Cannot determine logs path")
return nil, err
@ -275,7 +266,10 @@ func (s *Service) SetColorSchemeName(ctx context.Context, name *wrapperspb.Strin
return nil, status.Error(codes.NotFound, "Color scheme not available")
}
s.bridge.Set(settings.ColorScheme, name.Value)
if err := s.bridge.SetColorScheme(name.Value); err != nil {
s.log.WithError(err).Error("Failed to set color scheme")
return nil, status.Errorf(codes.Internal, "failed to set color scheme: %v", err)
}
return &emptypb.Empty{}, nil
}
@ -283,10 +277,13 @@ func (s *Service) SetColorSchemeName(ctx context.Context, name *wrapperspb.Strin
func (s *Service) ColorSchemeName(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.StringValue, error) {
s.log.Debug("ColorSchemeName")
current := s.bridge.Get(settings.ColorScheme)
current := s.bridge.GetColorScheme()
if !theme.IsAvailable(theme.Theme(current)) {
current = string(theme.DefaultTheme())
s.bridge.Set(settings.ColorScheme, current)
if err := s.bridge.SetColorScheme(current); err != nil {
s.log.WithError(err).Error("Failed to set color scheme")
return nil, status.Errorf(codes.Internal, "failed to set color scheme: %v", err)
}
}
return wrapperspb.String(current), nil
@ -312,6 +309,7 @@ func (s *Service) ReportBug(ctx context.Context, report *ReportBugRequest) (*emp
defer func() { _ = s.SendEvent(NewReportBugFinishedEvent()) }()
if err := s.bridge.ReportBug(
context.Background(),
report.OsType,
report.OsVersion,
report.Description,
@ -331,6 +329,7 @@ func (s *Service) ReportBug(ctx context.Context, report *ReportBugRequest) (*emp
return &emptypb.Empty{}, nil
}
/*
func (s *Service) ForceLauncher(ctx context.Context, launcher *wrapperspb.StringValue) (*emptypb.Empty, error) {
s.log.WithField("launcher", launcher.Value).Debug("ForceLauncher")
@ -350,6 +349,7 @@ func (s *Service) SetMainExecutable(ctx context.Context, exe *wrapperspb.StringV
}()
return &emptypb.Empty{}, nil
}
*/
func (s *Service) Login(ctx context.Context, login *LoginRequest) (*emptypb.Empty, error) {
s.log.WithField("username", login.Username).Debug("Login")
@ -357,135 +357,44 @@ func (s *Service) Login(ctx context.Context, login *LoginRequest) (*emptypb.Empt
go func() {
defer s.panicHandler.HandlePanic()
var err error
s.password, err = base64.StdEncoding.DecodeString(login.Password)
password, err := base64.StdEncoding.DecodeString(login.Password)
if err != nil {
s.log.WithError(err).Error("Cannot decode password")
_ = s.SendEvent(NewLoginError(LoginErrorType_USERNAME_PASSWORD_ERROR, "Cannot decode password"))
s.loginClean()
return
}
s.authClient, s.auth, err = s.bridge.Login(login.Username, s.password)
// TODO: Handle different error types!
// - bad credentials
// - bad proton plan
// - user already exists
userID, err := s.bridge.LoginUser(context.Background(), login.Username, string(password), nil, nil)
if err != nil {
if err == pmapi.ErrPasswordWrong {
// Remove error message since it is hardcoded in QML.
_ = s.SendEvent(NewLoginError(LoginErrorType_USERNAME_PASSWORD_ERROR, ""))
s.loginClean()
return
}
if err == pmapi.ErrPaidPlanRequired {
_ = s.SendEvent(NewLoginError(LoginErrorType_FREE_USER, ""))
s.loginClean()
return
}
_ = s.SendEvent(NewLoginError(LoginErrorType_USERNAME_PASSWORD_ERROR, err.Error()))
s.loginClean()
s.log.WithError(err).Error("Cannot login user")
_ = s.SendEvent(NewLoginError(LoginErrorType_USERNAME_PASSWORD_ERROR, "Cannot login user"))
return
}
if s.auth.HasTwoFactor() {
_ = s.SendEvent(NewLoginTfaRequestedEvent(login.Username))
return
}
if s.auth.HasMailboxPassword() {
_ = s.SendEvent(NewLoginTwoPasswordsRequestedEvent())
return
}
s.finishLogin()
}()
return &emptypb.Empty{}, nil
}
func (s *Service) Login2FA(ctx context.Context, login *LoginRequest) (*emptypb.Empty, error) {
s.log.WithField("username", login.Username).Debug("Login2FA")
go func() {
defer s.panicHandler.HandlePanic()
if s.auth == nil || s.authClient == nil {
s.log.Errorf("Login 2FA: authethication incomplete %p %p", s.auth, s.authClient)
_ = s.SendEvent(NewLoginError(LoginErrorType_TFA_ABORT, "Missing authentication, try again."))
s.loginClean()
return
}
twoFA, err := base64.StdEncoding.DecodeString(login.Password)
if err != nil {
s.log.WithError(err).Error("Cannot decode 2fa code")
_ = s.SendEvent(NewLoginError(LoginErrorType_USERNAME_PASSWORD_ERROR, "Cannot decode 2fa code"))
s.loginClean()
return
}
err = s.authClient.Auth2FA(context.Background(), string(twoFA))
if err == pmapi.ErrBad2FACodeTryAgain {
s.log.Warn("Login 2FA: retry 2fa")
_ = s.SendEvent(NewLoginError(LoginErrorType_TFA_ERROR, ""))
return
}
if err == pmapi.ErrBad2FACode {
s.log.Warn("Login 2FA: abort 2fa")
_ = s.SendEvent(NewLoginError(LoginErrorType_TFA_ABORT, ""))
s.loginClean()
return
}
if err != nil {
s.log.WithError(err).Warn("Login 2FA: failed.")
_ = s.SendEvent(NewLoginError(LoginErrorType_TFA_ABORT, err.Error()))
s.loginClean()
return
}
if s.auth.HasMailboxPassword() {
_ = s.SendEvent(NewLoginTwoPasswordsRequestedEvent())
return
}
s.finishLogin()
_ = s.SendEvent(NewLoginFinishedEvent(userID))
}()
return &emptypb.Empty{}, nil
}
func (s *Service) Login2Passwords(ctx context.Context, login *LoginRequest) (*emptypb.Empty, error) {
s.log.WithField("username", login.Username).Debug("Login2Passwords")
go func() {
defer s.panicHandler.HandlePanic()
var err error
s.password, err = base64.StdEncoding.DecodeString(login.Password)
if err != nil {
s.log.WithError(err).Error("Cannot decode mbox password")
_ = s.SendEvent(NewLoginError(LoginErrorType_USERNAME_PASSWORD_ERROR, "Cannot decode mbox password"))
s.loginClean()
return
}
s.finishLogin()
}()
return &emptypb.Empty{}, nil
func (s *Service) Login2FA(_ context.Context, login *LoginRequest) (*emptypb.Empty, error) {
panic("TODO")
}
func (s *Service) LoginAbort(ctx context.Context, loginAbort *LoginAbortRequest) (*emptypb.Empty, error) {
s.log.WithField("username", loginAbort.Username).Debug("LoginAbort")
go func() {
defer s.panicHandler.HandlePanic()
s.loginAbort()
}()
return &emptypb.Empty{}, nil
func (s *Service) Login2Passwords(_ context.Context, login *LoginRequest) (*emptypb.Empty, error) {
panic("TODO")
}
func (s *Service) CheckUpdate(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
func (s *Service) LoginAbort(_ context.Context, loginAbort *LoginAbortRequest) (*emptypb.Empty, error) {
panic("TODO")
}
/*
func (s *Service) CheckUpdate(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
s.log.Debug("CheckUpdate")
go func() {
@ -507,21 +416,20 @@ func (s *Service) InstallUpdate(ctx context.Context, _ *emptypb.Empty) (*emptypb
return &emptypb.Empty{}, nil
}
*/
func (s *Service) SetIsAutomaticUpdateOn(ctx context.Context, isOn *wrapperspb.BoolValue) (*emptypb.Empty, error) {
s.log.WithField("isOn", isOn.Value).Debug("SetIsAutomaticUpdateOn")
currentlyOn := s.bridge.GetBool(settings.AutoUpdateKey)
currentlyOn := s.bridge.GetAutoUpdate()
if currentlyOn == isOn.Value {
return &emptypb.Empty{}, nil
}
s.bridge.SetBool(settings.AutoUpdateKey, isOn.Value)
go func() {
defer s.panicHandler.HandlePanic()
s.checkUpdateAndNotify(false)
}()
if err := s.bridge.SetAutoUpdate(isOn.Value); err != nil {
s.log.WithError(err).Error("Failed to set auto update")
return nil, status.Errorf(codes.Internal, "failed to set auto update: %v", err)
}
return &emptypb.Empty{}, nil
}
@ -529,51 +437,21 @@ func (s *Service) SetIsAutomaticUpdateOn(ctx context.Context, isOn *wrapperspb.B
func (s *Service) IsAutomaticUpdateOn(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.BoolValue, error) {
s.log.Debug("IsAutomaticUpdateOn")
return wrapperspb.Bool(s.bridge.GetBool(settings.AutoUpdateKey)), nil
}
func (s *Service) IsCacheOnDiskEnabled(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.BoolValue, error) {
s.log.Debug("IsCacheOnDiskEnabled")
return wrapperspb.Bool(s.bridge.GetBool(settings.CacheEnabledKey)), nil
return wrapperspb.Bool(s.bridge.GetAutoUpdate()), nil
}
func (s *Service) DiskCachePath(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.StringValue, error) {
s.log.Debug("DiskCachePath")
return wrapperspb.String(s.bridge.Get(settings.CacheLocationKey)), nil
return wrapperspb.String(s.bridge.GetGluonDir()), nil
}
func (s *Service) ChangeLocalCache(ctx context.Context, change *ChangeLocalCacheRequest) (*emptypb.Empty, error) {
s.log.WithField("enableDiskCache", change.EnableDiskCache).
WithField("diskCachePath", change.DiskCachePath).
Debug("DiskCachePath")
s.log.WithField("diskCachePath", change.DiskCachePath).Debug("DiskCachePath")
restart := false
defer func(willRestart *bool) {
_ = s.SendEvent(NewCacheChangeLocalCacheFinishedEvent(*willRestart))
if *willRestart {
_, _ = s.Restart(ctx, &emptypb.Empty{})
}
}(&restart)
if change.EnableDiskCache != s.bridge.GetBool(settings.CacheEnabledKey) {
if change.EnableDiskCache {
if err := s.bridge.EnableCache(); err != nil {
s.log.WithError(err).Error("Cannot enable disk cache")
} else {
restart = true
_ = s.SendEvent(NewIsCacheOnDiskEnabledChanged(s.bridge.GetBool(settings.CacheEnabledKey)))
}
} else {
if err := s.bridge.DisableCache(); err != nil {
s.log.WithError(err).Error("Cannot disable disk cache")
} else {
restart = true
_ = s.SendEvent(NewIsCacheOnDiskEnabledChanged(s.bridge.GetBool(settings.CacheEnabledKey)))
}
}
}
defer func() {
_ = s.SendEvent(NewCacheChangeLocalCacheFinishedEvent(false))
}()
path := change.DiskCachePath
//goland:noinspection GoBoolExpressions
@ -581,16 +459,14 @@ func (s *Service) ChangeLocalCache(ctx context.Context, change *ChangeLocalCache
path = path[1:]
}
if change.EnableDiskCache && path != s.bridge.Get(settings.CacheLocationKey) {
if err := s.bridge.MigrateCache(s.bridge.Get(settings.CacheLocationKey), path); err != nil {
if path != s.bridge.GetGluonDir() {
if err := s.bridge.SetGluonDir(ctx, path); err != nil {
s.log.WithError(err).Error("The local cache location could not be changed.")
_ = s.SendEvent(NewCacheErrorEvent(CacheErrorType_CACHE_CANT_MOVE_ERROR))
return &emptypb.Empty{}, nil
}
s.bridge.Set(settings.CacheLocationKey, path)
restart = true
_ = s.SendEvent(NewDiskCachePathChanged(s.bridge.Get(settings.CacheLocationKey)))
_ = s.SendEvent(NewDiskCachePathChanged(s.bridge.GetGluonDir()))
}
_ = s.SendEvent(NewCacheLocationChangeSuccessEvent())
@ -601,7 +477,10 @@ func (s *Service) ChangeLocalCache(ctx context.Context, change *ChangeLocalCache
func (s *Service) SetIsDoHEnabled(ctx context.Context, isEnabled *wrapperspb.BoolValue) (*emptypb.Empty, error) {
s.log.WithField("isEnabled", isEnabled.Value).Debug("SetIsDohEnabled")
s.bridge.SetProxyAllowed(isEnabled.Value)
if err := s.bridge.SetProxyAllowed(isEnabled.Value); err != nil {
s.log.WithError(err).Error("Failed to set DoH")
return nil, status.Errorf(codes.Internal, "failed to set DoH: %v", err)
}
return &emptypb.Empty{}, nil
}
@ -615,13 +494,14 @@ func (s *Service) IsDoHEnabled(ctx context.Context, _ *emptypb.Empty) (*wrappers
func (s *Service) SetUseSslForSmtp(ctx context.Context, useSsl *wrapperspb.BoolValue) (*emptypb.Empty, error) { //nolint:revive,stylecheck
s.log.WithField("useSsl", useSsl.Value).Debug("SetUseSslForSmtp")
if s.bridge.GetBool(settings.SMTPSSLKey) == useSsl.Value {
if s.bridge.GetSMTPSSL() == useSsl.Value {
return &emptypb.Empty{}, nil
}
s.bridge.SetBool(settings.SMTPSSLKey, useSsl.Value)
defer func() { _, _ = s.Restart(ctx, &emptypb.Empty{}) }()
if err := s.bridge.SetSMTPSSL(useSsl.Value); err != nil {
s.log.WithError(err).Error("Failed to set SMTP SSL")
return nil, status.Errorf(codes.Internal, "failed to set SMTP SSL: %v", err)
}
return &emptypb.Empty{}, s.SendEvent(NewMailSettingsUseSslForSmtpFinishedEvent())
}
@ -629,34 +509,39 @@ func (s *Service) SetUseSslForSmtp(ctx context.Context, useSsl *wrapperspb.BoolV
func (s *Service) UseSslForSmtp(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.BoolValue, error) { //nolint:revive,stylecheck
s.log.Debug("UseSslForSmtp")
return wrapperspb.Bool(s.bridge.GetBool(settings.SMTPSSLKey)), nil
return wrapperspb.Bool(s.bridge.GetSMTPSSL()), nil
}
func (s *Service) Hostname(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.StringValue, error) {
s.log.Debug("Hostname")
return wrapperspb.String(bridge.Host), nil
return wrapperspb.String(constants.Host), nil
}
func (s *Service) ImapPort(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.Int32Value, error) {
s.log.Debug("ImapPort")
return wrapperspb.Int32(int32(s.bridge.GetInt(settings.IMAPPortKey))), nil
return wrapperspb.Int32(int32(s.bridge.GetIMAPPort())), nil
}
func (s *Service) SmtpPort(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.Int32Value, error) { //nolint:revive,stylecheck
s.log.Debug("SmtpPort")
return wrapperspb.Int32(int32(s.bridge.GetInt(settings.SMTPPortKey))), nil
return wrapperspb.Int32(int32(s.bridge.GetSMTPPort())), nil
}
func (s *Service) ChangePorts(ctx context.Context, ports *ChangePortsRequest) (*emptypb.Empty, error) {
s.log.WithField("imapPort", ports.ImapPort).WithField("smtpPort", ports.SmtpPort).Debug("ChangePorts")
s.bridge.SetInt(settings.IMAPPortKey, int(ports.ImapPort))
s.bridge.SetInt(settings.SMTPPortKey, int(ports.SmtpPort))
if err := s.bridge.SetIMAPPort(int(ports.ImapPort)); err != nil {
s.log.WithError(err).Error("Failed to set IMAP port")
return nil, status.Errorf(codes.Internal, "failed to set IMAP port: %v", err)
}
defer func() { _, _ = s.Restart(ctx, &emptypb.Empty{}) }()
if err := s.bridge.SetSMTPPort(int(ports.SmtpPort)); err != nil {
s.log.WithError(err).Error("Failed to set SMTP port")
return nil, status.Errorf(codes.Internal, "failed to set SMTP port: %v", err)
}
return &emptypb.Empty{}, s.SendEvent(NewMailSettingsChangePortFinishedEvent())
}
@ -670,12 +555,7 @@ func (s *Service) IsPortFree(ctx context.Context, port *wrapperspb.Int32Value) (
func (s *Service) AvailableKeychains(ctx context.Context, _ *emptypb.Empty) (*AvailableKeychainsResponse, error) {
s.log.Debug("AvailableKeychains")
keychains := make([]string, 0, len(keychain.Helpers))
for chain := range keychain.Helpers {
keychains = append(keychains, chain)
}
return &AvailableKeychainsResponse{Keychains: keychains}, nil
return &AvailableKeychainsResponse{Keychains: maps.Keys(keychain.Helpers)}, nil
}
func (s *Service) SetCurrentKeychain(ctx context.Context, keychain *wrapperspb.StringValue) (*emptypb.Empty, error) {
@ -684,11 +564,20 @@ func (s *Service) SetCurrentKeychain(ctx context.Context, keychain *wrapperspb.S
defer func() { _, _ = s.Restart(ctx, &emptypb.Empty{}) }()
defer func() { _ = s.SendEvent(NewKeychainChangeKeychainFinishedEvent()) }()
if s.bridge.GetKeychainApp() == keychain.Value {
helper, err := s.bridge.GetKeychainApp()
if err != nil {
s.log.WithError(err).Error("Failed to get current keychain")
return nil, status.Errorf(codes.Internal, "failed to get current keychain: %v", err)
}
if helper == keychain.Value {
return &emptypb.Empty{}, nil
}
s.bridge.SetKeychainApp(keychain.Value)
if err := s.bridge.SetKeychainApp(keychain.Value); err != nil {
s.log.WithError(err).Error("Failed to set keychain")
return nil, status.Errorf(codes.Internal, "failed to set keychain: %v", err)
}
return &emptypb.Empty{}, nil
}
@ -696,5 +585,11 @@ func (s *Service) SetCurrentKeychain(ctx context.Context, keychain *wrapperspb.S
func (s *Service) CurrentKeychain(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.StringValue, error) {
s.log.Debug("CurrentKeychain")
return wrapperspb.String(s.bridge.GetKeychainApp()), nil
helper, err := s.bridge.GetKeychainApp()
if err != nil {
s.log.WithError(err).Error("Failed to get current keychain")
return nil, status.Errorf(codes.Internal, "failed to get current keychain: %v", err)
}
return wrapperspb.String(helper), nil
}

View File

@ -87,12 +87,8 @@ func (s *Service) StopEventStream(ctx context.Context, _ *emptypb.Empty) (*empty
// SendEvent sends an event to the via the gRPC event stream.
func (s *Service) SendEvent(event *StreamEvent) error {
s.eventQueueMutex.Lock()
defer s.eventQueueMutex.Unlock()
if s.eventStreamCh == nil {
// nobody is connected to the event stream, we queue events
s.eventQueue = append(s.eventQueue, event)
if s.eventStreamCh == nil { // nobody is connected to the event stream, we queue events
s.queueEvent(event)
return nil
}
@ -167,3 +163,14 @@ func (s *Service) StartEventTest() error { //nolint:funlen
return nil
}
func (s *Service) queueEvent(event *StreamEvent) {
s.eventQueueMutex.Lock()
defer s.eventQueueMutex.Unlock()
if event.isInternetStatus() {
s.eventQueue = append(filterOutInternetStatusEvents(s.eventQueue), event)
} else {
s.eventQueue = append(s.eventQueue, event)
}
}

View File

@ -0,0 +1,68 @@
package grpc
/*
func (s *Service) checkUpdate() {
version, err := s.updater.Check()
if err != nil {
s.log.WithError(err).Error("An error occurred while checking for updates")
s.SetVersion(updater.VersionInfo{})
return
}
s.SetVersion(version)
}
func (s *Service) updateForce() {
s.updateCheckMutex.Lock()
defer s.updateCheckMutex.Unlock()
s.checkUpdate()
_ = s.SendEvent(NewUpdateForceEvent(s.newVersionInfo.Version.String()))
}
func (s *Service) checkUpdateAndNotify(isReqFromUser bool) {
s.updateCheckMutex.Lock()
defer func() {
s.updateCheckMutex.Unlock()
_ = s.SendEvent(NewUpdateCheckFinishedEvent())
}()
s.checkUpdate()
version := s.newVersionInfo
if version.Version.String() == "" {
if isReqFromUser {
_ = s.SendEvent(NewUpdateErrorEvent(UpdateErrorType_UPDATE_MANUAL_ERROR))
}
return
}
if !s.updater.IsUpdateApplicable(s.newVersionInfo) {
s.log.Info("No need to update")
if isReqFromUser {
_ = s.SendEvent(NewUpdateIsLatestVersionEvent())
}
} else if isReqFromUser {
s.NotifyManualUpdate(s.newVersionInfo, s.updater.CanInstall(s.newVersionInfo))
}
}
func (s *Service) installUpdate() {
s.updateCheckMutex.Lock()
defer s.updateCheckMutex.Unlock()
if !s.updater.CanInstall(s.newVersionInfo) {
s.log.Warning("Skipping update installation, current version too old")
_ = s.SendEvent(NewUpdateErrorEvent(UpdateErrorType_UPDATE_MANUAL_ERROR))
return
}
if err := s.updater.InstallUpdate(s.newVersionInfo); err != nil {
if errors.Cause(err) == updater.ErrDownloadVerify {
s.log.WithError(err).Warning("Skipping update installation due to temporary error")
} else {
s.log.WithError(err).Error("The update couldn't be installed")
_ = s.SendEvent(NewUpdateErrorEvent(UpdateErrorType_UPDATE_MANUAL_ERROR))
}
return
}
_ = s.SendEvent(NewUpdateSilentRestartNeededEvent())
}
*/

View File

@ -19,9 +19,8 @@ package grpc
import (
"context"
"time"
"github.com/ProtonMail/proton-bridge/v2/internal/users"
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
"github.com/sirupsen/logrus"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
@ -75,15 +74,15 @@ func (s *Service) SetUserSplitMode(ctx context.Context, splitMode *UserSplitMode
defer s.panicHandler.HandlePanic()
defer func() { _ = s.SendEvent(NewUserToggleSplitModeFinishedEvent(splitMode.UserID)) }()
var targetMode users.AddressMode
var targetMode bridge.AddressMode
if splitMode.Active && user.Mode == users.CombinedMode {
targetMode = users.SplitMode
} else if !splitMode.Active && user.Mode == users.SplitMode {
targetMode = users.CombinedMode
if splitMode.Active && user.AddressMode == bridge.CombinedMode {
targetMode = bridge.SplitMode
} else if !splitMode.Active && user.AddressMode == bridge.SplitMode {
targetMode = bridge.CombinedMode
}
if err := s.bridge.SetAddressMode(user.ID, targetMode); err != nil {
if err := s.bridge.SetAddressMode(user.UserID, targetMode); err != nil {
logrus.WithError(err).Error("Failed to set address mode")
}
}()
@ -101,7 +100,7 @@ func (s *Service) LogoutUser(ctx context.Context, userID *wrapperspb.StringValue
go func() {
defer s.panicHandler.HandlePanic()
if err := s.bridge.LogoutUser(userID.Value); err != nil {
if err := s.bridge.LogoutUser(context.Background(), userID.Value); err != nil {
logrus.WithError(err).Error("Failed to log user out")
}
}()
@ -116,7 +115,7 @@ func (s *Service) RemoveUser(ctx context.Context, userID *wrapperspb.StringValue
defer s.panicHandler.HandlePanic()
// remove preferences
if err := s.bridge.DeleteUser(userID.Value, false); err != nil {
if err := s.bridge.DeleteUser(context.Background(), userID.Value); err != nil {
s.log.WithError(err).Error("Failed to remove user")
// notification
}
@ -127,18 +126,10 @@ func (s *Service) RemoveUser(ctx context.Context, userID *wrapperspb.StringValue
func (s *Service) ConfigureUserAppleMail(ctx context.Context, request *ConfigureAppleMailRequest) (*emptypb.Empty, error) {
s.log.WithField("UserID", request.UserID).WithField("Address", request.Address).Debug("ConfigureUserAppleMail")
restart, err := s.bridge.ConfigureAppleMail(request.UserID, request.Address)
if err != nil {
if err := s.bridge.ConfigureAppleMail(request.UserID, request.Address); err != nil {
s.log.WithField("userID", request.UserID).Error("Cannot configure AppleMail for user")
return nil, status.Error(codes.Internal, "Apple Mail config failed")
}
// There is delay needed for external window to open.
if restart {
s.log.Warn("Detected Catalina or newer with bad SMTP SSL settings, now using SSL, bridge needs to restart")
time.Sleep(2 * time.Second)
return s.Restart(ctx, &emptypb.Empty{})
}
return &emptypb.Empty{}, nil
}

View File

@ -21,7 +21,7 @@ import (
"regexp"
"strings"
"github.com/ProtonMail/proton-bridge/v2/internal/users"
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
"github.com/sirupsen/logrus"
)
@ -58,17 +58,17 @@ func getInitials(fullName string) string {
}
// grpcUserFromInfo converts a bridge user to a gRPC user.
func grpcUserFromInfo(user users.UserInfo) *User {
func grpcUserFromInfo(user bridge.UserInfo) *User {
return &User{
Id: user.ID,
Id: user.UserID,
Username: user.Username,
AvatarText: getInitials(user.Username),
LoggedIn: user.Connected,
SplitMode: user.Mode == users.SplitMode,
SplitMode: user.AddressMode == bridge.SplitMode,
SetupGuideSeen: true, // users listed have already seen the setup guide.
UsedBytes: user.UsedBytes,
TotalBytes: user.TotalBytes,
Password: user.Password,
UsedBytes: int64(user.UsedSpace),
TotalBytes: int64(user.MaxSpace),
Password: user.BridgePass,
Addresses: user.Addresses,
}
}