commit de82595181c9977959f45aade2162d6bb5ab61c8 Author: wh1te909 Date: Mon Oct 5 14:35:16 2020 -0700 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..57d3551 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.exe +*.db +*.log +*.7z +tacticalrmm \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..90a28f7 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,25 @@ +{ + "go.useLanguageServer": true, + "[go]": { + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": true, + }, + // Optional: Disable snippets, as they conflict with completion ranking. + "editor.snippetSuggestions": "none", + }, + "[go.mod]": { + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": true, + }, + }, + "gopls": { + // Add parameter placeholders when completing a function. + "usePlaceholders": true, + + // If true, enable additional analyses with staticcheck. + // Warning: This will significantly increase memory usage. + "staticcheck": true, + } +} \ No newline at end of file diff --git a/agent/agent_windows.go b/agent/agent_windows.go new file mode 100644 index 0000000..2410524 --- /dev/null +++ b/agent/agent_windows.go @@ -0,0 +1,254 @@ +// Package agent todo change this +package agent + +import ( + "database/sql" + "fmt" + "os" + "path/filepath" + "runtime" + + ps "github.com/elastic/go-sysinfo" + _ "github.com/mattn/go-sqlite3" + svc "github.com/shirou/gopsutil/winservices" + "github.com/sirupsen/logrus" +) + +// WindowsAgent struct +type WindowsAgent struct { + DB + Host + ProgramDir string + SystemDrive string + SaltCall string + Nssm string + SaltMinion string + SaltInstaller string + MeshInstaller string + Headers map[string]string + Logger *logrus.Logger + Version string +} + +// New __init__ +func New(logger *logrus.Logger, version string) *WindowsAgent { + host, _ := ps.Host() + info := host.Info() + pd := filepath.Join(os.Getenv("ProgramFiles"), "TacticalAgent") + dbFile := filepath.Join(pd, "agentdb.db") + sd := os.Getenv("SystemDrive") + sc := filepath.Join(sd, "\\salt\\salt-call.bat") + nssm, mesh, saltexe, saltinstaller := ArchInfo(pd) + db := LoadDB(dbFile, logger) + + headers := make(map[string]string) + if len(db.Token) > 0 { + headers["Content-Type"] = "application/json" + headers["Authorization"] = fmt.Sprintf("Token %s", db.Token) + } + + return &WindowsAgent{ + DB: DB{ + db.Server, + db.AgentID, + db.MeshNodeID, + db.Token, + db.AgentPK, + db.SaltMaster, + db.SaltID, + db.Cert, + }, + Host: Host{ + Hostname: info.Hostname, + Arch: info.Architecture, + Timezone: info.Timezone, + }, + ProgramDir: pd, + SystemDrive: sd, + SaltCall: sc, + Nssm: nssm, + SaltMinion: saltexe, + SaltInstaller: saltinstaller, + MeshInstaller: mesh, + Headers: headers, + Logger: logger, + Version: version, + } + +} + +// LoadDB loads database info called during agent init +func LoadDB(file string, logger *logrus.Logger) *DB { + if !FileExists(file) { + return &DB{} + } + + db, err := sql.Open("sqlite3", file) + if err != nil { + logger.Fatalln(err) + } + defer db.Close() + + rows, err := db. + Query("select server, agentid, mesh_node_id, token, agentpk, salt_master, salt_id, cert from agentstorage") + if err != nil { + logger.Fatalln(err) + } + defer rows.Close() + + var ( + server string + agentid string + meshid string + token string + pk int32 + saltmaster string + saltid string + cert *string + ) + for rows.Next() { + err = rows. + Scan(&server, &agentid, &meshid, &token, &pk, &saltmaster, &saltid, &cert) + if err != nil { + logger.Fatalln(err) + } + } + + var ret string + if cert != nil { + ret = *cert + } + + err = rows.Err() + if err != nil { + logger.Fatalln(err) + } + + return &DB{server, agentid, meshid, token, pk, saltmaster, saltid, ret} +} + +// ArchInfo returns arch specific filenames and urls +func ArchInfo(programDir string) (nssm, mesh, saltexe, saltinstaller string) { + baseURL := "https://github.com/wh1te909/winagent/raw/master/bin/" + switch runtime.GOARCH { + case "amd64": + nssm = filepath.Join(programDir, "nssm.exe") + mesh = "meshagent.exe" + saltexe = baseURL + "salt-minion-setup.exe" + saltinstaller = "salt-minion-setup.exe" + case "386": + nssm = filepath.Join(programDir, "nssm-x86.exe") + mesh = "meshagent-x86.exe" + saltexe = baseURL + "salt-minion-setup-x86.exe" + saltinstaller = "salt-minion-setup-x86.exe" + } + return +} + +// OSInfo returns os names formatted +func OSInfo() (plat, osFullName string) { + host, _ := ps.Host() + info := host.Info() + os := info.OS + + var arch string + switch info.Architecture { + case "x86_64": + arch = "64 bit" + case "x86": + arch = "32 bit" + } + + plat = os.Platform + osFullName = fmt.Sprintf("%s, %s (build %s)", os.Name, arch, os.Build) + return +} + +// https://docs.microsoft.com/en-us/dotnet/api/system.serviceprocess.servicecontrollerstatus?view=dotnet-plat-ext-3.1 +func serviceStatusText(num uint32) string { + switch num { + case 1: + return "stopped" + case 2: + return "start_pending" + case 3: + return "stop_pending" + case 4: + return "running" + case 5: + return "continue_pending" + case 6: + return "pause_pending" + case 7: + return "paused" + default: + return "unknown" + } +} + +// WindowsService holds windows service info +type WindowsService struct { + Name string `json:"name"` + Status string `json:"status"` + DisplayName string `json:"display_name"` + BinPath string `json:"binpath"` + Description string `json:"description"` + Username string `json:"username"` + PID uint32 `json:"pid"` +} + +// GetServices returns a list of windows services +func (a *WindowsAgent) GetServices() []WindowsService { + ret := make([]WindowsService, 0) + services, err := svc.ListServices() + if err != nil { + a.Logger.Debugln(err) + return ret + } + + for _, s := range services { + srv, err := svc.NewService(s.Name) + if err == nil { + derr := srv.GetServiceDetail() + conf, qerr := srv.QueryServiceConfig() + if derr == nil && qerr == nil { + winsvc := WindowsService{ + Name: s.Name, + Status: serviceStatusText(uint32(srv.Status.State)), + DisplayName: conf.DisplayName, + BinPath: conf.BinaryPathName, + Description: conf.Description, + Username: conf.ServiceStartName, + PID: uint32(srv.Status.Pid), + } + ret = append(ret, winsvc) + } else { + if derr != nil { + a.Logger.Debugln(derr) + } + if qerr != nil { + a.Logger.Debugln(qerr) + } + } + } + } + return ret +} + +//RecoverSalt recovers salt minion +func (a *WindowsAgent) RecoverSalt() { + a.Logger.Debugln("Attempting salt recovery on", a.Hostname) + // TODO +} + +//RecoverMesh recovers mesh agent +func (a *WindowsAgent) RecoverMesh() { + a.Logger.Debugln("Attempting mesh recovery on", a.Hostname) + // TODO +} + +//RecoverCMD runs shell recovery command +func (a *WindowsAgent) RecoverCMD(cmd string) { + a.Logger.Debugln("Attempting shell recovery on", a.Hostname) + // TODO +} diff --git a/agent/host.go b/agent/host.go new file mode 100644 index 0000000..7b96e78 --- /dev/null +++ b/agent/host.go @@ -0,0 +1,20 @@ +package agent + +// Host struct +type Host struct { + Hostname string `json:"hostname"` + Arch string `json:"arch"` + Timezone string `json:"timezone"` +} + +// DB sqlite database stores RMM and agent info +type DB struct { + Server string + AgentID string + MeshNodeID string + Token string + AgentPK int32 + SaltMaster string + SaltID string + Cert string +} diff --git a/agent/service_windows.go b/agent/service_windows.go new file mode 100644 index 0000000..e4e81b9 --- /dev/null +++ b/agent/service_windows.go @@ -0,0 +1,119 @@ +package agent + +import ( + "encoding/json" + "math/rand" + "strings" + "time" +) + +//HelloPost post +type HelloPost struct { + Agentid string `json:"agent_id"` + Hostname string `json:"hostname"` + OS string `json:"operating_system"` + TotalRAM float64 `json:"total_ram"` + Platform string `json:"plat"` + Version string `json:"version"` + BootTime int64 `json:"boot_time"` + SaltVersion string `json:"salt_ver"` +} + +//HelloPatch patch +type HelloPatch struct { + Agentid string `json:"agent_id"` + Services []WindowsService `json:"services"` + PublicIP string `json:"public_ip"` + Disks string + Username string + Version string `json:"version"` + BootTime int64 `json:"boot_time"` +} + +// RunAsService tacticalagent windows nssm service +func (a *WindowsAgent) RunAsService() { + a.Logger.Infoln("Agent service started") + var data map[string]interface{} + var sleep int + + url := a.Server + "/api/v2/hello/" + plat, osinfo := OSInfo() + + postPayload := HelloPost{ + Agentid: a.AgentID, + Hostname: a.Hostname, + OS: osinfo, + TotalRAM: TotalRAM(), + Platform: plat, + Version: a.Version, + BootTime: BootTime(), + SaltVersion: a.GetProgramVersion("salt minion"), + } + + request1 := &APIRequest{ + URL: url, + Method: "POST", + Payload: postPayload, + Headers: a.Headers, + Timeout: 15, + } + + _, err := MakeRequest(request1) + if err != nil { + a.Logger.Debugln(err) + } + + time.Sleep(3 * time.Second) + + for { + patchPayload := HelloPatch{ + Agentid: a.AgentID, + Services: a.GetServices(), + PublicIP: "todo", + Disks: "todo", + Username: "todo", + Version: a.Version, + BootTime: BootTime(), + } + + request2 := &APIRequest{ + URL: url, + Method: "PATCH", + Payload: patchPayload, + Headers: a.Headers, + Timeout: 15, + } + + r, err := MakeRequest(request2) + if err != nil { + a.Logger.Debugln(err) + } else { + ret := strings.Trim(r.String(), `"`) + if len(ret) > 0 && ret != "ok" { + if err := json.Unmarshal(r.Body(), &data); err != nil { + a.Logger.Debugln(err) + } else { + if action, ok := data["recovery"].(string); ok { + switch action { + case "salt": + go a.RecoverSalt() + case "mesh": + go a.RecoverMesh() + case "command": + if cmd, ok := data["cmd"].(string); ok { + go a.RecoverCMD(cmd) + } + } + } + } + } + } + sleep = randRange(30, 120) + time.Sleep(time.Duration(sleep) * time.Second) + } +} + +func randRange(min, max int) int { + rand.Seed(time.Now().UnixNano()) + return rand.Intn(max-min) + min +} diff --git a/agent/software_windows_386.go b/agent/software_windows_386.go new file mode 100644 index 0000000..44ede6d --- /dev/null +++ b/agent/software_windows_386.go @@ -0,0 +1,124 @@ +package agent + +import ( + "fmt" + "strings" + "time" + + so "github.com/iamacarpet/go-win64api/shared" + "golang.org/x/sys/windows/registry" +) + +// GetProgramVersion loops through the registry for software +// and if found, returns its version +func (a *WindowsAgent) GetProgramVersion(name string) string { + sw, err := InstalledSoftwareList() + if err != nil { + a.Logger.Debugf("%s\r\n", err.Error()) + return "0.0.0" + } + + var lowerName string + for _, s := range sw { + lowerName = strings.ToLower(s.Name()) + if strings.Contains(lowerName, name) { + return s.Version() + } + } + return "0.0.0" +} + +func InstalledSoftwareList() ([]so.Software, error) { + sw32, err := getSoftwareList(`SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall`, "X32") + if err != nil { + return nil, err + } + + return sw32, nil +} + +// https://github.com/iamacarpet/go-win64api/blob/master/software.go +func getSoftwareList(baseKey string, arch string) ([]so.Software, error) { + k, err := registry.OpenKey(registry.LOCAL_MACHINE, baseKey, registry.QUERY_VALUE|registry.ENUMERATE_SUB_KEYS) + if err != nil { + return nil, fmt.Errorf("Error reading from registry: %s", err.Error()) + } + defer k.Close() + + swList := make([]so.Software, 0) + + subkeys, err := k.ReadSubKeyNames(-1) + if err != nil { + return nil, fmt.Errorf("Error reading subkey list from registry: %s", err.Error()) + } + for _, sw := range subkeys { + sk, err := registry.OpenKey(registry.LOCAL_MACHINE, baseKey+`\`+sw, registry.QUERY_VALUE) + if err != nil { + return nil, fmt.Errorf("Error reading from registry (subkey %s): %s", sw, err.Error()) + } + + dn, _, err := sk.GetStringValue("DisplayName") + if err == nil { + swv := so.Software{DisplayName: dn, Arch: arch} + + dv, _, err := sk.GetStringValue("DisplayVersion") + if err == nil { + swv.DisplayVersion = dv + } + + pub, _, err := sk.GetStringValue("Publisher") + if err == nil { + swv.Publisher = pub + } + + id, _, err := sk.GetStringValue("InstallDate") + if err == nil { + swv.InstallDate, _ = time.Parse("20060102", id) + } + + es, _, err := sk.GetIntegerValue("EstimatedSize") + if err == nil { + swv.EstimatedSize = es + } + + cont, _, err := sk.GetStringValue("Contact") + if err == nil { + swv.Contact = cont + } + + hlp, _, err := sk.GetStringValue("HelpLink") + if err == nil { + swv.HelpLink = hlp + } + + isource, _, err := sk.GetStringValue("InstallSource") + if err == nil { + swv.InstallSource = isource + } + + ilocaction, _, err := sk.GetStringValue("InstallLocation") + if err == nil { + swv.InstallLocation = ilocaction + } + + ustring, _, err := sk.GetStringValue("UninstallString") + if err == nil { + swv.UninstallString = ustring + } + + mver, _, err := sk.GetIntegerValue("VersionMajor") + if err == nil { + swv.VersionMajor = mver + } + + mnver, _, err := sk.GetIntegerValue("VersionMinor") + if err == nil { + swv.VersionMinor = mnver + } + + swList = append(swList, swv) + } + } + + return swList, nil +} diff --git a/agent/software_windows_amd64.go b/agent/software_windows_amd64.go new file mode 100644 index 0000000..1b4dae0 --- /dev/null +++ b/agent/software_windows_amd64.go @@ -0,0 +1,26 @@ +package agent + +import ( + "strings" + + wapi "github.com/iamacarpet/go-win64api" +) + +// GetProgramVersion loops through the registry for software +// and if found, returns its version +func (a *WindowsAgent) GetProgramVersion(name string) string { + sw, err := wapi.InstalledSoftwareList() + if err != nil { + a.Logger.Debugf("%s\r\n", err.Error()) + return "0.0.0" + } + + var lowerName string + for _, s := range sw { + lowerName = strings.ToLower(s.Name()) + if strings.Contains(lowerName, name) { + return s.Version() + } + } + return "0.0.0" +} diff --git a/agent/utils.go b/agent/utils.go new file mode 100644 index 0000000..e04b2f3 --- /dev/null +++ b/agent/utils.go @@ -0,0 +1,101 @@ +package agent + +import ( + "fmt" + "math" + "math/rand" + "os" + "path/filepath" + "runtime" + "time" + + ps "github.com/elastic/go-sysinfo" + "github.com/go-resty/resty/v2" +) + +var client = resty.New() + +// APIRequest struct +type APIRequest struct { + URL string + Method string + Payload interface{} + Headers map[string]string + Timeout time.Duration + LocalCert string +} + +// MakeRequest creates an api request to the RMM +func MakeRequest(r *APIRequest) (*resty.Response, error) { + client.SetCloseConnection(true) + client.SetHeaders(r.Headers) + client.SetTimeout(r.Timeout * time.Second) + + if len(r.LocalCert) > 0 { + client.SetRootCertificate(r.LocalCert) + } + + var resp *resty.Response + var err error + + switch r.Method { + case "GET": + resp, err = client.R().Get(r.URL) + case "POST": + resp, err = client.R().SetBody(r.Payload).Post(r.URL) + case "PATCH": + resp, err = client.R().SetBody(r.Payload).Patch(r.URL) + case "PUT": + resp, err = client.R().SetBody(r.Payload).Put(r.URL) + } + + if err != nil { + return &resty.Response{}, err + } + return resp, nil +} + +// GenerateAgentID creates and returns a unique agent id +func GenerateAgentID(hostname string) string { + rand.Seed(time.Now().UnixNano()) + letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + b := make([]rune, 35) + for i := range b { + b[i] = letters[rand.Intn(len(letters))] + } + return string(b) + "-" + hostname +} + +// 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"), "TacticalAgent")) + } +} + +// FileExists checks whether a file exists +func FileExists(path string) bool { + if _, err := os.Stat(path); err != nil { + if os.IsNotExist(err) { + return false + } + } + return true +} + +// TotalRAM returns total RAM in GB +func TotalRAM() float64 { + host, _ := ps.Host() + mem, _ := host.Memory() + return math.Ceil(float64(mem.Total) / 1073741824.0) +} + +// BootTime returns system boot time as a unix timestamp +func BootTime() int64 { + host, _ := ps.Host() + info := host.Info() + return info.BootTime.Unix() +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..eb3c889 --- /dev/null +++ b/go.mod @@ -0,0 +1,15 @@ +module github.com/wh1te909/rmmagent + +go 1.15 + +require ( + github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d + github.com/elastic/go-sysinfo v1.4.0 + github.com/go-ole/go-ole v1.2.4 // indirect + github.com/go-resty/resty/v2 v2.3.0 + github.com/iamacarpet/go-win64api v0.0.0-20200715182619-8cbc936e1a5a + github.com/mattn/go-sqlite3 v1.14.3 + github.com/shirou/gopsutil v2.20.9-0.20200919012454-c5b7357407cb+incompatible + github.com/sirupsen/logrus v1.7.0 + golang.org/x/sys v0.0.0-20200926100807-9d91bd62050c +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a433d70 --- /dev/null +++ b/go.sum @@ -0,0 +1,49 @@ +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/elastic/go-sysinfo v1.4.0 h1:LUnK6TNOuy8JEByuDzTAQH3iQ6bIywy55+Z+QlKNSWk= +github.com/elastic/go-sysinfo v1.4.0/go.mod h1:i1ZYdU10oLNfRzq4vq62BEwD2fH8KaWh6eh0ikPT9F0= +github.com/elastic/go-windows v1.0.0 h1:qLURgZFkkrYyTTkvYpsZIgf83AUsdIHfvlJaqaZ7aSY= +github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU= +github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= +github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= +github.com/go-resty/resty/v2 v2.3.0 h1:JOOeAvjSlapTT92p8xiS19Zxev1neGikoHsXJeOq8So= +github.com/go-resty/resty/v2 v2.3.0/go.mod h1:UpN9CgLZNsv4e9XG50UU8xdI0F43UQ4HmxLBDwaroHU= +github.com/iamacarpet/go-win64api v0.0.0-20200715182619-8cbc936e1a5a h1:PotlHkUHYTVAjZTW1HbXw43luragnCAI0XzyZ5Tek3k= +github.com/iamacarpet/go-win64api v0.0.0-20200715182619-8cbc936e1a5a/go.mod h1:oGJx9dz0Ny7HC7U55RZ0Smd6N9p3hXP/+hOFtuYrAxM= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8l6qbCUTSiRLG/iKnW3K3/QfPPuSsBt4= +github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mattn/go-sqlite3 v1.14.3 h1:j7a/xn1U6TKA/PHHxqZuzh64CdtRc7rU9M+AvkOl5bA= +github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/shirou/gopsutil v2.20.9-0.20200919012454-c5b7357407cb+incompatible h1:7dLbOHoySZBvGTAvf6lUgEb1o4cIj6n8FUpMjpLAX+0= +github.com/shirou/gopsutil v2.20.9-0.20200919012454-c5b7357407cb+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120 h1:EZ3cVSzKOlJxAd8e8YAJ7no8nNypTxexh/YE/xW3ZEY= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200622182413-4b0db7f3f76b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200926100807-9d91bd62050c h1:38q6VNPWR010vN82/SB121GujZNIfAUb4YttE2rhGuc= +golang.org/x/sys v0.0.0-20200926100807-9d91bd62050c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M= +howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= diff --git a/main.go b/main.go new file mode 100644 index 0000000..2b1b510 --- /dev/null +++ b/main.go @@ -0,0 +1,72 @@ +package main + +// cross compile from linux for windows +// apt install build-essential gcc-multilib gcc-mingw-w64-x86-64 gcc-mingw-w64-i686 +// 64 bit: CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ GOOS=windows GOARCH=amd64 go build -o tacticalrmm.exe +// 32 bit: CGO_ENABLED=1 CC=i686-w64-mingw32-gcc CXX=i686-w64-mingw32-g++ GOOS=windows GOARCH=386 go build -o tacticalrmm-x86.exe + +// building 32 bit from windows from git bash +// env CGO_ENABLED=1 CC=i686-w64-mingw32-gcc CXX=i686-w64-mingw32-g++ GOARCH=386 go build -o tacticalrmm-x86.exe + +import ( + "flag" + "os" + + "github.com/sirupsen/logrus" + "github.com/wh1te909/rmmagent/agent" +) + +var ( + version = "1.0.0" + log = logrus.New() + logFile *os.File +) + +func main() { + ver := flag.Bool("version", false, "Prints version") + mode := flag.String("m", "", "The mode to run") + logLevel := flag.String("log", "INFO", "The log level") + logTo := flag.String("logto", "file", "Where to log to") + flag.Parse() + + if *ver { + agent.ShowVersionInfo(version) + return + } + + if len(os.Args) == 1 { + // TODO show agent status + return + } + + setupLogging(logLevel, logTo) + defer logFile.Close() + + a := *agent.New(log, version) + + switch *mode { + case "install": + log.SetOutput(os.Stdout) + // TODO + return + case "winagentsvc": + a.RunAsService() + default: + // TODO + } +} + +func setupLogging(level *string, to *string) { + ll, err := logrus.ParseLevel(*level) + if err != nil { + ll = logrus.InfoLevel + } + log.SetLevel(ll) + + if *to == "stdout" { + log.SetOutput(os.Stdout) + } else { + logFile, _ = os.OpenFile("agent.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0664) + log.SetOutput(logFile) + } +}