Restructure modules (#1572)
* Move portbase into monorepo * Add new simple module mgr * [WIP] Switch to new simple module mgr * Add StateMgr and more worker variants * [WIP] Switch more modules * [WIP] Switch more modules * [WIP] swtich more modules * [WIP] switch all SPN modules * [WIP] switch all service modules * [WIP] Convert all workers to the new module system * [WIP] add new task system to module manager * [WIP] Add second take for scheduling workers * [WIP] Add FIXME for bugs in new scheduler * [WIP] Add minor improvements to scheduler * [WIP] Add new worker scheduler * [WIP] Fix more bug related to new module system * [WIP] Fix start handing of the new module system * [WIP] Improve startup process * [WIP] Fix minor issues * [WIP] Fix missing subsystem in settings * [WIP] Initialize managers in constructor * [WIP] Move module event initialization to constrictors * [WIP] Fix setting for enabling and disabling the SPN module * [WIP] Move API registeration into module construction * [WIP] Update states mgr for all modules * [WIP] Add CmdLine operation support * Add state helper methods to module group and instance * Add notification and module status handling to status package * Fix starting issues * Remove pilot widget and update security lock to new status data * Remove debug logs * Improve http server shutdown * Add workaround for cleanly shutting down firewall+netquery * Improve logging * Add syncing states with notifications for new module system * Improve starting, stopping, shutdown; resolve FIXMEs/TODOs * [WIP] Fix most unit tests * Review new module system and fix minor issues * Push shutdown and restart events again via API * Set sleep mode via interface * Update example/template module * [WIP] Fix spn/cabin unit test * Remove deprecated UI elements * Make log output more similar for the logging transition phase * Switch spn hub and observer cmds to new module system * Fix log sources * Make worker mgr less error prone * Fix tests and minor issues * Fix observation hub * Improve shutdown and restart handling * Split up big connection.go source file * Move varint and dsd packages to structures repo * Improve expansion test * Fix linter warnings * Fix interception module on windows * Fix linter errors --------- Co-authored-by: Vladimir Stoilov <vladimir@safing.io>
This commit is contained in:
51
base/utils/osdetail/colors_windows.go
Normal file
51
base/utils/osdetail/colors_windows.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package osdetail
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var (
|
||||
colorSupport bool
|
||||
|
||||
colorSupportChecked bool
|
||||
checkingColorSupport sync.Mutex
|
||||
)
|
||||
|
||||
// EnableColorSupport tries to enable color support for cmd on windows and returns whether it is enabled.
|
||||
func EnableColorSupport() bool {
|
||||
checkingColorSupport.Lock()
|
||||
defer checkingColorSupport.Unlock()
|
||||
|
||||
if !colorSupportChecked {
|
||||
colorSupport = enableColorSupport()
|
||||
colorSupportChecked = true
|
||||
}
|
||||
return colorSupport
|
||||
}
|
||||
|
||||
func enableColorSupport() bool {
|
||||
if IsAtLeastWindowsNTVersionWithDefault("10", false) {
|
||||
|
||||
// check if windows.Stdout is file
|
||||
if windows.GetFileInformationByHandle(windows.Stdout, &windows.ByHandleFileInformation{}) == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var mode uint32
|
||||
err := windows.GetConsoleMode(windows.Stdout, &mode)
|
||||
if err == nil {
|
||||
if mode&windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 {
|
||||
mode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||
err = windows.SetConsoleMode(windows.Stdout, mode)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
51
base/utils/osdetail/command.go
Normal file
51
base/utils/osdetail/command.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package osdetail
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// RunCmd runs the given command and run error checks on the output.
|
||||
func RunCmd(command ...string) (output []byte, err error) {
|
||||
// Create command to execute.
|
||||
var cmd *exec.Cmd
|
||||
switch len(command) {
|
||||
case 0:
|
||||
return nil, errors.New("no command supplied")
|
||||
case 1:
|
||||
cmd = exec.Command(command[0])
|
||||
default:
|
||||
cmd = exec.Command(command[0], command[1:]...)
|
||||
}
|
||||
|
||||
// Create and assign output buffers.
|
||||
var stdoutBuf bytes.Buffer
|
||||
var stderrBuf bytes.Buffer
|
||||
cmd.Stdout = &stdoutBuf
|
||||
cmd.Stderr = &stderrBuf
|
||||
|
||||
// Run command and collect output.
|
||||
err = cmd.Run()
|
||||
stdout, stderr := stdoutBuf.Bytes(), stderrBuf.Bytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Command might not return an error, but just write to stdout instead.
|
||||
if len(stderr) > 0 {
|
||||
return nil, errors.New(strings.SplitN(string(stderr), "\n", 2)[0])
|
||||
}
|
||||
|
||||
// Debugging output:
|
||||
// fmt.Printf("command stdout: %s\n", stdout)
|
||||
// fmt.Printf("command stderr: %s\n", stderr)
|
||||
|
||||
// Finalize stdout.
|
||||
cleanedOutput := bytes.TrimSpace(stdout)
|
||||
if len(cleanedOutput) == 0 {
|
||||
return nil, ErrEmptyOutput
|
||||
}
|
||||
|
||||
return cleanedOutput, nil
|
||||
}
|
||||
17
base/utils/osdetail/dnscache_windows.go
Normal file
17
base/utils/osdetail/dnscache_windows.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package osdetail
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// EnableDNSCache enables the Windows Service "DNS Client" by setting the registry value "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\Dnscache" to 2 (Automatic).
|
||||
// A reboot is required for this setting to take effect.
|
||||
func EnableDNSCache() error {
|
||||
return exec.Command("reg", "add", "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\services\\Dnscache", "/v", "Start", "/t", "REG_DWORD", "/d", "2", "/f").Run()
|
||||
}
|
||||
|
||||
// DisableDNSCache disables the Windows Service "DNS Client" by setting the registry value "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\Dnscache" to 4 (Disabled).
|
||||
// A reboot is required for this setting to take effect.
|
||||
func DisableDNSCache() error {
|
||||
return exec.Command("reg", "add", "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\services\\Dnscache", "/v", "Start", "/t", "REG_DWORD", "/d", "4", "/f").Run()
|
||||
}
|
||||
12
base/utils/osdetail/errors.go
Normal file
12
base/utils/osdetail/errors.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package osdetail
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
// ErrNotSupported is returned when an operation is not supported on the current platform.
|
||||
ErrNotSupported = errors.New("not supported")
|
||||
// ErrNotFound is returned when the desired data is not found.
|
||||
ErrNotFound = errors.New("not found")
|
||||
// ErrEmptyOutput is a special error that is returned when an operation has no error, but also returns to data.
|
||||
ErrEmptyOutput = errors.New("command succeeded with empty output")
|
||||
)
|
||||
112
base/utils/osdetail/service_windows.go
Normal file
112
base/utils/osdetail/service_windows.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package osdetail
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Service Status
|
||||
const (
|
||||
StatusUnknown uint8 = iota
|
||||
StatusRunningStoppable
|
||||
StatusRunningNotStoppable
|
||||
StatusStartPending
|
||||
StatusStopPending
|
||||
StatusStopped
|
||||
)
|
||||
|
||||
// Exported errors
|
||||
var (
|
||||
ErrServiceNotStoppable = errors.New("the service is not stoppable")
|
||||
)
|
||||
|
||||
// GetServiceStatus returns the current status of a Windows Service (limited implementation).
|
||||
func GetServiceStatus(name string) (status uint8, err error) {
|
||||
|
||||
output, err := exec.Command("sc", "query", name).Output()
|
||||
if err != nil {
|
||||
return StatusUnknown, fmt.Errorf("failed to query service: %s", err)
|
||||
}
|
||||
outputString := string(output)
|
||||
|
||||
switch {
|
||||
case strings.Contains(outputString, "RUNNING"):
|
||||
if strings.Contains(outputString, "NOT_STOPPABLE") {
|
||||
return StatusRunningNotStoppable, nil
|
||||
}
|
||||
return StatusRunningStoppable, nil
|
||||
case strings.Contains(outputString, "STOP_PENDING"):
|
||||
return StatusStopPending, nil
|
||||
case strings.Contains(outputString, "STOPPED"):
|
||||
return StatusStopped, nil
|
||||
case strings.Contains(outputString, "START_PENDING"):
|
||||
return StatusStopPending, nil
|
||||
}
|
||||
|
||||
return StatusUnknown, errors.New("unknown service status")
|
||||
}
|
||||
|
||||
// StopService stops a Windows Service.
|
||||
func StopService(name string) (err error) {
|
||||
pendingCnt := 0
|
||||
for {
|
||||
|
||||
// get status
|
||||
status, err := GetServiceStatus(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch status {
|
||||
case StatusRunningStoppable:
|
||||
err := exec.Command("sc", "stop", name).Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to stop service: %s", err)
|
||||
}
|
||||
case StatusRunningNotStoppable:
|
||||
return ErrServiceNotStoppable
|
||||
case StatusStartPending, StatusStopPending:
|
||||
pendingCnt++
|
||||
if pendingCnt > 50 {
|
||||
return errors.New("service stuck in pending status (5s)")
|
||||
}
|
||||
case StatusStopped:
|
||||
return nil
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
// SartService starts a Windows Service.
|
||||
func SartService(name string) (err error) {
|
||||
pendingCnt := 0
|
||||
for {
|
||||
|
||||
// get status
|
||||
status, err := GetServiceStatus(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch status {
|
||||
case StatusRunningStoppable, StatusRunningNotStoppable:
|
||||
return nil
|
||||
case StatusStartPending, StatusStopPending:
|
||||
pendingCnt++
|
||||
if pendingCnt > 50 {
|
||||
return errors.New("service stuck in pending status (5s)")
|
||||
}
|
||||
case StatusStopped:
|
||||
err := exec.Command("sc", "start", name).Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to stop service: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
49
base/utils/osdetail/shell_windows.go
Normal file
49
base/utils/osdetail/shell_windows.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package osdetail
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// RunPowershellCmd runs a powershell command and returns its output.
|
||||
func RunPowershellCmd(script string) (output []byte, err error) {
|
||||
// Create command to execute.
|
||||
return RunCmd(
|
||||
"powershell.exe",
|
||||
"-ExecutionPolicy", "Bypass",
|
||||
"-NoProfile",
|
||||
"-NonInteractive",
|
||||
"[System.Console]::OutputEncoding = [System.Text.Encoding]::UTF8\n"+script,
|
||||
)
|
||||
}
|
||||
|
||||
const outputSeparator = "pwzzhtuvpwdgozhzbnjj"
|
||||
|
||||
// RunTerminalCmd runs a Windows cmd command and returns its output.
|
||||
// It sets the output of the cmd to UTF-8 in order to avoid encoding errors.
|
||||
func RunTerminalCmd(command ...string) (output []byte, err error) {
|
||||
output, err = RunCmd(append([]string{
|
||||
"cmd.exe",
|
||||
"/c",
|
||||
"chcp", // Set output encoding...
|
||||
"65001", // ...to UTF-8.
|
||||
"&",
|
||||
"echo",
|
||||
outputSeparator,
|
||||
"&",
|
||||
},
|
||||
command...,
|
||||
)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Find correct start of output and shift start.
|
||||
index := bytes.IndexAny(output, outputSeparator+"\r\n")
|
||||
if index < 0 {
|
||||
return nil, errors.New("failed to post-process output: could not find output separator")
|
||||
}
|
||||
output = output[index+len(outputSeparator)+2:]
|
||||
|
||||
return output, nil
|
||||
}
|
||||
120
base/utils/osdetail/svchost_windows.go
Normal file
120
base/utils/osdetail/svchost_windows.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package osdetail
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
serviceNames map[int32][]string
|
||||
serviceNamesLock sync.Mutex
|
||||
)
|
||||
|
||||
// Errors
|
||||
var (
|
||||
ErrServiceNotFound = errors.New("no service with the given PID was found")
|
||||
)
|
||||
|
||||
// GetServiceNames returns all service names assosicated with a svchost.exe process on Windows.
|
||||
func GetServiceNames(pid int32) ([]string, error) {
|
||||
serviceNamesLock.Lock()
|
||||
defer serviceNamesLock.Unlock()
|
||||
|
||||
if serviceNames != nil {
|
||||
names, ok := serviceNames[pid]
|
||||
if ok {
|
||||
return names, nil
|
||||
}
|
||||
}
|
||||
|
||||
serviceNames, err := GetAllServiceNames()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
names, ok := serviceNames[pid]
|
||||
if ok {
|
||||
return names, nil
|
||||
}
|
||||
|
||||
return nil, ErrServiceNotFound
|
||||
}
|
||||
|
||||
// GetAllServiceNames returns a list of service names assosicated with svchost.exe processes on Windows.
|
||||
func GetAllServiceNames() (map[int32][]string, error) {
|
||||
output, err := exec.Command("tasklist", "/svc", "/fi", "imagename eq svchost.exe").Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get svchost tasklist: %s", err)
|
||||
}
|
||||
|
||||
// file scanner
|
||||
scanner := bufio.NewScanner(bytes.NewReader(output))
|
||||
scanner.Split(bufio.ScanLines)
|
||||
|
||||
// skip output header
|
||||
for scanner.Scan() {
|
||||
if strings.HasPrefix(scanner.Text(), "=") {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
pid int32
|
||||
services []string
|
||||
collection = make(map[int32][]string)
|
||||
)
|
||||
|
||||
for scanner.Scan() {
|
||||
// get fields of line
|
||||
fields := strings.Fields(scanner.Text())
|
||||
|
||||
// check fields length
|
||||
if len(fields) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// new entry
|
||||
if fields[0] == "svchost.exe" {
|
||||
// save old entry
|
||||
if pid != 0 {
|
||||
collection[pid] = services
|
||||
}
|
||||
// reset PID
|
||||
pid = 0
|
||||
services = make([]string, 0, len(fields))
|
||||
|
||||
// check fields length
|
||||
if len(fields) < 3 {
|
||||
continue
|
||||
}
|
||||
|
||||
// get pid
|
||||
i, err := strconv.ParseInt(fields[1], 10, 32)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
pid = int32(i)
|
||||
|
||||
// skip used fields
|
||||
fields = fields[2:]
|
||||
}
|
||||
|
||||
// add service names
|
||||
for _, field := range fields {
|
||||
services = append(services, strings.Trim(strings.TrimSpace(field), ","))
|
||||
}
|
||||
}
|
||||
|
||||
if pid != 0 {
|
||||
// save last entry
|
||||
collection[pid] = services
|
||||
}
|
||||
|
||||
return collection, nil
|
||||
}
|
||||
99
base/utils/osdetail/version_windows.go
Normal file
99
base/utils/osdetail/version_windows.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package osdetail
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/go-version"
|
||||
"github.com/shirou/gopsutil/host"
|
||||
)
|
||||
|
||||
var (
|
||||
// versionRe = regexp.MustCompile(`[0-9\.]+`)
|
||||
|
||||
windowsNTVersion string
|
||||
windowsNTVersionForCmp *version.Version
|
||||
|
||||
fetching sync.Mutex
|
||||
fetched bool
|
||||
)
|
||||
|
||||
// WindowsNTVersion returns the current Windows version.
|
||||
func WindowsNTVersion() (string, error) {
|
||||
var err error
|
||||
fetching.Lock()
|
||||
defer fetching.Unlock()
|
||||
|
||||
if !fetched {
|
||||
_, _, windowsNTVersion, err = host.PlatformInformation()
|
||||
|
||||
windowsNTVersion = strings.SplitN(windowsNTVersion, " ", 2)[0]
|
||||
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to obtain Windows-Version: %s", err)
|
||||
}
|
||||
|
||||
windowsNTVersionForCmp, err = version.NewVersion(windowsNTVersion)
|
||||
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse Windows-Version %s: %s", windowsNTVersion, err)
|
||||
}
|
||||
|
||||
fetched = true
|
||||
}
|
||||
|
||||
return windowsNTVersion, err
|
||||
}
|
||||
|
||||
// IsAtLeastWindowsNTVersion returns whether the current WindowsNT version is at least the given version or newer.
|
||||
func IsAtLeastWindowsNTVersion(v string) (bool, error) {
|
||||
_, err := WindowsNTVersion()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
versionForCmp, err := version.NewVersion(v)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return windowsNTVersionForCmp.GreaterThanOrEqual(versionForCmp), nil
|
||||
}
|
||||
|
||||
// IsAtLeastWindowsNTVersionWithDefault is like IsAtLeastWindowsNTVersion(), but keeps the Error and returns the default Value in Errorcase
|
||||
func IsAtLeastWindowsNTVersionWithDefault(v string, defaultValue bool) bool {
|
||||
val, err := IsAtLeastWindowsNTVersion(v)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// IsAtLeastWindowsVersion returns whether the current Windows version is at least the given version or newer.
|
||||
func IsAtLeastWindowsVersion(v string) (bool, error) {
|
||||
var NTVersion string
|
||||
switch v {
|
||||
case "7":
|
||||
NTVersion = "6.1"
|
||||
case "8":
|
||||
NTVersion = "6.2"
|
||||
case "8.1":
|
||||
NTVersion = "6.3"
|
||||
case "10":
|
||||
NTVersion = "10"
|
||||
default:
|
||||
return false, fmt.Errorf("failed to compare Windows-Version: Windows %s is unknown", v)
|
||||
}
|
||||
|
||||
return IsAtLeastWindowsNTVersion(NTVersion)
|
||||
}
|
||||
|
||||
// IsAtLeastWindowsVersionWithDefault is like IsAtLeastWindowsVersion(), but keeps the Error and returns the default Value in Errorcase
|
||||
func IsAtLeastWindowsVersionWithDefault(v string, defaultValue bool) bool {
|
||||
val, err := IsAtLeastWindowsVersion(v)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
return val
|
||||
}
|
||||
29
base/utils/osdetail/version_windows_test.go
Normal file
29
base/utils/osdetail/version_windows_test.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package osdetail
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestWindowsNTVersion(t *testing.T) {
|
||||
if str, err := WindowsNTVersion(); str == "" || err != nil {
|
||||
t.Fatalf("failed to obtain windows version: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAtLeastWindowsNTVersion(t *testing.T) {
|
||||
ret, err := IsAtLeastWindowsNTVersion("6")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to compare windows versions: %s", err)
|
||||
}
|
||||
if !ret {
|
||||
t.Fatalf("WindowsNTVersion is less than 6 (Vista)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAtLeastWindowsVersion(t *testing.T) {
|
||||
ret, err := IsAtLeastWindowsVersion("7")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to compare windows versions: %s", err)
|
||||
}
|
||||
if !ret {
|
||||
t.Fatalf("WindowsVersion is less than 7")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user