testing and generate
This commit is contained in:
parent
9743d7ee22
commit
3841aee4b2
24 changed files with 1087 additions and 610 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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{}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
39
agent/tactical/install/install_unix.go
Normal file
39
agent/tactical/install/install_unix.go
Normal 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() {}
|
||||
349
agent/tactical/install/install_windows.go
Normal file
349
agent/tactical/install/install_windows.go
Normal 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)
|
||||
}
|
||||
}
|
||||
31
agent/tactical/install/structs.go
Normal file
31
agent/tactical/install/structs.go
Normal 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"`
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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"`
|
||||
}
|
||||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
@ -70,3 +72,23 @@ 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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
package tactical
|
||||
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
2
main.go
2
main.go
|
|
@ -1,3 +1,5 @@
|
|||
//go:generate goversioninfo
|
||||
|
||||
/*
|
||||
Copyright 2022 AmidaWare LLC.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue