mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-25 11:05:45 +00:00
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>
This commit is contained in:
parent
9189fe007b
commit
bd2a6d5386
@ -157,7 +157,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
tailscale.com/util/syspolicy from tailscale.com/ipn
|
||||
tailscale.com/util/vizerror from tailscale.com/tailcfg+
|
||||
W 💣 tailscale.com/util/winutil from tailscale.com/hostinfo+
|
||||
W 💣 tailscale.com/util/winutil/winenv from tailscale.com/hostinfo
|
||||
W 💣 tailscale.com/util/winutil/winenv from tailscale.com/hostinfo+
|
||||
tailscale.com/version from tailscale.com/derp+
|
||||
tailscale.com/version/distro from tailscale.com/envknob+
|
||||
tailscale.com/wgengine/filter from tailscale.com/types/netmap
|
||||
|
@ -164,7 +164,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
tailscale.com/util/vizerror from tailscale.com/tailcfg+
|
||||
💣 tailscale.com/util/winutil from tailscale.com/clientupdate+
|
||||
W 💣 tailscale.com/util/winutil/authenticode from tailscale.com/clientupdate
|
||||
W 💣 tailscale.com/util/winutil/winenv from tailscale.com/hostinfo
|
||||
W 💣 tailscale.com/util/winutil/winenv from tailscale.com/hostinfo+
|
||||
tailscale.com/version from tailscale.com/client/web+
|
||||
tailscale.com/version/distro from tailscale.com/client/web+
|
||||
tailscale.com/wgengine/capture from tailscale.com/cmd/tailscale/cli
|
||||
|
@ -400,7 +400,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
💣 tailscale.com/util/winutil from tailscale.com/clientupdate+
|
||||
W 💣 tailscale.com/util/winutil/authenticode from tailscale.com/clientupdate+
|
||||
W tailscale.com/util/winutil/policy from tailscale.com/ipn/ipnlocal
|
||||
W 💣 tailscale.com/util/winutil/winenv from tailscale.com/hostinfo
|
||||
W 💣 tailscale.com/util/winutil/winenv from tailscale.com/hostinfo+
|
||||
tailscale.com/util/zstdframe from tailscale.com/control/controlclient+
|
||||
tailscale.com/version from tailscale.com/client/web+
|
||||
tailscale.com/version/distro from tailscale.com/client/web+
|
||||
|
@ -7,6 +7,7 @@
|
||||
//go:generate go run golang.org/x/tools/cmd/goimports -w zsyscall_windows.go
|
||||
|
||||
//sys getApplicationRestartSettings(process windows.Handle, commandLine *uint16, commandLineLen *uint32, flags *uint32) (ret wingoes.HRESULT) = kernel32.GetApplicationRestartSettings
|
||||
//sys loadUserProfile(token windows.Token, profileInfo *_PROFILEINFO) (err error) [int32(failretval)==0] = userenv.LoadUserProfileW
|
||||
//sys queryServiceConfig2(hService windows.Handle, infoLevel uint32, buf *byte, bufLen uint32, bytesNeeded *uint32) (err error) [failretval==0] = advapi32.QueryServiceConfig2W
|
||||
//sys registerApplicationRestart(cmdLineExclExeName *uint16, flags uint32) (ret wingoes.HRESULT) = kernel32.RegisterApplicationRestart
|
||||
//sys rmEndSession(session _RMHANDLE) (ret error) = rstrtmgr.RmEndSession
|
||||
@ -14,3 +15,4 @@
|
||||
//sys rmJoinSession(pSession *_RMHANDLE, sessionKey *uint16) (ret error) = rstrtmgr.RmJoinSession
|
||||
//sys rmRegisterResources(session _RMHANDLE, nFiles uint32, rgsFileNames **uint16, nApplications uint32, rgApplications *_RM_UNIQUE_PROCESS, nServices uint32, rgsServiceNames **uint16) (ret error) = rstrtmgr.RmRegisterResources
|
||||
//sys rmStartSession(pSession *_RMHANDLE, flags uint32, sessionKey *uint16) (ret error) = rstrtmgr.RmStartSession
|
||||
//sys unloadUserProfile(token windows.Token, profile registry.Key) (err error) [int32(failretval)==0] = userenv.UnloadUserProfile
|
||||
|
205
util/winutil/userprofile_windows.go
Normal file
205
util/winutil/userprofile_windows.go
Normal file
@ -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
|
||||
}
|
@ -8,6 +8,7 @@
|
||||
|
||||
"github.com/dblohm7/wingoes"
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
var _ unsafe.Pointer
|
||||
@ -42,6 +43,7 @@ func errnoErr(e syscall.Errno) error {
|
||||
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
|
||||
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||
modrstrtmgr = windows.NewLazySystemDLL("rstrtmgr.dll")
|
||||
moduserenv = windows.NewLazySystemDLL("userenv.dll")
|
||||
|
||||
procQueryServiceConfig2W = modadvapi32.NewProc("QueryServiceConfig2W")
|
||||
procGetApplicationRestartSettings = modkernel32.NewProc("GetApplicationRestartSettings")
|
||||
@ -51,6 +53,8 @@ func errnoErr(e syscall.Errno) error {
|
||||
procRmJoinSession = modrstrtmgr.NewProc("RmJoinSession")
|
||||
procRmRegisterResources = modrstrtmgr.NewProc("RmRegisterResources")
|
||||
procRmStartSession = modrstrtmgr.NewProc("RmStartSession")
|
||||
procLoadUserProfileW = moduserenv.NewProc("LoadUserProfileW")
|
||||
procUnloadUserProfile = moduserenv.NewProc("UnloadUserProfile")
|
||||
)
|
||||
|
||||
func queryServiceConfig2(hService windows.Handle, infoLevel uint32, buf *byte, bufLen uint32, bytesNeeded *uint32) (err error) {
|
||||
@ -112,3 +116,19 @@ func rmStartSession(pSession *_RMHANDLE, flags uint32, sessionKey *uint16) (ret
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func loadUserProfile(token windows.Token, profileInfo *_PROFILEINFO) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procLoadUserProfileW.Addr(), 2, uintptr(token), uintptr(unsafe.Pointer(profileInfo)), 0)
|
||||
if int32(r1) == 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func unloadUserProfile(token windows.Token, profile registry.Key) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procUnloadUserProfile.Addr(), 2, uintptr(token), uintptr(profile), 0)
|
||||
if int32(r1) == 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user