144 lines
3.9 KiB
Go
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
|
|
}
|