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() {