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>main
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