rmmagent/agent/tactical/service/service.go
2022-06-23 15:07:18 -07:00

587 lines
16 KiB
Go

package service
import (
"fmt"
"os"
"runtime"
"strconv"
"sync"
"sync/atomic"
"time"
"github.com/amidaware/rmmagent/agent/choco"
"github.com/amidaware/rmmagent/agent/events"
"github.com/amidaware/rmmagent/agent/network"
"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/tactical"
"github.com/amidaware/rmmagent/agent/tactical/api"
"github.com/amidaware/rmmagent/agent/tactical/checks"
"github.com/amidaware/rmmagent/agent/tactical/config"
"github.com/amidaware/rmmagent/agent/tactical/mesh"
"github.com/amidaware/rmmagent/agent/tactical/shared"
ttasks "github.com/amidaware/rmmagent/agent/tactical/tasks"
"github.com/amidaware/rmmagent/agent/tasks"
"github.com/amidaware/rmmagent/agent/utils"
ksvc "github.com/kardianos/service"
"github.com/nats-io/nats.go"
"github.com/ugorji/go/codec"
)
var (
agentUpdateLocker uint32
getWinUpdateLocker uint32
installWinUpdateLocker uint32
)
var natsCheckin = []string{"agent-hello", "agent-agentinfo", "agent-disks", "agent-winsvc", "agent-publicip", "agent-wmi"}
func RunRPC() error {
version := tactical.GetVersion()
config := config.NewAgentConfig()
go RunAsService(version)
var wg sync.WaitGroup
wg.Add(1)
opts := SetupNatsOptions()
server := fmt.Sprintf("tls://%s:4222", config.APIURL)
nc, err := nats.Connect(server, opts...)
if err != nil {
return err
}
nc.Subscribe(config.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 {
return
}
switch payload.Func {
case "ping":
go func() {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
ret.Encode("pong")
msg.Respond(resp)
}()
case "patchmgmt":
go func(p *NatsMsg) {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
err := patching.PatchMgmnt(p.PatchMgmt)
if err != nil {
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 := tasks.CreateSchedTask(p.ScheduledTask)
if err != nil {
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 := tasks.DeleteSchedTask(p.ScheduledTask.Name)
if err != nil {
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, _ := tasks.ListSchedTasks()
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, _ := events.GetEventLog(p.Data["logname"], days)
ret.Encode(evtLog)
msg.Respond(resp)
}(payload)
case "procs":
go func() {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
procs := system.GetProcsRPC()
ret.Encode(procs)
msg.Respond(resp)
}()
case "killproc":
go func(p *NatsMsg) {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
err := system.KillProc(p.ProcPID)
if err != nil {
ret.Encode(err.Error())
} else {
ret.Encode("ok")
}
msg.Respond(resp)
}(payload)
case "rawcmd":
go func(p *NatsMsg) {
var resp []byte
var resultData RawCMDResp
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
switch runtime.GOOS {
case "windows":
out, _ := system.CMDShell(p.Data["shell"], []string{}, p.Data["command"], p.Timeout, false)
if out[1] != "" {
ret.Encode(out[1])
resultData.Results = out[1]
} else {
ret.Encode(out[0])
resultData.Results = out[0]
}
default:
opts := system.NewCMDOpts()
opts.Shell = p.Data["shell"]
opts.Command = p.Data["command"]
opts.Timeout = time.Duration(p.Timeout)
out := system.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 {
api.Patch(resultData, fmt.Sprintf("/api/v3/%d/%s/histresult/", p.ID, config.AgentID))
}
}(payload)
case "winservices":
go func() {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
svcs, _, _ := services.GetServices()
ret.Encode(svcs)
msg.Respond(resp)
}()
case "winsvcdetail":
go func(p *NatsMsg) {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
svc := services.GetServiceDetail(p.Data["name"])
ret.Encode(svc)
msg.Respond(resp)
}(payload)
case "winsvcaction":
go func(p *NatsMsg) {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
retData := services.ControlService(p.Data["name"], p.Data["action"])
ret.Encode(retData)
msg.Respond(resp)
}(payload)
case "editwinsvc":
go func(p *NatsMsg) {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
retData := services.EditService(p.Data["name"], p.Data["startType"])
ret.Encode(retData)
msg.Respond(resp)
}(payload)
case "runscript":
go func(p *NatsMsg) {
var resp []byte
var retData string
var resultData RunScriptResp
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
start := time.Now()
stdout, stderr, retcode, err := system.RunScript(p.Data["code"], p.Data["shell"], p.ScriptArgs, p.Timeout)
resultData.ExecTime = time.Since(start).Seconds()
resultData.ID = p.ID
if err != nil {
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}
api.Patch(results, fmt.Sprintf("/api/v3/%d/%s/histresult/", p.ID, config.AgentID))
}
}(payload)
case "runscriptfull":
go func(p *NatsMsg) {
var resp []byte
var retData RunScriptResp
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
start := time.Now()
stdout, stderr, retcode, err := system.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}
api.Patch(results, fmt.Sprintf("/api/v3/%d/%s/histresult/", p.ID, config.AgentID))
}
}(payload)
case "recover":
go func(p *NatsMsg) {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
switch p.Data["mode"] {
case "mesh":
mesh.RecoverMesh()
}
ret.Encode("ok")
msg.Respond(resp)
}(payload)
case "softwarelist":
go func() {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
sw, _ := software.GetInstalledSoftware()
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" {
system.CMD("shutdown.exe", []string{"/r", "/t", "5", "/f"}, 15, false)
} else {
opts := system.NewCMDOpts()
opts.Command = "reboot"
system.CmdV2(opts)
}
}()
case "needsreboot":
go func() {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
out, err := system.SystemRebootRequired()
if err == nil {
ret.Encode(out)
} else {
ret.Encode(false)
}
msg.Respond(resp)
}()
case "sysinfo":
go func() {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
modes := []string{"agent-agentinfo", "agent-disks", "agent-wmi", "agent-publicip"}
for _, m := range modes {
NatsMessage(version, nc, m)
}
ret.Encode("ok")
msg.Respond(resp)
}()
case "wmi":
go func() {
NatsMessage(version, nc, "agent-wmi")
}()
case "cpuloadavg":
go func() {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
loadAvg := system.GetCPULoadAvg()
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 checks.ChecksRunning() {
ret.Encode("busy")
msg.Respond(resp)
} else {
ret.Encode("ok")
msg.Respond(resp)
_, checkerr := system.CMD(shared.GetProgramBin(), []string{"-m", "runchecks"}, 600, false)
if checkerr != nil {
}
}
} else {
ret.Encode("ok")
msg.Respond(resp)
checks.RunChecks(config.AgentID, true)
}
}()
case "runtask":
go func(p *NatsMsg) {
ttasks.RunTask(p.TaskPK)
}(payload)
case "publicip":
go func() {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
ret.Encode(network.PublicIP(config.Proxy))
msg.Respond(resp)
}()
case "installpython":
go shared.GetPython(true)
case "installchoco":
go choco.InstallChoco()
case "installwithchoco":
go func(p *NatsMsg) {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
ret.Encode("ok")
msg.Respond(resp)
out, _ := choco.InstallWithChoco(p.ChocoProgName)
results := map[string]string{"results": out}
url := fmt.Sprintf("/api/v3/%d/chocoresult/", p.PendingActionPK)
api.Patch(results, 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)
patching.GetUpdates()
}
}()
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)
patching.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)
tactical.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)
tactical.AgentUninstall(p.Code)
nc.Flush()
nc.Close()
os.Exit(0)
}(payload)
}
})
nc.Flush()
if err := nc.LastError(); err != nil {
return err
}
wg.Wait()
return nil
}
func RunAsService(version string) {
var wg sync.WaitGroup
wg.Add(1)
go AgentSvc(version)
go checks.CheckRunner(version)
wg.Wait()
}
func AgentSvc(version string) error {
config := config.NewAgentConfig()
go shared.GetPython(false)
utils.CreateTRMMTempDir()
shared.RunMigrations()
sleepDelay := utils.RandRange(14, 22)
time.Sleep(time.Duration(sleepDelay) * time.Second)
opts := SetupNatsOptions()
server := fmt.Sprintf("tls://%s:4222", config.APIURL)
nc, err := nats.Connect(server, opts...)
if err != nil {
return err
}
for _, s := range natsCheckin {
NatsMessage(version, nc, s)
time.Sleep(time.Duration(utils.RandRange(100, 400)) * time.Millisecond)
}
go mesh.SyncMeshNodeID()
time.Sleep(time.Duration(utils.RandRange(1, 3)) * time.Second)
AgentStartup(config.AgentID)
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(version, nc, "agent-hello")
case <-checkInAgentInfoTicker.C:
NatsMessage(version, nc, "agent-agentinfo")
case <-checkInWinSvcTicker.C:
NatsMessage(version, nc, "agent-winsvc")
case <-checkInPubIPTicker.C:
NatsMessage(version, nc, "agent-publicip")
case <-checkInDisksTicker.C:
NatsMessage(version, nc, "agent-disks")
case <-checkInSWTicker.C:
SendSoftware()
case <-checkInWMITicker.C:
NatsMessage(version, nc, "agent-wmi")
case <-syncMeshTicker.C:
mesh.SyncMeshNodeID()
}
}
}
func SetupNatsOptions() []nats.Option {
config := config.NewAgentConfig()
opts := make([]nats.Option, 0)
opts = append(opts, nats.Name("TacticalRMM"))
opts = append(opts, nats.UserInfo(config.AgentID, config.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 DoNatsCheckIn(version string) {
opts := SetupNatsOptions()
server := fmt.Sprintf("tls://%s:4222", config.NewAgentConfig().APIURL)
nc, err := nats.Connect(server, opts...)
if err != nil {
return
}
for _, s := range natsCheckin {
time.Sleep(time.Duration(utils.RandRange(100, 400)) * time.Millisecond)
NatsMessage(version, nc, s)
}
nc.Close()
}
func AgentStartup(agentID string) error {
payload := map[string]interface{}{"agent_id": agentID}
err := api.PostPayload(payload, "/api/v3/checkin/")
return err
}
func SendSoftware() error {
config := config.NewAgentConfig()
sw, _ := software.GetInstalledSoftware()
payload := map[string]interface{}{"agent_id": config.AgentID, "software": sw}
err := api.PostPayload(payload, "/api/v3/software/")
if err != nil {
return err
}
return nil
}
func (r IService) Start(_ ksvc.Service) error {
go RunRPC()
return nil
}
func (r IService) Stop(_ ksvc.Service) error { return nil }