308 lines
6.5 KiB
Go
308 lines
6.5 KiB
Go
|
/*
|
||
|
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"
|
||
|
}
|
||
|
}
|