/* Copyright 2023 AmidaWare Inc. Licensed under the Tactical RMM License Version 1.0 (the “License”). You may only use the Licensed Software in accordance with the License. A copy of the License is available at: https://license.tacticalrmm.com */ package agent import ( "encoding/json" "fmt" "math" "runtime" "strings" "sync" "time" rmm "github.com/amidaware/rmmagent/shared" ps "github.com/elastic/go-sysinfo" "github.com/go-resty/resty/v2" "github.com/shirou/gopsutil/v3/disk" ) func (a *Agent) CheckRunner() { sleepDelay := randRange(14, 22) a.Logger.Debugf("CheckRunner() init sleeping for %v seconds", sleepDelay) time.Sleep(time.Duration(sleepDelay) * time.Second) for { interval, err := a.GetCheckInterval() if err == nil && !a.ChecksRunning() { if runtime.GOOS == "windows" { _, err = CMD(a.EXE, []string{"-m", "checkrunner"}, 600, false) if err != nil { a.Logger.Errorln("Checkrunner RunChecks", err) } } else { a.RunChecks(false) } } a.Logger.Debugln("Checkrunner sleeping for", interval) time.Sleep(time.Duration(interval) * time.Second) } } func (a *Agent) GetCheckInterval() (int, error) { r, err := a.rClient.R().SetResult(&rmm.CheckInfo{}).Get(fmt.Sprintf("/api/v3/%s/checkinterval/", a.AgentID)) if err != nil { a.Logger.Debugln(err) return 120, err } if r.IsError() { a.Logger.Debugln("Checkinterval response code:", r.StatusCode()) return 120, fmt.Errorf("checkinterval response code: %v", r.StatusCode()) } interval := r.Result().(*rmm.CheckInfo).Interval return interval, nil } func (a *Agent) RunChecks(force bool) error { data := rmm.AllChecks{} var url string if force { url = fmt.Sprintf("/api/v3/%s/runchecks/", a.AgentID) } else { url = fmt.Sprintf("/api/v3/%s/checkrunner/", a.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() 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 } type ScriptCheckResult struct { ID int `json:"id"` AgentID string `json:"agent_id"` Stdout string `json:"stdout"` Stderr string `json:"stderr"` Retcode int `json:"retcode"` Runtime float64 `json:"runtime"` } // ScriptCheck runs either bat, powershell or python script func (a *Agent) ScriptCheck(data rmm.Check, r *resty.Client) { start := time.Now() stdout, stderr, retcode, _ := a.RunScript(data.Script.Code, data.Script.Shell, data.ScriptArgs, data.Timeout, data.Script.RunAsUser, data.EnvVars) payload := ScriptCheckResult{ ID: data.CheckPK, AgentID: a.AgentID, Stdout: stdout, Stderr: stderr, Retcode: retcode, Runtime: time.Since(start).Seconds(), } _, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/") if err != nil { a.Logger.Debugln(err) } } func (a *Agent) SendDiskCheckResult(payload DiskCheckResult, r *resty.Client) { _, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/") if err != nil { a.Logger.Debugln(err) } } 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"` } // 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 { payload.Exists = false payload.MoreInfo = fmt.Sprintf("Disk %s does not exist", data.Disk) a.Logger.Debugln("Disk", data.Disk, err) return } payload.Exists = true payload.PercentUsed = usage.UsedPercent payload.MoreInfo = fmt.Sprintf("Total: %s, Free: %s", ByteCountSI(usage.Total), ByteCountSI(usage.Free)) return } type CPUMemResult struct { ID int `json:"id"` AgentID string `json:"agent_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()} _, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/") if err != nil { a.Logger.Debugln(err) } } // MemCheck checks mem percentage func (a *Agent) MemCheck(data rmm.Check, r *resty.Client) { host, _ := ps.Host() mem, _ := host.Memory() percent := (float64(mem.Used) / float64(mem.Total)) * 100 payload := CPUMemResult{ID: data.CheckPK, AgentID: a.AgentID, Percent: int(math.Round(percent))} _, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/") if err != nil { a.Logger.Debugln(err) } } type EventLogCheckResult struct { ID int `json:"id"` AgentID string `json:"agent_id"` Log []rmm.EventLogMsg `json:"log"` } func (a *Agent) EventLogCheck(data rmm.Check, r *resty.Client) { log := make([]rmm.EventLogMsg, 0) evtLog := a.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, AgentID: a.AgentID, Log: log} _, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/") if err != nil { a.Logger.Debugln(err) } } func (a *Agent) SendPingCheckResult(payload rmm.PingCheckResponse, r *resty.Client) { _, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/") if err != nil { a.Logger.Debugln(err) } } 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 { a.Logger.Debugln("PingCheck:", err) payload.Status = "failing" payload.Output = err.Error() return } payload.Status = out.Status payload.Output = out.Output return } type WinSvcCheckResult struct { ID int `json:"id"` AgentID string `json:"agent_id"` MoreInfo string `json:"more_info"` Status string `json:"status"` } func (a *Agent) SendWinSvcCheckResult(payload WinSvcCheckResult, r *resty.Client) { _, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/") if err != nil { a.Logger.Debugln(err) } } 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 { if data.PassNotExist { payload.Status = "passing" } else { payload.Status = "failing" } payload.MoreInfo = err.Error() a.Logger.Debugln("Service", data.ServiceName, err) 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 := a.ControlService(data.ServiceName, "start") if ret.Success { payload.Status = "passing" payload.MoreInfo = "Status: running" } else { payload.Status = "failing" } } else { payload.Status = "failing" } } return }