258 lines
5.7 KiB
Go
258 lines
5.7 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"math/rand"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/jmoiron/sqlx"
|
|
_ "github.com/lib/pq"
|
|
nats "github.com/nats-io/nats.go"
|
|
"github.com/ugorji/go/codec"
|
|
)
|
|
|
|
type JsonFile struct {
|
|
Agents []string `json:"agents"`
|
|
Key string `json:"key"`
|
|
NatsURL string `json:"natsurl"`
|
|
}
|
|
|
|
type DjangoConfig struct {
|
|
Key string `json:"key"`
|
|
NatsURL string `json:"natsurl"`
|
|
User string `json:"user"`
|
|
Pass string `json:"pass"`
|
|
Host string `json:"host"`
|
|
Port int `json:"port"`
|
|
DBName string `json:"dbname"`
|
|
}
|
|
|
|
type Agent struct {
|
|
ID int `db:"id"`
|
|
AgentID string `db:"agent_id"`
|
|
}
|
|
|
|
type Recovery struct {
|
|
Func string `json:"func"`
|
|
Data map[string]string `json:"payload"`
|
|
}
|
|
|
|
func setupNatsOptions(key string) []nats.Option {
|
|
opts := []nats.Option{
|
|
nats.Name("TacticalRMM"),
|
|
nats.UserInfo("tacticalrmm", key),
|
|
nats.ReconnectWait(time.Second * 2),
|
|
nats.RetryOnFailedConnect(true),
|
|
nats.MaxReconnects(3),
|
|
nats.ReconnectBufSize(-1),
|
|
}
|
|
return opts
|
|
}
|
|
|
|
func CheckIn(file string) {
|
|
agents, db, r, err := GetAgents(file)
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
|
|
var payload []byte
|
|
ret := codec.NewEncoderBytes(&payload, new(codec.MsgpackHandle))
|
|
ret.Encode(map[string]string{"func": "ping"})
|
|
|
|
opts := setupNatsOptions(r.Key)
|
|
|
|
nc, err := nats.Connect(r.NatsURL, opts...)
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
defer nc.Close()
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(len(agents))
|
|
|
|
loc, _ := time.LoadLocation("UTC")
|
|
now := time.Now().In(loc)
|
|
|
|
for _, a := range agents {
|
|
go func(id string, pk int, nc *nats.Conn, wg *sync.WaitGroup, db *sqlx.DB, now time.Time) {
|
|
defer wg.Done()
|
|
|
|
var resp string
|
|
var mh codec.MsgpackHandle
|
|
mh.RawToString = true
|
|
|
|
time.Sleep(time.Duration(randRange(100, 1500)) * time.Millisecond)
|
|
out, err := nc.Request(id, payload, 1*time.Second)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
dec := codec.NewDecoderBytes(out.Data, &mh)
|
|
if err := dec.Decode(&resp); err == nil {
|
|
if resp == "pong" {
|
|
_, err = db.NamedExec(
|
|
`UPDATE agents_agent SET last_seen=:lastSeen WHERE agents_agent.id=:pk`,
|
|
map[string]interface{}{"lastSeen": now, "pk": pk},
|
|
)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
}
|
|
}
|
|
}(a.AgentID, a.ID, nc, &wg, db, now)
|
|
}
|
|
wg.Wait()
|
|
db.Close()
|
|
}
|
|
|
|
func GetAgents(file string) (agents []Agent, db *sqlx.DB, r DjangoConfig, err error) {
|
|
jret, _ := ioutil.ReadFile(file)
|
|
err = json.Unmarshal(jret, &r)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
psqlInfo := fmt.Sprintf("host=%s port=%d user=%s "+
|
|
"password=%s dbname=%s sslmode=disable",
|
|
r.Host, r.Port, r.User, r.Pass, r.DBName)
|
|
|
|
db, err = sqlx.Connect("postgres", psqlInfo)
|
|
if err != nil {
|
|
return
|
|
}
|
|
db.SetMaxOpenConns(15)
|
|
|
|
agent := Agent{}
|
|
rows, err := db.Queryx("SELECT agents_agent.id, agents_agent.agent_id FROM agents_agent")
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
for rows.Next() {
|
|
err := rows.StructScan(&agent)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
agents = append(agents, agent)
|
|
}
|
|
return
|
|
}
|
|
|
|
func AgentInfo(file string) {
|
|
agents, db, r, err := GetAgents(file)
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
|
|
var payload []byte
|
|
ret := codec.NewEncoderBytes(&payload, new(codec.MsgpackHandle))
|
|
ret.Encode(map[string]string{"func": "agentinfo"})
|
|
|
|
opts := setupNatsOptions(r.Key)
|
|
|
|
nc, err := nats.Connect(r.NatsURL, opts...)
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
defer nc.Close()
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(len(agents))
|
|
|
|
for _, a := range agents {
|
|
go func(id string, pk int, nc *nats.Conn, wg *sync.WaitGroup, db *sqlx.DB) {
|
|
defer wg.Done()
|
|
|
|
var r AgentInfoRet
|
|
var mh codec.MsgpackHandle
|
|
mh.RawToString = true
|
|
|
|
time.Sleep(time.Duration(randRange(100, 1500)) * time.Millisecond)
|
|
out, err := nc.Request(id, payload, 1*time.Second)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
dec := codec.NewDecoderBytes(out.Data, &mh)
|
|
if err := dec.Decode(&r); err == nil {
|
|
stmt := `
|
|
UPDATE agents_agent
|
|
SET version=$1, hostname=$2, operating_system=$3,
|
|
plat=$4, total_ram=$5, boot_time=$6, needs_reboot=$7, logged_in_username=$8
|
|
WHERE agents_agent.id=$9;`
|
|
|
|
_, err = db.Exec(stmt, r.Version, r.Hostname, r.OS, r.Platform, r.TotalRAM, r.BootTime, r.RebootNeeded, r.Username, pk)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
|
|
if r.Username != "None" {
|
|
stmt = `UPDATE agents_agent SET last_logged_in_user=$1 WHERE agents_agent.id=$2;`
|
|
_, err = db.Exec(stmt, r.Username, pk)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
}
|
|
}
|
|
}(a.AgentID, a.ID, nc, &wg, db)
|
|
}
|
|
wg.Wait()
|
|
db.Close()
|
|
}
|
|
|
|
func GetWMI(file string) {
|
|
var result JsonFile
|
|
var payload []byte
|
|
var mh codec.MsgpackHandle
|
|
mh.RawToString = true
|
|
ret := codec.NewEncoderBytes(&payload, new(codec.MsgpackHandle))
|
|
ret.Encode(map[string]string{"func": "wmi"})
|
|
|
|
jret, _ := ioutil.ReadFile(file)
|
|
err := json.Unmarshal(jret, &result)
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
|
|
opts := setupNatsOptions(result.Key)
|
|
|
|
nc, err := nats.Connect(result.NatsURL, opts...)
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
defer nc.Close()
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(len(result.Agents))
|
|
|
|
for _, id := range result.Agents {
|
|
go func(id string, nc *nats.Conn, wg *sync.WaitGroup) {
|
|
defer wg.Done()
|
|
time.Sleep(time.Duration(randRange(0, 28)) * time.Second)
|
|
nc.Publish(id, payload)
|
|
}(id, nc, &wg)
|
|
}
|
|
wg.Wait()
|
|
}
|
|
|
|
func randRange(min, max int) int {
|
|
rand.Seed(time.Now().UnixNano())
|
|
return rand.Intn(max-min) + min
|
|
}
|
|
|
|
type AgentInfoRet struct {
|
|
AgentPK int `json:"id"`
|
|
Version string `json:"version"`
|
|
Username string `json:"logged_in_username"`
|
|
Hostname string `json:"hostname"`
|
|
OS string `json:"operating_system"`
|
|
Platform string `json:"plat"`
|
|
TotalRAM float64 `json:"total_ram"`
|
|
BootTime int64 `json:"boot_time"`
|
|
RebootNeeded bool `json:"needs_reboot"`
|
|
}
|