From a2ed11b2fb187c76ac4840aa433bf440e7704665 Mon Sep 17 00:00:00 2001 From: redanthrax Date: Mon, 20 Jun 2022 11:00:36 -0700 Subject: [PATCH] returned existing files to origin during restruct --- agent/agent.go | 218 ++++++- agent/agent_windows.go | 760 ++++++++++++++++++++++++ agent/checks.go | 23 +- agent/events/events_windows.go | 125 ++++ agent/install.go | 1 - agent/services_windows.go | 41 +- agent/structs.go | 2 +- agent/syscall/structs.go | 20 + agent/syscall/syscall_windows.go | 146 +++++ agent/system/system_windows.go | 23 - agent/{ => tactical}/rpc/rpc_windows.go | 98 +-- agent/{ => tactical}/rpc/structs.go | 0 agent/tactical/service/service.go | 1 - agent/tactical/tactical.go | 2 +- agent/tactical/tactical_windows.go | 9 +- agent/tasks/tasks_windows.go | 203 +++++++ agent/tasks_windows.go | 19 + agent/utils.go | 72 ++- agent/utils/utils.go | 17 +- 19 files changed, 1678 insertions(+), 102 deletions(-) create mode 100644 agent/events/events_windows.go create mode 100644 agent/syscall/structs.go create mode 100644 agent/syscall/syscall_windows.go rename agent/{ => tactical}/rpc/rpc_windows.go (82%) rename agent/{ => tactical}/rpc/structs.go (100%) diff --git a/agent/agent.go b/agent/agent.go index a584b34..843318e 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -29,6 +29,7 @@ import ( rmm "github.com/amidaware/rmmagent/shared" ps "github.com/elastic/go-sysinfo" + gocmd "github.com/go-cmd/cmd" "github.com/go-resty/resty/v2" "github.com/kardianos/service" nats "github.com/nats-io/nats.go" @@ -136,6 +137,114 @@ func New(logger *logrus.Logger, version string) *Agent { } } +type CmdStatus struct { + Status gocmd.Status + Stdout string + Stderr string +} + +type CmdOptions struct { + Shell string + Command string + Args []string + Timeout time.Duration + IsScript bool + IsExecutable bool + Detached bool +} + +func (a *Agent) NewCMDOpts() *CmdOptions { + return &CmdOptions{ + Shell: "/bin/bash", + Timeout: 30, + } +} + +func (a *Agent) CmdV2(c *CmdOptions) CmdStatus { + + ctx, cancel := context.WithTimeout(context.Background(), c.Timeout*time.Second) + defer cancel() + + // Disable output buffering, enable streaming + cmdOptions := gocmd.Options{ + Buffered: false, + Streaming: true, + } + + // have a child process that is in a different process group so that + // parent terminating doesn't kill child + if c.Detached { + cmdOptions.BeforeExec = []func(cmd *exec.Cmd){ + func(cmd *exec.Cmd) { + cmd.SysProcAttr = SetDetached() + }, + } + } + + var envCmd *gocmd.Cmd + if c.IsScript { + envCmd = gocmd.NewCmdOptions(cmdOptions, c.Shell, c.Args...) // call script directly + } else if c.IsExecutable { + envCmd = gocmd.NewCmdOptions(cmdOptions, c.Shell, c.Command) // c.Shell: bin + c.Command: args as one string + } else { + envCmd = gocmd.NewCmdOptions(cmdOptions, c.Shell, "-c", c.Command) // /bin/bash -c 'ls -l /var/log/...' + } + + var stdoutBuf bytes.Buffer + var stderrBuf bytes.Buffer + // Print STDOUT and STDERR lines streaming from Cmd + doneChan := make(chan struct{}) + go func() { + defer close(doneChan) + // Done when both channels have been closed + // https://dave.cheney.net/2013/04/30/curious-channels + for envCmd.Stdout != nil || envCmd.Stderr != nil { + select { + case line, open := <-envCmd.Stdout: + if !open { + envCmd.Stdout = nil + continue + } + fmt.Fprintln(&stdoutBuf, line) + a.Logger.Debugln(line) + + case line, open := <-envCmd.Stderr: + if !open { + envCmd.Stderr = nil + continue + } + fmt.Fprintln(&stderrBuf, line) + a.Logger.Debugln(line) + } + } + }() + + // Run and wait for Cmd to return, discard Status + envCmd.Start() + + go func() { + select { + case <-doneChan: + return + case <-ctx.Done(): + a.Logger.Debugf("Command timed out after %d seconds\n", c.Timeout) + pid := envCmd.Status().PID + a.Logger.Debugln("Killing process with PID", pid) + KillProc(int32(pid)) + } + }() + + // Wait for goroutine to print everything + <-doneChan + ret := CmdStatus{ + Status: envCmd.Status(), + Stdout: CleanString(stdoutBuf.String()), + Stderr: CleanString(stderrBuf.String()), + } + a.Logger.Debugf("%+v\n", ret) + return ret +} + func (a *Agent) GetCPULoadAvg() int { fallback := false pyCode := ` @@ -187,7 +296,7 @@ func (a *Agent) ForceKillMesh() { for _, pid := range pids { a.Logger.Debugln("Killing mesh process with pid %d", pid) - if err := utils.KillProc(int32(pid)); err != nil { + if err := KillProc(int32(pid)); err != nil { a.Logger.Debugln(err) } } @@ -213,8 +322,111 @@ func (a *Agent) SyncMeshNodeID() { } } +func (a *Agent) setupNatsOptions() []nats.Option { + opts := make([]nats.Option, 0) + opts = append(opts, nats.Name("TacticalRMM")) + opts = append(opts, nats.UserInfo(a.AgentID, a.Token)) + opts = append(opts, nats.ReconnectWait(time.Second*5)) + opts = append(opts, nats.RetryOnFailedConnect(true)) + opts = append(opts, nats.MaxReconnects(-1)) + opts = append(opts, nats.ReconnectBufSize(-1)) + return opts +} +func (a *Agent) GetUninstallExe() string { + cderr := os.Chdir(a.ProgramDir) + if cderr == nil { + files, err := filepath.Glob("unins*.exe") + if err == nil { + for _, f := range files { + if strings.Contains(f, "001") { + return f + } + } + } + } + return "unins000.exe" +} +func (a *Agent) CleanupAgentUpdates() { + cderr := os.Chdir(a.ProgramDir) + if cderr != nil { + a.Logger.Errorln(cderr) + return + } + + files, err := filepath.Glob("winagent-v*.exe") + if err == nil { + for _, f := range files { + os.Remove(f) + } + } + + cderr = os.Chdir(os.Getenv("TMP")) + if cderr != nil { + a.Logger.Errorln(cderr) + return + } + folders, err := filepath.Glob("tacticalrmm*") + if err == nil { + for _, f := range folders { + os.RemoveAll(f) + } + } +} + +func (a *Agent) RunPythonCode(code string, timeout int, args []string) (string, error) { + content := []byte(code) + dir, err := ioutil.TempDir("", "tacticalpy") + if err != nil { + a.Logger.Debugln(err) + return "", err + } + defer os.RemoveAll(dir) + + tmpfn, _ := ioutil.TempFile(dir, "*.py") + if _, err := tmpfn.Write(content); err != nil { + a.Logger.Debugln(err) + return "", err + } + if err := tmpfn.Close(); err != nil { + a.Logger.Debugln(err) + return "", err + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + defer cancel() + + var outb, errb bytes.Buffer + cmdArgs := []string{tmpfn.Name()} + if len(args) > 0 { + cmdArgs = append(cmdArgs, args...) + } + a.Logger.Debugln(cmdArgs) + cmd := exec.CommandContext(ctx, a.PyBin, cmdArgs...) + cmd.Stdout = &outb + cmd.Stderr = &errb + + cmdErr := cmd.Run() + + if ctx.Err() == context.DeadlineExceeded { + a.Logger.Debugln("RunPythonCode:", ctx.Err()) + return "", ctx.Err() + } + + if cmdErr != nil { + a.Logger.Debugln("RunPythonCode:", cmdErr) + return "", cmdErr + } + + if errb.String() != "" { + a.Logger.Debugln(errb.String()) + return errb.String(), errors.New("RunPythonCode stderr") + } + + return outb.String(), nil + +} func (a *Agent) CreateTRMMTempDir() { // create the temp dir for running scripts @@ -226,7 +438,3 @@ func (a *Agent) CreateTRMMTempDir() { } } } - -func (a *Agent) GetDisks() []trmm.Disk { - return disk.GetDisks() -} \ No newline at end of file diff --git a/agent/agent_windows.go b/agent/agent_windows.go index 4e9eb01..5d75b2d 100644 --- a/agent/agent_windows.go +++ b/agent/agent_windows.go @@ -42,15 +42,751 @@ import ( "golang.org/x/sys/windows/registry" ) +var ( + getDriveType = windows.NewLazySystemDLL("kernel32.dll").NewProc("GetDriveTypeW") +) +func NewAgentConfig() *rmm.AgentConfig { + k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\TacticalRMM`, registry.ALL_ACCESS) + if err != nil { + return &rmm.AgentConfig{} + } + baseurl, _, _ := k.GetStringValue("BaseURL") + agentid, _, _ := k.GetStringValue("AgentID") + apiurl, _, _ := k.GetStringValue("ApiURL") + token, _, _ := k.GetStringValue("Token") + agentpk, _, _ := k.GetStringValue("AgentPK") + pk, _ := strconv.Atoi(agentpk) + cert, _, _ := k.GetStringValue("Cert") + proxy, _, _ := k.GetStringValue("Proxy") + customMeshDir, _, _ := k.GetStringValue("MeshDir") + return &rmm.AgentConfig{ + BaseURL: baseurl, + AgentID: agentid, + APIURL: apiurl, + Token: token, + AgentPK: agentpk, + PK: pk, + Cert: cert, + Proxy: proxy, + CustomMeshDir: customMeshDir, + } +} +func (a *Agent) RunScript(code string, shell string, args []string, timeout int) (stdout, stderr string, exitcode int, e error) { + content := []byte(code) + dir := filepath.Join(os.TempDir(), "trmm") + if !trmm.FileExists(dir) { + a.CreateTRMMTempDir() + } + const defaultExitCode = 1 + var ( + outb bytes.Buffer + errb bytes.Buffer + exe string + ext string + cmdArgs []string + ) + + switch shell { + case "powershell": + ext = "*.ps1" + case "python": + ext = "*.py" + case "cmd": + ext = "*.bat" + } + + tmpfn, err := ioutil.TempFile(dir, ext) + if err != nil { + a.Logger.Errorln(err) + return "", err.Error(), 85, err + } + defer os.Remove(tmpfn.Name()) + + if _, err := tmpfn.Write(content); err != nil { + a.Logger.Errorln(err) + return "", err.Error(), 85, err + } + if err := tmpfn.Close(); err != nil { + a.Logger.Errorln(err) + return "", err.Error(), 85, err + } + + switch shell { + case "powershell": + exe = "Powershell" + cmdArgs = []string{"-NonInteractive", "-NoProfile", "-ExecutionPolicy", "Bypass", tmpfn.Name()} + case "python": + exe = a.PyBin + cmdArgs = []string{tmpfn.Name()} + case "cmd": + exe = tmpfn.Name() + } + + if len(args) > 0 { + cmdArgs = append(cmdArgs, args...) + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + defer cancel() + + var timedOut bool = false + cmd := exec.Command(exe, cmdArgs...) + cmd.Stdout = &outb + cmd.Stderr = &errb + + if cmdErr := cmd.Start(); cmdErr != nil { + a.Logger.Debugln(cmdErr) + return "", cmdErr.Error(), 65, cmdErr + } + pid := int32(cmd.Process.Pid) + + // custom context handling, we need to kill child procs if this is a batch script, + // otherwise it will hang forever + // the normal exec.CommandContext() doesn't work since it only kills the parent process + go func(p int32) { + + <-ctx.Done() + + _ = KillProc(p) + timedOut = true + }(pid) + + cmdErr := cmd.Wait() + + if timedOut { + stdout = CleanString(outb.String()) + stderr = fmt.Sprintf("%s\nScript timed out after %d seconds", CleanString(errb.String()), timeout) + exitcode = 98 + a.Logger.Debugln("Script check timeout:", ctx.Err()) + } else { + stdout = CleanString(outb.String()) + stderr = CleanString(errb.String()) + + // get the exit code + if cmdErr != nil { + if exitError, ok := cmdErr.(*exec.ExitError); ok { + if ws, ok := exitError.Sys().(syscall.WaitStatus); ok { + exitcode = ws.ExitStatus() + } else { + exitcode = defaultExitCode + } + } else { + exitcode = defaultExitCode + } + + } else { + if ws, ok := cmd.ProcessState.Sys().(syscall.WaitStatus); ok { + exitcode = ws.ExitStatus() + } else { + exitcode = 0 + } + } + } + return stdout, stderr, exitcode, nil +} + +func SetDetached() *syscall.SysProcAttr { + return &syscall.SysProcAttr{ + CreationFlags: windows.DETACHED_PROCESS | windows.CREATE_NEW_PROCESS_GROUP, + } +} + +func CMD(exe string, args []string, timeout int, detached bool) (output [2]string, e error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + defer cancel() + + var outb, errb bytes.Buffer + cmd := exec.CommandContext(ctx, exe, args...) + if detached { + cmd.SysProcAttr = &windows.SysProcAttr{ + CreationFlags: windows.DETACHED_PROCESS | windows.CREATE_NEW_PROCESS_GROUP, + } + } + cmd.Stdout = &outb + cmd.Stderr = &errb + err := cmd.Run() + if err != nil { + return [2]string{"", ""}, fmt.Errorf("%s: %s", err, CleanString(errb.String())) + } + + if ctx.Err() == context.DeadlineExceeded { + return [2]string{"", ""}, ctx.Err() + } + + return [2]string{CleanString(outb.String()), CleanString(errb.String())}, nil +} + +func CMDShell(shell string, cmdArgs []string, command string, timeout int, detached bool) (output [2]string, e error) { + var ( + outb bytes.Buffer + errb bytes.Buffer + cmd *exec.Cmd + timedOut = false + ) + + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + defer cancel() + + if len(cmdArgs) > 0 && command == "" { + switch shell { + case "cmd": + cmdArgs = append([]string{"/C"}, cmdArgs...) + cmd = exec.Command("cmd.exe", cmdArgs...) + case "powershell": + cmdArgs = append([]string{"-NonInteractive", "-NoProfile"}, cmdArgs...) + cmd = exec.Command("powershell.exe", cmdArgs...) + } + } else { + switch shell { + case "cmd": + cmd = exec.Command("cmd.exe") + cmd.SysProcAttr = &windows.SysProcAttr{ + CmdLine: fmt.Sprintf("cmd.exe /C %s", command), + } + case "powershell": + cmd = exec.Command("Powershell", "-NonInteractive", "-NoProfile", command) + } + } + + // https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags + if detached { + cmd.SysProcAttr = &windows.SysProcAttr{ + CreationFlags: windows.DETACHED_PROCESS | windows.CREATE_NEW_PROCESS_GROUP, + } + } + cmd.Stdout = &outb + cmd.Stderr = &errb + cmd.Start() + + pid := int32(cmd.Process.Pid) + + go func(p int32) { + + <-ctx.Done() + + _ = KillProc(p) + timedOut = true + }(pid) + + err := cmd.Wait() + + if timedOut { + return [2]string{CleanString(outb.String()), CleanString(errb.String())}, ctx.Err() + } + + if err != nil { + return [2]string{CleanString(outb.String()), CleanString(errb.String())}, err + } + + return [2]string{CleanString(outb.String()), CleanString(errb.String())}, nil +} + +// GetDisks returns a list of fixed disks +func (a *Agent) GetDisks() []trmm.Disk { + ret := make([]trmm.Disk, 0) + partitions, err := disk.Partitions(false) + if err != nil { + a.Logger.Debugln(err) + return ret + } + + for _, p := range partitions { + typepath, _ := windows.UTF16PtrFromString(p.Device) + typeval, _, _ := getDriveType.Call(uintptr(unsafe.Pointer(typepath))) + // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getdrivetypea + if typeval != 3 { + continue + } + + usage, err := disk.Usage(p.Mountpoint) + if err != nil { + a.Logger.Debugln(err) + continue + } + + d := trmm.Disk{ + Device: p.Device, + Fstype: p.Fstype, + Total: ByteCountSI(usage.Total), + Used: ByteCountSI(usage.Used), + Free: ByteCountSI(usage.Free), + Percent: int(usage.UsedPercent), + } + ret = append(ret, d) + } + return ret +} + +// LoggedOnUser returns the first logged on user it finds +func (a *Agent) LoggedOnUser() string { + pyCode := ` +import psutil + +try: + u = psutil.users()[0].name + if u.isascii(): + print(u, end='') + else: + print('notascii', end='') +except Exception as e: + print("None", end='') + +` + // try with psutil first, if fails, fallback to golang + user, err := a.RunPythonCode(pyCode, 5, []string{}) + if err == nil && user != "notascii" { + return user + } + + users, err := wapf.ListLoggedInUsers() + if err != nil { + a.Logger.Debugln("LoggedOnUser error", err) + return "None" + } + + if len(users) == 0 { + return "None" + } + + for _, u := range users { + // remove the computername or domain + return strings.Split(u.FullUser(), `\`)[1] + } + return "None" +} + +// ShowStatus prints windows service status +// If called from an interactive desktop, pops up a message box +// Otherwise prints to the console +func ShowStatus(version string) { + statusMap := make(map[string]string) + svcs := []string{winSvcName, meshSvcName} + + for _, service := range svcs { + status, err := GetServiceStatus(service) + if err != nil { + statusMap[service] = "Not Installed" + continue + } + statusMap[service] = status + } + + window := w32.GetForegroundWindow() + if window != 0 { + _, consoleProcID := w32.GetWindowThreadProcessId(window) + if w32.GetCurrentProcessId() == consoleProcID { + w32.ShowWindow(window, w32.SW_HIDE) + } + var handle w32.HWND + msg := fmt.Sprintf("Agent: %s\n\nMesh Agent: %s", statusMap[winSvcName], statusMap[meshSvcName]) + w32.MessageBox(handle, msg, fmt.Sprintf("Tactical RMM v%s", version), w32.MB_OK|w32.MB_ICONINFORMATION) + } else { + fmt.Println("Tactical RMM Version", version) + fmt.Println("Tactical Agent:", statusMap[winSvcName]) + fmt.Println("Mesh Agent:", statusMap[meshSvcName]) + } +} + +// PatchMgmnt enables/disables automatic update +// 0 - Enable Automatic Updates (Default) +// 1 - Disable Automatic Updates +// https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd939844(v=ws.10)?redirectedfrom=MSDN +func (a *Agent) PatchMgmnt(enable bool) error { + var val uint32 + k, _, err := registry.CreateKey(registry.LOCAL_MACHINE, `SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU`, registry.ALL_ACCESS) + if err != nil { + return err + } + + if enable { + val = 1 + } else { + val = 0 + } + + err = k.SetDWordValue("AUOptions", val) + if err != nil { + return err + } + + return nil +} + +func (a *Agent) PlatVer() (string, error) { + k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.ALL_ACCESS) + if err != nil { + return "n/a", err + } + defer k.Close() + + dv, _, err := k.GetStringValue("DisplayVersion") + if err == nil { + return dv, nil + } + + relid, _, err := k.GetStringValue("ReleaseId") + if err != nil { + return "n/a", err + } + return relid, nil +} + +// EnablePing enables ping +func EnablePing() { + args := make([]string, 0) + cmd := `netsh advfirewall firewall add rule name="ICMP Allow incoming V4 echo request" protocol=icmpv4:8,any dir=in action=allow` + _, err := CMDShell("cmd", args, cmd, 10, false) + if err != nil { + fmt.Println(err) + } +} + +// EnableRDP enables Remote Desktop +func EnableRDP() { + k, _, err := registry.CreateKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control\Terminal Server`, registry.ALL_ACCESS) + if err != nil { + fmt.Println(err) + } + defer k.Close() + + err = k.SetDWordValue("fDenyTSConnections", 0) + if err != nil { + fmt.Println(err) + } + + args := make([]string, 0) + cmd := `netsh advfirewall firewall set rule group="remote desktop" new enable=Yes` + _, cerr := CMDShell("cmd", args, cmd, 10, false) + if cerr != nil { + fmt.Println(cerr) + } +} + +// DisableSleepHibernate disables sleep and hibernate +func DisableSleepHibernate() { + k, _, err := registry.CreateKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control\Session Manager\Power`, registry.ALL_ACCESS) + if err != nil { + fmt.Println(err) + } + defer k.Close() + + err = k.SetDWordValue("HiberbootEnabled", 0) + if err != nil { + fmt.Println(err) + } + + args := make([]string, 0) + + var wg sync.WaitGroup + currents := []string{"ac", "dc"} + for _, i := range currents { + wg.Add(1) + go func(c string) { + defer wg.Done() + _, _ = CMDShell("cmd", args, fmt.Sprintf("powercfg /set%svalueindex scheme_current sub_buttons lidaction 0", c), 5, false) + _, _ = CMDShell("cmd", args, fmt.Sprintf("powercfg /x -standby-timeout-%s 0", c), 5, false) + _, _ = CMDShell("cmd", args, fmt.Sprintf("powercfg /x -hibernate-timeout-%s 0", c), 5, false) + _, _ = CMDShell("cmd", args, fmt.Sprintf("powercfg /x -disk-timeout-%s 0", c), 5, false) + _, _ = CMDShell("cmd", args, fmt.Sprintf("powercfg /x -monitor-timeout-%s 0", c), 5, false) + }(i) + } + wg.Wait() + _, _ = CMDShell("cmd", args, "powercfg -S SCHEME_CURRENT", 5, false) +} + +// NewCOMObject creates a new COM object for the specifed ProgramID. +func NewCOMObject(id string) (*ole.IDispatch, error) { + unknown, err := oleutil.CreateObject(id) + if err != nil { + return nil, fmt.Errorf("unable to create initial unknown object: %v", err) + } + defer unknown.Release() + + obj, err := unknown.QueryInterface(ole.IID_IDispatch) + if err != nil { + return nil, fmt.Errorf("unable to create query interface: %v", err) + } + + return obj, nil +} + +// SystemRebootRequired checks whether a system reboot is required. +func (a *Agent) SystemRebootRequired() (bool, error) { + regKeys := []string{ + `SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired`, + } + for _, key := range regKeys { + k, err := registry.OpenKey(registry.LOCAL_MACHINE, key, registry.QUERY_VALUE) + if err == nil { + k.Close() + return true, nil + } else if err != registry.ErrNotExist { + return false, err + } + } + return false, nil +} + +func (a *Agent) SendSoftware() { + sw := a.GetInstalledSoftware() + a.Logger.Debugln(sw) + + payload := map[string]interface{}{"agent_id": a.AgentID, "software": sw} + _, err := a.rClient.R().SetBody(payload).Post("/api/v3/software/") + if err != nil { + a.Logger.Debugln(err) + } +} + +func (a *Agent) UninstallCleanup() { + registry.DeleteKey(registry.LOCAL_MACHINE, `SOFTWARE\TacticalRMM`) + a.PatchMgmnt(false) + a.CleanupAgentUpdates() + CleanupSchedTasks() +} + +func (a *Agent) AgentUpdate(url, inno, version string) { + time.Sleep(time.Duration(randRange(1, 15)) * time.Second) + a.KillHungUpdates() + a.CleanupAgentUpdates() + updater := filepath.Join(a.ProgramDir, inno) + a.Logger.Infof("Agent updating from %s to %s", a.Version, version) + a.Logger.Infoln("Downloading agent update from", url) + + rClient := resty.New() + rClient.SetCloseConnection(true) + rClient.SetTimeout(15 * time.Minute) + rClient.SetDebug(a.Debug) + if len(a.Proxy) > 0 { + rClient.SetProxy(a.Proxy) + } + r, err := rClient.R().SetOutput(updater).Get(url) + if err != nil { + a.Logger.Errorln(err) + CMD("net", []string{"start", winSvcName}, 10, false) + return + } + if r.IsError() { + a.Logger.Errorln("Download failed with status code", r.StatusCode()) + CMD("net", []string{"start", winSvcName}, 10, false) + return + } + + dir, err := ioutil.TempDir("", "tacticalrmm") + if err != nil { + a.Logger.Errorln("Agentupdate create tempdir:", err) + CMD("net", []string{"start", winSvcName}, 10, false) + return + } + + innoLogFile := filepath.Join(dir, "tacticalrmm.txt") + + args := []string{"/C", updater, "/VERYSILENT", fmt.Sprintf("/LOG=%s", innoLogFile)} + cmd := exec.Command("cmd.exe", args...) + cmd.SysProcAttr = &windows.SysProcAttr{ + CreationFlags: windows.DETACHED_PROCESS | windows.CREATE_NEW_PROCESS_GROUP, + } + cmd.Start() + time.Sleep(1 * time.Second) +} + +func (a *Agent) osString() string { + host, _ := ps.Host() + info := host.Info() + osInf := info.OS + + var arch string + switch info.Architecture { + case "x86_64": + arch = "64 bit" + case "x86": + arch = "32 bit" + } + + var osFullName string + platver, err := a.PlatVer() + if err != nil { + osFullName = fmt.Sprintf("%s, %s (build %s)", osInf.Name, arch, osInf.Build) + } else { + osFullName = fmt.Sprintf("%s, %s v%s (build %s)", osInf.Name, arch, platver, osInf.Build) + } + return osFullName +} + +func (a *Agent) AgentUninstall(code string) { + a.KillHungUpdates() + tacUninst := filepath.Join(a.ProgramDir, a.GetUninstallExe()) + args := []string{"/C", tacUninst, "/VERYSILENT"} + cmd := exec.Command("cmd.exe", args...) + cmd.SysProcAttr = &windows.SysProcAttr{ + CreationFlags: windows.DETACHED_PROCESS | windows.CREATE_NEW_PROCESS_GROUP, + } + cmd.Start() +} + +func (a *Agent) addDefenderExlusions() { + code := ` +Add-MpPreference -ExclusionPath 'C:\Program Files\TacticalAgent\*' +Add-MpPreference -ExclusionPath 'C:\Windows\Temp\winagent-v*.exe' +Add-MpPreference -ExclusionPath 'C:\Windows\Temp\trmm\*' +Add-MpPreference -ExclusionPath 'C:\Program Files\Mesh Agent\*' +` + _, _, _, err := a.RunScript(code, "powershell", []string{}, 20) + if err != nil { + a.Logger.Debugln(err) + } +} + +// RunMigrations cleans up unused stuff from older agents +func (a *Agent) RunMigrations() { + for _, i := range []string{"nssm.exe", "nssm-x86.exe"} { + nssm := filepath.Join(a.ProgramDir, i) + if trmm.FileExists(nssm) { + os.Remove(nssm) + } + } +} + +func (a *Agent) installMesh(meshbin, exe, proxy string) (string, error) { + var meshNodeID string + meshInstallArgs := []string{"-fullinstall"} + if len(proxy) > 0 { + meshProxy := fmt.Sprintf("--WebProxy=%s", proxy) + meshInstallArgs = append(meshInstallArgs, meshProxy) + } + a.Logger.Debugln("Mesh install args:", meshInstallArgs) + + meshOut, meshErr := CMD(meshbin, meshInstallArgs, int(90), false) + if meshErr != nil { + fmt.Println(meshOut[0]) + fmt.Println(meshOut[1]) + fmt.Println(meshErr) + } + + fmt.Println(meshOut) + a.Logger.Debugln("Sleeping for 5") + time.Sleep(5 * time.Second) + + meshSuccess := false + + for !meshSuccess { + a.Logger.Debugln("Getting mesh node id") + pMesh, pErr := CMD(exe, []string{"-nodeid"}, int(30), false) + if pErr != nil { + a.Logger.Errorln(pErr) + time.Sleep(5 * time.Second) + continue + } + if pMesh[1] != "" { + a.Logger.Errorln(pMesh[1]) + time.Sleep(5 * time.Second) + continue + } + meshNodeID = StripAll(pMesh[0]) + a.Logger.Debugln("Node id:", meshNodeID) + if strings.Contains(strings.ToLower(meshNodeID), "not defined") { + a.Logger.Errorln(meshNodeID) + time.Sleep(5 * time.Second) + continue + } + meshSuccess = true + } + + return meshNodeID, nil +} + +// ChecksRunning prevents duplicate checks from running +// Have to do it this way, can't use atomic because they can run from both rpc and tacticalagent services +func (a *Agent) ChecksRunning() bool { + running := false + procs, err := ps.Processes() + if err != nil { + return running + } + +Out: + for _, process := range procs { + p, err := process.Info() + if err != nil { + continue + } + if p.PID == 0 { + continue + } + if p.Exe != a.EXE { + continue + } + + for _, arg := range p.Args { + if arg == "runchecks" || arg == "checkrunner" { + running = true + break Out + } + } + } + return running +} + +func (a *Agent) GetPython(force bool) { + if trmm.FileExists(a.PyBin) && !force { + return + } + + var archZip string + var folder string + switch runtime.GOARCH { + case "amd64": + archZip = "py38-x64.zip" + folder = "py38-x64" + case "386": + archZip = "py38-x32.zip" + folder = "py38-x32" + } + pyFolder := filepath.Join(a.ProgramDir, folder) + pyZip := filepath.Join(a.ProgramDir, archZip) + a.Logger.Debugln(pyZip) + a.Logger.Debugln(a.PyBin) + defer os.Remove(pyZip) + + if force { + os.RemoveAll(pyFolder) + } + + rClient := resty.New() + rClient.SetTimeout(20 * time.Minute) + rClient.SetRetryCount(10) + rClient.SetRetryWaitTime(1 * time.Minute) + rClient.SetRetryMaxWaitTime(15 * time.Minute) + if len(a.Proxy) > 0 { + rClient.SetProxy(a.Proxy) + } + + url := fmt.Sprintf("https://github.com/amidaware/rmmagent/releases/download/v2.0.0/%s", archZip) + a.Logger.Debugln(url) + r, err := rClient.R().SetOutput(pyZip).Get(url) + if err != nil { + a.Logger.Errorln("Unable to download py3.zip from github.", err) + return + } + if r.IsError() { + a.Logger.Errorln("Unable to download py3.zip from github. Status code", r.StatusCode()) + return + } + + err = Unzip(pyZip, a.ProgramDir) + if err != nil { + a.Logger.Errorln(err) + } +} func (a *Agent) RecoverMesh() { a.Logger.Infoln("Attempting mesh recovery") @@ -61,9 +797,33 @@ func (a *Agent) RecoverMesh() { a.SyncMeshNodeID() } +func (a *Agent) getMeshNodeID() (string, error) { + out, err := CMD(a.MeshSystemBin, []string{"-nodeid"}, 10, false) + if err != nil { + a.Logger.Debugln(err) + return "", err + } + stdout := out[0] + stderr := out[1] + if stderr != "" { + a.Logger.Debugln(stderr) + return "", err + } + if stdout == "" || strings.Contains(strings.ToLower(StripAll(stdout)), "not defined") { + a.Logger.Debugln("Failed getting mesh node id", stdout) + return "", errors.New("failed to get mesh node id") + } + + return stdout, nil +} + +func (a *Agent) Start(_ service.Service) error { + go a.RunRPC() + return nil +} func (a *Agent) Stop(_ service.Service) error { return nil diff --git a/agent/checks.go b/agent/checks.go index 3fb9ed8..faad683 100644 --- a/agent/checks.go +++ b/agent/checks.go @@ -159,7 +159,6 @@ func (a *Agent) RunChecks(force bool) error { type ScriptCheckResult struct { ID int `json:"id"` - AgentID string `json:"agent_id"` Stdout string `json:"stdout"` Stderr string `json:"stderr"` Retcode int `json:"retcode"` @@ -173,7 +172,6 @@ func (a *Agent) ScriptCheck(data rmm.Check, r *resty.Client) { payload := ScriptCheckResult{ ID: data.CheckPK, - AgentID: a.AgentID, Stdout: stdout, Stderr: stderr, Retcode: retcode, @@ -195,7 +193,6 @@ func (a *Agent) SendDiskCheckResult(payload DiskCheckResult, r *resty.Client) { type DiskCheckResult struct { ID int `json:"id"` - AgentID string `json:"agent_id"` MoreInfo string `json:"more_info"` PercentUsed float64 `json:"percent_used"` Exists bool `json:"exists"` @@ -204,7 +201,6 @@ type DiskCheckResult struct { // DiskCheck checks disk usage func (a *Agent) DiskCheck(data rmm.Check) (payload DiskCheckResult) { payload.ID = data.CheckPK - payload.AgentID = a.AgentID usage, err := disk.Usage(data.Disk) if err != nil { @@ -221,14 +217,13 @@ func (a *Agent) DiskCheck(data rmm.Check) (payload DiskCheckResult) { } type CPUMemResult struct { - ID int `json:"id"` - AgentID string `json:"agent_id"` - Percent int `json:"percent"` + ID int `json:"id"` + Percent int `json:"percent"` } // CPULoadCheck checks avg cpu load func (a *Agent) CPULoadCheck(data rmm.Check, r *resty.Client) { - payload := CPUMemResult{ID: data.CheckPK, AgentID: a.AgentID, Percent: a.GetCPULoadAvg()} + payload := CPUMemResult{ID: data.CheckPK, Percent: a.GetCPULoadAvg()} _, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/") if err != nil { a.Logger.Debugln(err) @@ -241,7 +236,7 @@ func (a *Agent) MemCheck(data rmm.Check, r *resty.Client) { mem, _ := host.Memory() percent := (float64(mem.Used) / float64(mem.Total)) * 100 - payload := CPUMemResult{ID: data.CheckPK, AgentID: a.AgentID, Percent: int(math.Round(percent))} + payload := CPUMemResult{ID: data.CheckPK, Percent: int(math.Round(percent))} _, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/") if err != nil { a.Logger.Debugln(err) @@ -249,9 +244,8 @@ func (a *Agent) MemCheck(data rmm.Check, r *resty.Client) { } type EventLogCheckResult struct { - ID int `json:"id"` - AgentID string `json:"agent_id"` - Log []rmm.EventLogMsg `json:"log"` + ID int `json:"id"` + Log []rmm.EventLogMsg `json:"log"` } func (a *Agent) EventLogCheck(data rmm.Check, r *resty.Client) { @@ -305,7 +299,7 @@ func (a *Agent) EventLogCheck(data rmm.Check, r *resty.Client) { } } - payload := EventLogCheckResult{ID: data.CheckPK, AgentID: a.AgentID, Log: log} + payload := EventLogCheckResult{ID: data.CheckPK, Log: log} _, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/") if err != nil { a.Logger.Debugln(err) @@ -321,7 +315,6 @@ func (a *Agent) SendPingCheckResult(payload rmm.PingCheckResponse, r *resty.Clie func (a *Agent) PingCheck(data rmm.Check) (payload rmm.PingCheckResponse) { payload.ID = data.CheckPK - payload.AgentID = a.AgentID out, err := DoPing(data.IP) if err != nil { @@ -338,7 +331,6 @@ func (a *Agent) PingCheck(data rmm.Check) (payload rmm.PingCheckResponse) { type WinSvcCheckResult struct { ID int `json:"id"` - AgentID string `json:"agent_id"` MoreInfo string `json:"more_info"` Status string `json:"status"` } @@ -352,7 +344,6 @@ func (a *Agent) SendWinSvcCheckResult(payload WinSvcCheckResult, r *resty.Client func (a *Agent) WinSvcCheck(data rmm.Check) (payload WinSvcCheckResult) { payload.ID = data.CheckPK - payload.AgentID = a.AgentID status, err := GetServiceStatus(data.ServiceName) if err != nil { diff --git a/agent/events/events_windows.go b/agent/events/events_windows.go new file mode 100644 index 0000000..5205d13 --- /dev/null +++ b/agent/events/events_windows.go @@ -0,0 +1,125 @@ +package events + +import ( + "time" + "unicode/utf16" + "unsafe" + + "github.com/amidaware/rmmagent/agent/syscall" + rmm "github.com/amidaware/rmmagent/shared" + "github.com/gonutz/w32/v2" + "golang.org/x/sys/windows" +) + +func GetEventLog(logName string, searchLastDays int) []rmm.EventLogMsg { + var ( + oldestLog uint32 + nextSize uint32 + readBytes uint32 + ) + buf := []byte{0} + size := uint32(1) + + ret := make([]rmm.EventLogMsg, 0) + startTime := time.Now().Add(time.Duration(-(time.Duration(searchLastDays)) * (24 * time.Hour))) + + h := w32.OpenEventLog("", logName) + defer w32.CloseEventLog(h) + + numRecords, _ := w32.GetNumberOfEventLogRecords(h) + syscall.GetOldestEventLogRecord(h, &oldestLog) + + startNum := numRecords + oldestLog - 1 + uid := 0 + for i := startNum; i >= oldestLog; i-- { + flags := syscall.EVENTLOG_BACKWARDS_READ | syscall.EVENTLOG_SEEK_READ + + err := syscall.ReadEventLog(h, flags, i, &buf[0], size, &readBytes, &nextSize) + if err != nil { + if err != windows.ERROR_INSUFFICIENT_BUFFER { + //a.Logger.Debugln(err) + break + } + buf = make([]byte, nextSize) + size = nextSize + err = syscall.ReadEventLog(h, flags, i, &buf[0], size, &readBytes, &nextSize) + if err != nil { + //a.Logger.Debugln(err) + break + } + + } + + r := *(*syscall.EVENTLOGRECORD)(unsafe.Pointer(&buf[0])) + + timeWritten := time.Unix(int64(r.TimeWritten), 0) + if searchLastDays != 0 { + if timeWritten.Before(startTime) { + break + } + } + + eventID := r.EventID & 0x0000FFFF + sourceName, _ := bytesToString(buf[unsafe.Sizeof(syscall.EVENTLOGRECORD{}):]) + eventType := getEventType(r.EventType) + + off := uint32(0) + args := make([]*byte, uintptr(r.NumStrings)*unsafe.Sizeof((*uint16)(nil))) + for n := 0; n < int(r.NumStrings); n++ { + args[n] = &buf[r.StringOffset+off] + _, boff := bytesToString(buf[r.StringOffset+off:]) + off += boff + 2 + } + + var argsptr uintptr + if r.NumStrings > 0 { + argsptr = uintptr(unsafe.Pointer(&args[0])) + } + message, _ := syscall.GetResourceMessage(logName, sourceName, r.EventID, argsptr) + + uid++ + eventLogMsg := rmm.EventLogMsg{ + Source: sourceName, + EventType: eventType, + EventID: eventID, + Message: message, + Time: timeWritten.String(), + UID: uid, + } + ret = append(ret, eventLogMsg) + } + return ret +} + +// https://github.com/mackerelio/go-check-plugins/blob/ad7910fdc45ccb892b5e5fda65ba0956c2b2885d/check-windows-eventlog/lib/check-windows-eventlog.go#L219 +func bytesToString(b []byte) (string, uint32) { + var i int + s := make([]uint16, len(b)/2) + for i = range s { + s[i] = uint16(b[i*2]) + uint16(b[(i*2)+1])<<8 + if s[i] == 0 { + s = s[0:i] + break + } + } + return string(utf16.Decode(s)), uint32(i * 2) +} + +func getEventType(et uint16) string { + switch et { + case windows.EVENTLOG_INFORMATION_TYPE: + return "INFO" + case windows.EVENTLOG_WARNING_TYPE: + return "WARNING" + case windows.EVENTLOG_ERROR_TYPE: + return "ERROR" + case windows.EVENTLOG_SUCCESS: + return "SUCCESS" + case windows.EVENTLOG_AUDIT_SUCCESS: + return "AUDIT_SUCCESS" + case windows.EVENTLOG_AUDIT_FAILURE: + return "AUDIT_FAILURE" + default: + return "Unknown" + } +} diff --git a/agent/install.go b/agent/install.go index d13c6f9..af3654e 100644 --- a/agent/install.go +++ b/agent/install.go @@ -251,7 +251,6 @@ func (a *Agent) Install(i *Installer) { time.Sleep(1 * time.Second) a.Logger.Infoln("Starting service...") out := a.ControlService(winSvcName, "start") - if !out.Success { a.installerMsg(out.ErrorMsg, "error", i.Silent) } diff --git a/agent/services_windows.go b/agent/services_windows.go index 5f77a7a..75624f5 100644 --- a/agent/services_windows.go +++ b/agent/services_windows.go @@ -20,7 +20,26 @@ import ( "golang.org/x/sys/windows/svc/mgr" ) +func GetServiceStatus(name string) (string, error) { + conn, err := mgr.Connect() + if err != nil { + return "n/a", err + } + defer conn.Disconnect() + srv, err := conn.OpenService(name) + if err != nil { + return "n/a", err + } + defer srv.Close() + + q, err := srv.Query() + if err != nil { + return "n/a", err + } + + return serviceStatusText(uint32(q.State)), nil +} func (a *Agent) ControlService(name, action string) rmm.WinSvcResp { conn, err := mgr.Connect() @@ -247,7 +266,27 @@ func serviceExists(name string) bool { return true } - +// https://docs.microsoft.com/en-us/dotnet/api/system.serviceprocess.servicecontrollerstatus?view=dotnet-plat-ext-3.1 +func serviceStatusText(num uint32) string { + switch num { + case 1: + return "stopped" + case 2: + return "start_pending" + case 3: + return "stop_pending" + case 4: + return "running" + case 5: + return "continue_pending" + case 6: + return "pause_pending" + case 7: + return "paused" + default: + return "unknown" + } +} // https://docs.microsoft.com/en-us/dotnet/api/system.serviceprocess.servicestartmode?view=dotnet-plat-ext-3.1 func serviceStartType(num uint32) string { diff --git a/agent/structs.go b/agent/structs.go index 03f181e..55644a9 100644 --- a/agent/structs.go +++ b/agent/structs.go @@ -36,4 +36,4 @@ type Agent struct { Platform string GoArch string ServiceConfig *service.Config -} \ No newline at end of file +} diff --git a/agent/syscall/structs.go b/agent/syscall/structs.go new file mode 100644 index 0000000..fa367f2 --- /dev/null +++ b/agent/syscall/structs.go @@ -0,0 +1,20 @@ +package syscall + +type EVENTLOGRECORD struct { + Length uint32 + Reserved uint32 + RecordNumber uint32 + TimeGenerated uint32 + TimeWritten uint32 + EventID uint32 + EventType uint16 + NumStrings uint16 + EventCategory uint16 + ReservedFlags uint16 + ClosingRecordNumber uint32 + StringOffset uint32 + UserSidLength uint32 + UserSidOffset uint32 + DataLength uint32 + DataOffset uint32 +} diff --git a/agent/syscall/syscall_windows.go b/agent/syscall/syscall_windows.go new file mode 100644 index 0000000..3b918ab --- /dev/null +++ b/agent/syscall/syscall_windows.go @@ -0,0 +1,146 @@ +package syscall + +import ( + "fmt" + "strings" + "syscall" + "unsafe" + + "github.com/amidaware/rmmagent/agent/utils" + "github.com/gonutz/w32/v2" + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/registry" +) + +type ReadFlag uint32 + +const ( + EVENTLOG_SEQUENTIAL_READ ReadFlag = 1 << iota + EVENTLOG_SEEK_READ + EVENTLOG_FORWARDS_READ + EVENTLOG_BACKWARDS_READ +) + +const ( + DONT_RESOLVE_DLL_REFERENCES uint32 = 0x0001 + LOAD_LIBRARY_AS_DATAFILE uint32 = 0x0002 +) + +var ( + modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") + modkernel32 = windows.NewLazySystemDLL("kernel32.dll") + + procFormatMessageW = modkernel32.NewProc("FormatMessageW") + procGetOldestEventLogRecord = modadvapi32.NewProc("GetOldestEventLogRecord") + procLoadLibraryExW = modkernel32.NewProc("LoadLibraryExW") + procReadEventLogW = modadvapi32.NewProc("ReadEventLogW") +) + +func GetOldestEventLogRecord(eventLog w32.HANDLE, oldestRecord *uint32) (err error) { + r1, _, e1 := syscall.Syscall(procGetOldestEventLogRecord.Addr(), 2, uintptr(eventLog), uintptr(unsafe.Pointer(oldestRecord)), 0) + if r1 == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func ReadEventLog(eventLog w32.HANDLE, readFlags ReadFlag, recordOffset uint32, buffer *byte, numberOfBytesToRead uint32, bytesRead *uint32, minNumberOfBytesNeeded *uint32) (err error) { + r1, _, e1 := syscall.Syscall9(procReadEventLogW.Addr(), 7, uintptr(eventLog), uintptr(readFlags), uintptr(recordOffset), uintptr(unsafe.Pointer(buffer)), uintptr(numberOfBytesToRead), uintptr(unsafe.Pointer(bytesRead)), uintptr(unsafe.Pointer(minNumberOfBytesNeeded)), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func LoadLibraryEx(filename *uint16, file syscall.Handle, flags uint32) (handle syscall.Handle, err error) { + r0, _, e1 := syscall.Syscall(procLoadLibraryExW.Addr(), 3, uintptr(unsafe.Pointer(filename)), uintptr(file), uintptr(flags)) + handle = syscall.Handle(r0) + if handle == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func FormatMessage(flags uint32, source syscall.Handle, messageID uint32, languageID uint32, buffer *byte, bufferSize uint32, arguments uintptr) (numChars uint32, err error) { + r0, _, e1 := syscall.Syscall9(procFormatMessageW.Addr(), 7, uintptr(flags), uintptr(source), uintptr(messageID), uintptr(languageID), uintptr(unsafe.Pointer(buffer)), uintptr(bufferSize), uintptr(arguments), 0, 0) + numChars = uint32(r0) + if numChars == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + + return +} + +// https://github.com/mackerelio/go-check-plugins/blob/ad7910fdc45ccb892b5e5fda65ba0956c2b2885d/check-windows-eventlog/lib/check-windows-eventlog.go#L232 +func GetResourceMessage(providerName, sourceName string, eventID uint32, argsptr uintptr) (string, error) { + regkey := fmt.Sprintf( + "SYSTEM\\CurrentControlSet\\Services\\EventLog\\%s\\%s", + providerName, sourceName) + key, err := registry.OpenKey(registry.LOCAL_MACHINE, regkey, registry.QUERY_VALUE) + if err != nil { + return "", err + } + + defer key.Close() + val, _, err := key.GetStringValue("EventMessageFile") + if err != nil { + return "", err + } + + val, err = registry.ExpandString(val) + if err != nil { + return "", err + } + + handle, err := LoadLibraryEx(syscall.StringToUTF16Ptr(val), 0, + DONT_RESOLVE_DLL_REFERENCES|LOAD_LIBRARY_AS_DATAFILE) + if err != nil { + return "", err + } + + defer syscall.CloseHandle(handle) + + msgbuf := make([]byte, 1<<16) + numChars, err := FormatMessage( + syscall.FORMAT_MESSAGE_FROM_SYSTEM| + syscall.FORMAT_MESSAGE_FROM_HMODULE| + syscall.FORMAT_MESSAGE_ARGUMENT_ARRAY, + handle, + eventID, + 0, + &msgbuf[0], + uint32(len(msgbuf)), + argsptr) + if err != nil { + return "", err + } + + message, _ := utils.BytesToString(msgbuf[:numChars*2]) + message = strings.Replace(message, "\r", "", -1) + message = strings.TrimSuffix(message, "\n") + return message, nil +} + +func StringToUTF16Ptr(val string) *uint16 { + return syscall.StringToUTF16Ptr(val) +} + +func CloseHandle(handle syscall.Handle) { + defer syscall.CloseHandle(handle) +} diff --git a/agent/system/system_windows.go b/agent/system/system_windows.go index f26da1d..a50b371 100644 --- a/agent/system/system_windows.go +++ b/agent/system/system_windows.go @@ -15,7 +15,6 @@ import ( "time" "github.com/amidaware/rmmagent/agent/utils" - "github.com/amidaware/taskmaster" ps "github.com/elastic/go-sysinfo" "github.com/go-ole/go-ole" "github.com/go-ole/go-ole/oleutil" @@ -433,28 +432,6 @@ func SystemRebootRequired() (bool, error) { return false, nil } -// CleanupSchedTasks removes all tacticalrmm sched tasks during uninstall -func CleanupSchedTasks() { - conn, err := taskmaster.Connect() - if err != nil { - return - } - defer conn.Disconnect() - - tasks, err := conn.GetRegisteredTasks() - if err != nil { - return - } - - for _, task := range tasks { - if strings.HasPrefix(task.Name, "TacticalRMM_") { - conn.DeleteTask(fmt.Sprintf("\\%s", task.Name)) - } - } - - tasks.Release() -} - func KillHungUpdates() { procs, err := ps.Processes() if err != nil { diff --git a/agent/rpc/rpc_windows.go b/agent/tactical/rpc/rpc_windows.go similarity index 82% rename from agent/rpc/rpc_windows.go rename to agent/tactical/rpc/rpc_windows.go index 1d9c9ff..5e85b07 100644 --- a/agent/rpc/rpc_windows.go +++ b/agent/tactical/rpc/rpc_windows.go @@ -9,21 +9,25 @@ import ( "sync/atomic" "time" + "github.com/amidaware/rmmagent/agent/events" + "github.com/amidaware/rmmagent/agent/patching" + "github.com/amidaware/rmmagent/agent/tactical/service" + "github.com/amidaware/rmmagent/agent/tasks" rmm "github.com/amidaware/rmmagent/shared" nats "github.com/nats-io/nats.go" "github.com/ugorji/go/codec" ) -func RunRPC(version string) { +func RunRPC(a *rmm.AgentConfig) { //a.Logger.Infoln("Agent service started") go service.RunAsService() var wg sync.WaitGroup wg.Add(1) - opts := a.setupNatsOptions() - server := fmt.Sprintf("tls://%s:4222", a.ApiURL) + opts := service.SetupNatsOptions() + server := fmt.Sprintf("tls://%s:4222", a.APIURL) nc, err := nats.Connect(server, opts...) if err != nil { - a.Logger.Fatalln("RunRPC() nats.Connect()", err) + //a.Logger.Fatalln("RunRPC() nats.Connect()", err) } nc.Subscribe(a.AgentID, func(msg *nats.Msg) { @@ -33,7 +37,7 @@ func RunRPC(version string) { dec := codec.NewDecoderBytes(msg.Data, &mh) if err := dec.Decode(&payload); err != nil { - a.Logger.Errorln(err) + //a.Logger.Errorln(err) return } @@ -42,7 +46,7 @@ func RunRPC(version string) { go func() { var resp []byte ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) - a.Logger.Debugln("pong") + //a.Logger.Debugln("pong") ret.Encode("pong") msg.Respond(resp) }() @@ -51,9 +55,9 @@ func RunRPC(version string) { go func(p *NatsMsg) { var resp []byte ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) - err := a.PatchMgmnt(p.PatchMgmt) + err := patching.PatchMgmnt(p.PatchMgmt) if err != nil { - a.Logger.Errorln("PatchMgmnt:", err.Error()) + //a.Logger.Errorln("PatchMgmnt:", err.Error()) ret.Encode(err.Error()) } else { ret.Encode("ok") @@ -65,9 +69,9 @@ func RunRPC(version string) { go func(p *NatsMsg) { var resp []byte ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) - success, err := a.CreateSchedTask(p.ScheduledTask) + success, err := tasks.CreateSchedTask(p.ScheduledTask) if err != nil { - a.Logger.Errorln(err.Error()) + //a.Logger.Errorln(err.Error()) ret.Encode(err.Error()) } else if !success { ret.Encode("Something went wrong") @@ -81,9 +85,9 @@ func RunRPC(version string) { go func(p *NatsMsg) { var resp []byte ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) - err := DeleteSchedTask(p.ScheduledTask.Name) + err := tasks.DeleteSchedTask(p.ScheduledTask.Name) if err != nil { - a.Logger.Errorln(err.Error()) + //a.Logger.Errorln(err.Error()) ret.Encode(err.Error()) } else { ret.Encode("ok") @@ -95,8 +99,8 @@ func RunRPC(version string) { go func() { var resp []byte ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) - tasks := ListSchedTasks() - a.Logger.Debugln(tasks) + tasks := tasks.ListSchedTasks() + //a.Logger.Debugln(tasks) ret.Encode(tasks) msg.Respond(resp) }() @@ -106,8 +110,8 @@ func RunRPC(version string) { var resp []byte ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) days, _ := strconv.Atoi(p.Data["days"]) - evtLog := a.GetEventLog(p.Data["logname"], days) - a.Logger.Debugln(evtLog) + evtLog := events.GetEventLog(p.Data["logname"], days) + //a.Logger.Debugln(evtLog) ret.Encode(evtLog) msg.Respond(resp) }(payload) @@ -117,7 +121,7 @@ func RunRPC(version string) { var resp []byte ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) procs := a.GetProcsRPC() - a.Logger.Debugln(procs) + //a.Logger.Debugln(procs) ret.Encode(procs) msg.Respond(resp) }() @@ -129,7 +133,7 @@ func RunRPC(version string) { err := KillProc(p.ProcPID) if err != nil { ret.Encode(err.Error()) - a.Logger.Debugln(err.Error()) + //a.Logger.Debugln(err.Error()) } else { ret.Encode("ok") } @@ -145,7 +149,7 @@ func RunRPC(version string) { switch runtime.GOOS { case "windows": out, _ := CMDShell(p.Data["shell"], []string{}, p.Data["command"], p.Timeout, false) - a.Logger.Debugln(out) + //a.Logger.Debugln(out) if out[1] != "" { ret.Encode(out[1]) resultData.Results = out[1] @@ -182,7 +186,7 @@ func RunRPC(version string) { var resp []byte ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) svcs := a.GetServices() - a.Logger.Debugln(svcs) + //a.Logger.Debugln(svcs) ret.Encode(svcs) msg.Respond(resp) }() @@ -192,7 +196,7 @@ func RunRPC(version string) { var resp []byte ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) svc := a.GetServiceDetail(p.Data["name"]) - a.Logger.Debugln(svc) + //a.Logger.Debugln(svc) ret.Encode(svc) msg.Respond(resp) }(payload) @@ -202,7 +206,7 @@ func RunRPC(version string) { var resp []byte ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) retData := a.ControlService(p.Data["name"], p.Data["action"]) - a.Logger.Debugln(retData) + //a.Logger.Debugln(retData) ret.Encode(retData) msg.Respond(resp) }(payload) @@ -212,7 +216,7 @@ func RunRPC(version string) { var resp []byte ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) retData := a.EditService(p.Data["name"], p.Data["startType"]) - a.Logger.Debugln(retData) + //a.Logger.Debugln(retData) ret.Encode(retData) msg.Respond(resp) }(payload) @@ -229,7 +233,7 @@ func RunRPC(version string) { resultData.ID = p.ID if err != nil { - a.Logger.Debugln(err) + //a.Logger.Debugln(err) retData = err.Error() resultData.Retcode = 1 resultData.Stderr = err.Error() @@ -239,7 +243,7 @@ func RunRPC(version string) { resultData.Stdout = stdout resultData.Stderr = stderr } - a.Logger.Debugln(retData) + //a.Logger.Debugln(retData) ret.Encode(retData) msg.Respond(resp) if p.ID != 0 { @@ -266,7 +270,7 @@ func RunRPC(version string) { retData.Retcode = retcode } retData.ID = p.ID - a.Logger.Debugln(retData) + //a.Logger.Debugln(retData) ret.Encode(retData) msg.Respond(resp) if p.ID != 0 { @@ -282,7 +286,7 @@ func RunRPC(version string) { switch p.Data["mode"] { case "mesh": - a.Logger.Debugln("Recovering mesh") + //a.Logger.Debugln("Recovering mesh") a.RecoverMesh() } @@ -294,14 +298,14 @@ func RunRPC(version string) { var resp []byte ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) sw := a.GetInstalledSoftware() - a.Logger.Debugln(sw) + //a.Logger.Debugln(sw) ret.Encode(sw) msg.Respond(resp) }() case "rebootnow": go func() { - a.Logger.Debugln("Scheduling immediate reboot") + //a.Logger.Debugln("Scheduling immediate reboot") var resp []byte ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) ret.Encode("ok") @@ -316,15 +320,15 @@ func RunRPC(version string) { }() case "needsreboot": go func() { - a.Logger.Debugln("Checking if reboot needed") + //a.Logger.Debugln("Checking if reboot needed") var resp []byte ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) out, err := a.SystemRebootRequired() if err == nil { - a.Logger.Debugln("Reboot needed:", out) + //a.Logger.Debugln("Reboot needed:", out) ret.Encode(out) } else { - a.Logger.Debugln("Error checking if reboot needed:", err) + //a.Logger.Debugln("Error checking if reboot needed:", err) ret.Encode(false) } msg.Respond(resp) @@ -333,7 +337,7 @@ func RunRPC(version string) { go func() { var resp []byte ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) - a.Logger.Debugln("Getting sysinfo with WMI") + //a.Logger.Debugln("Getting sysinfo with WMI") modes := []string{"agent-agentinfo", "agent-disks", "agent-wmi", "agent-publicip"} for _, m := range modes { a.NatsMessage(nc, m) @@ -343,16 +347,16 @@ func RunRPC(version string) { }() case "wmi": go func() { - a.Logger.Debugln("Sending WMI") + //a.Logger.Debugln("Sending WMI") a.NatsMessage(nc, "agent-wmi") }() case "cpuloadavg": go func() { var resp []byte ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) - a.Logger.Debugln("Getting CPU Load Avg") + //a.Logger.Debugln("Getting CPU Load Avg") loadAvg := a.GetCPULoadAvg() - a.Logger.Debugln("CPU Load Avg:", loadAvg) + //a.Logger.Debugln("CPU Load Avg:", loadAvg) ret.Encode(loadAvg) msg.Respond(resp) }() @@ -364,27 +368,27 @@ func RunRPC(version string) { if a.ChecksRunning() { ret.Encode("busy") msg.Respond(resp) - a.Logger.Debugln("Checks are already running, please wait") + //a.Logger.Debugln("Checks are already running, please wait") } else { ret.Encode("ok") msg.Respond(resp) - a.Logger.Debugln("Running checks") + //a.Logger.Debugln("Running checks") _, checkerr := CMD(a.EXE, []string{"-m", "runchecks"}, 600, false) if checkerr != nil { - a.Logger.Errorln("RPC RunChecks", checkerr) + //a.Logger.Errorln("RPC RunChecks", checkerr) } } } else { ret.Encode("ok") msg.Respond(resp) - a.Logger.Debugln("Running checks") + //a.Logger.Debugln("Running checks") a.RunChecks(true) } }() case "runtask": go func(p *NatsMsg) { - a.Logger.Debugln("Running task") + //a.Logger.Debugln("Running task") a.RunTask(p.TaskPK) }(payload) @@ -413,9 +417,9 @@ func RunRPC(version string) { case "getwinupdates": go func() { if !atomic.CompareAndSwapUint32(&getWinUpdateLocker, 0, 1) { - a.Logger.Debugln("Already checking for windows updates") + //a.Logger.Debugln("Already checking for windows updates") } else { - a.Logger.Debugln("Checking for windows updates") + //a.Logger.Debugln("Checking for windows updates") defer atomic.StoreUint32(&getWinUpdateLocker, 0) a.GetWinUpdates() } @@ -423,9 +427,9 @@ func RunRPC(version string) { case "installwinupdates": go func(p *NatsMsg) { if !atomic.CompareAndSwapUint32(&installWinUpdateLocker, 0, 1) { - a.Logger.Debugln("Already installing windows updates") + //a.Logger.Debugln("Already installing windows updates") } else { - a.Logger.Debugln("Installing windows updates", p.UpdateGUIDs) + //a.Logger.Debugln("Installing windows updates", p.UpdateGUIDs) defer atomic.StoreUint32(&installWinUpdateLocker, 0) a.InstallUpdates(p.UpdateGUIDs) } @@ -435,7 +439,7 @@ func RunRPC(version string) { var resp []byte ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) if !atomic.CompareAndSwapUint32(&agentUpdateLocker, 0, 1) { - a.Logger.Debugln("Agent update already running") + //a.Logger.Debugln("Agent update already running") ret.Encode("updaterunning") msg.Respond(resp) } else { @@ -465,7 +469,7 @@ func RunRPC(version string) { nc.Flush() if err := nc.LastError(); err != nil { - a.Logger.Errorln(err) + //a.Logger.Errorln(err) os.Exit(1) } diff --git a/agent/rpc/structs.go b/agent/tactical/rpc/structs.go similarity index 100% rename from agent/rpc/structs.go rename to agent/tactical/rpc/structs.go diff --git a/agent/tactical/service/service.go b/agent/tactical/service/service.go index 0304827..5cdbd6c 100644 --- a/agent/tactical/service/service.go +++ b/agent/tactical/service/service.go @@ -9,7 +9,6 @@ import ( "github.com/amidaware/rmmagent/agent/disk" "github.com/amidaware/rmmagent/agent/services" "github.com/amidaware/rmmagent/agent/system" - "github.com/amidaware/rmmagent/agent/tactical" "github.com/amidaware/rmmagent/agent/tactical/checks" "github.com/amidaware/rmmagent/agent/utils" "github.com/amidaware/rmmagent/agent/wmi" diff --git a/agent/tactical/tactical.go b/agent/tactical/tactical.go index 4e18ffc..547a94f 100644 --- a/agent/tactical/tactical.go +++ b/agent/tactical/tactical.go @@ -15,7 +15,7 @@ func PostRequest(url string, body interface{}, timeout time.Duration) (resty.Res client.SetTimeout(timeout * time.Second) client.SetCloseConnection(true) if len(agentConfig.Proxy) > 0 { - + client.SetProxy(agentConfig.Proxy) } if shared.DEBUG { diff --git a/agent/tactical/tactical_windows.go b/agent/tactical/tactical_windows.go index 5c55436..38db38a 100644 --- a/agent/tactical/tactical_windows.go +++ b/agent/tactical/tactical_windows.go @@ -16,7 +16,10 @@ import ( "github.com/amidaware/rmmagent/agent/services" "github.com/amidaware/rmmagent/agent/software" "github.com/amidaware/rmmagent/agent/system" + "github.com/amidaware/rmmagent/agent/tactical/rpc" + "github.com/amidaware/rmmagent/agent/tasks" "github.com/amidaware/rmmagent/agent/utils" + rmm "github.com/amidaware/rmmagent/shared" "github.com/go-resty/resty/v2" "github.com/kardianos/service" trmm "github.com/wh1te909/trmm-shared" @@ -109,7 +112,7 @@ func UninstallCleanup() { registry.DeleteKey(registry.LOCAL_MACHINE, `SOFTWARE\TacticalRMM`) patching.PatchMgmnt(false) CleanupAgentUpdates() - system.CleanupSchedTasks() + tasks.CleanupSchedTasks() } func AgentUpdate(url, inno, version string) { @@ -278,7 +281,7 @@ func installMesh(meshbin, exe, proxy string) (string, error) { } func Start(_ service.Service) error { - go rpc.RunRPC() + go rpc.RunRPC(NewAgentConfig()) return nil } @@ -333,4 +336,4 @@ func GetPython(force bool) { if err != nil { //a.Logger.Errorln(err) } -} \ No newline at end of file +} diff --git a/agent/tasks/tasks_windows.go b/agent/tasks/tasks_windows.go index 5ba87cf..d5960cb 100644 --- a/agent/tasks/tasks_windows.go +++ b/agent/tasks/tasks_windows.go @@ -1,2 +1,205 @@ package tasks +import ( + "fmt" + "os" + "path/filepath" + "strings" + "time" + + "github.com/amidaware/taskmaster" +) + +func CreateSchedTask(st SchedTask) (bool, error) { + //a.Logger.Debugf("%+v\n", st) + conn, err := taskmaster.Connect() + if err != nil { + //a.Logger.Errorln(err) + return false, err + } + defer conn.Disconnect() + + var trigger taskmaster.Trigger + var action taskmaster.ExecAction + var tasktrigger taskmaster.TaskTrigger + + var now = time.Now() + if st.Trigger == "manual" { + tasktrigger = taskmaster.TaskTrigger{ + Enabled: st.Enabled, + StartBoundary: now, + } + } else { + tasktrigger = taskmaster.TaskTrigger{ + Enabled: st.Enabled, + StartBoundary: time.Date(st.StartYear, st.StartMonth, st.StartDay, st.StartHour, st.StartMinute, 0, 0, now.Location()), + RepetitionPattern: taskmaster.RepetitionPattern{ + RepetitionInterval: st.RepetitionInterval, + RepetitionDuration: st.RepetitionDuration, + StopAtDurationEnd: st.StopAtDurationEnd, + }, + } + } + + if st.ExpireMinute != 0 { + tasktrigger.EndBoundary = time.Date(st.ExpireYear, st.ExpireMonth, st.ExpireDay, st.ExpireHour, st.ExpireMinute, 0, 0, now.Location()) + } + + var path, workdir, args string + def := conn.NewTaskDefinition() + + switch st.Trigger { + case "runonce": + trigger = taskmaster.TimeTrigger{ + TaskTrigger: tasktrigger, + RandomDelay: st.RandomDelay, + } + + case "daily": + trigger = taskmaster.DailyTrigger{ + TaskTrigger: tasktrigger, + DayInterval: st.DayInterval, + RandomDelay: st.RandomDelay, + } + + case "weekly": + trigger = taskmaster.WeeklyTrigger{ + TaskTrigger: tasktrigger, + DaysOfWeek: st.DaysOfWeek, + WeekInterval: st.WeekInterval, + RandomDelay: st.RandomDelay, + } + + case "monthly": + trigger = taskmaster.MonthlyTrigger{ + TaskTrigger: tasktrigger, + DaysOfMonth: st.DaysOfMonth, + MonthsOfYear: st.MonthsOfYear, + RandomDelay: st.RandomDelay, + RunOnLastDayOfMonth: st.RunOnLastDayOfMonth, + } + + case "monthlydow": + trigger = taskmaster.MonthlyDOWTrigger{ + TaskTrigger: tasktrigger, + DaysOfWeek: st.DaysOfWeek, + MonthsOfYear: st.MonthsOfYear, + WeeksOfMonth: st.WeeksOfMonth, + RandomDelay: st.RandomDelay, + } + + case "manual": + trigger = taskmaster.TimeTrigger{ + TaskTrigger: tasktrigger, + } + } + + def.AddTrigger(trigger) + + switch st.Type { + case "rmm": + path = winExeName + workdir = a.ProgramDir + args = fmt.Sprintf("-m taskrunner -p %d", st.PK) + case "schedreboot": + path = "shutdown.exe" + workdir = filepath.Join(os.Getenv("SYSTEMROOT"), "System32") + args = "/r /t 5 /f" + case "custom": + path = st.Path + workdir = st.WorkDir + args = st.Args + } + + action = taskmaster.ExecAction{ + Path: path, + WorkingDir: workdir, + Args: args, + } + def.AddAction(action) + + def.Principal.RunLevel = taskmaster.TASK_RUNLEVEL_HIGHEST + def.Principal.LogonType = taskmaster.TASK_LOGON_SERVICE_ACCOUNT + def.Principal.UserID = "SYSTEM" + def.Settings.AllowDemandStart = true + def.Settings.AllowHardTerminate = true + def.Settings.DontStartOnBatteries = false + def.Settings.Enabled = st.Enabled + def.Settings.StopIfGoingOnBatteries = false + def.Settings.WakeToRun = true + def.Settings.MultipleInstances = st.TaskPolicy + + if st.DeleteAfter { + def.Settings.DeleteExpiredTaskAfter = "PT15M" + } + + if st.RunASAPAfterMissed { + def.Settings.StartWhenAvailable = true + } + + _, success, err := conn.CreateTask(fmt.Sprintf("\\%s", st.Name), def, st.Overwrite) + if err != nil { + //a.Logger.Errorln(err) + return false, err + } + + return success, nil +} + +func DeleteSchedTask(name string) error { + conn, err := taskmaster.Connect() + if err != nil { + return err + } + + defer conn.Disconnect() + err = conn.DeleteTask(fmt.Sprintf("\\%s", name)) + if err != nil { + return err + } + + return nil +} + +func ListSchedTasks() []string { + ret := make([]string, 0) + conn, err := taskmaster.Connect() + if err != nil { + return ret + } + + defer conn.Disconnect() + tasks, err := conn.GetRegisteredTasks() + if err != nil { + return ret + } + + for _, task := range tasks { + ret = append(ret, task.Name) + } + + tasks.Release() + return ret +} + +// CleanupSchedTasks removes all tacticalrmm sched tasks during uninstall +func CleanupSchedTasks() { + conn, err := taskmaster.Connect() + if err != nil { + return + } + defer conn.Disconnect() + + tasks, err := conn.GetRegisteredTasks() + if err != nil { + return + } + + for _, task := range tasks { + if strings.HasPrefix(task.Name, "TacticalRMM_") { + conn.DeleteTask(fmt.Sprintf("\\%s", task.Name)) + } + } + + tasks.Release() +} diff --git a/agent/tasks_windows.go b/agent/tasks_windows.go index b0fe77b..dabf1a3 100644 --- a/agent/tasks_windows.go +++ b/agent/tasks_windows.go @@ -311,7 +311,26 @@ func DeleteSchedTask(name string) error { return nil } +// CleanupSchedTasks removes all tacticalrmm sched tasks during uninstall +func CleanupSchedTasks() { + conn, err := taskmaster.Connect() + if err != nil { + return + } + defer conn.Disconnect() + tasks, err := conn.GetRegisteredTasks() + if err != nil { + return + } + + for _, task := range tasks { + if strings.HasPrefix(task.Name, "TacticalRMM_") { + conn.DeleteTask(fmt.Sprintf("\\%s", task.Name)) + } + } + tasks.Release() +} func ListSchedTasks() []string { ret := make([]string, 0) diff --git a/agent/utils.go b/agent/utils.go index b95ecc2..6d9a851 100644 --- a/agent/utils.go +++ b/agent/utils.go @@ -172,6 +172,36 @@ func IsValidIP(ip string) bool { return net.ParseIP(ip) != nil } +// StripAll strips all whitespace and newline chars +func StripAll(s string) string { + s = strings.TrimSpace(s) + s = strings.Trim(s, "\n") + s = strings.Trim(s, "\r") + return s +} + +// KillProc kills a process and its children +func KillProc(pid int32) error { + p, err := process.NewProcess(pid) + if err != nil { + return err + } + + children, err := p.Children() + if err == nil { + for _, child := range children { + if err := child.Kill(); err != nil { + continue + } + } + } + + if err := p.Kill(); err != nil { + return err + } + return nil +} + // DjangoStringResp removes double quotes from django rest api resp func DjangoStringResp(resp string) string { return strings.Trim(resp, `"`) @@ -186,6 +216,13 @@ func TestTCP(addr string) error { return nil } +// CleanString removes invalid utf-8 byte sequences +func CleanString(s string) string { + r := strings.NewReplacer("\x00", "") + s = r.Replace(s) + return strings.ToValidUTF8(s, "") +} + // https://golangcode.com/unzip-files-in-go/ func Unzip(src, dest string) error { r, err := zip.OpenReader(src) @@ -238,6 +275,21 @@ func Unzip(src, dest string) error { return nil } +// https://yourbasic.org/golang/formatting-byte-size-to-human-readable-format/ +func ByteCountSI(b uint64) string { + const unit = 1024 + if b < unit { + return fmt.Sprintf("%d B", b) + } + div, exp := int64(unit), 0 + for n := b / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + return fmt.Sprintf("%.1f %cB", + float64(b)/float64(div), "kMGTPE"[exp]) +} + func randRange(min, max int) int { rand.Seed(time.Now().UnixNano()) return rand.Intn(max-min) + min @@ -247,6 +299,22 @@ func randomCheckDelay() { time.Sleep(time.Duration(randRange(300, 950)) * time.Millisecond) } +func removeWinNewLines(s string) string { + return strings.ReplaceAll(s, "\r\n", "\n") +} - - +func createTmpFile() (*os.File, error) { + var f *os.File + f, err := os.CreateTemp("", "trmm") + if err != nil { + cwd, err := os.Getwd() + if err != nil { + return f, err + } + f, err = os.CreateTemp(cwd, "trmm") + if err != nil { + return f, err + } + } + return f, nil +} diff --git a/agent/utils/utils.go b/agent/utils/utils.go index 1dae319..2ded21a 100644 --- a/agent/utils/utils.go +++ b/agent/utils/utils.go @@ -10,6 +10,7 @@ import ( "path/filepath" "strings" "time" + "unicode/utf16" "github.com/amidaware/rmmagent/shared" "github.com/go-resty/resty/v2" @@ -183,4 +184,18 @@ func Unzip(src, dest string) error { func RandomCheckDelay() { time.Sleep(time.Duration(RandRange(300, 950)) * time.Millisecond) -} \ No newline at end of file +} + +// https://github.com/mackerelio/go-check-plugins/blob/ad7910fdc45ccb892b5e5fda65ba0956c2b2885d/check-windows-eventlog/lib/check-windows-eventlog.go#L219 +func BytesToString(b []byte) (string, uint32) { + var i int + s := make([]uint16, len(b)/2) + for i = range s { + s[i] = uint16(b[i*2]) + uint16(b[(i*2)+1])<<8 + if s[i] == 0 { + s = s[0:i] + break + } + } + return string(utf16.Decode(s)), uint32(i * 2) +}