diff --git a/.earthlyignore b/.earthlyignore index 8953460f..d1dc7512 100644 --- a/.earthlyignore +++ b/.earthlyignore @@ -12,11 +12,11 @@ desktop/tauri/src-tauri/target # Copy from .gitignore: # Compiled binaries -*.exe -dist/ +# *.exe +# dist/ # Dist dir -dist +# dist # Custom dev deops go.mod.* diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2ec7fa30..d89171fc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,6 +1,12 @@ name: Release on: + push: + branches: + - v2.0 + - feature/new-installer + tags: + - v* workflow_dispatch: jobs: @@ -26,7 +32,6 @@ jobs: - name: Upload Dist uses: actions/upload-artifact@v4 with: - name: dist path: ./dist/ if-no-files-found: error @@ -51,6 +56,12 @@ jobs: run: earthly --ci --remote-cache=ghcr.io/safing/build-cache --push +installer-linux # --ci include --no-output flag + - name: Upload Installers + uses: actions/upload-artifact@v4 + with: + path: ./dist/linux_amd64/ + if-no-files-found: error + installer-windows: name: Installer windows runs-on: windows-latest @@ -62,9 +73,13 @@ jobs: - name: Download Dist uses: actions/download-artifact@v4 with: - name: dist path: dist/ - name: Build windows artifacts run: powershell -NoProfile -File ./packaging/windows/generate_windows_installers.ps1 + - name: Upload Installers + uses: actions/upload-artifact@v4 + with: + path: ./dist/windows_amd64/ + if-no-files-found: error diff --git a/.github/workflows/windows-dll.yml b/.github/workflows/windows-dll.yml new file mode 100644 index 00000000..aeb7a042 --- /dev/null +++ b/.github/workflows/windows-dll.yml @@ -0,0 +1,41 @@ +name: Windows Portmaster Core DLL + +on: + push: + paths: + - 'windows_core_dll/**' + branches: + - master + - develop + + pull_request: + paths: + - 'windows_core_dll/**' + branches: + - master + - develop + workflow_dispatch: + +jobs: + build: + name: Build + runs-on: windows-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + - name: Add msbuild to PATH + uses: microsoft/setup-msbuild@v2 + - name: Build DLL + run: msbuild windows_core_dll\windows_core_dll.sln -t:rebuild -property:Configuration=Release + - name: Verify DLL + shell: powershell + run: | + if (!(Test-Path "windows_core_dll/x64/Release/portmaster-core.dll")) { + Write-Error "DLL build failed: portmaster-core.dll not found" + exit 1 + } + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: portmaster-core-dll + path: windows_core_dll/x64/Release/portmaster-core.dll diff --git a/.gitignore b/.gitignore index e0a6550a..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 @@ -52,3 +51,4 @@ go.work.sum # Kext releases windows_kext/release/kext_release_*.zip +windows_core_dll/.vs/windows_core_dll diff --git a/Earthfile b/Earthfile index 8937bd05..dae6aa93 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" @@ -421,7 +426,7 @@ rust-base: DO rust+INIT --keep_fingerprints=true # For now we need tauri-cli 2.0.0 for bulding - DO rust+CARGO --args="install tauri-cli --version ${tauri_version} --locked" + DO rust+CARGO --args="install tauri-cli --version 2.1.0 --locked" # Explicitly cache here. SAVE IMAGE --cache-hint @@ -475,10 +480,11 @@ tauri-build: # Binaries SAVE ARTIFACT --if-exists --keep-ts "target/${target}/release/portmaster" AS LOCAL "${outputDir}/${GO_ARCH_STRING}/portmaster" SAVE ARTIFACT --if-exists --keep-ts "target/${target}/release/portmaster.exe" AS LOCAL "${outputDir}/${GO_ARCH_STRING}/portmaster.exe" - # SAVE ARTIFACT --if-exists --keep-ts "target/${target}/release/WebView2Loader.dll" AS LOCAL "${outputDir}/${GO_ARCH_STRING}/WebView2Loader.dll" + SAVE ARTIFACT --if-exists --keep-ts "target/${target}/release/WebView2Loader.dll" AS LOCAL "${outputDir}/${GO_ARCH_STRING}/WebView2Loader.dll" SAVE ARTIFACT --if-exists --keep-ts "target/${target}/release/portmaster" ./output/portmaster SAVE ARTIFACT --if-exists --keep-ts "target/${target}/release/portmaster.exe" ./output/portmaster.exe + SAVE ARTIFACT --if-exists --keep-ts "target/${target}/release/WebView2Loader.dll" ./output/WebView2Loader.dll tauri-release: @@ -512,43 +518,37 @@ tauri-lint: release-prep: FROM +rust-base + WORKDIR /app + # Linux specific COPY (+tauri-build/output/portmaster --target="x86_64-unknown-linux-gnu") ./output/binary/linux_amd64/portmaster COPY (+go-build/output/portmaster-core --GOARCH=amd64 --GOOS=linux --CMDS=portmaster-core) ./output/binary/linux_amd64/portmaster-core # Windows specific COPY (+tauri-build/output/portmaster.exe --target="x86_64-pc-windows-gnu") ./output/binary/windows_amd64/portmaster.exe + COPY (+tauri-build/output/WebView2Loader.dll --target="x86_64-pc-windows-gnu") ./output/binary/windows_amd64/WebView2Loader.dll COPY (+go-build/output/portmaster-core.exe --GOARCH=amd64 --GOOS=windows --CMDS=portmaster-core) ./output/binary/windows_amd64/portmaster-core.exe - # TODO(vladimir): figure out a way to get the lastest release of the kext. - RUN touch ./output/binary/windows_amd64/portmaster-kext.sys # All platforms COPY (+assets/assets.zip) ./output/binary/all/assets.zip COPY (+angular-project/output/portmaster.zip --project=portmaster --dist=./dist --configuration=production --baseHref=/ui/modules/portmaster/) ./output/binary/all/portmaster.zip - # Intel - # TODO(vladimir): figure out a way to download all latest intel data. - RUN mkdir -p ./output/intel - RUN wget -O ./output/intel/geoipv4.mmdb.gz "https://updates.safing.io/all/intel/geoip/geoipv4_v20240820-0-1.mmdb.gz" && \ - wget -O ./output/intel/geoipv6.mmdb.gz "https://updates.safing.io/all/intel/geoip/geoipv6_v20240820-0-1.mmdb.gz" && \ - wget -O ./output/intel/index.dsd "https://updates.safing.io/all/intel/lists/index_v2023-6-13.dsd" && \ - wget -O ./output/intel/base.dsdl "https://updates.safing.io/all/intel/lists/base_v20241001-0-9.dsdl" && \ - wget -O ./output/intel/intermediate.dsdl "https://updates.safing.io/all/intel/lists/intermediate_v20240929-0-0.dsdl" && \ - wget -O ./output/intel/urgent.dsdl "https://updates.safing.io/all/intel/lists/urgent_v20241002-2-14.dsdl" - + # Build update manager COPY (+go-build/output/updatemgr --GOARCH=amd64 --GOOS=linux --CMDS=updatemgr) ./updatemgr - RUN ./updatemgr scan --dir "./output/binary" > ./output/binary/index.json - RUN ./updatemgr scan --dir "./output/intel" > ./output/intel/index.json + + # Get binary artifacts from current release + RUN mkdir -p ./output/download/windows_amd64 && ./updatemgr download https://updates.safing.io/stable.v3.json --platform windows_amd64 ./output/download/windows_amd64 - # Intel Extracted (needed for the installers) - RUN mkdir -p ./output/intel_decompressed - RUN cp ./output/intel/index.json ./output/intel_decompressed/index.json - RUN gzip -dc ./output/intel/geoipv4.mmdb.gz > ./output/intel_decompressed/geoipv4.mmdb - RUN gzip -dc ./output/intel/geoipv6.mmdb.gz > ./output/intel_decompressed/geoipv6.mmdb - RUN cp ./output/intel/index.dsd ./output/intel_decompressed/index.dsd - RUN cp ./output/intel/base.dsdl ./output/intel_decompressed/base.dsdl - RUN cp ./output/intel/intermediate.dsdl ./output/intel_decompressed/intermediate.dsdl - RUN cp ./output/intel/urgent.dsdl ./output/intel_decompressed/urgent.dsdl + # Copy required artifacts + RUN cp ./output/download/windows_amd64/portmaster-kext.sys ./output/binary/windows_amd64/portmaster-kext.sys + RUN cp ./output/download/windows_amd64/portmaster-kext.pdb ./output/binary/windows_amd64/portmaster-kext.pdb + RUN cp ./output/download/windows_amd64/portmaster-core.dll ./output/binary/windows_amd64/portmaster-core.dll + + # Create new binary index from artifacts + RUN ./updatemgr scan --dir "./output/binary" > ./output/binary/index.json + + # Get intel index and assets + RUN mkdir -p ./output/intel && ./updatemgr download https://updates.safing.io/intel.v3.json ./output/intel # Save all artifacts to output folder SAVE ARTIFACT --if-exists --keep-ts "output/binary/index.json" AS LOCAL "${outputDir}/binary/index.json" @@ -556,7 +556,6 @@ release-prep: SAVE ARTIFACT --if-exists --keep-ts "output/binary/linux_amd64/*" AS LOCAL "${outputDir}/binary/linux_amd64/" SAVE ARTIFACT --if-exists --keep-ts "output/binary/windows_amd64/*" AS LOCAL "${outputDir}/binary/windows_amd64/" SAVE ARTIFACT --if-exists --keep-ts "output/intel/*" AS LOCAL "${outputDir}/intel/" - SAVE ARTIFACT --if-exists --keep-ts "output/intel_decompressed/*" AS LOCAL "${outputDir}/intel_decompressed/" # Save all artifacts to the container output folder so other containers can access it. SAVE ARTIFACT --if-exists --keep-ts "output/binary/index.json" "output/binary/index.json" @@ -564,7 +563,7 @@ release-prep: SAVE ARTIFACT --if-exists --keep-ts "output/binary/linux_amd64/*" "output/binary/linux_amd64/" SAVE ARTIFACT --if-exists --keep-ts "output/binary/windows_amd64/*" "output/binary/windows_amd64/" SAVE ARTIFACT --if-exists --keep-ts "output/intel/*" "output/intel/" - SAVE ARTIFACT --if-exists --keep-ts "output/intel_decompressed/*" "output/intel_decompressed/" + SAVE ARTIFACT --if-exists --keep-ts "output/download/*" "output/download/" installer-linux: FROM +rust-base @@ -594,7 +593,7 @@ installer-linux: # Download the intel data RUN mkdir -p intel - COPY (+release-prep/output/intel_decompressed/*) ./intel/ + COPY (+release-prep/output/intel/*) ./intel/ # build the installers RUN cargo tauri bundle --ci --target="${target}" diff --git a/base/database/main.go b/base/database/main.go index ed0bb934..e11a73f8 100644 --- a/base/database/main.go +++ b/base/database/main.go @@ -3,9 +3,9 @@ package database import ( "errors" "fmt" - "os" "path/filepath" + "github.com/safing/portmaster/base/utils" "github.com/tevino/abool" ) @@ -23,10 +23,10 @@ func Initialize(databasesRootDir string) error { if initialized.SetToIf(false, true) { rootDir = databasesRootDir - // Ensure database root dir exists. - err := os.MkdirAll(rootDir, 0o0700) + // ensure root and databases dirs + err := utils.EnsureDirectory(rootDir, utils.AdminOnlyExecPermission) 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) } return nil @@ -59,7 +59,7 @@ func getLocation(name, storageType string) (string, error) { location := filepath.Join(rootDir, name, storageType) // Make sure location exists. - err := os.MkdirAll(location, 0o0700) + err := utils.EnsureDirectory(location, utils.AdminOnlyExecPermission) if err != nil { return "", fmt.Errorf("failed to create/check database dir %q: %w", location, err) } diff --git a/base/database/storage/fstree/fstree.go b/base/database/storage/fstree/fstree.go index 44cf384f..4b04f41d 100644 --- a/base/database/storage/fstree/fstree.go +++ b/base/database/storage/fstree/fstree.go @@ -288,10 +288,10 @@ 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 { - if err := t.Chmod(perm); err != nil { - return err - } + // TODO(vladimir): to set permissions on windows we need the full path of the file. + err = t.Chmod(perm) + if err != nil { + return err } if _, err := t.Write(data); err != nil { diff --git a/base/info/version.go b/base/info/version.go index 2c6c3058..be241e7e 100644 --- a/base/info/version.go +++ b/base/info/version.go @@ -10,8 +10,6 @@ import ( "sync" ) -// FIXME: version does not show in portmaster - var ( name string license string @@ -76,6 +74,7 @@ func Set(setName string, setVersion string, setLicenseName string) { if setVersion != "" { version = setVersion + versionNumber = setVersion } } @@ -167,9 +166,9 @@ func CondensedVersion() string { } return fmt.Sprintf( - "%s %s (%s; built with %s [%s %s] from %s [%s] at %s)", + "%s %s (%s/%s; built with %s [%s %s] from %s [%s] at %s)", info.Name, version, - runtime.GOOS, + runtime.GOOS, runtime.GOARCH, runtime.Version(), runtime.Compiler, cgoInfo, info.Commit, dirtyInfo, info.CommitTime, ) diff --git a/base/log/logging.go b/base/log/logging.go index d9ed43d9..93be59e7 100644 --- a/base/log/logging.go +++ b/base/log/logging.go @@ -191,7 +191,7 @@ func ParseLevel(level string) Severity { } // Start starts the logging system. Must be called in order to see logs. -func Start(level string, logToStdout bool, logDir string) (err error) { +func Start(level string, logToStdout bool, logDir string) error { if !initializing.SetToIf(false, true) { return nil } @@ -232,13 +232,13 @@ func Start(level string, logToStdout bool, logDir string) (err error) { // Delete all logs older than one month. if !logToStdout { - err = CleanOldLogs(logDir, 30*24*time.Hour) + err := CleanOldLogs(logDir, 30*24*time.Hour) if err != nil { Errorf("log: failed to clean old log files: %s", err) } } - return err + return nil } // Shutdown writes remaining log lines and then stops the log system. diff --git a/base/log/slog.go b/base/log/slog.go index 5901c146..65dec5ab 100644 --- a/base/log/slog.go +++ b/base/log/slog.go @@ -1,14 +1,19 @@ package log import ( + "io" "log/slog" "os" "runtime" "github.com/lmittmann/tint" + "github.com/mattn/go-colorable" + "github.com/mattn/go-isatty" ) func setupSLog(level Severity) { + // TODO: Changes in the log level are not yet reflected onto the slog handlers in the modules. + // Set highest possible level, so it can be changed in runtime. handlerLogLevel := level.toSLogLevel() @@ -17,21 +22,23 @@ func setupSLog(level Severity) { switch runtime.GOOS { case "windows": logHandler = tint.NewHandler( - GlobalWriter, + windowsColoring(GlobalWriter), // Enable coloring on Windows. &tint.Options{ AddSource: true, Level: handlerLogLevel, TimeFormat: timeFormat, - NoColor: !GlobalWriter.IsStdout(), // FIXME: also check for tty. + NoColor: !( /* Color: */ GlobalWriter.IsStdout() && isatty.IsTerminal(GlobalWriter.file.Fd())), }, ) + case "linux": logHandler = tint.NewHandler(GlobalWriter, &tint.Options{ AddSource: true, Level: handlerLogLevel, TimeFormat: timeFormat, - NoColor: !GlobalWriter.IsStdout(), // FIXME: also check for tty. + NoColor: !( /* Color: */ GlobalWriter.IsStdout() && isatty.IsTerminal(GlobalWriter.file.Fd())), }) + default: logHandler = tint.NewHandler(os.Stdout, &tint.Options{ AddSource: true, @@ -43,6 +50,11 @@ func setupSLog(level Severity) { // Set as default logger. slog.SetDefault(slog.New(logHandler)) - // Set actual log level. - slog.SetLogLoggerLevel(handlerLogLevel) +} + +func windowsColoring(lw *LogWriter) io.Writer { + if lw.IsStdout() { + return colorable.NewColorable(lw.file) + } + return lw } diff --git a/base/utils/fs.go b/base/utils/fs.go index b612a069..edb42108 100644 --- a/base/utils/fs.go +++ b/base/utils/fs.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "io/fs" + "log/slog" "os" "runtime" ) @@ -12,7 +13,11 @@ 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 { + if !perm.IsExecPermission() { + slog.Warn("utils: setting not executable permission for directory", "dir", path) + } + // open path f, err := os.Stat(path) if err == nil { @@ -20,10 +25,11 @@ func EnsureDirectory(path string, perm os.FileMode) error { if f.IsDir() { // directory exists, check permissions if isWindows { - // TODO: set correct permission on windows - // acl.Chmod(path, perm) - } else if f.Mode().Perm() != perm { - return os.Chmod(path, perm) + // Ignore windows permission error. For none admin users it will always fail. + _ = SetFilePermission(path, perm) + return nil + } else if f.Mode().Perm() != perm.AsUnixPermission() { + return SetFilePermission(path, perm) } return nil } @@ -34,11 +40,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.AsUnixPermission()) if err != nil { return fmt.Errorf("could not create dir %s: %w", path, err) } - return os.Chmod(path, perm) + // Set permissions. + err = SetFilePermission(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..328ac884 --- /dev/null +++ b/base/utils/permissions.go @@ -0,0 +1,10 @@ +//go:build !windows + +package utils + +import "os" + +// SetFilePermission sets the permission of a file or directory. +func SetFilePermission(path string, perm FSPermission) error { + return os.Chmod(path, perm.AsUnixPermission()) +} diff --git a/base/utils/permissions_windows.go b/base/utils/permissions_windows.go new file mode 100644 index 00000000..8a65016d --- /dev/null +++ b/base/utils/permissions_windows.go @@ -0,0 +1,68 @@ +//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) + } +} + +// SetFilePermission sets the permission of a file or directory. +func SetFilePermission(path string, perm FSPermission) error { + switch perm { + case AdminOnlyPermission, AdminOnlyExecPermission: + // 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, PublicReadExecPermission: + // 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, PublicWriteExecPermission: + // 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), + ) + } + return nil +} diff --git a/base/utils/renameio/writefile.go b/base/utils/renameio/writefile.go index 21153025..073a1e1b 100644 --- a/base/utils/renameio/writefile.go +++ b/base/utils/renameio/writefile.go @@ -1,6 +1,11 @@ package renameio -import "os" +import ( + "os" + "runtime" + + "github.com/hectane/go-acl" +) // WriteFile mirrors os.WriteFile, replacing an existing file with the same // name atomically. @@ -14,7 +19,12 @@ func WriteFile(filename string, data []byte, perm os.FileMode) error { }() // Set permissions before writing data, in case the data is sensitive. - if err := t.Chmod(perm); err != nil { + if runtime.GOOS == "windows" { + err = acl.Chmod(t.path, perm) + } else { + err = t.Chmod(perm) + } + if err != nil { return err } diff --git a/base/utils/structure.go b/base/utils/structure.go index 5a50d97e..3209b27b 100644 --- a/base/utils/structure.go +++ b/base/utils/structure.go @@ -2,25 +2,64 @@ package utils import ( "fmt" - "os" + "io/fs" "path/filepath" "strings" "sync" ) +type FSPermission uint8 + +const ( + AdminOnlyPermission FSPermission = iota + AdminOnlyExecPermission + PublicReadPermission + PublicReadExecPermission + PublicWritePermission + PublicWriteExecPermission +) + +// AsUnixDirExecPermission return the corresponding unix permission for a directory or executable. +func (perm FSPermission) AsUnixPermission() fs.FileMode { + switch perm { + case AdminOnlyPermission: + return 0o600 + case AdminOnlyExecPermission: + return 0o700 + case PublicReadPermission: + return 0o644 + case PublicReadExecPermission: + return 0o755 + case PublicWritePermission: + return 0o666 + case PublicWriteExecPermission: + return 0o777 + } + + return 0 +} + +func (perm FSPermission) IsExecPermission() bool { + switch perm { + case AdminOnlyExecPermission, PublicReadExecPermission, PublicWriteExecPermission: + return true + } + return false +} + // 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 +68,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/portmaster-core/run.go b/cmds/cmdbase/service.go similarity index 53% rename from cmds/portmaster-core/run.go rename to cmds/cmdbase/service.go index 6c41bd56..28bc5e60 100644 --- a/cmds/portmaster-core/run.go +++ b/cmds/cmdbase/service.go @@ -1,12 +1,14 @@ -package main +package cmdbase import ( + "context" "errors" - "flag" "fmt" "io" "log/slog" "os" + "os/exec" + "runtime" "runtime/pprof" "time" @@ -15,14 +17,12 @@ import ( "github.com/safing/portmaster/base/log" "github.com/safing/portmaster/service" "github.com/safing/portmaster/service/mgr" - "github.com/safing/portmaster/spn/conf" ) -var printStackOnExit bool - -func init() { - flag.BoolVar(&printStackOnExit, "print-stack-on-exit", false, "prints the stack before of shutting down") -} +var ( + RebootOnRestart bool + PrintStackOnExit bool +) type SystemService interface { Run() @@ -30,21 +30,47 @@ type SystemService interface { RestartService() error } -func cmdRun(cmd *cobra.Command, args []string) { - // Run platform specific setup or switches. - runPlatformSpecifics(cmd, args) +type ServiceInstance interface { + Ready() bool + Start() error + Stop() error + Restart() + Shutdown() + Ctx() context.Context + IsShuttingDown() bool + ShuttingDown() <-chan struct{} + ShutdownCtx() context.Context + IsShutDown() bool + ShutdownComplete() <-chan struct{} + ExitCode() int + ShouldRestartIsSet() bool + CommandLineOperationIsSet() bool + CommandLineOperationExecute() error +} - // SETUP +var ( + SvcFactory func(*service.ServiceConfig) (ServiceInstance, error) + SvcConfig *service.ServiceConfig +) - // Enable SPN client mode. - // TODO: Move this to service config. - conf.EnableClient(true) - conf.EnableIntegration(true) +func RunService(cmd *cobra.Command, args []string) { + if SvcFactory == nil || SvcConfig == nil { + fmt.Fprintln(os.Stderr, "internal error: service not set up in cmdbase") + os.Exit(1) + } + + // Start logging. + // Note: Must be created before the service instance, so that they use the right logger. + err := log.Start(SvcConfig.LogLevel, SvcConfig.LogToStdout, SvcConfig.LogDir) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(4) + } // Create instance. // Instance modules might request a cmdline execution of a function. var execCmdLine bool - instance, err := service.New(svcCfg) + instance, err := SvcFactory(SvcConfig) switch { case err == nil: // Continue @@ -59,13 +85,13 @@ func cmdRun(cmd *cobra.Command, args []string) { switch { case !execCmdLine: // Run service. - case instance.CommandLineOperation == nil: + case !instance.CommandLineOperationIsSet(): fmt.Println("command line operation execution requested, but not set") os.Exit(3) default: // Run the function and exit. fmt.Println("executing cmdline op") - err = instance.CommandLineOperation() + err = instance.CommandLineOperationExecute() if err != nil { fmt.Fprintf(os.Stderr, "command line operation failed: %s\n", err) os.Exit(3) @@ -75,16 +101,6 @@ func cmdRun(cmd *cobra.Command, args []string) { // START - // FIXME: fix color and duplicate level when logging with slog - // FIXME: check for tty for color enabling - - // Start logging. - err = log.Start(svcCfg.LogLevel, svcCfg.LogToStdout, svcCfg.LogDir) - if err != nil { - fmt.Fprintln(os.Stderr, err.Error()) - os.Exit(4) - } - // Create system service. service := NewSystemService(instance) @@ -102,7 +118,7 @@ func cmdRun(cmd *cobra.Command, args []string) { select { case <-instance.ShutdownComplete(): // Print stack on shutdown, if enabled. - if printStackOnExit { + if PrintStackOnExit { printStackTo(log.GlobalWriter, "PRINTING STACK ON EXIT") } case <-time.After(3 * time.Minute): @@ -110,9 +126,22 @@ func cmdRun(cmd *cobra.Command, args []string) { } // Check if restart was triggered and send start service command if true. - if instance.ShouldRestart && service.IsService() { - if err := service.RestartService(); err != nil { - slog.Error("failed to restart service", "err", err) + if instance.ShouldRestartIsSet() && service.IsService() { + // Check if we should reboot instead. + var rebooting bool + if RebootOnRestart { + // Trigger system reboot and record success. + rebooting = triggerSystemReboot() + if !rebooting { + log.Warningf("updates: rebooting failed, only restarting service instead") + } + } + + // Restart service if not rebooting. + if !rebooting { + if err := service.RestartService(); err != nil { + slog.Error("failed to restart service", "err", err) + } } } @@ -138,3 +167,19 @@ func printStackTo(writer io.Writer, msg string) { slog.Error("failed to write stack trace", "err", err) } } + +func triggerSystemReboot() (success bool) { + switch runtime.GOOS { + case "linux": + err := exec.Command("systemctl", "reboot").Run() + if err != nil { + log.Errorf("updates: triggering reboot with systemctl failed: %s", err) + return false + } + default: + log.Warningf("updates: rebooting is not support on %s", runtime.GOOS) + return false + } + + return true +} diff --git a/cmds/portmaster-core/run_linux.go b/cmds/cmdbase/service_linux.go similarity index 81% rename from cmds/portmaster-core/run_linux.go rename to cmds/cmdbase/service_linux.go index 858ed022..085b8649 100644 --- a/cmds/portmaster-core/run_linux.go +++ b/cmds/cmdbase/service_linux.go @@ -1,4 +1,4 @@ -package main +package cmdbase import ( "fmt" @@ -9,17 +9,15 @@ import ( "syscall" processInfo "github.com/shirou/gopsutil/process" - "github.com/spf13/cobra" "github.com/safing/portmaster/base/log" - "github.com/safing/portmaster/service" ) type LinuxSystemService struct { - instance *service.Instance + instance ServiceInstance } -func NewSystemService(instance *service.Instance) *LinuxSystemService { +func NewSystemService(instance ServiceInstance) *LinuxSystemService { return &LinuxSystemService{instance: instance} } @@ -30,7 +28,7 @@ func (s *LinuxSystemService) Run() { slog.Error("failed to start", "err", err) // Print stack on start failure, if enabled. - if printStackOnExit { + if PrintStackOnExit { printStackTo(log.GlobalWriter, "PRINTING STACK ON START FAILURE") } @@ -62,7 +60,7 @@ wait: continue wait } else { // Trigger shutdown. - fmt.Printf(" ", sig) // CLI output. + fmt.Printf(" \n", sig) // CLI output. slog.Warn("received stop signal", "signal", sig) s.instance.Shutdown() break wait @@ -128,18 +126,3 @@ func (s *LinuxSystemService) IsService() bool { // Check if the parent process ID is 1 == init system return ppid == 1 } - -func runPlatformSpecifics(cmd *cobra.Command, args []string) { - // If recover-iptables flag is set, run the recover-iptables command. - // This is for backwards compatibility - if recoverIPTables { - exitCode := 0 - err := recover(cmd, args) - if err != nil { - fmt.Printf("failed: %s", err) - exitCode = 1 - } - - os.Exit(exitCode) - } -} diff --git a/cmds/portmaster-core/run_windows.go b/cmds/cmdbase/service_windows.go similarity index 93% rename from cmds/portmaster-core/run_windows.go rename to cmds/cmdbase/service_windows.go index 09593630..fdb6df74 100644 --- a/cmds/portmaster-core/run_windows.go +++ b/cmds/cmdbase/service_windows.go @@ -1,4 +1,4 @@ -package main +package cmdbase // Based on the official Go examples from // https://github.com/golang/sys/blob/master/windows/svc/example @@ -13,21 +13,19 @@ import ( "os/signal" "syscall" - "github.com/spf13/cobra" "golang.org/x/sys/windows/svc" "golang.org/x/sys/windows/svc/debug" "github.com/safing/portmaster/base/log" - "github.com/safing/portmaster/service" ) const serviceName = "PortmasterCore" type WindowsSystemService struct { - instance *service.Instance + instance ServiceInstance } -func NewSystemService(instance *service.Instance) *WindowsSystemService { +func NewSystemService(instance ServiceInstance) *WindowsSystemService { return &WindowsSystemService{instance: instance} } @@ -67,7 +65,7 @@ func (s *WindowsSystemService) Execute(args []string, changeRequests <-chan svc. fmt.Printf("failed to start: %s\n", err) // Print stack on start failure, if enabled. - if printStackOnExit { + if PrintStackOnExit { printStackTo(log.GlobalWriter, "PRINTING STACK ON START FAILURE") } @@ -102,7 +100,7 @@ waitSignal: select { case sig := <-signalCh: // Trigger shutdown. - fmt.Printf(" ", sig) // CLI output. + fmt.Printf(" \n", sig) // CLI output. slog.Warn("received stop signal", "signal", sig) break waitSignal @@ -112,7 +110,7 @@ waitSignal: changes <- c.CurrentStatus case svc.Stop, svc.Shutdown: - fmt.Printf(" ", serviceCmdName(c.Cmd)) // CLI output. + fmt.Printf(" \n", serviceCmdName(c.Cmd)) // CLI output. slog.Warn("received service shutdown command", "cmd", c.Cmd) break waitSignal @@ -201,8 +199,6 @@ sc.exe start $serviceName` return nil } -func runPlatformSpecifics(cmd *cobra.Command, args []string) - func serviceCmdName(cmd svc.Cmd) string { switch cmd { case svc.Stop: diff --git a/cmds/portmaster-core/update.go b/cmds/cmdbase/update.go similarity index 88% rename from cmds/portmaster-core/update.go rename to cmds/cmdbase/update.go index 866aab61..65c20a83 100644 --- a/cmds/portmaster-core/update.go +++ b/cmds/cmdbase/update.go @@ -1,4 +1,4 @@ -package main +package cmdbase import ( "fmt" @@ -12,32 +12,28 @@ import ( "github.com/safing/portmaster/service/updates" ) -var updateCmd = &cobra.Command{ +var UpdateCmd = &cobra.Command{ Use: "update", Short: "Force an update of all components.", RunE: update, } -func init() { - rootCmd.AddCommand(updateCmd) -} - func update(cmd *cobra.Command, args []string) error { // Finalize config. - err := svcCfg.Init() + err := SvcConfig.Init() if err != nil { return fmt.Errorf("internal configuration error: %w", err) } // Force logging to stdout. - svcCfg.LogToStdout = true + SvcConfig.LogToStdout = true // Start logging. - _ = log.Start(svcCfg.LogLevel, svcCfg.LogToStdout, svcCfg.LogDir) + _ = log.Start(SvcConfig.LogLevel, SvcConfig.LogToStdout, SvcConfig.LogDir) defer log.Shutdown() // Create updaters. instance := &updateDummyInstance{} - binaryUpdateConfig, intelUpdateConfig, err := service.MakeUpdateConfigs(svcCfg) + binaryUpdateConfig, intelUpdateConfig, err := service.MakeUpdateConfigs(SvcConfig) if err != nil { return fmt.Errorf("init updater config: %w", err) } diff --git a/cmds/cmdbase/version.go b/cmds/cmdbase/version.go new file mode 100644 index 00000000..86244dac --- /dev/null +++ b/cmds/cmdbase/version.go @@ -0,0 +1,20 @@ +package cmdbase + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/safing/portmaster/base/info" +) + +var VersionCmd = &cobra.Command{ + Use: "version", + Short: "Show version and related metadata.", + RunE: Version, +} + +func Version(cmd *cobra.Command, args []string) error { + fmt.Println(info.FullVersion()) + return nil +} diff --git a/cmds/hub/main.go b/cmds/hub/main.go index 1fdc8809..0d453df1 100644 --- a/cmds/hub/main.go +++ b/cmds/hub/main.go @@ -1,158 +1,95 @@ package main import ( - "errors" "flag" "fmt" - "io" - "log/slog" "os" - "os/signal" "runtime" - "runtime/pprof" - "syscall" - "time" + + "github.com/spf13/cobra" "github.com/safing/portmaster/base/info" - "github.com/safing/portmaster/base/log" "github.com/safing/portmaster/base/metrics" - "github.com/safing/portmaster/service/mgr" + "github.com/safing/portmaster/cmds/cmdbase" + "github.com/safing/portmaster/service" + "github.com/safing/portmaster/service/configure" "github.com/safing/portmaster/service/updates" - "github.com/safing/portmaster/spn" "github.com/safing/portmaster/spn/conf" ) +var ( + rootCmd = &cobra.Command{ + Use: "spn-hub", + PersistentPreRun: initializeGlobals, + Run: cmdbase.RunService, + } + + binDir string + dataDir string + + logToStdout bool + logDir string + logLevel string +) + func init() { - // flag.BoolVar(&updates.RebootOnRestart, "reboot-on-restart", false, "reboot server on auto-upgrade") - // FIXME + // 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)") + + // Add flags for service only. + rootCmd.Flags().BoolVar(&logToStdout, "log-stdout", false, "log to stdout instead of file") + rootCmd.Flags().StringVar(&logDir, "log-dir", "", "set directory for logs") + rootCmd.Flags().StringVar(&logLevel, "log", "", "set log level to [trace|debug|info|warning|error|critical]") + rootCmd.Flags().BoolVar(&cmdbase.PrintStackOnExit, "print-stack-on-exit", false, "prints the stack before of shutting down") + rootCmd.Flags().BoolVar(&cmdbase.RebootOnRestart, "reboot-on-restart", false, "reboot server instead of service restart") + + // Add other commands. + rootCmd.AddCommand(cmdbase.VersionCmd) + rootCmd.AddCommand(cmdbase.UpdateCmd) } -var sigUSR1 = syscall.Signal(0xa) - func main() { - flag.Parse() + // Add Go's default flag set. + // TODO: Move flags throughout Portmaster to here and add their values to the service config. + rootCmd.Flags().AddGoFlagSet(flag.CommandLine) + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func initializeGlobals(cmd *cobra.Command, args []string) { // Set name and license. - info.Set("SPN Hub", "", "GPLv3") + info.Set("SPN Hub", "0.7.8", "GPLv3") // Configure metrics. _ = metrics.SetNamespace("hub") - // Configure user agent and updates. + // Configure user agent. updates.UserAgent = fmt.Sprintf("SPN Hub (%s %s)", runtime.GOOS, runtime.GOARCH) - // helper.IntelOnly() // Set SPN public hub mode. conf.EnablePublicHub(true) - // Start logger with default log level. - _ = log.Start(log.WarningLevel) - - // FIXME: Use service? - - // Create instance. - var execCmdLine bool - instance, err := spn.New() - switch { - case err == nil: - // Continue - case errors.Is(err, mgr.ErrExecuteCmdLineOp): - execCmdLine = true - default: - fmt.Printf("error creating an instance: %s\n", err) - os.Exit(2) + // Configure service. + cmdbase.SvcFactory = func(svcCfg *service.ServiceConfig) (cmdbase.ServiceInstance, error) { + svc, err := service.New(svcCfg) + return svc, err } - // Execute command line operation, if requested or available. - switch { - case !execCmdLine: - // Run service. - case instance.CommandLineOperation == nil: - fmt.Println("command line operation execution requested, but not set") - os.Exit(3) - default: - // Run the function and exit. - err = instance.CommandLineOperation() - if err != nil { - fmt.Fprintf(os.Stderr, "command line operation failed: %s\n", err) - os.Exit(3) - } - os.Exit(0) - } + cmdbase.SvcConfig = &service.ServiceConfig{ + BinDir: binDir, + DataDir: dataDir, - // Start - go func() { - err = instance.Start() - if err != nil { - fmt.Printf("instance start failed: %s\n", err) - os.Exit(1) - } - }() + LogToStdout: logToStdout, + LogDir: logDir, + LogLevel: logLevel, - // Wait for signal. - signalCh := make(chan os.Signal, 1) - signal.Notify( - signalCh, - os.Interrupt, - syscall.SIGHUP, - syscall.SIGINT, - syscall.SIGTERM, - syscall.SIGQUIT, - sigUSR1, - ) - - select { - case sig := <-signalCh: - // Only print and continue to wait if SIGUSR1 - if sig == sigUSR1 { - printStackTo(os.Stderr, "PRINTING STACK ON REQUEST") - } else { - fmt.Println(" ") // CLI output. - slog.Warn("program was interrupted, stopping") - } - - case <-instance.ShutdownComplete(): - log.Shutdown() - os.Exit(instance.ExitCode()) - } - - // Catch signals during shutdown. - // Rapid unplanned disassembly after 5 interrupts. - go func() { - forceCnt := 5 - for { - <-signalCh - forceCnt-- - if forceCnt > 0 { - fmt.Printf(" again, but already shutting down - %d more to force\n", forceCnt) - } else { - printStackTo(os.Stderr, "PRINTING STACK ON FORCED EXIT") - os.Exit(1) - } - } - }() - - // Rapid unplanned disassembly after 3 minutes. - go func() { - time.Sleep(3 * time.Minute) - printStackTo(os.Stderr, "PRINTING STACK - TAKING TOO LONG FOR SHUTDOWN") - os.Exit(1) - }() - - // Stop instance. - if err := instance.Stop(); err != nil { - slog.Error("failed to stop", "err", err) - } - log.Shutdown() - os.Exit(instance.ExitCode()) -} - -func printStackTo(writer io.Writer, msg string) { - _, err := fmt.Fprintf(writer, "===== %s =====\n", msg) - if err == nil { - err = pprof.Lookup("goroutine").WriteTo(writer, 1) - } - if err != nil { - slog.Error("failed to write stack trace", "err", err) + BinariesIndexURLs: configure.DefaultStableBinaryIndexURLs, + IntelIndexURLs: configure.DefaultIntelIndexURLs, + VerifyBinaryUpdates: configure.BinarySigningTrustStore, + VerifyIntelUpdates: configure.BinarySigningTrustStore, } } diff --git a/cmds/observation-hub/main.go b/cmds/observation-hub/main.go index 598680f0..d1cfadb7 100644 --- a/cmds/observation-hub/main.go +++ b/cmds/observation-hub/main.go @@ -1,41 +1,75 @@ package main import ( - "errors" "flag" "fmt" - "io" - "log/slog" "os" - "os/signal" "runtime" - "runtime/pprof" - "syscall" - "time" "github.com/safing/portmaster/base/api" "github.com/safing/portmaster/base/info" - "github.com/safing/portmaster/base/log" "github.com/safing/portmaster/base/metrics" - "github.com/safing/portmaster/service/mgr" + "github.com/safing/portmaster/cmds/cmdbase" + "github.com/safing/portmaster/service" + "github.com/safing/portmaster/service/configure" "github.com/safing/portmaster/service/updates" - "github.com/safing/portmaster/spn" "github.com/safing/portmaster/spn/captain" "github.com/safing/portmaster/spn/conf" "github.com/safing/portmaster/spn/sluice" + "github.com/spf13/cobra" ) -var sigUSR1 = syscall.Signal(0xa) +var ( + rootCmd = &cobra.Command{ + Use: "observation-hub", + PersistentPreRun: initializeGlobals, + Run: cmdbase.RunService, + } + + binDir string + dataDir string + + logToStdout bool + logDir string + logLevel string +) + +func init() { + // 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)") + + // Add flags for service only. + rootCmd.Flags().BoolVar(&logToStdout, "log-stdout", false, "log to stdout instead of file") + rootCmd.Flags().StringVar(&logDir, "log-dir", "", "set directory for logs") + rootCmd.Flags().StringVar(&logLevel, "log", "", "set log level to [trace|debug|info|warning|error|critical]") + rootCmd.Flags().BoolVar(&cmdbase.PrintStackOnExit, "print-stack-on-exit", false, "prints the stack before of shutting down") + rootCmd.Flags().BoolVar(&cmdbase.RebootOnRestart, "reboot-on-restart", false, "reboot server instead of service restart") + + // Add other commands. + rootCmd.AddCommand(cmdbase.VersionCmd) + rootCmd.AddCommand(cmdbase.UpdateCmd) +} func main() { - flag.Parse() + // Add Go's default flag set. + // TODO: Move flags throughout Portmaster to here and add their values to the service config. + rootCmd.Flags().AddGoFlagSet(flag.CommandLine) + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func initializeGlobals(cmd *cobra.Command, args []string) { + // Set version info. info.Set("SPN Observation Hub", "", "GPLv3") // Configure metrics. _ = metrics.SetNamespace("observer") - // Configure user agent and updates. + // Configure user agent. updates.UserAgent = fmt.Sprintf("SPN Observation Hub (%s %s)", runtime.GOOS, runtime.GOARCH) // Configure SPN mode. @@ -46,129 +80,37 @@ func main() { sluice.EnableListener = false api.EnableServer = false - // Start logger with default log level. - _ = log.Start(log.WarningLevel) + // Configure service. + cmdbase.SvcFactory = func(svcCfg *service.ServiceConfig) (cmdbase.ServiceInstance, error) { + svc, err := service.New(svcCfg) - // Create instance. - var execCmdLine bool - instance, err := spn.New() - switch { - case err == nil: - // Continue - case errors.Is(err, mgr.ErrExecuteCmdLineOp): - execCmdLine = true - default: - fmt.Printf("error creating an instance: %s\n", err) - os.Exit(2) - } - - // Add additional modules. - observer, err := New(instance) - if err != nil { - fmt.Printf("error creating an instance: create observer module: %s\n", err) - os.Exit(2) - } - instance.AddModule(observer) - - _, err = NewApprise(instance) - if err != nil { - fmt.Printf("error creating an instance: create apprise module: %s\n", err) - os.Exit(2) - } - instance.AddModule(observer) - - // FIXME: Use service? - - // Execute command line operation, if requested or available. - switch { - case !execCmdLine: - // Run service. - case instance.CommandLineOperation == nil: - fmt.Println("command line operation execution requested, but not set") - os.Exit(3) - default: - // Run the function and exit. - err = instance.CommandLineOperation() + // Add additional modules. + observer, err := New(svc) if err != nil { - fmt.Fprintf(os.Stderr, "command line operation failed: %s\n", err) - os.Exit(3) + fmt.Printf("error creating an instance: create observer module: %s\n", err) + os.Exit(2) } - os.Exit(0) - } - - // Start - go func() { - err = instance.Start() + svc.AddModule(observer) + _, err = NewApprise(svc) if err != nil { - fmt.Printf("instance start failed: %s\n", err) - os.Exit(1) + fmt.Printf("error creating an instance: create apprise module: %s\n", err) + os.Exit(2) } - }() + svc.AddModule(observer) - // Wait for signal. - signalCh := make(chan os.Signal, 1) - signal.Notify( - signalCh, - os.Interrupt, - syscall.SIGHUP, - syscall.SIGINT, - syscall.SIGTERM, - syscall.SIGQUIT, - sigUSR1, - ) - - select { - case sig := <-signalCh: - // Only print and continue to wait if SIGUSR1 - if sig == sigUSR1 { - printStackTo(os.Stderr, "PRINTING STACK ON REQUEST") - } else { - fmt.Println(" ") // CLI output. - slog.Warn("program was interrupted, stopping") - } - - case <-instance.ShuttingDown(): - log.Shutdown() - os.Exit(instance.ExitCode()) + return svc, err } + cmdbase.SvcConfig = &service.ServiceConfig{ + BinDir: binDir, + DataDir: dataDir, - // Catch signals during shutdown. - // Rapid unplanned disassembly after 5 interrupts. - go func() { - forceCnt := 5 - for { - <-signalCh - forceCnt-- - if forceCnt > 0 { - fmt.Printf(" again, but already shutting down - %d more to force\n", forceCnt) - } else { - printStackTo(os.Stderr, "PRINTING STACK ON FORCED EXIT") - os.Exit(1) - } - } - }() + LogToStdout: logToStdout, + LogDir: logDir, + LogLevel: logLevel, - // Rapid unplanned disassembly after 3 minutes. - go func() { - time.Sleep(3 * time.Minute) - printStackTo(os.Stderr, "PRINTING STACK - TAKING TOO LONG FOR SHUTDOWN") - os.Exit(1) - }() - - // Stop instance. - if err := instance.Stop(); err != nil { - slog.Error("failed to stop", "err", err) - } - log.Shutdown() - os.Exit(instance.ExitCode()) -} - -func printStackTo(writer io.Writer, msg string) { - _, err := fmt.Fprintf(writer, "===== %s =====\n", msg) - if err == nil { - err = pprof.Lookup("goroutine").WriteTo(writer, 1) - } - if err != nil { - slog.Error("failed to write stack trace", "err", err) + BinariesIndexURLs: configure.DefaultStableBinaryIndexURLs, + IntelIndexURLs: configure.DefaultIntelIndexURLs, + VerifyBinaryUpdates: configure.BinarySigningTrustStore, + VerifyIntelUpdates: configure.BinarySigningTrustStore, } } diff --git a/cmds/portmaster-core/main.go b/cmds/portmaster-core/main.go index 8cd80864..88f187a0 100644 --- a/cmds/portmaster-core/main.go +++ b/cmds/portmaster-core/main.go @@ -10,7 +10,9 @@ import ( "github.com/safing/portmaster/base/info" "github.com/safing/portmaster/base/metrics" + "github.com/safing/portmaster/cmds/cmdbase" "github.com/safing/portmaster/service" + "github.com/safing/portmaster/service/configure" "github.com/safing/portmaster/service/updates" ) @@ -18,7 +20,7 @@ var ( rootCmd = &cobra.Command{ Use: "portmaster-core", PersistentPreRun: initializeGlobals, - Run: cmdRun, + Run: mainRun, } binDir string @@ -28,15 +30,11 @@ var ( logDir string logLevel string - svcCfg *service.ServiceConfig + printVersion bool ) func init() { - // Add Go's default flag set. - // TODO: Move flags throughout Portmaster to here and add their values to the service config. - rootCmd.Flags().AddGoFlagSet(flag.CommandLine) - - // 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)") @@ -44,17 +42,32 @@ func init() { rootCmd.Flags().BoolVar(&logToStdout, "log-stdout", false, "log to stdout instead of file") rootCmd.Flags().StringVar(&logDir, "log-dir", "", "set directory for logs") rootCmd.Flags().StringVar(&logLevel, "log", "", "set log level to [trace|debug|info|warning|error|critical]") + rootCmd.Flags().BoolVar(&printVersion, "version", false, "print version (backward compatibility; use command instead)") + rootCmd.Flags().BoolVar(&cmdbase.PrintStackOnExit, "print-stack-on-exit", false, "prints the stack before of shutting down") + + // Add other commands. + rootCmd.AddCommand(cmdbase.VersionCmd) + rootCmd.AddCommand(cmdbase.UpdateCmd) } func main() { + // Add Go's default flag set. + // TODO: Move flags throughout Portmaster to here and add their values to the service config. + rootCmd.Flags().AddGoFlagSet(flag.CommandLine) + if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) } } +func mainRun(cmd *cobra.Command, args []string) { + runPlatformSpecifics(cmd, args) + cmdbase.RunService(cmd, args) +} + func initializeGlobals(cmd *cobra.Command, args []string) { - // set information + // Set version info. info.Set("Portmaster", "", "GPLv3") // Configure metrics. @@ -63,8 +76,12 @@ func initializeGlobals(cmd *cobra.Command, args []string) { // Configure user agent. updates.UserAgent = fmt.Sprintf("Portmaster Core (%s %s)", runtime.GOOS, runtime.GOARCH) - // Create service config. - svcCfg = &service.ServiceConfig{ + // Configure service. + cmdbase.SvcFactory = func(svcCfg *service.ServiceConfig) (cmdbase.ServiceInstance, error) { + svc, err := service.New(svcCfg) + return svc, err + } + cmdbase.SvcConfig = &service.ServiceConfig{ BinDir: binDir, DataDir: dataDir, @@ -72,9 +89,18 @@ func initializeGlobals(cmd *cobra.Command, args []string) { LogDir: logDir, LogLevel: logLevel, - BinariesIndexURLs: service.DefaultStableBinaryIndexURLs, - IntelIndexURLs: service.DefaultIntelIndexURLs, - VerifyBinaryUpdates: service.BinarySigningTrustStore, - VerifyIntelUpdates: service.BinarySigningTrustStore, + BinariesIndexURLs: configure.DefaultStableBinaryIndexURLs, + IntelIndexURLs: configure.DefaultIntelIndexURLs, + VerifyBinaryUpdates: configure.BinarySigningTrustStore, + VerifyIntelUpdates: configure.BinarySigningTrustStore, } } + +func runFlagCmd(fn func(cmd *cobra.Command, args []string) error, cmd *cobra.Command, args []string) { + if err := fn(cmd, args); err != nil { + fmt.Printf("failed: %s\n", err) + os.Exit(1) + } + + os.Exit(0) +} diff --git a/cmds/portmaster-core/main_linux.go b/cmds/portmaster-core/main_linux.go new file mode 100644 index 00000000..e4d4783d --- /dev/null +++ b/cmds/portmaster-core/main_linux.go @@ -0,0 +1,21 @@ +package main + +import ( + "github.com/safing/portmaster/cmds/cmdbase" + "github.com/spf13/cobra" +) + +var recoverIPTablesFlag bool + +func init() { + rootCmd.Flags().BoolVar(&recoverIPTablesFlag, "recover-iptables", false, "recovers ip table rules (backward compatibility; use command instead)") +} + +func runPlatformSpecifics(cmd *cobra.Command, args []string) { + switch { + case printVersion: + runFlagCmd(cmdbase.Version, cmd, args) + case recoverIPTablesFlag: + runFlagCmd(recoverIPTables, cmd, args) + } +} diff --git a/cmds/portmaster-core/main_windows.go b/cmds/portmaster-core/main_windows.go new file mode 100644 index 00000000..5d2066f3 --- /dev/null +++ b/cmds/portmaster-core/main_windows.go @@ -0,0 +1,13 @@ +package main + +import ( + "github.com/safing/portmaster/cmds/cmdbase" + "github.com/spf13/cobra" +) + +func runPlatformSpecifics(cmd *cobra.Command, args []string) { + switch { + case printVersion: + runFlagCmd(cmdbase.Version, cmd, args) + } +} diff --git a/cmds/portmaster-core/recover_linux.go b/cmds/portmaster-core/recover_linux.go index 6f5532c2..61fecda7 100644 --- a/cmds/portmaster-core/recover_linux.go +++ b/cmds/portmaster-core/recover_linux.go @@ -2,7 +2,6 @@ package main import ( "errors" - "flag" "fmt" "os" "strings" @@ -13,23 +12,17 @@ import ( "github.com/safing/portmaster/service/firewall/interception" ) -var ( - recoverCmd = &cobra.Command{ - Use: "recover-iptables", - Short: "Force an update of all components.", - RunE: update, - } - - recoverIPTables bool -) +var recoverCmd = &cobra.Command{ + Use: "recover-iptables", + Short: "Clean up Portmaster rules in iptables", + RunE: recoverIPTables, +} func init() { rootCmd.AddCommand(recoverCmd) - - flag.BoolVar(&recoverIPTables, "recover-iptables", false, "recovers ip table rules (backward compatibility; use command instead)") } -func recover(cmd *cobra.Command, args []string) error { +func recoverIPTables(cmd *cobra.Command, args []string) error { // interception.DeactiveNfqueueFirewall uses coreos/go-iptables // which shells out to the /sbin/iptables binary. As a result, // we don't get the errno of the actual error and need to parse the diff --git a/cmds/testsuite/db.go b/cmds/testsuite/db.go index b23a6fd7..44286a8c 100644 --- a/cmds/testsuite/db.go +++ b/cmds/testsuite/db.go @@ -6,7 +6,7 @@ import ( ) func setupDatabases(path string) error { - err := database.InitializeWithPath(path) + err := database.Initialize(path) if err != nil { return err } diff --git a/cmds/trafficgen/main.go b/cmds/trafficgen/main.go index efcfd390..59f8aeca 100644 --- a/cmds/trafficgen/main.go +++ b/cmds/trafficgen/main.go @@ -37,13 +37,12 @@ func main() { } // Start logging. - err := log.Start() + err := log.Start("trace", true, "") if err != nil { fmt.Printf("failed to start logging: %s\n", err) os.Exit(1) } defer log.Shutdown() - log.SetLogLevel(log.TraceLevel) log.Info("starting traffic generator") // Execute requests diff --git a/cmds/updatemgr/convert.go b/cmds/updatemgr/convert.go new file mode 100644 index 00000000..1db8d658 --- /dev/null +++ b/cmds/updatemgr/convert.go @@ -0,0 +1,123 @@ +package main + +import ( + "encoding/json" + "fmt" + "path" + "strings" + "time" + + "github.com/safing/portmaster/service/updates" +) + +func convertV1(indexData []byte, baseURL string, lastUpdate time.Time) (*updates.Index, error) { + // Parse old index. + oldIndex := make(map[string]string) + err := json.Unmarshal(indexData, &oldIndex) + if err != nil { + return nil, fmt.Errorf("failed to parse old v1 index: %w", err) + } + + // Create new index. + newIndex := &updates.Index{ + Published: lastUpdate, + Artifacts: make([]*updates.Artifact, 0, len(oldIndex)), + } + + // Convert all entries. + if err := convertEntries(newIndex, baseURL, oldIndex); err != nil { + return nil, err + } + + return newIndex, nil +} + +type IndexV2 struct { + Channel string + Published time.Time + Releases map[string]string +} + +func convertV2(indexData []byte, baseURL string) (*updates.Index, error) { + // Parse old index. + oldIndex := &IndexV2{} + err := json.Unmarshal(indexData, oldIndex) + if err != nil { + return nil, fmt.Errorf("failed to parse old v2 index: %w", err) + } + + // Create new index. + newIndex := &updates.Index{ + Published: oldIndex.Published, + Artifacts: make([]*updates.Artifact, 0, len(oldIndex.Releases)), + } + + // Convert all entries. + if err := convertEntries(newIndex, baseURL, oldIndex.Releases); err != nil { + return nil, err + } + + return newIndex, nil +} + +func convertEntries(index *updates.Index, baseURL string, entries map[string]string) error { +entries: + for identifier, version := range entries { + dir, filename := path.Split(identifier) + artifactPath := GetVersionedPath(identifier, version) + + // Check if file is to be ignored. + if scanConfig.IsIgnored(artifactPath) { + continue entries + } + + // Get the platform. + var platform string + splittedPath := strings.Split(dir, "/") + if len(splittedPath) >= 1 { + platform = splittedPath[0] + if platform == "all" { + platform = "" + } + } else { + continue entries + } + + // Create new artifact. + newArtifact := &updates.Artifact{ + Filename: filename, + URLs: []string{baseURL + artifactPath}, + Platform: platform, + Version: version, + } + + // Derive unpack setting. + unpack, err := scanConfig.UnpackSetting(filename) + if err != nil { + return fmt.Errorf("failed to get unpack setting for %s: %w", filename, err) + } + newArtifact.Unpack = unpack + + // Add to new index. + index.Artifacts = append(index.Artifacts, newArtifact) + } + + return nil +} + +// GetVersionedPath combines the identifier and version and returns it as a file path. +func GetVersionedPath(identifier, version string) (versionedPath string) { + identifierPath, filename := path.Split(identifier) + + // Split the filename where the version should go. + splittedFilename := strings.SplitN(filename, ".", 2) + // Replace `.` with `-` for the filename format. + transformedVersion := strings.Replace(version, ".", "-", 2) + + // Put everything back together and return it. + versionedPath = identifierPath + splittedFilename[0] + "_v" + transformedVersion + if len(splittedFilename) > 1 { + versionedPath += "." + splittedFilename[1] + } + return versionedPath +} diff --git a/cmds/updatemgr/download.go b/cmds/updatemgr/download.go new file mode 100644 index 00000000..9c6bc58a --- /dev/null +++ b/cmds/updatemgr/download.go @@ -0,0 +1,88 @@ +package main + +import ( + "errors" + "fmt" + "os" + "runtime" + + "github.com/spf13/cobra" + + "github.com/safing/portmaster/base/log" + "github.com/safing/portmaster/service/updates" +) + +const currentPlatform = runtime.GOOS + "_" + runtime.GOARCH + +var ( + downloadCmd = &cobra.Command{ + Use: "download [index URL] [download dir]", + Short: "Download all artifacts by an index to a directory", + RunE: download, + Args: cobra.ExactArgs(2), + } + + downloadPlatform string +) + +func init() { + rootCmd.AddCommand(downloadCmd) + downloadCmd.Flags().StringVarP(&downloadPlatform, "platform", "p", currentPlatform, "Define platform to download artifacts for") +} + +func download(cmd *cobra.Command, args []string) error { + // Args. + indexURL := args[0] + targetDir := args[1] + + // Check target dir. + stat, err := os.Stat(targetDir) + if err != nil { + return fmt.Errorf("failed to access target dir: %w", err) + } + if !stat.IsDir() { + return errors.New("target is not a directory") + } + + // Create temporary directories. + tmpDownload, err := os.MkdirTemp("", "portmaster-updatemgr-download-") + if err != nil { + return err + } + tmpPurge, err := os.MkdirTemp("", "portmaster-updatemgr-purge-") + if err != nil { + return err + } + + // Create updater. + u, err := updates.New(nil, "", updates.Config{ + Name: "Downloader", + Directory: targetDir, + DownloadDirectory: tmpDownload, + PurgeDirectory: tmpPurge, + IndexURLs: []string{indexURL}, + IndexFile: "index.json", + Platform: downloadPlatform, + }) + if err != nil { + return err + } + + // Start logging. + err = log.Start(log.InfoLevel.Name(), true, "") + if err != nil { + return err + } + + // Download artifacts. + err = u.ForceUpdate() + + // Stop logging. + log.Shutdown() + + // Remove tmp dirs + os.RemoveAll(tmpDownload) + os.RemoveAll(tmpPurge) + + return err +} diff --git a/cmds/updatemgr/mirror.go b/cmds/updatemgr/mirror.go new file mode 100644 index 00000000..1e6f049c --- /dev/null +++ b/cmds/updatemgr/mirror.go @@ -0,0 +1,215 @@ +package main + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "os" + "path" + "path/filepath" + "runtime" + "strings" + "time" + + "github.com/spf13/cobra" + + "github.com/safing/portmaster/service/updates" +) + +var ( + // UserAgent is an HTTP User-Agent that is used to add + // more context to requests made by the registry when + // fetching resources from the update server. + UserAgent = fmt.Sprintf("Portmaster Update Mgr (%s %s)", runtime.GOOS, runtime.GOARCH) + + client http.Client +) + +func init() { + rootCmd.AddCommand(mirrorCmd) +} + +var ( + mirrorCmd = &cobra.Command{ + Use: "mirror [index URL] [mirror dir]", + Short: "Mirror all artifacts by an index to a directory, keeping the directory structure and file names intact", + RunE: mirror, + Args: cobra.ExactArgs(2), + } +) + +func mirror(cmd *cobra.Command, args []string) error { + // Args. + indexURL := args[0] + targetDir := args[1] + + // Check target dir. + stat, err := os.Stat(targetDir) + if err != nil { + return fmt.Errorf("failed to access target dir: %w", err) + } + if !stat.IsDir() { + return errors.New("target is not a directory") + } + + // Calculate Base URL. + u, err := url.Parse(indexURL) + if err != nil { + return fmt.Errorf("invalid index URL: %w", err) + } + indexPath := u.Path + u.RawQuery = "" + u.RawFragment = "" + u.Path = "" + u.RawPath = "" + baseURL := u.String() + "/" + + // Download Index. + fmt.Println("downloading index...") + indexData, err := downloadData(cmd.Context(), indexURL) + if err != nil { + return fmt.Errorf("download index: %w", err) + } + + // Parse (and convert) index. + var index *updates.Index + _, newIndexName := path.Split(indexPath) + switch { + case strings.HasSuffix(indexPath, ".v3.json"): + index = &updates.Index{} + err := json.Unmarshal(indexData, index) + if err != nil { + return fmt.Errorf("parse v3 index: %w", err) + } + case strings.HasSuffix(indexPath, ".v2.json"): + index, err = convertV2(indexData, baseURL) + if err != nil { + return fmt.Errorf("convert v2 index: %w", err) + } + newIndexName = strings.TrimSuffix(newIndexName, ".v2.json") + ".v3.json" + case strings.HasSuffix(indexPath, ".json"): + index, err = convertV1(indexData, baseURL, time.Now()) + if err != nil { + return fmt.Errorf("convert v1 index: %w", err) + } + newIndexName = strings.TrimSuffix(newIndexName, ".json") + ".v3.json" + default: + return errors.New("invalid index file extension") + } + + // Download and save artifacts. + for _, artifact := range index.Artifacts { + fmt.Printf("downloading %s...\n", artifact.Filename) + + // Download artifact and add any missing checksums. + artifactData, artifactLocation, err := getArtifact(cmd.Context(), artifact) + if err != nil { + return fmt.Errorf("get artifact %s: %w", artifact.Filename, err) + } + + // Write artifact to correct location. + artifactDst := filepath.Join(targetDir, filepath.FromSlash(artifactLocation)) + artifactDir, _ := filepath.Split(artifactDst) + err = os.MkdirAll(artifactDir, 0o0755) + if err != nil { + return fmt.Errorf("create artifact dir %s: %w", artifactDir, err) + } + err = os.WriteFile(artifactDst, artifactData, 0o0644) + if err != nil { + return fmt.Errorf("save artifact %s: %w", artifact.Filename, err) + } + } + + // Save index. + indexJson, err := json.MarshalIndent(index, "", " ") + if err != nil { + return fmt.Errorf("marshal index: %w", err) + } + indexDst := filepath.Join(targetDir, newIndexName) + err = os.WriteFile(indexDst, indexJson, 0o0644) + if err != nil { + return fmt.Errorf("write index to %s: %w", indexDst, err) + } + + return err +} + +func getArtifact(ctx context.Context, artifact *updates.Artifact) (artifactData []byte, artifactLocation string, err error) { + // Check URL. + if len(artifact.URLs) == 0 { + return nil, "", errors.New("no URLs defined") + } + u, err := url.Parse(artifact.URLs[0]) + if err != nil { + return nil, "", fmt.Errorf("invalid URL: %w", err) + } + + // Download data from URL. + artifactData, err = downloadData(ctx, artifact.URLs[0]) + if err != nil { + return nil, "", fmt.Errorf("GET artifact: %w", err) + } + + // Decompress artifact data, if configured. + var finalArtifactData []byte + if artifact.Unpack != "" { + finalArtifactData, err = updates.Decompress(artifact.Unpack, artifactData) + if err != nil { + return nil, "", fmt.Errorf("decompress: %w", err) + } + } else { + finalArtifactData = artifactData + } + + // Verify or generate checksum. + if artifact.SHA256 != "" { + if err := updates.CheckSHA256Sum(finalArtifactData, artifact.SHA256); err != nil { + return nil, "", err + } + } else { + fileHash := sha256.New() + if _, err := io.Copy(fileHash, bytes.NewReader(finalArtifactData)); err != nil { + return nil, "", fmt.Errorf("digest file: %w", err) + } + artifact.SHA256 = hex.EncodeToString(fileHash.Sum(nil)) + } + + return artifactData, u.Path, nil +} + +func downloadData(ctx context.Context, url string) ([]byte, error) { + // Setup request. + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody) + if err != nil { + return nil, fmt.Errorf("failed to create GET request to %s: %w", url, err) + } + if UserAgent != "" { + req.Header.Set("User-Agent", UserAgent) + } + + // Start request with shared http client. + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("failed a get file request to: %w", err) + } + defer func() { _ = resp.Body.Close() }() + + // Check for HTTP status errors. + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("server returned non-OK status: %d %s", resp.StatusCode, resp.Status) + } + + // Read the full body and return it. + content, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read body of response: %w", err) + } + return content, nil +} diff --git a/cmds/updatemgr/sign.go b/cmds/updatemgr/sign.go index 5f6d8b31..8bed7fed 100644 --- a/cmds/updatemgr/sign.go +++ b/cmds/updatemgr/sign.go @@ -69,7 +69,7 @@ func sign(cmd *cobra.Command, args []string) error { } // Parse index and check if it is valid. - index, err := updates.ParseIndex(unsignedIndexData, nil) + index, err := updates.ParseIndex(unsignedIndexData, "", nil) if err != nil { return fmt.Errorf("invalid index: %w", err) } @@ -85,7 +85,7 @@ func sign(cmd *cobra.Command, args []string) error { } // Check by parsing again. - index, err = updates.ParseIndex(signedIndexData, nil) + index, err = updates.ParseIndex(signedIndexData, "", nil) if err != nil { return fmt.Errorf("invalid index after signing: %w", err) } diff --git a/desktop/angular/package-lock.json b/desktop/angular/package-lock.json index b8527c1d..8bf3faab 100644 --- a/desktop/angular/package-lock.json +++ b/desktop/angular/package-lock.json @@ -23,13 +23,13 @@ "@fortawesome/free-brands-svg-icons": "^6.4.0", "@fortawesome/free-regular-svg-icons": "^6.4.0", "@fortawesome/free-solid-svg-icons": "^6.4.0", - "@tauri-apps/api": ">=2.0.0-rc.1", - "@tauri-apps/plugin-cli": ">=2.0.0-rc.1", - "@tauri-apps/plugin-clipboard-manager": ">=2.0.0-rc.1", - "@tauri-apps/plugin-dialog": ">=2.0.0-rc.1", - "@tauri-apps/plugin-notification": ">=2.0.0-rc.1", - "@tauri-apps/plugin-os": ">=2.0.0-rc.1", - "@tauri-apps/plugin-shell": "^2.0.0-rc", + "@tauri-apps/api": ">=2.1.1", + "@tauri-apps/plugin-cli": ">=2.0.0", + "@tauri-apps/plugin-clipboard-manager": ">=2.0.0", + "@tauri-apps/plugin-dialog": ">=2.0.0", + "@tauri-apps/plugin-notification": ">=2.0.0", + "@tauri-apps/plugin-os": ">=2.0.0", + "@tauri-apps/plugin-shell": "^2.0.1", "autoprefixer": "^10.4.14", "d3": "^7.8.4", "data-urls": "^5.0.0", @@ -4406,9 +4406,9 @@ "peer": true }, "node_modules/@tauri-apps/api": { - "version": "2.0.0-rc.4", - "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.0.0-rc.4.tgz", - "integrity": "sha512-UNiIhhKG08j4ooss2oEEVexffmWkgkYlC2M3GcX3VPtNsqFgVNL8Mcw/4Y7rO9M9S+ffAMnLOF5ypzyuyb8tyg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.1.1.tgz", + "integrity": "sha512-fzUfFFKo4lknXGJq8qrCidkUcKcH2UHhfaaCNt4GzgzGaW2iS26uFOg4tS3H4P8D6ZEeUxtiD5z0nwFF0UN30A==", "license": "Apache-2.0 OR MIT", "funding": { "type": "opencollective", @@ -4416,57 +4416,57 @@ } }, "node_modules/@tauri-apps/plugin-cli": { - "version": "2.0.0-rc.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-cli/-/plugin-cli-2.0.0-rc.1.tgz", - "integrity": "sha512-EcSTRfEU3zzlNbgwVtZVzqB19z3PNjyXD9H+YXuuLpV+Hwuh6Oi1fhUdCI0mp5zr9HSMWE+HzHkpBI7sVP1RyA==", - "license": "MIT or APACHE-2.0", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-cli/-/plugin-cli-2.0.0.tgz", + "integrity": "sha512-glQmlL1IiCGEa1FHYa/PTPSeYhfu56omLRgHXWlJECDt6DbJyRuJWVgtkQfUxtqnVdYnnU+DGIGeiInoEqtjLw==", + "license": "MIT OR Apache-2.0", "dependencies": { - "@tauri-apps/api": "^2.0.0-rc.4" + "@tauri-apps/api": "^2.0.0" } }, "node_modules/@tauri-apps/plugin-clipboard-manager": { - "version": "2.0.0-rc.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-clipboard-manager/-/plugin-clipboard-manager-2.0.0-rc.1.tgz", - "integrity": "sha512-hFgUABMmQuVGKwHb8PR9fuqfk0WRkedbWUt/ZV5sL4Q6kLrsp3JYJvtzVPeMYdeBvMqHl8WXNxAc/zwSld2h9w==", - "license": "MIT or APACHE-2.0", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-clipboard-manager/-/plugin-clipboard-manager-2.0.0.tgz", + "integrity": "sha512-V1sXmbjnwfXt/r48RJMwfUmDMSaP/8/YbH4CLNxt+/sf1eHlIP8PRFdFDQwLN0cNQKu2rqQVbG/Wc/Ps6cDUhw==", + "license": "MIT OR Apache-2.0", "dependencies": { - "@tauri-apps/api": "^2.0.0-rc.4" + "@tauri-apps/api": "^2.0.0" } }, "node_modules/@tauri-apps/plugin-dialog": { - "version": "2.0.0-rc.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.0.0-rc.1.tgz", - "integrity": "sha512-H28gh6BfZtjflHQ+HrmWwunDriBI3AQLAKnMs50GA6zeNUULqbQr7VXbAAKeJL/0CmWcecID4PKXVoSlaWRhEg==", - "license": "MIT or APACHE-2.0", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.0.1.tgz", + "integrity": "sha512-fnUrNr6EfvTqdls/ufusU7h6UbNFzLKvHk/zTuOiBq01R3dTODqwctZlzakdbfSp/7pNwTKvgKTAgl/NAP/Z0Q==", + "license": "MIT OR Apache-2.0", "dependencies": { - "@tauri-apps/api": "^2.0.0-rc.4" + "@tauri-apps/api": "^2.0.0" } }, "node_modules/@tauri-apps/plugin-notification": { - "version": "2.0.0-rc.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-notification/-/plugin-notification-2.0.0-rc.1.tgz", - "integrity": "sha512-ddDj7xM8XR7Zv2vdpofNXlLjcp49p/VjlL0D+/eBcMuyooaLNMor3jz/+H6s23iHerdxMWA50mzy26BRN1BySA==", - "license": "MIT or APACHE-2.0", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-notification/-/plugin-notification-2.0.0.tgz", + "integrity": "sha512-6qEDYJS7mgXZWLXA0EFL+DVCJh8sJlzSoyw6B50pxhLPVFjc5Vr5DVzl5W3mUHaYhod5wsC984eQnlCCGqxYDA==", + "license": "MIT OR Apache-2.0", "dependencies": { - "@tauri-apps/api": "^2.0.0-rc.4" + "@tauri-apps/api": "^2.0.0" } }, "node_modules/@tauri-apps/plugin-os": { - "version": "2.0.0-rc.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-os/-/plugin-os-2.0.0-rc.1.tgz", - "integrity": "sha512-PV8zlSTmYfiN2xzILUmlDSEycS7UYbH2yXk/ZqF+qQU6/s+OVQvmSth4EhllFjcpvPbtqELvpzfjw+2qEouchA==", - "license": "MIT or APACHE-2.0", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-os/-/plugin-os-2.0.0.tgz", + "integrity": "sha512-M7hG/nNyQYTJxVG/UhTKhp9mpXriwWzrs9mqDreB8mIgqA3ek5nHLdwRZJWhkKjZrnDT4v9CpA9BhYeplTlAiA==", + "license": "MIT OR Apache-2.0", "dependencies": { - "@tauri-apps/api": "^2.0.0-rc.4" + "@tauri-apps/api": "^2.0.0" } }, "node_modules/@tauri-apps/plugin-shell": { - "version": "2.0.0-rc.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-shell/-/plugin-shell-2.0.0-rc.1.tgz", - "integrity": "sha512-JtNROc0rqEwN/g93ig5pK4cl1vUo2yn+osCpY9de64cy/d9hRzof7AuYOgvt/Xcd5VPQmlgo2AGvUh5sQRSR1A==", - "license": "MIT or APACHE-2.0", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-shell/-/plugin-shell-2.0.1.tgz", + "integrity": "sha512-akU1b77sw3qHiynrK0s930y8zKmcdrSD60htjH+mFZqv5WaakZA/XxHR3/sF1nNv9Mgmt/Shls37HwnOr00aSw==", + "license": "MIT OR Apache-2.0", "dependencies": { - "@tauri-apps/api": "^2.0.0-rc.4" + "@tauri-apps/api": "^2.0.0" } }, "node_modules/@tootallnate/once": { @@ -21067,56 +21067,56 @@ "peer": true }, "@tauri-apps/api": { - "version": "2.0.0-rc.4", - "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.0.0-rc.4.tgz", - "integrity": "sha512-UNiIhhKG08j4ooss2oEEVexffmWkgkYlC2M3GcX3VPtNsqFgVNL8Mcw/4Y7rO9M9S+ffAMnLOF5ypzyuyb8tyg==" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.1.1.tgz", + "integrity": "sha512-fzUfFFKo4lknXGJq8qrCidkUcKcH2UHhfaaCNt4GzgzGaW2iS26uFOg4tS3H4P8D6ZEeUxtiD5z0nwFF0UN30A==" }, "@tauri-apps/plugin-cli": { - "version": "2.0.0-rc.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-cli/-/plugin-cli-2.0.0-rc.1.tgz", - "integrity": "sha512-EcSTRfEU3zzlNbgwVtZVzqB19z3PNjyXD9H+YXuuLpV+Hwuh6Oi1fhUdCI0mp5zr9HSMWE+HzHkpBI7sVP1RyA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-cli/-/plugin-cli-2.0.0.tgz", + "integrity": "sha512-glQmlL1IiCGEa1FHYa/PTPSeYhfu56omLRgHXWlJECDt6DbJyRuJWVgtkQfUxtqnVdYnnU+DGIGeiInoEqtjLw==", "requires": { - "@tauri-apps/api": "^2.0.0-rc.4" + "@tauri-apps/api": "^2.0.0" } }, "@tauri-apps/plugin-clipboard-manager": { - "version": "2.0.0-rc.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-clipboard-manager/-/plugin-clipboard-manager-2.0.0-rc.1.tgz", - "integrity": "sha512-hFgUABMmQuVGKwHb8PR9fuqfk0WRkedbWUt/ZV5sL4Q6kLrsp3JYJvtzVPeMYdeBvMqHl8WXNxAc/zwSld2h9w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-clipboard-manager/-/plugin-clipboard-manager-2.0.0.tgz", + "integrity": "sha512-V1sXmbjnwfXt/r48RJMwfUmDMSaP/8/YbH4CLNxt+/sf1eHlIP8PRFdFDQwLN0cNQKu2rqQVbG/Wc/Ps6cDUhw==", "requires": { - "@tauri-apps/api": "^2.0.0-rc.4" + "@tauri-apps/api": "^2.0.0" } }, "@tauri-apps/plugin-dialog": { - "version": "2.0.0-rc.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.0.0-rc.1.tgz", - "integrity": "sha512-H28gh6BfZtjflHQ+HrmWwunDriBI3AQLAKnMs50GA6zeNUULqbQr7VXbAAKeJL/0CmWcecID4PKXVoSlaWRhEg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.0.1.tgz", + "integrity": "sha512-fnUrNr6EfvTqdls/ufusU7h6UbNFzLKvHk/zTuOiBq01R3dTODqwctZlzakdbfSp/7pNwTKvgKTAgl/NAP/Z0Q==", "requires": { - "@tauri-apps/api": "^2.0.0-rc.4" + "@tauri-apps/api": "^2.0.0" } }, "@tauri-apps/plugin-notification": { - "version": "2.0.0-rc.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-notification/-/plugin-notification-2.0.0-rc.1.tgz", - "integrity": "sha512-ddDj7xM8XR7Zv2vdpofNXlLjcp49p/VjlL0D+/eBcMuyooaLNMor3jz/+H6s23iHerdxMWA50mzy26BRN1BySA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-notification/-/plugin-notification-2.0.0.tgz", + "integrity": "sha512-6qEDYJS7mgXZWLXA0EFL+DVCJh8sJlzSoyw6B50pxhLPVFjc5Vr5DVzl5W3mUHaYhod5wsC984eQnlCCGqxYDA==", "requires": { - "@tauri-apps/api": "^2.0.0-rc.4" + "@tauri-apps/api": "^2.0.0" } }, "@tauri-apps/plugin-os": { - "version": "2.0.0-rc.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-os/-/plugin-os-2.0.0-rc.1.tgz", - "integrity": "sha512-PV8zlSTmYfiN2xzILUmlDSEycS7UYbH2yXk/ZqF+qQU6/s+OVQvmSth4EhllFjcpvPbtqELvpzfjw+2qEouchA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-os/-/plugin-os-2.0.0.tgz", + "integrity": "sha512-M7hG/nNyQYTJxVG/UhTKhp9mpXriwWzrs9mqDreB8mIgqA3ek5nHLdwRZJWhkKjZrnDT4v9CpA9BhYeplTlAiA==", "requires": { - "@tauri-apps/api": "^2.0.0-rc.4" + "@tauri-apps/api": "^2.0.0" } }, "@tauri-apps/plugin-shell": { - "version": "2.0.0-rc.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-shell/-/plugin-shell-2.0.0-rc.1.tgz", - "integrity": "sha512-JtNROc0rqEwN/g93ig5pK4cl1vUo2yn+osCpY9de64cy/d9hRzof7AuYOgvt/Xcd5VPQmlgo2AGvUh5sQRSR1A==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-shell/-/plugin-shell-2.0.1.tgz", + "integrity": "sha512-akU1b77sw3qHiynrK0s930y8zKmcdrSD60htjH+mFZqv5WaakZA/XxHR3/sF1nNv9Mgmt/Shls37HwnOr00aSw==", "requires": { - "@tauri-apps/api": "^2.0.0-rc.4" + "@tauri-apps/api": "^2.0.0" } }, "@tootallnate/once": { diff --git a/desktop/angular/package.json b/desktop/angular/package.json index 5e916e51..2b0f9ea2 100644 --- a/desktop/angular/package.json +++ b/desktop/angular/package.json @@ -37,13 +37,13 @@ "@fortawesome/free-brands-svg-icons": "^6.4.0", "@fortawesome/free-regular-svg-icons": "^6.4.0", "@fortawesome/free-solid-svg-icons": "^6.4.0", - "@tauri-apps/api": ">=2.0.0-rc.1", - "@tauri-apps/plugin-cli": ">=2.0.0-rc.1", - "@tauri-apps/plugin-clipboard-manager": ">=2.0.0-rc.1", - "@tauri-apps/plugin-dialog": ">=2.0.0-rc.1", - "@tauri-apps/plugin-notification": ">=2.0.0-rc.1", - "@tauri-apps/plugin-os": ">=2.0.0-rc.1", - "@tauri-apps/plugin-shell": "^2.0.0-rc", + "@tauri-apps/api": ">=2.1.1", + "@tauri-apps/plugin-cli": ">=2.0.0", + "@tauri-apps/plugin-clipboard-manager": ">=2.0.0", + "@tauri-apps/plugin-dialog": ">=2.0.0", + "@tauri-apps/plugin-notification": ">=2.0.0", + "@tauri-apps/plugin-os": ">=2.0.0", + "@tauri-apps/plugin-shell": "^2.0.1", "autoprefixer": "^10.4.14", "d3": "^7.8.4", "data-urls": "^5.0.0", diff --git a/desktop/angular/src/app/shared/edit-profile-dialog/edit-profile-dialog.ts b/desktop/angular/src/app/shared/edit-profile-dialog/edit-profile-dialog.ts index 60b5b514..2528d81a 100644 --- a/desktop/angular/src/app/shared/edit-profile-dialog/edit-profile-dialog.ts +++ b/desktop/angular/src/app/shared/edit-profile-dialog/edit-profile-dialog.ts @@ -238,13 +238,13 @@ export class EditProfileDialog implements OnInit, OnDestroy { this.portapi.delete(icon.Value).subscribe(); } - // FIXME(ppacher): we cannot yet delete API based icons ... + // TODO(ppacher): we cannot yet delete API based icons ... }); if (this.iconData !== '') { // save the new icon in the cache database - // FIXME(ppacher): we currently need to calls because the icon API in portmaster + // TODO(ppacher): we currently need to calls because the icon API in portmaster // does not update the profile but just saves the file and returns the filename. // So we still need to update the profile manually. updateIcon = this.profileService @@ -261,7 +261,7 @@ export class EditProfileDialog implements OnInit, OnDestroy { }) ); - // FIXME(ppacher): reset presentationpath + // TODO(ppacher): reset presentationpath } else { // just clear out that there was an icon this.profile.Icons = []; diff --git a/desktop/angular/src/app/shared/netquery/line-chart/line-chart.ts b/desktop/angular/src/app/shared/netquery/line-chart/line-chart.ts index 8d95a061..23a9007c 100644 --- a/desktop/angular/src/app/shared/netquery/line-chart/line-chart.ts +++ b/desktop/angular/src/app/shared/netquery/line-chart/line-chart.ts @@ -543,7 +543,7 @@ export class SfngNetqueryLineChartComponent implemen .append("title") .text(d => d.text) - // FIXME(ppacher): somehow d3 does not recognize which data points must be removed + // TODO(ppacher): somehow d3 does not recognize which data points must be removed // or re-placed. For now, just remove them all this.svgInner .select('.points') diff --git a/desktop/angular/src/app/shared/netquery/searchbar/searchbar.ts b/desktop/angular/src/app/shared/netquery/searchbar/searchbar.ts index 46a42f51..044f8618 100644 --- a/desktop/angular/src/app/shared/netquery/searchbar/searchbar.ts +++ b/desktop/angular/src/app/shared/netquery/searchbar/searchbar.ts @@ -184,7 +184,7 @@ export class SfngNetquerySearchbarComponent implements ControlValueAccessor, OnI const queries: Observable>[] = []; const queryKeys: (keyof Partial)[] = []; - // FIXME(ppacher): confirm .type is an actually allowed field + // TODO(ppacher): confirm .type is an actually allowed field if (!!parser.lastUnterminatedCondition) { fields = [parser.lastUnterminatedCondition.type as keyof NetqueryConnection]; limit = 0; diff --git a/desktop/tauri/src-tauri/Cargo.lock b/desktop/tauri/src-tauri/Cargo.lock index 72551e9a..5e5aed17 100644 --- a/desktop/tauri/src-tauri/Cargo.lock +++ b/desktop/tauri/src-tauri/Cargo.lock @@ -4,19 +4,13 @@ version = 3 [[package]] name = "addr2line" -version = "0.22.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "adler2" version = "2.0.0" @@ -115,9 +109,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.18" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" [[package]] name = "android-tzdata" @@ -162,9 +156,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -177,43 +171,43 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" [[package]] name = "app-store-connect" @@ -223,7 +217,7 @@ checksum = "33fb5489b9bfcfa3aec2f68cc79eafb999b5af9b9d9d70ca8dfe36acdd1b2b05" dependencies = [ "anyhow", "base64 0.21.7", - "clap 4.5.17", + "clap 4.5.21", "dirs 5.0.1", "env_logger 0.10.2", "jsonwebtoken", @@ -234,7 +228,7 @@ dependencies = [ "rsa", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "x509-certificate", ] @@ -270,7 +264,7 @@ dependencies = [ "bitflags 2.6.0", "bytes", "chrono", - "clap 4.5.17", + "clap 4.5.21", "cryptographic-message-syntax", "der 0.7.9", "dialoguer", @@ -318,9 +312,9 @@ dependencies = [ "spki 0.7.3", "subtle", "tempfile", - "thiserror", + "thiserror 1.0.69", "tokio", - "tungstenite", + "tungstenite 0.21.0", "uuid", "walkdir", "widestring", @@ -346,7 +340,7 @@ dependencies = [ "scroll", "serde", "serde-xml-rs", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -372,7 +366,7 @@ dependencies = [ "sha1", "sha2", "signature 2.2.0", - "thiserror", + "thiserror 1.0.69", "url", "x509-certificate", "xml-rs", @@ -387,18 +381,18 @@ checksum = "d67af77d68a931ecd5cbd8a3b5987d63a1d1d1278f7f6a60ae33db485cdebb69" [[package]] name = "arbitrary" -version = "1.3.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" dependencies = [ "derive_arbitrary", ] [[package]] name = "arboard" -version = "3.4.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb4009533e8ff8f1450a5bcbc30f4242a1d34442221f72314bea1f5dc9c7f89" +checksum = "df099ccb16cd014ff054ac1bf392c67feeef57164b05c42f037cd40f5d4357f4" dependencies = [ "clipboard-win", "core-graphics 0.23.2", @@ -420,7 +414,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -437,9 +431,9 @@ dependencies = [ [[package]] name = "arrayref" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" @@ -473,26 +467,9 @@ dependencies = [ [[package]] name = "ashpd" -version = "0.8.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd884d7c72877a94102c3715f3b1cd09ff4fac28221add3e57cfbe25c236d093" -dependencies = [ - "enumflags2", - "futures-channel", - "futures-util", - "rand 0.8.5", - "serde", - "serde_repr", - "tokio", - "url", - "zbus 4.4.0", -] - -[[package]] -name = "ashpd" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe7e0dd0ac5a401dc116ed9f9119cf9decc625600474cb41f0fc0a0050abc9a" +checksum = "e9c39d707614dbcc6bed00015539f488d8e3fe3e66ed60961efc0c90f4b380b3" dependencies = [ "enumflags2", "futures-channel", @@ -506,7 +483,7 @@ dependencies = [ "wayland-backend", "wayland-client", "wayland-protocols", - "zbus 4.4.0", + "zbus 5.1.1", ] [[package]] @@ -521,7 +498,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -533,7 +510,7 @@ dependencies = [ "proc-macro2", "quote", "syn 1.0.109", - "synstructure", + "synstructure 0.12.6", ] [[package]] @@ -595,14 +572,14 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7ebdfa2ebdab6b1760375fa7d6f382b9f486eac35fc994625a00e89280bdbb7" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" dependencies = [ "async-task", "concurrent-queue", - "fastrand 2.1.1", - "futures-lite 2.3.0", + "fastrand 2.2.0", + "futures-lite 2.5.0", "slab", ] @@ -626,7 +603,7 @@ checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" dependencies = [ "async-lock 3.4.0", "blocking", - "futures-lite 2.3.0", + "futures-lite 2.5.0", ] [[package]] @@ -651,18 +628,18 @@ dependencies = [ [[package]] name = "async-io" -version = "2.3.4" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" +checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" dependencies = [ "async-lock 3.4.0", "cfg-if", "concurrent-queue", "futures-io", - "futures-lite 2.3.0", + "futures-lite 2.5.0", "parking", - "polling 3.7.3", - "rustix 0.38.35", + "polling 3.7.4", + "rustix 0.38.41", "slab", "tracing", "windows-sys 0.59.0", @@ -694,9 +671,9 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" dependencies = [ - "async-io 2.3.4", + "async-io 2.4.0", "blocking", - "futures-lite 2.3.0", + "futures-lite 2.5.0", ] [[package]] @@ -712,28 +689,27 @@ dependencies = [ "cfg-if", "event-listener 3.1.0", "futures-lite 1.13.0", - "rustix 0.38.35", + "rustix 0.38.41", "windows-sys 0.48.0", ] [[package]] name = "async-process" -version = "2.2.4" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a07789659a4d385b79b18b9127fc27e1a59e1e89117c78c5ea3b806f016374" +checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" dependencies = [ "async-channel", - "async-io 2.3.4", + "async-io 2.4.0", "async-lock 3.4.0", "async-signal", "async-task", "blocking", "cfg-if", "event-listener 5.3.1", - "futures-lite 2.3.0", - "rustix 0.38.35", + "futures-lite 2.5.0", + "rustix 0.38.41", "tracing", - "windows-sys 0.59.0", ] [[package]] @@ -744,7 +720,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -753,13 +729,13 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" dependencies = [ - "async-io 2.3.4", + "async-io 2.4.0", "async-lock 3.4.0", "atomic-waker", "cfg-if", "futures-core", "futures-io", - "rustix 0.38.35", + "rustix 0.38.41", "signal-hook-registry", "slab", "windows-sys 0.59.0", @@ -773,13 +749,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.82" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -833,9 +809,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "av1-grain" @@ -853,18 +829,18 @@ dependencies = [ [[package]] name = "avif-serialize" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" +checksum = "e335041290c43101ca215eed6f43ec437eb5a42125573f600fc3fa42b9bddd62" dependencies = [ "arrayvec 0.7.6", ] [[package]] name = "aws-config" -version = "1.5.8" +version = "1.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7198e6f03240fdceba36656d8be440297b6b82270325908c7381f37d826a74f6" +checksum = "9b49afaa341e8dd8577e1a2200468f98956d6eda50bcf4a53246cc00174ba924" dependencies = [ "aws-credential-types", "aws-runtime", @@ -879,7 +855,7 @@ dependencies = [ "aws-smithy-types", "aws-types", "bytes", - "fastrand 2.1.1", + "fastrand 2.2.0", "hex", "http 0.2.12", "ring", @@ -918,7 +894,7 @@ dependencies = [ "aws-smithy-types", "aws-types", "bytes", - "fastrand 2.1.1", + "fastrand 2.2.0", "http 0.2.12", "http-body 0.4.6", "once_cell", @@ -930,11 +906,10 @@ dependencies = [ [[package]] name = "aws-sdk-s3" -version = "1.54.0" +version = "1.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2f2a62020f3e06f9b352b2a23547f6e1d110b6bf1e18a6b588ae36114eaf6e2" +checksum = "f43850204a109a5eea1ea93951cf0440268cef98b0d27dfef4534949e23735f7" dependencies = [ - "ahash 0.8.11", "aws-credential-types", "aws-runtime", "aws-sigv4", @@ -949,7 +924,7 @@ dependencies = [ "aws-smithy-xml", "aws-types", "bytes", - "fastrand 2.1.1", + "fastrand 2.2.0", "hex", "hmac", "http 0.2.12", @@ -965,9 +940,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.45.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33ae899566f3d395cbf42858e433930682cc9c1889fa89318896082fef45efb" +checksum = "09677244a9da92172c8dc60109b4a9658597d4d298b188dd0018b6a66b410ca4" dependencies = [ "aws-credential-types", "aws-runtime", @@ -987,9 +962,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.46.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f39c09e199ebd96b9f860b0fce4b6625f211e064ad7c8693b72ecf7ef03881e0" +checksum = "81fea2f3a8bb3bd10932ae7ad59cc59f65f270fc9183a7e91f501dc5efbef7ee" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1009,9 +984,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.45.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d95f93a98130389eb6233b9d615249e543f6c24a68ca1f109af9ca5164a8765" +checksum = "6ada54e5f26ac246dc79727def52f7f8ed38915cb47781e2a72213957dc3a7d5" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1032,9 +1007,9 @@ dependencies = [ [[package]] name = "aws-sigv4" -version = "1.2.4" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc8db6904450bafe7473c6ca9123f88cc11089e41a025408f992db4e22d3be68" +checksum = "5619742a0d8f253be760bfbb8e8e8368c69e3587e4637af5754e488a611499b1" dependencies = [ "aws-credential-types", "aws-smithy-eventstream", @@ -1072,9 +1047,9 @@ dependencies = [ [[package]] name = "aws-smithy-checksums" -version = "0.60.12" +version = "0.60.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598b1689d001c4d4dc3cb386adb07d37786783aee3ac4b324bcadac116bf3d23" +checksum = "ba1a71073fca26775c8b5189175ea8863afb1c9ea2cceb02a5de5ad9dfbaa795" dependencies = [ "aws-smithy-http", "aws-smithy-types", @@ -1144,22 +1119,22 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.7.1" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1ce695746394772e7000b39fe073095db6d45a862d0767dd5ad0ac0d7f8eb87" +checksum = "be28bd063fa91fd871d131fc8b68d7cd4c5fa0869bea68daca50dcb1cbd76be2" dependencies = [ "aws-smithy-async", "aws-smithy-http", "aws-smithy-runtime-api", "aws-smithy-types", "bytes", - "fastrand 2.1.1", + "fastrand 2.2.0", "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", "http-body 1.0.1", "httparse", - "hyper 0.14.30", + "hyper 0.14.31", "hyper-rustls 0.24.2", "once_cell", "pin-project-lite", @@ -1171,9 +1146,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.7.2" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e086682a53d3aa241192aa110fa8dfce98f2f5ac2ead0de84d41582c7e8fdb96" +checksum = "92165296a47a812b267b4f41032ff8069ab7ff783696d217f0994a0d7ab585cd" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -1188,9 +1163,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.2.7" +version = "1.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147100a7bea70fa20ef224a6bad700358305f5dc0f84649c53769761395b355b" +checksum = "4fbd94a32b3a7d55d3806fe27d98d3ad393050439dd05eb53ece36ec5e3d3510" dependencies = [ "base64-simd", "bytes", @@ -1201,7 +1176,7 @@ dependencies = [ "http-body 0.4.6", "http-body 1.0.1", "http-body-util", - "itoa 1.0.11", + "itoa 1.0.14", "num-integer", "pin-project-lite", "pin-utils", @@ -1237,21 +1212,21 @@ dependencies = [ [[package]] name = "axum" -version = "0.7.5" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" dependencies = [ "async-trait", "axum-core", - "base64 0.21.7", + "base64 0.22.1", "bytes", "futures-util", "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.4.1", + "hyper 1.5.1", "hyper-util", - "itoa 1.0.11", + "itoa 1.0.14", "matchit", "memchr", "mime", @@ -1263,10 +1238,10 @@ dependencies = [ "serde_path_to_error", "serde_urlencoded", "sha1", - "sync_wrapper 1.0.1", + "sync_wrapper 1.0.2", "tokio", "tokio-tungstenite", - "tower", + "tower 0.5.1", "tower-layer", "tower-service", "tracing", @@ -1274,9 +1249,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" dependencies = [ "async-trait", "bytes", @@ -1287,7 +1262,7 @@ dependencies = [ "mime", "pin-project-lite", "rustversion", - "sync_wrapper 0.1.2", + "sync_wrapper 1.0.2", "tower-layer", "tower-service", "tracing", @@ -1295,17 +1270,17 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.73" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", - "miniz_oxide 0.7.4", - "object 0.36.4", + "miniz_oxide", + "object 0.36.5", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -1413,16 +1388,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57792b99d555ebf109c83169228076f7d997e2b37ba1a653850ccd703ac7bab0" dependencies = [ "sysctl", - "thiserror", + "thiserror 1.0.69", "uname", "winapi", ] [[package]] name = "bitstream-io" -version = "2.5.3" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b81e1519b0d82120d2fd469d5bfb2919a9361c48b02d82d04befc1cdd2002452" +checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" [[package]] name = "bitvec" @@ -1508,7 +1483,7 @@ dependencies = [ "async-channel", "async-task", "futures-io", - "futures-lite 2.3.0", + "futures-lite 2.5.0", "piper", ] @@ -1524,9 +1499,9 @@ dependencies = [ [[package]] name = "borsh" -version = "1.5.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" +checksum = "2506947f73ad44e344215ccd6403ac2ae18cd8e046e581a441bf8d199f257f03" dependencies = [ "borsh-derive", "cfg_aliases", @@ -1534,23 +1509,22 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.5.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" +checksum = "c2593a3b8b938bd68373196c9832f516be11fa487ef4ae745eb282e6a56a7244" dependencies = [ "once_cell", "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.77", - "syn_derive", + "syn 2.0.89", ] [[package]] name = "brotli" -version = "6.0.0" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" +checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -1569,9 +1543,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" dependencies = [ "memchr", "serde", @@ -1594,9 +1568,9 @@ dependencies = [ [[package]] name = "built" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "236e6289eda5a812bc6b53c3b024039382a2895fbbeef2d748b2931546d392c4" +checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b" [[package]] name = "bumpalo" @@ -1609,9 +1583,9 @@ dependencies = [ [[package]] name = "byte-unit" -version = "5.1.4" +version = "5.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ac19bdf0b2665407c39d82dbc937e951e7e2001609f0fb32edd0af45a2d63e" +checksum = "e1cd29c3c585209b0cbc7309bfe3ed7efd8c84c21b7af29c8bfae908f8777174" dependencies = [ "rust_decimal", "serde", @@ -1648,9 +1622,9 @@ checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" [[package]] name = "bytemuck" -version = "1.17.1" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773d90827bc3feecfb67fab12e24de0749aad83c74b9504ecde46237b5cd24e2" +checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" [[package]] name = "byteorder" @@ -1666,9 +1640,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" dependencies = [ "serde", ] @@ -1722,7 +1696,7 @@ dependencies = [ "hashbrown 0.14.5", "instant", "once_cell", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -1754,7 +1728,7 @@ dependencies = [ "glib", "libc", "once_cell", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -1789,9 +1763,9 @@ dependencies = [ [[package]] name = "cargo-mobile2" -version = "0.17.3" +version = "0.17.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8052fc43184dc6c572437c2f8dae83e4ca9a5b27c790e269b90b080c1b9301" +checksum = "197498a32cc339dac8e77a319eacf00688b5ac633628f007c4465f303cc69105" dependencies = [ "colored", "core-foundation 0.10.0", @@ -1815,7 +1789,7 @@ dependencies = [ "serde", "serde_json", "textwrap 0.16.1", - "thiserror", + "thiserror 1.0.69", "toml 0.8.19", "ureq", "which", @@ -1843,7 +1817,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -1885,9 +1859,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.16" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9d013ecb737093c0e86b151a7b837993cf9ec6c502946cfb44bedc392421e0b" +checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" dependencies = [ "jobserver", "libc", @@ -1984,9 +1958,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.17" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" +checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" dependencies = [ "clap_builder", "clap_derive", @@ -1994,9 +1968,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.17" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" +checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" dependencies = [ "anstream", "anstyle", @@ -2006,30 +1980,30 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.25" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d7f143a7e709cbe6c34853dcd3bb1370c7e1bb4d9e7310ca8cb40b490ae035" +checksum = "d9647a559c112175f17cf724dc72d3645680a883c58481332779192b0d8e7a01" dependencies = [ - "clap 4.5.17", + "clap 4.5.21", ] [[package]] name = "clap_derive" -version = "4.5.13" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] name = "clipboard-win" @@ -2089,9 +2063,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "colored" @@ -2127,7 +2101,7 @@ checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" dependencies = [ "castaway", "cfg-if", - "itoa 1.0.11", + "itoa 1.0.14", "rustversion", "ryu", "static_assertions", @@ -2199,6 +2173,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "time", + "version_check", +] + [[package]] name = "cookie-factory" version = "0.3.3" @@ -2293,9 +2277,9 @@ dependencies = [ [[package]] name = "cpio" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e3adec7390c7643049466136117057188edf5f23efc5c8b4fc8079c8dc34a6" +checksum = "938e716cb1ade5d6c8f959c13a7248b889c07491fc7e41167c3afe20f8f0de1e" [[package]] name = "cpio-archive" @@ -2306,14 +2290,14 @@ dependencies = [ "chrono", "is_executable", "simple-file-manifest", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "cpufeatures" -version = "0.2.13" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -2464,17 +2448,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] name = "ctor" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -2521,7 +2505,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -2587,7 +2571,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -2609,14 +2593,14 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core 0.20.10", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] name = "dashmap" -version = "6.0.1" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804c8821570c3f8b70230c2ba75ffa5c0f9a4189b9a432b6656c536712acae28" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ "cfg-if", "crossbeam-utils", @@ -2711,44 +2695,44 @@ dependencies = [ [[package]] name = "derive_arbitrary" -version = "1.3.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] name = "derive_builder" -version = "0.20.1" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd33f37ee6a119146a1781d3356a7c26028f83d779b2e04ecd45fdc75c76877b" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" dependencies = [ "derive_builder_macro", ] [[package]] name = "derive_builder_core" -version = "0.20.1" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7431fa049613920234f22c47fdc33e6cf3ee83067091ea4277a3f8c4587aae38" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" dependencies = [ "darling 0.20.10", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] name = "derive_builder_macro" -version = "0.20.1" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4abae7035bf79b9877b779505d8cf3749285b80c43941eda66604841889451dc" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -2761,7 +2745,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -2794,7 +2778,7 @@ dependencies = [ "console", "shell-words", "tempfile", - "thiserror", + "thiserror 1.0.69", "zeroize", ] @@ -2883,7 +2867,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -2892,7 +2876,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading", + "libloading 0.8.5", ] [[package]] @@ -2915,7 +2899,7 @@ checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -3121,9 +3105,9 @@ dependencies = [ [[package]] name = "embed-resource" -version = "2.4.3" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4edcacde9351c33139a41e3c97eb2334351a81a2791bebb0b243df837128f602" +checksum = "b68b6f9f63a0b6a38bc447d4ce84e2b388f3ec95c99c641c8ff0dd3ef89a6379" dependencies = [ "cc", "memchr", @@ -3147,9 +3131,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.34" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] @@ -3185,7 +3169,7 @@ checksum = "ba7795da175654fe16979af73f81f26a8ea27638d8d9823d317016888a63dc4c" dependencies = [ "num-traits", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -3206,7 +3190,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -3273,9 +3257,9 @@ dependencies = [ [[package]] name = "error-code" -version = "3.2.0" +version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b" +checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" [[package]] name = "event-listener" @@ -3317,15 +3301,14 @@ dependencies = [ [[package]] name = "exr" -version = "1.72.0" +version = "1.73.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4" +checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" dependencies = [ "bit_field", - "flume", "half", "lebe", - "miniz_oxide 0.7.4", + "miniz_oxide", "rayon-core", "smallvec", "zune-inflate", @@ -3353,24 +3336,24 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "fdeflate" -version = "0.3.4" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +checksum = "07c6f4c64c1d33a3111c4466f7365ebdcc37c5bd1ea0d62aae2e3d722aacbedb" dependencies = [ "simd-adler32", ] [[package]] name = "fern" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee" +checksum = "69ff9c9d5fb3e6da8ac2f77ab76fe7e8087d512ce095200f8f29ac5b656cf6dc" dependencies = [ "log", ] @@ -3439,12 +3422,12 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", - "miniz_oxide 0.8.0", + "miniz_oxide", ] [[package]] @@ -3453,24 +3436,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" -[[package]] -name = "fluent-uri" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "flume" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" -dependencies = [ - "spin", -] - [[package]] name = "fnv" version = "1.0.7" @@ -3533,7 +3498,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -3574,7 +3539,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db9c27b72f19a99a895f8ca89e2d26e4ef31013376e56fdafef697627306c3e4" dependencies = [ "nom", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -3604,9 +3569,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -3619,9 +3584,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -3629,15 +3594,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -3646,9 +3611,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" @@ -3667,11 +3632,11 @@ dependencies = [ [[package]] name = "futures-lite" -version = "2.3.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1" dependencies = [ - "fastrand 2.1.1", + "fastrand 2.2.0", "futures-core", "futures-io", "parking", @@ -3680,26 +3645,26 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-timer" @@ -3709,9 +3674,9 @@ checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -3860,7 +3825,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc3655aa6818d65bc620d6911f05aa7b6aeb596291e1e9f79e52df85583d1e30" dependencies = [ - "rustix 0.38.35", + "rustix 0.38.41", "windows-targets 0.52.6", ] @@ -3910,9 +3875,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.29.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "gio" @@ -3930,7 +3895,7 @@ dependencies = [ "once_cell", "pin-project-lite", "smallvec", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -3966,7 +3931,7 @@ dependencies = [ "memchr", "once_cell", "smallvec", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -3980,7 +3945,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4001,9 +3966,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "globset" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" dependencies = [ "aho-corasick", "bstr", @@ -4105,7 +4070,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4120,7 +4085,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.5.0", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", @@ -4129,9 +4094,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" dependencies = [ "atomic-waker", "bytes", @@ -4139,7 +4104,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.1.0", - "indexmap 2.5.0", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", @@ -4158,16 +4123,17 @@ dependencies = [ [[package]] name = "handlebars" -version = "6.0.0" +version = "6.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5226a0e122dc74917f3a701484482bed3ee86d016c7356836abbaa033133a157" +checksum = "fd4ccde012831f9a071a637b0d4e31df31c0f6c525784b35ae76a9ac6bc1e315" dependencies = [ "log", + "num-order", "pest", "pest_derive", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -4191,9 +4157,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.0" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ "allocator-api2", "equivalent", @@ -4288,7 +4254,7 @@ checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", - "itoa 1.0.11", + "itoa 1.0.14", ] [[package]] @@ -4299,7 +4265,7 @@ checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", - "itoa 1.0.11", + "itoa 1.0.14", ] [[package]] @@ -4338,9 +4304,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -4356,9 +4322,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.30" +version = "0.14.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" +checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" dependencies = [ "bytes", "futures-channel", @@ -4369,7 +4335,7 @@ dependencies = [ "http-body 0.4.6", "httparse", "httpdate", - "itoa 1.0.11", + "itoa 1.0.14", "pin-project-lite", "socket2 0.5.7", "tokio", @@ -4380,19 +4346,19 @@ dependencies = [ [[package]] name = "hyper" -version = "1.4.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.6", + "h2 0.4.7", "http 1.1.0", "http-body 1.0.1", "httparse", "httpdate", - "itoa 1.0.11", + "itoa 1.0.14", "pin-project-lite", "smallvec", "tokio", @@ -4407,7 +4373,7 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http 0.2.12", - "hyper 0.14.30", + "hyper 0.14.31", "log", "rustls 0.21.12", "rustls-native-certs 0.6.3", @@ -4423,9 +4389,9 @@ checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http 1.1.0", - "hyper 1.4.1", + "hyper 1.5.1", "hyper-util", - "rustls 0.23.12", + "rustls 0.23.19", "rustls-pki-types", "tokio", "tokio-rustls 0.26.0", @@ -4440,7 +4406,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.4.1", + "hyper 1.5.1", "hyper-util", "native-tls", "tokio", @@ -4450,29 +4416,28 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.7" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", "futures-channel", "futures-util", "http 1.1.0", "http-body 1.0.1", - "hyper 1.4.1", + "hyper 1.5.1", "pin-project-lite", "socket2 0.5.7", "tokio", - "tower", "tower-service", "tracing", ] [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -4501,6 +4466,124 @@ dependencies = [ "png", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + [[package]] name = "idea" version = "0.5.1" @@ -4518,19 +4601,30 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.5.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] name = "ignore" -version = "0.4.22" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" +checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" dependencies = [ "crossbeam-deque", "globset", @@ -4544,16 +4638,16 @@ dependencies = [ [[package]] name = "image" -version = "0.25.2" +version = "0.25.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10" +checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b" dependencies = [ "bytemuck", "byteorder-lite", "color_quant", "exr", "gif", - "image-webp", + "image-webp 0.2.0", "num-traits", "png", "qoi", @@ -4575,6 +4669,16 @@ dependencies = [ "quick-error", ] +[[package]] +name = "image-webp" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e031e8e3d94711a9ccb5d6ea357439ef3dcbed361798bd4071dc4d9793fbe22f" +dependencies = [ + "byteorder-lite", + "quick-error", +] + [[package]] name = "imagesize" version = "0.13.0" @@ -4583,9 +4687,9 @@ checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" [[package]] name = "imgref" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" +checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" [[package]] name = "include_dir" @@ -4619,12 +4723,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.2", "serde", ] @@ -4699,7 +4803,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -4715,9 +4819,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.9.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "is-docker" @@ -4814,9 +4918,9 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "java-properties" @@ -4863,7 +4967,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] @@ -4891,9 +4995,9 @@ checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] @@ -4906,19 +5010,19 @@ checksum = "ec9ad60d674508f3ca8f380a928cfe7b096bc729c4e2dbfe3852bc45da3ab30b" dependencies = [ "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "json-patch" -version = "2.0.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b1fb8864823fad91877e6caea0baca82e49e8db50f8e5c9f9a453e27d3330fc" +checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" dependencies = [ "jsonptr", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -4934,20 +5038,19 @@ dependencies = [ [[package]] name = "jsonptr" -version = "0.4.7" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c6e529149475ca0b2820835d3dce8fcc41c6b943ca608d32f35b449255e4627" +checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" dependencies = [ - "fluent-uri", "serde", "serde_json", ] [[package]] name = "jsonrpsee" -version = "0.24.6" +version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02f01f48e04e0d7da72280ab787c9943695699c9b32b99158ece105e8ad0afea" +checksum = "c5c71d8c1a731cc4227c2f698d377e7848ca12c8a48866fc5e6951c43a4db843" dependencies = [ "jsonrpsee-core", "jsonrpsee-server", @@ -4957,9 +5060,9 @@ dependencies = [ [[package]] name = "jsonrpsee-client-transport" -version = "0.24.6" +version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d80eccbd47a7b9f1e67663fd846928e941cb49c65236e297dd11c9ea3c5e3387" +checksum = "548125b159ba1314104f5bb5f38519e03a41862786aa3925cf349aae9cdd546e" dependencies = [ "base64 0.22.1", "futures-util", @@ -4967,7 +5070,7 @@ dependencies = [ "jsonrpsee-core", "pin-project", "soketto", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-util", "tracing", @@ -4976,9 +5079,9 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.24.6" +version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c2709a32915d816a6e8f625bf72cf74523ebe5d8829f895d6b041b1d3137818" +checksum = "f2882f6f8acb9fdaec7cefc4fd607119a9bd709831df7d7672a1d3b644628280" dependencies = [ "async-trait", "bytes", @@ -4994,7 +5097,7 @@ dependencies = [ "rustc-hash", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-stream", "tracing", @@ -5002,15 +5105,15 @@ dependencies = [ [[package]] name = "jsonrpsee-server" -version = "0.24.6" +version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e30110d0f2d7866c8cc6c86483bdab2eb9f4d2f0e20db55518b2bca84651ba8e" +checksum = "82ad8ddc14be1d4290cd68046e7d1d37acd408efed6d3ca08aefcc3ad6da069c" dependencies = [ "futures-util", "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.4.1", + "hyper 1.5.1", "hyper-util", "jsonrpsee-core", "jsonrpsee-types", @@ -5019,31 +5122,31 @@ dependencies = [ "serde", "serde_json", "soketto", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-stream", "tokio-util", - "tower", + "tower 0.4.13", "tracing", ] [[package]] name = "jsonrpsee-types" -version = "0.24.6" +version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ca331cd7b3fe95b33432825c2d4c9f5a43963e207fdc01ae67f9fd80ab0930f" +checksum = "a178c60086f24cc35bb82f57c651d0d25d99c4742b4d335de04e97fa1f08a8a1" dependencies = [ "http 1.1.0", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "jsonrpsee-ws-client" -version = "0.24.6" +version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "755ca3da1c67671f1fae01cd1a47f41dfb2233a8f19a643e587ab0a663942044" +checksum = "0fe322e0896d0955a3ebdd5bf813571c53fea29edd713bc315b76620b327e86d" dependencies = [ "http 1.1.0", "jsonrpsee-client-transport", @@ -5054,27 +5157,27 @@ dependencies = [ [[package]] name = "jsonschema" -version = "0.18.1" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5f037c58cadb17e8591b620b523cc6a7ab2b91b6ce3121f8eb4171f8d80115c" +checksum = "fa0f4bea31643be4c6a678e9aa4ae44f0db9e5609d5ca9dc9083d06eb3e9a27a" dependencies = [ "ahash 0.8.11", "anyhow", "base64 0.22.1", "bytecount", - "clap 4.5.17", + "clap 4.5.21", "fancy-regex", "fraction", "getrandom 0.2.15", "iso8601", - "itoa 1.0.11", + "itoa 1.0.14", "memchr", "num-cmp", "once_cell", "parking_lot", "percent-encoding", "regex", - "reqwest 0.12.7", + "reqwest 0.12.9", "serde", "serde_json", "time", @@ -5105,9 +5208,9 @@ checksum = "17ab85f84ca42c5ec520e6f3c9966ba1fd62909ce260f8837e248857d2560509" [[package]] name = "k256" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" dependencies = [ "cfg-if", "ecdsa 0.16.9", @@ -5139,9 +5242,9 @@ dependencies = [ [[package]] name = "konst" -version = "0.3.9" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50a0ba6de5f7af397afff922f22c149ff605c766cd3269cf6c1cd5e466dbe3b9" +checksum = "b65f00fb3910881e52bf0850ae2a82aea411488a557e1c02820ceaa60963dce3" dependencies = [ "const_panic", "konst_kernel", @@ -5150,9 +5253,9 @@ dependencies = [ [[package]] name = "konst_kernel" -version = "0.3.9" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be0a455a1719220fd6adf756088e1c69a85bf14b6a9e24537a5cc04f503edb2b" +checksum = "599c1232f55c72c7fc378335a3efe1c878c92720838c8e6a4fd87784ef7764de" dependencies = [ "typewit", ] @@ -5192,9 +5295,9 @@ dependencies = [ [[package]] name = "kurbo" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e5aa9f0f96a938266bdb12928a67169e8d22c6a786fda8ed984b85e6ba93c3c" +checksum = "89234b2cc610a7dd927ebde6b41dd1a5d4214cffaef4cf1fb2195d592f92518f" dependencies = [ "arrayvec 0.7.6", "smallvec", @@ -5235,25 +5338,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" dependencies = [ "gtk-sys", - "libloading", + "libloading 0.7.4", "once_cell", ] [[package]] name = "libc" -version = "0.2.158" +version = "0.2.166" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "c2ccc108bbc0b1331bd061864e7cd823c0cab660bbe6970e66e2c0614decde36" [[package]] name = "libfuzzer-sys" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +checksum = "9b9569d2f74e257076d8c6bfa73fb505b46b851e51ddaecc825944aa3bed17fa" dependencies = [ "arbitrary", "cc", - "once_cell", ] [[package]] @@ -5267,10 +5369,20 @@ dependencies = [ ] [[package]] -name = "libm" -version = "0.2.8" +name = "libloading" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libredox" @@ -5280,7 +5392,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", - "redox_syscall 0.5.3", + "redox_syscall 0.5.7", ] [[package]] @@ -5296,15 +5408,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] -name = "local-ip-address" -version = "0.6.2" +name = "litemap" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b435d7dd476416a905f9634dff8c330cee8d3168fdd1fbd472a17d1a75c00c3e" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + +[[package]] +name = "local-ip-address" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3669cf5561f8d27e8fc84cc15e58350e70f557d4d65f70e3154e54cd2f8e1782" dependencies = [ "libc", "neli", - "thiserror", - "windows-sys 0.48.0", + "thiserror 1.0.69", + "windows-sys 0.59.0", ] [[package]] @@ -5347,7 +5465,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.0", + "hashbrown 0.15.2", ] [[package]] @@ -5369,9 +5487,9 @@ checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" [[package]] name = "mac-notification-sys" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51fca4d74ff9dbaac16a01b924bc3693fa2bba0862c2c633abc73f9a8ea21f64" +checksum = "dce8f34f3717aa37177e723df6c1fc5fb02b2a1087374ea3fe0ea42316dc8f91" dependencies = [ "cc", "dirs-next", @@ -5435,6 +5553,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" dependencies = [ "cfg-if", + "rayon", ] [[package]] @@ -5461,9 +5580,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memmap2" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" dependencies = [ "libc", ] @@ -5488,27 +5607,27 @@ dependencies = [ [[package]] name = "miette" -version = "7.2.0" +version = "7.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4edc8853320c2a0dab800fbda86253c8938f6ea88510dc92c5f1ed20e794afc1" +checksum = "317f146e2eb7021892722af37cf1b971f0a70c8406f487e24952667616192c64" dependencies = [ "cfg-if", "miette-derive", "owo-colors", "textwrap 0.16.1", - "thiserror", + "thiserror 1.0.69", "unicode-width", ] [[package]] name = "miette-derive" -version = "7.2.0" +version = "7.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" +checksum = "23c9b935fbe1d6cbd1dac857b54a688145e2d93f48db36010514d0f612d0ad67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -5554,16 +5673,6 @@ dependencies = [ "scrypt", ] -[[package]] -name = "miniz_oxide" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" -dependencies = [ - "adler", - "simd-adler32", -] - [[package]] name = "miniz_oxide" version = "0.8.0" @@ -5571,6 +5680,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ "adler2", + "simd-adler32", ] [[package]] @@ -5599,9 +5709,9 @@ dependencies = [ [[package]] name = "muda" -version = "0.15.1" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8123dfd4996055ac9b15a60ad263b44b01e539007523ad7a4a533a3d93b0591" +checksum = "fdae9c00e61cc0579bcac625e8ad22104c60548a025bfc972dc83868a28e1484" dependencies = [ "crossbeam-channel", "dpi", @@ -5613,7 +5723,7 @@ dependencies = [ "once_cell", "png", "serde", - "thiserror", + "thiserror 1.0.69", "windows-sys 0.59.0", ] @@ -5646,7 +5756,7 @@ dependencies = [ "ndk-sys", "num_enum", "raw-window-handle", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -5780,9 +5890,9 @@ dependencies = [ [[package]] name = "notify-rust" -version = "4.11.1" +version = "4.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26a1d03b6305ecefdd9c6c60150179bb8d9f0cd4e64bbcad1e41419e7bf5e414" +checksum = "5134a72dc570b178bff81b01e81ab14a6fcc015391ed4b3b14853090658cd3a3" dependencies = [ "log", "mac-notification-sys", @@ -5862,7 +5972,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -5885,6 +5995,21 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-modular" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17bb261bf36fa7d83f4c294f834e91256769097b3cb505d44831e0a179ac647f" + +[[package]] +name = "num-order" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537b596b97c40fcf8056d153049eb22f481c17ebce72a513ec9286e4986d1bb6" +dependencies = [ + "num-modular", +] + [[package]] name = "num-rational" version = "0.4.2" @@ -5924,7 +6049,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -5943,7 +6068,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ "malloc_buf", - "objc_exception", ] [[package]] @@ -5962,6 +6086,9 @@ name = "objc-sys" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" +dependencies = [ + "cc", +] [[package]] name = "objc2" @@ -5989,6 +6116,30 @@ dependencies = [ "objc2-quartz-core", ] +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + [[package]] name = "objc2-core-data" version = "0.2.2" @@ -6013,6 +6164,18 @@ dependencies = [ "objc2-metal", ] +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2", + "objc2", + "objc2-contacts", + "objc2-foundation", +] + [[package]] name = "objc2-encode" version = "4.0.3" @@ -6032,6 +6195,18 @@ dependencies = [ "objc2", ] +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2", + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + [[package]] name = "objc2-metal" version = "0.2.2" @@ -6058,12 +6233,71 @@ dependencies = [ ] [[package]] -name = "objc_exception" -version = "0.1.2" +name = "objc2-symbols" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" dependencies = [ - "cc", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "objc2-web-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68bc69301064cebefc6c4c90ce9cba69225239e4b8ff99d445a2b5563797da65" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-app-kit", + "objc2-foundation", ] [[package]] @@ -6084,16 +6318,16 @@ dependencies = [ "crc32fast", "flate2", "hashbrown 0.14.5", - "indexmap 2.5.0", + "indexmap 2.6.0", "memchr", "ruzstd", ] [[package]] name = "object" -version = "0.36.4" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] @@ -6131,9 +6365,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "opaque-debug" @@ -6143,9 +6377,9 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "open" -version = "5.3.0" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a877bf6abd716642a53ef1b89fb498923a4afca5c754f9050b4d081c05c4b3" +checksum = "3ecd52f0b8d15c40ce4820aa251ed5de032e5d91fab27f7db2f40d42a8bdf69c" dependencies = [ "is-wsl", "libc", @@ -6154,9 +6388,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.66" +version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -6175,7 +6409,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -6186,9 +6420,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.103" +version = "0.9.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" dependencies = [ "cc", "libc", @@ -6260,15 +6494,15 @@ checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" [[package]] name = "owo-colors" -version = "4.0.0" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f" +checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56" [[package]] name = "oxc_allocator" -version = "0.24.3" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20afc1d4a7b0b878d28f7b7f9f1f7ab670a7c7e8feb29101fb49eac1faa483fa" +checksum = "466379b9ab2e05996bfedfae9c96753a633bb5a53aaf0898eb0e0ab09e169514" dependencies = [ "allocator-api2", "bumpalo", @@ -6276,9 +6510,9 @@ dependencies = [ [[package]] name = "oxc_ast" -version = "0.24.3" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1599f878d8ac6fcc229be06426f04134f7663dcd5c71046414d8ddd12a20ad3b" +checksum = "34bd4f56fe32adea489153f6d681d9ee01f0336b9b6a89f062611488d8f80797" dependencies = [ "bitflags 2.6.0", "num-bigint", @@ -6290,20 +6524,20 @@ dependencies = [ [[package]] name = "oxc_ast_macros" -version = "0.24.3" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a07c44bbe07756ba25605059fa4a94543f6a75730fd8bd1105795d0b3d668d" +checksum = "197b36739db0e80919e19a90785233eea5664697d4cd829bd49af34838ec43d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] name = "oxc_diagnostics" -version = "0.24.3" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c53d201660e8accd6e53f1af7efda36967316aa4263d0a6847c631bdd8705e0f" +checksum = "2cd4bb48b9527f5825c84acb688ec1485df4a5edadc17b3582626bb49736752b" dependencies = [ "miette", "owo-colors", @@ -6313,15 +6547,15 @@ dependencies = [ [[package]] name = "oxc_index" -version = "0.24.3" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa1d58c483b1ec74c7219b1525648b4b6beea7ff4685b02ad74693190df43308" +checksum = "bc9aa9446f6d2a64d0baa02fe20dc3d64e3e112083854b84fdacb82261be2b84" [[package]] name = "oxc_parser" -version = "0.24.3" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce38833b8b0d1121779b2ceaa9aa07d4142105ccc6941f73112a8d836b87cd" +checksum = "8f3432e80a58cfb38f9a138203e64d0f9a621d4c4e9d18e3e3bd870b51ce1f0e" dependencies = [ "assert-unchecked", "bitflags 2.6.0", @@ -6331,6 +6565,7 @@ dependencies = [ "oxc_allocator", "oxc_ast", "oxc_diagnostics", + "oxc_regular_expression", "oxc_span", "oxc_syntax", "rustc-hash", @@ -6338,10 +6573,24 @@ dependencies = [ ] [[package]] -name = "oxc_span" -version = "0.24.3" +name = "oxc_regular_expression" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a4f6e525a64e61bcfa256e4707e46be54f3261e0b524670d0a1c6827c28931" +checksum = "8fc6d05fec98ad6cc864ba8cfe7ece2e258106059a9a57e35b02450650b06979" +dependencies = [ + "oxc_allocator", + "oxc_diagnostics", + "oxc_span", + "phf 0.11.2", + "rustc-hash", + "unicode-id-start", +] + +[[package]] +name = "oxc_span" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a862a896ac3abd269863a19d4f77302b019458d90513705c7a017b138c8449b" dependencies = [ "compact_str", "miette", @@ -6351,9 +6600,9 @@ dependencies = [ [[package]] name = "oxc_syntax" -version = "0.24.3" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcb27d1a7e00a63e5d490fa4ee9a008d45e45db3d505ca21f0e63de2097cf743" +checksum = "d50c7ea034fb12f65376cfffc8ae4bfde3cda0a1e14407f82ffba1d26431703d" dependencies = [ "bitflags 2.6.0", "dashmap", @@ -6460,9 +6709,9 @@ dependencies = [ [[package]] name = "parking" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" @@ -6482,7 +6731,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.3", + "redox_syscall 0.5.7", "smallvec", "windows-targets 0.52.6", ] @@ -6518,9 +6767,9 @@ dependencies = [ [[package]] name = "pathdiff" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] name = "pbkdf2" @@ -6552,7 +6801,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -6582,20 +6831,20 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.11" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" +checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" dependencies = [ "memchr", - "thiserror", + "thiserror 1.0.69", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.11" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a" +checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" dependencies = [ "pest", "pest_generator", @@ -6603,22 +6852,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.11" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183" +checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] name = "pest_meta" -version = "2.7.11" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f" +checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" dependencies = [ "once_cell", "pest", @@ -6683,7 +6932,7 @@ dependencies = [ "sha3", "signature 2.2.0", "smallvec", - "thiserror", + "thiserror 1.0.69", "twofish", "x25519-dalek", "zeroize", @@ -6793,7 +7042,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -6831,29 +7080,29 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "pin-project" -version = "1.1.5" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -6868,7 +7117,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" dependencies = [ "atomic-waker", - "fastrand 2.1.1", + "fastrand 2.2.0", "futures-io", ] @@ -6905,9 +7154,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "plain" @@ -6922,7 +7171,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" dependencies = [ "base64 0.22.1", - "indexmap 2.5.0", + "indexmap 2.6.0", "quick-xml 0.32.0", "serde", "time", @@ -6930,15 +7179,15 @@ dependencies = [ [[package]] name = "png" -version = "0.17.13" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" dependencies = [ "bitflags 1.3.2", "crc32fast", "fdeflate", "flate2", - "miniz_oxide 0.7.4", + "miniz_oxide", ] [[package]] @@ -6959,15 +7208,15 @@ dependencies = [ [[package]] name = "polling" -version = "3.7.3" +version = "3.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" +checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi 0.4.0", "pin-project-lite", - "rustix 0.38.35", + "rustix 0.38.41", "tracing", "windows-sys 0.59.0", ] @@ -7008,8 +7257,8 @@ dependencies = [ "log", "notify-rust", "open", - "reqwest 0.12.7", - "rfd 0.14.1", + "reqwest 0.12.9", + "rfd", "rust-ini", "serde", "serde_json", @@ -7026,7 +7275,7 @@ dependencies = [ "tauri-plugin-single-instance", "tauri-plugin-window-state", "tauri-winrt-notification 0.3.1", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-websockets", "url", @@ -7091,7 +7340,7 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "toml_edit 0.22.20", + "toml_edit 0.22.22", ] [[package]] @@ -7126,9 +7375,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -7141,28 +7390,28 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", "version_check", "yansi", ] [[package]] name = "profiling" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" +checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" dependencies = [ "profiling-procmacros", ] [[package]] name = "profiling-procmacros" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" +checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" dependencies = [ "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -7391,22 +7640,23 @@ dependencies = [ "rand_chacha 0.3.1", "simd_helpers", "system-deps", - "thiserror", + "thiserror 1.0.69", "v_frame", "wasm-bindgen", ] [[package]] name = "ravif" -version = "0.11.10" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f0bfd976333248de2078d350bfdf182ff96e168a24d23d2436cef320dd4bdd" +checksum = "2413fd96bd0ea5cdeeb37eaf446a22e6ed7b981d792828721e74ded1980a45c6" dependencies = [ "avif-serialize", "imgref", "loop9", "quick-error", "rav1e", + "rayon", "rgb", ] @@ -7453,9 +7703,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_syscall" -version = "0.5.3" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] @@ -7479,14 +7729,14 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom 0.2.15", "libredox", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "regex" -version = "1.10.6" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -7496,9 +7746,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -7513,9 +7763,9 @@ checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rend" @@ -7540,7 +7790,7 @@ dependencies = [ "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.30", + "hyper 0.14.31", "hyper-rustls 0.24.2", "ipnet", "js-sys", @@ -7570,9 +7820,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.7" +version = "0.12.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" dependencies = [ "base64 0.22.1", "bytes", @@ -7580,11 +7830,11 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2 0.4.6", + "h2 0.4.7", "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.4.1", + "hyper 1.5.1", "hyper-rustls 0.27.3", "hyper-tls", "hyper-util", @@ -7596,11 +7846,11 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile 2.1.3", + "rustls-pemfile 2.2.0", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 1.0.1", + "sync_wrapper 1.0.2", "system-configuration 0.6.1", "tokio", "tokio-native-tls", @@ -7611,7 +7861,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "windows-registry", + "windows-registry 0.2.0", ] [[package]] @@ -7621,7 +7871,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7314563c59c7ce31c18e23ad3dd092c37b928a0fa4e1c0a1a6504351ab411d1" dependencies = [ "gif", - "image-webp", + "image-webp 0.1.3", "log", "pico-args", "rgb", @@ -7654,35 +7904,11 @@ dependencies = [ [[package]] name = "rfd" -version = "0.14.1" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25a73a7337fc24366edfca76ec521f51877b114e42dab584008209cca6719251" +checksum = "46f6f80a9b882647d9014673ca9925d30ffc9750f2eed2b4490e189eaebd01e8" dependencies = [ - "ashpd 0.8.1", - "block", - "dispatch", - "glib-sys", - "gobject-sys", - "gtk-sys", - "js-sys", - "log", - "objc", - "objc-foundation", - "objc_id", - "raw-window-handle", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "windows-sys 0.48.0", -] - -[[package]] -name = "rfd" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8af382a047821a08aa6bfc09ab0d80ff48d45d8726f7cd8e44891f7cb4a4278e" -dependencies = [ - "ashpd 0.9.1", + "ashpd 0.10.2", "block2", "glib-sys", "gobject-sys", @@ -7809,16 +8035,16 @@ dependencies = [ "pgp", "sha1", "sha2", - "thiserror", + "thiserror 1.0.69", "xz2", "zstd", ] [[package]] name = "rsa" -version = "0.9.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" dependencies = [ "const-oid", "digest", @@ -7928,9 +8154,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.35" +version = "0.38.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" dependencies = [ "bitflags 2.6.0", "errno", @@ -7960,22 +8186,22 @@ dependencies = [ "log", "ring", "rustls-pki-types", - "rustls-webpki 0.102.7", + "rustls-webpki 0.102.8", "subtle", "zeroize", ] [[package]] name = "rustls" -version = "0.23.12" +version = "0.23.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" dependencies = [ "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.102.7", + "rustls-webpki 0.102.8", "subtle", "zeroize", ] @@ -7999,7 +8225,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" dependencies = [ "openssl-probe", - "rustls-pemfile 2.1.3", + "rustls-pemfile 2.2.0", "rustls-pki-types", "schannel", "security-framework", @@ -8016,19 +8242,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64 0.22.1", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" [[package]] name = "rustls-webpki" @@ -8042,9 +8267,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.7" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84678086bd54edf2b415183ed7a94d0efb049f1b646a33e22a36f3794be6ae56" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", "rustls-pki-types", @@ -8053,9 +8278,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" [[package]] name = "rustybuzz" @@ -8112,11 +8337,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -8143,7 +8368,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -8175,7 +8400,7 @@ checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -8248,9 +8473,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.1" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" dependencies = [ "core-foundation-sys", "libc", @@ -8293,9 +8518,9 @@ checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" [[package]] name = "serde" -version = "1.0.209" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] @@ -8329,19 +8554,19 @@ checksum = "fb3aa78ecda1ebc9ec9847d5d3aba7d618823446a049ba2491940506da6e2782" dependencies = [ "log", "serde", - "thiserror", + "thiserror 1.0.69", "xml-rs", ] [[package]] name = "serde_derive" -version = "1.0.209" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -8352,7 +8577,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -8366,12 +8591,12 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ - "indexmap 2.5.0", - "itoa 1.0.11", + "indexmap 2.6.0", + "itoa 1.0.14", "memchr", "ryu", "serde", @@ -8383,7 +8608,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" dependencies = [ - "itoa 1.0.11", + "itoa 1.0.14", "serde", ] @@ -8395,14 +8620,14 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] name = "serde_spanned" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -8414,22 +8639,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.11", + "itoa 1.0.14", "ryu", "serde", ] [[package]] name = "serde_with" -version = "3.9.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" +checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.5.0", + "indexmap 2.6.0", "serde", "serde_derive", "serde_json", @@ -8439,14 +8664,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.9.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" +checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d" dependencies = [ "darling 0.20.10", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -8455,8 +8680,8 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.5.0", - "itoa 1.0.11", + "indexmap 2.6.0", + "itoa 1.0.14", "ryu", "serde", "unsafe-libyaml", @@ -8619,9 +8844,9 @@ dependencies = [ [[package]] name = "simdutf8" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "simple-file-manifest" @@ -8637,7 +8862,7 @@ checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" dependencies = [ "num-bigint", "num-traits", - "thiserror", + "thiserror 1.0.69", "time", ] @@ -8748,25 +8973,24 @@ dependencies = [ [[package]] name = "softbuffer" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d623bff5d06f60d738990980d782c8c866997d9194cfe79ecad00aa2f76826dd" +checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" dependencies = [ "bytemuck", "cfg_aliases", - "core-graphics 0.23.2", + "core-graphics 0.24.0", "foreign-types 0.5.0", "js-sys", "log", "objc2", - "objc2-app-kit", "objc2-foundation", "objc2-quartz-core", "raw-window-handle", - "redox_syscall 0.5.3", + "redox_syscall 0.5.7", "wasm-bindgen", "web-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -8828,9 +9052,6 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] [[package]] name = "spki" @@ -8943,15 +9164,15 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sval" -version = "2.13.0" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53eb957fbc79a55306d5d25d87daf3627bc3800681491cda0709eef36c748bfe" +checksum = "f6dc0f9830c49db20e73273ffae9b5240f63c42e515af1da1fceefb69fceafd8" [[package]] name = "sval_buffer" -version = "2.13.0" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96e860aef60e9cbf37888d4953a13445abf523c534640d1f6174d310917c410d" +checksum = "429922f7ad43c0ef8fd7309e14d750e38899e32eb7e8da656ea169dd28ee212f" dependencies = [ "sval", "sval_ref", @@ -8959,40 +9180,40 @@ dependencies = [ [[package]] name = "sval_dynamic" -version = "2.13.0" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea3f2b07929a1127d204ed7cb3905049381708245727680e9139dac317ed556f" +checksum = "68f16ff5d839396c11a30019b659b0976348f3803db0626f736764c473b50ff4" dependencies = [ "sval", ] [[package]] name = "sval_fmt" -version = "2.13.0" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4e188677497de274a1367c4bda15bd2296de4070d91729aac8f0a09c1abf64d" +checksum = "c01c27a80b6151b0557f9ccbe89c11db571dc5f68113690c1e028d7e974bae94" dependencies = [ - "itoa 1.0.11", + "itoa 1.0.14", "ryu", "sval", ] [[package]] name = "sval_json" -version = "2.13.0" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f456c07dae652744781f2245d5e3b78e6a9ebad70790ac11eb15dbdbce5282" +checksum = "0deef63c70da622b2a8069d8600cf4b05396459e665862e7bdb290fd6cf3f155" dependencies = [ - "itoa 1.0.11", + "itoa 1.0.14", "ryu", "sval", ] [[package]] name = "sval_nested" -version = "2.13.0" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "886feb24709f0476baaebbf9ac10671a50163caa7e439d7a7beb7f6d81d0a6fb" +checksum = "a39ce5976ae1feb814c35d290cf7cf8cd4f045782fe1548d6bc32e21f6156e9f" dependencies = [ "sval", "sval_buffer", @@ -9001,18 +9222,18 @@ dependencies = [ [[package]] name = "sval_ref" -version = "2.13.0" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be2e7fc517d778f44f8cb64140afa36010999565528d48985f55e64d45f369ce" +checksum = "bb7c6ee3751795a728bc9316a092023529ffea1783499afbc5c66f5fabebb1fa" dependencies = [ "sval", ] [[package]] name = "sval_serde" -version = "2.13.0" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79bf66549a997ff35cd2114a27ac4b0c2843280f2cfa84b240d169ecaa0add46" +checksum = "2a5572d0321b68109a343634e3a5d576bf131b82180c6c442dee06349dfc652a" dependencies = [ "serde", "sval", @@ -9053,27 +9274,15 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] -[[package]] -name = "syn_derive" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.77", -] - [[package]] name = "sync_wrapper" version = "0.1.2" @@ -9082,9 +9291,9 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "sync_wrapper" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] @@ -9102,10 +9311,21 @@ dependencies = [ ] [[package]] -name = "sys-locale" -version = "0.3.1" +name = "synstructure" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e801cf239ecd6ccd71f03d270d67dd53d13e90aab208bf4b8fe4ad957ea949b0" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + +[[package]] +name = "sys-locale" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" dependencies = [ "libc", ] @@ -9119,7 +9339,7 @@ dependencies = [ "bitflags 1.3.2", "byteorder", "libc", - "thiserror", + "thiserror 1.0.69", "walkdir", ] @@ -9180,9 +9400,9 @@ dependencies = [ [[package]] name = "tao" -version = "0.30.3" +version = "0.30.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0dbbebe82d02044dfa481adca1550d6dd7bd16e086bc34fa0fbecceb5a63751" +checksum = "6682a07cf5bab0b8a2bd20d0a542917ab928b5edb75ebd4eda6b05cbaab872da" dependencies = [ "bitflags 2.6.0", "cocoa", @@ -9225,7 +9445,7 @@ checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -9236,9 +9456,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tar" -version = "0.4.41" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb797dad5fb5b76fcf519e702f4a589483b5ef06567f160c392832c1f5e44909" +checksum = "c65998313f8e17d0d553d28f91a0df93e4dbbbf770279c7bc21ca0f09ea1a1f6" dependencies = [ "filetime", "libc", @@ -9253,9 +9473,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.0.2" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5920aad0804ea5e86808d4b6e8753d3bcbae7efc8f4e41a4da00b45427559868" +checksum = "e545de0a2dfe296fa67db208266cd397c5a55ae782da77973ef4c4fac90e9f2c" dependencies = [ "anyhow", "bytes", @@ -9280,7 +9500,7 @@ dependencies = [ "percent-encoding", "plist", "raw-window-handle", - "reqwest 0.12.7", + "reqwest 0.12.9", "serde", "serde_json", "serde_repr", @@ -9290,8 +9510,8 @@ dependencies = [ "tauri-macros", "tauri-runtime", "tauri-runtime-wry", - "tauri-utils 2.0.1", - "thiserror", + "tauri-utils 2.1.0", + "thiserror 2.0.3", "tokio", "tray-icon", "url", @@ -9304,21 +9524,21 @@ dependencies = [ [[package]] name = "tauri-build" -version = "2.0.1" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "935f9b3c49b22b3e2e485a57f46d61cd1ae07b1cbb2ba87387a387caf2d8c4e7" +checksum = "7bd2a4bcfaf5fb9f4be72520eefcb61ae565038f8ccba2a497d8c28f463b8c01" dependencies = [ "anyhow", "cargo_toml", "dirs 5.0.1", "glob", "heck 0.5.0", - "json-patch 2.0.0", + "json-patch 3.0.1", "schemars", "semver", "serde", "serde_json", - "tauri-utils 2.0.1", + "tauri-utils 2.1.0", "tauri-winres", "toml 0.8.19", "walkdir", @@ -9326,9 +9546,9 @@ dependencies = [ [[package]] name = "tauri-bundler" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc59028d72f54cd39aecd61c662fc0a879473962bedb3de98eb129f59ccb0207" +checksum = "a829580e402e515d2d1947f7c945c46cb8b843b0f80965d363aa6c62160e2663" dependencies = [ "anyhow", "ar", @@ -9356,31 +9576,31 @@ dependencies = [ "tar", "tauri-icns", "tauri-macos-sign", - "tauri-utils 2.0.1", + "tauri-utils 2.1.0", "tempfile", - "thiserror", + "thiserror 2.0.3", "time", "ureq", "url", "uuid", "walkdir", - "windows-registry", + "windows-registry 0.3.0", "windows-sys 0.59.0", - "zip 2.2.0", + "zip 2.2.1", ] [[package]] name = "tauri-cli" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c793c2f9430eb95040abac4ac78cd5a955c7aae13213a7d54e3e209c161c369" +checksum = "41bd283c8aba340b8db472647f43511768ad280fe49e3bd3e76b4117903e7590" dependencies = [ "anyhow", "ar", "axum", "base64 0.22.1", "cargo-mobile2", - "clap 4.5.17", + "clap 4.5.21", "clap_complete", "colored", "common-path", @@ -9399,7 +9619,7 @@ dependencies = [ "image", "include_dir", "itertools 0.13.0", - "json-patch 2.0.0", + "json-patch 3.0.1", "jsonrpsee", "jsonrpsee-client-transport", "jsonrpsee-core", @@ -9414,7 +9634,7 @@ dependencies = [ "minisign", "notify", "notify-debouncer-mini", - "object 0.36.4", + "object 0.36.5", "os_info", "os_pipe", "oxc_allocator", @@ -9436,11 +9656,11 @@ dependencies = [ "tauri-icns", "tauri-macos-sign", "tauri-utils 1.6.0", - "tauri-utils 2.0.1", + "tauri-utils 2.1.0", "tempfile", "tokio", "toml 0.8.19", - "toml_edit 0.22.20", + "toml_edit 0.22.22", "ureq", "url", "uuid", @@ -9450,14 +9670,14 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "2.0.1" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95d7443dd4f0b597704b6a14b964ee2ed16e99928d8e6292ae9825f09fbcd30e" +checksum = "bf79faeecf301d3e969b1fae977039edb77a4c1f25cc0a961be298b54bff97cf" dependencies = [ "base64 0.22.1", "brotli", "ico", - "json-patch 2.0.0", + "json-patch 3.0.1", "plist", "png", "proc-macro2", @@ -9466,9 +9686,9 @@ dependencies = [ "serde", "serde_json", "sha2", - "syn 2.0.77", - "tauri-utils 2.0.1", - "thiserror", + "syn 2.0.89", + "tauri-utils 2.1.0", + "thiserror 2.0.3", "time", "url", "uuid", @@ -9509,23 +9729,23 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.0.1" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d2c0963ccfc3f5194415f2cce7acc975942a8797fbabfb0aa1ed6f59326ae7f" +checksum = "c52027c8c5afb83166dacddc092ee8fff50772f9646d461d8c33ee887e447a03" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", "tauri-codegen", - "tauri-utils 2.0.1", + "tauri-utils 2.1.0", ] [[package]] name = "tauri-plugin" -version = "2.0.1" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e6660a409963e4d57b9bfab4addd141eeff41bd3a7fb14e13004a832cf7ef6" +checksum = "e753f2a30933a9bbf0a202fa47d7cc4a3401f06e8d6dcc53b79aa62954828c79" dependencies = [ "anyhow", "glob", @@ -9533,50 +9753,49 @@ dependencies = [ "schemars", "serde", "serde_json", - "tauri-utils 2.0.1", + "tauri-utils 2.1.0", "toml 0.8.19", "walkdir", ] [[package]] name = "tauri-plugin-clipboard-manager" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b7d556886c15849198c0948fd7f4c880492f0461539176da0a8a70272e2904" +checksum = "2a66feaa0fb7fce8e5073323d11ca381c9da7ac06f458e42b9ff77364b76a360" dependencies = [ "arboard", - "image", "log", "serde", "serde_json", "tauri", "tauri-plugin", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "tauri-plugin-dialog" -version = "2.0.1" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddb2fe88b602461c118722c574e2775ab26a4e68886680583874b2f6520608b7" +checksum = "4307310e1d2c09ab110235834722e7c2b85099b683e1eb7342ab351b0be5ada3" dependencies = [ "log", "raw-window-handle", - "rfd 0.15.0", + "rfd", "serde", "serde_json", "tauri", "tauri-plugin", "tauri-plugin-fs", - "thiserror", + "thiserror 1.0.69", "url", ] [[package]] name = "tauri-plugin-fs" -version = "2.0.1" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab300488ebec3487ca5f56289692e7e45feb07eea8d5e1dba497f7dc9dd9c407" +checksum = "96ba7d46e86db8c830d143ef90ab5a453328365b0cc834c24edea4267b16aba0" dependencies = [ "anyhow", "dunce", @@ -9588,16 +9807,16 @@ dependencies = [ "serde_repr", "tauri", "tauri-plugin", - "thiserror", + "thiserror 1.0.69", "url", "uuid", ] [[package]] name = "tauri-plugin-log" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a49f2c05d15e6375ab7f7e528b3049150ba4dfafdf61f85e5178d0aef18e3f5" +checksum = "8aa13d15daf90230ba26d5a9b4a4612975fa64ce17290cb7f6e0f89bb6997d82" dependencies = [ "android_logger", "byte-unit", @@ -9611,7 +9830,7 @@ dependencies = [ "swift-rs", "tauri", "tauri-plugin", - "thiserror", + "thiserror 1.0.69", "time", ] @@ -9629,7 +9848,7 @@ dependencies = [ "serde_repr", "tauri", "tauri-plugin", - "thiserror", + "thiserror 1.0.69", "time", "url", ] @@ -9649,14 +9868,14 @@ dependencies = [ "sys-locale", "tauri", "tauri-plugin", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "tauri-plugin-shell" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "371fb9aca2823990a2d0db7970573be5fdf07881fcaa2b835b29631feb84aec1" +checksum = "0ad7880c5586b6b2104be451e3d7fc0f3800c84bda69e9ba81c828f87cb34267" dependencies = [ "encoding_rs", "log", @@ -9669,7 +9888,7 @@ dependencies = [ "shared_child", "tauri", "tauri-plugin", - "thiserror", + "thiserror 1.0.69", "tokio", ] @@ -9683,16 +9902,16 @@ dependencies = [ "serde", "serde_json", "tauri", - "thiserror", + "thiserror 1.0.69", "windows-sys 0.59.0", "zbus 4.4.0", ] [[package]] name = "tauri-plugin-window-state" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd1cef203a15b4772898e7bc8e57c1f34696e39848987dfcd294d51ba0525650" +checksum = "683c8764751fbbcebf3a594bcee24cf84c62773fa0080d1b40fc80698472421e" dependencies = [ "bitflags 2.6.0", "log", @@ -9700,14 +9919,14 @@ dependencies = [ "serde_json", "tauri", "tauri-plugin", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "tauri-runtime" -version = "2.0.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af12ad1af974b274ef1d32a94e6eba27a312b429ef28fcb98abc710df7f9151d" +checksum = "cce18d43f80d4aba3aa8a0c953bbe835f3d0f2370aca75e8dbb14bd4bab27958" dependencies = [ "dpi", "gtk", @@ -9716,17 +9935,17 @@ dependencies = [ "raw-window-handle", "serde", "serde_json", - "tauri-utils 2.0.1", - "thiserror", + "tauri-utils 2.1.0", + "thiserror 2.0.3", "url", "windows 0.58.0", ] [[package]] name = "tauri-runtime-wry" -version = "2.0.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e45e88aa0b11b302d836e6ea3e507a6359044c4a8bc86b865ba99868c695753d" +checksum = "9f442a38863e10129ffe2cec7bd09c2dcf8a098a3a27801a476a304d5bb991d2" dependencies = [ "gtk", "http 1.1.0", @@ -9740,7 +9959,7 @@ dependencies = [ "softbuffer", "tao", "tauri-runtime", - "tauri-utils 2.0.1", + "tauri-utils 2.1.0", "url", "webkit2gtk", "webview2-com", @@ -9773,7 +9992,7 @@ dependencies = [ "serde_json", "serde_with", "serialize-to-javascript", - "thiserror", + "thiserror 1.0.69", "toml 0.7.8", "url", "windows-version", @@ -9781,9 +10000,9 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c38b0230d6880cf6dd07b6d7dd7789a0869f98ac12146e0d18d1c1049215a045" +checksum = "9271a88f99b4adea0dc71d0baca4505475a0bbd139fb135f62958721aaa8fe54" dependencies = [ "aes-gcm", "brotli", @@ -9793,8 +10012,9 @@ dependencies = [ "getrandom 0.2.15", "glob", "html5ever", + "http 1.1.0", "infer 0.16.0", - "json-patch 2.0.0", + "json-patch 3.0.1", "json5", "kuchikiki", "log", @@ -9811,7 +10031,7 @@ dependencies = [ "serde_with", "serialize-to-javascript", "swift-rs", - "thiserror", + "thiserror 2.0.3", "toml 0.8.19", "url", "urlpattern", @@ -9853,14 +10073,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", - "fastrand 2.1.1", + "fastrand 2.2.0", "once_cell", - "rustix 0.38.35", + "rustix 0.38.41", "windows-sys 0.59.0", ] @@ -9923,22 +10143,42 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +dependencies = [ + "thiserror-impl 2.0.3", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", ] [[package]] @@ -9959,7 +10199,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", - "itoa 1.0.11", + "itoa 1.0.14", "libc", "num-conv", "num_threads", @@ -10020,6 +10260,16 @@ dependencies = [ "strict-num", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -10037,9 +10287,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.40.0" +version = "1.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" dependencies = [ "backtrace", "bytes", @@ -10061,7 +10311,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -10101,16 +10351,16 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.12", + "rustls 0.23.19", "rustls-pki-types", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" dependencies = [ "futures-core", "pin-project-lite", @@ -10120,14 +10370,14 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.21.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" dependencies = [ "futures-util", "log", "tokio", - "tungstenite", + "tungstenite 0.24.0", ] [[package]] @@ -10182,11 +10432,11 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.6.0", "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.20", + "toml_edit 0.22.22", ] [[package]] @@ -10204,7 +10454,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.6.0", "serde", "serde_spanned", "toml_datetime", @@ -10217,22 +10467,22 @@ version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.6.0", "toml_datetime", "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.22.20" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.6.0", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.18", + "winnow 0.6.20", ] [[package]] @@ -10245,6 +10495,21 @@ dependencies = [ "futures-util", "pin-project", "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 0.1.2", "tokio", "tower-layer", "tower-service", @@ -10265,9 +10530,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -10277,29 +10542,29 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", ] [[package]] name = "tray-icon" -version = "0.19.0" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533fc2d4105e0e3d96ce1c71f2d308c9fbbe2ef9c587cab63dd627ab5bde218f" +checksum = "d48a05076dd272615d03033bf04f480199f7d1b66a8ac64d75c625fc4a70c06b" dependencies = [ "core-graphics 0.24.0", "crossbeam-channel", @@ -10312,7 +10577,7 @@ dependencies = [ "once_cell", "png", "serde", - "thiserror", + "thiserror 1.0.69", "windows-sys 0.59.0", ] @@ -10348,11 +10613,29 @@ dependencies = [ "rustls-native-certs 0.7.3", "rustls-pki-types", "sha1", - "thiserror", + "thiserror 1.0.69", "url", "utf-8", ] +[[package]] +name = "tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.1.0", + "httparse", + "log", + "rand 0.8.5", + "sha1", + "thiserror 1.0.69", + "utf-8", +] + [[package]] name = "twofish" version = "0.7.1" @@ -10386,9 +10669,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "typewit" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fb9ae6a3cafaf0a5d14c2302ca525f9ae8e07a0f0e6949de88d882c37a6e24" +checksum = "d51dbd25812f740f45e2a9769f84711982e000483b13b73a8a1852e092abac8c" dependencies = [ "typewit_proc_macros", ] @@ -10401,9 +10684,9 @@ checksum = "e36a83ea2b3c704935a01b4642946aadd445cea40b10935e3f8bd8052b8193d6" [[package]] name = "ucd-trie" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "uds_windows" @@ -10477,9 +10760,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-bidi-mirroring" @@ -10495,15 +10778,15 @@ checksum = "260bc6647b3893a9a90668360803a15f96b85a5257b1c3a0c3daf6ae2496de42" [[package]] name = "unicode-id-start" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc3882f69607a2ac8cc4de3ee7993d8f68bb06f2974271195065b3bd07f2edea" +checksum = "2f322b60f6b9736017344fa0635d64be2f458fbc04eef65f6be22976dd1ffd5b" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-linebreak" @@ -10511,32 +10794,23 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" -[[package]] -name = "unicode-normalization" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" -dependencies = [ - "tinyvec", -] - [[package]] name = "unicode-properties" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" [[package]] name = "unicode-script" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8d71f5726e5f285a935e9fe8edfd53f0491eb6e9a5774097fdabee7cd8c9cd" +checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f" [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-vo" @@ -10546,9 +10820,9 @@ checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-xid" @@ -10588,18 +10862,18 @@ dependencies = [ "flate2", "log", "once_cell", - "rustls 0.23.12", + "rustls 0.23.19", "rustls-pki-types", "socks", "url", - "webpki-roots 0.26.5", + "webpki-roots 0.26.7", ] [[package]] name = "url" -version = "2.5.2" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -10658,12 +10932,24 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + [[package]] name = "utf8-width" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -10672,9 +10958,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ "getrandom 0.2.15", "serde", @@ -10694,9 +10980,9 @@ dependencies = [ [[package]] name = "value-bag" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" +checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2" dependencies = [ "value-bag-serde1", "value-bag-sval2", @@ -10704,9 +10990,9 @@ dependencies = [ [[package]] name = "value-bag-serde1" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccacf50c5cb077a9abb723c5bcb5e0754c1a433f1e1de89edc328e2760b6328b" +checksum = "4bb773bd36fd59c7ca6e336c94454d9c66386416734817927ac93d81cb3c5b0b" dependencies = [ "erased-serde", "serde", @@ -10715,9 +11001,9 @@ dependencies = [ [[package]] name = "value-bag-sval2" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1785bae486022dfb9703915d42287dcb284c1ee37bd1080eeba78cc04721285b" +checksum = "53a916a702cac43a88694c97657d449775667bcd14b70419441d05b7fea4a83a" dependencies = [ "sval", "sval_buffer", @@ -10823,9 +11109,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", "once_cell", @@ -10834,24 +11120,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.43" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" dependencies = [ "cfg-if", "js-sys", @@ -10861,9 +11147,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -10871,28 +11157,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "wasm-streams" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" dependencies = [ "futures-util", "js-sys", @@ -10909,7 +11195,7 @@ checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6" dependencies = [ "cc", "downcast-rs", - "rustix 0.38.35", + "rustix 0.38.41", "scoped-tls", "smallvec", "wayland-sys", @@ -10917,21 +11203,21 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.6" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3f45d1222915ef1fd2057220c1d9d9624b7654443ea35c3877f7a52bd0a5a2d" +checksum = "b66249d3fc69f76fd74c82cc319300faa554e9d865dab1f7cd66cc20db10b280" dependencies = [ "bitflags 2.6.0", - "rustix 0.38.35", + "rustix 0.38.41", "wayland-backend", "wayland-scanner", ] [[package]] name = "wayland-protocols" -version = "0.32.4" +version = "0.32.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b5755d77ae9040bb872a25026555ce4cb0ae75fd923e90d25fba07d81057de0" +checksum = "7cd0ade57c4e6e9a8952741325c30bf82f4246885dca8bf561898b86d0c1f58e" dependencies = [ "bitflags 2.6.0", "wayland-backend", @@ -10963,9 +11249,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" dependencies = [ "js-sys", "wasm-bindgen", @@ -11023,9 +11309,9 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webpki-roots" -version = "0.26.5" +version = "0.26.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a" +checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" dependencies = [ "rustls-pki-types", ] @@ -11052,7 +11338,7 @@ checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -11061,7 +11347,7 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3a3e2eeb58f82361c93f9777014668eb3d07e7d174ee4c819575a9208011886" dependencies = [ - "thiserror", + "thiserror 1.0.69", "windows 0.58.0", "windows-core 0.58.0", ] @@ -11080,7 +11366,7 @@ checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" dependencies = [ "either", "home", - "rustix 0.38.35", + "rustix 0.38.41", "winsafe", ] @@ -11123,12 +11409,13 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "window-vibrancy" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8cdd6999298d969289d8078dae02ce798ad23452075985cccba8b6326711ecf" +checksum = "3ea403deff7b51fff19e261330f71608ff2cdef5721d72b64180bb95be7c4150" dependencies = [ - "cocoa", - "objc", + "objc2", + "objc2-app-kit", + "objc2-foundation", "raw-window-handle", "windows-sys 0.59.0", "windows-version", @@ -11204,7 +11491,7 @@ dependencies = [ "windows-implement 0.58.0", "windows-interface 0.58.0", "windows-result 0.2.0", - "windows-strings", + "windows-strings 0.1.0", "windows-targets 0.52.6", ] @@ -11216,7 +11503,7 @@ checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -11227,7 +11514,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -11238,7 +11525,7 @@ checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -11249,7 +11536,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -11259,7 +11546,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" dependencies = [ "windows-result 0.2.0", - "windows-strings", + "windows-strings 0.1.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-registry" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bafa604f2104cf5ae2cc2db1dee84b7e6a5d11b05f737b60def0ffdc398cbc0a" +dependencies = [ + "windows-result 0.2.0", + "windows-strings 0.2.0", "windows-targets 0.52.6", ] @@ -11302,6 +11600,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-strings" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978d65aedf914c664c510d9de43c8fd85ca745eaff1ed53edf409b479e441663" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -11536,9 +11843,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] @@ -11570,15 +11877,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" [[package]] -name = "wry" -version = "0.44.1" +name = "write16" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "440600584cfbd8b0d28eace95c1f2c253db05dae43780b79380aa1e868f04c73" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "wry" +version = "0.47.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ce51277d65170f6379d8cda935c80e3c2d1f0ff712a123c8bddb11b31a4b73" dependencies = [ "base64 0.22.1", - "block", - "cocoa", - "core-graphics 0.24.0", + "block2", + "cookie", "crossbeam-channel", "dpi", "dunce", @@ -11591,15 +11909,19 @@ dependencies = [ "kuchikiki", "libc", "ndk", - "objc", - "objc_id", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", "once_cell", "percent-encoding", "raw-window-handle", "sha2", "soup3", "tao-macros", - "thiserror", + "thiserror 1.0.69", + "url", "webkit2gtk", "webkit2gtk-sys", "webview2-com", @@ -11646,7 +11968,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" dependencies = [ "gethostname 0.4.3", - "rustix 0.38.35", + "rustix 0.38.41", "x11rb-protocol", ] @@ -11693,7 +12015,7 @@ dependencies = [ "ring", "signature 2.2.0", "spki 0.7.3", - "thiserror", + "thiserror 1.0.69", "zeroize", ] @@ -11705,7 +12027,7 @@ checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" dependencies = [ "libc", "linux-raw-sys 0.4.14", - "rustix 0.38.35", + "rustix 0.38.41", ] [[package]] @@ -11726,9 +12048,9 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.22" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af4e2e2f7cba5a093896c1e150fbfe177d1883e7448200efb81d40b9d339ef26" +checksum = "af310deaae937e48a26602b730250b4949e125f468f11e6990be3e5304ddd96f" [[package]] name = "xmlparser" @@ -11763,6 +12085,30 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", + "synstructure 0.13.1", +] + [[package]] name = "zbus" version = "3.15.2" @@ -11813,9 +12159,9 @@ dependencies = [ "async-broadcast 0.7.1", "async-executor", "async-fs 2.1.2", - "async-io 2.3.4", + "async-io 2.4.0", "async-lock 3.4.0", - "async-process 2.2.4", + "async-process 2.3.0", "async-recursion", "async-task", "async-trait", @@ -11833,7 +12179,6 @@ dependencies = [ "serde_repr", "sha1", "static_assertions", - "tokio", "tracing", "uds_windows", "windows-sys 0.52.0", @@ -11843,6 +12188,36 @@ dependencies = [ "zvariant 4.2.0", ] +[[package]] +name = "zbus" +version = "5.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1162094dc63b1629fcc44150bcceeaa80798cd28bcbe7fa987b65a034c258608" +dependencies = [ + "async-broadcast 0.7.1", + "async-recursion", + "async-trait", + "enumflags2", + "event-listener 5.3.1", + "futures-core", + "futures-util", + "hex", + "nix 0.29.0", + "ordered-stream", + "serde", + "serde_repr", + "static_assertions", + "tokio", + "tracing", + "uds_windows", + "windows-sys 0.59.0", + "winnow 0.6.20", + "xdg-home", + "zbus_macros 5.1.1", + "zbus_names 4.1.0", + "zvariant 5.1.0", +] + [[package]] name = "zbus_macros" version = "3.15.2" @@ -11866,10 +12241,25 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", "zvariant_utils 2.1.0", ] +[[package]] +name = "zbus_macros" +version = "5.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cd2dcdce3e2727f7d74b7e33b5a89539b3cc31049562137faf7ae4eb86cd16d" +dependencies = [ + "proc-macro-crate 3.2.0", + "proc-macro2", + "quote", + "syn 2.0.89", + "zbus_names 4.1.0", + "zvariant 5.1.0", + "zvariant_utils 3.0.2", +] + [[package]] name = "zbus_names" version = "2.6.1" @@ -11892,6 +12282,18 @@ dependencies = [ "zvariant 4.2.0", ] +[[package]] +name = "zbus_names" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "856b7a38811f71846fd47856ceee8bccaec8399ff53fb370247e66081ace647b" +dependencies = [ + "serde", + "static_assertions", + "winnow 0.6.20", + "zvariant 5.1.0", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -11910,7 +12312,28 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", + "synstructure 0.13.1", ] [[package]] @@ -11930,7 +12353,29 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", ] [[package]] @@ -11947,18 +12392,18 @@ dependencies = [ [[package]] name = "zip" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc5e4288ea4057ae23afc69a4472434a87a2495cafce6632fd1c4ec9f5cf3494" +checksum = "99d52293fc86ea7cf13971b3bb81eb21683636e7ae24c729cdaf1b7c4157a352" dependencies = [ "arbitrary", "crc32fast", "crossbeam-utils", "displaydoc", "flate2", - "indexmap 2.5.0", + "indexmap 2.6.0", "memchr", - "thiserror", + "thiserror 2.0.3", "zopfli", ] @@ -11970,7 +12415,7 @@ checksum = "ce824a6bfffe8942820fa36d24973b7c83a40896749a42e33de0abdd11750ee5" dependencies = [ "byteorder", "bytesize", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -12064,10 +12509,25 @@ dependencies = [ "enumflags2", "serde", "static_assertions", - "url", "zvariant_derive 4.2.0", ] +[[package]] +name = "zvariant" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1200ee6ac32f1e5a312e455a949a4794855515d34f9909f4a3e082d14e1a56f" +dependencies = [ + "endi", + "enumflags2", + "serde", + "static_assertions", + "url", + "winnow 0.6.20", + "zvariant_derive 5.1.0", + "zvariant_utils 3.0.2", +] + [[package]] name = "zvariant_derive" version = "3.15.2" @@ -12090,10 +12550,23 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", "zvariant_utils 2.1.0", ] +[[package]] +name = "zvariant_derive" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "687e3b97fae6c9104fbbd36c73d27d149abf04fb874e2efbd84838763daa8916" +dependencies = [ + "proc-macro-crate 3.2.0", + "proc-macro2", + "quote", + "syn 2.0.89", + "zvariant_utils 3.0.2", +] + [[package]] name = "zvariant_utils" version = "1.0.1" @@ -12113,5 +12586,19 @@ checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", +] + +[[package]] +name = "zvariant_utils" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20d1d011a38f12360e5fcccceeff5e2c42a8eb7f27f0dcba97a0862ede05c9c6" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "static_assertions", + "syn 2.0.89", + "winnow 0.6.20", ] diff --git a/desktop/tauri/src-tauri/Cargo.toml b/desktop/tauri/src-tauri/Cargo.toml index 78f87926..c30303c5 100644 --- a/desktop/tauri/src-tauri/Cargo.toml +++ b/desktop/tauri/src-tauri/Cargo.toml @@ -12,21 +12,21 @@ rust-version = "1.64" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [build-dependencies] -tauri-build = { version = "2.0.1", features = [] } +tauri-build = { version = "2.0.3", features = [] } [dependencies] # Tauri -tauri = { version = "2.0.1", features = ["tray-icon", "image-png", "config-json5", "devtools"] } -tauri-plugin-shell = "2.0.1" -tauri-plugin-dialog = "2.0.1" -tauri-plugin-clipboard-manager = "2.0.1" +tauri = { version = "2.1.1", features = ["tray-icon", "image-png", "config-json5", "devtools"] } +tauri-plugin-shell = "2.0.2" +tauri-plugin-dialog = "2.0.3" +tauri-plugin-clipboard-manager = "2.0.2" tauri-plugin-os = "2.0.1" tauri-plugin-single-instance = "2.0.1" tauri-plugin-notification = "2.0.1" -tauri-plugin-log = "2.0.1" -tauri-plugin-window-state = "2.0.1" +tauri-plugin-log = "2.0.2" +tauri-plugin-window-state = "2.0.2" -tauri-cli = "2.0.1" +tauri-cli = "2.1.0" clap_lex = "0.7.2" # General @@ -82,4 +82,4 @@ ctor = "0.2.6" custom-protocol = [ "tauri/custom-protocol" ] [package.metadata.clippy] -allow = ["clippy::collapsible_else_if"] \ No newline at end of file +allow = ["clippy::collapsible_else_if"] diff --git a/desktop/tauri/src-tauri/gen/schemas/desktop-schema.json b/desktop/tauri/src-tauri/gen/schemas/desktop-schema.json index 10fb08fb..905008b7 100644 --- a/desktop/tauri/src-tauri/gen/schemas/desktop-schema.json +++ b/desktop/tauri/src-tauri/gen/schemas/desktop-schema.json @@ -37,7 +37,7 @@ ], "definitions": { "Capability": { - "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, \"platforms\": [\"macOS\",\"windows\"] } ```", + "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", "type": "object", "required": [ "identifier", @@ -84,7 +84,7 @@ } }, "permissions": { - "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ```", + "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", "type": "array", "items": { "$ref": "#/definitions/PermissionEntry" diff --git a/desktop/tauri/src-tauri/gen/schemas/linux-schema.json b/desktop/tauri/src-tauri/gen/schemas/linux-schema.json index 10fb08fb..905008b7 100644 --- a/desktop/tauri/src-tauri/gen/schemas/linux-schema.json +++ b/desktop/tauri/src-tauri/gen/schemas/linux-schema.json @@ -37,7 +37,7 @@ ], "definitions": { "Capability": { - "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, \"platforms\": [\"macOS\",\"windows\"] } ```", + "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", "type": "object", "required": [ "identifier", @@ -84,7 +84,7 @@ } }, "permissions": { - "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ```", + "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", "type": "array", "items": { "$ref": "#/definitions/PermissionEntry" diff --git a/desktop/tauri/src-tauri/gen/schemas/windows-schema.json b/desktop/tauri/src-tauri/gen/schemas/windows-schema.json index 797ccb5c..905008b7 100644 --- a/desktop/tauri/src-tauri/gen/schemas/windows-schema.json +++ b/desktop/tauri/src-tauri/gen/schemas/windows-schema.json @@ -37,7 +37,7 @@ ], "definitions": { "Capability": { - "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, \"platforms\": [\"macOS\",\"windows\"] } ```", + "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", "type": "object", "required": [ "identifier", @@ -84,7 +84,7 @@ } }, "permissions": { - "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ```", + "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", "type": "array", "items": { "$ref": "#/definitions/PermissionEntry" @@ -133,2803 +133,2202 @@ { "description": "Reference a permission or permission set by identifier and extends its scope.", "type": "object", - "oneOf": [ + "allOf": [ { - "type": "object", - "required": [ - "identifier" - ], + "if": { + "properties": { + "identifier": { + "anyOf": [ + { + "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality without any specific\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n", + "type": "string", + "const": "shell:default" + }, + { + "description": "Enables the execute command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-execute" + }, + { + "description": "Enables the kill command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-kill" + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-open" + }, + { + "description": "Enables the spawn command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-spawn" + }, + { + "description": "Enables the stdin_write command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-stdin-write" + }, + { + "description": "Denies the execute command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-execute" + }, + { + "description": "Denies the kill command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-kill" + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-open" + }, + { + "description": "Denies the spawn command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-spawn" + }, + { + "description": "Denies the stdin_write command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-stdin-write" + } + ] + } + } + }, + "then": { + "properties": { + "allow": { + "items": { + "title": "ShellScopeEntry", + "description": "Shell scope entry.", + "anyOf": [ + { + "type": "object", + "required": [ + "cmd", + "name" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "cmd": { + "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "name", + "sidecar" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + }, + "sidecar": { + "description": "If this command is a sidecar command.", + "type": "boolean" + } + }, + "additionalProperties": false + } + ] + } + }, + "deny": { + "items": { + "title": "ShellScopeEntry", + "description": "Shell scope entry.", + "anyOf": [ + { + "type": "object", + "required": [ + "cmd", + "name" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "cmd": { + "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "name", + "sidecar" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + }, + "sidecar": { + "description": "If this command is a sidecar command.", + "type": "boolean" + } + }, + "additionalProperties": false + } + ] + } + } + } + }, "properties": { "identifier": { - "oneOf": [ + "description": "Identifier of the permission or permission set.", + "allOf": [ { - "description": "shell:default -> This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality without any specific\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n", - "type": "string", - "enum": [ - "shell:default" - ] - }, + "$ref": "#/definitions/Identifier" + } + ] + } + } + }, + { + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ { - "description": "shell:allow-execute -> Enables the execute command without any pre-configured scope.", - "type": "string", - "enum": [ - "shell:allow-execute" - ] - }, - { - "description": "shell:allow-kill -> Enables the kill command without any pre-configured scope.", - "type": "string", - "enum": [ - "shell:allow-kill" - ] - }, - { - "description": "shell:allow-open -> Enables the open command without any pre-configured scope.", - "type": "string", - "enum": [ - "shell:allow-open" - ] - }, - { - "description": "shell:allow-spawn -> Enables the spawn command without any pre-configured scope.", - "type": "string", - "enum": [ - "shell:allow-spawn" - ] - }, - { - "description": "shell:allow-stdin-write -> Enables the stdin_write command without any pre-configured scope.", - "type": "string", - "enum": [ - "shell:allow-stdin-write" - ] - }, - { - "description": "shell:deny-execute -> Denies the execute command without any pre-configured scope.", - "type": "string", - "enum": [ - "shell:deny-execute" - ] - }, - { - "description": "shell:deny-kill -> Denies the kill command without any pre-configured scope.", - "type": "string", - "enum": [ - "shell:deny-kill" - ] - }, - { - "description": "shell:deny-open -> Denies the open command without any pre-configured scope.", - "type": "string", - "enum": [ - "shell:deny-open" - ] - }, - { - "description": "shell:deny-spawn -> Denies the spawn command without any pre-configured scope.", - "type": "string", - "enum": [ - "shell:deny-spawn" - ] - }, - { - "description": "shell:deny-stdin-write -> Denies the stdin_write command without any pre-configured scope.", - "type": "string", - "enum": [ - "shell:deny-stdin-write" - ] + "$ref": "#/definitions/Identifier" } ] }, "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], "items": { - "title": "Entry", - "description": "A command allowed to be executed by the webview API.", - "type": "object", - "required": [ - "args", - "cmd", - "name", - "sidecar" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellAllowedArgs" - } - ] - }, - "cmd": { - "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - }, - "sidecar": { - "description": "If this command is a sidecar command.", - "type": "boolean" - } - } + "$ref": "#/definitions/Value" } }, "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], "items": { - "title": "Entry", - "description": "A command allowed to be executed by the webview API.", - "type": "object", - "required": [ - "args", - "cmd", - "name", - "sidecar" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellAllowedArgs" - } - ] - }, - "cmd": { - "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - }, - "sidecar": { - "description": "If this command is a sidecar command.", - "type": "boolean" - } - } + "$ref": "#/definitions/Value" } } } } + ], + "required": [ + "identifier" ] } ] }, "Identifier": { + "description": "Permission identifier", "oneOf": [ { - "description": "clipboard-manager:default -> No features are enabled by default, as we believe\nthe clipboard can be inherently dangerous and it is \napplication specific if read and/or write access is needed.\n\nClipboard interaction needs to be explicitly enabled.\n", + "description": "No features are enabled by default, as we believe\nthe clipboard can be inherently dangerous and it is \napplication specific if read and/or write access is needed.\n\nClipboard interaction needs to be explicitly enabled.\n", "type": "string", - "enum": [ - "clipboard-manager:default" - ] + "const": "clipboard-manager:default" }, { - "description": "clipboard-manager:allow-clear -> Enables the clear command without any pre-configured scope.", + "description": "Enables the clear command without any pre-configured scope.", "type": "string", - "enum": [ - "clipboard-manager:allow-clear" - ] + "const": "clipboard-manager:allow-clear" }, { - "description": "clipboard-manager:allow-read-image -> Enables the read_image command without any pre-configured scope.", + "description": "Enables the read_image command without any pre-configured scope.", "type": "string", - "enum": [ - "clipboard-manager:allow-read-image" - ] + "const": "clipboard-manager:allow-read-image" }, { - "description": "clipboard-manager:allow-read-text -> Enables the read_text command without any pre-configured scope.", + "description": "Enables the read_text command without any pre-configured scope.", "type": "string", - "enum": [ - "clipboard-manager:allow-read-text" - ] + "const": "clipboard-manager:allow-read-text" }, { - "description": "clipboard-manager:allow-write-html -> Enables the write_html command without any pre-configured scope.", + "description": "Enables the write_html command without any pre-configured scope.", "type": "string", - "enum": [ - "clipboard-manager:allow-write-html" - ] + "const": "clipboard-manager:allow-write-html" }, { - "description": "clipboard-manager:allow-write-image -> Enables the write_image command without any pre-configured scope.", + "description": "Enables the write_image command without any pre-configured scope.", "type": "string", - "enum": [ - "clipboard-manager:allow-write-image" - ] + "const": "clipboard-manager:allow-write-image" }, { - "description": "clipboard-manager:allow-write-text -> Enables the write_text command without any pre-configured scope.", + "description": "Enables the write_text command without any pre-configured scope.", "type": "string", - "enum": [ - "clipboard-manager:allow-write-text" - ] + "const": "clipboard-manager:allow-write-text" }, { - "description": "clipboard-manager:deny-clear -> Denies the clear command without any pre-configured scope.", + "description": "Denies the clear command without any pre-configured scope.", "type": "string", - "enum": [ - "clipboard-manager:deny-clear" - ] + "const": "clipboard-manager:deny-clear" }, { - "description": "clipboard-manager:deny-read-image -> Denies the read_image command without any pre-configured scope.", + "description": "Denies the read_image command without any pre-configured scope.", "type": "string", - "enum": [ - "clipboard-manager:deny-read-image" - ] + "const": "clipboard-manager:deny-read-image" }, { - "description": "clipboard-manager:deny-read-text -> Denies the read_text command without any pre-configured scope.", + "description": "Denies the read_text command without any pre-configured scope.", "type": "string", - "enum": [ - "clipboard-manager:deny-read-text" - ] + "const": "clipboard-manager:deny-read-text" }, { - "description": "clipboard-manager:deny-write-html -> Denies the write_html command without any pre-configured scope.", + "description": "Denies the write_html command without any pre-configured scope.", "type": "string", - "enum": [ - "clipboard-manager:deny-write-html" - ] + "const": "clipboard-manager:deny-write-html" }, { - "description": "clipboard-manager:deny-write-image -> Denies the write_image command without any pre-configured scope.", + "description": "Denies the write_image command without any pre-configured scope.", "type": "string", - "enum": [ - "clipboard-manager:deny-write-image" - ] + "const": "clipboard-manager:deny-write-image" }, { - "description": "clipboard-manager:deny-write-text -> Denies the write_text command without any pre-configured scope.", + "description": "Denies the write_text command without any pre-configured scope.", "type": "string", - "enum": [ - "clipboard-manager:deny-write-text" - ] + "const": "clipboard-manager:deny-write-text" }, { - "description": "core:app:default -> Default permissions for the plugin.", + "description": "Default core plugins set which includes:\n- 'core:path:default'\n- 'core:event:default'\n- 'core:window:default'\n- 'core:webview:default'\n- 'core:app:default'\n- 'core:image:default'\n- 'core:resources:default'\n- 'core:menu:default'\n- 'core:tray:default'\n", "type": "string", - "enum": [ - "core:app:default" - ] + "const": "core:default" }, { - "description": "core:app:allow-app-hide -> Enables the app_hide command without any pre-configured scope.", + "description": "Default permissions for the plugin.", "type": "string", - "enum": [ - "core:app:allow-app-hide" - ] + "const": "core:app:default" }, { - "description": "core:app:allow-app-show -> Enables the app_show command without any pre-configured scope.", + "description": "Enables the app_hide command without any pre-configured scope.", "type": "string", - "enum": [ - "core:app:allow-app-show" - ] + "const": "core:app:allow-app-hide" }, { - "description": "core:app:allow-default-window-icon -> Enables the default_window_icon command without any pre-configured scope.", + "description": "Enables the app_show command without any pre-configured scope.", "type": "string", - "enum": [ - "core:app:allow-default-window-icon" - ] + "const": "core:app:allow-app-show" }, { - "description": "core:app:allow-name -> Enables the name command without any pre-configured scope.", + "description": "Enables the default_window_icon command without any pre-configured scope.", "type": "string", - "enum": [ - "core:app:allow-name" - ] + "const": "core:app:allow-default-window-icon" }, { - "description": "core:app:allow-tauri-version -> Enables the tauri_version command without any pre-configured scope.", + "description": "Enables the name command without any pre-configured scope.", "type": "string", - "enum": [ - "core:app:allow-tauri-version" - ] + "const": "core:app:allow-name" }, { - "description": "core:app:allow-version -> Enables the version command without any pre-configured scope.", + "description": "Enables the set_app_theme command without any pre-configured scope.", "type": "string", - "enum": [ - "core:app:allow-version" - ] + "const": "core:app:allow-set-app-theme" }, { - "description": "core:app:deny-app-hide -> Denies the app_hide command without any pre-configured scope.", + "description": "Enables the tauri_version command without any pre-configured scope.", "type": "string", - "enum": [ - "core:app:deny-app-hide" - ] + "const": "core:app:allow-tauri-version" }, { - "description": "core:app:deny-app-show -> Denies the app_show command without any pre-configured scope.", + "description": "Enables the version command without any pre-configured scope.", "type": "string", - "enum": [ - "core:app:deny-app-show" - ] + "const": "core:app:allow-version" }, { - "description": "core:app:deny-default-window-icon -> Denies the default_window_icon command without any pre-configured scope.", + "description": "Denies the app_hide command without any pre-configured scope.", "type": "string", - "enum": [ - "core:app:deny-default-window-icon" - ] + "const": "core:app:deny-app-hide" }, { - "description": "core:app:deny-name -> Denies the name command without any pre-configured scope.", + "description": "Denies the app_show command without any pre-configured scope.", "type": "string", - "enum": [ - "core:app:deny-name" - ] + "const": "core:app:deny-app-show" }, { - "description": "core:app:deny-tauri-version -> Denies the tauri_version command without any pre-configured scope.", + "description": "Denies the default_window_icon command without any pre-configured scope.", "type": "string", - "enum": [ - "core:app:deny-tauri-version" - ] + "const": "core:app:deny-default-window-icon" }, { - "description": "core:app:deny-version -> Denies the version command without any pre-configured scope.", + "description": "Denies the name command without any pre-configured scope.", "type": "string", - "enum": [ - "core:app:deny-version" - ] + "const": "core:app:deny-name" }, { - "description": "core:event:default -> Default permissions for the plugin.", + "description": "Denies the set_app_theme command without any pre-configured scope.", "type": "string", - "enum": [ - "core:event:default" - ] + "const": "core:app:deny-set-app-theme" }, { - "description": "core:event:allow-emit -> Enables the emit command without any pre-configured scope.", + "description": "Denies the tauri_version command without any pre-configured scope.", "type": "string", - "enum": [ - "core:event:allow-emit" - ] + "const": "core:app:deny-tauri-version" }, { - "description": "core:event:allow-emit-to -> Enables the emit_to command without any pre-configured scope.", + "description": "Denies the version command without any pre-configured scope.", "type": "string", - "enum": [ - "core:event:allow-emit-to" - ] + "const": "core:app:deny-version" }, { - "description": "core:event:allow-listen -> Enables the listen command without any pre-configured scope.", + "description": "Default permissions for the plugin.", "type": "string", - "enum": [ - "core:event:allow-listen" - ] + "const": "core:event:default" }, { - "description": "core:event:allow-unlisten -> Enables the unlisten command without any pre-configured scope.", + "description": "Enables the emit command without any pre-configured scope.", "type": "string", - "enum": [ - "core:event:allow-unlisten" - ] + "const": "core:event:allow-emit" }, { - "description": "core:event:deny-emit -> Denies the emit command without any pre-configured scope.", + "description": "Enables the emit_to command without any pre-configured scope.", "type": "string", - "enum": [ - "core:event:deny-emit" - ] + "const": "core:event:allow-emit-to" }, { - "description": "core:event:deny-emit-to -> Denies the emit_to command without any pre-configured scope.", + "description": "Enables the listen command without any pre-configured scope.", "type": "string", - "enum": [ - "core:event:deny-emit-to" - ] + "const": "core:event:allow-listen" }, { - "description": "core:event:deny-listen -> Denies the listen command without any pre-configured scope.", + "description": "Enables the unlisten command without any pre-configured scope.", "type": "string", - "enum": [ - "core:event:deny-listen" - ] + "const": "core:event:allow-unlisten" }, { - "description": "core:event:deny-unlisten -> Denies the unlisten command without any pre-configured scope.", + "description": "Denies the emit command without any pre-configured scope.", "type": "string", - "enum": [ - "core:event:deny-unlisten" - ] + "const": "core:event:deny-emit" }, { - "description": "core:image:default -> Default permissions for the plugin.", + "description": "Denies the emit_to command without any pre-configured scope.", "type": "string", - "enum": [ - "core:image:default" - ] + "const": "core:event:deny-emit-to" }, { - "description": "core:image:allow-from-bytes -> Enables the from_bytes command without any pre-configured scope.", + "description": "Denies the listen command without any pre-configured scope.", "type": "string", - "enum": [ - "core:image:allow-from-bytes" - ] + "const": "core:event:deny-listen" }, { - "description": "core:image:allow-from-path -> Enables the from_path command without any pre-configured scope.", + "description": "Denies the unlisten command without any pre-configured scope.", "type": "string", - "enum": [ - "core:image:allow-from-path" - ] + "const": "core:event:deny-unlisten" }, { - "description": "core:image:allow-new -> Enables the new command without any pre-configured scope.", + "description": "Default permissions for the plugin.", "type": "string", - "enum": [ - "core:image:allow-new" - ] + "const": "core:image:default" }, { - "description": "core:image:allow-rgba -> Enables the rgba command without any pre-configured scope.", + "description": "Enables the from_bytes command without any pre-configured scope.", "type": "string", - "enum": [ - "core:image:allow-rgba" - ] + "const": "core:image:allow-from-bytes" }, { - "description": "core:image:allow-size -> Enables the size command without any pre-configured scope.", + "description": "Enables the from_path command without any pre-configured scope.", "type": "string", - "enum": [ - "core:image:allow-size" - ] + "const": "core:image:allow-from-path" }, { - "description": "core:image:deny-from-bytes -> Denies the from_bytes command without any pre-configured scope.", + "description": "Enables the new command without any pre-configured scope.", "type": "string", - "enum": [ - "core:image:deny-from-bytes" - ] + "const": "core:image:allow-new" }, { - "description": "core:image:deny-from-path -> Denies the from_path command without any pre-configured scope.", + "description": "Enables the rgba command without any pre-configured scope.", "type": "string", - "enum": [ - "core:image:deny-from-path" - ] + "const": "core:image:allow-rgba" }, { - "description": "core:image:deny-new -> Denies the new command without any pre-configured scope.", + "description": "Enables the size command without any pre-configured scope.", "type": "string", - "enum": [ - "core:image:deny-new" - ] + "const": "core:image:allow-size" }, { - "description": "core:image:deny-rgba -> Denies the rgba command without any pre-configured scope.", + "description": "Denies the from_bytes command without any pre-configured scope.", "type": "string", - "enum": [ - "core:image:deny-rgba" - ] + "const": "core:image:deny-from-bytes" }, { - "description": "core:image:deny-size -> Denies the size command without any pre-configured scope.", + "description": "Denies the from_path command without any pre-configured scope.", "type": "string", - "enum": [ - "core:image:deny-size" - ] + "const": "core:image:deny-from-path" }, { - "description": "core:menu:default -> Default permissions for the plugin.", + "description": "Denies the new command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:default" - ] + "const": "core:image:deny-new" }, { - "description": "core:menu:allow-append -> Enables the append command without any pre-configured scope.", + "description": "Denies the rgba command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:allow-append" - ] + "const": "core:image:deny-rgba" }, { - "description": "core:menu:allow-create-default -> Enables the create_default command without any pre-configured scope.", + "description": "Denies the size command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:allow-create-default" - ] + "const": "core:image:deny-size" }, { - "description": "core:menu:allow-get -> Enables the get command without any pre-configured scope.", + "description": "Default permissions for the plugin.", "type": "string", - "enum": [ - "core:menu:allow-get" - ] + "const": "core:menu:default" }, { - "description": "core:menu:allow-insert -> Enables the insert command without any pre-configured scope.", + "description": "Enables the append command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:allow-insert" - ] + "const": "core:menu:allow-append" }, { - "description": "core:menu:allow-is-checked -> Enables the is_checked command without any pre-configured scope.", + "description": "Enables the create_default command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:allow-is-checked" - ] + "const": "core:menu:allow-create-default" }, { - "description": "core:menu:allow-is-enabled -> Enables the is_enabled command without any pre-configured scope.", + "description": "Enables the get command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:allow-is-enabled" - ] + "const": "core:menu:allow-get" }, { - "description": "core:menu:allow-items -> Enables the items command without any pre-configured scope.", + "description": "Enables the insert command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:allow-items" - ] + "const": "core:menu:allow-insert" }, { - "description": "core:menu:allow-new -> Enables the new command without any pre-configured scope.", + "description": "Enables the is_checked command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:allow-new" - ] + "const": "core:menu:allow-is-checked" }, { - "description": "core:menu:allow-popup -> Enables the popup command without any pre-configured scope.", + "description": "Enables the is_enabled command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:allow-popup" - ] + "const": "core:menu:allow-is-enabled" }, { - "description": "core:menu:allow-prepend -> Enables the prepend command without any pre-configured scope.", + "description": "Enables the items command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:allow-prepend" - ] + "const": "core:menu:allow-items" }, { - "description": "core:menu:allow-remove -> Enables the remove command without any pre-configured scope.", + "description": "Enables the new command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:allow-remove" - ] + "const": "core:menu:allow-new" }, { - "description": "core:menu:allow-remove-at -> Enables the remove_at command without any pre-configured scope.", + "description": "Enables the popup command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:allow-remove-at" - ] + "const": "core:menu:allow-popup" }, { - "description": "core:menu:allow-set-accelerator -> Enables the set_accelerator command without any pre-configured scope.", + "description": "Enables the prepend command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:allow-set-accelerator" - ] + "const": "core:menu:allow-prepend" }, { - "description": "core:menu:allow-set-as-app-menu -> Enables the set_as_app_menu command without any pre-configured scope.", + "description": "Enables the remove command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:allow-set-as-app-menu" - ] + "const": "core:menu:allow-remove" }, { - "description": "core:menu:allow-set-as-help-menu-for-nsapp -> Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "description": "Enables the remove_at command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:allow-set-as-help-menu-for-nsapp" - ] + "const": "core:menu:allow-remove-at" }, { - "description": "core:menu:allow-set-as-window-menu -> Enables the set_as_window_menu command without any pre-configured scope.", + "description": "Enables the set_accelerator command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:allow-set-as-window-menu" - ] + "const": "core:menu:allow-set-accelerator" }, { - "description": "core:menu:allow-set-as-windows-menu-for-nsapp -> Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "description": "Enables the set_as_app_menu command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:allow-set-as-windows-menu-for-nsapp" - ] + "const": "core:menu:allow-set-as-app-menu" }, { - "description": "core:menu:allow-set-checked -> Enables the set_checked command without any pre-configured scope.", + "description": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:allow-set-checked" - ] + "const": "core:menu:allow-set-as-help-menu-for-nsapp" }, { - "description": "core:menu:allow-set-enabled -> Enables the set_enabled command without any pre-configured scope.", + "description": "Enables the set_as_window_menu command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:allow-set-enabled" - ] + "const": "core:menu:allow-set-as-window-menu" }, { - "description": "core:menu:allow-set-icon -> Enables the set_icon command without any pre-configured scope.", + "description": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:allow-set-icon" - ] + "const": "core:menu:allow-set-as-windows-menu-for-nsapp" }, { - "description": "core:menu:allow-set-text -> Enables the set_text command without any pre-configured scope.", + "description": "Enables the set_checked command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:allow-set-text" - ] + "const": "core:menu:allow-set-checked" }, { - "description": "core:menu:allow-text -> Enables the text command without any pre-configured scope.", + "description": "Enables the set_enabled command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:allow-text" - ] + "const": "core:menu:allow-set-enabled" }, { - "description": "core:menu:deny-append -> Denies the append command without any pre-configured scope.", + "description": "Enables the set_icon command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:deny-append" - ] + "const": "core:menu:allow-set-icon" }, { - "description": "core:menu:deny-create-default -> Denies the create_default command without any pre-configured scope.", + "description": "Enables the set_text command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:deny-create-default" - ] + "const": "core:menu:allow-set-text" }, { - "description": "core:menu:deny-get -> Denies the get command without any pre-configured scope.", + "description": "Enables the text command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:deny-get" - ] + "const": "core:menu:allow-text" }, { - "description": "core:menu:deny-insert -> Denies the insert command without any pre-configured scope.", + "description": "Denies the append command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:deny-insert" - ] + "const": "core:menu:deny-append" }, { - "description": "core:menu:deny-is-checked -> Denies the is_checked command without any pre-configured scope.", + "description": "Denies the create_default command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:deny-is-checked" - ] + "const": "core:menu:deny-create-default" }, { - "description": "core:menu:deny-is-enabled -> Denies the is_enabled command without any pre-configured scope.", + "description": "Denies the get command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:deny-is-enabled" - ] + "const": "core:menu:deny-get" }, { - "description": "core:menu:deny-items -> Denies the items command without any pre-configured scope.", + "description": "Denies the insert command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:deny-items" - ] + "const": "core:menu:deny-insert" }, { - "description": "core:menu:deny-new -> Denies the new command without any pre-configured scope.", + "description": "Denies the is_checked command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:deny-new" - ] + "const": "core:menu:deny-is-checked" }, { - "description": "core:menu:deny-popup -> Denies the popup command without any pre-configured scope.", + "description": "Denies the is_enabled command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:deny-popup" - ] + "const": "core:menu:deny-is-enabled" }, { - "description": "core:menu:deny-prepend -> Denies the prepend command without any pre-configured scope.", + "description": "Denies the items command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:deny-prepend" - ] + "const": "core:menu:deny-items" }, { - "description": "core:menu:deny-remove -> Denies the remove command without any pre-configured scope.", + "description": "Denies the new command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:deny-remove" - ] + "const": "core:menu:deny-new" }, { - "description": "core:menu:deny-remove-at -> Denies the remove_at command without any pre-configured scope.", + "description": "Denies the popup command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:deny-remove-at" - ] + "const": "core:menu:deny-popup" }, { - "description": "core:menu:deny-set-accelerator -> Denies the set_accelerator command without any pre-configured scope.", + "description": "Denies the prepend command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:deny-set-accelerator" - ] + "const": "core:menu:deny-prepend" }, { - "description": "core:menu:deny-set-as-app-menu -> Denies the set_as_app_menu command without any pre-configured scope.", + "description": "Denies the remove command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:deny-set-as-app-menu" - ] + "const": "core:menu:deny-remove" }, { - "description": "core:menu:deny-set-as-help-menu-for-nsapp -> Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "description": "Denies the remove_at command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:deny-set-as-help-menu-for-nsapp" - ] + "const": "core:menu:deny-remove-at" }, { - "description": "core:menu:deny-set-as-window-menu -> Denies the set_as_window_menu command without any pre-configured scope.", + "description": "Denies the set_accelerator command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:deny-set-as-window-menu" - ] + "const": "core:menu:deny-set-accelerator" }, { - "description": "core:menu:deny-set-as-windows-menu-for-nsapp -> Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "description": "Denies the set_as_app_menu command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:deny-set-as-windows-menu-for-nsapp" - ] + "const": "core:menu:deny-set-as-app-menu" }, { - "description": "core:menu:deny-set-checked -> Denies the set_checked command without any pre-configured scope.", + "description": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:deny-set-checked" - ] + "const": "core:menu:deny-set-as-help-menu-for-nsapp" }, { - "description": "core:menu:deny-set-enabled -> Denies the set_enabled command without any pre-configured scope.", + "description": "Denies the set_as_window_menu command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:deny-set-enabled" - ] + "const": "core:menu:deny-set-as-window-menu" }, { - "description": "core:menu:deny-set-icon -> Denies the set_icon command without any pre-configured scope.", + "description": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:deny-set-icon" - ] + "const": "core:menu:deny-set-as-windows-menu-for-nsapp" }, { - "description": "core:menu:deny-set-text -> Denies the set_text command without any pre-configured scope.", + "description": "Denies the set_checked command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:deny-set-text" - ] + "const": "core:menu:deny-set-checked" }, { - "description": "core:menu:deny-text -> Denies the text command without any pre-configured scope.", + "description": "Denies the set_enabled command without any pre-configured scope.", "type": "string", - "enum": [ - "core:menu:deny-text" - ] + "const": "core:menu:deny-set-enabled" }, { - "description": "core:path:default -> Default permissions for the plugin.", + "description": "Denies the set_icon command without any pre-configured scope.", "type": "string", - "enum": [ - "core:path:default" - ] + "const": "core:menu:deny-set-icon" }, { - "description": "core:path:allow-basename -> Enables the basename command without any pre-configured scope.", + "description": "Denies the set_text command without any pre-configured scope.", "type": "string", - "enum": [ - "core:path:allow-basename" - ] + "const": "core:menu:deny-set-text" }, { - "description": "core:path:allow-dirname -> Enables the dirname command without any pre-configured scope.", + "description": "Denies the text command without any pre-configured scope.", "type": "string", - "enum": [ - "core:path:allow-dirname" - ] + "const": "core:menu:deny-text" }, { - "description": "core:path:allow-extname -> Enables the extname command without any pre-configured scope.", + "description": "Default permissions for the plugin.", "type": "string", - "enum": [ - "core:path:allow-extname" - ] + "const": "core:path:default" }, { - "description": "core:path:allow-is-absolute -> Enables the is_absolute command without any pre-configured scope.", + "description": "Enables the basename command without any pre-configured scope.", "type": "string", - "enum": [ - "core:path:allow-is-absolute" - ] + "const": "core:path:allow-basename" }, { - "description": "core:path:allow-join -> Enables the join command without any pre-configured scope.", + "description": "Enables the dirname command without any pre-configured scope.", "type": "string", - "enum": [ - "core:path:allow-join" - ] + "const": "core:path:allow-dirname" }, { - "description": "core:path:allow-normalize -> Enables the normalize command without any pre-configured scope.", + "description": "Enables the extname command without any pre-configured scope.", "type": "string", - "enum": [ - "core:path:allow-normalize" - ] + "const": "core:path:allow-extname" }, { - "description": "core:path:allow-resolve -> Enables the resolve command without any pre-configured scope.", + "description": "Enables the is_absolute command without any pre-configured scope.", "type": "string", - "enum": [ - "core:path:allow-resolve" - ] + "const": "core:path:allow-is-absolute" }, { - "description": "core:path:allow-resolve-directory -> Enables the resolve_directory command without any pre-configured scope.", + "description": "Enables the join command without any pre-configured scope.", "type": "string", - "enum": [ - "core:path:allow-resolve-directory" - ] + "const": "core:path:allow-join" }, { - "description": "core:path:deny-basename -> Denies the basename command without any pre-configured scope.", + "description": "Enables the normalize command without any pre-configured scope.", "type": "string", - "enum": [ - "core:path:deny-basename" - ] + "const": "core:path:allow-normalize" }, { - "description": "core:path:deny-dirname -> Denies the dirname command without any pre-configured scope.", + "description": "Enables the resolve command without any pre-configured scope.", "type": "string", - "enum": [ - "core:path:deny-dirname" - ] + "const": "core:path:allow-resolve" }, { - "description": "core:path:deny-extname -> Denies the extname command without any pre-configured scope.", + "description": "Enables the resolve_directory command without any pre-configured scope.", "type": "string", - "enum": [ - "core:path:deny-extname" - ] + "const": "core:path:allow-resolve-directory" }, { - "description": "core:path:deny-is-absolute -> Denies the is_absolute command without any pre-configured scope.", + "description": "Denies the basename command without any pre-configured scope.", "type": "string", - "enum": [ - "core:path:deny-is-absolute" - ] + "const": "core:path:deny-basename" }, { - "description": "core:path:deny-join -> Denies the join command without any pre-configured scope.", + "description": "Denies the dirname command without any pre-configured scope.", "type": "string", - "enum": [ - "core:path:deny-join" - ] + "const": "core:path:deny-dirname" }, { - "description": "core:path:deny-normalize -> Denies the normalize command without any pre-configured scope.", + "description": "Denies the extname command without any pre-configured scope.", "type": "string", - "enum": [ - "core:path:deny-normalize" - ] + "const": "core:path:deny-extname" }, { - "description": "core:path:deny-resolve -> Denies the resolve command without any pre-configured scope.", + "description": "Denies the is_absolute command without any pre-configured scope.", "type": "string", - "enum": [ - "core:path:deny-resolve" - ] + "const": "core:path:deny-is-absolute" }, { - "description": "core:path:deny-resolve-directory -> Denies the resolve_directory command without any pre-configured scope.", + "description": "Denies the join command without any pre-configured scope.", "type": "string", - "enum": [ - "core:path:deny-resolve-directory" - ] + "const": "core:path:deny-join" }, { - "description": "core:resources:default -> Default permissions for the plugin.", + "description": "Denies the normalize command without any pre-configured scope.", "type": "string", - "enum": [ - "core:resources:default" - ] + "const": "core:path:deny-normalize" }, { - "description": "core:resources:allow-close -> Enables the close command without any pre-configured scope.", + "description": "Denies the resolve command without any pre-configured scope.", "type": "string", - "enum": [ - "core:resources:allow-close" - ] + "const": "core:path:deny-resolve" }, { - "description": "core:resources:deny-close -> Denies the close command without any pre-configured scope.", + "description": "Denies the resolve_directory command without any pre-configured scope.", "type": "string", - "enum": [ - "core:resources:deny-close" - ] + "const": "core:path:deny-resolve-directory" }, { - "description": "core:tray:default -> Default permissions for the plugin.", + "description": "Default permissions for the plugin.", "type": "string", - "enum": [ - "core:tray:default" - ] + "const": "core:resources:default" }, { - "description": "core:tray:allow-get-by-id -> Enables the get_by_id command without any pre-configured scope.", + "description": "Enables the close command without any pre-configured scope.", "type": "string", - "enum": [ - "core:tray:allow-get-by-id" - ] + "const": "core:resources:allow-close" }, { - "description": "core:tray:allow-new -> Enables the new command without any pre-configured scope.", + "description": "Denies the close command without any pre-configured scope.", "type": "string", - "enum": [ - "core:tray:allow-new" - ] + "const": "core:resources:deny-close" }, { - "description": "core:tray:allow-remove-by-id -> Enables the remove_by_id command without any pre-configured scope.", + "description": "Default permissions for the plugin.", "type": "string", - "enum": [ - "core:tray:allow-remove-by-id" - ] + "const": "core:tray:default" }, { - "description": "core:tray:allow-set-icon -> Enables the set_icon command without any pre-configured scope.", + "description": "Enables the get_by_id command without any pre-configured scope.", "type": "string", - "enum": [ - "core:tray:allow-set-icon" - ] + "const": "core:tray:allow-get-by-id" }, { - "description": "core:tray:allow-set-icon-as-template -> Enables the set_icon_as_template command without any pre-configured scope.", + "description": "Enables the new command without any pre-configured scope.", "type": "string", - "enum": [ - "core:tray:allow-set-icon-as-template" - ] + "const": "core:tray:allow-new" }, { - "description": "core:tray:allow-set-menu -> Enables the set_menu command without any pre-configured scope.", + "description": "Enables the remove_by_id command without any pre-configured scope.", "type": "string", - "enum": [ - "core:tray:allow-set-menu" - ] + "const": "core:tray:allow-remove-by-id" }, { - "description": "core:tray:allow-set-show-menu-on-left-click -> Enables the set_show_menu_on_left_click command without any pre-configured scope.", + "description": "Enables the set_icon command without any pre-configured scope.", "type": "string", - "enum": [ - "core:tray:allow-set-show-menu-on-left-click" - ] + "const": "core:tray:allow-set-icon" }, { - "description": "core:tray:allow-set-temp-dir-path -> Enables the set_temp_dir_path command without any pre-configured scope.", + "description": "Enables the set_icon_as_template command without any pre-configured scope.", "type": "string", - "enum": [ - "core:tray:allow-set-temp-dir-path" - ] + "const": "core:tray:allow-set-icon-as-template" }, { - "description": "core:tray:allow-set-title -> Enables the set_title command without any pre-configured scope.", + "description": "Enables the set_menu command without any pre-configured scope.", "type": "string", - "enum": [ - "core:tray:allow-set-title" - ] + "const": "core:tray:allow-set-menu" }, { - "description": "core:tray:allow-set-tooltip -> Enables the set_tooltip command without any pre-configured scope.", + "description": "Enables the set_show_menu_on_left_click command without any pre-configured scope.", "type": "string", - "enum": [ - "core:tray:allow-set-tooltip" - ] + "const": "core:tray:allow-set-show-menu-on-left-click" }, { - "description": "core:tray:allow-set-visible -> Enables the set_visible command without any pre-configured scope.", + "description": "Enables the set_temp_dir_path command without any pre-configured scope.", "type": "string", - "enum": [ - "core:tray:allow-set-visible" - ] + "const": "core:tray:allow-set-temp-dir-path" }, { - "description": "core:tray:deny-get-by-id -> Denies the get_by_id command without any pre-configured scope.", + "description": "Enables the set_title command without any pre-configured scope.", "type": "string", - "enum": [ - "core:tray:deny-get-by-id" - ] + "const": "core:tray:allow-set-title" }, { - "description": "core:tray:deny-new -> Denies the new command without any pre-configured scope.", + "description": "Enables the set_tooltip command without any pre-configured scope.", "type": "string", - "enum": [ - "core:tray:deny-new" - ] + "const": "core:tray:allow-set-tooltip" }, { - "description": "core:tray:deny-remove-by-id -> Denies the remove_by_id command without any pre-configured scope.", + "description": "Enables the set_visible command without any pre-configured scope.", "type": "string", - "enum": [ - "core:tray:deny-remove-by-id" - ] + "const": "core:tray:allow-set-visible" }, { - "description": "core:tray:deny-set-icon -> Denies the set_icon command without any pre-configured scope.", + "description": "Denies the get_by_id command without any pre-configured scope.", "type": "string", - "enum": [ - "core:tray:deny-set-icon" - ] + "const": "core:tray:deny-get-by-id" }, { - "description": "core:tray:deny-set-icon-as-template -> Denies the set_icon_as_template command without any pre-configured scope.", + "description": "Denies the new command without any pre-configured scope.", "type": "string", - "enum": [ - "core:tray:deny-set-icon-as-template" - ] + "const": "core:tray:deny-new" }, { - "description": "core:tray:deny-set-menu -> Denies the set_menu command without any pre-configured scope.", + "description": "Denies the remove_by_id command without any pre-configured scope.", "type": "string", - "enum": [ - "core:tray:deny-set-menu" - ] + "const": "core:tray:deny-remove-by-id" }, { - "description": "core:tray:deny-set-show-menu-on-left-click -> Denies the set_show_menu_on_left_click command without any pre-configured scope.", + "description": "Denies the set_icon command without any pre-configured scope.", "type": "string", - "enum": [ - "core:tray:deny-set-show-menu-on-left-click" - ] + "const": "core:tray:deny-set-icon" }, { - "description": "core:tray:deny-set-temp-dir-path -> Denies the set_temp_dir_path command without any pre-configured scope.", + "description": "Denies the set_icon_as_template command without any pre-configured scope.", "type": "string", - "enum": [ - "core:tray:deny-set-temp-dir-path" - ] + "const": "core:tray:deny-set-icon-as-template" }, { - "description": "core:tray:deny-set-title -> Denies the set_title command without any pre-configured scope.", + "description": "Denies the set_menu command without any pre-configured scope.", "type": "string", - "enum": [ - "core:tray:deny-set-title" - ] + "const": "core:tray:deny-set-menu" }, { - "description": "core:tray:deny-set-tooltip -> Denies the set_tooltip command without any pre-configured scope.", + "description": "Denies the set_show_menu_on_left_click command without any pre-configured scope.", "type": "string", - "enum": [ - "core:tray:deny-set-tooltip" - ] + "const": "core:tray:deny-set-show-menu-on-left-click" }, { - "description": "core:tray:deny-set-visible -> Denies the set_visible command without any pre-configured scope.", + "description": "Denies the set_temp_dir_path command without any pre-configured scope.", "type": "string", - "enum": [ - "core:tray:deny-set-visible" - ] + "const": "core:tray:deny-set-temp-dir-path" }, { - "description": "core:webview:default -> Default permissions for the plugin.", + "description": "Denies the set_title command without any pre-configured scope.", "type": "string", - "enum": [ - "core:webview:default" - ] + "const": "core:tray:deny-set-title" }, { - "description": "core:webview:allow-create-webview -> Enables the create_webview command without any pre-configured scope.", + "description": "Denies the set_tooltip command without any pre-configured scope.", "type": "string", - "enum": [ - "core:webview:allow-create-webview" - ] + "const": "core:tray:deny-set-tooltip" }, { - "description": "core:webview:allow-create-webview-window -> Enables the create_webview_window command without any pre-configured scope.", + "description": "Denies the set_visible command without any pre-configured scope.", "type": "string", - "enum": [ - "core:webview:allow-create-webview-window" - ] + "const": "core:tray:deny-set-visible" }, { - "description": "core:webview:allow-get-all-webviews -> Enables the get_all_webviews command without any pre-configured scope.", + "description": "Default permissions for the plugin.", "type": "string", - "enum": [ - "core:webview:allow-get-all-webviews" - ] + "const": "core:webview:default" }, { - "description": "core:webview:allow-internal-toggle-devtools -> Enables the internal_toggle_devtools command without any pre-configured scope.", + "description": "Enables the clear_all_browsing_data command without any pre-configured scope.", "type": "string", - "enum": [ - "core:webview:allow-internal-toggle-devtools" - ] + "const": "core:webview:allow-clear-all-browsing-data" }, { - "description": "core:webview:allow-print -> Enables the print command without any pre-configured scope.", + "description": "Enables the create_webview command without any pre-configured scope.", "type": "string", - "enum": [ - "core:webview:allow-print" - ] + "const": "core:webview:allow-create-webview" }, { - "description": "core:webview:allow-reparent -> Enables the reparent command without any pre-configured scope.", + "description": "Enables the create_webview_window command without any pre-configured scope.", "type": "string", - "enum": [ - "core:webview:allow-reparent" - ] + "const": "core:webview:allow-create-webview-window" }, { - "description": "core:webview:allow-set-webview-focus -> Enables the set_webview_focus command without any pre-configured scope.", + "description": "Enables the get_all_webviews command without any pre-configured scope.", "type": "string", - "enum": [ - "core:webview:allow-set-webview-focus" - ] + "const": "core:webview:allow-get-all-webviews" }, { - "description": "core:webview:allow-set-webview-position -> Enables the set_webview_position command without any pre-configured scope.", + "description": "Enables the internal_toggle_devtools command without any pre-configured scope.", "type": "string", - "enum": [ - "core:webview:allow-set-webview-position" - ] + "const": "core:webview:allow-internal-toggle-devtools" }, { - "description": "core:webview:allow-set-webview-size -> Enables the set_webview_size command without any pre-configured scope.", + "description": "Enables the print command without any pre-configured scope.", "type": "string", - "enum": [ - "core:webview:allow-set-webview-size" - ] + "const": "core:webview:allow-print" }, { - "description": "core:webview:allow-set-webview-zoom -> Enables the set_webview_zoom command without any pre-configured scope.", + "description": "Enables the reparent command without any pre-configured scope.", "type": "string", - "enum": [ - "core:webview:allow-set-webview-zoom" - ] + "const": "core:webview:allow-reparent" }, { - "description": "core:webview:allow-webview-close -> Enables the webview_close command without any pre-configured scope.", + "description": "Enables the set_webview_focus command without any pre-configured scope.", "type": "string", - "enum": [ - "core:webview:allow-webview-close" - ] + "const": "core:webview:allow-set-webview-focus" }, { - "description": "core:webview:allow-webview-position -> Enables the webview_position command without any pre-configured scope.", + "description": "Enables the set_webview_position command without any pre-configured scope.", "type": "string", - "enum": [ - "core:webview:allow-webview-position" - ] + "const": "core:webview:allow-set-webview-position" }, { - "description": "core:webview:allow-webview-size -> Enables the webview_size command without any pre-configured scope.", + "description": "Enables the set_webview_size command without any pre-configured scope.", "type": "string", - "enum": [ - "core:webview:allow-webview-size" - ] + "const": "core:webview:allow-set-webview-size" }, { - "description": "core:webview:deny-create-webview -> Denies the create_webview command without any pre-configured scope.", + "description": "Enables the set_webview_zoom command without any pre-configured scope.", "type": "string", - "enum": [ - "core:webview:deny-create-webview" - ] + "const": "core:webview:allow-set-webview-zoom" }, { - "description": "core:webview:deny-create-webview-window -> Denies the create_webview_window command without any pre-configured scope.", + "description": "Enables the webview_close command without any pre-configured scope.", "type": "string", - "enum": [ - "core:webview:deny-create-webview-window" - ] + "const": "core:webview:allow-webview-close" }, { - "description": "core:webview:deny-get-all-webviews -> Denies the get_all_webviews command without any pre-configured scope.", + "description": "Enables the webview_hide command without any pre-configured scope.", "type": "string", - "enum": [ - "core:webview:deny-get-all-webviews" - ] + "const": "core:webview:allow-webview-hide" }, { - "description": "core:webview:deny-internal-toggle-devtools -> Denies the internal_toggle_devtools command without any pre-configured scope.", + "description": "Enables the webview_position command without any pre-configured scope.", "type": "string", - "enum": [ - "core:webview:deny-internal-toggle-devtools" - ] + "const": "core:webview:allow-webview-position" }, { - "description": "core:webview:deny-print -> Denies the print command without any pre-configured scope.", + "description": "Enables the webview_show command without any pre-configured scope.", "type": "string", - "enum": [ - "core:webview:deny-print" - ] + "const": "core:webview:allow-webview-show" }, { - "description": "core:webview:deny-reparent -> Denies the reparent command without any pre-configured scope.", + "description": "Enables the webview_size command without any pre-configured scope.", "type": "string", - "enum": [ - "core:webview:deny-reparent" - ] + "const": "core:webview:allow-webview-size" }, { - "description": "core:webview:deny-set-webview-focus -> Denies the set_webview_focus command without any pre-configured scope.", + "description": "Denies the clear_all_browsing_data command without any pre-configured scope.", "type": "string", - "enum": [ - "core:webview:deny-set-webview-focus" - ] + "const": "core:webview:deny-clear-all-browsing-data" }, { - "description": "core:webview:deny-set-webview-position -> Denies the set_webview_position command without any pre-configured scope.", + "description": "Denies the create_webview command without any pre-configured scope.", "type": "string", - "enum": [ - "core:webview:deny-set-webview-position" - ] + "const": "core:webview:deny-create-webview" }, { - "description": "core:webview:deny-set-webview-size -> Denies the set_webview_size command without any pre-configured scope.", + "description": "Denies the create_webview_window command without any pre-configured scope.", "type": "string", - "enum": [ - "core:webview:deny-set-webview-size" - ] + "const": "core:webview:deny-create-webview-window" }, { - "description": "core:webview:deny-set-webview-zoom -> Denies the set_webview_zoom command without any pre-configured scope.", + "description": "Denies the get_all_webviews command without any pre-configured scope.", "type": "string", - "enum": [ - "core:webview:deny-set-webview-zoom" - ] + "const": "core:webview:deny-get-all-webviews" }, { - "description": "core:webview:deny-webview-close -> Denies the webview_close command without any pre-configured scope.", + "description": "Denies the internal_toggle_devtools command without any pre-configured scope.", "type": "string", - "enum": [ - "core:webview:deny-webview-close" - ] + "const": "core:webview:deny-internal-toggle-devtools" }, { - "description": "core:webview:deny-webview-position -> Denies the webview_position command without any pre-configured scope.", + "description": "Denies the print command without any pre-configured scope.", "type": "string", - "enum": [ - "core:webview:deny-webview-position" - ] + "const": "core:webview:deny-print" }, { - "description": "core:webview:deny-webview-size -> Denies the webview_size command without any pre-configured scope.", + "description": "Denies the reparent command without any pre-configured scope.", "type": "string", - "enum": [ - "core:webview:deny-webview-size" - ] + "const": "core:webview:deny-reparent" }, { - "description": "core:window:default -> Default permissions for the plugin.", + "description": "Denies the set_webview_focus command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:default" - ] + "const": "core:webview:deny-set-webview-focus" }, { - "description": "core:window:allow-available-monitors -> Enables the available_monitors command without any pre-configured scope.", + "description": "Denies the set_webview_position command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-available-monitors" - ] + "const": "core:webview:deny-set-webview-position" }, { - "description": "core:window:allow-center -> Enables the center command without any pre-configured scope.", + "description": "Denies the set_webview_size command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-center" - ] + "const": "core:webview:deny-set-webview-size" }, { - "description": "core:window:allow-close -> Enables the close command without any pre-configured scope.", + "description": "Denies the set_webview_zoom command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-close" - ] + "const": "core:webview:deny-set-webview-zoom" }, { - "description": "core:window:allow-create -> Enables the create command without any pre-configured scope.", + "description": "Denies the webview_close command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-create" - ] + "const": "core:webview:deny-webview-close" }, { - "description": "core:window:allow-current-monitor -> Enables the current_monitor command without any pre-configured scope.", + "description": "Denies the webview_hide command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-current-monitor" - ] + "const": "core:webview:deny-webview-hide" }, { - "description": "core:window:allow-cursor-position -> Enables the cursor_position command without any pre-configured scope.", + "description": "Denies the webview_position command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-cursor-position" - ] + "const": "core:webview:deny-webview-position" }, { - "description": "core:window:allow-destroy -> Enables the destroy command without any pre-configured scope.", + "description": "Denies the webview_show command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-destroy" - ] + "const": "core:webview:deny-webview-show" }, { - "description": "core:window:allow-get-all-windows -> Enables the get_all_windows command without any pre-configured scope.", + "description": "Denies the webview_size command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-get-all-windows" - ] + "const": "core:webview:deny-webview-size" }, { - "description": "core:window:allow-hide -> Enables the hide command without any pre-configured scope.", + "description": "Default permissions for the plugin.", "type": "string", - "enum": [ - "core:window:allow-hide" - ] + "const": "core:window:default" }, { - "description": "core:window:allow-inner-position -> Enables the inner_position command without any pre-configured scope.", + "description": "Enables the available_monitors command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-inner-position" - ] + "const": "core:window:allow-available-monitors" }, { - "description": "core:window:allow-inner-size -> Enables the inner_size command without any pre-configured scope.", + "description": "Enables the center command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-inner-size" - ] + "const": "core:window:allow-center" }, { - "description": "core:window:allow-internal-toggle-maximize -> Enables the internal_toggle_maximize command without any pre-configured scope.", + "description": "Enables the close command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-internal-toggle-maximize" - ] + "const": "core:window:allow-close" }, { - "description": "core:window:allow-is-closable -> Enables the is_closable command without any pre-configured scope.", + "description": "Enables the create command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-is-closable" - ] + "const": "core:window:allow-create" }, { - "description": "core:window:allow-is-decorated -> Enables the is_decorated command without any pre-configured scope.", + "description": "Enables the current_monitor command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-is-decorated" - ] + "const": "core:window:allow-current-monitor" }, { - "description": "core:window:allow-is-focused -> Enables the is_focused command without any pre-configured scope.", + "description": "Enables the cursor_position command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-is-focused" - ] + "const": "core:window:allow-cursor-position" }, { - "description": "core:window:allow-is-fullscreen -> Enables the is_fullscreen command without any pre-configured scope.", + "description": "Enables the destroy command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-is-fullscreen" - ] + "const": "core:window:allow-destroy" }, { - "description": "core:window:allow-is-maximizable -> Enables the is_maximizable command without any pre-configured scope.", + "description": "Enables the get_all_windows command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-is-maximizable" - ] + "const": "core:window:allow-get-all-windows" }, { - "description": "core:window:allow-is-maximized -> Enables the is_maximized command without any pre-configured scope.", + "description": "Enables the hide command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-is-maximized" - ] + "const": "core:window:allow-hide" }, { - "description": "core:window:allow-is-minimizable -> Enables the is_minimizable command without any pre-configured scope.", + "description": "Enables the inner_position command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-is-minimizable" - ] + "const": "core:window:allow-inner-position" }, { - "description": "core:window:allow-is-minimized -> Enables the is_minimized command without any pre-configured scope.", + "description": "Enables the inner_size command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-is-minimized" - ] + "const": "core:window:allow-inner-size" }, { - "description": "core:window:allow-is-resizable -> Enables the is_resizable command without any pre-configured scope.", + "description": "Enables the internal_toggle_maximize command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-is-resizable" - ] + "const": "core:window:allow-internal-toggle-maximize" }, { - "description": "core:window:allow-is-visible -> Enables the is_visible command without any pre-configured scope.", + "description": "Enables the is_closable command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-is-visible" - ] + "const": "core:window:allow-is-closable" }, { - "description": "core:window:allow-maximize -> Enables the maximize command without any pre-configured scope.", + "description": "Enables the is_decorated command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-maximize" - ] + "const": "core:window:allow-is-decorated" }, { - "description": "core:window:allow-minimize -> Enables the minimize command without any pre-configured scope.", + "description": "Enables the is_enabled command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-minimize" - ] + "const": "core:window:allow-is-enabled" }, { - "description": "core:window:allow-monitor-from-point -> Enables the monitor_from_point command without any pre-configured scope.", + "description": "Enables the is_focused command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-monitor-from-point" - ] + "const": "core:window:allow-is-focused" }, { - "description": "core:window:allow-outer-position -> Enables the outer_position command without any pre-configured scope.", + "description": "Enables the is_fullscreen command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-outer-position" - ] + "const": "core:window:allow-is-fullscreen" }, { - "description": "core:window:allow-outer-size -> Enables the outer_size command without any pre-configured scope.", + "description": "Enables the is_maximizable command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-outer-size" - ] + "const": "core:window:allow-is-maximizable" }, { - "description": "core:window:allow-primary-monitor -> Enables the primary_monitor command without any pre-configured scope.", + "description": "Enables the is_maximized command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-primary-monitor" - ] + "const": "core:window:allow-is-maximized" }, { - "description": "core:window:allow-request-user-attention -> Enables the request_user_attention command without any pre-configured scope.", + "description": "Enables the is_minimizable command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-request-user-attention" - ] + "const": "core:window:allow-is-minimizable" }, { - "description": "core:window:allow-scale-factor -> Enables the scale_factor command without any pre-configured scope.", + "description": "Enables the is_minimized command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-scale-factor" - ] + "const": "core:window:allow-is-minimized" }, { - "description": "core:window:allow-set-always-on-bottom -> Enables the set_always_on_bottom command without any pre-configured scope.", + "description": "Enables the is_resizable command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-set-always-on-bottom" - ] + "const": "core:window:allow-is-resizable" }, { - "description": "core:window:allow-set-always-on-top -> Enables the set_always_on_top command without any pre-configured scope.", + "description": "Enables the is_visible command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-set-always-on-top" - ] + "const": "core:window:allow-is-visible" }, { - "description": "core:window:allow-set-closable -> Enables the set_closable command without any pre-configured scope.", + "description": "Enables the maximize command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-set-closable" - ] + "const": "core:window:allow-maximize" }, { - "description": "core:window:allow-set-content-protected -> Enables the set_content_protected command without any pre-configured scope.", + "description": "Enables the minimize command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-set-content-protected" - ] + "const": "core:window:allow-minimize" }, { - "description": "core:window:allow-set-cursor-grab -> Enables the set_cursor_grab command without any pre-configured scope.", + "description": "Enables the monitor_from_point command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-set-cursor-grab" - ] + "const": "core:window:allow-monitor-from-point" }, { - "description": "core:window:allow-set-cursor-icon -> Enables the set_cursor_icon command without any pre-configured scope.", + "description": "Enables the outer_position command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-set-cursor-icon" - ] + "const": "core:window:allow-outer-position" }, { - "description": "core:window:allow-set-cursor-position -> Enables the set_cursor_position command without any pre-configured scope.", + "description": "Enables the outer_size command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-set-cursor-position" - ] + "const": "core:window:allow-outer-size" }, { - "description": "core:window:allow-set-cursor-visible -> Enables the set_cursor_visible command without any pre-configured scope.", + "description": "Enables the primary_monitor command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-set-cursor-visible" - ] + "const": "core:window:allow-primary-monitor" }, { - "description": "core:window:allow-set-decorations -> Enables the set_decorations command without any pre-configured scope.", + "description": "Enables the request_user_attention command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-set-decorations" - ] + "const": "core:window:allow-request-user-attention" }, { - "description": "core:window:allow-set-effects -> Enables the set_effects command without any pre-configured scope.", + "description": "Enables the scale_factor command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-set-effects" - ] + "const": "core:window:allow-scale-factor" }, { - "description": "core:window:allow-set-focus -> Enables the set_focus command without any pre-configured scope.", + "description": "Enables the set_always_on_bottom command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-set-focus" - ] + "const": "core:window:allow-set-always-on-bottom" }, { - "description": "core:window:allow-set-fullscreen -> Enables the set_fullscreen command without any pre-configured scope.", + "description": "Enables the set_always_on_top command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-set-fullscreen" - ] + "const": "core:window:allow-set-always-on-top" }, { - "description": "core:window:allow-set-icon -> Enables the set_icon command without any pre-configured scope.", + "description": "Enables the set_closable command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-set-icon" - ] + "const": "core:window:allow-set-closable" }, { - "description": "core:window:allow-set-ignore-cursor-events -> Enables the set_ignore_cursor_events command without any pre-configured scope.", + "description": "Enables the set_content_protected command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-set-ignore-cursor-events" - ] + "const": "core:window:allow-set-content-protected" }, { - "description": "core:window:allow-set-max-size -> Enables the set_max_size command without any pre-configured scope.", + "description": "Enables the set_cursor_grab command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-set-max-size" - ] + "const": "core:window:allow-set-cursor-grab" }, { - "description": "core:window:allow-set-maximizable -> Enables the set_maximizable command without any pre-configured scope.", + "description": "Enables the set_cursor_icon command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-set-maximizable" - ] + "const": "core:window:allow-set-cursor-icon" }, { - "description": "core:window:allow-set-min-size -> Enables the set_min_size command without any pre-configured scope.", + "description": "Enables the set_cursor_position command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-set-min-size" - ] + "const": "core:window:allow-set-cursor-position" }, { - "description": "core:window:allow-set-minimizable -> Enables the set_minimizable command without any pre-configured scope.", + "description": "Enables the set_cursor_visible command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-set-minimizable" - ] + "const": "core:window:allow-set-cursor-visible" }, { - "description": "core:window:allow-set-position -> Enables the set_position command without any pre-configured scope.", + "description": "Enables the set_decorations command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-set-position" - ] + "const": "core:window:allow-set-decorations" }, { - "description": "core:window:allow-set-progress-bar -> Enables the set_progress_bar command without any pre-configured scope.", + "description": "Enables the set_effects command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-set-progress-bar" - ] + "const": "core:window:allow-set-effects" }, { - "description": "core:window:allow-set-resizable -> Enables the set_resizable command without any pre-configured scope.", + "description": "Enables the set_enabled command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-set-resizable" - ] + "const": "core:window:allow-set-enabled" }, { - "description": "core:window:allow-set-shadow -> Enables the set_shadow command without any pre-configured scope.", + "description": "Enables the set_focus command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-set-shadow" - ] + "const": "core:window:allow-set-focus" }, { - "description": "core:window:allow-set-size -> Enables the set_size command without any pre-configured scope.", + "description": "Enables the set_fullscreen command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-set-size" - ] + "const": "core:window:allow-set-fullscreen" }, { - "description": "core:window:allow-set-size-constraints -> Enables the set_size_constraints command without any pre-configured scope.", + "description": "Enables the set_icon command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-set-size-constraints" - ] + "const": "core:window:allow-set-icon" }, { - "description": "core:window:allow-set-skip-taskbar -> Enables the set_skip_taskbar command without any pre-configured scope.", + "description": "Enables the set_ignore_cursor_events command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-set-skip-taskbar" - ] + "const": "core:window:allow-set-ignore-cursor-events" }, { - "description": "core:window:allow-set-title -> Enables the set_title command without any pre-configured scope.", + "description": "Enables the set_max_size command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-set-title" - ] + "const": "core:window:allow-set-max-size" }, { - "description": "core:window:allow-set-title-bar-style -> Enables the set_title_bar_style command without any pre-configured scope.", + "description": "Enables the set_maximizable command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-set-title-bar-style" - ] + "const": "core:window:allow-set-maximizable" }, { - "description": "core:window:allow-set-visible-on-all-workspaces -> Enables the set_visible_on_all_workspaces command without any pre-configured scope.", + "description": "Enables the set_min_size command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-set-visible-on-all-workspaces" - ] + "const": "core:window:allow-set-min-size" }, { - "description": "core:window:allow-show -> Enables the show command without any pre-configured scope.", + "description": "Enables the set_minimizable command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-show" - ] + "const": "core:window:allow-set-minimizable" }, { - "description": "core:window:allow-start-dragging -> Enables the start_dragging command without any pre-configured scope.", + "description": "Enables the set_position command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-start-dragging" - ] + "const": "core:window:allow-set-position" }, { - "description": "core:window:allow-start-resize-dragging -> Enables the start_resize_dragging command without any pre-configured scope.", + "description": "Enables the set_progress_bar command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-start-resize-dragging" - ] + "const": "core:window:allow-set-progress-bar" }, { - "description": "core:window:allow-theme -> Enables the theme command without any pre-configured scope.", + "description": "Enables the set_resizable command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-theme" - ] + "const": "core:window:allow-set-resizable" }, { - "description": "core:window:allow-title -> Enables the title command without any pre-configured scope.", + "description": "Enables the set_shadow command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-title" - ] + "const": "core:window:allow-set-shadow" }, { - "description": "core:window:allow-toggle-maximize -> Enables the toggle_maximize command without any pre-configured scope.", + "description": "Enables the set_size command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-toggle-maximize" - ] + "const": "core:window:allow-set-size" }, { - "description": "core:window:allow-unmaximize -> Enables the unmaximize command without any pre-configured scope.", + "description": "Enables the set_size_constraints command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-unmaximize" - ] + "const": "core:window:allow-set-size-constraints" }, { - "description": "core:window:allow-unminimize -> Enables the unminimize command without any pre-configured scope.", + "description": "Enables the set_skip_taskbar command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:allow-unminimize" - ] + "const": "core:window:allow-set-skip-taskbar" }, { - "description": "core:window:deny-available-monitors -> Denies the available_monitors command without any pre-configured scope.", + "description": "Enables the set_theme command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-available-monitors" - ] + "const": "core:window:allow-set-theme" }, { - "description": "core:window:deny-center -> Denies the center command without any pre-configured scope.", + "description": "Enables the set_title command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-center" - ] + "const": "core:window:allow-set-title" }, { - "description": "core:window:deny-close -> Denies the close command without any pre-configured scope.", + "description": "Enables the set_title_bar_style command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-close" - ] + "const": "core:window:allow-set-title-bar-style" }, { - "description": "core:window:deny-create -> Denies the create command without any pre-configured scope.", + "description": "Enables the set_visible_on_all_workspaces command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-create" - ] + "const": "core:window:allow-set-visible-on-all-workspaces" }, { - "description": "core:window:deny-current-monitor -> Denies the current_monitor command without any pre-configured scope.", + "description": "Enables the show command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-current-monitor" - ] + "const": "core:window:allow-show" }, { - "description": "core:window:deny-cursor-position -> Denies the cursor_position command without any pre-configured scope.", + "description": "Enables the start_dragging command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-cursor-position" - ] + "const": "core:window:allow-start-dragging" }, { - "description": "core:window:deny-destroy -> Denies the destroy command without any pre-configured scope.", + "description": "Enables the start_resize_dragging command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-destroy" - ] + "const": "core:window:allow-start-resize-dragging" }, { - "description": "core:window:deny-get-all-windows -> Denies the get_all_windows command without any pre-configured scope.", + "description": "Enables the theme command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-get-all-windows" - ] + "const": "core:window:allow-theme" }, { - "description": "core:window:deny-hide -> Denies the hide command without any pre-configured scope.", + "description": "Enables the title command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-hide" - ] + "const": "core:window:allow-title" }, { - "description": "core:window:deny-inner-position -> Denies the inner_position command without any pre-configured scope.", + "description": "Enables the toggle_maximize command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-inner-position" - ] + "const": "core:window:allow-toggle-maximize" }, { - "description": "core:window:deny-inner-size -> Denies the inner_size command without any pre-configured scope.", + "description": "Enables the unmaximize command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-inner-size" - ] + "const": "core:window:allow-unmaximize" }, { - "description": "core:window:deny-internal-toggle-maximize -> Denies the internal_toggle_maximize command without any pre-configured scope.", + "description": "Enables the unminimize command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-internal-toggle-maximize" - ] + "const": "core:window:allow-unminimize" }, { - "description": "core:window:deny-is-closable -> Denies the is_closable command without any pre-configured scope.", + "description": "Denies the available_monitors command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-is-closable" - ] + "const": "core:window:deny-available-monitors" }, { - "description": "core:window:deny-is-decorated -> Denies the is_decorated command without any pre-configured scope.", + "description": "Denies the center command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-is-decorated" - ] + "const": "core:window:deny-center" }, { - "description": "core:window:deny-is-focused -> Denies the is_focused command without any pre-configured scope.", + "description": "Denies the close command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-is-focused" - ] + "const": "core:window:deny-close" }, { - "description": "core:window:deny-is-fullscreen -> Denies the is_fullscreen command without any pre-configured scope.", + "description": "Denies the create command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-is-fullscreen" - ] + "const": "core:window:deny-create" }, { - "description": "core:window:deny-is-maximizable -> Denies the is_maximizable command without any pre-configured scope.", + "description": "Denies the current_monitor command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-is-maximizable" - ] + "const": "core:window:deny-current-monitor" }, { - "description": "core:window:deny-is-maximized -> Denies the is_maximized command without any pre-configured scope.", + "description": "Denies the cursor_position command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-is-maximized" - ] + "const": "core:window:deny-cursor-position" }, { - "description": "core:window:deny-is-minimizable -> Denies the is_minimizable command without any pre-configured scope.", + "description": "Denies the destroy command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-is-minimizable" - ] + "const": "core:window:deny-destroy" }, { - "description": "core:window:deny-is-minimized -> Denies the is_minimized command without any pre-configured scope.", + "description": "Denies the get_all_windows command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-is-minimized" - ] + "const": "core:window:deny-get-all-windows" }, { - "description": "core:window:deny-is-resizable -> Denies the is_resizable command without any pre-configured scope.", + "description": "Denies the hide command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-is-resizable" - ] + "const": "core:window:deny-hide" }, { - "description": "core:window:deny-is-visible -> Denies the is_visible command without any pre-configured scope.", + "description": "Denies the inner_position command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-is-visible" - ] + "const": "core:window:deny-inner-position" }, { - "description": "core:window:deny-maximize -> Denies the maximize command without any pre-configured scope.", + "description": "Denies the inner_size command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-maximize" - ] + "const": "core:window:deny-inner-size" }, { - "description": "core:window:deny-minimize -> Denies the minimize command without any pre-configured scope.", + "description": "Denies the internal_toggle_maximize command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-minimize" - ] + "const": "core:window:deny-internal-toggle-maximize" }, { - "description": "core:window:deny-monitor-from-point -> Denies the monitor_from_point command without any pre-configured scope.", + "description": "Denies the is_closable command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-monitor-from-point" - ] + "const": "core:window:deny-is-closable" }, { - "description": "core:window:deny-outer-position -> Denies the outer_position command without any pre-configured scope.", + "description": "Denies the is_decorated command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-outer-position" - ] + "const": "core:window:deny-is-decorated" }, { - "description": "core:window:deny-outer-size -> Denies the outer_size command without any pre-configured scope.", + "description": "Denies the is_enabled command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-outer-size" - ] + "const": "core:window:deny-is-enabled" }, { - "description": "core:window:deny-primary-monitor -> Denies the primary_monitor command without any pre-configured scope.", + "description": "Denies the is_focused command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-primary-monitor" - ] + "const": "core:window:deny-is-focused" }, { - "description": "core:window:deny-request-user-attention -> Denies the request_user_attention command without any pre-configured scope.", + "description": "Denies the is_fullscreen command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-request-user-attention" - ] + "const": "core:window:deny-is-fullscreen" }, { - "description": "core:window:deny-scale-factor -> Denies the scale_factor command without any pre-configured scope.", + "description": "Denies the is_maximizable command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-scale-factor" - ] + "const": "core:window:deny-is-maximizable" }, { - "description": "core:window:deny-set-always-on-bottom -> Denies the set_always_on_bottom command without any pre-configured scope.", + "description": "Denies the is_maximized command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-set-always-on-bottom" - ] + "const": "core:window:deny-is-maximized" }, { - "description": "core:window:deny-set-always-on-top -> Denies the set_always_on_top command without any pre-configured scope.", + "description": "Denies the is_minimizable command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-set-always-on-top" - ] + "const": "core:window:deny-is-minimizable" }, { - "description": "core:window:deny-set-closable -> Denies the set_closable command without any pre-configured scope.", + "description": "Denies the is_minimized command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-set-closable" - ] + "const": "core:window:deny-is-minimized" }, { - "description": "core:window:deny-set-content-protected -> Denies the set_content_protected command without any pre-configured scope.", + "description": "Denies the is_resizable command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-set-content-protected" - ] + "const": "core:window:deny-is-resizable" }, { - "description": "core:window:deny-set-cursor-grab -> Denies the set_cursor_grab command without any pre-configured scope.", + "description": "Denies the is_visible command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-set-cursor-grab" - ] + "const": "core:window:deny-is-visible" }, { - "description": "core:window:deny-set-cursor-icon -> Denies the set_cursor_icon command without any pre-configured scope.", + "description": "Denies the maximize command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-set-cursor-icon" - ] + "const": "core:window:deny-maximize" }, { - "description": "core:window:deny-set-cursor-position -> Denies the set_cursor_position command without any pre-configured scope.", + "description": "Denies the minimize command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-set-cursor-position" - ] + "const": "core:window:deny-minimize" }, { - "description": "core:window:deny-set-cursor-visible -> Denies the set_cursor_visible command without any pre-configured scope.", + "description": "Denies the monitor_from_point command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-set-cursor-visible" - ] + "const": "core:window:deny-monitor-from-point" }, { - "description": "core:window:deny-set-decorations -> Denies the set_decorations command without any pre-configured scope.", + "description": "Denies the outer_position command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-set-decorations" - ] + "const": "core:window:deny-outer-position" }, { - "description": "core:window:deny-set-effects -> Denies the set_effects command without any pre-configured scope.", + "description": "Denies the outer_size command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-set-effects" - ] + "const": "core:window:deny-outer-size" }, { - "description": "core:window:deny-set-focus -> Denies the set_focus command without any pre-configured scope.", + "description": "Denies the primary_monitor command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-set-focus" - ] + "const": "core:window:deny-primary-monitor" }, { - "description": "core:window:deny-set-fullscreen -> Denies the set_fullscreen command without any pre-configured scope.", + "description": "Denies the request_user_attention command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-set-fullscreen" - ] + "const": "core:window:deny-request-user-attention" }, { - "description": "core:window:deny-set-icon -> Denies the set_icon command without any pre-configured scope.", + "description": "Denies the scale_factor command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-set-icon" - ] + "const": "core:window:deny-scale-factor" }, { - "description": "core:window:deny-set-ignore-cursor-events -> Denies the set_ignore_cursor_events command without any pre-configured scope.", + "description": "Denies the set_always_on_bottom command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-set-ignore-cursor-events" - ] + "const": "core:window:deny-set-always-on-bottom" }, { - "description": "core:window:deny-set-max-size -> Denies the set_max_size command without any pre-configured scope.", + "description": "Denies the set_always_on_top command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-set-max-size" - ] + "const": "core:window:deny-set-always-on-top" }, { - "description": "core:window:deny-set-maximizable -> Denies the set_maximizable command without any pre-configured scope.", + "description": "Denies the set_closable command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-set-maximizable" - ] + "const": "core:window:deny-set-closable" }, { - "description": "core:window:deny-set-min-size -> Denies the set_min_size command without any pre-configured scope.", + "description": "Denies the set_content_protected command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-set-min-size" - ] + "const": "core:window:deny-set-content-protected" }, { - "description": "core:window:deny-set-minimizable -> Denies the set_minimizable command without any pre-configured scope.", + "description": "Denies the set_cursor_grab command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-set-minimizable" - ] + "const": "core:window:deny-set-cursor-grab" }, { - "description": "core:window:deny-set-position -> Denies the set_position command without any pre-configured scope.", + "description": "Denies the set_cursor_icon command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-set-position" - ] + "const": "core:window:deny-set-cursor-icon" }, { - "description": "core:window:deny-set-progress-bar -> Denies the set_progress_bar command without any pre-configured scope.", + "description": "Denies the set_cursor_position command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-set-progress-bar" - ] + "const": "core:window:deny-set-cursor-position" }, { - "description": "core:window:deny-set-resizable -> Denies the set_resizable command without any pre-configured scope.", + "description": "Denies the set_cursor_visible command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-set-resizable" - ] + "const": "core:window:deny-set-cursor-visible" }, { - "description": "core:window:deny-set-shadow -> Denies the set_shadow command without any pre-configured scope.", + "description": "Denies the set_decorations command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-set-shadow" - ] + "const": "core:window:deny-set-decorations" }, { - "description": "core:window:deny-set-size -> Denies the set_size command without any pre-configured scope.", + "description": "Denies the set_effects command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-set-size" - ] + "const": "core:window:deny-set-effects" }, { - "description": "core:window:deny-set-size-constraints -> Denies the set_size_constraints command without any pre-configured scope.", + "description": "Denies the set_enabled command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-set-size-constraints" - ] + "const": "core:window:deny-set-enabled" }, { - "description": "core:window:deny-set-skip-taskbar -> Denies the set_skip_taskbar command without any pre-configured scope.", + "description": "Denies the set_focus command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-set-skip-taskbar" - ] + "const": "core:window:deny-set-focus" }, { - "description": "core:window:deny-set-title -> Denies the set_title command without any pre-configured scope.", + "description": "Denies the set_fullscreen command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-set-title" - ] + "const": "core:window:deny-set-fullscreen" }, { - "description": "core:window:deny-set-title-bar-style -> Denies the set_title_bar_style command without any pre-configured scope.", + "description": "Denies the set_icon command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-set-title-bar-style" - ] + "const": "core:window:deny-set-icon" }, { - "description": "core:window:deny-set-visible-on-all-workspaces -> Denies the set_visible_on_all_workspaces command without any pre-configured scope.", + "description": "Denies the set_ignore_cursor_events command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-set-visible-on-all-workspaces" - ] + "const": "core:window:deny-set-ignore-cursor-events" }, { - "description": "core:window:deny-show -> Denies the show command without any pre-configured scope.", + "description": "Denies the set_max_size command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-show" - ] + "const": "core:window:deny-set-max-size" }, { - "description": "core:window:deny-start-dragging -> Denies the start_dragging command without any pre-configured scope.", + "description": "Denies the set_maximizable command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-start-dragging" - ] + "const": "core:window:deny-set-maximizable" }, { - "description": "core:window:deny-start-resize-dragging -> Denies the start_resize_dragging command without any pre-configured scope.", + "description": "Denies the set_min_size command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-start-resize-dragging" - ] + "const": "core:window:deny-set-min-size" }, { - "description": "core:window:deny-theme -> Denies the theme command without any pre-configured scope.", + "description": "Denies the set_minimizable command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-theme" - ] + "const": "core:window:deny-set-minimizable" }, { - "description": "core:window:deny-title -> Denies the title command without any pre-configured scope.", + "description": "Denies the set_position command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-title" - ] + "const": "core:window:deny-set-position" }, { - "description": "core:window:deny-toggle-maximize -> Denies the toggle_maximize command without any pre-configured scope.", + "description": "Denies the set_progress_bar command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-toggle-maximize" - ] + "const": "core:window:deny-set-progress-bar" }, { - "description": "core:window:deny-unmaximize -> Denies the unmaximize command without any pre-configured scope.", + "description": "Denies the set_resizable command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-unmaximize" - ] + "const": "core:window:deny-set-resizable" }, { - "description": "core:window:deny-unminimize -> Denies the unminimize command without any pre-configured scope.", + "description": "Denies the set_shadow command without any pre-configured scope.", "type": "string", - "enum": [ - "core:window:deny-unminimize" - ] + "const": "core:window:deny-set-shadow" }, { - "description": "dialog:default -> This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n", + "description": "Denies the set_size command without any pre-configured scope.", "type": "string", - "enum": [ - "dialog:default" - ] + "const": "core:window:deny-set-size" }, { - "description": "dialog:allow-ask -> Enables the ask command without any pre-configured scope.", + "description": "Denies the set_size_constraints command without any pre-configured scope.", "type": "string", - "enum": [ - "dialog:allow-ask" - ] + "const": "core:window:deny-set-size-constraints" }, { - "description": "dialog:allow-confirm -> Enables the confirm command without any pre-configured scope.", + "description": "Denies the set_skip_taskbar command without any pre-configured scope.", "type": "string", - "enum": [ - "dialog:allow-confirm" - ] + "const": "core:window:deny-set-skip-taskbar" }, { - "description": "dialog:allow-message -> Enables the message command without any pre-configured scope.", + "description": "Denies the set_theme command without any pre-configured scope.", "type": "string", - "enum": [ - "dialog:allow-message" - ] + "const": "core:window:deny-set-theme" }, { - "description": "dialog:allow-open -> Enables the open command without any pre-configured scope.", + "description": "Denies the set_title command without any pre-configured scope.", "type": "string", - "enum": [ - "dialog:allow-open" - ] + "const": "core:window:deny-set-title" }, { - "description": "dialog:allow-save -> Enables the save command without any pre-configured scope.", + "description": "Denies the set_title_bar_style command without any pre-configured scope.", "type": "string", - "enum": [ - "dialog:allow-save" - ] + "const": "core:window:deny-set-title-bar-style" }, { - "description": "dialog:deny-ask -> Denies the ask command without any pre-configured scope.", + "description": "Denies the set_visible_on_all_workspaces command without any pre-configured scope.", "type": "string", - "enum": [ - "dialog:deny-ask" - ] + "const": "core:window:deny-set-visible-on-all-workspaces" }, { - "description": "dialog:deny-confirm -> Denies the confirm command without any pre-configured scope.", + "description": "Denies the show command without any pre-configured scope.", "type": "string", - "enum": [ - "dialog:deny-confirm" - ] + "const": "core:window:deny-show" }, { - "description": "dialog:deny-message -> Denies the message command without any pre-configured scope.", + "description": "Denies the start_dragging command without any pre-configured scope.", "type": "string", - "enum": [ - "dialog:deny-message" - ] + "const": "core:window:deny-start-dragging" }, { - "description": "dialog:deny-open -> Denies the open command without any pre-configured scope.", + "description": "Denies the start_resize_dragging command without any pre-configured scope.", "type": "string", - "enum": [ - "dialog:deny-open" - ] + "const": "core:window:deny-start-resize-dragging" }, { - "description": "dialog:deny-save -> Denies the save command without any pre-configured scope.", + "description": "Denies the theme command without any pre-configured scope.", "type": "string", - "enum": [ - "dialog:deny-save" - ] + "const": "core:window:deny-theme" }, { - "description": "log:default -> Allows the log command", + "description": "Denies the title command without any pre-configured scope.", "type": "string", - "enum": [ - "log:default" - ] + "const": "core:window:deny-title" }, { - "description": "log:allow-log -> Enables the log command without any pre-configured scope.", + "description": "Denies the toggle_maximize command without any pre-configured scope.", "type": "string", - "enum": [ - "log:allow-log" - ] + "const": "core:window:deny-toggle-maximize" }, { - "description": "log:deny-log -> Denies the log command without any pre-configured scope.", + "description": "Denies the unmaximize command without any pre-configured scope.", "type": "string", - "enum": [ - "log:deny-log" - ] + "const": "core:window:deny-unmaximize" }, { - "description": "notification:default -> This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n", + "description": "Denies the unminimize command without any pre-configured scope.", "type": "string", - "enum": [ - "notification:default" - ] + "const": "core:window:deny-unminimize" }, { - "description": "notification:allow-batch -> Enables the batch command without any pre-configured scope.", + "description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n", "type": "string", - "enum": [ - "notification:allow-batch" - ] + "const": "dialog:default" }, { - "description": "notification:allow-cancel -> Enables the cancel command without any pre-configured scope.", + "description": "Enables the ask command without any pre-configured scope.", "type": "string", - "enum": [ - "notification:allow-cancel" - ] + "const": "dialog:allow-ask" }, { - "description": "notification:allow-check-permissions -> Enables the check_permissions command without any pre-configured scope.", + "description": "Enables the confirm command without any pre-configured scope.", "type": "string", - "enum": [ - "notification:allow-check-permissions" - ] + "const": "dialog:allow-confirm" }, { - "description": "notification:allow-create-channel -> Enables the create_channel command without any pre-configured scope.", + "description": "Enables the message command without any pre-configured scope.", "type": "string", - "enum": [ - "notification:allow-create-channel" - ] + "const": "dialog:allow-message" }, { - "description": "notification:allow-delete-channel -> Enables the delete_channel command without any pre-configured scope.", + "description": "Enables the open command without any pre-configured scope.", "type": "string", - "enum": [ - "notification:allow-delete-channel" - ] + "const": "dialog:allow-open" }, { - "description": "notification:allow-get-active -> Enables the get_active command without any pre-configured scope.", + "description": "Enables the save command without any pre-configured scope.", "type": "string", - "enum": [ - "notification:allow-get-active" - ] + "const": "dialog:allow-save" }, { - "description": "notification:allow-get-pending -> Enables the get_pending command without any pre-configured scope.", + "description": "Denies the ask command without any pre-configured scope.", "type": "string", - "enum": [ - "notification:allow-get-pending" - ] + "const": "dialog:deny-ask" }, { - "description": "notification:allow-is-permission-granted -> Enables the is_permission_granted command without any pre-configured scope.", + "description": "Denies the confirm command without any pre-configured scope.", "type": "string", - "enum": [ - "notification:allow-is-permission-granted" - ] + "const": "dialog:deny-confirm" }, { - "description": "notification:allow-list-channels -> Enables the list_channels command without any pre-configured scope.", + "description": "Denies the message command without any pre-configured scope.", "type": "string", - "enum": [ - "notification:allow-list-channels" - ] + "const": "dialog:deny-message" }, { - "description": "notification:allow-notify -> Enables the notify command without any pre-configured scope.", + "description": "Denies the open command without any pre-configured scope.", "type": "string", - "enum": [ - "notification:allow-notify" - ] + "const": "dialog:deny-open" }, { - "description": "notification:allow-permission-state -> Enables the permission_state command without any pre-configured scope.", + "description": "Denies the save command without any pre-configured scope.", "type": "string", - "enum": [ - "notification:allow-permission-state" - ] + "const": "dialog:deny-save" }, { - "description": "notification:allow-register-action-types -> Enables the register_action_types command without any pre-configured scope.", + "description": "Allows the log command", "type": "string", - "enum": [ - "notification:allow-register-action-types" - ] + "const": "log:default" }, { - "description": "notification:allow-register-listener -> Enables the register_listener command without any pre-configured scope.", + "description": "Enables the log command without any pre-configured scope.", "type": "string", - "enum": [ - "notification:allow-register-listener" - ] + "const": "log:allow-log" }, { - "description": "notification:allow-remove-active -> Enables the remove_active command without any pre-configured scope.", + "description": "Denies the log command without any pre-configured scope.", "type": "string", - "enum": [ - "notification:allow-remove-active" - ] + "const": "log:deny-log" }, { - "description": "notification:allow-request-permission -> Enables the request_permission command without any pre-configured scope.", + "description": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n", "type": "string", - "enum": [ - "notification:allow-request-permission" - ] + "const": "notification:default" }, { - "description": "notification:allow-show -> Enables the show command without any pre-configured scope.", + "description": "Enables the batch command without any pre-configured scope.", "type": "string", - "enum": [ - "notification:allow-show" - ] + "const": "notification:allow-batch" }, { - "description": "notification:deny-batch -> Denies the batch command without any pre-configured scope.", + "description": "Enables the cancel command without any pre-configured scope.", "type": "string", - "enum": [ - "notification:deny-batch" - ] + "const": "notification:allow-cancel" }, { - "description": "notification:deny-cancel -> Denies the cancel command without any pre-configured scope.", + "description": "Enables the check_permissions command without any pre-configured scope.", "type": "string", - "enum": [ - "notification:deny-cancel" - ] + "const": "notification:allow-check-permissions" }, { - "description": "notification:deny-check-permissions -> Denies the check_permissions command without any pre-configured scope.", + "description": "Enables the create_channel command without any pre-configured scope.", "type": "string", - "enum": [ - "notification:deny-check-permissions" - ] + "const": "notification:allow-create-channel" }, { - "description": "notification:deny-create-channel -> Denies the create_channel command without any pre-configured scope.", + "description": "Enables the delete_channel command without any pre-configured scope.", "type": "string", - "enum": [ - "notification:deny-create-channel" - ] + "const": "notification:allow-delete-channel" }, { - "description": "notification:deny-delete-channel -> Denies the delete_channel command without any pre-configured scope.", + "description": "Enables the get_active command without any pre-configured scope.", "type": "string", - "enum": [ - "notification:deny-delete-channel" - ] + "const": "notification:allow-get-active" }, { - "description": "notification:deny-get-active -> Denies the get_active command without any pre-configured scope.", + "description": "Enables the get_pending command without any pre-configured scope.", "type": "string", - "enum": [ - "notification:deny-get-active" - ] + "const": "notification:allow-get-pending" }, { - "description": "notification:deny-get-pending -> Denies the get_pending command without any pre-configured scope.", + "description": "Enables the is_permission_granted command without any pre-configured scope.", "type": "string", - "enum": [ - "notification:deny-get-pending" - ] + "const": "notification:allow-is-permission-granted" }, { - "description": "notification:deny-is-permission-granted -> Denies the is_permission_granted command without any pre-configured scope.", + "description": "Enables the list_channels command without any pre-configured scope.", "type": "string", - "enum": [ - "notification:deny-is-permission-granted" - ] + "const": "notification:allow-list-channels" }, { - "description": "notification:deny-list-channels -> Denies the list_channels command without any pre-configured scope.", + "description": "Enables the notify command without any pre-configured scope.", "type": "string", - "enum": [ - "notification:deny-list-channels" - ] + "const": "notification:allow-notify" }, { - "description": "notification:deny-notify -> Denies the notify command without any pre-configured scope.", + "description": "Enables the permission_state command without any pre-configured scope.", "type": "string", - "enum": [ - "notification:deny-notify" - ] + "const": "notification:allow-permission-state" }, { - "description": "notification:deny-permission-state -> Denies the permission_state command without any pre-configured scope.", + "description": "Enables the register_action_types command without any pre-configured scope.", "type": "string", - "enum": [ - "notification:deny-permission-state" - ] + "const": "notification:allow-register-action-types" }, { - "description": "notification:deny-register-action-types -> Denies the register_action_types command without any pre-configured scope.", + "description": "Enables the register_listener command without any pre-configured scope.", "type": "string", - "enum": [ - "notification:deny-register-action-types" - ] + "const": "notification:allow-register-listener" }, { - "description": "notification:deny-register-listener -> Denies the register_listener command without any pre-configured scope.", + "description": "Enables the remove_active command without any pre-configured scope.", "type": "string", - "enum": [ - "notification:deny-register-listener" - ] + "const": "notification:allow-remove-active" }, { - "description": "notification:deny-remove-active -> Denies the remove_active command without any pre-configured scope.", + "description": "Enables the request_permission command without any pre-configured scope.", "type": "string", - "enum": [ - "notification:deny-remove-active" - ] + "const": "notification:allow-request-permission" }, { - "description": "notification:deny-request-permission -> Denies the request_permission command without any pre-configured scope.", + "description": "Enables the show command without any pre-configured scope.", "type": "string", - "enum": [ - "notification:deny-request-permission" - ] + "const": "notification:allow-show" }, { - "description": "notification:deny-show -> Denies the show command without any pre-configured scope.", + "description": "Denies the batch command without any pre-configured scope.", "type": "string", - "enum": [ - "notification:deny-show" - ] + "const": "notification:deny-batch" }, { - "description": "os:default -> This permission set configures which\noperating system information are available\nto gather from the frontend.\n\n#### Granted Permissions\n\nAll information except the host name are available.\n\n", + "description": "Denies the cancel command without any pre-configured scope.", "type": "string", - "enum": [ - "os:default" - ] + "const": "notification:deny-cancel" }, { - "description": "os:allow-arch -> Enables the arch command without any pre-configured scope.", + "description": "Denies the check_permissions command without any pre-configured scope.", "type": "string", - "enum": [ - "os:allow-arch" - ] + "const": "notification:deny-check-permissions" }, { - "description": "os:allow-exe-extension -> Enables the exe_extension command without any pre-configured scope.", + "description": "Denies the create_channel command without any pre-configured scope.", "type": "string", - "enum": [ - "os:allow-exe-extension" - ] + "const": "notification:deny-create-channel" }, { - "description": "os:allow-family -> Enables the family command without any pre-configured scope.", + "description": "Denies the delete_channel command without any pre-configured scope.", "type": "string", - "enum": [ - "os:allow-family" - ] + "const": "notification:deny-delete-channel" }, { - "description": "os:allow-hostname -> Enables the hostname command without any pre-configured scope.", + "description": "Denies the get_active command without any pre-configured scope.", "type": "string", - "enum": [ - "os:allow-hostname" - ] + "const": "notification:deny-get-active" }, { - "description": "os:allow-locale -> Enables the locale command without any pre-configured scope.", + "description": "Denies the get_pending command without any pre-configured scope.", "type": "string", - "enum": [ - "os:allow-locale" - ] + "const": "notification:deny-get-pending" }, { - "description": "os:allow-os-type -> Enables the os_type command without any pre-configured scope.", + "description": "Denies the is_permission_granted command without any pre-configured scope.", "type": "string", - "enum": [ - "os:allow-os-type" - ] + "const": "notification:deny-is-permission-granted" }, { - "description": "os:allow-platform -> Enables the platform command without any pre-configured scope.", + "description": "Denies the list_channels command without any pre-configured scope.", "type": "string", - "enum": [ - "os:allow-platform" - ] + "const": "notification:deny-list-channels" }, { - "description": "os:allow-version -> Enables the version command without any pre-configured scope.", + "description": "Denies the notify command without any pre-configured scope.", "type": "string", - "enum": [ - "os:allow-version" - ] + "const": "notification:deny-notify" }, { - "description": "os:deny-arch -> Denies the arch command without any pre-configured scope.", + "description": "Denies the permission_state command without any pre-configured scope.", "type": "string", - "enum": [ - "os:deny-arch" - ] + "const": "notification:deny-permission-state" }, { - "description": "os:deny-exe-extension -> Denies the exe_extension command without any pre-configured scope.", + "description": "Denies the register_action_types command without any pre-configured scope.", "type": "string", - "enum": [ - "os:deny-exe-extension" - ] + "const": "notification:deny-register-action-types" }, { - "description": "os:deny-family -> Denies the family command without any pre-configured scope.", + "description": "Denies the register_listener command without any pre-configured scope.", "type": "string", - "enum": [ - "os:deny-family" - ] + "const": "notification:deny-register-listener" }, { - "description": "os:deny-hostname -> Denies the hostname command without any pre-configured scope.", + "description": "Denies the remove_active command without any pre-configured scope.", "type": "string", - "enum": [ - "os:deny-hostname" - ] + "const": "notification:deny-remove-active" }, { - "description": "os:deny-locale -> Denies the locale command without any pre-configured scope.", + "description": "Denies the request_permission command without any pre-configured scope.", "type": "string", - "enum": [ - "os:deny-locale" - ] + "const": "notification:deny-request-permission" }, { - "description": "os:deny-os-type -> Denies the os_type command without any pre-configured scope.", + "description": "Denies the show command without any pre-configured scope.", "type": "string", - "enum": [ - "os:deny-os-type" - ] + "const": "notification:deny-show" }, { - "description": "os:deny-platform -> Denies the platform command without any pre-configured scope.", + "description": "This permission set configures which\noperating system information are available\nto gather from the frontend.\n\n#### Granted Permissions\n\nAll information except the host name are available.\n\n", "type": "string", - "enum": [ - "os:deny-platform" - ] + "const": "os:default" }, { - "description": "os:deny-version -> Denies the version command without any pre-configured scope.", + "description": "Enables the arch command without any pre-configured scope.", "type": "string", - "enum": [ - "os:deny-version" - ] + "const": "os:allow-arch" }, { - "description": "shell:default -> This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality without any specific\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n", + "description": "Enables the exe_extension command without any pre-configured scope.", "type": "string", - "enum": [ - "shell:default" - ] + "const": "os:allow-exe-extension" }, { - "description": "shell:allow-execute -> Enables the execute command without any pre-configured scope.", + "description": "Enables the family command without any pre-configured scope.", "type": "string", - "enum": [ - "shell:allow-execute" - ] + "const": "os:allow-family" }, { - "description": "shell:allow-kill -> Enables the kill command without any pre-configured scope.", + "description": "Enables the hostname command without any pre-configured scope.", "type": "string", - "enum": [ - "shell:allow-kill" - ] + "const": "os:allow-hostname" }, { - "description": "shell:allow-open -> Enables the open command without any pre-configured scope.", + "description": "Enables the locale command without any pre-configured scope.", "type": "string", - "enum": [ - "shell:allow-open" - ] + "const": "os:allow-locale" }, { - "description": "shell:allow-spawn -> Enables the spawn command without any pre-configured scope.", + "description": "Enables the os_type command without any pre-configured scope.", "type": "string", - "enum": [ - "shell:allow-spawn" - ] + "const": "os:allow-os-type" }, { - "description": "shell:allow-stdin-write -> Enables the stdin_write command without any pre-configured scope.", + "description": "Enables the platform command without any pre-configured scope.", "type": "string", - "enum": [ - "shell:allow-stdin-write" - ] + "const": "os:allow-platform" }, { - "description": "shell:deny-execute -> Denies the execute command without any pre-configured scope.", + "description": "Enables the version command without any pre-configured scope.", "type": "string", - "enum": [ - "shell:deny-execute" - ] + "const": "os:allow-version" }, { - "description": "shell:deny-kill -> Denies the kill command without any pre-configured scope.", + "description": "Denies the arch command without any pre-configured scope.", "type": "string", - "enum": [ - "shell:deny-kill" - ] + "const": "os:deny-arch" }, { - "description": "shell:deny-open -> Denies the open command without any pre-configured scope.", + "description": "Denies the exe_extension command without any pre-configured scope.", "type": "string", - "enum": [ - "shell:deny-open" - ] + "const": "os:deny-exe-extension" }, { - "description": "shell:deny-spawn -> Denies the spawn command without any pre-configured scope.", + "description": "Denies the family command without any pre-configured scope.", "type": "string", - "enum": [ - "shell:deny-spawn" - ] + "const": "os:deny-family" }, { - "description": "shell:deny-stdin-write -> Denies the stdin_write command without any pre-configured scope.", + "description": "Denies the hostname command without any pre-configured scope.", "type": "string", - "enum": [ - "shell:deny-stdin-write" - ] + "const": "os:deny-hostname" }, { - "description": "window-state:default -> This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n", + "description": "Denies the locale command without any pre-configured scope.", "type": "string", - "enum": [ - "window-state:default" - ] + "const": "os:deny-locale" }, { - "description": "window-state:allow-filename -> Enables the filename command without any pre-configured scope.", + "description": "Denies the os_type command without any pre-configured scope.", "type": "string", - "enum": [ - "window-state:allow-filename" - ] + "const": "os:deny-os-type" }, { - "description": "window-state:allow-restore-state -> Enables the restore_state command without any pre-configured scope.", + "description": "Denies the platform command without any pre-configured scope.", "type": "string", - "enum": [ - "window-state:allow-restore-state" - ] + "const": "os:deny-platform" }, { - "description": "window-state:allow-save-window-state -> Enables the save_window_state command without any pre-configured scope.", + "description": "Denies the version command without any pre-configured scope.", "type": "string", - "enum": [ - "window-state:allow-save-window-state" - ] + "const": "os:deny-version" }, { - "description": "window-state:deny-filename -> Denies the filename command without any pre-configured scope.", + "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality without any specific\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n", "type": "string", - "enum": [ - "window-state:deny-filename" - ] + "const": "shell:default" }, { - "description": "window-state:deny-restore-state -> Denies the restore_state command without any pre-configured scope.", + "description": "Enables the execute command without any pre-configured scope.", "type": "string", - "enum": [ - "window-state:deny-restore-state" - ] + "const": "shell:allow-execute" }, { - "description": "window-state:deny-save-window-state -> Denies the save_window_state command without any pre-configured scope.", + "description": "Enables the kill command without any pre-configured scope.", "type": "string", - "enum": [ - "window-state:deny-save-window-state" - ] + "const": "shell:allow-kill" + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-open" + }, + { + "description": "Enables the spawn command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-spawn" + }, + { + "description": "Enables the stdin_write command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-stdin-write" + }, + { + "description": "Denies the execute command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-execute" + }, + { + "description": "Denies the kill command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-kill" + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-open" + }, + { + "description": "Denies the spawn command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-spawn" + }, + { + "description": "Denies the stdin_write command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-stdin-write" + }, + { + "description": "This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n", + "type": "string", + "const": "window-state:default" + }, + { + "description": "Enables the filename command without any pre-configured scope.", + "type": "string", + "const": "window-state:allow-filename" + }, + { + "description": "Enables the restore_state command without any pre-configured scope.", + "type": "string", + "const": "window-state:allow-restore-state" + }, + { + "description": "Enables the save_window_state command without any pre-configured scope.", + "type": "string", + "const": "window-state:allow-save-window-state" + }, + { + "description": "Denies the filename command without any pre-configured scope.", + "type": "string", + "const": "window-state:deny-filename" + }, + { + "description": "Denies the restore_state command without any pre-configured scope.", + "type": "string", + "const": "window-state:deny-restore-state" + }, + { + "description": "Denies the save_window_state command without any pre-configured scope.", + "type": "string", + "const": "window-state:deny-save-window-state" } ] }, @@ -3027,7 +2426,7 @@ } ] }, - "ShellAllowedArg": { + "ShellScopeEntryAllowedArg": { "description": "A command argument allowed to be executed by the webview API.", "anyOf": [ { @@ -3055,18 +2454,18 @@ } ] }, - "ShellAllowedArgs": { - "description": "A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration.", + "ShellScopeEntryAllowedArgs": { + "description": "A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellScopeEntryAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration.", "anyOf": [ { "description": "Use a simple boolean to allow all or disable all arguments to this command configuration.", "type": "boolean" }, { - "description": "A specific set of [`ShellAllowedArg`] that are valid to call for the command configuration.", + "description": "A specific set of [`ShellScopeEntryAllowedArg`] that are valid to call for the command configuration.", "type": "array", "items": { - "$ref": "#/definitions/ShellAllowedArg" + "$ref": "#/definitions/ShellScopeEntryAllowedArg" } } ] diff --git a/desktop/tauri/src-tauri/src/cli.rs b/desktop/tauri/src-tauri/src/cli.rs index d11c2aed..61a7462e 100644 --- a/desktop/tauri/src-tauri/src/cli.rs +++ b/desktop/tauri/src-tauri/src/cli.rs @@ -1,9 +1,9 @@ use log::LevelFilter; -#[cfg(not(debug_assertions))] -const DEFAULT_LOG_LEVEL: log::LevelFilter = log::LevelFilter::Warn; +// #[cfg(not(debug_assertions))] +// const DEFAULT_LOG_LEVEL: log::LevelFilter = log::LevelFilter::Warn; -#[cfg(debug_assertions)] +// #[cfg(debug_assertions)] const DEFAULT_LOG_LEVEL: log::LevelFilter = log::LevelFilter::Debug; #[derive(Debug)] @@ -43,8 +43,8 @@ pub fn parse(raw: impl IntoIterator>) -> Cl data: None, log_level: DEFAULT_LOG_LEVEL, background: false, - with_prompts: false, - with_notifications: false, + with_prompts: true, + with_notifications: true, }; let raw = clap_lex::RawArgs::new(raw); @@ -67,11 +67,11 @@ pub fn parse(raw: impl IntoIterator>) -> Cl Ok("background") => { cli.background = true; } - Ok("with_prompts") => { - cli.with_prompts = true; + Ok("no-prompts") => { + cli.with_prompts = false; } - Ok("with_notifications") => { - cli.with_notifications = true; + Ok("no-notifications") => { + cli.with_notifications = false; } _ => { // Ignore unexpected flags diff --git a/desktop/tauri/src-tauri/src/main.rs b/desktop/tauri/src-tauri/src/main.rs index cce1b266..360aee63 100644 --- a/desktop/tauri/src-tauri/src/main.rs +++ b/desktop/tauri/src-tauri/src/main.rs @@ -126,15 +126,16 @@ fn main() { let cli_args = cli::parse(std::env::args()); + // TODO(vladimir): Support for other log targets? #[cfg(target_os = "linux")] - let log_target = if let Some(data_dir) = cli_args.data { - tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::Folder { - path: Path::new(&format!("{}/logs/app2", data_dir)).into(), - file_name: None, - }) - } else { - tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::Stdout) - }; + let log_target = tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::Stdout); + // let log_target = if let Some(data_dir) = cli_args.data { + // tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::Folder { + // path: Path::new(&format!("{}/logs/app2", data_dir)).into(), + // file_name: None, + // }) + // } else { + // }; // TODO(vladimir): Permission for logs/app2 folder are not guaranteed. Use the default location for now. #[cfg(target_os = "windows")] diff --git a/desktop/tauri/src-tauri/src/portmaster/notifications.rs b/desktop/tauri/src-tauri/src/portmaster/notifications.rs index 452db996..7128eda0 100644 --- a/desktop/tauri/src-tauri/src/portmaster/notifications.rs +++ b/desktop/tauri/src-tauri/src/portmaster/notifications.rs @@ -2,6 +2,7 @@ use crate::portapi::client::*; use crate::portapi::message::*; use crate::portapi::models::notification::*; use crate::portapi::types::*; +use log::debug; use log::error; use serde_json::json; use tauri::async_runtime; @@ -25,12 +26,12 @@ pub async fn notification_handler(cli: PortAPI) { Ok(n) => { // Skip if this one should not be shown using the system notifications if !n.show_on_system { - return; + continue; } // Skip if this action has already been acted on - if n.selected_action_id.is_empty() { - return; + if !n.selected_action_id.is_empty() { + continue; } show_notification(&cli, key, n).await; } diff --git a/desktop/tauri/src-tauri/src/traymenu.rs b/desktop/tauri/src-tauri/src/traymenu.rs index 0983db28..777b9d23 100644 --- a/desktop/tauri/src-tauri/src/traymenu.rs +++ b/desktop/tauri/src-tauri/src/traymenu.rs @@ -30,7 +30,7 @@ use crate::{ portmaster::PortmasterExt, window::{create_main_window, may_navigate_to_ui, open_window}, }; -use tauri_plugin_dialog::DialogExt; +use tauri_plugin_dialog::{DialogExt, MessageDialogButtons}; pub type AppIcon = TrayIcon; @@ -185,7 +185,7 @@ pub fn setup_tray_menu( app.dialog() .message("This does not stop the Portmaster system service") .title("Do you really want to quit the user interface?") - .buttons(tauri_plugin_dialog::MessageDialogButtons::OkCancelCustom( + .buttons(MessageDialogButtons::OkCancelCustom( "Yes, exit".to_owned(), "No".to_owned(), )) diff --git a/desktop/tauri/src-tauri/tauri.conf.json5 b/desktop/tauri/src-tauri/tauri.conf.json5 index 5bd85972..00a9cced 100644 --- a/desktop/tauri/src-tauri/tauri.conf.json5 +++ b/desktop/tauri/src-tauri/tauri.conf.json5 @@ -28,12 +28,12 @@ "takesValue": true }, { - "name": "with-notifications", - "description": "Enable experimental notifications via Tauri. Replaces the notifier app." + "name": "no-notifications", + "description": "Disable notifications via Tauri." }, { - "name": "with-prompts", - "description": "Enable experimental prompt support via Tauri. Replaces the notifier app." + "name": "no-prompts", + "description": "Disable prompt support via Tauri." }, ] } @@ -63,13 +63,13 @@ "/usr/lib/systemd/system/portmaster.service": "../../../packaging/linux/portmaster.service", // Binary files - "/usr/lib/portmaster/bin-index.json": "binary/bin-index.json", + "/usr/lib/portmaster/index.json": "binary/index.json", "/usr/lib/portmaster/portmaster-core": "binary/portmaster-core", "/usr/lib/portmaster/portmaster.zip": "binary/portmaster.zip", "/usr/lib/portmaster/assets.zip": "binary/assets.zip", // Intel files - "/var/lib/portmaster/intel/intel-index.json": "intel/intel-index.json", + "/var/lib/portmaster/intel/index.json": "intel/index.json", "/var/lib/portmaster/intel/base.dsdl": "intel/base.dsdl", "/var/lib/portmaster/intel/geoipv4.mmdb": "intel/geoipv4.mmdb", "/var/lib/portmaster/intel/geoipv6.mmdb": "intel/geoipv6.mmdb", @@ -94,13 +94,13 @@ "/usr/lib/systemd/system/portmaster.service": "../../../packaging/linux/portmaster.service", // Binary files - "/usr/lib/portmaster/bin-index.json": "binary/bin-index.json", + "/usr/lib/portmaster/index.json": "binary/index.json", "/usr/lib/portmaster/portmaster-core": "binary/portmaster-core", "/usr/lib/portmaster/portmaster.zip": "binary/portmaster.zip", "/usr/lib/portmaster/assets.zip": "binary/assets.zip", // Intel files - "/var/lib/portmaster/intel/intel-index.json": "intel/intel-index.json", + "/var/lib/portmaster/intel/index.json": "intel/index.json", "/var/lib/portmaster/intel/base.dsdl": "intel/base.dsdl", "/var/lib/portmaster/intel/geoipv4.mmdb": "intel/geoipv4.mmdb", "/var/lib/portmaster/intel/geoipv6.mmdb": "intel/geoipv6.mmdb", diff --git a/desktop/tauri/src-tauri/templates/files.wxs b/desktop/tauri/src-tauri/templates/files.wxs index 4828178f..14159aa8 100644 --- a/desktop/tauri/src-tauri/templates/files.wxs +++ b/desktop/tauri/src-tauri/templates/files.wxs @@ -12,15 +12,17 @@ - + + + - + diff --git a/desktop/tauri/src-tauri/templates/nsis_install_hooks.nsh b/desktop/tauri/src-tauri/templates/nsis_install_hooks.nsh index 6b5afb58..cc6ea2f2 100644 --- a/desktop/tauri/src-tauri/templates/nsis_install_hooks.nsh +++ b/desktop/tauri/src-tauri/templates/nsis_install_hooks.nsh @@ -3,15 +3,17 @@ SetOutPath "$INSTDIR" - File "..\..\..\..\binary\bin-index.json" + File "..\..\..\..\binary\index.json" File "..\..\..\..\binary\portmaster-core.exe" File "..\..\..\..\binary\portmaster-kext.sys" + File "..\..\..\..\binary\portmaster-core.dll" + File "..\..\..\..\binary\WebView2Loader.dll" File "..\..\..\..\binary\portmaster.zip" File "..\..\..\..\binary\assets.zip" SetOutPath "$COMMONPROGRAMDATA\Portmaster\intel" - File "..\..\..\..\intel\intel-index.json" + File "..\..\..\..\intel\index.json" File "..\..\..\..\intel\base.dsdl" File "..\..\..\..\intel\geoipv4.mmdb" File "..\..\..\..\intel\geoipv6.mmdb" @@ -25,7 +27,7 @@ !macroend !macro NSIS_HOOK_POSTINSTALL - ExecWait 'sc.exe create PortmasterCore binPath= "$INSTDIR\portmaster-core.exe" --data="$COMMONPROGRAMDATA\Portmaster\data"' + ExecWait 'sc.exe create PortmasterCore binPath= "$INSTDIR\portmaster-core.exe --log-dir=%PROGRAMDATA%\Portmaster\logs"' !macroend !macro NSIS_HOOK_PREUNINSTALL diff --git a/desktop/tauri/src-tauri/templates/service.wxs b/desktop/tauri/src-tauri/templates/service.wxs index 4ade87cd..3e829bd7 100644 --- a/desktop/tauri/src-tauri/templates/service.wxs +++ b/desktop/tauri/src-tauri/templates/service.wxs @@ -3,7 +3,7 @@ $null } - Write-Output "Copying binary files" -Copy-Item -Force -Path "dist/binary/index.json" -Destination "$binaryDir/index.json" -Copy-Item -Force -Path "dist/binary/windows_amd64/portmaster-core.exe" -Destination "$binaryDir/portmaster-core.exe" -Copy-Item -Force -Path "dist/binary/windows_amd64/portmaster-kext.sys" -Destination "$binaryDir/portmaster-kext.sys" +Copy-Item -Force -Path "dist/download/windows_amd64/portmaster-core.exe" -Destination "$binaryDir/portmaster-core.exe" +Copy-Item -Force -Path "dist/download/windows_amd64/portmaster-kext.sys" -Destination "$binaryDir/portmaster-kext.sys" +Copy-Item -Force -Path "dist/download/windows_amd64/portmaster-kext.dll" -Destination "$binaryDir/portmaster-kext.dll" Copy-Item -Force -Path "dist/binary/all/portmaster.zip" -Destination "$binaryDir/portmaster.zip" Copy-Item -Force -Path "dist/binary/all/assets.zip" -Destination "$binaryDir/assets.zip" @@ -39,7 +38,7 @@ if (-not (Test-Path -Path $intelDir)) { } Write-Output "Copying intel files" -Copy-Item -Force -Path "dist/intel_decompressed/*" -Destination "$intelDir/" +Copy-Item -Force -Path "dist/intel/*" -Destination "$intelDir/" Set-Location $destinationDir @@ -53,7 +52,8 @@ if (-not (Get-Command cargo -ErrorAction SilentlyContinue)) { } Write-Output "Downloading tauri-cli" -Invoke-WebRequest -Uri https://github.com/tauri-apps/tauri/releases/download/tauri-cli-v2.0.1/cargo-tauri-x86_64-pc-windows-msvc.zip -OutFile tauri-cli.zip + +Invoke-WebRequest -Uri https://github.com/tauri-apps/tauri/releases/download/tauri-cli-v2.1.0/cargo-tauri-x86_64-pc-windows-msvc.zip -OutFile tauri-cli.zip Expand-Archive -Force tauri-cli.zip ./tauri-cli/cargo-tauri.exe bundle diff --git a/service/broadcasts/data.go b/service/broadcasts/data.go index e92ce84f..b9a1a453 100644 --- a/service/broadcasts/data.go +++ b/service/broadcasts/data.go @@ -5,6 +5,7 @@ import ( "time" "github.com/safing/portmaster/base/config" + "github.com/safing/portmaster/service/core" "github.com/safing/portmaster/service/intel/geoip" "github.com/safing/portmaster/service/netenv" "github.com/safing/portmaster/spn/access" @@ -17,19 +18,18 @@ var portmasterStarted = time.Now() func collectData() interface{} { data := make(map[string]interface{}) - // TODO(vladimir) // Get data about versions. - // versions := updates.GetSimpleVersions() - // data["Updates"] = versions - // data["Version"] = versions.Build.Version - // numericVersion, err := MakeNumericVersion(versions.Build.Version) - // if err != nil { - // data["NumericVersion"] = &DataError{ - // Error: err, - // } - // } else { - // data["NumericVersion"] = numericVersion - // } + versions := core.GetSimpleVersions() + data["Updates"] = versions + data["Version"] = versions.Build.Version + numericVersion, err := MakeNumericVersion(versions.Build.Version) + if err != nil { + data["NumericVersion"] = &DataError{ + Error: err, + } + } else { + data["NumericVersion"] = numericVersion + } // Get data about install. installInfo, err := GetInstallInfo() diff --git a/service/broadcasts/module.go b/service/broadcasts/module.go index a9a87074..08324ef6 100644 --- a/service/broadcasts/module.go +++ b/service/broadcasts/module.go @@ -43,10 +43,6 @@ var ( startOnce sync.Once ) -func init() { - // module = modules.Register("broadcasts", prep, start, nil, "updates", "netenv", "notifications") -} - func prep() error { // Register API endpoints. if err := registerAPIEndpoints(); err != nil { diff --git a/service/broadcasts/notify.go b/service/broadcasts/notify.go index 235a1bf0..73c9f232 100644 --- a/service/broadcasts/notify.go +++ b/service/broadcasts/notify.go @@ -21,7 +21,7 @@ import ( ) const ( - broadcastsResourcePath = "intel/portmaster/notifications.yaml" + broadcastsResourceName = "notifications.yaml" broadcastNotificationIDPrefix = "broadcasts:" @@ -67,7 +67,7 @@ type BroadcastNotification struct { func broadcastNotify(ctx *mgr.WorkerCtx) error { // Get broadcast notifications file, load it from disk and parse it. - broadcastsResource, err := module.instance.IntelUpdates().GetFile(broadcastsResourcePath) + broadcastsResource, err := module.instance.IntelUpdates().GetFile(broadcastsResourceName) if err != nil { return fmt.Errorf("failed to get broadcast notifications update: %w", err) } diff --git a/service/compat/callbacks.go b/service/compat/callbacks.go index 2abfa858..71fd8b69 100644 --- a/service/compat/callbacks.go +++ b/service/compat/callbacks.go @@ -3,6 +3,7 @@ package compat import ( "net" + "github.com/safing/portmaster/service/mgr" "github.com/safing/portmaster/service/network/packet" "github.com/safing/portmaster/service/process" ) @@ -31,10 +32,16 @@ func SubmitDNSCheckDomain(subdomain string) (respondWith net.IP) { // ReportSecureDNSBypassIssue reports a DNS bypassing issue for the given process. func ReportSecureDNSBypassIssue(p *process.Process) { - secureDNSBypassIssue.notify(p) + module.mgr.Go("report secure dns bypass issue", func(w *mgr.WorkerCtx) error { + secureDNSBypassIssue.notify(p) + return nil + }) } // ReportMultiPeerUDPTunnelIssue reports a multi-peer UDP tunnel for the given process. func ReportMultiPeerUDPTunnelIssue(p *process.Process) { - multiPeerUDPTunnelIssue.notify(p) + module.mgr.Go("report multi-peer udp tunnel issue", func(w *mgr.WorkerCtx) error { + multiPeerUDPTunnelIssue.notify(p) + return nil + }) } diff --git a/service/compat/module.go b/service/compat/module.go index 5ac97b51..e6781c9b 100644 --- a/service/compat/module.go +++ b/service/compat/module.go @@ -181,4 +181,5 @@ func New(instance instance) (*Compat, error) { type instance interface { NetEnv() *netenv.NetEnv + Resolver() *resolver.ResolverModule } diff --git a/service/compat/selfcheck.go b/service/compat/selfcheck.go index 27efd488..0bbef4e4 100644 --- a/service/compat/selfcheck.go +++ b/service/compat/selfcheck.go @@ -158,6 +158,12 @@ func selfcheck(ctx context.Context) (issue *systemIssue, err error) { // Step 3: Have the nameserver respond with random data in the answer section. + // Check if the resolver is enabled + if module.instance.Resolver().IsDisabled() { + // There is no control over the response, there is nothing more that can be checked. + return nil, nil + } + // Wait for the reply from the resolver. select { case err := <-dnsCheckLookupError: diff --git a/service/config.go b/service/config.go index 7b7e0f08..f7b1f12c 100644 --- a/service/config.go +++ b/service/config.go @@ -9,6 +9,8 @@ import ( "github.com/safing/jess" "github.com/safing/portmaster/base/log" + "github.com/safing/portmaster/service/configure" + "github.com/safing/portmaster/service/updates" ) type ServiceConfig struct { @@ -76,11 +78,10 @@ func (sc *ServiceConfig) Init() error { // Apply defaults for required fields. if len(sc.BinariesIndexURLs) == 0 { - // FIXME: Select based on setting. - sc.BinariesIndexURLs = DefaultStableBinaryIndexURLs + sc.BinariesIndexURLs = configure.DefaultStableBinaryIndexURLs } if len(sc.IntelIndexURLs) == 0 { - sc.IntelIndexURLs = DefaultIntelIndexURLs + sc.IntelIndexURLs = configure.DefaultIntelIndexURLs } // Check log level. @@ -109,3 +110,71 @@ func getCurrentBinaryFolder() (string, error) { return installDir, nil } + +func MakeUpdateConfigs(svcCfg *ServiceConfig) (binaryUpdateConfig, intelUpdateConfig *updates.Config, err error) { + switch runtime.GOOS { + case "windows": + binaryUpdateConfig = &updates.Config{ + Name: "binaries", + Directory: svcCfg.BinDir, + DownloadDirectory: filepath.Join(svcCfg.DataDir, "download_binaries"), + PurgeDirectory: filepath.Join(svcCfg.BinDir, "upgrade_obsolete_binaries"), + Ignore: []string{"databases", "intel", "config.json"}, + IndexURLs: svcCfg.BinariesIndexURLs, // May be changed by config during instance startup. + IndexFile: "index.json", + Verify: svcCfg.VerifyBinaryUpdates, + AutoCheck: true, // May be changed by config during instance startup. + AutoDownload: false, + AutoApply: false, + NeedsRestart: true, + Notify: true, + } + intelUpdateConfig = &updates.Config{ + Name: "intel", + Directory: filepath.Join(svcCfg.DataDir, "intel"), + DownloadDirectory: filepath.Join(svcCfg.DataDir, "download_intel"), + PurgeDirectory: filepath.Join(svcCfg.DataDir, "upgrade_obsolete_intel"), + IndexURLs: svcCfg.IntelIndexURLs, + IndexFile: "index.json", + Verify: svcCfg.VerifyIntelUpdates, + AutoCheck: true, // May be changed by config during instance startup. + AutoDownload: true, + AutoApply: true, + NeedsRestart: false, + Notify: false, + } + + case "linux": + binaryUpdateConfig = &updates.Config{ + Name: "binaries", + Directory: svcCfg.BinDir, + DownloadDirectory: filepath.Join(svcCfg.DataDir, "download_binaries"), + PurgeDirectory: filepath.Join(svcCfg.DataDir, "upgrade_obsolete_binaries"), + Ignore: []string{"databases", "intel", "config.json"}, + IndexURLs: svcCfg.BinariesIndexURLs, // May be changed by config during instance startup. + IndexFile: "index.json", + Verify: svcCfg.VerifyBinaryUpdates, + AutoCheck: true, // May be changed by config during instance startup. + AutoDownload: false, + AutoApply: false, + NeedsRestart: true, + Notify: true, + } + intelUpdateConfig = &updates.Config{ + Name: "intel", + Directory: filepath.Join(svcCfg.DataDir, "intel"), + DownloadDirectory: filepath.Join(svcCfg.DataDir, "download_intel"), + PurgeDirectory: filepath.Join(svcCfg.DataDir, "upgrade_obsolete_intel"), + IndexURLs: svcCfg.IntelIndexURLs, + IndexFile: "index.json", + Verify: svcCfg.VerifyIntelUpdates, + AutoCheck: true, // May be changed by config during instance startup. + AutoDownload: true, + AutoApply: true, + NeedsRestart: false, + Notify: false, + } + } + + return +} diff --git a/service/configure/updates.go b/service/configure/updates.go new file mode 100644 index 00000000..3fec4afc --- /dev/null +++ b/service/configure/updates.go @@ -0,0 +1,65 @@ +package configure + +import ( + "github.com/safing/jess" +) + +var ( + DefaultStableBinaryIndexURLs = []string{ + "https://updates.safing.io/stable.v3.json", + } + DefaultBetaBinaryIndexURLs = []string{ + "https://updates.safing.io/beta.v3.json", + } + DefaultStagingBinaryIndexURLs = []string{ + "https://updates.safing.io/staging.v3.json", + } + DefaultSupportBinaryIndexURLs = []string{ + "https://updates.safing.io/support.v3.json", + } + + DefaultIntelIndexURLs = []string{ + "https://updates.safing.io/intel.v3.json", + } + + // BinarySigningKeys holds the signing keys in text format. + BinarySigningKeys = []string{ + // Safing Code Signing Key #1 + "recipient:public-ed25519-key:safing-code-signing-key-1:92bgBLneQUWrhYLPpBDjqHbpFPuNVCPAaivQ951A4aq72HcTiw7R1QmPJwFM1mdePAvEVDjkeb8S4fp2pmRCsRa8HrCvWQEjd88rfZ6TznJMfY4g7P8ioGFjfpyx2ZJ8WCZJG5Qt4Z9nkabhxo2Nbi3iywBTYDLSbP5CXqi7jryW7BufWWuaRVufFFzhwUC2ryWFWMdkUmsAZcvXwde4KLN9FrkWAy61fGaJ8GCwGnGCSitANnU2cQrsGBXZzxmzxwrYD", + // Safing Code Signing Key #2 + "recipient:public-ed25519-key:safing-code-signing-key-2:92bgBLneQUWrhYLPpBDjqHbPC2d1o5JMyZFdavWBNVtdvbPfzDewLW95ScXfYPHd3QvWHSWCtB4xpthaYWxSkK1kYiGp68DPa2HaU8yQ5dZhaAUuV4Kzv42pJcWkCeVnBYqgGBXobuz52rFqhDJy3rz7soXEmYhJEJWwLwMeioK3VzN3QmGSYXXjosHMMNC76rjufSoLNtUQUWZDSnHmqbuxbKMCCsjFXUGGhtZVyb7bnu7QLTLk6SKHBJDMB6zdL9sw3", + } + + // BinarySigningTrustStore is an in-memory trust store with the signing keys. + BinarySigningTrustStore = jess.NewMemTrustStore() +) + +func init() { + for _, signingKey := range BinarySigningKeys { + rcpt, err := jess.RecipientFromTextFormat(signingKey) + if err != nil { + panic(err) + } + err = BinarySigningTrustStore.StoreSignet(rcpt) + if err != nil { + panic(err) + } + } +} + +// GetBinaryUpdateURLs returns the correct binary update URLs for the given release channel. +// Silently falls back to stable if release channel is invalid. +func GetBinaryUpdateURLs(releaseChannel string) []string { + switch releaseChannel { + case "stable": + return DefaultStableBinaryIndexURLs + case "beta": + return DefaultBetaBinaryIndexURLs + case "staging": + return DefaultStagingBinaryIndexURLs + case "support": + return DefaultSupportBinaryIndexURLs + default: + return DefaultStableBinaryIndexURLs + } +} diff --git a/service/core/api.go b/service/core/api.go index ea4f18d1..6f419ced 100644 --- a/service/core/api.go +++ b/service/core/api.go @@ -1,19 +1,27 @@ package core import ( + "bytes" "context" "encoding/hex" "errors" "fmt" + "io" "net/http" "net/url" + "os" + "path/filepath" + "strings" "time" + "github.com/ghodss/yaml" + "github.com/safing/portmaster/base/api" "github.com/safing/portmaster/base/config" "github.com/safing/portmaster/base/log" "github.com/safing/portmaster/base/notifications" "github.com/safing/portmaster/base/rng" + "github.com/safing/portmaster/base/utils" "github.com/safing/portmaster/base/utils/debug" "github.com/safing/portmaster/service/compat" "github.com/safing/portmaster/service/process" @@ -149,6 +157,17 @@ func registerAPIEndpoints() error { return err } + if err := api.RegisterEndpoint(api.Endpoint{ + Name: "Get Resource", + Description: "Returns the requested resource from the udpate system", + Path: `updates/get/?{artifact_path:[A-Za-z0-9/\.\-_]{1,255}}/{artifact_name:[A-Za-z0-9\.\-_]{1,255}}`, + Read: api.PermitUser, + ReadMethod: http.MethodGet, + HandlerFunc: getUpdateResource, + }); err != nil { + return err + } + return nil } @@ -170,6 +189,113 @@ func restart(_ *api.Request) (msg string, err error) { return "restart initiated", nil } +func getUpdateResource(w http.ResponseWriter, r *http.Request) { + // Get identifier from URL. + var identifier string + if ar := api.GetAPIRequest(r); ar != nil { + identifier = ar.URLVars["artifact_name"] + } + if identifier == "" { + http.Error(w, "no resource specified", http.StatusBadRequest) + return + } + + // Get resource. + artifact, err := module.instance.BinaryUpdates().GetFile(identifier) + if err != nil { + intelArtifact, intelErr := module.instance.IntelUpdates().GetFile(identifier) + if intelErr == nil { + artifact = intelArtifact + } else { + http.Error(w, err.Error(), http.StatusNotFound) + return + } + } + + // Open file for reading. + file, err := os.Open(artifact.Path()) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer file.Close() //nolint:errcheck,gosec + + // Assign file to reader + var reader io.Reader = file + + // Add version and hash to header. + if artifact.Version != "" { + w.Header().Set("Resource-Version", artifact.Version) + } + if artifact.SHA256 != "" { + w.Header().Set("Resource-SHA256", artifact.SHA256) + } + + // Set Content-Type. + contentType, _ := utils.MimeTypeByExtension(filepath.Ext(artifact.Path())) + w.Header().Set("Content-Type", contentType) + + // Check if the content type may be returned. + accept := r.Header.Get("Accept") + if accept != "" { + mimeTypes := strings.Split(accept, ",") + // First, clean mime types. + for i, mimeType := range mimeTypes { + mimeType = strings.TrimSpace(mimeType) + mimeType, _, _ = strings.Cut(mimeType, ";") + mimeTypes[i] = mimeType + } + // Second, check if we may return anything. + var acceptsAny bool + for _, mimeType := range mimeTypes { + switch mimeType { + case "*", "*/*": + acceptsAny = true + } + } + // Third, check if we can convert. + if !acceptsAny { + var converted bool + sourceType, _, _ := strings.Cut(contentType, ";") + findConvertiblePair: + for _, mimeType := range mimeTypes { + switch { + case sourceType == "application/yaml" && mimeType == "application/json": + yamlData, err := io.ReadAll(reader) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + jsonData, err := yaml.YAMLToJSON(yamlData) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + reader = bytes.NewReader(jsonData) + converted = true + break findConvertiblePair + } + } + + // If we could not convert to acceptable format, return an error. + if !converted { + http.Error(w, "conversion to requested format not supported", http.StatusNotAcceptable) + return + } + } + } + + // Write file. + w.WriteHeader(http.StatusOK) + if r.Method != http.MethodHead { + _, err = io.Copy(w, reader) + if err != nil { + log.Errorf("updates: failed to serve resource file: %s", err) + return + } + } +} + // debugInfo returns the debugging information for support requests. func debugInfo(ar *api.Request) (data []byte, err error) { // Create debug information helper. @@ -192,7 +318,7 @@ func debugInfo(ar *api.Request) (data []byte, err error) { config.AddToDebugInfo(di) // Detailed information. - // TODO(vladimir): updates.AddToDebugInfo(di) + AddVersionsToDebugInfo(di) compat.AddToDebugInfo(di) module.instance.AddWorkerInfoToDebugInfo(di) di.AddGoroutineStack() diff --git a/service/core/base/global.go b/service/core/base/global.go deleted file mode 100644 index 912e975a..00000000 --- a/service/core/base/global.go +++ /dev/null @@ -1,46 +0,0 @@ -package base - -import ( - "errors" - "flag" - "fmt" - - "github.com/safing/portmaster/base/api" - "github.com/safing/portmaster/base/info" - "github.com/safing/portmaster/service/mgr" -) - -// Default Values (changeable for testing). -var ( - DefaultAPIListenAddress = "127.0.0.1:817" - - showVersion bool -) - -func init() { - flag.BoolVar(&showVersion, "version", false, "show version and exit") -} - -func prep(instance instance) error { - // check if meta info is ok - err := info.CheckVersion() - if err != nil { - return errors.New("compile error: please compile using the provided build script") - } - - // print version - if showVersion { - instance.SetCmdLineOperation(printVersion) - return mgr.ErrExecuteCmdLineOp - } - - // set api listen address - api.SetDefaultAPIListenAddress(DefaultAPIListenAddress) - - return nil -} - -func printVersion() error { - fmt.Println(info.FullVersion()) - return nil -} diff --git a/service/core/base/module.go b/service/core/base/module.go index 0bb7aba2..6e37f797 100644 --- a/service/core/base/module.go +++ b/service/core/base/module.go @@ -4,9 +4,13 @@ import ( "errors" "sync/atomic" + "github.com/safing/portmaster/base/api" "github.com/safing/portmaster/service/mgr" ) +// DefaultAPIListenAddress is the default listen address for the API. +var DefaultAPIListenAddress = "127.0.0.1:817" + // Base is the base module. type Base struct { mgr *mgr.Manager @@ -21,7 +25,7 @@ func (b *Base) Manager() *mgr.Manager { // Start starts the module. func (b *Base) Start() error { startProfiling() - registerLogCleaner() + // registerLogCleaner() return nil } @@ -47,9 +51,9 @@ func New(instance instance) (*Base, error) { instance: instance, } - if err := prep(instance); err != nil { - return nil, err - } + // Set api listen address. + api.SetDefaultAPIListenAddress(DefaultAPIListenAddress) + if err := registerDatabases(); err != nil { return nil, err } diff --git a/service/core/core.go b/service/core/core.go index 356bd2eb..3b433491 100644 --- a/service/core/core.go +++ b/service/core/core.go @@ -6,10 +6,11 @@ import ( "fmt" "sync/atomic" + "github.com/safing/portmaster/base/config" + "github.com/safing/portmaster/base/database" "github.com/safing/portmaster/base/log" "github.com/safing/portmaster/base/metrics" "github.com/safing/portmaster/base/utils/debug" - _ "github.com/safing/portmaster/service/broadcasts" "github.com/safing/portmaster/service/mgr" _ "github.com/safing/portmaster/service/netenv" _ "github.com/safing/portmaster/service/netquery" @@ -19,6 +20,11 @@ import ( "github.com/safing/portmaster/service/updates" ) +var db = database.NewInterface(&database.Options{ + Local: true, + Internal: true, +}) + // Core is the core service module. type Core struct { m *mgr.Manager @@ -56,8 +62,10 @@ func init() { func prep() error { // init config - err := registerConfig() - if err != nil { + if err := registerConfig(); err != nil { + return err + } + if err := registerUpdateConfig(); err != nil { return err } @@ -77,6 +85,10 @@ func start() error { return fmt.Errorf("failed to start plattform-specific components: %w", err) } + // Setup update system. + initUpdateConfig() + initVersionExport() + // Enable persistent metrics. if err := metrics.EnableMetricPersistence("core:metrics/storage"); err != nil { log.Warningf("core: failed to enable persisted metrics: %s", err) @@ -116,6 +128,7 @@ type instance interface { Shutdown() Restart() AddWorkerInfoToDebugInfo(di *debug.Info) + Config() *config.Config BinaryUpdates() *updates.Updater IntelUpdates() *updates.Updater } diff --git a/service/core/update_config.go b/service/core/update_config.go new file mode 100644 index 00000000..10fdb84c --- /dev/null +++ b/service/core/update_config.go @@ -0,0 +1,134 @@ +package core + +import ( + "github.com/safing/portmaster/base/config" + "github.com/safing/portmaster/service/configure" + "github.com/safing/portmaster/service/mgr" +) + +// Release Channel Configuration Keys. +const ( + ReleaseChannelKey = "core/releaseChannel" + ReleaseChannelJSONKey = "core.releaseChannel" +) + +// Release Channels. +const ( + ReleaseChannelStable = "stable" + ReleaseChannelBeta = "beta" + ReleaseChannelStaging = "staging" + ReleaseChannelSupport = "support" +) + +const ( + enableSoftwareUpdatesKey = "core/automaticUpdates" + enableIntelUpdatesKey = "core/automaticIntelUpdates" +) + +var ( + releaseChannel config.StringOption + enableSoftwareUpdates config.BoolOption + enableIntelUpdates config.BoolOption + + initialReleaseChannel string +) + +func registerUpdateConfig() error { + err := config.Register(&config.Option{ + Name: "Release Channel", + Key: ReleaseChannelKey, + Description: `Use "Stable" for the best experience. The "Beta" channel will have the newest features and fixes, but may also break and cause interruption. Use others only temporarily and when instructed.`, + OptType: config.OptTypeString, + ExpertiseLevel: config.ExpertiseLevelExpert, + ReleaseLevel: config.ReleaseLevelStable, + RequiresRestart: true, + DefaultValue: ReleaseChannelStable, + PossibleValues: []config.PossibleValue{ + { + Name: "Stable", + Description: "Production releases.", + Value: ReleaseChannelStable, + }, + { + Name: "Beta", + Description: "Production releases for testing new features that may break and cause interruption.", + Value: ReleaseChannelBeta, + }, + { + Name: "Support", + Description: "Support releases or version changes for troubleshooting. Only use temporarily and when instructed.", + Value: ReleaseChannelSupport, + }, + { + Name: "Staging", + Description: "Dangerous development releases for testing random things and experimenting. Only use temporarily and when instructed.", + Value: ReleaseChannelStaging, + }, + }, + Annotations: config.Annotations{ + config.DisplayOrderAnnotation: -4, + config.DisplayHintAnnotation: config.DisplayHintOneOf, + config.CategoryAnnotation: "Updates", + }, + }) + if err != nil { + return err + } + + err = config.Register(&config.Option{ + Name: "Automatic Software Updates", + Key: enableSoftwareUpdatesKey, + Description: "Automatically check for and download software updates. This does not include intelligence data updates.", + OptType: config.OptTypeBool, + ExpertiseLevel: config.ExpertiseLevelExpert, + ReleaseLevel: config.ReleaseLevelStable, + RequiresRestart: false, + DefaultValue: true, + Annotations: config.Annotations{ + config.DisplayOrderAnnotation: -12, + config.CategoryAnnotation: "Updates", + }, + }) + if err != nil { + return err + } + + err = config.Register(&config.Option{ + Name: "Automatic Intelligence Data Updates", + Key: enableIntelUpdatesKey, + Description: "Automatically check for and download intelligence data updates. This includes filter lists, geo-ip data, and more. Does not include software updates.", + OptType: config.OptTypeBool, + ExpertiseLevel: config.ExpertiseLevelExpert, + ReleaseLevel: config.ReleaseLevelStable, + RequiresRestart: false, + DefaultValue: true, + Annotations: config.Annotations{ + config.DisplayOrderAnnotation: -11, + config.CategoryAnnotation: "Updates", + }, + }) + if err != nil { + return err + } + + return nil +} + +func initUpdateConfig() { + releaseChannel = config.Concurrent.GetAsString(ReleaseChannelKey, ReleaseChannelStable) + enableSoftwareUpdates = config.Concurrent.GetAsBool(enableSoftwareUpdatesKey, true) + enableIntelUpdates = config.Concurrent.GetAsBool(enableIntelUpdatesKey, true) + + initialReleaseChannel = releaseChannel() + + module.instance.Config().EventConfigChange.AddCallback("configure updates", func(wc *mgr.WorkerCtx, s struct{}) (cancel bool, err error) { + configureUpdates() + return false, nil + }) + configureUpdates() +} + +func configureUpdates() { + module.instance.BinaryUpdates().Configure(enableSoftwareUpdates(), configure.GetBinaryUpdateURLs(releaseChannel())) + module.instance.IntelUpdates().Configure(enableIntelUpdates(), configure.DefaultIntelIndexURLs) +} diff --git a/service/core/update_versions.go b/service/core/update_versions.go new file mode 100644 index 00000000..8427a505 --- /dev/null +++ b/service/core/update_versions.go @@ -0,0 +1,176 @@ +package core + +import ( + "bytes" + "fmt" + "sync" + "text/tabwriter" + + "github.com/safing/portmaster/base/database/record" + "github.com/safing/portmaster/base/info" + "github.com/safing/portmaster/base/utils/debug" + "github.com/safing/portmaster/service/mgr" + "github.com/safing/portmaster/service/updates" +) + +const ( + // versionsDBKey is the database key for update version information. + versionsDBKey = "core:status/versions" + + // versionsDBKey is the database key for simple update version information. + simpleVersionsDBKey = "core:status/simple-versions" +) + +// Versions holds update versions and status information. +type Versions struct { + record.Base + sync.Mutex + + Core *info.Info + Resources map[string]*updates.Artifact + Channel string + Beta bool + Staging bool +} + +// SimpleVersions holds simplified update versions and status information. +type SimpleVersions struct { + record.Base + sync.Mutex + + Build *info.Info + Resources map[string]*SimplifiedResourceVersion + Channel string +} + +// SimplifiedResourceVersion holds version information about one resource. +type SimplifiedResourceVersion struct { + Version string +} + +// GetVersions returns the update versions and status information. +// Resources must be locked when accessed. +func GetVersions() *Versions { + // Get all artifacts. + resources := make(map[string]*updates.Artifact) + if artifacts, err := module.instance.BinaryUpdates().GetFiles(); err == nil { + for _, artifact := range artifacts { + resources[artifact.Filename] = artifact + } + } + if artifacts, err := module.instance.IntelUpdates().GetFiles(); err == nil { + for _, artifact := range artifacts { + resources[artifact.Filename] = artifact + } + } + + return &Versions{ + Core: info.GetInfo(), + Resources: resources, + Channel: initialReleaseChannel, + Beta: initialReleaseChannel == ReleaseChannelBeta, + Staging: initialReleaseChannel == ReleaseChannelStaging, + } +} + +// GetSimpleVersions returns the simplified update versions and status information. +func GetSimpleVersions() *SimpleVersions { + // Get all artifacts, simply map. + resources := make(map[string]*SimplifiedResourceVersion) + if artifacts, err := module.instance.BinaryUpdates().GetFiles(); err == nil { + for _, artifact := range artifacts { + resources[artifact.Filename] = &SimplifiedResourceVersion{ + Version: artifact.Version, + } + } + } + if artifacts, err := module.instance.IntelUpdates().GetFiles(); err == nil { + for _, artifact := range artifacts { + resources[artifact.Filename] = &SimplifiedResourceVersion{ + Version: artifact.Version, + } + } + } + + // Fill base info. + return &SimpleVersions{ + Build: info.GetInfo(), + Resources: resources, + Channel: initialReleaseChannel, + } +} + +func initVersionExport() { + module.instance.BinaryUpdates().EventResourcesUpdated.AddCallback("export version status", export) + module.instance.IntelUpdates().EventResourcesUpdated.AddCallback("export version status", export) + + _, _ = export(nil, struct{}{}) +} + +func (v *Versions) save() error { + if !v.KeyIsSet() { + v.SetKey(versionsDBKey) + } + return db.Put(v) +} + +func (v *SimpleVersions) save() error { + if !v.KeyIsSet() { + v.SetKey(simpleVersionsDBKey) + } + return db.Put(v) +} + +// export is an event hook. +func export(_ *mgr.WorkerCtx, _ struct{}) (cancel bool, err error) { + // Export versions. + if err := GetVersions().save(); err != nil { + return false, err + } + if err := GetSimpleVersions().save(); err != nil { + return false, err + } + + return false, nil +} + +// AddVersionsToDebugInfo adds the update system status to the given debug.Info. +func AddVersionsToDebugInfo(di *debug.Info) { + overviewBuf := bytes.NewBuffer(nil) + tableBuf := bytes.NewBuffer(nil) + tabWriter := tabwriter.NewWriter(tableBuf, 8, 4, 3, ' ', 0) + fmt.Fprint(tabWriter, "\nFile\tVersion\tIndex\tSHA256\n") + + // Collect data for debug info. + var cnt int + if index, err := module.instance.BinaryUpdates().GetIndex(); err == nil { + fmt.Fprintf(overviewBuf, "Binaries Index: v%s from %s\n", index.Version, index.Published) + for _, artifact := range index.Artifacts { + fmt.Fprintf(tabWriter, "\n%s\t%s\t%s\t%s", artifact.Filename, vStr(artifact.Version), "binaries", artifact.SHA256) + cnt++ + } + } + if index, err := module.instance.IntelUpdates().GetIndex(); err == nil { + fmt.Fprintf(overviewBuf, "Intel Index: v%s from %s\n", index.Version, index.Published) + for _, artifact := range index.Artifacts { + fmt.Fprintf(tabWriter, "\n%s\t%s\t%s\t%s", artifact.Filename, vStr(artifact.Version), "intel", artifact.SHA256) + cnt++ + } + } + _ = tabWriter.Flush() + + // Add section. + di.AddSection( + fmt.Sprintf("Updates: %s (%d)", initialReleaseChannel, cnt), + debug.UseCodeSection, + overviewBuf.String(), + tableBuf.String(), + ) +} + +func vStr(v string) string { + if v != "" { + return v + } + return "unknown" +} diff --git a/service/firewall/bypassing.go b/service/firewall/bypassing.go index 415fc6c8..4fe9a119 100644 --- a/service/firewall/bypassing.go +++ b/service/firewall/bypassing.go @@ -43,8 +43,24 @@ func PreventBypassing(ctx context.Context, conn *network.Connection) (endpoints. return endpoints.NoMatch, "", nil } + // If Portmaster resolver is disabled allow requests going to system dns resolver. + // And allow all connections out of the System Resolver. + if module.instance.Resolver().IsDisabled() { + // TODO(vladimir): Is there a more specific check that can be done? + if conn.Process().IsSystemResolver() { + return endpoints.NoMatch, "", nil + } + if conn.Entity.Port == 53 && conn.Entity.IPScope.IsLocalhost() { + return endpoints.NoMatch, "", nil + } + } + // Block bypass attempts using an (encrypted) DNS server. switch { + case looksLikeOutgoingDNSRequest(conn) && module.instance.Resolver().IsDisabled(): + // Allow. Packet will be analyzed and blocked if its not a dns request, before sent. + conn.Inspecting = true + return endpoints.NoMatch, "", nil case conn.Entity.Port == 53: return endpoints.Denied, "blocked DNS query, manual dns setup required", @@ -62,3 +78,17 @@ func PreventBypassing(ctx context.Context, conn *network.Connection) (endpoints. return endpoints.NoMatch, "", nil } + +func looksLikeOutgoingDNSRequest(conn *network.Connection) bool { + // Outbound on remote port 53, UDP. + if conn.Inbound { + return false + } + if conn.Entity.Port != 53 { + return false + } + if conn.IPProtocol != packet.UDP { + return false + } + return true +} diff --git a/service/firewall/dns.go b/service/firewall/dns.go index 8a6e1973..e3434708 100644 --- a/service/firewall/dns.go +++ b/service/firewall/dns.go @@ -287,6 +287,30 @@ func UpdateIPsAndCNAMEs(q *resolver.Query, rrCache *resolver.RRCache, conn *netw } } + // Create new record for this IP. + record := resolver.ResolvedDomain{ + Domain: q.FQDN, + Resolver: rrCache.Resolver, + DNSRequestContext: rrCache.ToDNSRequestContext(), + Expires: rrCache.Expires, + } + // Process CNAMEs + record.AddCNAMEs(cnames) + // Link connection with cnames. + if conn.Type == network.DNSRequest { + conn.Entity.CNAME = record.CNAMEs + } + + SaveIPsInCache(ips, profileID, record) +} + +// formatRR is a friendlier alternative to miekg/dns.RR.String(). +func formatRR(rr dns.RR) string { + return strings.ReplaceAll(rr.String(), "\t", " ") +} + +// SaveIPsInCache saves the provided ips in the dns cashe assoseted with the record Domain and CNAMEs. +func SaveIPsInCache(ips []net.IP, profileID string, record resolver.ResolvedDomain) { // Package IPs and CNAMEs into IPInfo structs. for _, ip := range ips { // Never save domain attributions for localhost IPs. @@ -294,31 +318,6 @@ func UpdateIPsAndCNAMEs(q *resolver.Query, rrCache *resolver.RRCache, conn *netw continue } - // Create new record for this IP. - record := resolver.ResolvedDomain{ - Domain: q.FQDN, - Resolver: rrCache.Resolver, - DNSRequestContext: rrCache.ToDNSRequestContext(), - Expires: rrCache.Expires, - } - - // Resolve all CNAMEs in the correct order and add the to the record. - domain := q.FQDN - for { - nextDomain, isCNAME := cnames[domain] - if !isCNAME { - break - } - - record.CNAMEs = append(record.CNAMEs, nextDomain) - domain = nextDomain - } - - // Update the entity to include the CNAMEs of the query response. - conn.Entity.CNAME = record.CNAMEs - - // Check if there is an existing record for this DNS response. - // Else create a new one. ipString := ip.String() info, err := resolver.GetIPInfo(profileID, ipString) if err != nil { @@ -341,8 +340,3 @@ func UpdateIPsAndCNAMEs(q *resolver.Query, rrCache *resolver.RRCache, conn *netw } } } - -// formatRR is a friendlier alternative to miekg/dns.RR.String(). -func formatRR(rr dns.RR) string { - return strings.ReplaceAll(rr.String(), "\t", " ") -} diff --git a/service/firewall/interception/dnsmonitor/etwlink_windows.go b/service/firewall/interception/dnsmonitor/etwlink_windows.go new file mode 100644 index 00000000..6f9bd16d --- /dev/null +++ b/service/firewall/interception/dnsmonitor/etwlink_windows.go @@ -0,0 +1,109 @@ +//go:build windows +// +build windows + +package dnsmonitor + +import ( + "fmt" + "runtime" + "sync" + "sync/atomic" + + "github.com/safing/portmaster/service/integration" + "golang.org/x/sys/windows" +) + +type ETWSession struct { + i *integration.ETWFunctions + + shutdownGuard atomic.Bool + shutdownMutex sync.Mutex + + state uintptr +} + +// NewSession creates new ETW event listener and initializes it. This is a low level interface, make sure to call DestroySession when you are done using it. +func NewSession(etwInterface *integration.ETWFunctions, callback func(domain string, pid uint32, result string)) (*ETWSession, error) { + if etwInterface == nil { + return nil, fmt.Errorf("etw interface was nil") + } + etwSession := &ETWSession{ + i: etwInterface, + } + + // Make sure session from previous instances are not running. + _ = etwSession.i.StopOldSession() + + // Initialize notification activated callback + win32Callback := windows.NewCallback(func(domain *uint16, pid uint32, result *uint16) uintptr { + callback(windows.UTF16PtrToString(domain), pid, windows.UTF16PtrToString(result)) + return 0 + }) + // The function only allocates memory it will not fail. + etwSession.state = etwSession.i.CreateState(win32Callback) + + // Make sure DestroySession is called even if caller forgets to call it. + runtime.SetFinalizer(etwSession, func(s *ETWSession) { + _ = s.i.DestroySession(s.state) + }) + + // Initialize session. + err := etwSession.i.InitializeSession(etwSession.state) + if err != nil { + return nil, fmt.Errorf("failed to initialize session: %q", err) + } + + return etwSession, nil +} + +// StartTrace starts the tracing session of dns events. This is a blocking call. It will not return until the trace is stopped. +func (l *ETWSession) StartTrace() error { + return l.i.StartTrace(l.state) +} + +// IsRunning returns true if DestroySession has NOT been called. +func (l *ETWSession) IsRunning() bool { + return !l.shutdownGuard.Load() +} + +// 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() + + // Make sure session is still running. + if l.shutdownGuard.Load() { + return nil + } + + return l.i.FlushTrace(l.state) +} + +// StopTrace stops the trace. This will cause StartTrace to return. +func (l *ETWSession) StopTrace() error { + return l.i.StopTrace(l.state) +} + +// 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() + + if l.shutdownGuard.Swap(true) { + return nil + } + + err := l.i.DestroySession(l.state) + if err != nil { + return err + } + l.state = 0 + return nil +} diff --git a/service/firewall/interception/dnsmonitor/eventlistener.go b/service/firewall/interception/dnsmonitor/eventlistener.go new file mode 100644 index 00000000..911130c9 --- /dev/null +++ b/service/firewall/interception/dnsmonitor/eventlistener.go @@ -0,0 +1,19 @@ +//go:build !linux && !windows +// +build !linux,!windows + +package dnsmonitor + +type Listener struct{} + +func newListener(_ *DNSMonitor) (*Listener, error) { + return &Listener{}, nil +} + +func (l *Listener) flush() error { + // Nothing to flush + return nil +} + +func (l *Listener) stop() error { + return nil +} diff --git a/service/firewall/interception/dnsmonitor/eventlistener_linux.go b/service/firewall/interception/dnsmonitor/eventlistener_linux.go new file mode 100644 index 00000000..f4fc99a0 --- /dev/null +++ b/service/firewall/interception/dnsmonitor/eventlistener_linux.go @@ -0,0 +1,145 @@ +//go:build linux +// +build linux + +package dnsmonitor + +import ( + "errors" + "fmt" + "net" + "os" + + "github.com/miekg/dns" + "github.com/varlink/go/varlink" + + "github.com/safing/portmaster/base/log" + "github.com/safing/portmaster/service/mgr" + "github.com/safing/portmaster/service/resolver" +) + +type Listener struct { + varlinkConn *varlink.Connection +} + +func newListener(module *DNSMonitor) (*Listener, error) { + // Set source of the resolver. + ResolverInfo.Source = resolver.ServerSourceSystemd + + // Check if the system has systemd-resolver. + _, err := os.Stat("/run/systemd/resolve/io.systemd.Resolve.Monitor") + if err != nil { + return nil, fmt.Errorf("system does not support systemd resolver monitor") + } + + listener := &Listener{} + + restartAttempts := 0 + + module.mgr.Go("systemd-resolver-event-listener", func(w *mgr.WorkerCtx) error { + // Abort initialization if the connection failed after too many tries. + if restartAttempts > 10 { + return nil + } + restartAttempts += 1 + + // Initialize varlink connection + varlinkConn, err := varlink.NewConnection(module.mgr.Ctx(), "unix:/run/systemd/resolve/io.systemd.Resolve.Monitor") + if err != nil { + return fmt.Errorf("failed to connect to systemd-resolver varlink service: %w", err) + } + defer func() { + if varlinkConn != nil { + err = varlinkConn.Close() + if err != nil { + log.Errorf("dnsmonitor: failed to close varlink connection: %s", err) + } + } + }() + + listener.varlinkConn = varlinkConn + // Subscribe to the dns query events + receive, err := listener.varlinkConn.Send(w.Ctx(), "io.systemd.Resolve.Monitor.SubscribeQueryResults", nil, varlink.More) + if err != nil { + var varlinkErr *varlink.Error + if errors.As(err, &varlinkErr) { + return fmt.Errorf("failed to issue Varlink call: %+v", varlinkErr.Parameters) + } else { + return fmt.Errorf("failed to issue Varlink call: %w", err) + } + } + + for { + queryResult := QueryResult{} + // Receive the next event from the resolver. + flags, err := receive(w.Ctx(), &queryResult) + if err != nil { + var varlinkErr *varlink.Error + if errors.As(err, &varlinkErr) { + return fmt.Errorf("failed to receive Varlink reply: %+v", varlinkErr.Parameters) + } else { + return fmt.Errorf("failed to receive Varlink reply: %w", err) + } + } + + // Check if the reply indicates the end of the stream + if flags&varlink.Continues == 0 { + break + } + + // Ignore if there is no question. + if queryResult.Question == nil || len(*queryResult.Question) == 0 { + continue + } + + // Protmaster self check + domain := (*queryResult.Question)[0].Name + if processIfSelfCheckDomain(dns.Fqdn(domain)) { + // Not need to process result. + continue + } + + if queryResult.Rcode != nil { + continue // Ignore DNS errors + } + + listener.processAnswer(domain, &queryResult) + } + return nil + }) + return listener, nil +} + +func (l *Listener) flush() error { + // Nothing to flush + return nil +} + +func (l *Listener) stop() error { + return nil +} + +func (l *Listener) processAnswer(domain string, queryResult *QueryResult) { + // Allocated data struct for the parsed result. + cnames := make(map[string]string) + ips := make([]net.IP, 0, 5) + + // Check if the query is valid + if queryResult.Answer == nil { + return + } + + // Go trough each answer entry. + for _, a := range *queryResult.Answer { + if a.RR.Address != nil { + ip := net.IP(*a.RR.Address) + // Answer contains ip address. + ips = append(ips, ip) + + } else if a.RR.Name != nil { + // Answer is a CNAME. + cnames[domain] = *a.RR.Name + } + } + + saveDomain(domain, ips, cnames, resolver.IPInfoProfileScopeGlobal) +} diff --git a/service/firewall/interception/dnsmonitor/eventlistener_windows.go b/service/firewall/interception/dnsmonitor/eventlistener_windows.go new file mode 100644 index 00000000..d71c3c43 --- /dev/null +++ b/service/firewall/interception/dnsmonitor/eventlistener_windows.go @@ -0,0 +1,130 @@ +//go:build windows +// +build windows + +package dnsmonitor + +import ( + "context" + "fmt" + "net" + "strconv" + "strings" + + "github.com/miekg/dns" + "github.com/safing/portmaster/service/mgr" + "github.com/safing/portmaster/service/process" + "github.com/safing/portmaster/service/resolver" +) + +type Listener struct { + etw *ETWSession +} + +func newListener(module *DNSMonitor) (*Listener, error) { + // Set source of the resolver. + ResolverInfo.Source = resolver.ServerSourceETW + + listener := &Listener{} + // Initialize new dns event session. + err := initializeSessions(module, listener) + if err != nil { + // 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 + }) + } + 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() +} + +func (l *Listener) stop() error { + if l == nil { + return fmt.Errorf("listener is nil") + } + if l.etw == nil { + return fmt.Errorf("invalid etw session") + } + // Stop and destroy trace. Destroy should be called even if stop fails for some reason. + err := l.etw.StopTrace() + err2 := l.etw.DestroySession() + + if err != nil { + return fmt.Errorf("StopTrace failed: %w", err) + } + + if err2 != nil { + return fmt.Errorf("DestroySession failed: %w", err2) + } + return nil +} + +func (l *Listener) processEvent(domain string, pid uint32, result string) { + if processIfSelfCheckDomain(dns.Fqdn(domain)) { + // Not need to process result. + return + } + + // Ignore empty results + if len(result) == 0 { + return + } + + profileScope := resolver.IPInfoProfileScopeGlobal + // Get the profile ID if the process can be found + if proc, err := process.GetOrFindProcess(context.Background(), int(pid)); err == nil { + if profile := proc.Profile(); profile != nil { + if localProfile := profile.LocalProfile(); localProfile != nil { + profileScope = localProfile.ID + } + } + } + cnames := make(map[string]string) + ips := []net.IP{} + + resultArray := strings.Split(result, ";") + for _, r := range resultArray { + // For results other than IP addresses, the string starts with "type:" + if strings.HasPrefix(r, "type:") { + dnsValueArray := strings.Split(r, " ") + if len(dnsValueArray) < 3 { + continue + } + + // Ignore everything except CNAME records + if value, err := strconv.ParseInt(dnsValueArray[1], 10, 16); err == nil && value == int64(dns.TypeCNAME) { + cnames[domain] = dnsValueArray[2] + } + + } else { + // If the event doesn't start with "type:", it's an IP address + ip := net.ParseIP(r) + if ip != nil { + ips = append(ips, ip) + } + } + } + saveDomain(domain, ips, cnames, profileScope) +} diff --git a/service/firewall/interception/dnsmonitor/module.go b/service/firewall/interception/dnsmonitor/module.go new file mode 100644 index 00000000..918d1e88 --- /dev/null +++ b/service/firewall/interception/dnsmonitor/module.go @@ -0,0 +1,139 @@ +package dnsmonitor + +import ( + "errors" + "net" + "strings" + + "github.com/miekg/dns" + + "github.com/safing/portmaster/base/database" + "github.com/safing/portmaster/base/log" + "github.com/safing/portmaster/service/compat" + "github.com/safing/portmaster/service/integration" + "github.com/safing/portmaster/service/mgr" + "github.com/safing/portmaster/service/network/netutils" + "github.com/safing/portmaster/service/resolver" +) + +var ResolverInfo = resolver.ResolverInfo{ + Name: "SystemResolver", + Type: resolver.ServerTypeMonitor, +} + +type DNSMonitor struct { + instance instance + mgr *mgr.Manager + + listener *Listener +} + +// Manager returns the module manager. +func (dl *DNSMonitor) Manager() *mgr.Manager { + return dl.mgr +} + +// Start starts the module. +func (dl *DNSMonitor) Start() error { + // Initialize dns event listener + var err error + dl.listener, err = newListener(dl) + if err != nil { + log.Warningf("dnsmonitor: failed to start dns listener: %s", err) + } + + return nil +} + +// Stop stops the module. +func (dl *DNSMonitor) Stop() error { + if dl.listener != nil { + err := dl.listener.stop() + if err != nil { + log.Errorf("dnsmonitor: failed to close listener: %s", err) + } + } + return nil +} + +// Flush flushes the buffer forcing all events to be processed. +func (dl *DNSMonitor) Flush() error { + return dl.listener.flush() +} + +func saveDomain(domain string, ips []net.IP, cnames map[string]string, profileScope string) { + fqdn := dns.Fqdn(domain) + // Create new record for this IP. + record := resolver.ResolvedDomain{ + Domain: fqdn, + Resolver: &ResolverInfo, + DNSRequestContext: &resolver.DNSRequestContext{}, + Expires: 0, + } + + // Process cnames + record.AddCNAMEs(cnames) + + // Add to cache + saveIPsInCache(ips, profileScope, record) +} + +func New(instance instance) (*DNSMonitor, error) { + // Initialize module + m := mgr.New("DNSMonitor") + module := &DNSMonitor{ + mgr: m, + instance: instance, + } + + return module, nil +} + +type instance interface { + OSIntegration() *integration.OSIntegration +} + +func processIfSelfCheckDomain(fqdn string) bool { + // Check for compat check dns request. + if strings.HasSuffix(fqdn, compat.DNSCheckInternalDomainScope) { + subdomain := strings.TrimSuffix(fqdn, compat.DNSCheckInternalDomainScope) + _ = compat.SubmitDNSCheckDomain(subdomain) + log.Infof("dnsmonitor: self-check domain received") + // No need to parse the answer. + return true + } + + return false +} + +// saveIPsInCache saves the provided ips in the dns cashe assoseted with the record Domain and CNAMEs. +func saveIPsInCache(ips []net.IP, profileID string, record resolver.ResolvedDomain) { + // Package IPs and CNAMEs into IPInfo structs. + for _, ip := range ips { + // Never save domain attributions for localhost IPs. + if netutils.GetIPScope(ip) == netutils.HostLocal { + continue + } + + ipString := ip.String() + info, err := resolver.GetIPInfo(profileID, ipString) + if err != nil { + if !errors.Is(err, database.ErrNotFound) { + log.Errorf("dnsmonitor: failed to search for IP info record: %s", err) + } + + info = &resolver.IPInfo{ + IP: ipString, + ProfileID: profileID, + } + } + + // Add the new record to the resolved domains for this IP and scope. + info.AddDomain(record) + + // Save if the record is new or has been updated. + if err := info.Save(); err != nil { + log.Errorf("dnsmonitor: failed to save IP info record: %s", err) + } + } +} diff --git a/service/firewall/interception/dnsmonitor/varlinktypes.go b/service/firewall/interception/dnsmonitor/varlinktypes.go new file mode 100644 index 00000000..3021ac18 --- /dev/null +++ b/service/firewall/interception/dnsmonitor/varlinktypes.go @@ -0,0 +1,83 @@ +//go:build linux +// +build linux + +package dnsmonitor + +// List of struct that define the systemd-resolver varlink dns event protocol. +// Source: `sudo varlinkctl introspect /run/systemd/resolve/io.systemd.Resolve.Monitor io.systemd.Resolve.Monitor` + +type ResourceKey struct { + Class int `json:"class"` + Type int `json:"type"` + Name string `json:"name"` +} + +type ResourceRecord struct { + Key ResourceKey `json:"key"` + Name *string `json:"name,omitempty"` + Address *[]byte `json:"address,omitempty"` + // Rest of the fields are not used. + // Priority *int `json:"priority,omitempty"` + // Weight *int `json:"weight,omitempty"` + // Port *int `json:"port,omitempty"` + // CPU *string `json:"cpu,omitempty"` + // OS *string `json:"os,omitempty"` + // Items *[]string `json:"items,omitempty"` + // MName *string `json:"mname,omitempty"` + // RName *string `json:"rname,omitempty"` + // Serial *int `json:"serial,omitempty"` + // Refresh *int `json:"refresh,omitempty"` + // Expire *int `json:"expire,omitempty"` + // Minimum *int `json:"minimum,omitempty"` + // Exchange *string `json:"exchange,omitempty"` + // Version *int `json:"version,omitempty"` + // Size *int `json:"size,omitempty"` + // HorizPre *int `json:"horiz_pre,omitempty"` + // VertPre *int `json:"vert_pre,omitempty"` + // Latitude *int `json:"latitude,omitempty"` + // Longitude *int `json:"longitude,omitempty"` + // Altitude *int `json:"altitude,omitempty"` + // KeyTag *int `json:"key_tag,omitempty"` + // Algorithm *int `json:"algorithm,omitempty"` + // DigestType *int `json:"digest_type,omitempty"` + // Digest *string `json:"digest,omitempty"` + // FPType *int `json:"fptype,omitempty"` + // Fingerprint *string `json:"fingerprint,omitempty"` + // Flags *int `json:"flags,omitempty"` + // Protocol *int `json:"protocol,omitempty"` + // DNSKey *string `json:"dnskey,omitempty"` + // Signer *string `json:"signer,omitempty"` + // TypeCovered *int `json:"type_covered,omitempty"` + // Labels *int `json:"labels,omitempty"` + // OriginalTTL *int `json:"original_ttl,omitempty"` + // Expiration *int `json:"expiration,omitempty"` + // Inception *int `json:"inception,omitempty"` + // Signature *string `json:"signature,omitempty"` + // NextDomain *string `json:"next_domain,omitempty"` + // Types *[]int `json:"types,omitempty"` + // Iterations *int `json:"iterations,omitempty"` + // Salt *string `json:"salt,omitempty"` + // Hash *string `json:"hash,omitempty"` + // CertUsage *int `json:"cert_usage,omitempty"` + // Selector *int `json:"selector,omitempty"` + // MatchingType *int `json:"matching_type,omitempty"` + // Data *string `json:"data,omitempty"` + // Tag *string `json:"tag,omitempty"` + // Value *string `json:"value,omitempty"` +} + +type Answer struct { + RR *ResourceRecord `json:"rr,omitempty"` + Raw string `json:"raw"` + IfIndex *int `json:"ifindex,omitempty"` +} + +type QueryResult struct { + Ready *bool `json:"ready,omitempty"` + State *string `json:"state,omitempty"` + Rcode *int `json:"rcode,omitempty"` + Errno *int `json:"errno,omitempty"` + Question *[]ResourceKey `json:"question,omitempty"` + CollectedQuestions *[]ResourceKey `json:"collectedQuestions,omitempty"` + Answer *[]Answer `json:"answer,omitempty"` +} diff --git a/service/firewall/interception/nfq/nfq.go b/service/firewall/interception/nfq/nfq.go index 22a5b390..397d1857 100644 --- a/service/firewall/interception/nfq/nfq.go +++ b/service/firewall/interception/nfq/nfq.go @@ -188,7 +188,7 @@ func (q *Queue) packetHandler(ctx context.Context) func(nfqueue.Attribute) int { return 0 } - if err := pmpacket.Parse(*attrs.Payload, &pkt.Base); err != nil { + if err := pmpacket.ParseLayer3(*attrs.Payload, &pkt.Base); err != nil { log.Warningf("nfqueue: failed to parse payload: %s", err) _ = pkt.Drop() return 0 diff --git a/service/firewall/interception/windowskext/packet.go b/service/firewall/interception/windowskext/packet.go index 5942d7d9..9145926c 100644 --- a/service/firewall/interception/windowskext/packet.go +++ b/service/firewall/interception/windowskext/packet.go @@ -59,7 +59,7 @@ func (pkt *Packet) LoadPacketData() error { return packet.ErrFailedToLoadPayload } - err = packet.Parse(payload, &pkt.Base) + err = packet.ParseLayer3(payload, &pkt.Base) if err != nil { log.Tracer(pkt.Ctx()).Warningf("windowskext: failed to parse payload: %s", err) return packet.ErrFailedToLoadPayload diff --git a/service/firewall/interception/windowskext2/handler.go b/service/firewall/interception/windowskext2/handler.go index 57f74c71..d144fa63 100644 --- a/service/firewall/interception/windowskext2/handler.go +++ b/service/firewall/interception/windowskext2/handler.go @@ -55,6 +55,7 @@ func Handler(ctx context.Context, packets chan packet.Packet, bandwidthUpdate ch newPacket := &Packet{ verdictRequest: conn.ID, payload: conn.Payload, + payloadLayer: conn.PayloadLayer, verdictSet: abool.NewBool(false), } info := newPacket.Info() diff --git a/service/firewall/interception/windowskext2/packet.go b/service/firewall/interception/windowskext2/packet.go index 52a7a2a7..00d95036 100644 --- a/service/firewall/interception/windowskext2/packet.go +++ b/service/firewall/interception/windowskext2/packet.go @@ -4,6 +4,7 @@ package windowskext import ( + "fmt" "sync" "github.com/tevino/abool" @@ -19,6 +20,7 @@ type Packet struct { verdictRequest uint64 payload []byte + payloadLayer uint8 verdictSet *abool.AtomicBool payloadLoaded bool @@ -51,7 +53,15 @@ func (pkt *Packet) LoadPacketData() error { pkt.payloadLoaded = true if len(pkt.payload) > 0 { - err := packet.Parse(pkt.payload, &pkt.Base) + var err error + switch pkt.payloadLayer { + case 3: + err = packet.ParseLayer3(pkt.payload, &pkt.Base) + case 4: + err = packet.ParseLayer4(pkt.payload, &pkt.Base) + default: + err = fmt.Errorf("unsupported payload layer: %d", pkt.payloadLayer) + } if err != nil { log.Tracef("payload: %#v", pkt.payload) log.Tracer(pkt.Ctx()).Warningf("windowskext: failed to parse payload: %s", err) diff --git a/service/firewall/module.go b/service/firewall/module.go index 70226e55..0fbcbc56 100644 --- a/service/firewall/module.go +++ b/service/firewall/module.go @@ -16,6 +16,7 @@ import ( "github.com/safing/portmaster/service/netquery" "github.com/safing/portmaster/service/network" "github.com/safing/portmaster/service/profile" + "github.com/safing/portmaster/service/resolver" "github.com/safing/portmaster/service/updates" "github.com/safing/portmaster/spn/access" "github.com/safing/portmaster/spn/captain" @@ -35,8 +36,7 @@ func (ss *stringSliceFlag) Set(value string) error { var allowedClients stringSliceFlag type Firewall struct { - mgr *mgr.Manager - + mgr *mgr.Manager instance instance } @@ -168,4 +168,5 @@ type instance interface { Access() *access.Access Network() *network.Network NetQuery() *netquery.NetQuery + Resolver() *resolver.ResolverModule } diff --git a/service/firewall/packet_handler.go b/service/firewall/packet_handler.go index a290182f..c0e59cb7 100644 --- a/service/firewall/packet_handler.go +++ b/service/firewall/packet_handler.go @@ -6,10 +6,12 @@ import ( "fmt" "net" "os" + "strings" "sync/atomic" "time" "github.com/google/gopacket/layers" + "github.com/miekg/dns" "github.com/tevino/abool" "github.com/safing/portmaster/base/log" @@ -23,6 +25,7 @@ import ( "github.com/safing/portmaster/service/network/netutils" "github.com/safing/portmaster/service/network/packet" "github.com/safing/portmaster/service/process" + "github.com/safing/portmaster/service/resolver" "github.com/safing/portmaster/spn/access" ) @@ -444,8 +447,9 @@ func filterHandler(conn *network.Connection, pkt packet.Packet) { filterConnection = false log.Tracer(pkt.Ctx()).Infof("filter: granting own pre-authenticated connection %s", conn) - // Redirect outbound DNS packets if enabled, + // Redirect outbound DNS packets if enabled, case dnsQueryInterception() && + !module.instance.Resolver().IsDisabled() && pkt.IsOutbound() && pkt.Info().DstPort == 53 && // that don't match the address of our nameserver, @@ -478,11 +482,13 @@ func filterHandler(conn *network.Connection, pkt packet.Packet) { // Decide how to continue handling connection. switch { + case conn.Inspecting && looksLikeOutgoingDNSRequest(conn): + inspectDNSPacket(conn, pkt) + conn.UpdateFirewallHandler(inspectDNSPacket) case conn.Inspecting: log.Tracer(pkt.Ctx()).Trace("filter: start inspecting") conn.UpdateFirewallHandler(inspectAndVerdictHandler) inspectAndVerdictHandler(conn, pkt) - default: conn.StopFirewallHandler() verdictHandler(conn, pkt) @@ -506,7 +512,7 @@ func FilterConnection(ctx context.Context, conn *network.Connection, pkt packet. } // TODO: Enable inspection framework again. - conn.Inspecting = false + // conn.Inspecting = false // TODO: Quick fix for the SPN. // Use inspection framework for proper encryption detection. @@ -580,6 +586,98 @@ func inspectAndVerdictHandler(conn *network.Connection, pkt packet.Packet) { issueVerdict(conn, pkt, 0, true) } +func inspectDNSPacket(conn *network.Connection, pkt packet.Packet) { + // Ignore info-only packets in this handler. + if pkt.InfoOnly() { + return + } + + dnsPacket := new(dns.Msg) + err := pkt.LoadPacketData() + if err != nil { + _ = pkt.Block() + log.Errorf("filter: failed to load packet payload: %s", err) + return + } + + // Parse and block invalid packets. + err = dnsPacket.Unpack(pkt.Payload()) + if err != nil { + err = pkt.PermanentBlock() + if err != nil { + log.Errorf("filter: failed to block packet: %s", err) + } + _ = conn.SetVerdict(network.VerdictBlock, "none DNS data on DNS port", "", nil) + conn.VerdictPermanent = true + conn.Save() + return + } + + // Packet was parsed. + // Allow it but only after the answer was added to the cache. + defer func() { + err = pkt.Accept() + if err != nil { + log.Errorf("filter: failed to accept dns packet: %s", err) + } + }() + + // Check if packet has a question. + if len(dnsPacket.Question) == 0 { + return + } + + // Read create structs with the needed data. + question := dnsPacket.Question[0] + fqdn := dns.Fqdn(question.Name) + + // Check for compat check dns request. + if strings.HasSuffix(fqdn, compat.DNSCheckInternalDomainScope) { + subdomain := strings.TrimSuffix(fqdn, compat.DNSCheckInternalDomainScope) + _ = compat.SubmitDNSCheckDomain(subdomain) + log.Infof("packet_handler: self-check domain received") + // No need to parse the answer. + return + } + + // Check if there is an answer. + if len(dnsPacket.Answer) == 0 { + return + } + + resolverInfo := &resolver.ResolverInfo{ + Name: "DNSRequestObserver", + Type: resolver.ServerTypeFirewall, + Source: resolver.ServerSourceFirewall, + IP: conn.Entity.IP, + Domain: conn.Entity.Domain, + IPScope: conn.Entity.IPScope, + } + + rrCache := &resolver.RRCache{ + Domain: fqdn, + Question: dns.Type(question.Qtype), + RCode: dnsPacket.Rcode, + Answer: dnsPacket.Answer, + Ns: dnsPacket.Ns, + Extra: dnsPacket.Extra, + Resolver: resolverInfo, + } + + query := &resolver.Query{ + FQDN: fqdn, + QType: dns.Type(question.Qtype), + NoCaching: false, + IgnoreFailing: false, + LocalResolversOnly: false, + ICANNSpace: false, + DomainRoot: "", + } + + // Save to cache + UpdateIPsAndCNAMEs(query, rrCache, conn) +} + func icmpFilterHandler(conn *network.Connection, pkt packet.Packet) { // Load packet data. err := pkt.LoadPacketData() diff --git a/service/instance.go b/service/instance.go index ddb23671..c71f6388 100644 --- a/service/instance.go +++ b/service/instance.go @@ -3,7 +3,6 @@ package service import ( "context" "fmt" - "os" "sync/atomic" "time" @@ -14,12 +13,15 @@ import ( "github.com/safing/portmaster/base/notifications" "github.com/safing/portmaster/base/rng" "github.com/safing/portmaster/base/runtime" + "github.com/safing/portmaster/base/utils" "github.com/safing/portmaster/service/broadcasts" "github.com/safing/portmaster/service/compat" "github.com/safing/portmaster/service/core" "github.com/safing/portmaster/service/core/base" "github.com/safing/portmaster/service/firewall" "github.com/safing/portmaster/service/firewall/interception" + "github.com/safing/portmaster/service/firewall/interception/dnsmonitor" + "github.com/safing/portmaster/service/integration" "github.com/safing/portmaster/service/intel/customlists" "github.com/safing/portmaster/service/intel/filterlists" "github.com/safing/portmaster/service/intel/geoip" @@ -74,6 +76,7 @@ type Instance struct { core *core.Core binaryUpdates *updates.Updater intelUpdates *updates.Updater + integration *integration.OSIntegration geoip *geoip.GeoIP netenv *netenv.NetEnv ui *ui.UI @@ -83,6 +86,7 @@ type Instance struct { firewall *firewall.Firewall filterLists *filterlists.FilterLists interception *interception.Interception + dnsmonitor *dnsmonitor.DNSMonitor customlist *customlists.CustomList status *status.Status broadcasts *broadcasts.Broadcasts @@ -119,7 +123,7 @@ func New(svcCfg *ServiceConfig) (*Instance, error) { //nolint:maintidx } // Make sure data dir exists, so that child directories don't dictate the permissions. - err = os.MkdirAll(svcCfg.DataDir, 0o0755) + err = utils.EnsureDirectory(svcCfg.DataDir, utils.PublicReadExecPermission) if err != nil { return nil, fmt.Errorf("data directory %s is not accessible: %w", svcCfg.DataDir, err) } @@ -167,10 +171,6 @@ func New(svcCfg *ServiceConfig) (*Instance, error) { //nolint:maintidx } // Service modules - instance.core, err = core.New(instance) - if err != nil { - return instance, fmt.Errorf("create core module: %w", err) - } binaryUpdateConfig, intelUpdateConfig, err := MakeUpdateConfigs(svcCfg) if err != nil { return instance, fmt.Errorf("create updates config: %w", err) @@ -183,6 +183,14 @@ func New(svcCfg *ServiceConfig) (*Instance, error) { //nolint:maintidx if err != nil { return instance, fmt.Errorf("create updates module: %w", err) } + instance.core, err = core.New(instance) + if err != nil { + return instance, fmt.Errorf("create core module: %w", err) + } + instance.integration, err = integration.New(instance) + if err != nil { + return instance, fmt.Errorf("create integration module: %w", err) + } instance.geoip, err = geoip.New(instance) if err != nil { return instance, fmt.Errorf("create customlist module: %w", err) @@ -219,6 +227,10 @@ func New(svcCfg *ServiceConfig) (*Instance, error) { //nolint:maintidx if err != nil { return instance, fmt.Errorf("create interception module: %w", err) } + instance.dnsmonitor, err = dnsmonitor.New(instance) + if err != nil { + return instance, fmt.Errorf("create dns-listener module: %w", err) + } instance.customlist, err = customlists.New(instance) if err != nil { return instance, fmt.Errorf("create customlist module: %w", err) @@ -309,6 +321,7 @@ func New(svcCfg *ServiceConfig) (*Instance, error) { //nolint:maintidx instance.core, instance.binaryUpdates, instance.intelUpdates, + instance.integration, instance.geoip, instance.netenv, @@ -322,6 +335,7 @@ func New(svcCfg *ServiceConfig) (*Instance, error) { //nolint:maintidx instance.filterLists, instance.customlist, instance.interception, + instance.dnsmonitor, instance.compat, instance.status, @@ -429,6 +443,11 @@ func (i *Instance) IntelUpdates() *updates.Updater { return i.intelUpdates } +// OSIntegration returns the integration module. +func (i *Instance) OSIntegration() *integration.OSIntegration { + return i.integration +} + // GeoIP returns the geoip module. func (i *Instance) GeoIP() *geoip.GeoIP { return i.geoip @@ -514,6 +533,11 @@ func (i *Instance) Interception() *interception.Interception { return i.interception } +// DNSMonitor returns the dns-listener module. +func (i *Instance) DNSMonitor() *dnsmonitor.DNSMonitor { + return i.dnsmonitor +} + // CustomList returns the customlist module. func (i *Instance) CustomList() *customlists.CustomList { return i.customlist @@ -708,3 +732,23 @@ func (i *Instance) ShutdownComplete() <-chan struct{} { func (i *Instance) ExitCode() int { return int(i.exitCode.Load()) } + +// ShouldRestartIsSet returns whether the service/instance should be restarted. +func (i *Instance) ShouldRestartIsSet() bool { + return i.ShouldRestart +} + +// CommandLineOperationIsSet returns whether the command line option is set. +func (i *Instance) CommandLineOperationIsSet() bool { + return i.CommandLineOperation != nil +} + +// CommandLineOperationExecute executes the set command line option. +func (i *Instance) CommandLineOperationExecute() error { + return i.CommandLineOperation() +} + +// AddModule adds a module to the service group. +func (i *Instance) AddModule(m mgr.Module) { + i.serviceGroup.Add(m) +} diff --git a/service/integration/etw_windows.go b/service/integration/etw_windows.go new file mode 100644 index 00000000..d655967a --- /dev/null +++ b/service/integration/etw_windows.go @@ -0,0 +1,114 @@ +//go:build windows +// +build windows + +package integration + +import ( + "fmt" + + "golang.org/x/sys/windows" +) + +type ETWFunctions struct { + createState *windows.Proc + initializeSession *windows.Proc + startTrace *windows.Proc + flushTrace *windows.Proc + stopTrace *windows.Proc + destroySession *windows.Proc + stopOldSession *windows.Proc +} + +func initializeETW(dll *windows.DLL) (*ETWFunctions, error) { + functions := &ETWFunctions{} + var err error + functions.createState, err = dll.FindProc("PM_ETWCreateState") + if err != nil { + return functions, fmt.Errorf("failed to load function PM_ETWCreateState: %q", err) + } + functions.initializeSession, err = dll.FindProc("PM_ETWInitializeSession") + if err != nil { + return functions, fmt.Errorf("failed to load function PM_ETWInitializeSession: %q", err) + } + functions.startTrace, err = dll.FindProc("PM_ETWStartTrace") + if err != nil { + return functions, fmt.Errorf("failed to load function PM_ETWStartTrace: %q", err) + } + functions.flushTrace, err = dll.FindProc("PM_ETWFlushTrace") + if err != nil { + return functions, fmt.Errorf("failed to load function PM_ETWFlushTrace: %q", err) + } + functions.stopTrace, err = dll.FindProc("PM_ETWStopTrace") + if err != nil { + return functions, fmt.Errorf("failed to load function PM_ETWStopTrace: %q", err) + } + functions.destroySession, err = dll.FindProc("PM_ETWDestroySession") + if err != nil { + return functions, fmt.Errorf("failed to load function PM_ETWDestroySession: %q", err) + } + functions.stopOldSession, err = dll.FindProc("PM_ETWStopOldSession") + if err != nil { + return functions, fmt.Errorf("failed to load function PM_ETWDestroySession: %q", err) + } + return functions, nil +} + +// CreateState calls the dll createState C function. +func (etw ETWFunctions) CreateState(callback uintptr) uintptr { + state, _, _ := etw.createState.Call(callback) + return state +} + +// InitializeSession calls the dll initializeSession C function. +func (etw ETWFunctions) InitializeSession(state uintptr) error { + rc, _, _ := etw.initializeSession.Call(state) + if rc != 0 { + return fmt.Errorf("failed with status code: %d", rc) + } + return nil +} + +// StartTrace calls the dll startTrace C function. +func (etw ETWFunctions) StartTrace(state uintptr) error { + rc, _, _ := etw.startTrace.Call(state) + if rc != 0 { + return fmt.Errorf("failed with status code: %d", rc) + } + return nil +} + +// FlushTrace calls the dll flushTrace C function. +func (etw ETWFunctions) FlushTrace(state uintptr) error { + rc, _, _ := etw.flushTrace.Call(state) + if rc != 0 { + return fmt.Errorf("failed with status code: %d", rc) + } + return nil +} + +// StopTrace calls the dll stopTrace C function. +func (etw ETWFunctions) StopTrace(state uintptr) error { + rc, _, _ := etw.stopTrace.Call(state) + if rc != 0 { + return fmt.Errorf("failed with status code: %d", rc) + } + return nil +} + +// DestroySession calls the dll destroySession C function. +func (etw ETWFunctions) DestroySession(state uintptr) error { + rc, _, _ := etw.destroySession.Call(state) + if rc != 0 { + return fmt.Errorf("failed with status code: %d", rc) + } + return nil +} + +// StopOldSession calls the dll stopOldSession C function. +func (etw ETWFunctions) StopOldSession() error { + rc, _, _ := etw.stopOldSession.Call() + if rc != 0 { + return fmt.Errorf("failed with status code: %d", rc) + } + return nil +} diff --git a/service/integration/integration.go b/service/integration/integration.go new file mode 100644 index 00000000..2189b152 --- /dev/null +++ b/service/integration/integration.go @@ -0,0 +1,16 @@ +//go:build !windows +// +build !windows + +package integration + +type OSSpecific struct{} + +// Initialize is empty on any OS different then Windows. +func (i *OSIntegration) Initialize() error { + return nil +} + +// CleanUp releases any resourses allocated during initializaion. +func (i *OSIntegration) CleanUp() error { + return nil +} diff --git a/service/integration/integration_windows.go b/service/integration/integration_windows.go new file mode 100644 index 00000000..b049638d --- /dev/null +++ b/service/integration/integration_windows.go @@ -0,0 +1,85 @@ +//go:build windows +// +build windows + +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 +} + +// 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 { + return fmt.Errorf("failed to load dll: %q", err) + } + + // Enumerate all needed dll functions. + i.os.etwFunctions, err = initializeETW(i.os.dll) + if err != nil { + return err + } + + // Notify listeners + i.OnInitializedEvent.Submit(struct{}{}) + + return nil +} + +// CleanUp releases any resources allocated during initialization. +func (i *OSIntegration) CleanUp() error { + if i.os.dll != nil { + return i.os.dll.Release() + } + return nil +} + +// 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 new file mode 100644 index 00000000..4250920b --- /dev/null +++ b/service/integration/module.go @@ -0,0 +1,49 @@ +package integration + +import ( + "github.com/safing/portmaster/service/mgr" + "github.com/safing/portmaster/service/updates" +) + +// OSIntegration module provides special integration with the OS. +type OSIntegration struct { + m *mgr.Manager + + OnInitializedEvent *mgr.EventMgr[struct{}] + + //nolint:unused + os OSSpecific + + instance instance +} + +// New returns a new OSIntegration module. +func New(instance instance) (*OSIntegration, error) { + m := mgr.New("OSIntegration") + module := &OSIntegration{ + m: m, + OnInitializedEvent: mgr.NewEventMgr[struct{}]("on-initialized", m), + instance: instance, + } + + return module, nil +} + +// Manager returns the module manager. +func (i *OSIntegration) Manager() *mgr.Manager { + return i.m +} + +// Start starts the module. +func (i *OSIntegration) Start() error { + return i.Initialize() +} + +// Stop stops the module. +func (i *OSIntegration) Stop() error { + return i.CleanUp() +} + +type instance interface { + BinaryUpdates() *updates.Updater +} 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/intel/filterlists/index.go b/service/intel/filterlists/index.go index 9ba0ec17..8293f84c 100644 --- a/service/intel/filterlists/index.go +++ b/service/intel/filterlists/index.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "os" + "strings" "sync" "github.com/safing/portmaster/base/database" @@ -181,18 +182,18 @@ func updateListIndex() error { } // Check if the version in the cache is current. - _, err = getListIndexFromCache() + index, err := getListIndexFromCache() switch { case errors.Is(err, database.ErrNotFound): log.Info("filterlists: index not in cache, starting update") case err != nil: log.Warningf("filterlists: failed to load index from cache, starting update: %s", err) - // case !listIndexUpdate.EqualsVersion(strings.TrimPrefix(index.Version, "v")): - // log.Infof( - // "filterlists: index from cache is outdated, starting update (%s != %s)", - // strings.TrimPrefix(index.Version, "v"), - // listIndexUpdate.Version(), - // ) + case listIndexUpdate.Version != strings.TrimPrefix(index.Version, "v"): + log.Infof( + "filterlists: index from cache is outdated, starting update (%s != %s)", + strings.TrimPrefix(index.Version, "v"), + listIndexUpdate.Version, + ) default: // List is in cache and current, there is nothing to do. log.Debug("filterlists: index is up to date") @@ -202,8 +203,6 @@ func updateListIndex() error { return nil } - // case listIndexUpdate.UpgradeAvailable(): - // log.Info("filterlists: index update available, starting update") default: // Index is loaded and no update is available, there is nothing to do. return nil @@ -236,19 +235,22 @@ func updateListIndex() error { // ResolveListIDs resolves a slice of source or category IDs into // a slice of distinct source IDs. func ResolveListIDs(ids []string) ([]string, error) { + // Try get the list index, err := getListIndexFromCache() if err != nil { if errors.Is(err, database.ErrNotFound) { - if err := updateListIndex(); err != nil { + // Update the list index + if err = updateListIndex(); err != nil { return nil, err } - - // retry resolving IDs - return ResolveListIDs(ids) + // Retry getting the list. + if index, err = getListIndexFromCache(); err != nil { + return nil, err + } + } else { + log.Errorf("failed to resolved ids %v: %s", ids, err) + return nil, err } - - log.Errorf("failed to resolved ids %v: %s", ids, err) - return nil, err } resolved := index.getDistictSourceIDs(ids...) 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/nameserver/nameserver.go b/service/nameserver/nameserver.go index c699cd99..1d346220 100644 --- a/service/nameserver/nameserver.go +++ b/service/nameserver/nameserver.go @@ -224,8 +224,8 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, request *dns.Msg) } // Save the request as open, as we don't know if there will be a connection or not. - network.SaveOpenDNSRequest(q, rrCache, conn) firewall.UpdateIPsAndCNAMEs(q, rrCache, conn) + network.SaveOpenDNSRequest(q, rrCache, conn) case network.VerdictUndeterminable: fallthrough diff --git a/service/netquery/database.go b/service/netquery/database.go index d66e3222..81eedf2d 100644 --- a/service/netquery/database.go +++ b/service/netquery/database.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "io" - "os" "path/filepath" "sort" "strings" @@ -19,6 +18,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" @@ -128,7 +128,8 @@ type ( // handed over to SQLite. func New(dbPath string) (*Database, error) { historyParentDir := filepath.Join(module.instance.DataDir(), "databases") - if err := os.MkdirAll(historyParentDir, 0o0700); err != nil { + err := utils.EnsureDirectory(historyParentDir, utils.AdminOnlyExecPermission) + if err != nil { return nil, fmt.Errorf("failed to ensure database directory exists: %w", err) } @@ -226,7 +227,8 @@ func (db *Database) Close() error { // VacuumHistory rewrites the history database in order to purge deleted records. func VacuumHistory(ctx context.Context) (err error) { historyParentDir := filepath.Join(module.instance.DataDir(), "databases") - if err := os.MkdirAll(historyParentDir, 0o0700); err != nil { + err = utils.EnsureDirectory(historyParentDir, utils.AdminOnlyExecPermission) + if err != nil { return fmt.Errorf("failed to ensure database directory exists: %w", err) } diff --git a/service/network/api.go b/service/network/api.go index 8af6eb26..5c18bcfd 100644 --- a/service/network/api.go +++ b/service/network/api.go @@ -93,7 +93,6 @@ func debugInfo(ar *api.Request) (data []byte, err error) { config.AddToDebugInfo(di) // Detailed information. - // TODO(vladimir): updates.AddToDebugInfo(di) // compat.AddToDebugInfo(di) // TODO: Cannot use due to interception import requirement which we don't want for SPN Hubs. di.AddGoroutineStack() diff --git a/service/network/connection.go b/service/network/connection.go index 7ea96400..2cdf12e7 100644 --- a/service/network/connection.go +++ b/service/network/connection.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net" + "runtime" "sync" "sync/atomic" "time" @@ -18,6 +19,7 @@ import ( "github.com/safing/portmaster/service/netenv" "github.com/safing/portmaster/service/network/netutils" "github.com/safing/portmaster/service/network/packet" + "github.com/safing/portmaster/service/network/reference" "github.com/safing/portmaster/service/process" _ "github.com/safing/portmaster/service/process/tags" "github.com/safing/portmaster/service/resolver" @@ -536,12 +538,41 @@ func (conn *Connection) GatherConnectionInfo(pkt packet.Packet) (err error) { // Find domain and DNS context of entity. if conn.Entity.Domain == "" && conn.process.Profile() != nil { + profileScope := conn.process.Profile().LocalProfile().ID // check if we can find a domain for that IP - ipinfo, err := resolver.GetIPInfo(conn.process.Profile().LocalProfile().ID, pkt.Info().RemoteIP().String()) + ipinfo, err := resolver.GetIPInfo(profileScope, pkt.Info().RemoteIP().String()) if err != nil { // Try again with the global scope, in case DNS went through the system resolver. ipinfo, err = resolver.GetIPInfo(resolver.IPInfoProfileScopeGlobal, pkt.Info().RemoteIP().String()) } + + if runtime.GOOS == "windows" && err != nil { + // On windows domains may come with delay. + if module.instance.Resolver().IsDisabled() && conn.shouldWaitForDomain() { + // Flush the dns listener buffer and try again. + for i := range 4 { + err = module.instance.DNSMonitor().Flush() + if err != nil { + // Error flushing, dont try again. + break + } + // Try with profile scope + ipinfo, err = resolver.GetIPInfo(profileScope, pkt.Info().RemoteIP().String()) + if err == nil { + log.Tracer(pkt.Ctx()).Debugf("network: found domain with scope (%s) from dnsmonitor after %d tries", profileScope, +1) + break + } + // Try again with the global scope + 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) + break + } + time.Sleep(5 * time.Millisecond) + } + } + } + if err == nil { lastResolvedDomain := ipinfo.MostRecentDomain() if lastResolvedDomain != nil { @@ -869,3 +900,17 @@ func (conn *Connection) String() string { return fmt.Sprintf("%s -> %s", conn.process, conn.Entity.IP) } } + +func (conn *Connection) shouldWaitForDomain() bool { + // Should wait for Global Unicast, outgoing and not ICMP connections + switch { + case conn.Entity.IPScope != netutils.Global: + return false + case conn.Inbound: + return false + case reference.IsICMP(conn.Entity.Protocol): + return false + } + + return true +} diff --git a/service/network/module.go b/service/network/module.go index 4cab1cb1..eb9b452d 100644 --- a/service/network/module.go +++ b/service/network/module.go @@ -9,10 +9,12 @@ import ( "sync/atomic" "github.com/safing/portmaster/base/log" + "github.com/safing/portmaster/service/firewall/interception/dnsmonitor" "github.com/safing/portmaster/service/mgr" "github.com/safing/portmaster/service/netenv" "github.com/safing/portmaster/service/network/state" "github.com/safing/portmaster/service/profile" + "github.com/safing/portmaster/service/resolver" ) // Events. @@ -188,4 +190,6 @@ func New(instance instance) (*Network, error) { type instance interface { Profile() *profile.ProfileModule + Resolver() *resolver.ResolverModule + DNSMonitor() *dnsmonitor.DNSMonitor } diff --git a/service/network/packet/parse.go b/service/network/packet/parse.go index 562546af..adfc69d9 100644 --- a/service/network/packet/parse.go +++ b/service/network/packet/parse.go @@ -106,11 +106,12 @@ func checkError(packet gopacket.Packet, info *Info) error { return nil } -// Parse parses an IP packet and saves the information in the given packet object. -func Parse(packetData []byte, pktBase *Base) (err error) { +// ParseLayer3 parses an IP packet and saves the information in the given packet object. +func ParseLayer3(packetData []byte, pktBase *Base) (err error) { if len(packetData) == 0 { return errors.New("empty packet") } + pktBase.layer3Data = packetData ipVersion := packetData[0] >> 4 @@ -155,6 +156,62 @@ func Parse(packetData []byte, pktBase *Base) (err error) { return nil } +// ParseLayer4 parses an layer 4 packet and saves the information in the given packet object. +func ParseLayer4(packetData []byte, pktBase *Base) (err error) { + if len(packetData) == 0 { + return errors.New("empty packet") + } + + var layer gopacket.LayerType + switch pktBase.info.Protocol { + case ICMP: + layer = layers.LayerTypeICMPv4 + case IGMP: + layer = layers.LayerTypeIGMP + case TCP: + layer = layers.LayerTypeTCP + case UDP: + layer = layers.LayerTypeUDP + case ICMPv6: + layer = layers.LayerTypeICMPv6 + case UDPLite: + return fmt.Errorf("UDPLite not supported") + case RAW: + return fmt.Errorf("RAW protocol not supported") + case AnyHostInternalProtocol61: + return fmt.Errorf("AnyHostInternalProtocol61 protocol not supported") + default: + return fmt.Errorf("protocol not supported") + } + + packet := gopacket.NewPacket(packetData, layer, gopacket.DecodeOptions{ + Lazy: true, + NoCopy: true, + }) + + availableDecoders := []func(gopacket.Packet, *Info) error{ + parseTCP, + parseUDP, + // parseUDPLite, // We don't yet support udplite. + parseICMPv4, + parseICMPv6, + parseIGMP, + checkError, + } + + for _, dec := range availableDecoders { + if err := dec(packet, pktBase.Info()); err != nil { + return err + } + } + + pktBase.layers = packet + if transport := packet.TransportLayer(); transport != nil { + pktBase.layer5Data = transport.LayerPayload() + } + return nil +} + func init() { genIPProtocolFromLayerType() } diff --git a/service/profile/config-update.go b/service/profile/config-update.go index f3e8161c..d958e307 100644 --- a/service/profile/config-update.go +++ b/service/profile/config-update.go @@ -143,7 +143,7 @@ func updateGlobalConfigProfile(_ context.Context) error { module.states.Add(mgr.State{ ID: globalConfigProfileErrorID, Name: "Internal Settings Failure", - Message: fmt.Sprintf("Some global settings might not be applied correctly. You can try restarting the Portmaster to resolve this problem. Error: %s", err), + Message: fmt.Sprintf("Some global settings might not be applied correctly. You can try restarting the Portmaster to resolve this problem. Error: %s", lastErr), Type: mgr.StateTypeWarning, }) } diff --git a/service/profile/module.go b/service/profile/module.go index 68845047..06dffecd 100644 --- a/service/profile/module.go +++ b/service/profile/module.go @@ -3,7 +3,6 @@ package profile import ( "errors" "fmt" - "os" "path/filepath" "sync/atomic" @@ -11,6 +10,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 +66,18 @@ func prep() error { } // Setup icon storage location. - iconsDir := filepath.Join(module.instance.DataDir(), "databases", "icons") - if err := os.MkdirAll(iconsDir, 0o0700); err != nil { - return fmt.Errorf("failed to create/check icons directory: %w", err) + databaseDir := filepath.Join(module.instance.DataDir(), "databases") + // Ensure folder existents and permission + err := utils.EnsureDirectory(databaseDir, utils.AdminOnlyExecPermission) + if err != nil { + return fmt.Errorf("failed to ensure directory existence %s: %w", databaseDir, err) } + iconsDir := filepath.Join(databaseDir, "icons") + err = utils.EnsureDirectory(iconsDir, utils.AdminOnlyExecPermission) + if err != nil { + return fmt.Errorf("failed to ensure directory existence %s: %w", iconsDir, err) + } + binmeta.ProfileIconStoragePath = iconsDir return nil diff --git a/service/resolver/ipinfo.go b/service/resolver/ipinfo.go index 89cf9297..32cc0cc3 100644 --- a/service/resolver/ipinfo.go +++ b/service/resolver/ipinfo.go @@ -52,6 +52,27 @@ type ResolvedDomain struct { Expires int64 } +// AddCNAMEs adds all cnames from the map related to its set Domain. +func (resolved *ResolvedDomain) AddCNAMEs(cnames map[string]string) { + // Resolve all CNAMEs in the correct order and add the to the record - up to max 50 layers. + domain := resolved.Domain +domainLoop: + for range 50 { + nextDomain, isCNAME := cnames[domain] + switch { + case !isCNAME: + break domainLoop + case nextDomain == resolved.Domain: + break domainLoop + case nextDomain == domain: + break domainLoop + } + + resolved.CNAMEs = append(resolved.CNAMEs, nextDomain) + domain = nextDomain + } +} + // String returns a string representation of ResolvedDomain including // the CNAME chain. It implements fmt.Stringer. func (resolved *ResolvedDomain) String() string { diff --git a/service/resolver/main.go b/service/resolver/main.go index 8a43d12b..107d5fc8 100644 --- a/service/resolver/main.go +++ b/service/resolver/main.go @@ -29,6 +29,8 @@ type ResolverModule struct { //nolint failingResolverWorkerMgr *mgr.WorkerMgr suggestUsingStaleCacheTask *mgr.WorkerMgr + isDisabled atomic.Bool + states *mgr.StateMgr } @@ -52,6 +54,10 @@ func (rm *ResolverModule) Stop() error { return nil } +func (rm *ResolverModule) IsDisabled() bool { + return rm.isDisabled.Load() +} + func prep() error { // Set DNS test connectivity function for the online status check netenv.DNSTestQueryFunc = func(ctx context.Context, fdqn string) (ips []net.IP, ok bool, err error) { diff --git a/service/resolver/resolver.go b/service/resolver/resolver.go index 1a1a12f4..35d71329 100644 --- a/service/resolver/resolver.go +++ b/service/resolver/resolver.go @@ -17,17 +17,22 @@ import ( // DNS Resolver Attributes. const ( - ServerTypeDNS = "dns" - ServerTypeTCP = "tcp" - ServerTypeDoT = "dot" - ServerTypeDoH = "doh" - ServerTypeMDNS = "mdns" - ServerTypeEnv = "env" + ServerTypeDNS = "dns" + ServerTypeTCP = "tcp" + ServerTypeDoT = "dot" + ServerTypeDoH = "doh" + ServerTypeMDNS = "mdns" + ServerTypeEnv = "env" + ServerTypeMonitor = "monitor" + ServerTypeFirewall = "firewall" ServerSourceConfigured = "config" ServerSourceOperatingSystem = "system" ServerSourceMDNS = "mdns" ServerSourceEnv = "env" + ServerSourceETW = "etw" + ServerSourceSystemd = "systemd" + ServerSourceFirewall = "firewall" ) // DNS resolver scheme aliases. @@ -82,11 +87,11 @@ type ResolverInfo struct { //nolint:golint,maligned // TODO Name string // Type describes the type of the resolver. - // Possible values include dns, tcp, dot, doh, mdns, env. + // Possible values include dns, tcp, dot, doh, mdns, env, monitor, firewall. Type string // Source describes where the resolver configuration came from. - // Possible values include config, system, mdns, env. + // Possible values include config, system, mdns, env, etw, systemd, firewall. Source string // IP is the IP address of the resolver diff --git a/service/resolver/resolvers.go b/service/resolver/resolvers.go index c5609a01..6510036b 100644 --- a/service/resolver/resolvers.go +++ b/service/resolver/resolvers.go @@ -388,7 +388,6 @@ func loadResolvers() { // Resolve module error about missing resolvers. module.states.Remove(missingResolversErrorID) - // Check if settings were changed and clear name cache when they did. newResolverConfig := configuredNameServers() if len(currentResolverConfig) > 0 && @@ -399,6 +398,14 @@ func loadResolvers() { return err }) } + + // If no resolvers are configure set the disabled state. So other modules knows that the users does not want to use Portmaster resolver. + if len(newResolverConfig) == 0 { + module.isDisabled.Store(true) + } else { + module.isDisabled.Store(false) + } + currentResolverConfig = newResolverConfig newResolvers := append( @@ -431,7 +438,7 @@ func loadResolvers() { // save resolvers globalResolvers = newResolvers - // assing resolvers to scopes + // assign resolvers to scopes setScopedResolvers(globalResolvers) // set active resolvers (for cache validation) @@ -503,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..a058c508 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.PublicWriteExecPermission) + if err != nil { + log.Warningf("ui: failed to set permissions to directory %s: %s", execDir, err) + } + return nil } diff --git a/service/updates.go b/service/updates.go deleted file mode 100644 index 3c717951..00000000 --- a/service/updates.go +++ /dev/null @@ -1,120 +0,0 @@ -package service - -import ( - "path/filepath" - go_runtime "runtime" - - "github.com/safing/jess" - "github.com/safing/portmaster/service/updates" -) - -var ( - DefaultStableBinaryIndexURLs = []string{ - "https://updates.safing.io/stable.v3.json", - } - DefaultBetaBinaryIndexURLs = []string{ - "https://updates.safing.io/beta.v3.json", - } - DefaultStagingBinaryIndexURLs = []string{ - "https://updates.safing.io/staging.v3.json", - } - DefaultSupportBinaryIndexURLs = []string{ - "https://updates.safing.io/support.v3.json", - } - - DefaultIntelIndexURLs = []string{ - "https://updates.safing.io/intel.v3.json", - } - - // BinarySigningKeys holds the signing keys in text format. - BinarySigningKeys = []string{ - // Safing Code Signing Key #1 - "recipient:public-ed25519-key:safing-code-signing-key-1:92bgBLneQUWrhYLPpBDjqHbpFPuNVCPAaivQ951A4aq72HcTiw7R1QmPJwFM1mdePAvEVDjkeb8S4fp2pmRCsRa8HrCvWQEjd88rfZ6TznJMfY4g7P8ioGFjfpyx2ZJ8WCZJG5Qt4Z9nkabhxo2Nbi3iywBTYDLSbP5CXqi7jryW7BufWWuaRVufFFzhwUC2ryWFWMdkUmsAZcvXwde4KLN9FrkWAy61fGaJ8GCwGnGCSitANnU2cQrsGBXZzxmzxwrYD", - // Safing Code Signing Key #2 - "recipient:public-ed25519-key:safing-code-signing-key-2:92bgBLneQUWrhYLPpBDjqHbPC2d1o5JMyZFdavWBNVtdvbPfzDewLW95ScXfYPHd3QvWHSWCtB4xpthaYWxSkK1kYiGp68DPa2HaU8yQ5dZhaAUuV4Kzv42pJcWkCeVnBYqgGBXobuz52rFqhDJy3rz7soXEmYhJEJWwLwMeioK3VzN3QmGSYXXjosHMMNC76rjufSoLNtUQUWZDSnHmqbuxbKMCCsjFXUGGhtZVyb7bnu7QLTLk6SKHBJDMB6zdL9sw3", - } - - // BinarySigningTrustStore is an in-memory trust store with the signing keys. - BinarySigningTrustStore = jess.NewMemTrustStore() -) - -func init() { - for _, signingKey := range BinarySigningKeys { - rcpt, err := jess.RecipientFromTextFormat(signingKey) - if err != nil { - panic(err) - } - err = BinarySigningTrustStore.StoreSignet(rcpt) - if err != nil { - panic(err) - } - } -} - -func MakeUpdateConfigs(svcCfg *ServiceConfig) (binaryUpdateConfig, intelUpdateConfig *updates.Config, err error) { - switch go_runtime.GOOS { - case "windows": - binaryUpdateConfig = &updates.Config{ - Name: "binaries", - Directory: svcCfg.BinDir, - DownloadDirectory: filepath.Join(svcCfg.DataDir, "download_binaries"), - PurgeDirectory: filepath.Join(svcCfg.BinDir, "upgrade_obsolete_binaries"), - Ignore: []string{"databases", "intel", "config.json"}, - IndexURLs: svcCfg.BinariesIndexURLs, - IndexFile: "index.json", - Verify: svcCfg.VerifyBinaryUpdates, - AutoCheck: true, // FIXME: Get from setting. - AutoDownload: false, - AutoApply: false, - NeedsRestart: true, - Notify: true, - } - intelUpdateConfig = &updates.Config{ - Name: "intel", - Directory: filepath.Join(svcCfg.DataDir, "intel"), - DownloadDirectory: filepath.Join(svcCfg.DataDir, "download_intel"), - PurgeDirectory: filepath.Join(svcCfg.DataDir, "upgrade_obsolete_intel"), - IndexURLs: svcCfg.IntelIndexURLs, - IndexFile: "index.json", - Verify: svcCfg.VerifyIntelUpdates, - AutoCheck: true, // FIXME: Get from setting. - AutoDownload: true, - AutoApply: true, - NeedsRestart: false, - Notify: false, - } - - case "linux": - binaryUpdateConfig = &updates.Config{ - Name: "binaries", - Directory: svcCfg.BinDir, - DownloadDirectory: filepath.Join(svcCfg.DataDir, "download_binaries"), - PurgeDirectory: filepath.Join(svcCfg.DataDir, "upgrade_obsolete_binaries"), - Ignore: []string{"databases", "intel", "config.json"}, - IndexURLs: svcCfg.BinariesIndexURLs, - IndexFile: "index.json", - Verify: svcCfg.VerifyBinaryUpdates, - AutoCheck: true, // FIXME: Get from setting. - AutoDownload: false, - AutoApply: false, - NeedsRestart: true, - Notify: true, - } - intelUpdateConfig = &updates.Config{ - Name: "intel", - Directory: filepath.Join(svcCfg.DataDir, "intel"), - DownloadDirectory: filepath.Join(svcCfg.DataDir, "download_intel"), - PurgeDirectory: filepath.Join(svcCfg.DataDir, "upgrade_obsolete_intel"), - IndexURLs: svcCfg.IntelIndexURLs, - IndexFile: "index.json", - Verify: svcCfg.VerifyIntelUpdates, - AutoCheck: true, // FIXME: Get from setting. - AutoDownload: true, - AutoApply: true, - NeedsRestart: false, - Notify: false, - } - } - - return -} diff --git a/service/updates/downloader.go b/service/updates/downloader.go index 30c32261..af51bbad 100644 --- a/service/updates/downloader.go +++ b/service/updates/downloader.go @@ -16,6 +16,7 @@ import ( "path/filepath" "github.com/safing/portmaster/base/log" + "github.com/safing/portmaster/base/utils" ) type Downloader struct { @@ -37,7 +38,7 @@ func NewDownloader(u *Updater, indexURLs []string) *Downloader { func (d *Downloader) updateIndex(ctx context.Context) error { // Make sure dir exists. - err := os.MkdirAll(d.u.cfg.DownloadDirectory, defaultDirMode) + err := utils.EnsureDirectory(d.u.cfg.DownloadDirectory, utils.PublicReadExecPermission) if err != nil { return fmt.Errorf("create download directory: %s", d.u.cfg.DownloadDirectory) } @@ -65,7 +66,7 @@ func (d *Downloader) updateIndex(ctx context.Context) error { // Write the index into a file. indexFilepath := filepath.Join(d.u.cfg.DownloadDirectory, d.u.cfg.IndexFile) - err = os.WriteFile(indexFilepath, indexData, defaultFileMode) + err = os.WriteFile(indexFilepath, indexData, utils.PublicReadExecPermission.AsUnixPermission()) if err != nil { return fmt.Errorf("write index file: %w", err) } @@ -81,7 +82,7 @@ func (d *Downloader) getIndex(ctx context.Context, url string) (indexData []byte } // Verify and parse index. - bundle, err = ParseIndex(indexData, d.u.cfg.Verify) + bundle, err = ParseIndex(indexData, d.u.cfg.Platform, d.u.cfg.Verify) if err != nil { return nil, nil, fmt.Errorf("parse index: %w", err) } @@ -130,7 +131,7 @@ func (d *Downloader) gatherExistingFiles(dir string) error { func (d *Downloader) downloadArtifacts(ctx context.Context) error { // Make sure dir exists. - err := os.MkdirAll(d.u.cfg.DownloadDirectory, defaultDirMode) + err := utils.EnsureDirectory(d.u.cfg.DownloadDirectory, utils.PublicReadExecPermission) if err != nil { return fmt.Errorf("create download directory: %s", d.u.cfg.DownloadDirectory) } @@ -176,11 +177,13 @@ artifacts: // Write artifact to temporary file. tmpFilename := dstFilePath + ".download" - err = os.WriteFile(tmpFilename, artifactData, artifact.GetFileMode()) + err = os.WriteFile(tmpFilename, artifactData, artifact.GetFileMode().AsUnixPermission()) if err != nil { return fmt.Errorf("write %s to temp file: %w", artifact.Filename, err) } + _ = utils.SetFilePermission(tmpFilename, artifact.GetFileMode()) + // Rename/Move to actual location. err = os.Rename(tmpFilename, dstFilePath) if err != nil { @@ -192,7 +195,7 @@ artifacts: return nil } -func (d *Downloader) getArtifact(ctx context.Context, artifact Artifact, url string) ([]byte, error) { +func (d *Downloader) getArtifact(ctx context.Context, artifact *Artifact, url string) ([]byte, error) { // Download data from URL. artifactData, err := d.downloadData(ctx, url) if err != nil { @@ -203,14 +206,14 @@ func (d *Downloader) getArtifact(ctx context.Context, artifact Artifact, url str // TODO: Normally we should do operations on "untrusted" data _after_ verification, // but we really want the checksum to be for the unpacked data. Should we add another checksum, or is HTTPS enough? if artifact.Unpack != "" { - artifactData, err = decompress(artifact.Unpack, artifactData) + artifactData, err = Decompress(artifact.Unpack, artifactData) if err != nil { return nil, fmt.Errorf("decompress: %w", err) } } // Verify checksum. - if err := checkSHA256Sum(artifactData, artifact.SHA256); err != nil { + if err := CheckSHA256Sum(artifactData, artifact.SHA256); err != nil { return nil, err } @@ -247,7 +250,8 @@ func (d *Downloader) downloadData(ctx context.Context, url string) ([]byte, erro return content, nil } -func decompress(cType string, fileBytes []byte) ([]byte, error) { +// Decompress decompresses the given data according to the specified type. +func Decompress(cType string, fileBytes []byte) ([]byte, error) { switch cType { case "zip": return decompressZip(fileBytes) diff --git a/service/updates/index.go b/service/updates/index.go index 8a30f643..d98d92d7 100644 --- a/service/updates/index.go +++ b/service/updates/index.go @@ -8,7 +8,6 @@ import ( "errors" "fmt" "io" - "io/fs" "os" "path/filepath" "runtime" @@ -18,6 +17,7 @@ import ( "github.com/safing/jess" "github.com/safing/jess/filesig" + "github.com/safing/portmaster/base/utils" ) // MaxUnpackSize defines the maximum size that is allowed to be unpacked. @@ -41,17 +41,12 @@ type Artifact struct { } // GetFileMode returns the required filesystem permission for the artifact. -func (a *Artifact) GetFileMode() os.FileMode { - // Special case for portmaster ui. Should be able to be executed from the regular user - if a.Platform == currentPlatform && a.Filename == "portmaster" { - return executableUIFileMode - } - +func (a *Artifact) GetFileMode() utils.FSPermission { if a.Platform == currentPlatform { - return executableFileMode + return utils.PublicReadExecPermission } - return defaultFileMode + return utils.PublicReadPermission } // Path returns the absolute path to the local file. @@ -117,16 +112,17 @@ func (a *Artifact) export(dir string, indexVersion *semver.Version) *Artifact { // Index represents a collection of artifacts with metadata. type Index struct { - Name string `json:"Name"` - Version string `json:"Version"` - Published time.Time `json:"Published"` - Artifacts []Artifact `json:"Artifacts"` + Name string `json:"Name"` + Version string `json:"Version"` + Published time.Time `json:"Published"` + Artifacts []*Artifact `json:"Artifacts"` versionNum *semver.Version } // LoadIndex loads and parses an index from the given filename. -func LoadIndex(filename string, trustStore jess.TrustStore) (*Index, error) { +// Leave platform empty to use current platform. +func LoadIndex(filename string, platform string, trustStore jess.TrustStore) (*Index, error) { // Read index file from disk. content, err := os.ReadFile(filename) if err != nil { @@ -134,11 +130,12 @@ func LoadIndex(filename string, trustStore jess.TrustStore) (*Index, error) { } // Parse and return. - return ParseIndex(content, trustStore) + return ParseIndex(content, platform, trustStore) } // ParseIndex parses an index from a json string. -func ParseIndex(jsonContent []byte, trustStore jess.TrustStore) (*Index, error) { +// Leave platform empty to use current platform. +func ParseIndex(jsonContent []byte, platform string, trustStore jess.TrustStore) (*Index, error) { // Verify signature. if trustStore != nil { if err := filesig.VerifyJSONSignature(jsonContent, trustStore); err != nil { @@ -153,8 +150,13 @@ func ParseIndex(jsonContent []byte, trustStore jess.TrustStore) (*Index, error) return nil, fmt.Errorf("parse index: %w", err) } + // Check platform. + if platform == "" { + platform = currentPlatform + } + // Initialize data. - err = index.init() + err = index.init(platform) if err != nil { return nil, err } @@ -162,7 +164,7 @@ func ParseIndex(jsonContent []byte, trustStore jess.TrustStore) (*Index, error) return index, nil } -func (index *Index) init() error { +func (index *Index) init(platform string) error { // Parse version number, if set. if index.Version != "" { versionNum, err := semver.NewVersion(index.Version) @@ -172,10 +174,10 @@ func (index *Index) init() error { index.versionNum = versionNum } - // Filter artifacts by current platform. - filtered := make([]Artifact, 0) + // Filter artifacts by platform. + filtered := make([]*Artifact, 0) for _, a := range index.Artifacts { - if a.Platform == "" || a.Platform == currentPlatform { + if a.Platform == "" || a.Platform == platform { filtered = append(filtered, a) } } @@ -189,6 +191,7 @@ func (index *Index) init() error { a.versionNum = v } } else { + a.Version = index.Version a.versionNum = index.versionNum } } @@ -252,7 +255,7 @@ func (index *Index) ShouldUpgradeTo(newIndex *Index) error { // VerifyArtifacts checks if all artifacts are present in the given dir and have the correct hash. func (index *Index) VerifyArtifacts(dir string) error { for _, artifact := range index.Artifacts { - err := checkSHA256SumFile(filepath.Join(dir, artifact.Filename), artifact.SHA256) + err := CheckSHA256SumFile(filepath.Join(dir, artifact.Filename), artifact.SHA256) if err != nil { return fmt.Errorf("verify %s: %w", artifact.Filename, err) } @@ -287,7 +290,8 @@ func (index *Index) Export(signingKey *jess.Signet, trustStore jess.TrustStore) return signedIndex, nil } -func checkSHA256SumFile(filename string, sha256sum string) error { +// CheckSHA256SumFile checks the sha256sum of the given file. +func CheckSHA256SumFile(filename string, sha256sum string) error { // Check expected hash. expectedDigest, err := hex.DecodeString(sha256sum) if err != nil { @@ -316,7 +320,8 @@ func checkSHA256SumFile(filename string, sha256sum string) error { return nil } -func checkSHA256Sum(fileData []byte, sha256sum string) error { +// CheckSHA256Sum checks the sha256sum of the given data. +func CheckSHA256Sum(fileData []byte, sha256sum string) error { // Check expected hash. expectedDigest, err := hex.DecodeString(sha256sum) if err != nil { @@ -337,7 +342,7 @@ func checkSHA256Sum(fileData []byte, sha256sum string) error { // copyAndCheckSHA256Sum copies the file from src to dst and check the sha256 sum. // As a special case, if the sha256sum is not given, it is not checked. -func copyAndCheckSHA256Sum(src, dst, sha256sum string, fileMode fs.FileMode) error { +func copyAndCheckSHA256Sum(src, dst, sha256sum string, filePermission utils.FSPermission) error { // Check expected hash. var expectedDigest []byte if sha256sum != "" { @@ -366,7 +371,7 @@ func copyAndCheckSHA256Sum(src, dst, sha256sum string, fileMode fs.FileMode) err // Write to temporary file. tmpDst := dst + ".copy" - err = os.WriteFile(tmpDst, fileData, fileMode) + err = os.WriteFile(tmpDst, fileData, filePermission.AsUnixPermission()) if err != nil { return fmt.Errorf("write temp dst file: %w", err) } @@ -376,6 +381,7 @@ func copyAndCheckSHA256Sum(src, dst, sha256sum string, fileMode fs.FileMode) err if err != nil { return fmt.Errorf("rename dst file after write: %w", err) } + utils.SetFilePermission(dst, filePermission) return nil } diff --git a/service/updates/index_scan.go b/service/updates/index_scan.go index 6e6a6af2..1dc1a7f6 100644 --- a/service/updates/index_scan.go +++ b/service/updates/index_scan.go @@ -95,7 +95,7 @@ settings: // GenerateIndexFromDir generates a index from a given folder. func GenerateIndexFromDir(sourceDir string, cfg IndexScanConfig) (*Index, error) { //nolint:maintidx - artifacts := make(map[string]Artifact) + artifacts := make(map[string]*Artifact) // Initialize. err := cfg.init() @@ -187,11 +187,12 @@ func GenerateIndexFromDir(sourceDir string, cfg IndexScanConfig) (*Index, error) // Step 3: Create new Artifact. - artifact := Artifact{} + artifact := &Artifact{} // Check if the caller provided a template for the artifact. if t, ok := cfg.Templates[identifier]; ok { - artifact = t + fromTemplate := t + artifact = &fromTemplate } // Set artifact properties. @@ -249,10 +250,10 @@ func GenerateIndexFromDir(sourceDir string, cfg IndexScanConfig) (*Index, error) } // Convert to slice and compute hashes. - export := make([]Artifact, 0, len(artifacts)) + export := make([]*Artifact, 0, len(artifacts)) for _, artifact := range artifacts { // Compute hash. - hash, err := getSHA256(artifact.localFile, artifact.Unpack) + hash, err := GetSHA256(artifact.localFile, artifact.Unpack) if err != nil { return nil, fmt.Errorf("calculate hash of file: %s %w", artifact.localFile, err) } @@ -273,7 +274,7 @@ func GenerateIndexFromDir(sourceDir string, cfg IndexScanConfig) (*Index, error) } // Sort final artifacts. - slices.SortFunc(export, func(a, b Artifact) int { + slices.SortFunc(export, func(a, b *Artifact) int { switch { case a.Filename != b.Filename: return strings.Compare(a.Filename, b.Filename) @@ -293,7 +294,8 @@ func GenerateIndexFromDir(sourceDir string, cfg IndexScanConfig) (*Index, error) return index, nil } -func getSHA256(path string, unpackType string) (string, error) { +// GetSHA256 gets the sha256sum of the given file and unpacks it if necessary. +func GetSHA256(path string, unpackType string) (string, error) { content, err := os.ReadFile(path) if err != nil { return "", err @@ -301,7 +303,7 @@ func getSHA256(path string, unpackType string) (string, error) { // Decompress if compression was applied to the file. if unpackType != "" { - content, err = decompress(unpackType, content) + content, err = Decompress(unpackType, content) if err != nil { return "", err } diff --git a/service/updates/module.go b/service/updates/module.go index e5b32129..fa6dc3ab 100644 --- a/service/updates/module.go +++ b/service/updates/module.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" "runtime" + "slices" "strings" "sync" "time" @@ -67,6 +68,8 @@ type Config struct { IndexFile string // Verify enables and specifies the trust the index signatures will be checked against. Verify jess.TrustStore + // Platform defines the platform to download artifacts for. Defaults to current platform. + Platform string // AutoCheck defines that new indexes may be downloaded automatically without outside trigger. AutoCheck bool @@ -115,6 +118,11 @@ func (cfg *Config) Check() error { } } + // Check platform. + if cfg.Platform == "" { + cfg.Platform = currentPlatform + } + return nil } @@ -132,9 +140,11 @@ type Updater struct { EventResourcesUpdated *mgr.EventMgr[struct{}] - corruptedInstallation bool + corruptedInstallation error isUpdateRunning *abool.AtomicBool + started *abool.AtomicBool + configureLock sync.Mutex instance instance } @@ -150,6 +160,7 @@ func New(instance instance, name string, cfg Config) (*Updater, error) { EventResourcesUpdated: mgr.NewEventMgr[struct{}](ResourceUpdateEvent, m), isUpdateRunning: abool.NewBool(false), + started: abool.NewBool(false), instance: instance, } @@ -164,8 +175,14 @@ func New(instance instance, name string, cfg Config) (*Updater, error) { module.upgradeWorkerMgr = m.NewWorkerMgr("upgrader", module.upgradeWorker, nil) // Load index. - index, err := LoadIndex(filepath.Join(cfg.Directory, cfg.IndexFile), cfg.Verify) + index, err := LoadIndex(filepath.Join(cfg.Directory, cfg.IndexFile), cfg.Platform, cfg.Verify) if err == nil { + // Verify artifacts. + if err := index.VerifyArtifacts(cfg.Directory); err != nil { + module.corruptedInstallation = fmt.Errorf("invalid artifact: %w", err) + } + + // Save index to module and return. module.index = index return module, nil } @@ -173,9 +190,10 @@ func New(instance instance, name string, cfg Config) (*Updater, error) { // Fall back to scanning the directory. if !errors.Is(err, os.ErrNotExist) { log.Errorf("updates/%s: invalid index file, falling back to dir scan: %s", cfg.Name, err) + module.corruptedInstallation = fmt.Errorf("invalid index: %w", err) } index, err = GenerateIndexFromDir(cfg.Directory, IndexScanConfig{Version: "0.0.0"}) - if err == nil && index.init() == nil { + if err == nil && index.init(currentPlatform) == nil { module.index = index return module, nil } @@ -203,7 +221,7 @@ func (u *Updater) updateAndUpgrade(w *mgr.WorkerCtx, indexURLs []string, ignoreV } } else { // Otherwise, load index from download dir. - downloader.index, err = LoadIndex(filepath.Join(u.cfg.DownloadDirectory, u.cfg.IndexFile), u.cfg.Verify) + downloader.index, err = LoadIndex(filepath.Join(u.cfg.DownloadDirectory, u.cfg.IndexFile), u.cfg.Platform, u.cfg.Verify) if err != nil { return fmt.Errorf("load previously downloaded index file: %w", err) } @@ -259,6 +277,7 @@ func (u *Updater) updateAndUpgrade(w *mgr.WorkerCtx, indexURLs []string, ignoreV // Check if automatic downloads are enabled. if !u.cfg.AutoDownload && !forceApply { + log.Infof("updates/%s: new update to v%s available, action required to download and upgrade", u.cfg.Name, downloader.index.Version) if u.cfg.Notify && u.instance.Notifications() != nil { u.instance.Notifications().Notify(¬ifications.Notification{ EventID: updateAvailableNotificationID, @@ -304,6 +323,7 @@ func (u *Updater) updateAndUpgrade(w *mgr.WorkerCtx, indexURLs []string, ignoreV // Notify the user that an upgrade is available. if !u.cfg.AutoApply && !forceApply { + log.Infof("updates/%s: new update to v%s available, action required to upgrade", u.cfg.Name, downloader.index.Version) if u.cfg.Notify && u.instance.Notifications() != nil { u.instance.Notifications().Notify(¬ifications.Notification{ EventID: updateAvailableNotificationID, @@ -387,8 +407,15 @@ func (u *Updater) updateAndUpgrade(w *mgr.WorkerCtx, indexURLs []string, ignoreV return nil } +func (u *Updater) getIndexURLsWithLock() []string { + u.configureLock.Lock() + defer u.configureLock.Unlock() + + return u.cfg.IndexURLs +} + func (u *Updater) updateCheckWorker(w *mgr.WorkerCtx) error { - err := u.updateAndUpgrade(w, u.cfg.IndexURLs, false, false) + err := u.updateAndUpgrade(w, u.getIndexURLsWithLock(), false, false) switch { case err == nil: return nil // Success! @@ -404,7 +431,7 @@ func (u *Updater) updateCheckWorker(w *mgr.WorkerCtx) error { } func (u *Updater) upgradeWorker(w *mgr.WorkerCtx) error { - err := u.updateAndUpgrade(w, u.cfg.IndexURLs, false, true) + err := u.updateAndUpgrade(w, u.getIndexURLsWithLock(), false, true) switch { case err == nil: return nil // Success! @@ -423,7 +450,7 @@ func (u *Updater) upgradeWorker(w *mgr.WorkerCtx) error { // and is intended to be used only within a tool, not a service. func (u *Updater) ForceUpdate() error { return u.m.Do("update and upgrade", func(w *mgr.WorkerCtx) error { - return u.updateAndUpgrade(w, u.cfg.IndexURLs, true, true) + return u.updateAndUpgrade(w, u.getIndexURLsWithLock(), true, true) }) } @@ -437,6 +464,33 @@ func (u *Updater) UpdateFromURL(url string) error { return nil } +// Configure makes slight configuration changes to the updater. +// It locks the index, which can take a while an update is running. +func (u *Updater) Configure(autoCheck bool, indexURLs []string) { + u.configureLock.Lock() + defer u.configureLock.Unlock() + + // Apply new config. + var changed bool + if u.cfg.AutoCheck != autoCheck { + u.cfg.AutoCheck = autoCheck + changed = true + } + if !slices.Equal(u.cfg.IndexURLs, indexURLs) { + u.cfg.IndexURLs = indexURLs + changed = true + } + + // Trigger update check if enabled and something changed. + if changed && u.started.IsSet() { + if autoCheck { + u.updateCheckWorkerMgr.Repeat(updateTaskRepeatDuration).Go() + } else { + u.updateCheckWorkerMgr.Repeat(0) + } + } +} + // TriggerUpdateCheck triggers an update check. func (u *Updater) TriggerUpdateCheck() { u.updateCheckWorkerMgr.Go() @@ -459,13 +513,17 @@ func (u *Updater) Manager() *mgr.Manager { // Start starts the module. func (u *Updater) Start() error { - if u.corruptedInstallation && u.cfg.Notify && u.instance.Notifications() != nil { - // FIXME: this might make sense as a module state - u.instance.Notifications().NotifyError( - corruptInstallationNotificationID, - "Install Corruption", - "Portmaster has detected that one or more of its own files have been corrupted. Please re-install the software.", - ) + u.configureLock.Lock() + defer u.configureLock.Unlock() + + if u.corruptedInstallation != nil && u.cfg.Notify && u.instance.Notifications() != nil { + u.states.Add(mgr.State{ + ID: corruptInstallationNotificationID, + Name: "Install Corruption", + Message: "Portmaster has detected that one or more of its own files have been corrupted. Please re-install the software. Error: " + u.corruptedInstallation.Error(), + Type: mgr.StateTypeError, + Data: u.corruptedInstallation, + }) } // Check for updates automatically, if enabled. @@ -474,6 +532,8 @@ func (u *Updater) Start() error { Repeat(updateTaskRepeatDuration). Delay(15 * time.Second) } + + u.started.SetTo(true) return nil } @@ -481,6 +541,62 @@ func (u *Updater) GetMainDir() string { return u.cfg.Directory } +// GetIndex returns a copy of the index. +func (u *Updater) GetIndex() (*Index, error) { + // Copy Artifacts. + artifacts, err := u.GetFiles() + if err != nil { + return nil, err + } + + u.indexLock.Lock() + defer u.indexLock.Unlock() + + // Check if any index is active. + if u.index == nil { + return nil, ErrNotFound + } + + return &Index{ + Name: u.index.Name, + Version: u.index.Version, + Published: u.index.Published, + Artifacts: artifacts, + versionNum: u.index.versionNum, + }, nil +} + +// GetFiles returns all artifacts. Returns ErrNotFound if no artifacts are found. +func (u *Updater) GetFiles() ([]*Artifact, error) { + u.indexLock.Lock() + defer u.indexLock.Unlock() + + // Check if any index is active. + if u.index == nil { + return nil, ErrNotFound + } + + // Export all artifacts. + export := make([]*Artifact, 0, len(u.index.Artifacts)) + for _, artifact := range u.index.Artifacts { + switch { + case artifact.Platform != "" && artifact.Platform != u.cfg.Platform: + // Platform is defined and does not match. + // Platforms are usually pre-filtered, but just to be sure. + default: + // Artifact matches! + export = append(export, artifact.export(u.cfg.Directory, u.index.versionNum)) + } + } + + // Check if anything was exported. + if len(export) == 0 { + return nil, ErrNotFound + } + + return export, nil +} + // GetFile returns the path of a file given the name. Returns ErrNotFound if file is not found. func (u *Updater) GetFile(name string) (*Artifact, error) { u.indexLock.Lock() @@ -495,7 +611,7 @@ func (u *Updater) GetFile(name string) (*Artifact, error) { switch { case artifact.Filename != name: // Name does not match. - case artifact.Platform != "" && artifact.Platform != currentPlatform: + case artifact.Platform != "" && artifact.Platform != u.cfg.Platform: // Platform is defined and does not match. // Platforms are usually pre-filtered, but just to be sure. default: @@ -509,6 +625,7 @@ func (u *Updater) GetFile(name string) (*Artifact, error) { // Stop stops the module. func (u *Updater) Stop() error { + u.started.SetTo(false) return nil } diff --git a/service/updates/updates_test.go b/service/updates/updates_test.go index d79706c7..9dea5886 100644 --- a/service/updates/updates_test.go +++ b/service/updates/updates_test.go @@ -84,7 +84,7 @@ func TestPerformUpdate(t *testing.T) { } if data, err := os.ReadFile(filepath.Join(updateDir, "index.json")); err == nil { fmt.Println(string(data)) - idx, err := ParseIndex(data, nil) + idx, err := ParseIndex(data, updater.cfg.Platform, nil) if err == nil { fmt.Println(idx.Version) fmt.Println(idx.versionNum) @@ -97,7 +97,7 @@ func TestPerformUpdate(t *testing.T) { }) // Check if the current version is now the new. - newIndex, err := LoadIndex(filepath.Join(installedDir, "index.json"), nil) + newIndex, err := LoadIndex(filepath.Join(installedDir, "index.json"), updater.cfg.Platform, nil) if err != nil { t.Fatal(err) } diff --git a/service/updates/upgrade.go b/service/updates/upgrade.go index 86077ee1..9d18de98 100644 --- a/service/updates/upgrade.go +++ b/service/updates/upgrade.go @@ -3,22 +3,13 @@ package updates import ( "errors" "fmt" - "io/fs" "os" "path/filepath" "slices" "strings" "github.com/safing/portmaster/base/log" -) - -// FIXME: previous update system did in-place service file upgrades. Check if this is still necessary and if changes are in current installers. - -const ( - defaultFileMode = os.FileMode(0o0644) - executableFileMode = os.FileMode(0o0744) - executableUIFileMode = os.FileMode(0o0755) - defaultDirMode = os.FileMode(0o0755) + "github.com/safing/portmaster/base/utils" ) func (u *Updater) upgrade(downloader *Downloader, ignoreVersion bool) error { @@ -57,7 +48,7 @@ func (u *Updater) upgradeMoveFiles(downloader *Downloader) error { // Reset purge directory, so that we can do a clean rollback later. _ = os.RemoveAll(u.cfg.PurgeDirectory) - err := os.MkdirAll(u.cfg.PurgeDirectory, defaultDirMode) + err := utils.EnsureDirectory(u.cfg.PurgeDirectory, utils.PublicReadExecPermission) if err != nil { return fmt.Errorf("failed to create purge directory: %w", err) } @@ -71,7 +62,7 @@ func (u *Updater) upgradeMoveFiles(downloader *Downloader) error { if !errors.Is(err, os.ErrNotExist) { return fmt.Errorf("read current directory: %w", err) } - err = os.MkdirAll(u.cfg.Directory, defaultDirMode) + err := utils.EnsureDirectory(u.cfg.PurgeDirectory, utils.PublicReadExecPermission) if err != nil { return fmt.Errorf("create current directory: %w", err) } @@ -86,7 +77,7 @@ func (u *Updater) upgradeMoveFiles(downloader *Downloader) error { // Otherwise, move file to purge dir. src := filepath.Join(u.cfg.Directory, file.Name()) dst := filepath.Join(u.cfg.PurgeDirectory, file.Name()) - err := u.moveFile(src, dst, "", file.Type().Perm()) + err := u.moveFile(src, dst, "", utils.PublicReadPermission) if err != nil { return fmt.Errorf("failed to move current file %s to purge dir: %w", file.Name(), err) } @@ -97,7 +88,7 @@ func (u *Updater) upgradeMoveFiles(downloader *Downloader) error { log.Debugf("updates/%s: installing the new version (v%s from %s)", u.cfg.Name, downloader.index.Version, downloader.index.Published) src := filepath.Join(u.cfg.DownloadDirectory, u.cfg.IndexFile) dst := filepath.Join(u.cfg.Directory, u.cfg.IndexFile) - err = u.moveFile(src, dst, "", defaultFileMode) + err = u.moveFile(src, dst, "", utils.PublicReadPermission) if err != nil { return fmt.Errorf("failed to move index file to %s: %w", dst, err) } @@ -122,17 +113,18 @@ func (u *Updater) upgradeMoveFiles(downloader *Downloader) error { } // moveFile moves a file and falls back to copying if it fails. -func (u *Updater) moveFile(currentPath, newPath string, sha256sum string, fileMode fs.FileMode) error { +func (u *Updater) moveFile(currentPath, newPath string, sha256sum string, filePermission utils.FSPermission) error { // Try to simply move file. err := os.Rename(currentPath, newPath) if err == nil { // Moving was successful, return. + utils.SetFilePermission(newPath, filePermission) return nil } log.Tracef("updates/%s: failed to move to %q, falling back to copy+delete: %s", u.cfg.Name, newPath, err) // Copy and check the checksum while we are at it. - err = copyAndCheckSHA256Sum(currentPath, newPath, sha256sum, fileMode) + err = copyAndCheckSHA256Sum(currentPath, newPath, sha256sum, filePermission) if err != nil { return fmt.Errorf("move failed, copy+delete fallback failed: %w", err) } @@ -152,7 +144,7 @@ func (u *Updater) recoverFromFailedUpgrade() error { for _, file := range files { purgedFile := filepath.Join(u.cfg.PurgeDirectory, file.Name()) activeFile := filepath.Join(u.cfg.Directory, file.Name()) - err := u.moveFile(purgedFile, activeFile, "", file.Type().Perm()) + err := u.moveFile(purgedFile, activeFile, "", utils.PublicReadPermission) if err != nil { // Only warn and continue to recover as many files as possible. log.Warningf("updates/%s: failed to roll back file %s: %s", u.cfg.Name, file.Name(), err) diff --git a/spn/instance.go b/spn/instance.go index dcfe01aa..e8fa1789 100644 --- a/spn/instance.go +++ b/spn/instance.go @@ -9,11 +9,12 @@ import ( "github.com/safing/portmaster/base/api" "github.com/safing/portmaster/base/config" "github.com/safing/portmaster/base/database/dbmodule" - "github.com/safing/portmaster/base/log" "github.com/safing/portmaster/base/metrics" "github.com/safing/portmaster/base/notifications" "github.com/safing/portmaster/base/rng" "github.com/safing/portmaster/base/runtime" + "github.com/safing/portmaster/base/utils" + "github.com/safing/portmaster/service" "github.com/safing/portmaster/service/core" "github.com/safing/portmaster/service/core/base" "github.com/safing/portmaster/service/intel/filterlists" @@ -79,27 +80,27 @@ type Instance struct { } // New returns a new Portmaster service instance. -func New() (*Instance, error) { +func New(svcCfg *service.ServiceConfig) (*Instance, error) { + // Initialize config. + err := svcCfg.Init() + if err != nil { + return nil, fmt.Errorf("internal service config error: %w", err) + } + + // Make sure data dir exists, so that child directories don't dictate the permissions. + err = utils.EnsureDirectory(svcCfg.DataDir, utils.PublicReadExecPermission) + if err != nil { + return nil, fmt.Errorf("data directory %s is not accessible: %w", svcCfg.DataDir, err) + } + // Create instance to pass it to modules. - instance := &Instance{} + instance := &Instance{ + binDir: svcCfg.BinDir, + dataDir: svcCfg.DataDir, + } instance.ctx, instance.cancelCtx = context.WithCancel(context.Background()) instance.shutdownCtx, instance.cancelShutdownCtx = context.WithCancel(context.Background()) - binaryUpdateIndex := updates.Config{ - // FIXME: fill - } - - intelUpdateIndex := updates.Config{ - // FIXME: fill - } - - // Initialize log - log.GlobalWriter = log.NewStdoutWriter() - - // FIXME: initialize log file. - - var err error - // Base modules instance.base, err = base.New(instance) if err != nil { @@ -131,18 +132,22 @@ func New() (*Instance, error) { } // Service modules + binaryUpdateConfig, intelUpdateConfig, err := service.MakeUpdateConfigs(svcCfg) + if err != nil { + return instance, fmt.Errorf("create updates config: %w", err) + } + instance.binaryUpdates, err = updates.New(instance, "Binary Updater", *binaryUpdateConfig) + if err != nil { + return instance, fmt.Errorf("create updates module: %w", err) + } + instance.intelUpdates, err = updates.New(instance, "Intel Updater", *intelUpdateConfig) + if err != nil { + return instance, fmt.Errorf("create updates module: %w", err) + } instance.core, err = core.New(instance) if err != nil { return instance, fmt.Errorf("create core module: %w", err) } - instance.binaryUpdates, err = updates.New(instance, "Binary Updater", binaryUpdateIndex) - if err != nil { - return instance, fmt.Errorf("create updates module: %w", err) - } - instance.intelUpdates, err = updates.New(instance, "Intel Updater", intelUpdateIndex) - if err != nil { - return instance, fmt.Errorf("create updates module: %w", err) - } instance.geoip, err = geoip.New(instance) if err != nil { return instance, fmt.Errorf("create customlist module: %w", err) 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 diff --git a/windows_core_dll/build.ps1 b/windows_core_dll/build.ps1 new file mode 100644 index 00000000..d58f45ed --- /dev/null +++ b/windows_core_dll/build.ps1 @@ -0,0 +1,2 @@ +msbuild .\windows_core_dll.sln /p:Configuration=Release +ls .\x64\Release\portmaster-core.dll \ No newline at end of file diff --git a/windows_core_dll/dllmain.cpp b/windows_core_dll/dllmain.cpp new file mode 100644 index 00000000..7674539f --- /dev/null +++ b/windows_core_dll/dllmain.cpp @@ -0,0 +1,197 @@ +// dllmain.cpp : Defines the entry point for the DLL application. +#include "pch.h" + +#pragma comment(lib, "tdh.lib") + +// GUID of the DNS log provider +static const GUID DNS_CLIENT_PROVIDER_GUID = { + 0x1C95126E, + 0x7EEA, + 0x49A9, + {0xA3, 0xFE, 0xA3, 0x78, 0xB0, 0x3D, 0xDB, 0x4D} }; + +// GUID of the event session. This should be unique for the application. +static const GUID PORTMASTER_ETW_SESSION_GUID = { + 0x0211d070, + 0xc3b2, + 0x4609, + {0x92, 0xf5, 0x28, 0xe7, 0x18, 0xb2, 0x3b, 0x18} }; + +// Name of the session. This is visble when user queries all ETW sessions. +// (example `logman query -ets`) +#define LOGSESSION_NAME L"PortmasterDNSEventListener" + +// Fuction type of the callback that will be called on each event. +typedef uint64_t(*GoEventRecordCallback)(wchar_t* domain, uint32_t pid, wchar_t* result); + +// Holds the state of the ETW Session. +struct ETWSessionState { + TRACEHANDLE SessionTraceHandle; + EVENT_TRACE_PROPERTIES* SessionProperties; + TRACEHANDLE sessionHandle; + GoEventRecordCallback callback; +}; + +// getPropertyValue reads a property from the event. +static bool getPropertyValue(PEVENT_RECORD evt, LPWSTR prop, PBYTE* pData) { + // Describe the data that needs to be retrieved from the event. + PROPERTY_DATA_DESCRIPTOR DataDescriptor; + ZeroMemory(&DataDescriptor, sizeof(DataDescriptor)); + DataDescriptor.PropertyName = (ULONGLONG)(prop); + DataDescriptor.ArrayIndex = 0; + + DWORD PropertySize = 0; + // Check if the data is available and what is the size of it. + DWORD status = + TdhGetPropertySize(evt, 0, NULL, 1, &DataDescriptor, &PropertySize); + if (ERROR_SUCCESS != status) { + return false; + } + + // Allocate memory for the data. + *pData = (PBYTE)malloc(PropertySize); + if (NULL == *pData) { + return false; + } + + // Get the data. + status = + TdhGetProperty(evt, 0, NULL, 1, &DataDescriptor, PropertySize, *pData); + if (ERROR_SUCCESS != status) { + if (*pData) { + free(*pData); + *pData = NULL; + } + return false; + } + + return true; +} + +// EventRecordCallback is a callback called on each event. +static void WINAPI EventRecordCallback(PEVENT_RECORD eventRecord) { + PBYTE resultValue = NULL; + PBYTE domainValue = NULL; + + getPropertyValue(eventRecord, (LPWSTR)L"QueryResults", &resultValue); + getPropertyValue(eventRecord, (LPWSTR)L"QueryName", &domainValue); + + ETWSessionState* state = (ETWSessionState*)eventRecord->UserContext; + + if (resultValue != NULL && domainValue != NULL) { + state->callback((wchar_t*)domainValue, eventRecord->EventHeader.ProcessId, (wchar_t*)resultValue); + } + + free(resultValue); + free(domainValue); +} + +extern "C" { + // PM_ETWCreateState allocates memory for the state and initializes the config for the session. PM_ETWDestroySession must be called to avoid leaks. + // callback must be set to a valid function pointer. + __declspec(dllexport) ETWSessionState* PM_ETWCreateState(GoEventRecordCallback callback) { + // Create trace session properties. + ULONG BufferSize = sizeof(EVENT_TRACE_PROPERTIES) + sizeof(LOGSESSION_NAME); + EVENT_TRACE_PROPERTIES* SessionProperties = + (EVENT_TRACE_PROPERTIES*)calloc(1, BufferSize); + SessionProperties->Wnode.BufferSize = BufferSize; + SessionProperties->Wnode.Flags = WNODE_FLAG_TRACED_GUID; + SessionProperties->Wnode.ClientContext = 1; // QPC clock resolution + SessionProperties->Wnode.Guid = PORTMASTER_ETW_SESSION_GUID; + SessionProperties->LogFileMode = EVENT_TRACE_REAL_TIME_MODE; + SessionProperties->MaximumFileSize = 1; // MB + SessionProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES); + + // Create state + ETWSessionState* state = (ETWSessionState*)calloc(1, sizeof(ETWSessionState)); + state->SessionProperties = SessionProperties; + state->callback = callback; + return state; + } + + // PM_ETWInitializeSession initializes the session. + __declspec(dllexport) uint32_t PM_ETWInitializeSession(ETWSessionState* state) { + return StartTrace(&state->SessionTraceHandle, LOGSESSION_NAME, + state->SessionProperties); + } + + // PM_ETWStartTrace subscribes to the dns events and start listening. The function blocks while the listener is running. + // Call PM_ETWStopTrace to stop the listener. + __declspec(dllexport) uint32_t PM_ETWStartTrace(ETWSessionState* state) { + ULONG status = + EnableTraceEx2(state->SessionTraceHandle, (LPCGUID)&DNS_CLIENT_PROVIDER_GUID, + EVENT_CONTROL_CODE_ENABLE_PROVIDER, + TRACE_LEVEL_INFORMATION, 0, 0, 0, NULL); + + if (status != ERROR_SUCCESS) { + return status; + } + + EVENT_TRACE_LOGFILE trace = { 0 }; + + trace.LoggerName = (LPWSTR)(LOGSESSION_NAME); + trace.ProcessTraceMode = + PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD; + trace.EventRecordCallback = EventRecordCallback; + trace.Context = state; + + state->sessionHandle = OpenTrace(&trace); + if (state->sessionHandle == INVALID_PROCESSTRACE_HANDLE) { + return 1; + } + + status = ProcessTrace(&state->sessionHandle, 1, NULL, NULL); + if (status != ERROR_SUCCESS) { + return 1; + } + + return ERROR_SUCCESS; + } + + // PM_ETWFlushTrace flushes the event buffer. + __declspec(dllexport) uint32_t PM_ETWFlushTrace(ETWSessionState* state) { + return ControlTrace(state->SessionTraceHandle, LOGSESSION_NAME, + state->SessionProperties, EVENT_TRACE_CONTROL_FLUSH); + } + + // PM_ETWFlushTrace stops the listener. + __declspec(dllexport) uint32_t PM_ETWStopTrace(ETWSessionState* state) { + return ControlTrace(state->SessionTraceHandle, LOGSESSION_NAME, state->SessionProperties, + EVENT_TRACE_CONTROL_STOP); + } + + // PM_ETWFlushTrace Closes the session and frees recourses. + __declspec(dllexport) uint32_t PM_ETWDestroySession(ETWSessionState* state) { + if (state == NULL) { + return 1; + } + uint32_t status = CloseTrace(state->sessionHandle); + + // Free memory. + free(state->SessionProperties); + free(state); + return status; + } + + // PM_ETWStopOldSession removes old session with the same name if they exist. + // It returns success(0) only if its able to delete the old session. + __declspec(dllexport) ULONG PM_ETWStopOldSession() { + ULONG status = ERROR_SUCCESS; + TRACEHANDLE sessionHandle = 0; + + // Create trace session properties + size_t bufferSize = sizeof(EVENT_TRACE_PROPERTIES) + sizeof(LOGSESSION_NAME); + EVENT_TRACE_PROPERTIES* sessionProperties = (EVENT_TRACE_PROPERTIES*)calloc(1, bufferSize); + sessionProperties->Wnode.BufferSize = (ULONG)bufferSize; + sessionProperties->Wnode.Flags = WNODE_FLAG_TRACED_GUID; + sessionProperties->Wnode.ClientContext = 1; // QPC clock resolution + sessionProperties->Wnode.Guid = PORTMASTER_ETW_SESSION_GUID; + sessionProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES); + + // Use Control trace will stop the session which will trigger a delete. + status = ControlTrace(NULL, LOGSESSION_NAME, sessionProperties, EVENT_TRACE_CONTROL_STOP); + + free(sessionProperties); + return status; + } +} \ No newline at end of file diff --git a/windows_core_dll/framework.h b/windows_core_dll/framework.h new file mode 100644 index 00000000..a9744f82 --- /dev/null +++ b/windows_core_dll/framework.h @@ -0,0 +1,5 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files +#include diff --git a/windows_core_dll/pch.cpp b/windows_core_dll/pch.cpp new file mode 100644 index 00000000..91c22df2 --- /dev/null +++ b/windows_core_dll/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: source file corresponding to the pre-compiled header + +#include "pch.h" + +// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. diff --git a/windows_core_dll/pch.h b/windows_core_dll/pch.h new file mode 100644 index 00000000..e5658cac --- /dev/null +++ b/windows_core_dll/pch.h @@ -0,0 +1,22 @@ +// pch.h: This is a precompiled header file. +// Files listed below are compiled only once, improving build performance for future builds. +// This also affects IntelliSense performance, including code completion and many code browsing features. +// However, files listed here are ALL re-compiled if any one of them is updated between builds. +// Do not add files here that you will be updating frequently as this negates the performance advantage. + +#ifndef PCH_H +#define PCH_H + +// add headers that you want to pre-compile here +#include "framework.h" + +#include +#include +#include +#include +#include +#include +#include + + +#endif //PCH_H diff --git a/windows_core_dll/windows_core_dll.sln b/windows_core_dll/windows_core_dll.sln new file mode 100644 index 00000000..73d46ca7 --- /dev/null +++ b/windows_core_dll/windows_core_dll.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.11.35222.181 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "windows_core_dll", "windows_core_dll.vcxproj", "{6F3C7EAF-8511-4822-AAF0-1086D27E4DA9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6F3C7EAF-8511-4822-AAF0-1086D27E4DA9}.Debug|x64.ActiveCfg = Debug|x64 + {6F3C7EAF-8511-4822-AAF0-1086D27E4DA9}.Debug|x64.Build.0 = Debug|x64 + {6F3C7EAF-8511-4822-AAF0-1086D27E4DA9}.Debug|x86.ActiveCfg = Debug|Win32 + {6F3C7EAF-8511-4822-AAF0-1086D27E4DA9}.Debug|x86.Build.0 = Debug|Win32 + {6F3C7EAF-8511-4822-AAF0-1086D27E4DA9}.Release|x64.ActiveCfg = Release|x64 + {6F3C7EAF-8511-4822-AAF0-1086D27E4DA9}.Release|x64.Build.0 = Release|x64 + {6F3C7EAF-8511-4822-AAF0-1086D27E4DA9}.Release|x86.ActiveCfg = Release|Win32 + {6F3C7EAF-8511-4822-AAF0-1086D27E4DA9}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {8E60106D-49DF-49C7-AC08-02775342FEAE} + EndGlobalSection +EndGlobal diff --git a/windows_core_dll/windows_core_dll.vcxproj b/windows_core_dll/windows_core_dll.vcxproj new file mode 100644 index 00000000..cf6d65f1 --- /dev/null +++ b/windows_core_dll/windows_core_dll.vcxproj @@ -0,0 +1,158 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {6f3c7eaf-8511-4822-aaf0-1086d27e4da9} + windowscoredll + 10.0 + portmaster-core + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;WINDOWSCOREDLL_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + false + + + + + Level3 + true + true + true + WIN32;NDEBUG;WINDOWSCOREDLL_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + true + true + false + + + + + Level3 + true + _DEBUG;WINDOWSCOREDLL_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + false + + + + + Level3 + true + true + true + NDEBUG;WINDOWSCOREDLL_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + true + true + false + + + + + + + + + + Create + Create + Create + Create + + + + + + \ No newline at end of file diff --git a/windows_core_dll/windows_core_dll.vcxproj.filters b/windows_core_dll/windows_core_dll.vcxproj.filters new file mode 100644 index 00000000..f99bb483 --- /dev/null +++ b/windows_core_dll/windows_core_dll.vcxproj.filters @@ -0,0 +1,33 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/windows_core_dll/windows_core_dll.vcxproj.user b/windows_core_dll/windows_core_dll.vcxproj.user new file mode 100644 index 00000000..0f14913f --- /dev/null +++ b/windows_core_dll/windows_core_dll.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/windows_kext/README.md b/windows_kext/README.md index f77de347..ce80d0b1 100644 --- a/windows_kext/README.md +++ b/windows_kext/README.md @@ -5,51 +5,55 @@ Implementation of Safing's Portmaster Windows kernel extension in Rust. - [Driver](driver/README.md) -> entry point. - [WDK](wdk/README.md) -> Windows Driver Kit interface. -- [Packet Path](PacketDoc.md) -> Detiled documentation of what happens to a packet when it enters the kernel extension. -- [Release](release/README.md) -> Guide how to do a release build +- [Packet Path](PacketFlow.md) -> Detailed documentation of what happens to a packet when it enters the kernel extension. +- [Release](release/README.md) -> Guide how to do a release build. +- [Windows Filtering Platform - MS](https://learn.microsoft.com/en-us/windows-hardware/drivers/network/roadmap-for-developing-wfp-callout-drivers) -> The driver is build on top of WFP. + ### Building The Windows Portmaster Kernel Extension is currently only developed and tested for the amd64 (64-bit) architecture. -__Prerequesites:__ +__Prerequirements:__ - Visual Studio 2022 - Install C++ and Windows 11 SDK (22H2) components - Add `link.exe` and `signtool` in the PATH -- Rust +- Windows Driver Kit + - https://learn.microsoft.com/en-us/windows-hardware/drivers/download-the-wdk +- Rust (Can be separate machine) - https://www.rust-lang.org/tools/install -- Cargo make(optional) - - https://github.com/sagiegurari/cargo-make __Setup Test Signing:__ -In order to test the driver on your machine, you will have to test sign it (starting with Windows 10). +> Not recommended for a work machine. Usually done on virtual machine dedicated for testing. +In order to test the driver on your machine, you will have to sign it (starting with Windows 10). Create a new certificate for test signing: - :: Open a *x64 Free Build Environment* console as Administrator. +```ps1 + # Open a *x64 Free Build Environment* console as Administrator. - :: Run the MakeCert.exe tool to create a test certificate: + # Run the MakeCert.exe tool to create a test certificate: MakeCert -r -pe -ss PrivateCertStore -n "CN=DriverCertificate" DriverCertificate.cer - :: Install the test certificate with CertMgr.exe: + # Install the test certificate with CertMgr.exe: CertMgr /add DriverCertificate.cer /s /r localMachine root - +``` Enable Test Signing on the dev machine: - - :: Before you can load test-signed drivers, you must enable Windows test mode. To do this, run this command: +```ps1 + # Before you can load test-signed drivers, you must enable Windows test mode. To do this, run this command: Bcdedit.exe -set TESTSIGNING ON - :: Then, restart Windows. For more information, see The TESTSIGNING Boot Configuration Option. - + # Then, restart Windows. For more information, see The TESTSIGNING Boot Configuration Option. +``` __Build driver:__ -``` -cd driver -cargo build +```sh + cd driver + cargo build ``` > Build also works on linux @@ -63,9 +67,9 @@ Run `link.bat`. - Install go - https://go.dev/dl/ -``` -cd kext_tester -go run . +```sh + cd kext_tester + go run . ``` > make sure the hardcoded path in main.go is pointing to the correct `.sys` file diff --git a/windows_kext/driver/Cargo.lock b/windows_kext/driver/Cargo.lock index b8746745..4a0e0b39 100644 --- a/windows_kext/driver/Cargo.lock +++ b/windows_kext/driver/Cargo.lock @@ -2,18 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "ahash" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - [[package]] name = "atomic-polyfill" version = "1.0.3" @@ -57,7 +45,6 @@ checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" name = "driver" version = "0.0.0" dependencies = [ - "hashbrown", "num", "num-derive", "num-traits", @@ -76,15 +63,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "hashbrown" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" -dependencies = [ - "ahash", -] - [[package]] name = "heapless" version = "0.7.17" @@ -217,12 +195,6 @@ dependencies = [ "syn", ] -[[package]] -name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - [[package]] name = "proc-macro2" version = "1.0.78" @@ -316,12 +288,6 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - [[package]] name = "wdk" version = "0.0.0" @@ -399,23 +365,3 @@ source = "git+https://github.com/microsoft/windows-rs?rev=dffa8b03dc4987c278d82e name = "windows_x86_64_msvc" version = "0.52.5" source = "git+https://github.com/microsoft/windows-rs?rev=dffa8b03dc4987c278d82e88015ffe96aa8ac317#dffa8b03dc4987c278d82e88015ffe96aa8ac317" - -[[package]] -name = "zerocopy" -version = "0.7.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d6f15f7ade05d2a4935e34a457b936c23dc70a05cc1d97133dc99e7a3fe0f0e" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbbad221e3f78500350ecbd7dfa4e63ef945c05f4c61cb7f4d3f84cd0bba649b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/windows_kext/driver/Cargo.toml b/windows_kext/driver/Cargo.toml index 66dffaca..034627c0 100644 --- a/windows_kext/driver/Cargo.toml +++ b/windows_kext/driver/Cargo.toml @@ -17,7 +17,6 @@ num = { version = "0.4", default-features = false } num-derive = { version = "0.4", default-features = false } num-traits = { version = "0.2", default-features = false } smoltcp = { version = "0.10", default-features = false, features = ["proto-ipv4", "proto-ipv6"] } -hashbrown = { version = "0.14.3", default-features = false, features = ["ahash"]} # WARNING: Do not update. The version was choosen for a reason. See wdk/README.md for more detiels. [dependencies.windows-sys] diff --git a/windows_kext/driver/src/ale_callouts.rs b/windows_kext/driver/src/ale_callouts.rs index ed478938..51c5cc30 100644 --- a/windows_kext/driver/src/ale_callouts.rs +++ b/windows_kext/driver/src/ale_callouts.rs @@ -105,6 +105,9 @@ pub fn ale_layer_connect_v6(data: CalloutData) { } fn ale_layer_auth(mut data: CalloutData, ale_data: AleLayerData) { + // Make the default path as drop. + data.block_and_absorb(); + let Some(device) = crate::entry::get_device() else { return; }; diff --git a/windows_kext/driver/src/bandwidth.rs b/windows_kext/driver/src/bandwidth.rs index 4fb48786..0105ac72 100644 --- a/windows_kext/driver/src/bandwidth.rs +++ b/windows_kext/driver/src/bandwidth.rs @@ -1,14 +1,10 @@ +use alloc::collections::BTreeMap; use protocol::info::{BandwidthValueV4, BandwidthValueV6, Info}; use smoltcp::wire::{IpProtocol, Ipv4Address, Ipv6Address}; use wdk::rw_spin_lock::RwSpinLock; -use crate::driver_hashmap::DeviceHashMap; - -#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)] -pub struct Key
-where - Address: Eq + PartialEq, -{ +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)] +pub struct Key { pub local_ip: Address, pub local_port: u16, pub remote_ip: Address, @@ -25,32 +21,32 @@ enum Direction { Rx(usize), } pub struct Bandwidth { - stats_tcp_v4: DeviceHashMap, Value>, + stats_tcp_v4: BTreeMap, Value>, stats_tcp_v4_lock: RwSpinLock, - stats_tcp_v6: DeviceHashMap, Value>, + stats_tcp_v6: BTreeMap, Value>, stats_tcp_v6_lock: RwSpinLock, - stats_udp_v4: DeviceHashMap, Value>, + stats_udp_v4: BTreeMap, Value>, stats_udp_v4_lock: RwSpinLock, - stats_udp_v6: DeviceHashMap, Value>, + stats_udp_v6: BTreeMap, Value>, stats_udp_v6_lock: RwSpinLock, } impl Bandwidth { pub fn new() -> Self { Self { - stats_tcp_v4: DeviceHashMap::new(), + stats_tcp_v4: BTreeMap::new(), stats_tcp_v4_lock: RwSpinLock::default(), - stats_tcp_v6: DeviceHashMap::new(), + stats_tcp_v6: BTreeMap::new(), stats_tcp_v6_lock: RwSpinLock::default(), - stats_udp_v4: DeviceHashMap::new(), + stats_udp_v4: BTreeMap::new(), stats_udp_v4_lock: RwSpinLock::default(), - stats_udp_v6: DeviceHashMap::new(), + stats_udp_v6: BTreeMap::new(), stats_udp_v6_lock: RwSpinLock::default(), } } @@ -62,7 +58,7 @@ impl Bandwidth { if self.stats_tcp_v4.is_empty() { return None; } - stats_map = core::mem::replace(&mut self.stats_tcp_v4, DeviceHashMap::new()); + stats_map = core::mem::replace(&mut self.stats_tcp_v4, BTreeMap::new()); } let mut values = alloc::vec::Vec::with_capacity(stats_map.len()); @@ -89,7 +85,7 @@ impl Bandwidth { if self.stats_tcp_v6.is_empty() { return None; } - stats_map = core::mem::replace(&mut self.stats_tcp_v6, DeviceHashMap::new()); + stats_map = core::mem::replace(&mut self.stats_tcp_v6, BTreeMap::new()); } let mut values = alloc::vec::Vec::with_capacity(stats_map.len()); @@ -116,7 +112,7 @@ impl Bandwidth { if self.stats_udp_v4.is_empty() { return None; } - stats_map = core::mem::replace(&mut self.stats_udp_v4, DeviceHashMap::new()); + stats_map = core::mem::replace(&mut self.stats_udp_v4, BTreeMap::new()); } let mut values = alloc::vec::Vec::with_capacity(stats_map.len()); @@ -140,10 +136,10 @@ impl Bandwidth { let stats_map; { let _guard = self.stats_udp_v6_lock.write_lock(); - if self.stats_tcp_v6.is_empty() { + if self.stats_udp_v6.is_empty() { return None; } - stats_map = core::mem::replace(&mut self.stats_tcp_v6, DeviceHashMap::new()); + stats_map = core::mem::replace(&mut self.stats_udp_v6, BTreeMap::new()); } let mut values = alloc::vec::Vec::with_capacity(stats_map.len()); @@ -235,8 +231,8 @@ impl Bandwidth { ); } - fn update( - map: &mut DeviceHashMap, Value>, + fn update( + map: &mut BTreeMap, Value>, lock: &mut RwSpinLock, key: Key
, bytes: Direction, diff --git a/windows_kext/driver/src/connection_cache.rs b/windows_kext/driver/src/connection_cache.rs index 665e60f4..a076df02 100644 --- a/windows_kext/driver/src/connection_cache.rs +++ b/windows_kext/driver/src/connection_cache.rs @@ -1,10 +1,8 @@ -use core::time::Duration; - use crate::{ connection::{Connection, ConnectionV4, ConnectionV6, RedirectInfo, Verdict}, connection_map::{ConnectionMap, Key}, }; -use alloc::{format, string::String, vec::Vec}; +use alloc::vec::Vec; use smoltcp::wire::IpProtocol; use wdk::rw_spin_lock::RwSpinLock; @@ -128,73 +126,4 @@ impl ConnectionCache { return size; } - - #[allow(dead_code)] - pub fn get_full_cache_info(&self) -> String { - let mut info = String::new(); - let now = wdk::utils::get_system_timestamp_ms(); - { - let _guard = self.lock_v4.read_lock(); - for ((protocol, port), connections) in self.connections_v4.iter() { - info.push_str(&format!("{} -> {}\n", protocol, port,)); - for conn in connections { - let active_time_seconds = - Duration::from_millis(now - conn.get_last_accessed_time()).as_secs(); - info.push_str(&format!( - "\t{}:{} -> {}:{} {} last active {}m {}s ago", - conn.local_address, - conn.local_port, - conn.remote_address, - conn.remote_port, - conn.verdict, - active_time_seconds / 60, - active_time_seconds % 60 - )); - if conn.has_ended() { - let end_time_seconds = - Duration::from_millis(now - conn.get_end_time()).as_secs(); - info.push_str(&format!( - "\t ended {}m {}s ago", - end_time_seconds / 60, - end_time_seconds % 60 - )); - } - info.push('\n'); - } - } - } - - { - let _guard = self.lock_v6.read_lock(); - for ((protocol, port), connections) in self.connections_v6.iter() { - info.push_str(&format!("{} -> {} \n", protocol, port)); - for conn in connections { - let active_time_seconds = - Duration::from_millis(now - conn.get_last_accessed_time()).as_secs(); - info.push_str(&format!( - "\t{}:{} -> {}:{} {} last active {}m {}s ago", - conn.local_address, - conn.local_port, - conn.remote_address, - conn.remote_port, - conn.verdict, - active_time_seconds / 60, - active_time_seconds % 60 - )); - if conn.has_ended() { - let end_time_seconds = - Duration::from_millis(now - conn.get_end_time()).as_secs(); - info.push_str(&format!( - "\t ended {}m {}s ago", - end_time_seconds / 60, - end_time_seconds % 60 - )); - } - info.push('\n'); - } - } - } - - return info; - } } diff --git a/windows_kext/driver/src/connection_map.rs b/windows_kext/driver/src/connection_map.rs index bf2210f8..7cadf87b 100644 --- a/windows_kext/driver/src/connection_map.rs +++ b/windows_kext/driver/src/connection_map.rs @@ -1,8 +1,7 @@ use core::{fmt::Display, time::Duration}; use crate::connection::Connection; -use alloc::vec::Vec; -use hashbrown::HashMap; +use alloc::{collections::BTreeMap, vec::Vec}; use smoltcp::wire::{IpAddress, IpProtocol}; #[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord)] @@ -63,11 +62,11 @@ impl Key { } } -pub struct ConnectionMap(HashMap<(IpProtocol, u16), Vec>); +pub struct ConnectionMap(BTreeMap<(IpProtocol, u16), Vec>); impl ConnectionMap { pub fn new() -> Self { - Self(HashMap::new()) + Self(BTreeMap::new()) } pub fn add(&mut self, conn: T) { @@ -164,7 +163,6 @@ impl ConnectionMap { self.0.retain(|_, v| !v.is_empty()); } - #[allow(dead_code)] pub fn get_count(&self) -> usize { let mut count = 0; for conn in self.0.values() { @@ -172,8 +170,4 @@ impl ConnectionMap { } return count; } - - pub fn iter(&self) -> hashbrown::hash_map::Iter<'_, (IpProtocol, u16), Vec> { - self.0.iter() - } } diff --git a/windows_kext/driver/src/driver_hashmap.rs b/windows_kext/driver/src/driver_hashmap.rs deleted file mode 100644 index 1c8b706a..00000000 --- a/windows_kext/driver/src/driver_hashmap.rs +++ /dev/null @@ -1,25 +0,0 @@ -use core::ops::{Deref, DerefMut}; - -use hashbrown::HashMap; - -pub struct DeviceHashMap(Option>); - -impl DeviceHashMap { - pub fn new() -> Self { - Self(Some(HashMap::new())) - } -} - -impl Deref for DeviceHashMap { - type Target = HashMap; - - fn deref(&self) -> &Self::Target { - self.0.as_ref().unwrap() - } -} - -impl DerefMut for DeviceHashMap { - fn deref_mut(&mut self) -> &mut Self::Target { - self.0.as_mut().unwrap() - } -} diff --git a/windows_kext/driver/src/lib.rs b/windows_kext/driver/src/lib.rs index d13e9d3f..45bc6c60 100644 --- a/windows_kext/driver/src/lib.rs +++ b/windows_kext/driver/src/lib.rs @@ -13,7 +13,6 @@ mod connection; mod connection_cache; mod connection_map; mod device; -mod driver_hashmap; mod entry; mod id_cache; pub mod logger; @@ -23,6 +22,11 @@ mod stream_callouts; use wdk::allocator::WindowsAllocator; +// For consistent behavior during development and production only release mode should be used. +// Certain behavior of the compiler will change and this can result in errors and different behavior in debug and release mode. +#[cfg(debug_assertions)] +compile_error!("Must be built in release mode to ensure consistent behavior and prevent optimization-related issues. Use `cargo build --release`."); + #[cfg(not(test))] use core::panic::PanicInfo; diff --git a/windows_kext/driver/src/logger.rs b/windows_kext/driver/src/logger.rs index 5a0440a2..129f9f7c 100644 --- a/windows_kext/driver/src/logger.rs +++ b/windows_kext/driver/src/logger.rs @@ -6,11 +6,8 @@ use core::{ }; use protocol::info::{Info, Severity}; -#[cfg(not(debug_assertions))] pub const LOG_LEVEL: u8 = Severity::Warning as u8; - -#[cfg(debug_assertions)] -pub const LOG_LEVEL: u8 = Severity::Trace as u8; +// pub const LOG_LEVEL: u8 = Severity::Trace as u8; pub const MAX_LOG_LINE_SIZE: usize = 150; diff --git a/windows_kext/driver/src/packet_callouts.rs b/windows_kext/driver/src/packet_callouts.rs index fb3ee90b..1e8c28f1 100644 --- a/windows_kext/driver/src/packet_callouts.rs +++ b/windows_kext/driver/src/packet_callouts.rs @@ -110,6 +110,16 @@ fn ip_packet_layer( interface_index: u32, sub_interface_index: u32, ) { + // Make the default path as drop. + data.block_and_absorb(); + + // Block all fragment data. No easy way to keep track of the origin and they are rarely used. + if data.is_fragment_data() { + data.block_and_absorb(); + crate::err!("blocked fragment packet"); + return; + } + let Some(device) = crate::entry::get_device() else { return; }; @@ -140,7 +150,7 @@ fn ip_packet_layer( } { Ok(key) => key, Err(err) => { - crate::dbg!("failed to get key from nbl: {}", err); + crate::err!("failed to get key from nbl: {}", err); return; } }; diff --git a/windows_kext/driver/src/packet_util.rs b/windows_kext/driver/src/packet_util.rs index 42b4dac7..6f5b4eb3 100644 --- a/windows_kext/driver/src/packet_util.rs +++ b/windows_kext/driver/src/packet_util.rs @@ -245,8 +245,6 @@ fn print_packet(packet: &[u8]) { /// /// * `Ok(Key)` - A key containing the protocol, local and remote addresses and ports. /// * `Err(String)` - An error message if the function fails to get net_buffer data. -const HEADERS_LEN: usize = smoltcp::wire::IPV4_HEADER_LEN + smoltcp::wire::TCP_HEADER_LEN; - fn get_ports(packet: &[u8], protocol: smoltcp::wire::IpProtocol) -> (u16, u16) { match protocol { smoltcp::wire::IpProtocol::Tcp => { @@ -262,12 +260,13 @@ fn get_ports(packet: &[u8], protocol: smoltcp::wire::IpProtocol) -> (u16, u16) { } pub fn get_key_from_nbl_v4(nbl: &NetBufferList, direction: Direction) -> Result { - // Get bytes - let mut headers = [0; HEADERS_LEN]; + // Get first bytes of the packet. IP header + src port (2 bytes) + dst port (2 bytes) + let mut headers = [0; smoltcp::wire::IPV4_HEADER_LEN + 4]; if nbl.read_bytes(&mut headers).is_err() { return Err("failed to get net_buffer data".to_string()); } + // This will panic in debug mode, probably because of runtime checks. // Parse packet let ip_packet = Ipv4Packet::new_unchecked(&headers); let (src_port, dst_port) = get_ports( @@ -307,11 +306,13 @@ pub fn get_key_from_nbl_v4(nbl: &NetBufferList, direction: Direction) -> Result< /// * `Ok(Key)` - A key containing the protocol, local and remote addresses and ports. /// * `Err(String)` - An error message if the function fails to get net_buffer data. pub fn get_key_from_nbl_v6(nbl: &NetBufferList, direction: Direction) -> Result { - // Get bytes - let mut headers = [0; smoltcp::wire::IPV6_HEADER_LEN + smoltcp::wire::TCP_HEADER_LEN]; + // Get first bytes of the packet. IP header + src port (2 bytes) + dst port (2 bytes) + let mut headers = [0; smoltcp::wire::IPV6_HEADER_LEN + 4]; let Ok(()) = nbl.read_bytes(&mut headers) else { return Err("failed to get net_buffer data".to_string()); }; + + // This will panic in debug mode, probably because of runtime checks. // Parse packet let ip_packet = Ipv6Packet::new_unchecked(&headers); let (src_port, dst_port) = get_ports( diff --git a/windows_kext/driver/src/stream_callouts.rs b/windows_kext/driver/src/stream_callouts.rs index a6393764..f0a0f0d0 100644 --- a/windows_kext/driver/src/stream_callouts.rs +++ b/windows_kext/driver/src/stream_callouts.rs @@ -4,6 +4,8 @@ use wdk::filter_engine::{callout_data::CalloutData, layer, net_buffer::NetBuffer use crate::{bandwidth, connection::Direction}; pub fn stream_layer_tcp_v4(data: CalloutData) { + type Fields = layer::FieldsStreamV4; + let Some(device) = crate::entry::get_device() else { return; }; @@ -16,7 +18,6 @@ pub fn stream_layer_tcp_v4(data: CalloutData) { } else { return; }; - type Fields = layer::FieldsStreamV4; let local_ip = Ipv4Address::from_bytes( &data .get_value_u32(Fields::IpLocalAddress as usize) @@ -56,6 +57,8 @@ pub fn stream_layer_tcp_v4(data: CalloutData) { } pub fn stream_layer_tcp_v6(data: CalloutData) { + type Fields = layer::FieldsStreamV6; + let Some(device) = crate::entry::get_device() else { return; }; @@ -68,16 +71,18 @@ pub fn stream_layer_tcp_v6(data: CalloutData) { } else { return; }; - type Fields = layer::FieldsStreamV6; + if data_length == 0 { return; } let local_ip = Ipv6Address::from_bytes(data.get_value_byte_array16(Fields::IpLocalAddress as usize)); let local_port = data.get_value_u16(Fields::IpLocalPort as usize); + let remote_ip = Ipv6Address::from_bytes(data.get_value_byte_array16(Fields::IpRemoteAddress as usize)); let remote_port = data.get_value_u16(Fields::IpRemotePort as usize); + match direction { Direction::Outbound => { device.bandwidth_stats.update_tcp_v6_tx( @@ -105,6 +110,8 @@ pub fn stream_layer_tcp_v6(data: CalloutData) { } pub fn stream_layer_udp_v4(data: CalloutData) { + type Fields = layer::FieldsDatagramDataV4; + let Some(device) = crate::entry::get_device() else { return; }; @@ -112,7 +119,6 @@ pub fn stream_layer_udp_v4(data: CalloutData) { for nbl in NetBufferListIter::new(data.get_layer_data() as _) { data_length += nbl.get_data_length() as usize; } - type Fields = layer::FieldsDatagramDataV4; let mut direction = Direction::Inbound; if data.get_value_u8(Fields::Direction as usize) == 0 { direction = Direction::Outbound; @@ -157,6 +163,8 @@ pub fn stream_layer_udp_v4(data: CalloutData) { } pub fn stream_layer_udp_v6(data: CalloutData) { + type Fields = layer::FieldsDatagramDataV6; + let Some(device) = crate::entry::get_device() else { return; }; @@ -164,7 +172,6 @@ pub fn stream_layer_udp_v6(data: CalloutData) { for nbl in NetBufferListIter::new(data.get_layer_data() as _) { data_length += nbl.get_data_length() as usize; } - type Fields = layer::FieldsDatagramDataV6; let mut direction = Direction::Inbound; if data.get_value_u8(Fields::Direction as usize) == 0 { direction = Direction::Outbound; diff --git a/windows_kext/kextinterface/kext.go b/windows_kext/kextinterface/kext.go index 8322ead8..c9b61c7c 100644 --- a/windows_kext/kextinterface/kext.go +++ b/windows_kext/kextinterface/kext.go @@ -38,7 +38,7 @@ var ( ) const ( - winInvalidHandleValue = windows.Handle(^uintptr(0)) // Max value + winInvalidHandleValue = windows.InvalidHandle stopServiceTimeoutDuration = time.Duration(30 * time.Second) ) @@ -48,7 +48,7 @@ type KextService struct { } func (s *KextService) isValid() bool { - return s != nil && s.handle != winInvalidHandleValue && s.handle != 0 + return s != nil && s.handle != windows.InvalidHandle && s.handle != 0 } func (s *KextService) isRunning() (bool, error) { @@ -99,7 +99,7 @@ func (s *KextService) Start(wait bool) error { _ = windows.ControlService(s.handle, windows.SERVICE_CONTROL_STOP, &status) _ = windows.DeleteService(s.handle) _ = windows.CloseServiceHandle(s.handle) - s.handle = winInvalidHandleValue + s.handle = windows.InvalidHandle return err } } @@ -158,7 +158,7 @@ func (s *KextService) Delete() error { return fmt.Errorf("failed to close service handle: %s", err) } - s.handle = winInvalidHandleValue + s.handle = windows.InvalidHandle return nil } @@ -234,7 +234,7 @@ func CreateKextService(driverName string, driverPath string) (*KextService, erro return nil, err } - service = winInvalidHandleValue + service = windows.InvalidHandle log.Warning("kext: old driver service was deleted successfully") } diff --git a/windows_kext/kextinterface/kext_file.go b/windows_kext/kextinterface/kext_file.go index 045ee06e..fac6c5cd 100644 --- a/windows_kext/kextinterface/kext_file.go +++ b/windows_kext/kextinterface/kext_file.go @@ -4,6 +4,8 @@ package kextinterface import ( + "fmt" + "golang.org/x/sys/windows" ) @@ -13,7 +15,16 @@ type KextFile struct { read_slice []byte } +// Read tries to read the supplied buffer length from the driver. +// The data from the driver is read in chunks `len(f.buffer)` and the extra data is cached for the next call. +// The performance penalty of calling the function with small buffers is very small. +// The function will block until the next info packet is received from the kext. func (f *KextFile) Read(buffer []byte) (int, error) { + if err := f.IsValid(); err != nil { + return 0, fmt.Errorf("failed to read: %w", err) + } + + // If no data is available from previous calls, read from kext. if f.read_slice == nil || len(f.read_slice) == 0 { err := f.refill_read_buffer() if err != nil { @@ -22,14 +33,19 @@ func (f *KextFile) Read(buffer []byte) (int, error) { } if len(f.read_slice) >= len(buffer) { - // Write all requested bytes. + // There is enough data to fill the requested buffer. copy(buffer, f.read_slice[0:len(buffer)]) + // Move the slice to contain the remaining data. f.read_slice = f.read_slice[len(buffer):] } else { - // Write all available bytes and read again. + // There is not enough data to fill the requested buffer. + + // Write everything available. copy(buffer[0:len(f.read_slice)], f.read_slice) copiedBytes := len(f.read_slice) f.read_slice = nil + + // Read again. _, err := f.Read(buffer[copiedBytes:]) if err != nil { return 0, err @@ -51,20 +67,33 @@ func (f *KextFile) refill_read_buffer() error { return nil } +// Write sends the buffer bytes to the kext. The function will block until the whole buffer is written to the kext. func (f *KextFile) Write(buffer []byte) (int, error) { + if err := f.IsValid(); err != nil { + return 0, fmt.Errorf("failed to write: %w", err) + } var count uint32 = 0 overlapped := &windows.Overlapped{} err := windows.WriteFile(f.handle, buffer, &count, overlapped) return int(count), err } +// Close closes the handle to the kext. This will cancel all active Reads and Writes. func (f *KextFile) Close() error { + if err := f.IsValid(); err != nil { + return fmt.Errorf("failed to close: %w", err) + } err := windows.CloseHandle(f.handle) - f.handle = winInvalidHandleValue + f.handle = windows.InvalidHandle return err } +// deviceIOControl exists for compatibility with the old kext. func (f *KextFile) deviceIOControl(code uint32, inData []byte, outData []byte) (*windows.Overlapped, error) { + if err := f.IsValid(); err != nil { + return nil, fmt.Errorf("failed to send io control: %w", err) + } + // Prepare the input data var inDataPtr *byte = nil var inDataSize uint32 = 0 if inData != nil { @@ -72,6 +101,7 @@ func (f *KextFile) deviceIOControl(code uint32, inData []byte, outData []byte) ( inDataSize = uint32(len(inData)) } + // Prepare the output data var outDataPtr *byte = nil var outDataSize uint32 = 0 if outData != nil { @@ -79,6 +109,7 @@ func (f *KextFile) deviceIOControl(code uint32, inData []byte, outData []byte) ( outDataSize = uint32(len(outData)) } + // Make the request to the kext. overlapped := &windows.Overlapped{} err := windows.DeviceIoControl(f.handle, code, @@ -92,6 +123,20 @@ func (f *KextFile) deviceIOControl(code uint32, inData []byte, outData []byte) ( return overlapped, nil } +// GetHandle returns the handle of the kext. func (f *KextFile) GetHandle() windows.Handle { return f.handle } + +// IsValid checks if kext file holds a valid handle to the kext driver. +func (f *KextFile) IsValid() error { + if f == nil { + return fmt.Errorf("nil kext file") + } + + if f.handle == windows.Handle(0) || f.handle == windows.InvalidHandle { + return fmt.Errorf("invalid handle") + } + + return nil +} diff --git a/windows_kext/kextinterface/version.txt b/windows_kext/kextinterface/version.txt index 19dfc079..186fe0af 100644 --- a/windows_kext/kextinterface/version.txt +++ b/windows_kext/kextinterface/version.txt @@ -1 +1 @@ -[2, 0, 3, 0] \ No newline at end of file +[2, 0, 6, 0] diff --git a/windows_kext/protocol/README.md b/windows_kext/protocol/README.md index cde5d85c..2547f988 100644 --- a/windows_kext/protocol/README.md +++ b/windows_kext/protocol/README.md @@ -2,3 +2,42 @@ Defines protocol that communicates with `kextinterface` / Portmaster. +The crate implements simple binary protocol. The communications is designed to be concurrent stream of packets. +Input and output work independent of each other. + - Pormtaster can read multiple info packets from the queue with single read request. + - Portmaster can write one command packet to the kernel extension with single write request. + +## Info: Kext -> Portmaster + +Info is a packet that sends information/events from the kernel extension to portmaster. +For example: `new connection`, `end of connection`, `bandwidth stats` ... check `info.rs` for full list. + +The Info packet contains a header that is 5 bytes +``` +0 1 2 3 4 +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Info Type | Length | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +``` +> Note that one tick mark represents one bit position. + +The header is followed by the info data. + + +## Command: Portmaster -> Kext + +Command is a packet that portmaster sends to the kernel extension. +For example: `verdict response`, `shutdown`, `get logs` ... check `command.rs` for full list. + +The header of the command packet is 1 byte +``` +0 1 2 3 4 5 6 7 ++-+-+-+-+-+-+-+-+ +| Command Type | ++-+-+-+-+-+-+-+-+ +``` +> Note that one tick mark represents one bit position. + +Rest of the packet will be the payload of the command (some commands don't contain payload just the command type). + diff --git a/windows_kext/release/README.md b/windows_kext/release/README.md index 939f88d6..319bbd2c 100644 --- a/windows_kext/release/README.md +++ b/windows_kext/release/README.md @@ -1,22 +1,26 @@ # Kext release tool ### Generate the zip file + - Make sure `kextinterface/version.txt` is up to date - Execute: `cargo run` * This will generate release `kext_release_vX-X-X.zip` file. Which contains all the necessary files to make the release. ### Generate the cab file + - Copy the zip and extract it on a windows machine. - * Some version Visual Studio needs to be installed. + * Visual Studio 2022 and WDK need to be installed. - From VS Command Prompt / PowerShell run: ``` cd kext_release_v.../ ./build_cab.bat ``` +> Script is written for VS `$SDK_Version = "10.0.22621.0"`. If different version is used update the script. -3. Sing the cab file +- Sing the cab file ### Let Microsoft Sign + - Go to https://partner.microsoft.com/en-us/dashboard/hardware/driver/New - Enter "PortmasterKext vX.X.X #1" as the product name - Upload `portmaster-kext_vX-X-X.cab` @@ -25,4 +29,4 @@ cd kext_release_v.../ - Wait for the process to finish, download the `.zip`. The zip will contain the release files. -> Optionally sign the .sys file. +> Optionally sign the .sys file, with company certificate. diff --git a/windows_kext/wdk/src/filter_engine/callout_data.rs b/windows_kext/wdk/src/filter_engine/callout_data.rs index bb861f84..ff155dd1 100644 --- a/windows_kext/wdk/src/filter_engine/callout_data.rs +++ b/windows_kext/wdk/src/filter_engine/callout_data.rs @@ -133,6 +133,10 @@ impl<'a> CalloutData<'a> { } } + pub fn is_fragment_data(&self) -> bool { + unsafe { (*self.metadata).is_fragment_data() } + } + pub fn pend_operation( &mut self, packet_list: Option, @@ -157,24 +161,28 @@ impl<'a> CalloutData<'a> { pub fn action_permit(&mut self) { unsafe { (*self.classify_out).action_permit(); + (*self.classify_out).clear_absorb_flag(); } } pub fn action_continue(&mut self) { unsafe { (*self.classify_out).action_continue(); + (*self.classify_out).clear_absorb_flag(); } } pub fn action_block(&mut self) { unsafe { (*self.classify_out).action_block(); + (*self.classify_out).clear_absorb_flag(); } } pub fn action_none(&mut self) { unsafe { (*self.classify_out).set_none(); + (*self.classify_out).clear_absorb_flag(); } } @@ -194,13 +202,6 @@ impl<'a> CalloutData<'a> { self.get_value_u32(flags_index) & FWP_CONDITION_FLAG_IS_REAUTHORIZE > 0 } - pub fn parmit_and_absorb(&mut self) { - unsafe { - (*self.classify_out).action_permit(); - (*self.classify_out).set_absorb(); - } - } - pub fn get_callout_id(&self) -> usize { self.callout_id } diff --git a/windows_kext/wdk/src/filter_engine/classify.rs b/windows_kext/wdk/src/filter_engine/classify.rs index 1acff2ed..6d5b9b05 100644 --- a/windows_kext/wdk/src/filter_engine/classify.rs +++ b/windows_kext/wdk/src/filter_engine/classify.rs @@ -80,6 +80,11 @@ impl ClassifyOut { self.flags |= FWPS_CLASSIFY_OUT_FLAG_ABSORB; } + // Removes the absorb flag. + pub fn clear_absorb_flag(&mut self) { + self.flags &= !FWPS_CLASSIFY_OUT_FLAG_ABSORB; + } + // Clear the write flag permission. Next filter in the chain will not change the action. pub fn clear_write_flag(&mut self) { self.rights &= !FWPS_RIGHT_ACTION_WRITE; diff --git a/windows_kext/wdk/src/filter_engine/ffi.rs b/windows_kext/wdk/src/filter_engine/ffi.rs index 45103272..bf5fa361 100644 --- a/windows_kext/wdk/src/filter_engine/ffi.rs +++ b/windows_kext/wdk/src/filter_engine/ffi.rs @@ -62,7 +62,7 @@ pub(crate) fn register_sublayer( sublayer.displayData.name = name.as_ptr() as _; sublayer.displayData.description = description.as_ptr() as _; sublayer.flags = 0; - sublayer.weight = 0xFFFF; + sublayer.weight = 0xFFFF; // Set to Max value. Weight compared to other sublayers. let status = FwpmSubLayerAdd0(filter_engine_handle, &sublayer, core::ptr::null_mut()); check_ntstatus(status as i32)?; diff --git a/windows_kext/wdk/src/filter_engine/metadata.rs b/windows_kext/wdk/src/filter_engine/metadata.rs index 632830fa..55b3b7de 100644 --- a/windows_kext/wdk/src/filter_engine/metadata.rs +++ b/windows_kext/wdk/src/filter_engine/metadata.rs @@ -7,9 +7,9 @@ use windows_sys::Win32::{ NetworkManagement::{ IpHelper::IP_ADDRESS_PREFIX, WindowsFilteringPlatform::{ - FWPS_METADATA_FIELD_COMPLETION_HANDLE, FWPS_METADATA_FIELD_PROCESS_ID, - FWPS_METADATA_FIELD_PROCESS_PATH, FWPS_METADATA_FIELD_REMOTE_SCOPE_ID, - FWPS_METADATA_FIELD_TRANSPORT_CONTROL_DATA, + FWPS_METADATA_FIELD_COMPLETION_HANDLE, FWPS_METADATA_FIELD_FRAGMENT_DATA, + FWPS_METADATA_FIELD_PROCESS_ID, FWPS_METADATA_FIELD_PROCESS_PATH, + FWPS_METADATA_FIELD_REMOTE_SCOPE_ID, FWPS_METADATA_FIELD_TRANSPORT_CONTROL_DATA, FWPS_METADATA_FIELD_TRANSPORT_ENDPOINT_HANDLE, FWP_BYTE_BLOB, FWP_DIRECTION, }, }, @@ -137,6 +137,14 @@ impl FwpsIncomingMetadataValues { None } + pub(crate) fn is_fragment_data(&self) -> bool { + if self.has_field(FWPS_METADATA_FIELD_FRAGMENT_DATA) { + return self.fragment_metadata.fragment_offset != 0; + } + + false + } + pub(crate) unsafe fn get_control_data(&self) -> Option> { if self.has_field(FWPS_METADATA_FIELD_TRANSPORT_CONTROL_DATA) { if self.control_data.is_null() || self.control_data_length == 0 {