diff --git a/agent/agent.go b/agent/agent.go index 0712142..a584b34 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -213,111 +213,8 @@ 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 diff --git a/agent/agent_windows.go b/agent/agent_windows.go index f466150..4e9eb01 100644 --- a/agent/agent_windows.go +++ b/agent/agent_windows.go @@ -42,607 +42,15 @@ import ( "golang.org/x/sys/windows/registry" ) -var ( - getDriveType = windows.NewLazySystemDLL("kernel32.dll").NewProc("GetDriveTypeW") -) -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") @@ -653,33 +61,9 @@ 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/disk/disk_windows.go b/agent/disk/disk_windows.go new file mode 100644 index 0000000..5939a96 --- /dev/null +++ b/agent/disk/disk_windows.go @@ -0,0 +1,50 @@ +package disk + +import ( + "unsafe" + + "github.com/amidaware/rmmagent/agent/utils" + "github.com/shirou/gopsutil/disk" + trmm "github.com/wh1te909/trmm-shared" + "golang.org/x/sys/windows" +) + +var ( + getDriveType = windows.NewLazySystemDLL("kernel32.dll").NewProc("GetDriveTypeW") +) + +// GetDisks returns a list of fixed disks +func 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: utils.ByteCountSI(usage.Total), + Used: utils.ByteCountSI(usage.Used), + Free: utils.ByteCountSI(usage.Free), + Percent: int(usage.UsedPercent), + } + ret = append(ret, d) + } + return ret +} \ No newline at end of file diff --git a/agent/patching/patching_windows.go b/agent/patching/patching_windows.go new file mode 100644 index 0000000..b934191 --- /dev/null +++ b/agent/patching/patching_windows.go @@ -0,0 +1,28 @@ +package patching + +import "golang.org/x/sys/windows/registry" + +// 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 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 +} \ No newline at end of file diff --git a/agent/rpc/rpc_windows.go b/agent/rpc/rpc_windows.go new file mode 100644 index 0000000..1d9c9ff --- /dev/null +++ b/agent/rpc/rpc_windows.go @@ -0,0 +1,473 @@ +package rpc + +import ( + "fmt" + "os" + "runtime" + "strconv" + "sync" + "sync/atomic" + "time" + + rmm "github.com/amidaware/rmmagent/shared" + nats "github.com/nats-io/nats.go" + "github.com/ugorji/go/codec" +) + +func RunRPC(version string) { + //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) + nc, err := nats.Connect(server, opts...) + if err != nil { + a.Logger.Fatalln("RunRPC() nats.Connect()", err) + } + + nc.Subscribe(a.AgentID, func(msg *nats.Msg) { + var payload *NatsMsg + var mh codec.MsgpackHandle + mh.RawToString = true + + dec := codec.NewDecoderBytes(msg.Data, &mh) + if err := dec.Decode(&payload); err != nil { + a.Logger.Errorln(err) + return + } + + switch payload.Func { + case "ping": + go func() { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + a.Logger.Debugln("pong") + ret.Encode("pong") + msg.Respond(resp) + }() + + case "patchmgmt": + go func(p *NatsMsg) { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + err := a.PatchMgmnt(p.PatchMgmt) + if err != nil { + a.Logger.Errorln("PatchMgmnt:", err.Error()) + ret.Encode(err.Error()) + } else { + ret.Encode("ok") + } + msg.Respond(resp) + }(payload) + + case "schedtask": + go func(p *NatsMsg) { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + success, err := a.CreateSchedTask(p.ScheduledTask) + if err != nil { + a.Logger.Errorln(err.Error()) + ret.Encode(err.Error()) + } else if !success { + ret.Encode("Something went wrong") + } else { + ret.Encode("ok") + } + msg.Respond(resp) + }(payload) + + case "delschedtask": + go func(p *NatsMsg) { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + err := DeleteSchedTask(p.ScheduledTask.Name) + if err != nil { + a.Logger.Errorln(err.Error()) + ret.Encode(err.Error()) + } else { + ret.Encode("ok") + } + msg.Respond(resp) + }(payload) + + case "listschedtasks": + go func() { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + tasks := ListSchedTasks() + a.Logger.Debugln(tasks) + ret.Encode(tasks) + msg.Respond(resp) + }() + + case "eventlog": + go func(p *NatsMsg) { + 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) + ret.Encode(evtLog) + msg.Respond(resp) + }(payload) + + case "procs": + go func() { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + procs := a.GetProcsRPC() + a.Logger.Debugln(procs) + ret.Encode(procs) + msg.Respond(resp) + }() + + case "killproc": + go func(p *NatsMsg) { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + err := KillProc(p.ProcPID) + if err != nil { + ret.Encode(err.Error()) + a.Logger.Debugln(err.Error()) + } else { + ret.Encode("ok") + } + msg.Respond(resp) + }(payload) + + case "rawcmd": + go func(p *NatsMsg) { + var resp []byte + var resultData rmm.RawCMDResp + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + + switch runtime.GOOS { + case "windows": + out, _ := CMDShell(p.Data["shell"], []string{}, p.Data["command"], p.Timeout, false) + a.Logger.Debugln(out) + if out[1] != "" { + ret.Encode(out[1]) + resultData.Results = out[1] + } else { + ret.Encode(out[0]) + resultData.Results = out[0] + } + default: + opts := a.NewCMDOpts() + opts.Shell = p.Data["shell"] + opts.Command = p.Data["command"] + opts.Timeout = time.Duration(p.Timeout) + out := a.CmdV2(opts) + tmp := "" + if len(out.Stdout) > 0 { + tmp += out.Stdout + } + if len(out.Stderr) > 0 { + tmp += "\n" + tmp += out.Stderr + } + ret.Encode(tmp) + resultData.Results = tmp + } + + msg.Respond(resp) + if p.ID != 0 { + a.rClient.R().SetBody(resultData).Patch(fmt.Sprintf("/api/v3/%d/%s/histresult/", p.ID, a.AgentID)) + } + }(payload) + + case "winservices": + go func() { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + svcs := a.GetServices() + a.Logger.Debugln(svcs) + ret.Encode(svcs) + msg.Respond(resp) + }() + + case "winsvcdetail": + go func(p *NatsMsg) { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + svc := a.GetServiceDetail(p.Data["name"]) + a.Logger.Debugln(svc) + ret.Encode(svc) + msg.Respond(resp) + }(payload) + + case "winsvcaction": + go func(p *NatsMsg) { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + retData := a.ControlService(p.Data["name"], p.Data["action"]) + a.Logger.Debugln(retData) + ret.Encode(retData) + msg.Respond(resp) + }(payload) + + case "editwinsvc": + go func(p *NatsMsg) { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + retData := a.EditService(p.Data["name"], p.Data["startType"]) + a.Logger.Debugln(retData) + ret.Encode(retData) + msg.Respond(resp) + }(payload) + + case "runscript": + go func(p *NatsMsg) { + var resp []byte + var retData string + var resultData rmm.RunScriptResp + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + start := time.Now() + stdout, stderr, retcode, err := a.RunScript(p.Data["code"], p.Data["shell"], p.ScriptArgs, p.Timeout) + resultData.ExecTime = time.Since(start).Seconds() + resultData.ID = p.ID + + if err != nil { + a.Logger.Debugln(err) + retData = err.Error() + resultData.Retcode = 1 + resultData.Stderr = err.Error() + } else { + retData = stdout + stderr // to keep backwards compat + resultData.Retcode = retcode + resultData.Stdout = stdout + resultData.Stderr = stderr + } + a.Logger.Debugln(retData) + ret.Encode(retData) + msg.Respond(resp) + if p.ID != 0 { + results := map[string]interface{}{"script_results": resultData} + a.rClient.R().SetBody(results).Patch(fmt.Sprintf("/api/v3/%d/%s/histresult/", p.ID, a.AgentID)) + } + }(payload) + + case "runscriptfull": + go func(p *NatsMsg) { + var resp []byte + var retData rmm.RunScriptResp + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + start := time.Now() + stdout, stderr, retcode, err := a.RunScript(p.Data["code"], p.Data["shell"], p.ScriptArgs, p.Timeout) + + retData.ExecTime = time.Since(start).Seconds() + if err != nil { + retData.Stderr = err.Error() + retData.Retcode = 1 + } else { + retData.Stdout = stdout + retData.Stderr = stderr + retData.Retcode = retcode + } + retData.ID = p.ID + a.Logger.Debugln(retData) + ret.Encode(retData) + msg.Respond(resp) + if p.ID != 0 { + results := map[string]interface{}{"script_results": retData} + a.rClient.R().SetBody(results).Patch(fmt.Sprintf("/api/v3/%d/%s/histresult/", p.ID, a.AgentID)) + } + }(payload) + + case "recover": + go func(p *NatsMsg) { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + + switch p.Data["mode"] { + case "mesh": + a.Logger.Debugln("Recovering mesh") + a.RecoverMesh() + } + + ret.Encode("ok") + msg.Respond(resp) + }(payload) + case "softwarelist": + go func() { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + sw := a.GetInstalledSoftware() + a.Logger.Debugln(sw) + ret.Encode(sw) + msg.Respond(resp) + }() + + case "rebootnow": + go func() { + a.Logger.Debugln("Scheduling immediate reboot") + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + ret.Encode("ok") + msg.Respond(resp) + if runtime.GOOS == "windows" { + CMD("shutdown.exe", []string{"/r", "/t", "5", "/f"}, 15, false) + } else { + opts := a.NewCMDOpts() + opts.Command = "reboot" + a.CmdV2(opts) + } + }() + case "needsreboot": + go func() { + 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) + ret.Encode(out) + } else { + a.Logger.Debugln("Error checking if reboot needed:", err) + ret.Encode(false) + } + msg.Respond(resp) + }() + case "sysinfo": + go func() { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + 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) + } + ret.Encode("ok") + msg.Respond(resp) + }() + case "wmi": + go func() { + 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") + loadAvg := a.GetCPULoadAvg() + a.Logger.Debugln("CPU Load Avg:", loadAvg) + ret.Encode(loadAvg) + msg.Respond(resp) + }() + case "runchecks": + go func() { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + if runtime.GOOS == "windows" { + if a.ChecksRunning() { + ret.Encode("busy") + msg.Respond(resp) + a.Logger.Debugln("Checks are already running, please wait") + } else { + ret.Encode("ok") + msg.Respond(resp) + a.Logger.Debugln("Running checks") + _, checkerr := CMD(a.EXE, []string{"-m", "runchecks"}, 600, false) + if checkerr != nil { + a.Logger.Errorln("RPC RunChecks", checkerr) + } + } + } else { + ret.Encode("ok") + msg.Respond(resp) + a.Logger.Debugln("Running checks") + a.RunChecks(true) + } + + }() + case "runtask": + go func(p *NatsMsg) { + a.Logger.Debugln("Running task") + a.RunTask(p.TaskPK) + }(payload) + + case "publicip": + go func() { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + ret.Encode(a.PublicIP()) + msg.Respond(resp) + }() + case "installpython": + go a.GetPython(true) + case "installchoco": + go a.InstallChoco() + case "installwithchoco": + go func(p *NatsMsg) { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + ret.Encode("ok") + msg.Respond(resp) + out, _ := a.InstallWithChoco(p.ChocoProgName) + results := map[string]string{"results": out} + url := fmt.Sprintf("/api/v3/%d/chocoresult/", p.PendingActionPK) + a.rClient.R().SetBody(results).Patch(url) + }(payload) + case "getwinupdates": + go func() { + if !atomic.CompareAndSwapUint32(&getWinUpdateLocker, 0, 1) { + a.Logger.Debugln("Already checking for windows updates") + } else { + a.Logger.Debugln("Checking for windows updates") + defer atomic.StoreUint32(&getWinUpdateLocker, 0) + a.GetWinUpdates() + } + }() + case "installwinupdates": + go func(p *NatsMsg) { + if !atomic.CompareAndSwapUint32(&installWinUpdateLocker, 0, 1) { + a.Logger.Debugln("Already installing windows updates") + } else { + a.Logger.Debugln("Installing windows updates", p.UpdateGUIDs) + defer atomic.StoreUint32(&installWinUpdateLocker, 0) + a.InstallUpdates(p.UpdateGUIDs) + } + }(payload) + case "agentupdate": + go func(p *NatsMsg) { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + if !atomic.CompareAndSwapUint32(&agentUpdateLocker, 0, 1) { + a.Logger.Debugln("Agent update already running") + ret.Encode("updaterunning") + msg.Respond(resp) + } else { + ret.Encode("ok") + msg.Respond(resp) + a.AgentUpdate(p.Data["url"], p.Data["inno"], p.Data["version"]) + atomic.StoreUint32(&agentUpdateLocker, 0) + nc.Flush() + nc.Close() + os.Exit(0) + } + }(payload) + + case "uninstall": + go func(p *NatsMsg) { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + ret.Encode("ok") + msg.Respond(resp) + a.AgentUninstall(p.Code) + nc.Flush() + nc.Close() + os.Exit(0) + }(payload) + } + }) + nc.Flush() + + if err := nc.LastError(); err != nil { + a.Logger.Errorln(err) + os.Exit(1) + } + + wg.Wait() +} diff --git a/agent/rpc/structs.go b/agent/rpc/structs.go new file mode 100644 index 0000000..d3247f0 --- /dev/null +++ b/agent/rpc/structs.go @@ -0,0 +1,20 @@ +package rpc + +import "github.com/amidaware/rmmagent/agent/tasks" + +type NatsMsg struct { + Func string `json:"func"` + Timeout int `json:"timeout"` + Data map[string]string `json:"payload"` + ScriptArgs []string `json:"script_args"` + ProcPID int32 `json:"procpid"` + TaskPK int `json:"taskpk"` + ScheduledTask tasks.SchedTask `json:"schedtaskpayload"` + RecoveryCommand string `json:"recoverycommand"` + UpdateGUIDs []string `json:"guids"` + ChocoProgName string `json:"choco_prog_name"` + PendingActionPK int `json:"pending_action_pk"` + PatchMgmt bool `json:"patch_mgmt"` + ID int `json:"id"` + Code string `json:"code"` +} \ No newline at end of file diff --git a/agent/services/services_windows.go b/agent/services/services_windows.go new file mode 100644 index 0000000..0c7bbe8 --- /dev/null +++ b/agent/services/services_windows.go @@ -0,0 +1,164 @@ +package services + +import ( + "fmt" + + "github.com/amidaware/rmmagent/agent/utils" + "github.com/gonutz/w32/v2" + trmm "github.com/wh1te909/trmm-shared" + "golang.org/x/sys/windows/svc/mgr" +) + +const ( + WinSvcName = "tacticalrmm" + meshSvcName = "mesh agent" +) + +// 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]) + } +} + +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 +} + +// 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" + } +} + +// GetServices returns a list of windows services +func GetServices() []trmm.WindowsService { + ret := make([]trmm.WindowsService, 0) + + conn, err := mgr.Connect() + if err != nil { + //a.Logger.Debugln(err) + return ret + } + defer conn.Disconnect() + + svcs, err := conn.ListServices() + + if err != nil { + //a.Logger.Debugln(err) + return ret + } + + for _, s := range svcs { + srv, err := conn.OpenService(s) + if err != nil { + if err.Error() != "Access is denied." { + //a.Logger.Debugln("Open Service", s, err) + } + + continue + } + + defer srv.Close() + q, err := srv.Query() + if err != nil { + //a.Logger.Debugln(err) + continue + } + + conf, err := srv.Config() + if err != nil { + //a.Logger.Debugln(err) + continue + } + + ret = append(ret, trmm.WindowsService{ + Name: s, + Status: serviceStatusText(uint32(q.State)), + DisplayName: utils.CleanString(conf.DisplayName), + BinPath: utils.CleanString(conf.BinaryPathName), + Description: utils.CleanString(conf.Description), + Username: utils.CleanString(conf.ServiceStartName), + PID: q.ProcessId, + StartType: serviceStartType(uint32(conf.StartType)), + DelayedAutoStart: conf.DelayedAutoStart, + }) + } + return ret +} + +// https://docs.microsoft.com/en-us/dotnet/api/system.serviceprocess.servicestartmode?view=dotnet-plat-ext-3.1 +func serviceStartType(num uint32) string { + switch num { + case 0: + return "Boot" + case 1: + return "System" + case 2: + return "Automatic" + case 3: + return "Manual" + case 4: + return "Disabled" + default: + return "Unknown" + } +} diff --git a/agent/services_windows.go b/agent/services_windows.go index 75624f5..5f77a7a 100644 --- a/agent/services_windows.go +++ b/agent/services_windows.go @@ -20,26 +20,7 @@ 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() @@ -266,27 +247,7 @@ 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/software/software_linux.go b/agent/software/software_linux.go index 81ff929..8ccec96 100644 --- a/agent/software/software_linux.go +++ b/agent/software/software_linux.go @@ -4,8 +4,6 @@ import ( trmm "github.com/wh1te909/trmm-shared" ) -func SendSoftware() {} - func GetInstalledSoftware() []trmm.WinSoftwareList { return []trmm.WinSoftwareList{} } func InstallChoco() {} diff --git a/agent/software/software_windows_386.go b/agent/software/software_windows_386.go new file mode 100644 index 0000000..b08c94d --- /dev/null +++ b/agent/software/software_windows_386.go @@ -0,0 +1,33 @@ +package software + +import ( + "fmt" + + "github.com/amidaware/rmmagent/agent/utils" + wapi "github.com/iamacarpet/go-win64api" + trmm "github.com/wh1te909/trmm-shared" +) + +func GetInstalledSoftware() []trmm.WinSoftwareList { + ret := make([]trmm.WinSoftwareList, 0) + + sw, err := installedSoftwareList() + if err != nil { + return ret + } + + for _, s := range sw { + t := s.InstallDate + ret = append(ret, trmm.WinSoftwareList{ + Name: utils.CleanString(s.Name()), + Version: utils.CleanString(s.Version()), + Publisher: utils.CleanString(s.Publisher), + InstallDate: fmt.Sprintf("%02d-%d-%02d", t.Year(), t.Month(), t.Day()), + Size: utils.ByteCountSI(s.EstimatedSize * 1024), + Source: utils.CleanString(s.InstallSource), + Location: utils.CleanString(s.InstallLocation), + Uninstall: utils.CleanString(s.UninstallString), + }) + } + return ret +} \ No newline at end of file diff --git a/agent/software/software_windows_amd64.go b/agent/software/software_windows_amd64.go new file mode 100644 index 0000000..c1caf8f --- /dev/null +++ b/agent/software/software_windows_amd64.go @@ -0,0 +1,33 @@ +package software + +import ( + "fmt" + + "github.com/amidaware/rmmagent/agent/utils" + wapi "github.com/iamacarpet/go-win64api" + trmm "github.com/wh1te909/trmm-shared" +) + +func GetInstalledSoftware() []trmm.WinSoftwareList { + ret := make([]trmm.WinSoftwareList, 0) + + sw, err := wapi.InstalledSoftwareList() + if err != nil { + return ret + } + + for _, s := range sw { + t := s.InstallDate + ret = append(ret, trmm.WinSoftwareList{ + Name: utils.CleanString(s.Name()), + Version: utils.CleanString(s.Version()), + Publisher: utils.CleanString(s.Publisher), + InstallDate: fmt.Sprintf("%02d-%d-%02d", t.Year(), t.Month(), t.Day()), + Size: utils.ByteCountSI(s.EstimatedSize * 1024), + Source: utils.CleanString(s.InstallSource), + Location: utils.CleanString(s.InstallLocation), + Uninstall: utils.CleanString(s.UninstallString), + }) + } + return ret +} diff --git a/agent/system/system.go b/agent/system/system.go index 4c868c8..7f7e72f 100644 --- a/agent/system/system.go +++ b/agent/system/system.go @@ -3,11 +3,16 @@ package system import ( "bytes" "context" + "errors" "fmt" + "io/ioutil" + "math" + "os" "os/exec" "time" "github.com/amidaware/rmmagent/agent/utils" + ps "github.com/elastic/go-sysinfo" gocmd "github.com/go-cmd/cmd" ) @@ -17,9 +22,16 @@ type CmdStatus struct { Stderr string } +func NewCMDOpts() *CmdOptions { + return &CmdOptions{ + Shell: "/bin/bash", + Timeout: 30, + } +} + func CmdV2(c *CmdOptions) CmdStatus { - ctx, cancel := context.WithTimeout(context.Background(), c.Timeout * time.Second) + ctx, cancel := context.WithTimeout(context.Background(), c.Timeout*time.Second) defer cancel() // Disable output buffering, enable streaming @@ -85,7 +97,7 @@ func CmdV2(c *CmdOptions) CmdStatus { return case <-ctx.Done(): pid := envCmd.Status().PID - KillProc(int32(pid)) + utils.KillProc(int32(pid)) } }() @@ -99,3 +111,88 @@ func CmdV2(c *CmdOptions) CmdStatus { return ret } + +func 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, GetPythonBin(), 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 GetHostname() string { + host, _ := ps.Host() + info := host.Info() + return info.Hostname +} + +// TotalRAM returns total RAM in GB +func TotalRAM() float64 { + host, err := ps.Host() + if err != nil { + return 8.0 + } + + mem, err := host.Memory() + if err != nil { + return 8.0 + } + + return math.Ceil(float64(mem.Total) / 1073741824.0) +} + +// BootTime returns system boot time as a unix timestamp +func BootTime() int64 { + host, err := ps.Host() + if err != nil { + return 1000 + } + + info := host.Info() + return info.BootTime.Unix() +} diff --git a/agent/system/system_linux.go b/agent/system/system_linux.go index 98cefea..0636a35 100644 --- a/agent/system/system_linux.go +++ b/agent/system/system_linux.go @@ -19,13 +19,6 @@ import ( trmm "github.com/wh1te909/trmm-shared" ) -func NewCMDOpts() *CmdOptions { - return &CmdOptions{ - Shell: "/bin/bash", - Timeout: 30, - } -} - func SetDetached() *syscall.SysProcAttr { return &syscall.SysProcAttr{Setpgid: true} } diff --git a/agent/system/system_windows.go b/agent/system/system_windows.go index f081b15..f26da1d 100644 --- a/agent/system/system_windows.go +++ b/agent/system/system_windows.go @@ -1,16 +1,43 @@ package system +import ( + "bytes" + "context" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "sync" + "syscall" + "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" + wapf "github.com/wh1te909/go-win64api" + trmm "github.com/wh1te909/trmm-shared" + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/registry" +) + +const ( + ProgFilesName = "TacticalAgent" + winExeName = "tacticalrmm.exe" +) + func 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() + utils.CreateTRMMTempDir() } const defaultExitCode = 1 - var ( outb bytes.Buffer errb bytes.Buffer @@ -30,17 +57,18 @@ func RunScript(code string, shell string, args []string, timeout int) (stdout, s tmpfn, err := ioutil.TempFile(dir, ext) if err != nil { - a.Logger.Errorln(err) + //a.Logger.Errorln(err) return "", err.Error(), 85, err } - defer os.Remove(tmpfn.Name()) + defer os.Remove(tmpfn.Name()) if _, err := tmpfn.Write(content); err != nil { - a.Logger.Errorln(err) + //a.Logger.Errorln(err) return "", err.Error(), 85, err } + if err := tmpfn.Close(); err != nil { - a.Logger.Errorln(err) + //a.Logger.Errorln(err) return "", err.Error(), 85, err } @@ -49,7 +77,7 @@ func RunScript(code string, shell string, args []string, timeout int) (stdout, s exe = "Powershell" cmdArgs = []string{"-NonInteractive", "-NoProfile", "-ExecutionPolicy", "Bypass", tmpfn.Name()} case "python": - exe = a.PyBin + exe = GetPythonBin() cmdArgs = []string{tmpfn.Name()} case "cmd": exe = tmpfn.Name() @@ -61,16 +89,15 @@ func RunScript(code string, shell string, args []string, timeout int) (stdout, s 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) + //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, @@ -80,20 +107,19 @@ func RunScript(code string, shell string, args []string, timeout int) (stdout, s <-ctx.Done() - _ = KillProc(p) + _ = utils.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) + stdout = utils.CleanString(outb.String()) + stderr = fmt.Sprintf("%s\nScript timed out after %d seconds", utils.CleanString(errb.String()), timeout) exitcode = 98 - a.Logger.Debugln("Script check timeout:", ctx.Err()) + //a.Logger.Debugln("Script check timeout:", ctx.Err()) } else { - stdout = CleanString(outb.String()) - stderr = CleanString(errb.String()) + stdout = utils.CleanString(outb.String()) + stderr = utils.CleanString(errb.String()) // get the exit code if cmdErr != nil { @@ -115,5 +141,371 @@ func RunScript(code string, shell string, args []string, timeout int) (stdout, s } } } + return stdout, stderr, exitcode, nil -} \ No newline at end of file +} + +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, utils.CleanString(errb.String())) + } + + if ctx.Err() == context.DeadlineExceeded { + return [2]string{"", ""}, ctx.Err() + } + + return [2]string{ + utils.CleanString(outb.String()), + utils.CleanString(errb.String()), + }, nil +} + +func SetDetached() *syscall.SysProcAttr { + return &syscall.SysProcAttr{ + CreationFlags: windows.DETACHED_PROCESS | windows.CREATE_NEW_PROCESS_GROUP, + } +} + +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() + + _ = utils.KillProc(p) + timedOut = true + }(pid) + + err := cmd.Wait() + + if timedOut { + return [2]string{ + utils.CleanString(outb.String()), + utils.CleanString(errb.String())}, + ctx.Err() + } + + if err != nil { + return [2]string{ + utils.CleanString(outb.String()), + utils.CleanString(errb.String())}, + err + } + + return [2]string{ + utils.CleanString(outb.String()), + utils.CleanString(errb.String())}, + nil +} + +func GetProgramDirectory() string { + pd := filepath.Join(os.Getenv("ProgramFiles"), ProgFilesName) + return pd +} + +func GetProgramEXE() string { + exe := filepath.Join(GetProgramDirectory(), winExeName) + return exe +} + +func GetPythonBin() string { + var pybin string + switch runtime.GOARCH { + case "amd64": + pybin = filepath.Join(GetProgramDirectory(), "py38-x64", "python.exe") + case "386": + pybin = filepath.Join(GetProgramDirectory(), "py38-x32", "python.exe") + } + + return pybin +} + +// LoggedOnUser returns the first logged on user it finds +func 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 := 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" +} + +func 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 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 +} + +// 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 { + return + } + + for _, process := range procs { + p, err := process.Info() + if err != nil { + continue + } + if strings.Contains(p.Exe, "winagent-v") { + //a.Logger.Debugln("killing process", p.Exe) + utils.KillProc(int32(p.PID)) + } + } +} + +func 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 := 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 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 := RunScript(code, "powershell", []string{}, 20) + if err != nil { + //a.Logger.Debugln(err) + } +} diff --git a/agent/tactical/checks/checks_windows.go b/agent/tactical/checks/checks_windows.go new file mode 100644 index 0000000..9b5aa45 --- /dev/null +++ b/agent/tactical/checks/checks_windows.go @@ -0,0 +1,180 @@ +package checks + +import ( + "encoding/json" + "fmt" + "runtime" + "sync" + "time" + + "github.com/amidaware/rmmagent/agent/system" + "github.com/amidaware/rmmagent/agent/utils" + rmm "github.com/amidaware/rmmagent/shared" + ps "github.com/elastic/go-sysinfo" + "github.com/go-resty/resty/v2" +) + +func CheckRunner(agentID string) { + sleepDelay := utils.RandRange(14, 22) + //a.Logger.Debugf("CheckRunner() init sleeping for %v seconds", sleepDelay) + time.Sleep(time.Duration(sleepDelay) * time.Second) + for { + interval, err := GetCheckInterval(agentID) + if err == nil && !ChecksRunning() { + if runtime.GOOS == "windows" { + _, err = system.CMD(system.GetProgramEXE(), []string{"-m", "checkrunner"}, 600, false) + if err != nil { + //a.Logger.Errorln("Checkrunner RunChecks", err) + } + } else { + RunChecks(agentID, false) + } + } + + //a.Logger.Debugln("Checkrunner sleeping for", interval) + time.Sleep(time.Duration(interval) * time.Second) + } +} + +func GetCheckInterval(agentID string) (int, error) { + r, err := a.rClient.R().SetResult(&rmm.CheckInfo{}).Get(fmt.Sprintf("/api/v3/%s/checkinterval/", a.AgentID)) + if err != nil { + a.Logger.Debugln(err) + return 120, err + } + if r.IsError() { + a.Logger.Debugln("Checkinterval response code:", r.StatusCode()) + return 120, fmt.Errorf("checkinterval response code: %v", r.StatusCode()) + } + interval := r.Result().(*rmm.CheckInfo).Interval + return interval, 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 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 != system.GetProgramEXE() { + continue + } + + for _, arg := range p.Args { + if arg == "runchecks" || arg == "checkrunner" { + running = true + break Out + } + } + } + + return running +} + +func RunChecks(agentID string, force bool) error { + data := rmm.AllChecks{} + var url string + if force { + url = fmt.Sprintf("/api/v3/%s/runchecks/", agentID) + } else { + url = fmt.Sprintf("/api/v3/%s/checkrunner/", agentID) + } + + r, err := a.rClient.R().Get(url) + if err != nil { + //a.Logger.Debugln(err) + return err + } + + if r.IsError() { + //a.Logger.Debugln("Checkrunner response code:", r.StatusCode()) + return nil + } + + if err := json.Unmarshal(r.Body(), &data); err != nil { + //a.Logger.Debugln(err) + return err + } + + var wg sync.WaitGroup + eventLogChecks := make([]rmm.Check, 0) + winServiceChecks := make([]rmm.Check, 0) + for _, check := range data.Checks { + switch check.CheckType { + case "diskspace": + wg.Add(1) + go func(c rmm.Check, wg *sync.WaitGroup, r *resty.Client) { + defer wg.Done() + utils.RandomCheckDelay() + a.SendDiskCheckResult(a.DiskCheck(c), r) + }(check, &wg, a.rClient) + case "cpuload": + wg.Add(1) + go func(c rmm.Check, wg *sync.WaitGroup, r *resty.Client) { + defer wg.Done() + a.CPULoadCheck(c, r) + }(check, &wg, a.rClient) + case "memory": + wg.Add(1) + go func(c rmm.Check, wg *sync.WaitGroup, r *resty.Client) { + defer wg.Done() + randomCheckDelay() + a.MemCheck(c, r) + }(check, &wg, a.rClient) + case "ping": + wg.Add(1) + go func(c rmm.Check, wg *sync.WaitGroup, r *resty.Client) { + defer wg.Done() + randomCheckDelay() + a.SendPingCheckResult(a.PingCheck(c), r) + }(check, &wg, a.rClient) + case "script": + wg.Add(1) + go func(c rmm.Check, wg *sync.WaitGroup, r *resty.Client) { + defer wg.Done() + randomCheckDelay() + a.ScriptCheck(c, r) + }(check, &wg, a.rClient) + case "winsvc": + winServiceChecks = append(winServiceChecks, check) + case "eventlog": + eventLogChecks = append(eventLogChecks, check) + default: + continue + } + } + + if len(winServiceChecks) > 0 { + wg.Add(len(winServiceChecks)) + go func(wg *sync.WaitGroup, r *resty.Client) { + for _, winSvcCheck := range winServiceChecks { + defer wg.Done() + a.SendWinSvcCheckResult(a.WinSvcCheck(winSvcCheck), r) + } + }(&wg, a.rClient) + } + + if len(eventLogChecks) > 0 { + wg.Add(len(eventLogChecks)) + go func(wg *sync.WaitGroup, r *resty.Client) { + for _, evtCheck := range eventLogChecks { + defer wg.Done() + a.EventLogCheck(evtCheck, r) + } + }(&wg, a.rClient) + } + + wg.Wait() + return nil +} \ No newline at end of file diff --git a/agent/tactical/service/service.go b/agent/tactical/service/service.go new file mode 100644 index 0000000..0304827 --- /dev/null +++ b/agent/tactical/service/service.go @@ -0,0 +1,179 @@ +package service + +import ( + "fmt" + "runtime" + "sync" + "time" + + "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" + "github.com/nats-io/nats.go" + "github.com/ugorji/go/codec" + trmm "github.com/wh1te909/trmm-shared" +) + +var natsCheckin = []string{"agent-hello", "agent-agentinfo", "agent-disks", "agent-winsvc", "agent-publicip", "agent-wmi"} + +func RunAsService(agentID string, version string) { + var wg sync.WaitGroup + wg.Add(1) + go AgentSvc(version) + go checks.CheckRunner(agentID) + wg.Wait() +} + +func AgentSvc(version string) { + config := tactical.NewAgentConfig() + go tactical.GetPython(false) + utils.CreateTRMMTempDir() + tactical.RunMigrations() + + sleepDelay := utils.RandRange(14, 22) + //a.Logger.Debugf("AgentSvc() sleeping for %v seconds", sleepDelay) + time.Sleep(time.Duration(sleepDelay) * time.Second) + + opts := SetupNatsOptions(config.AgentID, config.Token) + server := fmt.Sprintf("tls://%s:4222", config.APIURL) + nc, err := nats.Connect(server, opts...) + if err != nil { + //a.Logger.Fatalln("AgentSvc() nats.Connect()", err) + } + + for _, s := range natsCheckin { + NatsMessage(config.AgentID, version, nc, s) + time.Sleep(time.Duration(utils.RandRange(100, 400)) * time.Millisecond) + } + + go tactical.SyncMeshNodeID() + + time.Sleep(time.Duration(utils.RandRange(1, 3)) * time.Second) + AgentStartup(config.AgentID) + tactical.SendSoftware() + + checkInHelloTicker := time.NewTicker(time.Duration(utils.RandRange(30, 60)) * time.Second) + checkInAgentInfoTicker := time.NewTicker(time.Duration(utils.RandRange(200, 400)) * time.Second) + checkInWinSvcTicker := time.NewTicker(time.Duration(utils.RandRange(2400, 3000)) * time.Second) + checkInPubIPTicker := time.NewTicker(time.Duration(utils.RandRange(300, 500)) * time.Second) + checkInDisksTicker := time.NewTicker(time.Duration(utils.RandRange(1000, 2000)) * time.Second) + checkInSWTicker := time.NewTicker(time.Duration(utils.RandRange(2800, 3500)) * time.Second) + checkInWMITicker := time.NewTicker(time.Duration(utils.RandRange(3000, 4000)) * time.Second) + syncMeshTicker := time.NewTicker(time.Duration(utils.RandRange(800, 1200)) * time.Second) + + for { + select { + case <-checkInHelloTicker.C: + NatsMessage(config.AgentID, version, nc, "agent-hello") + case <-checkInAgentInfoTicker.C: + NatsMessage(config.AgentID, version, nc, "agent-agentinfo") + case <-checkInWinSvcTicker.C: + NatsMessage(config.AgentID, version, nc, "agent-winsvc") + case <-checkInPubIPTicker.C: + NatsMessage(config.AgentID, version, nc, "agent-publicip") + case <-checkInDisksTicker.C: + NatsMessage(config.AgentID, version, nc, "agent-disks") + case <-checkInSWTicker.C: + tactical.SendSoftware() + case <-checkInWMITicker.C: + NatsMessage(config.AgentID, version, nc, "agent-wmi") + case <-syncMeshTicker.C: + tactical.SyncMeshNodeID() + } + } +} + +func SetupNatsOptions(agentID string, token string) []nats.Option { + opts := make([]nats.Option, 0) + opts = append(opts, nats.Name("TacticalRMM")) + opts = append(opts, nats.UserInfo(agentID, 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 NatsMessage(agentID string, version string, nc *nats.Conn, mode string) { + var resp []byte + var payload interface{} + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + + switch mode { + case "agent-hello": + payload = trmm.CheckInNats{ + Agentid: agentID, + Version: version, + } + case "agent-winsvc": + payload = trmm.WinSvcNats{ + Agentid: agentID, + WinSvcs: services.GetServices(), + } + case "agent-agentinfo": + osinfo := system.OsString() + reboot, err := system.SystemRebootRequired() + if err != nil { + reboot = false + } + payload = trmm.AgentInfoNats{ + Agentid: agentID, + Username: system.LoggedOnUser(), + Hostname: system.GetHostname(), + OS: osinfo, + Platform: runtime.GOOS, + TotalRAM: system.TotalRAM(), + BootTime: system.BootTime(), + RebootNeeded: reboot, + GoArch: runtime.GOARCH, + } + case "agent-wmi": + payload = trmm.WinWMINats{ + Agentid: agentID, + WMI: wmi.GetWMIInfo(), + } + case "agent-disks": + payload = trmm.WinDisksNats{ + Agentid: agentID, + Disks: disk.GetDisks(), + } + case "agent-publicip": + payload = trmm.PublicIPNats{ + Agentid: agentID, + PublicIP: a.PublicIP(), + } + } + + //a.Logger.Debugln(mode, payload) + ret.Encode(payload) + nc.PublishRequest(a.AgentID, mode, resp) +} + +func DoNatsCheckIn() { + opts := SetupNatsOptions() + server := fmt.Sprintf("tls://%s:4222", a.ApiURL) + nc, err := nats.Connect(server, opts...) + if err != nil { + //a.Logger.Errorln(err) + return + } + + for _, s := range natsCheckin { + time.Sleep(time.Duration(utils.RandRange(100, 400)) * time.Millisecond) + NatsMessage(nc, s) + } + nc.Close() +} + +func AgentStartup(agentID string) { + url := "/api/v3/checkin/" + payload := map[string]interface{}{"agent_id": agentID} + _, err := tactical.PostRequest(url, payload, 15) + if err != nil { + //a.Logger.Debugln("AgentStartup()", err) + } +} diff --git a/agent/tactical/tactical.go b/agent/tactical/tactical.go index dca783e..4e18ffc 100644 --- a/agent/tactical/tactical.go +++ b/agent/tactical/tactical.go @@ -8,6 +8,25 @@ import ( "github.com/go-resty/resty/v2" ) +func PostRequest(url string, body interface{}, timeout time.Duration) (resty.Response, error) { + agentConfig := NewAgentConfig() + client := resty.New() + client.SetBaseURL(agentConfig.BaseURL) + client.SetTimeout(timeout * time.Second) + client.SetCloseConnection(true) + if len(agentConfig.Proxy) > 0 { + + } + + if shared.DEBUG { + client.SetDebug(true) + } + + response, err := client.R().SetBody(body).Post(url) + + return *response, err +} + func SyncMeshNodeID() bool { id, err := GetMeshNodeID() if err != nil { @@ -16,21 +35,12 @@ func SyncMeshNodeID() bool { } agentConfig := NewAgentConfig() - payload := shared.MeshNodeID{ Func: "syncmesh", Agentid: agentConfig.AgentID, NodeID: utils.StripAll(id), } - client := resty.New() - client.SetBaseURL(agentConfig.BaseURL) - client.SetTimeout(15 * time.Second) - client.SetCloseConnection(true) - if shared.DEBUG { - client.SetDebug(true) - } - - _, err = client.R().SetBody(payload).Post("/api/v3/syncmesh/") + _, err = PostRequest("/api/v3/syncmesh/", payload, 15) return err == nil -} +} \ No newline at end of file diff --git a/agent/tactical/tactical_linux.go b/agent/tactical/tactical_linux.go index 108a73b..fa3cd98 100644 --- a/agent/tactical/tactical_linux.go +++ b/agent/tactical/tactical_linux.go @@ -181,4 +181,6 @@ func RunTask(id int) error { return nil } func installMesh(meshbin, exe, proxy string) (string, error) { return "not implemented", nil -} \ No newline at end of file +} + +func SendSoftware() {} \ No newline at end of file diff --git a/agent/tactical/tactical_windows.go b/agent/tactical/tactical_windows.go index 5d5376d..5c55436 100644 --- a/agent/tactical/tactical_windows.go +++ b/agent/tactical/tactical_windows.go @@ -1,5 +1,41 @@ package tactical +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "runtime" + "strconv" + "strings" + "time" + + "github.com/amidaware/rmmagent/agent/patching" + "github.com/amidaware/rmmagent/agent/services" + "github.com/amidaware/rmmagent/agent/software" + "github.com/amidaware/rmmagent/agent/system" + "github.com/amidaware/rmmagent/agent/utils" + "github.com/go-resty/resty/v2" + "github.com/kardianos/service" + trmm "github.com/wh1te909/trmm-shared" + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/registry" +) + +func GetMeshBinary() string { + var MeshSysBin string + ac := NewAgentConfig() + if len(ac.CustomMeshDir) > 0 { + MeshSysBin = filepath.Join(ac.CustomMeshDir, "MeshAgent.exe") + } else { + MeshSysBin = filepath.Join(os.Getenv("ProgramFiles"), "Mesh Agent", "MeshAgent.exe") + } + + return MeshSysBin +} + func NewAgentConfig() *rmm.AgentConfig { k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\TacticalRMM`, registry.ALL_ACCESS) @@ -28,4 +64,273 @@ func NewAgentConfig() *rmm.AgentConfig { Proxy: proxy, CustomMeshDir: customMeshDir, } +} + +func GetMeshNodeID() (string, error) { + out, err := system.CMD(GetMeshBinary(), []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(utils.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 SendSoftware() { + sw := software.GetInstalledSoftware() + //a.Logger.Debugln(sw) + + config := NewAgentConfig() + payload := map[string]interface{}{ + "agent_id": config.AgentID, + "software": sw, + } + + _, err := PostRequest("/api/v3/software/", payload, 15) + if err != nil { + //a.Logger.Debugln(err) + } +} + +func UninstallCleanup() { + registry.DeleteKey(registry.LOCAL_MACHINE, `SOFTWARE\TacticalRMM`) + patching.PatchMgmnt(false) + CleanupAgentUpdates() + system.CleanupSchedTasks() +} + +func AgentUpdate(url, inno, version string) { + time.Sleep(time.Duration(utils.RandRange(1, 15)) * time.Second) + system.KillHungUpdates() + CleanupAgentUpdates() + updater := filepath.Join(system.GetProgramDirectory(), inno) + //a.Logger.Infof("Agent updating from %s to %s", a.Version, version) + //a.Logger.Infoln("Downloading agent update from", url) + + config := NewAgentConfig() + rClient := resty.New() + rClient.SetCloseConnection(true) + rClient.SetTimeout(15 * time.Minute) + rClient.SetDebug(rmm.DEBUG) + if len(config.Proxy) > 0 { + rClient.SetProxy(config.Proxy) + } + + r, err := rClient.R().SetOutput(updater).Get(url) + if err != nil { + //a.Logger.Errorln(err) + system.CMD("net", []string{"start", services.WinSvcName}, 10, false) + return + } + + if r.IsError() { + //a.Logger.Errorln("Download failed with status code", r.StatusCode()) + system.CMD("net", []string{"start", services.WinSvcName}, 10, false) + return + } + + dir, err := ioutil.TempDir("", "tacticalrmm") + if err != nil { + //a.Logger.Errorln("Agentupdate create tempdir:", err) + system.CMD("net", []string{"start", services.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 CleanupAgentUpdates() { + pd := filepath.Join(os.Getenv("ProgramFiles"), system.ProgFilesName) + cderr := os.Chdir(pd) + 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 AgentUninstall(code string) { + system.KillHungUpdates() + tacUninst := filepath.Join(system.GetProgramDirectory(), 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 GetUninstallExe() string { + cderr := os.Chdir(system.GetProgramDirectory()) + 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" +} + +// RunMigrations cleans up unused stuff from older agents +func RunMigrations() { + for _, i := range []string{"nssm.exe", "nssm-x86.exe"} { + nssm := filepath.Join(system.GetProgramDirectory(), i) + if trmm.FileExists(nssm) { + os.Remove(nssm) + } + } +} + +func 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 := system.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 := system.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 = utils.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 +} + +func Start(_ service.Service) error { + go rpc.RunRPC() + return nil +} + +func GetPython(force bool) { + if trmm.FileExists(system.GetPythonBin()) && !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(system.GetProgramDirectory(), folder) + pyZip := filepath.Join(system.GetProgramDirectory(), archZip) + //a.Logger.Debugln(pyZip) + //a.Logger.Debugln(a.PyBin) + defer os.Remove(pyZip) + + if force { + os.RemoveAll(pyFolder) + } + + config := NewAgentConfig() + rClient := resty.New() + rClient.SetTimeout(20 * time.Minute) + rClient.SetRetryCount(10) + rClient.SetRetryWaitTime(1 * time.Minute) + rClient.SetRetryMaxWaitTime(15 * time.Minute) + if len(config.Proxy) > 0 { + rClient.SetProxy(config.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 = utils.Unzip(pyZip, system.GetProgramDirectory()) + if err != nil { + //a.Logger.Errorln(err) + } } \ No newline at end of file diff --git a/agent/tasks/structs.go b/agent/tasks/structs.go new file mode 100644 index 0000000..5ba87cf --- /dev/null +++ b/agent/tasks/structs.go @@ -0,0 +1,2 @@ +package tasks + diff --git a/agent/tasks/structs_windows.go b/agent/tasks/structs_windows.go new file mode 100644 index 0000000..7e41407 --- /dev/null +++ b/agent/tasks/structs_windows.go @@ -0,0 +1,44 @@ +package tasks + +import ( + "time" + + "github.com/amidaware/taskmaster" + "github.com/rickb777/date/period" +) + +type SchedTask struct { + PK int `json:"pk"` + Type string `json:"type"` + Name string `json:"name"` + Trigger string `json:"trigger"` + Enabled bool `json:"enabled"` + DayInterval taskmaster.DayInterval `json:"day_interval"` + WeekInterval taskmaster.WeekInterval `json:"week_interval"` + DaysOfWeek taskmaster.DayOfWeek `json:"days_of_week"` + DaysOfMonth taskmaster.DayOfMonth `json:"days_of_month"` + RunOnLastDayOfMonth bool `json:"run_on_last_day_of_month"` + MonthsOfYear taskmaster.Month `json:"months_of_year"` + WeeksOfMonth taskmaster.Week `json:"weeks_of_month"` + StartYear int `json:"start_year"` + StartMonth time.Month `json:"start_month"` + StartDay int `json:"start_day"` + StartHour int `json:"start_hour"` + StartMinute int `json:"start_min"` + ExpireYear int `json:"expire_year"` + ExpireMonth time.Month `json:"expire_month"` + ExpireDay int `json:"expire_day"` + ExpireHour int `json:"expire_hour"` + ExpireMinute int `json:"expire_min"` + RandomDelay period.Period `json:"random_delay"` + RepetitionInterval period.Period `json:"repetition_interval"` + RepetitionDuration period.Period `json:"repetition_duration"` + StopAtDurationEnd bool `json:"stop_at_duration_end"` + Path string `json:"path"` + WorkDir string `json:"workdir"` + Args string `json:"args"` + TaskPolicy taskmaster.TaskInstancesPolicy `json:"multiple_instances"` + RunASAPAfterMissed bool `json:"start_when_available"` + DeleteAfter bool `json:"delete_expired_task_after"` + Overwrite bool `json:"overwrite_task"` +} \ No newline at end of file diff --git a/agent/tasks/tasks_windows.go b/agent/tasks/tasks_windows.go new file mode 100644 index 0000000..5ba87cf --- /dev/null +++ b/agent/tasks/tasks_windows.go @@ -0,0 +1,2 @@ +package tasks + diff --git a/agent/tasks_windows.go b/agent/tasks_windows.go index dabf1a3..b0fe77b 100644 --- a/agent/tasks_windows.go +++ b/agent/tasks_windows.go @@ -311,26 +311,7 @@ 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/utils.go b/agent/utils/utils.go index 1c6e6a9..1dae319 100644 --- a/agent/utils/utils.go +++ b/agent/utils/utils.go @@ -1,15 +1,20 @@ package utils import ( + "archive/zip" "bytes" "fmt" "io" + "math/rand" "os" + "path/filepath" "strings" "time" "github.com/amidaware/rmmagent/shared" "github.com/go-resty/resty/v2" + "github.com/shirou/gopsutil/v3/process" + trmm "github.com/wh1te909/trmm-shared" ) func CaptureOutput(f func()) string { @@ -77,7 +82,7 @@ func WebRequest(requestType string, timeout time.Duration, payload map[string]st } result, err := client.R().Get(url) - return *result, err + return *result, err } // StripAll strips all whitespace and newline chars @@ -86,4 +91,96 @@ func StripAll(s string) string { 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 +} + +func CreateTRMMTempDir() { + // create the temp dir for running scripts + dir := filepath.Join(os.TempDir(), "trmm") + if !trmm.FileExists(dir) { + err := os.Mkdir(dir, 0775) + if err != nil { + //a.Logger.Errorln(err) + } + } +} + +func RandRange(min, max int) int { + rand.Seed(time.Now().UnixNano()) + return rand.Intn(max-min) + min +} + +// https://golangcode.com/unzip-files-in-go/ +func Unzip(src, dest string) error { + r, err := zip.OpenReader(src) + if err != nil { + return err + } + + defer r.Close() + for _, f := range r.File { + // Store filename/path for returning and using later on + fpath := filepath.Join(dest, f.Name) + // Check for ZipSlip. More Info: http://bit.ly/2MsjAWE + if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) { + return fmt.Errorf("%s: illegal file path", fpath) + } + + if f.FileInfo().IsDir() { + // Make Folder + os.MkdirAll(fpath, os.ModePerm) + continue + } + + // Make File + if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil { + return err + } + + outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return err + } + + rc, err := f.Open() + if err != nil { + return err + } + + _, err = io.Copy(outFile, rc) + // Close the file without defer to close before next iteration of loop + outFile.Close() + rc.Close() + + if err != nil { + return err + } + } + + return nil +} + +func RandomCheckDelay() { + time.Sleep(time.Duration(RandRange(300, 950)) * time.Millisecond) } \ No newline at end of file diff --git a/agent/wmi/wmi_windows.go b/agent/wmi/wmi_windows.go new file mode 100644 index 0000000..38f4c50 --- /dev/null +++ b/agent/wmi/wmi_windows.go @@ -0,0 +1,614 @@ +package wmi + +import ( + "encoding/json" + + rmm "github.com/amidaware/rmmagent/shared" + "github.com/yusufpapurcu/wmi" +) + +func GetWMIInfo() map[string]interface{} { + wmiInfo := make(map[string]interface{}) + + compSysProd, err := GetWin32_ComputerSystemProduct() + if err != nil { + //a.Logger.Debugln(err) + } + + compSys, err := GetWin32_ComputerSystem() + if err != nil { + //a.Logger.Debugln(err) + } + + netAdaptConfig, err := GetWin32_NetworkAdapterConfiguration() + if err != nil { + //a.Logger.Debugln(err) + } + + physMem, err := GetWin32_PhysicalMemory() + if err != nil { + //a.Logger.Debugln(err) + } + + winOS, err := GetWin32_OperatingSystem() + if err != nil { + //a.Logger.Debugln(err) + } + + baseBoard, err := GetWin32_BaseBoard() + if err != nil { + //a.Logger.Debugln(err) + } + + bios, err := GetWin32_BIOS() + if err != nil { + //a.Logger.Debugln(err) + } + + disk, err := GetWin32_DiskDrive() + if err != nil { + //a.Logger.Debugln(err) + } + + netAdapt, err := GetWin32_NetworkAdapter() + if err != nil { + //a.Logger.Debugln(err) + } + + desktopMon, err := GetWin32_DesktopMonitor() + if err != nil { + //a.Logger.Debugln(err) + } + + cpu, err := GetWin32_Processor() + if err != nil { + //a.Logger.Debugln(err) + } + + usb, err := GetWin32_USBController() + if err != nil { + //a.Logger.Debugln(err) + } + + graphics, err := GetWin32_VideoController() + if err != nil { + //a.Logger.Debugln(err) + } + + wmiInfo["comp_sys_prod"] = compSysProd + wmiInfo["comp_sys"] = compSys + wmiInfo["network_config"] = netAdaptConfig + wmiInfo["mem"] = physMem + wmiInfo["os"] = winOS + wmiInfo["base_board"] = baseBoard + wmiInfo["bios"] = bios + wmiInfo["disk"] = disk + wmiInfo["network_adapter"] = netAdapt + wmiInfo["desktop_monitor"] = desktopMon + wmiInfo["cpu"] = cpu + wmiInfo["usb"] = usb + wmiInfo["graphics"] = graphics + + return wmiInfo +} + +func GetWin32_ComputerSystemProduct() ([]interface{}, error) { + var dst []rmm.Win32_ComputerSystemProduct + ret := make([]interface{}, 0) + q := wmi.CreateQuery(&dst, "") + err := wmi.Query(q, &dst) + if err != nil { + return ret, err + } + + for _, val := range dst { + b, err := json.Marshal(val) + if err != nil { + return ret, err + } + // this creates an extra unneeded array but keeping for now + // for backwards compatibility with the python agent + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + tmp = append(tmp, un) + ret = append(ret, tmp) + } + + return ret, nil +} + +func GetWin32_ComputerSystem() ([]interface{}, error) { + var ( + dstEX []rmm.Win32_ComputerSystemEX + dst []rmm.Win32_ComputerSystem + errEX error + errORIG error + fallback bool = false + ) + ret := make([]interface{}, 0) + q := "SELECT * FROM Win32_ComputerSystem" + + errEX = wmi.Query(q, &dstEX) + if errEX != nil { + errORIG = wmi.Query(q, &dst) + if errORIG != nil { + return ret, errORIG + } + } + + if errEX == nil { + for _, val := range dstEX { + b, err := json.Marshal(val) + if err != nil { + fallback = true + break + } + // this creates an extra unneeded array but keeping for now + // for backwards compatibility with the python agent + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + tmp = append(tmp, un) + ret = append(ret, tmp) + } + if !fallback { + return ret, nil + } + } + + if errORIG == nil { + for _, val := range dst { + b, err := json.Marshal(val) + if err != nil { + return ret, err + } + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + tmp = append(tmp, un) + ret = append(ret, tmp) + } + + return ret, nil + } + + return ret, nil +} + +func GetWin32_NetworkAdapterConfiguration() ([]interface{}, error) { + var dst []rmm.Win32_NetworkAdapterConfiguration + ret := make([]interface{}, 0) + + q := wmi.CreateQuery(&dst, "") + err := wmi.Query(q, &dst) + if err != nil { + return ret, err + } + + for _, val := range dst { + b, err := json.Marshal(val) + if err != nil { + return ret, err + } + + // this creates an extra unneeded array but keeping for now + // for backwards compatibility with the python agent + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + + tmp = append(tmp, un) + ret = append(ret, tmp) + } + + return ret, nil +} + +func GetWin32_PhysicalMemory() ([]interface{}, error) { + var ( + dstEX []rmm.Win32_PhysicalMemoryEX + dst []rmm.Win32_PhysicalMemory + errEX error + errORIG error + fallback bool = false + ) + + ret := make([]interface{}, 0) + q := "SELECT * FROM Win32_PhysicalMemory" + errEX = wmi.Query(q, &dstEX) + if errEX != nil { + errORIG = wmi.Query(q, &dst) + if errORIG != nil { + return ret, errORIG + } + } + + if errEX == nil { + for _, val := range dstEX { + b, err := json.Marshal(val) + if err != nil { + fallback = true + break + } + // this creates an extra unneeded array but keeping for now + // for backwards compatibility with the python agent + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + tmp = append(tmp, un) + ret = append(ret, tmp) + } + if !fallback { + return ret, nil + } + } + + if errORIG == nil { + for _, val := range dst { + b, err := json.Marshal(val) + if err != nil { + return ret, err + } + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + tmp = append(tmp, un) + ret = append(ret, tmp) + } + + return ret, nil + } + + return ret, nil +} + +func GetWin32_OperatingSystem() ([]interface{}, error) { + var dst []rmm.Win32_OperatingSystem + ret := make([]interface{}, 0) + q := wmi.CreateQuery(&dst, "") + err := wmi.Query(q, &dst) + if err != nil { + return ret, err + } + + for _, val := range dst { + b, err := json.Marshal(val) + if err != nil { + return ret, err + } + + // this creates an extra unneeded array but keeping for now + // for backwards compatibility with the python agent + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + + tmp = append(tmp, un) + ret = append(ret, tmp) + } + + return ret, nil +} + +func GetWin32_BaseBoard() ([]interface{}, error) { + var dst []rmm.Win32_BaseBoard + ret := make([]interface{}, 0) + q := wmi.CreateQuery(&dst, "") + err := wmi.Query(q, &dst) + if err != nil { + return ret, err + } + + for _, val := range dst { + b, err := json.Marshal(val) + if err != nil { + return ret, err + } + + // this creates an extra unneeded array but keeping for now + // for backwards compatibility with the python agent + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + + tmp = append(tmp, un) + ret = append(ret, tmp) + } + + return ret, nil +} + +func GetWin32_BIOS() ([]interface{}, error) { + var ( + dstEX []rmm.Win32_BIOSEX + dst []rmm.Win32_BIOS + errEX error + errORIG error + fallback bool = false + ) + ret := make([]interface{}, 0) + q := "SELECT * FROM Win32_BIOS" + + errEX = wmi.Query(q, &dstEX) + if errEX != nil { + errORIG = wmi.Query(q, &dst) + if errORIG != nil { + return ret, errORIG + } + } + + if errEX == nil { + for _, val := range dstEX { + b, err := json.Marshal(val) + if err != nil { + fallback = true + break + } + // this creates an extra unneeded array but keeping for now + // for backwards compatibility with the python agent + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + tmp = append(tmp, un) + ret = append(ret, tmp) + } + + if !fallback { + return ret, nil + } + } + + if errORIG == nil { + for _, val := range dst { + b, err := json.Marshal(val) + if err != nil { + return ret, err + } + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + tmp = append(tmp, un) + ret = append(ret, tmp) + } + + return ret, nil + } + + return ret, nil +} + +func GetWin32_DiskDrive() ([]interface{}, error) { + var dst []rmm.Win32_DiskDrive + ret := make([]interface{}, 0) + q := wmi.CreateQuery(&dst, "") + err := wmi.Query(q, &dst) + if err != nil { + return ret, err + } + + for _, val := range dst { + b, err := json.Marshal(val) + if err != nil { + return ret, err + } + + // this creates an extra unneeded array but keeping for now + // for backwards compatibility with the python agent + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + + tmp = append(tmp, un) + ret = append(ret, tmp) + } + + return ret, nil +} + +func GetWin32_NetworkAdapter() ([]interface{}, error) { + var dst []rmm.Win32_NetworkAdapter + ret := make([]interface{}, 0) + q := wmi.CreateQuery(&dst, "") + err := wmi.Query(q, &dst) + if err != nil { + return ret, err + } + + for _, val := range dst { + b, err := json.Marshal(val) + if err != nil { + return ret, err + } + + // this creates an extra unneeded array but keeping for now + // for backwards compatibility with the python agent + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + + tmp = append(tmp, un) + ret = append(ret, tmp) + } + + return ret, nil +} + +func GetWin32_DesktopMonitor() ([]interface{}, error) { + var dst []rmm.Win32_DesktopMonitor + ret := make([]interface{}, 0) + + q := wmi.CreateQuery(&dst, "") + err := wmi.Query(q, &dst) + if err != nil { + return ret, err + } + + for _, val := range dst { + b, err := json.Marshal(val) + if err != nil { + return ret, err + } + + // this creates an extra unneeded array but keeping for now + // for backwards compatibility with the python agent + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + + tmp = append(tmp, un) + ret = append(ret, tmp) + } + + return ret, nil +} + +func GetWin32_Processor() ([]interface{}, error) { + var ( + dstEX []rmm.Win32_ProcessorEX + dst []rmm.Win32_Processor + errEX error + errORIG error + fallback bool = false + ) + ret := make([]interface{}, 0) + q := "SELECT * FROM Win32_Processor" + + errEX = wmi.Query(q, &dstEX) + if errEX != nil { + errORIG = wmi.Query(q, &dst) + if errORIG != nil { + return ret, errORIG + } + } + + if errEX == nil { + for _, val := range dstEX { + b, err := json.Marshal(val) + if err != nil { + fallback = true + break + } + // this creates an extra unneeded array but keeping for now + // for backwards compatibility with the python agent + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + tmp = append(tmp, un) + ret = append(ret, tmp) + } + + if !fallback { + return ret, nil + } + } + + if errORIG == nil { + for _, val := range dst { + b, err := json.Marshal(val) + if err != nil { + return ret, err + } + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + tmp = append(tmp, un) + ret = append(ret, tmp) + } + + return ret, nil + } + + return ret, nil +} + +func GetWin32_USBController() ([]interface{}, error) { + var dst []rmm.Win32_USBController + ret := make([]interface{}, 0) + + q := wmi.CreateQuery(&dst, "") + err := wmi.Query(q, &dst) + if err != nil { + return ret, err + } + + for _, val := range dst { + b, err := json.Marshal(val) + if err != nil { + return ret, err + } + + // this creates an extra unneeded array but keeping for now + // for backwards compatibility with the python agent + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + + tmp = append(tmp, un) + ret = append(ret, tmp) + } + + return ret, nil +} + +func GetWin32_VideoController() ([]interface{}, error) { + var dst []rmm.Win32_VideoController + ret := make([]interface{}, 0) + q := wmi.CreateQuery(&dst, "") + err := wmi.Query(q, &dst) + if err != nil { + return ret, err + } + + for _, val := range dst { + b, err := json.Marshal(val) + if err != nil { + return ret, err + } + + // this creates an extra unneeded array but keeping for now + // for backwards compatibility with the python agent + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + + tmp = append(tmp, un) + ret = append(ret, tmp) + } + + return ret, nil +}