/* 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" "net/url" "os" "path/filepath" "regexp" "strconv" "strings" "time" "github.com/go-resty/resty/v2" "github.com/gonutz/w32/v2" trmm "github.com/wh1te909/trmm-shared" "golang.org/x/sys/windows/registry" ) 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 installerMeshSystemEXE string if len(i.MeshDir) > 0 { installerMeshSystemEXE = filepath.Join(i.MeshDir, "MeshAgent.exe") } else { installerMeshSystemEXE = a.MeshSystemBin } var meshNodeID string mesh := filepath.Join(a.ProgramDir, a.MeshInstaller) if i.LocalMesh == "" { a.Logger.Infoln("Downloading mesh agent...") payload := map[string]string{"goarch": a.GoArch, "plat": a.Platform} r, err := rClient.R().SetBody(payload).SetOutput(mesh).Post(fmt.Sprintf("%s/api/v3/meshexe/", baseURL)) 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() // 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 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) } }