v2.0.0
This commit is contained in:
commit
e455af7f1f
33 changed files with 8471 additions and 0 deletions
300
agent/utils.go
Normal file
300
agent/utils.go
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
/*
|
||||
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 (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
ps "github.com/elastic/go-sysinfo"
|
||||
"github.com/go-ping/ping"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/shirou/gopsutil/v3/process"
|
||||
)
|
||||
|
||||
type PingResponse struct {
|
||||
Status string
|
||||
Output string
|
||||
}
|
||||
|
||||
func DoPing(host string) (PingResponse, error) {
|
||||
var ret PingResponse
|
||||
pinger, err := ping.NewPinger(host)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
pinger.OnRecv = func(pkt *ping.Packet) {
|
||||
fmt.Fprintf(&buf, "%d bytes from %s: icmp_seq=%d time=%v\n",
|
||||
pkt.Nbytes, pkt.IPAddr, pkt.Seq, pkt.Rtt)
|
||||
}
|
||||
|
||||
pinger.OnFinish = func(stats *ping.Statistics) {
|
||||
fmt.Fprintf(&buf, "\n--- %s ping statistics ---\n", stats.Addr)
|
||||
fmt.Fprintf(&buf, "%d packets transmitted, %d packets received, %v%% packet loss\n",
|
||||
stats.PacketsSent, stats.PacketsRecv, stats.PacketLoss)
|
||||
fmt.Fprintf(&buf, "round-trip min/avg/max/stddev = %v/%v/%v/%v\n",
|
||||
stats.MinRtt, stats.AvgRtt, stats.MaxRtt, stats.StdDevRtt)
|
||||
}
|
||||
|
||||
pinger.Count = 3
|
||||
pinger.Size = 24
|
||||
pinger.Interval = time.Second
|
||||
pinger.Timeout = 5 * time.Second
|
||||
pinger.SetPrivileged(true)
|
||||
|
||||
err = pinger.Run()
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
ret.Output = buf.String()
|
||||
|
||||
stats := pinger.Statistics()
|
||||
|
||||
if stats.PacketsRecv == stats.PacketsSent || stats.PacketLoss == 0 {
|
||||
ret.Status = "passing"
|
||||
} else {
|
||||
ret.Status = "failing"
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// PublicIP returns the agent's public ip
|
||||
// Tries 3 times before giving up
|
||||
func (a *Agent) PublicIP() string {
|
||||
a.Logger.Debugln("PublicIP start")
|
||||
client := resty.New()
|
||||
client.SetTimeout(4 * time.Second)
|
||||
if len(a.Proxy) > 0 {
|
||||
client.SetProxy(a.Proxy)
|
||||
}
|
||||
urls := []string{"https://icanhazip.tacticalrmm.io/", "https://icanhazip.com", "https://ifconfig.co/ip"}
|
||||
ip := "error"
|
||||
|
||||
for _, url := range urls {
|
||||
r, err := client.R().Get(url)
|
||||
if err != nil {
|
||||
a.Logger.Debugln("PublicIP err", err)
|
||||
continue
|
||||
}
|
||||
ip = StripAll(r.String())
|
||||
if !IsValidIP(ip) {
|
||||
a.Logger.Debugln("PublicIP not valid", ip)
|
||||
continue
|
||||
}
|
||||
v4 := net.ParseIP(ip)
|
||||
if v4.To4() == nil {
|
||||
r1, err := client.R().Get("https://ifconfig.me/ip")
|
||||
if err != nil {
|
||||
return ip
|
||||
}
|
||||
ipv4 := StripAll(r1.String())
|
||||
if !IsValidIP(ipv4) {
|
||||
continue
|
||||
}
|
||||
a.Logger.Debugln("Forcing ipv4:", ipv4)
|
||||
return ipv4
|
||||
}
|
||||
a.Logger.Debugln("PublicIP return: ", ip)
|
||||
break
|
||||
}
|
||||
return ip
|
||||
}
|
||||
|
||||
// GenerateAgentID creates and returns a unique agent id
|
||||
func GenerateAgentID() string {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
b := make([]rune, 40)
|
||||
for i := range b {
|
||||
b[i] = letters[rand.Intn(len(letters))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// ShowVersionInfo prints basic debugging info
|
||||
func ShowVersionInfo(ver string) {
|
||||
fmt.Println("Tactical RMM Agent:", ver)
|
||||
fmt.Println("Arch:", runtime.GOARCH)
|
||||
fmt.Println("Go version:", runtime.Version())
|
||||
if runtime.GOOS == "windows" {
|
||||
fmt.Println("Program Directory:", filepath.Join(os.Getenv("ProgramFiles"), progFilesName))
|
||||
}
|
||||
}
|
||||
|
||||
// TotalRAM returns total RAM in GB
|
||||
func (a *Agent) TotalRAM() float64 {
|
||||
host, err := ps.Host()
|
||||
if err != nil {
|
||||
return 8.0
|
||||
}
|
||||
mem, err := host.Memory()
|
||||
if err != nil {
|
||||
return 8.0
|
||||
}
|
||||
return math.Ceil(float64(mem.Total) / 1073741824.0)
|
||||
}
|
||||
|
||||
// BootTime returns system boot time as a unix timestamp
|
||||
func (a *Agent) BootTime() int64 {
|
||||
host, err := ps.Host()
|
||||
if err != nil {
|
||||
return 1000
|
||||
}
|
||||
info := host.Info()
|
||||
return info.BootTime.Unix()
|
||||
}
|
||||
|
||||
// IsValidIP checks for a valid ipv4 or ipv6
|
||||
func IsValidIP(ip string) bool {
|
||||
return net.ParseIP(ip) != nil
|
||||
}
|
||||
|
||||
// StripAll strips all whitespace and newline chars
|
||||
func StripAll(s string) string {
|
||||
s = strings.TrimSpace(s)
|
||||
s = strings.Trim(s, "\n")
|
||||
s = strings.Trim(s, "\r")
|
||||
return s
|
||||
}
|
||||
|
||||
// KillProc kills a process and its children
|
||||
func KillProc(pid int32) error {
|
||||
p, err := process.NewProcess(pid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
children, err := p.Children()
|
||||
if err == nil {
|
||||
for _, child := range children {
|
||||
if err := child.Kill(); err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := p.Kill(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DjangoStringResp removes double quotes from django rest api resp
|
||||
func DjangoStringResp(resp string) string {
|
||||
return strings.Trim(resp, `"`)
|
||||
}
|
||||
|
||||
func TestTCP(addr string) error {
|
||||
conn, err := net.Dial("tcp4", addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanString removes invalid utf-8 byte sequences
|
||||
func CleanString(s string) string {
|
||||
r := strings.NewReplacer("\x00", "")
|
||||
s = r.Replace(s)
|
||||
return strings.ToValidUTF8(s, "")
|
||||
}
|
||||
|
||||
// https://golangcode.com/unzip-files-in-go/
|
||||
func Unzip(src, dest string) error {
|
||||
r, err := zip.OpenReader(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
for _, f := range r.File {
|
||||
|
||||
// Store filename/path for returning and using later on
|
||||
fpath := filepath.Join(dest, f.Name)
|
||||
|
||||
// Check for ZipSlip. More Info: http://bit.ly/2MsjAWE
|
||||
if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
|
||||
return fmt.Errorf("%s: illegal file path", fpath)
|
||||
}
|
||||
|
||||
if f.FileInfo().IsDir() {
|
||||
// Make Folder
|
||||
os.MkdirAll(fpath, os.ModePerm)
|
||||
continue
|
||||
}
|
||||
|
||||
// Make File
|
||||
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(outFile, rc)
|
||||
|
||||
// Close the file without defer to close before next iteration of loop
|
||||
outFile.Close()
|
||||
rc.Close()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// https://yourbasic.org/golang/formatting-byte-size-to-human-readable-format/
|
||||
func ByteCountSI(b uint64) string {
|
||||
const unit = 1024
|
||||
if b < unit {
|
||||
return fmt.Sprintf("%d B", b)
|
||||
}
|
||||
div, exp := int64(unit), 0
|
||||
for n := b / unit; n >= unit; n /= unit {
|
||||
div *= unit
|
||||
exp++
|
||||
}
|
||||
return fmt.Sprintf("%.1f %cB",
|
||||
float64(b)/float64(div), "kMGTPE"[exp])
|
||||
}
|
||||
|
||||
func randRange(min, max int) int {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
return rand.Intn(max-min) + min
|
||||
}
|
||||
|
||||
func randomCheckDelay() {
|
||||
time.Sleep(time.Duration(randRange(300, 950)) * time.Millisecond)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue