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