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// +build windows
6
7package svc
8
9import (
10	"errors"
11	"syscall"
12	"unsafe"
13
14	"golang.org/x/sys/windows"
15)
16
17func allocSid(subAuth0 uint32) (*windows.SID, error) {
18	var sid *windows.SID
19	err := windows.AllocateAndInitializeSid(&windows.SECURITY_NT_AUTHORITY,
20		1, subAuth0, 0, 0, 0, 0, 0, 0, 0, &sid)
21	if err != nil {
22		return nil, err
23	}
24	return sid, nil
25}
26
27// IsAnInteractiveSession determines if calling process is running interactively.
28// It queries the process token for membership in the Interactive group.
29// http://stackoverflow.com/questions/2668851/how-do-i-detect-that-my-application-is-running-as-service-or-in-an-interactive-s
30//
31// Deprecated: Use IsWindowsService instead.
32func IsAnInteractiveSession() (bool, error) {
33	interSid, err := allocSid(windows.SECURITY_INTERACTIVE_RID)
34	if err != nil {
35		return false, err
36	}
37	defer windows.FreeSid(interSid)
38
39	serviceSid, err := allocSid(windows.SECURITY_SERVICE_RID)
40	if err != nil {
41		return false, err
42	}
43	defer windows.FreeSid(serviceSid)
44
45	t, err := windows.OpenCurrentProcessToken()
46	if err != nil {
47		return false, err
48	}
49	defer t.Close()
50
51	gs, err := t.GetTokenGroups()
52	if err != nil {
53		return false, err
54	}
55
56	for _, g := range gs.AllGroups() {
57		if windows.EqualSid(g.Sid, interSid) {
58			return true, nil
59		}
60		if windows.EqualSid(g.Sid, serviceSid) {
61			return false, nil
62		}
63	}
64	return false, nil
65}
66
67var (
68	ntdll                      = windows.NewLazySystemDLL("ntdll.dll")
69	_NtQueryInformationProcess = ntdll.NewProc("NtQueryInformationProcess")
70
71	kernel32                    = windows.NewLazySystemDLL("kernel32.dll")
72	_QueryFullProcessImageNameA = kernel32.NewProc("QueryFullProcessImageNameA")
73)
74
75// IsWindowsService reports whether the process is currently executing
76// as a Windows service.
77func IsWindowsService() (bool, error) {
78	// This code was copied from runtime.isWindowsService function.
79
80	// The below technique looks a bit hairy, but it's actually
81	// exactly what the .NET framework does for the similarly named function:
82	// https://github.com/dotnet/extensions/blob/f4066026ca06984b07e90e61a6390ac38152ba93/src/Hosting/WindowsServices/src/WindowsServiceHelpers.cs#L26-L31
83	// Specifically, it looks up whether the parent process has session ID zero
84	// and is called "services".
85	const _CURRENT_PROCESS = ^uintptr(0)
86	// pbi is a PROCESS_BASIC_INFORMATION struct, where we just care about
87	// the 6th pointer inside of it, which contains the pid of the process
88	// parent:
89	// https://github.com/wine-mirror/wine/blob/42cb7d2ad1caba08de235e6319b9967296b5d554/include/winternl.h#L1294
90	var pbi [6]uintptr
91	var pbiLen uint32
92	r0, _, _ := syscall.Syscall6(_NtQueryInformationProcess.Addr(), 5, _CURRENT_PROCESS, 0, uintptr(unsafe.Pointer(&pbi[0])), uintptr(unsafe.Sizeof(pbi)), uintptr(unsafe.Pointer(&pbiLen)), 0)
93	if r0 != 0 {
94		return false, errors.New("NtQueryInformationProcess failed: error=" + itoa(int(r0)))
95	}
96	var psid uint32
97	err := windows.ProcessIdToSessionId(uint32(pbi[5]), &psid)
98	if err != nil {
99		return false, err
100	}
101	if psid != 0 {
102		// parent session id should be 0 for service process
103		return false, nil
104	}
105
106	pproc, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pbi[5]))
107	if err != nil {
108		return false, err
109	}
110	defer windows.CloseHandle(pproc)
111
112	// exeName gets the path to the executable image of the parent process
113	var exeName [261]byte
114	exeNameLen := uint32(len(exeName) - 1)
115	r0, _, e0 := syscall.Syscall6(_QueryFullProcessImageNameA.Addr(), 4, uintptr(pproc), 0, uintptr(unsafe.Pointer(&exeName[0])), uintptr(unsafe.Pointer(&exeNameLen)), 0, 0)
116	if r0 == 0 {
117		if e0 != 0 {
118			return false, e0
119		} else {
120			return false, syscall.EINVAL
121		}
122	}
123	const (
124		servicesLower = "services.exe"
125		servicesUpper = "SERVICES.EXE"
126	)
127	i := int(exeNameLen) - 1
128	j := len(servicesLower) - 1
129	if i < j {
130		return false, nil
131	}
132	for {
133		if j == -1 {
134			return i == -1 || exeName[i] == '\\', nil
135		}
136		if exeName[i] != servicesLower[j] && exeName[i] != servicesUpper[j] {
137			return false, nil
138		}
139		i--
140		j--
141	}
142}
143
144func itoa(val int) string { // do it here rather than with fmt to avoid dependency
145	if val < 0 {
146		return "-" + itoa(-val)
147	}
148	var buf [32]byte // big enough for int64
149	i := len(buf) - 1
150	for val >= 10 {
151		buf[i] = byte(val%10 + '0')
152		i--
153		val /= 10
154	}
155	buf[i] = byte(val + '0')
156	return string(buf[i:])
157}
158