testing and generate

This commit is contained in:
redanthrax 2022-06-23 15:07:18 -07:00
parent 9743d7ee22
commit 3841aee4b2
24 changed files with 1087 additions and 610 deletions

View file

@ -3,12 +3,17 @@ https://github.com/amidaware/tacticalrmm
#### building the agent - linux
```
go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo
go generate
env CGO_ENABLED=0 GOOS=<GOOS> GOARCH=<GOARCH> go build -ldflags "-s -w -X 'main.version=v2.0.4'"
example: env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -X 'main.version=v2.0.4' -o build/output/rmmagent"
```
#### building the agent - windows
```
$env:CGO_ENABLED="0";$env:GOOS="windows";$env:GOARCH="amd64"; go build -ldflags "-s -w -X 'main.version=v2.0.4'"
go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo
go generate
$env:CGO_ENABLED="0";$env:GOOS="windows";$env:GOARCH="amd64"; go build -ldflags "-s -w -X 'main.version=v2.0.4' -o build/output/tacticalrmm.exe"
```
### tests

View file

@ -836,15 +836,15 @@ func (a *Agent) InstallService() error {
// skip on first call of inno setup if this is a new install
_, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\TacticalRMM`, registry.ALL_ACCESS)
if err != nil {
return nil
return err
}
s, err := service.New(a, a.ServiceConfig)
svc, err := service.New(a, a.ServiceConfig)
if err != nil {
return err
}
return service.Control(s, "install")
return service.Control(svc, "install")
}
// TODO add to stub

View file

@ -6,7 +6,32 @@ import (
"github.com/spf13/viper"
)
func TestInstall(t *testing.T) {
testTable := []struct {
name string
expectedError error
version string
}{
{
name: "Install",
expectedError: nil,
version: "2.0.4",
},
{
name: "Install Error",
expectedError: nil,
version: "bad ver",
},
}
for _, tt := range testTable {
t.Run(tt.name, func(t *testing.T) {
})
}
var (
version = "2.0.4"
log = logrus.New()

View file

@ -36,8 +36,6 @@ func PatchMgmnt(enable bool) error {
return nil
}
func GetUpdates() (PackageList, error) {
wuaupdates, err := wua.WUAUpdates("IsInstalled=1 or IsInstalled=0 and Type='Software' and IsHidden=0")
packages := []Package{}

View file

@ -297,3 +297,19 @@ func EditService(name, startupType string) WinSvcResp {
return WinSvcResp{Success: true, ErrorMsg: ""}
}
func ServiceExists(name string) (bool, error) {
conn, err := mgr.Connect()
if err != nil {
return false, err
}
defer conn.Disconnect()
srv, err := conn.OpenService(name)
if err != nil {
return false, err
}
defer srv.Close()
return true, nil
}

View file

@ -12,6 +12,7 @@ import (
"strconv"
"time"
"github.com/amidaware/rmmagent/agent/tactical/shared"
"github.com/amidaware/rmmagent/agent/utils"
ps "github.com/elastic/go-sysinfo"
gocmd "github.com/go-cmd/cmd"
@ -144,7 +145,7 @@ func RunPythonCode(code string, timeout int, args []string) (string, error) {
}
//a.Logger.Debugln(cmdArgs)
cmd := exec.CommandContext(ctx, GetPythonBin(), cmdArgs...)
cmd := exec.CommandContext(ctx, shared.GetPythonBin(), cmdArgs...)
cmd.Stdout = &outb
cmd.Stderr = &errb

View file

@ -8,12 +8,12 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"sync"
"syscall"
"time"
"github.com/amidaware/rmmagent/agent/tactical/shared"
"github.com/amidaware/rmmagent/agent/utils"
ps "github.com/elastic/go-sysinfo"
"github.com/go-ole/go-ole"
@ -25,11 +25,6 @@ import (
"golang.org/x/sys/windows/registry"
)
const (
ProgFilesName = "TacticalAgent"
winExeName = "tacticalrmm.exe"
)
func RunScript(code string, shell string, args []string, timeout int) (stdout, stderr string, exitcode int, e error) {
content := []byte(code)
dir := filepath.Join(os.TempDir(), "trmm")
@ -74,7 +69,7 @@ func RunScript(code string, shell string, args []string, timeout int) (stdout, s
exe = "Powershell"
cmdArgs = []string{"-NonInteractive", "-NoProfile", "-ExecutionPolicy", "Bypass", tmpfn.Name()}
case "python":
exe = GetPythonBin()
exe = shared.GetPythonBin()
cmdArgs = []string{tmpfn.Name()}
case "cmd":
exe = tmpfn.Name()
@ -250,28 +245,6 @@ func CMDShell(shell string, cmdArgs []string, command string, timeout int, detac
nil
}
func GetProgramDirectory() string {
pd := filepath.Join(os.Getenv("ProgramFiles"), ProgFilesName)
return pd
}
func GetProgramBin() string {
exe := filepath.Join(GetProgramDirectory(), winExeName)
return exe
}
func GetPythonBin() string {
var pybin string
switch runtime.GOARCH {
case "amd64":
pybin = filepath.Join(GetProgramDirectory(), "py38-x64", "python.exe")
case "386":
pybin = filepath.Join(GetProgramDirectory(), "py38-x32", "python.exe")
}
return pybin
}
// LoggedOnUser returns the first logged on user it finds
func LoggedOnUser() string {
pyCode := `
@ -471,7 +444,7 @@ func OsString() string {
return osFullName
}
func AddDefenderExlusions() error {
func AddDefenderExclusions() error {
code := `
Add-MpPreference -ExclusionPath 'C:\Program Files\TacticalAgent\*'
Add-MpPreference -ExclusionPath 'C:\Windows\Temp\winagent-v*.exe'
@ -507,4 +480,4 @@ func KillProc(pid int32) error {
}
return nil
}
}

View file

@ -13,6 +13,7 @@ import (
"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/shared"
"github.com/amidaware/rmmagent/agent/utils"
ps "github.com/elastic/go-sysinfo"
"github.com/go-ping/ping"
@ -25,7 +26,7 @@ func CheckRunner(agentID string) error {
for {
interval, err := GetCheckInterval(agentID)
if err == nil && !ChecksRunning() {
_, err = system.CMD(system.GetProgramBin(), []string{"-m", "checkrunner"}, 600, false)
_, err = system.CMD(shared.GetProgramBin(), []string{"-m", "checkrunner"}, 600, false)
if err != nil {
return err
}
@ -66,7 +67,7 @@ Out:
if p.PID == 0 {
continue
}
if p.Exe != system.GetProgramBin() {
if p.Exe != shared.GetProgramBin() {
continue
}

View file

@ -0,0 +1,39 @@
//go:build !windows
// +build !windows
package install
import (
"github.com/spf13/viper"
"log"
)
func CheckExistingAndRemove(silent bool) error {
return nil
}
func CreateAgentConfig(baseurl, agentid, apiurl, token, agentpk, cert, proxy, meshdir string) {
viper.SetConfigType("json")
viper.Set("baseurl", baseurl)
viper.Set("agentid", agentid)
viper.Set("apiurl", apiurl)
viper.Set("token", token)
viper.Set("agentpk", agentpk)
viper.Set("cert", cert)
viper.Set("proxy", proxy)
viper.Set("meshdir", meshdir)
viper.SetConfigPermissions(0660)
configLocation := "/etc/tacticalagent"
err := viper.SafeWriteConfigAs(configLocation)
if err != nil {
log.Fatalln("createAgentConfig", err)
}
}
func DisableSleepHibernate() {}
func EnablePing() {}
func EnableRDP() {}

View file

@ -0,0 +1,349 @@
package install
import (
"errors"
"fmt"
"io"
"math/rand"
"net/url"
"os"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"time"
"github.com/amidaware/rmmagent/agent/patching"
"github.com/amidaware/rmmagent/agent/services"
"github.com/amidaware/rmmagent/agent/system"
"github.com/amidaware/rmmagent/agent/tactical"
"github.com/amidaware/rmmagent/agent/tactical/mesh"
"github.com/amidaware/rmmagent/agent/tactical/service"
"github.com/amidaware/rmmagent/agent/tactical/shared"
"github.com/amidaware/rmmagent/agent/utils"
"github.com/go-resty/resty/v2"
"github.com/gonutz/w32/v2"
ksvc "github.com/kardianos/service"
"github.com/shirou/gopsutil/host"
"golang.org/x/sys/windows/registry"
)
const winSvcName = "tacticalrmm"
func Install(i *Installer) error {
CheckExistingAndRemove(i.Silent)
i.Headers = map[string]string{
"content-type": "application/json",
"Authorization": fmt.Sprintf("Token %s", i.Token),
}
AgentID := GenerateAgentID()
u, err := url.Parse(i.RMM)
if err != nil {
return err
}
if u.Scheme != "https" && u.Scheme != "http" {
return errors.New("Invalid URL (must contain https or http)")
}
// will match either ipv4 , or ipv4:port
var ipPort = regexp.MustCompile(`[0-9]+(?:\.[0-9]+){3}(:[0-9]+)?`)
// if ipv4:port, strip the port to get ip for salt master
if ipPort.MatchString(u.Host) && strings.Contains(u.Host, ":") {
i.SaltMaster = strings.Split(u.Host, ":")[0]
} else if strings.Contains(u.Host, ":") {
i.SaltMaster = strings.Split(u.Host, ":")[0]
} else {
i.SaltMaster = u.Host
}
terr := utils.TestTCP(fmt.Sprintf("%s:4222", i.SaltMaster))
if terr != nil {
return fmt.Errorf("ERROR: Either port 4222 TCP is not open on your RMM, or nats.service is not running.\n\n%s", terr.Error())
}
baseURL := u.Scheme + "://" + u.Host
iClient := resty.New()
iClient.SetCloseConnection(true)
iClient.SetTimeout(15 * time.Second)
iClient.SetHeaders(i.Headers)
// set proxy if applicable
if len(i.Proxy) > 0 {
iClient.SetProxy(i.Proxy)
}
creds, cerr := iClient.R().Get(fmt.Sprintf("%s/api/v3/installer/", baseURL))
if cerr != nil {
return cerr
}
if creds.StatusCode() == 401 {
return errors.New("Installer token has expired. Please generate a new one.")
}
verPayload := map[string]string{"version": i.Version}
iVersion, ierr := iClient.R().SetBody(verPayload).Post(fmt.Sprintf("%s/api/v3/installer/", baseURL))
if ierr != nil {
return ierr
}
if iVersion.StatusCode() != 200 {
return errors.New(DjangoStringResp(iVersion.String()))
}
rClient := resty.New()
rClient.SetCloseConnection(true)
rClient.SetTimeout(i.Timeout * time.Second)
// set rest knox headers
rClient.SetHeaders(i.Headers)
// set local cert if applicable
if len(i.Cert) > 0 {
if !utils.FileExists(i.Cert) {
return fmt.Errorf("%s does not exist", i.Cert)
}
rClient.SetRootCertificate(i.Cert)
}
if len(i.Proxy) > 0 {
rClient.SetProxy(i.Proxy)
}
var arch string
switch runtime.GOARCH {
case "x86_64":
arch = "64"
case "x86":
arch = "32"
}
var installerMeshSystemBin string
if len(i.MeshDir) > 0 {
installerMeshSystemBin = filepath.Join(i.MeshDir, "MeshAgent.exe")
} else {
installerMeshSystemBin = mesh.GetMeshBinLocation()
}
var meshNodeID string
if runtime.GOOS == "windows" && !i.NoMesh {
meshPath := filepath.Join(shared.GetProgramDirectory(), "meshagent.exe")
if i.LocalMesh == "" {
payload := map[string]string{"arch": arch, "plat": runtime.GOOS}
r, err := rClient.R().SetBody(payload).SetOutput(meshPath).Post(fmt.Sprintf("%s/api/v3/meshexe/", baseURL))
if err != nil {
}
if r.StatusCode() != 200 {
}
} else {
err := copyFile(i.LocalMesh, meshPath)
if err != nil {
}
}
time.Sleep(1 * time.Second)
meshNodeID, err = mesh.InstallMesh(meshPath, installerMeshSystemBin, i.Proxy)
if err != nil {
}
}
if len(i.MeshNodeID) > 0 {
meshNodeID = i.MeshNodeID
}
// add agent
host, _ := host.Info()
agentPayload := map[string]interface{}{
"agent_id": AgentID,
"hostname": host.Hostname,
"site": i.SiteID,
"monitoring_type": i.AgentType,
"mesh_node_id": meshNodeID,
"description": i.Description,
"goarch": runtime.GOARCH,
"plat": runtime.GOOS,
}
r, err := rClient.R().SetBody(agentPayload).SetResult(&NewAgentResp{}).Post(fmt.Sprintf("%s/api/v3/newagent/", baseURL))
if err != nil {
}
if r.StatusCode() != 200 {
}
agentPK := r.Result().(*NewAgentResp).AgentPK
agentToken := r.Result().(*NewAgentResp).Token
CreateAgentConfig(baseURL, AgentID, i.SaltMaster, agentToken, strconv.Itoa(agentPK), i.Cert, i.Proxy, i.MeshDir)
time.Sleep(1 * time.Second)
time.Sleep(3 * time.Second)
// check in once
service.DoNatsCheckIn(i.Version)
service.SendSoftware()
utils.CreateTRMMTempDir()
patching.PatchMgmnt(true)
svcConf := &ksvc.Config{
Executable: shared.GetProgramBin(),
Name: winSvcName,
DisplayName: "TacticalRMM Agent Service",
Arguments: []string{"-m", "svc"},
Description: "TacticalRMM Agent Service",
Option: ksvc.KeyValue{
"StartType": "automatic",
"OnFailure": "restart",
"OnFailureDelayDuration": "5s",
"OnFailureResetPeriod": 10,
},
}
err = service.InstallService(winSvcName, service.IService{}, svcConf)
if err != nil {
return err
}
time.Sleep(1 * time.Second)
out := services.ControlService(winSvcName, "start")
if !out.Success {
return errors.New(out.ErrorMsg)
}
system.AddDefenderExclusions()
if i.Power {
system.DisableSleepHibernate()
}
if i.Ping {
system.EnablePing()
}
if i.RDP {
system.EnableRDP()
}
return nil
}
func copyFile(src, dst string) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, in)
if err != nil {
return err
}
return nil
}
// GenerateAgentID creates and returns a unique agent id
func GenerateAgentID() string {
rand.Seed(time.Now().UnixNano())
letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
b := make([]rune, 40)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}
// DjangoStringResp removes double quotes from django rest api resp
func DjangoStringResp(resp string) string {
return strings.Trim(resp, `"`)
}
func CreateAgentConfig(baseurl, agentid, apiurl, token, agentpk, cert, proxy, meshdir string) error {
k, _, err := registry.CreateKey(registry.LOCAL_MACHINE, `SOFTWARE\TacticalRMM`, registry.ALL_ACCESS)
if err != nil {
return err
}
defer k.Close()
err = k.SetStringValue("BaseURL", baseurl)
if err != nil {
return err
}
err = k.SetStringValue("AgentID", agentid)
if err != nil {
return err
}
err = k.SetStringValue("ApiURL", apiurl)
if err != nil {
return err
}
err = k.SetStringValue("Token", token)
if err != nil {
return err
}
err = k.SetStringValue("AgentPK", agentpk)
if err != nil {
}
if len(cert) > 0 {
err = k.SetStringValue("Cert", cert)
if err != nil {
return err
}
}
if len(proxy) > 0 {
err = k.SetStringValue("Proxy", proxy)
if err != nil {
return err
}
}
if len(meshdir) > 0 {
err = k.SetStringValue("MeshDir", meshdir)
if err != nil {
return err
}
}
return nil
}
func CheckExistingAndRemove(silent bool) {
hasReg := false
_, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\TacticalRMM`, registry.ALL_ACCESS)
if err == nil {
hasReg = true
}
if hasReg {
tacUninst := filepath.Join(shared.GetProgramDirectory(), tactical.GetUninstallExe())
tacUninstArgs := [2]string{tacUninst, "/VERYSILENT"}
window := w32.GetForegroundWindow()
if !silent && window != 0 {
var handle w32.HWND
msg := "Existing installation found\nClick OK to remove, then re-run the installer.\nClick Cancel to abort."
action := w32.MessageBox(handle, msg, "Tactical RMM", w32.MB_OKCANCEL|w32.MB_ICONWARNING)
if action == w32.IDOK {
tactical.AgentUninstall("foo")
}
} else {
fmt.Println("Existing installation found and must be removed before attempting to reinstall.")
fmt.Println("Run the following command to uninstall, and then re-run this installer.")
fmt.Printf(`"%s" %s `, tacUninstArgs[0], tacUninstArgs[1])
}
os.Exit(0)
}
}

View file

@ -0,0 +1,31 @@
package install
import "time"
type Installer struct {
Headers map[string]string
RMM string
ClientID int
SiteID int
Description string
AgentType string
Power bool
RDP bool
Ping bool
Token string
LocalMesh string
Cert string
Proxy string
Timeout time.Duration
SaltMaster string
Silent bool
NoMesh bool
MeshDir string
MeshNodeID string
Version string
}
type NewAgentResp struct {
AgentPK int `json:"pk"`
Token string `json:"token"`
}

View file

@ -1,472 +0,0 @@
package rpc
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/service"
"github.com/amidaware/rmmagent/agent/tactical/shared"
ttasks "github.com/amidaware/rmmagent/agent/tactical/tasks"
"github.com/amidaware/rmmagent/agent/tasks"
ksvc "github.com/kardianos/service"
nats "github.com/nats-io/nats.go"
"github.com/ugorji/go/codec"
)
var (
agentUpdateLocker uint32
getWinUpdateLocker uint32
installWinUpdateLocker uint32
)
func RunRPC(version string) error {
config := config.NewAgentConfig()
go service.RunAsService(version)
var wg sync.WaitGroup
wg.Add(1)
opts := service.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 {
service.NatsMessage(version, nc, m)
}
ret.Encode("ok")
msg.Respond(resp)
}()
case "wmi":
go func() {
service.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(system.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 Start(version string, _ ksvc.Service) error {
go RunRPC(version)
return nil
}

View file

@ -1,31 +0,0 @@
package rpc_test
import (
"errors"
"testing"
"github.com/amidaware/rmmagent/agent/tactical/rpc"
)
func TestRunRPC(t *testing.T) {
testTable := []struct {
name string
expectedError error
version string
}{
{
name: "Run RPC",
expectedError: nil,
version: "development",
},
}
for _, tt := range testTable {
t.Run(tt.name, func(t *testing.T) {
err := rpc.RunRPC(tt.version)
if !errors.Is(tt.expectedError, err) {
t.Errorf("expected (%v), got (%v)", tt.expectedError, err)
}
})
}
}

View file

@ -1,32 +0,0 @@
package rpc
import "github.com/amidaware/rmmagent/agent/tasks"
type NatsMsg struct {
Func string `json:"func"`
Timeout int `json:"timeout"`
Data map[string]string `json:"payload"`
ScriptArgs []string `json:"script_args"`
ProcPID int32 `json:"procpid"`
TaskPK int `json:"taskpk"`
ScheduledTask tasks.SchedTask `json:"schedtaskpayload"`
RecoveryCommand string `json:"recoverycommand"`
UpdateGUIDs []string `json:"guids"`
ChocoProgName string `json:"choco_prog_name"`
PendingActionPK int `json:"pending_action_pk"`
PatchMgmt bool `json:"patch_mgmt"`
ID int `json:"id"`
Code string `json:"code"`
}
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

@ -2,20 +2,473 @@ 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)
@ -47,7 +500,7 @@ func AgentSvc(version string) error {
time.Sleep(time.Duration(utils.RandRange(1, 3)) * time.Second)
AgentStartup(config.AgentID)
shared.SendSoftware()
SendSoftware()
checkInHelloTicker := time.NewTicker(time.Duration(utils.RandRange(30, 60)) * time.Second)
checkInAgentInfoTicker := time.NewTicker(time.Duration(utils.RandRange(200, 400)) * time.Second)
@ -71,7 +524,7 @@ func AgentSvc(version string) error {
case <-checkInDisksTicker.C:
NatsMessage(version, nc, "agent-disks")
case <-checkInSWTicker.C:
shared.SendSoftware()
SendSoftware()
case <-checkInWMITicker.C:
NatsMessage(version, nc, "agent-wmi")
case <-syncMeshTicker.C:
@ -113,3 +566,22 @@ func AgentStartup(agentID string) error {
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 }

View file

@ -9,8 +9,10 @@ import (
"github.com/amidaware/rmmagent/agent/system"
"github.com/amidaware/rmmagent/agent/tactical/config"
"github.com/amidaware/rmmagent/agent/wmi"
ksvc "github.com/kardianos/service"
"github.com/nats-io/nats.go"
"github.com/ugorji/go/codec"
"golang.org/x/sys/windows/registry"
)
func NatsMessage(version string, nc *nats.Conn, mode string) {
@ -69,4 +71,24 @@ func NatsMessage(version string, nc *nats.Conn, mode string) {
ret.Encode(payload)
nc.PublishRequest(config.AgentID, mode, resp)
}
}
func InstallService(name string, svc IService, config *ksvc.Config) error {
exists, err := services.ServiceExists(name)
if exists {
return nil
}
// skip on first call of inno setup if this is a new install
_, err = registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\TacticalRMM`, registry.ALL_ACCESS)
if err != nil {
return nil
}
s, err := ksvc.New(svc, config)
if err != nil {
return err
}
return ksvc.Control(s, "install")
}

View file

@ -3,8 +3,11 @@ package service
import (
"github.com/amidaware/rmmagent/agent/disk"
"github.com/amidaware/rmmagent/agent/services"
"github.com/amidaware/rmmagent/agent/tasks"
)
type IService struct{}
type WinSvcNats struct {
Agentid string `json:"agent_id"`
WinSvcs []services.Service `json:"services"`
@ -41,3 +44,32 @@ type PublicIPNats struct {
Agentid string `json:"agent_id"`
PublicIP string `json:"public_ip"`
}
type NatsMsg struct {
Func string `json:"func"`
Timeout int `json:"timeout"`
Data map[string]string `json:"payload"`
ScriptArgs []string `json:"script_args"`
ProcPID int32 `json:"procpid"`
TaskPK int `json:"taskpk"`
ScheduledTask tasks.SchedTask `json:"schedtaskpayload"`
RecoveryCommand string `json:"recoverycommand"`
UpdateGUIDs []string `json:"guids"`
ChocoProgName string `json:"choco_prog_name"`
PendingActionPK int `json:"pending_action_pk"`
PatchMgmt bool `json:"patch_mgmt"`
ID int `json:"id"`
Code string `json:"code"`
}
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

@ -1,19 +0,0 @@
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

@ -7,14 +7,18 @@ import (
"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"
)
const (
ProgFilesName = "TacticalAgent"
winExeName = "tacticalrmm.exe"
)
func GetPython(force bool) {
if utils.FileExists(system.GetPythonBin()) && !force {
if utils.FileExists(GetPythonBin()) && !force {
return
}
@ -28,8 +32,8 @@ func GetPython(force bool) {
archZip = "py38-x32.zip"
folder = "py38-x32"
}
pyFolder := filepath.Join(system.GetProgramDirectory(), folder)
pyZip := filepath.Join(system.GetProgramDirectory(), archZip)
pyFolder := filepath.Join(GetProgramDirectory(), folder)
pyZip := filepath.Join(GetProgramDirectory(), archZip)
defer os.Remove(pyZip)
if force {
@ -55,16 +59,38 @@ func GetPython(force bool) {
return
}
err = utils.Unzip(pyZip, system.GetProgramDirectory())
err = utils.Unzip(pyZip, GetProgramDirectory())
if err != nil {
}
}
func RunMigrations() {
for _, i := range []string{"nssm.exe", "nssm-x86.exe"} {
nssm := filepath.Join(system.GetProgramDirectory(), i)
nssm := filepath.Join(GetProgramDirectory(), i)
if utils.FileExists(nssm) {
os.Remove(nssm)
}
}
}
}
func GetPythonBin() string {
var pybin string
switch runtime.GOARCH {
case "amd64":
pybin = filepath.Join(GetProgramDirectory(), "py38-x64", "python.exe")
case "386":
pybin = filepath.Join(GetProgramDirectory(), "py38-x32", "python.exe")
}
return pybin
}
func GetProgramDirectory() string {
pd := filepath.Join(os.Getenv("ProgramFiles"), ProgFilesName)
return pd
}
func GetProgramBin() string {
exe := filepath.Join(GetProgramDirectory(), winExeName)
return exe
}

View file

@ -1,2 +0,0 @@
package tactical

View file

@ -1,2 +1,12 @@
package tactical_test
import (
"testing"
"github.com/amidaware/rmmagent/agent/tactical"
)
func TestGetVersion(t *testing.T) {
version := tactical.GetVersion()
t.Logf("got version %s", version)
}

View file

@ -13,14 +13,34 @@ 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/shared"
"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/gonutz/w32/v2"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry"
)
func GetVersion() string {
path := shared.GetProgramBin()
size := w32.GetFileVersionInfoSize(path)
info := make([]byte, size)
w32.GetFileVersionInfo(path, info)
fixed, _ := w32.VerQueryValueRoot(info)
version := fixed.FileVersion()
stringVersion := fmt.Sprintf(
"%d.%d.%d",
version&0xFFFF000000000000>>48,
version&0x0000FFFF00000000>>32,
version&0x00000000FFFF0000>>16,
//drop last digit version&0x000000000000FFFF>>0,
)
return stringVersion
}
func UninstallCleanup() {
registry.DeleteKey(registry.LOCAL_MACHINE, `SOFTWARE\TacticalRMM`)
patching.PatchMgmnt(false)
@ -32,7 +52,7 @@ func AgentUpdate(url, inno, version string) {
time.Sleep(time.Duration(utils.RandRange(1, 15)) * time.Second)
system.KillHungUpdates()
CleanupAgentUpdates()
updater := filepath.Join(system.GetProgramDirectory(), inno)
updater := filepath.Join(shared.GetProgramDirectory(), inno)
config := config.NewAgentConfig()
rClient := resty.New()
rClient.SetCloseConnection(true)
@ -71,7 +91,7 @@ func AgentUpdate(url, inno, version string) {
}
func CleanupAgentUpdates() {
pd := filepath.Join(os.Getenv("ProgramFiles"), system.ProgFilesName)
pd := filepath.Join(os.Getenv("ProgramFiles"), shared.ProgFilesName)
cderr := os.Chdir(pd)
if cderr != nil {
return
@ -99,7 +119,7 @@ func CleanupAgentUpdates() {
func AgentUninstall(code string) {
system.KillHungUpdates()
tacUninst := filepath.Join(system.GetProgramDirectory(), GetUninstallExe())
tacUninst := filepath.Join(shared.GetProgramDirectory(), GetUninstallExe())
args := []string{"/C", tacUninst, "/VERYSILENT"}
cmd := exec.Command("cmd.exe", args...)
cmd.SysProcAttr = &windows.SysProcAttr{
@ -109,7 +129,7 @@ func AgentUninstall(code string) {
}
func GetUninstallExe() string {
cderr := os.Chdir(system.GetProgramDirectory())
cderr := os.Chdir(shared.GetProgramDirectory())
if cderr == nil {
files, err := filepath.Glob("unins*.exe")
if err == nil {

View file

@ -6,6 +6,7 @@ import (
"fmt"
"io"
"math/rand"
"net"
"os"
"path/filepath"
"strings"
@ -186,3 +187,13 @@ func FileExists(path string) bool {
}
return true
}
func TestTCP(addr string) error {
conn, err := net.Dial("tcp4", addr)
if err != nil {
return err
}
defer conn.Close()
return nil
}

View file

@ -1,3 +1,5 @@
//go:generate goversioninfo
/*
Copyright 2022 AmidaWare LLC.