mirror of https://github.com/tailscale/tailscale/
util/winutil: add UserProfile type for (un)loading user profiles
S4U logons do not automatically load the associated user profile. In this PR we add UserProfile to handle that part. Windows docs indicate that we should try to resolve a remote profile path when present, so we attempt to do so when the local computer is joined to a domain. Updates #12383 Signed-off-by: Aaron Klotz <aaron@tailscale.com>pull/11429/head
parent
9189fe007b
commit
bd2a6d5386
@ -0,0 +1,205 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package winutil
|
||||
|
||||
import (
|
||||
"os/user"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/winutil/winenv"
|
||||
)
|
||||
|
||||
type _PROFILEINFO struct {
|
||||
Size uint32
|
||||
Flags uint32
|
||||
UserName *uint16
|
||||
ProfilePath *uint16
|
||||
DefaultPath *uint16
|
||||
ServerName *uint16
|
||||
PolicyPath *uint16
|
||||
Profile registry.Key
|
||||
}
|
||||
|
||||
// _PROFILEINFO flags
|
||||
const (
|
||||
_PI_NOUI = 0x00000001
|
||||
)
|
||||
|
||||
type _USER_INFO_4 struct {
|
||||
Name *uint16
|
||||
Password *uint16
|
||||
PasswordAge uint32
|
||||
Priv uint32
|
||||
HomeDir *uint16
|
||||
Comment *uint16
|
||||
Flags uint32
|
||||
ScriptPath *uint16
|
||||
AuthFlags uint32
|
||||
FullName *uint16
|
||||
UsrComment *uint16
|
||||
Parms *uint16
|
||||
Workstations *uint16
|
||||
LastLogon uint32
|
||||
LastLogoff uint32
|
||||
AcctExpires uint32
|
||||
MaxStorage uint32
|
||||
UnitsPerWeek uint32
|
||||
LogonHours *byte
|
||||
BadPwCount uint32
|
||||
NumLogons uint32
|
||||
LogonServer *uint16
|
||||
CountryCode uint32
|
||||
CodePage uint32
|
||||
UserSID *windows.SID
|
||||
PrimaryGroupID uint32
|
||||
Profile *uint16
|
||||
HomeDirDrive *uint16
|
||||
PasswordExpired uint32
|
||||
}
|
||||
|
||||
// UserProfile encapsulates a loaded Windows user profile.
|
||||
type UserProfile struct {
|
||||
token windows.Token
|
||||
profileKey registry.Key
|
||||
}
|
||||
|
||||
// LoadUserProfile loads the Windows user profile associated with token and u.
|
||||
// u serves simply as a hint for speeding up resolution of the username and thus
|
||||
// must reference the same user as token. u may also be nil, in which case token
|
||||
// is queried for the username.
|
||||
func LoadUserProfile(token windows.Token, u *user.User) (up *UserProfile, err error) {
|
||||
computerName, userName, err := getComputerAndUserName(token, u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var roamingProfilePath *uint16
|
||||
if winenv.IsDomainJoined() {
|
||||
roamingProfilePath, err = getRoamingProfilePath(nil, computerName, userName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
pi := _PROFILEINFO{
|
||||
Size: uint32(unsafe.Sizeof(_PROFILEINFO{})),
|
||||
Flags: _PI_NOUI,
|
||||
UserName: userName,
|
||||
ProfilePath: roamingProfilePath,
|
||||
ServerName: computerName,
|
||||
}
|
||||
if err := loadUserProfile(token, &pi); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Duplicate the token so that we have a copy to use during cleanup without
|
||||
// consuming the token passed into this function.
|
||||
var dupToken windows.Handle
|
||||
cp := windows.CurrentProcess()
|
||||
if err := windows.DuplicateHandle(cp, windows.Handle(token), cp, &dupToken, 0,
|
||||
false, windows.DUPLICATE_SAME_ACCESS); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &UserProfile{
|
||||
token: windows.Token(dupToken),
|
||||
profileKey: pi.Profile,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RegKey returns the registry key associated with the user profile.
|
||||
// The caller must not close the returned key.
|
||||
func (up *UserProfile) RegKey() registry.Key {
|
||||
return up.profileKey
|
||||
}
|
||||
|
||||
// Close unloads the user profile and cleans up any other resources held by up.
|
||||
func (up *UserProfile) Close() error {
|
||||
if up.profileKey != 0 {
|
||||
if err := unloadUserProfile(up.token, up.profileKey); err != nil {
|
||||
return err
|
||||
}
|
||||
up.profileKey = 0
|
||||
}
|
||||
|
||||
if up.token != 0 {
|
||||
up.token.Close()
|
||||
up.token = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getRoamingProfilePath(logf logger.Logf, computerName, userName *uint16) (path *uint16, err error) {
|
||||
// logf is for debugging/testing.
|
||||
if logf == nil {
|
||||
logf = logger.Discard
|
||||
}
|
||||
|
||||
var pbuf *byte
|
||||
if err := windows.NetUserGetInfo(computerName, userName, 4, &pbuf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer windows.NetApiBufferFree(pbuf)
|
||||
|
||||
ui4 := (*_USER_INFO_4)(unsafe.Pointer(pbuf))
|
||||
logf("getRoamingProfilePath: got %#v", *ui4)
|
||||
profilePath := ui4.Profile
|
||||
if profilePath == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var sz int
|
||||
for ptr := unsafe.Pointer(profilePath); *(*uint16)(ptr) != 0; sz++ {
|
||||
ptr = unsafe.Pointer(uintptr(ptr) + unsafe.Sizeof(*profilePath))
|
||||
}
|
||||
|
||||
if sz == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
buf := unsafe.Slice(profilePath, sz+1)
|
||||
cp := append([]uint16{}, buf...)
|
||||
return unsafe.SliceData(cp), nil
|
||||
}
|
||||
|
||||
func getComputerAndUserName(token windows.Token, u *user.User) (computerName *uint16, userName *uint16, err error) {
|
||||
if u == nil {
|
||||
tokenUser, err := token.GetTokenUser()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
u, err = user.LookupId(tokenUser.User.Sid.String())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var strComputer, strUser string
|
||||
before, after, hasBackslash := strings.Cut(u.Username, `\`)
|
||||
if hasBackslash {
|
||||
strComputer = before
|
||||
strUser = after
|
||||
} else {
|
||||
strUser = before
|
||||
}
|
||||
|
||||
if strComputer != "" {
|
||||
computerName, err = windows.UTF16PtrFromString(strComputer)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
userName, err = windows.UTF16PtrFromString(strUser)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return computerName, userName, nil
|
||||
}
|
Loading…
Reference in New Issue