178 lines
4.1 KiB
Go
178 lines
4.1 KiB
Go
//go:build windows
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/example/sessionguard/internal/agent"
|
|
"github.com/example/sessionguard/internal/config"
|
|
"golang.org/x/sys/windows/svc"
|
|
"golang.org/x/sys/windows/svc/mgr"
|
|
)
|
|
|
|
const serviceName = "SessionGuardAgent"
|
|
|
|
func defaultConfigPath() string { return `C:\ProgramData\SessionGuard\agent.json` }
|
|
|
|
func platformMain(action, path string) error {
|
|
switch action {
|
|
case "install":
|
|
return installService(path)
|
|
case "uninstall":
|
|
return uninstallService()
|
|
case "start":
|
|
return startService()
|
|
case "stop":
|
|
return stopService()
|
|
case "run":
|
|
return runAsService(path)
|
|
case "":
|
|
isSvc, err := svc.IsWindowsService()
|
|
if err == nil && isSvc {
|
|
return runAsService(path)
|
|
}
|
|
return runConsole(path)
|
|
default:
|
|
return fmt.Errorf("unknown service action %q", action)
|
|
}
|
|
}
|
|
|
|
func setupLog(dataDir string) {
|
|
_ = os.MkdirAll(dataDir, 0o700)
|
|
f, err := os.OpenFile(filepath.Join(dataDir, "agent.log"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o600)
|
|
if err == nil {
|
|
log.SetOutput(f)
|
|
}
|
|
log.SetFlags(log.LstdFlags | log.Lmicroseconds | log.LUTC)
|
|
}
|
|
func loadApp(path string) (*agent.App, error) {
|
|
cfg, err := config.LoadAgent(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
setupLog(cfg.DataDir)
|
|
return agent.New(cfg)
|
|
}
|
|
func runConsole(path string) error {
|
|
app, err := loadApp(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
|
defer stop()
|
|
return app.Run(ctx)
|
|
}
|
|
|
|
type serviceHandler struct{ path string }
|
|
|
|
func (h *serviceHandler) Execute(args []string, requests <-chan svc.ChangeRequest, status chan<- svc.Status) (bool, uint32) {
|
|
const accepts = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptSessionChange
|
|
status <- svc.Status{State: svc.StartPending}
|
|
app, err := loadApp(h.path)
|
|
if err != nil {
|
|
return false, 1
|
|
}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
done := make(chan error, 1)
|
|
go func() { done <- app.Run(ctx) }()
|
|
status <- svc.Status{State: svc.Running, Accepts: accepts}
|
|
for {
|
|
select {
|
|
case c := <-requests:
|
|
switch c.Cmd {
|
|
case svc.Interrogate:
|
|
status <- c.CurrentStatus
|
|
case svc.SessionChange:
|
|
// Windows sends SERVICE_CONTROL_SESSIONCHANGE for logon, logoff, connect,
|
|
// disconnect, lock and unlock. Wake the agent worker without doing file or
|
|
// network I/O inside the Service Control Manager callback.
|
|
app.NotifySessionChange()
|
|
case svc.Stop, svc.Shutdown:
|
|
status <- svc.Status{State: svc.StopPending}
|
|
cancel()
|
|
select {
|
|
case <-done:
|
|
case <-time.After(10 * time.Second):
|
|
}
|
|
return false, 0
|
|
}
|
|
case err := <-done:
|
|
if err != nil {
|
|
log.Printf("service stopped: %v", err)
|
|
return false, 1
|
|
}
|
|
return false, 0
|
|
}
|
|
}
|
|
}
|
|
func runAsService(path string) error { return svc.Run(serviceName, &serviceHandler{path: path}) }
|
|
func installService(path string) error {
|
|
m, err := mgr.Connect()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer m.Disconnect()
|
|
if s, err := m.OpenService(serviceName); err == nil {
|
|
s.Close()
|
|
return fmt.Errorf("service already exists")
|
|
}
|
|
exe, err := os.Executable()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s, err := m.CreateService(serviceName, exe, mgr.Config{DisplayName: "SessionGuard Agent", Description: "SessionGuard Terminal Server Agent", StartType: mgr.StartAutomatic}, "-config", path, "-service", "run")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer s.Close()
|
|
return nil
|
|
}
|
|
func uninstallService() error {
|
|
m, err := mgr.Connect()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer m.Disconnect()
|
|
s, err := m.OpenService(serviceName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer s.Close()
|
|
return s.Delete()
|
|
}
|
|
func startService() error {
|
|
m, err := mgr.Connect()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer m.Disconnect()
|
|
s, err := m.OpenService(serviceName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer s.Close()
|
|
return s.Start()
|
|
}
|
|
func stopService() error {
|
|
m, err := mgr.Connect()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer m.Disconnect()
|
|
s, err := m.OpenService(serviceName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer s.Close()
|
|
_, err = s.Control(svc.Stop)
|
|
return err
|
|
}
|