// SPDX-License-Identifier: Apache-2.0 // // Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build windows package secur32 import ( "errors" "fmt" "time" "unsafe" "golang.org/x/sys/windows" ) // based on https://github.com/carlpett/winlsa/blob/master/winlsa.go // //nolint:gochecknoglobals var ( secur32 = windows.NewLazySystemDLL("Secur32.dll") advapi32 = windows.NewLazySystemDLL("advapi32.dll") procLsaEnumerateLogonSessions = secur32.NewProc("LsaEnumerateLogonSessions") procLsaGetLogonSessionData = secur32.NewProc("LsaGetLogonSessionData") procLsaFreeReturnBuffer = secur32.NewProc("LsaFreeReturnBuffer") procLsaNtStatusToWinError = advapi32.NewProc("LsaNtStatusToWinError") ) func GetLogonSessions() ([]*LogonSessionData, error) { var ( buffer uintptr sessionCount uint32 ) err := LsaEnumerateLogonSessions(&sessionCount, &buffer) if err != nil { return nil, err } if buffer != 0 { defer func(buffer uintptr) { _ = LsaFreeReturnBuffer(buffer) }(buffer) } sizeLUID := unsafe.Sizeof(windows.LUID{}) sessionDataSlice := make([]*LogonSessionData, 0, sessionCount) for i := range sessionCount { curPtr := unsafe.Pointer(buffer + (uintptr(i) * sizeLUID)) luid := (*windows.LUID)(curPtr) sessionData, err := GetLogonSessionData(luid) if err != nil { if errors.Is(err, windows.ERROR_ACCESS_DENIED) { // Skip logon sessions that we don't have access to continue } return nil, err } sessionDataSlice = append(sessionDataSlice, sessionData) } return sessionDataSlice, nil } func GetLogonSessionData(luid *windows.LUID) (*LogonSessionData, error) { var dataBuffer *SECURITY_LOGON_SESSION_DATA if err := LsaGetLogonSessionData(luid, &dataBuffer); err != nil { return nil, fmt.Errorf("failed to get logon session data: %w", err) } defer func(buffer uintptr) { _ = LsaFreeReturnBuffer(buffer) }(uintptr(unsafe.Pointer(dataBuffer))) return newLogonSessionData(dataBuffer), nil } func LsaEnumerateLogonSessions(sessionCount *uint32, sessions *uintptr) error { r0, _, _ := procLsaEnumerateLogonSessions.Call(uintptr(unsafe.Pointer(sessionCount)), uintptr(unsafe.Pointer(sessions))) return LsaNtStatusToWinError(r0) } func LsaGetLogonSessionData(luid *windows.LUID, ppLogonSessionData **SECURITY_LOGON_SESSION_DATA) error { r0, _, _ := procLsaGetLogonSessionData.Call(uintptr(unsafe.Pointer(luid)), uintptr(unsafe.Pointer(ppLogonSessionData))) return LsaNtStatusToWinError(r0) } func LsaFreeReturnBuffer(buffer uintptr) error { r0, _, _ := procLsaFreeReturnBuffer.Call(buffer) return LsaNtStatusToWinError(r0) } func LsaNtStatusToWinError(ntstatus uintptr) error { r0, _, err := procLsaNtStatusToWinError.Call(ntstatus) switch { case errors.Is(err, windows.ERROR_SUCCESS): if r0 == 0 { return nil } case errors.Is(err, windows.ERROR_MR_MID_NOT_FOUND): return fmt.Errorf("unknown LSA NTSTATUS code %x", ntstatus) } return windows.Errno(r0) } func newLogonSessionData(data *SECURITY_LOGON_SESSION_DATA) *LogonSessionData { return &LogonSessionData{ LogonId: data.LogonId, UserName: data.UserName.String(), LogonDomain: data.LogonDomain.String(), LogonType: data.LogonType, LogonTime: time.Unix(0, data.LogonTime.Nanoseconds()), } }