returned existing files to origin during restruct

This commit is contained in:
redanthrax 2022-06-20 11:00:36 -07:00
parent 7fbb0fe7e1
commit a2ed11b2fb
19 changed files with 1678 additions and 102 deletions

View file

@ -29,6 +29,7 @@ import (
rmm "github.com/amidaware/rmmagent/shared"
ps "github.com/elastic/go-sysinfo"
gocmd "github.com/go-cmd/cmd"
"github.com/go-resty/resty/v2"
"github.com/kardianos/service"
nats "github.com/nats-io/nats.go"
@ -136,6 +137,114 @@ func New(logger *logrus.Logger, version string) *Agent {
}
}
type CmdStatus struct {
Status gocmd.Status
Stdout string
Stderr string
}
type CmdOptions struct {
Shell string
Command string
Args []string
Timeout time.Duration
IsScript bool
IsExecutable bool
Detached bool
}
func (a *Agent) NewCMDOpts() *CmdOptions {
return &CmdOptions{
Shell: "/bin/bash",
Timeout: 30,
}
}
func (a *Agent) CmdV2(c *CmdOptions) CmdStatus {
ctx, cancel := context.WithTimeout(context.Background(), c.Timeout*time.Second)
defer cancel()
// Disable output buffering, enable streaming
cmdOptions := gocmd.Options{
Buffered: false,
Streaming: true,
}
// have a child process that is in a different process group so that
// parent terminating doesn't kill child
if c.Detached {
cmdOptions.BeforeExec = []func(cmd *exec.Cmd){
func(cmd *exec.Cmd) {
cmd.SysProcAttr = SetDetached()
},
}
}
var envCmd *gocmd.Cmd
if c.IsScript {
envCmd = gocmd.NewCmdOptions(cmdOptions, c.Shell, c.Args...) // call script directly
} else if c.IsExecutable {
envCmd = gocmd.NewCmdOptions(cmdOptions, c.Shell, c.Command) // c.Shell: bin + c.Command: args as one string
} else {
envCmd = gocmd.NewCmdOptions(cmdOptions, c.Shell, "-c", c.Command) // /bin/bash -c 'ls -l /var/log/...'
}
var stdoutBuf bytes.Buffer
var stderrBuf bytes.Buffer
// Print STDOUT and STDERR lines streaming from Cmd
doneChan := make(chan struct{})
go func() {
defer close(doneChan)
// Done when both channels have been closed
// https://dave.cheney.net/2013/04/30/curious-channels
for envCmd.Stdout != nil || envCmd.Stderr != nil {
select {
case line, open := <-envCmd.Stdout:
if !open {
envCmd.Stdout = nil
continue
}
fmt.Fprintln(&stdoutBuf, line)
a.Logger.Debugln(line)
case line, open := <-envCmd.Stderr:
if !open {
envCmd.Stderr = nil
continue
}
fmt.Fprintln(&stderrBuf, line)
a.Logger.Debugln(line)
}
}
}()
// Run and wait for Cmd to return, discard Status
envCmd.Start()
go func() {
select {
case <-doneChan:
return
case <-ctx.Done():
a.Logger.Debugf("Command timed out after %d seconds\n", c.Timeout)
pid := envCmd.Status().PID
a.Logger.Debugln("Killing process with PID", pid)
KillProc(int32(pid))
}
}()
// Wait for goroutine to print everything
<-doneChan
ret := CmdStatus{
Status: envCmd.Status(),
Stdout: CleanString(stdoutBuf.String()),
Stderr: CleanString(stderrBuf.String()),
}
a.Logger.Debugf("%+v\n", ret)
return ret
}
func (a *Agent) GetCPULoadAvg() int {
fallback := false
pyCode := `
@ -187,7 +296,7 @@ func (a *Agent) ForceKillMesh() {
for _, pid := range pids {
a.Logger.Debugln("Killing mesh process with pid %d", pid)
if err := utils.KillProc(int32(pid)); err != nil {
if err := KillProc(int32(pid)); err != nil {
a.Logger.Debugln(err)
}
}
@ -213,8 +322,111 @@ func (a *Agent) SyncMeshNodeID() {
}
}
func (a *Agent) setupNatsOptions() []nats.Option {
opts := make([]nats.Option, 0)
opts = append(opts, nats.Name("TacticalRMM"))
opts = append(opts, nats.UserInfo(a.AgentID, a.Token))
opts = append(opts, nats.ReconnectWait(time.Second*5))
opts = append(opts, nats.RetryOnFailedConnect(true))
opts = append(opts, nats.MaxReconnects(-1))
opts = append(opts, nats.ReconnectBufSize(-1))
return opts
}
func (a *Agent) GetUninstallExe() string {
cderr := os.Chdir(a.ProgramDir)
if cderr == nil {
files, err := filepath.Glob("unins*.exe")
if err == nil {
for _, f := range files {
if strings.Contains(f, "001") {
return f
}
}
}
}
return "unins000.exe"
}
func (a *Agent) CleanupAgentUpdates() {
cderr := os.Chdir(a.ProgramDir)
if cderr != nil {
a.Logger.Errorln(cderr)
return
}
files, err := filepath.Glob("winagent-v*.exe")
if err == nil {
for _, f := range files {
os.Remove(f)
}
}
cderr = os.Chdir(os.Getenv("TMP"))
if cderr != nil {
a.Logger.Errorln(cderr)
return
}
folders, err := filepath.Glob("tacticalrmm*")
if err == nil {
for _, f := range folders {
os.RemoveAll(f)
}
}
}
func (a *Agent) RunPythonCode(code string, timeout int, args []string) (string, error) {
content := []byte(code)
dir, err := ioutil.TempDir("", "tacticalpy")
if err != nil {
a.Logger.Debugln(err)
return "", err
}
defer os.RemoveAll(dir)
tmpfn, _ := ioutil.TempFile(dir, "*.py")
if _, err := tmpfn.Write(content); err != nil {
a.Logger.Debugln(err)
return "", err
}
if err := tmpfn.Close(); err != nil {
a.Logger.Debugln(err)
return "", err
}
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
defer cancel()
var outb, errb bytes.Buffer
cmdArgs := []string{tmpfn.Name()}
if len(args) > 0 {
cmdArgs = append(cmdArgs, args...)
}
a.Logger.Debugln(cmdArgs)
cmd := exec.CommandContext(ctx, a.PyBin, cmdArgs...)
cmd.Stdout = &outb
cmd.Stderr = &errb
cmdErr := cmd.Run()
if ctx.Err() == context.DeadlineExceeded {
a.Logger.Debugln("RunPythonCode:", ctx.Err())
return "", ctx.Err()
}
if cmdErr != nil {
a.Logger.Debugln("RunPythonCode:", cmdErr)
return "", cmdErr
}
if errb.String() != "" {
a.Logger.Debugln(errb.String())
return errb.String(), errors.New("RunPythonCode stderr")
}
return outb.String(), nil
}
func (a *Agent) CreateTRMMTempDir() {
// create the temp dir for running scripts
@ -226,7 +438,3 @@ func (a *Agent) CreateTRMMTempDir() {
}
}
}
func (a *Agent) GetDisks() []trmm.Disk {
return disk.GetDisks()
}

View file

@ -42,15 +42,751 @@ import (
"golang.org/x/sys/windows/registry"
)
var (
getDriveType = windows.NewLazySystemDLL("kernel32.dll").NewProc("GetDriveTypeW")
)
func NewAgentConfig() *rmm.AgentConfig {
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\TacticalRMM`, registry.ALL_ACCESS)
if err != nil {
return &rmm.AgentConfig{}
}
baseurl, _, _ := k.GetStringValue("BaseURL")
agentid, _, _ := k.GetStringValue("AgentID")
apiurl, _, _ := k.GetStringValue("ApiURL")
token, _, _ := k.GetStringValue("Token")
agentpk, _, _ := k.GetStringValue("AgentPK")
pk, _ := strconv.Atoi(agentpk)
cert, _, _ := k.GetStringValue("Cert")
proxy, _, _ := k.GetStringValue("Proxy")
customMeshDir, _, _ := k.GetStringValue("MeshDir")
return &rmm.AgentConfig{
BaseURL: baseurl,
AgentID: agentid,
APIURL: apiurl,
Token: token,
AgentPK: agentpk,
PK: pk,
Cert: cert,
Proxy: proxy,
CustomMeshDir: customMeshDir,
}
}
func (a *Agent) RunScript(code string, shell string, args []string, timeout int) (stdout, stderr string, exitcode int, e error) {
content := []byte(code)
dir := filepath.Join(os.TempDir(), "trmm")
if !trmm.FileExists(dir) {
a.CreateTRMMTempDir()
}
const defaultExitCode = 1
var (
outb bytes.Buffer
errb bytes.Buffer
exe string
ext string
cmdArgs []string
)
switch shell {
case "powershell":
ext = "*.ps1"
case "python":
ext = "*.py"
case "cmd":
ext = "*.bat"
}
tmpfn, err := ioutil.TempFile(dir, ext)
if err != nil {
a.Logger.Errorln(err)
return "", err.Error(), 85, err
}
defer os.Remove(tmpfn.Name())
if _, err := tmpfn.Write(content); err != nil {
a.Logger.Errorln(err)
return "", err.Error(), 85, err
}
if err := tmpfn.Close(); err != nil {
a.Logger.Errorln(err)
return "", err.Error(), 85, err
}
switch shell {
case "powershell":
exe = "Powershell"
cmdArgs = []string{"-NonInteractive", "-NoProfile", "-ExecutionPolicy", "Bypass", tmpfn.Name()}
case "python":
exe = a.PyBin
cmdArgs = []string{tmpfn.Name()}
case "cmd":
exe = tmpfn.Name()
}
if len(args) > 0 {
cmdArgs = append(cmdArgs, args...)
}
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
defer cancel()
var timedOut bool = false
cmd := exec.Command(exe, cmdArgs...)
cmd.Stdout = &outb
cmd.Stderr = &errb
if cmdErr := cmd.Start(); cmdErr != nil {
a.Logger.Debugln(cmdErr)
return "", cmdErr.Error(), 65, cmdErr
}
pid := int32(cmd.Process.Pid)
// custom context handling, we need to kill child procs if this is a batch script,
// otherwise it will hang forever
// the normal exec.CommandContext() doesn't work since it only kills the parent process
go func(p int32) {
<-ctx.Done()
_ = KillProc(p)
timedOut = true
}(pid)
cmdErr := cmd.Wait()
if timedOut {
stdout = CleanString(outb.String())
stderr = fmt.Sprintf("%s\nScript timed out after %d seconds", CleanString(errb.String()), timeout)
exitcode = 98
a.Logger.Debugln("Script check timeout:", ctx.Err())
} else {
stdout = CleanString(outb.String())
stderr = CleanString(errb.String())
// get the exit code
if cmdErr != nil {
if exitError, ok := cmdErr.(*exec.ExitError); ok {
if ws, ok := exitError.Sys().(syscall.WaitStatus); ok {
exitcode = ws.ExitStatus()
} else {
exitcode = defaultExitCode
}
} else {
exitcode = defaultExitCode
}
} else {
if ws, ok := cmd.ProcessState.Sys().(syscall.WaitStatus); ok {
exitcode = ws.ExitStatus()
} else {
exitcode = 0
}
}
}
return stdout, stderr, exitcode, nil
}
func SetDetached() *syscall.SysProcAttr {
return &syscall.SysProcAttr{
CreationFlags: windows.DETACHED_PROCESS | windows.CREATE_NEW_PROCESS_GROUP,
}
}
func CMD(exe string, args []string, timeout int, detached bool) (output [2]string, e error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
defer cancel()
var outb, errb bytes.Buffer
cmd := exec.CommandContext(ctx, exe, args...)
if detached {
cmd.SysProcAttr = &windows.SysProcAttr{
CreationFlags: windows.DETACHED_PROCESS | windows.CREATE_NEW_PROCESS_GROUP,
}
}
cmd.Stdout = &outb
cmd.Stderr = &errb
err := cmd.Run()
if err != nil {
return [2]string{"", ""}, fmt.Errorf("%s: %s", err, CleanString(errb.String()))
}
if ctx.Err() == context.DeadlineExceeded {
return [2]string{"", ""}, ctx.Err()
}
return [2]string{CleanString(outb.String()), CleanString(errb.String())}, nil
}
func CMDShell(shell string, cmdArgs []string, command string, timeout int, detached bool) (output [2]string, e error) {
var (
outb bytes.Buffer
errb bytes.Buffer
cmd *exec.Cmd
timedOut = false
)
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
defer cancel()
if len(cmdArgs) > 0 && command == "" {
switch shell {
case "cmd":
cmdArgs = append([]string{"/C"}, cmdArgs...)
cmd = exec.Command("cmd.exe", cmdArgs...)
case "powershell":
cmdArgs = append([]string{"-NonInteractive", "-NoProfile"}, cmdArgs...)
cmd = exec.Command("powershell.exe", cmdArgs...)
}
} else {
switch shell {
case "cmd":
cmd = exec.Command("cmd.exe")
cmd.SysProcAttr = &windows.SysProcAttr{
CmdLine: fmt.Sprintf("cmd.exe /C %s", command),
}
case "powershell":
cmd = exec.Command("Powershell", "-NonInteractive", "-NoProfile", command)
}
}
// https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags
if detached {
cmd.SysProcAttr = &windows.SysProcAttr{
CreationFlags: windows.DETACHED_PROCESS | windows.CREATE_NEW_PROCESS_GROUP,
}
}
cmd.Stdout = &outb
cmd.Stderr = &errb
cmd.Start()
pid := int32(cmd.Process.Pid)
go func(p int32) {
<-ctx.Done()
_ = KillProc(p)
timedOut = true
}(pid)
err := cmd.Wait()
if timedOut {
return [2]string{CleanString(outb.String()), CleanString(errb.String())}, ctx.Err()
}
if err != nil {
return [2]string{CleanString(outb.String()), CleanString(errb.String())}, err
}
return [2]string{CleanString(outb.String()), CleanString(errb.String())}, nil
}
// GetDisks returns a list of fixed disks
func (a *Agent) GetDisks() []trmm.Disk {
ret := make([]trmm.Disk, 0)
partitions, err := disk.Partitions(false)
if err != nil {
a.Logger.Debugln(err)
return ret
}
for _, p := range partitions {
typepath, _ := windows.UTF16PtrFromString(p.Device)
typeval, _, _ := getDriveType.Call(uintptr(unsafe.Pointer(typepath)))
// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getdrivetypea
if typeval != 3 {
continue
}
usage, err := disk.Usage(p.Mountpoint)
if err != nil {
a.Logger.Debugln(err)
continue
}
d := trmm.Disk{
Device: p.Device,
Fstype: p.Fstype,
Total: ByteCountSI(usage.Total),
Used: ByteCountSI(usage.Used),
Free: ByteCountSI(usage.Free),
Percent: int(usage.UsedPercent),
}
ret = append(ret, d)
}
return ret
}
// LoggedOnUser returns the first logged on user it finds
func (a *Agent) LoggedOnUser() string {
pyCode := `
import psutil
try:
u = psutil.users()[0].name
if u.isascii():
print(u, end='')
else:
print('notascii', end='')
except Exception as e:
print("None", end='')
`
// try with psutil first, if fails, fallback to golang
user, err := a.RunPythonCode(pyCode, 5, []string{})
if err == nil && user != "notascii" {
return user
}
users, err := wapf.ListLoggedInUsers()
if err != nil {
a.Logger.Debugln("LoggedOnUser error", err)
return "None"
}
if len(users) == 0 {
return "None"
}
for _, u := range users {
// remove the computername or domain
return strings.Split(u.FullUser(), `\`)[1]
}
return "None"
}
// ShowStatus prints windows service status
// If called from an interactive desktop, pops up a message box
// Otherwise prints to the console
func ShowStatus(version string) {
statusMap := make(map[string]string)
svcs := []string{winSvcName, meshSvcName}
for _, service := range svcs {
status, err := GetServiceStatus(service)
if err != nil {
statusMap[service] = "Not Installed"
continue
}
statusMap[service] = status
}
window := w32.GetForegroundWindow()
if window != 0 {
_, consoleProcID := w32.GetWindowThreadProcessId(window)
if w32.GetCurrentProcessId() == consoleProcID {
w32.ShowWindow(window, w32.SW_HIDE)
}
var handle w32.HWND
msg := fmt.Sprintf("Agent: %s\n\nMesh Agent: %s", statusMap[winSvcName], statusMap[meshSvcName])
w32.MessageBox(handle, msg, fmt.Sprintf("Tactical RMM v%s", version), w32.MB_OK|w32.MB_ICONINFORMATION)
} else {
fmt.Println("Tactical RMM Version", version)
fmt.Println("Tactical Agent:", statusMap[winSvcName])
fmt.Println("Mesh Agent:", statusMap[meshSvcName])
}
}
// PatchMgmnt enables/disables automatic update
// 0 - Enable Automatic Updates (Default)
// 1 - Disable Automatic Updates
// https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd939844(v=ws.10)?redirectedfrom=MSDN
func (a *Agent) PatchMgmnt(enable bool) error {
var val uint32
k, _, err := registry.CreateKey(registry.LOCAL_MACHINE, `SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU`, registry.ALL_ACCESS)
if err != nil {
return err
}
if enable {
val = 1
} else {
val = 0
}
err = k.SetDWordValue("AUOptions", val)
if err != nil {
return err
}
return nil
}
func (a *Agent) PlatVer() (string, error) {
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.ALL_ACCESS)
if err != nil {
return "n/a", err
}
defer k.Close()
dv, _, err := k.GetStringValue("DisplayVersion")
if err == nil {
return dv, nil
}
relid, _, err := k.GetStringValue("ReleaseId")
if err != nil {
return "n/a", err
}
return relid, nil
}
// EnablePing enables ping
func EnablePing() {
args := make([]string, 0)
cmd := `netsh advfirewall firewall add rule name="ICMP Allow incoming V4 echo request" protocol=icmpv4:8,any dir=in action=allow`
_, err := CMDShell("cmd", args, cmd, 10, false)
if err != nil {
fmt.Println(err)
}
}
// EnableRDP enables Remote Desktop
func EnableRDP() {
k, _, err := registry.CreateKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control\Terminal Server`, registry.ALL_ACCESS)
if err != nil {
fmt.Println(err)
}
defer k.Close()
err = k.SetDWordValue("fDenyTSConnections", 0)
if err != nil {
fmt.Println(err)
}
args := make([]string, 0)
cmd := `netsh advfirewall firewall set rule group="remote desktop" new enable=Yes`
_, cerr := CMDShell("cmd", args, cmd, 10, false)
if cerr != nil {
fmt.Println(cerr)
}
}
// DisableSleepHibernate disables sleep and hibernate
func DisableSleepHibernate() {
k, _, err := registry.CreateKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control\Session Manager\Power`, registry.ALL_ACCESS)
if err != nil {
fmt.Println(err)
}
defer k.Close()
err = k.SetDWordValue("HiberbootEnabled", 0)
if err != nil {
fmt.Println(err)
}
args := make([]string, 0)
var wg sync.WaitGroup
currents := []string{"ac", "dc"}
for _, i := range currents {
wg.Add(1)
go func(c string) {
defer wg.Done()
_, _ = CMDShell("cmd", args, fmt.Sprintf("powercfg /set%svalueindex scheme_current sub_buttons lidaction 0", c), 5, false)
_, _ = CMDShell("cmd", args, fmt.Sprintf("powercfg /x -standby-timeout-%s 0", c), 5, false)
_, _ = CMDShell("cmd", args, fmt.Sprintf("powercfg /x -hibernate-timeout-%s 0", c), 5, false)
_, _ = CMDShell("cmd", args, fmt.Sprintf("powercfg /x -disk-timeout-%s 0", c), 5, false)
_, _ = CMDShell("cmd", args, fmt.Sprintf("powercfg /x -monitor-timeout-%s 0", c), 5, false)
}(i)
}
wg.Wait()
_, _ = CMDShell("cmd", args, "powercfg -S SCHEME_CURRENT", 5, false)
}
// NewCOMObject creates a new COM object for the specifed ProgramID.
func NewCOMObject(id string) (*ole.IDispatch, error) {
unknown, err := oleutil.CreateObject(id)
if err != nil {
return nil, fmt.Errorf("unable to create initial unknown object: %v", err)
}
defer unknown.Release()
obj, err := unknown.QueryInterface(ole.IID_IDispatch)
if err != nil {
return nil, fmt.Errorf("unable to create query interface: %v", err)
}
return obj, nil
}
// SystemRebootRequired checks whether a system reboot is required.
func (a *Agent) SystemRebootRequired() (bool, error) {
regKeys := []string{
`SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired`,
}
for _, key := range regKeys {
k, err := registry.OpenKey(registry.LOCAL_MACHINE, key, registry.QUERY_VALUE)
if err == nil {
k.Close()
return true, nil
} else if err != registry.ErrNotExist {
return false, err
}
}
return false, nil
}
func (a *Agent) SendSoftware() {
sw := a.GetInstalledSoftware()
a.Logger.Debugln(sw)
payload := map[string]interface{}{"agent_id": a.AgentID, "software": sw}
_, err := a.rClient.R().SetBody(payload).Post("/api/v3/software/")
if err != nil {
a.Logger.Debugln(err)
}
}
func (a *Agent) UninstallCleanup() {
registry.DeleteKey(registry.LOCAL_MACHINE, `SOFTWARE\TacticalRMM`)
a.PatchMgmnt(false)
a.CleanupAgentUpdates()
CleanupSchedTasks()
}
func (a *Agent) AgentUpdate(url, inno, version string) {
time.Sleep(time.Duration(randRange(1, 15)) * time.Second)
a.KillHungUpdates()
a.CleanupAgentUpdates()
updater := filepath.Join(a.ProgramDir, inno)
a.Logger.Infof("Agent updating from %s to %s", a.Version, version)
a.Logger.Infoln("Downloading agent update from", url)
rClient := resty.New()
rClient.SetCloseConnection(true)
rClient.SetTimeout(15 * time.Minute)
rClient.SetDebug(a.Debug)
if len(a.Proxy) > 0 {
rClient.SetProxy(a.Proxy)
}
r, err := rClient.R().SetOutput(updater).Get(url)
if err != nil {
a.Logger.Errorln(err)
CMD("net", []string{"start", winSvcName}, 10, false)
return
}
if r.IsError() {
a.Logger.Errorln("Download failed with status code", r.StatusCode())
CMD("net", []string{"start", winSvcName}, 10, false)
return
}
dir, err := ioutil.TempDir("", "tacticalrmm")
if err != nil {
a.Logger.Errorln("Agentupdate create tempdir:", err)
CMD("net", []string{"start", winSvcName}, 10, false)
return
}
innoLogFile := filepath.Join(dir, "tacticalrmm.txt")
args := []string{"/C", updater, "/VERYSILENT", fmt.Sprintf("/LOG=%s", innoLogFile)}
cmd := exec.Command("cmd.exe", args...)
cmd.SysProcAttr = &windows.SysProcAttr{
CreationFlags: windows.DETACHED_PROCESS | windows.CREATE_NEW_PROCESS_GROUP,
}
cmd.Start()
time.Sleep(1 * time.Second)
}
func (a *Agent) osString() string {
host, _ := ps.Host()
info := host.Info()
osInf := info.OS
var arch string
switch info.Architecture {
case "x86_64":
arch = "64 bit"
case "x86":
arch = "32 bit"
}
var osFullName string
platver, err := a.PlatVer()
if err != nil {
osFullName = fmt.Sprintf("%s, %s (build %s)", osInf.Name, arch, osInf.Build)
} else {
osFullName = fmt.Sprintf("%s, %s v%s (build %s)", osInf.Name, arch, platver, osInf.Build)
}
return osFullName
}
func (a *Agent) AgentUninstall(code string) {
a.KillHungUpdates()
tacUninst := filepath.Join(a.ProgramDir, a.GetUninstallExe())
args := []string{"/C", tacUninst, "/VERYSILENT"}
cmd := exec.Command("cmd.exe", args...)
cmd.SysProcAttr = &windows.SysProcAttr{
CreationFlags: windows.DETACHED_PROCESS | windows.CREATE_NEW_PROCESS_GROUP,
}
cmd.Start()
}
func (a *Agent) addDefenderExlusions() {
code := `
Add-MpPreference -ExclusionPath 'C:\Program Files\TacticalAgent\*'
Add-MpPreference -ExclusionPath 'C:\Windows\Temp\winagent-v*.exe'
Add-MpPreference -ExclusionPath 'C:\Windows\Temp\trmm\*'
Add-MpPreference -ExclusionPath 'C:\Program Files\Mesh Agent\*'
`
_, _, _, err := a.RunScript(code, "powershell", []string{}, 20)
if err != nil {
a.Logger.Debugln(err)
}
}
// RunMigrations cleans up unused stuff from older agents
func (a *Agent) RunMigrations() {
for _, i := range []string{"nssm.exe", "nssm-x86.exe"} {
nssm := filepath.Join(a.ProgramDir, i)
if trmm.FileExists(nssm) {
os.Remove(nssm)
}
}
}
func (a *Agent) installMesh(meshbin, exe, proxy string) (string, error) {
var meshNodeID string
meshInstallArgs := []string{"-fullinstall"}
if len(proxy) > 0 {
meshProxy := fmt.Sprintf("--WebProxy=%s", proxy)
meshInstallArgs = append(meshInstallArgs, meshProxy)
}
a.Logger.Debugln("Mesh install args:", meshInstallArgs)
meshOut, meshErr := CMD(meshbin, meshInstallArgs, int(90), false)
if meshErr != nil {
fmt.Println(meshOut[0])
fmt.Println(meshOut[1])
fmt.Println(meshErr)
}
fmt.Println(meshOut)
a.Logger.Debugln("Sleeping for 5")
time.Sleep(5 * time.Second)
meshSuccess := false
for !meshSuccess {
a.Logger.Debugln("Getting mesh node id")
pMesh, pErr := CMD(exe, []string{"-nodeid"}, int(30), false)
if pErr != nil {
a.Logger.Errorln(pErr)
time.Sleep(5 * time.Second)
continue
}
if pMesh[1] != "" {
a.Logger.Errorln(pMesh[1])
time.Sleep(5 * time.Second)
continue
}
meshNodeID = StripAll(pMesh[0])
a.Logger.Debugln("Node id:", meshNodeID)
if strings.Contains(strings.ToLower(meshNodeID), "not defined") {
a.Logger.Errorln(meshNodeID)
time.Sleep(5 * time.Second)
continue
}
meshSuccess = true
}
return meshNodeID, nil
}
// ChecksRunning prevents duplicate checks from running
// Have to do it this way, can't use atomic because they can run from both rpc and tacticalagent services
func (a *Agent) ChecksRunning() bool {
running := false
procs, err := ps.Processes()
if err != nil {
return running
}
Out:
for _, process := range procs {
p, err := process.Info()
if err != nil {
continue
}
if p.PID == 0 {
continue
}
if p.Exe != a.EXE {
continue
}
for _, arg := range p.Args {
if arg == "runchecks" || arg == "checkrunner" {
running = true
break Out
}
}
}
return running
}
func (a *Agent) GetPython(force bool) {
if trmm.FileExists(a.PyBin) && !force {
return
}
var archZip string
var folder string
switch runtime.GOARCH {
case "amd64":
archZip = "py38-x64.zip"
folder = "py38-x64"
case "386":
archZip = "py38-x32.zip"
folder = "py38-x32"
}
pyFolder := filepath.Join(a.ProgramDir, folder)
pyZip := filepath.Join(a.ProgramDir, archZip)
a.Logger.Debugln(pyZip)
a.Logger.Debugln(a.PyBin)
defer os.Remove(pyZip)
if force {
os.RemoveAll(pyFolder)
}
rClient := resty.New()
rClient.SetTimeout(20 * time.Minute)
rClient.SetRetryCount(10)
rClient.SetRetryWaitTime(1 * time.Minute)
rClient.SetRetryMaxWaitTime(15 * time.Minute)
if len(a.Proxy) > 0 {
rClient.SetProxy(a.Proxy)
}
url := fmt.Sprintf("https://github.com/amidaware/rmmagent/releases/download/v2.0.0/%s", archZip)
a.Logger.Debugln(url)
r, err := rClient.R().SetOutput(pyZip).Get(url)
if err != nil {
a.Logger.Errorln("Unable to download py3.zip from github.", err)
return
}
if r.IsError() {
a.Logger.Errorln("Unable to download py3.zip from github. Status code", r.StatusCode())
return
}
err = Unzip(pyZip, a.ProgramDir)
if err != nil {
a.Logger.Errorln(err)
}
}
func (a *Agent) RecoverMesh() {
a.Logger.Infoln("Attempting mesh recovery")
@ -61,9 +797,33 @@ func (a *Agent) RecoverMesh() {
a.SyncMeshNodeID()
}
func (a *Agent) getMeshNodeID() (string, error) {
out, err := CMD(a.MeshSystemBin, []string{"-nodeid"}, 10, false)
if err != nil {
a.Logger.Debugln(err)
return "", err
}
stdout := out[0]
stderr := out[1]
if stderr != "" {
a.Logger.Debugln(stderr)
return "", err
}
if stdout == "" || strings.Contains(strings.ToLower(StripAll(stdout)), "not defined") {
a.Logger.Debugln("Failed getting mesh node id", stdout)
return "", errors.New("failed to get mesh node id")
}
return stdout, nil
}
func (a *Agent) Start(_ service.Service) error {
go a.RunRPC()
return nil
}
func (a *Agent) Stop(_ service.Service) error {
return nil

View file

@ -159,7 +159,6 @@ func (a *Agent) RunChecks(force bool) error {
type ScriptCheckResult struct {
ID int `json:"id"`
AgentID string `json:"agent_id"`
Stdout string `json:"stdout"`
Stderr string `json:"stderr"`
Retcode int `json:"retcode"`
@ -173,7 +172,6 @@ func (a *Agent) ScriptCheck(data rmm.Check, r *resty.Client) {
payload := ScriptCheckResult{
ID: data.CheckPK,
AgentID: a.AgentID,
Stdout: stdout,
Stderr: stderr,
Retcode: retcode,
@ -195,7 +193,6 @@ func (a *Agent) SendDiskCheckResult(payload DiskCheckResult, r *resty.Client) {
type DiskCheckResult struct {
ID int `json:"id"`
AgentID string `json:"agent_id"`
MoreInfo string `json:"more_info"`
PercentUsed float64 `json:"percent_used"`
Exists bool `json:"exists"`
@ -204,7 +201,6 @@ type DiskCheckResult struct {
// DiskCheck checks disk usage
func (a *Agent) DiskCheck(data rmm.Check) (payload DiskCheckResult) {
payload.ID = data.CheckPK
payload.AgentID = a.AgentID
usage, err := disk.Usage(data.Disk)
if err != nil {
@ -221,14 +217,13 @@ func (a *Agent) DiskCheck(data rmm.Check) (payload DiskCheckResult) {
}
type CPUMemResult struct {
ID int `json:"id"`
AgentID string `json:"agent_id"`
Percent int `json:"percent"`
ID int `json:"id"`
Percent int `json:"percent"`
}
// CPULoadCheck checks avg cpu load
func (a *Agent) CPULoadCheck(data rmm.Check, r *resty.Client) {
payload := CPUMemResult{ID: data.CheckPK, AgentID: a.AgentID, Percent: a.GetCPULoadAvg()}
payload := CPUMemResult{ID: data.CheckPK, Percent: a.GetCPULoadAvg()}
_, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/")
if err != nil {
a.Logger.Debugln(err)
@ -241,7 +236,7 @@ func (a *Agent) MemCheck(data rmm.Check, r *resty.Client) {
mem, _ := host.Memory()
percent := (float64(mem.Used) / float64(mem.Total)) * 100
payload := CPUMemResult{ID: data.CheckPK, AgentID: a.AgentID, Percent: int(math.Round(percent))}
payload := CPUMemResult{ID: data.CheckPK, Percent: int(math.Round(percent))}
_, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/")
if err != nil {
a.Logger.Debugln(err)
@ -249,9 +244,8 @@ func (a *Agent) MemCheck(data rmm.Check, r *resty.Client) {
}
type EventLogCheckResult struct {
ID int `json:"id"`
AgentID string `json:"agent_id"`
Log []rmm.EventLogMsg `json:"log"`
ID int `json:"id"`
Log []rmm.EventLogMsg `json:"log"`
}
func (a *Agent) EventLogCheck(data rmm.Check, r *resty.Client) {
@ -305,7 +299,7 @@ func (a *Agent) EventLogCheck(data rmm.Check, r *resty.Client) {
}
}
payload := EventLogCheckResult{ID: data.CheckPK, AgentID: a.AgentID, Log: log}
payload := EventLogCheckResult{ID: data.CheckPK, Log: log}
_, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/")
if err != nil {
a.Logger.Debugln(err)
@ -321,7 +315,6 @@ func (a *Agent) SendPingCheckResult(payload rmm.PingCheckResponse, r *resty.Clie
func (a *Agent) PingCheck(data rmm.Check) (payload rmm.PingCheckResponse) {
payload.ID = data.CheckPK
payload.AgentID = a.AgentID
out, err := DoPing(data.IP)
if err != nil {
@ -338,7 +331,6 @@ func (a *Agent) PingCheck(data rmm.Check) (payload rmm.PingCheckResponse) {
type WinSvcCheckResult struct {
ID int `json:"id"`
AgentID string `json:"agent_id"`
MoreInfo string `json:"more_info"`
Status string `json:"status"`
}
@ -352,7 +344,6 @@ func (a *Agent) SendWinSvcCheckResult(payload WinSvcCheckResult, r *resty.Client
func (a *Agent) WinSvcCheck(data rmm.Check) (payload WinSvcCheckResult) {
payload.ID = data.CheckPK
payload.AgentID = a.AgentID
status, err := GetServiceStatus(data.ServiceName)
if err != nil {

View file

@ -0,0 +1,125 @@
package events
import (
"time"
"unicode/utf16"
"unsafe"
"github.com/amidaware/rmmagent/agent/syscall"
rmm "github.com/amidaware/rmmagent/shared"
"github.com/gonutz/w32/v2"
"golang.org/x/sys/windows"
)
func GetEventLog(logName string, searchLastDays int) []rmm.EventLogMsg {
var (
oldestLog uint32
nextSize uint32
readBytes uint32
)
buf := []byte{0}
size := uint32(1)
ret := make([]rmm.EventLogMsg, 0)
startTime := time.Now().Add(time.Duration(-(time.Duration(searchLastDays)) * (24 * time.Hour)))
h := w32.OpenEventLog("", logName)
defer w32.CloseEventLog(h)
numRecords, _ := w32.GetNumberOfEventLogRecords(h)
syscall.GetOldestEventLogRecord(h, &oldestLog)
startNum := numRecords + oldestLog - 1
uid := 0
for i := startNum; i >= oldestLog; i-- {
flags := syscall.EVENTLOG_BACKWARDS_READ | syscall.EVENTLOG_SEEK_READ
err := syscall.ReadEventLog(h, flags, i, &buf[0], size, &readBytes, &nextSize)
if err != nil {
if err != windows.ERROR_INSUFFICIENT_BUFFER {
//a.Logger.Debugln(err)
break
}
buf = make([]byte, nextSize)
size = nextSize
err = syscall.ReadEventLog(h, flags, i, &buf[0], size, &readBytes, &nextSize)
if err != nil {
//a.Logger.Debugln(err)
break
}
}
r := *(*syscall.EVENTLOGRECORD)(unsafe.Pointer(&buf[0]))
timeWritten := time.Unix(int64(r.TimeWritten), 0)
if searchLastDays != 0 {
if timeWritten.Before(startTime) {
break
}
}
eventID := r.EventID & 0x0000FFFF
sourceName, _ := bytesToString(buf[unsafe.Sizeof(syscall.EVENTLOGRECORD{}):])
eventType := getEventType(r.EventType)
off := uint32(0)
args := make([]*byte, uintptr(r.NumStrings)*unsafe.Sizeof((*uint16)(nil)))
for n := 0; n < int(r.NumStrings); n++ {
args[n] = &buf[r.StringOffset+off]
_, boff := bytesToString(buf[r.StringOffset+off:])
off += boff + 2
}
var argsptr uintptr
if r.NumStrings > 0 {
argsptr = uintptr(unsafe.Pointer(&args[0]))
}
message, _ := syscall.GetResourceMessage(logName, sourceName, r.EventID, argsptr)
uid++
eventLogMsg := rmm.EventLogMsg{
Source: sourceName,
EventType: eventType,
EventID: eventID,
Message: message,
Time: timeWritten.String(),
UID: uid,
}
ret = append(ret, eventLogMsg)
}
return ret
}
// https://github.com/mackerelio/go-check-plugins/blob/ad7910fdc45ccb892b5e5fda65ba0956c2b2885d/check-windows-eventlog/lib/check-windows-eventlog.go#L219
func bytesToString(b []byte) (string, uint32) {
var i int
s := make([]uint16, len(b)/2)
for i = range s {
s[i] = uint16(b[i*2]) + uint16(b[(i*2)+1])<<8
if s[i] == 0 {
s = s[0:i]
break
}
}
return string(utf16.Decode(s)), uint32(i * 2)
}
func getEventType(et uint16) string {
switch et {
case windows.EVENTLOG_INFORMATION_TYPE:
return "INFO"
case windows.EVENTLOG_WARNING_TYPE:
return "WARNING"
case windows.EVENTLOG_ERROR_TYPE:
return "ERROR"
case windows.EVENTLOG_SUCCESS:
return "SUCCESS"
case windows.EVENTLOG_AUDIT_SUCCESS:
return "AUDIT_SUCCESS"
case windows.EVENTLOG_AUDIT_FAILURE:
return "AUDIT_FAILURE"
default:
return "Unknown"
}
}

View file

@ -251,7 +251,6 @@ func (a *Agent) Install(i *Installer) {
time.Sleep(1 * time.Second)
a.Logger.Infoln("Starting service...")
out := a.ControlService(winSvcName, "start")
if !out.Success {
a.installerMsg(out.ErrorMsg, "error", i.Silent)
}

View file

@ -20,7 +20,26 @@ import (
"golang.org/x/sys/windows/svc/mgr"
)
func GetServiceStatus(name string) (string, error) {
conn, err := mgr.Connect()
if err != nil {
return "n/a", err
}
defer conn.Disconnect()
srv, err := conn.OpenService(name)
if err != nil {
return "n/a", err
}
defer srv.Close()
q, err := srv.Query()
if err != nil {
return "n/a", err
}
return serviceStatusText(uint32(q.State)), nil
}
func (a *Agent) ControlService(name, action string) rmm.WinSvcResp {
conn, err := mgr.Connect()
@ -247,7 +266,27 @@ func serviceExists(name string) bool {
return true
}
// https://docs.microsoft.com/en-us/dotnet/api/system.serviceprocess.servicecontrollerstatus?view=dotnet-plat-ext-3.1
func serviceStatusText(num uint32) string {
switch num {
case 1:
return "stopped"
case 2:
return "start_pending"
case 3:
return "stop_pending"
case 4:
return "running"
case 5:
return "continue_pending"
case 6:
return "pause_pending"
case 7:
return "paused"
default:
return "unknown"
}
}
// https://docs.microsoft.com/en-us/dotnet/api/system.serviceprocess.servicestartmode?view=dotnet-plat-ext-3.1
func serviceStartType(num uint32) string {

View file

@ -36,4 +36,4 @@ type Agent struct {
Platform string
GoArch string
ServiceConfig *service.Config
}
}

20
agent/syscall/structs.go Normal file
View file

@ -0,0 +1,20 @@
package syscall
type EVENTLOGRECORD struct {
Length uint32
Reserved uint32
RecordNumber uint32
TimeGenerated uint32
TimeWritten uint32
EventID uint32
EventType uint16
NumStrings uint16
EventCategory uint16
ReservedFlags uint16
ClosingRecordNumber uint32
StringOffset uint32
UserSidLength uint32
UserSidOffset uint32
DataLength uint32
DataOffset uint32
}

View file

@ -0,0 +1,146 @@
package syscall
import (
"fmt"
"strings"
"syscall"
"unsafe"
"github.com/amidaware/rmmagent/agent/utils"
"github.com/gonutz/w32/v2"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry"
)
type ReadFlag uint32
const (
EVENTLOG_SEQUENTIAL_READ ReadFlag = 1 << iota
EVENTLOG_SEEK_READ
EVENTLOG_FORWARDS_READ
EVENTLOG_BACKWARDS_READ
)
const (
DONT_RESOLVE_DLL_REFERENCES uint32 = 0x0001
LOAD_LIBRARY_AS_DATAFILE uint32 = 0x0002
)
var (
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
procFormatMessageW = modkernel32.NewProc("FormatMessageW")
procGetOldestEventLogRecord = modadvapi32.NewProc("GetOldestEventLogRecord")
procLoadLibraryExW = modkernel32.NewProc("LoadLibraryExW")
procReadEventLogW = modadvapi32.NewProc("ReadEventLogW")
)
func GetOldestEventLogRecord(eventLog w32.HANDLE, oldestRecord *uint32) (err error) {
r1, _, e1 := syscall.Syscall(procGetOldestEventLogRecord.Addr(), 2, uintptr(eventLog), uintptr(unsafe.Pointer(oldestRecord)), 0)
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func ReadEventLog(eventLog w32.HANDLE, readFlags ReadFlag, recordOffset uint32, buffer *byte, numberOfBytesToRead uint32, bytesRead *uint32, minNumberOfBytesNeeded *uint32) (err error) {
r1, _, e1 := syscall.Syscall9(procReadEventLogW.Addr(), 7, uintptr(eventLog), uintptr(readFlags), uintptr(recordOffset), uintptr(unsafe.Pointer(buffer)), uintptr(numberOfBytesToRead), uintptr(unsafe.Pointer(bytesRead)), uintptr(unsafe.Pointer(minNumberOfBytesNeeded)), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func LoadLibraryEx(filename *uint16, file syscall.Handle, flags uint32) (handle syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall(procLoadLibraryExW.Addr(), 3, uintptr(unsafe.Pointer(filename)), uintptr(file), uintptr(flags))
handle = syscall.Handle(r0)
if handle == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func FormatMessage(flags uint32, source syscall.Handle, messageID uint32, languageID uint32, buffer *byte, bufferSize uint32, arguments uintptr) (numChars uint32, err error) {
r0, _, e1 := syscall.Syscall9(procFormatMessageW.Addr(), 7, uintptr(flags), uintptr(source), uintptr(messageID), uintptr(languageID), uintptr(unsafe.Pointer(buffer)), uintptr(bufferSize), uintptr(arguments), 0, 0)
numChars = uint32(r0)
if numChars == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
// https://github.com/mackerelio/go-check-plugins/blob/ad7910fdc45ccb892b5e5fda65ba0956c2b2885d/check-windows-eventlog/lib/check-windows-eventlog.go#L232
func GetResourceMessage(providerName, sourceName string, eventID uint32, argsptr uintptr) (string, error) {
regkey := fmt.Sprintf(
"SYSTEM\\CurrentControlSet\\Services\\EventLog\\%s\\%s",
providerName, sourceName)
key, err := registry.OpenKey(registry.LOCAL_MACHINE, regkey, registry.QUERY_VALUE)
if err != nil {
return "", err
}
defer key.Close()
val, _, err := key.GetStringValue("EventMessageFile")
if err != nil {
return "", err
}
val, err = registry.ExpandString(val)
if err != nil {
return "", err
}
handle, err := LoadLibraryEx(syscall.StringToUTF16Ptr(val), 0,
DONT_RESOLVE_DLL_REFERENCES|LOAD_LIBRARY_AS_DATAFILE)
if err != nil {
return "", err
}
defer syscall.CloseHandle(handle)
msgbuf := make([]byte, 1<<16)
numChars, err := FormatMessage(
syscall.FORMAT_MESSAGE_FROM_SYSTEM|
syscall.FORMAT_MESSAGE_FROM_HMODULE|
syscall.FORMAT_MESSAGE_ARGUMENT_ARRAY,
handle,
eventID,
0,
&msgbuf[0],
uint32(len(msgbuf)),
argsptr)
if err != nil {
return "", err
}
message, _ := utils.BytesToString(msgbuf[:numChars*2])
message = strings.Replace(message, "\r", "", -1)
message = strings.TrimSuffix(message, "\n")
return message, nil
}
func StringToUTF16Ptr(val string) *uint16 {
return syscall.StringToUTF16Ptr(val)
}
func CloseHandle(handle syscall.Handle) {
defer syscall.CloseHandle(handle)
}

View file

@ -15,7 +15,6 @@ import (
"time"
"github.com/amidaware/rmmagent/agent/utils"
"github.com/amidaware/taskmaster"
ps "github.com/elastic/go-sysinfo"
"github.com/go-ole/go-ole"
"github.com/go-ole/go-ole/oleutil"
@ -433,28 +432,6 @@ func SystemRebootRequired() (bool, error) {
return false, nil
}
// CleanupSchedTasks removes all tacticalrmm sched tasks during uninstall
func CleanupSchedTasks() {
conn, err := taskmaster.Connect()
if err != nil {
return
}
defer conn.Disconnect()
tasks, err := conn.GetRegisteredTasks()
if err != nil {
return
}
for _, task := range tasks {
if strings.HasPrefix(task.Name, "TacticalRMM_") {
conn.DeleteTask(fmt.Sprintf("\\%s", task.Name))
}
}
tasks.Release()
}
func KillHungUpdates() {
procs, err := ps.Processes()
if err != nil {

View file

@ -9,21 +9,25 @@ import (
"sync/atomic"
"time"
"github.com/amidaware/rmmagent/agent/events"
"github.com/amidaware/rmmagent/agent/patching"
"github.com/amidaware/rmmagent/agent/tactical/service"
"github.com/amidaware/rmmagent/agent/tasks"
rmm "github.com/amidaware/rmmagent/shared"
nats "github.com/nats-io/nats.go"
"github.com/ugorji/go/codec"
)
func RunRPC(version string) {
func RunRPC(a *rmm.AgentConfig) {
//a.Logger.Infoln("Agent service started")
go service.RunAsService()
var wg sync.WaitGroup
wg.Add(1)
opts := a.setupNatsOptions()
server := fmt.Sprintf("tls://%s:4222", a.ApiURL)
opts := service.SetupNatsOptions()
server := fmt.Sprintf("tls://%s:4222", a.APIURL)
nc, err := nats.Connect(server, opts...)
if err != nil {
a.Logger.Fatalln("RunRPC() nats.Connect()", err)
//a.Logger.Fatalln("RunRPC() nats.Connect()", err)
}
nc.Subscribe(a.AgentID, func(msg *nats.Msg) {
@ -33,7 +37,7 @@ func RunRPC(version string) {
dec := codec.NewDecoderBytes(msg.Data, &mh)
if err := dec.Decode(&payload); err != nil {
a.Logger.Errorln(err)
//a.Logger.Errorln(err)
return
}
@ -42,7 +46,7 @@ func RunRPC(version string) {
go func() {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
a.Logger.Debugln("pong")
//a.Logger.Debugln("pong")
ret.Encode("pong")
msg.Respond(resp)
}()
@ -51,9 +55,9 @@ func RunRPC(version string) {
go func(p *NatsMsg) {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
err := a.PatchMgmnt(p.PatchMgmt)
err := patching.PatchMgmnt(p.PatchMgmt)
if err != nil {
a.Logger.Errorln("PatchMgmnt:", err.Error())
//a.Logger.Errorln("PatchMgmnt:", err.Error())
ret.Encode(err.Error())
} else {
ret.Encode("ok")
@ -65,9 +69,9 @@ func RunRPC(version string) {
go func(p *NatsMsg) {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
success, err := a.CreateSchedTask(p.ScheduledTask)
success, err := tasks.CreateSchedTask(p.ScheduledTask)
if err != nil {
a.Logger.Errorln(err.Error())
//a.Logger.Errorln(err.Error())
ret.Encode(err.Error())
} else if !success {
ret.Encode("Something went wrong")
@ -81,9 +85,9 @@ func RunRPC(version string) {
go func(p *NatsMsg) {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
err := DeleteSchedTask(p.ScheduledTask.Name)
err := tasks.DeleteSchedTask(p.ScheduledTask.Name)
if err != nil {
a.Logger.Errorln(err.Error())
//a.Logger.Errorln(err.Error())
ret.Encode(err.Error())
} else {
ret.Encode("ok")
@ -95,8 +99,8 @@ func RunRPC(version string) {
go func() {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
tasks := ListSchedTasks()
a.Logger.Debugln(tasks)
tasks := tasks.ListSchedTasks()
//a.Logger.Debugln(tasks)
ret.Encode(tasks)
msg.Respond(resp)
}()
@ -106,8 +110,8 @@ func RunRPC(version string) {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
days, _ := strconv.Atoi(p.Data["days"])
evtLog := a.GetEventLog(p.Data["logname"], days)
a.Logger.Debugln(evtLog)
evtLog := events.GetEventLog(p.Data["logname"], days)
//a.Logger.Debugln(evtLog)
ret.Encode(evtLog)
msg.Respond(resp)
}(payload)
@ -117,7 +121,7 @@ func RunRPC(version string) {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
procs := a.GetProcsRPC()
a.Logger.Debugln(procs)
//a.Logger.Debugln(procs)
ret.Encode(procs)
msg.Respond(resp)
}()
@ -129,7 +133,7 @@ func RunRPC(version string) {
err := KillProc(p.ProcPID)
if err != nil {
ret.Encode(err.Error())
a.Logger.Debugln(err.Error())
//a.Logger.Debugln(err.Error())
} else {
ret.Encode("ok")
}
@ -145,7 +149,7 @@ func RunRPC(version string) {
switch runtime.GOOS {
case "windows":
out, _ := CMDShell(p.Data["shell"], []string{}, p.Data["command"], p.Timeout, false)
a.Logger.Debugln(out)
//a.Logger.Debugln(out)
if out[1] != "" {
ret.Encode(out[1])
resultData.Results = out[1]
@ -182,7 +186,7 @@ func RunRPC(version string) {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
svcs := a.GetServices()
a.Logger.Debugln(svcs)
//a.Logger.Debugln(svcs)
ret.Encode(svcs)
msg.Respond(resp)
}()
@ -192,7 +196,7 @@ func RunRPC(version string) {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
svc := a.GetServiceDetail(p.Data["name"])
a.Logger.Debugln(svc)
//a.Logger.Debugln(svc)
ret.Encode(svc)
msg.Respond(resp)
}(payload)
@ -202,7 +206,7 @@ func RunRPC(version string) {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
retData := a.ControlService(p.Data["name"], p.Data["action"])
a.Logger.Debugln(retData)
//a.Logger.Debugln(retData)
ret.Encode(retData)
msg.Respond(resp)
}(payload)
@ -212,7 +216,7 @@ func RunRPC(version string) {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
retData := a.EditService(p.Data["name"], p.Data["startType"])
a.Logger.Debugln(retData)
//a.Logger.Debugln(retData)
ret.Encode(retData)
msg.Respond(resp)
}(payload)
@ -229,7 +233,7 @@ func RunRPC(version string) {
resultData.ID = p.ID
if err != nil {
a.Logger.Debugln(err)
//a.Logger.Debugln(err)
retData = err.Error()
resultData.Retcode = 1
resultData.Stderr = err.Error()
@ -239,7 +243,7 @@ func RunRPC(version string) {
resultData.Stdout = stdout
resultData.Stderr = stderr
}
a.Logger.Debugln(retData)
//a.Logger.Debugln(retData)
ret.Encode(retData)
msg.Respond(resp)
if p.ID != 0 {
@ -266,7 +270,7 @@ func RunRPC(version string) {
retData.Retcode = retcode
}
retData.ID = p.ID
a.Logger.Debugln(retData)
//a.Logger.Debugln(retData)
ret.Encode(retData)
msg.Respond(resp)
if p.ID != 0 {
@ -282,7 +286,7 @@ func RunRPC(version string) {
switch p.Data["mode"] {
case "mesh":
a.Logger.Debugln("Recovering mesh")
//a.Logger.Debugln("Recovering mesh")
a.RecoverMesh()
}
@ -294,14 +298,14 @@ func RunRPC(version string) {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
sw := a.GetInstalledSoftware()
a.Logger.Debugln(sw)
//a.Logger.Debugln(sw)
ret.Encode(sw)
msg.Respond(resp)
}()
case "rebootnow":
go func() {
a.Logger.Debugln("Scheduling immediate reboot")
//a.Logger.Debugln("Scheduling immediate reboot")
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
ret.Encode("ok")
@ -316,15 +320,15 @@ func RunRPC(version string) {
}()
case "needsreboot":
go func() {
a.Logger.Debugln("Checking if reboot needed")
//a.Logger.Debugln("Checking if reboot needed")
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
out, err := a.SystemRebootRequired()
if err == nil {
a.Logger.Debugln("Reboot needed:", out)
//a.Logger.Debugln("Reboot needed:", out)
ret.Encode(out)
} else {
a.Logger.Debugln("Error checking if reboot needed:", err)
//a.Logger.Debugln("Error checking if reboot needed:", err)
ret.Encode(false)
}
msg.Respond(resp)
@ -333,7 +337,7 @@ func RunRPC(version string) {
go func() {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
a.Logger.Debugln("Getting sysinfo with WMI")
//a.Logger.Debugln("Getting sysinfo with WMI")
modes := []string{"agent-agentinfo", "agent-disks", "agent-wmi", "agent-publicip"}
for _, m := range modes {
a.NatsMessage(nc, m)
@ -343,16 +347,16 @@ func RunRPC(version string) {
}()
case "wmi":
go func() {
a.Logger.Debugln("Sending WMI")
//a.Logger.Debugln("Sending WMI")
a.NatsMessage(nc, "agent-wmi")
}()
case "cpuloadavg":
go func() {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
a.Logger.Debugln("Getting CPU Load Avg")
//a.Logger.Debugln("Getting CPU Load Avg")
loadAvg := a.GetCPULoadAvg()
a.Logger.Debugln("CPU Load Avg:", loadAvg)
//a.Logger.Debugln("CPU Load Avg:", loadAvg)
ret.Encode(loadAvg)
msg.Respond(resp)
}()
@ -364,27 +368,27 @@ func RunRPC(version string) {
if a.ChecksRunning() {
ret.Encode("busy")
msg.Respond(resp)
a.Logger.Debugln("Checks are already running, please wait")
//a.Logger.Debugln("Checks are already running, please wait")
} else {
ret.Encode("ok")
msg.Respond(resp)
a.Logger.Debugln("Running checks")
//a.Logger.Debugln("Running checks")
_, checkerr := CMD(a.EXE, []string{"-m", "runchecks"}, 600, false)
if checkerr != nil {
a.Logger.Errorln("RPC RunChecks", checkerr)
//a.Logger.Errorln("RPC RunChecks", checkerr)
}
}
} else {
ret.Encode("ok")
msg.Respond(resp)
a.Logger.Debugln("Running checks")
//a.Logger.Debugln("Running checks")
a.RunChecks(true)
}
}()
case "runtask":
go func(p *NatsMsg) {
a.Logger.Debugln("Running task")
//a.Logger.Debugln("Running task")
a.RunTask(p.TaskPK)
}(payload)
@ -413,9 +417,9 @@ func RunRPC(version string) {
case "getwinupdates":
go func() {
if !atomic.CompareAndSwapUint32(&getWinUpdateLocker, 0, 1) {
a.Logger.Debugln("Already checking for windows updates")
//a.Logger.Debugln("Already checking for windows updates")
} else {
a.Logger.Debugln("Checking for windows updates")
//a.Logger.Debugln("Checking for windows updates")
defer atomic.StoreUint32(&getWinUpdateLocker, 0)
a.GetWinUpdates()
}
@ -423,9 +427,9 @@ func RunRPC(version string) {
case "installwinupdates":
go func(p *NatsMsg) {
if !atomic.CompareAndSwapUint32(&installWinUpdateLocker, 0, 1) {
a.Logger.Debugln("Already installing windows updates")
//a.Logger.Debugln("Already installing windows updates")
} else {
a.Logger.Debugln("Installing windows updates", p.UpdateGUIDs)
//a.Logger.Debugln("Installing windows updates", p.UpdateGUIDs)
defer atomic.StoreUint32(&installWinUpdateLocker, 0)
a.InstallUpdates(p.UpdateGUIDs)
}
@ -435,7 +439,7 @@ func RunRPC(version string) {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
if !atomic.CompareAndSwapUint32(&agentUpdateLocker, 0, 1) {
a.Logger.Debugln("Agent update already running")
//a.Logger.Debugln("Agent update already running")
ret.Encode("updaterunning")
msg.Respond(resp)
} else {
@ -465,7 +469,7 @@ func RunRPC(version string) {
nc.Flush()
if err := nc.LastError(); err != nil {
a.Logger.Errorln(err)
//a.Logger.Errorln(err)
os.Exit(1)
}

View file

@ -9,7 +9,6 @@ import (
"github.com/amidaware/rmmagent/agent/disk"
"github.com/amidaware/rmmagent/agent/services"
"github.com/amidaware/rmmagent/agent/system"
"github.com/amidaware/rmmagent/agent/tactical"
"github.com/amidaware/rmmagent/agent/tactical/checks"
"github.com/amidaware/rmmagent/agent/utils"
"github.com/amidaware/rmmagent/agent/wmi"

View file

@ -15,7 +15,7 @@ func PostRequest(url string, body interface{}, timeout time.Duration) (resty.Res
client.SetTimeout(timeout * time.Second)
client.SetCloseConnection(true)
if len(agentConfig.Proxy) > 0 {
client.SetProxy(agentConfig.Proxy)
}
if shared.DEBUG {

View file

@ -16,7 +16,10 @@ import (
"github.com/amidaware/rmmagent/agent/services"
"github.com/amidaware/rmmagent/agent/software"
"github.com/amidaware/rmmagent/agent/system"
"github.com/amidaware/rmmagent/agent/tactical/rpc"
"github.com/amidaware/rmmagent/agent/tasks"
"github.com/amidaware/rmmagent/agent/utils"
rmm "github.com/amidaware/rmmagent/shared"
"github.com/go-resty/resty/v2"
"github.com/kardianos/service"
trmm "github.com/wh1te909/trmm-shared"
@ -109,7 +112,7 @@ func UninstallCleanup() {
registry.DeleteKey(registry.LOCAL_MACHINE, `SOFTWARE\TacticalRMM`)
patching.PatchMgmnt(false)
CleanupAgentUpdates()
system.CleanupSchedTasks()
tasks.CleanupSchedTasks()
}
func AgentUpdate(url, inno, version string) {
@ -278,7 +281,7 @@ func installMesh(meshbin, exe, proxy string) (string, error) {
}
func Start(_ service.Service) error {
go rpc.RunRPC()
go rpc.RunRPC(NewAgentConfig())
return nil
}
@ -333,4 +336,4 @@ func GetPython(force bool) {
if err != nil {
//a.Logger.Errorln(err)
}
}
}

View file

@ -1,2 +1,205 @@
package tasks
import (
"fmt"
"os"
"path/filepath"
"strings"
"time"
"github.com/amidaware/taskmaster"
)
func CreateSchedTask(st SchedTask) (bool, error) {
//a.Logger.Debugf("%+v\n", st)
conn, err := taskmaster.Connect()
if err != nil {
//a.Logger.Errorln(err)
return false, err
}
defer conn.Disconnect()
var trigger taskmaster.Trigger
var action taskmaster.ExecAction
var tasktrigger taskmaster.TaskTrigger
var now = time.Now()
if st.Trigger == "manual" {
tasktrigger = taskmaster.TaskTrigger{
Enabled: st.Enabled,
StartBoundary: now,
}
} else {
tasktrigger = taskmaster.TaskTrigger{
Enabled: st.Enabled,
StartBoundary: time.Date(st.StartYear, st.StartMonth, st.StartDay, st.StartHour, st.StartMinute, 0, 0, now.Location()),
RepetitionPattern: taskmaster.RepetitionPattern{
RepetitionInterval: st.RepetitionInterval,
RepetitionDuration: st.RepetitionDuration,
StopAtDurationEnd: st.StopAtDurationEnd,
},
}
}
if st.ExpireMinute != 0 {
tasktrigger.EndBoundary = time.Date(st.ExpireYear, st.ExpireMonth, st.ExpireDay, st.ExpireHour, st.ExpireMinute, 0, 0, now.Location())
}
var path, workdir, args string
def := conn.NewTaskDefinition()
switch st.Trigger {
case "runonce":
trigger = taskmaster.TimeTrigger{
TaskTrigger: tasktrigger,
RandomDelay: st.RandomDelay,
}
case "daily":
trigger = taskmaster.DailyTrigger{
TaskTrigger: tasktrigger,
DayInterval: st.DayInterval,
RandomDelay: st.RandomDelay,
}
case "weekly":
trigger = taskmaster.WeeklyTrigger{
TaskTrigger: tasktrigger,
DaysOfWeek: st.DaysOfWeek,
WeekInterval: st.WeekInterval,
RandomDelay: st.RandomDelay,
}
case "monthly":
trigger = taskmaster.MonthlyTrigger{
TaskTrigger: tasktrigger,
DaysOfMonth: st.DaysOfMonth,
MonthsOfYear: st.MonthsOfYear,
RandomDelay: st.RandomDelay,
RunOnLastDayOfMonth: st.RunOnLastDayOfMonth,
}
case "monthlydow":
trigger = taskmaster.MonthlyDOWTrigger{
TaskTrigger: tasktrigger,
DaysOfWeek: st.DaysOfWeek,
MonthsOfYear: st.MonthsOfYear,
WeeksOfMonth: st.WeeksOfMonth,
RandomDelay: st.RandomDelay,
}
case "manual":
trigger = taskmaster.TimeTrigger{
TaskTrigger: tasktrigger,
}
}
def.AddTrigger(trigger)
switch st.Type {
case "rmm":
path = winExeName
workdir = a.ProgramDir
args = fmt.Sprintf("-m taskrunner -p %d", st.PK)
case "schedreboot":
path = "shutdown.exe"
workdir = filepath.Join(os.Getenv("SYSTEMROOT"), "System32")
args = "/r /t 5 /f"
case "custom":
path = st.Path
workdir = st.WorkDir
args = st.Args
}
action = taskmaster.ExecAction{
Path: path,
WorkingDir: workdir,
Args: args,
}
def.AddAction(action)
def.Principal.RunLevel = taskmaster.TASK_RUNLEVEL_HIGHEST
def.Principal.LogonType = taskmaster.TASK_LOGON_SERVICE_ACCOUNT
def.Principal.UserID = "SYSTEM"
def.Settings.AllowDemandStart = true
def.Settings.AllowHardTerminate = true
def.Settings.DontStartOnBatteries = false
def.Settings.Enabled = st.Enabled
def.Settings.StopIfGoingOnBatteries = false
def.Settings.WakeToRun = true
def.Settings.MultipleInstances = st.TaskPolicy
if st.DeleteAfter {
def.Settings.DeleteExpiredTaskAfter = "PT15M"
}
if st.RunASAPAfterMissed {
def.Settings.StartWhenAvailable = true
}
_, success, err := conn.CreateTask(fmt.Sprintf("\\%s", st.Name), def, st.Overwrite)
if err != nil {
//a.Logger.Errorln(err)
return false, err
}
return success, nil
}
func DeleteSchedTask(name string) error {
conn, err := taskmaster.Connect()
if err != nil {
return err
}
defer conn.Disconnect()
err = conn.DeleteTask(fmt.Sprintf("\\%s", name))
if err != nil {
return err
}
return nil
}
func ListSchedTasks() []string {
ret := make([]string, 0)
conn, err := taskmaster.Connect()
if err != nil {
return ret
}
defer conn.Disconnect()
tasks, err := conn.GetRegisteredTasks()
if err != nil {
return ret
}
for _, task := range tasks {
ret = append(ret, task.Name)
}
tasks.Release()
return ret
}
// CleanupSchedTasks removes all tacticalrmm sched tasks during uninstall
func CleanupSchedTasks() {
conn, err := taskmaster.Connect()
if err != nil {
return
}
defer conn.Disconnect()
tasks, err := conn.GetRegisteredTasks()
if err != nil {
return
}
for _, task := range tasks {
if strings.HasPrefix(task.Name, "TacticalRMM_") {
conn.DeleteTask(fmt.Sprintf("\\%s", task.Name))
}
}
tasks.Release()
}

View file

@ -311,7 +311,26 @@ func DeleteSchedTask(name string) error {
return nil
}
// CleanupSchedTasks removes all tacticalrmm sched tasks during uninstall
func CleanupSchedTasks() {
conn, err := taskmaster.Connect()
if err != nil {
return
}
defer conn.Disconnect()
tasks, err := conn.GetRegisteredTasks()
if err != nil {
return
}
for _, task := range tasks {
if strings.HasPrefix(task.Name, "TacticalRMM_") {
conn.DeleteTask(fmt.Sprintf("\\%s", task.Name))
}
}
tasks.Release()
}
func ListSchedTasks() []string {
ret := make([]string, 0)

View file

@ -172,6 +172,36 @@ func IsValidIP(ip string) bool {
return net.ParseIP(ip) != nil
}
// StripAll strips all whitespace and newline chars
func StripAll(s string) string {
s = strings.TrimSpace(s)
s = strings.Trim(s, "\n")
s = strings.Trim(s, "\r")
return s
}
// KillProc kills a process and its children
func KillProc(pid int32) error {
p, err := process.NewProcess(pid)
if err != nil {
return err
}
children, err := p.Children()
if err == nil {
for _, child := range children {
if err := child.Kill(); err != nil {
continue
}
}
}
if err := p.Kill(); err != nil {
return err
}
return nil
}
// DjangoStringResp removes double quotes from django rest api resp
func DjangoStringResp(resp string) string {
return strings.Trim(resp, `"`)
@ -186,6 +216,13 @@ func TestTCP(addr string) error {
return nil
}
// CleanString removes invalid utf-8 byte sequences
func CleanString(s string) string {
r := strings.NewReplacer("\x00", "")
s = r.Replace(s)
return strings.ToValidUTF8(s, "")
}
// https://golangcode.com/unzip-files-in-go/
func Unzip(src, dest string) error {
r, err := zip.OpenReader(src)
@ -238,6 +275,21 @@ func Unzip(src, dest string) error {
return nil
}
// https://yourbasic.org/golang/formatting-byte-size-to-human-readable-format/
func ByteCountSI(b uint64) string {
const unit = 1024
if b < unit {
return fmt.Sprintf("%d B", b)
}
div, exp := int64(unit), 0
for n := b / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %cB",
float64(b)/float64(div), "kMGTPE"[exp])
}
func randRange(min, max int) int {
rand.Seed(time.Now().UnixNano())
return rand.Intn(max-min) + min
@ -247,6 +299,22 @@ func randomCheckDelay() {
time.Sleep(time.Duration(randRange(300, 950)) * time.Millisecond)
}
func removeWinNewLines(s string) string {
return strings.ReplaceAll(s, "\r\n", "\n")
}
func createTmpFile() (*os.File, error) {
var f *os.File
f, err := os.CreateTemp("", "trmm")
if err != nil {
cwd, err := os.Getwd()
if err != nil {
return f, err
}
f, err = os.CreateTemp(cwd, "trmm")
if err != nil {
return f, err
}
}
return f, nil
}

View file

@ -10,6 +10,7 @@ import (
"path/filepath"
"strings"
"time"
"unicode/utf16"
"github.com/amidaware/rmmagent/shared"
"github.com/go-resty/resty/v2"
@ -183,4 +184,18 @@ func Unzip(src, dest string) error {
func RandomCheckDelay() {
time.Sleep(time.Duration(RandRange(300, 950)) * time.Millisecond)
}
}
// https://github.com/mackerelio/go-check-plugins/blob/ad7910fdc45ccb892b5e5fda65ba0956c2b2885d/check-windows-eventlog/lib/check-windows-eventlog.go#L219
func BytesToString(b []byte) (string, uint32) {
var i int
s := make([]uint16, len(b)/2)
for i = range s {
s[i] = uint16(b[i*2]) + uint16(b[(i*2)+1])<<8
if s[i] == 0 {
s = s[0:i]
break
}
}
return string(utf16.Decode(s)), uint32(i * 2)
}