From 7631b9d28afef8d9a39eb3635fdc94f15160db85 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 18 Dec 2023 17:09:07 +0100 Subject: [PATCH 1/4] Expose SaveConfig for writing config to disk --- config/persistence.go | 4 ++-- config/set.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/persistence.go b/config/persistence.go index 5f37804..7755846 100644 --- a/config/persistence.go +++ b/config/persistence.go @@ -58,10 +58,10 @@ func loadConfig(requireValidConfig bool) error { return nil } -// saveConfig saves the current configuration to file. +// SaveConfig saves the current configuration to file. // It will acquire a read-lock on the global options registry // lock and must lock each option! -func saveConfig() error { +func SaveConfig() error { optionsLock.RLock() defer optionsLock.RUnlock() diff --git a/config/set.go b/config/set.go index daeef6a..2c40ca2 100644 --- a/config/set.go +++ b/config/set.go @@ -187,7 +187,7 @@ func setConfigOption(key string, value any, push bool) (err error) { // finalize change, activate triggers signalChanges() - return saveConfig() + return SaveConfig() } // SetDefaultConfigOption sets a single value in the (fallback) default config. From 05348192cb7523bc38904e7199197332ab71e8f7 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 18 Dec 2023 17:09:31 +0100 Subject: [PATCH 2/4] Disable CodeQL workflow --- .github/workflows/codeql-analysis.yml | 72 --------------------------- 1 file changed, 72 deletions(-) delete mode 100644 .github/workflows/codeql-analysis.yml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index 1592316..0000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,72 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL" - -on: - push: - branches: [ "develop", master ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ "develop" ] - schedule: - - cron: '17 17 * * 1' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'go' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - - # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality - - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v2 - - # ℹī¸ Command-line programs to run using the OS shell. - # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - - # If the Autobuild fails above, remove it and uncomment the following three lines. - # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. - - # - run: | - # echo "Run, Build Application using script" - # ./location_of_script_within_repo/buildscript.sh - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 From 75e24bea70ac02172dcb7b9891f01a035ad28ecb Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 18 Dec 2023 17:10:01 +0100 Subject: [PATCH 3/4] Remove binmeta utils, which moved to portmaster/profile/icons --- utils/osdetail/binmeta.go | 121 ---------------------------- utils/osdetail/binmeta_default.go | 15 ---- utils/osdetail/binmeta_test.go | 47 ----------- utils/osdetail/binmeta_windows.go | 78 ------------------ utils/osdetail/test/.gitignore | 2 - utils/osdetail/test/main_windows.go | 48 ----------- 6 files changed, 311 deletions(-) delete mode 100644 utils/osdetail/binmeta.go delete mode 100644 utils/osdetail/binmeta_default.go delete mode 100644 utils/osdetail/binmeta_test.go delete mode 100644 utils/osdetail/binmeta_windows.go delete mode 100644 utils/osdetail/test/.gitignore delete mode 100644 utils/osdetail/test/main_windows.go diff --git a/utils/osdetail/binmeta.go b/utils/osdetail/binmeta.go deleted file mode 100644 index 76c0e8d..0000000 --- a/utils/osdetail/binmeta.go +++ /dev/null @@ -1,121 +0,0 @@ -package osdetail - -import ( - "path/filepath" - "regexp" - "strings" -) - -var ( - segmentsSplitter = regexp.MustCompile("[^A-Za-z0-9]*[A-Z]?[a-z0-9]*") - nameOnly = regexp.MustCompile("^[A-Za-z0-9]+$") - delimitersAtStart = regexp.MustCompile("^[^A-Za-z0-9]+") - delimitersOnly = regexp.MustCompile("^[^A-Za-z0-9]+$") - removeQuotes = strings.NewReplacer(`"`, ``, `'`, ``) -) - -// GenerateBinaryNameFromPath generates a more human readable binary name from -// the given path. This function is used as fallback in the GetBinaryName -// functions. -func GenerateBinaryNameFromPath(path string) string { - // Get file name from path. - _, fileName := filepath.Split(path) - - // Split up into segments. - segments := segmentsSplitter.FindAllString(fileName, -1) - - // Remove last segment if it's an extension. - if len(segments) >= 2 { - switch strings.ToLower(segments[len(segments)-1]) { - case - ".exe", // Windows Executable - ".msi", // Windows Installer - ".bat", // Windows Batch File - ".cmd", // Windows Command Script - ".ps1", // Windows Powershell Cmdlet - ".run", // Linux Executable - ".appimage", // Linux AppImage - ".app", // MacOS Executable - ".action", // MacOS Automator Action - ".out": // Generic Compiled Executable - segments = segments[:len(segments)-1] - } - } - - // Debugging snippet: - // fmt.Printf("segments: %s\n", segments) - - // Go through segments and collect name parts. - nameParts := make([]string, 0, len(segments)) - var fragments string - for _, segment := range segments { - // Group very short segments. - if len(delimitersAtStart.ReplaceAllString(segment, "")) <= 2 { - fragments += segment - continue - } else if fragments != "" { - nameParts = append(nameParts, fragments) - fragments = "" - } - - // Add segment to name. - nameParts = append(nameParts, segment) - } - // Add last fragment. - if fragments != "" { - nameParts = append(nameParts, fragments) - } - - // Debugging snippet: - // fmt.Printf("parts: %s\n", nameParts) - - // Post-process name parts - for i := range nameParts { - // Remove any leading delimiters. - nameParts[i] = delimitersAtStart.ReplaceAllString(nameParts[i], "") - - // Title-case name-only parts. - if nameOnly.MatchString(nameParts[i]) { - nameParts[i] = strings.Title(nameParts[i]) //nolint:staticcheck - } - } - - // Debugging snippet: - // fmt.Printf("final: %s\n", nameParts) - - return strings.Join(nameParts, " ") -} - -func cleanFileDescription(fileDescr string) string { - fields := strings.Fields(fileDescr) - - // Clean out and `"` and `'`. - for i := range fields { - fields[i] = removeQuotes.Replace(fields[i]) - } - - // If there is a 1 or 2 character delimiter field, only use fields before it. - endIndex := len(fields) - for i, field := range fields { - // Ignore the first field as well as fields with more than two characters. - if i >= 1 && len(field) <= 2 && !nameOnly.MatchString(field) { - endIndex = i - break - } - } - - // Concatenate name - binName := strings.Join(fields[:endIndex], " ") - - // If there are multiple sentences, only use the first. - if strings.Contains(binName, ". ") { - binName = strings.SplitN(binName, ". ", 2)[0] - } - - // If does not have any characters or numbers, return an empty string. - if delimitersOnly.MatchString(binName) { - return "" - } - - return strings.TrimSpace(binName) -} diff --git a/utils/osdetail/binmeta_default.go b/utils/osdetail/binmeta_default.go deleted file mode 100644 index 754cceb..0000000 --- a/utils/osdetail/binmeta_default.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build !windows - -package osdetail - -// GetBinaryNameFromSystem queries the operating system for a human readable -// name for the given binary path. -func GetBinaryNameFromSystem(path string) (string, error) { - return "", ErrNotSupported -} - -// GetBinaryIconFromSystem queries the operating system for the associated icon -// for a given binary path. -func GetBinaryIconFromSystem(path string) (string, error) { - return "", ErrNotSupported -} diff --git a/utils/osdetail/binmeta_test.go b/utils/osdetail/binmeta_test.go deleted file mode 100644 index cec2a5d..0000000 --- a/utils/osdetail/binmeta_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package osdetail - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestGenerateBinaryNameFromPath(t *testing.T) { - t.Parallel() - - assert.Equal(t, "Nslookup", GenerateBinaryNameFromPath("nslookup.exe")) - assert.Equal(t, "System Settings", GenerateBinaryNameFromPath("SystemSettings.exe")) - assert.Equal(t, "One Drive Setup", GenerateBinaryNameFromPath("OneDriveSetup.exe")) - assert.Equal(t, "Msedge", GenerateBinaryNameFromPath("msedge.exe")) - assert.Equal(t, "SIH Client", GenerateBinaryNameFromPath("SIHClient.exe")) - assert.Equal(t, "Openvpn Gui", GenerateBinaryNameFromPath("openvpn-gui.exe")) - assert.Equal(t, "Portmaster Core v0-1-2", GenerateBinaryNameFromPath("portmaster-core_v0-1-2.exe")) - assert.Equal(t, "Win Store App", GenerateBinaryNameFromPath("WinStore.App.exe")) - assert.Equal(t, "Test Script", GenerateBinaryNameFromPath(".test-script")) - assert.Equal(t, "Browser Broker", GenerateBinaryNameFromPath("browser_broker.exe")) - assert.Equal(t, "Virtual Box VM", GenerateBinaryNameFromPath("VirtualBoxVM")) - assert.Equal(t, "Io Elementary Appcenter", GenerateBinaryNameFromPath("io.elementary.appcenter")) - assert.Equal(t, "Microsoft Windows Store", GenerateBinaryNameFromPath("Microsoft.WindowsStore")) -} - -func TestCleanFileDescription(t *testing.T) { - t.Parallel() - - assert.Equal(t, "Product Name", cleanFileDescription("Product Name. Does this and that.")) - assert.Equal(t, "Product Name", cleanFileDescription("Product Name - Does this and that.")) - assert.Equal(t, "Product Name", cleanFileDescription("Product Name / Does this and that.")) - assert.Equal(t, "Product Name", cleanFileDescription("Product Name :: Does this and that.")) - assert.Equal(t, "/ Product Name", cleanFileDescription("/ Product Name")) - assert.Equal(t, "Product", cleanFileDescription("Product / Name")) - assert.Equal(t, "Software 2", cleanFileDescription("Software 2")) - assert.Equal(t, "Launcher for Software 2", cleanFileDescription("Launcher for 'Software 2'")) - assert.Equal(t, "", cleanFileDescription(". / Name")) - assert.Equal(t, "", cleanFileDescription(". ")) - assert.Equal(t, "", cleanFileDescription(".")) - assert.Equal(t, "N/A", cleanFileDescription("N/A")) - - assert.Equal(t, - "Product Name a Does this and that.", - cleanFileDescription("Product Name a Does this and that."), - ) -} diff --git a/utils/osdetail/binmeta_windows.go b/utils/osdetail/binmeta_windows.go deleted file mode 100644 index 056445c..0000000 --- a/utils/osdetail/binmeta_windows.go +++ /dev/null @@ -1,78 +0,0 @@ -package osdetail - -import ( - "fmt" -) - -const powershellGetFileDescription = `Get-ItemProperty %q | Select -ExpandProperty VersionInfo | Select -ExpandProperty FileDescription` - -// GetBinaryNameFromSystem queries the operating system for a human readable -// name for the given binary path. -func GetBinaryNameFromSystem(path string) (string, error) { - // Get FileProperties via Powershell call. - output, err := RunPowershellCmd(fmt.Sprintf(powershellGetFileDescription, path)) - if err != nil { - return "", fmt.Errorf("failed to get file properties of %s: %s", path, err) - } - - // Clean name. - binName := cleanFileDescription(string(output)) - if binName != "" { - return binName, nil - } - - // Generate a default name as default. - return "", ErrNotFound -} - -const powershellGetIcon = `Add-Type -AssemblyName System.Drawing -$Icon = [System.Drawing.Icon]::ExtractAssociatedIcon(%q) -$MemoryStream = New-Object System.IO.MemoryStream -$Icon.save($MemoryStream) -$Bytes = $MemoryStream.ToArray() -$MemoryStream.Flush() -$MemoryStream.Dispose() -[convert]::ToBase64String($Bytes)` - -// TODO: This returns a small and crappy icon. - -// Saving a better icon to file works: -/* -Add-Type -AssemblyName System.Drawing -$ImgList = New-Object System.Windows.Forms.ImageList -$ImgList.ImageSize = New-Object System.Drawing.Size(256,256) -$ImgList.ColorDepth = 32 -$Icon = [System.Drawing.Icon]::ExtractAssociatedIcon("C:\Program Files (x86)\Mozilla Firefox\firefox.exe") -$ImgList.Images.Add($Icon); -$BigIcon = $ImgList.Images.Item(0) -$BigIcon.Save("test.png") -*/ - -// But not saving to a memory stream: -/* -Add-Type -AssemblyName System.Drawing -$ImgList = New-Object System.Windows.Forms.ImageList -$ImgList.ImageSize = New-Object System.Drawing.Size(256,256) -$ImgList.ColorDepth = 32 -$Icon = [System.Drawing.Icon]::ExtractAssociatedIcon("C:\Program Files (x86)\Mozilla Firefox\firefox.exe") -$ImgList.Images.Add($Icon); -$MemoryStream = New-Object System.IO.MemoryStream -$BigIcon = $ImgList.Images.Item(0) -$BigIcon.Save($MemoryStream) -$Bytes = $MemoryStream.ToArray() -$MemoryStream.Flush() -$MemoryStream.Dispose() -[convert]::ToBase64String($Bytes) -*/ - -// GetBinaryIconFromSystem queries the operating system for the associated icon -// for a given binary path and returns it as a data-URL. -func GetBinaryIconFromSystem(path string) (string, error) { - // Get Associated File Icon via Powershell call. - output, err := RunPowershellCmd(fmt.Sprintf(powershellGetIcon, path)) - if err != nil { - return "", fmt.Errorf("failed to get file properties of %s: %s", path, err) - } - - return "data:image/png;base64," + string(output), nil -} diff --git a/utils/osdetail/test/.gitignore b/utils/osdetail/test/.gitignore deleted file mode 100644 index 35ccb46..0000000 --- a/utils/osdetail/test/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -test -test.exe diff --git a/utils/osdetail/test/main_windows.go b/utils/osdetail/test/main_windows.go deleted file mode 100644 index 77928bd..0000000 --- a/utils/osdetail/test/main_windows.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/safing/portbase/utils/osdetail" -) - -func main() { - fmt.Println("Binary Names:") - printBinaryName("openvpn-gui.exe", `C:\Program Files\OpenVPN\bin\openvpn-gui.exe`) - printBinaryName("firefox.exe", `C:\Program Files (x86)\Mozilla Firefox\firefox.exe`) - printBinaryName("powershell.exe", `C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe`) - printBinaryName("explorer.exe", `C:\Windows\explorer.exe`) - printBinaryName("svchost.exe", `C:\Windows\System32\svchost.exe`) - - fmt.Println("\n\nBinary Icons:") - printBinaryIcon("openvpn-gui.exe", `C:\Program Files\OpenVPN\bin\openvpn-gui.exe`) - printBinaryIcon("firefox.exe", `C:\Program Files (x86)\Mozilla Firefox\firefox.exe`) - printBinaryIcon("powershell.exe", `C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe`) - printBinaryIcon("explorer.exe", `C:\Windows\explorer.exe`) - printBinaryIcon("svchost.exe", `C:\Windows\System32\svchost.exe`) - - fmt.Println("\n\nSvcHost Service Names:") - names, err := osdetail.GetAllServiceNames() - if err != nil { - panic(err) - } - fmt.Printf("%+v\n", names) -} - -func printBinaryName(name, path string) { - binName, err := osdetail.GetBinaryNameFromSystem(path) - if err != nil { - fmt.Printf("%s: ERROR: %s\n", name, err) - } else { - fmt.Printf("%s: %s\n", name, binName) - } -} - -func printBinaryIcon(name, path string) { - binIcon, err := osdetail.GetBinaryIconFromSystem(path) - if err != nil { - fmt.Printf("%s: ERROR: %s\n", name, err) - } else { - fmt.Printf("%s: %s\n", name, binIcon) - } -} From 865cb5dd8f6da5ce103d405568863010f7eeaa78 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 18 Dec 2023 17:10:21 +0100 Subject: [PATCH 4/4] Improve migration logs --- database/migration/migration.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/migration/migration.go b/database/migration/migration.go index 63a0845..5b14b97 100644 --- a/database/migration/migration.go +++ b/database/migration/migration.go @@ -114,7 +114,7 @@ func (reg *Registry) Migrate(ctx context.Context) (err error) { if err := m.MigrateFunc(migrationCtx, lastAppliedMigration, target, db); err != nil { diag.Wrapped = err diag.FailedMigration = m.Description - tracer.Infof("migration: applied migration for %s: %s - %s", reg.key, target.String(), m.Description) + tracer.Errorf("migration: migration for %s failed: %s - %s", reg.key, target.String(), m.Description) tracer.Submit() return diag }