Compare commits
152 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c3500971ea | ||
![]() |
0fb702cc76 | ||
![]() |
cfbbdc89e7 | ||
![]() |
ff9c271c76 | ||
![]() |
4a79b3dce3 | ||
![]() |
7dc08fdbf3 | ||
![]() |
d4faa2a233 | ||
![]() |
f0736faf4b | ||
![]() |
060d222941 | ||
![]() |
751b50e071 | ||
![]() |
db17e3e28e | ||
![]() |
1814d0f19d | ||
![]() |
021f4febbb | ||
![]() |
e6fea56198 | ||
![]() |
753242a949 | ||
![]() |
2e0a42a6a1 | ||
![]() |
08488cee15 | ||
![]() |
e0f2957e96 | ||
![]() |
4f91148753 | ||
![]() |
90d0bbf020 | ||
![]() |
0777195423 | ||
![]() |
9f22576136 | ||
![]() |
9f3f5c2f9b | ||
![]() |
2cff41f719 | ||
![]() |
c4b006b212 | ||
![]() |
6c1fa2f061 | ||
![]() |
830f418888 | ||
![]() |
f00b03b973 | ||
![]() |
8e6858778b | ||
![]() |
e51e4ccf83 | ||
![]() |
150e1eda22 | ||
![]() |
7f9e3f0f7d | ||
![]() |
836c274e83 | ||
![]() |
ee19c7a4fc | ||
![]() |
5a23a55e39 | ||
![]() |
e15b1a50c3 | ||
![]() |
d62966dd74 | ||
![]() |
50ff4ba0ac | ||
![]() |
071ebba4ae | ||
![]() |
569a6662f0 | ||
![]() |
5adb986f7f | ||
![]() |
9975ce3536 | ||
![]() |
4f01e214fd | ||
![]() |
588a4bcbf7 | ||
![]() |
ec49d4941d | ||
![]() |
2da107455f | ||
![]() |
9c6c67b1b2 | ||
![]() |
549aaba08f | ||
![]() |
56cbf8a9d7 | ||
![]() |
407f2a8072 | ||
![]() |
bee870bd58 | ||
![]() |
7ae664b9c6 | ||
![]() |
a0828c98ab | ||
![]() |
2316271bf9 | ||
![]() |
1d00f0ad41 | ||
![]() |
08b6b5ecda | ||
![]() |
396d7db835 | ||
![]() |
d3b7d4090b | ||
![]() |
ff75a8eb89 | ||
![]() |
4b1f993a76 | ||
![]() |
c3e33a6def | ||
![]() |
293151ea0a | ||
![]() |
63fe3bcd73 | ||
![]() |
524837627f | ||
![]() |
3cb6b92a80 | ||
![]() |
ec6ea9aded | ||
![]() |
de01fa5d80 | ||
![]() |
c71e495184 | ||
![]() |
8d7dfeef25 | ||
![]() |
3ff004afa0 | ||
![]() |
36e9065474 | ||
![]() |
7358907b3c | ||
![]() |
6ac14b6d64 | ||
![]() |
9565fea27c | ||
![]() |
79dba2f84c | ||
![]() |
f1db416d56 | ||
![]() |
9ccb95449e | ||
![]() |
d3bcb10f93 | ||
![]() |
bcfae9dc66 | ||
![]() |
f200862b1a | ||
![]() |
7a75d4d5a3 | ||
![]() |
db5cd5b0e0 | ||
![]() |
44c63d00b6 | ||
![]() |
bf5258eb78 | ||
![]() |
93f4de35fe | ||
![]() |
337c519b99 | ||
![]() |
473cfd3ced | ||
![]() |
a057c65a02 | ||
![]() |
e11c38dd49 | ||
![]() |
eddafb873b | ||
![]() |
935d5d8ab8 | ||
![]() |
552d63ea6e | ||
![]() |
16df03678c | ||
![]() |
56d2356db2 | ||
![]() |
43d13a78f2 | ||
![]() |
cd93ec12fa | ||
![]() |
852dfee29f | ||
![]() |
e0cfb7c90e | ||
![]() |
89be0e7d99 | ||
![]() |
f746f78c63 | ||
![]() |
af040d18ee | ||
![]() |
e8c743c197 | ||
![]() |
b21929d044 | ||
![]() |
89d7ec8242 | ||
![]() |
7b8d524a81 | ||
![]() |
662c41794b | ||
![]() |
607a5283ac | ||
![]() |
41597d7d26 | ||
![]() |
381f9696eb | ||
![]() |
adee74ffd0 | ||
![]() |
0c536f13b0 | ||
![]() |
5bf3ef5356 | ||
![]() |
50cebb950d | ||
![]() |
ad1ae2a6a1 | ||
![]() |
eb386a4ee2 | ||
![]() |
1fbf2be562 | ||
![]() |
f60be3deed | ||
![]() |
be050ea16f | ||
![]() |
480ee21785 | ||
![]() |
7aa6c571c3 | ||
![]() |
ed56a2af31 | ||
![]() |
92dd7c110f | ||
![]() |
38907f6bc2 | ||
![]() |
ac2637d14b | ||
![]() |
8d2511129c | ||
![]() |
aed33b9a95 | ||
![]() |
1ac1ca57e2 | ||
![]() |
b0a1b6335d | ||
![]() |
d3753535d5 | ||
![]() |
d8da131623 | ||
![]() |
eaf74850d2 | ||
![]() |
73deda7d04 | ||
![]() |
959a1bb8c8 | ||
![]() |
42be72f0b4 | ||
![]() |
891b7febcf | ||
![]() |
24d51a30e1 | ||
![]() |
506c58aded | ||
![]() |
13b5474cd8 | ||
![]() |
30123bc023 | ||
![]() |
dfe2881cd5 | ||
![]() |
89669af3ae | ||
![]() |
d7936a0e96 | ||
![]() |
5c559318a7 | ||
![]() |
7c46970b67 | ||
![]() |
7457bf0b93 | ||
![]() |
6457ad290f | ||
![]() |
d1df98ad3e | ||
![]() |
ecddc2fe21 | ||
![]() |
b7ad579d3b | ||
![]() |
e0c8e75f00 | ||
![]() |
f45029f3c6 | ||
![]() |
88bf11f5b1 |
42
.github/workflows/ci.yml
vendored
Normal file
42
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
name: Run tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "*"
|
||||
pull_request:
|
||||
branches:
|
||||
- "*"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
name: Run tests
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.20.13'
|
||||
|
||||
- name: Ensure linux agent compiles
|
||||
run: |
|
||||
ARCHS='amd64 386 arm64 arm'
|
||||
for i in ${ARCHS}; do
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=${i} go build -ldflags "-s -w"
|
||||
done
|
||||
|
||||
- name: Ensure windows agent compiles
|
||||
run: |
|
||||
ARCHS='amd64 386'
|
||||
for i in ${ARCHS}; do
|
||||
env CGO_ENABLED=0 GOOS=windows GOARCH=${i} go build -ldflags "-s -w"
|
||||
done
|
||||
|
||||
- name: Ensure mac agent compiles
|
||||
run: |
|
||||
ARCHS='amd64 arm64'
|
||||
for i in ${ARCHS}; do
|
||||
env CGO_ENABLED=0 GOOS=darwin GOARCH=${i} go build -ldflags "-s -w"
|
||||
done
|
14
LICENSE.md
14
LICENSE.md
@@ -1,11 +1,11 @@
|
||||
### Tactical RMM License Version 1.0
|
||||
|
||||
Text of license:   Copyright © 2022 AmidaWare LLC. All rights reserved.<br>
|
||||
Text of license:   Copyright © 2023 AmidaWare Inc. All rights reserved.<br>
|
||||
          Amending the text of this license is not permitted.
|
||||
|
||||
Trade Mark:    "Tactical RMM" is a trade mark of AmidaWare LLC.
|
||||
Trade Mark:    "Tactical RMM" is a trade mark of AmidaWare Inc.
|
||||
|
||||
Licensor:      AmidaWare LLC of 1968 S Coast Hwy PMB 3847 Laguna Beach, CA, USA.
|
||||
Licensor:      AmidaWare Inc. of 1968 S Coast Hwy PMB 3847 Laguna Beach, CA, USA.
|
||||
|
||||
Licensed Software:  The software known as Tactical RMM Version v0.12.0 (and all subsequent releases and versions) and the Tactical RMM Agent v2.0.0 (and all subsequent releases and versions).
|
||||
|
||||
@@ -26,7 +26,7 @@ This license does not allow the functionality of the Licensed Software (whether
|
||||
* the offering of installation and/or configuration services;
|
||||
* the offer for sale, distribution or sale of any service or product (whether or not branded as Tactical RMM).
|
||||
|
||||
The prior written approval of AmidaWare LLC must be obtained for all commercial use and/or for-profit service use of the (i) Licensed Software (whether in whole or in part), (ii) a modified version of the Licensed Software and/or (iii) a derivative work.
|
||||
The prior written approval of AmidaWare Inc. must be obtained for all commercial use and/or for-profit service use of the (i) Licensed Software (whether in whole or in part), (ii) a modified version of the Licensed Software and/or (iii) a derivative work.
|
||||
|
||||
The terms of this license apply to all copies of the Licensed Software (including modified versions) and derivative works.
|
||||
|
||||
@@ -38,7 +38,7 @@ If a derivative work is created which is based on or otherwise incorporates all
|
||||
### 4. Copyright Notice
|
||||
The following copyright notice shall be included in all copies of the Licensed Software:
|
||||
|
||||
   Copyright © 2022 AmidaWare LLC.
|
||||
   Copyright © 2023 AmidaWare Inc.
|
||||
|
||||
   Licensed under the Tactical RMM License Version 1.0 (the “License”).<br>
|
||||
   You may only use the Licensed Software in accordance with the License.<br>
|
||||
@@ -54,13 +54,13 @@ THE FOLLOWING EXCLUSIONS SHALL APPLY TO THE FULLEST EXTENT PERMISSIBLE AT LAW.
|
||||
This license shall terminate with immediate effect if there is a material breach of any of its terms.
|
||||
|
||||
### 8. No partnership, agency or joint venture
|
||||
Nothing in this license agreement is intended to, or shall be deemed to, establish any partnership or joint venture or any relationship of agency between AmidaWare LLC and any other person.
|
||||
Nothing in this license agreement is intended to, or shall be deemed to, establish any partnership or joint venture or any relationship of agency between AmidaWare Inc. and any other person.
|
||||
|
||||
### 9. No endorsement
|
||||
The names of the authors and/or the copyright holders must not be used to promote or endorse any products or services which are in any way derived from the Licensed Software without prior written consent.
|
||||
|
||||
### 10. Trademarks
|
||||
No permission is granted to use the trademark “Tactical RMM” or any other trade name, trademark, service mark or product name of AmidaWare LLC except to the extent necessary to comply with the notice requirements in Section 4 (Copyright Notice).
|
||||
No permission is granted to use the trademark “Tactical RMM” or any other trade name, trademark, service mark or product name of AmidaWare Inc. except to the extent necessary to comply with the notice requirements in Section 4 (Copyright Notice).
|
||||
|
||||
### 11. Entire agreement
|
||||
This license contains the whole agreement relating to its subject matter.
|
||||
|
367
agent/agent.go
367
agent/agent.go
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 AmidaWare LLC.
|
||||
Copyright 2023 AmidaWare Inc.
|
||||
|
||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||
You may only use the Licensed Software in accordance with the License.
|
||||
@@ -14,9 +14,9 @@ package agent
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"time"
|
||||
|
||||
@@ -40,42 +41,61 @@ import (
|
||||
|
||||
// Agent struct
|
||||
type Agent struct {
|
||||
Hostname string
|
||||
Arch string
|
||||
AgentID string
|
||||
BaseURL string
|
||||
ApiURL string
|
||||
Token string
|
||||
AgentPK int
|
||||
Cert string
|
||||
ProgramDir string
|
||||
EXE string
|
||||
SystemDrive string
|
||||
MeshInstaller string
|
||||
MeshSystemEXE string
|
||||
MeshSVC string
|
||||
PyBin string
|
||||
Headers map[string]string
|
||||
Logger *logrus.Logger
|
||||
Version string
|
||||
Debug bool
|
||||
rClient *resty.Client
|
||||
Proxy string
|
||||
LogTo string
|
||||
LogFile *os.File
|
||||
Platform string
|
||||
GoArch string
|
||||
ServiceConfig *service.Config
|
||||
Hostname string
|
||||
Arch string
|
||||
AgentID string
|
||||
BaseURL string
|
||||
ApiURL string
|
||||
Token string
|
||||
AgentPK int
|
||||
Cert string
|
||||
ProgramDir string
|
||||
EXE string
|
||||
SystemDrive string
|
||||
WinTmpDir string
|
||||
WinRunAsUserTmpDir string
|
||||
MeshInstaller string
|
||||
MeshSystemEXE string
|
||||
MeshSVC string
|
||||
PyBin string
|
||||
Headers map[string]string
|
||||
Logger *logrus.Logger
|
||||
Version string
|
||||
Debug bool
|
||||
rClient *resty.Client
|
||||
Proxy string
|
||||
LogTo string
|
||||
LogFile *os.File
|
||||
Platform string
|
||||
GoArch string
|
||||
ServiceConfig *service.Config
|
||||
NatsServer string
|
||||
NatsProxyPath string
|
||||
NatsProxyPort string
|
||||
NatsPingInterval int
|
||||
NatsWSCompression bool
|
||||
Insecure bool
|
||||
}
|
||||
|
||||
const (
|
||||
progFilesName = "TacticalAgent"
|
||||
winExeName = "tacticalrmm.exe"
|
||||
winSvcName = "tacticalrmm"
|
||||
meshSvcName = "mesh agent"
|
||||
progFilesName = "TacticalAgent"
|
||||
winExeName = "tacticalrmm.exe"
|
||||
winSvcName = "tacticalrmm"
|
||||
meshSvcName = "mesh agent"
|
||||
etcConfig = "/etc/tacticalagent"
|
||||
nixAgentDir = "/opt/tacticalagent"
|
||||
nixMeshDir = "/opt/tacticalmesh"
|
||||
nixAgentBin = nixAgentDir + "/tacticalagent"
|
||||
nixMeshAgentBin = nixMeshDir + "/meshagent"
|
||||
macPlistPath = "/Library/LaunchDaemons/tacticalagent.plist"
|
||||
macPlistName = "tacticalagent"
|
||||
defaultMacMeshSvcDir = "/usr/local/mesh_services"
|
||||
)
|
||||
|
||||
var defaultWinTmpDir = filepath.Join(os.Getenv("PROGRAMDATA"), "TacticalRMM")
|
||||
var winMeshDir = filepath.Join(os.Getenv("PROGRAMFILES"), "Mesh Agent")
|
||||
var natsCheckin = []string{"agent-hello", "agent-agentinfo", "agent-disks", "agent-winsvc", "agent-publicip", "agent-wmi"}
|
||||
var limitNatsData = []string{"agent-winsvc", "agent-wmi"}
|
||||
|
||||
func New(logger *logrus.Logger, version string) *Agent {
|
||||
host, _ := ps.Host()
|
||||
@@ -83,6 +103,13 @@ func New(logger *logrus.Logger, version string) *Agent {
|
||||
pd := filepath.Join(os.Getenv("ProgramFiles"), progFilesName)
|
||||
exe := filepath.Join(pd, winExeName)
|
||||
sd := os.Getenv("SystemDrive")
|
||||
winTempDir := defaultWinTmpDir
|
||||
winRunAsUserTmpDir := defaultWinTmpDir
|
||||
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
hostname = info.Hostname
|
||||
}
|
||||
|
||||
var pybin string
|
||||
switch runtime.GOARCH {
|
||||
@@ -100,12 +127,20 @@ func New(logger *logrus.Logger, version string) *Agent {
|
||||
headers["Authorization"] = fmt.Sprintf("Token %s", ac.Token)
|
||||
}
|
||||
|
||||
insecure := ac.Insecure == "true"
|
||||
|
||||
restyC := resty.New()
|
||||
restyC.SetBaseURL(ac.BaseURL)
|
||||
restyC.SetCloseConnection(true)
|
||||
restyC.SetHeaders(headers)
|
||||
restyC.SetTimeout(15 * time.Second)
|
||||
restyC.SetDebug(logger.IsLevelEnabled(logrus.DebugLevel))
|
||||
if insecure {
|
||||
insecureConf := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
restyC.SetTLSClientConfig(insecureConf)
|
||||
}
|
||||
|
||||
if len(ac.Proxy) > 0 {
|
||||
restyC.SetProxy(ac.Proxy)
|
||||
@@ -114,15 +149,30 @@ func New(logger *logrus.Logger, version string) *Agent {
|
||||
restyC.SetRootCertificate(ac.Cert)
|
||||
}
|
||||
|
||||
var MeshSysExe string
|
||||
if len(ac.CustomMeshDir) > 0 {
|
||||
MeshSysExe = filepath.Join(ac.CustomMeshDir, "MeshAgent.exe")
|
||||
} else {
|
||||
MeshSysExe = filepath.Join(os.Getenv("ProgramFiles"), "Mesh Agent", "MeshAgent.exe")
|
||||
if len(ac.WinTmpDir) > 0 {
|
||||
winTempDir = ac.WinTmpDir
|
||||
}
|
||||
|
||||
if runtime.GOOS == "linux" {
|
||||
MeshSysExe = "/opt/tacticalmesh/meshagent"
|
||||
if len(ac.WinRunAsUserTmpDir) > 0 {
|
||||
winRunAsUserTmpDir = ac.WinRunAsUserTmpDir
|
||||
}
|
||||
|
||||
var MeshSysExe string
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
if len(ac.CustomMeshDir) > 0 {
|
||||
MeshSysExe = filepath.Join(ac.CustomMeshDir, "MeshAgent.exe")
|
||||
} else {
|
||||
MeshSysExe = filepath.Join(os.Getenv("ProgramFiles"), "Mesh Agent", "MeshAgent.exe")
|
||||
}
|
||||
case "darwin":
|
||||
if trmm.FileExists(nixMeshAgentBin) {
|
||||
MeshSysExe = nixMeshAgentBin
|
||||
} else {
|
||||
MeshSysExe = "/usr/local/mesh_services/meshagent/meshagent"
|
||||
}
|
||||
default:
|
||||
MeshSysExe = nixMeshAgentBin
|
||||
}
|
||||
|
||||
svcConf := &service.Config{
|
||||
@@ -134,36 +184,69 @@ func New(logger *logrus.Logger, version string) *Agent {
|
||||
Option: service.KeyValue{
|
||||
"StartType": "automatic",
|
||||
"OnFailure": "restart",
|
||||
"OnFailureDelayDuration": "5s",
|
||||
"OnFailureDelayDuration": "12s",
|
||||
"OnFailureResetPeriod": 10,
|
||||
},
|
||||
}
|
||||
|
||||
var natsProxyPath, natsProxyPort string
|
||||
if ac.NatsProxyPath == "" {
|
||||
natsProxyPath = "natsws"
|
||||
}
|
||||
|
||||
if ac.NatsProxyPort == "" {
|
||||
natsProxyPort = "443"
|
||||
}
|
||||
|
||||
// check if using nats standard tcp, otherwise use nats websockets by default
|
||||
var natsServer string
|
||||
var natsWsCompression bool
|
||||
if ac.NatsStandardPort != "" {
|
||||
natsServer = fmt.Sprintf("tls://%s:%s", ac.APIURL, ac.NatsStandardPort)
|
||||
} else {
|
||||
natsServer = fmt.Sprintf("wss://%s:%s", ac.APIURL, natsProxyPort)
|
||||
natsWsCompression = true
|
||||
}
|
||||
|
||||
var natsPingInterval int
|
||||
if ac.NatsPingInterval == 0 {
|
||||
natsPingInterval = randRange(35, 45)
|
||||
} else {
|
||||
natsPingInterval = ac.NatsPingInterval
|
||||
}
|
||||
|
||||
return &Agent{
|
||||
Hostname: info.Hostname,
|
||||
Arch: info.Architecture,
|
||||
BaseURL: ac.BaseURL,
|
||||
AgentID: ac.AgentID,
|
||||
ApiURL: ac.APIURL,
|
||||
Token: ac.Token,
|
||||
AgentPK: ac.PK,
|
||||
Cert: ac.Cert,
|
||||
ProgramDir: pd,
|
||||
EXE: exe,
|
||||
SystemDrive: sd,
|
||||
MeshInstaller: "meshagent.exe",
|
||||
MeshSystemEXE: MeshSysExe,
|
||||
MeshSVC: meshSvcName,
|
||||
PyBin: pybin,
|
||||
Headers: headers,
|
||||
Logger: logger,
|
||||
Version: version,
|
||||
Debug: logger.IsLevelEnabled(logrus.DebugLevel),
|
||||
rClient: restyC,
|
||||
Proxy: ac.Proxy,
|
||||
Platform: runtime.GOOS,
|
||||
GoArch: runtime.GOARCH,
|
||||
ServiceConfig: svcConf,
|
||||
Hostname: hostname,
|
||||
BaseURL: ac.BaseURL,
|
||||
AgentID: ac.AgentID,
|
||||
ApiURL: ac.APIURL,
|
||||
Token: ac.Token,
|
||||
AgentPK: ac.PK,
|
||||
Cert: ac.Cert,
|
||||
ProgramDir: pd,
|
||||
EXE: exe,
|
||||
SystemDrive: sd,
|
||||
WinTmpDir: winTempDir,
|
||||
WinRunAsUserTmpDir: winRunAsUserTmpDir,
|
||||
MeshInstaller: "meshagent.exe",
|
||||
MeshSystemEXE: MeshSysExe,
|
||||
MeshSVC: meshSvcName,
|
||||
PyBin: pybin,
|
||||
Headers: headers,
|
||||
Logger: logger,
|
||||
Version: version,
|
||||
Debug: logger.IsLevelEnabled(logrus.DebugLevel),
|
||||
rClient: restyC,
|
||||
Proxy: ac.Proxy,
|
||||
Platform: runtime.GOOS,
|
||||
GoArch: runtime.GOARCH,
|
||||
ServiceConfig: svcConf,
|
||||
NatsServer: natsServer,
|
||||
NatsProxyPath: natsProxyPath,
|
||||
NatsProxyPort: natsProxyPort,
|
||||
NatsPingInterval: natsPingInterval,
|
||||
NatsWSCompression: natsWsCompression,
|
||||
Insecure: insecure,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,12 +264,13 @@ type CmdOptions struct {
|
||||
IsScript bool
|
||||
IsExecutable bool
|
||||
Detached bool
|
||||
EnvVars []string
|
||||
}
|
||||
|
||||
func (a *Agent) NewCMDOpts() *CmdOptions {
|
||||
return &CmdOptions{
|
||||
Shell: "/bin/bash",
|
||||
Timeout: 30,
|
||||
Timeout: 60,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,11 +288,16 @@ func (a *Agent) CmdV2(c *CmdOptions) CmdStatus {
|
||||
// have a child process that is in a different process group so that
|
||||
// parent terminating doesn't kill child
|
||||
if c.Detached {
|
||||
cmdOptions.BeforeExec = []func(cmd *exec.Cmd){
|
||||
func(cmd *exec.Cmd) {
|
||||
cmd.SysProcAttr = SetDetached()
|
||||
},
|
||||
}
|
||||
cmdOptions.BeforeExec = append(cmdOptions.BeforeExec, func(cmd *exec.Cmd) {
|
||||
cmd.SysProcAttr = SetDetached()
|
||||
})
|
||||
}
|
||||
|
||||
if len(c.EnvVars) > 0 {
|
||||
cmdOptions.BeforeExec = append(cmdOptions.BeforeExec, func(cmd *exec.Cmd) {
|
||||
cmd.Env = os.Environ()
|
||||
cmd.Env = append(cmd.Env, c.EnvVars...)
|
||||
})
|
||||
}
|
||||
|
||||
var envCmd *gocmd.Cmd
|
||||
@@ -217,7 +306,8 @@ func (a *Agent) CmdV2(c *CmdOptions) CmdStatus {
|
||||
} else if c.IsExecutable {
|
||||
envCmd = gocmd.NewCmdOptions(cmdOptions, c.Shell, c.Command) // c.Shell: bin + c.Command: args as one string
|
||||
} else {
|
||||
envCmd = gocmd.NewCmdOptions(cmdOptions, c.Shell, "-c", c.Command) // /bin/bash -c 'ls -l /var/log/...'
|
||||
commandArray := append(strings.Fields(c.Shell), "-c", c.Command)
|
||||
envCmd = gocmd.NewCmdOptions(cmdOptions, commandArray[0], commandArray[1:]...) // /bin/bash -c 'ls -l /var/log/...'
|
||||
}
|
||||
|
||||
var stdoutBuf bytes.Buffer
|
||||
@@ -249,25 +339,46 @@ func (a *Agent) CmdV2(c *CmdOptions) CmdStatus {
|
||||
}
|
||||
}()
|
||||
|
||||
// Run and wait for Cmd to return, discard Status
|
||||
envCmd.Start()
|
||||
|
||||
statusChan := make(chan gocmd.Status, 1)
|
||||
// workaround for https://github.com/golang/go/issues/22315
|
||||
go func() {
|
||||
select {
|
||||
case <-doneChan:
|
||||
for i := 0; i < 5; i++ {
|
||||
finalStatus := <-envCmd.Start()
|
||||
if errors.Is(finalStatus.Error, syscall.ETXTBSY) {
|
||||
a.Logger.Errorln("CmdV2 syscall.ETXTBSY, retrying...")
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
statusChan <- finalStatus
|
||||
return
|
||||
case <-ctx.Done():
|
||||
a.Logger.Debugf("Command timed out after %d seconds\n", c.Timeout)
|
||||
pid := envCmd.Status().PID
|
||||
a.Logger.Debugln("Killing process with PID", pid)
|
||||
KillProc(int32(pid))
|
||||
}
|
||||
}()
|
||||
|
||||
var finalStatus gocmd.Status
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
a.Logger.Debugf("Command timed out after %d seconds\n", c.Timeout)
|
||||
pid := envCmd.Status().PID
|
||||
a.Logger.Debugln("Killing process with PID", pid)
|
||||
KillProc(int32(pid))
|
||||
finalStatus.Exit = 98
|
||||
ret := CmdStatus{
|
||||
Status: finalStatus,
|
||||
Stdout: CleanString(stdoutBuf.String()),
|
||||
Stderr: fmt.Sprintf("%s\nTimed out after %d seconds", CleanString(stderrBuf.String()), c.Timeout),
|
||||
}
|
||||
a.Logger.Debugf("%+v\n", ret)
|
||||
return ret
|
||||
case finalStatus = <-statusChan:
|
||||
// done
|
||||
}
|
||||
|
||||
// Wait for goroutine to print everything
|
||||
<-doneChan
|
||||
|
||||
ret := CmdStatus{
|
||||
Status: envCmd.Status(),
|
||||
Status: finalStatus,
|
||||
Stdout: CleanString(stdoutBuf.String()),
|
||||
Stderr: CleanString(stderrBuf.String()),
|
||||
}
|
||||
@@ -325,7 +436,7 @@ func (a *Agent) ForceKillMesh() {
|
||||
}
|
||||
|
||||
for _, pid := range pids {
|
||||
a.Logger.Debugln("Killing mesh process with pid %d", pid)
|
||||
a.Logger.Debugln("Killing mesh process with pid:", pid)
|
||||
if err := KillProc(int32(pid)); err != nil {
|
||||
a.Logger.Debugln(err)
|
||||
}
|
||||
@@ -353,13 +464,37 @@ func (a *Agent) SyncMeshNodeID() {
|
||||
}
|
||||
|
||||
func (a *Agent) setupNatsOptions() []nats.Option {
|
||||
reconnectWait := randRange(2, 8)
|
||||
opts := make([]nats.Option, 0)
|
||||
opts = append(opts, nats.Name("TacticalRMM"))
|
||||
opts = append(opts, nats.Name(a.AgentID))
|
||||
opts = append(opts, nats.UserInfo(a.AgentID, a.Token))
|
||||
opts = append(opts, nats.ReconnectWait(time.Second*5))
|
||||
opts = append(opts, nats.ReconnectWait(time.Duration(reconnectWait)*time.Second))
|
||||
opts = append(opts, nats.RetryOnFailedConnect(true))
|
||||
opts = append(opts, nats.IgnoreAuthErrorAbort())
|
||||
opts = append(opts, nats.PingInterval(time.Duration(a.NatsPingInterval)*time.Second))
|
||||
opts = append(opts, nats.Compression(a.NatsWSCompression))
|
||||
opts = append(opts, nats.MaxReconnects(-1))
|
||||
opts = append(opts, nats.ReconnectBufSize(-1))
|
||||
opts = append(opts, nats.ProxyPath(a.NatsProxyPath))
|
||||
opts = append(opts, nats.ReconnectJitter(500*time.Millisecond, 4*time.Second))
|
||||
opts = append(opts, nats.DisconnectErrHandler(func(nc *nats.Conn, err error) {
|
||||
a.Logger.Debugln("NATS disconnected:", err)
|
||||
a.Logger.Debugf("%+v\n", nc.Statistics)
|
||||
}))
|
||||
opts = append(opts, nats.ReconnectHandler(func(nc *nats.Conn) {
|
||||
a.Logger.Debugln("NATS reconnected")
|
||||
a.Logger.Debugf("%+v\n", nc.Statistics)
|
||||
}))
|
||||
opts = append(opts, nats.ErrorHandler(func(nc *nats.Conn, sub *nats.Subscription, err error) {
|
||||
a.Logger.Errorln("NATS error:", err)
|
||||
a.Logger.Errorf("%+v\n", sub)
|
||||
}))
|
||||
if a.Insecure {
|
||||
insecureConf := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
opts = append(opts, nats.Secure(insecureConf))
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
@@ -379,46 +514,47 @@ func (a *Agent) GetUninstallExe() string {
|
||||
}
|
||||
|
||||
func (a *Agent) CleanupAgentUpdates() {
|
||||
cderr := os.Chdir(a.ProgramDir)
|
||||
if cderr != nil {
|
||||
a.Logger.Errorln(cderr)
|
||||
return
|
||||
}
|
||||
// TODO remove a.ProgramDir, updates are now in winTempDir
|
||||
dirs := [3]string{a.WinTmpDir, os.Getenv("TMP"), a.ProgramDir}
|
||||
for _, dir := range dirs {
|
||||
err := os.Chdir(dir)
|
||||
if err != nil {
|
||||
a.Logger.Debugln("CleanupAgentUpdates()", dir, err)
|
||||
continue
|
||||
}
|
||||
|
||||
files, err := filepath.Glob("winagent-v*.exe")
|
||||
if err == nil {
|
||||
for _, f := range files {
|
||||
os.Remove(f)
|
||||
// TODO winagent-v* is deprecated
|
||||
globs := [3]string{"tacticalagent-v*", "is-*.tmp", "winagent-v*"}
|
||||
for _, glob := range globs {
|
||||
files, err := filepath.Glob(glob)
|
||||
if err == nil {
|
||||
for _, f := range files {
|
||||
a.Logger.Debugln("CleanupAgentUpdates() Removing file:", f)
|
||||
os.Remove(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cderr = os.Chdir(os.Getenv("TMP"))
|
||||
if cderr != nil {
|
||||
a.Logger.Errorln(cderr)
|
||||
return
|
||||
}
|
||||
folders, err := filepath.Glob("tacticalrmm*")
|
||||
err := os.Chdir(os.Getenv("TMP"))
|
||||
if err == nil {
|
||||
for _, f := range folders {
|
||||
os.RemoveAll(f)
|
||||
dirs, err := filepath.Glob("tacticalrmm*")
|
||||
if err == nil {
|
||||
for _, f := range dirs {
|
||||
os.RemoveAll(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) RunPythonCode(code string, timeout int, args []string) (string, error) {
|
||||
content := []byte(code)
|
||||
dir, err := ioutil.TempDir("", "tacticalpy")
|
||||
if err != nil {
|
||||
a.Logger.Debugln(err)
|
||||
return "", err
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
tmpfn, _ := ioutil.TempFile(dir, "*.py")
|
||||
tmpfn, _ := os.CreateTemp(a.WinTmpDir, "*.py")
|
||||
if _, err := tmpfn.Write(content); err != nil {
|
||||
a.Logger.Debugln(err)
|
||||
return "", err
|
||||
}
|
||||
defer os.Remove(tmpfn.Name())
|
||||
if err := tmpfn.Close(); err != nil {
|
||||
a.Logger.Debugln(err)
|
||||
return "", err
|
||||
@@ -458,13 +594,12 @@ func (a *Agent) RunPythonCode(code string, timeout int, args []string) (string,
|
||||
|
||||
}
|
||||
|
||||
func (a *Agent) CreateTRMMTempDir() {
|
||||
// create the temp dir for running scripts
|
||||
dir := filepath.Join(os.TempDir(), "trmm")
|
||||
if !trmm.FileExists(dir) {
|
||||
err := os.Mkdir(dir, 0775)
|
||||
func createWinTempDir() error {
|
||||
if !trmm.FileExists(defaultWinTmpDir) {
|
||||
err := os.Mkdir(defaultWinTmpDir, 0775)
|
||||
if err != nil {
|
||||
a.Logger.Errorln(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@@ -1,5 +1,8 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
/*
|
||||
Copyright 2022 AmidaWare LLC.
|
||||
Copyright 2023 AmidaWare Inc.
|
||||
|
||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||
You may only use the Licensed Software in accordance with the License.
|
||||
@@ -13,8 +16,11 @@ package agent
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -46,7 +52,7 @@ func (a *Agent) GetDisks() []trmm.Disk {
|
||||
}
|
||||
|
||||
for _, p := range partitions {
|
||||
if strings.Contains(p.Device, "dev/loop") {
|
||||
if strings.Contains(p.Device, "dev/loop") || strings.Contains(p.Device, "devfs") {
|
||||
continue
|
||||
}
|
||||
usage, err := disk.Usage(p.Mountpoint)
|
||||
@@ -70,12 +76,35 @@ func (a *Agent) GetDisks() []trmm.Disk {
|
||||
}
|
||||
|
||||
func (a *Agent) SystemRebootRequired() (bool, error) {
|
||||
paths := [2]string{"/var/run/reboot-required", "/usr/bin/needs-restarting"}
|
||||
// deb
|
||||
paths := [2]string{"/var/run/reboot-required", "/run/reboot-required"}
|
||||
for _, p := range paths {
|
||||
if trmm.FileExists(p) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
// rhel
|
||||
bins := [2]string{"/usr/bin/needs-restarting", "/bin/needs-restarting"}
|
||||
for _, bin := range bins {
|
||||
if trmm.FileExists(bin) {
|
||||
opts := a.NewCMDOpts()
|
||||
// https://man7.org/linux/man-pages/man1/needs-restarting.1.html
|
||||
// -r Only report whether a full reboot is required (exit code 1) or not (exit code 0).
|
||||
opts.Command = fmt.Sprintf("%s -r", bin)
|
||||
out := a.CmdV2(opts)
|
||||
|
||||
if out.Status.Error != nil {
|
||||
a.Logger.Debugln("SystemRebootRequired(): ", out.Status.Error.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
if out.Status.Exit == 1 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@@ -119,26 +148,31 @@ func NewAgentConfig() *rmm.AgentConfig {
|
||||
pk, _ := strconv.Atoi(agentpk)
|
||||
|
||||
ret := &rmm.AgentConfig{
|
||||
BaseURL: viper.GetString("baseurl"),
|
||||
AgentID: viper.GetString("agentid"),
|
||||
APIURL: viper.GetString("apiurl"),
|
||||
Token: viper.GetString("token"),
|
||||
AgentPK: agentpk,
|
||||
PK: pk,
|
||||
Cert: viper.GetString("cert"),
|
||||
Proxy: viper.GetString("proxy"),
|
||||
CustomMeshDir: viper.GetString("meshdir"),
|
||||
BaseURL: viper.GetString("baseurl"),
|
||||
AgentID: viper.GetString("agentid"),
|
||||
APIURL: viper.GetString("apiurl"),
|
||||
Token: viper.GetString("token"),
|
||||
AgentPK: agentpk,
|
||||
PK: pk,
|
||||
Cert: viper.GetString("cert"),
|
||||
Proxy: viper.GetString("proxy"),
|
||||
CustomMeshDir: viper.GetString("meshdir"),
|
||||
NatsProxyPath: viper.GetString("natsproxypath"),
|
||||
NatsProxyPort: viper.GetString("natsproxyport"),
|
||||
NatsStandardPort: viper.GetString("natsstandardport"),
|
||||
NatsPingInterval: viper.GetInt("natspinginterval"),
|
||||
Insecure: viper.GetString("insecure"),
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (a *Agent) RunScript(code string, shell string, args []string, timeout int) (stdout, stderr string, exitcode int, e error) {
|
||||
func (a *Agent) RunScript(code string, shell string, args []string, timeout int, runasuser bool, envVars []string) (stdout, stderr string, exitcode int, e error) {
|
||||
code = removeWinNewLines(code)
|
||||
content := []byte(code)
|
||||
|
||||
f, err := createTmpFile()
|
||||
f, err := createNixTmpFile()
|
||||
if err != nil {
|
||||
a.Logger.Errorln("RunScript createTmpFile()", err)
|
||||
a.Logger.Errorln("RunScript createNixTmpFile()", err)
|
||||
return "", err.Error(), 85, err
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
@@ -162,6 +196,7 @@ func (a *Agent) RunScript(code string, shell string, args []string, timeout int)
|
||||
opts.IsScript = true
|
||||
opts.Shell = f.Name()
|
||||
opts.Args = args
|
||||
opts.EnvVars = envVars
|
||||
opts.Timeout = time.Duration(timeout)
|
||||
out := a.CmdV2(opts)
|
||||
retError := ""
|
||||
@@ -179,23 +214,34 @@ func SetDetached() *syscall.SysProcAttr {
|
||||
return &syscall.SysProcAttr{Setpgid: true}
|
||||
}
|
||||
|
||||
func (a *Agent) AgentUpdate(url, inno, version string) {
|
||||
func (a *Agent) seEnforcing() bool {
|
||||
opts := a.NewCMDOpts()
|
||||
opts.Command = "getenforce"
|
||||
out := a.CmdV2(opts)
|
||||
return out.Status.Exit == 0 && strings.Contains(out.Stdout, "Enforcing")
|
||||
}
|
||||
|
||||
func (a *Agent) AgentUpdate(url, inno, version string) error {
|
||||
|
||||
self, err := os.Executable()
|
||||
if err != nil {
|
||||
a.Logger.Errorln("AgentUpdate() os.Executable():", err)
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := createTmpFile()
|
||||
// more reliable method to get current working directory than os.Getwd()
|
||||
cwd := filepath.Dir(self)
|
||||
// create a tmpfile in same location as current binary
|
||||
// avoids issues with /tmp dir and other fs mount issues
|
||||
f, err := os.CreateTemp(cwd, "trmm")
|
||||
if err != nil {
|
||||
a.Logger.Errorln("AgentUpdate createTmpFile()", err)
|
||||
return
|
||||
a.Logger.Errorln("AgentUpdate() os.CreateTemp:", err)
|
||||
return err
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
a.Logger.Infof("Agent updating from %s to %s", a.Version, version)
|
||||
a.Logger.Infoln("Downloading agent update from", url)
|
||||
a.Logger.Debugln("Downloading agent update from", url)
|
||||
|
||||
rClient := resty.New()
|
||||
rClient.SetCloseConnection(true)
|
||||
@@ -204,17 +250,23 @@ func (a *Agent) AgentUpdate(url, inno, version string) {
|
||||
if len(a.Proxy) > 0 {
|
||||
rClient.SetProxy(a.Proxy)
|
||||
}
|
||||
if a.Insecure {
|
||||
insecureConf := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
rClient.SetTLSClientConfig(insecureConf)
|
||||
}
|
||||
|
||||
r, err := rClient.R().SetOutput(f.Name()).Get(url)
|
||||
if err != nil {
|
||||
a.Logger.Errorln("AgentUpdate() download:", err)
|
||||
f.Close()
|
||||
return
|
||||
return err
|
||||
}
|
||||
if r.IsError() {
|
||||
a.Logger.Errorln("AgentUpdate() status code:", r.StatusCode())
|
||||
f.Close()
|
||||
return
|
||||
return errors.New("err")
|
||||
}
|
||||
|
||||
f.Close()
|
||||
@@ -222,19 +274,35 @@ func (a *Agent) AgentUpdate(url, inno, version string) {
|
||||
err = os.Rename(f.Name(), self)
|
||||
if err != nil {
|
||||
a.Logger.Errorln("AgentUpdate() os.Rename():", err)
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
if runtime.GOOS == "linux" && a.seEnforcing() {
|
||||
se := a.NewCMDOpts()
|
||||
se.Command = fmt.Sprintf("restorecon -rv %s", self)
|
||||
out := a.CmdV2(se)
|
||||
a.Logger.Debugf("%+v\n", out)
|
||||
}
|
||||
|
||||
opts := a.NewCMDOpts()
|
||||
opts.Detached = true
|
||||
opts.Command = "systemctl restart tacticalagent.service"
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
opts.Command = "systemctl restart tacticalagent.service"
|
||||
case "darwin":
|
||||
opts.Command = "launchctl kickstart -k system/tacticalagent"
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
a.CmdV2(opts)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Agent) AgentUninstall(code string) {
|
||||
f, err := createTmpFile()
|
||||
f, err := createNixTmpFile()
|
||||
if err != nil {
|
||||
a.Logger.Errorln("AgentUninstall createTmpFile():", err)
|
||||
a.Logger.Errorln("AgentUninstall createNixTmpFile():", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -245,7 +313,9 @@ func (a *Agent) AgentUninstall(code string) {
|
||||
opts := a.NewCMDOpts()
|
||||
opts.IsScript = true
|
||||
opts.Shell = f.Name()
|
||||
opts.Args = []string{"uninstall"}
|
||||
if runtime.GOOS == "linux" {
|
||||
opts.Args = []string{"uninstall"}
|
||||
}
|
||||
opts.Detached = true
|
||||
a.CmdV2(opts)
|
||||
}
|
||||
@@ -289,7 +359,15 @@ func (a *Agent) getMeshNodeID() (string, error) {
|
||||
func (a *Agent) RecoverMesh() {
|
||||
a.Logger.Infoln("Attempting mesh recovery")
|
||||
opts := a.NewCMDOpts()
|
||||
opts.Command = "systemctl restart meshagent.service"
|
||||
def := "systemctl restart meshagent.service"
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
opts.Command = def
|
||||
case "darwin":
|
||||
opts.Command = "launchctl kickstart -k system/meshagent"
|
||||
default:
|
||||
opts.Command = def
|
||||
}
|
||||
a.CmdV2(opts)
|
||||
a.SyncMeshNodeID()
|
||||
}
|
||||
@@ -317,11 +395,12 @@ func (a *Agent) GetWMIInfo() map[string]interface{} {
|
||||
|
||||
// disks
|
||||
block, err := ghw.Block(ghw.WithDisableWarnings())
|
||||
ignore := []string{"ram", "loop"}
|
||||
if err != nil {
|
||||
a.Logger.Errorln("ghw.Block()", err)
|
||||
} else {
|
||||
for _, disk := range block.Disks {
|
||||
if disk.IsRemovable || strings.Contains(disk.Name, "ram") {
|
||||
if disk.IsRemovable || contains(disk.Name, ignore) {
|
||||
continue
|
||||
}
|
||||
ret := fmt.Sprintf("%s %s %s %s %s %s", disk.Vendor, disk.Model, disk.StorageController, disk.DriveType, disk.Name, ByteCountSI(disk.SizeBytes))
|
||||
@@ -348,18 +427,25 @@ func (a *Agent) GetWMIInfo() map[string]interface{} {
|
||||
wmiInfo["make_model"] = ""
|
||||
chassis, err := ghw.Chassis(ghw.WithDisableWarnings())
|
||||
if err != nil {
|
||||
a.Logger.Errorln("ghw.Chassis()", err)
|
||||
a.Logger.Debugln("ghw.Chassis()", err)
|
||||
} else {
|
||||
if chassis.Vendor != "" || chassis.Version != "" {
|
||||
wmiInfo["make_model"] = fmt.Sprintf("%s %s", chassis.Vendor, chassis.Version)
|
||||
}
|
||||
}
|
||||
|
||||
if runtime.GOOS == "darwin" {
|
||||
opts := a.NewCMDOpts()
|
||||
opts.Command = "sysctl hw.model"
|
||||
out := a.CmdV2(opts)
|
||||
wmiInfo["make_model"] = strings.ReplaceAll(out.Stdout, "hw.model: ", "")
|
||||
}
|
||||
|
||||
// gfx cards
|
||||
|
||||
gpu, err := ghw.GPU(ghw.WithDisableWarnings())
|
||||
if err != nil {
|
||||
a.Logger.Errorln("ghw.GPU()", err)
|
||||
a.Logger.Debugln("ghw.GPU()", err)
|
||||
} else {
|
||||
for _, i := range gpu.GraphicsCards {
|
||||
if i.DeviceInfo != nil {
|
||||
@@ -387,6 +473,30 @@ func (a *Agent) GetWMIInfo() map[string]interface{} {
|
||||
}
|
||||
}
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
baseboard, err := ghw.Baseboard()
|
||||
if err != nil {
|
||||
a.Logger.Debugln("ghw.Baseboard()", err)
|
||||
wmiInfo["serialnumber"] = "n/a"
|
||||
} else {
|
||||
wmiInfo["serialnumber"] = baseboard.SerialNumber
|
||||
}
|
||||
case "darwin":
|
||||
opts := a.NewCMDOpts()
|
||||
serialCmd := `ioreg -l | grep IOPlatformSerialNumber | grep -o '"IOPlatformSerialNumber" = "[^"]*"' | awk -F'"' '{print $4}'`
|
||||
opts.Command = serialCmd
|
||||
out := a.CmdV2(opts)
|
||||
if out.Status.Error != nil {
|
||||
a.Logger.Debugln("ioreg get serial number: ", out.Status.Error.Error())
|
||||
wmiInfo["serialnumber"] = "n/a"
|
||||
} else {
|
||||
wmiInfo["serialnumber"] = removeNewlines(out.Stdout)
|
||||
}
|
||||
default:
|
||||
wmiInfo["serialnumber"] = "n/a"
|
||||
}
|
||||
|
||||
if len(cpus) == 0 {
|
||||
wmiInfo["cpus"] = []string{makeModel}
|
||||
}
|
||||
@@ -401,6 +511,9 @@ func (a *Agent) GetWMIInfo() map[string]interface{} {
|
||||
}
|
||||
|
||||
// windows only below TODO add into stub file
|
||||
func (a *Agent) GetAgentCheckInConfig(ret AgentCheckInConfig) AgentCheckInConfig {
|
||||
return ret
|
||||
}
|
||||
|
||||
func (a *Agent) PlatVer() (string, error) { return "", nil }
|
||||
|
||||
@@ -456,7 +569,7 @@ func (a *Agent) installMesh(meshbin, exe, proxy string) (string, error) {
|
||||
return "not implemented", nil
|
||||
}
|
||||
|
||||
func CMDShell(shell string, cmdArgs []string, command string, timeout int, detached bool) (output [2]string, e error) {
|
||||
func CMDShell(shell string, cmdArgs []string, command string, timeout int, detached bool, runasuser bool) (output [2]string, e error) {
|
||||
return [2]string{"", ""}, nil
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 AmidaWare LLC.
|
||||
Copyright 2023 AmidaWare Inc.
|
||||
|
||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||
You may only use the Licensed Software in accordance with the License.
|
||||
@@ -14,9 +14,9 @@ package agent
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
|
||||
rmm "github.com/amidaware/rmmagent/shared"
|
||||
ps "github.com/elastic/go-sysinfo"
|
||||
"github.com/fourcorelabs/wintoken"
|
||||
"github.com/go-ole/go-ole"
|
||||
"github.com/go-ole/go-ole/oleutil"
|
||||
"github.com/go-resty/resty/v2"
|
||||
@@ -61,27 +62,43 @@ func NewAgentConfig() *rmm.AgentConfig {
|
||||
cert, _, _ := k.GetStringValue("Cert")
|
||||
proxy, _, _ := k.GetStringValue("Proxy")
|
||||
customMeshDir, _, _ := k.GetStringValue("MeshDir")
|
||||
winTmpDir, _, _ := k.GetStringValue("WinTmpDir")
|
||||
winRunAsUserTmpDir, _, _ := k.GetStringValue("WinRunAsUserTmpDir")
|
||||
natsProxyPath, _, _ := k.GetStringValue("NatsProxyPath")
|
||||
natsProxyPort, _, _ := k.GetStringValue("NatsProxyPort")
|
||||
natsStandardPort, _, _ := k.GetStringValue("NatsStandardPort")
|
||||
natsPingInterval, _, _ := k.GetStringValue("NatsPingInterval")
|
||||
npi, _ := strconv.Atoi(natsPingInterval)
|
||||
insecure, _, _ := k.GetStringValue("Insecure")
|
||||
|
||||
return &rmm.AgentConfig{
|
||||
BaseURL: baseurl,
|
||||
AgentID: agentid,
|
||||
APIURL: apiurl,
|
||||
Token: token,
|
||||
AgentPK: agentpk,
|
||||
PK: pk,
|
||||
Cert: cert,
|
||||
Proxy: proxy,
|
||||
CustomMeshDir: customMeshDir,
|
||||
BaseURL: baseurl,
|
||||
AgentID: agentid,
|
||||
APIURL: apiurl,
|
||||
Token: token,
|
||||
AgentPK: agentpk,
|
||||
PK: pk,
|
||||
Cert: cert,
|
||||
Proxy: proxy,
|
||||
CustomMeshDir: customMeshDir,
|
||||
WinTmpDir: winTmpDir,
|
||||
WinRunAsUserTmpDir: winRunAsUserTmpDir,
|
||||
NatsProxyPath: natsProxyPath,
|
||||
NatsProxyPort: natsProxyPort,
|
||||
NatsStandardPort: natsStandardPort,
|
||||
NatsPingInterval: npi,
|
||||
Insecure: insecure,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) RunScript(code string, shell string, args []string, timeout int) (stdout, stderr string, exitcode int, e error) {
|
||||
func (a *Agent) RunScript(code string, shell string, args []string, timeout int, runasuser bool, envVars []string) (stdout, stderr string, exitcode int, e error) {
|
||||
|
||||
content := []byte(code)
|
||||
|
||||
dir := filepath.Join(os.TempDir(), "trmm")
|
||||
if !trmm.FileExists(dir) {
|
||||
a.CreateTRMMTempDir()
|
||||
err := createWinTempDir()
|
||||
if err != nil {
|
||||
a.Logger.Errorln(err)
|
||||
return "", err.Error(), 85, err
|
||||
}
|
||||
|
||||
const defaultExitCode = 1
|
||||
@@ -103,7 +120,13 @@ func (a *Agent) RunScript(code string, shell string, args []string, timeout int)
|
||||
ext = "*.bat"
|
||||
}
|
||||
|
||||
tmpfn, err := ioutil.TempFile(dir, ext)
|
||||
tmpDir := a.WinTmpDir
|
||||
|
||||
if runasuser {
|
||||
tmpDir = a.WinRunAsUserTmpDir
|
||||
}
|
||||
|
||||
tmpfn, err := os.CreateTemp(tmpDir, ext)
|
||||
if err != nil {
|
||||
a.Logger.Errorln(err)
|
||||
return "", err.Error(), 85, err
|
||||
@@ -121,7 +144,7 @@ func (a *Agent) RunScript(code string, shell string, args []string, timeout int)
|
||||
|
||||
switch shell {
|
||||
case "powershell":
|
||||
exe = "Powershell"
|
||||
exe = getPowershellExe()
|
||||
cmdArgs = []string{"-NonInteractive", "-NoProfile", "-ExecutionPolicy", "Bypass", tmpfn.Name()}
|
||||
case "python":
|
||||
exe = a.PyBin
|
||||
@@ -137,8 +160,35 @@ func (a *Agent) RunScript(code string, shell string, args []string, timeout int)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var timedOut bool = false
|
||||
var timedOut = false
|
||||
var token *wintoken.Token
|
||||
var envBlock *uint16
|
||||
usingEnvVars := len(envVars) > 0
|
||||
cmd := exec.Command(exe, cmdArgs...)
|
||||
if runasuser {
|
||||
token, err = wintoken.GetInteractiveToken(wintoken.TokenImpersonation)
|
||||
if err == nil {
|
||||
defer token.Close()
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Token: syscall.Token(token.Token()), HideWindow: true}
|
||||
|
||||
if usingEnvVars {
|
||||
envBlock, err = CreateEnvironmentBlock(syscall.Token(token.Token()))
|
||||
if err == nil {
|
||||
defer DestroyEnvironmentBlock(envBlock)
|
||||
userEnv := EnvironmentBlockToSlice(envBlock)
|
||||
cmd.Env = userEnv
|
||||
} else {
|
||||
cmd.Env = os.Environ()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if usingEnvVars {
|
||||
cmd.Env = os.Environ()
|
||||
}
|
||||
|
||||
if usingEnvVars {
|
||||
cmd.Env = append(cmd.Env, envVars...)
|
||||
}
|
||||
cmd.Stdout = &outb
|
||||
cmd.Stderr = &errb
|
||||
|
||||
@@ -224,7 +274,7 @@ func CMD(exe string, args []string, timeout int, detached bool) (output [2]strin
|
||||
return [2]string{CleanString(outb.String()), CleanString(errb.String())}, nil
|
||||
}
|
||||
|
||||
func CMDShell(shell string, cmdArgs []string, command string, timeout int, detached bool) (output [2]string, e error) {
|
||||
func CMDShell(shell string, cmdArgs []string, command string, timeout int, detached bool, runasuser bool) (output [2]string, e error) {
|
||||
var (
|
||||
outb bytes.Buffer
|
||||
errb bytes.Buffer
|
||||
@@ -235,33 +285,45 @@ func CMDShell(shell string, cmdArgs []string, command string, timeout int, detac
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
sysProcAttr := &windows.SysProcAttr{}
|
||||
cmdExe := getCMDExe()
|
||||
powershell := getPowershellExe()
|
||||
|
||||
if len(cmdArgs) > 0 && command == "" {
|
||||
switch shell {
|
||||
case "cmd":
|
||||
cmdArgs = append([]string{"/C"}, cmdArgs...)
|
||||
cmd = exec.Command("cmd.exe", cmdArgs...)
|
||||
cmd = exec.Command(cmdExe, cmdArgs...)
|
||||
case "powershell":
|
||||
cmdArgs = append([]string{"-NonInteractive", "-NoProfile"}, cmdArgs...)
|
||||
cmd = exec.Command("powershell.exe", cmdArgs...)
|
||||
cmd = exec.Command(powershell, cmdArgs...)
|
||||
}
|
||||
} else {
|
||||
switch shell {
|
||||
case "cmd":
|
||||
cmd = exec.Command("cmd.exe")
|
||||
cmd.SysProcAttr = &windows.SysProcAttr{
|
||||
CmdLine: fmt.Sprintf("cmd.exe /C %s", command),
|
||||
}
|
||||
cmd = exec.Command(cmdExe)
|
||||
sysProcAttr.CmdLine = fmt.Sprintf("%s /C %s", cmdExe, command)
|
||||
case "powershell":
|
||||
cmd = exec.Command("Powershell", "-NonInteractive", "-NoProfile", command)
|
||||
cmd = exec.Command(powershell, "-NonInteractive", "-NoProfile", command)
|
||||
}
|
||||
}
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags
|
||||
if detached {
|
||||
cmd.SysProcAttr = &windows.SysProcAttr{
|
||||
CreationFlags: windows.DETACHED_PROCESS | windows.CREATE_NEW_PROCESS_GROUP,
|
||||
}
|
||||
sysProcAttr.CreationFlags = windows.DETACHED_PROCESS | windows.CREATE_NEW_PROCESS_GROUP
|
||||
}
|
||||
|
||||
if runasuser {
|
||||
token, err := wintoken.GetInteractiveToken(wintoken.TokenImpersonation)
|
||||
if err != nil {
|
||||
return [2]string{"", CleanString(err.Error())}, err
|
||||
}
|
||||
defer token.Close()
|
||||
sysProcAttr.Token = syscall.Token(token.Token())
|
||||
sysProcAttr.HideWindow = true
|
||||
}
|
||||
|
||||
cmd.SysProcAttr = sysProcAttr
|
||||
cmd.Stdout = &outb
|
||||
cmd.Stderr = &errb
|
||||
cmd.Start()
|
||||
@@ -443,7 +505,7 @@ func (a *Agent) PlatVer() (string, error) {
|
||||
func EnablePing() {
|
||||
args := make([]string, 0)
|
||||
cmd := `netsh advfirewall firewall add rule name="ICMP Allow incoming V4 echo request" protocol=icmpv4:8,any dir=in action=allow`
|
||||
_, err := CMDShell("cmd", args, cmd, 10, false)
|
||||
_, err := CMDShell("cmd", args, cmd, 10, false, false)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
@@ -464,7 +526,7 @@ func EnableRDP() {
|
||||
|
||||
args := make([]string, 0)
|
||||
cmd := `netsh advfirewall firewall set rule group="remote desktop" new enable=Yes`
|
||||
_, cerr := CMDShell("cmd", args, cmd, 10, false)
|
||||
_, cerr := CMDShell("cmd", args, cmd, 10, false, false)
|
||||
if cerr != nil {
|
||||
fmt.Println(cerr)
|
||||
}
|
||||
@@ -491,15 +553,15 @@ func DisableSleepHibernate() {
|
||||
wg.Add(1)
|
||||
go func(c string) {
|
||||
defer wg.Done()
|
||||
_, _ = CMDShell("cmd", args, fmt.Sprintf("powercfg /set%svalueindex scheme_current sub_buttons lidaction 0", c), 5, false)
|
||||
_, _ = CMDShell("cmd", args, fmt.Sprintf("powercfg /x -standby-timeout-%s 0", c), 5, false)
|
||||
_, _ = CMDShell("cmd", args, fmt.Sprintf("powercfg /x -hibernate-timeout-%s 0", c), 5, false)
|
||||
_, _ = CMDShell("cmd", args, fmt.Sprintf("powercfg /x -disk-timeout-%s 0", c), 5, false)
|
||||
_, _ = CMDShell("cmd", args, fmt.Sprintf("powercfg /x -monitor-timeout-%s 0", c), 5, false)
|
||||
_, _ = CMDShell("cmd", args, fmt.Sprintf("powercfg /set%svalueindex scheme_current sub_buttons lidaction 0", c), 5, false, false)
|
||||
_, _ = CMDShell("cmd", args, fmt.Sprintf("powercfg /x -standby-timeout-%s 0", c), 5, false, false)
|
||||
_, _ = CMDShell("cmd", args, fmt.Sprintf("powercfg /x -hibernate-timeout-%s 0", c), 5, false, false)
|
||||
_, _ = CMDShell("cmd", args, fmt.Sprintf("powercfg /x -disk-timeout-%s 0", c), 5, false, false)
|
||||
_, _ = CMDShell("cmd", args, fmt.Sprintf("powercfg /x -monitor-timeout-%s 0", c), 5, false, false)
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
_, _ = CMDShell("cmd", args, "powercfg -S SCHEME_CURRENT", 5, false)
|
||||
_, _ = CMDShell("cmd", args, "powercfg -S SCHEME_CURRENT", 5, false, false)
|
||||
}
|
||||
|
||||
// NewCOMObject creates a new COM object for the specifed ProgramID.
|
||||
@@ -551,15 +613,18 @@ func (a *Agent) UninstallCleanup() {
|
||||
a.PatchMgmnt(false)
|
||||
a.CleanupAgentUpdates()
|
||||
CleanupSchedTasks()
|
||||
os.RemoveAll(a.WinTmpDir)
|
||||
os.RemoveAll(a.WinRunAsUserTmpDir)
|
||||
}
|
||||
|
||||
func (a *Agent) AgentUpdate(url, inno, version string) {
|
||||
func (a *Agent) AgentUpdate(url, inno, version string) error {
|
||||
time.Sleep(time.Duration(randRange(1, 15)) * time.Second)
|
||||
a.KillHungUpdates()
|
||||
time.Sleep(1 * time.Second)
|
||||
a.CleanupAgentUpdates()
|
||||
updater := filepath.Join(a.ProgramDir, inno)
|
||||
updater := filepath.Join(a.WinTmpDir, inno)
|
||||
a.Logger.Infof("Agent updating from %s to %s", a.Version, version)
|
||||
a.Logger.Infoln("Downloading agent update from", url)
|
||||
a.Logger.Debugln("Downloading agent update from", url)
|
||||
|
||||
rClient := resty.New()
|
||||
rClient.SetCloseConnection(true)
|
||||
@@ -568,26 +633,24 @@ func (a *Agent) AgentUpdate(url, inno, version string) {
|
||||
if len(a.Proxy) > 0 {
|
||||
rClient.SetProxy(a.Proxy)
|
||||
}
|
||||
if a.Insecure {
|
||||
insecureConf := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
rClient.SetTLSClientConfig(insecureConf)
|
||||
}
|
||||
r, err := rClient.R().SetOutput(updater).Get(url)
|
||||
if err != nil {
|
||||
a.Logger.Errorln(err)
|
||||
CMD("net", []string{"start", winSvcName}, 10, false)
|
||||
return
|
||||
return err
|
||||
}
|
||||
if r.IsError() {
|
||||
a.Logger.Errorln("Download failed with status code", r.StatusCode())
|
||||
CMD("net", []string{"start", winSvcName}, 10, false)
|
||||
return
|
||||
ret := fmt.Sprintf("Download failed with status code %d", r.StatusCode())
|
||||
a.Logger.Errorln(ret)
|
||||
return errors.New(ret)
|
||||
}
|
||||
|
||||
dir, err := ioutil.TempDir("", "tacticalrmm")
|
||||
if err != nil {
|
||||
a.Logger.Errorln("Agentupdate create tempdir:", err)
|
||||
CMD("net", []string{"start", winSvcName}, 10, false)
|
||||
return
|
||||
}
|
||||
|
||||
innoLogFile := filepath.Join(dir, "tacticalrmm.txt")
|
||||
innoLogFile := filepath.Join(a.WinTmpDir, fmt.Sprintf("tacticalagent_update_v%s.txt", version))
|
||||
|
||||
args := []string{"/C", updater, "/VERYSILENT", fmt.Sprintf("/LOG=%s", innoLogFile)}
|
||||
cmd := exec.Command("cmd.exe", args...)
|
||||
@@ -596,6 +659,7 @@ func (a *Agent) AgentUpdate(url, inno, version string) {
|
||||
}
|
||||
cmd.Start()
|
||||
time.Sleep(1 * time.Second)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Agent) osString() string {
|
||||
@@ -632,19 +696,6 @@ func (a *Agent) AgentUninstall(code string) {
|
||||
cmd.Start()
|
||||
}
|
||||
|
||||
func (a *Agent) addDefenderExlusions() {
|
||||
code := `
|
||||
Add-MpPreference -ExclusionPath 'C:\Program Files\TacticalAgent\*'
|
||||
Add-MpPreference -ExclusionPath 'C:\Windows\Temp\winagent-v*.exe'
|
||||
Add-MpPreference -ExclusionPath 'C:\Windows\Temp\trmm\*'
|
||||
Add-MpPreference -ExclusionPath 'C:\Program Files\Mesh Agent\*'
|
||||
`
|
||||
_, _, _, err := a.RunScript(code, "powershell", []string{}, 20)
|
||||
if err != nil {
|
||||
a.Logger.Debugln(err)
|
||||
}
|
||||
}
|
||||
|
||||
// RunMigrations cleans up unused stuff from older agents
|
||||
func (a *Agent) RunMigrations() {
|
||||
for _, i := range []string{"nssm.exe", "nssm-x86.exe"} {
|
||||
@@ -847,6 +898,43 @@ func (a *Agent) InstallService() error {
|
||||
return service.Control(s, "install")
|
||||
}
|
||||
|
||||
func (a *Agent) GetAgentCheckInConfig(ret AgentCheckInConfig) AgentCheckInConfig {
|
||||
// if local config present, overwrite
|
||||
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\TacticalRMM`, registry.ALL_ACCESS)
|
||||
if err == nil {
|
||||
if checkInHello, _, err := k.GetStringValue("CheckInHello"); err == nil {
|
||||
ret.Hello = regRangeToInt(checkInHello)
|
||||
}
|
||||
if checkInAgentInfo, _, err := k.GetStringValue("CheckInAgentInfo"); err == nil {
|
||||
ret.AgentInfo = regRangeToInt(checkInAgentInfo)
|
||||
}
|
||||
if checkInWinSvc, _, err := k.GetStringValue("CheckInWinSvc"); err == nil {
|
||||
ret.WinSvc = regRangeToInt(checkInWinSvc)
|
||||
}
|
||||
if checkInPubIP, _, err := k.GetStringValue("CheckInPubIP"); err == nil {
|
||||
ret.PubIP = regRangeToInt(checkInPubIP)
|
||||
}
|
||||
if checkInDisks, _, err := k.GetStringValue("CheckInDisks"); err == nil {
|
||||
ret.Disks = regRangeToInt(checkInDisks)
|
||||
}
|
||||
if checkInSW, _, err := k.GetStringValue("CheckInSW"); err == nil {
|
||||
ret.SW = regRangeToInt(checkInSW)
|
||||
}
|
||||
if checkInWMI, _, err := k.GetStringValue("CheckInWMI"); err == nil {
|
||||
ret.WMI = regRangeToInt(checkInWMI)
|
||||
}
|
||||
if checkInSyncMesh, _, err := k.GetStringValue("CheckInSyncMesh"); err == nil {
|
||||
ret.SyncMesh = regRangeToInt(checkInSyncMesh)
|
||||
}
|
||||
if checkInLimitData, _, err := k.GetStringValue("CheckInLimitData"); err == nil {
|
||||
if checkInLimitData == "true" {
|
||||
ret.LimitData = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// TODO add to stub
|
||||
func (a *Agent) NixMeshNodeID() string {
|
||||
return "not implemented"
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 AmidaWare LLC.
|
||||
Copyright 2023 AmidaWare Inc.
|
||||
|
||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||
You may only use the Licensed Software in accordance with the License.
|
||||
@@ -12,7 +12,6 @@ https://license.tacticalrmm.com
|
||||
package agent
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
@@ -78,8 +77,7 @@ func (a *Agent) NatsMessage(nc *nats.Conn, mode string) {
|
||||
|
||||
func (a *Agent) DoNatsCheckIn() {
|
||||
opts := a.setupNatsOptions()
|
||||
server := fmt.Sprintf("tls://%s:4222", a.ApiURL)
|
||||
nc, err := nats.Connect(server, opts...)
|
||||
nc, err := nats.Connect(a.NatsServer, opts...)
|
||||
if err != nil {
|
||||
a.Logger.Errorln(err)
|
||||
return
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 AmidaWare LLC.
|
||||
Copyright 2023 AmidaWare Inc.
|
||||
|
||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||
You may only use the Licensed Software in accordance with the License.
|
||||
@@ -169,7 +169,7 @@ type ScriptCheckResult struct {
|
||||
// ScriptCheck runs either bat, powershell or python script
|
||||
func (a *Agent) ScriptCheck(data rmm.Check, r *resty.Client) {
|
||||
start := time.Now()
|
||||
stdout, stderr, retcode, _ := a.RunScript(data.Script.Code, data.Script.Shell, data.ScriptArgs, data.Timeout)
|
||||
stdout, stderr, retcode, _ := a.RunScript(data.Script.Code, data.Script.Shell, data.ScriptArgs, data.Timeout, data.Script.RunAsUser, data.EnvVars)
|
||||
|
||||
payload := ScriptCheckResult{
|
||||
ID: data.CheckPK,
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 AmidaWare LLC.
|
||||
Copyright 2023 AmidaWare Inc.
|
||||
|
||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||
You may only use the Licensed Software in accordance with the License.
|
||||
@@ -12,6 +12,9 @@ https://license.tacticalrmm.com
|
||||
package agent
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
rmm "github.com/amidaware/rmmagent/shared"
|
||||
@@ -42,7 +45,7 @@ func (a *Agent) InstallChoco() {
|
||||
return
|
||||
}
|
||||
|
||||
_, _, exitcode, err := a.RunScript(string(r.Body()), "powershell", []string{}, 900)
|
||||
_, _, exitcode, err := a.RunScript(string(r.Body()), "powershell", []string{}, 900, false, []string{})
|
||||
if err != nil {
|
||||
a.Logger.Debugln(err)
|
||||
a.rClient.R().SetBody(result).Post(url)
|
||||
@@ -59,7 +62,14 @@ func (a *Agent) InstallChoco() {
|
||||
}
|
||||
|
||||
func (a *Agent) InstallWithChoco(name string) (string, error) {
|
||||
out, err := CMD("choco.exe", []string{"install", name, "--yes", "--force", "--force-dependencies", "--no-progress"}, 1200, false)
|
||||
var exe string
|
||||
choco, err := exec.LookPath("choco.exe")
|
||||
if err != nil || choco == "" {
|
||||
exe = filepath.Join(os.Getenv("PROGRAMDATA"), `chocolatey\bin\choco.exe`)
|
||||
} else {
|
||||
exe = choco
|
||||
}
|
||||
out, err := CMD(exe, []string{"install", name, "--yes", "--force", "--force-dependencies", "--no-progress"}, 1200, false)
|
||||
if err != nil {
|
||||
a.Logger.Errorln(err)
|
||||
return err.Error(), err
|
||||
|
24
agent/embed_darwin.go
Normal file
24
agent/embed_darwin.go
Normal file
@@ -0,0 +1,24 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
/*
|
||||
Copyright 2023 Amidaware Inc.
|
||||
|
||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||
You may only use the Licensed Software in accordance with the License.
|
||||
A copy of the License is available at:
|
||||
|
||||
https://license.tacticalrmm.com
|
||||
|
||||
*/
|
||||
|
||||
package agent
|
||||
|
||||
import _ "embed"
|
||||
|
||||
//go:embed scripts/macos_fix_mesh_install.sh
|
||||
var ventura_mesh_fix string
|
||||
|
||||
func (a *Agent) FixVenturaMesh() {
|
||||
a.RunScript(ventura_mesh_fix, "foo", []string{}, 45, false, []string{})
|
||||
}
|
17
agent/embed_stub.go
Normal file
17
agent/embed_stub.go
Normal file
@@ -0,0 +1,17 @@
|
||||
//go:build !darwin
|
||||
// +build !darwin
|
||||
|
||||
/*
|
||||
Copyright 2023 Amidaware Inc.
|
||||
|
||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||
You may only use the Licensed Software in accordance with the License.
|
||||
A copy of the License is available at:
|
||||
|
||||
https://license.tacticalrmm.com
|
||||
|
||||
*/
|
||||
|
||||
package agent
|
||||
|
||||
func (a *Agent) FixVenturaMesh() {}
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 AmidaWare LLC.
|
||||
Copyright 2023 AmidaWare Inc.
|
||||
|
||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||
You may only use the Licensed Software in accordance with the License.
|
||||
|
171
agent/install.go
171
agent/install.go
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 AmidaWare LLC.
|
||||
Copyright 2023 AmidaWare Inc.
|
||||
|
||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||
You may only use the Licensed Software in accordance with the License.
|
||||
@@ -12,6 +12,7 @@ https://license.tacticalrmm.com
|
||||
package agent
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
@@ -28,25 +29,27 @@ import (
|
||||
)
|
||||
|
||||
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
|
||||
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
|
||||
Insecure bool
|
||||
NatsStandardPort string
|
||||
}
|
||||
|
||||
func (a *Agent) Install(i *Installer) {
|
||||
@@ -82,11 +85,6 @@ func (a *Agent) Install(i *Installer) {
|
||||
|
||||
a.Logger.Debugln("API:", i.SaltMaster)
|
||||
|
||||
terr := TestTCP(fmt.Sprintf("%s:4222", i.SaltMaster))
|
||||
if terr != nil {
|
||||
a.installerMsg(fmt.Sprintf("ERROR: Either port 4222 TCP is not open on your RMM, or nats.service is not running.\n\n%s", terr.Error()), "error", i.Silent)
|
||||
}
|
||||
|
||||
baseURL := u.Scheme + "://" + u.Host
|
||||
a.Logger.Debugln("Base URL:", baseURL)
|
||||
|
||||
@@ -102,6 +100,14 @@ func (a *Agent) Install(i *Installer) {
|
||||
iClient.SetProxy(i.Proxy)
|
||||
}
|
||||
|
||||
insecureConf := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
|
||||
if i.Insecure {
|
||||
iClient.SetTLSClientConfig(insecureConf)
|
||||
}
|
||||
|
||||
creds, cerr := iClient.R().Get(fmt.Sprintf("%s/api/v3/installer/", baseURL))
|
||||
if cerr != nil {
|
||||
a.installerMsg(cerr.Error(), "error", i.Silent)
|
||||
@@ -138,12 +144,8 @@ func (a *Agent) Install(i *Installer) {
|
||||
rClient.SetProxy(i.Proxy)
|
||||
}
|
||||
|
||||
var arch string
|
||||
switch a.Arch {
|
||||
case "x86_64":
|
||||
arch = "64"
|
||||
case "x86":
|
||||
arch = "32"
|
||||
if i.Insecure {
|
||||
rClient.SetTLSClientConfig(insecureConf)
|
||||
}
|
||||
|
||||
var installerMeshSystemEXE string
|
||||
@@ -153,34 +155,58 @@ func (a *Agent) Install(i *Installer) {
|
||||
installerMeshSystemEXE = a.MeshSystemEXE
|
||||
}
|
||||
|
||||
var meshNodeID string
|
||||
var meshNodeID, meshOutput string
|
||||
|
||||
if runtime.GOOS == "windows" && !i.NoMesh {
|
||||
mesh := filepath.Join(a.ProgramDir, a.MeshInstaller)
|
||||
if i.LocalMesh == "" {
|
||||
if !i.NoMesh && runtime.GOOS != "linux" {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
meshOutput = filepath.Join(a.ProgramDir, a.MeshInstaller)
|
||||
case "darwin":
|
||||
tmp, err := createNixTmpFile()
|
||||
if err != nil {
|
||||
a.Logger.Fatalln("Failed to create mesh temp file", err)
|
||||
}
|
||||
meshOutput = tmp.Name()
|
||||
os.Chmod(meshOutput, 0755)
|
||||
defer os.Remove(meshOutput)
|
||||
defer os.Remove(meshOutput + ".msh")
|
||||
}
|
||||
|
||||
if runtime.GOOS == "windows" && i.LocalMesh != "" {
|
||||
err := copyFile(i.LocalMesh, meshOutput)
|
||||
if err != nil {
|
||||
a.installerMsg(err.Error(), "error", i.Silent)
|
||||
}
|
||||
} else {
|
||||
a.Logger.Infoln("Downloading mesh agent...")
|
||||
payload := map[string]string{"arch": arch, "plat": a.Platform}
|
||||
r, err := rClient.R().SetBody(payload).SetOutput(mesh).Post(fmt.Sprintf("%s/api/v3/meshexe/", baseURL))
|
||||
payload := map[string]string{"goarch": a.GoArch, "plat": a.Platform}
|
||||
r, err := rClient.R().SetBody(payload).SetOutput(meshOutput).Post(fmt.Sprintf("%s/api/v3/meshexe/", baseURL))
|
||||
if err != nil {
|
||||
a.installerMsg(fmt.Sprintf("Failed to download mesh agent: %s", err.Error()), "error", i.Silent)
|
||||
}
|
||||
if r.StatusCode() != 200 {
|
||||
a.installerMsg(fmt.Sprintf("Unable to download the mesh agent from the RMM. %s", r.String()), "error", i.Silent)
|
||||
}
|
||||
} else {
|
||||
err := copyFile(i.LocalMesh, mesh)
|
||||
if err != nil {
|
||||
a.installerMsg(err.Error(), "error", i.Silent)
|
||||
}
|
||||
}
|
||||
|
||||
a.Logger.Infoln("Installing mesh agent...")
|
||||
a.Logger.Debugln("Mesh agent:", mesh)
|
||||
a.Logger.Debugln("Mesh agent:", meshOutput)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
meshNodeID, err = a.installMesh(mesh, installerMeshSystemEXE, i.Proxy)
|
||||
if err != nil {
|
||||
a.installerMsg(fmt.Sprintf("Failed to install mesh agent: %s", err.Error()), "error", i.Silent)
|
||||
if runtime.GOOS == "windows" {
|
||||
meshNodeID, err = a.installMesh(meshOutput, installerMeshSystemEXE, i.Proxy)
|
||||
if err != nil {
|
||||
a.installerMsg(fmt.Sprintf("Failed to install mesh agent: %s", err.Error()), "error", i.Silent)
|
||||
}
|
||||
} else {
|
||||
opts := a.NewCMDOpts()
|
||||
opts.Command = fmt.Sprintf("%s -install --installPath=%s", meshOutput, nixMeshDir)
|
||||
opts.Timeout = i.Timeout
|
||||
out := a.CmdV2(opts)
|
||||
if out.Status.Exit != 0 {
|
||||
a.Logger.Fatalln("Error installing mesh agent:", out.Stderr)
|
||||
}
|
||||
fmt.Println(out.Stdout)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +245,7 @@ func (a *Agent) Install(i *Installer) {
|
||||
a.Logger.Debugln("Agent token:", agentToken)
|
||||
a.Logger.Debugln("Agent PK:", agentPK)
|
||||
|
||||
createAgentConfig(baseURL, a.AgentID, i.SaltMaster, agentToken, strconv.Itoa(agentPK), i.Cert, i.Proxy, i.MeshDir)
|
||||
createAgentConfig(baseURL, a.AgentID, i.SaltMaster, agentToken, strconv.Itoa(agentPK), i.Cert, i.Proxy, i.MeshDir, i.NatsStandardPort, i.Insecure)
|
||||
time.Sleep(1 * time.Second)
|
||||
// refresh our agent with new values
|
||||
a = New(a.Logger, a.Version)
|
||||
@@ -232,18 +258,62 @@ func (a *Agent) Install(i *Installer) {
|
||||
// check in once
|
||||
a.DoNatsCheckIn()
|
||||
|
||||
if runtime.GOOS == "darwin" {
|
||||
os.MkdirAll(nixAgentDir, 0755)
|
||||
self, _ := os.Executable()
|
||||
copyFile(self, nixAgentBin)
|
||||
os.Chmod(nixAgentBin, 0755)
|
||||
svc := fmt.Sprintf(`
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>%s</string>
|
||||
|
||||
<key>ServiceDescription</key>
|
||||
<string>TacticalAgent Service</string>
|
||||
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>%s</string>
|
||||
<string>-m</string>
|
||||
<string>svc</string>
|
||||
</array>
|
||||
|
||||
<key>WorkingDirectory</key>
|
||||
<string>%s/</string>
|
||||
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
`, macPlistName, nixAgentBin, nixAgentDir)
|
||||
|
||||
os.WriteFile(macPlistPath, []byte(svc), 0644)
|
||||
opts := a.NewCMDOpts()
|
||||
opts.Command = fmt.Sprintf("launchctl bootstrap system %s", macPlistPath)
|
||||
a.CmdV2(opts)
|
||||
}
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
// send software api
|
||||
a.SendSoftware()
|
||||
|
||||
a.Logger.Debugln("Creating temp dir")
|
||||
a.CreateTRMMTempDir()
|
||||
err := createWinTempDir()
|
||||
if err != nil {
|
||||
a.Logger.Errorln("Install() createWinTempDir():", err)
|
||||
}
|
||||
|
||||
a.Logger.Debugln("Disabling automatic windows updates")
|
||||
a.PatchMgmnt(true)
|
||||
|
||||
a.Logger.Infoln("Installing service...")
|
||||
err := a.InstallService()
|
||||
err = a.InstallService()
|
||||
if err != nil {
|
||||
a.installerMsg(err.Error(), "error", i.Silent)
|
||||
}
|
||||
@@ -255,9 +325,6 @@ func (a *Agent) Install(i *Installer) {
|
||||
a.installerMsg(out.ErrorMsg, "error", i.Silent)
|
||||
}
|
||||
|
||||
a.Logger.Infoln("Adding windows defender exclusions")
|
||||
a.addDefenderExlusions()
|
||||
|
||||
if i.Power {
|
||||
a.Logger.Infoln("Disabling sleep/hibernate...")
|
||||
DisableSleepHibernate()
|
||||
|
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
Copyright 2022 AmidaWare LLC.
|
||||
|
||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||
You may only use the Licensed Software in accordance with the License.
|
||||
A copy of the License is available at:
|
||||
|
||||
https://license.tacticalrmm.com
|
||||
|
||||
*/
|
||||
|
||||
package agent
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
etcConfig = "/etc/tacticalagent"
|
||||
)
|
||||
|
||||
func (a *Agent) checkExistingAndRemove(silent bool) {}
|
||||
|
||||
func (a *Agent) installerMsg(msg, alert string, silent bool) {
|
||||
if alert == "error" {
|
||||
a.Logger.Fatalln(msg)
|
||||
} else {
|
||||
a.Logger.Info(msg)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
err := viper.SafeWriteConfigAs(etcConfig)
|
||||
if err != nil {
|
||||
log.Fatalln("createAgentConfig", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) addDefenderExlusions() {}
|
||||
|
||||
func DisableSleepHibernate() {}
|
||||
|
||||
func EnablePing() {}
|
||||
|
||||
func EnableRDP() {}
|
87
agent/install_unix.go
Normal file
87
agent/install_unix.go
Normal file
@@ -0,0 +1,87 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
/*
|
||||
Copyright 2023 AmidaWare Inc.
|
||||
|
||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||
You may only use the Licensed Software in accordance with the License.
|
||||
A copy of the License is available at:
|
||||
|
||||
https://license.tacticalrmm.com
|
||||
|
||||
*/
|
||||
|
||||
package agent
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
trmm "github.com/wh1te909/trmm-shared"
|
||||
)
|
||||
|
||||
func (a *Agent) installerMsg(msg, alert string, silent bool) {
|
||||
if alert == "error" {
|
||||
a.Logger.Fatalln(msg)
|
||||
} else {
|
||||
a.Logger.Info(msg)
|
||||
}
|
||||
}
|
||||
|
||||
func createAgentConfig(baseurl, agentid, apiurl, token, agentpk, cert, proxy, meshdir, natsport string, insecure bool) {
|
||||
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.Set("natsstandardport", natsport)
|
||||
if insecure {
|
||||
viper.Set("insecure", "true")
|
||||
}
|
||||
viper.SetConfigPermissions(0660)
|
||||
err := viper.SafeWriteConfigAs(etcConfig)
|
||||
if err != nil {
|
||||
log.Fatalln("createAgentConfig", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) checkExistingAndRemove(silent bool) {
|
||||
if runtime.GOOS == "darwin" {
|
||||
if trmm.FileExists(a.MeshSystemEXE) {
|
||||
a.Logger.Infoln("Existing meshagent found, attempting to remove...")
|
||||
uopts := a.NewCMDOpts()
|
||||
uopts.Command = fmt.Sprintf("%s -fulluninstall", a.MeshSystemEXE)
|
||||
uout := a.CmdV2(uopts)
|
||||
fmt.Println(uout.Stdout)
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
if trmm.FileExists(macPlistPath) {
|
||||
a.Logger.Infoln("Existing tacticalagent plist found, attempting to remove...")
|
||||
opts := a.NewCMDOpts()
|
||||
opts.Command = fmt.Sprintf("launchctl bootout system %s", macPlistPath)
|
||||
a.CmdV2(opts)
|
||||
}
|
||||
|
||||
os.RemoveAll(defaultMacMeshSvcDir)
|
||||
os.RemoveAll(nixMeshDir)
|
||||
os.Remove(etcConfig)
|
||||
os.RemoveAll(nixAgentDir)
|
||||
os.Remove(macPlistPath)
|
||||
}
|
||||
}
|
||||
|
||||
func DisableSleepHibernate() {}
|
||||
|
||||
func EnablePing() {}
|
||||
|
||||
func EnableRDP() {}
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 AmidaWare LLC.
|
||||
Copyright 2023 AmidaWare Inc.
|
||||
|
||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||
You may only use the Licensed Software in accordance with the License.
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
func createAgentConfig(baseurl, agentid, apiurl, token, agentpk, cert, proxy, meshdir string) {
|
||||
func createAgentConfig(baseurl, agentid, apiurl, token, agentpk, cert, proxy, meshdir, natsport string, insecure bool) {
|
||||
k, _, err := registry.CreateKey(registry.LOCAL_MACHINE, `SOFTWARE\TacticalRMM`, registry.ALL_ACCESS)
|
||||
if err != nil {
|
||||
log.Fatalln("Error creating registry key:", err)
|
||||
@@ -73,6 +73,20 @@ func createAgentConfig(baseurl, agentid, apiurl, token, agentpk, cert, proxy, me
|
||||
log.Fatalln("Error creating MeshDir registry key:", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(natsport) > 0 {
|
||||
err = k.SetStringValue("NatsStandardPort", natsport)
|
||||
if err != nil {
|
||||
log.Fatalln("Error creating NatsStandardPort registry key:", err)
|
||||
}
|
||||
}
|
||||
|
||||
if insecure {
|
||||
err = k.SetStringValue("Insecure", "true")
|
||||
if err != nil {
|
||||
log.Fatalln("Error creating Insecure registry key:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) checkExistingAndRemove(silent bool) {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 AmidaWare LLC.
|
||||
Copyright 2023 AmidaWare Inc.
|
||||
|
||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||
You may only use the Licensed Software in accordance with the License.
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 AmidaWare LLC.
|
||||
Copyright 2023 AmidaWare Inc.
|
||||
|
||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||
You may only use the Licensed Software in accordance with the License.
|
||||
@@ -64,9 +64,16 @@ func (a *Agent) KillHungUpdates() {
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// winagent-v* is deprecated
|
||||
if strings.Contains(p.Exe, "winagent-v") {
|
||||
a.Logger.Debugln("killing process", p.Exe)
|
||||
KillProc(int32(p.PID))
|
||||
}
|
||||
|
||||
if strings.Contains(p.Exe, "tacticalagent-v") {
|
||||
a.Logger.Debugln("killing process", p.Exe)
|
||||
KillProc(int32(p.PID))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
31
agent/rpc.go
31
agent/rpc.go
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 AmidaWare LLC.
|
||||
Copyright 2023 AmidaWare Inc.
|
||||
|
||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||
You may only use the Licensed Software in accordance with the License.
|
||||
@@ -40,6 +40,8 @@ type NatsMsg struct {
|
||||
PatchMgmt bool `json:"patch_mgmt"`
|
||||
ID int `json:"id"`
|
||||
Code string `json:"code"`
|
||||
RunAsUser bool `json:"run_as_user"`
|
||||
EnvVars []string `json:"env_vars"`
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -50,16 +52,20 @@ var (
|
||||
|
||||
func (a *Agent) RunRPC() {
|
||||
a.Logger.Infoln("Agent service started")
|
||||
go a.RunAsService()
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
opts := a.setupNatsOptions()
|
||||
server := fmt.Sprintf("tls://%s:4222", a.ApiURL)
|
||||
nc, err := nats.Connect(server, opts...)
|
||||
nc, err := nats.Connect(a.NatsServer, opts...)
|
||||
a.Logger.Debugf("%+v\n", nc)
|
||||
a.Logger.Debugf("%+v\n", nc.Opts)
|
||||
if err != nil {
|
||||
a.Logger.Fatalln("RunRPC() nats.Connect()", err)
|
||||
}
|
||||
|
||||
go a.RunAsService(nc)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
nc.Subscribe(a.AgentID, func(msg *nats.Msg) {
|
||||
var payload *NatsMsg
|
||||
var mh codec.MsgpackHandle
|
||||
@@ -178,7 +184,7 @@ func (a *Agent) RunRPC() {
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
out, _ := CMDShell(p.Data["shell"], []string{}, p.Data["command"], p.Timeout, false)
|
||||
out, _ := CMDShell(p.Data["shell"], []string{}, p.Data["command"], p.Timeout, false, p.RunAsUser)
|
||||
a.Logger.Debugln(out)
|
||||
if out[1] != "" {
|
||||
ret.Encode(out[1])
|
||||
@@ -258,7 +264,7 @@ func (a *Agent) RunRPC() {
|
||||
var resultData rmm.RunScriptResp
|
||||
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
|
||||
start := time.Now()
|
||||
stdout, stderr, retcode, err := a.RunScript(p.Data["code"], p.Data["shell"], p.ScriptArgs, p.Timeout)
|
||||
stdout, stderr, retcode, err := a.RunScript(p.Data["code"], p.Data["shell"], p.ScriptArgs, p.Timeout, p.RunAsUser, p.EnvVars)
|
||||
resultData.ExecTime = time.Since(start).Seconds()
|
||||
resultData.ID = p.ID
|
||||
|
||||
@@ -288,7 +294,7 @@ func (a *Agent) RunRPC() {
|
||||
var retData rmm.RunScriptResp
|
||||
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
|
||||
start := time.Now()
|
||||
stdout, stderr, retcode, err := a.RunScript(p.Data["code"], p.Data["shell"], p.ScriptArgs, p.Timeout)
|
||||
stdout, stderr, retcode, err := a.RunScript(p.Data["code"], p.Data["shell"], p.ScriptArgs, p.Timeout, p.RunAsUser, p.EnvVars)
|
||||
|
||||
retData.ExecTime = time.Since(start).Seconds()
|
||||
if err != nil {
|
||||
@@ -475,10 +481,15 @@ func (a *Agent) RunRPC() {
|
||||
} else {
|
||||
ret.Encode("ok")
|
||||
msg.Respond(resp)
|
||||
a.AgentUpdate(p.Data["url"], p.Data["inno"], p.Data["version"])
|
||||
err := a.AgentUpdate(p.Data["url"], p.Data["inno"], p.Data["version"])
|
||||
if err != nil {
|
||||
atomic.StoreUint32(&agentUpdateLocker, 0)
|
||||
return
|
||||
}
|
||||
atomic.StoreUint32(&agentUpdateLocker, 0)
|
||||
nc.Flush()
|
||||
nc.Close()
|
||||
a.ControlService(winSvcName, "stop")
|
||||
os.Exit(0)
|
||||
}
|
||||
}(payload)
|
||||
|
90
agent/scripts/macos_fix_mesh_install.sh
Normal file
90
agent/scripts/macos_fix_mesh_install.sh
Normal file
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# source: https://github.com/amidaware/community-scripts/blob/main/scripts_staging/macos_fix_mesh_install.sh
|
||||
# author: https://github.com/NiceGuyIT
|
||||
|
||||
# This script fixes MeshAgent issue #161: MacOS Ventura - Not starting meshagent on boot (Maybe Solved)
|
||||
# https://github.com/Ylianst/MeshAgent/issues/161
|
||||
#
|
||||
# The following actions are taken:
|
||||
# 1) Add the eXecute bit for directory traversal for the installation directory. This allows regular users
|
||||
# access to run the binary inside the directory, fixing the "meshagent" LaunchAgent integration with the
|
||||
# user.
|
||||
# 2) Rename the LaunchAgent "meshagent.plist" to prevent conflicts with the LaunchDaemon "meshagent.plist".
|
||||
# This may not be needed but is done for good measure.
|
||||
# 3) Rename the service Label inside the plist. Using "defaults" causes the plist to be rewritten in plist
|
||||
# format, not ascii.
|
||||
#
|
||||
# Here's the original plist from my install.
|
||||
# <?xml version="1.0" encoding="UTF-8"?>
|
||||
# <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
# <plist version="1.0">
|
||||
# <dict>
|
||||
# <key>Label</key>
|
||||
# <string>meshagent</string>
|
||||
# <key>ProgramArguments</key>
|
||||
# <array>
|
||||
# <string>/opt/tacticalmesh/meshagent</string>
|
||||
# <string>-kvm1</string>
|
||||
# </array>
|
||||
#
|
||||
# <key>WorkingDirectory</key>
|
||||
# <string>/opt/tacticalmesh</string>
|
||||
#
|
||||
# <key>RunAtLoad</key>
|
||||
# <true/>
|
||||
# <key>LimitLoadToSessionType</key>
|
||||
# <array>
|
||||
# <string>LoginWindow</string>
|
||||
# </array>
|
||||
# <key>KeepAlive</key>
|
||||
# <dict>
|
||||
# <key>Crashed</key>
|
||||
# <true/>
|
||||
# </dict>
|
||||
# </dict>
|
||||
# </plist>
|
||||
|
||||
|
||||
mesh_install_dir="/opt/tacticalmesh/"
|
||||
mesh_agent_plist_old="/Library/LaunchAgents/meshagent.plist"
|
||||
mesh_agent_plist="/Library/LaunchAgents/meshagent-agent.plist"
|
||||
mesh_daemon_plist="/Library/LaunchDaemons/meshagent.plist"
|
||||
|
||||
if [ ! -f "${mesh_daemon_plist}" ]
|
||||
then
|
||||
echo "meshagent LaunchDaemon does not exist to cause the duplicate service name issue. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if /usr/bin/stat -f "%Sp" "${mesh_install_dir}" | grep -v 'x$' >/dev/null
|
||||
then
|
||||
echo "Fixing permissions on meshagent installation directory: ${mesh_install_dir}"
|
||||
chmod o+X "${mesh_install_dir}"
|
||||
else
|
||||
echo "No action taken. Permissions on meshagent installation directory have already been fixed."
|
||||
fi
|
||||
echo
|
||||
|
||||
if [ -f "${mesh_agent_plist_old}" ]
|
||||
then
|
||||
echo "Renaming agent plist: ${mesh_agent_plist_old}"
|
||||
mv "${mesh_agent_plist_old}" "${mesh_agent_plist}"
|
||||
else
|
||||
echo "No action taken. meshagent.plist was already renamed: ${mesh_agent_plist}"
|
||||
fi
|
||||
echo
|
||||
|
||||
# New file has to exist before renaming the label.
|
||||
if [ -f "${mesh_agent_plist}" ]
|
||||
then
|
||||
label=$(defaults read "${mesh_agent_plist}" Label)
|
||||
if [ "${label}" != "meshagent-agent" ]
|
||||
then
|
||||
echo "Renaming meshagent label in plist: ${mesh_agent_plist}"
|
||||
echo "Warning: This will convert the plist from a text file to a binary plist file."
|
||||
defaults write "${mesh_agent_plist}" Label "meshagent-agent"
|
||||
else
|
||||
echo "No action taken. meshagent label was already renamed: ${label}"
|
||||
fi
|
||||
fi
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 AmidaWare LLC.
|
||||
Copyright 2023 AmidaWare Inc.
|
||||
|
||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||
You may only use the Licensed Software in accordance with the License.
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 AmidaWare LLC.
|
||||
Copyright 2023 AmidaWare Inc.
|
||||
|
||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||
You may only use the Licensed Software in accordance with the License.
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 AmidaWare LLC.
|
||||
Copyright 2023 AmidaWare Inc.
|
||||
|
||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||
You may only use the Licensed Software in accordance with the License.
|
||||
|
106
agent/svc.go
106
agent/svc.go
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 AmidaWare LLC.
|
||||
Copyright 2023 AmidaWare Inc.
|
||||
|
||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||
You may only use the Licensed Software in accordance with the License.
|
||||
@@ -13,56 +13,85 @@ package agent
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
nats "github.com/nats-io/nats.go"
|
||||
)
|
||||
|
||||
func (a *Agent) RunAsService() {
|
||||
func (a *Agent) RunAsService(nc *nats.Conn) {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go a.AgentSvc()
|
||||
go a.AgentSvc(nc)
|
||||
go a.CheckRunner()
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func (a *Agent) AgentSvc() {
|
||||
go a.GetPython(false)
|
||||
type AgentCheckInConfig struct {
|
||||
Hello int `json:"checkin_hello"`
|
||||
AgentInfo int `json:"checkin_agentinfo"`
|
||||
WinSvc int `json:"checkin_winsvc"`
|
||||
PubIP int `json:"checkin_pubip"`
|
||||
Disks int `json:"checkin_disks"`
|
||||
SW int `json:"checkin_sw"`
|
||||
WMI int `json:"checkin_wmi"`
|
||||
SyncMesh int `json:"checkin_syncmesh"`
|
||||
LimitData bool `json:"limit_data"`
|
||||
}
|
||||
|
||||
a.CreateTRMMTempDir()
|
||||
func (a *Agent) AgentSvc(nc *nats.Conn) {
|
||||
if runtime.GOOS == "windows" {
|
||||
go a.GetPython(false)
|
||||
|
||||
err := createWinTempDir()
|
||||
if err != nil {
|
||||
a.Logger.Errorln("AgentSvc() createWinTempDir():", err)
|
||||
}
|
||||
}
|
||||
a.RunMigrations()
|
||||
|
||||
sleepDelay := randRange(14, 22)
|
||||
sleepDelay := randRange(7, 25)
|
||||
a.Logger.Debugf("AgentSvc() sleeping for %v seconds", sleepDelay)
|
||||
time.Sleep(time.Duration(sleepDelay) * time.Second)
|
||||
|
||||
opts := a.setupNatsOptions()
|
||||
server := fmt.Sprintf("tls://%s:4222", a.ApiURL)
|
||||
nc, err := nats.Connect(server, opts...)
|
||||
if err != nil {
|
||||
a.Logger.Fatalln("AgentSvc() nats.Connect()", err)
|
||||
if runtime.GOOS == "windows" {
|
||||
a.KillHungUpdates()
|
||||
time.Sleep(1 * time.Second)
|
||||
a.CleanupAgentUpdates()
|
||||
}
|
||||
|
||||
conf := a.GetAgentCheckInConfig(a.GetCheckInConfFromAPI())
|
||||
a.Logger.Debugf("+%v\n", conf)
|
||||
for _, s := range natsCheckin {
|
||||
a.NatsMessage(nc, s)
|
||||
time.Sleep(time.Duration(randRange(100, 400)) * time.Millisecond)
|
||||
if conf.LimitData && stringInSlice(s, limitNatsData) {
|
||||
continue
|
||||
} else {
|
||||
a.NatsMessage(nc, s)
|
||||
time.Sleep(time.Duration(randRange(100, 400)) * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
go a.SyncMeshNodeID()
|
||||
|
||||
time.Sleep(time.Duration(randRange(1, 3)) * time.Second)
|
||||
a.AgentStartup()
|
||||
a.SendSoftware()
|
||||
if runtime.GOOS == "windows" && !conf.LimitData {
|
||||
a.AgentStartup()
|
||||
a.SendSoftware()
|
||||
}
|
||||
|
||||
checkInHelloTicker := time.NewTicker(time.Duration(randRange(30, 60)) * time.Second)
|
||||
checkInAgentInfoTicker := time.NewTicker(time.Duration(randRange(200, 400)) * time.Second)
|
||||
checkInWinSvcTicker := time.NewTicker(time.Duration(randRange(2400, 3000)) * time.Second)
|
||||
checkInPubIPTicker := time.NewTicker(time.Duration(randRange(300, 500)) * time.Second)
|
||||
checkInDisksTicker := time.NewTicker(time.Duration(randRange(1000, 2000)) * time.Second)
|
||||
checkInSWTicker := time.NewTicker(time.Duration(randRange(2800, 3500)) * time.Second)
|
||||
checkInWMITicker := time.NewTicker(time.Duration(randRange(3000, 4000)) * time.Second)
|
||||
syncMeshTicker := time.NewTicker(time.Duration(randRange(800, 1200)) * time.Second)
|
||||
if runtime.GOOS == "darwin" {
|
||||
go a.FixVenturaMesh()
|
||||
}
|
||||
|
||||
checkInHelloTicker := time.NewTicker(time.Duration(conf.Hello) * time.Second)
|
||||
checkInAgentInfoTicker := time.NewTicker(time.Duration(conf.AgentInfo) * time.Second)
|
||||
checkInWinSvcTicker := time.NewTicker(time.Duration(conf.WinSvc) * time.Second)
|
||||
checkInPubIPTicker := time.NewTicker(time.Duration(conf.PubIP) * time.Second)
|
||||
checkInDisksTicker := time.NewTicker(time.Duration(conf.Disks) * time.Second)
|
||||
checkInSWTicker := time.NewTicker(time.Duration(conf.SW) * time.Second)
|
||||
checkInWMITicker := time.NewTicker(time.Duration(conf.WMI) * time.Second)
|
||||
syncMeshTicker := time.NewTicker(time.Duration(conf.SyncMesh) * time.Second)
|
||||
|
||||
for {
|
||||
select {
|
||||
@@ -94,3 +123,32 @@ func (a *Agent) AgentStartup() {
|
||||
a.Logger.Debugln("AgentStartup()", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) GetCheckInConfFromAPI() AgentCheckInConfig {
|
||||
ret := AgentCheckInConfig{}
|
||||
url := fmt.Sprintf("/api/v3/%s/config/", a.AgentID)
|
||||
r, err := a.rClient.R().SetResult(&AgentCheckInConfig{}).Get(url)
|
||||
if err != nil {
|
||||
a.Logger.Debugln("GetAgentCheckInConfig()", err)
|
||||
ret.Hello = randRange(30, 60)
|
||||
ret.AgentInfo = randRange(200, 400)
|
||||
ret.WinSvc = randRange(2400, 3000)
|
||||
ret.PubIP = randRange(300, 500)
|
||||
ret.Disks = randRange(1000, 2000)
|
||||
ret.SW = randRange(2800, 3500)
|
||||
ret.WMI = randRange(3000, 4000)
|
||||
ret.SyncMesh = randRange(800, 1200)
|
||||
ret.LimitData = false
|
||||
} else {
|
||||
ret.Hello = r.Result().(*AgentCheckInConfig).Hello
|
||||
ret.AgentInfo = r.Result().(*AgentCheckInConfig).AgentInfo
|
||||
ret.WinSvc = r.Result().(*AgentCheckInConfig).WinSvc
|
||||
ret.PubIP = r.Result().(*AgentCheckInConfig).PubIP
|
||||
ret.Disks = r.Result().(*AgentCheckInConfig).Disks
|
||||
ret.SW = r.Result().(*AgentCheckInConfig).SW
|
||||
ret.WMI = r.Result().(*AgentCheckInConfig).WMI
|
||||
ret.SyncMesh = r.Result().(*AgentCheckInConfig).SyncMesh
|
||||
ret.LimitData = r.Result().(*AgentCheckInConfig).LimitData
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 AmidaWare LLC.
|
||||
Copyright 2023 AmidaWare Inc.
|
||||
|
||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||
You may only use the Licensed Software in accordance with the License.
|
||||
@@ -24,11 +24,14 @@ var _ unsafe.Pointer
|
||||
var (
|
||||
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
|
||||
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||
userenv = windows.NewLazyDLL("userenv.dll")
|
||||
|
||||
procFormatMessageW = modkernel32.NewProc("FormatMessageW")
|
||||
procGetOldestEventLogRecord = modadvapi32.NewProc("GetOldestEventLogRecord")
|
||||
procLoadLibraryExW = modkernel32.NewProc("LoadLibraryExW")
|
||||
procReadEventLogW = modadvapi32.NewProc("ReadEventLogW")
|
||||
procCreateEnvironmentBlock = userenv.NewProc("CreateEnvironmentBlock")
|
||||
procDestroyEnvironmentBlock = userenv.NewProc("DestroyEnvironmentBlock")
|
||||
)
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-eventlogrecord
|
||||
@@ -114,3 +117,47 @@ func ReadEventLog(eventLog w32.HANDLE, readFlags ReadFlag, recordOffset uint32,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func CreateEnvironmentBlock(token syscall.Token) (*uint16, error) {
|
||||
var envBlock *uint16
|
||||
|
||||
ret, _, err := procCreateEnvironmentBlock.Call(
|
||||
uintptr(unsafe.Pointer(&envBlock)),
|
||||
uintptr(token),
|
||||
0,
|
||||
)
|
||||
if ret == 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return envBlock, nil
|
||||
}
|
||||
|
||||
func DestroyEnvironmentBlock(envBlock *uint16) error {
|
||||
ret, _, err := procDestroyEnvironmentBlock.Call(uintptr(unsafe.Pointer(envBlock)))
|
||||
if ret == 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func EnvironmentBlockToSlice(envBlock *uint16) []string {
|
||||
var envs []string
|
||||
|
||||
for {
|
||||
len := 0
|
||||
for *(*uint16)(unsafe.Pointer(uintptr(unsafe.Pointer(envBlock)) + uintptr(len*2))) != 0 {
|
||||
len++
|
||||
}
|
||||
|
||||
if len == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
env := syscall.UTF16ToString((*[1 << 29]uint16)(unsafe.Pointer(envBlock))[:len])
|
||||
envs = append(envs, env)
|
||||
envBlock = (*uint16)(unsafe.Pointer(uintptr(unsafe.Pointer(envBlock)) + uintptr((len+1)*2)))
|
||||
}
|
||||
|
||||
return envs
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 AmidaWare LLC.
|
||||
Copyright 2023 AmidaWare Inc.
|
||||
|
||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||
You may only use the Licensed Software in accordance with the License.
|
||||
@@ -59,7 +59,7 @@ func (a *Agent) RunTask(id int) error {
|
||||
|
||||
action_start := time.Now()
|
||||
if action.ActionType == "script" {
|
||||
stdout, stderr, retcode, err := a.RunScript(action.Code, action.Shell, action.Args, action.Timeout)
|
||||
stdout, stderr, retcode, err := a.RunScript(action.Code, action.Shell, action.Args, action.Timeout, action.RunAsUser, action.EnvVars)
|
||||
|
||||
if err != nil {
|
||||
a.Logger.Debugln(err)
|
||||
@@ -83,7 +83,7 @@ func (a *Agent) RunTask(id int) error {
|
||||
|
||||
} else if action.ActionType == "cmd" {
|
||||
// out[0] == stdout, out[1] == stderr
|
||||
out, err := CMDShell(action.Shell, []string{}, action.Command, action.Timeout, false)
|
||||
out, err := CMDShell(action.Shell, []string{}, action.Command, action.Timeout, false, action.RunAsUser)
|
||||
|
||||
if err != nil {
|
||||
a.Logger.Debugln(err)
|
||||
@@ -175,12 +175,17 @@ func (a *Agent) CreateSchedTask(st SchedTask) (bool, error) {
|
||||
var tasktrigger taskmaster.TaskTrigger
|
||||
|
||||
var now = time.Now()
|
||||
if st.Trigger == "manual" {
|
||||
switch st.Trigger {
|
||||
case "manual":
|
||||
tasktrigger = taskmaster.TaskTrigger{
|
||||
Enabled: st.Enabled,
|
||||
StartBoundary: now,
|
||||
}
|
||||
} else {
|
||||
case "onboarding":
|
||||
tasktrigger = taskmaster.TaskTrigger{
|
||||
Enabled: st.Enabled,
|
||||
}
|
||||
default:
|
||||
tasktrigger = taskmaster.TaskTrigger{
|
||||
Enabled: st.Enabled,
|
||||
StartBoundary: time.Date(st.StartYear, st.StartMonth, st.StartDay, st.StartHour, st.StartMinute, 0, 0, now.Location()),
|
||||
@@ -205,6 +210,11 @@ func (a *Agent) CreateSchedTask(st SchedTask) (bool, error) {
|
||||
TaskTrigger: tasktrigger,
|
||||
RandomDelay: st.RandomDelay,
|
||||
}
|
||||
case "onboarding":
|
||||
trigger = taskmaster.RegistrationTrigger{
|
||||
TaskTrigger: tasktrigger,
|
||||
Delay: st.RandomDelay,
|
||||
}
|
||||
|
||||
case "daily":
|
||||
trigger = taskmaster.DailyTrigger{
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 AmidaWare LLC.
|
||||
Copyright 2023 AmidaWare Inc.
|
||||
|
||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||
You may only use the Licensed Software in accordance with the License.
|
||||
@@ -20,8 +20,11 @@ import (
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
goDebug "runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -58,7 +61,7 @@ func DoPing(host string) (PingResponse, error) {
|
||||
}
|
||||
|
||||
pinger.Count = 3
|
||||
pinger.Size = 24
|
||||
pinger.Size = 548
|
||||
pinger.Interval = time.Second
|
||||
pinger.Timeout = 5 * time.Second
|
||||
pinger.SetPrivileged(true)
|
||||
@@ -125,7 +128,6 @@ func (a *Agent) PublicIP() string {
|
||||
|
||||
// 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 {
|
||||
@@ -138,10 +140,13 @@ func GenerateAgentID() string {
|
||||
func ShowVersionInfo(ver string) {
|
||||
fmt.Println("Tactical RMM Agent:", ver)
|
||||
fmt.Println("Arch:", runtime.GOARCH)
|
||||
fmt.Println("Go version:", runtime.Version())
|
||||
if runtime.GOOS == "windows" {
|
||||
fmt.Println("Program Directory:", filepath.Join(os.Getenv("ProgramFiles"), progFilesName))
|
||||
}
|
||||
bi, ok := goDebug.ReadBuildInfo()
|
||||
if ok {
|
||||
fmt.Println(bi.String())
|
||||
}
|
||||
}
|
||||
|
||||
// TotalRAM returns total RAM in GB
|
||||
@@ -291,7 +296,6 @@ func ByteCountSI(b uint64) string {
|
||||
}
|
||||
|
||||
func randRange(min, max int) int {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
return rand.Intn(max-min) + min
|
||||
}
|
||||
|
||||
@@ -303,18 +307,72 @@ func removeWinNewLines(s string) string {
|
||||
return strings.ReplaceAll(s, "\r\n", "\n")
|
||||
}
|
||||
|
||||
func createTmpFile() (*os.File, error) {
|
||||
var f *os.File
|
||||
f, err := os.CreateTemp("", "trmm")
|
||||
if err != nil {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return f, err
|
||||
}
|
||||
f, err = os.CreateTemp(cwd, "trmm")
|
||||
if err != nil {
|
||||
return f, err
|
||||
func removeNewlines(s string) string {
|
||||
return strings.ReplaceAll(s, "\n", "")
|
||||
}
|
||||
|
||||
func contains(s string, substrs []string) bool {
|
||||
for _, substr := range substrs {
|
||||
if strings.Contains(s, substr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func stringInSlice(a string, list []string) bool {
|
||||
for _, b := range list {
|
||||
if b == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func regRangeToInt(s string) int {
|
||||
split := strings.Split(s, ",")
|
||||
min, _ := strconv.Atoi(split[0])
|
||||
max, _ := strconv.Atoi(split[1])
|
||||
return randRange(min, max)
|
||||
}
|
||||
|
||||
func getPowershellExe() string {
|
||||
powershell, err := exec.LookPath("powershell.exe")
|
||||
if err != nil || powershell == "" {
|
||||
return filepath.Join(os.Getenv("WINDIR"), `System32\WindowsPowerShell\v1.0\powershell.exe`)
|
||||
}
|
||||
return powershell
|
||||
}
|
||||
|
||||
func getCMDExe() string {
|
||||
cmdExe, err := exec.LookPath("cmd.exe")
|
||||
if err != nil || cmdExe == "" {
|
||||
return filepath.Join(os.Getenv("WINDIR"), `System32\cmd.exe`)
|
||||
}
|
||||
return cmdExe
|
||||
}
|
||||
|
||||
// more accurate than os.Getwd()
|
||||
func getCwd() (string, error) {
|
||||
self, err := os.Executable()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return filepath.Dir(self), nil
|
||||
}
|
||||
|
||||
func createNixTmpFile() (*os.File, error) {
|
||||
var f *os.File
|
||||
cwd, err := getCwd()
|
||||
if err != nil {
|
||||
return f, err
|
||||
}
|
||||
|
||||
f, err = os.CreateTemp(cwd, "trmm")
|
||||
if err != nil {
|
||||
return f, err
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 AmidaWare LLC.
|
||||
Copyright 2023 AmidaWare Inc.
|
||||
|
||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||
You may only use the Licensed Software in accordance with the License.
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 AmidaWare LLC.
|
||||
Copyright 2023 AmidaWare Inc.
|
||||
|
||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||
You may only use the Licensed Software in accordance with the License.
|
||||
|
@@ -3,7 +3,7 @@
|
||||
<assemblyIdentity
|
||||
type="win32"
|
||||
name="TacticalRMM"
|
||||
version="2.0.3.0"
|
||||
version="2.6.1.0"
|
||||
processorArchitecture="*"/>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
#define MyAppName "Tactical RMM Agent"
|
||||
#define MyAppVersion "2.0.3"
|
||||
#define MyAppPublisher "AmidaWare LLC"
|
||||
#define MyAppVersion "2.6.1"
|
||||
#define MyAppPublisher "AmidaWare Inc"
|
||||
#define MyAppURL "https://github.com/amidaware"
|
||||
#define MyAppExeName "tacticalrmm.exe"
|
||||
#define MESHEXE "meshagent.exe"
|
||||
|
105
go.mod
105
go.mod
@@ -1,74 +1,83 @@
|
||||
module github.com/amidaware/rmmagent
|
||||
|
||||
go 1.17
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/StackExchange/wmi v1.2.1
|
||||
github.com/elastic/go-sysinfo v1.7.1
|
||||
github.com/go-ole/go-ole v1.2.6
|
||||
github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534
|
||||
github.com/go-resty/resty/v2 v2.7.0
|
||||
github.com/gonutz/w32/v2 v2.4.0
|
||||
github.com/iamacarpet/go-win64api v0.0.0-20211130162011-82e31fe23f80
|
||||
github.com/nats-io/nats-server/v2 v2.4.0 // indirect
|
||||
github.com/nats-io/nats.go v1.14.0
|
||||
github.com/rickb777/date v1.15.3
|
||||
github.com/shirou/gopsutil/v3 v3.22.2
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/ugorji/go/codec v1.2.7
|
||||
github.com/wh1te909/go-win64api v0.0.0-20210906074314-ab23795a6ae5
|
||||
github.com/elastic/go-sysinfo v1.11.2
|
||||
github.com/go-ole/go-ole v1.3.0
|
||||
github.com/go-ping/ping v1.1.0
|
||||
github.com/go-resty/resty/v2 v2.11.0
|
||||
github.com/gonutz/w32/v2 v2.11.1
|
||||
github.com/iamacarpet/go-win64api v0.0.0-20230324134531-ef6dbdd6db97
|
||||
github.com/nats-io/nats.go v1.32.0
|
||||
github.com/rickb777/date v1.19.1
|
||||
github.com/shirou/gopsutil/v3 v3.23.12
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/ugorji/go/codec v1.2.12
|
||||
github.com/wh1te909/go-win64api v0.0.0-20230802051600-21b24f62e846
|
||||
github.com/wh1te909/trmm-shared v0.0.0-20220227075846-f9f757361139
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9
|
||||
golang.org/x/net v0.19.0 // indirect
|
||||
golang.org/x/sys v0.16.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/amidaware/taskmaster v0.0.0-20220111015025-c9cd178bbbf2
|
||||
github.com/go-cmd/cmd v1.4.0
|
||||
github.com/go-cmd/cmd v1.4.2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/jaypipes/ghw v0.8.0
|
||||
github.com/kardianos/service v1.2.1
|
||||
github.com/spf13/viper v1.10.1
|
||||
github.com/fourcorelabs/wintoken v1.0.0
|
||||
github.com/jaypipes/ghw v0.12.0
|
||||
github.com/kardianos/service v1.2.2
|
||||
github.com/spf13/viper v1.18.2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/elastic/go-windows v1.0.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||
github.com/elastic/go-windows v1.0.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/google/cabbie v1.0.2 // indirect
|
||||
github.com/google/glazier v0.0.0-20211029225403-9f766cca891d // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/google/cabbie v1.0.3 // indirect
|
||||
github.com/google/glazier v0.0.0-20220520121753-83447cca4ea7 // indirect
|
||||
github.com/google/uuid v1.4.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/jaypipes/pcidb v0.6.0 // indirect
|
||||
github.com/jaypipes/pcidb v1.0.0 // indirect
|
||||
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 // indirect
|
||||
github.com/kr/pretty v0.2.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/magiconair/properties v1.8.5 // indirect
|
||||
github.com/klauspost/compress v1.17.2 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20220517141722-cf486979b281 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.3 // indirect
|
||||
github.com/nats-io/nkeys v0.3.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/nats-io/nkeys v0.4.7 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/pelletier/go-toml v1.9.4 // indirect
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/prometheus/procfs v0.0.8 // indirect
|
||||
github.com/rickb777/plural v1.3.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/rickb777/plural v1.4.1 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/scjalliance/comshim v0.0.0-20190308082608-cf06d2532c4e // indirect
|
||||
github.com/spf13/afero v1.6.0 // indirect
|
||||
github.com/spf13/cast v1.4.1 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.2.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.9 // indirect
|
||||
github.com/tklauser/numcpus v0.3.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
gopkg.in/ini.v1 v1.66.2 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
golang.org/x/crypto v0.18.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/sync v0.5.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/toast.v1 v1.0.0-20180812000517-0a84660828b2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
howett.net/plist v0.0.0-20181124034731-591f970eefbb // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
howett.net/plist v1.0.0 // indirect
|
||||
)
|
||||
|
69
main.go
69
main.go
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 AmidaWare LLC.
|
||||
Copyright 2023 AmidaWare Inc.
|
||||
|
||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||
You may only use the Licensed Software in accordance with the License.
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
version = "2.0.3"
|
||||
version = "2.6.1"
|
||||
log = logrus.New()
|
||||
logFile *os.File
|
||||
)
|
||||
@@ -51,11 +51,10 @@ func main() {
|
||||
meshDir := flag.String("meshdir", "", "Path to custom meshcentral dir")
|
||||
meshNodeID := flag.String("meshnodeid", "", "Mesh Node ID")
|
||||
cert := flag.String("cert", "", "Path to domain CA .pem")
|
||||
updateurl := flag.String("updateurl", "", "Download link to updater")
|
||||
inno := flag.String("inno", "", "Inno setup file")
|
||||
updatever := flag.String("updatever", "", "Update version")
|
||||
silent := flag.Bool("silent", false, "Do not popup any message boxes during installation")
|
||||
proxy := flag.String("proxy", "", "Use a http proxy")
|
||||
insecure := flag.Bool("insecure", false, "Insecure for testing only")
|
||||
natsport := flag.String("natsport", "", "nats standard port")
|
||||
flag.Parse()
|
||||
|
||||
if *ver {
|
||||
@@ -85,6 +84,8 @@ func main() {
|
||||
a.Logger.Debugf("%+v\n", a)
|
||||
|
||||
switch *mode {
|
||||
case "getenv":
|
||||
fmt.Println(os.Getenv(flag.Arg(0)))
|
||||
case "nixmeshnodeid":
|
||||
fmt.Print(a.NixMeshNodeID())
|
||||
case "installsvc":
|
||||
@@ -120,17 +121,13 @@ func main() {
|
||||
a.RunMigrations()
|
||||
case "recovermesh":
|
||||
a.RecoverMesh()
|
||||
case "macventurafix":
|
||||
a.FixVenturaMesh()
|
||||
case "taskrunner":
|
||||
if len(os.Args) < 5 || *taskPK == 0 {
|
||||
return
|
||||
}
|
||||
a.RunTask(*taskPK)
|
||||
case "update":
|
||||
if *updateurl == "" || *inno == "" || *updatever == "" {
|
||||
updateUsage()
|
||||
return
|
||||
}
|
||||
a.AgentUpdate(*updateurl, *inno, *updatever)
|
||||
case "install":
|
||||
if runtime.GOOS != "windows" {
|
||||
u, err := user.Current()
|
||||
@@ -143,27 +140,28 @@ func main() {
|
||||
}
|
||||
|
||||
if *api == "" || *clientID == 0 || *siteID == 0 || *token == "" {
|
||||
installUsage()
|
||||
return
|
||||
}
|
||||
a.Install(&agent.Installer{
|
||||
RMM: *api,
|
||||
ClientID: *clientID,
|
||||
SiteID: *siteID,
|
||||
Description: *desc,
|
||||
AgentType: *atype,
|
||||
Power: *power,
|
||||
RDP: *rdp,
|
||||
Ping: *ping,
|
||||
Token: *token,
|
||||
LocalMesh: *localMesh,
|
||||
Cert: *cert,
|
||||
Proxy: *proxy,
|
||||
Timeout: *timeout,
|
||||
Silent: *silent,
|
||||
NoMesh: *noMesh,
|
||||
MeshDir: *meshDir,
|
||||
MeshNodeID: *meshNodeID,
|
||||
RMM: *api,
|
||||
ClientID: *clientID,
|
||||
SiteID: *siteID,
|
||||
Description: *desc,
|
||||
AgentType: *atype,
|
||||
Power: *power,
|
||||
RDP: *rdp,
|
||||
Ping: *ping,
|
||||
Token: *token,
|
||||
LocalMesh: *localMesh,
|
||||
Cert: *cert,
|
||||
Proxy: *proxy,
|
||||
Timeout: *timeout,
|
||||
Silent: *silent,
|
||||
NoMesh: *noMesh,
|
||||
MeshDir: *meshDir,
|
||||
MeshNodeID: *meshNodeID,
|
||||
Insecure: *insecure,
|
||||
NatsStandardPort: *natsport,
|
||||
})
|
||||
default:
|
||||
agent.ShowStatus(version)
|
||||
@@ -183,20 +181,9 @@ func setupLogging(level, to *string) {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
logFile, _ = os.OpenFile(filepath.Join(os.Getenv("ProgramFiles"), "TacticalAgent", "agent.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0664)
|
||||
case "linux":
|
||||
default:
|
||||
logFile, _ = os.OpenFile(filepath.Join("/var/log/", "tacticalagent.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0664)
|
||||
}
|
||||
log.SetOutput(logFile)
|
||||
}
|
||||
}
|
||||
|
||||
func installUsage() {
|
||||
exe, _ := os.Executable()
|
||||
u := fmt.Sprintf(`Usage: %s -m install -api <https://api.example.com> -client-id X -site-id X -auth <TOKEN>`, exe)
|
||||
fmt.Println(u)
|
||||
}
|
||||
|
||||
func updateUsage() {
|
||||
u := `Usage: tacticalrmm.exe -m update -updateurl https://example.com/winagent-vX.X.X.exe -inno winagent-vX.X.X.exe -updatever 1.1.1`
|
||||
fmt.Println(u)
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 AmidaWare LLC.
|
||||
Copyright 2023 AmidaWare Inc.
|
||||
|
||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||
You may only use the Licensed Software in accordance with the License.
|
||||
@@ -33,15 +33,22 @@ type ProcessMsg struct {
|
||||
}
|
||||
|
||||
type AgentConfig struct {
|
||||
BaseURL string
|
||||
AgentID string
|
||||
APIURL string
|
||||
Token string
|
||||
AgentPK string
|
||||
PK int
|
||||
Cert string
|
||||
Proxy string
|
||||
CustomMeshDir string
|
||||
BaseURL string
|
||||
AgentID string
|
||||
APIURL string
|
||||
Token string
|
||||
AgentPK string
|
||||
PK int
|
||||
Cert string
|
||||
Proxy string
|
||||
CustomMeshDir string
|
||||
WinTmpDir string
|
||||
WinRunAsUserTmpDir string
|
||||
NatsProxyPath string
|
||||
NatsProxyPort string
|
||||
NatsStandardPort string
|
||||
NatsPingInterval int
|
||||
Insecure string
|
||||
}
|
||||
|
||||
type RunScriptResp struct {
|
||||
@@ -138,8 +145,10 @@ type AssignedTask struct {
|
||||
}
|
||||
|
||||
type Script struct {
|
||||
Shell string `json:"shell"`
|
||||
Code string `json:"code"`
|
||||
Shell string `json:"shell"`
|
||||
Code string `json:"code"`
|
||||
RunAsUser bool `json:"run_as_user"`
|
||||
EnvVars []string `json:"env_vars"`
|
||||
}
|
||||
|
||||
type CheckInfo struct {
|
||||
@@ -157,6 +166,7 @@ type Check struct {
|
||||
Disk string `json:"disk"`
|
||||
IP string `json:"ip"`
|
||||
ScriptArgs []string `json:"script_args"`
|
||||
EnvVars []string `json:"env_vars"`
|
||||
Timeout int `json:"timeout"`
|
||||
ServiceName string `json:"svc_name"`
|
||||
PassStartPending bool `json:"pass_if_start_pending"`
|
||||
@@ -185,6 +195,8 @@ type TaskAction struct {
|
||||
Code string `json:"code"`
|
||||
Args []string `json:"script_args"`
|
||||
Timeout int `json:"timeout"`
|
||||
RunAsUser bool `json:"run_as_user"`
|
||||
EnvVars []string `json:"env_vars"`
|
||||
}
|
||||
|
||||
type AutomatedTask struct {
|
||||
|
@@ -2,14 +2,14 @@
|
||||
"FixedFileInfo": {
|
||||
"FileVersion": {
|
||||
"Major": 2,
|
||||
"Minor": 0,
|
||||
"Patch": 3,
|
||||
"Minor": 6,
|
||||
"Patch": 1,
|
||||
"Build": 0
|
||||
},
|
||||
"ProductVersion": {
|
||||
"Major": 2,
|
||||
"Minor": 0,
|
||||
"Patch": 3,
|
||||
"Minor": 6,
|
||||
"Patch": 1,
|
||||
"Build": 0
|
||||
},
|
||||
"FileFlagsMask": "3f",
|
||||
@@ -20,16 +20,16 @@
|
||||
},
|
||||
"StringFileInfo": {
|
||||
"Comments": "",
|
||||
"CompanyName": "AmidaWare LLC",
|
||||
"CompanyName": "AmidaWare Inc",
|
||||
"FileDescription": "Tactical RMM Agent",
|
||||
"FileVersion": "v2.0.3.0",
|
||||
"FileVersion": "v2.6.1.0",
|
||||
"InternalName": "tacticalrmm.exe",
|
||||
"LegalCopyright": "Copyright (c) 2022 AmidaWare LLC",
|
||||
"LegalCopyright": "Copyright (c) 2023 AmidaWare Inc",
|
||||
"LegalTrademarks": "",
|
||||
"OriginalFilename": "tacticalrmm.exe",
|
||||
"PrivateBuild": "",
|
||||
"ProductName": "Tactical RMM Agent",
|
||||
"ProductVersion": "v2.0.3.0",
|
||||
"ProductVersion": "v2.6.1.0",
|
||||
"SpecialBuild": ""
|
||||
},
|
||||
"VarFileInfo": {
|
||||
|
Reference in New Issue
Block a user