| // Copyright 2012 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // +build windows |
| |
| package svc |
| |
| import ( |
| "errors" |
| "syscall" |
| "unsafe" |
| |
| "golang.org/x/sys/windows" |
| ) |
| |
| func allocSid(subAuth0 uint32) (*windows.SID, error) { |
| var sid *windows.SID |
| err := windows.AllocateAndInitializeSid(&windows.SECURITY_NT_AUTHORITY, |
| 1, subAuth0, 0, 0, 0, 0, 0, 0, 0, &sid) |
| if err != nil { |
| return nil, err |
| } |
| return sid, nil |
| } |
| |
| // IsAnInteractiveSession determines if calling process is running interactively. |
| // It queries the process token for membership in the Interactive group. |
| // http://stackoverflow.com/questions/2668851/how-do-i-detect-that-my-application-is-running-as-service-or-in-an-interactive-s |
| // |
| // Deprecated: Use IsWindowsService instead. |
| func IsAnInteractiveSession() (bool, error) { |
| interSid, err := allocSid(windows.SECURITY_INTERACTIVE_RID) |
| if err != nil { |
| return false, err |
| } |
| defer windows.FreeSid(interSid) |
| |
| serviceSid, err := allocSid(windows.SECURITY_SERVICE_RID) |
| if err != nil { |
| return false, err |
| } |
| defer windows.FreeSid(serviceSid) |
| |
| t, err := windows.OpenCurrentProcessToken() |
| if err != nil { |
| return false, err |
| } |
| defer t.Close() |
| |
| gs, err := t.GetTokenGroups() |
| if err != nil { |
| return false, err |
| } |
| |
| for _, g := range gs.AllGroups() { |
| if windows.EqualSid(g.Sid, interSid) { |
| return true, nil |
| } |
| if windows.EqualSid(g.Sid, serviceSid) { |
| return false, nil |
| } |
| } |
| return false, nil |
| } |
| |
| var ( |
| ntdll = windows.NewLazySystemDLL("ntdll.dll") |
| _NtQueryInformationProcess = ntdll.NewProc("NtQueryInformationProcess") |
| |
| kernel32 = windows.NewLazySystemDLL("kernel32.dll") |
| _QueryFullProcessImageNameA = kernel32.NewProc("QueryFullProcessImageNameA") |
| ) |
| |
| // IsWindowsService reports whether the process is currently executing |
| // as a Windows service. |
| func IsWindowsService() (bool, error) { |
| // This code was copied from runtime.isWindowsService function. |
| |
| // The below technique looks a bit hairy, but it's actually |
| // exactly what the .NET framework does for the similarly named function: |
| // https://github.com/dotnet/extensions/blob/f4066026ca06984b07e90e61a6390ac38152ba93/src/Hosting/WindowsServices/src/WindowsServiceHelpers.cs#L26-L31 |
| // Specifically, it looks up whether the parent process has session ID zero |
| // and is called "services". |
| const _CURRENT_PROCESS = ^uintptr(0) |
| // pbi is a PROCESS_BASIC_INFORMATION struct, where we just care about |
| // the 6th pointer inside of it, which contains the pid of the process |
| // parent: |
| // https://github.com/wine-mirror/wine/blob/42cb7d2ad1caba08de235e6319b9967296b5d554/include/winternl.h#L1294 |
| var pbi [6]uintptr |
| var pbiLen uint32 |
| r0, _, _ := syscall.Syscall6(_NtQueryInformationProcess.Addr(), 5, _CURRENT_PROCESS, 0, uintptr(unsafe.Pointer(&pbi[0])), uintptr(unsafe.Sizeof(pbi)), uintptr(unsafe.Pointer(&pbiLen)), 0) |
| if r0 != 0 { |
| return false, errors.New("NtQueryInformationProcess failed: error=" + itoa(int(r0))) |
| } |
| var psid uint32 |
| err := windows.ProcessIdToSessionId(uint32(pbi[5]), &psid) |
| if err != nil { |
| if err == windows.ERROR_INVALID_PARAMETER { |
| // This error happens when Windows cannot find process parent. |
| // Perhaps process parent exited. |
| // Assume we are not running in a service, because service |
| // parent process (services.exe) cannot exit. |
| return false, nil |
| } |
| return false, err |
| } |
| if psid != 0 { |
| // parent session id should be 0 for service process |
| return false, nil |
| } |
| |
| pproc, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pbi[5])) |
| if err != nil { |
| return false, err |
| } |
| defer windows.CloseHandle(pproc) |
| |
| // exeName gets the path to the executable image of the parent process |
| var exeName [261]byte |
| exeNameLen := uint32(len(exeName) - 1) |
| r0, _, e0 := syscall.Syscall6(_QueryFullProcessImageNameA.Addr(), 4, uintptr(pproc), 0, uintptr(unsafe.Pointer(&exeName[0])), uintptr(unsafe.Pointer(&exeNameLen)), 0, 0) |
| if r0 == 0 { |
| if e0 != 0 { |
| return false, e0 |
| } else { |
| return false, syscall.EINVAL |
| } |
| } |
| const ( |
| servicesLower = "services.exe" |
| servicesUpper = "SERVICES.EXE" |
| ) |
| i := int(exeNameLen) - 1 |
| j := len(servicesLower) - 1 |
| if i < j { |
| return false, nil |
| } |
| for { |
| if j == -1 { |
| return i == -1 || exeName[i] == '\\', nil |
| } |
| if exeName[i] != servicesLower[j] && exeName[i] != servicesUpper[j] { |
| return false, nil |
| } |
| i-- |
| j-- |
| } |
| } |
| |
| func itoa(val int) string { // do it here rather than with fmt to avoid dependency |
| if val < 0 { |
| return "-" + itoa(-val) |
| } |
| var buf [32]byte // big enough for int64 |
| i := len(buf) - 1 |
| for val >= 10 { |
| buf[i] = byte(val%10 + '0') |
| i-- |
| val /= 10 |
| } |
| buf[i] = byte(val + '0') |
| return string(buf[i:]) |
| } |