Compare commits
186 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
697e112536 | ||
|
|
687e890a10 | ||
|
|
c64fc857c9 | ||
|
|
5fef58f764 | ||
|
|
ecad4d02d9 | ||
|
|
b09eaf84de | ||
|
|
12f1b6dc02 | ||
|
|
38bb9e4edd | ||
|
|
c91cfcff43 | ||
|
|
3e1546e08b | ||
|
|
51f6e93a35 | ||
|
|
57fb99996c | ||
|
|
964c31e119 | ||
|
|
eca05c3e76 | ||
|
|
98cae02ec6 | ||
|
|
996631e108 | ||
|
|
47b482419a | ||
|
|
67abc85168 | ||
|
|
a29709c599 | ||
|
|
e40dcf6079 | ||
|
|
dc10c33be8 | ||
|
|
d3eb6e041b | ||
|
|
055f11b0e3 | ||
|
|
729e568b12 | ||
|
|
c3500971ea | ||
|
|
0fb702cc76 | ||
|
|
cfbbdc89e7 | ||
|
|
ff9c271c76 | ||
|
|
4a79b3dce3 | ||
|
|
7dc08fdbf3 | ||
|
|
d4faa2a233 | ||
|
|
f0736faf4b | ||
|
|
2afdfd7ab8 | ||
|
|
060d222941 | ||
|
|
751b50e071 | ||
|
|
db17e3e28e | ||
|
|
1814d0f19d | ||
|
|
021f4febbb | ||
|
|
e6fea56198 | ||
|
|
87e1b29ef6 | ||
|
|
6ba3272dc0 | ||
|
|
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 | ||
|
|
072bd8096c | ||
|
|
de5d4871a1 | ||
|
|
a2f9b047a8 | ||
|
|
fb6402f756 | ||
|
|
6409f214a7 | ||
|
|
7509389cfc | ||
|
|
e8f11a852e |
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
|
||||||
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -3,14 +3,14 @@
|
|||||||
"[go]": {
|
"[go]": {
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.organizeImports": false,
|
"source.organizeImports": "always"
|
||||||
},
|
},
|
||||||
"editor.snippetSuggestions": "none",
|
"editor.snippetSuggestions": "none",
|
||||||
},
|
},
|
||||||
"[go.mod]": {
|
"[go.mod]": {
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.organizeImports": true,
|
"source.organizeImports": "explicit"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"gopls": {
|
"gopls": {
|
||||||
|
|||||||
14
LICENSE.md
14
LICENSE.md
@@ -1,11 +1,11 @@
|
|||||||
### Tactical RMM License Version 1.0
|
### 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.
|
          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).
|
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 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 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.
|
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
|
### 4. Copyright Notice
|
||||||
The following copyright notice shall be included in all copies of the Licensed Software:
|
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>
|
   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>
|
   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.
|
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
|
### 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
|
### 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.
|
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
|
### 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
|
### 11. Entire agreement
|
||||||
This license contains the whole agreement relating to its subject matter.
|
This license contains the whole agreement relating to its subject matter.
|
||||||
|
|||||||
410
agent/agent.go
410
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”).
|
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||||
You may only use the Licensed Software in accordance with the License.
|
You may only use the Licensed Software in accordance with the License.
|
||||||
@@ -14,9 +14,10 @@ package agent
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -24,6 +25,7 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -51,10 +53,18 @@ type Agent struct {
|
|||||||
ProgramDir string
|
ProgramDir string
|
||||||
EXE string
|
EXE string
|
||||||
SystemDrive string
|
SystemDrive string
|
||||||
|
WinTmpDir string
|
||||||
|
WinRunAsUserTmpDir string
|
||||||
MeshInstaller string
|
MeshInstaller string
|
||||||
MeshSystemEXE string
|
MeshSystemEXE string
|
||||||
MeshSVC string
|
MeshSVC string
|
||||||
PyBin string
|
PyBin string
|
||||||
|
PyVer string
|
||||||
|
PyBaseDir string
|
||||||
|
PyDir string
|
||||||
|
NuBin string
|
||||||
|
DenoBin string
|
||||||
|
AgentHeader string
|
||||||
Headers map[string]string
|
Headers map[string]string
|
||||||
Logger *logrus.Logger
|
Logger *logrus.Logger
|
||||||
Version string
|
Version string
|
||||||
@@ -66,6 +76,12 @@ type Agent struct {
|
|||||||
Platform string
|
Platform string
|
||||||
GoArch string
|
GoArch string
|
||||||
ServiceConfig *service.Config
|
ServiceConfig *service.Config
|
||||||
|
NatsServer string
|
||||||
|
NatsProxyPath string
|
||||||
|
NatsProxyPort string
|
||||||
|
NatsPingInterval int
|
||||||
|
NatsWSCompression bool
|
||||||
|
Insecure bool
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -73,9 +89,22 @@ const (
|
|||||||
winExeName = "tacticalrmm.exe"
|
winExeName = "tacticalrmm.exe"
|
||||||
winSvcName = "tacticalrmm"
|
winSvcName = "tacticalrmm"
|
||||||
meshSvcName = "mesh agent"
|
meshSvcName = "mesh agent"
|
||||||
|
etcConfig = "/etc/tacticalagent"
|
||||||
|
nixAgentDir = "/opt/tacticalagent"
|
||||||
|
nixMeshDir = "/opt/tacticalmesh"
|
||||||
|
nixAgentBin = nixAgentDir + "/tacticalagent"
|
||||||
|
nixAgentBinDir = nixAgentDir + "/bin"
|
||||||
|
nixAgentEtcDir = nixAgentDir + "/etc"
|
||||||
|
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 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 {
|
func New(logger *logrus.Logger, version string) *Agent {
|
||||||
host, _ := ps.Host()
|
host, _ := ps.Host()
|
||||||
@@ -83,29 +112,73 @@ func New(logger *logrus.Logger, version string) *Agent {
|
|||||||
pd := filepath.Join(os.Getenv("ProgramFiles"), progFilesName)
|
pd := filepath.Join(os.Getenv("ProgramFiles"), progFilesName)
|
||||||
exe := filepath.Join(pd, winExeName)
|
exe := filepath.Join(pd, winExeName)
|
||||||
sd := os.Getenv("SystemDrive")
|
sd := os.Getenv("SystemDrive")
|
||||||
|
winTempDir := defaultWinTmpDir
|
||||||
|
winRunAsUserTmpDir := defaultWinTmpDir
|
||||||
|
|
||||||
var pybin string
|
hostname, err := os.Hostname()
|
||||||
switch runtime.GOARCH {
|
if err != nil {
|
||||||
case "amd64":
|
hostname = info.Hostname
|
||||||
pybin = filepath.Join(pd, "py38-x64", "python.exe")
|
}
|
||||||
case "386":
|
|
||||||
pybin = filepath.Join(pd, "py38-x32", "python.exe")
|
pyver := "n/a"
|
||||||
|
pybin := "n/a"
|
||||||
|
pyBaseDir := "n/a"
|
||||||
|
pydir := "n/a"
|
||||||
|
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
major := info.OS.Major
|
||||||
|
minor := info.OS.Minor
|
||||||
|
if major > 6 || (major == 6 && minor >= 3) {
|
||||||
|
// Windows 8.1 or higher
|
||||||
|
pyver = "3.11.9"
|
||||||
|
} else {
|
||||||
|
pyver = "3.8.7"
|
||||||
|
}
|
||||||
|
|
||||||
|
pydir = "py" + pyver + "_" + runtime.GOARCH
|
||||||
|
pyBaseDir = filepath.Join(pd, "python")
|
||||||
|
pybin = filepath.Join(pyBaseDir, pydir, "python.exe")
|
||||||
|
}
|
||||||
|
|
||||||
|
var nuBin string
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "windows":
|
||||||
|
nuBin = filepath.Join(pd, "bin", "nu.exe")
|
||||||
|
default:
|
||||||
|
nuBin = filepath.Join(nixAgentBinDir, "nu")
|
||||||
|
}
|
||||||
|
|
||||||
|
var denoBin string
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "windows":
|
||||||
|
denoBin = filepath.Join(pd, "bin", "deno.exe")
|
||||||
|
default:
|
||||||
|
denoBin = filepath.Join(nixAgentBinDir, "deno")
|
||||||
}
|
}
|
||||||
|
|
||||||
ac := NewAgentConfig()
|
ac := NewAgentConfig()
|
||||||
|
|
||||||
|
agentHeader := fmt.Sprintf("trmm/%s/%s/%s", version, runtime.GOOS, runtime.GOARCH)
|
||||||
headers := make(map[string]string)
|
headers := make(map[string]string)
|
||||||
if len(ac.Token) > 0 {
|
if len(ac.Token) > 0 {
|
||||||
headers["Content-Type"] = "application/json"
|
headers["Content-Type"] = "application/json"
|
||||||
headers["Authorization"] = fmt.Sprintf("Token %s", ac.Token)
|
headers["Authorization"] = fmt.Sprintf("Token %s", ac.Token)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
insecure := ac.Insecure == "true"
|
||||||
|
|
||||||
restyC := resty.New()
|
restyC := resty.New()
|
||||||
restyC.SetBaseURL(ac.BaseURL)
|
restyC.SetBaseURL(ac.BaseURL)
|
||||||
restyC.SetCloseConnection(true)
|
restyC.SetCloseConnection(true)
|
||||||
restyC.SetHeaders(headers)
|
restyC.SetHeaders(headers)
|
||||||
restyC.SetTimeout(15 * time.Second)
|
restyC.SetTimeout(15 * time.Second)
|
||||||
restyC.SetDebug(logger.IsLevelEnabled(logrus.DebugLevel))
|
restyC.SetDebug(logger.IsLevelEnabled(logrus.DebugLevel))
|
||||||
|
if insecure {
|
||||||
|
insecureConf := &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
}
|
||||||
|
restyC.SetTLSClientConfig(insecureConf)
|
||||||
|
}
|
||||||
|
|
||||||
if len(ac.Proxy) > 0 {
|
if len(ac.Proxy) > 0 {
|
||||||
restyC.SetProxy(ac.Proxy)
|
restyC.SetProxy(ac.Proxy)
|
||||||
@@ -114,15 +187,30 @@ func New(logger *logrus.Logger, version string) *Agent {
|
|||||||
restyC.SetRootCertificate(ac.Cert)
|
restyC.SetRootCertificate(ac.Cert)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(ac.WinTmpDir) > 0 {
|
||||||
|
winTempDir = ac.WinTmpDir
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ac.WinRunAsUserTmpDir) > 0 {
|
||||||
|
winRunAsUserTmpDir = ac.WinRunAsUserTmpDir
|
||||||
|
}
|
||||||
|
|
||||||
var MeshSysExe string
|
var MeshSysExe string
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "windows":
|
||||||
if len(ac.CustomMeshDir) > 0 {
|
if len(ac.CustomMeshDir) > 0 {
|
||||||
MeshSysExe = filepath.Join(ac.CustomMeshDir, "MeshAgent.exe")
|
MeshSysExe = filepath.Join(ac.CustomMeshDir, "MeshAgent.exe")
|
||||||
} else {
|
} else {
|
||||||
MeshSysExe = filepath.Join(os.Getenv("ProgramFiles"), "Mesh Agent", "MeshAgent.exe")
|
MeshSysExe = filepath.Join(os.Getenv("ProgramFiles"), "Mesh Agent", "MeshAgent.exe")
|
||||||
}
|
}
|
||||||
|
case "darwin":
|
||||||
if runtime.GOOS == "linux" {
|
if trmm.FileExists(nixMeshAgentBin) {
|
||||||
MeshSysExe = "/opt/tacticalmesh/meshagent"
|
MeshSysExe = nixMeshAgentBin
|
||||||
|
} else {
|
||||||
|
MeshSysExe = "/usr/local/mesh_services/meshagent/meshagent"
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
MeshSysExe = nixMeshAgentBin
|
||||||
}
|
}
|
||||||
|
|
||||||
svcConf := &service.Config{
|
svcConf := &service.Config{
|
||||||
@@ -134,14 +222,39 @@ func New(logger *logrus.Logger, version string) *Agent {
|
|||||||
Option: service.KeyValue{
|
Option: service.KeyValue{
|
||||||
"StartType": "automatic",
|
"StartType": "automatic",
|
||||||
"OnFailure": "restart",
|
"OnFailure": "restart",
|
||||||
"OnFailureDelayDuration": "5s",
|
"OnFailureDelayDuration": "12s",
|
||||||
"OnFailureResetPeriod": 10,
|
"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{
|
return &Agent{
|
||||||
Hostname: info.Hostname,
|
Hostname: hostname,
|
||||||
Arch: info.Architecture,
|
|
||||||
BaseURL: ac.BaseURL,
|
BaseURL: ac.BaseURL,
|
||||||
AgentID: ac.AgentID,
|
AgentID: ac.AgentID,
|
||||||
ApiURL: ac.APIURL,
|
ApiURL: ac.APIURL,
|
||||||
@@ -151,11 +264,19 @@ func New(logger *logrus.Logger, version string) *Agent {
|
|||||||
ProgramDir: pd,
|
ProgramDir: pd,
|
||||||
EXE: exe,
|
EXE: exe,
|
||||||
SystemDrive: sd,
|
SystemDrive: sd,
|
||||||
|
WinTmpDir: winTempDir,
|
||||||
|
WinRunAsUserTmpDir: winRunAsUserTmpDir,
|
||||||
MeshInstaller: "meshagent.exe",
|
MeshInstaller: "meshagent.exe",
|
||||||
MeshSystemEXE: MeshSysExe,
|
MeshSystemEXE: MeshSysExe,
|
||||||
MeshSVC: meshSvcName,
|
MeshSVC: meshSvcName,
|
||||||
PyBin: pybin,
|
PyBin: pybin,
|
||||||
|
PyVer: pyver,
|
||||||
|
PyBaseDir: pyBaseDir,
|
||||||
|
PyDir: pydir,
|
||||||
|
NuBin: nuBin,
|
||||||
|
DenoBin: denoBin,
|
||||||
Headers: headers,
|
Headers: headers,
|
||||||
|
AgentHeader: agentHeader,
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
Version: version,
|
Version: version,
|
||||||
Debug: logger.IsLevelEnabled(logrus.DebugLevel),
|
Debug: logger.IsLevelEnabled(logrus.DebugLevel),
|
||||||
@@ -164,6 +285,12 @@ func New(logger *logrus.Logger, version string) *Agent {
|
|||||||
Platform: runtime.GOOS,
|
Platform: runtime.GOOS,
|
||||||
GoArch: runtime.GOARCH,
|
GoArch: runtime.GOARCH,
|
||||||
ServiceConfig: svcConf,
|
ServiceConfig: svcConf,
|
||||||
|
NatsServer: natsServer,
|
||||||
|
NatsProxyPath: natsProxyPath,
|
||||||
|
NatsProxyPort: natsProxyPort,
|
||||||
|
NatsPingInterval: natsPingInterval,
|
||||||
|
NatsWSCompression: natsWsCompression,
|
||||||
|
Insecure: insecure,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,12 +308,13 @@ type CmdOptions struct {
|
|||||||
IsScript bool
|
IsScript bool
|
||||||
IsExecutable bool
|
IsExecutable bool
|
||||||
Detached bool
|
Detached bool
|
||||||
|
EnvVars []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Agent) NewCMDOpts() *CmdOptions {
|
func (a *Agent) NewCMDOpts() *CmdOptions {
|
||||||
return &CmdOptions{
|
return &CmdOptions{
|
||||||
Shell: "/bin/bash",
|
Shell: "/bin/bash",
|
||||||
Timeout: 30,
|
Timeout: 60,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,11 +332,16 @@ func (a *Agent) CmdV2(c *CmdOptions) CmdStatus {
|
|||||||
// have a child process that is in a different process group so that
|
// have a child process that is in a different process group so that
|
||||||
// parent terminating doesn't kill child
|
// parent terminating doesn't kill child
|
||||||
if c.Detached {
|
if c.Detached {
|
||||||
cmdOptions.BeforeExec = []func(cmd *exec.Cmd){
|
cmdOptions.BeforeExec = append(cmdOptions.BeforeExec, func(cmd *exec.Cmd) {
|
||||||
func(cmd *exec.Cmd) {
|
|
||||||
cmd.SysProcAttr = SetDetached()
|
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
|
var envCmd *gocmd.Cmd
|
||||||
@@ -217,7 +350,8 @@ func (a *Agent) CmdV2(c *CmdOptions) CmdStatus {
|
|||||||
} else if c.IsExecutable {
|
} else if c.IsExecutable {
|
||||||
envCmd = gocmd.NewCmdOptions(cmdOptions, c.Shell, c.Command) // c.Shell: bin + c.Command: args as one string
|
envCmd = gocmd.NewCmdOptions(cmdOptions, c.Shell, c.Command) // c.Shell: bin + c.Command: args as one string
|
||||||
} else {
|
} 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
|
var stdoutBuf bytes.Buffer
|
||||||
@@ -249,25 +383,46 @@ func (a *Agent) CmdV2(c *CmdOptions) CmdStatus {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Run and wait for Cmd to return, discard Status
|
statusChan := make(chan gocmd.Status, 1)
|
||||||
envCmd.Start()
|
// workaround for https://github.com/golang/go/issues/22315
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
select {
|
for i := 0; i < 5; i++ {
|
||||||
case <-doneChan:
|
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
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var finalStatus gocmd.Status
|
||||||
|
|
||||||
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
a.Logger.Debugf("Command timed out after %d seconds\n", c.Timeout)
|
a.Logger.Debugf("Command timed out after %d seconds\n", c.Timeout)
|
||||||
pid := envCmd.Status().PID
|
pid := envCmd.Status().PID
|
||||||
a.Logger.Debugln("Killing process with PID", pid)
|
a.Logger.Debugln("Killing process with PID", pid)
|
||||||
KillProc(int32(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
|
// Wait for goroutine to print everything
|
||||||
<-doneChan
|
<-doneChan
|
||||||
|
|
||||||
ret := CmdStatus{
|
ret := CmdStatus{
|
||||||
Status: envCmd.Status(),
|
Status: finalStatus,
|
||||||
Stdout: CleanString(stdoutBuf.String()),
|
Stdout: CleanString(stdoutBuf.String()),
|
||||||
Stderr: CleanString(stderrBuf.String()),
|
Stderr: CleanString(stderrBuf.String()),
|
||||||
}
|
}
|
||||||
@@ -325,7 +480,7 @@ func (a *Agent) ForceKillMesh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, pid := range pids {
|
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 {
|
if err := KillProc(int32(pid)); err != nil {
|
||||||
a.Logger.Debugln(err)
|
a.Logger.Debugln(err)
|
||||||
}
|
}
|
||||||
@@ -353,13 +508,37 @@ func (a *Agent) SyncMeshNodeID() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Agent) setupNatsOptions() []nats.Option {
|
func (a *Agent) setupNatsOptions() []nats.Option {
|
||||||
|
reconnectWait := randRange(2, 8)
|
||||||
opts := make([]nats.Option, 0)
|
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.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.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.MaxReconnects(-1))
|
||||||
opts = append(opts, nats.ReconnectBufSize(-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
|
return opts
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,46 +558,47 @@ func (a *Agent) GetUninstallExe() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Agent) CleanupAgentUpdates() {
|
func (a *Agent) CleanupAgentUpdates() {
|
||||||
cderr := os.Chdir(a.ProgramDir)
|
// TODO remove a.ProgramDir, updates are now in winTempDir
|
||||||
if cderr != nil {
|
dirs := [3]string{a.WinTmpDir, os.Getenv("TMP"), a.ProgramDir}
|
||||||
a.Logger.Errorln(cderr)
|
for _, dir := range dirs {
|
||||||
return
|
err := os.Chdir(dir)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Debugln("CleanupAgentUpdates()", dir, err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
files, err := filepath.Glob("winagent-v*.exe")
|
// 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 {
|
if err == nil {
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
|
a.Logger.Debugln("CleanupAgentUpdates() Removing file:", f)
|
||||||
os.Remove(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 {
|
if err == nil {
|
||||||
for _, f := range folders {
|
dirs, err := filepath.Glob("tacticalrmm*")
|
||||||
|
if err == nil {
|
||||||
|
for _, f := range dirs {
|
||||||
os.RemoveAll(f)
|
os.RemoveAll(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Agent) RunPythonCode(code string, timeout int, args []string) (string, error) {
|
func (a *Agent) RunPythonCode(code string, timeout int, args []string) (string, error) {
|
||||||
content := []byte(code)
|
content := []byte(code)
|
||||||
dir, err := ioutil.TempDir("", "tacticalpy")
|
tmpfn, _ := os.CreateTemp(a.WinTmpDir, "*.py")
|
||||||
if err != nil {
|
|
||||||
a.Logger.Debugln(err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
|
|
||||||
tmpfn, _ := ioutil.TempFile(dir, "*.py")
|
|
||||||
if _, err := tmpfn.Write(content); err != nil {
|
if _, err := tmpfn.Write(content); err != nil {
|
||||||
a.Logger.Debugln(err)
|
a.Logger.Debugln(err)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
defer os.Remove(tmpfn.Name())
|
||||||
if err := tmpfn.Close(); err != nil {
|
if err := tmpfn.Close(); err != nil {
|
||||||
a.Logger.Debugln(err)
|
a.Logger.Debugln(err)
|
||||||
return "", err
|
return "", err
|
||||||
@@ -458,13 +638,133 @@ func (a *Agent) RunPythonCode(code string, timeout int, args []string) (string,
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Agent) CreateTRMMTempDir() {
|
func createWinTempDir() error {
|
||||||
// create the temp dir for running scripts
|
if !trmm.FileExists(defaultWinTmpDir) {
|
||||||
dir := filepath.Join(os.TempDir(), "trmm")
|
err := os.Mkdir(defaultWinTmpDir, 0775)
|
||||||
if !trmm.FileExists(dir) {
|
|
||||||
err := os.Mkdir(dir, 0775)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.Logger.Errorln(err)
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Agent) RunTask(id int) error {
|
||||||
|
data := rmm.AutomatedTask{}
|
||||||
|
url := fmt.Sprintf("/api/v3/%d/%s/taskrunner/", id, a.AgentID)
|
||||||
|
r1, gerr := a.rClient.R().Get(url)
|
||||||
|
if gerr != nil {
|
||||||
|
a.Logger.Debugln(gerr)
|
||||||
|
return gerr
|
||||||
|
}
|
||||||
|
|
||||||
|
if r1.IsError() {
|
||||||
|
a.Logger.Debugln("Run Task:", r1.String())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(r1.Body(), &data); err != nil {
|
||||||
|
a.Logger.Debugln(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
type TaskResult struct {
|
||||||
|
Stdout string `json:"stdout"`
|
||||||
|
Stderr string `json:"stderr"`
|
||||||
|
RetCode int `json:"retcode"`
|
||||||
|
ExecTime float64 `json:"execution_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := TaskResult{}
|
||||||
|
|
||||||
|
// loop through all task actions
|
||||||
|
for _, action := range data.TaskActions {
|
||||||
|
|
||||||
|
action_start := time.Now()
|
||||||
|
if action.ActionType == "script" {
|
||||||
|
stdout, stderr, retcode, err := a.RunScript(action.Code, action.Shell, action.Args, action.Timeout, action.RunAsUser, action.EnvVars, action.NushellEnableConfig, action.DenoDefaultPermissions)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Debugln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add text to stdout showing which script ran if more than 1 script
|
||||||
|
action_exec_time := time.Since(action_start).Seconds()
|
||||||
|
|
||||||
|
if len(data.TaskActions) > 1 {
|
||||||
|
payload.Stdout += fmt.Sprintf("\n------------\nRunning Script: %s. Execution Time: %f\n------------\n\n", action.ScriptName, action_exec_time)
|
||||||
|
}
|
||||||
|
|
||||||
|
// save results
|
||||||
|
payload.Stdout += stdout
|
||||||
|
payload.Stderr += stderr
|
||||||
|
payload.RetCode = retcode
|
||||||
|
|
||||||
|
if !data.ContinueOnError && stderr != "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if action.ActionType == "cmd" {
|
||||||
|
var stdout, stderr string
|
||||||
|
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "windows":
|
||||||
|
out, err := CMDShell(action.Shell, []string{}, action.Command, action.Timeout, false, action.RunAsUser)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Debugln(err)
|
||||||
|
}
|
||||||
|
stdout = out[0]
|
||||||
|
stderr = out[1]
|
||||||
|
|
||||||
|
if stderr == "" {
|
||||||
|
payload.RetCode = 0
|
||||||
|
} else {
|
||||||
|
payload.RetCode = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
opts := a.NewCMDOpts()
|
||||||
|
opts.Shell = action.Shell
|
||||||
|
opts.Command = action.Command
|
||||||
|
opts.Timeout = time.Duration(action.Timeout)
|
||||||
|
out := a.CmdV2(opts)
|
||||||
|
|
||||||
|
if out.Status.Error != nil {
|
||||||
|
a.Logger.Debugln("RunTask() cmd: ", out.Status.Error.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
stdout = out.Stdout
|
||||||
|
stderr = out.Stderr
|
||||||
|
payload.RetCode = out.Status.Exit
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data.TaskActions) > 1 {
|
||||||
|
action_exec_time := time.Since(action_start).Seconds()
|
||||||
|
|
||||||
|
// add text to stdout showing which script ran
|
||||||
|
payload.Stdout += fmt.Sprintf("\n------------\nRunning Command: %s. Execution Time: %f\n------------\n\n", action.Command, action_exec_time)
|
||||||
|
}
|
||||||
|
// save results
|
||||||
|
payload.Stdout += stdout
|
||||||
|
payload.Stderr += stderr
|
||||||
|
|
||||||
|
if payload.RetCode != 0 {
|
||||||
|
if !data.ContinueOnError {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
a.Logger.Debugln("Invalid Action", action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
payload.ExecTime = time.Since(start).Seconds()
|
||||||
|
|
||||||
|
_, perr := a.rClient.R().SetBody(payload).Patch(url)
|
||||||
|
if perr != nil {
|
||||||
|
a.Logger.Debugln(perr)
|
||||||
|
return perr
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,464 +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 (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
rmm "github.com/amidaware/rmmagent/shared"
|
|
||||||
ps "github.com/elastic/go-sysinfo"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
"github.com/jaypipes/ghw"
|
|
||||||
"github.com/kardianos/service"
|
|
||||||
"github.com/shirou/gopsutil/v3/cpu"
|
|
||||||
"github.com/shirou/gopsutil/v3/disk"
|
|
||||||
psHost "github.com/shirou/gopsutil/v3/host"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
trmm "github.com/wh1te909/trmm-shared"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ShowStatus(version string) {
|
|
||||||
fmt.Println(version)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Agent) GetDisks() []trmm.Disk {
|
|
||||||
ret := make([]trmm.Disk, 0)
|
|
||||||
partitions, err := disk.Partitions(false)
|
|
||||||
if err != nil {
|
|
||||||
a.Logger.Debugln(err)
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range partitions {
|
|
||||||
if strings.Contains(p.Device, "dev/loop") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
usage, err := disk.Usage(p.Mountpoint)
|
|
||||||
if err != nil {
|
|
||||||
a.Logger.Debugln(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
d := trmm.Disk{
|
|
||||||
Device: p.Device,
|
|
||||||
Fstype: p.Fstype,
|
|
||||||
Total: ByteCountSI(usage.Total),
|
|
||||||
Used: ByteCountSI(usage.Used),
|
|
||||||
Free: ByteCountSI(usage.Free),
|
|
||||||
Percent: int(usage.UsedPercent),
|
|
||||||
}
|
|
||||||
ret = append(ret, d)
|
|
||||||
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Agent) SystemRebootRequired() (bool, error) {
|
|
||||||
paths := [2]string{"/var/run/reboot-required", "/usr/bin/needs-restarting"}
|
|
||||||
for _, p := range paths {
|
|
||||||
if trmm.FileExists(p) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Agent) LoggedOnUser() string {
|
|
||||||
var ret string
|
|
||||||
users, err := psHost.Users()
|
|
||||||
if err != nil {
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// return the first logged in user
|
|
||||||
for _, user := range users {
|
|
||||||
if user.User != "" {
|
|
||||||
ret = user.User
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Agent) osString() string {
|
|
||||||
h, err := psHost.Info()
|
|
||||||
if err != nil {
|
|
||||||
return "error getting host info"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s %s %s %s", strings.Title(h.Platform), h.PlatformVersion, h.KernelArch, h.KernelVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAgentConfig() *rmm.AgentConfig {
|
|
||||||
viper.SetConfigName("tacticalagent")
|
|
||||||
viper.SetConfigType("json")
|
|
||||||
viper.AddConfigPath("/etc/")
|
|
||||||
viper.AddConfigPath(".")
|
|
||||||
err := viper.ReadInConfig()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return &rmm.AgentConfig{}
|
|
||||||
}
|
|
||||||
|
|
||||||
agentpk := viper.GetString("agentpk")
|
|
||||||
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"),
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Agent) RunScript(code string, shell string, args []string, timeout int) (stdout, stderr string, exitcode int, e error) {
|
|
||||||
code = removeWinNewLines(code)
|
|
||||||
content := []byte(code)
|
|
||||||
|
|
||||||
f, err := createTmpFile()
|
|
||||||
if err != nil {
|
|
||||||
a.Logger.Errorln("RunScript createTmpFile()", err)
|
|
||||||
return "", err.Error(), 85, err
|
|
||||||
}
|
|
||||||
defer os.Remove(f.Name())
|
|
||||||
|
|
||||||
if _, err := f.Write(content); err != nil {
|
|
||||||
a.Logger.Errorln(err)
|
|
||||||
return "", err.Error(), 85, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := f.Close(); err != nil {
|
|
||||||
a.Logger.Errorln(err)
|
|
||||||
return "", err.Error(), 85, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.Chmod(f.Name(), 0770); err != nil {
|
|
||||||
a.Logger.Errorln(err)
|
|
||||||
return "", err.Error(), 85, err
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := a.NewCMDOpts()
|
|
||||||
opts.IsScript = true
|
|
||||||
opts.Shell = f.Name()
|
|
||||||
opts.Args = args
|
|
||||||
opts.Timeout = time.Duration(timeout)
|
|
||||||
out := a.CmdV2(opts)
|
|
||||||
retError := ""
|
|
||||||
if out.Status.Error != nil {
|
|
||||||
retError += CleanString(out.Status.Error.Error())
|
|
||||||
retError += "\n"
|
|
||||||
}
|
|
||||||
if len(out.Stderr) > 0 {
|
|
||||||
retError += out.Stderr
|
|
||||||
}
|
|
||||||
return out.Stdout, retError, out.Status.Exit, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetDetached() *syscall.SysProcAttr {
|
|
||||||
return &syscall.SysProcAttr{Setpgid: true}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Agent) AgentUpdate(url, inno, version string) {
|
|
||||||
|
|
||||||
self, err := os.Executable()
|
|
||||||
if err != nil {
|
|
||||||
a.Logger.Errorln("AgentUpdate() os.Executable():", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := createTmpFile()
|
|
||||||
if err != nil {
|
|
||||||
a.Logger.Errorln("AgentUpdate createTmpFile()", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
|
|
||||||
rClient := resty.New()
|
|
||||||
rClient.SetCloseConnection(true)
|
|
||||||
rClient.SetTimeout(15 * time.Minute)
|
|
||||||
rClient.SetDebug(a.Debug)
|
|
||||||
if len(a.Proxy) > 0 {
|
|
||||||
rClient.SetProxy(a.Proxy)
|
|
||||||
}
|
|
||||||
|
|
||||||
r, err := rClient.R().SetOutput(f.Name()).Get(url)
|
|
||||||
if err != nil {
|
|
||||||
a.Logger.Errorln("AgentUpdate() download:", err)
|
|
||||||
f.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if r.IsError() {
|
|
||||||
a.Logger.Errorln("AgentUpdate() status code:", r.StatusCode())
|
|
||||||
f.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Close()
|
|
||||||
os.Chmod(f.Name(), 0755)
|
|
||||||
err = os.Rename(f.Name(), self)
|
|
||||||
if err != nil {
|
|
||||||
a.Logger.Errorln("AgentUpdate() os.Rename():", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := a.NewCMDOpts()
|
|
||||||
opts.Detached = true
|
|
||||||
opts.Command = "systemctl restart tacticalagent.service"
|
|
||||||
a.CmdV2(opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Agent) AgentUninstall(code string) {
|
|
||||||
f, err := createTmpFile()
|
|
||||||
if err != nil {
|
|
||||||
a.Logger.Errorln("AgentUninstall createTmpFile():", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Write([]byte(code))
|
|
||||||
f.Close()
|
|
||||||
os.Chmod(f.Name(), 0770)
|
|
||||||
|
|
||||||
opts := a.NewCMDOpts()
|
|
||||||
opts.IsScript = true
|
|
||||||
opts.Shell = f.Name()
|
|
||||||
opts.Args = []string{"uninstall"}
|
|
||||||
opts.Detached = true
|
|
||||||
a.CmdV2(opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Agent) NixMeshNodeID() string {
|
|
||||||
var meshNodeID string
|
|
||||||
meshSuccess := false
|
|
||||||
a.Logger.Debugln("Getting mesh node id")
|
|
||||||
opts := a.NewCMDOpts()
|
|
||||||
opts.IsExecutable = true
|
|
||||||
opts.Shell = a.MeshSystemEXE
|
|
||||||
opts.Command = "-nodeid"
|
|
||||||
|
|
||||||
for !meshSuccess {
|
|
||||||
out := a.CmdV2(opts)
|
|
||||||
meshNodeID = out.Stdout
|
|
||||||
a.Logger.Debugln("Stdout:", out.Stdout)
|
|
||||||
a.Logger.Debugln("Stderr:", out.Stderr)
|
|
||||||
if meshNodeID == "" {
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
continue
|
|
||||||
} else if strings.Contains(strings.ToLower(meshNodeID), "graphical version") || strings.Contains(strings.ToLower(meshNodeID), "zenity") {
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
meshSuccess = true
|
|
||||||
}
|
|
||||||
return meshNodeID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Agent) getMeshNodeID() (string, error) {
|
|
||||||
return a.NixMeshNodeID(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Agent) RecoverMesh() {
|
|
||||||
a.Logger.Infoln("Attempting mesh recovery")
|
|
||||||
opts := a.NewCMDOpts()
|
|
||||||
opts.Command = "systemctl restart meshagent.service"
|
|
||||||
a.CmdV2(opts)
|
|
||||||
a.SyncMeshNodeID()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Agent) GetWMIInfo() map[string]interface{} {
|
|
||||||
wmiInfo := make(map[string]interface{})
|
|
||||||
ips := make([]string, 0)
|
|
||||||
disks := make([]string, 0)
|
|
||||||
cpus := make([]string, 0)
|
|
||||||
gpus := make([]string, 0)
|
|
||||||
|
|
||||||
// local ips
|
|
||||||
host, err := ps.Host()
|
|
||||||
if err != nil {
|
|
||||||
a.Logger.Errorln("GetWMIInfo() ps.Host()", err)
|
|
||||||
} else {
|
|
||||||
for _, ip := range host.Info().IPs {
|
|
||||||
if strings.Contains(ip, "127.0.") || strings.Contains(ip, "::1/128") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ips = append(ips, ip)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wmiInfo["local_ips"] = ips
|
|
||||||
|
|
||||||
// disks
|
|
||||||
block, err := ghw.Block(ghw.WithDisableWarnings())
|
|
||||||
if err != nil {
|
|
||||||
a.Logger.Errorln("ghw.Block()", err)
|
|
||||||
} else {
|
|
||||||
for _, disk := range block.Disks {
|
|
||||||
if disk.IsRemovable || strings.Contains(disk.Name, "ram") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ret := fmt.Sprintf("%s %s %s %s %s %s", disk.Vendor, disk.Model, disk.StorageController, disk.DriveType, disk.Name, ByteCountSI(disk.SizeBytes))
|
|
||||||
ret = strings.TrimSpace(strings.ReplaceAll(ret, "unknown", ""))
|
|
||||||
disks = append(disks, ret)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wmiInfo["disks"] = disks
|
|
||||||
|
|
||||||
// cpus
|
|
||||||
cpuInfo, err := cpu.Info()
|
|
||||||
if err != nil {
|
|
||||||
a.Logger.Errorln("cpu.Info()", err)
|
|
||||||
} else {
|
|
||||||
if len(cpuInfo) > 0 {
|
|
||||||
if cpuInfo[0].ModelName != "" {
|
|
||||||
cpus = append(cpus, cpuInfo[0].ModelName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wmiInfo["cpus"] = cpus
|
|
||||||
|
|
||||||
// make/model
|
|
||||||
wmiInfo["make_model"] = ""
|
|
||||||
chassis, err := ghw.Chassis(ghw.WithDisableWarnings())
|
|
||||||
if err != nil {
|
|
||||||
a.Logger.Errorln("ghw.Chassis()", err)
|
|
||||||
} else {
|
|
||||||
if chassis.Vendor != "" || chassis.Version != "" {
|
|
||||||
wmiInfo["make_model"] = fmt.Sprintf("%s %s", chassis.Vendor, chassis.Version)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// gfx cards
|
|
||||||
|
|
||||||
gpu, err := ghw.GPU(ghw.WithDisableWarnings())
|
|
||||||
if err != nil {
|
|
||||||
a.Logger.Errorln("ghw.GPU()", err)
|
|
||||||
} else {
|
|
||||||
for _, i := range gpu.GraphicsCards {
|
|
||||||
if i.DeviceInfo != nil {
|
|
||||||
ret := fmt.Sprintf("%s %s", i.DeviceInfo.Vendor.Name, i.DeviceInfo.Product.Name)
|
|
||||||
gpus = append(gpus, ret)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wmiInfo["gpus"] = gpus
|
|
||||||
|
|
||||||
// temp hack for ARM cpu/make/model if rasp pi
|
|
||||||
var makeModel string
|
|
||||||
if strings.Contains(runtime.GOARCH, "arm") {
|
|
||||||
file, _ := os.Open("/proc/cpuinfo")
|
|
||||||
scanner := bufio.NewScanner(file)
|
|
||||||
for scanner.Scan() {
|
|
||||||
if strings.Contains(strings.ToLower(scanner.Text()), "raspberry") {
|
|
||||||
model := strings.Split(scanner.Text(), ":")
|
|
||||||
if len(model) == 2 {
|
|
||||||
makeModel = strings.TrimSpace(model[1])
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cpus) == 0 {
|
|
||||||
wmiInfo["cpus"] = []string{makeModel}
|
|
||||||
}
|
|
||||||
if makeModel != "" && (wmiInfo["make_model"] == "" || wmiInfo["make_model"] == "unknown unknown") {
|
|
||||||
wmiInfo["make_model"] = makeModel
|
|
||||||
}
|
|
||||||
|
|
||||||
return wmiInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
// windows only below TODO add into stub file
|
|
||||||
|
|
||||||
func (a *Agent) PlatVer() (string, error) { return "", nil }
|
|
||||||
|
|
||||||
func (a *Agent) SendSoftware() {}
|
|
||||||
|
|
||||||
func (a *Agent) UninstallCleanup() {}
|
|
||||||
|
|
||||||
func (a *Agent) RunMigrations() {}
|
|
||||||
|
|
||||||
func GetServiceStatus(name string) (string, error) { return "", nil }
|
|
||||||
|
|
||||||
func (a *Agent) GetPython(force bool) {}
|
|
||||||
|
|
||||||
type SchedTask struct{ Name string }
|
|
||||||
|
|
||||||
func (a *Agent) PatchMgmnt(enable bool) error { return nil }
|
|
||||||
|
|
||||||
func (a *Agent) CreateSchedTask(st SchedTask) (bool, error) { return false, nil }
|
|
||||||
|
|
||||||
func DeleteSchedTask(name string) error { return nil }
|
|
||||||
|
|
||||||
func ListSchedTasks() []string { return []string{} }
|
|
||||||
|
|
||||||
func (a *Agent) GetEventLog(logName string, searchLastDays int) []rmm.EventLogMsg {
|
|
||||||
return []rmm.EventLogMsg{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Agent) GetServiceDetail(name string) trmm.WindowsService { return trmm.WindowsService{} }
|
|
||||||
|
|
||||||
func (a *Agent) ControlService(name, action string) rmm.WinSvcResp {
|
|
||||||
return rmm.WinSvcResp{Success: false, ErrorMsg: "/na"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Agent) EditService(name, startupType string) rmm.WinSvcResp {
|
|
||||||
return rmm.WinSvcResp{Success: false, ErrorMsg: "/na"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Agent) GetInstalledSoftware() []trmm.WinSoftwareList { return []trmm.WinSoftwareList{} }
|
|
||||||
|
|
||||||
func (a *Agent) ChecksRunning() bool { return false }
|
|
||||||
|
|
||||||
func (a *Agent) RunTask(id int) error { return nil }
|
|
||||||
|
|
||||||
func (a *Agent) InstallChoco() {}
|
|
||||||
|
|
||||||
func (a *Agent) InstallWithChoco(name string) (string, error) { return "", nil }
|
|
||||||
|
|
||||||
func (a *Agent) GetWinUpdates() {}
|
|
||||||
|
|
||||||
func (a *Agent) InstallUpdates(guids []string) {}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
return [2]string{"", ""}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CMD(exe string, args []string, timeout int, detached bool) (output [2]string, e error) {
|
|
||||||
return [2]string{"", ""}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Agent) GetServices() []trmm.WindowsService { return []trmm.WindowsService{} }
|
|
||||||
|
|
||||||
func (a *Agent) Start(_ service.Service) error { return nil }
|
|
||||||
|
|
||||||
func (a *Agent) Stop(_ service.Service) error { return nil }
|
|
||||||
|
|
||||||
func (a *Agent) InstallService() error { return nil }
|
|
||||||
970
agent/agent_unix.go
Normal file
970
agent/agent_unix.go
Normal file
@@ -0,0 +1,970 @@
|
|||||||
|
//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 (
|
||||||
|
"bufio"
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
rmm "github.com/amidaware/rmmagent/shared"
|
||||||
|
ps "github.com/elastic/go-sysinfo"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
"github.com/jaypipes/ghw"
|
||||||
|
"github.com/kardianos/service"
|
||||||
|
"github.com/shirou/gopsutil/v3/cpu"
|
||||||
|
"github.com/shirou/gopsutil/v3/disk"
|
||||||
|
psHost "github.com/shirou/gopsutil/v3/host"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
trmm "github.com/wh1te909/trmm-shared"
|
||||||
|
"golang.org/x/text/cases"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ShowStatus(version string) {
|
||||||
|
fmt.Println(version)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Agent) GetDisks() []trmm.Disk {
|
||||||
|
ret := make([]trmm.Disk, 0)
|
||||||
|
partitions, err := disk.Partitions(false)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Debugln(err)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range partitions {
|
||||||
|
if strings.Contains(p.Device, "dev/loop") || strings.Contains(p.Device, "devfs") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
usage, err := disk.Usage(p.Mountpoint)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Debugln(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
d := trmm.Disk{
|
||||||
|
Device: p.Device,
|
||||||
|
Fstype: p.Fstype,
|
||||||
|
Total: ByteCountSI(usage.Total),
|
||||||
|
Used: ByteCountSI(usage.Used),
|
||||||
|
Free: ByteCountSI(usage.Free),
|
||||||
|
Percent: int(usage.UsedPercent),
|
||||||
|
}
|
||||||
|
ret = append(ret, d)
|
||||||
|
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Agent) SystemRebootRequired() (bool, error) {
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Agent) LoggedOnUser() string {
|
||||||
|
var ret string
|
||||||
|
users, err := psHost.Users()
|
||||||
|
if err != nil {
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the first logged in user
|
||||||
|
for _, user := range users {
|
||||||
|
if user.User != "" {
|
||||||
|
ret = user.User
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Agent) osString() string {
|
||||||
|
h, err := psHost.Info()
|
||||||
|
if err != nil {
|
||||||
|
return "error getting host info"
|
||||||
|
}
|
||||||
|
plat := cases.Title(language.AmericanEnglish).String(h.Platform)
|
||||||
|
return fmt.Sprintf("%s %s %s %s", plat, h.PlatformVersion, h.KernelArch, h.KernelVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAgentConfig() *rmm.AgentConfig {
|
||||||
|
viper.SetConfigName("tacticalagent")
|
||||||
|
viper.SetConfigType("json")
|
||||||
|
viper.AddConfigPath("/etc/")
|
||||||
|
viper.AddConfigPath(".")
|
||||||
|
err := viper.ReadInConfig()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return &rmm.AgentConfig{}
|
||||||
|
}
|
||||||
|
|
||||||
|
agentpk := viper.GetString("agentpk")
|
||||||
|
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"),
|
||||||
|
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, runasuser bool, envVars []string, nushellEnableConfig bool, denoDefaultPermissions string) (stdout, stderr string, exitcode int, e error) {
|
||||||
|
code = removeWinNewLines(code)
|
||||||
|
content := []byte(code)
|
||||||
|
|
||||||
|
f, err := createNixTmpFile(shell)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("RunScript createNixTmpFile()", err)
|
||||||
|
return "", err.Error(), 85, err
|
||||||
|
}
|
||||||
|
defer os.Remove(f.Name())
|
||||||
|
|
||||||
|
if _, err := f.Write(content); err != nil {
|
||||||
|
a.Logger.Errorln(err)
|
||||||
|
return "", err.Error(), 85, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := f.Close(); err != nil {
|
||||||
|
a.Logger.Errorln(err)
|
||||||
|
return "", err.Error(), 85, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Chmod(f.Name(), 0770); err != nil {
|
||||||
|
a.Logger.Errorln(err)
|
||||||
|
return "", err.Error(), 85, err
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := a.NewCMDOpts()
|
||||||
|
opts.IsScript = true
|
||||||
|
switch shell {
|
||||||
|
case "nushell":
|
||||||
|
var nushellArgs []string
|
||||||
|
if nushellEnableConfig {
|
||||||
|
nushellArgs = []string{
|
||||||
|
"--config",
|
||||||
|
filepath.Join(nixAgentEtcDir, "nushell", "config.nu"),
|
||||||
|
"--env-config",
|
||||||
|
filepath.Join(nixAgentEtcDir, "nushell", "env.nu"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nushellArgs = []string{"--no-config-file"}
|
||||||
|
}
|
||||||
|
opts.Shell = a.NuBin
|
||||||
|
opts.Args = nushellArgs
|
||||||
|
opts.Args = append(opts.Args, f.Name())
|
||||||
|
opts.Args = append(opts.Args, args...)
|
||||||
|
if !trmm.FileExists(a.NuBin) {
|
||||||
|
a.Logger.Errorln("RunScript(): Executable does not exist. Install Nu and try again:", a.NuBin)
|
||||||
|
err := errors.New("File Not Found: " + a.NuBin)
|
||||||
|
return "", err.Error(), 85, err
|
||||||
|
}
|
||||||
|
|
||||||
|
case "deno":
|
||||||
|
opts.Shell = a.DenoBin
|
||||||
|
opts.Args = []string{
|
||||||
|
"run",
|
||||||
|
"--no-prompt",
|
||||||
|
}
|
||||||
|
if !trmm.FileExists(a.DenoBin) {
|
||||||
|
a.Logger.Errorln("RunScript(): Executable does not exist. Install deno and try again:", a.DenoBin)
|
||||||
|
err := errors.New("File Not Found: " + a.DenoBin)
|
||||||
|
return "", err.Error(), 85, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search the environment variables for DENO_PERMISSIONS and use that to set permissions for the script.
|
||||||
|
// https://docs.deno.com/runtime/manual/basics/permissions#permissions-list
|
||||||
|
// DENO_PERMISSIONS is not an official environment variable.
|
||||||
|
// https://docs.deno.com/runtime/manual/basics/env_variables
|
||||||
|
// DENO_DEFAULT_PERMISSIONS is used if not found in the environment variables.
|
||||||
|
found := false
|
||||||
|
for i, v := range envVars {
|
||||||
|
if strings.HasPrefix(v, "DENO_PERMISSIONS=") {
|
||||||
|
permissions := strings.Split(v, "=")[1]
|
||||||
|
opts.Args = append(opts.Args, strings.Split(permissions, " ")...)
|
||||||
|
// Remove the DENO_PERMISSIONS variable from the environment variables slice.
|
||||||
|
// It's possible more variables may exist with the same prefix.
|
||||||
|
envVars = append(envVars[:i], envVars[i+1:]...)
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found && denoDefaultPermissions != "" {
|
||||||
|
opts.Args = append(opts.Args, strings.Split(denoDefaultPermissions, " ")...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can't append a variadic slice after a string arg.
|
||||||
|
// https://pkg.go.dev/builtin#append
|
||||||
|
opts.Args = append(opts.Args, f.Name())
|
||||||
|
opts.Args = append(opts.Args, args...)
|
||||||
|
|
||||||
|
default:
|
||||||
|
opts.Shell = f.Name()
|
||||||
|
opts.Args = args
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.EnvVars = envVars
|
||||||
|
opts.Timeout = time.Duration(timeout)
|
||||||
|
a.Logger.Debugln("RunScript():", opts.Shell, opts.Args)
|
||||||
|
out := a.CmdV2(opts)
|
||||||
|
retError := ""
|
||||||
|
if out.Status.Error != nil {
|
||||||
|
retError += CleanString(out.Status.Error.Error())
|
||||||
|
retError += "\n"
|
||||||
|
}
|
||||||
|
if len(out.Stderr) > 0 {
|
||||||
|
retError += out.Stderr
|
||||||
|
}
|
||||||
|
return out.Stdout, retError, out.Status.Exit, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetDetached() *syscall.SysProcAttr {
|
||||||
|
return &syscall.SysProcAttr{Setpgid: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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() os.CreateTemp:", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer os.Remove(f.Name())
|
||||||
|
|
||||||
|
a.Logger.Infof("Agent updating from %s to %s", a.Version, version)
|
||||||
|
a.Logger.Debugln("Downloading agent update from", url)
|
||||||
|
|
||||||
|
rClient := resty.New()
|
||||||
|
rClient.SetCloseConnection(true)
|
||||||
|
rClient.SetTimeout(15 * time.Minute)
|
||||||
|
rClient.SetDebug(a.Debug)
|
||||||
|
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 err
|
||||||
|
}
|
||||||
|
if r.IsError() {
|
||||||
|
a.Logger.Errorln("AgentUpdate() status code:", r.StatusCode())
|
||||||
|
f.Close()
|
||||||
|
return errors.New("err")
|
||||||
|
}
|
||||||
|
|
||||||
|
f.Close()
|
||||||
|
os.Chmod(f.Name(), 0755)
|
||||||
|
err = os.Rename(f.Name(), self)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("AgentUpdate() os.Rename():", err)
|
||||||
|
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
|
||||||
|
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 := createNixTmpFile()
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("AgentUninstall createNixTmpFile():", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f.Write([]byte(code))
|
||||||
|
f.Close()
|
||||||
|
os.Chmod(f.Name(), 0770)
|
||||||
|
|
||||||
|
opts := a.NewCMDOpts()
|
||||||
|
opts.IsScript = true
|
||||||
|
opts.Shell = f.Name()
|
||||||
|
if runtime.GOOS == "linux" {
|
||||||
|
opts.Args = []string{"uninstall"}
|
||||||
|
}
|
||||||
|
opts.Detached = true
|
||||||
|
a.CmdV2(opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Agent) NixMeshNodeID() string {
|
||||||
|
var meshNodeID string
|
||||||
|
meshSuccess := false
|
||||||
|
a.Logger.Debugln("Getting mesh node id")
|
||||||
|
|
||||||
|
if !trmm.FileExists(a.MeshSystemEXE) {
|
||||||
|
a.Logger.Debugln(a.MeshSystemEXE, "does not exist. Skipping.")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := a.NewCMDOpts()
|
||||||
|
opts.IsExecutable = true
|
||||||
|
opts.Shell = a.MeshSystemEXE
|
||||||
|
opts.Command = "-nodeid"
|
||||||
|
|
||||||
|
for !meshSuccess {
|
||||||
|
out := a.CmdV2(opts)
|
||||||
|
meshNodeID = out.Stdout
|
||||||
|
a.Logger.Debugln("Stdout:", out.Stdout)
|
||||||
|
a.Logger.Debugln("Stderr:", out.Stderr)
|
||||||
|
if meshNodeID == "" {
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
continue
|
||||||
|
} else if strings.Contains(strings.ToLower(meshNodeID), "graphical version") || strings.Contains(strings.ToLower(meshNodeID), "zenity") {
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
meshSuccess = true
|
||||||
|
}
|
||||||
|
return meshNodeID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Agent) getMeshNodeID() (string, error) {
|
||||||
|
return a.NixMeshNodeID(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Agent) RecoverMesh() {
|
||||||
|
a.Logger.Infoln("Attempting mesh recovery")
|
||||||
|
opts := a.NewCMDOpts()
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Agent) GetWMIInfo() map[string]interface{} {
|
||||||
|
wmiInfo := make(map[string]interface{})
|
||||||
|
ips := make([]string, 0)
|
||||||
|
disks := make([]string, 0)
|
||||||
|
cpus := make([]string, 0)
|
||||||
|
gpus := make([]string, 0)
|
||||||
|
|
||||||
|
// local ips
|
||||||
|
host, err := ps.Host()
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("GetWMIInfo() ps.Host()", err)
|
||||||
|
} else {
|
||||||
|
for _, ip := range host.Info().IPs {
|
||||||
|
if strings.Contains(ip, "127.0.") || strings.Contains(ip, "::1/128") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ips = append(ips, ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wmiInfo["local_ips"] = ips
|
||||||
|
|
||||||
|
// 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 || 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))
|
||||||
|
ret = strings.TrimSpace(strings.ReplaceAll(ret, "unknown", ""))
|
||||||
|
disks = append(disks, ret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wmiInfo["disks"] = disks
|
||||||
|
|
||||||
|
// cpus
|
||||||
|
cpuInfo, err := cpu.Info()
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("cpu.Info()", err)
|
||||||
|
} else {
|
||||||
|
if len(cpuInfo) > 0 {
|
||||||
|
if cpuInfo[0].ModelName != "" {
|
||||||
|
cpus = append(cpus, cpuInfo[0].ModelName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wmiInfo["cpus"] = cpus
|
||||||
|
|
||||||
|
// make/model
|
||||||
|
wmiInfo["make_model"] = ""
|
||||||
|
chassis, err := ghw.Chassis(ghw.WithDisableWarnings())
|
||||||
|
if err != nil {
|
||||||
|
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.Debugln("ghw.GPU()", err)
|
||||||
|
} else {
|
||||||
|
for _, i := range gpu.GraphicsCards {
|
||||||
|
if i.DeviceInfo != nil {
|
||||||
|
ret := fmt.Sprintf("%s %s", i.DeviceInfo.Vendor.Name, i.DeviceInfo.Product.Name)
|
||||||
|
gpus = append(gpus, ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wmiInfo["gpus"] = gpus
|
||||||
|
|
||||||
|
// temp hack for ARM cpu/make/model if rasp pi
|
||||||
|
var makeModel string
|
||||||
|
if strings.Contains(runtime.GOARCH, "arm") {
|
||||||
|
file, _ := os.Open("/proc/cpuinfo")
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
if strings.Contains(strings.ToLower(scanner.Text()), "raspberry") {
|
||||||
|
model := strings.Split(scanner.Text(), ":")
|
||||||
|
if len(model) == 2 {
|
||||||
|
makeModel = strings.TrimSpace(model[1])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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}
|
||||||
|
}
|
||||||
|
if makeModel != "" && (wmiInfo["make_model"] == "" || wmiInfo["make_model"] == "unknown unknown") {
|
||||||
|
wmiInfo["make_model"] = makeModel
|
||||||
|
}
|
||||||
|
if len(gpus) == 1 && gpus[0] == "unknown unknown" {
|
||||||
|
wmiInfo["gpus"] = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return wmiInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstallNushell will download nushell from GitHub and install (copy) it to nixAgentBinDir
|
||||||
|
func (a *Agent) InstallNushell(force bool) {
|
||||||
|
sleepDelay := randRange(1, 10)
|
||||||
|
a.Logger.Debugf("InstallNushell() sleeping for %v seconds", sleepDelay)
|
||||||
|
time.Sleep(time.Duration(sleepDelay) * time.Second)
|
||||||
|
|
||||||
|
conf := a.GetAgentCheckInConfig(a.GetCheckInConfFromAPI())
|
||||||
|
if !conf.InstallNushell {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if trmm.FileExists(a.NuBin) {
|
||||||
|
if force {
|
||||||
|
a.Logger.Debugln(a.NuBin, "InstallNushell(): Forced install. Removing nu binary.")
|
||||||
|
err := os.Remove(a.NuBin)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallNushell(): Error removing nu binary:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !trmm.FileExists(nixAgentBinDir) {
|
||||||
|
err := os.MkdirAll(nixAgentBinDir, 0755)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallNushell(): Error creating nixAgentBinDir:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf.NushellEnableConfig {
|
||||||
|
// Create 0-byte config files for Nushell
|
||||||
|
nushellPath := filepath.Join(nixAgentEtcDir, "nushell")
|
||||||
|
nushellConfig := filepath.Join(nushellPath, "config.nu")
|
||||||
|
nushellEnv := filepath.Join(nushellPath, "env.nu")
|
||||||
|
if !trmm.FileExists(nushellPath) {
|
||||||
|
err := os.MkdirAll(nushellPath, 0755)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallNushell(): Error creating nixAgentEtcDir/nushell:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !trmm.FileExists(nushellConfig) {
|
||||||
|
_, err := os.Create(nushellConfig)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallNushell(): Error creating nushell config.nu:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = os.Chmod(nushellConfig, 0744)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallNushell(): Error changing permissions for nushell config.nu:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !trmm.FileExists(nushellEnv) {
|
||||||
|
_, err := os.Create(nushellEnv)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallNushell(): Error creating nushell env.nu:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = os.Chmod(nushellEnv, 0744)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallNushell(): Error changing permissions for nushell env.nu:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
assetName string
|
||||||
|
url string
|
||||||
|
targzDirName string
|
||||||
|
)
|
||||||
|
|
||||||
|
if conf.InstallNushellUrl != "" {
|
||||||
|
url = conf.InstallNushellUrl
|
||||||
|
url = strings.ReplaceAll(url, "{OS}", runtime.GOOS)
|
||||||
|
url = strings.ReplaceAll(url, "{ARCH}", runtime.GOARCH)
|
||||||
|
url = strings.ReplaceAll(url, "{VERSION}", conf.InstallNushellVersion)
|
||||||
|
} else {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "darwin":
|
||||||
|
switch runtime.GOARCH {
|
||||||
|
case "arm64":
|
||||||
|
// https://github.com/nushell/nushell/releases/download/0.87.0/nu-0.87.0-aarch64-darwin-full.tar.gz
|
||||||
|
assetName = fmt.Sprintf("nu-%s-aarch64-darwin-full.tar.gz", conf.InstallNushellVersion)
|
||||||
|
default:
|
||||||
|
a.Logger.Debugln("InstallNushell(): Unsupported architecture and OS:", runtime.GOARCH, runtime.GOOS)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "linux":
|
||||||
|
switch runtime.GOARCH {
|
||||||
|
case "amd64":
|
||||||
|
// https://github.com/nushell/nushell/releases/download/0.87.0/nu-0.87.0-x86_64-linux-musl-full.tar.gz
|
||||||
|
assetName = fmt.Sprintf("nu-%s-x86_64-linux-musl-full.tar.gz", conf.InstallNushellVersion)
|
||||||
|
case "arm64":
|
||||||
|
// https://github.com/nushell/nushell/releases/download/0.87.0/nu-0.87.0-aarch64-linux-gnu-full.tar.gz
|
||||||
|
assetName = fmt.Sprintf("nu-%s-aarch64-linux-gnu-full.tar.gz", conf.InstallNushellVersion)
|
||||||
|
default:
|
||||||
|
a.Logger.Debugln("InstallNushell(): Unsupported architecture and OS:", runtime.GOARCH, runtime.GOOS)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
a.Logger.Debugln("InstallNushell(): Unsupported OS:", runtime.GOOS)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
url = fmt.Sprintf("https://github.com/nushell/nushell/releases/download/%s/%s", conf.InstallNushellVersion, assetName)
|
||||||
|
}
|
||||||
|
a.Logger.Debugln("InstallNushell(): Nu download url:", url)
|
||||||
|
|
||||||
|
tmpDir, err := os.MkdirTemp("", "nutemp")
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallNushell(): Error creating nushell temp directory:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func(path string) {
|
||||||
|
err := os.RemoveAll(path)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallNushell(): Error removing nushell temp directory:", err)
|
||||||
|
}
|
||||||
|
}(tmpDir)
|
||||||
|
|
||||||
|
tmpAssetName := filepath.Join(tmpDir, assetName)
|
||||||
|
a.Logger.Debugln("InstallNushell(): tmpAssetName:", tmpAssetName)
|
||||||
|
|
||||||
|
rClient := resty.New()
|
||||||
|
rClient.SetTimeout(20 * time.Minute)
|
||||||
|
rClient.SetRetryCount(10)
|
||||||
|
rClient.SetRetryWaitTime(1 * time.Minute)
|
||||||
|
rClient.SetRetryMaxWaitTime(15 * time.Minute)
|
||||||
|
if len(a.Proxy) > 0 {
|
||||||
|
rClient.SetProxy(a.Proxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := rClient.R().SetOutput(tmpAssetName).Get(url)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallNushell(): Unable to download nu:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.IsError() {
|
||||||
|
a.Logger.Errorln("InstallNushell(): Unable to download nu. Status code:", r.StatusCode())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf.InstallNushellUrl != "" {
|
||||||
|
// InstallNushellUrl is not compressed.
|
||||||
|
err = copyFile(filepath.Join(tmpDir, tmpAssetName), a.NuBin)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallNushell(): Failed to copy nu file to install dir:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// GitHub asset is tar.gz compressed.
|
||||||
|
targzDirName, err = a.ExtractTarGz(tmpAssetName, tmpDir)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallNushell(): Failed to extract downloaded tar.gz file:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = copyFile(filepath.Join(tmpDir, targzDirName, "nu"), a.NuBin)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallNushell(): Failed to copy nu file to install dir:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Chmod(a.NuBin, 0755)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallNushell(): Failed to chmod nu binary:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstallDeno will download deno from GitHub and install (copy) it to nixAgentBinDir
|
||||||
|
func (a *Agent) InstallDeno(force bool) {
|
||||||
|
sleepDelay := randRange(1, 10)
|
||||||
|
a.Logger.Debugf("InstallDeno() sleeping for %v seconds", sleepDelay)
|
||||||
|
time.Sleep(time.Duration(sleepDelay) * time.Second)
|
||||||
|
|
||||||
|
conf := a.GetAgentCheckInConfig(a.GetCheckInConfFromAPI())
|
||||||
|
if !conf.InstallDeno {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if trmm.FileExists(a.DenoBin) {
|
||||||
|
if force {
|
||||||
|
a.Logger.Debugln(a.NuBin, "InstallDeno(): Forced install. Removing deno binary.")
|
||||||
|
err := os.Remove(a.DenoBin)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallDeno(): Error removing deno binary:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !trmm.FileExists(nixAgentBinDir) {
|
||||||
|
err := os.MkdirAll(nixAgentBinDir, 0755)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallDeno(): Error creating nixAgentBinDir:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
assetName string
|
||||||
|
url string
|
||||||
|
)
|
||||||
|
|
||||||
|
if conf.InstallDenoUrl != "" {
|
||||||
|
url = conf.InstallDenoUrl
|
||||||
|
url = strings.ReplaceAll(url, "{OS}", runtime.GOOS)
|
||||||
|
url = strings.ReplaceAll(url, "{ARCH}", runtime.GOARCH)
|
||||||
|
url = strings.ReplaceAll(url, "{VERSION}", conf.InstallDenoVersion)
|
||||||
|
} else {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "darwin":
|
||||||
|
switch runtime.GOARCH {
|
||||||
|
case "arm64":
|
||||||
|
// https://github.com/denoland/deno/releases/download/v1.38.2/deno-aarch64-apple-darwin.zip
|
||||||
|
assetName = "deno-aarch64-apple-darwin.zip"
|
||||||
|
case "amd64":
|
||||||
|
// https://github.com/denoland/deno/releases/download/v1.38.2/deno-x86_64-apple-darwin.zip
|
||||||
|
assetName = "deno-x86_64-apple-darwin.zip"
|
||||||
|
default:
|
||||||
|
a.Logger.Debugln("InstallDeno(): Unsupported architecture and OS:", runtime.GOARCH, runtime.GOOS)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "linux":
|
||||||
|
switch runtime.GOARCH {
|
||||||
|
case "amd64":
|
||||||
|
// https://github.com/denoland/deno/releases/download/v1.38.2/deno-x86_64-unknown-linux-gnu.zip
|
||||||
|
assetName = "deno-x86_64-unknown-linux-gnu.zip"
|
||||||
|
default:
|
||||||
|
a.Logger.Debugln("InstallDeno(): Unsupported architecture and OS:", runtime.GOARCH, runtime.GOOS)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
a.Logger.Debugln("InstallDeno(): Unsupported OS:", runtime.GOOS)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
url = fmt.Sprintf("https://github.com/denoland/deno/releases/download/%s/%s", conf.InstallDenoVersion, assetName)
|
||||||
|
}
|
||||||
|
a.Logger.Debugln("InstallDeno(): Deno download url:", url)
|
||||||
|
|
||||||
|
tmpDir, err := os.MkdirTemp("", "denotemp")
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallDeno(): Error creating deno temp directory:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func(path string) {
|
||||||
|
err := os.RemoveAll(path)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallDeno(): Error removing deno temp directory:", err)
|
||||||
|
}
|
||||||
|
}(tmpDir)
|
||||||
|
|
||||||
|
tmpAssetName := filepath.Join(tmpDir, assetName)
|
||||||
|
a.Logger.Debugln("InstallDeno(): tmpAssetName:", tmpAssetName)
|
||||||
|
|
||||||
|
rClient := resty.New()
|
||||||
|
rClient.SetTimeout(20 * time.Minute)
|
||||||
|
rClient.SetRetryCount(10)
|
||||||
|
rClient.SetRetryWaitTime(1 * time.Minute)
|
||||||
|
rClient.SetRetryMaxWaitTime(15 * time.Minute)
|
||||||
|
if len(a.Proxy) > 0 {
|
||||||
|
rClient.SetProxy(a.Proxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := rClient.R().SetOutput(tmpAssetName).Get(url)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallDeno(): Unable to download deno:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.IsError() {
|
||||||
|
a.Logger.Errorln("InstallDeno(): Unable to download deno. Status code:", r.StatusCode())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf.InstallDenoUrl != "" {
|
||||||
|
// InstallDenoUrl is not compressed.
|
||||||
|
err = copyFile(filepath.Join(tmpDir, tmpAssetName), a.DenoBin)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallDeno(): Failed to copy deno file to install dir:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// GitHub asset is zip compressed.
|
||||||
|
err = Unzip(tmpAssetName, tmpDir)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallDeno(): Failed to unzip downloaded zip file:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = copyFile(filepath.Join(tmpDir, "deno"), a.DenoBin)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallDeno(): Failed to copy deno file to install dir:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Chmod(a.DenoBin, 0755)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallDeno(): Failed to chmod deno binary:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAgentCheckInConfig will get the agent configuration from the server.
|
||||||
|
// The Windows agent stores the configuration in the registry. The UNIX agent does not store the config.
|
||||||
|
// @return AgentCheckInConfig
|
||||||
|
func (a *Agent) GetAgentCheckInConfig(ret AgentCheckInConfig) AgentCheckInConfig {
|
||||||
|
// TODO: Persist the config to disk.
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// windows only below TODO add into stub file
|
||||||
|
func (a *Agent) PlatVer() (string, error) { return "", nil }
|
||||||
|
|
||||||
|
func (a *Agent) SendSoftware() {}
|
||||||
|
|
||||||
|
func (a *Agent) UninstallCleanup() {}
|
||||||
|
|
||||||
|
func (a *Agent) RunMigrations() {}
|
||||||
|
|
||||||
|
func GetServiceStatus(name string) (string, error) { return "", nil }
|
||||||
|
|
||||||
|
func (a *Agent) GetPython(force bool) {}
|
||||||
|
|
||||||
|
type SchedTask struct{ Name string }
|
||||||
|
|
||||||
|
func (a *Agent) PatchMgmnt(enable bool) error { return nil }
|
||||||
|
|
||||||
|
func (a *Agent) CreateSchedTask(st SchedTask) (bool, error) { return false, nil }
|
||||||
|
|
||||||
|
func DeleteSchedTask(name string) error { return nil }
|
||||||
|
|
||||||
|
func ListSchedTasks() []string { return []string{} }
|
||||||
|
|
||||||
|
func (a *Agent) GetEventLog(logName string, searchLastDays int) []rmm.EventLogMsg {
|
||||||
|
return []rmm.EventLogMsg{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Agent) GetServiceDetail(name string) trmm.WindowsService { return trmm.WindowsService{} }
|
||||||
|
|
||||||
|
func (a *Agent) ControlService(name, action string) rmm.WinSvcResp {
|
||||||
|
return rmm.WinSvcResp{Success: false, ErrorMsg: "/na"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Agent) EditService(name, startupType string) rmm.WinSvcResp {
|
||||||
|
return rmm.WinSvcResp{Success: false, ErrorMsg: "/na"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Agent) GetInstalledSoftware() []trmm.WinSoftwareList { return []trmm.WinSoftwareList{} }
|
||||||
|
|
||||||
|
func (a *Agent) ChecksRunning() bool { return false }
|
||||||
|
|
||||||
|
func (a *Agent) InstallChoco() {}
|
||||||
|
|
||||||
|
func (a *Agent) InstallWithChoco(name string) (string, error) { return "", nil }
|
||||||
|
|
||||||
|
func (a *Agent) GetWinUpdates() {}
|
||||||
|
|
||||||
|
func (a *Agent) InstallUpdates(guids []string) {}
|
||||||
|
|
||||||
|
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, runasuser bool) (output [2]string, e error) {
|
||||||
|
return [2]string{"", ""}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CMD(exe string, args []string, timeout int, detached bool) (output [2]string, e error) {
|
||||||
|
return [2]string{"", ""}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Agent) GetServices() []trmm.WindowsService { return []trmm.WindowsService{} }
|
||||||
|
|
||||||
|
func (a *Agent) Start(_ service.Service) error { return nil }
|
||||||
|
|
||||||
|
func (a *Agent) Stop(_ service.Service) error { return nil }
|
||||||
|
|
||||||
|
func (a *Agent) InstallService() error { return nil }
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2022 AmidaWare LLC.
|
Copyright 2023 AmidaWare Inc.
|
||||||
|
|
||||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||||
You may only use the Licensed Software in accordance with the License.
|
You may only use the Licensed Software in accordance with the License.
|
||||||
@@ -14,9 +14,9 @@ package agent
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -30,6 +30,7 @@ import (
|
|||||||
|
|
||||||
rmm "github.com/amidaware/rmmagent/shared"
|
rmm "github.com/amidaware/rmmagent/shared"
|
||||||
ps "github.com/elastic/go-sysinfo"
|
ps "github.com/elastic/go-sysinfo"
|
||||||
|
"github.com/fourcorelabs/wintoken"
|
||||||
"github.com/go-ole/go-ole"
|
"github.com/go-ole/go-ole"
|
||||||
"github.com/go-ole/go-ole/oleutil"
|
"github.com/go-ole/go-ole/oleutil"
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
@@ -61,6 +62,14 @@ func NewAgentConfig() *rmm.AgentConfig {
|
|||||||
cert, _, _ := k.GetStringValue("Cert")
|
cert, _, _ := k.GetStringValue("Cert")
|
||||||
proxy, _, _ := k.GetStringValue("Proxy")
|
proxy, _, _ := k.GetStringValue("Proxy")
|
||||||
customMeshDir, _, _ := k.GetStringValue("MeshDir")
|
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{
|
return &rmm.AgentConfig{
|
||||||
BaseURL: baseurl,
|
BaseURL: baseurl,
|
||||||
@@ -72,16 +81,24 @@ func NewAgentConfig() *rmm.AgentConfig {
|
|||||||
Cert: cert,
|
Cert: cert,
|
||||||
Proxy: proxy,
|
Proxy: proxy,
|
||||||
CustomMeshDir: customMeshDir,
|
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, nushellEnableConfig bool, denoDefaultPermissions string) (stdout, stderr string, exitcode int, e error) {
|
||||||
|
|
||||||
content := []byte(code)
|
content := []byte(code)
|
||||||
|
|
||||||
dir := filepath.Join(os.TempDir(), "trmm")
|
err := createWinTempDir()
|
||||||
if !trmm.FileExists(dir) {
|
if err != nil {
|
||||||
a.CreateTRMMTempDir()
|
a.Logger.Errorln(err)
|
||||||
|
return "", err.Error(), 85, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultExitCode = 1
|
const defaultExitCode = 1
|
||||||
@@ -101,9 +118,19 @@ func (a *Agent) RunScript(code string, shell string, args []string, timeout int)
|
|||||||
ext = "*.py"
|
ext = "*.py"
|
||||||
case "cmd":
|
case "cmd":
|
||||||
ext = "*.bat"
|
ext = "*.bat"
|
||||||
|
case "nushell":
|
||||||
|
ext = "*.nu"
|
||||||
|
case "deno":
|
||||||
|
ext = "*.ts"
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpfn, err := ioutil.TempFile(dir, ext)
|
tmpDir := a.WinTmpDir
|
||||||
|
|
||||||
|
if runasuser {
|
||||||
|
tmpDir = a.WinRunAsUserTmpDir
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpfn, err := os.CreateTemp(tmpDir, ext)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.Logger.Errorln(err)
|
a.Logger.Errorln(err)
|
||||||
return "", err.Error(), 85, err
|
return "", err.Error(), 85, err
|
||||||
@@ -121,13 +148,63 @@ func (a *Agent) RunScript(code string, shell string, args []string, timeout int)
|
|||||||
|
|
||||||
switch shell {
|
switch shell {
|
||||||
case "powershell":
|
case "powershell":
|
||||||
exe = "Powershell"
|
exe = getPowershellExe()
|
||||||
cmdArgs = []string{"-NonInteractive", "-NoProfile", "-ExecutionPolicy", "Bypass", tmpfn.Name()}
|
cmdArgs = []string{"-NonInteractive", "-NoProfile", "-ExecutionPolicy", "Bypass", tmpfn.Name()}
|
||||||
case "python":
|
case "python":
|
||||||
exe = a.PyBin
|
exe = a.PyBin
|
||||||
cmdArgs = []string{tmpfn.Name()}
|
cmdArgs = []string{tmpfn.Name()}
|
||||||
case "cmd":
|
case "cmd":
|
||||||
exe = tmpfn.Name()
|
exe = tmpfn.Name()
|
||||||
|
case "nushell":
|
||||||
|
exe = a.NuBin
|
||||||
|
var nushellArgs []string
|
||||||
|
if nushellEnableConfig {
|
||||||
|
nushellArgs = []string{
|
||||||
|
"--config",
|
||||||
|
filepath.Join(a.ProgramDir, "etc", "nushell", "config.nu"),
|
||||||
|
"--env-config",
|
||||||
|
filepath.Join(a.ProgramDir, "etc", "nushell", "env.nu"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nushellArgs = []string{"--no-config-file"}
|
||||||
|
}
|
||||||
|
cmdArgs = append(nushellArgs, tmpfn.Name())
|
||||||
|
if !trmm.FileExists(a.NuBin) {
|
||||||
|
a.Logger.Errorln("RunScript(): Executable does not exist. Install Nu and try again:", a.NuBin)
|
||||||
|
err := errors.New("File Not Found: " + a.NuBin)
|
||||||
|
return "", err.Error(), 85, err
|
||||||
|
}
|
||||||
|
case "deno":
|
||||||
|
exe = a.DenoBin
|
||||||
|
cmdArgs = []string{"run", "--no-prompt"}
|
||||||
|
if !trmm.FileExists(a.DenoBin) {
|
||||||
|
a.Logger.Errorln("RunScript(): Executable does not exist. Install deno and try again:", a.DenoBin)
|
||||||
|
err := errors.New("File Not Found: " + a.DenoBin)
|
||||||
|
return "", err.Error(), 85, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search the environment variables for DENO_PERMISSIONS and use that to set permissions for the script.
|
||||||
|
// https://docs.deno.com/runtime/manual/basics/permissions#permissions-list
|
||||||
|
// DENO_PERMISSIONS is not an official environment variable.
|
||||||
|
// https://docs.deno.com/runtime/manual/basics/env_variables
|
||||||
|
// DENO_DEFAULT_PERMISSIONS is used if not found in the environment variables.
|
||||||
|
found := false
|
||||||
|
for i, v := range envVars {
|
||||||
|
if strings.HasPrefix(v, "DENO_PERMISSIONS=") {
|
||||||
|
permissions := strings.Split(v, "=")[1]
|
||||||
|
cmdArgs = append(cmdArgs, strings.Split(permissions, " ")...)
|
||||||
|
// Remove the DENO_PERMISSIONS variable from the environment variables slice.
|
||||||
|
// It's possible more variables may exist with the same prefix.
|
||||||
|
envVars = append(envVars[:i], envVars[i+1:]...)
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found && denoDefaultPermissions != "" {
|
||||||
|
cmdArgs = append(cmdArgs, strings.Split(denoDefaultPermissions, " ")...)
|
||||||
|
}
|
||||||
|
cmdArgs = append(cmdArgs, tmpfn.Name())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
@@ -137,8 +214,35 @@ func (a *Agent) RunScript(code string, shell string, args []string, timeout int)
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
|
||||||
defer cancel()
|
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...)
|
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.Stdout = &outb
|
||||||
cmd.Stderr = &errb
|
cmd.Stderr = &errb
|
||||||
|
|
||||||
@@ -224,7 +328,7 @@ func CMD(exe string, args []string, timeout int, detached bool) (output [2]strin
|
|||||||
return [2]string{CleanString(outb.String()), CleanString(errb.String())}, nil
|
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 (
|
var (
|
||||||
outb bytes.Buffer
|
outb bytes.Buffer
|
||||||
errb bytes.Buffer
|
errb bytes.Buffer
|
||||||
@@ -235,33 +339,45 @@ func CMDShell(shell string, cmdArgs []string, command string, timeout int, detac
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
sysProcAttr := &windows.SysProcAttr{}
|
||||||
|
cmdExe := getCMDExe()
|
||||||
|
powershell := getPowershellExe()
|
||||||
|
|
||||||
if len(cmdArgs) > 0 && command == "" {
|
if len(cmdArgs) > 0 && command == "" {
|
||||||
switch shell {
|
switch shell {
|
||||||
case "cmd":
|
case "cmd":
|
||||||
cmdArgs = append([]string{"/C"}, cmdArgs...)
|
cmdArgs = append([]string{"/C"}, cmdArgs...)
|
||||||
cmd = exec.Command("cmd.exe", cmdArgs...)
|
cmd = exec.Command(cmdExe, cmdArgs...)
|
||||||
case "powershell":
|
case "powershell":
|
||||||
cmdArgs = append([]string{"-NonInteractive", "-NoProfile"}, cmdArgs...)
|
cmdArgs = append([]string{"-NonInteractive", "-NoProfile"}, cmdArgs...)
|
||||||
cmd = exec.Command("powershell.exe", cmdArgs...)
|
cmd = exec.Command(powershell, cmdArgs...)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
switch shell {
|
switch shell {
|
||||||
case "cmd":
|
case "cmd":
|
||||||
cmd = exec.Command("cmd.exe")
|
cmd = exec.Command(cmdExe)
|
||||||
cmd.SysProcAttr = &windows.SysProcAttr{
|
sysProcAttr.CmdLine = fmt.Sprintf("%s /C %s", cmdExe, command)
|
||||||
CmdLine: fmt.Sprintf("cmd.exe /C %s", command),
|
|
||||||
}
|
|
||||||
case "powershell":
|
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
|
// https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags
|
||||||
if detached {
|
if detached {
|
||||||
cmd.SysProcAttr = &windows.SysProcAttr{
|
sysProcAttr.CreationFlags = windows.DETACHED_PROCESS | windows.CREATE_NEW_PROCESS_GROUP
|
||||||
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.Stdout = &outb
|
||||||
cmd.Stderr = &errb
|
cmd.Stderr = &errb
|
||||||
cmd.Start()
|
cmd.Start()
|
||||||
@@ -443,7 +559,7 @@ func (a *Agent) PlatVer() (string, error) {
|
|||||||
func EnablePing() {
|
func EnablePing() {
|
||||||
args := make([]string, 0)
|
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`
|
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 {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
@@ -464,7 +580,7 @@ func EnableRDP() {
|
|||||||
|
|
||||||
args := make([]string, 0)
|
args := make([]string, 0)
|
||||||
cmd := `netsh advfirewall firewall set rule group="remote desktop" new enable=Yes`
|
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 {
|
if cerr != nil {
|
||||||
fmt.Println(cerr)
|
fmt.Println(cerr)
|
||||||
}
|
}
|
||||||
@@ -491,15 +607,15 @@ func DisableSleepHibernate() {
|
|||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(c string) {
|
go func(c string) {
|
||||||
defer wg.Done()
|
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 /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)
|
_, _ = 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)
|
_, _ = 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)
|
_, _ = 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)
|
_, _ = CMDShell("cmd", args, fmt.Sprintf("powercfg /x -monitor-timeout-%s 0", c), 5, false, false)
|
||||||
}(i)
|
}(i)
|
||||||
}
|
}
|
||||||
wg.Wait()
|
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.
|
// NewCOMObject creates a new COM object for the specifed ProgramID.
|
||||||
@@ -551,15 +667,18 @@ func (a *Agent) UninstallCleanup() {
|
|||||||
a.PatchMgmnt(false)
|
a.PatchMgmnt(false)
|
||||||
a.CleanupAgentUpdates()
|
a.CleanupAgentUpdates()
|
||||||
CleanupSchedTasks()
|
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)
|
time.Sleep(time.Duration(randRange(1, 15)) * time.Second)
|
||||||
a.KillHungUpdates()
|
a.KillHungUpdates()
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
a.CleanupAgentUpdates()
|
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.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 := resty.New()
|
||||||
rClient.SetCloseConnection(true)
|
rClient.SetCloseConnection(true)
|
||||||
@@ -568,26 +687,24 @@ func (a *Agent) AgentUpdate(url, inno, version string) {
|
|||||||
if len(a.Proxy) > 0 {
|
if len(a.Proxy) > 0 {
|
||||||
rClient.SetProxy(a.Proxy)
|
rClient.SetProxy(a.Proxy)
|
||||||
}
|
}
|
||||||
|
if a.Insecure {
|
||||||
|
insecureConf := &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
}
|
||||||
|
rClient.SetTLSClientConfig(insecureConf)
|
||||||
|
}
|
||||||
r, err := rClient.R().SetOutput(updater).Get(url)
|
r, err := rClient.R().SetOutput(updater).Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.Logger.Errorln(err)
|
a.Logger.Errorln(err)
|
||||||
CMD("net", []string{"start", winSvcName}, 10, false)
|
return err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if r.IsError() {
|
if r.IsError() {
|
||||||
a.Logger.Errorln("Download failed with status code", r.StatusCode())
|
ret := fmt.Sprintf("Download failed with status code %d", r.StatusCode())
|
||||||
CMD("net", []string{"start", winSvcName}, 10, false)
|
a.Logger.Errorln(ret)
|
||||||
return
|
return errors.New(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
dir, err := ioutil.TempDir("", "tacticalrmm")
|
innoLogFile := filepath.Join(a.WinTmpDir, fmt.Sprintf("tacticalagent_update_v%s.txt", version))
|
||||||
if err != nil {
|
|
||||||
a.Logger.Errorln("Agentupdate create tempdir:", err)
|
|
||||||
CMD("net", []string{"start", winSvcName}, 10, false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
innoLogFile := filepath.Join(dir, "tacticalrmm.txt")
|
|
||||||
|
|
||||||
args := []string{"/C", updater, "/VERYSILENT", fmt.Sprintf("/LOG=%s", innoLogFile)}
|
args := []string{"/C", updater, "/VERYSILENT", fmt.Sprintf("/LOG=%s", innoLogFile)}
|
||||||
cmd := exec.Command("cmd.exe", args...)
|
cmd := exec.Command("cmd.exe", args...)
|
||||||
@@ -596,6 +713,7 @@ func (a *Agent) AgentUpdate(url, inno, version string) {
|
|||||||
}
|
}
|
||||||
cmd.Start()
|
cmd.Start()
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Agent) osString() string {
|
func (a *Agent) osString() string {
|
||||||
@@ -632,21 +750,17 @@ func (a *Agent) AgentUninstall(code string) {
|
|||||||
cmd.Start()
|
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
|
// RunMigrations cleans up unused stuff from older agents
|
||||||
func (a *Agent) RunMigrations() {
|
func (a *Agent) RunMigrations() {
|
||||||
|
|
||||||
|
// changed pybin dirs in v2.8.0
|
||||||
|
for _, i := range []string{"py38-x64", "py38-x32"} {
|
||||||
|
py := filepath.Join(a.ProgramDir, i)
|
||||||
|
if trmm.FileExists(py) {
|
||||||
|
os.RemoveAll(py)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, i := range []string{"nssm.exe", "nssm-x86.exe"} {
|
for _, i := range []string{"nssm.exe", "nssm-x86.exe"} {
|
||||||
nssm := filepath.Join(a.ProgramDir, i)
|
nssm := filepath.Join(a.ProgramDir, i)
|
||||||
if trmm.FileExists(nssm) {
|
if trmm.FileExists(nssm) {
|
||||||
@@ -740,26 +854,23 @@ func (a *Agent) GetPython(force bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var archZip string
|
|
||||||
var folder string
|
|
||||||
switch runtime.GOARCH {
|
|
||||||
case "amd64":
|
|
||||||
archZip = "py38-x64.zip"
|
|
||||||
folder = "py38-x64"
|
|
||||||
case "386":
|
|
||||||
archZip = "py38-x32.zip"
|
|
||||||
folder = "py38-x32"
|
|
||||||
}
|
|
||||||
pyFolder := filepath.Join(a.ProgramDir, folder)
|
|
||||||
pyZip := filepath.Join(a.ProgramDir, archZip)
|
|
||||||
a.Logger.Debugln(pyZip)
|
|
||||||
a.Logger.Debugln(a.PyBin)
|
|
||||||
defer os.Remove(pyZip)
|
|
||||||
|
|
||||||
if force {
|
if force {
|
||||||
os.RemoveAll(pyFolder)
|
os.RemoveAll(a.PyBaseDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sleepDelay := randRange(1, 10)
|
||||||
|
a.Logger.Debugf("GetPython() sleeping for %v seconds\n", sleepDelay)
|
||||||
|
time.Sleep(time.Duration(sleepDelay) * time.Second)
|
||||||
|
|
||||||
|
if !trmm.FileExists(a.PyBaseDir) {
|
||||||
|
os.MkdirAll(a.PyBaseDir, 0775)
|
||||||
|
}
|
||||||
|
|
||||||
|
archZip := a.PyDir + ".zip"
|
||||||
|
|
||||||
|
pyZip := filepath.Join(a.PyBaseDir, archZip)
|
||||||
|
defer os.Remove(pyZip)
|
||||||
|
|
||||||
rClient := resty.New()
|
rClient := resty.New()
|
||||||
rClient.SetTimeout(20 * time.Minute)
|
rClient.SetTimeout(20 * time.Minute)
|
||||||
rClient.SetRetryCount(10)
|
rClient.SetRetryCount(10)
|
||||||
@@ -769,7 +880,7 @@ func (a *Agent) GetPython(force bool) {
|
|||||||
rClient.SetProxy(a.Proxy)
|
rClient.SetProxy(a.Proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
url := fmt.Sprintf("https://github.com/amidaware/rmmagent/releases/download/v2.0.0/%s", archZip)
|
url := fmt.Sprintf("https://github.com/amidaware/rmmagent/releases/download/v2.8.0/%s", archZip)
|
||||||
a.Logger.Debugln(url)
|
a.Logger.Debugln(url)
|
||||||
r, err := rClient.R().SetOutput(pyZip).Get(url)
|
r, err := rClient.R().SetOutput(pyZip).Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -781,12 +892,306 @@ func (a *Agent) GetPython(force bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = Unzip(pyZip, a.ProgramDir)
|
err = Unzip(pyZip, a.PyBaseDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.Logger.Errorln(err)
|
a.Logger.Errorln(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InstallNushell will download nushell from GitHub and install (copy) it to ProgramDir\bin, where ProgramDir is
|
||||||
|
// initialized to C:\Program Files\TacticalAgent
|
||||||
|
func (a *Agent) InstallNushell(force bool) {
|
||||||
|
sleepDelay := randRange(1, 10)
|
||||||
|
a.Logger.Debugf("InstallNushell() sleeping for %v seconds", sleepDelay)
|
||||||
|
time.Sleep(time.Duration(sleepDelay) * time.Second)
|
||||||
|
|
||||||
|
conf := a.GetAgentCheckInConfig(a.GetCheckInConfFromAPI())
|
||||||
|
if !conf.InstallNushell {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if trmm.FileExists(a.NuBin) {
|
||||||
|
if force {
|
||||||
|
a.Logger.Debugln(a.NuBin, "InstallNushell(): Forced install. Removing nu.exe binary.")
|
||||||
|
err := os.Remove(a.NuBin)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallNushell(): Error removing nu.exe binary:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
programBinDir := filepath.Join(a.ProgramDir, "bin")
|
||||||
|
if !trmm.FileExists(programBinDir) {
|
||||||
|
err := os.MkdirAll(programBinDir, 0755)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallNushell(): Error creating Program Files bin folder:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf.NushellEnableConfig {
|
||||||
|
// Create 0-byte config files for Nushell
|
||||||
|
nushellPath := filepath.Join(a.ProgramDir, "etc", "nushell")
|
||||||
|
nushellConfig := filepath.Join(nushellPath, "config.nu")
|
||||||
|
nushellEnv := filepath.Join(nushellPath, "env.nu")
|
||||||
|
if !trmm.FileExists(nushellPath) {
|
||||||
|
err := os.MkdirAll(nushellPath, 0755)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallNushell(): Error creating Program Files/nushell:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !trmm.FileExists(nushellConfig) {
|
||||||
|
_, err := os.Create(nushellConfig)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallNushell(): Error creating nushell config.nu:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = os.Chmod(nushellConfig, 0744)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallNushell(): Error changing permissions for nushell config.nu:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !trmm.FileExists(nushellEnv) {
|
||||||
|
_, err := os.Create(nushellEnv)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallNushell(): Error creating nushell env.nu:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = os.Chmod(nushellEnv, 0744)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallNushell(): Error changing permissions for nushell env.nu:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
assetName string
|
||||||
|
url string
|
||||||
|
)
|
||||||
|
|
||||||
|
if conf.InstallNushellUrl != "" {
|
||||||
|
url = conf.InstallNushellUrl
|
||||||
|
url = strings.ReplaceAll(url, "{OS}", runtime.GOOS)
|
||||||
|
url = strings.ReplaceAll(url, "{ARCH}", runtime.GOARCH)
|
||||||
|
url = strings.ReplaceAll(url, "{VERSION}", conf.InstallNushellVersion)
|
||||||
|
} else {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "windows":
|
||||||
|
switch runtime.GOARCH {
|
||||||
|
case "amd64":
|
||||||
|
// https://github.com/nushell/nushell/releases/download/0.87.0/nu-0.87.0-x86_64-windows-msvc-full.zip
|
||||||
|
assetName = fmt.Sprintf("nu-%s-x86_64-windows-msvc-full.zip", conf.InstallNushellVersion)
|
||||||
|
case "arm64":
|
||||||
|
// https://github.com/nushell/nushell/releases/download/0.87.0/nu-0.87.0-aarch64-windows-msvc-full.zip
|
||||||
|
assetName = fmt.Sprintf("nu-%s-aarch64-windows-msvc-full.zip", conf.InstallNushellVersion)
|
||||||
|
default:
|
||||||
|
a.Logger.Debugln("InstallNushell(): Unsupported architecture and OS:", runtime.GOARCH, runtime.GOOS)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
a.Logger.Debugln("InstallNushell(): Unsupported OS:", runtime.GOOS)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
url = fmt.Sprintf("https://github.com/nushell/nushell/releases/download/%s/%s", conf.InstallNushellVersion, assetName)
|
||||||
|
}
|
||||||
|
a.Logger.Debugln("InstallNushell(): Nu download url:", url)
|
||||||
|
|
||||||
|
err := createWinTempDir()
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallNushell(): createWinTempDir:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpDir, err := os.MkdirTemp(a.WinTmpDir, "nutemp")
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallNushell(): Error creating nushell temp directory:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func(path string) {
|
||||||
|
err := os.RemoveAll(path)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallNushell(): Error removing nushell temp directory:", err)
|
||||||
|
}
|
||||||
|
}(tmpDir)
|
||||||
|
|
||||||
|
tmpAssetName := filepath.Join(tmpDir, assetName)
|
||||||
|
a.Logger.Debugln("InstallNushell(): tmpAssetName:", tmpAssetName)
|
||||||
|
|
||||||
|
rClient := resty.New()
|
||||||
|
rClient.SetTimeout(20 * time.Minute)
|
||||||
|
rClient.SetRetryCount(10)
|
||||||
|
rClient.SetRetryWaitTime(1 * time.Minute)
|
||||||
|
rClient.SetRetryMaxWaitTime(15 * time.Minute)
|
||||||
|
if len(a.Proxy) > 0 {
|
||||||
|
rClient.SetProxy(a.Proxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := rClient.R().SetOutput(tmpAssetName).Get(url)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallNushell(): Unable to download nu:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.IsError() {
|
||||||
|
a.Logger.Errorln("InstallNushell(): Unable to download nu. Status code:", r.StatusCode())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf.InstallNushellUrl != "" {
|
||||||
|
// InstallNushellUrl is not compressed.
|
||||||
|
err = copyFile(filepath.Join(tmpDir, tmpAssetName), a.NuBin)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallNushell(): Failed to copy nu file to install dir:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = Unzip(tmpAssetName, tmpDir)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallNushell(): Failed to unzip downloaded zip file:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = copyFile(filepath.Join(tmpDir, "nu.exe"), a.NuBin)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallNushell(): Failed to copy nu.exe file to install dir:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstallDeno will download deno from GitHub and install (copy) it to ProgramDir\bin, where ProgramDir is
|
||||||
|
// initialized to C:\Program Files\TacticalAgent
|
||||||
|
func (a *Agent) InstallDeno(force bool) {
|
||||||
|
sleepDelay := randRange(1, 10)
|
||||||
|
a.Logger.Debugf("InstallDeno() sleeping for %v seconds", sleepDelay)
|
||||||
|
time.Sleep(time.Duration(sleepDelay) * time.Second)
|
||||||
|
|
||||||
|
conf := a.GetAgentCheckInConfig(a.GetCheckInConfFromAPI())
|
||||||
|
if !conf.InstallDeno {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if trmm.FileExists(a.DenoBin) {
|
||||||
|
if force {
|
||||||
|
err := os.Remove(a.DenoBin)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallDeno(): Error removing deno binary:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
programBinDir := filepath.Join(a.ProgramDir, "bin")
|
||||||
|
if !trmm.FileExists(programBinDir) {
|
||||||
|
err := os.MkdirAll(programBinDir, 0755)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallDeno(): Error creating Program Files bin folder:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
assetName string
|
||||||
|
url string
|
||||||
|
)
|
||||||
|
|
||||||
|
if conf.InstallDenoUrl != "" {
|
||||||
|
url = conf.InstallDenoUrl
|
||||||
|
url = strings.ReplaceAll(url, "{OS}", runtime.GOOS)
|
||||||
|
url = strings.ReplaceAll(url, "{ARCH}", runtime.GOARCH)
|
||||||
|
url = strings.ReplaceAll(url, "{VERSION}", conf.InstallDenoVersion)
|
||||||
|
} else {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "windows":
|
||||||
|
switch runtime.GOARCH {
|
||||||
|
case "amd64":
|
||||||
|
// https://github.com/denoland/deno/releases/download/v1.38.2/deno-x86_64-pc-windows-msvc.zip
|
||||||
|
assetName = "deno-x86_64-pc-windows-msvc.zip"
|
||||||
|
default:
|
||||||
|
a.Logger.Debugln("InstallDeno(): Unsupported architecture and OS:", runtime.GOARCH, runtime.GOOS)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
a.Logger.Debugln("InstallDeno(): Unsupported OS:", runtime.GOOS)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
url = fmt.Sprintf("https://github.com/denoland/deno/releases/download/%s/%s", conf.InstallDenoVersion, assetName)
|
||||||
|
}
|
||||||
|
a.Logger.Debugln("InstallDeno(): Deno download url:", url)
|
||||||
|
|
||||||
|
err := createWinTempDir()
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallDeno(): createWinTempDir:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpDir, err := os.MkdirTemp(a.WinTmpDir, "denotemp")
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallDeno(): Error creating deno temp directory:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func(path string) {
|
||||||
|
err := os.RemoveAll(path)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallDeno(): Error removing deno temp directory:", err)
|
||||||
|
}
|
||||||
|
}(tmpDir)
|
||||||
|
|
||||||
|
tmpAssetName := filepath.Join(tmpDir, assetName)
|
||||||
|
a.Logger.Debugln("InstallDeno(): tmpAssetName:", tmpAssetName)
|
||||||
|
|
||||||
|
rClient := resty.New()
|
||||||
|
rClient.SetTimeout(20 * time.Minute)
|
||||||
|
rClient.SetRetryCount(10)
|
||||||
|
rClient.SetRetryWaitTime(1 * time.Minute)
|
||||||
|
rClient.SetRetryMaxWaitTime(15 * time.Minute)
|
||||||
|
if len(a.Proxy) > 0 {
|
||||||
|
rClient.SetProxy(a.Proxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := rClient.R().SetOutput(tmpAssetName).Get(url)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallDeno(): Unable to download deno:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.IsError() {
|
||||||
|
a.Logger.Errorln("InstallDeno(): Unable to download deno. Status code:", r.StatusCode())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf.InstallDenoUrl != "" {
|
||||||
|
// InstallDenoUrl is not compressed.
|
||||||
|
err = copyFile(filepath.Join(tmpDir, tmpAssetName), a.DenoBin)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallDeno(): Failed to copy deno file to install dir:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// GitHub asset is zip compressed.
|
||||||
|
err = Unzip(tmpAssetName, tmpDir)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallDeno(): Failed to unzip downloaded zip file:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = copyFile(filepath.Join(tmpDir, "deno.exe"), a.DenoBin)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("InstallDeno(): Failed to copy deno.exe file to install dir:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func (a *Agent) RecoverMesh() {
|
func (a *Agent) RecoverMesh() {
|
||||||
a.Logger.Infoln("Attempting mesh recovery")
|
a.Logger.Infoln("Attempting mesh recovery")
|
||||||
defer CMD("net", []string{"start", a.MeshSVC}, 60, false)
|
defer CMD("net", []string{"start", a.MeshSVC}, 60, false)
|
||||||
@@ -847,6 +1252,43 @@ func (a *Agent) InstallService() error {
|
|||||||
return service.Control(s, "install")
|
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
|
// TODO add to stub
|
||||||
func (a *Agent) NixMeshNodeID() string {
|
func (a *Agent) NixMeshNodeID() string {
|
||||||
return "not implemented"
|
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”).
|
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||||
You may only use the Licensed Software in accordance with the License.
|
You may only use the Licensed Software in accordance with the License.
|
||||||
@@ -12,7 +12,6 @@ https://license.tacticalrmm.com
|
|||||||
package agent
|
package agent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -78,8 +77,7 @@ func (a *Agent) NatsMessage(nc *nats.Conn, mode string) {
|
|||||||
|
|
||||||
func (a *Agent) DoNatsCheckIn() {
|
func (a *Agent) DoNatsCheckIn() {
|
||||||
opts := a.setupNatsOptions()
|
opts := a.setupNatsOptions()
|
||||||
server := fmt.Sprintf("tls://%s:4222", a.ApiURL)
|
nc, err := nats.Connect(a.NatsServer, opts...)
|
||||||
nc, err := nats.Connect(server, opts...)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.Logger.Errorln(err)
|
a.Logger.Errorln(err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2022 AmidaWare LLC.
|
Copyright 2023 AmidaWare Inc.
|
||||||
|
|
||||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||||
You may only use the Licensed Software in accordance with the License.
|
You may only use the Licensed Software in accordance with the License.
|
||||||
@@ -159,6 +159,7 @@ func (a *Agent) RunChecks(force bool) error {
|
|||||||
|
|
||||||
type ScriptCheckResult struct {
|
type ScriptCheckResult struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
|
AgentID string `json:"agent_id"`
|
||||||
Stdout string `json:"stdout"`
|
Stdout string `json:"stdout"`
|
||||||
Stderr string `json:"stderr"`
|
Stderr string `json:"stderr"`
|
||||||
Retcode int `json:"retcode"`
|
Retcode int `json:"retcode"`
|
||||||
@@ -168,10 +169,11 @@ type ScriptCheckResult struct {
|
|||||||
// ScriptCheck runs either bat, powershell or python script
|
// ScriptCheck runs either bat, powershell or python script
|
||||||
func (a *Agent) ScriptCheck(data rmm.Check, r *resty.Client) {
|
func (a *Agent) ScriptCheck(data rmm.Check, r *resty.Client) {
|
||||||
start := time.Now()
|
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, data.NushellEnableConfig, data.DenoDefaultPermissions)
|
||||||
|
|
||||||
payload := ScriptCheckResult{
|
payload := ScriptCheckResult{
|
||||||
ID: data.CheckPK,
|
ID: data.CheckPK,
|
||||||
|
AgentID: a.AgentID,
|
||||||
Stdout: stdout,
|
Stdout: stdout,
|
||||||
Stderr: stderr,
|
Stderr: stderr,
|
||||||
Retcode: retcode,
|
Retcode: retcode,
|
||||||
@@ -193,6 +195,7 @@ func (a *Agent) SendDiskCheckResult(payload DiskCheckResult, r *resty.Client) {
|
|||||||
|
|
||||||
type DiskCheckResult struct {
|
type DiskCheckResult struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
|
AgentID string `json:"agent_id"`
|
||||||
MoreInfo string `json:"more_info"`
|
MoreInfo string `json:"more_info"`
|
||||||
PercentUsed float64 `json:"percent_used"`
|
PercentUsed float64 `json:"percent_used"`
|
||||||
Exists bool `json:"exists"`
|
Exists bool `json:"exists"`
|
||||||
@@ -201,6 +204,7 @@ type DiskCheckResult struct {
|
|||||||
// DiskCheck checks disk usage
|
// DiskCheck checks disk usage
|
||||||
func (a *Agent) DiskCheck(data rmm.Check) (payload DiskCheckResult) {
|
func (a *Agent) DiskCheck(data rmm.Check) (payload DiskCheckResult) {
|
||||||
payload.ID = data.CheckPK
|
payload.ID = data.CheckPK
|
||||||
|
payload.AgentID = a.AgentID
|
||||||
|
|
||||||
usage, err := disk.Usage(data.Disk)
|
usage, err := disk.Usage(data.Disk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -218,12 +222,13 @@ func (a *Agent) DiskCheck(data rmm.Check) (payload DiskCheckResult) {
|
|||||||
|
|
||||||
type CPUMemResult struct {
|
type CPUMemResult struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
|
AgentID string `json:"agent_id"`
|
||||||
Percent int `json:"percent"`
|
Percent int `json:"percent"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CPULoadCheck checks avg cpu load
|
// CPULoadCheck checks avg cpu load
|
||||||
func (a *Agent) CPULoadCheck(data rmm.Check, r *resty.Client) {
|
func (a *Agent) CPULoadCheck(data rmm.Check, r *resty.Client) {
|
||||||
payload := CPUMemResult{ID: data.CheckPK, Percent: a.GetCPULoadAvg()}
|
payload := CPUMemResult{ID: data.CheckPK, AgentID: a.AgentID, Percent: a.GetCPULoadAvg()}
|
||||||
_, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/")
|
_, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.Logger.Debugln(err)
|
a.Logger.Debugln(err)
|
||||||
@@ -236,7 +241,7 @@ func (a *Agent) MemCheck(data rmm.Check, r *resty.Client) {
|
|||||||
mem, _ := host.Memory()
|
mem, _ := host.Memory()
|
||||||
percent := (float64(mem.Used) / float64(mem.Total)) * 100
|
percent := (float64(mem.Used) / float64(mem.Total)) * 100
|
||||||
|
|
||||||
payload := CPUMemResult{ID: data.CheckPK, Percent: int(math.Round(percent))}
|
payload := CPUMemResult{ID: data.CheckPK, AgentID: a.AgentID, Percent: int(math.Round(percent))}
|
||||||
_, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/")
|
_, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.Logger.Debugln(err)
|
a.Logger.Debugln(err)
|
||||||
@@ -245,6 +250,7 @@ func (a *Agent) MemCheck(data rmm.Check, r *resty.Client) {
|
|||||||
|
|
||||||
type EventLogCheckResult struct {
|
type EventLogCheckResult struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
|
AgentID string `json:"agent_id"`
|
||||||
Log []rmm.EventLogMsg `json:"log"`
|
Log []rmm.EventLogMsg `json:"log"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,7 +260,7 @@ func (a *Agent) EventLogCheck(data rmm.Check, r *resty.Client) {
|
|||||||
|
|
||||||
for _, i := range evtLog {
|
for _, i := range evtLog {
|
||||||
if i.EventType == data.EventType {
|
if i.EventType == data.EventType {
|
||||||
if !data.EventIDWildcard && !(int(i.EventID) == data.EventID) {
|
if !data.EventIDWildcard && (int(i.EventID) != data.EventID) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,7 +305,7 @@ func (a *Agent) EventLogCheck(data rmm.Check, r *resty.Client) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
payload := EventLogCheckResult{ID: data.CheckPK, Log: log}
|
payload := EventLogCheckResult{ID: data.CheckPK, AgentID: a.AgentID, Log: log}
|
||||||
_, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/")
|
_, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.Logger.Debugln(err)
|
a.Logger.Debugln(err)
|
||||||
@@ -315,6 +321,7 @@ func (a *Agent) SendPingCheckResult(payload rmm.PingCheckResponse, r *resty.Clie
|
|||||||
|
|
||||||
func (a *Agent) PingCheck(data rmm.Check) (payload rmm.PingCheckResponse) {
|
func (a *Agent) PingCheck(data rmm.Check) (payload rmm.PingCheckResponse) {
|
||||||
payload.ID = data.CheckPK
|
payload.ID = data.CheckPK
|
||||||
|
payload.AgentID = a.AgentID
|
||||||
|
|
||||||
out, err := DoPing(data.IP)
|
out, err := DoPing(data.IP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -331,6 +338,7 @@ func (a *Agent) PingCheck(data rmm.Check) (payload rmm.PingCheckResponse) {
|
|||||||
|
|
||||||
type WinSvcCheckResult struct {
|
type WinSvcCheckResult struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
|
AgentID string `json:"agent_id"`
|
||||||
MoreInfo string `json:"more_info"`
|
MoreInfo string `json:"more_info"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
}
|
}
|
||||||
@@ -344,6 +352,7 @@ func (a *Agent) SendWinSvcCheckResult(payload WinSvcCheckResult, r *resty.Client
|
|||||||
|
|
||||||
func (a *Agent) WinSvcCheck(data rmm.Check) (payload WinSvcCheckResult) {
|
func (a *Agent) WinSvcCheck(data rmm.Check) (payload WinSvcCheckResult) {
|
||||||
payload.ID = data.CheckPK
|
payload.ID = data.CheckPK
|
||||||
|
payload.AgentID = a.AgentID
|
||||||
|
|
||||||
status, err := GetServiceStatus(data.ServiceName)
|
status, err := GetServiceStatus(data.ServiceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2022 AmidaWare LLC.
|
Copyright 2023 AmidaWare Inc.
|
||||||
|
|
||||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||||
You may only use the Licensed Software in accordance with the License.
|
You may only use the Licensed Software in accordance with the License.
|
||||||
@@ -12,6 +12,9 @@ https://license.tacticalrmm.com
|
|||||||
package agent
|
package agent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
rmm "github.com/amidaware/rmmagent/shared"
|
rmm "github.com/amidaware/rmmagent/shared"
|
||||||
@@ -42,7 +45,7 @@ func (a *Agent) InstallChoco() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, exitcode, err := a.RunScript(string(r.Body()), "powershell", []string{}, 900)
|
_, _, exitcode, err := a.RunScript(string(r.Body()), "powershell", []string{}, 900, false, []string{}, false, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.Logger.Debugln(err)
|
a.Logger.Debugln(err)
|
||||||
a.rClient.R().SetBody(result).Post(url)
|
a.rClient.R().SetBody(result).Post(url)
|
||||||
@@ -59,7 +62,14 @@ func (a *Agent) InstallChoco() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Agent) InstallWithChoco(name string) (string, error) {
|
func (a *Agent) InstallWithChoco(name string) (string, error) {
|
||||||
out, err := CMD("choco.exe", []string{"install", name, "--yes", "--force", "--force-dependencies"}, 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 {
|
if err != nil {
|
||||||
a.Logger.Errorln(err)
|
a.Logger.Errorln(err)
|
||||||
return err.Error(), 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{}, false, "")
|
||||||
|
}
|
||||||
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”).
|
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||||
You may only use the Licensed Software in accordance with the License.
|
You may only use the Licensed Software in accordance with the License.
|
||||||
@@ -158,8 +158,11 @@ func getResourceMessage(providerName, sourceName string, eventID uint32, argsptr
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
handle, err := LoadLibraryEx(syscall.StringToUTF16Ptr(val), 0,
|
handlePtr, err := windows.UTF16PtrFromString(val)
|
||||||
DONT_RESOLVE_DLL_REFERENCES|LOAD_LIBRARY_AS_DATAFILE)
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
handle, err := LoadLibraryEx(handlePtr, 0, DONT_RESOLVE_DLL_REFERENCES|LOAD_LIBRARY_AS_DATAFILE)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -180,7 +183,7 @@ func getResourceMessage(providerName, sourceName string, eventID uint32, argsptr
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
message, _ := bytesToString(msgbuf[:numChars*2])
|
message, _ := bytesToString(msgbuf[:numChars*2])
|
||||||
message = strings.Replace(message, "\r", "", -1)
|
message = strings.ReplaceAll(message, "\r", "")
|
||||||
message = strings.TrimSuffix(message, "\n")
|
message = strings.TrimSuffix(message, "\n")
|
||||||
return message, nil
|
return message, nil
|
||||||
}
|
}
|
||||||
|
|||||||
138
agent/install.go
138
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”).
|
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||||
You may only use the Licensed Software in accordance with the License.
|
You may only use the Licensed Software in accordance with the License.
|
||||||
@@ -12,6 +12,7 @@ https://license.tacticalrmm.com
|
|||||||
package agent
|
package agent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -47,6 +48,8 @@ type Installer struct {
|
|||||||
NoMesh bool
|
NoMesh bool
|
||||||
MeshDir string
|
MeshDir string
|
||||||
MeshNodeID string
|
MeshNodeID string
|
||||||
|
Insecure bool
|
||||||
|
NatsStandardPort string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Agent) Install(i *Installer) {
|
func (a *Agent) Install(i *Installer) {
|
||||||
@@ -82,11 +85,6 @@ func (a *Agent) Install(i *Installer) {
|
|||||||
|
|
||||||
a.Logger.Debugln("API:", i.SaltMaster)
|
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
|
baseURL := u.Scheme + "://" + u.Host
|
||||||
a.Logger.Debugln("Base URL:", baseURL)
|
a.Logger.Debugln("Base URL:", baseURL)
|
||||||
|
|
||||||
@@ -102,6 +100,14 @@ func (a *Agent) Install(i *Installer) {
|
|||||||
iClient.SetProxy(i.Proxy)
|
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))
|
creds, cerr := iClient.R().Get(fmt.Sprintf("%s/api/v3/installer/", baseURL))
|
||||||
if cerr != nil {
|
if cerr != nil {
|
||||||
a.installerMsg(cerr.Error(), "error", i.Silent)
|
a.installerMsg(cerr.Error(), "error", i.Silent)
|
||||||
@@ -138,12 +144,8 @@ func (a *Agent) Install(i *Installer) {
|
|||||||
rClient.SetProxy(i.Proxy)
|
rClient.SetProxy(i.Proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
var arch string
|
if i.Insecure {
|
||||||
switch a.Arch {
|
rClient.SetTLSClientConfig(insecureConf)
|
||||||
case "x86_64":
|
|
||||||
arch = "64"
|
|
||||||
case "x86":
|
|
||||||
arch = "32"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var installerMeshSystemEXE string
|
var installerMeshSystemEXE string
|
||||||
@@ -153,35 +155,59 @@ func (a *Agent) Install(i *Installer) {
|
|||||||
installerMeshSystemEXE = a.MeshSystemEXE
|
installerMeshSystemEXE = a.MeshSystemEXE
|
||||||
}
|
}
|
||||||
|
|
||||||
var meshNodeID string
|
var meshNodeID, meshOutput string
|
||||||
|
|
||||||
if runtime.GOOS == "windows" && !i.NoMesh {
|
if !i.NoMesh && runtime.GOOS != "linux" {
|
||||||
mesh := filepath.Join(a.ProgramDir, a.MeshInstaller)
|
switch runtime.GOOS {
|
||||||
if i.LocalMesh == "" {
|
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...")
|
a.Logger.Infoln("Downloading mesh agent...")
|
||||||
payload := map[string]string{"arch": arch, "plat": a.Platform}
|
payload := map[string]string{"goarch": a.GoArch, "plat": a.Platform}
|
||||||
r, err := rClient.R().SetBody(payload).SetOutput(mesh).Post(fmt.Sprintf("%s/api/v3/meshexe/", baseURL))
|
r, err := rClient.R().SetBody(payload).SetOutput(meshOutput).Post(fmt.Sprintf("%s/api/v3/meshexe/", baseURL))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.installerMsg(fmt.Sprintf("Failed to download mesh agent: %s", err.Error()), "error", i.Silent)
|
a.installerMsg(fmt.Sprintf("Failed to download mesh agent: %s", err.Error()), "error", i.Silent)
|
||||||
}
|
}
|
||||||
if r.StatusCode() != 200 {
|
if r.StatusCode() != 200 {
|
||||||
a.installerMsg(fmt.Sprintf("Unable to download the mesh agent from the RMM. %s", r.String()), "error", i.Silent)
|
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.Infoln("Installing mesh agent...")
|
||||||
a.Logger.Debugln("Mesh agent:", mesh)
|
a.Logger.Debugln("Mesh agent:", meshOutput)
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
meshNodeID, err = a.installMesh(mesh, installerMeshSystemEXE, i.Proxy)
|
if runtime.GOOS == "windows" {
|
||||||
|
meshNodeID, err = a.installMesh(meshOutput, installerMeshSystemEXE, i.Proxy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.installerMsg(fmt.Sprintf("Failed to install mesh agent: %s", err.Error()), "error", i.Silent)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(i.MeshNodeID) > 0 {
|
if len(i.MeshNodeID) > 0 {
|
||||||
@@ -219,7 +245,7 @@ func (a *Agent) Install(i *Installer) {
|
|||||||
a.Logger.Debugln("Agent token:", agentToken)
|
a.Logger.Debugln("Agent token:", agentToken)
|
||||||
a.Logger.Debugln("Agent PK:", agentPK)
|
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)
|
time.Sleep(1 * time.Second)
|
||||||
// refresh our agent with new values
|
// refresh our agent with new values
|
||||||
a = New(a.Logger, a.Version)
|
a = New(a.Logger, a.Version)
|
||||||
@@ -232,18 +258,69 @@ func (a *Agent) Install(i *Installer) {
|
|||||||
// check in once
|
// check in once
|
||||||
a.DoNatsCheckIn()
|
a.DoNatsCheckIn()
|
||||||
|
|
||||||
|
if runtime.GOOS == "linux" {
|
||||||
|
// Used for Nushell and Deno binaries
|
||||||
|
os.MkdirAll(nixAgentBinDir, 0755)
|
||||||
|
}
|
||||||
|
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
os.MkdirAll(nixAgentBinDir, 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" {
|
if runtime.GOOS == "windows" {
|
||||||
|
os.MkdirAll(filepath.Join(a.ProgramDir, "bin"), 0755)
|
||||||
|
|
||||||
// send software api
|
// send software api
|
||||||
a.SendSoftware()
|
a.SendSoftware()
|
||||||
|
|
||||||
a.Logger.Debugln("Creating temp dir")
|
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.Logger.Debugln("Disabling automatic windows updates")
|
||||||
a.PatchMgmnt(true)
|
a.PatchMgmnt(true)
|
||||||
|
|
||||||
a.Logger.Infoln("Installing service...")
|
a.Logger.Infoln("Installing service...")
|
||||||
err := a.InstallService()
|
err = a.InstallService()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.installerMsg(err.Error(), "error", i.Silent)
|
a.installerMsg(err.Error(), "error", i.Silent)
|
||||||
}
|
}
|
||||||
@@ -255,9 +332,6 @@ func (a *Agent) Install(i *Installer) {
|
|||||||
a.installerMsg(out.ErrorMsg, "error", i.Silent)
|
a.installerMsg(out.ErrorMsg, "error", i.Silent)
|
||||||
}
|
}
|
||||||
|
|
||||||
a.Logger.Infoln("Adding windows defender exclusions")
|
|
||||||
a.addDefenderExlusions()
|
|
||||||
|
|
||||||
if i.Power {
|
if i.Power {
|
||||||
a.Logger.Infoln("Disabling sleep/hibernate...")
|
a.Logger.Infoln("Disabling sleep/hibernate...")
|
||||||
DisableSleepHibernate()
|
DisableSleepHibernate()
|
||||||
@@ -274,7 +348,7 @@ func (a *Agent) Install(i *Installer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a.installerMsg("Installation was successfull!\nAllow a few minutes for the agent to properly display in the RMM", "info", i.Silent)
|
a.installerMsg("Installation was successful!\nAllow a few minutes for the agent to properly display in the RMM", "info", i.Silent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyFile(src, dst string) error {
|
func copyFile(src, dst string) error {
|
||||||
|
|||||||
@@ -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”).
|
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||||
You may only use the Licensed Software in accordance with 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"
|
"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)
|
k, _, err := registry.CreateKey(registry.LOCAL_MACHINE, `SOFTWARE\TacticalRMM`, registry.ALL_ACCESS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("Error creating registry key:", err)
|
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)
|
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) {
|
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”).
|
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||||
You may only use the Licensed Software in accordance with 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”).
|
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||||
You may only use the Licensed Software in accordance with 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 {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// winagent-v* is deprecated
|
||||||
if strings.Contains(p.Exe, "winagent-v") {
|
if strings.Contains(p.Exe, "winagent-v") {
|
||||||
a.Logger.Debugln("killing process", p.Exe)
|
a.Logger.Debugln("killing process", p.Exe)
|
||||||
KillProc(int32(p.PID))
|
KillProc(int32(p.PID))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.Contains(p.Exe, "tacticalagent-v") {
|
||||||
|
a.Logger.Debugln("killing process", p.Exe)
|
||||||
|
KillProc(int32(p.PID))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
53
agent/rpc.go
53
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”).
|
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||||
You may only use the Licensed Software in accordance with the License.
|
You may only use the Licensed Software in accordance with the License.
|
||||||
@@ -40,6 +40,10 @@ type NatsMsg struct {
|
|||||||
PatchMgmt bool `json:"patch_mgmt"`
|
PatchMgmt bool `json:"patch_mgmt"`
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
Code string `json:"code"`
|
Code string `json:"code"`
|
||||||
|
RunAsUser bool `json:"run_as_user"`
|
||||||
|
EnvVars []string `json:"env_vars"`
|
||||||
|
NushellEnableConfig bool `json:"nushell_enable_config"`
|
||||||
|
DenoDefaultPermissions string `json:"deno_default_permissions"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -50,16 +54,20 @@ var (
|
|||||||
|
|
||||||
func (a *Agent) RunRPC() {
|
func (a *Agent) RunRPC() {
|
||||||
a.Logger.Infoln("Agent service started")
|
a.Logger.Infoln("Agent service started")
|
||||||
go a.RunAsService()
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(1)
|
|
||||||
opts := a.setupNatsOptions()
|
opts := a.setupNatsOptions()
|
||||||
server := fmt.Sprintf("tls://%s:4222", a.ApiURL)
|
nc, err := nats.Connect(a.NatsServer, opts...)
|
||||||
nc, err := nats.Connect(server, opts...)
|
a.Logger.Debugf("%+v\n", nc)
|
||||||
|
a.Logger.Debugf("%+v\n", nc.Opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.Logger.Fatalln("RunRPC() nats.Connect()", err)
|
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) {
|
nc.Subscribe(a.AgentID, func(msg *nats.Msg) {
|
||||||
var payload *NatsMsg
|
var payload *NatsMsg
|
||||||
var mh codec.MsgpackHandle
|
var mh codec.MsgpackHandle
|
||||||
@@ -178,7 +186,7 @@ func (a *Agent) RunRPC() {
|
|||||||
|
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "windows":
|
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)
|
a.Logger.Debugln(out)
|
||||||
if out[1] != "" {
|
if out[1] != "" {
|
||||||
ret.Encode(out[1])
|
ret.Encode(out[1])
|
||||||
@@ -258,7 +266,7 @@ func (a *Agent) RunRPC() {
|
|||||||
var resultData rmm.RunScriptResp
|
var resultData rmm.RunScriptResp
|
||||||
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
|
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
|
||||||
start := time.Now()
|
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, p.NushellEnableConfig, p.DenoDefaultPermissions)
|
||||||
resultData.ExecTime = time.Since(start).Seconds()
|
resultData.ExecTime = time.Since(start).Seconds()
|
||||||
resultData.ID = p.ID
|
resultData.ID = p.ID
|
||||||
|
|
||||||
@@ -288,7 +296,7 @@ func (a *Agent) RunRPC() {
|
|||||||
var retData rmm.RunScriptResp
|
var retData rmm.RunScriptResp
|
||||||
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
|
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
|
||||||
start := time.Now()
|
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, p.NushellEnableConfig, p.DenoDefaultPermissions)
|
||||||
|
|
||||||
retData.ExecTime = time.Since(start).Seconds()
|
retData.ExecTime = time.Since(start).Seconds()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -333,6 +341,22 @@ func (a *Agent) RunRPC() {
|
|||||||
msg.Respond(resp)
|
msg.Respond(resp)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
case "shutdown":
|
||||||
|
go func() {
|
||||||
|
a.Logger.Debugln("Scheduling immediate shutdown")
|
||||||
|
var resp []byte
|
||||||
|
ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle))
|
||||||
|
ret.Encode("ok")
|
||||||
|
msg.Respond(resp)
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
CMD("shutdown.exe", []string{"/s", "/t", "5", "/f"}, 15, false)
|
||||||
|
} else {
|
||||||
|
opts := a.NewCMDOpts()
|
||||||
|
opts.Command = "shutdown -h now"
|
||||||
|
a.CmdV2(opts)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
case "rebootnow":
|
case "rebootnow":
|
||||||
go func() {
|
go func() {
|
||||||
a.Logger.Debugln("Scheduling immediate reboot")
|
a.Logger.Debugln("Scheduling immediate reboot")
|
||||||
@@ -431,6 +455,10 @@ func (a *Agent) RunRPC() {
|
|||||||
}()
|
}()
|
||||||
case "installpython":
|
case "installpython":
|
||||||
go a.GetPython(true)
|
go a.GetPython(true)
|
||||||
|
case "installnushell":
|
||||||
|
go a.InstallNushell(true)
|
||||||
|
case "installdeno":
|
||||||
|
go a.InstallDeno(true)
|
||||||
case "installchoco":
|
case "installchoco":
|
||||||
go a.InstallChoco()
|
go a.InstallChoco()
|
||||||
case "installwithchoco":
|
case "installwithchoco":
|
||||||
@@ -475,10 +503,15 @@ func (a *Agent) RunRPC() {
|
|||||||
} else {
|
} else {
|
||||||
ret.Encode("ok")
|
ret.Encode("ok")
|
||||||
msg.Respond(resp)
|
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)
|
atomic.StoreUint32(&agentUpdateLocker, 0)
|
||||||
nc.Flush()
|
nc.Flush()
|
||||||
nc.Close()
|
nc.Close()
|
||||||
|
a.ControlService(winSvcName, "stop")
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
}(payload)
|
}(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”).
|
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||||
You may only use the Licensed Software in accordance with the License.
|
You may only use the Licensed Software in accordance with the License.
|
||||||
@@ -119,9 +119,10 @@ func (a *Agent) EditService(name, startupType string) rmm.WinSvcResp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conf.StartType = startType
|
conf.StartType = startType
|
||||||
if startupType == "autodelay" {
|
switch startupType {
|
||||||
|
case "autodelay":
|
||||||
conf.DelayedAutoStart = true
|
conf.DelayedAutoStart = true
|
||||||
} else if startupType == "auto" {
|
case "auto":
|
||||||
conf.DelayedAutoStart = false
|
conf.DelayedAutoStart = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2022 AmidaWare LLC.
|
Copyright 2023 AmidaWare Inc.
|
||||||
|
|
||||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||||
You may only use the Licensed Software in accordance with 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”).
|
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||||
You may only use the Licensed Software in accordance with the License.
|
You may only use the Licensed Software in accordance with the License.
|
||||||
|
|||||||
135
agent/svc.go
135
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”).
|
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||||
You may only use the Licensed Software in accordance with the License.
|
You may only use the Licensed Software in accordance with the License.
|
||||||
@@ -13,56 +13,104 @@ package agent
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
nats "github.com/nats-io/nats.go"
|
nats "github.com/nats-io/nats.go"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *Agent) RunAsService() {
|
func (a *Agent) RunAsService(nc *nats.Conn) {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go a.AgentSvc()
|
go a.AgentSvc(nc)
|
||||||
go a.CheckRunner()
|
go a.CheckRunner()
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Agent) AgentSvc() {
|
type AgentCheckInConfig struct {
|
||||||
go a.GetPython(false)
|
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"`
|
||||||
|
InstallNushell bool `json:"install_nushell"`
|
||||||
|
InstallNushellVersion string `json:"install_nushell_version"`
|
||||||
|
InstallNushellUrl string `json:"install_nushell_url"`
|
||||||
|
NushellEnableConfig bool `json:"nushell_enable_config"`
|
||||||
|
InstallDeno bool `json:"install_deno"`
|
||||||
|
InstallDenoVersion string `json:"install_deno_version"`
|
||||||
|
InstallDenoUrl string `json:"install_deno_url"`
|
||||||
|
DenoDefaultPermissions string `json:"deno_default_permissions"`
|
||||||
|
}
|
||||||
|
|
||||||
a.CreateTRMMTempDir()
|
func (a *Agent) AgentSvc(nc *nats.Conn) {
|
||||||
a.RunMigrations()
|
a.RunMigrations()
|
||||||
|
|
||||||
sleepDelay := randRange(14, 22)
|
if runtime.GOOS == "windows" {
|
||||||
|
go a.GetPython(false)
|
||||||
|
|
||||||
|
err := createWinTempDir()
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("AgentSvc() createWinTempDir():", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sleepDelay := randRange(7, 25)
|
||||||
a.Logger.Debugf("AgentSvc() sleeping for %v seconds", sleepDelay)
|
a.Logger.Debugf("AgentSvc() sleeping for %v seconds", sleepDelay)
|
||||||
time.Sleep(time.Duration(sleepDelay) * time.Second)
|
time.Sleep(time.Duration(sleepDelay) * time.Second)
|
||||||
|
|
||||||
opts := a.setupNatsOptions()
|
if runtime.GOOS == "windows" {
|
||||||
server := fmt.Sprintf("tls://%s:4222", a.ApiURL)
|
a.KillHungUpdates()
|
||||||
nc, err := nats.Connect(server, opts...)
|
time.Sleep(1 * time.Second)
|
||||||
if err != nil {
|
a.CleanupAgentUpdates()
|
||||||
a.Logger.Fatalln("AgentSvc() nats.Connect()", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Windows has GetAgentCheckInConfig() while unix has a stub GetAgentCheckInConfig()
|
||||||
|
conf := a.GetAgentCheckInConfig(a.GetCheckInConfFromAPI())
|
||||||
|
a.Logger.Debugf("AgentCheckInConf: %+v\n", conf)
|
||||||
for _, s := range natsCheckin {
|
for _, s := range natsCheckin {
|
||||||
|
if conf.LimitData && stringInSlice(s, limitNatsData) {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
a.NatsMessage(nc, s)
|
a.NatsMessage(nc, s)
|
||||||
time.Sleep(time.Duration(randRange(100, 400)) * time.Millisecond)
|
time.Sleep(time.Duration(randRange(100, 400)) * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
a.SyncMeshNodeID()
|
// The server conf check is also done in the functions to keep the parameters the same.
|
||||||
|
// Don't force a download when restarting the service.
|
||||||
|
if conf.InstallNushell {
|
||||||
|
go a.InstallNushell(false)
|
||||||
|
}
|
||||||
|
if conf.InstallDeno {
|
||||||
|
go a.InstallDeno(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
go a.SyncMeshNodeID()
|
||||||
|
|
||||||
time.Sleep(time.Duration(randRange(1, 3)) * time.Second)
|
time.Sleep(time.Duration(randRange(1, 3)) * time.Second)
|
||||||
|
if runtime.GOOS == "windows" && !conf.LimitData {
|
||||||
a.AgentStartup()
|
a.AgentStartup()
|
||||||
a.SendSoftware()
|
a.SendSoftware()
|
||||||
|
}
|
||||||
|
|
||||||
checkInHelloTicker := time.NewTicker(time.Duration(randRange(30, 60)) * time.Second)
|
if runtime.GOOS == "darwin" {
|
||||||
checkInAgentInfoTicker := time.NewTicker(time.Duration(randRange(200, 400)) * time.Second)
|
go a.FixVenturaMesh()
|
||||||
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)
|
checkInHelloTicker := time.NewTicker(time.Duration(conf.Hello) * time.Second)
|
||||||
checkInSWTicker := time.NewTicker(time.Duration(randRange(2800, 3500)) * time.Second)
|
checkInAgentInfoTicker := time.NewTicker(time.Duration(conf.AgentInfo) * time.Second)
|
||||||
checkInWMITicker := time.NewTicker(time.Duration(randRange(3000, 4000)) * time.Second)
|
checkInWinSvcTicker := time.NewTicker(time.Duration(conf.WinSvc) * time.Second)
|
||||||
syncMeshTicker := time.NewTicker(time.Duration(randRange(800, 1200)) * 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 {
|
for {
|
||||||
select {
|
select {
|
||||||
@@ -94,3 +142,48 @@ func (a *Agent) AgentStartup() {
|
|||||||
a.Logger.Debugln("AgentStartup()", err)
|
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
|
||||||
|
ret.InstallNushell = false
|
||||||
|
ret.InstallNushellVersion = ""
|
||||||
|
ret.InstallNushellUrl = ""
|
||||||
|
ret.NushellEnableConfig = false
|
||||||
|
ret.InstallDeno = false
|
||||||
|
ret.InstallDenoVersion = ""
|
||||||
|
ret.InstallDenoUrl = ""
|
||||||
|
ret.DenoDefaultPermissions = ""
|
||||||
|
} 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
|
||||||
|
ret.InstallNushell = r.Result().(*AgentCheckInConfig).InstallNushell
|
||||||
|
ret.InstallNushellVersion = r.Result().(*AgentCheckInConfig).InstallNushellVersion
|
||||||
|
ret.InstallNushellUrl = r.Result().(*AgentCheckInConfig).InstallNushellUrl
|
||||||
|
ret.NushellEnableConfig = r.Result().(*AgentCheckInConfig).NushellEnableConfig
|
||||||
|
ret.InstallDeno = r.Result().(*AgentCheckInConfig).InstallDeno
|
||||||
|
ret.InstallDenoVersion = r.Result().(*AgentCheckInConfig).InstallDenoVersion
|
||||||
|
ret.InstallDenoUrl = r.Result().(*AgentCheckInConfig).InstallDenoUrl
|
||||||
|
ret.DenoDefaultPermissions = r.Result().(*AgentCheckInConfig).DenoDefaultPermissions
|
||||||
|
}
|
||||||
|
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”).
|
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||||
You may only use the Licensed Software in accordance with the License.
|
You may only use the Licensed Software in accordance with the License.
|
||||||
@@ -24,11 +24,14 @@ var _ unsafe.Pointer
|
|||||||
var (
|
var (
|
||||||
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
|
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
|
||||||
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||||
|
userenv = windows.NewLazyDLL("userenv.dll")
|
||||||
|
|
||||||
procFormatMessageW = modkernel32.NewProc("FormatMessageW")
|
procFormatMessageW = modkernel32.NewProc("FormatMessageW")
|
||||||
procGetOldestEventLogRecord = modadvapi32.NewProc("GetOldestEventLogRecord")
|
procGetOldestEventLogRecord = modadvapi32.NewProc("GetOldestEventLogRecord")
|
||||||
procLoadLibraryExW = modkernel32.NewProc("LoadLibraryExW")
|
procLoadLibraryExW = modkernel32.NewProc("LoadLibraryExW")
|
||||||
procReadEventLogW = modadvapi32.NewProc("ReadEventLogW")
|
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
|
// 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
|
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”).
|
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||||
You may only use the Licensed Software in accordance with the License.
|
You may only use the Licensed Software in accordance with the License.
|
||||||
@@ -12,119 +12,16 @@ https://license.tacticalrmm.com
|
|||||||
package agent
|
package agent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
rmm "github.com/amidaware/rmmagent/shared"
|
|
||||||
"github.com/amidaware/taskmaster"
|
"github.com/amidaware/taskmaster"
|
||||||
"github.com/rickb777/date/period"
|
"github.com/rickb777/date/period"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *Agent) RunTask(id int) error {
|
|
||||||
data := rmm.AutomatedTask{}
|
|
||||||
url := fmt.Sprintf("/api/v3/%d/%s/taskrunner/", id, a.AgentID)
|
|
||||||
r1, gerr := a.rClient.R().Get(url)
|
|
||||||
if gerr != nil {
|
|
||||||
a.Logger.Debugln(gerr)
|
|
||||||
return gerr
|
|
||||||
}
|
|
||||||
|
|
||||||
if r1.IsError() {
|
|
||||||
a.Logger.Debugln("Run Task:", r1.String())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.Unmarshal(r1.Body(), &data); err != nil {
|
|
||||||
a.Logger.Debugln(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
start := time.Now()
|
|
||||||
|
|
||||||
type TaskResult struct {
|
|
||||||
Stdout string `json:"stdout"`
|
|
||||||
Stderr string `json:"stderr"`
|
|
||||||
RetCode int `json:"retcode"`
|
|
||||||
ExecTime float64 `json:"execution_time"`
|
|
||||||
}
|
|
||||||
|
|
||||||
payload := TaskResult{}
|
|
||||||
|
|
||||||
// loop through all task actions
|
|
||||||
for _, action := range data.TaskActions {
|
|
||||||
|
|
||||||
action_start := time.Now()
|
|
||||||
if action.ActionType == "script" {
|
|
||||||
stdout, stderr, retcode, err := a.RunScript(action.Code, action.Shell, action.Args, action.Timeout)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
a.Logger.Debugln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// add text to stdout showing which script ran if more than 1 script
|
|
||||||
action_exec_time := time.Since(action_start).Seconds()
|
|
||||||
|
|
||||||
if len(data.TaskActions) > 1 {
|
|
||||||
payload.Stdout += fmt.Sprintf("\n------------\nRunning Script: %s. Execution Time: %f\n------------\n\n", action.ScriptName, action_exec_time)
|
|
||||||
}
|
|
||||||
|
|
||||||
// save results
|
|
||||||
payload.Stdout += stdout
|
|
||||||
payload.Stderr += stderr
|
|
||||||
payload.RetCode = retcode
|
|
||||||
|
|
||||||
if !data.ContinueOnError && stderr != "" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if action.ActionType == "cmd" {
|
|
||||||
// out[0] == stdout, out[1] == stderr
|
|
||||||
out, err := CMDShell(action.Shell, []string{}, action.Command, action.Timeout, false)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
a.Logger.Debugln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(data.TaskActions) > 1 {
|
|
||||||
action_exec_time := time.Since(action_start).Seconds()
|
|
||||||
|
|
||||||
// add text to stdout showing which script ran
|
|
||||||
payload.Stdout += fmt.Sprintf("\n------------\nRunning Command: %s. Execution Time: %f\n------------\n\n", action.Command, action_exec_time)
|
|
||||||
}
|
|
||||||
// save results
|
|
||||||
payload.Stdout += out[0]
|
|
||||||
payload.Stderr += out[1]
|
|
||||||
|
|
||||||
// no error
|
|
||||||
if out[1] == "" {
|
|
||||||
payload.RetCode = 0
|
|
||||||
} else {
|
|
||||||
payload.RetCode = 1
|
|
||||||
|
|
||||||
if !data.ContinueOnError {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
a.Logger.Debugln("Invalid Action", action)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
payload.ExecTime = time.Since(start).Seconds()
|
|
||||||
|
|
||||||
_, perr := a.rClient.R().SetBody(payload).Patch(url)
|
|
||||||
if perr != nil {
|
|
||||||
a.Logger.Debugln(perr)
|
|
||||||
return perr
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type SchedTask struct {
|
type SchedTask struct {
|
||||||
PK int `json:"pk"`
|
PK int `json:"pk"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
@@ -175,12 +72,17 @@ func (a *Agent) CreateSchedTask(st SchedTask) (bool, error) {
|
|||||||
var tasktrigger taskmaster.TaskTrigger
|
var tasktrigger taskmaster.TaskTrigger
|
||||||
|
|
||||||
var now = time.Now()
|
var now = time.Now()
|
||||||
if st.Trigger == "manual" {
|
switch st.Trigger {
|
||||||
|
case "manual":
|
||||||
tasktrigger = taskmaster.TaskTrigger{
|
tasktrigger = taskmaster.TaskTrigger{
|
||||||
Enabled: st.Enabled,
|
Enabled: st.Enabled,
|
||||||
StartBoundary: now,
|
StartBoundary: now,
|
||||||
}
|
}
|
||||||
} else {
|
case "onboarding":
|
||||||
|
tasktrigger = taskmaster.TaskTrigger{
|
||||||
|
Enabled: st.Enabled,
|
||||||
|
}
|
||||||
|
default:
|
||||||
tasktrigger = taskmaster.TaskTrigger{
|
tasktrigger = taskmaster.TaskTrigger{
|
||||||
Enabled: st.Enabled,
|
Enabled: st.Enabled,
|
||||||
StartBoundary: time.Date(st.StartYear, st.StartMonth, st.StartDay, st.StartHour, st.StartMinute, 0, 0, now.Location()),
|
StartBoundary: time.Date(st.StartYear, st.StartMonth, st.StartDay, st.StartHour, st.StartMinute, 0, 0, now.Location()),
|
||||||
@@ -205,6 +107,11 @@ func (a *Agent) CreateSchedTask(st SchedTask) (bool, error) {
|
|||||||
TaskTrigger: tasktrigger,
|
TaskTrigger: tasktrigger,
|
||||||
RandomDelay: st.RandomDelay,
|
RandomDelay: st.RandomDelay,
|
||||||
}
|
}
|
||||||
|
case "onboarding":
|
||||||
|
trigger = taskmaster.RegistrationTrigger{
|
||||||
|
TaskTrigger: tasktrigger,
|
||||||
|
Delay: st.RandomDelay,
|
||||||
|
}
|
||||||
|
|
||||||
case "daily":
|
case "daily":
|
||||||
trigger = taskmaster.DailyTrigger{
|
trigger = taskmaster.DailyTrigger{
|
||||||
|
|||||||
156
agent/utils.go
156
agent/utils.go
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2022 AmidaWare LLC.
|
Copyright 2023 AmidaWare Inc.
|
||||||
|
|
||||||
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||||
You may only use the Licensed Software in accordance with the License.
|
You may only use the Licensed Software in accordance with the License.
|
||||||
@@ -12,16 +12,22 @@ https://license.tacticalrmm.com
|
|||||||
package agent
|
package agent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/tar"
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
goDebug "runtime/debug"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -58,7 +64,7 @@ func DoPing(host string) (PingResponse, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pinger.Count = 3
|
pinger.Count = 3
|
||||||
pinger.Size = 24
|
pinger.Size = 548
|
||||||
pinger.Interval = time.Second
|
pinger.Interval = time.Second
|
||||||
pinger.Timeout = 5 * time.Second
|
pinger.Timeout = 5 * time.Second
|
||||||
pinger.SetPrivileged(true)
|
pinger.SetPrivileged(true)
|
||||||
@@ -86,6 +92,7 @@ func DoPing(host string) (PingResponse, error) {
|
|||||||
func (a *Agent) PublicIP() string {
|
func (a *Agent) PublicIP() string {
|
||||||
a.Logger.Debugln("PublicIP start")
|
a.Logger.Debugln("PublicIP start")
|
||||||
client := resty.New()
|
client := resty.New()
|
||||||
|
client.SetHeader("User-Agent", a.AgentHeader)
|
||||||
client.SetTimeout(4 * time.Second)
|
client.SetTimeout(4 * time.Second)
|
||||||
if len(a.Proxy) > 0 {
|
if len(a.Proxy) > 0 {
|
||||||
client.SetProxy(a.Proxy)
|
client.SetProxy(a.Proxy)
|
||||||
@@ -125,7 +132,6 @@ func (a *Agent) PublicIP() string {
|
|||||||
|
|
||||||
// GenerateAgentID creates and returns a unique agent id
|
// GenerateAgentID creates and returns a unique agent id
|
||||||
func GenerateAgentID() string {
|
func GenerateAgentID() string {
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||||
b := make([]rune, 40)
|
b := make([]rune, 40)
|
||||||
for i := range b {
|
for i := range b {
|
||||||
@@ -138,10 +144,13 @@ func GenerateAgentID() string {
|
|||||||
func ShowVersionInfo(ver string) {
|
func ShowVersionInfo(ver string) {
|
||||||
fmt.Println("Tactical RMM Agent:", ver)
|
fmt.Println("Tactical RMM Agent:", ver)
|
||||||
fmt.Println("Arch:", runtime.GOARCH)
|
fmt.Println("Arch:", runtime.GOARCH)
|
||||||
fmt.Println("Go version:", runtime.Version())
|
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
fmt.Println("Program Directory:", filepath.Join(os.Getenv("ProgramFiles"), progFilesName))
|
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
|
// TotalRAM returns total RAM in GB
|
||||||
@@ -275,6 +284,73 @@ func Unzip(src, dest string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtractTarGz extracts a tar.gz file to the specified directory.
|
||||||
|
// Returns the extracted directory name.
|
||||||
|
// https://stackoverflow.com/questions/57639648/how-to-decompress-tar-gz-file-in-go
|
||||||
|
func (a *Agent) ExtractTarGz(targz string, destDir string) (extractedDir string, err error) {
|
||||||
|
gzipStream, err := os.Open(targz)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("ExtractTarGz(): Open() failed:", err.Error())
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer gzipStream.Close()
|
||||||
|
|
||||||
|
uncompressedStream, err := gzip.NewReader(gzipStream)
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("ExtractTarGz(): NewReader() failed:", err.Error())
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer uncompressedStream.Close()
|
||||||
|
|
||||||
|
extractedDir = ""
|
||||||
|
tarReader := tar.NewReader(uncompressedStream)
|
||||||
|
for {
|
||||||
|
header, err := tarReader.Next()
|
||||||
|
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("ExtractTarGz(): Next() failed:", err.Error())
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch header.Typeflag {
|
||||||
|
case tar.TypeDir:
|
||||||
|
if err := os.MkdirAll(filepath.Join(destDir, header.Name), 0755); err != nil {
|
||||||
|
a.Logger.Errorln("ExtractTarGz(): Mkdir() failed:", err.Error())
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if extractedDir == "" {
|
||||||
|
extractedDir = header.Name
|
||||||
|
}
|
||||||
|
case tar.TypeReg:
|
||||||
|
outFile, err := os.Create(filepath.Join(destDir, header.Name))
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("ExtractTarGz(): Create() failed:", err.Error())
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(outFile, tarReader); err != nil {
|
||||||
|
a.Logger.Errorln("ExtractTarGz(): Copy() failed:", err.Error())
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
err = outFile.Close()
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("ExtractTarGz(): Close() failed:", err.Error())
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
errMsg := fmt.Sprintf("ExtractTarGz(): Unknown type: %v in %s", header.Typeflag, header.Name)
|
||||||
|
a.Logger.Errorln(errMsg)
|
||||||
|
return "", errors.New(errMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return extractedDir, nil
|
||||||
|
}
|
||||||
|
|
||||||
// https://yourbasic.org/golang/formatting-byte-size-to-human-readable-format/
|
// https://yourbasic.org/golang/formatting-byte-size-to-human-readable-format/
|
||||||
func ByteCountSI(b uint64) string {
|
func ByteCountSI(b uint64) string {
|
||||||
const unit = 1024
|
const unit = 1024
|
||||||
@@ -291,7 +367,6 @@ func ByteCountSI(b uint64) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func randRange(min, max int) int {
|
func randRange(min, max int) int {
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
return rand.Intn(max-min) + min
|
return rand.Intn(max-min) + min
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,18 +378,77 @@ func removeWinNewLines(s string) string {
|
|||||||
return strings.ReplaceAll(s, "\r\n", "\n")
|
return strings.ReplaceAll(s, "\r\n", "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTmpFile() (*os.File, error) {
|
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(shell ...string) (*os.File, error) {
|
||||||
var f *os.File
|
var f *os.File
|
||||||
f, err := os.CreateTemp("", "trmm")
|
cwd, err := getCwd()
|
||||||
if err != nil {
|
|
||||||
cwd, err := os.Getwd()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return f, err
|
return f, err
|
||||||
}
|
}
|
||||||
f, err = os.CreateTemp(cwd, "trmm")
|
|
||||||
|
ext := ""
|
||||||
|
if len(shell) > 0 && shell[0] == "deno" {
|
||||||
|
ext = ".ts"
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err = os.CreateTemp(cwd, fmt.Sprintf("trmm*%s", ext))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return f, err
|
return f, err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return f, nil
|
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”).
|
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||||
You may only use the Licensed Software in accordance with 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”).
|
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||||
You may only use the Licensed Software in accordance with the License.
|
You may only use the Licensed Software in accordance with the License.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<assemblyIdentity
|
<assemblyIdentity
|
||||||
type="win32"
|
type="win32"
|
||||||
name="TacticalRMM"
|
name="TacticalRMM"
|
||||||
version="2.0.1.0"
|
version="2.8.0.0"
|
||||||
processorArchitecture="*"/>
|
processorArchitecture="*"/>
|
||||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
<security>
|
<security>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#define MyAppName "Tactical RMM Agent"
|
#define MyAppName "Tactical RMM Agent"
|
||||||
#define MyAppVersion "2.0.1"
|
#define MyAppVersion "2.8.0"
|
||||||
#define MyAppPublisher "AmidaWare LLC"
|
#define MyAppPublisher "AmidaWare Inc"
|
||||||
#define MyAppURL "https://github.com/amidaware"
|
#define MyAppURL "https://github.com/amidaware"
|
||||||
#define MyAppExeName "tacticalrmm.exe"
|
#define MyAppExeName "tacticalrmm.exe"
|
||||||
#define MESHEXE "meshagent.exe"
|
#define MESHEXE "meshagent.exe"
|
||||||
@@ -28,6 +28,8 @@ WizardStyle=modern
|
|||||||
RestartApplications=no
|
RestartApplications=no
|
||||||
CloseApplications=no
|
CloseApplications=no
|
||||||
MinVersion=6.0
|
MinVersion=6.0
|
||||||
|
VersionInfoVersion=1.0.0.0
|
||||||
|
AppCopyright="Copyright (C) 2024 {#MyAppPublisher}"
|
||||||
|
|
||||||
[Languages]
|
[Languages]
|
||||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
|
|||||||
105
go.mod
105
go.mod
@@ -1,74 +1,83 @@
|
|||||||
module github.com/amidaware/rmmagent
|
module github.com/amidaware/rmmagent
|
||||||
|
|
||||||
go 1.17
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/StackExchange/wmi v1.2.1
|
github.com/StackExchange/wmi v1.2.1
|
||||||
github.com/elastic/go-sysinfo v1.7.1
|
github.com/elastic/go-sysinfo v1.11.2
|
||||||
github.com/go-ole/go-ole v1.2.6
|
github.com/go-ole/go-ole v1.3.0
|
||||||
github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534
|
github.com/go-ping/ping v1.1.0
|
||||||
github.com/go-resty/resty/v2 v2.7.0
|
github.com/go-resty/resty/v2 v2.13.1
|
||||||
github.com/gonutz/w32/v2 v2.4.0
|
github.com/gonutz/w32/v2 v2.11.1
|
||||||
github.com/iamacarpet/go-win64api v0.0.0-20211130162011-82e31fe23f80
|
github.com/iamacarpet/go-win64api v0.0.0-20230324134531-ef6dbdd6db97
|
||||||
github.com/nats-io/nats-server/v2 v2.4.0 // indirect
|
github.com/nats-io/nats.go v1.36.0
|
||||||
github.com/nats-io/nats.go v1.13.0
|
github.com/rickb777/date v1.19.1
|
||||||
github.com/rickb777/date v1.15.3
|
github.com/shirou/gopsutil/v3 v3.23.12
|
||||||
github.com/shirou/gopsutil/v3 v3.22.2
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/sirupsen/logrus v1.8.1
|
github.com/ugorji/go/codec v1.2.12
|
||||||
github.com/ugorji/go/codec v1.2.7
|
github.com/wh1te909/go-win64api v0.0.0-20230802051600-21b24f62e846
|
||||||
github.com/wh1te909/go-win64api v0.0.0-20210906074314-ab23795a6ae5
|
|
||||||
github.com/wh1te909/trmm-shared v0.0.0-20220227075846-f9f757361139
|
github.com/wh1te909/trmm-shared v0.0.0-20220227075846-f9f757361139
|
||||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
|
golang.org/x/net v0.25.0 // indirect
|
||||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9
|
golang.org/x/sys v0.20.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/amidaware/taskmaster v0.0.0-20220111015025-c9cd178bbbf2
|
github.com/amidaware/taskmaster v0.0.0-20220111015025-c9cd178bbbf2
|
||||||
github.com/go-cmd/cmd v1.4.0
|
github.com/go-cmd/cmd v1.4.3
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/jaypipes/ghw v0.8.0
|
github.com/fourcorelabs/wintoken v1.0.0
|
||||||
github.com/kardianos/service v1.2.1
|
github.com/jaypipes/ghw v0.12.0
|
||||||
github.com/spf13/viper v1.10.1
|
github.com/kardianos/service v1.2.2
|
||||||
|
github.com/spf13/viper v1.19.0
|
||||||
|
golang.org/x/text v0.15.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/elastic/go-windows v1.0.0 // indirect
|
github.com/elastic/go-windows v1.0.1 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
github.com/ghodss/yaml v1.0.0 // indirect
|
github.com/ghodss/yaml v1.0.0 // indirect
|
||||||
github.com/google/cabbie v1.0.2 // indirect
|
github.com/google/cabbie v1.0.3 // indirect
|
||||||
github.com/google/glazier v0.0.0-20211029225403-9f766cca891d // indirect
|
github.com/google/glazier v0.0.0-20220520121753-83447cca4ea7 // indirect
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
github.com/google/uuid v1.4.0 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.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/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 // indirect
|
||||||
github.com/kr/pretty v0.2.0 // indirect
|
github.com/klauspost/compress v1.17.2 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
github.com/lufia/plan9stats v0.0.0-20220517141722-cf486979b281 // indirect
|
||||||
github.com/magiconair/properties v1.8.5 // indirect
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.4.3 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/nats-io/nkeys v0.3.0 // indirect
|
github.com/nats-io/nkeys v0.4.7 // indirect
|
||||||
github.com/nats-io/nuid v1.0.1 // 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.2.2 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c // indirect
|
||||||
github.com/prometheus/procfs v0.0.8 // indirect
|
github.com/prometheus/procfs v0.8.0 // indirect
|
||||||
github.com/rickb777/plural v1.3.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/scjalliance/comshim v0.0.0-20190308082608-cf06d2532c4e // indirect
|
||||||
github.com/spf13/afero v1.6.0 // indirect
|
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||||
github.com/spf13/cast v1.4.1 // indirect
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
github.com/spf13/jwalterweatherman v1.1.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/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/subosito/gotenv v1.2.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.9 // indirect
|
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||||
github.com/tklauser/numcpus v0.3.0 // indirect
|
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
go.uber.org/multierr v1.9.0 // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
golang.org/x/crypto v0.23.0 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||||
gopkg.in/ini.v1 v1.66.2 // indirect
|
golang.org/x/sync v0.6.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
|
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
|
||||||
)
|
)
|
||||||
|
|||||||
39
main.go
39
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”).
|
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||||
You may only use the Licensed Software in accordance with the License.
|
You may only use the Licensed Software in accordance with the License.
|
||||||
@@ -25,7 +25,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
version = "2.0.1"
|
version = "2.8.0"
|
||||||
log = logrus.New()
|
log = logrus.New()
|
||||||
logFile *os.File
|
logFile *os.File
|
||||||
)
|
)
|
||||||
@@ -51,11 +51,10 @@ func main() {
|
|||||||
meshDir := flag.String("meshdir", "", "Path to custom meshcentral dir")
|
meshDir := flag.String("meshdir", "", "Path to custom meshcentral dir")
|
||||||
meshNodeID := flag.String("meshnodeid", "", "Mesh Node ID")
|
meshNodeID := flag.String("meshnodeid", "", "Mesh Node ID")
|
||||||
cert := flag.String("cert", "", "Path to domain CA .pem")
|
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")
|
silent := flag.Bool("silent", false, "Do not popup any message boxes during installation")
|
||||||
proxy := flag.String("proxy", "", "Use a http proxy")
|
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()
|
flag.Parse()
|
||||||
|
|
||||||
if *ver {
|
if *ver {
|
||||||
@@ -85,6 +84,8 @@ func main() {
|
|||||||
a.Logger.Debugf("%+v\n", a)
|
a.Logger.Debugf("%+v\n", a)
|
||||||
|
|
||||||
switch *mode {
|
switch *mode {
|
||||||
|
case "getenv":
|
||||||
|
fmt.Println(os.Getenv(flag.Arg(0)))
|
||||||
case "nixmeshnodeid":
|
case "nixmeshnodeid":
|
||||||
fmt.Print(a.NixMeshNodeID())
|
fmt.Print(a.NixMeshNodeID())
|
||||||
case "installsvc":
|
case "installsvc":
|
||||||
@@ -116,21 +117,21 @@ func main() {
|
|||||||
fmt.Println(a.PublicIP())
|
fmt.Println(a.PublicIP())
|
||||||
case "getpython":
|
case "getpython":
|
||||||
a.GetPython(true)
|
a.GetPython(true)
|
||||||
|
case "installdeno":
|
||||||
|
a.InstallDeno(true)
|
||||||
|
case "installnushell":
|
||||||
|
a.InstallNushell(true)
|
||||||
case "runmigrations":
|
case "runmigrations":
|
||||||
a.RunMigrations()
|
a.RunMigrations()
|
||||||
case "recovermesh":
|
case "recovermesh":
|
||||||
a.RecoverMesh()
|
a.RecoverMesh()
|
||||||
|
case "macventurafix":
|
||||||
|
a.FixVenturaMesh()
|
||||||
case "taskrunner":
|
case "taskrunner":
|
||||||
if len(os.Args) < 5 || *taskPK == 0 {
|
if len(os.Args) < 5 || *taskPK == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
a.RunTask(*taskPK)
|
a.RunTask(*taskPK)
|
||||||
case "update":
|
|
||||||
if *updateurl == "" || *inno == "" || *updatever == "" {
|
|
||||||
updateUsage()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
a.AgentUpdate(*updateurl, *inno, *updatever)
|
|
||||||
case "install":
|
case "install":
|
||||||
if runtime.GOOS != "windows" {
|
if runtime.GOOS != "windows" {
|
||||||
u, err := user.Current()
|
u, err := user.Current()
|
||||||
@@ -143,7 +144,6 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if *api == "" || *clientID == 0 || *siteID == 0 || *token == "" {
|
if *api == "" || *clientID == 0 || *siteID == 0 || *token == "" {
|
||||||
installUsage()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
a.Install(&agent.Installer{
|
a.Install(&agent.Installer{
|
||||||
@@ -164,6 +164,8 @@ func main() {
|
|||||||
NoMesh: *noMesh,
|
NoMesh: *noMesh,
|
||||||
MeshDir: *meshDir,
|
MeshDir: *meshDir,
|
||||||
MeshNodeID: *meshNodeID,
|
MeshNodeID: *meshNodeID,
|
||||||
|
Insecure: *insecure,
|
||||||
|
NatsStandardPort: *natsport,
|
||||||
})
|
})
|
||||||
default:
|
default:
|
||||||
agent.ShowStatus(version)
|
agent.ShowStatus(version)
|
||||||
@@ -183,20 +185,9 @@ func setupLogging(level, to *string) {
|
|||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "windows":
|
case "windows":
|
||||||
logFile, _ = os.OpenFile(filepath.Join(os.Getenv("ProgramFiles"), "TacticalAgent", "agent.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0664)
|
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)
|
logFile, _ = os.OpenFile(filepath.Join("/var/log/", "tacticalagent.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0664)
|
||||||
}
|
}
|
||||||
log.SetOutput(logFile)
|
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”).
|
Licensed under the Tactical RMM License Version 1.0 (the “License”).
|
||||||
You may only use the Licensed Software in accordance with the License.
|
You may only use the Licensed Software in accordance with the License.
|
||||||
@@ -42,6 +42,13 @@ type AgentConfig struct {
|
|||||||
Cert string
|
Cert string
|
||||||
Proxy string
|
Proxy string
|
||||||
CustomMeshDir string
|
CustomMeshDir string
|
||||||
|
WinTmpDir string
|
||||||
|
WinRunAsUserTmpDir string
|
||||||
|
NatsProxyPath string
|
||||||
|
NatsProxyPort string
|
||||||
|
NatsStandardPort string
|
||||||
|
NatsPingInterval int
|
||||||
|
Insecure string
|
||||||
}
|
}
|
||||||
|
|
||||||
type RunScriptResp struct {
|
type RunScriptResp struct {
|
||||||
@@ -70,6 +77,7 @@ type AgentInfo struct {
|
|||||||
|
|
||||||
type PingCheckResponse struct {
|
type PingCheckResponse struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
|
AgentID string `json:"agent_id"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Output string `json:"output"`
|
Output string `json:"output"`
|
||||||
}
|
}
|
||||||
@@ -139,6 +147,8 @@ type AssignedTask struct {
|
|||||||
type Script struct {
|
type Script struct {
|
||||||
Shell string `json:"shell"`
|
Shell string `json:"shell"`
|
||||||
Code string `json:"code"`
|
Code string `json:"code"`
|
||||||
|
RunAsUser bool `json:"run_as_user"`
|
||||||
|
EnvVars []string `json:"env_vars"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CheckInfo struct {
|
type CheckInfo struct {
|
||||||
@@ -156,6 +166,9 @@ type Check struct {
|
|||||||
Disk string `json:"disk"`
|
Disk string `json:"disk"`
|
||||||
IP string `json:"ip"`
|
IP string `json:"ip"`
|
||||||
ScriptArgs []string `json:"script_args"`
|
ScriptArgs []string `json:"script_args"`
|
||||||
|
EnvVars []string `json:"env_vars"`
|
||||||
|
NushellEnableConfig bool `json:"nushell_enable_config"`
|
||||||
|
DenoDefaultPermissions string `json:"deno_default_permissions"`
|
||||||
Timeout int `json:"timeout"`
|
Timeout int `json:"timeout"`
|
||||||
ServiceName string `json:"svc_name"`
|
ServiceName string `json:"svc_name"`
|
||||||
PassStartPending bool `json:"pass_if_start_pending"`
|
PassStartPending bool `json:"pass_if_start_pending"`
|
||||||
@@ -184,6 +197,10 @@ type TaskAction struct {
|
|||||||
Code string `json:"code"`
|
Code string `json:"code"`
|
||||||
Args []string `json:"script_args"`
|
Args []string `json:"script_args"`
|
||||||
Timeout int `json:"timeout"`
|
Timeout int `json:"timeout"`
|
||||||
|
RunAsUser bool `json:"run_as_user"`
|
||||||
|
EnvVars []string `json:"env_vars"`
|
||||||
|
NushellEnableConfig bool `json:"nushell_enable_config"`
|
||||||
|
DenoDefaultPermissions string `json:"deno_default_permissions"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AutomatedTask struct {
|
type AutomatedTask struct {
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
"FixedFileInfo": {
|
"FixedFileInfo": {
|
||||||
"FileVersion": {
|
"FileVersion": {
|
||||||
"Major": 2,
|
"Major": 2,
|
||||||
"Minor": 0,
|
"Minor": 8,
|
||||||
"Patch": 1,
|
"Patch": 0,
|
||||||
"Build": 0
|
"Build": 0
|
||||||
},
|
},
|
||||||
"ProductVersion": {
|
"ProductVersion": {
|
||||||
"Major": 2,
|
"Major": 2,
|
||||||
"Minor": 0,
|
"Minor": 8,
|
||||||
"Patch": 1,
|
"Patch": 0,
|
||||||
"Build": 0
|
"Build": 0
|
||||||
},
|
},
|
||||||
"FileFlagsMask": "3f",
|
"FileFlagsMask": "3f",
|
||||||
@@ -20,16 +20,16 @@
|
|||||||
},
|
},
|
||||||
"StringFileInfo": {
|
"StringFileInfo": {
|
||||||
"Comments": "",
|
"Comments": "",
|
||||||
"CompanyName": "AmidaWare LLC",
|
"CompanyName": "AmidaWare Inc",
|
||||||
"FileDescription": "Tactical RMM Agent",
|
"FileDescription": "Tactical RMM Agent",
|
||||||
"FileVersion": "v2.0.1.0",
|
"FileVersion": "v2.8.0.0",
|
||||||
"InternalName": "tacticalrmm.exe",
|
"InternalName": "tacticalrmm.exe",
|
||||||
"LegalCopyright": "Copyright (c) 2022 AmidaWare LLC",
|
"LegalCopyright": "Copyright (c) 2024 AmidaWare Inc",
|
||||||
"LegalTrademarks": "",
|
"LegalTrademarks": "",
|
||||||
"OriginalFilename": "tacticalrmm.exe",
|
"OriginalFilename": "tacticalrmm.exe",
|
||||||
"PrivateBuild": "",
|
"PrivateBuild": "",
|
||||||
"ProductName": "Tactical RMM Agent",
|
"ProductName": "Tactical RMM Agent",
|
||||||
"ProductVersion": "v2.0.1.0",
|
"ProductVersion": "v2.8.0.0",
|
||||||
"SpecialBuild": ""
|
"SpecialBuild": ""
|
||||||
},
|
},
|
||||||
"VarFileInfo": {
|
"VarFileInfo": {
|
||||||
|
|||||||
Reference in New Issue
Block a user