1
0
mirror of https://github.com/DNSCrypt/dnscrypt-proxy.git synced 2024-12-27 00:12:31 +01:00
dnscrypt-proxy/vendor/github.com/kardianos/service/service_windows.go
2022-10-15 10:37:07 +02:00

559 lines
13 KiB
Go

// Copyright 2015 Daniel Theophanes.
// Use of this source code is governed by a zlib-style
// license that can be found in the LICENSE file.
package service
import (
"fmt"
"os"
"os/signal"
"strconv"
"strings"
"sync"
"syscall"
"time"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/eventlog"
"golang.org/x/sys/windows/svc/mgr"
)
const (
version = "windows-service"
StartType = "StartType"
ServiceStartManual = "manual"
ServiceStartDisabled = "disabled"
ServiceStartAutomatic = "automatic"
OnFailure = "OnFailure"
OnFailureRestart = "restart"
OnFailureReboot = "reboot"
OnFailureNoAction = "noaction"
OnFailureDelayDuration = "OnFailureDelayDuration"
OnFailureResetPeriod = "OnFailureResetPeriod"
errnoServiceDoesNotExist syscall.Errno = 1060
)
type windowsService struct {
i Interface
*Config
errSync sync.Mutex
stopStartErr error
}
// WindowsLogger allows using windows specific logging methods.
type WindowsLogger struct {
ev *eventlog.Log
errs chan<- error
}
type windowsSystem struct{}
func (windowsSystem) String() string {
return version
}
func (windowsSystem) Detect() bool {
return true
}
func (windowsSystem) Interactive() bool {
return interactive
}
func (windowsSystem) New(i Interface, c *Config) (Service, error) {
ws := &windowsService{
i: i,
Config: c,
}
return ws, nil
}
func init() {
ChooseSystem(windowsSystem{})
}
func (l WindowsLogger) send(err error) error {
if err == nil {
return nil
}
if l.errs != nil {
l.errs <- err
}
return err
}
// Error logs an error message.
func (l WindowsLogger) Error(v ...interface{}) error {
return l.send(l.ev.Error(3, fmt.Sprint(v...)))
}
// Warning logs an warning message.
func (l WindowsLogger) Warning(v ...interface{}) error {
return l.send(l.ev.Warning(2, fmt.Sprint(v...)))
}
// Info logs an info message.
func (l WindowsLogger) Info(v ...interface{}) error {
return l.send(l.ev.Info(1, fmt.Sprint(v...)))
}
// Errorf logs an error message.
func (l WindowsLogger) Errorf(format string, a ...interface{}) error {
return l.send(l.ev.Error(3, fmt.Sprintf(format, a...)))
}
// Warningf logs an warning message.
func (l WindowsLogger) Warningf(format string, a ...interface{}) error {
return l.send(l.ev.Warning(2, fmt.Sprintf(format, a...)))
}
// Infof logs an info message.
func (l WindowsLogger) Infof(format string, a ...interface{}) error {
return l.send(l.ev.Info(1, fmt.Sprintf(format, a...)))
}
// NError logs an error message and an event ID.
func (l WindowsLogger) NError(eventID uint32, v ...interface{}) error {
return l.send(l.ev.Error(eventID, fmt.Sprint(v...)))
}
// NWarning logs an warning message and an event ID.
func (l WindowsLogger) NWarning(eventID uint32, v ...interface{}) error {
return l.send(l.ev.Warning(eventID, fmt.Sprint(v...)))
}
// NInfo logs an info message and an event ID.
func (l WindowsLogger) NInfo(eventID uint32, v ...interface{}) error {
return l.send(l.ev.Info(eventID, fmt.Sprint(v...)))
}
// NErrorf logs an error message and an event ID.
func (l WindowsLogger) NErrorf(eventID uint32, format string, a ...interface{}) error {
return l.send(l.ev.Error(eventID, fmt.Sprintf(format, a...)))
}
// NWarningf logs an warning message and an event ID.
func (l WindowsLogger) NWarningf(eventID uint32, format string, a ...interface{}) error {
return l.send(l.ev.Warning(eventID, fmt.Sprintf(format, a...)))
}
// NInfof logs an info message and an event ID.
func (l WindowsLogger) NInfof(eventID uint32, format string, a ...interface{}) error {
return l.send(l.ev.Info(eventID, fmt.Sprintf(format, a...)))
}
var interactive = false
func init() {
isService, err := svc.IsWindowsService()
if err != nil {
panic(err)
}
interactive = !isService
}
func (ws *windowsService) String() string {
if len(ws.DisplayName) > 0 {
return ws.DisplayName
}
return ws.Name
}
func (ws *windowsService) Platform() string {
return version
}
func (ws *windowsService) setError(err error) {
ws.errSync.Lock()
defer ws.errSync.Unlock()
ws.stopStartErr = err
}
func (ws *windowsService) getError() error {
ws.errSync.Lock()
defer ws.errSync.Unlock()
return ws.stopStartErr
}
func (ws *windowsService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) {
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown
changes <- svc.Status{State: svc.StartPending}
if err := ws.i.Start(ws); err != nil {
ws.setError(err)
return true, 1
}
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
loop:
for {
c := <-r
switch c.Cmd {
case svc.Interrogate:
changes <- c.CurrentStatus
case svc.Stop:
changes <- svc.Status{State: svc.StopPending}
if err := ws.i.Stop(ws); err != nil {
ws.setError(err)
return true, 2
}
break loop
case svc.Shutdown:
changes <- svc.Status{State: svc.StopPending}
var err error
if wsShutdown, ok := ws.i.(Shutdowner); ok {
err = wsShutdown.Shutdown(ws)
} else {
err = ws.i.Stop(ws)
}
if err != nil {
ws.setError(err)
return true, 2
}
break loop
default:
continue loop
}
}
return false, 0
}
func lowPrivMgr() (*mgr.Mgr, error) {
h, err := windows.OpenSCManager(nil, nil, windows.SC_MANAGER_CONNECT|windows.SC_MANAGER_ENUMERATE_SERVICE)
if err != nil {
return nil, err
}
return &mgr.Mgr{Handle: h}, nil
}
func lowPrivSvc(m *mgr.Mgr, name string) (*mgr.Service, error) {
h, err := windows.OpenService(
m.Handle, syscall.StringToUTF16Ptr(name),
windows.SERVICE_QUERY_CONFIG|windows.SERVICE_QUERY_STATUS|windows.SERVICE_START|windows.SERVICE_STOP)
if err != nil {
return nil, err
}
return &mgr.Service{Handle: h, Name: name}, nil
}
func (ws *windowsService) setEnvironmentVariablesInRegistry() error {
if len(ws.EnvVars) == 0 {
return nil
}
k, _, err := registry.CreateKey(
registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Services\`+ws.Name,
registry.QUERY_VALUE|registry.SET_VALUE|registry.CREATE_SUB_KEY)
if err != nil {
return fmt.Errorf("failed creating env var registry key, err = %v", err)
}
envStrings := make([]string, 0, len(ws.EnvVars))
for k, v := range ws.EnvVars {
envStrings = append(envStrings, k+"="+v)
}
if err := k.SetStringsValue("Environment", envStrings); err != nil {
return fmt.Errorf("failed setting env var registry key, err = %v", err)
}
if err := k.Close(); err != nil {
return fmt.Errorf("failed closing env var registry key, err = %v", err)
}
return nil
}
func (ws *windowsService) Install() error {
exepath, err := ws.execPath()
if err != nil {
return err
}
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
if err := ws.setEnvironmentVariablesInRegistry(); err != nil {
return err
}
s, err := m.OpenService(ws.Name)
if err == nil {
s.Close()
return fmt.Errorf("service %s already exists", ws.Name)
}
var startType int32
switch ws.Option.string(StartType, ServiceStartAutomatic) {
case ServiceStartAutomatic:
startType = mgr.StartAutomatic
case ServiceStartManual:
startType = mgr.StartManual
case ServiceStartDisabled:
startType = mgr.StartDisabled
}
serviceType := windows.SERVICE_WIN32_OWN_PROCESS
if ws.Option.bool("Interactive", false) {
serviceType = serviceType | windows.SERVICE_INTERACTIVE_PROCESS
}
s, err = m.CreateService(ws.Name, exepath, mgr.Config{
DisplayName: ws.DisplayName,
Description: ws.Description,
StartType: uint32(startType),
ServiceStartName: ws.UserName,
Password: ws.Option.string("Password", ""),
Dependencies: ws.Dependencies,
DelayedAutoStart: ws.Option.bool("DelayedAutoStart", false),
ServiceType: uint32(serviceType),
}, ws.Arguments...)
if err != nil {
return err
}
if onFailure := ws.Option.string(OnFailure, ""); onFailure != "" {
var delay = 1 * time.Second
if d, err := time.ParseDuration(ws.Option.string(OnFailureDelayDuration, "1s")); err == nil {
delay = d
}
var actionType int
switch onFailure {
case OnFailureReboot:
actionType = mgr.ComputerReboot
case OnFailureRestart:
actionType = mgr.ServiceRestart
case OnFailureNoAction:
actionType = mgr.NoAction
default:
actionType = mgr.ServiceRestart
}
if err := s.SetRecoveryActions([]mgr.RecoveryAction{
{
Type: actionType,
Delay: delay,
},
}, uint32(ws.Option.int(OnFailureResetPeriod, 10))); err != nil {
return err
}
}
defer s.Close()
err = eventlog.InstallAsEventCreate(ws.Name, eventlog.Error|eventlog.Warning|eventlog.Info)
if err != nil {
if !strings.Contains(err.Error(), "exists") {
s.Delete()
return fmt.Errorf("SetupEventLogSource() failed: %s", err)
}
}
return nil
}
func (ws *windowsService) Uninstall() error {
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
s, err := m.OpenService(ws.Name)
if err != nil {
return fmt.Errorf("service %s is not installed", ws.Name)
}
defer s.Close()
err = s.Delete()
if err != nil {
return err
}
err = eventlog.Remove(ws.Name)
if err != nil {
return fmt.Errorf("RemoveEventLogSource() failed: %s", err)
}
return nil
}
func (ws *windowsService) Run() error {
ws.setError(nil)
if !interactive {
// Return error messages from start and stop routines
// that get executed in the Execute method.
// Guarded with a mutex as it may run a different thread
// (callback from windows).
runErr := svc.Run(ws.Name, ws)
startStopErr := ws.getError()
if startStopErr != nil {
return startStopErr
}
if runErr != nil {
return runErr
}
return nil
}
err := ws.i.Start(ws)
if err != nil {
return err
}
sigChan := make(chan os.Signal)
signal.Notify(sigChan, os.Interrupt)
<-sigChan
return ws.i.Stop(ws)
}
func (ws *windowsService) Status() (Status, error) {
m, err := lowPrivMgr()
if err != nil {
return StatusUnknown, err
}
defer m.Disconnect()
s, err := lowPrivSvc(m, ws.Name)
if err != nil {
if errno, ok := err.(syscall.Errno); ok && errno == errnoServiceDoesNotExist {
return StatusUnknown, ErrNotInstalled
}
return StatusUnknown, err
}
defer s.Close()
status, err := s.Query()
if err != nil {
return StatusUnknown, err
}
switch status.State {
case svc.StartPending:
fallthrough
case svc.Running:
return StatusRunning, nil
case svc.PausePending:
fallthrough
case svc.Paused:
fallthrough
case svc.ContinuePending:
fallthrough
case svc.StopPending:
fallthrough
case svc.Stopped:
return StatusStopped, nil
default:
return StatusUnknown, fmt.Errorf("unknown status %v", status)
}
}
func (ws *windowsService) Start() error {
m, err := lowPrivMgr()
if err != nil {
return err
}
defer m.Disconnect()
s, err := lowPrivSvc(m, ws.Name)
if err != nil {
return err
}
defer s.Close()
return s.Start()
}
func (ws *windowsService) Stop() error {
m, err := lowPrivMgr()
if err != nil {
return err
}
defer m.Disconnect()
s, err := lowPrivSvc(m, ws.Name)
if err != nil {
return err
}
defer s.Close()
return ws.stopWait(s)
}
func (ws *windowsService) Restart() error {
m, err := lowPrivMgr()
if err != nil {
return err
}
defer m.Disconnect()
s, err := lowPrivSvc(m, ws.Name)
if err != nil {
return err
}
defer s.Close()
err = ws.stopWait(s)
if err != nil {
return err
}
return s.Start()
}
func (ws *windowsService) stopWait(s *mgr.Service) error {
// First stop the service. Then wait for the service to
// actually stop before starting it.
status, err := s.Control(svc.Stop)
if err != nil {
return err
}
timeDuration := time.Millisecond * 50
timeout := time.After(getStopTimeout() + (timeDuration * 2))
tick := time.NewTicker(timeDuration)
defer tick.Stop()
for status.State != svc.Stopped {
select {
case <-tick.C:
status, err = s.Query()
if err != nil {
return err
}
case <-timeout:
break
}
}
return nil
}
// getStopTimeout fetches the time before windows will kill the service.
func getStopTimeout() time.Duration {
// For default and paths see https://support.microsoft.com/en-us/kb/146092
defaultTimeout := time.Millisecond * 20000
key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control`, registry.READ)
if err != nil {
return defaultTimeout
}
sv, _, err := key.GetStringValue("WaitToKillServiceTimeout")
if err != nil {
return defaultTimeout
}
v, err := strconv.Atoi(sv)
if err != nil {
return defaultTimeout
}
return time.Millisecond * time.Duration(v)
}
func (ws *windowsService) Logger(errs chan<- error) (Logger, error) {
if interactive {
return ConsoleLogger, nil
}
return ws.SystemLogger(errs)
}
func (ws *windowsService) SystemLogger(errs chan<- error) (Logger, error) {
el, err := eventlog.Open(ws.Name)
if err != nil {
return nil, err
}
return WindowsLogger{el, errs}, nil
}