Files
rvbox/internal/client/supervisor/windows/discovery_windows.go
T

101 lines
3.1 KiB
Go

//go:build windows
package windows
import (
"fmt"
"unsafe"
"golang.org/x/sys/windows"
)
const (
wtsCurrentServerHandle windows.Handle = 0
invalidSessionID = ^uint32(0)
)
// DiscoverActiveSessions translates only verified active WTS user tokens into
// selector DTOs. It is intended for the LocalSystem service: WTSQueryUserToken
// requires LocalSystem and SeTcbPrivilege. A token-query failure leaves that
// WTS session unusable rather than guessing an identity.
func DiscoverActiveSessions() ([]SessionCandidate, error) {
var sessions *windows.WTS_SESSION_INFO
var count uint32
if err := windows.WTSEnumerateSessions(wtsCurrentServerHandle, 0, 1, &sessions, &count); err != nil {
return nil, fmt.Errorf("enumerate WTS sessions: %w", err)
}
if sessions == nil {
return nil, nil
}
defer windows.WTSFreeMemory(uintptr(unsafe.Pointer(sessions)))
console := windows.WTSGetActiveConsoleSessionId()
result := make([]SessionCandidate, 0, count)
for _, session := range unsafe.Slice(sessions, count) {
if session.State != windows.WTSActive {
continue
}
candidate, err := candidateFromSession(session.SessionID, console)
if err == nil {
result = append(result, candidate)
}
}
return result, nil
}
func candidateFromSession(sessionID, console uint32) (SessionCandidate, error) {
var token windows.Token
if err := windows.WTSQueryUserToken(sessionID, &token); err != nil {
return SessionCandidate{}, fmt.Errorf("query user token for session %d: %w", sessionID, err)
}
defer token.Close()
user, err := token.GetTokenUser()
if err != nil || user.User.Sid == nil {
return SessionCandidate{}, fmt.Errorf("query token user for session %d: %w", sessionID, err)
}
logonSID, err := tokenLogonSID(token)
if err != nil {
return SessionCandidate{}, fmt.Errorf("query token logon SID for session %d: %w", sessionID, err)
}
facts := tokenFacts(token)
if !facts.Usable {
return SessionCandidate{}, fmt.Errorf("token for session %d is not usable", sessionID)
}
return SessionCandidate{
SessionID: sessionID, Console: console != invalidSessionID && sessionID == console,
UserSID: user.User.Sid.String(), LogonSID: logonSID, Token: facts,
}, nil
}
func tokenFacts(token windows.Token) TokenFacts {
facts := TokenFacts{Usable: true}
if token.IsElevated() {
facts.FullAdministrator = true
return facts
}
// WTSQueryUserToken normally returns the user's standard/filtered primary
// token. The launch adapter re-verifies integrity and can create a
// restricted medium token if a legacy full-only token is encountered.
facts.StandardOrFiltered = true
linked, err := token.GetLinkedToken()
if err == nil {
facts.LinkedFullAvailable = linked.IsElevated()
_ = linked.Close()
}
return facts
}
func tokenLogonSID(token windows.Token) (string, error) {
groups, err := token.GetTokenGroups()
if err != nil {
return "", err
}
for _, group := range groups.AllGroups() {
if group.Sid != nil && group.Attributes&windows.SE_GROUP_LOGON_ID == windows.SE_GROUP_LOGON_ID {
return group.Sid.String(), nil
}
}
return "", fmt.Errorf("token has no logon SID")
}