commit e455af7f1f89dc90e32671a2fce76d5f664c3841 Author: wh1te909 Date: Sat Mar 19 11:55:43 2022 -0700 v2.0.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..363cf4a --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +*.exe +*.db +*.log +*.7z +*.zip +*.py +*.sh +*.syso +*.ico +*.bmp +build/Output +tacticalagent-v* diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f094099 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,21 @@ +{ + "go.useLanguageServer": true, + "[go]": { + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": false, + }, + "editor.snippetSuggestions": "none", + }, + "[go.mod]": { + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": true, + }, + }, + "gopls": { + "usePlaceholders": true, + "completeUnimported": true, + "staticcheck": true, + } +} \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..530804b --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,74 @@ +### Tactical RMM License Version 1.0 + +Text of license:   Copyright © 2022 AmidaWare LLC. All rights reserved.
+          Amending the text of this license is not permitted. + +Trade Mark:    "Tactical RMM" is a trade mark of AmidaWare LLC. + +Licensor:       AmidaWare LLC 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). + +### 1. Preamble +The Licensed Software is designed to facilitate the remote monitoring and management (RMM) of networks, systems, servers, computers and other devices. The Licensed Software is made available primarily for use by organisations and managed service providers for monitoring and management purposes. + +The Tactical RMM License is not an open-source software license. This license contains certain restrictions on the use of the Licensed Software. For example the functionality of the Licensed Software may not be made available as part of a SaaS (Software-as-a-Service) service or product to provide a commercial or for-profit service without the express prior permission of the Licensor. + +### 2. License Grant +Permission is hereby granted, free of charge, on a non-exclusive basis, to copy, modify, create derivative works and use the Licensed Software in source and binary forms subject to the following terms and conditions. No additional rights will be implied under this license. + +* The hosting and use of the Licensed Software to monitor and manage in-house networks/systems and/or customer networks/systems is permitted. + +This license does not allow the functionality of the Licensed Software (whether in whole or in part) or a modified version of the Licensed Software or a derivative work to be used or otherwise made available as part of any other commercial or for-profit service, including, without limitation, any of the following: +* a service allowing third parties to interact remotely through a computer network; +* as part of a SaaS service or product; +* as part of the provision of a managed hosting service or product; +* the offering of installation and/or configuration services; +* the offer for sale, distribution or sale of any service or product (whether or not branded as Tactical RMM). + +The prior written approval of AmidaWare LLC must be obtained for all commercial use and/or for-profit service use of the (i) Licensed Software (whether in whole or in part), (ii) a modified version of the Licensed Software and/or (iii) a derivative work. + +The terms of this license apply to all copies of the Licensed Software (including modified versions) and derivative works. + +All use of the Licensed Software must immediately cease if use breaches the terms of this license. + +### 3. Derivative Works +If a derivative work is created which is based on or otherwise incorporates all or any part of the Licensed Software, and the derivative work is made available to any other person, the complete corresponding machine readable source code (including all changes made to the Licensed Software) must accompany the derivative work and be made publicly available online. + +### 4. Copyright Notice +The following copyright notice shall be included in all copies of the Licensed Software: + +   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 + +### 5. Disclaimer of Warranty +THE LICENSED SOFTWARE IS PROVIDED "AS IS". TO THE FULLEST EXTENT PERMISSIBLE AT LAW ALL CONDITIONS, WARRANTIES OR OTHER TERMS OF ANY KIND WHICH MIGHT HAVE EFFECT OR BE IMPLIED OR INCORPORATED, WHETHER BY STATUTE, COMMON LAW OR OTHERWISE ARE HEREBY EXCLUDED, INCLUDING THE CONDITIONS, WARRANTIES OR OTHER TERMS AS TO SATISFACTORY QUALITY AND/OR MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, THE USE OF REASONABLE SKILL AND CARE AND NON-INFRINGEMENT. + +### 6. Limits of Liability +THE FOLLOWING EXCLUSIONS SHALL APPLY TO THE FULLEST EXTENT PERMISSIBLE AT LAW. NEITHER THE AUTHORS NOR THE COPYRIGHT HOLDERS SHALL IN ANY CIRCUMSTANCES HAVE ANY LIABILITY FOR ANY CLAIM, LOSSES, DAMAGES OR OTHER LIABILITY, WHETHER THE SAME ARE SUFFERED DIRECTLY OR INDIRECTLY OR ARE IMMEDIATE OR CONSEQUENTIAL, AND WHETHER THE SAME ARISE IN CONTRACT, TORT OR DELICT (INCLUDING NEGLIGENCE) OR OTHERWISE HOWSOEVER ARISING FROM, OUT OF OR IN CONNECTION WITH THE LICENSED SOFTWARE OR THE USE OR INABILITY TO USE THE LICENSED SOFTWARE OR OTHER DEALINGS IN THE LICENSED SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH LOSS OR DAMAGE. THE FOREGOING EXCLUSIONS SHALL INCLUDE, WITHOUT LIMITATION, LIABILITY FOR ANY LOSSES OR DAMAGES WHICH FALL WITHIN ANY OF THE FOLLOWING CATEGORIES: SPECIAL, EXEMPLARY, OR INCIDENTAL LOSS OR DAMAGE, LOSS OF PROFITS, LOSS OF ANTICIPATED SAVINGS, LOSS OF BUSINESS OPPORTUNITY, LOSS OF GOODWILL, AND LOSS OR CORRUPTION OF DATA. + +### 7. Termination +This license shall terminate with immediate effect if there is a material breach of any of its terms. + +### 8. No partnership, agency or joint venture +Nothing in this license agreement is intended to, or shall be deemed to, establish any partnership or joint venture or any relationship of agency between AmidaWare LLC and any other person. + +### 9. No endorsement +The names of the authors and/or the copyright holders must not be used to promote or endorse any products or services which are in any way derived from the Licensed Software without prior written consent. + +### 10. Trademarks +No permission is granted to use the trademark “Tactical RMM” or any other trade name, trademark, service mark or product name of AmidaWare LLC except to the extent necessary to comply with the notice requirements in Section 4 (Copyright Notice). + +### 11. Entire agreement +This license contains the whole agreement relating to its subject matter. + + + +### 12. Severance +If any provision or part-provision of this license is or becomes invalid, illegal or unenforceable, it shall be deemed deleted, but that shall not affect the validity and enforceability of the rest of this license. + +### 13. Acceptance of these terms +The terms and conditions of this license are accepted by copying, downloading, installing, redistributing, or otherwise using the Licensed Software. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d065784 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +### Tactical RMM Agent +https://github.com/amidaware/tacticalrmm + +#### building the agent +``` +env CGO_ENABLED=0 GOOS= GOARCH= go build -ldflags "-s -w" +``` + + diff --git a/agent/agent.go b/agent/agent.go new file mode 100644 index 0000000..567d56a --- /dev/null +++ b/agent/agent.go @@ -0,0 +1,470 @@ +/* +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 ( + "bytes" + "context" + "errors" + "fmt" + "io/ioutil" + "math" + "os" + "os/exec" + "path/filepath" + "runtime" + "strconv" + "strings" + + "time" + + rmm "github.com/amidaware/rmmagent/shared" + ps "github.com/elastic/go-sysinfo" + gocmd "github.com/go-cmd/cmd" + "github.com/go-resty/resty/v2" + "github.com/kardianos/service" + nats "github.com/nats-io/nats.go" + "github.com/shirou/gopsutil/v3/cpu" + "github.com/sirupsen/logrus" + trmm "github.com/wh1te909/trmm-shared" +) + +// Agent struct +type Agent struct { + Hostname string + Arch string + AgentID string + BaseURL string + ApiURL string + Token string + AgentPK int + Cert string + ProgramDir string + EXE string + SystemDrive string + MeshInstaller string + MeshSystemEXE string + MeshSVC string + PyBin string + Headers map[string]string + Logger *logrus.Logger + Version string + Debug bool + rClient *resty.Client + Proxy string + LogTo string + LogFile *os.File + Platform string + GoArch string + ServiceConfig *service.Config +} + +const ( + progFilesName = "TacticalAgent" + winExeName = "tacticalrmm.exe" + winSvcName = "tacticalrmm" + meshSvcName = "mesh agent" +) + +var natsCheckin = []string{"agent-hello", "agent-agentinfo", "agent-disks", "agent-winsvc", "agent-publicip", "agent-wmi"} + +func New(logger *logrus.Logger, version string) *Agent { + host, _ := ps.Host() + info := host.Info() + pd := filepath.Join(os.Getenv("ProgramFiles"), progFilesName) + exe := filepath.Join(pd, winExeName) + sd := os.Getenv("SystemDrive") + + var pybin string + switch runtime.GOARCH { + case "amd64": + pybin = filepath.Join(pd, "py38-x64", "python.exe") + case "386": + pybin = filepath.Join(pd, "py38-x32", "python.exe") + } + + ac := NewAgentConfig() + + headers := make(map[string]string) + if len(ac.Token) > 0 { + headers["Content-Type"] = "application/json" + headers["Authorization"] = fmt.Sprintf("Token %s", ac.Token) + } + + restyC := resty.New() + restyC.SetBaseURL(ac.BaseURL) + restyC.SetCloseConnection(true) + restyC.SetHeaders(headers) + restyC.SetTimeout(15 * time.Second) + restyC.SetDebug(logger.IsLevelEnabled(logrus.DebugLevel)) + + if len(ac.Proxy) > 0 { + restyC.SetProxy(ac.Proxy) + } + if len(ac.Cert) > 0 { + restyC.SetRootCertificate(ac.Cert) + } + + var MeshSysExe string + if len(ac.CustomMeshDir) > 0 { + MeshSysExe = filepath.Join(ac.CustomMeshDir, "MeshAgent.exe") + } else { + MeshSysExe = filepath.Join(os.Getenv("ProgramFiles"), "Mesh Agent", "MeshAgent.exe") + } + + if runtime.GOOS == "linux" { + MeshSysExe = "/opt/tacticalmesh/meshagent" + } + + svcConf := &service.Config{ + Executable: exe, + Name: winSvcName, + DisplayName: "TacticalRMM Agent Service", + Arguments: []string{"-m", "svc"}, + Description: "TacticalRMM Agent Service", + Option: service.KeyValue{ + "StartType": "automatic", + "OnFailure": "restart", + "OnFailureDelayDuration": "5s", + "OnFailureResetPeriod": 10, + }, + } + + return &Agent{ + Hostname: info.Hostname, + Arch: info.Architecture, + BaseURL: ac.BaseURL, + AgentID: ac.AgentID, + ApiURL: ac.APIURL, + Token: ac.Token, + AgentPK: ac.PK, + Cert: ac.Cert, + ProgramDir: pd, + EXE: exe, + SystemDrive: sd, + MeshInstaller: "meshagent.exe", + MeshSystemEXE: MeshSysExe, + MeshSVC: meshSvcName, + PyBin: pybin, + Headers: headers, + Logger: logger, + Version: version, + Debug: logger.IsLevelEnabled(logrus.DebugLevel), + rClient: restyC, + Proxy: ac.Proxy, + Platform: runtime.GOOS, + GoArch: runtime.GOARCH, + ServiceConfig: svcConf, + } +} + +type CmdStatus struct { + Status gocmd.Status + Stdout string + Stderr string +} + +type CmdOptions struct { + Shell string + Command string + Args []string + Timeout time.Duration + IsScript bool + IsExecutable bool + Detached bool +} + +func (a *Agent) NewCMDOpts() *CmdOptions { + return &CmdOptions{ + Shell: "/bin/bash", + Timeout: 30, + } +} + +func (a *Agent) CmdV2(c *CmdOptions) CmdStatus { + + ctx, cancel := context.WithTimeout(context.Background(), c.Timeout*time.Second) + defer cancel() + + // Disable output buffering, enable streaming + cmdOptions := gocmd.Options{ + Buffered: false, + Streaming: true, + } + + // have a child process that is in a different process group so that + // parent terminating doesn't kill child + if c.Detached { + cmdOptions.BeforeExec = []func(cmd *exec.Cmd){ + func(cmd *exec.Cmd) { + cmd.SysProcAttr = SetDetached() + }, + } + } + + var envCmd *gocmd.Cmd + if c.IsScript { + envCmd = gocmd.NewCmdOptions(cmdOptions, c.Shell, c.Args...) // call script directly + } else if c.IsExecutable { + envCmd = gocmd.NewCmdOptions(cmdOptions, c.Shell, c.Command) // c.Shell: bin + c.Command: args as one string + } else { + envCmd = gocmd.NewCmdOptions(cmdOptions, c.Shell, "-c", c.Command) // /bin/bash -c 'ls -l /var/log/...' + } + + var stdoutBuf bytes.Buffer + var stderrBuf bytes.Buffer + // Print STDOUT and STDERR lines streaming from Cmd + doneChan := make(chan struct{}) + go func() { + defer close(doneChan) + // Done when both channels have been closed + // https://dave.cheney.net/2013/04/30/curious-channels + for envCmd.Stdout != nil || envCmd.Stderr != nil { + select { + case line, open := <-envCmd.Stdout: + if !open { + envCmd.Stdout = nil + continue + } + fmt.Fprintln(&stdoutBuf, line) + a.Logger.Debugln(line) + + case line, open := <-envCmd.Stderr: + if !open { + envCmd.Stderr = nil + continue + } + fmt.Fprintln(&stderrBuf, line) + a.Logger.Debugln(line) + } + } + }() + + // Run and wait for Cmd to return, discard Status + envCmd.Start() + + go func() { + select { + case <-doneChan: + return + case <-ctx.Done(): + a.Logger.Debugf("Command timed out after %d seconds\n", c.Timeout) + pid := envCmd.Status().PID + a.Logger.Debugln("Killing process with PID", pid) + KillProc(int32(pid)) + } + }() + + // Wait for goroutine to print everything + <-doneChan + ret := CmdStatus{ + Status: envCmd.Status(), + Stdout: CleanString(stdoutBuf.String()), + Stderr: CleanString(stderrBuf.String()), + } + a.Logger.Debugf("%+v\n", ret) + return ret +} + +func (a *Agent) GetCPULoadAvg() int { + fallback := false + pyCode := ` +import psutil +try: + print(int(round(psutil.cpu_percent(interval=10))), end='') +except: + print("pyerror", end='') +` + pypercent, err := a.RunPythonCode(pyCode, 13, []string{}) + if err != nil || pypercent == "pyerror" { + fallback = true + } + + i, err := strconv.Atoi(pypercent) + if err != nil { + fallback = true + } + + if fallback { + percent, err := cpu.Percent(10*time.Second, false) + if err != nil { + a.Logger.Debugln("Go CPU Check:", err) + return 0 + } + return int(math.Round(percent[0])) + } + return i +} + +// ForceKillMesh kills all mesh agent related processes +func (a *Agent) ForceKillMesh() { + pids := make([]int, 0) + + procs, err := ps.Processes() + if err != nil { + return + } + + for _, process := range procs { + p, err := process.Info() + if err != nil { + continue + } + if strings.Contains(strings.ToLower(p.Name), "meshagent") { + pids = append(pids, p.PID) + } + } + + for _, pid := range pids { + a.Logger.Debugln("Killing mesh process with pid %d", pid) + if err := KillProc(int32(pid)); err != nil { + a.Logger.Debugln(err) + } + } +} + +func (a *Agent) SyncMeshNodeID() { + + id, err := a.getMeshNodeID() + if err != nil { + a.Logger.Errorln("SyncMeshNodeID() getMeshNodeID()", err) + return + } + + payload := rmm.MeshNodeID{ + Func: "syncmesh", + Agentid: a.AgentID, + NodeID: StripAll(id), + } + + _, err = a.rClient.R().SetBody(payload).Post("/api/v3/syncmesh/") + if err != nil { + a.Logger.Debugln("SyncMesh:", err) + } +} + +func (a *Agent) setupNatsOptions() []nats.Option { + opts := make([]nats.Option, 0) + opts = append(opts, nats.Name("TacticalRMM")) + opts = append(opts, nats.UserInfo(a.AgentID, a.Token)) + opts = append(opts, nats.ReconnectWait(time.Second*5)) + opts = append(opts, nats.RetryOnFailedConnect(true)) + opts = append(opts, nats.MaxReconnects(-1)) + opts = append(opts, nats.ReconnectBufSize(-1)) + return opts +} + +func (a *Agent) GetUninstallExe() string { + cderr := os.Chdir(a.ProgramDir) + if cderr == nil { + files, err := filepath.Glob("unins*.exe") + if err == nil { + for _, f := range files { + if strings.Contains(f, "001") { + return f + } + } + } + } + return "unins000.exe" +} + +func (a *Agent) CleanupAgentUpdates() { + cderr := os.Chdir(a.ProgramDir) + if cderr != nil { + a.Logger.Errorln(cderr) + return + } + + files, err := filepath.Glob("winagent-v*.exe") + if err == nil { + for _, f := range files { + os.Remove(f) + } + } + + cderr = os.Chdir(os.Getenv("TMP")) + if cderr != nil { + a.Logger.Errorln(cderr) + return + } + folders, err := filepath.Glob("tacticalrmm*") + if err == nil { + for _, f := range folders { + os.RemoveAll(f) + } + } +} + +func (a *Agent) RunPythonCode(code string, timeout int, args []string) (string, error) { + content := []byte(code) + dir, err := ioutil.TempDir("", "tacticalpy") + if err != nil { + a.Logger.Debugln(err) + return "", err + } + defer os.RemoveAll(dir) + + tmpfn, _ := ioutil.TempFile(dir, "*.py") + if _, err := tmpfn.Write(content); err != nil { + a.Logger.Debugln(err) + return "", err + } + if err := tmpfn.Close(); err != nil { + a.Logger.Debugln(err) + return "", err + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + defer cancel() + + var outb, errb bytes.Buffer + cmdArgs := []string{tmpfn.Name()} + if len(args) > 0 { + cmdArgs = append(cmdArgs, args...) + } + a.Logger.Debugln(cmdArgs) + cmd := exec.CommandContext(ctx, a.PyBin, cmdArgs...) + cmd.Stdout = &outb + cmd.Stderr = &errb + + cmdErr := cmd.Run() + + if ctx.Err() == context.DeadlineExceeded { + a.Logger.Debugln("RunPythonCode:", ctx.Err()) + return "", ctx.Err() + } + + if cmdErr != nil { + a.Logger.Debugln("RunPythonCode:", cmdErr) + return "", cmdErr + } + + if errb.String() != "" { + a.Logger.Debugln(errb.String()) + return errb.String(), errors.New("RunPythonCode stderr") + } + + return outb.String(), nil + +} + +func (a *Agent) CreateTRMMTempDir() { + // create the temp dir for running scripts + dir := filepath.Join(os.TempDir(), "trmm") + if !trmm.FileExists(dir) { + err := os.Mkdir(dir, 0775) + if err != nil { + a.Logger.Errorln(err) + } + } +} diff --git a/agent/agent_linux.go b/agent/agent_linux.go new file mode 100644 index 0000000..473eb71 --- /dev/null +++ b/agent/agent_linux.go @@ -0,0 +1,462 @@ +/* +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) { + content := []byte(code) + + f, err := os.CreateTemp("", "trmm") + if err != nil { + a.Logger.Errorln(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 := os.CreateTemp("", "") + if err != nil { + a.Logger.Errorln("AgentUpdate()", 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 := os.CreateTemp("", "trmm") + if err != nil { + a.Logger.Errorln("AgentUninstall CreateTemp:", 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 + if meshNodeID == "" { + time.Sleep(1 * time.Second) + continue + } else if strings.Contains(strings.ToLower(meshNodeID), "graphical version") || strings.Contains(strings.ToLower(meshNodeID), "zenity") { + a.Logger.Debugln(out.Stdout) + 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 } diff --git a/agent/agent_windows.go b/agent/agent_windows.go new file mode 100644 index 0000000..0912cd2 --- /dev/null +++ b/agent/agent_windows.go @@ -0,0 +1,853 @@ +/* +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 ( + "bytes" + "context" + "errors" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "runtime" + "strconv" + "strings" + "sync" + "syscall" + "time" + "unsafe" + + rmm "github.com/amidaware/rmmagent/shared" + ps "github.com/elastic/go-sysinfo" + "github.com/go-ole/go-ole" + "github.com/go-ole/go-ole/oleutil" + "github.com/go-resty/resty/v2" + "github.com/gonutz/w32/v2" + "github.com/kardianos/service" + "github.com/shirou/gopsutil/v3/disk" + wapf "github.com/wh1te909/go-win64api" + trmm "github.com/wh1te909/trmm-shared" + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/registry" +) + +var ( + getDriveType = windows.NewLazySystemDLL("kernel32.dll").NewProc("GetDriveTypeW") +) + +func NewAgentConfig() *rmm.AgentConfig { + k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\TacticalRMM`, registry.ALL_ACCESS) + if err != nil { + return &rmm.AgentConfig{} + } + + baseurl, _, _ := k.GetStringValue("BaseURL") + agentid, _, _ := k.GetStringValue("AgentID") + apiurl, _, _ := k.GetStringValue("ApiURL") + token, _, _ := k.GetStringValue("Token") + agentpk, _, _ := k.GetStringValue("AgentPK") + pk, _ := strconv.Atoi(agentpk) + cert, _, _ := k.GetStringValue("Cert") + proxy, _, _ := k.GetStringValue("Proxy") + customMeshDir, _, _ := k.GetStringValue("MeshDir") + + return &rmm.AgentConfig{ + BaseURL: baseurl, + AgentID: agentid, + APIURL: apiurl, + Token: token, + AgentPK: agentpk, + PK: pk, + Cert: cert, + Proxy: proxy, + CustomMeshDir: customMeshDir, + } +} + +func (a *Agent) RunScript(code string, shell string, args []string, timeout int) (stdout, stderr string, exitcode int, e error) { + + content := []byte(code) + + dir := filepath.Join(os.TempDir(), "trmm") + if !trmm.FileExists(dir) { + a.CreateTRMMTempDir() + } + + const defaultExitCode = 1 + + var ( + outb bytes.Buffer + errb bytes.Buffer + exe string + ext string + cmdArgs []string + ) + + switch shell { + case "powershell": + ext = "*.ps1" + case "python": + ext = "*.py" + case "cmd": + ext = "*.bat" + } + + tmpfn, err := ioutil.TempFile(dir, ext) + if err != nil { + a.Logger.Errorln(err) + return "", err.Error(), 85, err + } + defer os.Remove(tmpfn.Name()) + + if _, err := tmpfn.Write(content); err != nil { + a.Logger.Errorln(err) + return "", err.Error(), 85, err + } + if err := tmpfn.Close(); err != nil { + a.Logger.Errorln(err) + return "", err.Error(), 85, err + } + + switch shell { + case "powershell": + exe = "Powershell" + cmdArgs = []string{"-NonInteractive", "-NoProfile", "-ExecutionPolicy", "Bypass", tmpfn.Name()} + case "python": + exe = a.PyBin + cmdArgs = []string{tmpfn.Name()} + case "cmd": + exe = tmpfn.Name() + } + + if len(args) > 0 { + cmdArgs = append(cmdArgs, args...) + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + defer cancel() + + var timedOut bool = false + cmd := exec.Command(exe, cmdArgs...) + cmd.Stdout = &outb + cmd.Stderr = &errb + + if cmdErr := cmd.Start(); cmdErr != nil { + a.Logger.Debugln(cmdErr) + return "", cmdErr.Error(), 65, cmdErr + } + pid := int32(cmd.Process.Pid) + + // custom context handling, we need to kill child procs if this is a batch script, + // otherwise it will hang forever + // the normal exec.CommandContext() doesn't work since it only kills the parent process + go func(p int32) { + + <-ctx.Done() + + _ = KillProc(p) + timedOut = true + }(pid) + + cmdErr := cmd.Wait() + + if timedOut { + stdout = CleanString(outb.String()) + stderr = fmt.Sprintf("%s\nScript timed out after %d seconds", CleanString(errb.String()), timeout) + exitcode = 98 + a.Logger.Debugln("Script check timeout:", ctx.Err()) + } else { + stdout = CleanString(outb.String()) + stderr = CleanString(errb.String()) + + // get the exit code + if cmdErr != nil { + if exitError, ok := cmdErr.(*exec.ExitError); ok { + if ws, ok := exitError.Sys().(syscall.WaitStatus); ok { + exitcode = ws.ExitStatus() + } else { + exitcode = defaultExitCode + } + } else { + exitcode = defaultExitCode + } + + } else { + if ws, ok := cmd.ProcessState.Sys().(syscall.WaitStatus); ok { + exitcode = ws.ExitStatus() + } else { + exitcode = 0 + } + } + } + return stdout, stderr, exitcode, nil +} + +func SetDetached() *syscall.SysProcAttr { + return &syscall.SysProcAttr{ + CreationFlags: windows.DETACHED_PROCESS | windows.CREATE_NEW_PROCESS_GROUP, + } +} + +func CMD(exe string, args []string, timeout int, detached bool) (output [2]string, e error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + defer cancel() + + var outb, errb bytes.Buffer + cmd := exec.CommandContext(ctx, exe, args...) + if detached { + cmd.SysProcAttr = &windows.SysProcAttr{ + CreationFlags: windows.DETACHED_PROCESS | windows.CREATE_NEW_PROCESS_GROUP, + } + } + cmd.Stdout = &outb + cmd.Stderr = &errb + err := cmd.Run() + if err != nil { + return [2]string{"", ""}, fmt.Errorf("%s: %s", err, CleanString(errb.String())) + } + + if ctx.Err() == context.DeadlineExceeded { + return [2]string{"", ""}, ctx.Err() + } + + 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) { + var ( + outb bytes.Buffer + errb bytes.Buffer + cmd *exec.Cmd + timedOut = false + ) + + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + defer cancel() + + if len(cmdArgs) > 0 && command == "" { + switch shell { + case "cmd": + cmdArgs = append([]string{"/C"}, cmdArgs...) + cmd = exec.Command("cmd.exe", cmdArgs...) + case "powershell": + cmdArgs = append([]string{"-NonInteractive", "-NoProfile"}, cmdArgs...) + cmd = exec.Command("powershell.exe", cmdArgs...) + } + } else { + switch shell { + case "cmd": + cmd = exec.Command("cmd.exe") + cmd.SysProcAttr = &windows.SysProcAttr{ + CmdLine: fmt.Sprintf("cmd.exe /C %s", command), + } + case "powershell": + cmd = exec.Command("Powershell", "-NonInteractive", "-NoProfile", command) + } + } + + // https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags + if detached { + cmd.SysProcAttr = &windows.SysProcAttr{ + CreationFlags: windows.DETACHED_PROCESS | windows.CREATE_NEW_PROCESS_GROUP, + } + } + cmd.Stdout = &outb + cmd.Stderr = &errb + cmd.Start() + + pid := int32(cmd.Process.Pid) + + go func(p int32) { + + <-ctx.Done() + + _ = KillProc(p) + timedOut = true + }(pid) + + err := cmd.Wait() + + if timedOut { + return [2]string{CleanString(outb.String()), CleanString(errb.String())}, ctx.Err() + } + + if err != nil { + return [2]string{CleanString(outb.String()), CleanString(errb.String())}, err + } + + return [2]string{CleanString(outb.String()), CleanString(errb.String())}, nil +} + +// GetDisks returns a list of fixed disks +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 { + typepath, _ := windows.UTF16PtrFromString(p.Device) + typeval, _, _ := getDriveType.Call(uintptr(unsafe.Pointer(typepath))) + // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getdrivetypea + if typeval != 3 { + 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 +} + +// LoggedOnUser returns the first logged on user it finds +func (a *Agent) LoggedOnUser() string { + pyCode := ` +import psutil + +try: + u = psutil.users()[0].name + if u.isascii(): + print(u, end='') + else: + print('notascii', end='') +except Exception as e: + print("None", end='') + +` + // try with psutil first, if fails, fallback to golang + user, err := a.RunPythonCode(pyCode, 5, []string{}) + if err == nil && user != "notascii" { + return user + } + + users, err := wapf.ListLoggedInUsers() + if err != nil { + a.Logger.Debugln("LoggedOnUser error", err) + return "None" + } + + if len(users) == 0 { + return "None" + } + + for _, u := range users { + // remove the computername or domain + return strings.Split(u.FullUser(), `\`)[1] + } + return "None" +} + +// ShowStatus prints windows service status +// If called from an interactive desktop, pops up a message box +// Otherwise prints to the console +func ShowStatus(version string) { + statusMap := make(map[string]string) + svcs := []string{winSvcName, meshSvcName} + + for _, service := range svcs { + status, err := GetServiceStatus(service) + if err != nil { + statusMap[service] = "Not Installed" + continue + } + statusMap[service] = status + } + + window := w32.GetForegroundWindow() + if window != 0 { + _, consoleProcID := w32.GetWindowThreadProcessId(window) + if w32.GetCurrentProcessId() == consoleProcID { + w32.ShowWindow(window, w32.SW_HIDE) + } + var handle w32.HWND + msg := fmt.Sprintf("Agent: %s\n\nMesh Agent: %s", statusMap[winSvcName], statusMap[meshSvcName]) + w32.MessageBox(handle, msg, fmt.Sprintf("Tactical RMM v%s", version), w32.MB_OK|w32.MB_ICONINFORMATION) + } else { + fmt.Println("Tactical RMM Version", version) + fmt.Println("Tactical Agent:", statusMap[winSvcName]) + fmt.Println("Mesh Agent:", statusMap[meshSvcName]) + } +} + +// PatchMgmnt enables/disables automatic update +// 0 - Enable Automatic Updates (Default) +// 1 - Disable Automatic Updates +// https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd939844(v=ws.10)?redirectedfrom=MSDN +func (a *Agent) PatchMgmnt(enable bool) error { + var val uint32 + k, _, err := registry.CreateKey(registry.LOCAL_MACHINE, `SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU`, registry.ALL_ACCESS) + if err != nil { + return err + } + + if enable { + val = 1 + } else { + val = 0 + } + + err = k.SetDWordValue("AUOptions", val) + if err != nil { + return err + } + + return nil +} + +func (a *Agent) PlatVer() (string, error) { + k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.ALL_ACCESS) + if err != nil { + return "n/a", err + } + defer k.Close() + + dv, _, err := k.GetStringValue("DisplayVersion") + if err == nil { + return dv, nil + } + + relid, _, err := k.GetStringValue("ReleaseId") + if err != nil { + return "n/a", err + } + return relid, nil +} + +// EnablePing enables ping +func EnablePing() { + args := make([]string, 0) + cmd := `netsh advfirewall firewall add rule name="ICMP Allow incoming V4 echo request" protocol=icmpv4:8,any dir=in action=allow` + _, err := CMDShell("cmd", args, cmd, 10, false) + if err != nil { + fmt.Println(err) + } +} + +// EnableRDP enables Remote Desktop +func EnableRDP() { + k, _, err := registry.CreateKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control\Terminal Server`, registry.ALL_ACCESS) + if err != nil { + fmt.Println(err) + } + defer k.Close() + + err = k.SetDWordValue("fDenyTSConnections", 0) + if err != nil { + fmt.Println(err) + } + + args := make([]string, 0) + cmd := `netsh advfirewall firewall set rule group="remote desktop" new enable=Yes` + _, cerr := CMDShell("cmd", args, cmd, 10, false) + if cerr != nil { + fmt.Println(cerr) + } +} + +// DisableSleepHibernate disables sleep and hibernate +func DisableSleepHibernate() { + k, _, err := registry.CreateKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control\Session Manager\Power`, registry.ALL_ACCESS) + if err != nil { + fmt.Println(err) + } + defer k.Close() + + err = k.SetDWordValue("HiberbootEnabled", 0) + if err != nil { + fmt.Println(err) + } + + args := make([]string, 0) + + var wg sync.WaitGroup + currents := []string{"ac", "dc"} + for _, i := range currents { + wg.Add(1) + go func(c string) { + defer wg.Done() + _, _ = CMDShell("cmd", args, fmt.Sprintf("powercfg /set%svalueindex scheme_current sub_buttons lidaction 0", c), 5, false) + _, _ = CMDShell("cmd", args, fmt.Sprintf("powercfg /x -standby-timeout-%s 0", c), 5, false) + _, _ = CMDShell("cmd", args, fmt.Sprintf("powercfg /x -hibernate-timeout-%s 0", c), 5, false) + _, _ = CMDShell("cmd", args, fmt.Sprintf("powercfg /x -disk-timeout-%s 0", c), 5, false) + _, _ = CMDShell("cmd", args, fmt.Sprintf("powercfg /x -monitor-timeout-%s 0", c), 5, false) + }(i) + } + wg.Wait() + _, _ = CMDShell("cmd", args, "powercfg -S SCHEME_CURRENT", 5, false) +} + +// NewCOMObject creates a new COM object for the specifed ProgramID. +func NewCOMObject(id string) (*ole.IDispatch, error) { + unknown, err := oleutil.CreateObject(id) + if err != nil { + return nil, fmt.Errorf("unable to create initial unknown object: %v", err) + } + defer unknown.Release() + + obj, err := unknown.QueryInterface(ole.IID_IDispatch) + if err != nil { + return nil, fmt.Errorf("unable to create query interface: %v", err) + } + + return obj, nil +} + +// SystemRebootRequired checks whether a system reboot is required. +func (a *Agent) SystemRebootRequired() (bool, error) { + regKeys := []string{ + `SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired`, + } + for _, key := range regKeys { + k, err := registry.OpenKey(registry.LOCAL_MACHINE, key, registry.QUERY_VALUE) + if err == nil { + k.Close() + return true, nil + } else if err != registry.ErrNotExist { + return false, err + } + } + return false, nil +} + +func (a *Agent) SendSoftware() { + sw := a.GetInstalledSoftware() + a.Logger.Debugln(sw) + + payload := map[string]interface{}{"agent_id": a.AgentID, "software": sw} + _, err := a.rClient.R().SetBody(payload).Post("/api/v3/software/") + if err != nil { + a.Logger.Debugln(err) + } +} + +func (a *Agent) UninstallCleanup() { + registry.DeleteKey(registry.LOCAL_MACHINE, `SOFTWARE\TacticalRMM`) + a.PatchMgmnt(false) + a.CleanupAgentUpdates() + CleanupSchedTasks() +} + +func (a *Agent) AgentUpdate(url, inno, version string) { + time.Sleep(time.Duration(randRange(1, 15)) * time.Second) + a.KillHungUpdates() + a.CleanupAgentUpdates() + updater := filepath.Join(a.ProgramDir, inno) + 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(updater).Get(url) + if err != nil { + a.Logger.Errorln(err) + CMD("net", []string{"start", winSvcName}, 10, false) + return + } + if r.IsError() { + a.Logger.Errorln("Download failed with status code", r.StatusCode()) + CMD("net", []string{"start", winSvcName}, 10, false) + return + } + + dir, err := ioutil.TempDir("", "tacticalrmm") + if err != nil { + a.Logger.Errorln("Agentupdate create tempdir:", err) + CMD("net", []string{"start", winSvcName}, 10, false) + return + } + + innoLogFile := filepath.Join(dir, "tacticalrmm.txt") + + args := []string{"/C", updater, "/VERYSILENT", fmt.Sprintf("/LOG=%s", innoLogFile)} + cmd := exec.Command("cmd.exe", args...) + cmd.SysProcAttr = &windows.SysProcAttr{ + CreationFlags: windows.DETACHED_PROCESS | windows.CREATE_NEW_PROCESS_GROUP, + } + cmd.Start() + time.Sleep(1 * time.Second) +} + +func (a *Agent) osString() string { + host, _ := ps.Host() + info := host.Info() + osInf := info.OS + + var arch string + switch info.Architecture { + case "x86_64": + arch = "64 bit" + case "x86": + arch = "32 bit" + } + + var osFullName string + platver, err := a.PlatVer() + if err != nil { + osFullName = fmt.Sprintf("%s, %s (build %s)", osInf.Name, arch, osInf.Build) + } else { + osFullName = fmt.Sprintf("%s, %s v%s (build %s)", osInf.Name, arch, platver, osInf.Build) + } + return osFullName +} + +func (a *Agent) AgentUninstall(code string) { + a.KillHungUpdates() + tacUninst := filepath.Join(a.ProgramDir, a.GetUninstallExe()) + args := []string{"/C", tacUninst, "/VERYSILENT"} + cmd := exec.Command("cmd.exe", args...) + cmd.SysProcAttr = &windows.SysProcAttr{ + CreationFlags: windows.DETACHED_PROCESS | windows.CREATE_NEW_PROCESS_GROUP, + } + cmd.Start() +} + +func (a *Agent) addDefenderExlusions() { + code := ` +Add-MpPreference -ExclusionPath 'C:\Program Files\TacticalAgent\*' +Add-MpPreference -ExclusionPath 'C:\Windows\Temp\winagent-v*.exe' +Add-MpPreference -ExclusionPath 'C:\Windows\Temp\trmm\*' +Add-MpPreference -ExclusionPath 'C:\Program Files\Mesh Agent\*' +` + _, _, _, err := a.RunScript(code, "powershell", []string{}, 20) + if err != nil { + a.Logger.Debugln(err) + } +} + +// RunMigrations cleans up unused stuff from older agents +func (a *Agent) RunMigrations() { + for _, i := range []string{"nssm.exe", "nssm-x86.exe"} { + nssm := filepath.Join(a.ProgramDir, i) + if trmm.FileExists(nssm) { + os.Remove(nssm) + } + } +} + +func (a *Agent) installMesh(meshbin, exe, proxy string) (string, error) { + var meshNodeID string + meshInstallArgs := []string{"-fullinstall"} + if len(proxy) > 0 { + meshProxy := fmt.Sprintf("--WebProxy=%s", proxy) + meshInstallArgs = append(meshInstallArgs, meshProxy) + } + a.Logger.Debugln("Mesh install args:", meshInstallArgs) + + meshOut, meshErr := CMD(meshbin, meshInstallArgs, int(90), false) + if meshErr != nil { + fmt.Println(meshOut[0]) + fmt.Println(meshOut[1]) + fmt.Println(meshErr) + } + + fmt.Println(meshOut) + a.Logger.Debugln("Sleeping for 5") + time.Sleep(5 * time.Second) + + meshSuccess := false + + for !meshSuccess { + a.Logger.Debugln("Getting mesh node id") + pMesh, pErr := CMD(exe, []string{"-nodeid"}, int(30), false) + if pErr != nil { + a.Logger.Errorln(pErr) + time.Sleep(5 * time.Second) + continue + } + if pMesh[1] != "" { + a.Logger.Errorln(pMesh[1]) + time.Sleep(5 * time.Second) + continue + } + meshNodeID = StripAll(pMesh[0]) + a.Logger.Debugln("Node id:", meshNodeID) + if strings.Contains(strings.ToLower(meshNodeID), "not defined") { + a.Logger.Errorln(meshNodeID) + time.Sleep(5 * time.Second) + continue + } + meshSuccess = true + } + + return meshNodeID, nil +} + +// ChecksRunning prevents duplicate checks from running +// Have to do it this way, can't use atomic because they can run from both rpc and tacticalagent services +func (a *Agent) ChecksRunning() bool { + running := false + procs, err := ps.Processes() + if err != nil { + return running + } + +Out: + for _, process := range procs { + p, err := process.Info() + if err != nil { + continue + } + if p.PID == 0 { + continue + } + if p.Exe != a.EXE { + continue + } + + for _, arg := range p.Args { + if arg == "runchecks" || arg == "checkrunner" { + running = true + break Out + } + } + } + return running +} + +func (a *Agent) GetPython(force bool) { + if trmm.FileExists(a.PyBin) && !force { + 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 { + os.RemoveAll(pyFolder) + } + + 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) + } + + url := fmt.Sprintf("https://github.com/amidaware/rmmagent/releases/download/v2.0.0/%s", archZip) + a.Logger.Debugln(url) + r, err := rClient.R().SetOutput(pyZip).Get(url) + if err != nil { + a.Logger.Errorln("Unable to download py3.zip from github.", err) + return + } + if r.IsError() { + a.Logger.Errorln("Unable to download py3.zip from github. Status code", r.StatusCode()) + return + } + + err = Unzip(pyZip, a.ProgramDir) + if err != nil { + a.Logger.Errorln(err) + } +} + +func (a *Agent) RecoverMesh() { + a.Logger.Infoln("Attempting mesh recovery") + defer CMD("net", []string{"start", a.MeshSVC}, 60, false) + + _, _ = CMD("net", []string{"stop", a.MeshSVC}, 60, false) + a.ForceKillMesh() + a.SyncMeshNodeID() +} + +func (a *Agent) getMeshNodeID() (string, error) { + out, err := CMD(a.MeshSystemEXE, []string{"-nodeid"}, 10, false) + if err != nil { + a.Logger.Debugln(err) + return "", err + } + + stdout := out[0] + stderr := out[1] + + if stderr != "" { + a.Logger.Debugln(stderr) + return "", err + } + + if stdout == "" || strings.Contains(strings.ToLower(StripAll(stdout)), "not defined") { + a.Logger.Debugln("Failed getting mesh node id", stdout) + return "", errors.New("failed to get mesh node id") + } + + return stdout, nil +} + +func (a *Agent) Start(_ service.Service) error { + go a.RunRPC() + return nil +} + +func (a *Agent) Stop(_ service.Service) error { + return nil +} + +func (a *Agent) InstallService() error { + if serviceExists(winSvcName) { + return nil + } + + // skip on first call of inno setup if this is a new install + _, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\TacticalRMM`, registry.ALL_ACCESS) + if err != nil { + return nil + } + + s, err := service.New(a, a.ServiceConfig) + if err != nil { + return err + } + + return service.Control(s, "install") +} + +// TODO add to stub +func (a *Agent) NixMeshNodeID() string { + return "not implemented" +} diff --git a/agent/checkin.go b/agent/checkin.go new file mode 100644 index 0000000..1deebbd --- /dev/null +++ b/agent/checkin.go @@ -0,0 +1,93 @@ +/* +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 ( + "fmt" + "runtime" + "time" + + nats "github.com/nats-io/nats.go" + "github.com/ugorji/go/codec" + trmm "github.com/wh1te909/trmm-shared" +) + +func (a *Agent) NatsMessage(nc *nats.Conn, mode string) { + var resp []byte + var payload interface{} + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + + switch mode { + case "agent-hello": + payload = trmm.CheckInNats{ + Agentid: a.AgentID, + Version: a.Version, + } + case "agent-winsvc": + payload = trmm.WinSvcNats{ + Agentid: a.AgentID, + WinSvcs: a.GetServices(), + } + case "agent-agentinfo": + osinfo := a.osString() + reboot, err := a.SystemRebootRequired() + if err != nil { + reboot = false + } + payload = trmm.AgentInfoNats{ + Agentid: a.AgentID, + Username: a.LoggedOnUser(), + Hostname: a.Hostname, + OS: osinfo, + Platform: runtime.GOOS, + TotalRAM: a.TotalRAM(), + BootTime: a.BootTime(), + RebootNeeded: reboot, + GoArch: a.GoArch, + } + case "agent-wmi": + payload = trmm.WinWMINats{ + Agentid: a.AgentID, + WMI: a.GetWMIInfo(), + } + case "agent-disks": + payload = trmm.WinDisksNats{ + Agentid: a.AgentID, + Disks: a.GetDisks(), + } + case "agent-publicip": + payload = trmm.PublicIPNats{ + Agentid: a.AgentID, + PublicIP: a.PublicIP(), + } + } + + a.Logger.Debugln(mode, payload) + ret.Encode(payload) + nc.PublishRequest(a.AgentID, mode, resp) +} + +func (a *Agent) DoNatsCheckIn() { + opts := a.setupNatsOptions() + server := fmt.Sprintf("tls://%s:4222", a.ApiURL) + nc, err := nats.Connect(server, opts...) + if err != nil { + a.Logger.Errorln(err) + return + } + + for _, s := range natsCheckin { + time.Sleep(time.Duration(randRange(100, 400)) * time.Millisecond) + a.NatsMessage(nc, s) + } + nc.Close() +} diff --git a/agent/checks.go b/agent/checks.go new file mode 100644 index 0000000..faad683 --- /dev/null +++ b/agent/checks.go @@ -0,0 +1,379 @@ +/* +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 ( + "encoding/json" + "fmt" + "math" + "runtime" + "strings" + "sync" + "time" + + rmm "github.com/amidaware/rmmagent/shared" + ps "github.com/elastic/go-sysinfo" + "github.com/go-resty/resty/v2" + "github.com/shirou/gopsutil/v3/disk" +) + +func (a *Agent) CheckRunner() { + sleepDelay := randRange(14, 22) + a.Logger.Debugf("CheckRunner() init sleeping for %v seconds", sleepDelay) + time.Sleep(time.Duration(sleepDelay) * time.Second) + for { + interval, err := a.GetCheckInterval() + if err == nil && !a.ChecksRunning() { + if runtime.GOOS == "windows" { + _, err = CMD(a.EXE, []string{"-m", "checkrunner"}, 600, false) + if err != nil { + a.Logger.Errorln("Checkrunner RunChecks", err) + } + } else { + a.RunChecks(false) + } + } + a.Logger.Debugln("Checkrunner sleeping for", interval) + time.Sleep(time.Duration(interval) * time.Second) + } +} + +func (a *Agent) GetCheckInterval() (int, error) { + r, err := a.rClient.R().SetResult(&rmm.CheckInfo{}).Get(fmt.Sprintf("/api/v3/%s/checkinterval/", a.AgentID)) + if err != nil { + a.Logger.Debugln(err) + return 120, err + } + if r.IsError() { + a.Logger.Debugln("Checkinterval response code:", r.StatusCode()) + return 120, fmt.Errorf("checkinterval response code: %v", r.StatusCode()) + } + interval := r.Result().(*rmm.CheckInfo).Interval + return interval, nil +} + +func (a *Agent) RunChecks(force bool) error { + data := rmm.AllChecks{} + var url string + if force { + url = fmt.Sprintf("/api/v3/%s/runchecks/", a.AgentID) + } else { + url = fmt.Sprintf("/api/v3/%s/checkrunner/", a.AgentID) + } + r, err := a.rClient.R().Get(url) + if err != nil { + a.Logger.Debugln(err) + return err + } + + if r.IsError() { + a.Logger.Debugln("Checkrunner response code:", r.StatusCode()) + return nil + } + + if err := json.Unmarshal(r.Body(), &data); err != nil { + a.Logger.Debugln(err) + return err + } + + var wg sync.WaitGroup + eventLogChecks := make([]rmm.Check, 0) + winServiceChecks := make([]rmm.Check, 0) + + for _, check := range data.Checks { + switch check.CheckType { + case "diskspace": + wg.Add(1) + go func(c rmm.Check, wg *sync.WaitGroup, r *resty.Client) { + defer wg.Done() + randomCheckDelay() + a.SendDiskCheckResult(a.DiskCheck(c), r) + }(check, &wg, a.rClient) + case "cpuload": + wg.Add(1) + go func(c rmm.Check, wg *sync.WaitGroup, r *resty.Client) { + defer wg.Done() + a.CPULoadCheck(c, r) + }(check, &wg, a.rClient) + case "memory": + wg.Add(1) + go func(c rmm.Check, wg *sync.WaitGroup, r *resty.Client) { + defer wg.Done() + randomCheckDelay() + a.MemCheck(c, r) + }(check, &wg, a.rClient) + case "ping": + wg.Add(1) + go func(c rmm.Check, wg *sync.WaitGroup, r *resty.Client) { + defer wg.Done() + randomCheckDelay() + a.SendPingCheckResult(a.PingCheck(c), r) + }(check, &wg, a.rClient) + case "script": + wg.Add(1) + go func(c rmm.Check, wg *sync.WaitGroup, r *resty.Client) { + defer wg.Done() + randomCheckDelay() + a.ScriptCheck(c, r) + }(check, &wg, a.rClient) + case "winsvc": + winServiceChecks = append(winServiceChecks, check) + case "eventlog": + eventLogChecks = append(eventLogChecks, check) + default: + continue + } + } + + if len(winServiceChecks) > 0 { + wg.Add(len(winServiceChecks)) + go func(wg *sync.WaitGroup, r *resty.Client) { + for _, winSvcCheck := range winServiceChecks { + defer wg.Done() + a.SendWinSvcCheckResult(a.WinSvcCheck(winSvcCheck), r) + } + }(&wg, a.rClient) + } + + if len(eventLogChecks) > 0 { + wg.Add(len(eventLogChecks)) + go func(wg *sync.WaitGroup, r *resty.Client) { + for _, evtCheck := range eventLogChecks { + defer wg.Done() + a.EventLogCheck(evtCheck, r) + } + }(&wg, a.rClient) + } + wg.Wait() + return nil +} + +type ScriptCheckResult struct { + ID int `json:"id"` + Stdout string `json:"stdout"` + Stderr string `json:"stderr"` + Retcode int `json:"retcode"` + Runtime float64 `json:"runtime"` +} + +// ScriptCheck runs either bat, powershell or python script +func (a *Agent) ScriptCheck(data rmm.Check, r *resty.Client) { + start := time.Now() + stdout, stderr, retcode, _ := a.RunScript(data.Script.Code, data.Script.Shell, data.ScriptArgs, data.Timeout) + + payload := ScriptCheckResult{ + ID: data.CheckPK, + Stdout: stdout, + Stderr: stderr, + Retcode: retcode, + Runtime: time.Since(start).Seconds(), + } + + _, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/") + if err != nil { + a.Logger.Debugln(err) + } +} + +func (a *Agent) SendDiskCheckResult(payload DiskCheckResult, r *resty.Client) { + _, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/") + if err != nil { + a.Logger.Debugln(err) + } +} + +type DiskCheckResult struct { + ID int `json:"id"` + MoreInfo string `json:"more_info"` + PercentUsed float64 `json:"percent_used"` + Exists bool `json:"exists"` +} + +// DiskCheck checks disk usage +func (a *Agent) DiskCheck(data rmm.Check) (payload DiskCheckResult) { + payload.ID = data.CheckPK + + usage, err := disk.Usage(data.Disk) + if err != nil { + payload.Exists = false + payload.MoreInfo = fmt.Sprintf("Disk %s does not exist", data.Disk) + a.Logger.Debugln("Disk", data.Disk, err) + return + } + + payload.Exists = true + payload.PercentUsed = usage.UsedPercent + payload.MoreInfo = fmt.Sprintf("Total: %s, Free: %s", ByteCountSI(usage.Total), ByteCountSI(usage.Free)) + return +} + +type CPUMemResult struct { + ID int `json:"id"` + Percent int `json:"percent"` +} + +// CPULoadCheck checks avg cpu load +func (a *Agent) CPULoadCheck(data rmm.Check, r *resty.Client) { + payload := CPUMemResult{ID: data.CheckPK, Percent: a.GetCPULoadAvg()} + _, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/") + if err != nil { + a.Logger.Debugln(err) + } +} + +// MemCheck checks mem percentage +func (a *Agent) MemCheck(data rmm.Check, r *resty.Client) { + host, _ := ps.Host() + mem, _ := host.Memory() + percent := (float64(mem.Used) / float64(mem.Total)) * 100 + + payload := CPUMemResult{ID: data.CheckPK, Percent: int(math.Round(percent))} + _, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/") + if err != nil { + a.Logger.Debugln(err) + } +} + +type EventLogCheckResult struct { + ID int `json:"id"` + Log []rmm.EventLogMsg `json:"log"` +} + +func (a *Agent) EventLogCheck(data rmm.Check, r *resty.Client) { + log := make([]rmm.EventLogMsg, 0) + evtLog := a.GetEventLog(data.LogName, data.SearchLastDays) + + for _, i := range evtLog { + if i.EventType == data.EventType { + if !data.EventIDWildcard && !(int(i.EventID) == data.EventID) { + continue + } + + if data.EventSource == "" && data.EventMessage == "" { + if data.EventIDWildcard { + log = append(log, i) + } else if int(i.EventID) == data.EventID { + log = append(log, i) + } else { + continue + } + } + + if data.EventSource != "" && data.EventMessage != "" { + if data.EventIDWildcard { + if strings.Contains(i.Source, data.EventSource) && strings.Contains(i.Message, data.EventMessage) { + log = append(log, i) + } + } else if int(i.EventID) == data.EventID { + if strings.Contains(i.Source, data.EventSource) && strings.Contains(i.Message, data.EventMessage) { + log = append(log, i) + } + } + continue + } + + if data.EventSource != "" && strings.Contains(i.Source, data.EventSource) { + if data.EventIDWildcard { + log = append(log, i) + } else if int(i.EventID) == data.EventID { + log = append(log, i) + } + } + + if data.EventMessage != "" && strings.Contains(i.Message, data.EventMessage) { + if data.EventIDWildcard { + log = append(log, i) + } else if int(i.EventID) == data.EventID { + log = append(log, i) + } + } + } + } + + payload := EventLogCheckResult{ID: data.CheckPK, Log: log} + _, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/") + if err != nil { + a.Logger.Debugln(err) + } +} + +func (a *Agent) SendPingCheckResult(payload rmm.PingCheckResponse, r *resty.Client) { + _, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/") + if err != nil { + a.Logger.Debugln(err) + } +} + +func (a *Agent) PingCheck(data rmm.Check) (payload rmm.PingCheckResponse) { + payload.ID = data.CheckPK + + out, err := DoPing(data.IP) + if err != nil { + a.Logger.Debugln("PingCheck:", err) + payload.Status = "failing" + payload.Output = err.Error() + return + } + + payload.Status = out.Status + payload.Output = out.Output + return +} + +type WinSvcCheckResult struct { + ID int `json:"id"` + MoreInfo string `json:"more_info"` + Status string `json:"status"` +} + +func (a *Agent) SendWinSvcCheckResult(payload WinSvcCheckResult, r *resty.Client) { + _, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/") + if err != nil { + a.Logger.Debugln(err) + } +} + +func (a *Agent) WinSvcCheck(data rmm.Check) (payload WinSvcCheckResult) { + payload.ID = data.CheckPK + + status, err := GetServiceStatus(data.ServiceName) + if err != nil { + if data.PassNotExist { + payload.Status = "passing" + } else { + payload.Status = "failing" + } + payload.MoreInfo = err.Error() + a.Logger.Debugln("Service", data.ServiceName, err) + return + } + + payload.MoreInfo = fmt.Sprintf("Status: %s", status) + if status == "running" { + payload.Status = "passing" + } else if status == "start_pending" && data.PassStartPending { + payload.Status = "passing" + } else { + if data.RestartIfStopped { + ret := a.ControlService(data.ServiceName, "start") + if ret.Success { + payload.Status = "passing" + payload.MoreInfo = "Status: running" + } else { + payload.Status = "failing" + } + } else { + payload.Status = "failing" + } + } + return +} diff --git a/agent/choco_windows.go b/agent/choco_windows.go new file mode 100644 index 0000000..92b9e72 --- /dev/null +++ b/agent/choco_windows.go @@ -0,0 +1,71 @@ +/* +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 ( + "time" + + rmm "github.com/amidaware/rmmagent/shared" + "github.com/go-resty/resty/v2" +) + +func (a *Agent) InstallChoco() { + + var result rmm.ChocoInstalled + result.AgentID = a.AgentID + result.Installed = false + + rClient := resty.New() + rClient.SetTimeout(30 * time.Second) + if len(a.Proxy) > 0 { + rClient.SetProxy(a.Proxy) + } + + url := "/api/v3/choco/" + r, err := rClient.R().Get("https://chocolatey.org/install.ps1") + if err != nil { + a.Logger.Debugln(err) + a.rClient.R().SetBody(result).Post(url) + return + } + if r.IsError() { + a.rClient.R().SetBody(result).Post(url) + return + } + + _, _, exitcode, err := a.RunScript(string(r.Body()), "powershell", []string{}, 900) + if err != nil { + a.Logger.Debugln(err) + a.rClient.R().SetBody(result).Post(url) + return + } + + if exitcode != 0 { + a.rClient.R().SetBody(result).Post(url) + return + } + + result.Installed = true + a.rClient.R().SetBody(result).Post(url) +} + +func (a *Agent) InstallWithChoco(name string) (string, error) { + out, err := CMD("choco.exe", []string{"install", name, "--yes", "--force", "--force-dependencies"}, 1200, false) + if err != nil { + a.Logger.Errorln(err) + return err.Error(), err + } + if out[1] != "" { + return out[1], nil + } + return out[0], nil +} diff --git a/agent/eventlog_windows.go b/agent/eventlog_windows.go new file mode 100644 index 0000000..c1313ef --- /dev/null +++ b/agent/eventlog_windows.go @@ -0,0 +1,186 @@ +/* +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 ( + "fmt" + "strings" + "syscall" + "time" + "unicode/utf16" + "unsafe" + + rmm "github.com/amidaware/rmmagent/shared" + "github.com/gonutz/w32/v2" + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/registry" +) + +func (a *Agent) GetEventLog(logName string, searchLastDays int) []rmm.EventLogMsg { + var ( + oldestLog uint32 + nextSize uint32 + readBytes uint32 + ) + buf := []byte{0} + size := uint32(1) + + ret := make([]rmm.EventLogMsg, 0) + startTime := time.Now().Add(time.Duration(-(time.Duration(searchLastDays)) * (24 * time.Hour))) + + h := w32.OpenEventLog("", logName) + defer w32.CloseEventLog(h) + + numRecords, _ := w32.GetNumberOfEventLogRecords(h) + GetOldestEventLogRecord(h, &oldestLog) + + startNum := numRecords + oldestLog - 1 + uid := 0 + for i := startNum; i >= oldestLog; i-- { + flags := EVENTLOG_BACKWARDS_READ | EVENTLOG_SEEK_READ + + err := ReadEventLog(h, flags, i, &buf[0], size, &readBytes, &nextSize) + if err != nil { + if err != windows.ERROR_INSUFFICIENT_BUFFER { + a.Logger.Debugln(err) + break + } + buf = make([]byte, nextSize) + size = nextSize + err = ReadEventLog(h, flags, i, &buf[0], size, &readBytes, &nextSize) + if err != nil { + a.Logger.Debugln(err) + break + } + + } + + r := *(*EVENTLOGRECORD)(unsafe.Pointer(&buf[0])) + + timeWritten := time.Unix(int64(r.TimeWritten), 0) + if searchLastDays != 0 { + if timeWritten.Before(startTime) { + break + } + } + + eventID := r.EventID & 0x0000FFFF + sourceName, _ := bytesToString(buf[unsafe.Sizeof(EVENTLOGRECORD{}):]) + eventType := getEventType(r.EventType) + + off := uint32(0) + args := make([]*byte, uintptr(r.NumStrings)*unsafe.Sizeof((*uint16)(nil))) + for n := 0; n < int(r.NumStrings); n++ { + args[n] = &buf[r.StringOffset+off] + _, boff := bytesToString(buf[r.StringOffset+off:]) + off += boff + 2 + } + + var argsptr uintptr + if r.NumStrings > 0 { + argsptr = uintptr(unsafe.Pointer(&args[0])) + } + message, _ := getResourceMessage(logName, sourceName, r.EventID, argsptr) + + uid++ + eventLogMsg := rmm.EventLogMsg{ + Source: sourceName, + EventType: eventType, + EventID: eventID, + Message: message, + Time: timeWritten.String(), + UID: uid, + } + ret = append(ret, eventLogMsg) + } + return ret +} + +func getEventType(et uint16) string { + switch et { + case windows.EVENTLOG_INFORMATION_TYPE: + return "INFO" + case windows.EVENTLOG_WARNING_TYPE: + return "WARNING" + case windows.EVENTLOG_ERROR_TYPE: + return "ERROR" + case windows.EVENTLOG_SUCCESS: + return "SUCCESS" + case windows.EVENTLOG_AUDIT_SUCCESS: + return "AUDIT_SUCCESS" + case windows.EVENTLOG_AUDIT_FAILURE: + return "AUDIT_FAILURE" + default: + return "Unknown" + } +} + +// https://github.com/mackerelio/go-check-plugins/blob/ad7910fdc45ccb892b5e5fda65ba0956c2b2885d/check-windows-eventlog/lib/check-windows-eventlog.go#L219 +func bytesToString(b []byte) (string, uint32) { + var i int + s := make([]uint16, len(b)/2) + for i = range s { + s[i] = uint16(b[i*2]) + uint16(b[(i*2)+1])<<8 + if s[i] == 0 { + s = s[0:i] + break + } + } + return string(utf16.Decode(s)), uint32(i * 2) +} + +// https://github.com/mackerelio/go-check-plugins/blob/ad7910fdc45ccb892b5e5fda65ba0956c2b2885d/check-windows-eventlog/lib/check-windows-eventlog.go#L232 +func getResourceMessage(providerName, sourceName string, eventID uint32, argsptr uintptr) (string, error) { + regkey := fmt.Sprintf( + "SYSTEM\\CurrentControlSet\\Services\\EventLog\\%s\\%s", + providerName, sourceName) + key, err := registry.OpenKey(registry.LOCAL_MACHINE, regkey, registry.QUERY_VALUE) + if err != nil { + return "", err + } + defer key.Close() + + val, _, err := key.GetStringValue("EventMessageFile") + if err != nil { + return "", err + } + val, err = registry.ExpandString(val) + if err != nil { + return "", err + } + + handle, err := LoadLibraryEx(syscall.StringToUTF16Ptr(val), 0, + DONT_RESOLVE_DLL_REFERENCES|LOAD_LIBRARY_AS_DATAFILE) + if err != nil { + return "", err + } + defer syscall.CloseHandle(handle) + + msgbuf := make([]byte, 1<<16) + numChars, err := FormatMessage( + syscall.FORMAT_MESSAGE_FROM_SYSTEM| + syscall.FORMAT_MESSAGE_FROM_HMODULE| + syscall.FORMAT_MESSAGE_ARGUMENT_ARRAY, + handle, + eventID, + 0, + &msgbuf[0], + uint32(len(msgbuf)), + argsptr) + if err != nil { + return "", err + } + message, _ := bytesToString(msgbuf[:numChars*2]) + message = strings.Replace(message, "\r", "", -1) + message = strings.TrimSuffix(message, "\n") + return message, nil +} diff --git a/agent/install.go b/agent/install.go new file mode 100644 index 0000000..8614794 --- /dev/null +++ b/agent/install.go @@ -0,0 +1,298 @@ +/* +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 ( + "fmt" + "io" + "net/url" + "os" + "path/filepath" + "regexp" + "runtime" + "strconv" + "strings" + "time" + + "github.com/go-resty/resty/v2" + trmm "github.com/wh1te909/trmm-shared" +) + +type Installer struct { + Headers map[string]string + RMM string + ClientID int + SiteID int + Description string + AgentType string + Power bool + RDP bool + Ping bool + Token string + LocalMesh string + Cert string + Proxy string + Timeout time.Duration + SaltMaster string + Silent bool + NoMesh bool + MeshDir string + MeshNodeID string +} + +func (a *Agent) Install(i *Installer) { + a.checkExistingAndRemove(i.Silent) + + i.Headers = map[string]string{ + "content-type": "application/json", + "Authorization": fmt.Sprintf("Token %s", i.Token), + } + a.AgentID = GenerateAgentID() + a.Logger.Debugln("Agent ID:", a.AgentID) + + u, err := url.Parse(i.RMM) + if err != nil { + a.installerMsg(err.Error(), "error", i.Silent) + } + + if u.Scheme != "https" && u.Scheme != "http" { + a.installerMsg("Invalid URL (must contain https or http)", "error", i.Silent) + } + + // will match either ipv4 , or ipv4:port + var ipPort = regexp.MustCompile(`[0-9]+(?:\.[0-9]+){3}(:[0-9]+)?`) + + // if ipv4:port, strip the port to get ip for salt master + if ipPort.MatchString(u.Host) && strings.Contains(u.Host, ":") { + i.SaltMaster = strings.Split(u.Host, ":")[0] + } else if strings.Contains(u.Host, ":") { + i.SaltMaster = strings.Split(u.Host, ":")[0] + } else { + i.SaltMaster = u.Host + } + + a.Logger.Debugln("API:", i.SaltMaster) + + terr := TestTCP(fmt.Sprintf("%s:4222", i.SaltMaster)) + if terr != nil { + a.installerMsg(fmt.Sprintf("ERROR: Either port 4222 TCP is not open on your RMM, or nats.service is not running.\n\n%s", terr.Error()), "error", i.Silent) + } + + baseURL := u.Scheme + "://" + u.Host + a.Logger.Debugln("Base URL:", baseURL) + + iClient := resty.New() + iClient.SetCloseConnection(true) + iClient.SetTimeout(15 * time.Second) + iClient.SetDebug(a.Debug) + iClient.SetHeaders(i.Headers) + + // set proxy if applicable + if len(i.Proxy) > 0 { + a.Logger.Infoln("Using proxy:", i.Proxy) + iClient.SetProxy(i.Proxy) + } + + creds, cerr := iClient.R().Get(fmt.Sprintf("%s/api/v3/installer/", baseURL)) + if cerr != nil { + a.installerMsg(cerr.Error(), "error", i.Silent) + } + if creds.StatusCode() == 401 { + a.installerMsg("Installer token has expired. Please generate a new one.", "error", i.Silent) + } + + verPayload := map[string]string{"version": a.Version} + iVersion, ierr := iClient.R().SetBody(verPayload).Post(fmt.Sprintf("%s/api/v3/installer/", baseURL)) + if ierr != nil { + a.installerMsg(ierr.Error(), "error", i.Silent) + } + if iVersion.StatusCode() != 200 { + a.installerMsg(DjangoStringResp(iVersion.String()), "error", i.Silent) + } + + rClient := resty.New() + rClient.SetCloseConnection(true) + rClient.SetTimeout(i.Timeout * time.Second) + rClient.SetDebug(a.Debug) + // set rest knox headers + rClient.SetHeaders(i.Headers) + + // set local cert if applicable + if len(i.Cert) > 0 { + if !trmm.FileExists(i.Cert) { + a.installerMsg(fmt.Sprintf("%s does not exist", i.Cert), "error", i.Silent) + } + rClient.SetRootCertificate(i.Cert) + } + + if len(i.Proxy) > 0 { + rClient.SetProxy(i.Proxy) + } + + var arch string + switch a.Arch { + case "x86_64": + arch = "64" + case "x86": + arch = "32" + } + + var installerMeshSystemEXE string + if len(i.MeshDir) > 0 { + installerMeshSystemEXE = filepath.Join(i.MeshDir, "MeshAgent.exe") + } else { + installerMeshSystemEXE = a.MeshSystemEXE + } + + var meshNodeID string + + if runtime.GOOS == "windows" && !i.NoMesh { + mesh := filepath.Join(a.ProgramDir, a.MeshInstaller) + if i.LocalMesh == "" { + a.Logger.Infoln("Downloading mesh agent...") + payload := map[string]string{"arch": arch, "plat": a.Platform} + r, err := rClient.R().SetBody(payload).SetOutput(mesh).Post(fmt.Sprintf("%s/api/v3/meshexe/", baseURL)) + if err != nil { + a.installerMsg(fmt.Sprintf("Failed to download mesh agent: %s", err.Error()), "error", i.Silent) + } + if r.StatusCode() != 200 { + a.installerMsg(fmt.Sprintf("Unable to download the mesh agent from the RMM. %s", r.String()), "error", i.Silent) + } + } else { + err := copyFile(i.LocalMesh, mesh) + if err != nil { + a.installerMsg(err.Error(), "error", i.Silent) + } + } + + a.Logger.Infoln("Installing mesh agent...") + a.Logger.Debugln("Mesh agent:", mesh) + time.Sleep(1 * time.Second) + + meshNodeID, err = a.installMesh(mesh, installerMeshSystemEXE, i.Proxy) + if err != nil { + a.installerMsg(fmt.Sprintf("Failed to install mesh agent: %s", err.Error()), "error", i.Silent) + } + } + + if len(i.MeshNodeID) > 0 { + meshNodeID = i.MeshNodeID + } + + a.Logger.Infoln("Adding agent to dashboard") + // add agent + type NewAgentResp struct { + AgentPK int `json:"pk"` + Token string `json:"token"` + } + agentPayload := map[string]interface{}{ + "agent_id": a.AgentID, + "hostname": a.Hostname, + "site": i.SiteID, + "monitoring_type": i.AgentType, + "mesh_node_id": meshNodeID, + "description": i.Description, + "goarch": a.GoArch, + "plat": a.Platform, + } + + r, err := rClient.R().SetBody(agentPayload).SetResult(&NewAgentResp{}).Post(fmt.Sprintf("%s/api/v3/newagent/", baseURL)) + if err != nil { + a.installerMsg(err.Error(), "error", i.Silent) + } + if r.StatusCode() != 200 { + a.installerMsg(r.String(), "error", i.Silent) + } + + agentPK := r.Result().(*NewAgentResp).AgentPK + agentToken := r.Result().(*NewAgentResp).Token + + a.Logger.Debugln("Agent token:", agentToken) + a.Logger.Debugln("Agent PK:", agentPK) + + createAgentConfig(baseURL, a.AgentID, i.SaltMaster, agentToken, strconv.Itoa(agentPK), i.Cert, i.Proxy, i.MeshDir) + time.Sleep(1 * time.Second) + // refresh our agent with new values + a = New(a.Logger, a.Version) + a.Logger.Debugf("%+v\n", a) + + // set new headers, no longer knox auth...use agent auth + rClient.SetHeaders(a.Headers) + + time.Sleep(3 * time.Second) + // check in once + a.DoNatsCheckIn() + + if runtime.GOOS == "windows" { + // send software api + a.SendSoftware() + + a.Logger.Debugln("Creating temp dir") + a.CreateTRMMTempDir() + + a.Logger.Debugln("Disabling automatic windows updates") + a.PatchMgmnt(true) + + a.Logger.Infoln("Installing service...") + err := a.InstallService() + if err != nil { + a.installerMsg(err.Error(), "error", i.Silent) + } + + time.Sleep(1 * time.Second) + a.Logger.Infoln("Starting service...") + out := a.ControlService(winSvcName, "start") + if !out.Success { + a.installerMsg(out.ErrorMsg, "error", i.Silent) + } + + a.Logger.Infoln("Adding windows defender exclusions") + a.addDefenderExlusions() + + if i.Power { + a.Logger.Infoln("Disabling sleep/hibernate...") + DisableSleepHibernate() + } + + if i.Ping { + a.Logger.Infoln("Enabling ping...") + EnablePing() + } + + if i.RDP { + a.Logger.Infoln("Enabling RDP...") + EnableRDP() + } + } + + a.installerMsg("Installation was successfull!\nAllow a few minutes for the agent to properly display in the RMM", "info", i.Silent) +} + +func copyFile(src, dst string) error { + in, err := os.Open(src) + if err != nil { + return err + } + defer in.Close() + + out, err := os.Create(dst) + if err != nil { + return err + } + defer out.Close() + + _, err = io.Copy(out, in) + if err != nil { + return err + } + return nil +} diff --git a/agent/install_linux.go b/agent/install_linux.go new file mode 100644 index 0000000..b9debcc --- /dev/null +++ b/agent/install_linux.go @@ -0,0 +1,57 @@ +/* +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() {} diff --git a/agent/install_windows.go b/agent/install_windows.go new file mode 100644 index 0000000..3f8b630 --- /dev/null +++ b/agent/install_windows.go @@ -0,0 +1,130 @@ +/* +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 ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/gonutz/w32/v2" + "golang.org/x/sys/windows/registry" +) + +func createAgentConfig(baseurl, agentid, apiurl, token, agentpk, cert, proxy, meshdir string) { + k, _, err := registry.CreateKey(registry.LOCAL_MACHINE, `SOFTWARE\TacticalRMM`, registry.ALL_ACCESS) + if err != nil { + log.Fatalln("Error creating registry key:", err) + } + defer k.Close() + + err = k.SetStringValue("BaseURL", baseurl) + if err != nil { + log.Fatalln("Error creating BaseURL registry key:", err) + } + + err = k.SetStringValue("AgentID", agentid) + if err != nil { + log.Fatalln("Error creating AgentID registry key:", err) + } + + err = k.SetStringValue("ApiURL", apiurl) + if err != nil { + log.Fatalln("Error creating ApiURL registry key:", err) + } + + err = k.SetStringValue("Token", token) + if err != nil { + log.Fatalln("Error creating Token registry key:", err) + } + + err = k.SetStringValue("AgentPK", agentpk) + if err != nil { + log.Fatalln("Error creating AgentPK registry key:", err) + } + + if len(cert) > 0 { + err = k.SetStringValue("Cert", cert) + if err != nil { + log.Fatalln("Error creating Cert registry key:", err) + } + } + + if len(proxy) > 0 { + err = k.SetStringValue("Proxy", proxy) + if err != nil { + log.Fatalln("Error creating Proxy registry key:", err) + } + } + + if len(meshdir) > 0 { + err = k.SetStringValue("MeshDir", meshdir) + if err != nil { + log.Fatalln("Error creating MeshDir registry key:", err) + } + } +} + +func (a *Agent) checkExistingAndRemove(silent bool) { + hasReg := false + _, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\TacticalRMM`, registry.ALL_ACCESS) + if err == nil { + hasReg = true + } + if hasReg { + tacUninst := filepath.Join(a.ProgramDir, a.GetUninstallExe()) + tacUninstArgs := [2]string{tacUninst, "/VERYSILENT"} + + window := w32.GetForegroundWindow() + if !silent && window != 0 { + var handle w32.HWND + msg := "Existing installation found\nClick OK to remove, then re-run the installer.\nClick Cancel to abort." + action := w32.MessageBox(handle, msg, "Tactical RMM", w32.MB_OKCANCEL|w32.MB_ICONWARNING) + if action == w32.IDOK { + a.AgentUninstall("foo") + } + } else { + fmt.Println("Existing installation found and must be removed before attempting to reinstall.") + fmt.Println("Run the following command to uninstall, and then re-run this installer.") + fmt.Printf(`"%s" %s `, tacUninstArgs[0], tacUninstArgs[1]) + } + os.Exit(0) + } +} + +func (a *Agent) installerMsg(msg, alert string, silent bool) { + window := w32.GetForegroundWindow() + if !silent && window != 0 { + var ( + handle w32.HWND + flags uint + ) + + switch alert { + case "info": + flags = w32.MB_OK | w32.MB_ICONINFORMATION + case "error": + flags = w32.MB_OK | w32.MB_ICONERROR + default: + flags = w32.MB_OK | w32.MB_ICONINFORMATION + } + + w32.MessageBox(handle, msg, "Tactical RMM", flags) + } else { + fmt.Println(msg) + } + + if alert == "error" { + a.Logger.Fatalln(msg) + } +} diff --git a/agent/patches_windows.go b/agent/patches_windows.go new file mode 100644 index 0000000..7d41baf --- /dev/null +++ b/agent/patches_windows.go @@ -0,0 +1,114 @@ +/* +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 ( + "fmt" + "time" + + rmm "github.com/amidaware/rmmagent/shared" +) + +func (a *Agent) GetWinUpdates() { + updates, err := WUAUpdates("IsInstalled=1 or IsInstalled=0 and Type='Software' and IsHidden=0") + if err != nil { + a.Logger.Errorln(err) + return + } + + for _, update := range updates { + a.Logger.Debugln("GUID:", update.UpdateID) + a.Logger.Debugln("Downloaded:", update.Downloaded) + a.Logger.Debugln("Installed:", update.Installed) + a.Logger.Debugln("KB:", update.KBArticleIDs) + a.Logger.Debugln("--------------------------------") + } + + payload := rmm.WinUpdateResult{AgentID: a.AgentID, Updates: updates} + _, err = a.rClient.R().SetBody(payload).Post("/api/v3/winupdates/") + if err != nil { + a.Logger.Debugln(err) + } +} + +func (a *Agent) InstallUpdates(guids []string) { + session, err := NewUpdateSession() + if err != nil { + a.Logger.Errorln(err) + return + } + defer session.Close() + + for _, id := range guids { + var result rmm.WinUpdateInstallResult + result.AgentID = a.AgentID + result.UpdateID = id + + query := fmt.Sprintf("UpdateID='%s'", id) + a.Logger.Debugln("query:", query) + updts, err := session.GetWUAUpdateCollection(query) + if err != nil { + a.Logger.Errorln(err) + result.Success = false + a.rClient.R().SetBody(result).Patch("/api/v3/winupdates/") + continue + } + defer updts.Release() + + updtCnt, err := updts.Count() + if err != nil { + a.Logger.Errorln(err) + result.Success = false + a.rClient.R().SetBody(result).Patch("/api/v3/winupdates/") + continue + } + a.Logger.Debugln("updtCnt:", updtCnt) + + if updtCnt == 0 { + superseded := rmm.SupersededUpdate{AgentID: a.AgentID, UpdateID: id} + a.rClient.R().SetBody(superseded).Post("/api/v3/superseded/") + continue + } + + for i := 0; i < int(updtCnt); i++ { + u, err := updts.Item(i) + if err != nil { + a.Logger.Errorln(err) + result.Success = false + a.rClient.R().SetBody(result).Patch("/api/v3/winupdates/") + continue + } + a.Logger.Debugln("u:", u) + err = session.InstallWUAUpdate(u) + if err != nil { + a.Logger.Errorln(err) + result.Success = false + a.rClient.R().SetBody(result).Patch("/api/v3/winupdates/") + continue + } + result.Success = true + a.rClient.R().SetBody(result).Patch("/api/v3/winupdates/") + a.Logger.Debugln("Installed windows update with guid", id) + } + } + + time.Sleep(5 * time.Second) + needsReboot, err := a.SystemRebootRequired() + if err != nil { + a.Logger.Errorln(err) + } + rebootPayload := rmm.AgentNeedsReboot{AgentID: a.AgentID, NeedsReboot: needsReboot} + _, err = a.rClient.R().SetBody(rebootPayload).Put("/api/v3/winupdates/") + if err != nil { + a.Logger.Debugln("NeedsReboot:", err) + } +} diff --git a/agent/process.go b/agent/process.go new file mode 100644 index 0000000..bd3e4ee --- /dev/null +++ b/agent/process.go @@ -0,0 +1,72 @@ +/* +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 ( + "fmt" + "strings" + + rmm "github.com/amidaware/rmmagent/shared" + ps "github.com/elastic/go-sysinfo" + gops "github.com/shirou/gopsutil/v3/process" +) + +func (a *Agent) GetProcsRPC() []rmm.ProcessMsg { + ret := make([]rmm.ProcessMsg, 0) + + procs, _ := ps.Processes() + for i, process := range procs { + p, err := process.Info() + if err != nil { + continue + } + if p.PID == 0 { + continue + } + + m, _ := process.Memory() + proc, gerr := gops.NewProcess(int32(p.PID)) + if gerr != nil { + continue + } + cpu, _ := proc.CPUPercent() + user, _ := proc.Username() + + ret = append(ret, rmm.ProcessMsg{ + Name: p.Name, + Pid: p.PID, + MemBytes: m.Resident, + Username: user, + UID: i, + CPU: fmt.Sprintf("%.1f", cpu), + }) + } + return ret +} + +func (a *Agent) KillHungUpdates() { + procs, err := ps.Processes() + if err != nil { + return + } + + for _, process := range procs { + p, err := process.Info() + if err != nil { + continue + } + if strings.Contains(p.Exe, "winagent-v") { + a.Logger.Debugln("killing process", p.Exe) + KillProc(int32(p.PID)) + } + } +} diff --git a/agent/rpc.go b/agent/rpc.go new file mode 100644 index 0000000..7d24d0b --- /dev/null +++ b/agent/rpc.go @@ -0,0 +1,507 @@ +/* +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 ( + "fmt" + "os" + "runtime" + "strconv" + "sync" + "sync/atomic" + "time" + + rmm "github.com/amidaware/rmmagent/shared" + nats "github.com/nats-io/nats.go" + "github.com/ugorji/go/codec" +) + +type NatsMsg struct { + Func string `json:"func"` + Timeout int `json:"timeout"` + Data map[string]string `json:"payload"` + ScriptArgs []string `json:"script_args"` + ProcPID int32 `json:"procpid"` + TaskPK int `json:"taskpk"` + ScheduledTask SchedTask `json:"schedtaskpayload"` + RecoveryCommand string `json:"recoverycommand"` + UpdateGUIDs []string `json:"guids"` + ChocoProgName string `json:"choco_prog_name"` + PendingActionPK int `json:"pending_action_pk"` + PatchMgmt bool `json:"patch_mgmt"` + ID int `json:"id"` + Code string `json:"code"` +} + +var ( + agentUpdateLocker uint32 + getWinUpdateLocker uint32 + installWinUpdateLocker uint32 +) + +func (a *Agent) RunRPC() { + a.Logger.Infoln("Agent service started") + go a.RunAsService() + var wg sync.WaitGroup + wg.Add(1) + opts := a.setupNatsOptions() + server := fmt.Sprintf("tls://%s:4222", a.ApiURL) + nc, err := nats.Connect(server, opts...) + if err != nil { + a.Logger.Fatalln("RunRPC() nats.Connect()", err) + } + + nc.Subscribe(a.AgentID, func(msg *nats.Msg) { + var payload *NatsMsg + var mh codec.MsgpackHandle + mh.RawToString = true + + dec := codec.NewDecoderBytes(msg.Data, &mh) + if err := dec.Decode(&payload); err != nil { + a.Logger.Errorln(err) + return + } + + switch payload.Func { + case "ping": + go func() { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + a.Logger.Debugln("pong") + ret.Encode("pong") + msg.Respond(resp) + }() + + case "patchmgmt": + go func(p *NatsMsg) { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + err := a.PatchMgmnt(p.PatchMgmt) + if err != nil { + a.Logger.Errorln("PatchMgmnt:", err.Error()) + ret.Encode(err.Error()) + } else { + ret.Encode("ok") + } + msg.Respond(resp) + }(payload) + + case "schedtask": + go func(p *NatsMsg) { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + success, err := a.CreateSchedTask(p.ScheduledTask) + if err != nil { + a.Logger.Errorln(err.Error()) + ret.Encode(err.Error()) + } else if !success { + ret.Encode("Something went wrong") + } else { + ret.Encode("ok") + } + msg.Respond(resp) + }(payload) + + case "delschedtask": + go func(p *NatsMsg) { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + err := DeleteSchedTask(p.ScheduledTask.Name) + if err != nil { + a.Logger.Errorln(err.Error()) + ret.Encode(err.Error()) + } else { + ret.Encode("ok") + } + msg.Respond(resp) + }(payload) + + case "listschedtasks": + go func() { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + tasks := ListSchedTasks() + a.Logger.Debugln(tasks) + ret.Encode(tasks) + msg.Respond(resp) + }() + + case "eventlog": + go func(p *NatsMsg) { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + days, _ := strconv.Atoi(p.Data["days"]) + evtLog := a.GetEventLog(p.Data["logname"], days) + a.Logger.Debugln(evtLog) + ret.Encode(evtLog) + msg.Respond(resp) + }(payload) + + case "procs": + go func() { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + procs := a.GetProcsRPC() + a.Logger.Debugln(procs) + ret.Encode(procs) + msg.Respond(resp) + }() + + case "killproc": + go func(p *NatsMsg) { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + err := KillProc(p.ProcPID) + if err != nil { + ret.Encode(err.Error()) + a.Logger.Debugln(err.Error()) + } else { + ret.Encode("ok") + } + msg.Respond(resp) + }(payload) + + case "rawcmd": + go func(p *NatsMsg) { + var resp []byte + var resultData rmm.RawCMDResp + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + + switch runtime.GOOS { + case "windows": + out, _ := CMDShell(p.Data["shell"], []string{}, p.Data["command"], p.Timeout, false) + a.Logger.Debugln(out) + if out[1] != "" { + ret.Encode(out[1]) + resultData.Results = out[1] + } else { + ret.Encode(out[0]) + resultData.Results = out[0] + } + default: + opts := a.NewCMDOpts() + opts.Shell = p.Data["shell"] + opts.Command = p.Data["command"] + opts.Timeout = time.Duration(p.Timeout) + out := a.CmdV2(opts) + tmp := "" + if len(out.Stdout) > 0 { + tmp += out.Stdout + } + if len(out.Stderr) > 0 { + tmp += "\n" + tmp += out.Stderr + } + ret.Encode(tmp) + resultData.Results = tmp + } + + msg.Respond(resp) + if p.ID != 0 { + a.rClient.R().SetBody(resultData).Patch(fmt.Sprintf("/api/v3/%d/%s/histresult/", p.ID, a.AgentID)) + } + }(payload) + + case "winservices": + go func() { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + svcs := a.GetServices() + a.Logger.Debugln(svcs) + ret.Encode(svcs) + msg.Respond(resp) + }() + + case "winsvcdetail": + go func(p *NatsMsg) { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + svc := a.GetServiceDetail(p.Data["name"]) + a.Logger.Debugln(svc) + ret.Encode(svc) + msg.Respond(resp) + }(payload) + + case "winsvcaction": + go func(p *NatsMsg) { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + retData := a.ControlService(p.Data["name"], p.Data["action"]) + a.Logger.Debugln(retData) + ret.Encode(retData) + msg.Respond(resp) + }(payload) + + case "editwinsvc": + go func(p *NatsMsg) { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + retData := a.EditService(p.Data["name"], p.Data["startType"]) + a.Logger.Debugln(retData) + ret.Encode(retData) + msg.Respond(resp) + }(payload) + + case "runscript": + go func(p *NatsMsg) { + var resp []byte + var retData string + var resultData rmm.RunScriptResp + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + start := time.Now() + stdout, stderr, retcode, err := a.RunScript(p.Data["code"], p.Data["shell"], p.ScriptArgs, p.Timeout) + resultData.ExecTime = time.Since(start).Seconds() + resultData.ID = p.ID + + if err != nil { + a.Logger.Debugln(err) + retData = err.Error() + resultData.Retcode = 1 + resultData.Stderr = err.Error() + } else { + retData = stdout + stderr // to keep backwards compat + resultData.Retcode = retcode + resultData.Stdout = stdout + resultData.Stderr = stderr + } + a.Logger.Debugln(retData) + ret.Encode(retData) + msg.Respond(resp) + if p.ID != 0 { + results := map[string]interface{}{"script_results": resultData} + a.rClient.R().SetBody(results).Patch(fmt.Sprintf("/api/v3/%d/%s/histresult/", p.ID, a.AgentID)) + } + }(payload) + + case "runscriptfull": + go func(p *NatsMsg) { + var resp []byte + var retData rmm.RunScriptResp + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + start := time.Now() + stdout, stderr, retcode, err := a.RunScript(p.Data["code"], p.Data["shell"], p.ScriptArgs, p.Timeout) + + retData.ExecTime = time.Since(start).Seconds() + if err != nil { + retData.Stderr = err.Error() + retData.Retcode = 1 + } else { + retData.Stdout = stdout + retData.Stderr = stderr + retData.Retcode = retcode + } + retData.ID = p.ID + a.Logger.Debugln(retData) + ret.Encode(retData) + msg.Respond(resp) + if p.ID != 0 { + results := map[string]interface{}{"script_results": retData} + a.rClient.R().SetBody(results).Patch(fmt.Sprintf("/api/v3/%d/%s/histresult/", p.ID, a.AgentID)) + } + }(payload) + + case "recover": + go func(p *NatsMsg) { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + + switch p.Data["mode"] { + case "mesh": + a.Logger.Debugln("Recovering mesh") + a.RecoverMesh() + } + + ret.Encode("ok") + msg.Respond(resp) + }(payload) + case "softwarelist": + go func() { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + sw := a.GetInstalledSoftware() + a.Logger.Debugln(sw) + ret.Encode(sw) + msg.Respond(resp) + }() + + case "rebootnow": + go func() { + a.Logger.Debugln("Scheduling immediate reboot") + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + ret.Encode("ok") + msg.Respond(resp) + if runtime.GOOS == "windows" { + CMD("shutdown.exe", []string{"/r", "/t", "5", "/f"}, 15, false) + } else { + opts := a.NewCMDOpts() + opts.Command = "reboot" + a.CmdV2(opts) + } + }() + case "needsreboot": + go func() { + a.Logger.Debugln("Checking if reboot needed") + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + out, err := a.SystemRebootRequired() + if err == nil { + a.Logger.Debugln("Reboot needed:", out) + ret.Encode(out) + } else { + a.Logger.Debugln("Error checking if reboot needed:", err) + ret.Encode(false) + } + msg.Respond(resp) + }() + case "sysinfo": + go func() { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + a.Logger.Debugln("Getting sysinfo with WMI") + modes := []string{"agent-agentinfo", "agent-disks", "agent-wmi", "agent-publicip"} + for _, m := range modes { + a.NatsMessage(nc, m) + } + ret.Encode("ok") + msg.Respond(resp) + }() + case "wmi": + go func() { + a.Logger.Debugln("Sending WMI") + a.NatsMessage(nc, "agent-wmi") + }() + case "cpuloadavg": + go func() { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + a.Logger.Debugln("Getting CPU Load Avg") + loadAvg := a.GetCPULoadAvg() + a.Logger.Debugln("CPU Load Avg:", loadAvg) + ret.Encode(loadAvg) + msg.Respond(resp) + }() + case "runchecks": + go func() { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + if runtime.GOOS == "windows" { + if a.ChecksRunning() { + ret.Encode("busy") + msg.Respond(resp) + a.Logger.Debugln("Checks are already running, please wait") + } else { + ret.Encode("ok") + msg.Respond(resp) + a.Logger.Debugln("Running checks") + _, checkerr := CMD(a.EXE, []string{"-m", "runchecks"}, 600, false) + if checkerr != nil { + a.Logger.Errorln("RPC RunChecks", checkerr) + } + } + } else { + ret.Encode("ok") + msg.Respond(resp) + a.Logger.Debugln("Running checks") + a.RunChecks(true) + } + + }() + case "runtask": + go func(p *NatsMsg) { + a.Logger.Debugln("Running task") + a.RunTask(p.TaskPK) + }(payload) + + case "publicip": + go func() { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + ret.Encode(a.PublicIP()) + msg.Respond(resp) + }() + case "installpython": + go a.GetPython(true) + case "installchoco": + go a.InstallChoco() + case "installwithchoco": + go func(p *NatsMsg) { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + ret.Encode("ok") + msg.Respond(resp) + out, _ := a.InstallWithChoco(p.ChocoProgName) + results := map[string]string{"results": out} + url := fmt.Sprintf("/api/v3/%d/chocoresult/", p.PendingActionPK) + a.rClient.R().SetBody(results).Patch(url) + }(payload) + case "getwinupdates": + go func() { + if !atomic.CompareAndSwapUint32(&getWinUpdateLocker, 0, 1) { + a.Logger.Debugln("Already checking for windows updates") + } else { + a.Logger.Debugln("Checking for windows updates") + defer atomic.StoreUint32(&getWinUpdateLocker, 0) + a.GetWinUpdates() + } + }() + case "installwinupdates": + go func(p *NatsMsg) { + if !atomic.CompareAndSwapUint32(&installWinUpdateLocker, 0, 1) { + a.Logger.Debugln("Already installing windows updates") + } else { + a.Logger.Debugln("Installing windows updates", p.UpdateGUIDs) + defer atomic.StoreUint32(&installWinUpdateLocker, 0) + a.InstallUpdates(p.UpdateGUIDs) + } + }(payload) + case "agentupdate": + go func(p *NatsMsg) { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + if !atomic.CompareAndSwapUint32(&agentUpdateLocker, 0, 1) { + a.Logger.Debugln("Agent update already running") + ret.Encode("updaterunning") + msg.Respond(resp) + } else { + ret.Encode("ok") + msg.Respond(resp) + a.AgentUpdate(p.Data["url"], p.Data["inno"], p.Data["version"]) + atomic.StoreUint32(&agentUpdateLocker, 0) + nc.Flush() + nc.Close() + os.Exit(0) + } + }(payload) + + case "uninstall": + go func(p *NatsMsg) { + var resp []byte + ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) + ret.Encode("ok") + msg.Respond(resp) + a.AgentUninstall(p.Code) + nc.Flush() + nc.Close() + os.Exit(0) + }(payload) + } + }) + nc.Flush() + + if err := nc.LastError(); err != nil { + a.Logger.Errorln(err) + os.Exit(1) + } + + wg.Wait() +} diff --git a/agent/services_windows.go b/agent/services_windows.go new file mode 100644 index 0000000..75624f5 --- /dev/null +++ b/agent/services_windows.go @@ -0,0 +1,307 @@ +/* +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 ( + "time" + + rmm "github.com/amidaware/rmmagent/shared" + trmm "github.com/wh1te909/trmm-shared" + "golang.org/x/sys/windows/svc" + "golang.org/x/sys/windows/svc/mgr" +) + +func GetServiceStatus(name string) (string, error) { + conn, err := mgr.Connect() + if err != nil { + return "n/a", err + } + defer conn.Disconnect() + + srv, err := conn.OpenService(name) + if err != nil { + return "n/a", err + } + defer srv.Close() + + q, err := srv.Query() + if err != nil { + return "n/a", err + } + + return serviceStatusText(uint32(q.State)), nil +} + +func (a *Agent) ControlService(name, action string) rmm.WinSvcResp { + conn, err := mgr.Connect() + if err != nil { + return rmm.WinSvcResp{Success: false, ErrorMsg: err.Error()} + } + defer conn.Disconnect() + + srv, err := conn.OpenService(name) + if err != nil { + return rmm.WinSvcResp{Success: false, ErrorMsg: err.Error()} + } + defer srv.Close() + + var status svc.Status + switch action { + + case "stop": + status, err = srv.Control(svc.Stop) + if err != nil { + return rmm.WinSvcResp{Success: false, ErrorMsg: err.Error()} + } + timeout := time.Now().Add(30 * time.Second) + for status.State != svc.Stopped { + if timeout.Before(time.Now()) { + return rmm.WinSvcResp{Success: false, ErrorMsg: "Timed out waiting for service to stop"} + } + time.Sleep(500 * time.Millisecond) + status, err = srv.Query() + if err != nil { + return rmm.WinSvcResp{Success: false, ErrorMsg: err.Error()} + } + } + return rmm.WinSvcResp{Success: true, ErrorMsg: ""} + + case "start": + err := srv.Start() + if err != nil { + return rmm.WinSvcResp{Success: false, ErrorMsg: err.Error()} + } + return rmm.WinSvcResp{Success: true, ErrorMsg: ""} + } + + return rmm.WinSvcResp{Success: false, ErrorMsg: "Something went wrong"} +} + +func (a *Agent) EditService(name, startupType string) rmm.WinSvcResp { + conn, err := mgr.Connect() + if err != nil { + return rmm.WinSvcResp{Success: false, ErrorMsg: err.Error()} + } + defer conn.Disconnect() + + srv, err := conn.OpenService(name) + if err != nil { + return rmm.WinSvcResp{Success: false, ErrorMsg: err.Error()} + } + defer srv.Close() + + conf, err := srv.Config() + if err != nil { + return rmm.WinSvcResp{Success: false, ErrorMsg: err.Error()} + } + + var startType uint32 + switch startupType { + case "auto": + startType = 2 + case "autodelay": + startType = 2 + case "manual": + startType = 3 + case "disabled": + startType = 4 + default: + return rmm.WinSvcResp{Success: false, ErrorMsg: "Unknown startup type provided"} + } + + conf.StartType = startType + if startupType == "autodelay" { + conf.DelayedAutoStart = true + } else if startupType == "auto" { + conf.DelayedAutoStart = false + } + + err = srv.UpdateConfig(conf) + if err != nil { + return rmm.WinSvcResp{Success: false, ErrorMsg: err.Error()} + } + return rmm.WinSvcResp{Success: true, ErrorMsg: ""} +} + +func (a *Agent) GetServiceDetail(name string) trmm.WindowsService { + ret := trmm.WindowsService{} + + conn, err := mgr.Connect() + if err != nil { + a.Logger.Errorln(err) + return ret + } + defer conn.Disconnect() + + srv, err := conn.OpenService(name) + if err != nil { + a.Logger.Errorln(err) + return ret + } + defer srv.Close() + + q, err := srv.Query() + if err != nil { + a.Logger.Errorln(err) + return ret + } + + conf, err := srv.Config() + if err != nil { + a.Logger.Errorln(err) + return ret + } + + ret.BinPath = CleanString(conf.BinaryPathName) + ret.Description = CleanString(conf.Description) + ret.DisplayName = CleanString(conf.DisplayName) + ret.Name = name + ret.PID = q.ProcessId + ret.StartType = serviceStartType(uint32(conf.StartType)) + ret.Status = serviceStatusText(uint32(q.State)) + ret.Username = CleanString(conf.ServiceStartName) + ret.DelayedAutoStart = conf.DelayedAutoStart + return ret +} + +// GetServices returns a list of windows services +func (a *Agent) GetServices() []trmm.WindowsService { + ret := make([]trmm.WindowsService, 0) + + conn, err := mgr.Connect() + if err != nil { + a.Logger.Debugln(err) + return ret + } + defer conn.Disconnect() + + svcs, err := conn.ListServices() + + if err != nil { + a.Logger.Debugln(err) + return ret + } + + for _, s := range svcs { + srv, err := conn.OpenService(s) + if err != nil { + if err.Error() != "Access is denied." { + a.Logger.Debugln("Open Service", s, err) + } + continue + } + defer srv.Close() + + q, err := srv.Query() + if err != nil { + a.Logger.Debugln(err) + continue + } + + conf, err := srv.Config() + if err != nil { + a.Logger.Debugln(err) + continue + } + + ret = append(ret, trmm.WindowsService{ + Name: s, + Status: serviceStatusText(uint32(q.State)), + DisplayName: CleanString(conf.DisplayName), + BinPath: CleanString(conf.BinaryPathName), + Description: CleanString(conf.Description), + Username: CleanString(conf.ServiceStartName), + PID: q.ProcessId, + StartType: serviceStartType(uint32(conf.StartType)), + DelayedAutoStart: conf.DelayedAutoStart, + }) + } + return ret +} + +// WaitForService will wait for a service to be in X state for X retries +func WaitForService(name string, status string, retries int) { + attempts := 0 + for { + stat, err := GetServiceStatus(name) + if err != nil { + attempts++ + time.Sleep(5 * time.Second) + } else { + if stat != status { + attempts++ + time.Sleep(5 * time.Second) + } else { + attempts = 0 + } + } + if attempts == 0 || attempts >= retries { + break + } + } +} + +func serviceExists(name string) bool { + conn, err := mgr.Connect() + if err != nil { + return false + } + defer conn.Disconnect() + + srv, err := conn.OpenService(name) + if err != nil { + return false + } + defer srv.Close() + + return true +} + +// 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" + } +} + +// https://docs.microsoft.com/en-us/dotnet/api/system.serviceprocess.servicestartmode?view=dotnet-plat-ext-3.1 +func serviceStartType(num uint32) string { + switch num { + case 0: + return "Boot" + case 1: + return "System" + case 2: + return "Automatic" + case 3: + return "Manual" + case 4: + return "Disabled" + default: + return "Unknown" + } +} diff --git a/agent/software_windows_386.go b/agent/software_windows_386.go new file mode 100644 index 0000000..1be5cd0 --- /dev/null +++ b/agent/software_windows_386.go @@ -0,0 +1,53 @@ +/* +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 ( + "fmt" + + so "github.com/iamacarpet/go-win64api/shared" + wapf "github.com/wh1te909/go-win64api" + trmm "github.com/wh1te909/trmm-shared" +) + +func installedSoftwareList() ([]so.Software, error) { + sw32, err := wapf.GetSoftwareList(`SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall`, "X32") + if err != nil { + return nil, err + } + + return sw32, nil +} + +func (a *Agent) GetInstalledSoftware() []trmm.WinSoftwareList { + ret := make([]trmm.WinSoftwareList, 0) + + sw, err := installedSoftwareList() + if err != nil { + return ret + } + + for _, s := range sw { + t := s.InstallDate + ret = append(ret, trmm.WinSoftwareList{ + Name: CleanString(s.Name()), + Version: CleanString(s.Version()), + Publisher: CleanString(s.Publisher), + InstallDate: fmt.Sprintf("%02d-%d-%02d", t.Year(), t.Month(), t.Day()), + Size: ByteCountSI(s.EstimatedSize * 1024), + Source: CleanString(s.InstallSource), + Location: CleanString(s.InstallLocation), + Uninstall: CleanString(s.UninstallString), + }) + } + return ret +} diff --git a/agent/software_windows_amd64.go b/agent/software_windows_amd64.go new file mode 100644 index 0000000..ce84005 --- /dev/null +++ b/agent/software_windows_amd64.go @@ -0,0 +1,43 @@ +/* +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 ( + "fmt" + + wapi "github.com/iamacarpet/go-win64api" + trmm "github.com/wh1te909/trmm-shared" +) + +func (a *Agent) GetInstalledSoftware() []trmm.WinSoftwareList { + ret := make([]trmm.WinSoftwareList, 0) + + sw, err := wapi.InstalledSoftwareList() + if err != nil { + return ret + } + + for _, s := range sw { + t := s.InstallDate + ret = append(ret, trmm.WinSoftwareList{ + Name: CleanString(s.Name()), + Version: CleanString(s.Version()), + Publisher: CleanString(s.Publisher), + InstallDate: fmt.Sprintf("%02d-%d-%02d", t.Year(), t.Month(), t.Day()), + Size: ByteCountSI(s.EstimatedSize * 1024), + Source: CleanString(s.InstallSource), + Location: CleanString(s.InstallLocation), + Uninstall: CleanString(s.UninstallString), + }) + } + return ret +} diff --git a/agent/svc.go b/agent/svc.go new file mode 100644 index 0000000..8b004ff --- /dev/null +++ b/agent/svc.go @@ -0,0 +1,96 @@ +/* +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 ( + "fmt" + "sync" + "time" + + nats "github.com/nats-io/nats.go" +) + +func (a *Agent) RunAsService() { + var wg sync.WaitGroup + wg.Add(1) + go a.AgentSvc() + go a.CheckRunner() + wg.Wait() +} + +func (a *Agent) AgentSvc() { + go a.GetPython(false) + + a.CreateTRMMTempDir() + a.RunMigrations() + + sleepDelay := randRange(14, 22) + a.Logger.Debugf("AgentSvc() sleeping for %v seconds", sleepDelay) + time.Sleep(time.Duration(sleepDelay) * time.Second) + + opts := a.setupNatsOptions() + server := fmt.Sprintf("tls://%s:4222", a.ApiURL) + nc, err := nats.Connect(server, opts...) + if err != nil { + a.Logger.Fatalln("AgentSvc() nats.Connect()", err) + } + + for _, s := range natsCheckin { + a.NatsMessage(nc, s) + time.Sleep(time.Duration(randRange(100, 400)) * time.Millisecond) + } + + a.SyncMeshNodeID() + + time.Sleep(time.Duration(randRange(1, 3)) * time.Second) + a.AgentStartup() + a.SendSoftware() + + checkInHelloTicker := time.NewTicker(time.Duration(randRange(30, 60)) * time.Second) + checkInAgentInfoTicker := time.NewTicker(time.Duration(randRange(200, 400)) * time.Second) + checkInWinSvcTicker := time.NewTicker(time.Duration(randRange(2400, 3000)) * time.Second) + checkInPubIPTicker := time.NewTicker(time.Duration(randRange(300, 500)) * time.Second) + checkInDisksTicker := time.NewTicker(time.Duration(randRange(1000, 2000)) * time.Second) + checkInSWTicker := time.NewTicker(time.Duration(randRange(2800, 3500)) * time.Second) + checkInWMITicker := time.NewTicker(time.Duration(randRange(3000, 4000)) * time.Second) + syncMeshTicker := time.NewTicker(time.Duration(randRange(800, 1200)) * time.Second) + + for { + select { + case <-checkInHelloTicker.C: + a.NatsMessage(nc, "agent-hello") + case <-checkInAgentInfoTicker.C: + a.NatsMessage(nc, "agent-agentinfo") + case <-checkInWinSvcTicker.C: + a.NatsMessage(nc, "agent-winsvc") + case <-checkInPubIPTicker.C: + a.NatsMessage(nc, "agent-publicip") + case <-checkInDisksTicker.C: + a.NatsMessage(nc, "agent-disks") + case <-checkInSWTicker.C: + a.SendSoftware() + case <-checkInWMITicker.C: + a.NatsMessage(nc, "agent-wmi") + case <-syncMeshTicker.C: + a.SyncMeshNodeID() + } + } +} + +func (a *Agent) AgentStartup() { + url := "/api/v3/checkin/" + payload := map[string]interface{}{"agent_id": a.AgentID} + _, err := a.rClient.R().SetBody(payload).Post(url) + if err != nil { + a.Logger.Debugln("AgentStartup()", err) + } +} diff --git a/agent/syscall_windows.go b/agent/syscall_windows.go new file mode 100644 index 0000000..26173ef --- /dev/null +++ b/agent/syscall_windows.go @@ -0,0 +1,116 @@ +/* +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 ( + "syscall" + "unsafe" + + "github.com/gonutz/w32/v2" + "golang.org/x/sys/windows" +) + +var _ unsafe.Pointer + +var ( + modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") + modkernel32 = windows.NewLazySystemDLL("kernel32.dll") + + procFormatMessageW = modkernel32.NewProc("FormatMessageW") + procGetOldestEventLogRecord = modadvapi32.NewProc("GetOldestEventLogRecord") + procLoadLibraryExW = modkernel32.NewProc("LoadLibraryExW") + procReadEventLogW = modadvapi32.NewProc("ReadEventLogW") +) + +// https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-eventlogrecord +type EVENTLOGRECORD struct { + Length uint32 + Reserved uint32 + RecordNumber uint32 + TimeGenerated uint32 + TimeWritten uint32 + EventID uint32 + EventType uint16 + NumStrings uint16 + EventCategory uint16 + ReservedFlags uint16 + ClosingRecordNumber uint32 + StringOffset uint32 + UserSidLength uint32 + UserSidOffset uint32 + DataLength uint32 + DataOffset uint32 +} + +type ReadFlag uint32 + +const ( + EVENTLOG_SEQUENTIAL_READ ReadFlag = 1 << iota + EVENTLOG_SEEK_READ + EVENTLOG_FORWARDS_READ + EVENTLOG_BACKWARDS_READ +) + +const ( + DONT_RESOLVE_DLL_REFERENCES uint32 = 0x0001 + LOAD_LIBRARY_AS_DATAFILE uint32 = 0x0002 +) + +func FormatMessage(flags uint32, source syscall.Handle, messageID uint32, languageID uint32, buffer *byte, bufferSize uint32, arguments uintptr) (numChars uint32, err error) { + r0, _, e1 := syscall.Syscall9(procFormatMessageW.Addr(), 7, uintptr(flags), uintptr(source), uintptr(messageID), uintptr(languageID), uintptr(unsafe.Pointer(buffer)), uintptr(bufferSize), uintptr(arguments), 0, 0) + numChars = uint32(r0) + if numChars == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetOldestEventLogRecord(eventLog w32.HANDLE, oldestRecord *uint32) (err error) { + r1, _, e1 := syscall.Syscall(procGetOldestEventLogRecord.Addr(), 2, uintptr(eventLog), uintptr(unsafe.Pointer(oldestRecord)), 0) + if r1 == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func LoadLibraryEx(filename *uint16, file syscall.Handle, flags uint32) (handle syscall.Handle, err error) { + r0, _, e1 := syscall.Syscall(procLoadLibraryExW.Addr(), 3, uintptr(unsafe.Pointer(filename)), uintptr(file), uintptr(flags)) + handle = syscall.Handle(r0) + if handle == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func ReadEventLog(eventLog w32.HANDLE, readFlags ReadFlag, recordOffset uint32, buffer *byte, numberOfBytesToRead uint32, bytesRead *uint32, minNumberOfBytesNeeded *uint32) (err error) { + r1, _, e1 := syscall.Syscall9(procReadEventLogW.Addr(), 7, uintptr(eventLog), uintptr(readFlags), uintptr(recordOffset), uintptr(unsafe.Pointer(buffer)), uintptr(numberOfBytesToRead), uintptr(unsafe.Pointer(bytesRead)), uintptr(unsafe.Pointer(minNumberOfBytesNeeded)), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} diff --git a/agent/tasks_windows.go b/agent/tasks_windows.go new file mode 100644 index 0000000..dabf1a3 --- /dev/null +++ b/agent/tasks_windows.go @@ -0,0 +1,354 @@ +/* +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 ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + "time" + + rmm "github.com/amidaware/rmmagent/shared" + "github.com/amidaware/taskmaster" + "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 { + PK int `json:"pk"` + Type string `json:"type"` + Name string `json:"name"` + Trigger string `json:"trigger"` + Enabled bool `json:"enabled"` + DayInterval taskmaster.DayInterval `json:"day_interval"` + WeekInterval taskmaster.WeekInterval `json:"week_interval"` + DaysOfWeek taskmaster.DayOfWeek `json:"days_of_week"` + DaysOfMonth taskmaster.DayOfMonth `json:"days_of_month"` + RunOnLastDayOfMonth bool `json:"run_on_last_day_of_month"` + MonthsOfYear taskmaster.Month `json:"months_of_year"` + WeeksOfMonth taskmaster.Week `json:"weeks_of_month"` + StartYear int `json:"start_year"` + StartMonth time.Month `json:"start_month"` + StartDay int `json:"start_day"` + StartHour int `json:"start_hour"` + StartMinute int `json:"start_min"` + ExpireYear int `json:"expire_year"` + ExpireMonth time.Month `json:"expire_month"` + ExpireDay int `json:"expire_day"` + ExpireHour int `json:"expire_hour"` + ExpireMinute int `json:"expire_min"` + RandomDelay period.Period `json:"random_delay"` + RepetitionInterval period.Period `json:"repetition_interval"` + RepetitionDuration period.Period `json:"repetition_duration"` + StopAtDurationEnd bool `json:"stop_at_duration_end"` + Path string `json:"path"` + WorkDir string `json:"workdir"` + Args string `json:"args"` + TaskPolicy taskmaster.TaskInstancesPolicy `json:"multiple_instances"` + RunASAPAfterMissed bool `json:"start_when_available"` + DeleteAfter bool `json:"delete_expired_task_after"` + Overwrite bool `json:"overwrite_task"` +} + +func (a *Agent) CreateSchedTask(st SchedTask) (bool, error) { + a.Logger.Debugf("%+v\n", st) + conn, err := taskmaster.Connect() + if err != nil { + a.Logger.Errorln(err) + return false, err + } + defer conn.Disconnect() + + var trigger taskmaster.Trigger + var action taskmaster.ExecAction + var tasktrigger taskmaster.TaskTrigger + + var now = time.Now() + if st.Trigger == "manual" { + tasktrigger = taskmaster.TaskTrigger{ + Enabled: st.Enabled, + StartBoundary: now, + } + } else { + tasktrigger = taskmaster.TaskTrigger{ + Enabled: st.Enabled, + StartBoundary: time.Date(st.StartYear, st.StartMonth, st.StartDay, st.StartHour, st.StartMinute, 0, 0, now.Location()), + RepetitionPattern: taskmaster.RepetitionPattern{ + RepetitionInterval: st.RepetitionInterval, + RepetitionDuration: st.RepetitionDuration, + StopAtDurationEnd: st.StopAtDurationEnd, + }, + } + } + + if st.ExpireMinute != 0 { + tasktrigger.EndBoundary = time.Date(st.ExpireYear, st.ExpireMonth, st.ExpireDay, st.ExpireHour, st.ExpireMinute, 0, 0, now.Location()) + } + + var path, workdir, args string + def := conn.NewTaskDefinition() + + switch st.Trigger { + case "runonce": + trigger = taskmaster.TimeTrigger{ + TaskTrigger: tasktrigger, + RandomDelay: st.RandomDelay, + } + + case "daily": + trigger = taskmaster.DailyTrigger{ + TaskTrigger: tasktrigger, + DayInterval: st.DayInterval, + RandomDelay: st.RandomDelay, + } + + case "weekly": + trigger = taskmaster.WeeklyTrigger{ + TaskTrigger: tasktrigger, + DaysOfWeek: st.DaysOfWeek, + WeekInterval: st.WeekInterval, + RandomDelay: st.RandomDelay, + } + + case "monthly": + trigger = taskmaster.MonthlyTrigger{ + TaskTrigger: tasktrigger, + DaysOfMonth: st.DaysOfMonth, + MonthsOfYear: st.MonthsOfYear, + RandomDelay: st.RandomDelay, + RunOnLastDayOfMonth: st.RunOnLastDayOfMonth, + } + + case "monthlydow": + trigger = taskmaster.MonthlyDOWTrigger{ + TaskTrigger: tasktrigger, + DaysOfWeek: st.DaysOfWeek, + MonthsOfYear: st.MonthsOfYear, + WeeksOfMonth: st.WeeksOfMonth, + RandomDelay: st.RandomDelay, + } + + case "manual": + trigger = taskmaster.TimeTrigger{ + TaskTrigger: tasktrigger, + } + } + + def.AddTrigger(trigger) + + switch st.Type { + case "rmm": + path = winExeName + workdir = a.ProgramDir + args = fmt.Sprintf("-m taskrunner -p %d", st.PK) + case "schedreboot": + path = "shutdown.exe" + workdir = filepath.Join(os.Getenv("SYSTEMROOT"), "System32") + args = "/r /t 5 /f" + case "custom": + path = st.Path + workdir = st.WorkDir + args = st.Args + } + + action = taskmaster.ExecAction{ + Path: path, + WorkingDir: workdir, + Args: args, + } + def.AddAction(action) + + def.Principal.RunLevel = taskmaster.TASK_RUNLEVEL_HIGHEST + def.Principal.LogonType = taskmaster.TASK_LOGON_SERVICE_ACCOUNT + def.Principal.UserID = "SYSTEM" + def.Settings.AllowDemandStart = true + def.Settings.AllowHardTerminate = true + def.Settings.DontStartOnBatteries = false + def.Settings.Enabled = st.Enabled + def.Settings.StopIfGoingOnBatteries = false + def.Settings.WakeToRun = true + def.Settings.MultipleInstances = st.TaskPolicy + + if st.DeleteAfter { + def.Settings.DeleteExpiredTaskAfter = "PT15M" + } + + if st.RunASAPAfterMissed { + def.Settings.StartWhenAvailable = true + } + + _, success, err := conn.CreateTask(fmt.Sprintf("\\%s", st.Name), def, st.Overwrite) + if err != nil { + a.Logger.Errorln(err) + return false, err + } + + return success, nil +} + +func DeleteSchedTask(name string) error { + conn, err := taskmaster.Connect() + if err != nil { + return err + } + defer conn.Disconnect() + + err = conn.DeleteTask(fmt.Sprintf("\\%s", name)) + if err != nil { + return err + } + return nil +} + +// CleanupSchedTasks removes all tacticalrmm sched tasks during uninstall +func CleanupSchedTasks() { + conn, err := taskmaster.Connect() + if err != nil { + return + } + defer conn.Disconnect() + + tasks, err := conn.GetRegisteredTasks() + if err != nil { + return + } + + for _, task := range tasks { + if strings.HasPrefix(task.Name, "TacticalRMM_") { + conn.DeleteTask(fmt.Sprintf("\\%s", task.Name)) + } + } + tasks.Release() +} + +func ListSchedTasks() []string { + ret := make([]string, 0) + + conn, err := taskmaster.Connect() + if err != nil { + return ret + } + defer conn.Disconnect() + + tasks, err := conn.GetRegisteredTasks() + if err != nil { + return ret + } + + for _, task := range tasks { + ret = append(ret, task.Name) + } + tasks.Release() + return ret +} diff --git a/agent/utils.go b/agent/utils.go new file mode 100644 index 0000000..80a7f90 --- /dev/null +++ b/agent/utils.go @@ -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) +} diff --git a/agent/wmi_windows.go b/agent/wmi_windows.go new file mode 100644 index 0000000..194ecb0 --- /dev/null +++ b/agent/wmi_windows.go @@ -0,0 +1,601 @@ +/* +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 ( + "encoding/json" + + "github.com/StackExchange/wmi" + rmm "github.com/amidaware/rmmagent/shared" +) + +func GetWin32_USBController() ([]interface{}, error) { + var dst []rmm.Win32_USBController + ret := make([]interface{}, 0) + + q := wmi.CreateQuery(&dst, "") + err := wmi.Query(q, &dst) + if err != nil { + return ret, err + } + + for _, val := range dst { + b, err := json.Marshal(val) + if err != nil { + return ret, err + } + // this creates an extra unneeded array but keeping for now + // for backwards compatibility with the python agent + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + tmp = append(tmp, un) + ret = append(ret, tmp) + } + return ret, nil +} + +func GetWin32_Processor() ([]interface{}, error) { + var ( + dstEX []rmm.Win32_ProcessorEX + dst []rmm.Win32_Processor + errEX error + errORIG error + fallback bool = false + ) + ret := make([]interface{}, 0) + q := "SELECT * FROM Win32_Processor" + + errEX = wmi.Query(q, &dstEX) + if errEX != nil { + errORIG = wmi.Query(q, &dst) + if errORIG != nil { + return ret, errORIG + } + } + + if errEX == nil { + for _, val := range dstEX { + b, err := json.Marshal(val) + if err != nil { + fallback = true + break + } + // this creates an extra unneeded array but keeping for now + // for backwards compatibility with the python agent + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + tmp = append(tmp, un) + ret = append(ret, tmp) + } + if !fallback { + return ret, nil + } + } + + if errORIG == nil { + for _, val := range dst { + b, err := json.Marshal(val) + if err != nil { + return ret, err + } + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + tmp = append(tmp, un) + ret = append(ret, tmp) + } + return ret, nil + } + return ret, nil +} + +func GetWin32_DesktopMonitor() ([]interface{}, error) { + var dst []rmm.Win32_DesktopMonitor + ret := make([]interface{}, 0) + + q := wmi.CreateQuery(&dst, "") + err := wmi.Query(q, &dst) + if err != nil { + return ret, err + } + + for _, val := range dst { + b, err := json.Marshal(val) + if err != nil { + return ret, err + } + // this creates an extra unneeded array but keeping for now + // for backwards compatibility with the python agent + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + tmp = append(tmp, un) + ret = append(ret, tmp) + + } + return ret, nil +} + +func GetWin32_NetworkAdapter() ([]interface{}, error) { + var dst []rmm.Win32_NetworkAdapter + ret := make([]interface{}, 0) + + q := wmi.CreateQuery(&dst, "") + err := wmi.Query(q, &dst) + if err != nil { + return ret, err + } + + for _, val := range dst { + b, err := json.Marshal(val) + if err != nil { + return ret, err + } + // this creates an extra unneeded array but keeping for now + // for backwards compatibility with the python agent + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + tmp = append(tmp, un) + ret = append(ret, tmp) + + } + return ret, nil +} + +func GetWin32_DiskDrive() ([]interface{}, error) { + var dst []rmm.Win32_DiskDrive + ret := make([]interface{}, 0) + + q := wmi.CreateQuery(&dst, "") + err := wmi.Query(q, &dst) + if err != nil { + return ret, err + } + + for _, val := range dst { + b, err := json.Marshal(val) + if err != nil { + return ret, err + } + // this creates an extra unneeded array but keeping for now + // for backwards compatibility with the python agent + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + tmp = append(tmp, un) + ret = append(ret, tmp) + + } + return ret, nil +} + +func GetWin32_ComputerSystemProduct() ([]interface{}, error) { + var dst []rmm.Win32_ComputerSystemProduct + ret := make([]interface{}, 0) + + q := wmi.CreateQuery(&dst, "") + err := wmi.Query(q, &dst) + if err != nil { + return ret, err + } + + for _, val := range dst { + b, err := json.Marshal(val) + if err != nil { + return ret, err + } + // this creates an extra unneeded array but keeping for now + // for backwards compatibility with the python agent + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + tmp = append(tmp, un) + ret = append(ret, tmp) + + } + return ret, nil +} + +func GetWin32_BIOS() ([]interface{}, error) { + var ( + dstEX []rmm.Win32_BIOSEX + dst []rmm.Win32_BIOS + errEX error + errORIG error + fallback bool = false + ) + ret := make([]interface{}, 0) + q := "SELECT * FROM Win32_BIOS" + + errEX = wmi.Query(q, &dstEX) + if errEX != nil { + errORIG = wmi.Query(q, &dst) + if errORIG != nil { + return ret, errORIG + } + } + + if errEX == nil { + for _, val := range dstEX { + b, err := json.Marshal(val) + if err != nil { + fallback = true + break + } + // this creates an extra unneeded array but keeping for now + // for backwards compatibility with the python agent + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + tmp = append(tmp, un) + ret = append(ret, tmp) + } + if !fallback { + return ret, nil + } + } + + if errORIG == nil { + for _, val := range dst { + b, err := json.Marshal(val) + if err != nil { + return ret, err + } + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + tmp = append(tmp, un) + ret = append(ret, tmp) + } + return ret, nil + } + return ret, nil +} + +func GetWin32_ComputerSystem() ([]interface{}, error) { + var ( + dstEX []rmm.Win32_ComputerSystemEX + dst []rmm.Win32_ComputerSystem + errEX error + errORIG error + fallback bool = false + ) + ret := make([]interface{}, 0) + q := "SELECT * FROM Win32_ComputerSystem" + + errEX = wmi.Query(q, &dstEX) + if errEX != nil { + errORIG = wmi.Query(q, &dst) + if errORIG != nil { + return ret, errORIG + } + } + + if errEX == nil { + for _, val := range dstEX { + b, err := json.Marshal(val) + if err != nil { + fallback = true + break + } + // this creates an extra unneeded array but keeping for now + // for backwards compatibility with the python agent + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + tmp = append(tmp, un) + ret = append(ret, tmp) + } + if !fallback { + return ret, nil + } + } + + if errORIG == nil { + for _, val := range dst { + b, err := json.Marshal(val) + if err != nil { + return ret, err + } + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + tmp = append(tmp, un) + ret = append(ret, tmp) + } + return ret, nil + } + return ret, nil +} + +func GetWin32_NetworkAdapterConfiguration() ([]interface{}, error) { + var dst []rmm.Win32_NetworkAdapterConfiguration + ret := make([]interface{}, 0) + + q := wmi.CreateQuery(&dst, "") + err := wmi.Query(q, &dst) + if err != nil { + return ret, err + } + + for _, val := range dst { + b, err := json.Marshal(val) + if err != nil { + return ret, err + } + // this creates an extra unneeded array but keeping for now + // for backwards compatibility with the python agent + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + tmp = append(tmp, un) + ret = append(ret, tmp) + + } + return ret, nil +} + +func GetWin32_PhysicalMemory() ([]interface{}, error) { + var ( + dstEX []rmm.Win32_PhysicalMemoryEX + dst []rmm.Win32_PhysicalMemory + errEX error + errORIG error + fallback bool = false + ) + ret := make([]interface{}, 0) + q := "SELECT * FROM Win32_PhysicalMemory" + + errEX = wmi.Query(q, &dstEX) + if errEX != nil { + errORIG = wmi.Query(q, &dst) + if errORIG != nil { + return ret, errORIG + } + } + + if errEX == nil { + for _, val := range dstEX { + b, err := json.Marshal(val) + if err != nil { + fallback = true + break + } + // this creates an extra unneeded array but keeping for now + // for backwards compatibility with the python agent + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + tmp = append(tmp, un) + ret = append(ret, tmp) + } + if !fallback { + return ret, nil + } + } + + if errORIG == nil { + for _, val := range dst { + b, err := json.Marshal(val) + if err != nil { + return ret, err + } + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + tmp = append(tmp, un) + ret = append(ret, tmp) + } + return ret, nil + } + return ret, nil +} + +func GetWin32_OperatingSystem() ([]interface{}, error) { + var dst []rmm.Win32_OperatingSystem + ret := make([]interface{}, 0) + + q := wmi.CreateQuery(&dst, "") + err := wmi.Query(q, &dst) + if err != nil { + return ret, err + } + + for _, val := range dst { + b, err := json.Marshal(val) + if err != nil { + return ret, err + } + // this creates an extra unneeded array but keeping for now + // for backwards compatibility with the python agent + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + tmp = append(tmp, un) + ret = append(ret, tmp) + } + return ret, nil +} + +func GetWin32_BaseBoard() ([]interface{}, error) { + var dst []rmm.Win32_BaseBoard + ret := make([]interface{}, 0) + + q := wmi.CreateQuery(&dst, "") + err := wmi.Query(q, &dst) + if err != nil { + return ret, err + } + + for _, val := range dst { + b, err := json.Marshal(val) + if err != nil { + return ret, err + } + // this creates an extra unneeded array but keeping for now + // for backwards compatibility with the python agent + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + tmp = append(tmp, un) + ret = append(ret, tmp) + } + return ret, nil +} + +func GetWin32_VideoController() ([]interface{}, error) { + var dst []rmm.Win32_VideoController + ret := make([]interface{}, 0) + + q := wmi.CreateQuery(&dst, "") + err := wmi.Query(q, &dst) + if err != nil { + return ret, err + } + + for _, val := range dst { + b, err := json.Marshal(val) + if err != nil { + return ret, err + } + // this creates an extra unneeded array but keeping for now + // for backwards compatibility with the python agent + tmp := make([]interface{}, 0) + var un map[string]interface{} + if err := json.Unmarshal(b, &un); err != nil { + return ret, err + } + tmp = append(tmp, un) + ret = append(ret, tmp) + } + return ret, nil +} + +func (a *Agent) GetWMIInfo() map[string]interface{} { + wmiInfo := make(map[string]interface{}) + + compSysProd, err := GetWin32_ComputerSystemProduct() + if err != nil { + a.Logger.Debugln(err) + } + + compSys, err := GetWin32_ComputerSystem() + if err != nil { + a.Logger.Debugln(err) + } + + netAdaptConfig, err := GetWin32_NetworkAdapterConfiguration() + if err != nil { + a.Logger.Debugln(err) + } + + physMem, err := GetWin32_PhysicalMemory() + if err != nil { + a.Logger.Debugln(err) + } + + winOS, err := GetWin32_OperatingSystem() + if err != nil { + a.Logger.Debugln(err) + } + + baseBoard, err := GetWin32_BaseBoard() + if err != nil { + a.Logger.Debugln(err) + } + + bios, err := GetWin32_BIOS() + if err != nil { + a.Logger.Debugln(err) + } + + disk, err := GetWin32_DiskDrive() + if err != nil { + a.Logger.Debugln(err) + } + + netAdapt, err := GetWin32_NetworkAdapter() + if err != nil { + a.Logger.Debugln(err) + } + + desktopMon, err := GetWin32_DesktopMonitor() + if err != nil { + a.Logger.Debugln(err) + } + + cpu, err := GetWin32_Processor() + if err != nil { + a.Logger.Debugln(err) + } + + usb, err := GetWin32_USBController() + if err != nil { + a.Logger.Debugln(err) + } + + graphics, err := GetWin32_VideoController() + if err != nil { + a.Logger.Debugln(err) + } + + wmiInfo["comp_sys_prod"] = compSysProd + wmiInfo["comp_sys"] = compSys + wmiInfo["network_config"] = netAdaptConfig + wmiInfo["mem"] = physMem + wmiInfo["os"] = winOS + wmiInfo["base_board"] = baseBoard + wmiInfo["bios"] = bios + wmiInfo["disk"] = disk + wmiInfo["network_adapter"] = netAdapt + wmiInfo["desktop_monitor"] = desktopMon + wmiInfo["cpu"] = cpu + wmiInfo["usb"] = usb + wmiInfo["graphics"] = graphics + + return wmiInfo +} diff --git a/agent/wua_windows.go b/agent/wua_windows.go new file mode 100644 index 0000000..bc76230 --- /dev/null +++ b/agent/wua_windows.go @@ -0,0 +1,478 @@ +/* +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 + +*/ + +// Copyright 2018 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// code taken from https://github.com/GoogleCloudPlatform/osconfig/tree/master/ospatch +// and modified by https://github.com/wh1te909 +package agent + +import ( + "fmt" + "sync" + + rmm "github.com/amidaware/rmmagent/shared" + ole "github.com/go-ole/go-ole" + "github.com/go-ole/go-ole/oleutil" +) + +const ( + S_OK = 0 + S_FALSE = 1 +) + +var wuaSession sync.Mutex + +// IUpdateSession is a an IUpdateSession. +type IUpdateSession struct { + *ole.IDispatch +} + +func (s *IUpdateSession) Close() { + if s.IDispatch != nil { + s.IDispatch.Release() + } + ole.CoUninitialize() + wuaSession.Unlock() +} + +func NewUpdateSession() (*IUpdateSession, error) { + wuaSession.Lock() + if err := ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED); err != nil { + e, ok := err.(*ole.OleError) + // S_OK and S_FALSE are both are Success codes. + // https://docs.microsoft.com/en-us/windows/win32/learnwin32/error-handling-in-com + if !ok || (e.Code() != S_OK && e.Code() != S_FALSE) { + wuaSession.Unlock() + return nil, fmt.Errorf(`ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED): %v`, err) + } + } + + s := &IUpdateSession{} + + unknown, err := oleutil.CreateObject("Microsoft.Update.Session") + if err != nil { + s.Close() + return nil, fmt.Errorf(`oleutil.CreateObject("Microsoft.Update.Session"): %v`, err) + } + disp, err := unknown.QueryInterface(ole.IID_IDispatch) + if err != nil { + unknown.Release() + s.Close() + return nil, fmt.Errorf(`error creating Dispatch object from Microsoft.Update.Session connection: %v`, err) + } + s.IDispatch = disp + + return s, nil +} + +// InstallWUAUpdate install a WIndows update. +func (s *IUpdateSession) InstallWUAUpdate(updt *IUpdate) error { + _, err := updt.GetProperty("Title") + if err != nil { + return fmt.Errorf(`updt.GetProperty("Title"): %v`, err) + } + + updts, err := NewUpdateCollection() + if err != nil { + return err + } + defer updts.Release() + + eula, err := updt.GetProperty("EulaAccepted") + if err != nil { + return fmt.Errorf(`updt.GetProperty("EulaAccepted"): %v`, err) + } + // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-oaut/7b39eb24-9d39-498a-bcd8-75c38e5823d0 + if eula.Val == 0 { + if _, err := updt.CallMethod("AcceptEula"); err != nil { + return fmt.Errorf(`updt.CallMethod("AcceptEula"): %v`, err) + } + } + + if err := updts.Add(updt); err != nil { + return err + } + + if err := s.DownloadWUAUpdateCollection(updts); err != nil { + return fmt.Errorf("DownloadWUAUpdateCollection error: %v", err) + } + + if err := s.InstallWUAUpdateCollection(updts); err != nil { + return fmt.Errorf("InstallWUAUpdateCollection error: %v", err) + } + + return nil +} + +func NewUpdateCollection() (*IUpdateCollection, error) { + updateCollObj, err := oleutil.CreateObject("Microsoft.Update.UpdateColl") + if err != nil { + return nil, fmt.Errorf(`oleutil.CreateObject("Microsoft.Update.UpdateColl"): %v`, err) + } + defer updateCollObj.Release() + + updateColl, err := updateCollObj.IDispatch(ole.IID_IDispatch) + if err != nil { + return nil, err + } + + return &IUpdateCollection{IDispatch: updateColl}, nil +} + +type IUpdateCollection struct { + *ole.IDispatch +} + +type IUpdate struct { + *ole.IDispatch +} + +func (c *IUpdateCollection) Add(updt *IUpdate) error { + if _, err := c.CallMethod("Add", updt.IDispatch); err != nil { + return fmt.Errorf(`IUpdateCollection.CallMethod("Add", updt): %v`, err) + } + return nil +} + +func (c *IUpdateCollection) RemoveAt(i int) error { + if _, err := c.CallMethod("RemoveAt", i); err != nil { + return fmt.Errorf(`IUpdateCollection.CallMethod("RemoveAt", %d): %v`, i, err) + } + return nil +} + +func (c *IUpdateCollection) Count() (int32, error) { + return GetCount(c.IDispatch) +} + +func (c *IUpdateCollection) Item(i int) (*IUpdate, error) { + updtRaw, err := c.GetProperty("Item", i) + if err != nil { + return nil, fmt.Errorf(`IUpdateCollection.GetProperty("Item", %d): %v`, i, err) + } + return &IUpdate{IDispatch: updtRaw.ToIDispatch()}, nil +} + +// GetCount returns the Count property. +func GetCount(dis *ole.IDispatch) (int32, error) { + countRaw, err := dis.GetProperty("Count") + if err != nil { + return 0, fmt.Errorf(`IDispatch.GetProperty("Count"): %v`, err) + } + count, _ := countRaw.Value().(int32) + + return count, nil +} + +func (u *IUpdate) kbaIDs() ([]string, error) { + kbArticleIDsRaw, err := u.GetProperty("KBArticleIDs") + if err != nil { + return nil, fmt.Errorf(`IUpdate.GetProperty("KBArticleIDs"): %v`, err) + } + kbArticleIDs := kbArticleIDsRaw.ToIDispatch() + defer kbArticleIDs.Release() + + count, err := GetCount(kbArticleIDs) + if err != nil { + return nil, err + } + + if count == 0 { + return nil, nil + } + + var ss []string + for i := 0; i < int(count); i++ { + item, err := kbArticleIDs.GetProperty("Item", i) + if err != nil { + return nil, fmt.Errorf(`kbArticleIDs.GetProperty("Item", %d): %v`, i, err) + } + + ss = append(ss, item.ToString()) + } + return ss, nil +} + +func (u *IUpdate) categories() ([]string, []string, error) { + catRaw, err := u.GetProperty("Categories") + if err != nil { + return nil, nil, fmt.Errorf(`IUpdate.GetProperty("Categories"): %v`, err) + } + cat := catRaw.ToIDispatch() + defer cat.Release() + + count, err := GetCount(cat) + if err != nil { + return nil, nil, err + } + if count == 0 { + return nil, nil, nil + } + + var cns, cids []string + for i := 0; i < int(count); i++ { + itemRaw, err := cat.GetProperty("Item", i) + if err != nil { + return nil, nil, fmt.Errorf(`cat.GetProperty("Item", %d): %v`, i, err) + } + item := itemRaw.ToIDispatch() + defer item.Release() + + name, err := item.GetProperty("Name") + if err != nil { + return nil, nil, fmt.Errorf(`item.GetProperty("Name"): %v`, err) + } + + categoryID, err := item.GetProperty("CategoryID") + if err != nil { + return nil, nil, fmt.Errorf(`item.GetProperty("CategoryID"): %v`, err) + } + + cns = append(cns, name.ToString()) + cids = append(cids, categoryID.ToString()) + } + return cns, cids, nil +} + +func (u *IUpdate) moreInfoURLs() ([]string, error) { + moreInfoURLsRaw, err := u.GetProperty("MoreInfoURLs") + if err != nil { + return nil, fmt.Errorf(`IUpdate.GetProperty("MoreInfoURLs"): %v`, err) + } + moreInfoURLs := moreInfoURLsRaw.ToIDispatch() + defer moreInfoURLs.Release() + + count, err := GetCount(moreInfoURLs) + if err != nil { + return nil, err + } + + if count == 0 { + return nil, nil + } + + var ss []string + for i := 0; i < int(count); i++ { + item, err := moreInfoURLs.GetProperty("Item", i) + if err != nil { + return nil, fmt.Errorf(`moreInfoURLs.GetProperty("Item", %d): %v`, i, err) + } + + ss = append(ss, item.ToString()) + } + return ss, nil +} + +func (c *IUpdateCollection) extractPkg(item int) (*rmm.WUAPackage, error) { + updt, err := c.Item(item) + if err != nil { + return nil, err + } + defer updt.Release() + + title, err := updt.GetProperty("Title") + if err != nil { + return nil, fmt.Errorf(`updt.GetProperty("Title"): %v`, err) + } + + description, err := updt.GetProperty("Description") + if err != nil { + return nil, fmt.Errorf(`updt.GetProperty("Description"): %v`, err) + } + + kbArticleIDs, err := updt.kbaIDs() + if err != nil { + return nil, err + } + + categories, categoryIDs, err := updt.categories() + if err != nil { + return nil, err + } + + moreInfoURLs, err := updt.moreInfoURLs() + if err != nil { + return nil, err + } + + supportURL, err := updt.GetProperty("SupportURL") + if err != nil { + return nil, fmt.Errorf(`updt.GetProperty("SupportURL"): %v`, err) + } + + identityRaw, err := updt.GetProperty("Identity") + if err != nil { + return nil, fmt.Errorf(`updt.GetProperty("Identity"): %v`, err) + } + identity := identityRaw.ToIDispatch() + defer identity.Release() + + revisionNumber, err := identity.GetProperty("RevisionNumber") + if err != nil { + return nil, fmt.Errorf(`identity.GetProperty("RevisionNumber"): %v`, err) + } + + updateID, err := identity.GetProperty("UpdateID") + if err != nil { + return nil, fmt.Errorf(`identity.GetProperty("UpdateID"): %v`, err) + } + + severity, err := updt.GetProperty("MsrcSeverity") + if err != nil { + return nil, fmt.Errorf(`updt.GetProperty("MsrcSeverity"): %v`, err) + } + + isInstalled, err := updt.GetProperty("IsInstalled") + if err != nil { + return nil, fmt.Errorf(`updt.GetProperty("IsInstalled"): %v`, err) + } + + isDownloaded, err := updt.GetProperty("IsDownloaded") + if err != nil { + return nil, fmt.Errorf(`updt.GetProperty("IsDownloaded"): %v`, err) + } + + return &rmm.WUAPackage{ + Title: title.ToString(), + Description: description.ToString(), + SupportURL: supportURL.ToString(), + KBArticleIDs: kbArticleIDs, + UpdateID: updateID.ToString(), + Categories: categories, + CategoryIDs: categoryIDs, + MoreInfoURLs: moreInfoURLs, + Severity: severity.ToString(), + RevisionNumber: int32(revisionNumber.Val), + Downloaded: isDownloaded.Value().(bool), + Installed: isInstalled.Value().(bool), + }, nil +} + +// WUAUpdates queries the Windows Update Agent API searcher with the provided query. +func WUAUpdates(query string) ([]rmm.WUAPackage, error) { + session, err := NewUpdateSession() + if err != nil { + return nil, fmt.Errorf("error creating NewUpdateSession: %v", err) + } + defer session.Close() + + updts, err := session.GetWUAUpdateCollection(query) + if err != nil { + return nil, fmt.Errorf("error calling GetWUAUpdateCollection with query %q: %v", query, err) + } + defer updts.Release() + + updtCnt, err := updts.Count() + if err != nil { + return nil, err + } + + if updtCnt == 0 { + return nil, nil + } + + var packages []rmm.WUAPackage + for i := 0; i < int(updtCnt); i++ { + pkg, err := updts.extractPkg(i) + if err != nil { + return nil, err + } + packages = append(packages, *pkg) + } + return packages, nil +} + +// DownloadWUAUpdateCollection downloads all updates in a IUpdateCollection +func (s *IUpdateSession) DownloadWUAUpdateCollection(updates *IUpdateCollection) error { + // returns IUpdateDownloader + // https://docs.microsoft.com/en-us/windows/desktop/api/wuapi/nn-wuapi-iupdatedownloader + downloaderRaw, err := s.CallMethod("CreateUpdateDownloader") + if err != nil { + return fmt.Errorf("error calling method CreateUpdateDownloader on IUpdateSession: %v", err) + } + downloader := downloaderRaw.ToIDispatch() + defer downloader.Release() + + if _, err := downloader.PutProperty("Updates", updates.IDispatch); err != nil { + return fmt.Errorf("error calling PutProperty Updates on IUpdateDownloader: %v", err) + } + + if _, err := downloader.CallMethod("Download"); err != nil { + return fmt.Errorf("error calling method Download on IUpdateDownloader: %v", err) + } + return nil +} + +// InstallWUAUpdateCollection installs all updates in a IUpdateCollection +func (s *IUpdateSession) InstallWUAUpdateCollection(updates *IUpdateCollection) error { + // returns IUpdateInstallersession *ole.IDispatch, + // https://docs.microsoft.com/en-us/windows/desktop/api/wuapi/nf-wuapi-iupdatesession-createupdateinstaller + installerRaw, err := s.CallMethod("CreateUpdateInstaller") + if err != nil { + return fmt.Errorf("error calling method CreateUpdateInstaller on IUpdateSession: %v", err) + } + installer := installerRaw.ToIDispatch() + defer installer.Release() + + if _, err := installer.PutProperty("Updates", updates.IDispatch); err != nil { + return fmt.Errorf("error calling PutProperty Updates on IUpdateInstaller: %v", err) + } + + // TODO: Look into using the async methods and attempt to track/log progress. + if _, err := installer.CallMethod("Install"); err != nil { + return fmt.Errorf("error calling method Install on IUpdateInstaller: %v", err) + } + return nil +} + +// GetWUAUpdateCollection queries the Windows Update Agent API searcher with the provided query +// and returns a IUpdateCollection. +func (s *IUpdateSession) GetWUAUpdateCollection(query string) (*IUpdateCollection, error) { + // returns IUpdateSearcher + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa386515(v=vs.85).aspx + searcherRaw, err := s.CallMethod("CreateUpdateSearcher") + if err != nil { + return nil, fmt.Errorf("error calling CreateUpdateSearcher: %v", err) + } + searcher := searcherRaw.ToIDispatch() + defer searcher.Release() + + // returns ISearchResult + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa386077(v=vs.85).aspx + resultRaw, err := searcher.CallMethod("Search", query) + if err != nil { + return nil, fmt.Errorf("error calling method Search on IUpdateSearcher: %v", err) + } + result := resultRaw.ToIDispatch() + defer result.Release() + + // returns IUpdateCollection + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa386107(v=vs.85).aspx + updtsRaw, err := result.GetProperty("Updates") + if err != nil { + return nil, fmt.Errorf("error calling GetProperty Updates on ISearchResult: %v", err) + } + + return &IUpdateCollection{IDispatch: updtsRaw.ToIDispatch()}, nil +} diff --git a/build/rmm.exe.manifest b/build/rmm.exe.manifest new file mode 100644 index 0000000..810936f --- /dev/null +++ b/build/rmm.exe.manifest @@ -0,0 +1,17 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/build/setup.iss b/build/setup.iss new file mode 100644 index 0000000..1d39d1d --- /dev/null +++ b/build/setup.iss @@ -0,0 +1,101 @@ +#define MyAppName "Tactical RMM Agent" +#define MyAppVersion "2.0.0" +#define MyAppPublisher "AmidaWare LLC" +#define MyAppURL "https://github.com/amidaware" +#define MyAppExeName "tacticalrmm.exe" +#define MESHEXE "meshagent.exe" +#define MESHDIR "{sd}\Program Files\Mesh Agent" + +[Setup] +AppId={{0D34D278-5FAF-4159-A4A0-4E2D2C08139D} +AppName={#MyAppName} +AppVersion={#MyAppVersion} +AppVerName={#MyAppName} +AppPublisher={#MyAppPublisher} +AppPublisherURL={#MyAppURL} +AppSupportURL={#MyAppURL} +AppUpdatesURL={#MyAppURL} +DefaultDirName="{sd}\Program Files\TacticalAgent" +DisableDirPage=yes +SetupLogging=yes +DisableProgramGroupPage=yes +SetupIconFile=C:\Users\Public\Documents\agent\build\onit.ico +WizardSmallImageFile=C:\Users\Public\Documents\agent\build\onit.bmp +UninstallDisplayIcon={app}\{#MyAppExeName} +Compression=lzma +SolidCompression=yes +WizardStyle=modern +RestartApplications=no +CloseApplications=no +MinVersion=6.0 + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" + +[Files] +Source: "C:\Users\Public\Documents\agent\tacticalrmm.exe"; DestDir: "{app}"; Flags: ignoreversion; + +[Run] +Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent runascurrentuser + +[UninstallRun] +Filename: "{app}\{#MyAppExeName}"; Parameters: "-m cleanup"; RunOnceId: "cleanuprm"; +Filename: "{cmd}"; Parameters: "/c taskkill /F /IM tacticalrmm.exe"; RunOnceId: "killtacrmm"; +Filename: "{app}\{#MESHEXE}"; Parameters: "-fulluninstall"; RunOnceId: "meshrm"; + +[UninstallDelete] +Type: filesandordirs; Name: "{app}"; +Type: filesandordirs; Name: "{#MESHDIR}"; + +[Code] +function InitializeSetup(): boolean; +var + ResultCode: Integer; +begin + Exec('cmd.exe', '/c ping 127.0.0.1 -n 2 && net stop tacticalrpc', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + Log('Stop tacticalrpc: ' + IntToStr(ResultCode)); + + Exec('cmd.exe', '/c net stop tacticalagent', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + Log('Stop tacticalagent: ' + IntToStr(ResultCode)); + + Exec('cmd.exe', '/c ping 127.0.0.1 -n 2 && net stop tacticalrmm', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + Log('Stop tacticalrmm: ' + IntToStr(ResultCode)); + + Exec('cmd.exe', '/c taskkill /F /IM tacticalrmm.exe', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + Log('taskkill: ' + IntToStr(ResultCode)); + + Exec('cmd.exe', '/c sc delete tacticalagent', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + Log('delete tacticalagent: ' + IntToStr(ResultCode)); + + Exec('cmd.exe', '/c sc delete tacticalrpc', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + Log('delete tacticalrpc: ' + IntToStr(ResultCode)); + + Result := True; +end; + +procedure DeinitializeSetup(); +var + ResultCode: Integer; + WorkingDir: String; +begin + + WorkingDir := ExpandConstant('{sd}\Program Files\TacticalAgent'); + Exec('cmd.exe', ' /c tacticalrmm.exe -m installsvc', WorkingDir, SW_HIDE, ewWaitUntilTerminated, ResultCode); + Log('install service: ' + IntToStr(ResultCode)); + + Exec('cmd.exe', '/c net start tacticalrmm', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + Log('Start tacticalrmm: ' + IntToStr(ResultCode)); +end; + +function InitializeUninstall(): Boolean; +var + ResultCode: Integer; +begin + Exec('cmd.exe', '/c ping 127.0.0.1 -n 2 && net stop tacticalrmm', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + Exec('cmd.exe', '/c taskkill /F /IM tacticalrmm.exe', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + + Exec('cmd.exe', '/c sc delete tacticalrmm', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + Log('delete tacticalrmm: ' + IntToStr(ResultCode)); + + Result := True; +end; diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..89b2129 --- /dev/null +++ b/go.mod @@ -0,0 +1,74 @@ +module github.com/amidaware/rmmagent + +go 1.17 + +require ( + github.com/StackExchange/wmi v1.2.1 + github.com/elastic/go-sysinfo v1.7.1 + github.com/go-ole/go-ole v1.2.6 + github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534 + github.com/go-resty/resty/v2 v2.7.0 + github.com/gonutz/w32/v2 v2.4.0 + github.com/iamacarpet/go-win64api v0.0.0-20211130162011-82e31fe23f80 + github.com/nats-io/nats-server/v2 v2.4.0 // indirect + github.com/nats-io/nats.go v1.13.0 + github.com/rickb777/date v1.15.3 + github.com/shirou/gopsutil/v3 v3.22.2 + github.com/sirupsen/logrus v1.8.1 + github.com/ugorji/go/codec v1.2.7 + github.com/wh1te909/go-win64api v0.0.0-20210906074314-ab23795a6ae5 + github.com/wh1te909/trmm-shared v0.0.0-20220227075846-f9f757361139 + golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect + golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 +) + +require ( + github.com/amidaware/taskmaster v0.0.0-20220111015025-c9cd178bbbf2 + github.com/go-cmd/cmd v1.4.0 +) + +require ( + github.com/jaypipes/ghw v0.8.0 + github.com/kardianos/service v1.2.1 + github.com/spf13/viper v1.10.1 +) + +require ( + github.com/elastic/go-windows v1.0.0 // indirect + github.com/fsnotify/fsnotify v1.5.1 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/google/cabbie v1.0.2 // indirect + github.com/google/glazier v0.0.0-20211029225403-9f766cca891d // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/jaypipes/pcidb v0.6.0 // indirect + github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 // indirect + github.com/kr/pretty v0.2.0 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/magiconair/properties v1.8.5 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.4.3 // indirect + github.com/nats-io/nkeys v0.3.0 // indirect + github.com/nats-io/nuid v1.0.1 // indirect + github.com/pelletier/go-toml v1.9.4 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/prometheus/procfs v0.0.8 // indirect + github.com/rickb777/plural v1.3.0 // indirect + github.com/scjalliance/comshim v0.0.0-20190308082608-cf06d2532c4e // indirect + github.com/spf13/afero v1.6.0 // indirect + github.com/spf13/cast v1.4.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.2.0 // indirect + github.com/tklauser/go-sysconf v0.3.9 // indirect + github.com/tklauser/numcpus v0.3.0 // indirect + github.com/yusufpapurcu/wmi v1.2.2 // indirect + golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect + golang.org/x/text v0.3.7 // indirect + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect + gopkg.in/ini.v1 v1.66.2 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + howett.net/plist v0.0.0-20181124034731-591f970eefbb // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c365eb2 --- /dev/null +++ b/go.sum @@ -0,0 +1,931 @@ +bitbucket.org/creachadair/stringset v0.0.9/go.mod h1:t+4WcQ4+PXTa8aQdNKe40ZP6iwesoMFWAxPGd3UGjyY= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/StackExchange/wmi v1.2.0/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= +github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/amidaware/taskmaster v0.0.0-20220111015025-c9cd178bbbf2 h1:1K03qwtvgdJRXJr0nE1qvzFPOmbWHBnnnbeblU7+Bg8= +github.com/amidaware/taskmaster v0.0.0-20220111015025-c9cd178bbbf2/go.mod h1:5UVBogOiPFWC2F6fKT/Kb9qD4NCsp2y+dCj+pvXnDQE= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/capnspacehook/taskmaster v0.0.0-20210519235353-1629df7c85e9/go.mod h1:257CYs3Wd/CTlLQ3c72jKv+fFE2MV3WPNnV5jiroYUU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/creachadair/staticfile v0.1.3/go.mod h1:a3qySzCIXEprDGxk6tSxSI+dBBdLzqeBOMhZ+o2d3pM= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/elastic/go-sysinfo v1.7.1 h1:Wx4DSARcKLllpKT2TnFVdSUJOsybqMYCNQZq1/wO+s0= +github.com/elastic/go-sysinfo v1.7.1/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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-cmd/cmd v1.4.0 h1:dF+1JtZMlgCKAcsvstp2VNmVA/jXRjlRYFOF4/w7Bbo= +github.com/go-cmd/cmd v1.4.0/go.mod h1:tbBenttXtZU4c5djS1o7PWL5pd2xAr5sIqH1kGdNiRc= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534 h1:dhy9OQKGBh4zVXbjwbxxHjRxMJtLXj3zfgpBYQaR4Q4= +github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk= +github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= +github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M= +github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8= +github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v0.0.0-20210429001901-424d2337a529/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gonutz/w32/v2 v2.4.0 h1:k+R8/ddsnb9dwVDsTDWvkJGFmYJDI3ZMgcKgdpLuMpw= +github.com/gonutz/w32/v2 v2.4.0/go.mod h1:MgtHx0AScDVNKyB+kjyPder4xIi3XAcHS6LDDU2DmdE= +github.com/google/aukera v0.0.0-20201117230544-d145c8357fea/go.mod h1:oXqTZORBzdwQ6L32YjJmaPajqIV/hoGEouwpFMf4cJE= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/cabbie v1.0.2 h1:UtB+Nn6fPB43wGg5xs4tgU+P3hTZ6KsulgtaHtqZZfs= +github.com/google/cabbie v1.0.2/go.mod h1:6MmHaUrgfabehCHAIaxdrbmvHSxUVXj3Abs08FMABSo= +github.com/google/glazier v0.0.0-20210617205946-bf91b619f5d4/go.mod h1:g7oyIhindbeebnBh0hbFua5rv6XUt/nweDwIWdvxirg= +github.com/google/glazier v0.0.0-20211029225403-9f766cca891d h1:GBIF4RkD4E9USvSRT4O4tBCT77JExIr+qnruI9nkJQo= +github.com/google/glazier v0.0.0-20211029225403-9f766cca891d/go.mod h1:h2R3DLUecGbLSyi6CcxBs5bdgtJhgK+lIffglvAcGKg= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/logger v1.1.0/go.mod h1:w7O8nrRr0xufejBlQMI83MXqRusvREoJdaAxV+CoAB4= +github.com/google/logger v1.1.1/go.mod h1:BkeJZ+1FhQ+/d087r4dzojEg1u2ZX+ZqG1jTUrLM+zQ= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/winops v0.0.0-20210803215038-c8511b84de2b/go.mod h1:ShbX8v8clPm/3chw9zHVwtW3QhrFpL8mXOwNxClt4pg= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/groob/plist v0.0.0-20210519001750-9f754062e6d6/go.mod h1:itkABA+w2cw7x5nYUS/pLRef6ludkZKOigbROmCTaFw= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= +github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= +github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/iamacarpet/go-win64api v0.0.0-20210311141720-fe38760bed28/go.mod h1:oGJx9dz0Ny7HC7U55RZ0Smd6N9p3hXP/+hOFtuYrAxM= +github.com/iamacarpet/go-win64api v0.0.0-20211130162011-82e31fe23f80 h1:UOuVxO8DxSote09McZFyO1Wxd/DA93gfHiNReP7c5wU= +github.com/iamacarpet/go-win64api v0.0.0-20211130162011-82e31fe23f80/go.mod h1:B7zFQPAznj+ujXel5X+LUoK3LgY6VboCdVYHZNn7gpg= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jaypipes/ghw v0.8.0 h1:02q1pTm9CD83vuhBsEZZhOCS128pq87uyaQeJZkp3sQ= +github.com/jaypipes/ghw v0.8.0/go.mod h1:+gR9bjm3W/HnFi90liF+Fj9GpCe/Dsibl9Im8KmC7c4= +github.com/jaypipes/pcidb v0.6.0 h1:VIM7GKVaW4qba30cvB67xSCgJPTzkG8Kzw/cbs5PHWU= +github.com/jaypipes/pcidb v0.6.0/go.mod h1:L2RGk04sfRhp5wvHO0gfRAMoLY/F3PKv/nwJeVoho0o= +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/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kardianos/service v1.2.1 h1:AYndMsehS+ywIS6RB9KOlcXzteWUzxgMgBymJD7+BYk= +github.com/kardianos/service v1.2.1/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.13.4 h1:0zhec2I8zGnjWcKyLl6i3gPqKANCCn5e9xmviEEeX6s= +github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= +github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/minio/highwayhash v1.0.1 h1:dZ6IIu8Z14VlC0VpfKofAhCy74wu/Qb5gcn52yWoz/0= +github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= +github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nats-io/jwt v1.2.2 h1:w3GMTO969dFg+UOKTmmyuu7IGdusK+7Ytlt//OYH/uU= +github.com/nats-io/jwt v1.2.2/go.mod h1:/xX356yQA6LuXI9xWW7mZNpxgF2mBmGecH+Fj34sP5Q= +github.com/nats-io/jwt/v2 v2.0.3 h1:i/O6cmIsjpcQyWDYNcq2JyZ3/VTF8SJ4JWluI5OhpvI= +github.com/nats-io/jwt/v2 v2.0.3/go.mod h1:VRP+deawSXyhNjXmxPCHskrR6Mq50BqpEI5SEcNiGlY= +github.com/nats-io/nats-server/v2 v2.4.0 h1:auni7PHiuyXR4BnDPzLVs3iyO7W7XUmZs8J5cjVb2BE= +github.com/nats-io/nats-server/v2 v2.4.0/go.mod h1:TUAhMFYh1VISyY/D4WKJUMuGHg8yHtoUTuxkbiej1lc= +github.com/nats-io/nats.go v1.12.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= +github.com/nats-io/nats.go v1.13.0 h1:LvYqRB5epIzZWQp6lmeltOOZNLqCvm4b+qfvzZO03HE= +github.com/nats-io/nats.go v1.13.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= +github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s= +github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8= +github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.4 h1:NiTx7EEvBzu9sFOD1zORteLSt3o8gnlvZZwSE9TnY9U= +github.com/onsi/gomega v1.10.4/go.mod h1:g/HbgYopi++010VEqkFgJHKC09uJiW9UkXvMUuKHUCQ= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= +github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/rickb777/date v1.14.2/go.mod h1:swmf05C+hN+m8/Xh7gEq3uB6QJDNc5pQBWojKdHetOs= +github.com/rickb777/date v1.15.3 h1:f8BJHoB2ZWCWvYGd/oEdqds4MtkfqYvGWYXyalHDMnk= +github.com/rickb777/date v1.15.3/go.mod h1:+spwdRnUrpqbYLOmRM6y8FbQMXwpNwHrNcWuOUipge4= +github.com/rickb777/plural v1.2.2/go.mod h1:xyHbelv4YvJE51gjMnHvk+U2e9zIysg6lTnSQK8XUYA= +github.com/rickb777/plural v1.3.0 h1:cN3M4IcJCGiGpa92S3xJgiBQfqGDFj7J8JyObugVwAU= +github.com/rickb777/plural v1.3.0/go.mod h1:xyHbelv4YvJE51gjMnHvk+U2e9zIysg6lTnSQK8XUYA= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM= +github.com/scjalliance/comshim v0.0.0-20190308082608-cf06d2532c4e h1:+/AzLkOdIXEPrAQtwAeWOBnPQ0BnYlBW0aCZmSb47u4= +github.com/scjalliance/comshim v0.0.0-20190308082608-cf06d2532c4e/go.mod h1:9Tc1SKnfACJb9N7cw2eyuI6xzy845G7uZONBsi5uPEA= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shirou/gopsutil/v3 v3.22.2 h1:wCrArWFkHYIdDxx/FSfF5RB4dpJYW6t7rcp3+zL8uks= +github.com/shirou/gopsutil/v3 v3.22.2/go.mod h1:WapW1AOOPlHyXr+yOyw3uYx36enocrtSoSBy0L5vUHY= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= +github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk= +github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/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= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo= +github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs= +github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ= +github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= +github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/wh1te909/go-win64api v0.0.0-20210906074314-ab23795a6ae5 h1:8tp/+fGR+qjDQ00vm0ZorHzskquo76MY9/ChmFFunEA= +github.com/wh1te909/go-win64api v0.0.0-20210906074314-ab23795a6ae5/go.mod h1:qypF+Tf23flzLX5IXuodDb1314BrDrsbwK/OhTh6HBM= +github.com/wh1te909/trmm-shared v0.0.0-20220227075846-f9f757361139 h1:PfOl03o+Y+svWrfXAAu1QWUDePu1yqTq0pf4rpnN8eA= +github.com/wh1te909/trmm-shared v0.0.0-20220227075846-f9f757361139/go.mod h1:ILUz1utl5KgwrxmNHv0RpgMtKeh8gPAABvK2MiXBqv8= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= +github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +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-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200622182413-4b0db7f3f76b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210601080250-7ecdf8ef093b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211107104306-e0b2ad06fe42/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +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/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI= +gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M= +howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/main.go b/main.go new file mode 100644 index 0000000..03d3d27 --- /dev/null +++ b/main.go @@ -0,0 +1,202 @@ +/* +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 main + +import ( + "flag" + "fmt" + "os" + "os/user" + "path/filepath" + "runtime" + + "github.com/amidaware/rmmagent/agent" + "github.com/kardianos/service" + "github.com/sirupsen/logrus" +) + +var ( + version = "2.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") + taskPK := flag.Int("p", 0, "Task PK") + logLevel := flag.String("log", "INFO", "The log level") + logTo := flag.String("logto", "file", "Where to log to") + api := flag.String("api", "", "API URL") + clientID := flag.Int("client-id", 0, "Client ID") + siteID := flag.Int("site-id", 0, "Site ID") + timeout := flag.Duration("timeout", 900, "Installer timeout (seconds)") + desc := flag.String("desc", "", "Agent's Description") + atype := flag.String("agent-type", "server", "server or workstation") + token := flag.String("auth", "", "Token") + power := flag.Bool("power", false, "Disable sleep/hibernate") + rdp := flag.Bool("rdp", false, "Enable RDP") + ping := flag.Bool("ping", false, "Enable ping") + localMesh := flag.String("local-mesh", "", "Path to mesh executable") + noMesh := flag.Bool("nomesh", false, "Do not install mesh agent") + meshDir := flag.String("meshdir", "", "Path to custom meshcentral dir") + meshNodeID := flag.String("meshnodeid", "", "Mesh Node ID") + cert := flag.String("cert", "", "Path to domain CA .pem") + updateurl := flag.String("updateurl", "", "Download link to updater") + inno := flag.String("inno", "", "Inno setup file") + updatever := flag.String("updatever", "", "Update version") + silent := flag.Bool("silent", false, "Do not popup any message boxes during installation") + proxy := flag.String("proxy", "", "Use a http proxy") + flag.Parse() + + if *ver { + agent.ShowVersionInfo(version) + return + } + + if len(os.Args) == 1 { + switch runtime.GOOS { + case "windows": + agent.ShowStatus(version) + default: + agent.ShowVersionInfo(version) + } + return + } + + setupLogging(logLevel, logTo) + defer logFile.Close() + + a := *agent.New(log, version) + + if *mode == "install" { + a.Logger.SetOutput(os.Stdout) + } + + a.Logger.Debugf("%+v\n", a) + + switch *mode { + case "nixmeshnodeid": + fmt.Print(a.NixMeshNodeID()) + case "installsvc": + a.InstallService() + case "checkin": + a.DoNatsCheckIn() + case "rpc": + a.RunRPC() + case "svc": + if runtime.GOOS == "windows" { + s, _ := service.New(&a, a.ServiceConfig) + s.Run() + } else { + a.RunRPC() + } + case "pk": + fmt.Println(a.AgentPK) + case "winagentsvc": + fmt.Println("deprecated. use 'svc'") + case "runchecks": + a.RunChecks(true) + case "checkrunner": + a.RunChecks(false) + case "software": + a.SendSoftware() + case "cleanup": + a.UninstallCleanup() + case "publicip": + fmt.Println(a.PublicIP()) + case "getpython": + a.GetPython(true) + case "runmigrations": + a.RunMigrations() + case "recovermesh": + a.RecoverMesh() + case "taskrunner": + if len(os.Args) < 5 || *taskPK == 0 { + return + } + a.RunTask(*taskPK) + case "update": + if *updateurl == "" || *inno == "" || *updatever == "" { + updateUsage() + return + } + a.AgentUpdate(*updateurl, *inno, *updatever) + case "install": + if runtime.GOOS != "windows" { + u, err := user.Current() + if err != nil { + log.Fatalln(err) + } + if u.Uid != "0" { + log.Fatalln("must run as root") + } + } + + if *api == "" || *clientID == 0 || *siteID == 0 || *token == "" { + installUsage() + return + } + a.Install(&agent.Installer{ + RMM: *api, + ClientID: *clientID, + SiteID: *siteID, + Description: *desc, + AgentType: *atype, + Power: *power, + RDP: *rdp, + Ping: *ping, + Token: *token, + LocalMesh: *localMesh, + Cert: *cert, + Proxy: *proxy, + Timeout: *timeout, + Silent: *silent, + NoMesh: *noMesh, + MeshDir: *meshDir, + MeshNodeID: *meshNodeID, + }) + default: + agent.ShowStatus(version) + } +} + +func setupLogging(level, to *string) { + ll, err := logrus.ParseLevel(*level) + if err != nil { + ll = logrus.InfoLevel + } + log.SetLevel(ll) + + if *to == "stdout" { + log.SetOutput(os.Stdout) + } else { + switch runtime.GOOS { + case "windows": + logFile, _ = os.OpenFile(filepath.Join(os.Getenv("ProgramFiles"), "TacticalAgent", "agent.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0664) + case "linux": + logFile, _ = os.OpenFile(filepath.Join("/var/log/", "tacticalagent.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0664) + } + log.SetOutput(logFile) + } +} + +func installUsage() { + exe, _ := os.Executable() + u := fmt.Sprintf(`Usage: %s -m install -api -client-id X -site-id X -auth `, 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) +} diff --git a/shared/types.go b/shared/types.go new file mode 100644 index 0000000..5900d83 --- /dev/null +++ b/shared/types.go @@ -0,0 +1,947 @@ +/* +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 shared + +import ( + "time" + + trmm "github.com/wh1te909/trmm-shared" +) + +// WinSvcResp for sending service control status back to the rmm +type WinSvcResp struct { + Success bool `json:"success"` + ErrorMsg string `json:"errormsg"` +} + +type ProcessMsg struct { + Name string `json:"name"` + Pid int `json:"pid"` + MemBytes uint64 `json:"membytes"` + Username string `json:"username"` + UID int `json:"id"` + CPU string `json:"cpu_percent"` +} + +type AgentConfig struct { + BaseURL string + AgentID string + APIURL string + Token string + AgentPK string + PK int + Cert string + Proxy string + CustomMeshDir string +} + +type RunScriptResp struct { + Stdout string `json:"stdout"` + Stderr string `json:"stderr"` + Retcode int `json:"retcode"` + ExecTime float64 `json:"execution_time"` + ID int `json:"id"` +} + +type RawCMDResp struct { + Results string `json:"results"` +} + +type AgentInfo 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"` +} + +type PingCheckResponse struct { + ID int `json:"id"` + Status string `json:"status"` + Output string `json:"output"` +} + +type WinUpdateResult struct { + AgentID string `json:"agent_id"` + Updates []WUAPackage `json:"wua_updates"` +} + +type WUAPackage struct { + Title string `json:"title"` + Description string `json:"description"` + Categories []string `json:"categories"` + CategoryIDs []string `json:"category_ids"` + KBArticleIDs []string `json:"kb_article_ids"` + MoreInfoURLs []string `json:"more_info_urls"` + SupportURL string `json:"support_url"` + UpdateID string `json:"guid"` + RevisionNumber int32 `json:"revision_number"` + Severity string `json:"severity"` + Installed bool `json:"installed"` + Downloaded bool `json:"downloaded"` +} + +type WinUpdateInstallResult struct { + AgentID string `json:"agent_id"` + UpdateID string `json:"guid"` + Success bool `json:"success"` +} + +type SupersededUpdate struct { + AgentID string `json:"agent_id"` + UpdateID string `json:"guid"` +} + +type AgentNeedsReboot struct { + AgentID string `json:"agent_id"` + NeedsReboot bool `json:"needs_reboot"` +} + +type ChocoInstalled struct { + AgentID string `json:"agent_id"` + Installed bool `json:"installed"` +} + +// Disk holds physical disk info +type Disk struct { + Device string `json:"device"` + Fstype string `json:"fstype"` + Total uint64 `json:"total"` + Used uint64 `json:"used"` + Free uint64 `json:"free"` + Percent float64 `json:"percent"` +} + +type MeshNodeID struct { + Func string `json:"func"` + Agentid string `json:"agent_id"` + NodeID string `json:"nodeid"` +} + +type AssignedTask struct { + TaskPK int `json:"id"` + Enabled bool `json:"enabled"` +} + +type Script struct { + Shell string `json:"shell"` + Code string `json:"code"` +} + +type CheckInfo struct { + AgentPK int `json:"agent"` + Interval int `json:"check_interval"` +} + +type Check struct { + Script Script `json:"script"` + AssignedTasks []AssignedTask `json:"assigned_tasks"` + CheckPK int `json:"id"` + CheckType string `json:"check_type"` + Status string `json:"status"` + Threshold int `json:"threshold"` + Disk string `json:"disk"` + IP string `json:"ip"` + ScriptArgs []string `json:"script_args"` + Timeout int `json:"timeout"` + ServiceName string `json:"svc_name"` + PassStartPending bool `json:"pass_if_start_pending"` + PassNotExist bool `json:"pass_if_svc_not_exist"` + RestartIfStopped bool `json:"restart_if_stopped"` + LogName string `json:"log_name"` + EventID int `json:"event_id"` + EventIDWildcard bool `json:"event_id_is_wildcard"` + EventType string `json:"event_type"` + EventSource string `json:"event_source"` + EventMessage string `json:"event_message"` + FailWhen string `json:"fail_when"` + SearchLastDays int `json:"search_last_days"` +} + +type AllChecks struct { + CheckInfo + Checks []Check +} + +type TaskAction struct { + ActionType string `json:"type"` + Command string `json:"command"` + Shell string `json:"shell"` + ScriptName string `json:"script_name"` + Code string `json:"code"` + Args []string `json:"script_args"` + Timeout int `json:"timeout"` +} + +type AutomatedTask struct { + ID int `json:"id"` + TaskActions []TaskAction `json:"task_actions"` + Enabled bool `json:"enabled"` + ContinueOnError bool `json:"continue_on_error"` +} + +type EventLogMsg struct { + Source string `json:"source"` + EventType string `json:"eventType"` + EventID uint32 `json:"eventID"` + Message string `json:"message"` + Time string `json:"time"` + UID int `json:"uid"` // for vue +} + +type SoftwareList struct { + Name string `json:"name"` + Version string `json:"version"` + Publisher string `json:"publisher"` + InstallDate string `json:"install_date"` + Size string `json:"size"` + Source string `json:"source"` + Location string `json:"location"` + Uninstall string `json:"uninstall"` +} + +type CheckIn struct { + Func string `json:"func"` + Agentid string `json:"agent_id"` + Version string `json:"version"` +} + +type CheckInSW struct { + CheckIn + InstalledSW []SoftwareList `json:"software"` +} + +type CheckInOS struct { + CheckIn + 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"` +} + +type CheckInWinServices struct { + CheckIn + Services []trmm.WindowsService `json:"services"` +} + +type CheckInPublicIP struct { + CheckIn + PublicIP string `json:"public_ip"` +} + +type CheckInDisk struct { + CheckIn + Disks []Disk `json:"disks"` +} + +type CheckInLoggedUser struct { + CheckIn + Username string `json:"logged_in_username"` +} + +type Win32_ComputerSystemProduct struct { + Caption string + Description string + IdentifyingNumber string + Name string + SKUNumber string + Vendor string + Version string + UUID string +} + +type Win32_ComputerSystemEX struct { + AdminPasswordStatus uint16 + AutomaticManagedPagefile bool + AutomaticResetBootOption bool + AutomaticResetCapability bool + BootOptionOnLimit uint16 + BootOptionOnWatchDog uint16 + BootROMSupported bool + BootupState string + Caption string + ChassisBootupState uint16 + ChassisSKUNumber string // + CreationClassName string + CurrentTimeZone int16 + DaylightInEffect bool + Description string + DNSHostName string + Domain string + DomainRole uint16 + EnableDaylightSavingsTime bool + FrontPanelResetStatus uint16 + HypervisorPresent bool // + InfraredSupported bool + InstallDate time.Time + KeyboardPasswordStatus uint16 + Manufacturer string + Model string + Name string + NameFormat string + NetworkServerModeEnabled bool + NumberOfLogicalProcessors uint32 + NumberOfProcessors uint32 + OEMStringArray []string + PartOfDomain bool + PauseAfterReset int64 + PCSystemType uint16 + PCSystemTypeEx uint16 // + PowerManagementSupported bool + PowerOnPasswordStatus uint16 + PowerState uint16 + PowerSupplyState uint16 + PrimaryOwnerContact string + PrimaryOwnerName string + ResetCapability uint16 + ResetCount int16 + ResetLimit int16 + Roles []string + Status string + SupportContactDescription []string + SystemFamily string // + SystemSKUNumber string // + SystemType string + ThermalState uint16 + TotalPhysicalMemory uint64 + UserName string + WakeUpType uint16 + Workgroup string +} + +type Win32_ComputerSystem struct { + AdminPasswordStatus uint16 + AutomaticManagedPagefile bool + AutomaticResetBootOption bool + AutomaticResetCapability bool + BootOptionOnLimit uint16 + BootOptionOnWatchDog uint16 + BootROMSupported bool + BootupState string + Caption string + ChassisBootupState uint16 + CreationClassName string + CurrentTimeZone int16 + DaylightInEffect bool + Description string + DNSHostName string + Domain string + DomainRole uint16 + EnableDaylightSavingsTime bool + FrontPanelResetStatus uint16 + InfraredSupported bool + InstallDate time.Time + KeyboardPasswordStatus uint16 + Manufacturer string + Model string + Name string + NameFormat string + NetworkServerModeEnabled bool + NumberOfLogicalProcessors uint32 + NumberOfProcessors uint32 + OEMStringArray []string + PartOfDomain bool + PauseAfterReset int64 + PCSystemType uint16 + PowerManagementSupported bool + PowerOnPasswordStatus uint16 + PowerState uint16 + PowerSupplyState uint16 + PrimaryOwnerContact string + PrimaryOwnerName string + ResetCapability uint16 + ResetCount int16 + ResetLimit int16 + Roles []string + Status string + SupportContactDescription []string + SystemType string + ThermalState uint16 + TotalPhysicalMemory uint64 + UserName string + WakeUpType uint16 + Workgroup string +} + +type Win32_NetworkAdapterConfiguration struct { + Caption string + Description string + SettingID string + ArpAlwaysSourceRoute bool + ArpUseEtherSNAP bool + DatabasePath string + DeadGWDetectEnabled bool + DefaultIPGateway []string + DefaultTOS uint8 + DefaultTTL uint8 + DHCPEnabled bool + DHCPLeaseExpires time.Time + DHCPLeaseObtained time.Time + DHCPServer string + DNSDomain string + DNSDomainSuffixSearchOrder []string + DNSEnabledForWINSResolution bool + DNSHostName string + DNSServerSearchOrder []string + DomainDNSRegistrationEnabled bool + ForwardBufferMemory uint32 + FullDNSRegistrationEnabled bool + IGMPLevel uint8 + Index uint32 + InterfaceIndex uint32 + IPAddress []string + IPConnectionMetric uint32 + IPEnabled bool + IPFilterSecurityEnabled bool + IPSecPermitIPProtocols []string + IPSecPermitTCPPorts []string + IPSecPermitUDPPorts []string + IPSubnet []string + IPUseZeroBroadcast bool + KeepAliveInterval uint32 + KeepAliveTime uint32 + MACAddress string + MTU uint32 + NumForwardPackets uint32 + PMTUBHDetectEnabled bool + PMTUDiscoveryEnabled bool + ServiceName string + TcpipNetbiosOptions uint32 + TcpMaxConnectRetransmissions uint32 + TcpMaxDataRetransmissions uint32 + TcpNumConnections uint32 + TcpUseRFC1122UrgentPointer bool + TcpWindowSize uint16 + WINSEnableLMHostsLookup bool + WINSHostLookupFile string + WINSPrimaryServer string + WINSScopeID string + WINSSecondaryServer string +} + +type Win32_PhysicalMemoryEX struct { + Attributes uint32 // + BankLabel string + Capacity uint64 + Caption string + ConfiguredClockSpeed uint32 // + ConfiguredVoltage uint32 // + CreationClassName string + DataWidth uint16 + Description string + DeviceLocator string + FormFactor uint16 + HotSwappable bool + InstallDate time.Time + InterleaveDataDepth uint16 + InterleavePosition uint32 + Manufacturer string + MaxVoltage uint32 // + MemoryType uint16 + MinVoltage uint32 // + Model string + Name string + OtherIdentifyingInfo string + PartNumber string + PositionInRow uint32 + PoweredOn bool + Removable bool + Replaceable bool + SerialNumber string + SKU string + SMBIOSMemoryType uint32 // + Speed uint32 + Status string + Tag string + TotalWidth uint16 + TypeDetail uint16 + Version string +} + +type Win32_PhysicalMemory struct { + BankLabel string + Capacity uint64 + Caption string + CreationClassName string + DataWidth uint16 + Description string + DeviceLocator string + FormFactor uint16 + HotSwappable bool + InstallDate time.Time + InterleaveDataDepth uint16 + InterleavePosition uint32 + Manufacturer string + MemoryType uint16 + Model string + Name string + OtherIdentifyingInfo string + PartNumber string + PositionInRow uint32 + PoweredOn bool + Removable bool + Replaceable bool + SerialNumber string + SKU string + Speed uint32 + Status string + Tag string + TotalWidth uint16 + TypeDetail uint16 + Version string +} + +type Win32_OperatingSystem struct { + BootDevice string + BuildNumber string + BuildType string + Caption string + CodeSet string + CountryCode string + CreationClassName string + CSCreationClassName string + CSDVersion string + CSName string + CurrentTimeZone int16 + Debug bool + Description string + Distributed bool + EncryptionLevel uint32 + ForegroundApplicationBoost uint8 + FreePhysicalMemory uint64 + FreeSpaceInPagingFiles uint64 + FreeVirtualMemory uint64 + InstallDate time.Time + LastBootUpTime time.Time + LocalDateTime time.Time + Locale string + Manufacturer string + MaxNumberOfProcesses uint32 + MaxProcessMemorySize uint64 + MUILanguages []string + Name string + NumberOfLicensedUsers uint32 + NumberOfProcesses uint32 + NumberOfUsers uint32 + OperatingSystemSKU uint32 + Organization string + OSArchitecture string + OSLanguage uint32 + OSProductSuite uint32 + OSType uint16 + OtherTypeDescription string + PAEEnabled bool + PlusProductID string + PlusVersionNumber string + Primary bool + ProductType uint32 + RegisteredUser string + SerialNumber string + ServicePackMajorVersion uint16 + ServicePackMinorVersion uint16 + SizeStoredInPagingFiles uint64 + Status string + SuiteMask uint32 + SystemDevice string + SystemDirectory string + SystemDrive string + TotalSwapSpaceSize uint64 + TotalVirtualMemorySize uint64 + TotalVisibleMemorySize uint64 + Version string + WindowsDirectory string +} + +type Win32_BaseBoard struct { + Caption string + ConfigOptions []string + CreationClassName string + Depth float32 + Description string + Height float32 + HostingBoard bool + HotSwappable bool + InstallDate time.Time + Manufacturer string + Model string + Name string + OtherIdentifyingInfo string + PartNumber string + PoweredOn bool + Product string + Removable bool + Replaceable bool + RequirementsDescription string + RequiresDaughterBoard bool + SerialNumber string + SKU string + SlotLayout string + SpecialRequirements bool + Status string + Tag string + Version string + Weight int32 + Width int32 +} + +type Win32_BIOSEX struct { + BIOSVersion []string + BuildNumber string + Caption string + CodeSet string + CurrentLanguage string + Description string + EmbeddedControllerMajorVersion uint8 // + EmbeddedControllerMinorVersion uint8 // + IdentificationCode string + InstallableLanguages uint16 + InstallDate time.Time + LanguageEdition string + ListOfLanguages []string + Manufacturer string + Name string + OtherTargetOS string + PrimaryBIOS bool + ReleaseDate time.Time + SerialNumber string + SMBIOSBIOSVersion string + SMBIOSMajorVersion uint16 + SMBIOSMinorVersion uint16 + SMBIOSPresent bool + SoftwareElementID string + SoftwareElementState uint16 + Status string + SystemBiosMajorVersion uint8 // + SystemBiosMinorVersion uint8 // + TargetOperatingSystem uint16 + Version string +} + +type Win32_BIOS struct { + BIOSVersion []string + BuildNumber string + Caption string + CodeSet string + CurrentLanguage string + Description string + IdentificationCode string + InstallableLanguages uint16 + InstallDate time.Time + LanguageEdition string + ListOfLanguages []string + Manufacturer string + Name string + OtherTargetOS string + PrimaryBIOS bool + ReleaseDate time.Time + SerialNumber string + SMBIOSBIOSVersion string + SMBIOSMajorVersion uint16 + SMBIOSMinorVersion uint16 + SMBIOSPresent bool + SoftwareElementID string + SoftwareElementState uint16 + Status string + TargetOperatingSystem uint16 + Version string +} + +type Win32_DiskDrive struct { + Availability uint16 + BytesPerSector uint32 + CapabilityDescriptions []string + Caption string + CompressionMethod string + ConfigManagerErrorCode uint32 + ConfigManagerUserConfig bool + CreationClassName string + DefaultBlockSize uint64 + Description string + DeviceID string + ErrorCleared bool + ErrorDescription string + ErrorMethodology string + FirmwareRevision string + Index uint32 + InstallDate time.Time + InterfaceType string + LastErrorCode uint32 + Manufacturer string + MaxBlockSize uint64 + MaxMediaSize uint64 + MediaLoaded bool + MediaType string + MinBlockSize uint64 + Model string + Name string + NeedsCleaning bool + NumberOfMediaSupported uint32 + Partitions uint32 + PNPDeviceID string + PowerManagementSupported bool + SCSIBus uint32 + SCSILogicalUnit uint16 + SCSIPort uint16 + SCSITargetId uint16 + SectorsPerTrack uint32 + SerialNumber string + Signature uint32 + Size uint64 + Status string + StatusInfo uint16 + SystemCreationClassName string + SystemName string + TotalCylinders uint64 + TotalHeads uint32 + TotalSectors uint64 + TotalTracks uint64 + TracksPerCylinder uint32 +} + +type Win32_NetworkAdapter struct { + AdapterType string + AdapterTypeID uint16 + AutoSense bool + Availability uint16 + Caption string + ConfigManagerErrorCode uint32 + ConfigManagerUserConfig bool + CreationClassName string + Description string + DeviceID string + ErrorCleared bool + ErrorDescription string + GUID string + Index uint32 + InstallDate time.Time + InterfaceIndex uint32 + LastErrorCode uint32 + MACAddress string + Manufacturer string + MaxNumberControlled uint32 + MaxSpeed uint64 + Name string + NetConnectionID string + NetConnectionStatus uint16 + NetEnabled bool + NetworkAddresses []string + PermanentAddress string + PhysicalAdapter bool + PNPDeviceID string + PowerManagementSupported bool + ProductName string + ServiceName string + Speed uint64 + Status string + StatusInfo uint16 + SystemCreationClassName string + SystemName string + TimeOfLastReset time.Time +} + +type Win32_DesktopMonitor struct { + Availability uint16 + Bandwidth uint32 + Caption string + ConfigManagerErrorCode uint32 + ConfigManagerUserConfig bool + CreationClassName string + Description string + DeviceID string + DisplayType uint16 + ErrorCleared bool + ErrorDescription string + InstallDate time.Time + IsLocked bool + LastErrorCode uint32 + MonitorManufacturer string + MonitorType string + Name string + PixelsPerXLogicalInch uint32 + PixelsPerYLogicalInch uint32 + PNPDeviceID string + PowerManagementSupported bool + ScreenHeight uint32 + ScreenWidth uint32 + Status string + StatusInfo uint16 + SystemCreationClassName string + SystemName string +} + +type Win32_ProcessorEX struct { + AddressWidth uint16 + Architecture uint16 + AssetTag string // + Availability uint16 + Caption string + Characteristics uint32 // + ConfigManagerErrorCode uint32 + ConfigManagerUserConfig bool + CpuStatus uint16 + CreationClassName string + CurrentClockSpeed uint32 + CurrentVoltage uint16 + DataWidth uint16 + Description string + DeviceID string + ErrorCleared bool + ErrorDescription string + ExtClock uint32 + Family uint16 + InstallDate time.Time + L2CacheSize uint32 + L2CacheSpeed uint32 + L3CacheSize uint32 + L3CacheSpeed uint32 + LastErrorCode uint32 + Level uint16 + LoadPercentage uint16 + Manufacturer string + MaxClockSpeed uint32 + Name string + NumberOfCores uint32 + NumberOfEnabledCore uint32 // + NumberOfLogicalProcessors uint32 + OtherFamilyDescription string + PartNumber string // + PNPDeviceID string + PowerManagementSupported bool + ProcessorId string + ProcessorType uint16 + Revision uint16 + Role string + SecondLevelAddressTranslationExtensions bool // + SerialNumber string // + SocketDesignation string + Status string + StatusInfo uint16 + Stepping string + SystemCreationClassName string + SystemName string + ThreadCount uint32 // + UniqueId string + UpgradeMethod uint16 + Version string + VirtualizationFirmwareEnabled bool // + VMMonitorModeExtensions bool // + VoltageCaps uint32 +} + +type Win32_Processor struct { + AddressWidth uint16 + Architecture uint16 + Availability uint16 + Caption string + ConfigManagerErrorCode uint32 + ConfigManagerUserConfig bool + CpuStatus uint16 + CreationClassName string + CurrentClockSpeed uint32 + CurrentVoltage uint16 + DataWidth uint16 + Description string + DeviceID string + ErrorCleared bool + ErrorDescription string + ExtClock uint32 + Family uint16 + InstallDate time.Time + L2CacheSize uint32 + L2CacheSpeed uint32 + L3CacheSize uint32 + L3CacheSpeed uint32 + LastErrorCode uint32 + Level uint16 + LoadPercentage uint16 + Manufacturer string + MaxClockSpeed uint32 + Name string + NumberOfCores uint32 + NumberOfLogicalProcessors uint32 + OtherFamilyDescription string + PNPDeviceID string + PowerManagementSupported bool + ProcessorId string + ProcessorType uint16 + Revision uint16 + Role string + SocketDesignation string + Status string + StatusInfo uint16 + Stepping string + SystemCreationClassName string + SystemName string + UniqueId string + UpgradeMethod uint16 + Version string + VoltageCaps uint32 +} + +type Win32_USBController struct { + Availability uint16 + Caption string + ConfigManagerErrorCode uint32 + ConfigManagerUserConfig bool + CreationClassName string + Description string + DeviceID string + ErrorCleared bool + ErrorDescription string + InstallDate time.Time + LastErrorCode uint32 + Manufacturer string + MaxNumberControlled uint32 + Name string + PNPDeviceID string + PowerManagementSupported bool + ProtocolSupported uint16 + Status string + StatusInfo uint16 + SystemCreationClassName string + SystemName string + TimeOfLastReset time.Time +} + +type Win32_VideoController struct { + AcceleratorCapabilities []string + AdapterCompatibility string + AdapterDACType string + AdapterRAM uint32 + Availability uint16 + CapabilityDescriptions []string + Caption string + CurrentRefreshRate uint32 + CurrentVerticalResolution uint32 + Description string + DeviceID string + DriverDate time.Time + DriverVersion string + InstallDate time.Time + InstalledDisplayDrivers string + MaxMemorySupported uint32 + MaxRefreshRate uint32 + MinRefreshRate uint32 + Name string + Status string + SystemCreationClassName string + SystemName string + TimeOfLastReset time.Time + VideoModeDescription string + VideoProcessor string +} diff --git a/versioninfo.json b/versioninfo.json new file mode 100644 index 0000000..f9096fa --- /dev/null +++ b/versioninfo.json @@ -0,0 +1,43 @@ +{ + "FixedFileInfo": { + "FileVersion": { + "Major": 2, + "Minor": 0, + "Patch": 0, + "Build": 0 + }, + "ProductVersion": { + "Major": 2, + "Minor": 0, + "Patch": 0, + "Build": 0 + }, + "FileFlagsMask": "3f", + "FileFlags ": "00", + "FileOS": "040004", + "FileType": "01", + "FileSubType": "00" + }, + "StringFileInfo": { + "Comments": "", + "CompanyName": "AmidaWare LLC", + "FileDescription": "Tactical RMM Agent", + "FileVersion": "v2.0.0.0", + "InternalName": "tacticalrmm.exe", + "LegalCopyright": "Copyright (c) 2022 AmidaWare LLC", + "LegalTrademarks": "", + "OriginalFilename": "tacticalrmm.exe", + "PrivateBuild": "", + "ProductName": "Tactical RMM Agent", + "ProductVersion": "v2.0.0.0", + "SpecialBuild": "" + }, + "VarFileInfo": { + "Translation": { + "LangID": "0409", + "CharsetID": "04B0" + } + }, + "IconPath": "build/onit.ico", + "ManifestPath": "build/rmm.exe.manifest" +} \ No newline at end of file