refactor continues

This commit is contained in:
redanthrax 2022-06-21 16:12:14 -07:00
parent c038774f2c
commit 51f1eab127
30 changed files with 1373 additions and 474 deletions

View file

@ -3,12 +3,12 @@ https://github.com/amidaware/tacticalrmm
#### building the agent - linux
```
env CGO_ENABLED=0 GOOS=<GOOS> GOARCH=<GOARCH> go build -ldflags "-s -w"
env CGO_ENABLED=0 GOOS=<GOOS> GOARCH=<GOARCH> go build -ldflags "-s -w -X 'main.Version=v2.0.4"
```
#### building the agent - windows
```
$env:CGO_ENABLED="0";$env:GOOS="windows";$env:GOARCH="amd64"; go build -ldflags "-s -w"
$env:CGO_ENABLED="0";$env:GOOS="windows";$env:GOARCH="amd64"; go build -ldflags "-s -w -X 'main.Version=v2.0.4"
```
### tests

62
agent/choco/choco.go Normal file
View file

@ -0,0 +1,62 @@
package choco
import (
"time"
"github.com/amidaware/rmmagent/agent/system"
"github.com/amidaware/rmmagent/agent/tactical/api"
"github.com/amidaware/rmmagent/agent/tactical/config"
"github.com/go-resty/resty/v2"
)
func InstallChoco() {
config := config.NewAgentConfig()
var result ChocoInstalled
result.AgentID = config.AgentID
result.Installed = false
rClient := resty.New()
rClient.SetTimeout(30 * time.Second)
if len(config.Proxy) > 0 {
rClient.SetProxy(config.Proxy)
}
url := "/api/v3/choco/"
r, err := rClient.R().Get("https://chocolatey.org/install.ps1")
if err != nil {
api.PostPayload(result, url)
return
}
if r.IsError() {
api.PostPayload(result, url)
return
}
_, _, exitcode, err := system.RunScript(string(r.Body()), "powershell", []string{}, 900)
if err != nil {
api.PostPayload(result, url)
return
}
if exitcode != 0 {
api.PostPayload(result, url)
return
}
result.Installed = true
api.PostPayload(result, url)
}
func InstallWithChoco(name string) (string, error) {
out, err := system.CMD("choco.exe", []string{"install", name, "--yes", "--force", "--force-dependencies", "--no-progress"}, 1200, false)
if err != nil {
return err.Error(), err
}
if out[1] != "" {
return out[1], nil
}
return out[0], nil
}

6
agent/choco/structs.go Normal file
View file

@ -0,0 +1,6 @@
package choco
type ChocoInstalled struct {
AgentID string `json:"agent_id"`
Installed bool `json:"installed"`
}

57
agent/network/network.go Normal file
View file

@ -0,0 +1,57 @@
package network
import (
"net"
"time"
"github.com/amidaware/rmmagent/agent/utils"
"github.com/go-resty/resty/v2"
)
// PublicIP returns the agent's public ip
// Tries 3 times before giving up
func PublicIP(proxy string) string {
client := resty.New()
client.SetTimeout(4 * time.Second)
if len(proxy) > 0 {
client.SetProxy(proxy)
}
urls := []string{"https://icanhazip.tacticalrmm.io/", "https://icanhazip.com", "https://ifconfig.co/ip"}
ip := "error"
for _, url := range urls {
r, err := client.R().Get(url)
if err != nil {
continue
}
ip = utils.StripAll(r.String())
if !IsValidIP(ip) {
continue
}
v4 := net.ParseIP(ip)
if v4.To4() == nil {
r1, err := client.R().Get("https://ifconfig.me/ip")
if err != nil {
return ip
}
ipv4 := utils.StripAll(r1.String())
if !IsValidIP(ipv4) {
continue
}
return ipv4
}
break
}
return ip
}
// IsValidIP checks for a valid ipv4 or ipv6
func IsValidIP(ip string) bool {
return net.ParseIP(ip) != nil
}

View file

@ -1,7 +1,13 @@
package patching
import (
"fmt"
"time"
"github.com/amidaware/rmmagent/agent/patching/wua"
"github.com/amidaware/rmmagent/agent/system"
"github.com/amidaware/rmmagent/agent/tactical/api"
"github.com/amidaware/rmmagent/agent/tactical/config"
"golang.org/x/sys/windows/registry"
)
@ -59,3 +65,68 @@ func GetUpdates() (PackageList, error) {
// a.Logger.Debugln(err)
// }
}
func InstallUpdates(guids []string) {
config := config.NewAgentConfig()
session, err := wua.NewUpdateSession()
if err != nil {
return
}
defer session.Close()
for _, id := range guids {
var result WinUpdateInstallResult
result.AgentID = config.AgentID
result.UpdateID = id
query := fmt.Sprintf("UpdateID='%s'", id)
updts, err := session.GetWUAUpdateCollection(query)
if err != nil {
result.Success = false
api.Patch(result, "/api/v3/winupdates/")
continue
}
defer updts.Release()
updtCnt, err := updts.Count()
if err != nil {
result.Success = false
api.Patch(result, "/api/v3/winupdates/")
continue
}
if updtCnt == 0 {
superseded := SupersededUpdate{AgentID: config.AgentID, UpdateID: id}
api.PostPayload(superseded, "/api/v3/superseded/")
continue
}
for i := 0; i < int(updtCnt); i++ {
u, err := updts.Item(i)
if err != nil {
result.Success = false
api.Patch(result, "/api/v3/winupdates/")
continue
}
err = session.InstallWUAUpdate(u)
if err != nil {
result.Success = false
api.Patch(result, "/api/v3/winupdates/")
continue
}
result.Success = true
api.Patch(result, "/api/v3/winupdates/")
}
}
time.Sleep(5 * time.Second)
needsReboot, err := system.SystemRebootRequired()
if err != nil {
}
rebootPayload := AgentNeedsReboot{AgentID: config.AgentID, NeedsReboot: needsReboot}
err = api.Put(rebootPayload, "/api/v3/winupdates/")
if err != nil {
}
}

View file

@ -14,3 +14,19 @@ type Package struct {
Installed bool `json:"installed"`
Downloaded bool `json:"downloaded"`
}
type WinUpdateInstallResult struct {
AgentID string `json:"agent_id"`
UpdateID string `json:"guid"`
Success bool `json:"success"`
}
type SupersededUpdate struct {
AgentID string `json:"agent_id"`
UpdateID string `json:"guid"`
}
type AgentNeedsReboot struct {
AgentID string `json:"agent_id"`
NeedsReboot bool `json:"needs_reboot"`
}

View file

@ -2,9 +2,11 @@ package services
import (
"fmt"
"time"
"github.com/amidaware/rmmagent/agent/utils"
"github.com/gonutz/w32/v2"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/mgr"
)
@ -162,3 +164,136 @@ func serviceStartType(num uint32) string {
return "Unknown"
}
}
func ControlService(name, action string) WinSvcResp {
conn, err := mgr.Connect()
if err != nil {
return WinSvcResp{Success: false, ErrorMsg: err.Error()}
}
defer conn.Disconnect()
srv, err := conn.OpenService(name)
if err != nil {
return WinSvcResp{Success: false, ErrorMsg: err.Error()}
}
defer srv.Close()
var status svc.Status
switch action {
case "stop":
status, err = srv.Control(svc.Stop)
if err != nil {
return WinSvcResp{Success: false, ErrorMsg: err.Error()}
}
timeout := time.Now().Add(30 * time.Second)
for status.State != svc.Stopped {
if timeout.Before(time.Now()) {
return WinSvcResp{Success: false, ErrorMsg: "Timed out waiting for service to stop"}
}
time.Sleep(500 * time.Millisecond)
status, err = srv.Query()
if err != nil {
return WinSvcResp{Success: false, ErrorMsg: err.Error()}
}
}
return WinSvcResp{Success: true, ErrorMsg: ""}
case "start":
err := srv.Start()
if err != nil {
return WinSvcResp{Success: false, ErrorMsg: err.Error()}
}
return WinSvcResp{Success: true, ErrorMsg: ""}
}
return WinSvcResp{Success: false, ErrorMsg: "Something went wrong"}
}
func GetServiceDetail(name string) Service {
ret := Service{}
conn, err := mgr.Connect()
if err != nil {
return ret
}
defer conn.Disconnect()
srv, err := conn.OpenService(name)
if err != nil {
return ret
}
defer srv.Close()
q, err := srv.Query()
if err != nil {
return ret
}
conf, err := srv.Config()
if err != nil {
return ret
}
ret.BinPath = utils.CleanString(conf.BinaryPathName)
ret.Description = utils.CleanString(conf.Description)
ret.DisplayName = utils.CleanString(conf.DisplayName)
ret.Name = name
ret.PID = q.ProcessId
ret.StartType = serviceStartType(uint32(conf.StartType))
ret.Status = serviceStatusText(uint32(q.State))
ret.Username = utils.CleanString(conf.ServiceStartName)
ret.DelayedAutoStart = conf.DelayedAutoStart
return ret
}
func EditService(name, startupType string) WinSvcResp {
conn, err := mgr.Connect()
if err != nil {
return WinSvcResp{Success: false, ErrorMsg: err.Error()}
}
defer conn.Disconnect()
srv, err := conn.OpenService(name)
if err != nil {
return WinSvcResp{Success: false, ErrorMsg: err.Error()}
}
defer srv.Close()
conf, err := srv.Config()
if err != nil {
return WinSvcResp{Success: false, ErrorMsg: err.Error()}
}
var startType uint32
switch startupType {
case "auto":
startType = 2
case "autodelay":
startType = 2
case "manual":
startType = 3
case "disabled":
startType = 4
default:
return WinSvcResp{Success: false, ErrorMsg: "Unknown startup type provided"}
}
conf.StartType = startType
switch startupType {
case "autodelay":
conf.DelayedAutoStart = true
case "auto":
conf.DelayedAutoStart = false
}
err = srv.UpdateConfig(conf)
if err != nil {
return WinSvcResp{Success: false, ErrorMsg: err.Error()}
}
return WinSvcResp{Success: true, ErrorMsg: ""}
}

View file

@ -11,3 +11,8 @@ type Service struct {
StartType string `json:"start_type"`
DelayedAutoStart bool `json:"autodelay"`
}
type WinSvcResp struct {
Success bool `json:"success"`
ErrorMsg string `json:"errormsg"`
}

View file

@ -12,4 +12,13 @@ type CmdOptions struct {
Detached bool
}
type SchedTask struct{ Name string }
type SchedTask struct{ Name string }
type ProcessMsg struct {
Name string `json:"name"`
Pid int `json:"pid"`
MemBytes uint64 `json:"membytes"`
Username string `json:"username"`
UID int `json:"id"`
CPU string `json:"cpu_percent"`
}

View file

@ -9,11 +9,14 @@ import (
"math"
"os"
"os/exec"
"strconv"
"time"
"github.com/amidaware/rmmagent/agent/utils"
ps "github.com/elastic/go-sysinfo"
gocmd "github.com/go-cmd/cmd"
"github.com/shirou/gopsutil/cpu"
gops "github.com/shirou/gopsutil/v3/process"
)
type CmdStatus struct {
@ -196,3 +199,68 @@ func BootTime() int64 {
info := host.Info()
return info.BootTime.Unix()
}
func GetCPULoadAvg() int {
fallback := false
pyCode := `
import psutil
try:
print(int(round(psutil.cpu_percent(interval=10))), end='')
except:
print("pyerror", end='')
`
pypercent, err := RunPythonCode(pyCode, 13, []string{})
if err != nil || pypercent == "pyerror" {
fallback = true
}
i, err := strconv.Atoi(pypercent)
if err != nil {
fallback = true
}
if fallback {
percent, err := cpu.Percent(10*time.Second, false)
if err != nil {
return 0
}
return int(math.Round(percent[0]))
}
return i
}
func GetProcsRPC() []ProcessMsg {
ret := make([]ProcessMsg, 0)
procs, _ := ps.Processes()
for i, process := range procs {
p, err := process.Info()
if err != nil {
continue
}
if p.PID == 0 {
continue
}
m, _ := process.Memory()
proc, gerr := gops.NewProcess(int32(p.PID))
if gerr != nil {
continue
}
cpu, _ := proc.CPUPercent()
user, _ := proc.Username()
ret = append(ret, ProcessMsg{
Name: p.Name,
Pid: p.PID,
MemBytes: m.Resident,
Username: user,
UID: i,
CPU: fmt.Sprintf("%.1f", cpu),
})
}
return ret
}

View file

@ -37,11 +37,7 @@ func init() {
func PostPayload(payload interface{}, url string) error {
_, err := restyC.R().SetBody(payload).Post("/api/v3/syncmesh/")
if err != nil {
return err
}
return nil
return err
}
func GetResult(result interface{}, url string) (*resty.Response, error) {
@ -52,3 +48,22 @@ func GetResult(result interface{}, url string) (*resty.Response, error) {
return r, nil
}
func Get(url string) (*resty.Response, error) {
r, err := restyC.R().Get(url)
if err != nil {
return nil, err
}
return r, nil
}
func Patch(payload interface{}, url string) error {
_, err := restyC.R().SetBody(payload).Patch(url)
return err
}
func Put(payload interface{}, url string) error {
_, err := restyC.R().SetBody(payload).Put(url)
return err
}

View file

@ -0,0 +1,421 @@
package checks
import (
"bytes"
"encoding/json"
"fmt"
"math"
"strings"
"sync"
"time"
"github.com/amidaware/rmmagent/agent/events"
"github.com/amidaware/rmmagent/agent/services"
"github.com/amidaware/rmmagent/agent/system"
"github.com/amidaware/rmmagent/agent/tactical/api"
"github.com/amidaware/rmmagent/agent/utils"
ps "github.com/elastic/go-sysinfo"
"github.com/go-ping/ping"
"github.com/shirou/gopsutil/disk"
)
func CheckRunner(agentID string) error {
sleepDelay := utils.RandRange(14, 22)
time.Sleep(time.Duration(sleepDelay) * time.Second)
for {
interval, err := GetCheckInterval(agentID)
if err == nil && !ChecksRunning() {
_, err = system.CMD(system.GetProgramEXE(), []string{"-m", "checkrunner"}, 600, false)
if err != nil {
return err
}
}
time.Sleep(time.Duration(interval) * time.Second)
}
}
func GetCheckInterval(agentID string) (int, error) {
r, err := api.GetResult(CheckInfo{}, fmt.Sprintf("/api/v3/%s/checkinterval/", agentID))
if err != nil {
return 120, err
}
if r.IsError() {
return 120, fmt.Errorf("checkinterval response code: %v", r.StatusCode())
}
interval := r.Result().(*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 := 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 := api.Get(url)
if err != nil {
return err
}
if r.IsError() {
return fmt.Errorf("response error: %d", r.StatusCode())
}
if err := json.Unmarshal(r.Body(), &data); err != nil {
return err
}
var wg sync.WaitGroup
eventLogChecks := make([]Check, 0)
winServiceChecks := make([]Check, 0)
for _, check := range data.Checks {
switch check.CheckType {
case "diskspace":
wg.Add(1)
go func(c Check, wg *sync.WaitGroup) {
defer wg.Done()
utils.RandomCheckDelay()
SendDiskCheckResult(DiskCheck(c))
}(check, &wg)
case "cpuload":
wg.Add(1)
go func(c Check, wg *sync.WaitGroup) {
defer wg.Done()
CPULoadCheck(c)
}(check, &wg)
case "memory":
wg.Add(1)
go func(c Check, wg *sync.WaitGroup) {
defer wg.Done()
utils.RandomCheckDelay()
MemCheck(c)
}(check, &wg)
case "ping":
wg.Add(1)
go func(c Check, wg *sync.WaitGroup) {
defer wg.Done()
utils.RandomCheckDelay()
SendPingCheckResult(PingCheck(c))
}(check, &wg)
case "script":
wg.Add(1)
go func(c Check, wg *sync.WaitGroup) {
defer wg.Done()
utils.RandomCheckDelay()
ScriptCheck(c)
}(check, &wg)
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) {
for _, winSvcCheck := range winServiceChecks {
defer wg.Done()
SendWinSvcCheckResult(WinSvcCheck(winSvcCheck))
}
}(&wg)
}
if len(eventLogChecks) > 0 {
wg.Add(len(eventLogChecks))
go func(wg *sync.WaitGroup) {
for _, evtCheck := range eventLogChecks {
defer wg.Done()
EventLogCheck(evtCheck)
}
}(&wg)
}
wg.Wait()
return nil
}
func SendDiskCheckResult(payload DiskCheckResult) error {
err := api.Patch(payload, "/api/v3/checkrunner/")
if err != nil {
return err
}
return nil
}
func DiskCheck(data Check) (payload DiskCheckResult) {
payload.ID = data.CheckPK
usage, err := disk.Usage(data.Disk)
if err != nil {
payload.Exists = false
payload.MoreInfo = fmt.Sprintf("Disk %s does not exist", data.Disk)
}
payload.Exists = true
payload.PercentUsed = usage.UsedPercent
payload.MoreInfo = fmt.Sprintf("Total: %s, Free: %s", utils.ByteCountSI(usage.Total), utils.ByteCountSI(usage.Free))
return
}
func CPULoadCheck(data Check) error {
payload := CPUMemResult{
ID: data.CheckPK,
Percent: system.GetCPULoadAvg(),
}
err := api.PostPayload(payload, "/api/v3/checkrunner/")
if err != nil {
return err
}
return nil
}
func MemCheck(data Check) error {
host, _ := ps.Host()
mem, _ := host.Memory()
percent := (float64(mem.Used) / float64(mem.Total)) * 100
payload := CPUMemResult{ID: data.CheckPK, Percent: int(math.Round(percent))}
err := api.PostPayload(payload, "/api/v3/checkrunner/")
if err != nil {
return err
}
return nil
}
func SendPingCheckResult(payload PingCheckResponse) error {
err := api.Patch(payload, "/api/v3/checkrunner/")
if err != nil {
return err
}
return nil
}
func PingCheck(data Check) (payload PingCheckResponse) {
payload.ID = data.CheckPK
out, err := DoPing(data.IP)
if err != nil {
payload.Status = "failing"
payload.Output = err.Error()
return
}
payload.Status = out.Status
payload.Output = out.Output
return
}
func DoPing(host string) (PingResponse, error) {
var ret PingResponse
pinger, err := ping.NewPinger(host)
if err != nil {
return ret, err
}
var buf bytes.Buffer
pinger.OnRecv = func(pkt *ping.Packet) {
fmt.Fprintf(&buf, "%d bytes from %s: icmp_seq=%d time=%v\n",
pkt.Nbytes, pkt.IPAddr, pkt.Seq, pkt.Rtt)
}
pinger.OnFinish = func(stats *ping.Statistics) {
fmt.Fprintf(&buf, "\n--- %s ping statistics ---\n", stats.Addr)
fmt.Fprintf(&buf, "%d packets transmitted, %d packets received, %v%% packet loss\n",
stats.PacketsSent, stats.PacketsRecv, stats.PacketLoss)
fmt.Fprintf(&buf, "round-trip min/avg/max/stddev = %v/%v/%v/%v\n",
stats.MinRtt, stats.AvgRtt, stats.MaxRtt, stats.StdDevRtt)
}
pinger.Count = 3
pinger.Size = 24
pinger.Interval = time.Second
pinger.Timeout = 5 * time.Second
pinger.SetPrivileged(true)
err = pinger.Run()
if err != nil {
return ret, err
}
ret.Output = buf.String()
stats := pinger.Statistics()
if stats.PacketsRecv == stats.PacketsSent || stats.PacketLoss == 0 {
ret.Status = "passing"
} else {
ret.Status = "failing"
}
return ret, nil
}
func ScriptCheck(data Check) error {
start := time.Now()
stdout, stderr, retcode, _ := system.RunScript(data.Script.Code, data.Script.Shell, data.ScriptArgs, data.Timeout)
payload := ScriptCheckResult{
ID: data.CheckPK,
Stdout: stdout,
Stderr: stderr,
Retcode: retcode,
Runtime: time.Since(start).Seconds(),
}
err := api.Patch(payload, "/api/v3/checkrunner/")
if err != nil {
return err
}
return nil
}
func SendWinSvcCheckResult(payload WinSvcCheckResult) error {
err := api.Patch(payload, "/api/v3/checkrunner/")
if err != nil {
return err
}
return nil
}
func WinSvcCheck(data Check) (payload WinSvcCheckResult) {
payload.ID = data.CheckPK
status, err := services.GetServiceStatus(data.ServiceName)
if err != nil {
if data.PassNotExist {
payload.Status = "passing"
} else {
payload.Status = "failing"
}
payload.MoreInfo = err.Error()
return
}
payload.MoreInfo = fmt.Sprintf("Status: %s", status)
if status == "running" {
payload.Status = "passing"
} else if status == "start_pending" && data.PassStartPending {
payload.Status = "passing"
} else {
if data.RestartIfStopped {
ret := services.ControlService(data.ServiceName, "start")
if ret.Success {
payload.Status = "passing"
payload.MoreInfo = "Status: running"
} else {
payload.Status = "failing"
}
} else {
payload.Status = "failing"
}
}
return
}
func EventLogCheck(data Check) error {
log := make([]events.EventLogMsg, 0)
evtLog, _ := events.GetEventLog(data.LogName, data.SearchLastDays)
for _, i := range evtLog {
if i.EventType == data.EventType {
if !data.EventIDWildcard && !(int(i.EventID) == data.EventID) {
continue
}
if data.EventSource == "" && data.EventMessage == "" {
if data.EventIDWildcard {
log = append(log, i)
} else if int(i.EventID) == data.EventID {
log = append(log, i)
} else {
continue
}
}
if data.EventSource != "" && data.EventMessage != "" {
if data.EventIDWildcard {
if strings.Contains(i.Source, data.EventSource) && strings.Contains(i.Message, data.EventMessage) {
log = append(log, i)
}
} else if int(i.EventID) == data.EventID {
if strings.Contains(i.Source, data.EventSource) && strings.Contains(i.Message, data.EventMessage) {
log = append(log, i)
}
}
continue
}
if data.EventSource != "" && strings.Contains(i.Source, data.EventSource) {
if data.EventIDWildcard {
log = append(log, i)
} else if int(i.EventID) == data.EventID {
log = append(log, i)
}
}
if data.EventMessage != "" && strings.Contains(i.Message, data.EventMessage) {
if data.EventIDWildcard {
log = append(log, i)
} else if int(i.EventID) == data.EventID {
log = append(log, i)
}
}
}
}
payload := EventLogCheckResult{ID: data.CheckPK, Log: log}
err := api.Patch(payload, "/api/v3/checkrunner/")
if err != nil {
return err
}
return nil
}

View file

@ -1,175 +0,0 @@
package checks
import (
"encoding/json"
"fmt"
"sync"
"time"
"github.com/amidaware/rmmagent/agent/system"
"github.com/amidaware/rmmagent/agent/tactical/api"
"github.com/amidaware/rmmagent/agent/utils"
ps "github.com/elastic/go-sysinfo"
"github.com/go-resty/resty/v2"
)
func CheckRunner(agentID string) error {
sleepDelay := utils.RandRange(14, 22)
time.Sleep(time.Duration(sleepDelay) * time.Second)
for {
interval, err := GetCheckInterval(agentID)
if err == nil && !ChecksRunning() {
_, err = system.CMD(system.GetProgramEXE(), []string{"-m", "checkrunner"}, 600, false)
if err != nil {
return err
}
}
time.Sleep(time.Duration(interval) * time.Second)
}
return nil
}
func GetCheckInterval(agentID string) (int, error) {
r, err := api.GetResult(CheckInfo{}, fmt.Sprintf("/api/v3/%s/checkinterval/", agentID))
if err != nil {
return 120, err
}
if r.IsError() {
return 120, fmt.Errorf("checkinterval response code: %v", r.StatusCode())
}
interval := r.Result().(*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
}

View file

@ -1,6 +1,91 @@
package checks
import "github.com/amidaware/rmmagent/agent/events"
type CheckInfo struct {
AgentPK int `json:"agent"`
Interval int `json:"check_interval"`
}
}
type AllChecks struct {
CheckInfo
Checks []Check
}
type AssignedTask struct {
TaskPK int `json:"id"`
Enabled bool `json:"enabled"`
}
type Script struct {
Shell string `json:"shell"`
Code string `json:"code"`
}
type Check struct {
Script Script `json:"script"`
AssignedTasks []AssignedTask `json:"assigned_tasks"`
CheckPK int `json:"id"`
CheckType string `json:"check_type"`
Status string `json:"status"`
Threshold int `json:"threshold"`
Disk string `json:"disk"`
IP string `json:"ip"`
ScriptArgs []string `json:"script_args"`
Timeout int `json:"timeout"`
ServiceName string `json:"svc_name"`
PassStartPending bool `json:"pass_if_start_pending"`
PassNotExist bool `json:"pass_if_svc_not_exist"`
RestartIfStopped bool `json:"restart_if_stopped"`
LogName string `json:"log_name"`
EventID int `json:"event_id"`
EventIDWildcard bool `json:"event_id_is_wildcard"`
EventType string `json:"event_type"`
EventSource string `json:"event_source"`
EventMessage string `json:"event_message"`
FailWhen string `json:"fail_when"`
SearchLastDays int `json:"search_last_days"`
}
type DiskCheckResult struct {
ID int `json:"id"`
MoreInfo string `json:"more_info"`
PercentUsed float64 `json:"percent_used"`
Exists bool `json:"exists"`
}
type CPUMemResult struct {
ID int `json:"id"`
Percent int `json:"percent"`
}
type PingCheckResponse struct {
ID int `json:"id"`
AgentID string `json:"agent_id"`
Status string `json:"status"`
Output string `json:"output"`
}
type PingResponse struct {
Status string
Output string
}
type ScriptCheckResult struct {
ID int `json:"id"`
Stdout string `json:"stdout"`
Stderr string `json:"stderr"`
Retcode int `json:"retcode"`
Runtime float64 `json:"runtime"`
}
type WinSvcCheckResult struct {
ID int `json:"id"`
MoreInfo string `json:"more_info"`
Status string `json:"status"`
}
type EventLogCheckResult struct {
ID int `json:"id"`
Log []events.EventLogMsg `json:"log"`
}

View file

@ -2,10 +2,12 @@ package mesh
import (
"github.com/amidaware/rmmagent/agent/tactical/api"
"github.com/amidaware/rmmagent/agent/tactical/config"
"github.com/amidaware/rmmagent/agent/utils"
)
func SyncMeshNodeID(agentID string) error {
func SyncMeshNodeID() error {
config := config.NewAgentConfig()
id, err := GetMeshNodeID()
if err != nil {
return err
@ -13,7 +15,7 @@ func SyncMeshNodeID(agentID string) error {
payload := MeshNodeID{
Func: "syncmesh",
Agentid: agentID,
Agentid: config.AgentID,
NodeID: utils.StripAll(id),
}

View file

@ -2,9 +2,11 @@ package mesh
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"time"
"github.com/amidaware/rmmagent/agent/system"
"github.com/amidaware/rmmagent/agent/tactical/config"
@ -71,7 +73,7 @@ func getMeshBinLocation() string {
return MeshSysBin
}
func installMesh(meshbin, exe, proxy string) (string, error) {
func InstallMesh(meshbin, exe, proxy string) (string, error) {
var meshNodeID string
meshInstallArgs := []string{"-fullinstall"}
if len(proxy) > 0 {
@ -79,7 +81,6 @@ func installMesh(meshbin, exe, proxy string) (string, error) {
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])
@ -88,30 +89,24 @@ func installMesh(meshbin, exe, proxy string) (string, error) {
}
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
}
@ -121,3 +116,10 @@ func installMesh(meshbin, exe, proxy string) (string, error) {
return meshNodeID, nil
}
func RecoverMesh() {
defer system.CMD("net", []string{"start", "mesh agent"}, 60, false)
_, _ = system.CMD("net", []string{"stop", "mesh agent"}, 60, false)
ForceKillMesh()
SyncMeshNodeID()
}

View file

@ -0,0 +1,4 @@
package rpc
func RunRPC(version string) {
}

View file

@ -9,35 +9,51 @@ import (
"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/service"
"github.com/amidaware/rmmagent/agent/tactical/shared"
ttasks "github.com/amidaware/rmmagent/agent/tactical/tasks"
"github.com/amidaware/rmmagent/agent/tasks"
rmm "github.com/amidaware/rmmagent/shared"
ksvc "github.com/kardianos/service"
nats "github.com/nats-io/nats.go"
"github.com/ugorji/go/codec"
)
func RunRPC(a *rmm.AgentConfig) {
//a.Logger.Infoln("Agent service started")
go service.RunAsService()
var (
agentUpdateLocker uint32
getWinUpdateLocker uint32
installWinUpdateLocker uint32
)
func RunRPC(version string) {
config := config.NewAgentConfig()
go service.RunAsService(version)
var wg sync.WaitGroup
wg.Add(1)
opts := service.SetupNatsOptions()
server := fmt.Sprintf("tls://%s:4222", a.APIURL)
server := fmt.Sprintf("tls://%s:4222", config.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) {
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 {
//a.Logger.Errorln(err)
return
}
@ -46,7 +62,6 @@ func RunRPC(a *rmm.AgentConfig) {
go func() {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
//a.Logger.Debugln("pong")
ret.Encode("pong")
msg.Respond(resp)
}()
@ -57,7 +72,6 @@ func RunRPC(a *rmm.AgentConfig) {
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
err := patching.PatchMgmnt(p.PatchMgmt)
if err != nil {
//a.Logger.Errorln("PatchMgmnt:", err.Error())
ret.Encode(err.Error())
} else {
ret.Encode("ok")
@ -71,7 +85,6 @@ func RunRPC(a *rmm.AgentConfig) {
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
success, err := tasks.CreateSchedTask(p.ScheduledTask)
if err != nil {
//a.Logger.Errorln(err.Error())
ret.Encode(err.Error())
} else if !success {
ret.Encode("Something went wrong")
@ -87,7 +100,6 @@ func RunRPC(a *rmm.AgentConfig) {
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
err := tasks.DeleteSchedTask(p.ScheduledTask.Name)
if err != nil {
//a.Logger.Errorln(err.Error())
ret.Encode(err.Error())
} else {
ret.Encode("ok")
@ -99,8 +111,7 @@ func RunRPC(a *rmm.AgentConfig) {
go func() {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
tasks := tasks.ListSchedTasks()
//a.Logger.Debugln(tasks)
tasks, _ := tasks.ListSchedTasks()
ret.Encode(tasks)
msg.Respond(resp)
}()
@ -110,8 +121,7 @@ func RunRPC(a *rmm.AgentConfig) {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
days, _ := strconv.Atoi(p.Data["days"])
evtLog := events.GetEventLog(p.Data["logname"], days)
//a.Logger.Debugln(evtLog)
evtLog, _ := events.GetEventLog(p.Data["logname"], days)
ret.Encode(evtLog)
msg.Respond(resp)
}(payload)
@ -120,7 +130,7 @@ func RunRPC(a *rmm.AgentConfig) {
go func() {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
procs := a.GetProcsRPC()
procs := system.GetProcsRPC()
//a.Logger.Debugln(procs)
ret.Encode(procs)
msg.Respond(resp)
@ -130,10 +140,9 @@ func RunRPC(a *rmm.AgentConfig) {
go func(p *NatsMsg) {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
err := KillProc(p.ProcPID)
err := system.KillProc(p.ProcPID)
if err != nil {
ret.Encode(err.Error())
//a.Logger.Debugln(err.Error())
} else {
ret.Encode("ok")
}
@ -143,13 +152,12 @@ func RunRPC(a *rmm.AgentConfig) {
case "rawcmd":
go func(p *NatsMsg) {
var resp []byte
var resultData rmm.RawCMDResp
var resultData 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)
out, _ := system.CMDShell(p.Data["shell"], []string{}, p.Data["command"], p.Timeout, false)
if out[1] != "" {
ret.Encode(out[1])
resultData.Results = out[1]
@ -158,11 +166,11 @@ func RunRPC(a *rmm.AgentConfig) {
resultData.Results = out[0]
}
default:
opts := a.NewCMDOpts()
opts := system.NewCMDOpts()
opts.Shell = p.Data["shell"]
opts.Command = p.Data["command"]
opts.Timeout = time.Duration(p.Timeout)
out := a.CmdV2(opts)
out := system.CmdV2(opts)
tmp := ""
if len(out.Stdout) > 0 {
tmp += out.Stdout
@ -177,7 +185,7 @@ func RunRPC(a *rmm.AgentConfig) {
msg.Respond(resp)
if p.ID != 0 {
a.rClient.R().SetBody(resultData).Patch(fmt.Sprintf("/api/v3/%d/%s/histresult/", p.ID, a.AgentID))
api.Patch(resultData, fmt.Sprintf("/api/v3/%d/%s/histresult/", p.ID, config.AgentID))
}
}(payload)
@ -185,8 +193,7 @@ func RunRPC(a *rmm.AgentConfig) {
go func() {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
svcs := a.GetServices()
//a.Logger.Debugln(svcs)
svcs, _, _ := services.GetServices()
ret.Encode(svcs)
msg.Respond(resp)
}()
@ -195,8 +202,7 @@ func RunRPC(a *rmm.AgentConfig) {
go func(p *NatsMsg) {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
svc := a.GetServiceDetail(p.Data["name"])
//a.Logger.Debugln(svc)
svc := services.GetServiceDetail(p.Data["name"])
ret.Encode(svc)
msg.Respond(resp)
}(payload)
@ -205,8 +211,7 @@ func RunRPC(a *rmm.AgentConfig) {
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)
retData := services.ControlService(p.Data["name"], p.Data["action"])
ret.Encode(retData)
msg.Respond(resp)
}(payload)
@ -215,8 +220,7 @@ func RunRPC(a *rmm.AgentConfig) {
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)
retData := services.EditService(p.Data["name"], p.Data["startType"])
ret.Encode(retData)
msg.Respond(resp)
}(payload)
@ -225,15 +229,14 @@ func RunRPC(a *rmm.AgentConfig) {
go func(p *NatsMsg) {
var resp []byte
var retData string
var resultData rmm.RunScriptResp
var resultData 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)
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 {
//a.Logger.Debugln(err)
retData = err.Error()
resultData.Retcode = 1
resultData.Stderr = err.Error()
@ -248,17 +251,17 @@ func RunRPC(a *rmm.AgentConfig) {
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))
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 rmm.RunScriptResp
var retData 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)
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 {
@ -275,7 +278,7 @@ func RunRPC(a *rmm.AgentConfig) {
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))
api.Patch(results, fmt.Sprintf("/api/v3/%d/%s/histresult/", p.ID, config.AgentID))
}
}(payload)
@ -286,8 +289,7 @@ func RunRPC(a *rmm.AgentConfig) {
switch p.Data["mode"] {
case "mesh":
//a.Logger.Debugln("Recovering mesh")
a.RecoverMesh()
mesh.RecoverMesh()
}
ret.Encode("ok")
@ -297,8 +299,7 @@ func RunRPC(a *rmm.AgentConfig) {
go func() {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
sw := a.GetInstalledSoftware()
//a.Logger.Debugln(sw)
sw, _ := software.GetInstalledSoftware()
ret.Encode(sw)
msg.Respond(resp)
}()
@ -311,24 +312,21 @@ func RunRPC(a *rmm.AgentConfig) {
ret.Encode("ok")
msg.Respond(resp)
if runtime.GOOS == "windows" {
CMD("shutdown.exe", []string{"/r", "/t", "5", "/f"}, 15, false)
system.CMD("shutdown.exe", []string{"/r", "/t", "5", "/f"}, 15, false)
} else {
opts := a.NewCMDOpts()
opts := system.NewCMDOpts()
opts.Command = "reboot"
a.CmdV2(opts)
system.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()
out, err := system.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)
@ -337,26 +335,22 @@ func RunRPC(a *rmm.AgentConfig) {
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)
service.NatsMessage(version, nc, m)
}
ret.Encode("ok")
msg.Respond(resp)
}()
case "wmi":
go func() {
//a.Logger.Debugln("Sending WMI")
a.NatsMessage(nc, "agent-wmi")
service.NatsMessage(version, 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)
loadAvg := system.GetCPULoadAvg()
ret.Encode(loadAvg)
msg.Respond(resp)
}()
@ -365,54 +359,49 @@ func RunRPC(a *rmm.AgentConfig) {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
if runtime.GOOS == "windows" {
if a.ChecksRunning() {
if checks.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)
_, checkerr := system.CMD(system.GetProgramEXE(), []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)
checks.RunChecks(config.AgentID, true)
}
}()
case "runtask":
go func(p *NatsMsg) {
//a.Logger.Debugln("Running task")
a.RunTask(p.TaskPK)
ttasks.RunTask(p.TaskPK)
}(payload)
case "publicip":
go func() {
var resp []byte
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
ret.Encode(a.PublicIP())
ret.Encode(network.PublicIP(config.Proxy))
msg.Respond(resp)
}()
case "installpython":
go a.GetPython(true)
go shared.GetPython(true)
case "installchoco":
go a.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, _ := a.InstallWithChoco(p.ChocoProgName)
out, _ := choco.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)
api.Patch(results, url)
}(payload)
case "getwinupdates":
go func() {
@ -421,7 +410,7 @@ func RunRPC(a *rmm.AgentConfig) {
} else {
//a.Logger.Debugln("Checking for windows updates")
defer atomic.StoreUint32(&getWinUpdateLocker, 0)
a.GetWinUpdates()
patching.GetUpdates()
}
}()
case "installwinupdates":
@ -431,7 +420,7 @@ func RunRPC(a *rmm.AgentConfig) {
} else {
//a.Logger.Debugln("Installing windows updates", p.UpdateGUIDs)
defer atomic.StoreUint32(&installWinUpdateLocker, 0)
a.InstallUpdates(p.UpdateGUIDs)
patching.InstallUpdates(p.UpdateGUIDs)
}
}(payload)
case "agentupdate":
@ -445,7 +434,7 @@ func RunRPC(a *rmm.AgentConfig) {
} else {
ret.Encode("ok")
msg.Respond(resp)
a.AgentUpdate(p.Data["url"], p.Data["inno"], p.Data["version"])
tactical.AgentUpdate(p.Data["url"], p.Data["inno"], p.Data["version"])
atomic.StoreUint32(&agentUpdateLocker, 0)
nc.Flush()
nc.Close()
@ -459,7 +448,7 @@ func RunRPC(a *rmm.AgentConfig) {
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
ret.Encode("ok")
msg.Respond(resp)
a.AgentUninstall(p.Code)
tactical.AgentUninstall(p.Code)
nc.Flush()
nc.Close()
os.Exit(0)
@ -475,3 +464,8 @@ func RunRPC(a *rmm.AgentConfig) {
wg.Wait()
}
func Start(version string, _ ksvc.Service) error {
go RunRPC(version)
return nil
}

View file

@ -17,4 +17,16 @@ type NatsMsg struct {
PatchMgmt bool `json:"patch_mgmt"`
ID int `json:"id"`
Code string `json:"code"`
}
}
type RawCMDResp struct {
Results string `json:"results"`
}
type RunScriptResp struct {
Stdout string `json:"stdout"`
Stderr string `json:"stderr"`
Retcode int `json:"retcode"`
ExecTime float64 `json:"execution_time"`
ID int `json:"id"`
}

View file

@ -7,53 +7,53 @@ import (
"time"
"github.com/amidaware/rmmagent/agent/disk"
"github.com/amidaware/rmmagent/agent/network"
"github.com/amidaware/rmmagent/agent/services"
"github.com/amidaware/rmmagent/agent/system"
"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"
"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) {
func RunAsService(version string) {
var wg sync.WaitGroup
wg.Add(1)
go AgentSvc(version)
go checks.CheckRunner(agentID)
go checks.CheckRunner(version)
wg.Wait()
}
func AgentSvc(version string) {
config := tactical.NewAgentConfig()
go tactical.GetPython(false)
config := config.NewAgentConfig()
go shared.GetPython(false)
utils.CreateTRMMTempDir()
tactical.RunMigrations()
shared.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)
opts := SetupNatsOptions()
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)
NatsMessage(version, nc, s)
time.Sleep(time.Duration(utils.RandRange(100, 400)) * time.Millisecond)
}
go tactical.SyncMeshNodeID()
go mesh.SyncMeshNodeID()
time.Sleep(time.Duration(utils.RandRange(1, 3)) * time.Second)
AgentStartup(config.AgentID)
tactical.SendSoftware()
shared.SendSoftware()
checkInHelloTicker := time.NewTicker(time.Duration(utils.RandRange(30, 60)) * time.Second)
checkInAgentInfoTicker := time.NewTicker(time.Duration(utils.RandRange(200, 400)) * time.Second)
@ -67,29 +67,30 @@ func AgentSvc(version string) {
for {
select {
case <-checkInHelloTicker.C:
NatsMessage(config.AgentID, version, nc, "agent-hello")
NatsMessage(version, nc, "agent-hello")
case <-checkInAgentInfoTicker.C:
NatsMessage(config.AgentID, version, nc, "agent-agentinfo")
NatsMessage(version, nc, "agent-agentinfo")
case <-checkInWinSvcTicker.C:
NatsMessage(config.AgentID, version, nc, "agent-winsvc")
NatsMessage(version, nc, "agent-winsvc")
case <-checkInPubIPTicker.C:
NatsMessage(config.AgentID, version, nc, "agent-publicip")
NatsMessage(version, nc, "agent-publicip")
case <-checkInDisksTicker.C:
NatsMessage(config.AgentID, version, nc, "agent-disks")
NatsMessage(version, nc, "agent-disks")
case <-checkInSWTicker.C:
tactical.SendSoftware()
shared.SendSoftware()
case <-checkInWMITicker.C:
NatsMessage(config.AgentID, version, nc, "agent-wmi")
NatsMessage(version, nc, "agent-wmi")
case <-syncMeshTicker.C:
tactical.SyncMeshNodeID()
mesh.SyncMeshNodeID()
}
}
}
func SetupNatsOptions(agentID string, token string) []nats.Option {
func SetupNatsOptions() []nats.Option {
config := config.NewAgentConfig()
opts := make([]nats.Option, 0)
opts = append(opts, nats.Name("TacticalRMM"))
opts = append(opts, nats.UserInfo(agentID, token))
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))
@ -97,21 +98,23 @@ func SetupNatsOptions(agentID string, token string) []nats.Option {
return opts
}
func NatsMessage(agentID string, version string, nc *nats.Conn, mode string) {
func NatsMessage(version string, nc *nats.Conn, mode string) {
config := config.NewAgentConfig()
var resp []byte
var payload interface{}
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
switch mode {
case "agent-hello":
payload = trmm.CheckInNats{
Agentid: agentID,
payload = CheckInNats{
Agentid: config.AgentID,
Version: version,
}
case "agent-winsvc":
payload = trmm.WinSvcNats{
Agentid: agentID,
WinSvcs: services.GetServices(),
svcs, _, _ := services.GetServices()
payload = WinSvcNats{
Agentid: config.AgentID,
WinSvcs: svcs,
}
case "agent-agentinfo":
osinfo := system.OsString()
@ -119,8 +122,8 @@ func NatsMessage(agentID string, version string, nc *nats.Conn, mode string) {
if err != nil {
reboot = false
}
payload = trmm.AgentInfoNats{
Agentid: agentID,
payload = AgentInfoNats{
Agentid: config.AgentID,
Username: system.LoggedOnUser(),
Hostname: system.GetHostname(),
OS: osinfo,
@ -131,48 +134,47 @@ func NatsMessage(agentID string, version string, nc *nats.Conn, mode string) {
GoArch: runtime.GOARCH,
}
case "agent-wmi":
payload = trmm.WinWMINats{
Agentid: agentID,
WMI: wmi.GetWMIInfo(),
wmiinfo, _ := wmi.GetWMIInfo()
payload = WinWMINats{
Agentid: config.AgentID,
WMI: wmiinfo,
}
case "agent-disks":
payload = trmm.WinDisksNats{
Agentid: agentID,
Disks: disk.GetDisks(),
disks, _ := disk.GetDisks()
payload = WinDisksNats{
Agentid: config.AgentID,
Disks: disks,
}
case "agent-publicip":
payload = trmm.PublicIPNats{
Agentid: agentID,
PublicIP: a.PublicIP(),
payload = PublicIPNats{
Agentid: config.AgentID,
PublicIP: network.PublicIP(config.Proxy),
}
}
//a.Logger.Debugln(mode, payload)
ret.Encode(payload)
nc.PublishRequest(a.AgentID, mode, resp)
nc.PublishRequest(config.AgentID, mode, resp)
}
func DoNatsCheckIn() {
func DoNatsCheckIn(version string) {
opts := SetupNatsOptions()
server := fmt.Sprintf("tls://%s:4222", a.ApiURL)
server := fmt.Sprintf("tls://%s:4222", config.NewAgentConfig().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)
NatsMessage(version, nc, s)
}
nc.Close()
}
func AgentStartup(agentID string) {
url := "/api/v3/checkin/"
func AgentStartup(agentID string) error {
payload := map[string]interface{}{"agent_id": agentID}
_, err := tactical.PostRequest(url, payload, 15)
if err != nil {
//a.Logger.Debugln("AgentStartup()", err)
}
err := api.PostPayload(payload, "/api/v3/checkin/")
return err
}

View file

@ -0,0 +1,43 @@
package service
import (
"github.com/amidaware/rmmagent/agent/disk"
"github.com/amidaware/rmmagent/agent/services"
)
type WinSvcNats struct {
Agentid string `json:"agent_id"`
WinSvcs []services.Service `json:"services"`
}
type CheckInNats struct {
Agentid string `json:"agent_id"`
Version string `json:"version"`
}
type AgentInfoNats struct {
Agentid string `json:"agent_id"`
Username string `json:"logged_in_username"`
Hostname string `json:"hostname"`
OS string `json:"operating_system"`
Platform string `json:"plat"`
TotalRAM float64 `json:"total_ram"`
BootTime int64 `json:"boot_time"`
RebootNeeded bool `json:"needs_reboot"`
GoArch string `json:"goarch"`
}
type WinWMINats struct {
Agentid string `json:"agent_id"`
WMI interface{} `json:"wmi"`
}
type WinDisksNats struct {
Agentid string `json:"agent_id"`
Disks []disk.Disk `json:"disks"`
}
type PublicIPNats struct {
Agentid string `json:"agent_id"`
PublicIP string `json:"public_ip"`
}

View file

@ -0,0 +1,19 @@
package shared
import (
"github.com/amidaware/rmmagent/agent/software"
"github.com/amidaware/rmmagent/agent/tactical/api"
"github.com/amidaware/rmmagent/agent/tactical/config"
)
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
}

View file

@ -0,0 +1,70 @@
package shared
import (
"fmt"
"os"
"path/filepath"
"runtime"
"time"
"github.com/amidaware/rmmagent/agent/system"
"github.com/amidaware/rmmagent/agent/tactical/config"
"github.com/amidaware/rmmagent/agent/utils"
"github.com/go-resty/resty/v2"
)
func GetPython(force bool) {
if utils.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)
defer os.Remove(pyZip)
if force {
os.RemoveAll(pyFolder)
}
config := 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)
r, err := rClient.R().SetOutput(pyZip).Get(url)
if err != nil {
return
}
if r.IsError() {
return
}
err = utils.Unzip(pyZip, system.GetProgramDirectory())
if err != nil {
}
}
func RunMigrations() {
for _, i := range []string{"nssm.exe", "nssm-x86.exe"} {
nssm := filepath.Join(system.GetProgramDirectory(), i)
if utils.FileExists(nssm) {
os.Remove(nssm)
}
}
}

View file

@ -0,0 +1 @@
package tactical

View file

@ -1 +1,2 @@
package tactical

View file

@ -1,17 +1,2 @@
package tactical
package tactical_test
import "testing"
func TestSyncMeshNodeID(t *testing.T) {
agentConfig := NewAgentConfig()
if agentConfig.AgentID == "" {
t.Fatal("Could not get AgentID")
}
result := SyncMeshNodeID()
if !result {
t.Fatal("SyncMeshNodeID resulted in error")
}
t.Log("Synced mesh node id")
}

View file

@ -6,7 +6,6 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
@ -14,13 +13,10 @@ import (
"github.com/amidaware/rmmagent/agent/services"
"github.com/amidaware/rmmagent/agent/system"
"github.com/amidaware/rmmagent/agent/tactical/config"
"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"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry"
)
@ -37,9 +33,6 @@ func AgentUpdate(url, inno, version string) {
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 := config.NewAgentConfig()
rClient := resty.New()
rClient.SetCloseConnection(true)
@ -51,20 +44,17 @@ func AgentUpdate(url, inno, version string) {
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
}
@ -84,7 +74,6 @@ func CleanupAgentUpdates() {
pd := filepath.Join(os.Getenv("ProgramFiles"), system.ProgFilesName)
cderr := os.Chdir(pd)
if cderr != nil {
//a.Logger.Errorln(cderr)
return
}
@ -97,7 +86,6 @@ func CleanupAgentUpdates() {
cderr = os.Chdir(os.Getenv("TMP"))
if cderr != nil {
//a.Logger.Errorln(cderr)
return
}
@ -135,122 +123,3 @@ func GetUninstallExe() string {
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(NewAgentConfig())
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)
}
}

View file

@ -0,0 +1,25 @@
package tasks
type AutomatedTask struct {
ID int `json:"id"`
TaskActions []TaskAction `json:"task_actions"`
Enabled bool `json:"enabled"`
ContinueOnError bool `json:"continue_on_error"`
}
type TaskAction struct {
ActionType string `json:"type"`
Command string `json:"command"`
Shell string `json:"shell"`
ScriptName string `json:"script_name"`
Code string `json:"code"`
Args []string `json:"script_args"`
Timeout int `json:"timeout"`
}
type TaskResult struct {
Stdout string `json:"stdout"`
Stderr string `json:"stderr"`
RetCode int `json:"retcode"`
ExecTime float64 `json:"execution_time"`
}

View file

@ -0,0 +1,95 @@
package tasks
import (
"encoding/json"
"fmt"
"time"
"github.com/amidaware/rmmagent/agent/system"
"github.com/amidaware/rmmagent/agent/tactical/api"
"github.com/amidaware/rmmagent/agent/tactical/config"
)
func RunTask(id int) error {
config := config.NewAgentConfig()
data := AutomatedTask{}
url := fmt.Sprintf("/api/v3/%d/%s/taskrunner/", id, config.AgentID)
r1, gerr := api.Get(url)
if gerr != nil {
return gerr
}
if r1.IsError() {
return nil
}
if err := json.Unmarshal(r1.Body(), &data); err != nil {
return err
}
start := time.Now()
payload := TaskResult{}
// loop through all task actions
for _, action := range data.TaskActions {
action_start := time.Now()
if action.ActionType == "script" {
stdout, stderr, retcode, err := system.RunScript(action.Code, action.Shell, action.Args, action.Timeout)
if err != nil {
}
// add text to stdout showing which script ran if more than 1 script
action_exec_time := time.Since(action_start).Seconds()
if len(data.TaskActions) > 1 {
payload.Stdout += fmt.Sprintf("\n------------\nRunning Script: %s. Execution Time: %f\n------------\n\n", action.ScriptName, action_exec_time)
}
// save results
payload.Stdout += stdout
payload.Stderr += stderr
payload.RetCode = retcode
if !data.ContinueOnError && stderr != "" {
break
}
} else if action.ActionType == "cmd" {
// out[0] == stdout, out[1] == stderr
out, err := system.CMDShell(action.Shell, []string{}, action.Command, action.Timeout, false)
if err != nil {
}
if len(data.TaskActions) > 1 {
action_exec_time := time.Since(action_start).Seconds()
// add text to stdout showing which script ran
payload.Stdout += fmt.Sprintf("\n------------\nRunning Command: %s. Execution Time: %f\n------------\n\n", action.Command, action_exec_time)
}
// save results
payload.Stdout += out[0]
payload.Stderr += out[1]
// no error
if out[1] == "" {
payload.RetCode = 0
} else {
payload.RetCode = 1
if !data.ContinueOnError {
break
}
}
} else {
}
}
payload.ExecTime = time.Since(start).Seconds()
perr := api.Patch(payload, url)
if perr != nil {
return perr
}
return nil
}

View file

@ -14,17 +14,17 @@ package main
import (
"flag"
"fmt"
"github.com/amidaware/rmmagent/agent"
"github.com/kardianos/service"
"github.com/sirupsen/logrus"
"os"
"os/user"
"path/filepath"
"runtime"
"github.com/amidaware/rmmagent/agent"
"github.com/kardianos/service"
"github.com/sirupsen/logrus"
)
var (
version = "2.0.4"
version = "development"
log = logrus.New()
logFile *os.File
)