mirror of
https://github.com/safing/portbase
synced 2025-09-02 02:29:59 +00:00
Merge pull request #225 from safing/feature/pm-updates
Expose SaveConfig, Remove binmeta utils, improvements
This commit is contained in:
commit
88f974fa66
10 changed files with 4 additions and 387 deletions
72
.github/workflows/codeql-analysis.yml
vendored
72
.github/workflows/codeql-analysis.yml
vendored
|
@ -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
|
|
|
@ -58,10 +58,10 @@ func loadConfig(requireValidConfig bool) error {
|
||||||
return nil
|
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
|
// It will acquire a read-lock on the global options registry
|
||||||
// lock and must lock each option!
|
// lock and must lock each option!
|
||||||
func saveConfig() error {
|
func SaveConfig() error {
|
||||||
optionsLock.RLock()
|
optionsLock.RLock()
|
||||||
defer optionsLock.RUnlock()
|
defer optionsLock.RUnlock()
|
||||||
|
|
||||||
|
|
|
@ -187,7 +187,7 @@ func setConfigOption(key string, value any, push bool) (err error) {
|
||||||
// finalize change, activate triggers
|
// finalize change, activate triggers
|
||||||
signalChanges()
|
signalChanges()
|
||||||
|
|
||||||
return saveConfig()
|
return SaveConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDefaultConfigOption sets a single value in the (fallback) default config.
|
// SetDefaultConfigOption sets a single value in the (fallback) default config.
|
||||||
|
|
|
@ -114,7 +114,7 @@ func (reg *Registry) Migrate(ctx context.Context) (err error) {
|
||||||
if err := m.MigrateFunc(migrationCtx, lastAppliedMigration, target, db); err != nil {
|
if err := m.MigrateFunc(migrationCtx, lastAppliedMigration, target, db); err != nil {
|
||||||
diag.Wrapped = err
|
diag.Wrapped = err
|
||||||
diag.FailedMigration = m.Description
|
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()
|
tracer.Submit()
|
||||||
return diag
|
return diag
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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."),
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
2
utils/osdetail/test/.gitignore
vendored
2
utils/osdetail/test/.gitignore
vendored
|
@ -1,2 +0,0 @@
|
||||||
test
|
|
||||||
test.exe
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue