golang.zx2c4.com/wireguard/windows@v0.5.4-0.20230123132234-dcc0eb72a04b/manager/install.go (about)

     1  /* SPDX-License-Identifier: MIT
     2   *
     3   * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
     4   */
     5  
     6  package manager
     7  
     8  import (
     9  	"errors"
    10  	"log"
    11  	"os"
    12  	"strings"
    13  	"time"
    14  
    15  	"golang.org/x/sys/windows"
    16  	"golang.org/x/sys/windows/svc"
    17  	"golang.org/x/sys/windows/svc/mgr"
    18  
    19  	"golang.zx2c4.com/wireguard/windows/conf"
    20  )
    21  
    22  var cachedServiceManager *mgr.Mgr
    23  
    24  func serviceManager() (*mgr.Mgr, error) {
    25  	if cachedServiceManager != nil {
    26  		return cachedServiceManager, nil
    27  	}
    28  	m, err := mgr.Connect()
    29  	if err != nil {
    30  		return nil, err
    31  	}
    32  	cachedServiceManager = m
    33  	return cachedServiceManager, nil
    34  }
    35  
    36  var ErrManagerAlreadyRunning = errors.New("Manager already installed and running")
    37  
    38  func InstallManager() error {
    39  	m, err := serviceManager()
    40  	if err != nil {
    41  		return err
    42  	}
    43  	path, err := os.Executable()
    44  	if err != nil {
    45  		return nil
    46  	}
    47  
    48  	// TODO: Do we want to bail if executable isn't being run from the right location?
    49  
    50  	serviceName := "WireGuardManager"
    51  	service, err := m.OpenService(serviceName)
    52  	if err == nil {
    53  		status, err := service.Query()
    54  		if err != nil {
    55  			service.Close()
    56  			return err
    57  		}
    58  		if status.State != svc.Stopped {
    59  			service.Close()
    60  			if status.State == svc.StartPending {
    61  				// We were *just* started by something else, so return success here, assuming the other program
    62  				// starting this does the right thing. This can happen when, e.g., the updater relaunches the
    63  				// manager service and then invokes wireguard.exe to raise the UI.
    64  				return nil
    65  			}
    66  			return ErrManagerAlreadyRunning
    67  		}
    68  		err = service.Delete()
    69  		service.Close()
    70  		if err != nil {
    71  			return err
    72  		}
    73  		for {
    74  			service, err = m.OpenService(serviceName)
    75  			if err != nil {
    76  				break
    77  			}
    78  			service.Close()
    79  			time.Sleep(time.Second / 3)
    80  		}
    81  	}
    82  
    83  	config := mgr.Config{
    84  		ServiceType:  windows.SERVICE_WIN32_OWN_PROCESS,
    85  		StartType:    mgr.StartAutomatic,
    86  		ErrorControl: mgr.ErrorNormal,
    87  		DisplayName:  "WireGuard Manager",
    88  	}
    89  
    90  	service, err = m.CreateService(serviceName, path, config, "/managerservice")
    91  	if err != nil {
    92  		return err
    93  	}
    94  	service.Start()
    95  	return service.Close()
    96  }
    97  
    98  func UninstallManager() error {
    99  	m, err := serviceManager()
   100  	if err != nil {
   101  		return err
   102  	}
   103  	serviceName := "WireGuardManager"
   104  	service, err := m.OpenService(serviceName)
   105  	if err != nil {
   106  		return err
   107  	}
   108  	service.Control(svc.Stop)
   109  	err = service.Delete()
   110  	err2 := service.Close()
   111  	if err != nil {
   112  		return err
   113  	}
   114  	return err2
   115  }
   116  
   117  func InstallTunnel(configPath string) error {
   118  	m, err := serviceManager()
   119  	if err != nil {
   120  		return err
   121  	}
   122  	path, err := os.Executable()
   123  	if err != nil {
   124  		return nil
   125  	}
   126  
   127  	name, err := conf.NameFromPath(configPath)
   128  	if err != nil {
   129  		return err
   130  	}
   131  
   132  	serviceName, err := conf.ServiceNameOfTunnel(name)
   133  	if err != nil {
   134  		return err
   135  	}
   136  	service, err := m.OpenService(serviceName)
   137  	if err == nil {
   138  		status, err := service.Query()
   139  		if err != nil && err != windows.ERROR_SERVICE_MARKED_FOR_DELETE {
   140  			service.Close()
   141  			return err
   142  		}
   143  		if status.State != svc.Stopped && err != windows.ERROR_SERVICE_MARKED_FOR_DELETE {
   144  			service.Close()
   145  			return errors.New("Tunnel already installed and running")
   146  		}
   147  		err = service.Delete()
   148  		service.Close()
   149  		if err != nil && err != windows.ERROR_SERVICE_MARKED_FOR_DELETE {
   150  			return err
   151  		}
   152  		for {
   153  			service, err = m.OpenService(serviceName)
   154  			if err != nil && err != windows.ERROR_SERVICE_MARKED_FOR_DELETE {
   155  				break
   156  			}
   157  			service.Close()
   158  			time.Sleep(time.Second / 3)
   159  		}
   160  	}
   161  
   162  	config := mgr.Config{
   163  		ServiceType:  windows.SERVICE_WIN32_OWN_PROCESS,
   164  		StartType:    mgr.StartAutomatic,
   165  		ErrorControl: mgr.ErrorNormal,
   166  		Dependencies: []string{"Nsi", "TcpIp"},
   167  		DisplayName:  "WireGuard Tunnel: " + name,
   168  		SidType:      windows.SERVICE_SID_TYPE_UNRESTRICTED,
   169  	}
   170  	service, err = m.CreateService(serviceName, path, config, "/tunnelservice", configPath)
   171  	if err != nil {
   172  		return err
   173  	}
   174  
   175  	err = service.Start()
   176  	go trackTunnelService(name, service) // Pass off reference to handle.
   177  	return err
   178  }
   179  
   180  func UninstallTunnel(name string) error {
   181  	m, err := serviceManager()
   182  	if err != nil {
   183  		return err
   184  	}
   185  	serviceName, err := conf.ServiceNameOfTunnel(name)
   186  	if err != nil {
   187  		return err
   188  	}
   189  	service, err := m.OpenService(serviceName)
   190  	if err != nil {
   191  		return err
   192  	}
   193  	service.Control(svc.Stop)
   194  	err = service.Delete()
   195  	err2 := service.Close()
   196  	if err != nil && err != windows.ERROR_SERVICE_MARKED_FOR_DELETE {
   197  		return err
   198  	}
   199  	return err2
   200  }
   201  
   202  func changeTunnelServiceConfigFilePath(name, oldPath, newPath string) {
   203  	var err error
   204  	defer func() {
   205  		if err != nil {
   206  			log.Printf("Unable to change tunnel service command line argument from %#q to %#q: %v", oldPath, newPath, err)
   207  		}
   208  	}()
   209  	m, err := serviceManager()
   210  	if err != nil {
   211  		return
   212  	}
   213  	serviceName, err := conf.ServiceNameOfTunnel(name)
   214  	if err != nil {
   215  		return
   216  	}
   217  	service, err := m.OpenService(serviceName)
   218  	if err == windows.ERROR_SERVICE_DOES_NOT_EXIST {
   219  		err = nil
   220  		return
   221  	} else if err != nil {
   222  		return
   223  	}
   224  	defer service.Close()
   225  	config, err := service.Config()
   226  	if err != nil {
   227  		return
   228  	}
   229  	exePath, err := os.Executable()
   230  	if err != nil {
   231  		return
   232  	}
   233  	args, err := windows.DecomposeCommandLine(config.BinaryPathName)
   234  	if err != nil || len(args) != 3 ||
   235  		!strings.EqualFold(args[0], exePath) || args[1] != "/tunnelservice" || !strings.EqualFold(args[2], oldPath) {
   236  		err = nil
   237  		return
   238  	}
   239  	args[2] = newPath
   240  	config.BinaryPathName = windows.ComposeCommandLine(args)
   241  	err = service.UpdateConfig(config)
   242  }