diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 99ce16d1..59d36105 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -180,6 +180,7 @@ build-linux-qa: - export PATH=$GOPATH/bin:$PATH - export CGO_CPPFLAGS='-Wno-error -Wno-nullability-completeness -Wno-expansion-to-defined -Wno-builtin-requires-header' script: + - go version - make build - git diff && git diff-index --quiet HEAD cache: {} diff --git a/cmd/launcher/main.go b/cmd/launcher/main.go index 5caa746a..06b34250 100644 --- a/cmd/launcher/main.go +++ b/cmd/launcher/main.go @@ -98,7 +98,7 @@ func main() { //nolint:funlen logrus.WithError(err).Fatal("Failed to determine path to launcher") } - cmd := execabs.Command(exe, appendLauncherPath(launcher, os.Args[1:])...) // nolint:gosec + cmd := execabs.Command(exe, appendLauncherPath(launcher, os.Args[1:])...) //nolint:gosec cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout diff --git a/go.mod b/go.mod index 7f73b291..1031e1a5 100644 --- a/go.mod +++ b/go.mod @@ -73,7 +73,7 @@ require ( golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 golang.org/x/sys v0.0.0-20220111092808-5a964db01320 golang.org/x/text v0.3.7 - howett.net/plist v1.0.0 // indirect + howett.net/plist v1.0.0 ) replace ( diff --git a/internal/config/useragent/platform.go b/internal/config/useragent/platform.go index aed21d5c..bf3bfafe 100644 --- a/internal/config/useragent/platform.go +++ b/internal/config/useragent/platform.go @@ -22,7 +22,6 @@ import ( "strings" "github.com/Masterminds/semver/v3" - "golang.org/x/sys/execabs" ) // IsCatalinaOrNewer checks whether the host is MacOS Catalina 10.15.x or higher. @@ -35,20 +34,20 @@ func IsBigSurOrNewer() bool { return isThisDarwinNewerOrEqual(getMinBigSur()) } -func getMinCatalina() *semver.Version { return semver.MustParse("10.15.0") } -func getMinBigSur() *semver.Version { return semver.MustParse("10.16.0") } +func getMinCatalina() *semver.Version { return semver.MustParse("19.0.0") } +func getMinBigSur() *semver.Version { return semver.MustParse("20.0.0") } func isThisDarwinNewerOrEqual(minVersion *semver.Version) bool { if runtime.GOOS != "darwin" { return false } - rawVersion, err := execabs.Command("sw_vers", "-productVersion").Output() + rawVersion, err := getDarwinVersion() if err != nil { return false } - return isVersionEqualOrNewer(minVersion, strings.TrimSpace(string(rawVersion))) + return isVersionEqualOrNewer(minVersion, strings.TrimSpace(rawVersion)) } // isVersionEqualOrNewer is separated to be able to run test on other than darwin. diff --git a/internal/config/useragent/platform_darwin.go b/internal/config/useragent/platform_darwin.go new file mode 100644 index 00000000..acc2a5a6 --- /dev/null +++ b/internal/config/useragent/platform_darwin.go @@ -0,0 +1,29 @@ +// 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 . + +//go:build darwin +// +build darwin + +package useragent + +import ( + "syscall" +) + +func getDarwinVersion() (string, error) { + return syscall.Sysctl("kern.osrelease") +} diff --git a/internal/config/useragent/platform_default.go b/internal/config/useragent/platform_default.go new file mode 100644 index 00000000..23f7e1f8 --- /dev/null +++ b/internal/config/useragent/platform_default.go @@ -0,0 +1,27 @@ +// 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 . + +//go:build !darwin +// +build !darwin + +package useragent + +import "errors" + +func getDarwinVersion() (string, error) { + return "", errors.New("implemented only for darwin") +} diff --git a/internal/config/useragent/platform_test.go b/internal/config/useragent/platform_test.go index 27136444..53310d55 100644 --- a/internal/config/useragent/platform_test.go +++ b/internal/config/useragent/platform_test.go @@ -25,16 +25,11 @@ import ( func TestIsVersionCatalinaOrNewer(t *testing.T) { testData := map[struct{ version string }]bool{ - {""}: false, - {"9.0.0"}: false, - {"9.15.0"}: false, - {"10.13.0"}: false, - {"10.14.0"}: false, - {"10.14.99"}: false, - {"10.15.0"}: true, - {"10.16.0"}: true, - {"11.0.0"}: true, - {"11.1"}: true, + {""}: false, + {"18.0.0"}: false, + {"19.0.0"}: true, + {"20.0.0"}: true, + {"21.0.0"}: true, } for args, exp := range testData { @@ -45,16 +40,11 @@ func TestIsVersionCatalinaOrNewer(t *testing.T) { func TestIsVersionBigSurOrNewer(t *testing.T) { testData := map[struct{ version string }]bool{ - {""}: false, - {"9.0.0"}: false, - {"9.15.0"}: false, - {"10.13.0"}: false, - {"10.14.0"}: false, - {"10.14.99"}: false, - {"10.15.0"}: false, - {"10.16.0"}: true, - {"11.0.0"}: true, - {"11.1"}: true, + {""}: false, + {"18.0.0"}: false, + {"19.0.0"}: false, + {"20.0.0"}: true, + {"21.0.0"}: true, } for args, exp := range testData { diff --git a/internal/frontend/clientconfig/config_applemail.go b/internal/frontend/clientconfig/config_applemail.go index f2e7c637..58d2fd89 100644 --- a/internal/frontend/clientconfig/config_applemail.go +++ b/internal/frontend/clientconfig/config_applemail.go @@ -28,10 +28,10 @@ import ( "strings" "time" - "github.com/ProtonMail/proton-bridge/internal/bridge" - "github.com/ProtonMail/proton-bridge/internal/config/useragent" - "github.com/ProtonMail/proton-bridge/internal/frontend/types" - "github.com/ProtonMail/proton-bridge/pkg/mobileconfig" + "github.com/ProtonMail/proton-bridge/v2/internal/bridge" + "github.com/ProtonMail/proton-bridge/v2/internal/config/useragent" + "github.com/ProtonMail/proton-bridge/v2/internal/frontend/types" + "github.com/ProtonMail/proton-bridge/v2/pkg/mobileconfig" "golang.org/x/sys/execabs" ) diff --git a/internal/frontend/theme/detect_darwin.go b/internal/frontend/theme/detect_darwin.go index b7da2e9e..f1412961 100644 --- a/internal/frontend/theme/detect_darwin.go +++ b/internal/frontend/theme/detect_darwin.go @@ -21,15 +21,34 @@ package theme import ( - "strings" + "os" + "path/filepath" - "golang.org/x/sys/execabs" + "howett.net/plist" ) func detectSystemTheme() Theme { - out, err := execabs.Command("defaults", "read", "-g", "AppleInterfaceStyle").Output() //nolint:gosec - if err == nil && strings.TrimSpace(string(out)) == "Dark" { + home, err := os.UserHomeDir() + if err != nil { + return Light + } + + path := filepath.Join(home, "/Library/Preferences/.GlobalPreferences.plist") + prefFile, err := os.Open(path) + if err != nil { + return Light + } + defer prefFile.Close() + + var data struct { + AppleInterfaceStyle string `plist:AppleInterfaceStyle` + } + + dec := plist.NewDecoder(prefFile) + err = dec.Decode(&data) + if err == nil && data.AppleInterfaceStyle == "Dark" { return Dark } + return Light } diff --git a/internal/smtp/user.go b/internal/smtp/user.go index ec36ee52..9636e9a5 100644 --- a/internal/smtp/user.go +++ b/internal/smtp/user.go @@ -313,16 +313,16 @@ func (su *smtpUser) Send(returnPath string, to []string, messageReader io.Reader startTime := time.Now() for isSending && time.Since(startTime) < 90*time.Second { - log.Debug("Message is still in send queue, waiting for a bit") + log.Warn("Message is still in send queue, waiting for a bit") time.Sleep(15 * time.Second) isSending, wasSent = su.backend.sendRecorder.isSendingOrSent(su.client(), sendRecorderMessageHash) } if isSending { - log.Debug("Message is still in send queue, returning error to prevent client from adding it to the sent folder prematurely") + log.Warn("Message is still in send queue, returning error to prevent client from adding it to the sent folder prematurely") return errors.New("original message is still being sent") } if wasSent { - log.Debug("Message was already sent") + log.Warn("Message was already sent") return nil } diff --git a/internal/store/ulimit.go b/internal/store/ulimit_default.go similarity index 55% rename from internal/store/ulimit.go rename to internal/store/ulimit_default.go index 76c8527c..cbe3461c 100644 --- a/internal/store/ulimit.go +++ b/internal/store/ulimit_default.go @@ -15,46 +15,55 @@ // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . +//go:build !windows +// +build !windows + package store import ( - "os" "runtime" - - "golang.org/x/sys/unix" + "syscall" ) +func getCurrentFDLimit() (int, error) { + var limits syscall.Rlimit + err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limits) + if err != nil { + return 0, err + } + return int(limits.Cur), nil +} + +func countOpenedFDs(limit int) int { + openedFDs := 0 + + for i := 0; i < limit; i++ { + _, _, err := syscall.Syscall(syscall.SYS_FCNTL, uintptr(i), uintptr(syscall.F_GETFL), 0) + if err == 0 { + openedFDs++ + } + } + + return openedFDs +} + func isFdCloseToULimit() bool { if runtime.GOOS != "darwin" && runtime.GOOS != "linux" { return false } - var fdPath string - switch runtime.GOOS { - case "darwin": - fdPath = "/dev/fd" - case "linux": - fdPath = "/proc/self/fd" - } - f, err := os.Open(fdPath) + limit, err := getCurrentFDLimit() if err != nil { - log.Warn("isFdCloseToULimit: ", err) + log.WithError(err).Error("Cannot get current FD limit") return false } - d, err := f.ReadDir(-1) - if err != nil { - log.Warn("isFdCloseToULimit: ", err) - return false - } - fd := len(d) - 1 - var lim unix.Rlimit - err = unix.Getrlimit(unix.RLIMIT_NOFILE, &lim) - if err != nil { - log.Print(err) - } - ulimit := lim.Max + openedFDs := countOpenedFDs(limit) - log.Info("File descriptor check: num goroutines ", runtime.NumGoroutine(), " fd ", fd, " ulimit ", ulimit) - return fd >= int(0.95*float64(ulimit)) + log. + WithField("noGoroutines", runtime.NumCgoCall()). + WithField("noFDs", openedFDs). + WithField("limitFD", limit). + Info("File descriptor check") + return openedFDs >= int(0.95*float64(limit)) } diff --git a/internal/store/ulimit_windows.go b/internal/store/ulimit_windows.go new file mode 100644 index 00000000..c6775393 --- /dev/null +++ b/internal/store/ulimit_windows.go @@ -0,0 +1,23 @@ +// 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 . + +//go:build windows +// +build windows + +package store + +func isFdCloseToULimit() bool { return false } diff --git a/internal/updater/sync_test.go b/internal/updater/sync_test.go index 265d8e5b..c7d816b6 100644 --- a/internal/updater/sync_test.go +++ b/internal/updater/sync_test.go @@ -25,7 +25,6 @@ import ( "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" - "golang.org/x/sys/execabs" ) const ( @@ -39,77 +38,103 @@ const ( func TestSyncFolder(t *testing.T) { for _, srcType := range []string{EmptyType, FileType, SymlinkType, DirType} { for _, dstType := range []string{EmptyType, FileType, SymlinkType, DirType} { - require.NoError(t, checkCopyWorks(srcType, dstType)) + checkCopyWorks(t, srcType, dstType) logrus.Warn("OK: from ", srcType, " to ", dstType) } } } -func checkCopyWorks(srcType, dstType string) error { +func checkCopyWorks(tb testing.TB, srcType, dstType string) { + r := require.New(tb) dirName := "from_" + srcType + "_to_" + dstType AppCacheDir := "/tmp" srcDir := filepath.Join(AppCacheDir, "sync_src", dirName) destDir := filepath.Join(AppCacheDir, "sync_dst", dirName) // clear before - logrus.Info("remove all ", srcDir) - err := os.RemoveAll(srcDir) - if err != nil { - return err - } - - logrus.Info("remove all ", destDir) - err = os.RemoveAll(destDir) - if err != nil { - return err - } + r.NoError(os.RemoveAll(srcDir)) + r.NoError(os.RemoveAll(destDir)) // create - err = createTestFolder(srcDir, srcType) - if err != nil { - return err - } - - err = createTestFolder(destDir, dstType) - if err != nil { - return err - } + r.NoError(createTestFolder(srcDir, srcType)) + r.NoError(createTestFolder(destDir, dstType)) // copy - logrus.Info("Sync from ", srcDir, " to ", destDir) - err = syncFolders(destDir, srcDir) - if err != nil { - return err - } + r.NoError(syncFolders(destDir, srcDir)) // Check - logrus.Info("check ", srcDir, " and ", destDir) - err = checkThatFilesAreSame(srcDir, destDir) - if err != nil { - return err - } + checkThatFilesAreSame(r, srcDir, destDir) // clear after - logrus.Info("remove all ", srcDir) - err = os.RemoveAll(srcDir) - if err != nil { - return err - } - - logrus.Info("remove all ", destDir) - err = os.RemoveAll(destDir) - if err != nil { - return err - } - - return err + r.NoError(os.RemoveAll(srcDir)) + r.NoError(os.RemoveAll(destDir)) } -func checkThatFilesAreSame(src, dst string) error { - cmd := execabs.Command("diff", "-qr", src, dst) //nolint:gosec - cmd.Stderr = logrus.StandardLogger().WriterLevel(logrus.ErrorLevel) - cmd.Stdout = logrus.StandardLogger().WriterLevel(logrus.InfoLevel) - return cmd.Run() +func checkThatFilesAreSame(r *require.Assertions, src, dst string) { + srcFiles, srcDirs, err := walkDir(src) + r.NoError(err) + + dstFiles, dstDirs, err := walkDir(dst) + r.NoError(err) + + r.ElementsMatch(srcFiles, dstFiles) + r.ElementsMatch(srcDirs, dstDirs) + + for _, relPath := range srcFiles { + srcPath := filepath.Join(src, relPath) + r.FileExists(srcPath) + + dstPath := filepath.Join(dst, relPath) + r.FileExists(dstPath) + + srcInfo, err := os.Lstat(srcPath) + r.NoError(err) + + dstInfo, err := os.Lstat(dstPath) + r.NoError(err) + + r.Equal(srcInfo.Mode(), dstInfo.Mode()) + + if srcInfo.Mode()&os.ModeSymlink == os.ModeSymlink { + srcLnk, err := os.Readlink(srcPath) + r.NoError(err) + + dstLnk, err := os.Readlink(dstPath) + r.NoError(err) + + r.Equal(srcLnk, dstLnk) + } else { + srcContent, err := ioutil.ReadFile(srcPath) + r.NoError(err) + + dstContent, err := ioutil.ReadFile(dstPath) + r.NoError(err) + + r.Equal(srcContent, dstContent) + } + } +} + +func walkDir(dir string) (files, dirs []string, err error) { + err = filepath.Walk(dir, func(path string, info os.FileInfo, errWalk error) error { + if errWalk != nil { + return errWalk + } + + relPath, errRel := filepath.Rel(dir, path) + if errRel != nil { + return errRel + } + + if info.IsDir() { + dirs = append(dirs, relPath) + } else { + files = append(files, relPath) + } + + return nil + }) + return } func createTestFolder(dirPath, dirType string) error { diff --git a/test/context/context.go b/test/context/context.go index 381cda24..b4a4ff0d 100644 --- a/test/context/context.go +++ b/test/context/context.go @@ -175,5 +175,6 @@ func (ctx *TestContext) MessagePreparationFinished(username string) { } func (ctx *TestContext) CredentialsFailsOnWrite(shouldFail bool) { - ctx.credStore.(*fakeCredStore).failOnWrite = shouldFail //nolint:forcetypeassert + ctx.credStore.(*fakeCredStore).failOnWrite = shouldFail //nolint:forcetypeassert + ctx.addCleanup(func() { ctx.credStore.(*fakeCredStore).failOnWrite = false }, "credentials-cleanup") //nolint:forcetypeassert } diff --git a/test/context/credentials.go b/test/context/credentials.go index 790c8919..daa1a4cf 100644 --- a/test/context/credentials.go +++ b/test/context/credentials.go @@ -35,7 +35,7 @@ type fakeCredStore struct { // newFakeCredStore returns a fake credentials store (optionally configured with the given credentials). func newFakeCredStore(initCreds ...*credentials.Credentials) (c *fakeCredStore) { - c = &fakeCredStore{credentials: map[string]*credentials.Credentials{}} + c = &fakeCredStore{credentials: map[string]*credentials.Credentials{}, failOnWrite: false} for _, creds := range initCreds { if creds == nil { continue