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

View File

@ -22,7 +22,7 @@ import (
"strconv"
"strings"
"github.com/ProtonMail/proton-bridge/v2/internal/users"
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
"github.com/abiosoft/ishell"
)
@ -35,6 +35,7 @@ func (f *frontendCLI) completeUsernames(args []string) (usernames []string) {
if len(args) == 1 {
arg = args[0]
}
for _, userID := range f.bridge.GetUserIDs() {
user, err := f.bridge.GetUserInfo(userID)
if err != nil {
@ -50,8 +51,7 @@ func (f *frontendCLI) completeUsernames(args []string) (usernames []string) {
// noAccountWrapper is a decorator for functions which need any account to be properly functional.
func (f *frontendCLI) noAccountWrapper(callback func(*ishell.Context)) func(*ishell.Context) {
return func(c *ishell.Context) {
users := f.bridge.GetUserIDs()
if len(users) == 0 {
if len(f.bridge.GetUserIDs()) == 0 {
f.Println("No active accounts. Please add account to continue.")
} else {
callback(c)
@ -59,9 +59,9 @@ func (f *frontendCLI) noAccountWrapper(callback func(*ishell.Context)) func(*ish
}
}
func (f *frontendCLI) askUserByIndexOrName(c *ishell.Context) users.UserInfo {
func (f *frontendCLI) askUserByIndexOrName(c *ishell.Context) bridge.UserInfo {
user := f.getUserByIndexOrName("")
if user.ID != "" {
if user.UserID != "" {
return user
}
@ -69,24 +69,24 @@ func (f *frontendCLI) askUserByIndexOrName(c *ishell.Context) users.UserInfo {
indexRange := fmt.Sprintf("number between 0 and %d", numberOfAccounts-1)
if len(c.Args) == 0 {
f.Printf("Please choose %s or username.\n", indexRange)
return users.UserInfo{}
return bridge.UserInfo{}
}
arg := c.Args[0]
user = f.getUserByIndexOrName(arg)
if user.ID == "" {
if user.UserID == "" {
f.Printf("Wrong input '%s'. Choose %s or username.\n", bold(arg), indexRange)
return users.UserInfo{}
return bridge.UserInfo{}
}
return user
}
func (f *frontendCLI) getUserByIndexOrName(arg string) users.UserInfo {
func (f *frontendCLI) getUserByIndexOrName(arg string) bridge.UserInfo {
userIDs := f.bridge.GetUserIDs()
numberOfAccounts := len(userIDs)
if numberOfAccounts == 0 {
return users.UserInfo{}
return bridge.UserInfo{}
}
res := make([]users.UserInfo, len(userIDs))
res := make([]bridge.UserInfo, len(userIDs))
for idx, userID := range userIDs {
user, err := f.bridge.GetUserInfo(userID)
if err != nil {
@ -99,7 +99,7 @@ func (f *frontendCLI) getUserByIndexOrName(arg string) users.UserInfo {
}
if index, err := strconv.Atoi(arg); err == nil {
if index < 0 || index >= numberOfAccounts {
return users.UserInfo{}
return bridge.UserInfo{}
}
return res[index]
}
@ -108,5 +108,5 @@ func (f *frontendCLI) getUserByIndexOrName(arg string) users.UserInfo {
return user
}
}
return users.UserInfo{}
return bridge.UserInfo{}
}

View File

@ -22,8 +22,7 @@ import (
"strings"
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
"github.com/ProtonMail/proton-bridge/v2/internal/users"
"github.com/ProtonMail/proton-bridge/v2/internal/constants"
"github.com/abiosoft/ishell"
)
@ -40,7 +39,7 @@ func (f *frontendCLI) listAccounts(c *ishell.Context) {
connected = "connected"
}
mode := "split"
if user.Mode == users.CombinedMode {
if user.AddressMode == bridge.CombinedMode {
mode = "combined"
}
f.Printf(spacing, idx, user.Username, connected, mode)
@ -50,7 +49,7 @@ func (f *frontendCLI) listAccounts(c *ishell.Context) {
func (f *frontendCLI) showAccountInfo(c *ishell.Context) {
user := f.askUserByIndexOrName(c)
if user.ID == "" {
if user.UserID == "" {
return
}
@ -59,8 +58,8 @@ func (f *frontendCLI) showAccountInfo(c *ishell.Context) {
return
}
if user.Mode == users.CombinedMode {
f.showAccountAddressInfo(user, user.Addresses[user.Primary])
if user.AddressMode == bridge.CombinedMode {
f.showAccountAddressInfo(user, user.Addresses[0])
} else {
for _, address := range user.Addresses {
f.showAccountAddressInfo(user, address)
@ -68,25 +67,31 @@ func (f *frontendCLI) showAccountInfo(c *ishell.Context) {
}
}
func (f *frontendCLI) showAccountAddressInfo(user users.UserInfo, address string) {
func (f *frontendCLI) showAccountAddressInfo(user bridge.UserInfo, address string) {
imapSecurity := "STARTTLS"
if f.bridge.GetIMAPSSL() {
imapSecurity = "SSL"
}
smtpSecurity := "STARTTLS"
if f.bridge.GetBool(settings.SMTPSSLKey) {
if f.bridge.GetSMTPSSL() {
smtpSecurity = "SSL"
}
f.Println(bold("Configuration for " + address))
f.Printf("IMAP Settings\nAddress: %s\nIMAP port: %d\nUsername: %s\nPassword: %s\nSecurity: %s\n",
bridge.Host,
f.bridge.GetInt(settings.IMAPPortKey),
constants.Host,
f.bridge.GetIMAPPort(),
address,
user.Password,
"STARTTLS",
user.BridgePass,
imapSecurity,
)
f.Println("")
f.Printf("SMTP Settings\nAddress: %s\nSMTP port: %d\nUsername: %s\nPassword: %s\nSecurity: %s\n",
bridge.Host,
f.bridge.GetInt(settings.SMTPPortKey),
constants.Host,
f.bridge.GetSMTPPort(),
address,
user.Password,
user.BridgePass,
smtpSecurity,
)
f.Println("")
@ -99,8 +104,8 @@ func (f *frontendCLI) loginAccount(c *ishell.Context) { //nolint:funlen
loginName := ""
if len(c.Args) > 0 {
user := f.getUserByIndexOrName(c.Args[0])
if user.ID != "" {
loginName = user.Addresses[user.Primary]
if user.UserID != "" {
loginName = user.Addresses[0]
}
}
@ -119,41 +124,23 @@ func (f *frontendCLI) loginAccount(c *ishell.Context) { //nolint:funlen
}
f.Println("Authenticating ... ")
client, auth, err := f.bridge.Login(loginName, []byte(password))
userID, err := f.bridge.LoginUser(
context.Background(),
loginName,
password,
func() (string, error) {
return f.readStringInAttempts("Two factor code", c.ReadLine, isNotEmpty), nil
},
func() ([]byte, error) {
return []byte(f.readStringInAttempts("Mailbox password", c.ReadPassword, isNotEmpty)), nil
},
)
if err != nil {
f.processAPIError(err)
return
}
if auth.HasTwoFactor() {
twoFactor := f.readStringInAttempts("Two factor code", c.ReadLine, isNotEmpty)
if twoFactor == "" {
return
}
err = client.Auth2FA(context.Background(), twoFactor)
if err != nil {
f.processAPIError(err)
return
}
}
mailboxPassword := password
if auth.HasMailboxPassword() {
mailboxPassword = f.readStringInAttempts("Mailbox password", c.ReadPassword, isNotEmpty)
}
if mailboxPassword == "" {
return
}
f.Println("Adding account ...")
userID, err := f.bridge.FinishLogin(client, auth, []byte(mailboxPassword))
if err != nil {
log.WithField("username", loginName).WithError(err).Error("Login was unsuccessful")
f.Println("Adding account was unsuccessful:", err)
return
}
user, err := f.bridge.GetUserInfo(userID)
if err != nil {
panic(err)
@ -167,11 +154,12 @@ func (f *frontendCLI) logoutAccount(c *ishell.Context) {
defer f.ShowPrompt(true)
user := f.askUserByIndexOrName(c)
if user.ID == "" {
if user.UserID == "" {
return
}
if f.yesNoQuestion("Are you sure you want to logout account " + bold(user.Username)) {
if err := f.bridge.LogoutUser(user.ID); err != nil {
if err := f.bridge.LogoutUser(context.Background(), user.UserID); err != nil {
f.printAndLogError("Logging out failed: ", err)
}
}
@ -182,12 +170,12 @@ func (f *frontendCLI) deleteAccount(c *ishell.Context) {
defer f.ShowPrompt(true)
user := f.askUserByIndexOrName(c)
if user.ID == "" {
if user.UserID == "" {
return
}
if f.yesNoQuestion("Are you sure you want to " + bold("remove account "+user.Username)) {
clearCache := f.yesNoQuestion("Do you want to remove cache for this account")
if err := f.bridge.DeleteUser(user.ID, clearCache); err != nil {
if err := f.bridge.DeleteUser(context.Background(), user.UserID); err != nil {
f.printAndLogError("Cannot delete account: ", err)
return
}
@ -205,10 +193,13 @@ func (f *frontendCLI) deleteAccounts(c *ishell.Context) {
for _, userID := range f.bridge.GetUserIDs() {
user, err := f.bridge.GetUserInfo(userID)
if err != nil {
panic(err)
f.printAndLogError("Cannot get user info: ", err)
return
}
if err := f.bridge.DeleteUser(user.ID, false); err != nil {
if err := f.bridge.DeleteUser(context.Background(), user.UserID); err != nil {
f.printAndLogError("Cannot delete account ", user.Username, ": ", err)
return
}
}
@ -223,37 +214,50 @@ func (f *frontendCLI) deleteEverything(c *ishell.Context) {
return
}
f.bridge.FactoryReset()
f.bridge.FactoryReset(context.Background())
c.Println("Everything cleared")
// Clearing data removes everything (db, preferences, ...) so everything has to be stopped and started again.
f.restarter.SetToRestart()
f.Stop()
}
func (f *frontendCLI) changeMode(c *ishell.Context) {
user := f.askUserByIndexOrName(c)
if user.ID == "" {
if user.UserID == "" {
return
}
var targetMode users.AddressMode
var targetMode bridge.AddressMode
if user.Mode == users.CombinedMode {
targetMode = users.SplitMode
if user.AddressMode == bridge.CombinedMode {
targetMode = bridge.SplitMode
} else {
targetMode = users.CombinedMode
targetMode = bridge.CombinedMode
}
if !f.yesNoQuestion("Are you sure you want to change the mode for account " + bold(user.Username) + " to " + bold(targetMode)) {
return
}
if err := f.bridge.SetAddressMode(user.ID, targetMode); err != nil {
if err := f.bridge.SetAddressMode(user.UserID, targetMode); err != nil {
f.printAndLogError("Cannot switch address mode:", err)
}
f.Printf("Address mode for account %s changed to %s\n", user.Username, targetMode)
}
func (f *frontendCLI) configureAppleMail(c *ishell.Context) {
user := f.askUserByIndexOrName(c)
if user.UserID == "" {
return
}
if !f.yesNoQuestion("Are you sure you want to configure Apple Mail for " + bold(user.Username) + " with address " + bold(user.Addresses[0])) {
return
}
if err := f.bridge.ConfigureAppleMail(user.UserID, user.Addresses[0]); err != nil {
f.printAndLogError(err)
return
}
f.Printf("Apple Mail configured for %v with address %v\n", user.Username, user.Addresses[0])
}

View File

@ -19,11 +19,13 @@
package cli
import (
"errors"
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
"github.com/ProtonMail/proton-bridge/v2/internal/constants"
"github.com/ProtonMail/proton-bridge/v2/internal/events"
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/types"
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
"github.com/ProtonMail/proton-bridge/v2/pkg/listener"
"github.com/ProtonMail/proton-bridge/v2/internal/vault"
"gitlab.protontech.ch/go/liteapi"
"github.com/abiosoft/ishell"
"github.com/sirupsen/logrus"
@ -34,30 +36,14 @@ var log = logrus.WithField("pkg", "frontend/cli") //nolint:gochecknoglobals
type frontendCLI struct {
*ishell.Shell
eventListener listener.Listener
updater types.Updater
bridge types.Bridger
restarter types.Restarter
bridge *bridge.Bridge
}
// New returns a new CLI frontend configured with the given options.
func New( //nolint:funlen
panicHandler types.PanicHandler,
eventListener listener.Listener,
updater types.Updater,
bridge types.Bridger,
restarter types.Restarter,
) *frontendCLI { //nolint:revive
func New(bridge *bridge.Bridge) *frontendCLI {
fe := &frontendCLI{
Shell: ishell.New(),
eventListener: eventListener,
updater: updater,
bridge: bridge,
restarter: restarter,
Shell: ishell.New(),
bridge: bridge,
}
// Clear commands.
@ -66,12 +52,6 @@ func New( //nolint:funlen
Help: "remove stored accounts and preferences. (alias: cl)",
Aliases: []string{"cl"},
}
clearCmd.AddCmd(&ishell.Cmd{
Name: "cache",
Help: "remove stored preferences for accounts (aliases: c, prefs, preferences)",
Aliases: []string{"c", "prefs", "preferences"},
Func: fe.deleteCache,
})
clearCmd.AddCmd(&ishell.Cmd{
Name: "accounts",
Help: "remove all accounts from keychain. (aliases: a, k, keychain)",
@ -100,15 +80,30 @@ func New( //nolint:funlen
Completer: fe.completeUsernames,
})
changeCmd.AddCmd(&ishell.Cmd{
Name: "port",
Help: "change port numbers of IMAP and SMTP servers. (alias: p)",
Aliases: []string{"p"},
Func: fe.changePort,
Name: "change-location",
Help: "change the location of the encrypted message cache",
Func: fe.setGluonLocation,
})
changeCmd.AddCmd(&ishell.Cmd{
Name: "imap-port",
Help: "change port number of IMAP server.",
Func: fe.changeIMAPPort,
})
changeCmd.AddCmd(&ishell.Cmd{
Name: "smtp-port",
Help: "change port number of SMTP server.",
Func: fe.changeSMTPPort,
})
changeCmd.AddCmd(&ishell.Cmd{
Name: "imap-security",
Help: "change IMAP SSL settings servers.(alias: ssl-imap, starttls-imap)",
Aliases: []string{"ssl-imap", "starttls-imap"},
Func: fe.changeIMAPSecurity,
})
changeCmd.AddCmd(&ishell.Cmd{
Name: "smtp-security",
Help: "change port numbers of IMAP and SMTP servers.(alias: ssl, starttls)",
Aliases: []string{"ssl", "starttls"},
Help: "change SMTP SSL settings servers.(alias: ssl-smtp, starttls-smtp)",
Aliases: []string{"ssl-smtp", "starttls-smtp"},
Func: fe.changeSMTPSecurity,
})
fe.AddCmd(changeCmd)
@ -130,6 +125,22 @@ func New( //nolint:funlen
})
fe.AddCmd(dohCmd)
// Apple Mail commands.
configureCmd := &ishell.Cmd{
Name: "configure-apple-mail",
Help: "Configures Apple Mail to use ProtonMail Bridge",
Func: fe.configureAppleMail,
}
fe.AddCmd(configureCmd)
// TLS commands.
exportTLSCmd := &ishell.Cmd{
Name: "export-tls",
Help: "Export the TLS certificate used by the Bridge",
Func: fe.exportTLSCerts,
}
fe.AddCmd(exportTLSCmd)
// All mail visibility commands.
allMailCmd := &ishell.Cmd{
Name: "all-mail-visibility",
@ -147,28 +158,6 @@ func New( //nolint:funlen
})
fe.AddCmd(allMailCmd)
// Cache-On-Disk commands.
codCmd := &ishell.Cmd{
Name: "local-cache",
Help: "manage the local encrypted message cache",
}
codCmd.AddCmd(&ishell.Cmd{
Name: "enable",
Help: "enable the local cache",
Func: fe.enableCacheOnDisk,
})
codCmd.AddCmd(&ishell.Cmd{
Name: "disable",
Help: "disable the local cache",
Func: fe.disableCacheOnDisk,
})
codCmd.AddCmd(&ishell.Cmd{
Name: "change-location",
Help: "change the location of the local cache",
Func: fe.setCacheOnDiskLocation,
})
fe.AddCmd(codCmd)
// Updates commands.
updatesCmd := &ishell.Cmd{
Name: "updates",
@ -224,7 +213,6 @@ func New( //nolint:funlen
Aliases: []string{"man"},
Func: fe.printManual,
})
fe.AddCmd(&ishell.Cmd{
Name: "credits",
Help: "print used resources.",
@ -267,55 +255,122 @@ func New( //nolint:funlen
Completer: fe.completeUsernames,
})
// System commands.
fe.AddCmd(&ishell.Cmd{
Name: "restart",
Help: "restart the bridge.",
Func: fe.restart,
})
go fe.watchEvents()
go func() {
defer panicHandler.HandlePanic()
fe.watchEvents()
}()
return fe
}
func (f *frontendCLI) watchEvents() {
errorCh := f.eventListener.ProvideChannel(events.ErrorEvent)
credentialsErrorCh := f.eventListener.ProvideChannel(events.CredentialsErrorEvent)
internetConnChangedCh := f.eventListener.ProvideChannel(events.InternetConnChangedEvent)
addressChangedCh := f.eventListener.ProvideChannel(events.AddressChangedEvent)
addressChangedLogoutCh := f.eventListener.ProvideChannel(events.AddressChangedLogoutEvent)
logoutCh := f.eventListener.ProvideChannel(events.LogoutEvent)
certIssue := f.eventListener.ProvideChannel(events.TLSCertIssue)
for {
select {
case errorDetails := <-errorCh:
f.Println("Bridge failed:", errorDetails)
case <-credentialsErrorCh:
eventCh, done := f.bridge.GetEvents()
defer done()
// TODO: Better error events.
for _, err := range f.bridge.GetErrors() {
switch {
case errors.Is(err, vault.ErrCorrupt):
f.notifyCredentialsError()
case stat := <-internetConnChangedCh:
if stat == events.InternetOff {
case errors.Is(err, vault.ErrInsecure):
f.notifyCredentialsError()
case errors.Is(err, bridge.ErrServeIMAP):
f.Println("IMAP server error:", err)
case errors.Is(err, bridge.ErrServeSMTP):
f.Println("SMTP server error:", err)
}
}
for event := range eventCh {
switch event := event.(type) {
case events.ConnStatus:
switch event.Status {
case liteapi.StatusUp:
f.notifyInternetOn()
case liteapi.StatusDown:
f.notifyInternetOff()
}
if stat == events.InternetOn {
f.notifyInternetOn()
}
case address := <-addressChangedCh:
f.Printf("Address changed for %s. You may need to reconfigure your email client.", address)
case address := <-addressChangedLogoutCh:
f.notifyLogout(address)
case userID := <-logoutCh:
user, err := f.bridge.GetUserInfo(userID)
case events.UserDeauth:
user, err := f.bridge.GetUserInfo(event.UserID)
if err != nil {
return
}
f.notifyLogout(user.Username)
case <-certIssue:
case events.UserAddressChanged:
user, err := f.bridge.GetUserInfo(event.UserID)
if err != nil {
return
}
f.Printf("Address changed for %s. You may need to reconfigure your email client.\n", user.Username)
case events.UserAddressDeleted:
f.notifyLogout(event.Address)
case events.SyncStarted:
user, err := f.bridge.GetUserInfo(event.UserID)
if err != nil {
return
}
f.Printf("A sync has begun for %s.\n", user.Username)
case events.SyncFinished:
user, err := f.bridge.GetUserInfo(event.UserID)
if err != nil {
return
}
f.Printf("A sync has finished for %s.\n", user.Username)
case events.SyncProgress:
user, err := f.bridge.GetUserInfo(event.UserID)
if err != nil {
return
}
f.Printf(
"Sync (%v): %.1f%% (Elapsed: %0.1fs, ETA: %0.1fs)\n",
user.Username,
100*event.Progress,
event.Elapsed.Seconds(),
event.Remaining.Seconds(),
)
case events.UpdateAvailable:
f.Printf("An update is available (version %v)\n", event.Version.Version)
case events.UpdateForced:
f.notifyNeedUpgrade()
case events.TLSIssue:
f.notifyCertIssue()
}
}
/*
errorCh := f.eventListener.ProvideChannel(events.ErrorEvent)
credentialsErrorCh := f.eventListener.ProvideChannel(events.CredentialsErrorEvent)
for {
select {
case errorDetails := <-errorCh:
f.Println("Bridge failed:", errorDetails)
case <-credentialsErrorCh:
f.notifyCredentialsError()
case stat := <-internetConnChangedCh:
if stat == events.InternetOff {
f.notifyInternetOff()
}
if stat == events.InternetOn {
f.notifyInternetOn()
}
}
}
*/
}
// Loop starts the frontend loop with an interactive shell.
@ -340,12 +395,3 @@ func (f *frontendCLI) Loop() error {
f.Run()
return nil
}
func (f *frontendCLI) NotifyManualUpdate(update updater.VersionInfo, canInstall bool) {
// NOTE: Save the update somewhere so that it can be installed when user chooses "install now".
}
func (f *frontendCLI) WaitUntilFrontendIsReady() {}
func (f *frontendCLI) SetVersion(version updater.VersionInfo) {}
func (f *frontendCLI) NotifySilentUpdateInstalled() {}
func (f *frontendCLI) NotifySilentUpdateError(err error) {}

View File

@ -18,28 +18,21 @@
package cli
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
"github.com/ProtonMail/proton-bridge/v2/pkg/ports"
"github.com/abiosoft/ishell"
)
var currentPort = "" //nolint:gochecknoglobals
func (f *frontendCLI) restart(c *ishell.Context) {
if f.yesNoQuestion("Are you sure you want to restart the Bridge") {
f.Println("Restarting Bridge...")
f.restarter.SetToRestart()
f.Stop()
}
}
func (f *frontendCLI) printLogDir(c *ishell.Context) {
if path, err := f.bridge.ProvideLogsPath(); err != nil {
if path, err := f.bridge.GetLogsPath(); err != nil {
f.Println("Failed to determine location of log files")
} else {
f.Println("Log files are stored in\n\n ", path)
@ -50,79 +43,91 @@ func (f *frontendCLI) printManual(c *ishell.Context) {
f.Println("More instructions about the Bridge can be found at\n\n https://protonmail.com/bridge")
}
func (f *frontendCLI) deleteCache(c *ishell.Context) {
func (f *frontendCLI) printCredits(c *ishell.Context) {
for _, pkg := range strings.Split(bridge.Credits, ";") {
f.Println(pkg)
}
}
func (f *frontendCLI) changeIMAPSecurity(c *ishell.Context) {
f.ShowPrompt(false)
defer f.ShowPrompt(true)
if !f.yesNoQuestion("Do you really want to remove all stored preferences") {
return
newSecurity := "SSL"
if f.bridge.GetIMAPSSL() {
newSecurity = "STARTTLS"
}
if err := f.bridge.ClearData(); err != nil {
f.printAndLogError("Cache clear failed: ", err.Error())
return
msg := fmt.Sprintf("Are you sure you want to change IMAP setting to %q", newSecurity)
if f.yesNoQuestion(msg) {
if err := f.bridge.SetIMAPSSL(!f.bridge.GetIMAPSSL()); err != nil {
f.printAndLogError(err)
return
}
}
f.Println("Cached cleared, restarting bridge")
// Clearing data removes everything (db, preferences, ...) so everything has to be stopped and started again.
f.restarter.SetToRestart()
f.Stop()
}
func (f *frontendCLI) changeSMTPSecurity(c *ishell.Context) {
f.ShowPrompt(false)
defer f.ShowPrompt(true)
isSSL := f.bridge.GetBool(settings.SMTPSSLKey)
newSecurity := "SSL"
if isSSL {
if f.bridge.GetSMTPSSL() {
newSecurity = "STARTTLS"
}
msg := fmt.Sprintf("Are you sure you want to change SMTP setting to %q and restart the Bridge", newSecurity)
msg := fmt.Sprintf("Are you sure you want to change SMTP setting to %q", newSecurity)
if f.yesNoQuestion(msg) {
f.bridge.SetBool(settings.SMTPSSLKey, !isSSL)
f.Println("Restarting Bridge...")
f.restarter.SetToRestart()
f.Stop()
if err := f.bridge.SetSMTPSSL(!f.bridge.GetSMTPSSL()); err != nil {
f.printAndLogError(err)
return
}
}
}
func (f *frontendCLI) changePort(c *ishell.Context) {
func (f *frontendCLI) changeIMAPPort(c *ishell.Context) {
f.ShowPrompt(false)
defer f.ShowPrompt(true)
currentPort = f.bridge.Get(settings.IMAPPortKey)
newIMAPPort := f.readStringInAttempts("Set IMAP port (current "+currentPort+")", c.ReadLine, f.isPortFree)
newIMAPPort := f.readStringInAttempts(fmt.Sprintf("Set IMAP port (current %v)", f.bridge.GetIMAPPort()), c.ReadLine, f.isPortFree)
if newIMAPPort == "" {
newIMAPPort = currentPort
}
imapPortChanged := newIMAPPort != currentPort
currentPort = f.bridge.Get(settings.SMTPPortKey)
newSMTPPort := f.readStringInAttempts("Set SMTP port (current "+currentPort+")", c.ReadLine, f.isPortFree)
if newSMTPPort == "" {
newSMTPPort = currentPort
}
smtpPortChanged := newSMTPPort != currentPort
if newIMAPPort == newSMTPPort {
f.Println("SMTP and IMAP ports must be different!")
f.printAndLogError(errors.New("failed to get new port"))
return
}
if imapPortChanged || smtpPortChanged {
f.Println("Saving values IMAP:", newIMAPPort, "SMTP:", newSMTPPort)
f.bridge.Set(settings.IMAPPortKey, newIMAPPort)
f.bridge.Set(settings.SMTPPortKey, newSMTPPort)
f.Println("Restarting Bridge...")
f.restarter.SetToRestart()
f.Stop()
} else {
f.Println("Nothing changed")
newIMAPPortInt, err := strconv.Atoi(newIMAPPort)
if err != nil {
f.printAndLogError(err)
return
}
if err := f.bridge.SetIMAPPort(newIMAPPortInt); err != nil {
f.printAndLogError(err)
return
}
}
func (f *frontendCLI) changeSMTPPort(c *ishell.Context) {
f.ShowPrompt(false)
defer f.ShowPrompt(true)
newSMTPPort := f.readStringInAttempts(fmt.Sprintf("Set SMTP port (current %v)", f.bridge.GetSMTPPort()), c.ReadLine, f.isPortFree)
if newSMTPPort == "" {
f.printAndLogError(errors.New("failed to get new port"))
return
}
newSMTPPortInt, err := strconv.Atoi(newSMTPPort)
if err != nil {
f.printAndLogError(err)
return
}
if err := f.bridge.SetSMTPPort(newSMTPPortInt); err != nil {
f.printAndLogError(err)
return
}
}
@ -135,7 +140,10 @@ func (f *frontendCLI) allowProxy(c *ishell.Context) {
f.Println("Bridge is currently set to NOT use alternative routing to connect to Proton if it is being blocked.")
if f.yesNoQuestion("Are you sure you want to allow bridge to do this") {
f.bridge.SetProxyAllowed(true)
if err := f.bridge.SetProxyAllowed(true); err != nil {
f.printAndLogError(err)
return
}
}
}
@ -148,12 +156,15 @@ func (f *frontendCLI) disallowProxy(c *ishell.Context) {
f.Println("Bridge is currently set to use alternative routing to connect to Proton if it is being blocked.")
if f.yesNoQuestion("Are you sure you want to stop bridge from doing this") {
f.bridge.SetProxyAllowed(false)
if err := f.bridge.SetProxyAllowed(false); err != nil {
f.printAndLogError(err)
return
}
}
}
func (f *frontendCLI) hideAllMail(c *ishell.Context) {
if !f.bridge.IsAllMailVisible() {
if !f.bridge.GetShowAllMail() {
f.Println("All Mail folder is not listed in your local client.")
return
}
@ -161,12 +172,15 @@ func (f *frontendCLI) hideAllMail(c *ishell.Context) {
f.Println("All Mail folder is listed in your client right now.")
if f.yesNoQuestion("Do you want to hide All Mail folder") {
f.bridge.SetIsAllMailVisible(false)
if err := f.bridge.SetShowAllMail(false); err != nil {
f.printAndLogError(err)
return
}
}
}
func (f *frontendCLI) showAllMail(c *ishell.Context) {
if f.bridge.IsAllMailVisible() {
if f.bridge.GetShowAllMail() {
f.Println("All Mail folder is listed in your local client.")
return
}
@ -174,68 +188,47 @@ func (f *frontendCLI) showAllMail(c *ishell.Context) {
f.Println("All Mail folder is not listed in your client right now.")
if f.yesNoQuestion("Do you want to show All Mail folder") {
f.bridge.SetIsAllMailVisible(true)
if err := f.bridge.SetShowAllMail(true); err != nil {
f.printAndLogError(err)
return
}
}
}
func (f *frontendCLI) enableCacheOnDisk(c *ishell.Context) {
if f.bridge.GetBool(settings.CacheEnabledKey) {
f.Println("The local cache is already enabled.")
return
func (f *frontendCLI) setGluonLocation(c *ishell.Context) {
if gluonDir := f.bridge.GetGluonDir(); gluonDir != "" {
f.Println("The current message cache location is:", gluonDir)
}
if f.yesNoQuestion("Are you sure you want to enable the local cache") {
if err := f.bridge.EnableCache(); err != nil {
f.Println("The local cache could not be enabled.")
if location := f.readStringInAttempts("Enter a new location for the message cache", c.ReadLine, f.isCacheLocationUsable); location != "" {
if err := f.bridge.SetGluonDir(context.Background(), location); err != nil {
f.printAndLogError(err)
return
}
f.restarter.SetToRestart()
f.Stop()
}
}
func (f *frontendCLI) disableCacheOnDisk(c *ishell.Context) {
if !f.bridge.GetBool(settings.CacheEnabledKey) {
f.Println("The local cache is already disabled.")
return
}
func (f *frontendCLI) exportTLSCerts(c *ishell.Context) {
if location := f.readStringInAttempts("Enter a path to which to export the TLS certificate used for IMAP and SMTP", c.ReadLine, f.isCacheLocationUsable); location != "" {
cert, key := f.bridge.GetBridgeTLSCert()
if f.yesNoQuestion("Are you sure you want to disable the local cache") {
if err := f.bridge.DisableCache(); err != nil {
f.Println("The local cache could not be disabled.")
if err := os.WriteFile(filepath.Join(location, "cert.pem"), cert, 0600); err != nil {
f.printAndLogError(err)
return
}
f.restarter.SetToRestart()
f.Stop()
}
}
func (f *frontendCLI) setCacheOnDiskLocation(c *ishell.Context) {
if !f.bridge.GetBool(settings.CacheEnabledKey) {
f.Println("The local cache must be enabled.")
return
}
if location := f.bridge.Get(settings.CacheLocationKey); location != "" {
f.Println("The current local cache location is:", location)
}
if location := f.readStringInAttempts("Enter a new location for the cache", c.ReadLine, f.isCacheLocationUsable); location != "" {
if err := f.bridge.MigrateCache(f.bridge.Get(settings.CacheLocationKey), location); err != nil {
f.Println("The local cache location could not be changed.")
if err := os.WriteFile(filepath.Join(location, "key.pem"), key, 0600); err != nil {
f.printAndLogError(err)
return
}
f.restarter.SetToRestart()
f.Stop()
f.Println("TLS certificate exported to", location)
}
}
func (f *frontendCLI) isPortFree(port string) bool {
port = strings.ReplaceAll(port, ":", "")
if port == "" || port == currentPort {
if port == "" {
return true
}
number, err := strconv.Atoi(port)

View File

@ -18,36 +18,16 @@
package cli
import (
"strings"
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
"github.com/abiosoft/ishell"
)
func (f *frontendCLI) checkUpdates(c *ishell.Context) {
version, err := f.updater.Check()
if err != nil {
f.Println("An error occurred while checking for updates.")
return
}
if f.updater.IsUpdateApplicable(version) {
f.Println("An update is available.")
} else {
f.Println("Your version is up to date.")
}
}
func (f *frontendCLI) printCredits(c *ishell.Context) {
for _, pkg := range strings.Split(bridge.Credits, ";") {
f.Println(pkg)
}
f.bridge.CheckForUpdates()
}
func (f *frontendCLI) enableAutoUpdates(c *ishell.Context) {
if f.bridge.GetBool(settings.AutoUpdateKey) {
if f.bridge.GetAutoUpdate() {
f.Println("Bridge is already set to automatically install updates.")
return
}
@ -55,12 +35,15 @@ func (f *frontendCLI) enableAutoUpdates(c *ishell.Context) {
f.Println("Bridge is currently set to NOT automatically install updates.")
if f.yesNoQuestion("Are you sure you want to allow bridge to do this") {
f.bridge.SetBool(settings.AutoUpdateKey, true)
if err := f.bridge.SetAutoUpdate(true); err != nil {
f.printAndLogError(err)
return
}
}
}
func (f *frontendCLI) disableAutoUpdates(c *ishell.Context) {
if !f.bridge.GetBool(settings.AutoUpdateKey) {
if !f.bridge.GetAutoUpdate() {
f.Println("Bridge is already set to NOT automatically install updates.")
return
}
@ -68,7 +51,10 @@ func (f *frontendCLI) disableAutoUpdates(c *ishell.Context) {
f.Println("Bridge is currently set to automatically install updates.")
if f.yesNoQuestion("Are you sure you want to stop bridge from doing this") {
f.bridge.SetBool(settings.AutoUpdateKey, false)
if err := f.bridge.SetAutoUpdate(false); err != nil {
f.printAndLogError(err)
return
}
}
}
@ -81,7 +67,10 @@ func (f *frontendCLI) selectEarlyChannel(c *ishell.Context) {
f.Println("Bridge is currently on the stable update channel.")
if f.yesNoQuestion("Are you sure you want to switch to the early-access update channel") {
f.bridge.SetUpdateChannel(updater.EarlyChannel)
if err := f.bridge.SetUpdateChannel(updater.EarlyChannel); err != nil {
f.printAndLogError(err)
return
}
}
}
@ -95,6 +84,9 @@ func (f *frontendCLI) selectStableChannel(c *ishell.Context) {
f.Println("Switching to the stable channel may reset all data!")
if f.yesNoQuestion("Are you sure you want to switch to the stable update channel") {
f.bridge.SetUpdateChannel(updater.StableChannel)
if err := f.bridge.SetUpdateChannel(updater.StableChannel); err != nil {
f.printAndLogError(err)
return
}
}
}

View File

@ -20,7 +20,6 @@ package cli
import (
"strings"
pmapi "github.com/ProtonMail/proton-bridge/v2/pkg/pmapi"
"github.com/fatih/color"
)
@ -67,15 +66,7 @@ func (f *frontendCLI) printAndLogError(args ...interface{}) {
}
func (f *frontendCLI) processAPIError(err error) {
log.Warn("API error: ", err)
switch err {
case pmapi.ErrNoConnection:
f.notifyInternetOff()
case pmapi.ErrUpgradeApplication:
f.notifyNeedUpgrade()
default:
f.Println("Server error:", err.Error())
}
f.printAndLogError(err)
}
func (f *frontendCLI) notifyInternetOff() {
@ -91,12 +82,7 @@ func (f *frontendCLI) notifyLogout(address string) {
}
func (f *frontendCLI) notifyNeedUpgrade() {
version, err := f.updater.Check()
if err != nil {
log.WithError(err).Error("Failed to notify need upgrade")
return
}
f.Println("Please download and install the newest version of application from", version.LandingPage)
f.Println("Please download and install the newest version of the application.")
}
func (f *frontendCLI) notifyCredentialsError() {

View File

@ -1,75 +0,0 @@
// 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 frontend provides all interfaces of the Bridge.
package frontend
import (
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/cli"
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/grpc"
"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/pkg/listener"
)
type Frontend interface {
Loop() error
NotifyManualUpdate(update updater.VersionInfo, canInstall bool)
SetVersion(update updater.VersionInfo)
NotifySilentUpdateInstalled()
NotifySilentUpdateError(error)
WaitUntilFrontendIsReady()
}
// New returns initialized frontend based on `frontendType`, which can be `cli` or `grpc`.
func New(
frontendType string,
showWindowOnStart bool,
panicHandler types.PanicHandler,
eventListener listener.Listener,
updater types.Updater,
bridge *bridge.Bridge,
restarter types.Restarter,
locations *locations.Locations,
) Frontend {
switch frontendType {
case "grpc":
return grpc.NewService(
showWindowOnStart,
panicHandler,
eventListener,
updater,
bridge,
restarter,
locations,
)
case "cli":
return cli.New(
panicHandler,
eventListener,
updater,
bridge,
restarter,
)
default:
return nil
}
}

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,
}
}

View File

@ -1,101 +0,0 @@
// 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 types provides interfaces used in frontend packages.
package types
import (
"crypto/tls"
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
"github.com/ProtonMail/proton-bridge/v2/internal/users"
"github.com/ProtonMail/proton-bridge/v2/pkg/pmapi"
)
// PanicHandler is an interface of a type that can be used to gracefully handle panics which occur.
type PanicHandler interface {
HandlePanic()
}
// Restarter allows the app to set itself to restart next time it is closed.
type Restarter interface {
SetToRestart()
ForceLauncher(string)
SetMainExecutable(string)
}
type Updater interface {
Check() (updater.VersionInfo, error)
InstallUpdate(updater.VersionInfo) error
IsUpdateApplicable(updater.VersionInfo) bool
CanInstall(updater.VersionInfo) bool
}
// Bridger is an interface of bridge needed by frontend.
type Bridger interface {
Login(username string, password []byte) (pmapi.Client, *pmapi.Auth, error)
FinishLogin(client pmapi.Client, auth *pmapi.Auth, mailboxPassword []byte) (string, error)
GetUserIDs() []string
GetUserInfo(string) (users.UserInfo, error)
LogoutUser(userID string) error
DeleteUser(userID string, clearCache bool) error
SetAddressMode(userID string, split users.AddressMode) error
ClearData() error
ClearUsers() error
FactoryReset()
GetTLSConfig() (*tls.Config, error)
ProvideLogsPath() (string, error)
GetLicenseFilePath() string
GetDependencyLicensesLink() string
GetCurrentUserAgent() string
SetCurrentPlatform(string)
Get(settings.Key) string
Set(settings.Key, string)
GetBool(settings.Key) bool
SetBool(settings.Key, bool)
GetInt(settings.Key) int
SetInt(settings.Key, int)
ConfigureAppleMail(userID, address string) (bool, error)
// -- old --
ReportBug(osType, osVersion, description, accountName, address, emailClient string, attachLogs bool) error
SetProxyAllowed(bool)
GetProxyAllowed() bool
EnableCache() error
DisableCache() error
MigrateCache(from, to string) error
GetUpdateChannel() updater.UpdateChannel
SetUpdateChannel(updater.UpdateChannel)
GetKeychainApp() string
SetKeychainApp(keychain string)
HasError(err error) bool
IsAutostartEnabled() bool
EnableAutostart() error
DisableAutostart() error
GetLastVersion() string
IsFirstStart() bool
IsAllMailVisible() bool
SetIsAllMailVisible(bool)
}