Files
tsnet-proxy/vendor/github.com/dblohm7/wingoes/util.go
T
2026-10-22 12:27:19 +01:00

144 lines
3.9 KiB
Go

// Copyright (c) 2022 Tailscale Inc & AUTHORS. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build windows
package wingoes
import (
"fmt"
"os"
"strings"
"unicode/utf16"
"unsafe"
"golang.org/x/sys/windows"
_ "golang.org/x/sys/windows/registry"
)
// UserSIDs contains pointers to the SIDs for a user and their primary group.
type UserSIDs struct {
User *windows.SID
PrimaryGroup *windows.SID
}
// CurrentProcessUserSIDs returns a UserSIDs containing the SIDs of the user
// and primary group who own the current process.
func CurrentProcessUserSIDs() (*UserSIDs, error) {
token, err := windows.OpenCurrentProcessToken()
if err != nil {
return nil, err
}
defer token.Close()
userInfo, err := token.GetTokenUser()
if err != nil {
return nil, err
}
primaryGroup, err := token.GetTokenPrimaryGroup()
if err != nil {
return nil, err
}
// We just want the SIDs, not the rest of the structs that were output.
userSid, err := userInfo.User.Sid.Copy()
if err != nil {
return nil, err
}
primaryGroupSid, err := primaryGroup.PrimaryGroup.Copy()
if err != nil {
return nil, err
}
return &UserSIDs{User: userSid, PrimaryGroup: primaryGroupSid}, nil
}
// getTokenInfoVariableLen obtains variable-length token information. Use
// this function for information classes that output variable-length data.
func getTokenInfoVariableLen[T any](token windows.Token, infoClass uint32) (*T, error) {
var buf []byte
var desiredLen uint32
err := windows.GetTokenInformation(token, infoClass, nil, 0, &desiredLen)
for err == windows.ERROR_INSUFFICIENT_BUFFER {
buf = make([]byte, desiredLen)
err = windows.GetTokenInformation(token, infoClass, unsafe.SliceData(buf), desiredLen, &desiredLen)
}
if err != nil {
return nil, err
}
return (*T)(unsafe.Pointer(unsafe.SliceData(buf))), nil
}
// getTokenInfoFixedLen obtains known fixed-length token information. Use this
// function for information classes that output enumerations, BOOLs, integers etc.
func getTokenInfoFixedLen[T any](token windows.Token, infoClass uint32) (result T, _ error) {
var actualLen uint32
err := windows.GetTokenInformation(token, infoClass, (*byte)(unsafe.Pointer(&result)), uint32(unsafe.Sizeof(result)), &actualLen)
return result, err
}
// MultiSZDecode decodes a UTF-16 buffer containing either a [registry.MULTI_SZ]
// value or a Win32 environment block, and returns the contents as a slice of
// strings. It returns an empty slice if the value is empty, or nil if buf
// cannot be decoded.
func MultiSZDecode(buf []uint16) []string {
if buf == nil {
// Technically an empty value, but not a failure.
return []string{}
}
const doubleNulLen = 2
if len(buf) < doubleNulLen {
return nil
}
// We cannot rely on the len(buf) to determine the end of the data; registry
// APIs treat MULTI_SZ strings as double-nul-terminated C-style strings.
doubleNulIdx := int(-1)
fl := len(buf) - 1
for i, v := range buf {
if v == 0 && i < fl && buf[i+1] == 0 {
doubleNulIdx = i
break
}
}
if doubleNulIdx < 0 {
return nil
}
str := string(utf16.Decode(buf[:doubleNulIdx]))
if len(str) == 0 {
// Technically an empty value, but not a failure.
return []string{}
}
return strings.Split(str, "\x00")
}
// MultiSZEncode encodes strs into the format utilized by [registry.MULTI_SZ]
// values and Win32 environment blocks. strs may be nil or empty, but it must
// not contain any empty strings.
func MultiSZEncode(strs []string) ([]uint16, error) {
var buf strings.Builder
for _, v := range strs {
if len(v) == 0 {
return nil, fmt.Errorf("MultiSZEncode: %w: empty strings not permitted", os.ErrInvalid)
}
buf.WriteString(v)
buf.WriteByte(0)
}
if buf.Len() == 0 {
// So that we end with a double-nul in the empty case
buf.WriteByte(0)
}
buf.WriteByte(0)
return utf16.Encode([]rune(buf.String())), nil
}