1// Copyright 2012 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5//go:build windows
6// +build windows
7
8package svc
9
10import (
11	"path/filepath"
12	"strings"
13	"unsafe"
14
15	"golang.org/x/sys/windows"
16)
17
18func allocSid(subAuth0 uint32) (*windows.SID, error) {
19	var sid *windows.SID
20	err := windows.AllocateAndInitializeSid(&windows.SECURITY_NT_AUTHORITY,
21		1, subAuth0, 0, 0, 0, 0, 0, 0, 0, &sid)
22	if err != nil {
23		return nil, err
24	}
25	return sid, nil
26}
27
28// IsAnInteractiveSession determines if calling process is running interactively.
29// It queries the process token for membership in the Interactive group.
30// http://stackoverflow.com/questions/2668851/how-do-i-detect-that-my-application-is-running-as-service-or-in-an-interactive-s
31//
32// Deprecated: Use IsWindowsService instead.
33func IsAnInteractiveSession() (bool, error) {
34	interSid, err := allocSid(windows.SECURITY_INTERACTIVE_RID)
35	if err != nil {
36		return false, err
37	}
38	defer windows.FreeSid(interSid)
39
40	serviceSid, err := allocSid(windows.SECURITY_SERVICE_RID)
41	if err != nil {
42		return false, err
43	}
44	defer windows.FreeSid(serviceSid)
45
46	t, err := windows.OpenCurrentProcessToken()
47	if err != nil {
48		return false, err
49	}
50	defer t.Close()
51
52	gs, err := t.GetTokenGroups()
53	if err != nil {
54		return false, err
55	}
56
57	for _, g := range gs.AllGroups() {
58		if windows.EqualSid(g.Sid, interSid) {
59			return true, nil
60		}
61		if windows.EqualSid(g.Sid, serviceSid) {
62			return false, nil
63		}
64	}
65	return false, nil
66}
67
68// IsWindowsService reports whether the process is currently executing
69// as a Windows service.
70func IsWindowsService() (bool, error) {
71	// The below technique looks a bit hairy, but it's actually
72	// exactly what the .NET framework does for the similarly named function:
73	// https://github.com/dotnet/extensions/blob/f4066026ca06984b07e90e61a6390ac38152ba93/src/Hosting/WindowsServices/src/WindowsServiceHelpers.cs#L26-L31
74	// Specifically, it looks up whether the parent process has session ID zero
75	// and is called "services".
76
77	var pbi windows.PROCESS_BASIC_INFORMATION
78	pbiLen := uint32(unsafe.Sizeof(pbi))
79	err := windows.NtQueryInformationProcess(windows.CurrentProcess(), windows.ProcessBasicInformation, unsafe.Pointer(&pbi), pbiLen, &pbiLen)
80	if err != nil {
81		return false, err
82	}
83	var psid uint32
84	err = windows.ProcessIdToSessionId(uint32(pbi.InheritedFromUniqueProcessId), &psid)
85	if err != nil || psid != 0 {
86		return false, nil
87	}
88	pproc, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pbi.InheritedFromUniqueProcessId))
89	if err != nil {
90		return false, err
91	}
92	defer windows.CloseHandle(pproc)
93	var exeNameBuf [261]uint16
94	exeNameLen := uint32(len(exeNameBuf) - 1)
95	err = windows.QueryFullProcessImageName(pproc, 0, &exeNameBuf[0], &exeNameLen)
96	if err != nil {
97		return false, err
98	}
99	exeName := windows.UTF16ToString(exeNameBuf[:exeNameLen])
100	if !strings.EqualFold(filepath.Base(exeName), "services.exe") {
101		return false, nil
102	}
103	system32, err := windows.GetSystemDirectory()
104	if err != nil {
105		return false, err
106	}
107	targetExeName := filepath.Join(system32, "services.exe")
108	return strings.EqualFold(exeName, targetExeName), nil
109}
110