diff --git a/.gitignore b/.gitignore index 03d8b25f..0b8e5e9b 100644 --- a/.gitignore +++ b/.gitignore @@ -12,8 +12,7 @@ go.mod.* vendor # testing -testing -spn/testing/simple/testdata +testdata # Compiled Object files, Static and Dynamic libs (Shared Objects) *.a diff --git a/Earthfile b/Earthfile index e2de6900..1828867b 100644 --- a/Earthfile +++ b/Earthfile @@ -70,6 +70,11 @@ build: # ./dist/all/assets.zip BUILD +assets +build-spn: + BUILD +go-build --CMDS="hub" --GOOS="linux" --GOARCH="amd64" + BUILD +go-build --CMDS="hub" --GOOS="linux" --GOARCH="arm64" + # TODO: Add other platforms + go-ci: BUILD +go-build --GOOS="linux" --GOARCH="amd64" BUILD +go-build --GOOS="linux" --GOARCH="arm64" diff --git a/base/database/main.go b/base/database/main.go index ed0bb934..91cf02da 100644 --- a/base/database/main.go +++ b/base/database/main.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" + "github.com/safing/portmaster/base/utils" "github.com/tevino/abool" ) @@ -23,10 +24,14 @@ func Initialize(databasesRootDir string) error { if initialized.SetToIf(false, true) { rootDir = databasesRootDir - // Ensure database root dir exists. err := os.MkdirAll(rootDir, 0o0700) if err != nil { - return fmt.Errorf("could not create/open database directory (%s): %w", rootDir, err) + return fmt.Errorf("failed to create/check database dir %q: %w", rootDir, err) + } + // ensure root and databases dirs + err = utils.EnsureDirectory(rootDir, utils.AdminOnlyPermission) + if err != nil { + return fmt.Errorf("could not set permissions to database directory (%s): %w", rootDir, err) } return nil @@ -63,5 +68,9 @@ func getLocation(name, storageType string) (string, error) { if err != nil { return "", fmt.Errorf("failed to create/check database dir %q: %w", location, err) } + err = utils.EnsureDirectory(location, utils.AdminOnlyPermission) + if err != nil { + return "", fmt.Errorf("could not set permissions to directory (%s): %w", location, err) + } return location, nil } diff --git a/base/database/storage/fstree/fstree.go b/base/database/storage/fstree/fstree.go index 7965439a..4b04f41d 100644 --- a/base/database/storage/fstree/fstree.go +++ b/base/database/storage/fstree/fstree.go @@ -15,7 +15,6 @@ import ( "strings" "time" - "github.com/hectane/go-acl" "github.com/safing/portmaster/base/database/iterator" "github.com/safing/portmaster/base/database/query" "github.com/safing/portmaster/base/database/record" @@ -289,11 +288,8 @@ func writeFile(filename string, data []byte, perm os.FileMode) error { defer t.Cleanup() //nolint:errcheck // Set permissions before writing data, in case the data is sensitive. - if onWindows { - err = acl.Chmod(filename, perm) - } else { - err = t.Chmod(perm) - } + // TODO(vladimir): to set permissions on windows we need the full path of the file. + err = t.Chmod(perm) if err != nil { return err } diff --git a/base/info/version.go b/base/info/version.go index 24247b0d..be241e7e 100644 --- a/base/info/version.go +++ b/base/info/version.go @@ -74,6 +74,7 @@ func Set(setName string, setVersion string, setLicenseName string) { if setVersion != "" { version = setVersion + versionNumber = setVersion } } diff --git a/base/utils/fs.go b/base/utils/fs.go index bb59960f..d32715af 100644 --- a/base/utils/fs.go +++ b/base/utils/fs.go @@ -6,15 +6,13 @@ import ( "io/fs" "os" "runtime" - - "github.com/hectane/go-acl" ) const isWindows = runtime.GOOS == "windows" // EnsureDirectory ensures that the given directory exists and that is has the given permissions set. // If path is a file, it is deleted and a directory created. -func EnsureDirectory(path string, perm os.FileMode) error { +func EnsureDirectory(path string, perm FSPermission) error { // open path f, err := os.Stat(path) if err == nil { @@ -23,10 +21,10 @@ func EnsureDirectory(path string, perm os.FileMode) error { // directory exists, check permissions if isWindows { // Ignore windows permission error. For none admin users it will always fail. - acl.Chmod(path, perm) + _ = SetDirPermission(path, perm) return nil - } else if f.Mode().Perm() != perm { - return os.Chmod(path, perm) + } else if f.Mode().Perm() != perm.AsUnixDirExecPermission() { + return SetDirPermission(path, perm) } return nil } @@ -37,17 +35,17 @@ func EnsureDirectory(path string, perm os.FileMode) error { } // file does not exist (or has been deleted) if err == nil || errors.Is(err, fs.ErrNotExist) { - err = os.Mkdir(path, perm) + err = os.MkdirAll(path, perm.AsUnixDirExecPermission()) if err != nil { return fmt.Errorf("could not create dir %s: %w", path, err) } - if isWindows { - // Ignore windows permission error. For none admin users it will always fail. - acl.Chmod(path, perm) - return nil - } else { - return os.Chmod(path, perm) + // Set permissions. + err = SetDirPermission(path, perm) + // Ignore windows permission error. For none admin users it will always fail. + if !isWindows { + return err } + return nil } // other error opening path return fmt.Errorf("failed to access %s: %w", path, err) diff --git a/base/utils/permissions.go b/base/utils/permissions.go new file mode 100644 index 00000000..d7690724 --- /dev/null +++ b/base/utils/permissions.go @@ -0,0 +1,20 @@ +//go:build !windows + +package utils + +import "os" + +// SetDirPermission sets the permission of a directory. +func SetDirPermission(path string, perm FSPermission) error { + return os.Chmod(path, perm.AsUnixDirExecPermission()) +} + +// SetExecPermission sets the permission of an executable file. +func SetExecPermission(path string, perm FSPermission) error { + return SetDirPermission(path, perm) +} + +// SetFilePermission sets the permission of a non executable file. +func SetFilePermission(path string, perm FSPermission) error { + return os.Chmod(path, perm.AsUnixFilePermission()) +} diff --git a/base/utils/permissions_windows.go b/base/utils/permissions_windows.go new file mode 100644 index 00000000..13cbf583 --- /dev/null +++ b/base/utils/permissions_windows.go @@ -0,0 +1,79 @@ +//go:build windows + +package utils + +import ( + "github.com/hectane/go-acl" + "golang.org/x/sys/windows" +) + +var ( + systemSID *windows.SID + adminsSID *windows.SID + usersSID *windows.SID +) + +func init() { + // Initialize Security ID for all need groups. + // Reference: https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/understand-security-identifiers + var err error + systemSID, err = windows.StringToSid("S-1-5-18") // SYSTEM (Local System) + if err != nil { + panic(err) + } + adminsSID, err = windows.StringToSid("S-1-5-32-544") // Administrators + if err != nil { + panic(err) + } + usersSID, err = windows.StringToSid("S-1-5-32-545") // Users + if err != nil { + panic(err) + } +} + +// SetDirPermission sets the permission of a directory. +func SetDirPermission(path string, perm FSPermission) error { + SetFilePermission(path, perm) + return nil +} + +// SetExecPermission sets the permission of an executable file. +func SetExecPermission(path string, perm FSPermission) error { + SetFilePermission(path, perm) + return nil +} + +// SetFilePermission sets the permission of a non executable file. +func SetFilePermission(path string, perm FSPermission) { + switch perm { + case AdminOnlyPermission: + // Set only admin rights, remove all others. + acl.Apply( + path, + true, + false, + acl.GrantSid(windows.GENERIC_ALL|windows.STANDARD_RIGHTS_ALL, systemSID), + acl.GrantSid(windows.GENERIC_ALL|windows.STANDARD_RIGHTS_ALL, adminsSID), + ) + case PublicReadPermission: + // Set admin rights and read/execute rights for users, remove all others. + acl.Apply( + path, + true, + false, + acl.GrantSid(windows.GENERIC_ALL|windows.STANDARD_RIGHTS_ALL, systemSID), + acl.GrantSid(windows.GENERIC_ALL|windows.STANDARD_RIGHTS_ALL, adminsSID), + acl.GrantSid(windows.GENERIC_READ|windows.GENERIC_EXECUTE, usersSID), + ) + case PublicWritePermission: + // Set full control to admin and regular users. Guest users will not have access. + acl.Apply( + path, + true, + false, + acl.GrantSid(windows.GENERIC_ALL|windows.STANDARD_RIGHTS_ALL, systemSID), + acl.GrantSid(windows.GENERIC_ALL|windows.STANDARD_RIGHTS_ALL, adminsSID), + acl.GrantSid(windows.GENERIC_ALL|windows.STANDARD_RIGHTS_ALL, usersSID), + ) + } +} diff --git a/base/utils/structure.go b/base/utils/structure.go index 5a50d97e..8e1f0786 100644 --- a/base/utils/structure.go +++ b/base/utils/structure.go @@ -2,25 +2,61 @@ package utils import ( "fmt" - "os" + "io/fs" "path/filepath" "strings" "sync" ) +type FSPermission uint8 + +const ( + AdminOnlyPermission FSPermission = iota + PublicReadPermission + PublicWritePermission +) + +// AsUnixDirExecPermission return the corresponding unix permission for a directory or executable. +func (perm FSPermission) AsUnixDirExecPermission() fs.FileMode { + switch perm { + case AdminOnlyPermission: + return 0o700 + case PublicReadPermission: + return 0o755 + case PublicWritePermission: + return 0o777 + } + + return 0 +} + +// AsUnixFilePermission return the corresponding unix permission for a regular file. +func (perm FSPermission) AsUnixFilePermission() fs.FileMode { + switch perm { + case AdminOnlyPermission: + return 0o600 + case PublicReadPermission: + return 0o644 + case PublicWritePermission: + return 0o666 + } + + return 0 +} + // DirStructure represents a directory structure with permissions that should be enforced. type DirStructure struct { sync.Mutex Path string Dir string - Perm os.FileMode + Perm FSPermission Parent *DirStructure Children map[string]*DirStructure } // NewDirStructure returns a new DirStructure. -func NewDirStructure(path string, perm os.FileMode) *DirStructure { +func NewDirStructure(path string, perm FSPermission) *DirStructure { return &DirStructure{ Path: path, Perm: perm, @@ -29,7 +65,7 @@ func NewDirStructure(path string, perm os.FileMode) *DirStructure { } // ChildDir adds a new child DirStructure and returns it. Should the child already exist, the existing child is returned and the permissions are updated. -func (ds *DirStructure) ChildDir(dirName string, perm os.FileMode) (child *DirStructure) { +func (ds *DirStructure) ChildDir(dirName string, perm FSPermission) (child *DirStructure) { ds.Lock() defer ds.Unlock() diff --git a/base/utils/structure_test.go b/base/utils/structure_test.go index 2acfebd2..d3debcf7 100644 --- a/base/utils/structure_test.go +++ b/base/utils/structure_test.go @@ -13,13 +13,13 @@ func ExampleDirStructure() { // output: // / [755] // /repo [777] - // /repo/b [707] - // /repo/b/c [750] - // /repo/b/d [707] - // /repo/b/d/e [707] - // /repo/b/d/f [707] - // /repo/b/d/f/g [707] - // /repo/b/d/f/g/h [707] + // /repo/b [755] + // /repo/b/c [777] + // /repo/b/d [755] + // /repo/b/d/e [755] + // /repo/b/d/f [755] + // /repo/b/d/f/g [755] + // /repo/b/d/f/g/h [755] // /secret [700] basePath, err := os.MkdirTemp("", "") @@ -28,12 +28,12 @@ func ExampleDirStructure() { return } - ds := NewDirStructure(basePath, 0o0755) - secret := ds.ChildDir("secret", 0o0700) - repo := ds.ChildDir("repo", 0o0777) - _ = repo.ChildDir("a", 0o0700) - b := repo.ChildDir("b", 0o0707) - c := b.ChildDir("c", 0o0750) + ds := NewDirStructure(basePath, PublicReadPermission) + secret := ds.ChildDir("secret", AdminOnlyPermission) + repo := ds.ChildDir("repo", PublicWritePermission) + _ = repo.ChildDir("a", AdminOnlyPermission) + b := repo.ChildDir("b", PublicReadPermission) + c := b.ChildDir("c", PublicWritePermission) err = ds.Ensure() if err != nil { diff --git a/cmds/hub/main.go b/cmds/hub/main.go index 1e4e5116..0d453df1 100644 --- a/cmds/hub/main.go +++ b/cmds/hub/main.go @@ -33,7 +33,7 @@ var ( ) func init() { - // Add persisent flags for all commands. + // Add persistent flags for all commands. rootCmd.PersistentFlags().StringVar(&binDir, "bin-dir", "", "set directory for executable binaries (rw/ro)") rootCmd.PersistentFlags().StringVar(&dataDir, "data-dir", "", "set directory for variable data (rw)") @@ -61,8 +61,8 @@ func main() { } func initializeGlobals(cmd *cobra.Command, args []string) { - // Set version info. - info.Set("SPN Hub", "", "GPLv3") + // Set name and license. + info.Set("SPN Hub", "0.7.8", "GPLv3") // Configure metrics. _ = metrics.SetNamespace("hub") @@ -78,6 +78,7 @@ func initializeGlobals(cmd *cobra.Command, args []string) { svc, err := service.New(svcCfg) return svc, err } + cmdbase.SvcConfig = &service.ServiceConfig{ BinDir: binDir, DataDir: dataDir, diff --git a/cmds/observation-hub/main.go b/cmds/observation-hub/main.go index ec34d53b..d1cfadb7 100644 --- a/cmds/observation-hub/main.go +++ b/cmds/observation-hub/main.go @@ -35,7 +35,7 @@ var ( ) func init() { - // Add persisent flags for all commands. + // Add persistent flags for all commands. rootCmd.PersistentFlags().StringVar(&binDir, "bin-dir", "", "set directory for executable binaries (rw/ro)") rootCmd.PersistentFlags().StringVar(&dataDir, "data-dir", "", "set directory for variable data (rw)") diff --git a/go.mod b/go.mod index 97565451..ae834415 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/awalterschulze/gographviz v2.0.3+incompatible github.com/bluele/gcache v0.0.2 github.com/brianvoe/gofakeit v3.18.0+incompatible - github.com/cilium/ebpf v0.16.0 + github.com/cilium/ebpf v0.17.1 github.com/coreos/go-iptables v0.8.0 github.com/davecgh/go-spew v1.1.1 github.com/dgraph-io/badger v1.6.2 @@ -33,10 +33,10 @@ require ( github.com/hashicorp/go-version v1.7.0 github.com/hectane/go-acl v0.0.0-20230122075934-ca0b05cb1adb github.com/jackc/puddle/v2 v2.2.2 - github.com/lmittmann/tint v1.0.5 - github.com/maruel/panicparse/v2 v2.3.1 + github.com/lmittmann/tint v1.0.6 + github.com/maruel/panicparse/v2 v2.4.0 github.com/mat/besticon v3.12.0+incompatible - github.com/mattn/go-colorable v0.1.13 + github.com/mattn/go-colorable v0.1.14 github.com/mattn/go-isatty v0.0.20 github.com/miekg/dns v1.1.62 github.com/mitchellh/copystructure v1.2.0 @@ -61,29 +61,28 @@ require ( github.com/varlink/go v0.4.0 github.com/vincent-petithory/dataurl v1.0.0 go.etcd.io/bbolt v1.3.11 - golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f - golang.org/x/image v0.22.0 - golang.org/x/net v0.31.0 - golang.org/x/sync v0.9.0 - golang.org/x/sys v0.27.0 + golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 + golang.org/x/image v0.23.0 + golang.org/x/net v0.34.0 + golang.org/x/sync v0.10.0 + golang.org/x/sys v0.29.0 gopkg.in/yaml.v3 v3.0.1 zombiezen.com/go/sqlite v1.4.0 ) require ( + al.essio.dev/pkg/shellescape v1.5.1 // indirect github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect github.com/aead/ecdh v0.2.0 // indirect - github.com/alessio/shellescape v1.4.2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/danieljoos/wincred v1.2.1 // indirect - github.com/dgraph-io/ristretto v0.1.1 // indirect + github.com/danieljoos/wincred v1.2.2 // indirect + github.com/dgraph-io/ristretto v0.2.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f // indirect - github.com/golang/glog v1.2.1 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/uuid v1.6.0 // indirect @@ -112,16 +111,16 @@ require ( github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect - github.com/zalando/go-keyring v0.2.5 // indirect + github.com/zalando/go-keyring v0.2.6 // indirect github.com/zeebo/blake3 v0.2.4 // indirect - golang.org/x/crypto v0.29.0 // indirect + golang.org/x/crypto v0.32.0 // indirect golang.org/x/mod v0.22.0 // indirect - golang.org/x/text v0.20.0 // indirect - golang.org/x/tools v0.27.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect + golang.org/x/text v0.21.0 // indirect + golang.org/x/tools v0.29.0 // indirect + google.golang.org/protobuf v1.36.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - modernc.org/libc v1.61.0 // indirect - modernc.org/mathutil v1.6.0 // indirect - modernc.org/memory v1.8.0 // indirect - modernc.org/sqlite v1.33.1 // indirect + modernc.org/libc v1.61.7 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.8.1 // indirect + modernc.org/sqlite v1.34.4 // indirect ) diff --git a/go.sum b/go.sum index ec1cfa1a..9c99a07f 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho= +al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890= cloud.google.com/go v0.16.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= @@ -14,8 +16,6 @@ github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6 h1:5L8Mj9Co9sJVgW3TpY github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6/go.mod h1:3HgLJ9d18kXMLQlJvIY3+FszZYMxCz8WfE2MQ7hDY0w= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= -github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0= -github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= @@ -27,13 +27,12 @@ github.com/bradfitz/gomemcache v0.0.0-20170208213004-1952afaa557d/go.mod h1:PmM6 github.com/brianvoe/gofakeit v3.18.0+incompatible h1:wDOmHc9DLG4nRjUVVaxA+CEglKOW72Y5+4WNxUIkjM8= github.com/brianvoe/gofakeit v3.18.0+incompatible/go.mod h1:kfwdRA90vvNhPutZWfH7WPaDzUjz+CZFqG+rPkOjGOc= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cilium/ebpf v0.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= -github.com/cilium/ebpf v0.16.0 h1:+BiEnHL6Z7lXnlGUsXQPPAE7+kenAd4ES8MQ5min0Ok= -github.com/cilium/ebpf v0.16.0/go.mod h1:L7u2Blt2jMM/vLAVgjxluxtBKlz3/GWjB0dMOEngfwE= +github.com/cilium/ebpf v0.17.1 h1:G8mzU81R2JA1nE5/8SRubzqvBMmAmri2VL8BIZPWvV0= +github.com/cilium/ebpf v0.17.1/go.mod h1:vay2FaYSmIlv3r8dNACd4mW/OCaZLJKJOo+IHBvCIO8= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc= @@ -41,18 +40,19 @@ github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFE github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/danieljoos/wincred v1.2.1 h1:dl9cBrupW8+r5250DYkYxocLeZ1Y4vB1kxgtjxw8GQs= -github.com/danieljoos/wincred v1.2.1/go.mod h1:uGaFL9fDn3OLTvzCGulzE+SzjEe5NGlh5FdCcyfPwps= +github.com/danieljoos/wincred v1.2.2 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0= +github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8= github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= -github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= -github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= +github.com/dgraph-io/ristretto v0.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE= +github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dhaavi/winres v0.2.2 h1:SUago7FwhgLSMyDdeuV6enBZ+ZQSl0KwcnbWzvlfBls= github.com/dhaavi/winres v0.2.2/go.mod h1:1NTs+/DtKP1BplIL1+XQSoq4X1PUfLczexS7gf3x9T4= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -92,9 +92,6 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGw github.com/golang/gddo v0.0.0-20180823221919-9d8ff1c67be5/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4= github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f h1:16RtHeWGkJMc80Etb8RPCcKevXGldr57+LOyZt8zOlg= github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f/go.mod h1:ijRvpgDJDI262hYq/IQVYgf8hd8IHUs93Ol0kvMBAx4= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4= -github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/lint v0.0.0-20170918230701-e5d664eb928e/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= @@ -167,21 +164,18 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lmittmann/tint v1.0.5 h1:NQclAutOfYsqs2F1Lenue6OoWCajs5wJcP3DfWVpePw= -github.com/lmittmann/tint v1.0.5/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/lmittmann/tint v1.0.6 h1:vkkuDAZXc0EFGNzYjWcV0h7eEX+uujH48f/ifSkJWgc= +github.com/lmittmann/tint v1.0.6/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= github.com/magiconair/properties v1.7.4-0.20170902060319-8d7837e64d3c/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/maruel/panicparse/v2 v2.3.1 h1:NtJavmbMn0DyzmmSStE8yUsmPZrZmudPH7kplxBinOA= -github.com/maruel/panicparse/v2 v2.3.1/go.mod h1:s3UmQB9Fm/n7n/prcD2xBGDkwXD6y2LeZnhbEXvs9Dg= +github.com/maruel/panicparse/v2 v2.4.0 h1:yQKMIbQ0DKfinzVkTkcUzQyQ60UCiNnYfR7PWwTs2VI= +github.com/maruel/panicparse/v2 v2.4.0/go.mod h1:nOY2OKe8csO3F3SA5+hsxot05JLgukrF54B9x88fVp4= github.com/mat/besticon v3.12.0+incompatible h1:1KTD6wisfjfnX+fk9Kx/6VEZL+MAW1LhCkL9Q47H9Bg= github.com/mat/besticon v3.12.0+incompatible/go.mod h1:mA1auQYHt6CW5e7L9HJLmqVQC8SzNk2gVwouO0AbiEU= github.com/mattn/go-colorable v0.0.10-0.20170816031813-ad5389df28cd/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.2/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo= @@ -207,7 +201,6 @@ github.com/mdlayher/socket v0.1.0/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5A github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs= github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= -github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= @@ -325,8 +318,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -github.com/zalando/go-keyring v0.2.5 h1:Bc2HHpjALryKD62ppdEzaFG6VxL6Bc+5v0LYpN8Lba8= -github.com/zalando/go-keyring v0.2.5/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= +github.com/zalando/go-keyring v0.2.6 h1:r7Yc3+H+Ux0+M72zacZoItR3UDxeWfKTcabvkI8ua9s= +github.com/zalando/go-keyring v0.2.6/go.mod h1:2TCrxYrbUNYfNS/Kgy/LSrkSQzZ5UPVH85RwfczwvcI= github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= @@ -340,12 +333,12 @@ golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= -golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= -golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= -golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= -golang.org/x/image v0.22.0 h1:UtK5yLUzilVrkjMAZAZ34DXGpASN8i8pj8g+O+yd10g= -golang.org/x/image v0.22.0/go.mod h1:9hPFhljd4zZ1GNSIZJ49sqbp45GKK9t6w+iXvGqZUz4= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= +golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= +golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= +golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -374,15 +367,15 @@ golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/oauth2 v0.0.0-20170912212905-13449ad91cb2/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20170517211232-f52d1811a629/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -408,7 +401,6 @@ golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -416,13 +408,10 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -430,16 +419,16 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20170424234030-8be79e1e0910/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= -golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= -golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= +golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= +golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -448,8 +437,8 @@ google.golang.org/api v0.0.0-20170921000349-586095a6e407/go.mod h1:4mhQ8q/RsB7i+ google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20170918111702-1e559d0a00ee/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/grpc v1.2.1-0.20170921194603-d4b75ebd4f9f/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU= +google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -461,28 +450,28 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY= honnef.co/go/tools v0.2.2/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY= -modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= -modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= -modernc.org/ccgo/v4 v4.21.0 h1:kKPI3dF7RIag8YcToh5ZwDcVMIv6VGa0ED5cvh0LMW4= -modernc.org/ccgo/v4 v4.21.0/go.mod h1:h6kt6H/A2+ew/3MW/p6KEoQmrq/i3pr0J/SiwiaF/g0= +modernc.org/cc/v4 v4.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0= +modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.23.10 h1:DnDZT/H6TtoJvQmVf7d8W+lVqEZpIJY/+0ENFh1LIHE= +modernc.org/ccgo/v4 v4.23.10/go.mod h1:vdN4h2WR5aEoNondUx26K7G8X+nuBscYnAEWSRmN2/0= modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= -modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M= -modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= -modernc.org/libc v1.61.0 h1:eGFcvWpqlnoGwzZeZe3PWJkkKbM/3SUGyk1DVZQ0TpE= -modernc.org/libc v1.61.0/go.mod h1:DvxVX89wtGTu+r72MLGhygpfi3aUGgZRdAYGCAVVud0= -modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= -modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= -modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= -modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= -modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= -modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= -modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM= -modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k= -modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= -modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/gc/v2 v2.6.1 h1:+Qf6xdG8l7B27TQ8D8lw/iFMUj1RXRBOuMUWziJOsk8= +modernc.org/gc/v2 v2.6.1/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/libc v1.61.7 h1:exz8rasFniviSgh3dH7QBnQHqYh9lolA5hVYfsiwkfo= +modernc.org/libc v1.61.7/go.mod h1:xspSrXRNVSfWfcfqgvZDVe/Hw5kv4FVC6IRfoms5v/0= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.8.1 h1:HS1HRg1jEohnuONobEq2WrLEhLyw8+J42yLFTnllm2A= +modernc.org/memory v1.8.1/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.34.4 h1:sjdARozcL5KJBvYQvLlZEmctRgW9xqIZc2ncN7PU0P8= +modernc.org/sqlite v1.34.4/go.mod h1:3QQFCG2SEMtc2nv+Wq4cQCH7Hjcg+p/RMlS1XK+zwbk= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= zombiezen.com/go/sqlite v1.4.0 h1:N1s3RIljwtp4541Y8rM880qgGIgq3fTD2yks1xftnKU= diff --git a/service/firewall/interception/dnsmonitor/etwlink_windows.go b/service/firewall/interception/dnsmonitor/etwlink_windows.go index d014bbab..cb9d8675 100644 --- a/service/firewall/interception/dnsmonitor/etwlink_windows.go +++ b/service/firewall/interception/dnsmonitor/etwlink_windows.go @@ -14,7 +14,7 @@ import ( ) type ETWSession struct { - i integration.ETWFunctions + i *integration.ETWFunctions shutdownGuard atomic.Bool shutdownMutex sync.Mutex @@ -23,7 +23,10 @@ type ETWSession struct { } // NewSession creates new ETW event listener and initilizes it. This is a low level interface, make sure to call DestorySession when you are done using it. -func NewSession(etwInterface integration.ETWFunctions, callback func(domain string, result string)) (*ETWSession, error) { +func NewSession(etwInterface *integration.ETWFunctions, callback func(domain string, result string)) (*ETWSession, error) { + if etwInterface == nil { + return nil, fmt.Errorf("etw interface was nil") + } etwSession := &ETWSession{ i: etwInterface, } @@ -47,7 +50,7 @@ func NewSession(etwInterface integration.ETWFunctions, callback func(domain stri // Initialize session. err := etwSession.i.InitializeSession(etwSession.state) if err != nil { - return nil, fmt.Errorf("failed to initialzie session: %q", err) + return nil, fmt.Errorf("failed to initialize session: %q", err) } return etwSession, nil @@ -65,6 +68,10 @@ func (l *ETWSession) IsRunning() bool { // FlushTrace flushes the trace buffer. func (l *ETWSession) FlushTrace() error { + if l.i == nil { + return fmt.Errorf("session not initialized") + } + l.shutdownMutex.Lock() defer l.shutdownMutex.Unlock() @@ -83,6 +90,9 @@ func (l *ETWSession) StopTrace() error { // DestroySession closes the session and frees the allocated memory. Listener cannot be used after this function is called. func (l *ETWSession) DestroySession() error { + if l.i == nil { + return fmt.Errorf("session not initialized") + } l.shutdownMutex.Lock() defer l.shutdownMutex.Unlock() diff --git a/service/firewall/interception/dnsmonitor/eventlistener_windows.go b/service/firewall/interception/dnsmonitor/eventlistener_windows.go index b6a39fd8..a46e8cc6 100644 --- a/service/firewall/interception/dnsmonitor/eventlistener_windows.go +++ b/service/firewall/interception/dnsmonitor/eventlistener_windows.go @@ -23,22 +23,38 @@ func newListener(module *DNSMonitor) (*Listener, error) { ResolverInfo.Source = resolver.ServerSourceETW listener := &Listener{} - var err error // Initialize new dns event session. - listener.etw, err = NewSession(module.instance.OSIntegration().GetETWInterface(), listener.processEvent) + err := initializeSessions(module, listener) if err != nil { - return nil, err + // Listen for event if the dll has been loaded + module.instance.OSIntegration().OnInitializedEvent.AddCallback("loader-listener", func(wc *mgr.WorkerCtx, s struct{}) (cancel bool, err error) { + err = initializeSessions(module, listener) + if err != nil { + return false, err + } + return true, nil + }) } - - // Start listening for events. - module.mgr.Go("etw-dns-event-listener", func(w *mgr.WorkerCtx) error { - return listener.etw.StartTrace() - }) - return listener, nil } +func initializeSessions(module *DNSMonitor, listener *Listener) error { + var err error + listener.etw, err = NewSession(module.instance.OSIntegration().GetETWInterface(), listener.processEvent) + if err != nil { + return err + } + // Start listener + module.mgr.Go("etw-dns-event-listener", func(w *mgr.WorkerCtx) error { + return listener.etw.StartTrace() + }) + return nil +} + func (l *Listener) flush() error { + if l.etw == nil { + return fmt.Errorf("etw not initialized") + } return l.etw.FlushTrace() } diff --git a/service/integration/etw_windows.go b/service/integration/etw_windows.go index eac3ad8f..d655967a 100644 --- a/service/integration/etw_windows.go +++ b/service/integration/etw_windows.go @@ -19,8 +19,8 @@ type ETWFunctions struct { stopOldSession *windows.Proc } -func initializeETW(dll *windows.DLL) (ETWFunctions, error) { - var functions ETWFunctions +func initializeETW(dll *windows.DLL) (*ETWFunctions, error) { + functions := &ETWFunctions{} var err error functions.createState, err = dll.FindProc("PM_ETWCreateState") if err != nil { diff --git a/service/integration/integration_windows.go b/service/integration/integration_windows.go index 40d063f4..b049638d 100644 --- a/service/integration/integration_windows.go +++ b/service/integration/integration_windows.go @@ -5,23 +5,54 @@ package integration import ( "fmt" + "sync" + "github.com/safing/portmaster/base/log" + "github.com/safing/portmaster/service/mgr" "golang.org/x/sys/windows" ) type OSSpecific struct { dll *windows.DLL - etwFunctions ETWFunctions + etwFunctions *ETWFunctions } // Initialize loads the dll and finds all the needed functions from it. func (i *OSIntegration) Initialize() error { + // Try to load dll + err := i.loadDLL() + if err != nil { + log.Errorf("integration: failed to load dll: %s", err) + + callbackLock := sync.Mutex{} + // listen for event from the updater and try to load again if any. + i.instance.BinaryUpdates().EventResourcesUpdated.AddCallback("core-dll-loader", func(wc *mgr.WorkerCtx, s struct{}) (cancel bool, err error) { + // Make sure no multiple callas are executed at the same time. + callbackLock.Lock() + defer callbackLock.Unlock() + + // Try to load again. + err = i.loadDLL() + if err != nil { + log.Errorf("integration: failed to load dll: %s", err) + } else { + log.Info("integration: initialize successful after updater event") + } + return false, nil + }) + + } else { + log.Info("integration: initialize successful") + } + return nil +} + +func (i *OSIntegration) loadDLL() error { // Find path to the dll. file, err := i.instance.BinaryUpdates().GetFile("portmaster-core.dll") if err != nil { return err } - // Load the DLL. i.os.dll, err = windows.LoadDLL(file.Path()) if err != nil { @@ -34,6 +65,9 @@ func (i *OSIntegration) Initialize() error { return err } + // Notify listeners + i.OnInitializedEvent.Submit(struct{}{}) + return nil } @@ -45,7 +79,7 @@ func (i *OSIntegration) CleanUp() error { return nil } -// GetETWInterface return struct containing all the ETW related functions. -func (i *OSIntegration) GetETWInterface() ETWFunctions { +// GetETWInterface return struct containing all the ETW related functions, and nil if it was not loaded yet +func (i *OSIntegration) GetETWInterface() *ETWFunctions { return i.os.etwFunctions } diff --git a/service/integration/module.go b/service/integration/module.go index a8b422dc..4250920b 100644 --- a/service/integration/module.go +++ b/service/integration/module.go @@ -7,8 +7,9 @@ import ( // OSIntegration module provides special integration with the OS. type OSIntegration struct { - m *mgr.Manager - states *mgr.StateMgr + m *mgr.Manager + + OnInitializedEvent *mgr.EventMgr[struct{}] //nolint:unused os OSSpecific @@ -20,10 +21,9 @@ type OSIntegration struct { func New(instance instance) (*OSIntegration, error) { m := mgr.New("OSIntegration") module := &OSIntegration{ - m: m, - states: m.NewStateMgr(), - - instance: instance, + m: m, + OnInitializedEvent: mgr.NewEventMgr[struct{}]("on-initialized", m), + instance: instance, } return module, nil diff --git a/service/intel/filterlists/database.go b/service/intel/filterlists/database.go index 4d5c5c93..15c84a07 100644 --- a/service/intel/filterlists/database.go +++ b/service/intel/filterlists/database.go @@ -49,7 +49,7 @@ var ( var cache = database.NewInterface(&database.Options{ Local: true, Internal: true, - CacheSize: 2 ^ 8, + CacheSize: 256, }) // getFileFunc is the function used to get a file from diff --git a/service/mgr/sleepyticker.go b/service/mgr/sleepyticker.go index 075912a1..ce0b20b4 100644 --- a/service/mgr/sleepyticker.go +++ b/service/mgr/sleepyticker.go @@ -4,7 +4,7 @@ import "time" // SleepyTicker is wrapper over time.Ticker that respects the sleep mode of the module. type SleepyTicker struct { - ticker time.Ticker + ticker *time.Ticker normalDuration time.Duration sleepDuration time.Duration sleepMode bool @@ -16,7 +16,7 @@ type SleepyTicker struct { // If sleepDuration is set to 0 ticker will not tick during sleep. func NewSleepyTicker(normalDuration time.Duration, sleepDuration time.Duration) *SleepyTicker { st := &SleepyTicker{ - ticker: *time.NewTicker(normalDuration), + ticker: time.NewTicker(normalDuration), normalDuration: normalDuration, sleepDuration: sleepDuration, sleepMode: false, diff --git a/service/mgr/sleepyticker_test.go b/service/mgr/sleepyticker_test.go new file mode 100644 index 00000000..9e2175c7 --- /dev/null +++ b/service/mgr/sleepyticker_test.go @@ -0,0 +1,57 @@ +package mgr + +import ( + "testing" + "time" +) + +func TestSleepyTickerStop(t *testing.T) { + normalDuration := 100 * time.Millisecond + sleepDuration := 200 * time.Millisecond + + st := NewSleepyTicker(normalDuration, sleepDuration) + st.Stop() // no panic expected here +} + +func TestSleepyTicker(t *testing.T) { + normalDuration := 100 * time.Millisecond + sleepDuration := 200 * time.Millisecond + + st := NewSleepyTicker(normalDuration, sleepDuration) + + // Test normal mode + select { + case <-st.Wait(): + // Expected tick + case <-time.After(normalDuration + 50*time.Millisecond): + t.Error("expected tick in normal mode") + } + + // Test sleep mode + st.SetSleep(true) + select { + case <-st.Wait(): + // Expected tick + case <-time.After(sleepDuration + 50*time.Millisecond): + t.Error("expected tick in sleep mode") + } + + // Test sleep mode with sleepDuration == 0 + st = NewSleepyTicker(normalDuration, 0) + st.SetSleep(true) + select { + case <-st.Wait(): + t.Error("did not expect tick when sleepDuration is 0") + case <-time.After(normalDuration): + // Expected no tick + } + + // Test stopping the ticker + st.Stop() + select { + case <-st.Wait(): + t.Error("did not expect tick after stopping the ticker") + case <-time.After(normalDuration): + // Expected no tick + } +} diff --git a/service/netquery/database.go b/service/netquery/database.go index d66e3222..4bf28b0b 100644 --- a/service/netquery/database.go +++ b/service/netquery/database.go @@ -19,6 +19,7 @@ import ( "github.com/safing/portmaster/base/config" "github.com/safing/portmaster/base/log" + "github.com/safing/portmaster/base/utils" "github.com/safing/portmaster/service/netquery/orm" "github.com/safing/portmaster/service/network" "github.com/safing/portmaster/service/network/netutils" @@ -131,6 +132,10 @@ func New(dbPath string) (*Database, error) { if err := os.MkdirAll(historyParentDir, 0o0700); err != nil { return nil, fmt.Errorf("failed to ensure database directory exists: %w", err) } + err := utils.EnsureDirectory(historyParentDir, utils.AdminOnlyPermission) + if err != nil { + return nil, fmt.Errorf("failed to set permission to folder %s: %w", historyParentDir, err) + } // Get file location of history database. historyFile := filepath.Join(historyParentDir, "history.db") @@ -229,6 +234,10 @@ func VacuumHistory(ctx context.Context) (err error) { if err := os.MkdirAll(historyParentDir, 0o0700); err != nil { return fmt.Errorf("failed to ensure database directory exists: %w", err) } + err = utils.EnsureDirectory(historyParentDir, utils.AdminOnlyPermission) + if err != nil { + return fmt.Errorf("failed to set permission to folder %s: %w", historyParentDir, err) + } // Get file location of history database. historyFile := filepath.Join(historyParentDir, "history.db") diff --git a/service/network/connection.go b/service/network/connection.go index b3dd70fc..1c1bbf19 100644 --- a/service/network/connection.go +++ b/service/network/connection.go @@ -550,7 +550,11 @@ func (conn *Connection) GatherConnectionInfo(pkt packet.Packet) (err error) { if module.instance.Resolver().IsDisabled() && conn.shouldWaitForDomain() { // Flush the dns listener buffer and try again. for i := range 4 { - _ = module.instance.DNSMonitor().Flush() + err = module.instance.DNSMonitor().Flush() + if err != nil { + // Error flushing, dont try again. + break + } ipinfo, err = resolver.GetIPInfo(resolver.IPInfoProfileScopeGlobal, pkt.Info().RemoteIP().String()) if err == nil { log.Tracer(pkt.Ctx()).Debugf("network: found domain from dnsmonitor after %d tries", i+1) diff --git a/service/profile/module.go b/service/profile/module.go index 68845047..6124d090 100644 --- a/service/profile/module.go +++ b/service/profile/module.go @@ -11,6 +11,7 @@ import ( "github.com/safing/portmaster/base/database" "github.com/safing/portmaster/base/database/migration" "github.com/safing/portmaster/base/log" + "github.com/safing/portmaster/base/utils" _ "github.com/safing/portmaster/service/core/base" "github.com/safing/portmaster/service/mgr" "github.com/safing/portmaster/service/profile/binmeta" @@ -66,10 +67,22 @@ func prep() error { } // Setup icon storage location. - iconsDir := filepath.Join(module.instance.DataDir(), "databases", "icons") + databaseDir := filepath.Join(module.instance.DataDir(), "databases") + iconsDir := filepath.Join(databaseDir, "icons") if err := os.MkdirAll(iconsDir, 0o0700); err != nil { return fmt.Errorf("failed to create/check icons directory: %w", err) } + + // Ensure folder permissions + err := utils.EnsureDirectory(databaseDir, utils.AdminOnlyPermission) + if err != nil { + return fmt.Errorf("failed to set permission to folder %s: %w", databaseDir, err) + } + err = utils.EnsureDirectory(iconsDir, utils.AdminOnlyPermission) + if err != nil { + return fmt.Errorf("failed to set permission to folder %s: %w", iconsDir, err) + } + binmeta.ProfileIconStoragePath = iconsDir return nil diff --git a/service/resolver/resolvers.go b/service/resolver/resolvers.go index 45876b4e..6510036b 100644 --- a/service/resolver/resolvers.go +++ b/service/resolver/resolvers.go @@ -510,7 +510,7 @@ func setScopedResolvers(resolvers []*Resolver) { for _, resolver := range resolvers { if resolver.Info.IPScope.IsLAN() { localResolvers = append(localResolvers, resolver) - } else if _, err := netenv.GetLocalNetwork(resolver.Info.IP); err != nil { + } else if net, _ := netenv.GetLocalNetwork(resolver.Info.IP); net != nil { localResolvers = append(localResolvers, resolver) } diff --git a/service/ui/module.go b/service/ui/module.go index 36fa5ac6..dbc6b47e 100644 --- a/service/ui/module.go +++ b/service/ui/module.go @@ -8,6 +8,7 @@ import ( "github.com/safing/portmaster/base/api" "github.com/safing/portmaster/base/log" + "github.com/safing/portmaster/base/utils" "github.com/safing/portmaster/service/mgr" "github.com/safing/portmaster/service/updates" ) @@ -35,6 +36,12 @@ func start() error { log.Warningf("ui: failed to create safe exec dir: %s", err) } + // Ensure directory permission + err = utils.EnsureDirectory(execDir, utils.PublicWritePermission) + if err != nil { + log.Warningf("ui: failed to set permissions to directory %s: %s", execDir, err) + } + return nil } diff --git a/spn/testing/README.md b/spn/testing/README.md new file mode 100644 index 00000000..72c7ff74 --- /dev/null +++ b/spn/testing/README.md @@ -0,0 +1,25 @@ +# Testing Port17 + +## Simple Docker Setup + +Run `run.sh` to start the docker compose test network. +Then, connect to the test network, by starting the core with the "test" spn map and the correct bootstrap file. + +Run `stop.sh` to remove all docker resources again. + +Setup Guide can be found in the directory. + +## Advanced Setup with Shadow + +For advanced testing we use [shadow](https://github.com/shadow/shadow). +The following section will help you set up shadow and will guide you how to test Port17 in a local Shadow environment. + +### Setting up + +Download the docker version from here: [https://security.cs.georgetown.edu/shadow-docker-images/shadow-standalone.tar.gz](https://security.cs.georgetown.edu/shadow-docker-images/shadow-standalone.tar.gz) + +Then import the image into docker with `gunzip -c shadow-standalone.tar.gz | sudo docker load`. + +### Running + +Execute `sudo docker run -t -i -u shadow shadow-standalone /bin/bash` to start an interactive container with shadow. diff --git a/spn/testing/simple/README.md b/spn/testing/simple/README.md new file mode 100644 index 00000000..bec39dfb --- /dev/null +++ b/spn/testing/simple/README.md @@ -0,0 +1,50 @@ +# Setup Guide + +1. Build SPN Hub + +``` +cd ../../../cmds/hub/ +./build +``` + +2. Reset any previous state (for a fresh test) + +``` +./reset-databases.sh +``` + +3. Change compose file and config template as required + +Files: +- `docker-compose.yml` +- `config-template.json` + +4. Start test network + +``` +./run.sh +``` + +5. Option 1: Join as Hub + +For testing just one Hub with a different build or config, you can simply use `./join.sh` to join the network with the most recently build hub binary. + +6. Option 2: Join as Portmaster + +For connecting to the SPN test network with Portmaster, execute portmaster like this: + +``` +sudo ../../../cmds/portmaster-core/portmaster-core --disable-shutdown-event --devmode --log debug --data /opt/safing/portmaster --spn-map test --bootstrap-file ./testdata/shared/bootstrap.dsd +``` + +Note: +This uses the same portmaster data and config as your installed version. +As the SPN Test net operates under a different ID ("test" instead of "main"), this will not pollute the SPN state of your installed Portmaster. + +7. Stop the test net + +This is important, as just stopping the `./run.sh` script will leave you with interfaces with public IPs! + +``` +./stop.sh +``` diff --git a/spn/testing/simple/clientsim.sh b/spn/testing/simple/clientsim.sh new file mode 100755 index 00000000..a25cf3c4 --- /dev/null +++ b/spn/testing/simple/clientsim.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +cd "$( dirname "${BASH_SOURCE[0]}" )" + +realpath() { + path=`eval echo "$1"` + folder=$(dirname "$path") + echo $(cd "$folder"; pwd)/$(basename "$path"); +} + +if [[ ! -f "../../client" ]]; then + echo "please compile client.go in main directory (output: client)" + exit 1 +fi + +bin_path="$(realpath ../../client)" +data_path="$(realpath ./testdata)" +if [[ ! -d "$data_path" ]]; then + mkdir "$data_path" +fi +shared_path="$(realpath ./testdata/shared)" +if [[ ! -d "$shared_path" ]]; then + mkdir "$shared_path" +fi + +docker network ls | grep spn-simpletest-network >/dev/null 2>&1 +if [[ $? -ne 0 ]]; then + docker network create spn-simpletest-network --subnet 6.0.0.0/24 +fi + +docker run -ti --rm \ +--name spn-simpletest-clientsim \ +--network spn-simpletest-network \ +-v $bin_path:/opt/client:ro \ +-v $data_path/clientsim:/opt/data \ +-v $shared_path:/opt/shared \ +--entrypoint /opt/client \ +toolset.safing.network/dev \ +--data /opt/data \ +--bootstrap-file /opt/shared/bootstrap.dsd \ +--log trace $* \ No newline at end of file diff --git a/spn/testing/simple/config-template.json b/spn/testing/simple/config-template.json new file mode 100644 index 00000000..c9baca7e --- /dev/null +++ b/spn/testing/simple/config-template.json @@ -0,0 +1,19 @@ +{ + "core": { + "log": { + "level": "trace" + }, + "metrics": { + "instance": "test_$HUBNAME", + "push": "" + } + }, + "spn": { + "publicHub": { + "name": "test-$HUBNAME", + "transports": ["http:80", "http:8080", "tcp:17"], + "allowUnencrypted": true, + "bindToAdvertised": true + } + } +} diff --git a/spn/testing/simple/docker-compose.yml b/spn/testing/simple/docker-compose.yml new file mode 100644 index 00000000..3d48eb10 --- /dev/null +++ b/spn/testing/simple/docker-compose.yml @@ -0,0 +1,139 @@ +version: "2.4" + +networks: + default: + ipam: + driver: default + config: + - subnet: 6.0.0.0/24 + +services: + hub1: + container_name: spn-test-simple-hub1 + hostname: hub1 + image: toolset.safing.network/dev + entrypoint: "/opt/shared/entrypoint.sh" + volumes: + - ${SPN_TEST_BIN}:/opt/hub1:ro + - ${SPN_TEST_DATA_DIR}/hub1:/opt/data + - ${SPN_TEST_SHARED_DATA_DIR}:/opt/shared + networks: + default: + ipv4_address: 6.0.0.11 + + hub2: + container_name: spn-test-simple-hub2 + hostname: hub2 + image: alpine + entrypoint: "/opt/shared/entrypoint.sh" + volumes: + - ${SPN_TEST_BIN}:/opt/hub2:ro + - ${SPN_TEST_DATA_DIR}/hub2:/opt/data + - ${SPN_TEST_SHARED_DATA_DIR}:/opt/shared + networks: + default: + ipv4_address: 6.0.0.12 + + hub3: + container_name: spn-test-simple-hub3 + hostname: hub3 + image: toolset.safing.network/dev + entrypoint: "/opt/shared/entrypoint.sh" + volumes: + - ${SPN_TEST_BIN}:/opt/hub3:ro + - ${SPN_TEST_DATA_DIR}/hub3:/opt/data + - ${SPN_TEST_SHARED_DATA_DIR}:/opt/shared + networks: + default: + ipv4_address: 6.0.0.13 + + hub4: + container_name: spn-test-simple-hub4 + hostname: hub4 + image: toolset.safing.network/dev + entrypoint: "/opt/shared/entrypoint.sh" + volumes: + - ${SPN_TEST_BIN}:/opt/hub4:ro + - ${SPN_TEST_DATA_DIR}/hub4:/opt/data + - ${SPN_TEST_SHARED_DATA_DIR}:/opt/shared + networks: + default: + ipv4_address: 6.0.0.14 + + hub5: + container_name: spn-test-simple-hub5 + hostname: hub5 + image: toolset.safing.network/dev + entrypoint: "/opt/shared/entrypoint.sh" + volumes: + - ${SPN_TEST_BIN}:/opt/hub5:ro + - ${SPN_TEST_DATA_DIR}/hub5:/opt/data + - ${SPN_TEST_SHARED_DATA_DIR}:/opt/shared + networks: + default: + ipv4_address: 6.0.0.15 + + hub6: + container_name: spn-test-simple-hub6 + hostname: hub6 + image: toolset.safing.network/dev + entrypoint: "/opt/shared/entrypoint.sh" + volumes: + - ${SPN_TEST_OLD_BIN}:/opt/hub6:ro + - ${SPN_TEST_DATA_DIR}/hub6:/opt/data + - ${SPN_TEST_SHARED_DATA_DIR}:/opt/shared + networks: + default: + ipv4_address: 6.0.0.16 + + hub7: + container_name: spn-test-simple-hub7 + hostname: hub7 + image: toolset.safing.network/dev + entrypoint: "/opt/shared/entrypoint.sh" + volumes: + - ${SPN_TEST_OLD_BIN}:/opt/hub7:ro + - ${SPN_TEST_DATA_DIR}/hub7:/opt/data + - ${SPN_TEST_SHARED_DATA_DIR}:/opt/shared + networks: + default: + ipv4_address: 6.0.0.17 + + hub8: + container_name: spn-test-simple-hub8 + hostname: hub8 + image: toolset.safing.network/dev + entrypoint: "/opt/shared/entrypoint.sh" + volumes: + - ${SPN_TEST_OLD_BIN}:/opt/hub8:ro + - ${SPN_TEST_DATA_DIR}/hub8:/opt/data + - ${SPN_TEST_SHARED_DATA_DIR}:/opt/shared + networks: + default: + ipv4_address: 6.0.0.18 + + hub9: + container_name: spn-test-simple-hub9 + hostname: hub9 + image: toolset.safing.network/dev + entrypoint: "/opt/shared/entrypoint.sh" + volumes: + - ${SPN_TEST_OLD_BIN}:/opt/hub9:ro + - ${SPN_TEST_DATA_DIR}/hub9:/opt/data + - ${SPN_TEST_SHARED_DATA_DIR}:/opt/shared + networks: + default: + ipv4_address: 6.0.0.19 + + hub10: + container_name: spn-test-simple-hub10 + hostname: hub10 + image: toolset.safing.network/dev + entrypoint: "/opt/shared/entrypoint.sh" + volumes: + - ${SPN_TEST_OLD_BIN}:/opt/hub10:ro + - ${SPN_TEST_DATA_DIR}/hub10:/opt/data + - ${SPN_TEST_SHARED_DATA_DIR}:/opt/shared + networks: + default: + ipv4_address: 6.0.0.20 diff --git a/spn/testing/simple/entrypoint.sh b/spn/testing/simple/entrypoint.sh new file mode 100755 index 00000000..5fe516e0 --- /dev/null +++ b/spn/testing/simple/entrypoint.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +# Get hostname. +HUBNAME=$HOSTNAME +if [ "$HUBNAME" = "" ]; then + HUBNAME=$(cat /etc/hostname) +fi +export HUBNAME + +# Read, process and write config. +cat /opt/shared/config-template.json | sed "s/\$HUBNAME/$HUBNAME/g" > /opt/data/config.json + +# Get binary to start. +BIN=$(ls /opt/ | grep hub) + +# Start Hub. +/opt/$BIN --data /opt/data --log trace --spn-map test --bootstrap-file /opt/shared/bootstrap.dsd --api-address 0.0.0.0:817 --devmode diff --git a/spn/testing/simple/inject-intel.sh b/spn/testing/simple/inject-intel.sh new file mode 100755 index 00000000..a57cd72b --- /dev/null +++ b/spn/testing/simple/inject-intel.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +cd "$( dirname "${BASH_SOURCE[0]}" )" + +MAIN_INTEL_FILE="intel-testnet.json" + +if [[ ! -f $MAIN_INTEL_FILE ]]; then + echo "missing $MAIN_INTEL_FILE" + exit 1 +fi + +echo "if the containing directory cannot be created, you might need to adjust permissions, as nodes are run with root in test containers..." +echo "$ sudo chmod -R 777 data/hub*/updates" +echo "starting to update..." + +for hubDir in data/hub*; do + # Build destination path + hubIntelFile="${hubDir}/updates/all/intel/spn/main-intel_v0-0-0.dsd" + + # Copy file + mkdir -p "${hubDir}/updates/all/intel/spn" + echo -n "J" > "$hubIntelFile" + cat $MAIN_INTEL_FILE >> "$hubIntelFile" + + echo "updated $hubIntelFile" +done + +if [[ -d /var/lib/portmaster ]]; then + echo "updating intel for local portmaster installation..." + + portmasterSPNIntelFile="/var/lib/portmaster/updates/all/intel/spn/main-intel_v0-0-0.dsd" + echo -n "J" > "$portmasterSPNIntelFile" + cat $MAIN_INTEL_FILE >> "$portmasterSPNIntelFile" + echo "updated $portmasterSPNIntelFile" +fi diff --git a/spn/testing/simple/intel-client.yaml b/spn/testing/simple/intel-client.yaml new file mode 100644 index 00000000..28f0a685 --- /dev/null +++ b/spn/testing/simple/intel-client.yaml @@ -0,0 +1,25 @@ +# Get current list of IDs from test net: +# curl http://127.0.0.1:817/api/v1/spn/map/test/pins | jq ".[] | .ID" +# Then import into test client with: +# curl -X POST --upload-file intel-client.yaml http://127.0.0.1:817/api/v1/spn/map/test/intel/update +Hubs: + Zwm48YWWFGdwXjhE1MyEkWfqxPr9DiUBoXpusTZ1FMQnuK: + Trusted: true + Zwu5LkkbfCbAcYxWG3vtWF1VvWjgWpc1GJfkwRdLFNtytV: + Trusted: true + ZwuQpz5CqYmYoLnt9KXQ8oxnmosBzfrCYwCGhxT4fsG1Dz: + Trusted: true + ZwwmC3dHzr7J6XW9mc2KD6FDNuXwPVJUFi9dLnDSNMyjLk: + Trusted: true + ZwxSBdvqtJyz8zRWKZe6QyK51KH9av6VFay2GQvpFrWKHq: + Trusted: true + ZwxnuL6zMLj4AxJX8Bj369w2tNrVtYxzffVcXZuMxdxbGj: + Trusted: true + ZwyXdnC8JkC7m796skGD7QWGoYycByR3KVntkXMY8CxRZQ: + Trusted: true + Zwz7AHiH1EevD9eYFqvQQPbVWyBBcksTRxxafbRx5Cvc4F: + Trusted: true + ZwzMtc65t9XBMwmLm2xNSL69FvqHGPLiqeNBZ3eEN5a9sS: + Trusted: true + ZwzjnCUNGsuWnkYmN3QEj8JPLxU6V1QQFk9b47AigmPKiH: + Trusted: true diff --git a/spn/testing/simple/intel-testnet.json b/spn/testing/simple/intel-testnet.json new file mode 100644 index 00000000..388fa0e1 --- /dev/null +++ b/spn/testing/simple/intel-testnet.json @@ -0,0 +1,17 @@ +{ + "BootstrapHubs": [ + ], + "TrustedHubs": [ + "ZwrY9G9HDo1J3qQrrQs8VF2KD99bj7KyWesJ5kWFUDBU6r", + "Zwj56ZFXrsud8gc1Rw3zuxRwMLhGkwvtvnTxCVtJ8EWLhQ", + "ZwpdW87ityD9i3N9x8oweCJnbZEqo346VBg4mCsCvTr1Zo", + "ZwpJ6ebddk1sccUVpo92JUqicBfKzBN2w4pEGoEY7UsNhX", + "Zwte3Jffp9PWmeWfrn8RyGuvZZFCg3v7XR3tpQjdo9TpVt", + "ZwrTcdiPF5zR5h9q9EdjHCrrXzYVBdQe5HmEYUWXdLkke3", + "Zwv7tSn5iU6bYKn53NaGCxPtL8vSxSK7F9VdQezDaDCLBt", + "Zwvtdq3K9knP9iNaRS1Ju8CETWTqy7oRwbScjBtJGBpqhB" + ], + "AdviseOnlyTrustedHubs": true, + "AdviseOnlyTrustedHomeHubs": true, + "AdviseOnlyTrustedDestinationHubs": true +} diff --git a/spn/testing/simple/join.sh b/spn/testing/simple/join.sh new file mode 100755 index 00000000..b5ddf912 --- /dev/null +++ b/spn/testing/simple/join.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +cd "$( dirname "${BASH_SOURCE[0]}" )" + +realpath() { + path=`eval echo "$1"` + folder=$(dirname "$path") + echo $(cd "$folder"; pwd)/$(basename "$path"); +} + +leftover=$(docker ps -a | grep spn-test-simple-me | cut -d" " -f1) +if [[ $leftover != "" ]]; then + docker stop $leftover + docker rm $leftover +fi + +if [[ ! -f "../../../cmds/hub/hub" ]]; then + echo "please build the hub cmd using cmds/hub/build" + exit 1 +fi + +SPN_TEST_BIN="$(realpath ../../../cmds/hub/hub)" +SPN_TEST_DATA_DIR="$(realpath ./testdata)" +if [[ ! -d "$SPN_TEST_DATA_DIR" ]]; then + mkdir "$SPN_TEST_DATA_DIR" +fi +SPN_TEST_SHARED_DATA_DIR="$(realpath ./testdata/shared)" +if [[ ! -d "$SPN_TEST_SHARED_DATA_DIR" ]]; then + mkdir "$SPN_TEST_SHARED_DATA_DIR" +fi + +docker run -ti \ +--name spn-test-simple-me \ +--hostname me \ +--network spn-test-simple_default \ +-v $SPN_TEST_BIN:/opt/hub_me:ro \ +-v $SPN_TEST_DATA_DIR/me:/opt/data \ +-v $SPN_TEST_SHARED_DATA_DIR:/opt/shared \ +--entrypoint /opt/hub_me \ +toolset.safing.network/dev \ +--devmode --api-address 0.0.0.0:8081 \ +--data /opt/data -log trace --spn-map test --bootstrap-file /opt/shared/bootstrap.dsd diff --git a/spn/testing/simple/reset-databases.sh b/spn/testing/simple/reset-databases.sh new file mode 100755 index 00000000..3c8a2d19 --- /dev/null +++ b/spn/testing/simple/reset-databases.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +cd "$( dirname "${BASH_SOURCE[0]}" )" + +rm -rf testdata/me/* +rm -rf testdata/shared/* +rm -rf testdata/hub*/databases diff --git a/spn/testing/simple/run.sh b/spn/testing/simple/run.sh new file mode 100755 index 00000000..728cad3f --- /dev/null +++ b/spn/testing/simple/run.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +cd "$( dirname "${BASH_SOURCE[0]}" )" + +realpath() { + path=`eval echo "$1"` + folder=$(dirname "$path") + echo $(cd "$folder"; pwd)/$(basename "$path"); +} + +leftovers=$(docker ps -a | grep spn-test-simple | cut -d" " -f1) +if [[ $leftovers != "" ]]; then + docker stop $leftovers + docker rm $leftovers +fi + +if [[ ! -f "../../../cmds/hub/hub" ]]; then + echo "please build the hub cmd using cmds/hub/build" + exit 1 +fi + +# Create variables. +SPN_TEST_BIN="$(realpath ../../../cmds/hub/hub)" +SPN_TEST_DATA_DIR="$(realpath ./testdata)" +if [[ ! -d "$SPN_TEST_DATA_DIR" ]]; then + mkdir "$SPN_TEST_DATA_DIR" +fi +SPN_TEST_SHARED_DATA_DIR="$(realpath ./testdata/shared)" +if [[ ! -d "$SPN_TEST_SHARED_DATA_DIR" ]]; then + mkdir "$SPN_TEST_SHARED_DATA_DIR" +fi + +# Check if there is an old binary for testing. +SPN_TEST_OLD_BIN=$SPN_TEST_BIN +if [[ -f "./testdata/old-hub" ]]; then + SPN_TEST_OLD_BIN="$(realpath ./testdata/old-hub)" + echo "WARNING: running in hybrid mode with old version at $SPN_TEST_OLD_BIN" +fi + +# Export variables +export SPN_TEST_BIN +export SPN_TEST_OLD_BIN +export SPN_TEST_DATA_DIR +export SPN_TEST_SHARED_DATA_DIR + +# Copy files. +cp config-template.json ./testdata/shared/config-template.json +cp entrypoint.sh ./testdata/shared/entrypoint.sh +chmod 555 ./testdata/shared/entrypoint.sh + +# Run! +docker compose -p spn-test-simple up --remove-orphans diff --git a/spn/testing/simple/stop.sh b/spn/testing/simple/stop.sh new file mode 100755 index 00000000..f5af89a4 --- /dev/null +++ b/spn/testing/simple/stop.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +cd "$( dirname "${BASH_SOURCE[0]}" )" + +docker compose -p spn-test-simple stop +docker compose -p spn-test-simple rm + +oldnet=$(docker network ls | grep spn-test-simple | cut -d" " -f1) +if [[ $oldnet != "" ]]; then + docker network rm $oldnet +fi + +if [[ -d "data/shared" ]]; then + rm -r "data/shared" +fi