mirror of https://github.com/tailscale/tailscale/
util/winutil: add package for logging into Windows via Service-for-User (S4U)
This PR ties together pseudoconsoles, user profiles, s4u logons, and process creation into what is (hopefully) a simple API for various Tailscale services to obtain Windows access tokens without requiring knowledge of any Windows passwords. It works both for domain-joined machines (Kerberos) and non-domain-joined machines. The former case is fairly straightforward as it is fully documented. OTOH, the latter case is not documented, though it is fully defined in the C headers in the Windows SDK. The documentation blanks were filled in by reading the source code of Microsoft's Win32 port of OpenSSH. We need to do a bit of acrobatics to make conpty work correctly while creating a child process with an s4u token; see the doc comments above startProcessInternal for details. Updates #12383 Signed-off-by: Aaron Klotz <aaron@tailscale.com>pull/12553/head
parent
53a5d00fff
commit
da078b4c09
@ -0,0 +1,399 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package s4u
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/dblohm7/wingoes"
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
"tailscale.com/types/lazy"
|
||||||
|
"tailscale.com/util/winutil"
|
||||||
|
"tailscale.com/util/winutil/winenv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
_MICROSOFT_KERBEROS_NAME = "Kerberos"
|
||||||
|
_MSV1_0_PACKAGE_NAME = "MICROSOFT_AUTHENTICATION_PACKAGE_V1_0"
|
||||||
|
)
|
||||||
|
|
||||||
|
type _LSAHANDLE windows.Handle
|
||||||
|
type _LSA_OPERATIONAL_MODE uint32
|
||||||
|
|
||||||
|
type _KERB_LOGON_SUBMIT_TYPE int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
_KerbInteractiveLogon _KERB_LOGON_SUBMIT_TYPE = 2
|
||||||
|
_KerbSmartCardLogon _KERB_LOGON_SUBMIT_TYPE = 6
|
||||||
|
_KerbWorkstationUnlockLogon _KERB_LOGON_SUBMIT_TYPE = 7
|
||||||
|
_KerbSmartCardUnlockLogon _KERB_LOGON_SUBMIT_TYPE = 8
|
||||||
|
_KerbProxyLogon _KERB_LOGON_SUBMIT_TYPE = 9
|
||||||
|
_KerbTicketLogon _KERB_LOGON_SUBMIT_TYPE = 10
|
||||||
|
_KerbTicketUnlockLogon _KERB_LOGON_SUBMIT_TYPE = 11
|
||||||
|
_KerbS4ULogon _KERB_LOGON_SUBMIT_TYPE = 12
|
||||||
|
_KerbCertificateLogon _KERB_LOGON_SUBMIT_TYPE = 13
|
||||||
|
_KerbCertificateS4ULogon _KERB_LOGON_SUBMIT_TYPE = 14
|
||||||
|
_KerbCertificateUnlockLogon _KERB_LOGON_SUBMIT_TYPE = 15
|
||||||
|
_KerbNoElevationLogon _KERB_LOGON_SUBMIT_TYPE = 83
|
||||||
|
_KerbLuidLogon _KERB_LOGON_SUBMIT_TYPE = 84
|
||||||
|
)
|
||||||
|
|
||||||
|
type _KERB_S4U_LOGON_FLAGS uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
_KERB_S4U_LOGON_FLAG_CHECK_LOGONHOURS _KERB_S4U_LOGON_FLAGS = 0x2
|
||||||
|
//lint:ignore U1000 maps to a win32 API
|
||||||
|
_KERB_S4U_LOGON_FLAG_IDENTIFY _KERB_S4U_LOGON_FLAGS = 0x8
|
||||||
|
)
|
||||||
|
|
||||||
|
type _KERB_S4U_LOGON struct {
|
||||||
|
MessageType _KERB_LOGON_SUBMIT_TYPE
|
||||||
|
Flags _KERB_S4U_LOGON_FLAGS
|
||||||
|
ClientUpn windows.NTUnicodeString
|
||||||
|
ClientRealm windows.NTUnicodeString
|
||||||
|
}
|
||||||
|
|
||||||
|
type _MSV1_0_LOGON_SUBMIT_TYPE int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
_MsV1_0InteractiveLogon _MSV1_0_LOGON_SUBMIT_TYPE = 2
|
||||||
|
_MsV1_0Lm20Logon _MSV1_0_LOGON_SUBMIT_TYPE = 3
|
||||||
|
_MsV1_0NetworkLogon _MSV1_0_LOGON_SUBMIT_TYPE = 4
|
||||||
|
_MsV1_0SubAuthLogon _MSV1_0_LOGON_SUBMIT_TYPE = 5
|
||||||
|
_MsV1_0WorkstationUnlockLogon _MSV1_0_LOGON_SUBMIT_TYPE = 7
|
||||||
|
_MsV1_0S4ULogon _MSV1_0_LOGON_SUBMIT_TYPE = 12
|
||||||
|
_MsV1_0VirtualLogon _MSV1_0_LOGON_SUBMIT_TYPE = 82
|
||||||
|
_MsV1_0NoElevationLogon _MSV1_0_LOGON_SUBMIT_TYPE = 83
|
||||||
|
_MsV1_0LuidLogon _MSV1_0_LOGON_SUBMIT_TYPE = 84
|
||||||
|
)
|
||||||
|
|
||||||
|
type _MSV1_0_S4U_LOGON_FLAGS uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
_MSV1_0_S4U_LOGON_FLAG_CHECK_LOGONHOURS _MSV1_0_S4U_LOGON_FLAGS = 0x2
|
||||||
|
)
|
||||||
|
|
||||||
|
type _MSV1_0_S4U_LOGON struct {
|
||||||
|
MessageType _MSV1_0_LOGON_SUBMIT_TYPE
|
||||||
|
Flags _MSV1_0_S4U_LOGON_FLAGS
|
||||||
|
UserPrincipalName windows.NTUnicodeString
|
||||||
|
DomainName windows.NTUnicodeString
|
||||||
|
}
|
||||||
|
|
||||||
|
type _SECURITY_LOGON_TYPE int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
_UndefinedLogonType _SECURITY_LOGON_TYPE = 0
|
||||||
|
_Interactive _SECURITY_LOGON_TYPE = 2
|
||||||
|
_Network _SECURITY_LOGON_TYPE = 3
|
||||||
|
_Batch _SECURITY_LOGON_TYPE = 4
|
||||||
|
_Service _SECURITY_LOGON_TYPE = 5
|
||||||
|
_Proxy _SECURITY_LOGON_TYPE = 6
|
||||||
|
_Unlock _SECURITY_LOGON_TYPE = 7
|
||||||
|
_NetworkCleartext _SECURITY_LOGON_TYPE = 8
|
||||||
|
_NewCredentials _SECURITY_LOGON_TYPE = 9
|
||||||
|
_RemoteInteractive _SECURITY_LOGON_TYPE = 10
|
||||||
|
_CachedInteractive _SECURITY_LOGON_TYPE = 11
|
||||||
|
_CachedRemoteInteractive _SECURITY_LOGON_TYPE = 12
|
||||||
|
_CachedUnlock _SECURITY_LOGON_TYPE = 13
|
||||||
|
)
|
||||||
|
|
||||||
|
const _TOKEN_SOURCE_LENGTH = 8
|
||||||
|
|
||||||
|
type _TOKEN_SOURCE struct {
|
||||||
|
SourceName [_TOKEN_SOURCE_LENGTH]byte
|
||||||
|
SourceIdentifier windows.LUID
|
||||||
|
}
|
||||||
|
|
||||||
|
type _QUOTA_LIMITS struct {
|
||||||
|
PagedPoolLimit uintptr
|
||||||
|
NonPagedPoolLimit uintptr
|
||||||
|
MinimumWorkingSetSize uintptr
|
||||||
|
MaximumWorkingSetSize uintptr
|
||||||
|
PagefileLimit uintptr
|
||||||
|
TimeLimit int64
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrBadSrcName is returned if srcName contains non-ASCII characters, is
|
||||||
|
// empty, or is too long. It may be wrapped with additional information; use
|
||||||
|
// errors.Is when checking for it.
|
||||||
|
ErrBadSrcName = errors.New("srcName must be ASCII with length > 0 and <= 8")
|
||||||
|
)
|
||||||
|
|
||||||
|
// LSA packages (and their IDs) are always initialized during system startup,
|
||||||
|
// so we can retain their resolved IDs for the lifetime of our process.
|
||||||
|
var (
|
||||||
|
authPkgIDKerberos lazy.SyncValue[uint32]
|
||||||
|
authPkgIDMSV1_0 lazy.SyncValue[uint32]
|
||||||
|
)
|
||||||
|
|
||||||
|
type lsaSession struct {
|
||||||
|
handle _LSAHANDLE
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLSASessionForQuery() (lsa *lsaSession, err error) {
|
||||||
|
var h _LSAHANDLE
|
||||||
|
if e := wingoes.ErrorFromNTStatus(lsaConnectUntrusted(&h)); e.Failed() {
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
|
||||||
|
return &lsaSession{handle: h}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLSASessionForLogon(processName string) (lsa *lsaSession, err error) {
|
||||||
|
// processName is used by LSA for audit logging purposes.
|
||||||
|
// If empty, the current process name is used.
|
||||||
|
if processName == "" {
|
||||||
|
exe, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
processName = strings.TrimSuffix(filepath.Base(exe), filepath.Ext(exe))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := checkASCII(processName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
logonProcessName, err := windows.NewNTString(processName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var h _LSAHANDLE
|
||||||
|
var mode _LSA_OPERATIONAL_MODE
|
||||||
|
if e := wingoes.ErrorFromNTStatus(lsaRegisterLogonProcess(logonProcessName, &h, &mode)); e.Failed() {
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
|
||||||
|
return &lsaSession{handle: h}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *lsaSession) getAuthPkgID(pkgName string) (id uint32, err error) {
|
||||||
|
ntPkgName, err := windows.NewNTString(pkgName)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if e := wingoes.ErrorFromNTStatus(lsaLookupAuthenticationPackage(ls.handle, ntPkgName, &id)); e.Failed() {
|
||||||
|
return 0, e
|
||||||
|
}
|
||||||
|
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *lsaSession) Close() error {
|
||||||
|
if e := wingoes.ErrorFromNTStatus(lsaDeregisterLogonProcess(ls.handle)); e.Failed() {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
ls.handle = 0
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkASCII(s string) error {
|
||||||
|
for _, c := range []byte(s) {
|
||||||
|
if c > unicode.MaxASCII {
|
||||||
|
return fmt.Errorf("%q must be ASCII but contains value 0x%02X", s, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
thisComputer = []uint16{'.', 0}
|
||||||
|
computerName lazy.SyncValue[string]
|
||||||
|
)
|
||||||
|
|
||||||
|
func getComputerName() (string, error) {
|
||||||
|
var buf [windows.MAX_COMPUTERNAME_LENGTH + 1]uint16
|
||||||
|
size := uint32(len(buf))
|
||||||
|
if err := windows.GetComputerName(&buf[0], &size); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return windows.UTF16ToString(buf[:size]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkDomainAccount strips out the computer name (if any) from
|
||||||
|
// username and returns the result in sanitizedUserName. isDomainAccount is set
|
||||||
|
// to true if username contains a domain component that does not refer to the
|
||||||
|
// local computer.
|
||||||
|
func checkDomainAccount(username string) (sanitizedUserName string, isDomainAccount bool, err error) {
|
||||||
|
before, after, hasBackslash := strings.Cut(username, `\`)
|
||||||
|
if !hasBackslash {
|
||||||
|
return username, false, nil
|
||||||
|
}
|
||||||
|
if before == "." {
|
||||||
|
return after, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
comp, err := computerName.GetErr(getComputerName)
|
||||||
|
if err != nil {
|
||||||
|
return username, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.EqualFold(before, comp) {
|
||||||
|
return after, false, nil
|
||||||
|
}
|
||||||
|
return username, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// logonAs performs a S4U logon for u on behalf of srcName, and returns an
|
||||||
|
// access token for the user if successful. srcName must be non-empty, ASCII,
|
||||||
|
// and no more than 8 characters long. If srcName does not meet this criteria,
|
||||||
|
// LogonAs will return ErrBadSrcName wrapped with additional information; use
|
||||||
|
// errors.Is to check for it. When capLevel == CapCreateProcess, the logon
|
||||||
|
// enforces the user's logon hours policy (when present).
|
||||||
|
func (ls *lsaSession) logonAs(srcName string, u *user.User, capLevel CapabilityLevel) (token windows.Token, err error) {
|
||||||
|
if l := len(srcName); l == 0 || l > _TOKEN_SOURCE_LENGTH {
|
||||||
|
return 0, fmt.Errorf("%w, actual length is %d", ErrBadSrcName, l)
|
||||||
|
}
|
||||||
|
if err := checkASCII(srcName); err != nil {
|
||||||
|
return 0, fmt.Errorf("%w: %v", ErrBadSrcName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sanitizedUserName, isDomainUser, err := checkDomainAccount(u.Username)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if isDomainUser && !winenv.IsDomainJoined() {
|
||||||
|
return 0, fmt.Errorf("%w: cannot logon as domain user without being joined to a domain", os.ErrInvalid)
|
||||||
|
}
|
||||||
|
|
||||||
|
var pkgID uint32
|
||||||
|
var authInfo unsafe.Pointer
|
||||||
|
var authInfoLen uint32
|
||||||
|
enforceLogonHours := capLevel == CapCreateProcess
|
||||||
|
if isDomainUser {
|
||||||
|
pkgID, err = authPkgIDKerberos.GetErr(func() (uint32, error) {
|
||||||
|
return ls.getAuthPkgID(_MICROSOFT_KERBEROS_NAME)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
upn16, err := samToUPN16(sanitizedUserName)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("samToUPN16: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logonInfo, logonInfoLen, slcs := winutil.AllocateContiguousBuffer[_KERB_S4U_LOGON](upn16)
|
||||||
|
logonInfo.MessageType = _KerbS4ULogon
|
||||||
|
if enforceLogonHours {
|
||||||
|
logonInfo.Flags = _KERB_S4U_LOGON_FLAG_CHECK_LOGONHOURS
|
||||||
|
}
|
||||||
|
winutil.SetNTString(&logonInfo.ClientUpn, slcs[0])
|
||||||
|
|
||||||
|
authInfo = unsafe.Pointer(logonInfo)
|
||||||
|
authInfoLen = logonInfoLen
|
||||||
|
} else {
|
||||||
|
pkgID, err = authPkgIDMSV1_0.GetErr(func() (uint32, error) {
|
||||||
|
return ls.getAuthPkgID(_MSV1_0_PACKAGE_NAME)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
upn16, err := windows.UTF16FromString(sanitizedUserName)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
logonInfo, logonInfoLen, slcs := winutil.AllocateContiguousBuffer[_MSV1_0_S4U_LOGON](upn16, thisComputer)
|
||||||
|
logonInfo.MessageType = _MsV1_0S4ULogon
|
||||||
|
if enforceLogonHours {
|
||||||
|
logonInfo.Flags = _MSV1_0_S4U_LOGON_FLAG_CHECK_LOGONHOURS
|
||||||
|
}
|
||||||
|
for i, nts := range []*windows.NTUnicodeString{&logonInfo.UserPrincipalName, &logonInfo.DomainName} {
|
||||||
|
winutil.SetNTString(nts, slcs[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
authInfo = unsafe.Pointer(logonInfo)
|
||||||
|
authInfoLen = logonInfoLen
|
||||||
|
}
|
||||||
|
|
||||||
|
var srcContext _TOKEN_SOURCE
|
||||||
|
copy(srcContext.SourceName[:], []byte(srcName))
|
||||||
|
if err := allocateLocallyUniqueId(&srcContext.SourceIdentifier); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
originName, err := windows.NewNTString(srcName)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var profileBuf uintptr
|
||||||
|
var profileBufLen uint32
|
||||||
|
var logonID windows.LUID
|
||||||
|
var quotas _QUOTA_LIMITS
|
||||||
|
var subNTStatus windows.NTStatus
|
||||||
|
ntStatus := lsaLogonUser(ls.handle, originName, _Network, pkgID, authInfo, authInfoLen, nil, &srcContext, &profileBuf, &profileBufLen, &logonID, &token, "as, &subNTStatus)
|
||||||
|
if e := wingoes.ErrorFromNTStatus(ntStatus); e.Failed() {
|
||||||
|
return 0, fmt.Errorf("LsaLogonUser(%q): %w, SubStatus: %v", u.Username, e, subNTStatus)
|
||||||
|
}
|
||||||
|
if profileBuf != 0 {
|
||||||
|
lsaFreeReturnBuffer(profileBuf)
|
||||||
|
}
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// samToUPN16 converts SAM-style account name samName to a UPN account name,
|
||||||
|
// returned as a UTF-16 slice.
|
||||||
|
func samToUPN16(samName string) (upn16 []uint16, err error) {
|
||||||
|
_, samAccount, hasSep := strings.Cut(samName, `\`)
|
||||||
|
if !hasSep {
|
||||||
|
return nil, fmt.Errorf("%w: expected samName to contain a backslash", os.ErrInvalid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is essentially the same algorithm used by Win32-OpenSSH:
|
||||||
|
// First, try obtaining a UPN directly...
|
||||||
|
upn16, err = translateName(samName, windows.NameSamCompatible, windows.NameUserPrincipal)
|
||||||
|
if err == nil {
|
||||||
|
return upn16, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: Try manually composing a UPN. First obtain the canonical name...
|
||||||
|
canonical16, err := translateName(samName, windows.NameSamCompatible, windows.NameCanonical)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
canonical := windows.UTF16ToString(canonical16)
|
||||||
|
|
||||||
|
// Extract the domain name...
|
||||||
|
domain, _, _ := strings.Cut(canonical, "/")
|
||||||
|
|
||||||
|
// ...and finally create the UPN by joining the samAccount and domain.
|
||||||
|
upn := strings.Join([]string{samAccount, domain}, "@")
|
||||||
|
return windows.UTF16FromString(upn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func translateName(from string, fromFmt uint32, toFmt uint32) (result []uint16, err error) {
|
||||||
|
from16, err := windows.UTF16PtrFromString(from)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var to16Len uint32
|
||||||
|
if err := windows.TranslateName(from16, fromFmt, toFmt, nil, &to16Len); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
to16Buf := make([]uint16, to16Len)
|
||||||
|
if err := windows.TranslateName(from16, fromFmt, toFmt, unsafe.SliceData(to16Buf), &to16Len); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return to16Buf, nil
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package s4u
|
||||||
|
|
||||||
|
//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go mksyscall.go
|
||||||
|
//go:generate go run golang.org/x/tools/cmd/goimports -w zsyscall_windows.go
|
||||||
|
|
||||||
|
//sys allocateLocallyUniqueId(luid *windows.LUID) (err error) [int32(failretval)==0] = advapi32.AllocateLocallyUniqueId
|
||||||
|
//sys impersonateLoggedOnUser(token windows.Token) (err error) [int32(failretval)==0] = advapi32.ImpersonateLoggedOnUser
|
||||||
|
//sys lsaConnectUntrusted(lsaHandle *_LSAHANDLE) (ret windows.NTStatus) = secur32.LsaConnectUntrusted
|
||||||
|
//sys lsaDeregisterLogonProcess(lsaHandle _LSAHANDLE) (ret windows.NTStatus) = secur32.LsaDeregisterLogonProcess
|
||||||
|
//sys lsaFreeReturnBuffer(buffer uintptr) (ret windows.NTStatus) = secur32.LsaFreeReturnBuffer
|
||||||
|
//sys lsaLogonUser(lsaHandle _LSAHANDLE, originName *windows.NTString, logonType _SECURITY_LOGON_TYPE, authenticationPackage uint32, authenticationInformation unsafe.Pointer, authenticationInformationLength uint32, localGroups *windows.Tokengroups, sourceContext *_TOKEN_SOURCE, profileBuffer *uintptr, profileBufferLength *uint32, logonID *windows.LUID, token *windows.Token, quotas *_QUOTA_LIMITS, subStatus *windows.NTStatus) (ret windows.NTStatus) = secur32.LsaLogonUser
|
||||||
|
//sys lsaLookupAuthenticationPackage(lsaHandle _LSAHANDLE, packageName *windows.NTString, authenticationPackage *uint32) (ret windows.NTStatus) = secur32.LsaLookupAuthenticationPackage
|
||||||
|
//sys lsaRegisterLogonProcess(logonProcessName *windows.NTString, lsaHandle *_LSAHANDLE, securityMode *_LSA_OPERATIONAL_MODE) (ret windows.NTStatus) = secur32.LsaRegisterLogonProcess
|
@ -0,0 +1,941 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
// Package s4u is an API for accessing Service-For-User (S4U) functionality on Windows.
|
||||||
|
package s4u
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"runtime"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
"tailscale.com/cmd/tailscaled/childproc"
|
||||||
|
"tailscale.com/types/logger"
|
||||||
|
"tailscale.com/util/winutil"
|
||||||
|
"tailscale.com/util/winutil/conpty"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
childproc.Add("s4u", beRelay)
|
||||||
|
}
|
||||||
|
|
||||||
|
var errInsufficientCapabilityLevel = errors.New("insufficient capability level")
|
||||||
|
|
||||||
|
// ListGroupIDsForSSHPreAuthOnly returns user u's group memberships as a slice
|
||||||
|
// containing group SIDs. srcName must contain the name of the service that is
|
||||||
|
// retrieving this information. srcName must be non-empty, ASCII-only, and no
|
||||||
|
// longer than 8 characters.
|
||||||
|
//
|
||||||
|
// NOTE: This should only be used by Tailscale SSH! It is not a generic
|
||||||
|
// mechanism for access checks!
|
||||||
|
func ListGroupIDsForSSHPreAuthOnly(srcName string, u *user.User) ([]string, error) {
|
||||||
|
tok, err := createToken(srcName, u, tokenTypeIdentification, CapImpersonateOnly)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer tok.Close()
|
||||||
|
|
||||||
|
tokenGroups, err := tok.GetTokenGroups()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]string, 0, tokenGroups.GroupCount)
|
||||||
|
for _, group := range tokenGroups.AllGroups() {
|
||||||
|
if group.Attributes&windows.SE_GROUP_ENABLED != 0 {
|
||||||
|
result = append(result, group.Sid.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type tokenType uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
tokenTypeIdentification tokenType = iota
|
||||||
|
tokenTypeImpersonation
|
||||||
|
)
|
||||||
|
|
||||||
|
// createToken creates a new S4U access token for user u for the purposes
|
||||||
|
// specified by s4uType, with capability capLevel. srcName must contain the name
|
||||||
|
// of the service that is intended to use the token. srcName must be non-empty,
|
||||||
|
// ASCII-only, and no longer than 8 characters.
|
||||||
|
//
|
||||||
|
// When s4uType is tokenTypeImpersonation, the current OS thread's access token must have SeTcbPrivilege.
|
||||||
|
func createToken(srcName string, u *user.User, s4uType tokenType, capLevel CapabilityLevel) (tok windows.Token, err error) {
|
||||||
|
if u == nil {
|
||||||
|
return 0, os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
var lsa *lsaSession
|
||||||
|
switch s4uType {
|
||||||
|
case tokenTypeIdentification:
|
||||||
|
lsa, err = newLSASessionForQuery()
|
||||||
|
case tokenTypeImpersonation:
|
||||||
|
lsa, err = newLSASessionForLogon("")
|
||||||
|
default:
|
||||||
|
return 0, os.ErrInvalid
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer lsa.Close()
|
||||||
|
|
||||||
|
return lsa.logonAs(srcName, u, capLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Session encapsulates an S4U login session.
|
||||||
|
type Session struct {
|
||||||
|
refCnt atomic.Int32
|
||||||
|
logf logger.Logf
|
||||||
|
token windows.Token
|
||||||
|
userProfile *winutil.UserProfile
|
||||||
|
capLevel CapabilityLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
// CapabilityLevel specifies the desired capabilities that will be supported by a Session.
|
||||||
|
type CapabilityLevel uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
// The Session supports Do but none of the StartProcess* methods.
|
||||||
|
CapImpersonateOnly CapabilityLevel = iota
|
||||||
|
// The Session supports both Do and the StartProcess* methods.
|
||||||
|
CapCreateProcess
|
||||||
|
)
|
||||||
|
|
||||||
|
// Login logs user u into Windows on behalf of service srcName, loads the user's
|
||||||
|
// profile, and returns a Session that may be used for impersonating that user,
|
||||||
|
// or optionally creating processes as that user. Logs will be written to logf,
|
||||||
|
// if provided. srcName must be non-empty, ASCII-only, and no longer than 8
|
||||||
|
// characters.
|
||||||
|
//
|
||||||
|
// The current OS thread's access token must have SeTcbPrivilege.
|
||||||
|
func Login(logf logger.Logf, srcName string, u *user.User, capLevel CapabilityLevel) (sess *Session, err error) {
|
||||||
|
token, err := createToken(srcName, u, tokenTypeIdentification, capLevel)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
token.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
sessToken := token
|
||||||
|
if capLevel == CapCreateProcess {
|
||||||
|
// Obtain token's security descriptor so that it may be applied to
|
||||||
|
// a primary token.
|
||||||
|
sd, err := windows.GetSecurityInfo(windows.Handle(token),
|
||||||
|
windows.SE_KERNEL_OBJECT, windows.DACL_SECURITY_INFORMATION)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sa := windows.SecurityAttributes{
|
||||||
|
Length: uint32(unsafe.Sizeof(windows.SecurityAttributes{})),
|
||||||
|
SecurityDescriptor: sd,
|
||||||
|
}
|
||||||
|
|
||||||
|
// token is an impersonation token. Upgrade us to a primary token so that
|
||||||
|
// our StartProcess* methods will work correctly.
|
||||||
|
var dupToken windows.Token
|
||||||
|
if err := windows.DuplicateTokenEx(token, 0, &sa, windows.SecurityImpersonation,
|
||||||
|
windows.TokenPrimary, &dupToken); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sessToken = dupToken
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
sessToken.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
userProfile, err := winutil.LoadUserProfile(sessToken, u)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if logf == nil {
|
||||||
|
logf = logger.Discard
|
||||||
|
} else {
|
||||||
|
logf = logger.WithPrefix(logf, "(s4u) ")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Session{logf: logf, token: sessToken, userProfile: userProfile, capLevel: capLevel}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close unloads the user profile and S4U access token associated with the
|
||||||
|
// session. The close operation is not guaranteed to have finished when Close
|
||||||
|
// returns; it may remain alive until all processes created by ss have
|
||||||
|
// themselves been closed, and no more Do requests are pending.
|
||||||
|
func (ss *Session) Close() error {
|
||||||
|
refs := ss.refCnt.Load()
|
||||||
|
if (refs & 1) != 0 {
|
||||||
|
// Close already called
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the low bit to indicate that a close operation has been requested.
|
||||||
|
// We don't have atomic OR so we need to use CAS. Sigh.
|
||||||
|
for !ss.refCnt.CompareAndSwap(refs, refs|1) {
|
||||||
|
refs = ss.refCnt.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
if refs > 1 {
|
||||||
|
// Still active processes, just return.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ss.closeInternal()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss *Session) closeInternal() error {
|
||||||
|
if ss.userProfile != nil {
|
||||||
|
if err := ss.userProfile.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ss.userProfile = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if ss.token != 0 {
|
||||||
|
if err := ss.token.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ss.token = 0
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CapabilityLevel returns the CapabilityLevel that was specified when the
|
||||||
|
// session was created.
|
||||||
|
func (ss *Session) CapabilityLevel() CapabilityLevel {
|
||||||
|
return ss.capLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do executes fn while impersonating ss's user. Impersonation only affects
|
||||||
|
// the current goroutine; any new goroutines spawned by fn will not be
|
||||||
|
// impersonated. Do may be called concurrently by multiple goroutines.
|
||||||
|
//
|
||||||
|
// Do returns an error if impersonation did not succeed and fn could not be run.
|
||||||
|
// If called after ss has already been closed, it will panic.
|
||||||
|
func (ss *Session) Do(fn func()) error {
|
||||||
|
if fn == nil {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
ss.addRef()
|
||||||
|
defer ss.release()
|
||||||
|
|
||||||
|
// Impersonation touches thread-local state.
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
if err := impersonateLoggedOnUser(ss.token); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := windows.RevertToSelf(); err != nil {
|
||||||
|
// This is not recoverable in any way, shape, or form!
|
||||||
|
panic(fmt.Sprintf("RevertToSelf failed: %v", err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
fn()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss *Session) addRef() {
|
||||||
|
if (ss.refCnt.Add(2) & 1) != 0 {
|
||||||
|
panic("addRef after Close")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss *Session) release() {
|
||||||
|
rc := ss.refCnt.Add(-2)
|
||||||
|
if rc < 0 {
|
||||||
|
panic("negative refcount")
|
||||||
|
}
|
||||||
|
if rc == 1 {
|
||||||
|
ss.closeInternal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type startProcessOpts struct {
|
||||||
|
token windows.Token
|
||||||
|
extraEnv map[string]string
|
||||||
|
ptySize windows.Coord
|
||||||
|
pipes bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartProcess creates a new process running under ss via cmdLineInfo.
|
||||||
|
// The process will be started with its working directory set to the S4U user's
|
||||||
|
// profile directory and its environment set to the S4U user's environment.
|
||||||
|
// extraEnv, when specified, contains any additional environment variables to
|
||||||
|
// be added to the process's environment.
|
||||||
|
//
|
||||||
|
// If called after ss has already been closed, StartProcess will panic.
|
||||||
|
func (ss *Session) StartProcess(cmdLineInfo winutil.CommandLineInfo, extraEnv map[string]string) (psp *Process, err error) {
|
||||||
|
if ss.capLevel != CapCreateProcess {
|
||||||
|
return nil, errInsufficientCapabilityLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := startProcessOpts{
|
||||||
|
token: ss.token,
|
||||||
|
extraEnv: extraEnv,
|
||||||
|
}
|
||||||
|
return startProcessInternal(ss, ss.logf, cmdLineInfo, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartProcessWithPTY creates a new process running under ss via cmdLineInfo
|
||||||
|
// with a pseudoconsole initialized to initialPtySize. The resulting Process
|
||||||
|
// will return non-nil values from Stdin and Stdout, but Stderr will return nil.
|
||||||
|
// The process will be started with its working directory set to the S4U user's
|
||||||
|
// profile directory and its environment set to the S4U user's environment.
|
||||||
|
// extraEnv, when specified, contains any additional environment variables to
|
||||||
|
// be added to the process's environment.
|
||||||
|
//
|
||||||
|
// If called after ss has already been closed, StartProcessWithPTY will panic.
|
||||||
|
func (ss *Session) StartProcessWithPTY(cmdLineInfo winutil.CommandLineInfo, extraEnv map[string]string, initialPtySize windows.Coord) (psp *Process, err error) {
|
||||||
|
if ss.capLevel != CapCreateProcess {
|
||||||
|
return nil, errInsufficientCapabilityLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := startProcessOpts{
|
||||||
|
token: ss.token,
|
||||||
|
extraEnv: extraEnv,
|
||||||
|
ptySize: initialPtySize,
|
||||||
|
}
|
||||||
|
return startProcessInternal(ss, ss.logf, cmdLineInfo, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartProcessWithPipes creates a new process running under ss via cmdLineInfo
|
||||||
|
// with all standard handles set to pipes. The resulting Process will return
|
||||||
|
// non-nil values from Stdin, Stdout, and Stderr.
|
||||||
|
// The process will be started with its working directory set to the S4U user's
|
||||||
|
// profile directory and its environment set to the S4U user's environment.
|
||||||
|
// extraEnv, when specified, contains any additional environment variables to
|
||||||
|
// be added to the process's environment.
|
||||||
|
//
|
||||||
|
// If called after ss has already been closed, StartProcessWithPipes will panic.
|
||||||
|
func (ss *Session) StartProcessWithPipes(cmdLineInfo winutil.CommandLineInfo, extraEnv map[string]string) (psp *Process, err error) {
|
||||||
|
if ss.capLevel != CapCreateProcess {
|
||||||
|
return nil, errInsufficientCapabilityLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := startProcessOpts{
|
||||||
|
token: ss.token,
|
||||||
|
extraEnv: extraEnv,
|
||||||
|
pipes: true,
|
||||||
|
}
|
||||||
|
return startProcessInternal(ss, ss.logf, cmdLineInfo, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// startProcessInternal is the common implementation behind Session's exported
|
||||||
|
// StartProcess* methods. It uses opts to distinguish between the various
|
||||||
|
// requested modes of operation.
|
||||||
|
//
|
||||||
|
// A note on pseudoconsoles:
|
||||||
|
// The conpty API currently does not provide a way to create a pseudoconsole for
|
||||||
|
// a different user than the current process. The way we deal with this is
|
||||||
|
// to first create a "relay" process running with the desired user token,
|
||||||
|
// and then create the actual requested process as a child of the relay,
|
||||||
|
// at which time we create the pseudoconsole. The relay simply copies the
|
||||||
|
// PTY's I/O into/out of its own stdin and stdout, which are piped to the
|
||||||
|
// parent still running as LocalSystem. We also relay pseudoconsole resize requests.
|
||||||
|
func startProcessInternal(ss *Session, logf logger.Logf, cmdLineInfo winutil.CommandLineInfo, opts startProcessOpts) (psp *Process, err error) {
|
||||||
|
var sib winutil.StartupInfoBuilder
|
||||||
|
defer sib.Close()
|
||||||
|
|
||||||
|
var sp Process
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
sp.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var zeroCoord windows.Coord
|
||||||
|
ptySizeValid := opts.ptySize != zeroCoord
|
||||||
|
useToken := opts.token != 0
|
||||||
|
usePty := ptySizeValid && !useToken
|
||||||
|
useRelay := ptySizeValid && useToken
|
||||||
|
useSystem32WD := useToken && opts.token.IsElevated()
|
||||||
|
|
||||||
|
if usePty {
|
||||||
|
sp.pty, err = conpty.NewPseudoConsole(opts.ptySize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sp.pty.ConfigureStartupInfo(&sib); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sp.wStdin = sp.pty.InputPipe()
|
||||||
|
sp.rStdout = sp.pty.OutputPipe()
|
||||||
|
} else if useRelay || opts.pipes {
|
||||||
|
if sp.wStdin, sp.rStdout, sp.rStderr, err = createStdPipes(&sib); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var relayStderr io.ReadCloser
|
||||||
|
if useRelay {
|
||||||
|
// Later on we're going to use stderr for logging instead of providing it to the caller.
|
||||||
|
relayStderr = sp.rStderr
|
||||||
|
sp.rStderr = nil
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
relayStderr.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Set up a pipe to send PTY resize requests.
|
||||||
|
var resizeRead, resizeWrite windows.Handle
|
||||||
|
if err := windows.CreatePipe(&resizeRead, &resizeWrite, nil, 0); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sp.wResize = os.NewFile(uintptr(resizeWrite), "wPTYResizePipe")
|
||||||
|
defer windows.CloseHandle(resizeRead)
|
||||||
|
if err := sib.InheritHandles(resizeRead); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Revise the command line. First, get the existing one.
|
||||||
|
_, _, strCmdLine, err := cmdLineInfo.Resolve()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now rebuild it, passing the strCmdLine as the --cmd argument...
|
||||||
|
newArgs := []string{
|
||||||
|
"be-child", "s4u",
|
||||||
|
"--resize", fmt.Sprintf("0x%x", uintptr(resizeRead)),
|
||||||
|
"--x", strconv.Itoa(int(opts.ptySize.X)),
|
||||||
|
"--y", strconv.Itoa(int(opts.ptySize.Y)),
|
||||||
|
"--cmd", strCmdLine,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...to be passed in as arguments to our own executable.
|
||||||
|
cmdLineInfo.ExePath, err = os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cmdLineInfo.SetArgs(newArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
exePath, cmdLine, cmdLineStr, err := cmdLineInfo.Resolve()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
logf("starting %s", cmdLineStr)
|
||||||
|
|
||||||
|
var env []string
|
||||||
|
var wd16 *uint16
|
||||||
|
if useToken {
|
||||||
|
env, err = opts.token.Environ(false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
folderID := windows.FOLDERID_Profile
|
||||||
|
if useSystem32WD {
|
||||||
|
folderID = windows.FOLDERID_System
|
||||||
|
}
|
||||||
|
wd, err := opts.token.KnownFolderPath(folderID, windows.KF_FLAG_DEFAULT)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
wd16, err = windows.UTF16PtrFromString(wd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
env = os.Environ()
|
||||||
|
}
|
||||||
|
|
||||||
|
env = mergeEnv(env, opts.extraEnv)
|
||||||
|
|
||||||
|
var env16 *uint16
|
||||||
|
if useToken || len(opts.extraEnv) > 0 {
|
||||||
|
env16 = winutil.NewEnvBlock(env)
|
||||||
|
}
|
||||||
|
|
||||||
|
if useToken {
|
||||||
|
// We want the child process to be assigned to job such that when it exits,
|
||||||
|
// its descendents within the job will be terminated as well.
|
||||||
|
job, err := createJob()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// We don't need to hang onto job beyond this func...
|
||||||
|
defer job.Close()
|
||||||
|
|
||||||
|
if err := sib.AssignToJob(job.Handle()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...because we're now gonna make a read-only copy...
|
||||||
|
qjob, err := job.QueryOnlyClone()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer qjob.Close()
|
||||||
|
|
||||||
|
// ...which will be inherited by the child process.
|
||||||
|
// When the child process terminates, the job will too.
|
||||||
|
if err := sib.InheritHandles(qjob.Handle()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
si, inheritHandles, creationFlags, err := sib.Resolve()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var pi windows.ProcessInformation
|
||||||
|
if useToken {
|
||||||
|
// DETACHED_PROCESS so that the child does not receive a console.
|
||||||
|
// CREATE_NEW_PROCESS_GROUP so that the child's console group is isolated from ours.
|
||||||
|
creationFlags |= windows.DETACHED_PROCESS | windows.CREATE_NEW_PROCESS_GROUP
|
||||||
|
doCreate := func() {
|
||||||
|
err = windows.CreateProcessAsUser(opts.token, exePath, cmdLine, nil, nil, inheritHandles, creationFlags, env16, wd16, si, &pi)
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case useRelay:
|
||||||
|
doCreate()
|
||||||
|
case ss != nil:
|
||||||
|
// We want to ensure that the executable is accessible via the token's
|
||||||
|
// security context, not ours.
|
||||||
|
if err := ss.Do(doCreate); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("should not have reached here")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = windows.CreateProcess(exePath, cmdLine, nil, nil, inheritHandles, creationFlags, env16, wd16, si, &pi)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
windows.CloseHandle(pi.Thread)
|
||||||
|
|
||||||
|
if relayStderr != nil {
|
||||||
|
logw := logger.FuncWriter(logger.WithPrefix(logf, fmt.Sprintf("(s4u relay process %d [0x%x]) ", pi.ProcessId, pi.ProcessId)))
|
||||||
|
go func() {
|
||||||
|
defer relayStderr.Close()
|
||||||
|
io.Copy(logw, relayStderr)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
sp.hproc = pi.Process
|
||||||
|
sp.pid = pi.ProcessId
|
||||||
|
if ss != nil {
|
||||||
|
ss.addRef()
|
||||||
|
sp.sess = ss
|
||||||
|
}
|
||||||
|
return &sp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type jobObject windows.Handle
|
||||||
|
|
||||||
|
func createJob() (job *jobObject, err error) {
|
||||||
|
hjob, err := windows.CreateJobObject(nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
windows.CloseHandle(hjob)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
limitInfo := windows.JOBOBJECT_EXTENDED_LIMIT_INFORMATION{
|
||||||
|
BasicLimitInformation: windows.JOBOBJECT_BASIC_LIMIT_INFORMATION{
|
||||||
|
// We want every process within the job to terminate when the job is closed.
|
||||||
|
// We also want to allow processes within the job to create child processes
|
||||||
|
// that are outside the job (otherwise you couldn't leave background
|
||||||
|
// processes running after exiting a session, for example).
|
||||||
|
// These flags also match those used by the Win32 port of OpenSSH.
|
||||||
|
LimitFlags: windows.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | windows.JOB_OBJECT_LIMIT_BREAKAWAY_OK,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err = windows.SetInformationJobObject(hjob,
|
||||||
|
windows.JobObjectExtendedLimitInformation, uintptr(unsafe.Pointer(&limitInfo)),
|
||||||
|
uint32(unsafe.Sizeof(limitInfo)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
jo := jobObject(hjob)
|
||||||
|
return &jo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (job *jobObject) Close() error {
|
||||||
|
if hjob := job.Handle(); hjob != 0 {
|
||||||
|
windows.CloseHandle(hjob)
|
||||||
|
*job = 0
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (job *jobObject) Handle() windows.Handle {
|
||||||
|
if job == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return windows.Handle(*job)
|
||||||
|
}
|
||||||
|
|
||||||
|
const _JOB_OBJECT_QUERY = 0x0004
|
||||||
|
|
||||||
|
func (job *jobObject) QueryOnlyClone() (*jobObject, error) {
|
||||||
|
hjob := job.Handle()
|
||||||
|
cp := windows.CurrentProcess()
|
||||||
|
|
||||||
|
var dupe windows.Handle
|
||||||
|
err := windows.DuplicateHandle(cp, hjob, cp, &dupe, _JOB_OBJECT_QUERY, true, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := jobObject(dupe)
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createStdPipes(sib *winutil.StartupInfoBuilder) (stdin io.WriteCloser, stdout, stderr io.ReadCloser, err error) {
|
||||||
|
var rStdin, wStdin windows.Handle
|
||||||
|
if err := windows.CreatePipe(&rStdin, &wStdin, nil, 0); err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
windows.CloseHandle(rStdin)
|
||||||
|
windows.CloseHandle(wStdin)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var rStdout, wStdout windows.Handle
|
||||||
|
if err := windows.CreatePipe(&rStdout, &wStdout, nil, 0); err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
windows.CloseHandle(rStdout)
|
||||||
|
windows.CloseHandle(wStdout)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var rStderr, wStderr windows.Handle
|
||||||
|
if err := windows.CreatePipe(&rStderr, &wStderr, nil, 0); err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
windows.CloseHandle(rStderr)
|
||||||
|
windows.CloseHandle(wStderr)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := sib.SetStdHandles(rStdin, wStdout, wStderr); err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
stdin = os.NewFile(uintptr(wStdin), "wStdin")
|
||||||
|
stdout = os.NewFile(uintptr(rStdout), "rStdout")
|
||||||
|
stderr = os.NewFile(uintptr(rStderr), "rStderr")
|
||||||
|
return stdin, stdout, stderr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process encapsulates a child process started with a Session.
|
||||||
|
type Process struct {
|
||||||
|
sess *Session
|
||||||
|
wStdin io.WriteCloser
|
||||||
|
rStdout io.ReadCloser
|
||||||
|
rStderr io.ReadCloser
|
||||||
|
wResize io.WriteCloser
|
||||||
|
pty *conpty.PseudoConsole
|
||||||
|
hproc windows.Handle
|
||||||
|
pid uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stdin returns the write side of a pipe connected to the child process's
|
||||||
|
// stdin, or nil if no I/O was requested.
|
||||||
|
func (sp *Process) Stdin() io.WriteCloser {
|
||||||
|
return sp.wStdin
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stdout returns the read side of a pipe connected to the child process's
|
||||||
|
// stdout, or nil if no I/O was requested.
|
||||||
|
func (sp *Process) Stdout() io.ReadCloser {
|
||||||
|
return sp.rStdout
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stderr returns the read side of a pipe connected to the child process's
|
||||||
|
// stderr, or nil if no I/O was requested.
|
||||||
|
func (sp *Process) Stderr() io.ReadCloser {
|
||||||
|
return sp.rStderr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terminate kills the process.
|
||||||
|
func (sp *Process) Terminate() {
|
||||||
|
if sp.hproc != 0 {
|
||||||
|
windows.TerminateProcess(sp.hproc, 255)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close waits for sp to complete and then cleans up any resources owned by it.
|
||||||
|
// Close must wait because the Session associated with sp should not be destroyed
|
||||||
|
// until all its processes have terminated. If necessary, call Terminate to
|
||||||
|
// forcibly end the process.
|
||||||
|
//
|
||||||
|
// If the process was created with a pseudoconsole then the caller must continue
|
||||||
|
// concurrently draining sp's stdout until either Close finishes executing, or EOF.
|
||||||
|
func (sp *Process) Close() error {
|
||||||
|
for _, pc := range []*io.WriteCloser{&sp.wStdin, &sp.wResize} {
|
||||||
|
if *pc == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
(*pc).Close()
|
||||||
|
(*pc) = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if sp.pty != nil {
|
||||||
|
if err := sp.pty.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sp.pty = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if sp.hproc != 0 {
|
||||||
|
if _, err := sp.Wait(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
windows.CloseHandle(sp.hproc)
|
||||||
|
sp.hproc = 0
|
||||||
|
sp.pid = 0
|
||||||
|
if sp.sess != nil {
|
||||||
|
sp.sess.release()
|
||||||
|
sp.sess = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Order is important here. Do not close sp.rStdout until _after_
|
||||||
|
// ss.pty (when present) has been closed! We're going to do one better by
|
||||||
|
// doing this after the process is done.
|
||||||
|
for _, pc := range []*io.ReadCloser{&sp.rStdout, &sp.rStderr} {
|
||||||
|
if *pc == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
(*pc).Close()
|
||||||
|
(*pc) = nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait blocks the caller until sp terminates. It returns the process exit code.
|
||||||
|
// exitCode will be set to 254 if the process terminated but the exit code could
|
||||||
|
// not be retrieved.
|
||||||
|
func (sp *Process) Wait() (exitCode uint32, err error) {
|
||||||
|
_, err = windows.WaitForSingleObject(sp.hproc, windows.INFINITE)
|
||||||
|
if err == nil {
|
||||||
|
if err := windows.GetExitCodeProcess(sp.hproc, &exitCode); err != nil {
|
||||||
|
exitCode = 254
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return exitCode, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// OSProcess returns an *os.Process associated with sp. This is useful for
|
||||||
|
// integration with external code that expects an os.Process.
|
||||||
|
func (sp *Process) OSProcess() (*os.Process, error) {
|
||||||
|
if sp.hproc == 0 {
|
||||||
|
return nil, winutil.ErrDefunctProcess
|
||||||
|
}
|
||||||
|
return os.FindProcess(int(sp.pid))
|
||||||
|
}
|
||||||
|
|
||||||
|
// PTYResizer returns a function to be called to resize the pseudoconsole.
|
||||||
|
// It returns nil if no pseudoconsole was requested when creating sp.
|
||||||
|
func (sp *Process) PTYResizer() func(windows.Coord) error {
|
||||||
|
if sp.wResize != nil {
|
||||||
|
wResize := sp.wResize
|
||||||
|
return func(c windows.Coord) error {
|
||||||
|
return binary.Write(wResize, binary.LittleEndian, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sp.pty != nil {
|
||||||
|
pty := sp.pty
|
||||||
|
return func(c windows.Coord) error {
|
||||||
|
return pty.Resize(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type relayArgs struct {
|
||||||
|
command string
|
||||||
|
resize string
|
||||||
|
ptyX int
|
||||||
|
ptyY int
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseRelayArgs(args []string) (a relayArgs) {
|
||||||
|
flags := flag.NewFlagSet("", flag.ExitOnError)
|
||||||
|
flags.StringVar(&a.command, "cmd", "", "the command to run")
|
||||||
|
flags.StringVar(&a.resize, "resize", "", "handle to resize pipe")
|
||||||
|
flags.IntVar(&a.ptyX, "x", 80, "initial width of pty")
|
||||||
|
flags.IntVar(&a.ptyY, "y", 25, "initial height of pty")
|
||||||
|
flags.Parse(args)
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func flagSizeErr(flagName byte) error {
|
||||||
|
return fmt.Errorf("--%c must be greater than zero and less than %d", flagName, math.MaxInt16)
|
||||||
|
}
|
||||||
|
|
||||||
|
const debugRelay = false
|
||||||
|
|
||||||
|
func beRelay(args []string) error {
|
||||||
|
ra := parseRelayArgs(args)
|
||||||
|
if ra.command == "" {
|
||||||
|
return fmt.Errorf("--cmd must be specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
bitSize := int(unsafe.Sizeof(windows.Handle(0)) * 8)
|
||||||
|
resize64, err := strconv.ParseUint(ra.resize, 0, bitSize)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
hResize := windows.Handle(resize64)
|
||||||
|
if ft, _ := windows.GetFileType(hResize); ft != windows.FILE_TYPE_PIPE {
|
||||||
|
return fmt.Errorf("--resize is an invalid handle type")
|
||||||
|
}
|
||||||
|
resize := os.NewFile(uintptr(hResize), "rPTYResizePipe")
|
||||||
|
defer resize.Close()
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case ra.ptyX <= 0 || ra.ptyX > math.MaxInt16:
|
||||||
|
return flagSizeErr('x')
|
||||||
|
case ra.ptyY <= 0 || ra.ptyY > math.MaxInt16:
|
||||||
|
return flagSizeErr('y')
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
logf := logger.Discard
|
||||||
|
if debugRelay {
|
||||||
|
// Our parent process will write our stderr to its log.
|
||||||
|
logf = func(format string, args ...any) {
|
||||||
|
fmt.Fprintf(os.Stderr, format, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logf("starting")
|
||||||
|
argv, err := windows.DecomposeCommandLine(ra.command)
|
||||||
|
if err != nil {
|
||||||
|
logf("DecomposeCommandLine failed: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cli := winutil.CommandLineInfo{
|
||||||
|
ExePath: argv[0],
|
||||||
|
}
|
||||||
|
cli.SetArgs(argv[1:])
|
||||||
|
|
||||||
|
opts := startProcessOpts{
|
||||||
|
ptySize: windows.Coord{X: int16(ra.ptyX), Y: int16(ra.ptyY)},
|
||||||
|
}
|
||||||
|
psp, err := startProcessInternal(nil, logf, cli, opts)
|
||||||
|
if err != nil {
|
||||||
|
logf("startProcessInternal failed: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer psp.Close()
|
||||||
|
|
||||||
|
go resizeLoop(logf, resize, psp.PTYResizer())
|
||||||
|
if debugRelay {
|
||||||
|
go debugLogPTYInput(logf, psp.wStdin, os.Stdin)
|
||||||
|
go debugLogPTYOutput(logf, os.Stdout, psp.rStdout)
|
||||||
|
} else {
|
||||||
|
go io.Copy(psp.wStdin, os.Stdin)
|
||||||
|
go io.Copy(os.Stdout, psp.rStdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
exitCode, err := psp.Wait()
|
||||||
|
if err != nil {
|
||||||
|
logf("waiting on relayed process: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if exitCode > 0 {
|
||||||
|
logf("relayed process returned %v", exitCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := psp.Close(); err != nil {
|
||||||
|
logf("s4u.Process.Close error: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resizeLoop(logf logger.Logf, resizePipe io.Reader, resizeFn func(windows.Coord) error) {
|
||||||
|
var coord windows.Coord
|
||||||
|
for binary.Read(resizePipe, binary.LittleEndian, &coord) == nil {
|
||||||
|
logf("resizing pty window to %#v", coord)
|
||||||
|
resizeFn(coord)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func debugLogPTYInput(logf logger.Logf, w io.Writer, r io.Reader) {
|
||||||
|
logw := logger.FuncWriter(logger.WithPrefix(logf, "(pty input) "))
|
||||||
|
io.Copy(io.MultiWriter(w, logw), r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func debugLogPTYOutput(logf logger.Logf, w io.Writer, r io.Reader) {
|
||||||
|
logw := logger.FuncWriter(logger.WithPrefix(logf, "(pty output) "))
|
||||||
|
io.Copy(w, io.TeeReader(r, logw))
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeEnv returns the union of existingEnv and extraEnv, deduplicated and
|
||||||
|
// sorted.
|
||||||
|
func mergeEnv(existingEnv []string, extraEnv map[string]string) []string {
|
||||||
|
if len(extraEnv) == 0 {
|
||||||
|
return existingEnv
|
||||||
|
}
|
||||||
|
|
||||||
|
mergedMap := make(map[string]string, len(existingEnv)+len(extraEnv))
|
||||||
|
for _, line := range existingEnv {
|
||||||
|
k, v, _ := strings.Cut(line, "=")
|
||||||
|
mergedMap[strings.ToUpper(k)] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range extraEnv {
|
||||||
|
mergedMap[strings.ToUpper(k)] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]string, 0, len(mergedMap))
|
||||||
|
for k, v := range mergedMap {
|
||||||
|
result = append(result, strings.Join([]string{k, v}, "="))
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.SortFunc(result, func(l, r string) int {
|
||||||
|
kl, _, _ := strings.Cut(l, "=")
|
||||||
|
kr, _, _ := strings.Cut(r, "=")
|
||||||
|
return strings.Compare(kl, kr)
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
@ -0,0 +1,104 @@
|
|||||||
|
// Code generated by 'go generate'; DO NOT EDIT.
|
||||||
|
|
||||||
|
package s4u
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ unsafe.Pointer
|
||||||
|
|
||||||
|
// Do the interface allocations only once for common
|
||||||
|
// Errno values.
|
||||||
|
const (
|
||||||
|
errnoERROR_IO_PENDING = 997
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||||
|
errERROR_EINVAL error = syscall.EINVAL
|
||||||
|
)
|
||||||
|
|
||||||
|
// errnoErr returns common boxed Errno values, to prevent
|
||||||
|
// allocations at runtime.
|
||||||
|
func errnoErr(e syscall.Errno) error {
|
||||||
|
switch e {
|
||||||
|
case 0:
|
||||||
|
return errERROR_EINVAL
|
||||||
|
case errnoERROR_IO_PENDING:
|
||||||
|
return errERROR_IO_PENDING
|
||||||
|
}
|
||||||
|
// TODO: add more here, after collecting data on the common
|
||||||
|
// error values see on Windows. (perhaps when running
|
||||||
|
// all.bat?)
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
|
||||||
|
modsecur32 = windows.NewLazySystemDLL("secur32.dll")
|
||||||
|
|
||||||
|
procAllocateLocallyUniqueId = modadvapi32.NewProc("AllocateLocallyUniqueId")
|
||||||
|
procImpersonateLoggedOnUser = modadvapi32.NewProc("ImpersonateLoggedOnUser")
|
||||||
|
procLsaConnectUntrusted = modsecur32.NewProc("LsaConnectUntrusted")
|
||||||
|
procLsaDeregisterLogonProcess = modsecur32.NewProc("LsaDeregisterLogonProcess")
|
||||||
|
procLsaFreeReturnBuffer = modsecur32.NewProc("LsaFreeReturnBuffer")
|
||||||
|
procLsaLogonUser = modsecur32.NewProc("LsaLogonUser")
|
||||||
|
procLsaLookupAuthenticationPackage = modsecur32.NewProc("LsaLookupAuthenticationPackage")
|
||||||
|
procLsaRegisterLogonProcess = modsecur32.NewProc("LsaRegisterLogonProcess")
|
||||||
|
)
|
||||||
|
|
||||||
|
func allocateLocallyUniqueId(luid *windows.LUID) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procAllocateLocallyUniqueId.Addr(), 1, uintptr(unsafe.Pointer(luid)), 0, 0)
|
||||||
|
if int32(r1) == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func impersonateLoggedOnUser(token windows.Token) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procImpersonateLoggedOnUser.Addr(), 1, uintptr(token), 0, 0)
|
||||||
|
if int32(r1) == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lsaConnectUntrusted(lsaHandle *_LSAHANDLE) (ret windows.NTStatus) {
|
||||||
|
r0, _, _ := syscall.Syscall(procLsaConnectUntrusted.Addr(), 1, uintptr(unsafe.Pointer(lsaHandle)), 0, 0)
|
||||||
|
ret = windows.NTStatus(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lsaDeregisterLogonProcess(lsaHandle _LSAHANDLE) (ret windows.NTStatus) {
|
||||||
|
r0, _, _ := syscall.Syscall(procLsaDeregisterLogonProcess.Addr(), 1, uintptr(lsaHandle), 0, 0)
|
||||||
|
ret = windows.NTStatus(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lsaFreeReturnBuffer(buffer uintptr) (ret windows.NTStatus) {
|
||||||
|
r0, _, _ := syscall.Syscall(procLsaFreeReturnBuffer.Addr(), 1, uintptr(buffer), 0, 0)
|
||||||
|
ret = windows.NTStatus(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lsaLogonUser(lsaHandle _LSAHANDLE, originName *windows.NTString, logonType _SECURITY_LOGON_TYPE, authenticationPackage uint32, authenticationInformation unsafe.Pointer, authenticationInformationLength uint32, localGroups *windows.Tokengroups, sourceContext *_TOKEN_SOURCE, profileBuffer *uintptr, profileBufferLength *uint32, logonID *windows.LUID, token *windows.Token, quotas *_QUOTA_LIMITS, subStatus *windows.NTStatus) (ret windows.NTStatus) {
|
||||||
|
r0, _, _ := syscall.Syscall15(procLsaLogonUser.Addr(), 14, uintptr(lsaHandle), uintptr(unsafe.Pointer(originName)), uintptr(logonType), uintptr(authenticationPackage), uintptr(authenticationInformation), uintptr(authenticationInformationLength), uintptr(unsafe.Pointer(localGroups)), uintptr(unsafe.Pointer(sourceContext)), uintptr(unsafe.Pointer(profileBuffer)), uintptr(unsafe.Pointer(profileBufferLength)), uintptr(unsafe.Pointer(logonID)), uintptr(unsafe.Pointer(token)), uintptr(unsafe.Pointer(quotas)), uintptr(unsafe.Pointer(subStatus)), 0)
|
||||||
|
ret = windows.NTStatus(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lsaLookupAuthenticationPackage(lsaHandle _LSAHANDLE, packageName *windows.NTString, authenticationPackage *uint32) (ret windows.NTStatus) {
|
||||||
|
r0, _, _ := syscall.Syscall(procLsaLookupAuthenticationPackage.Addr(), 3, uintptr(lsaHandle), uintptr(unsafe.Pointer(packageName)), uintptr(unsafe.Pointer(authenticationPackage)))
|
||||||
|
ret = windows.NTStatus(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lsaRegisterLogonProcess(logonProcessName *windows.NTString, lsaHandle *_LSAHANDLE, securityMode *_LSA_OPERATIONAL_MODE) (ret windows.NTStatus) {
|
||||||
|
r0, _, _ := syscall.Syscall(procLsaRegisterLogonProcess.Addr(), 3, uintptr(unsafe.Pointer(logonProcessName)), uintptr(unsafe.Pointer(lsaHandle)), uintptr(unsafe.Pointer(securityMode)))
|
||||||
|
ret = windows.NTStatus(r0)
|
||||||
|
return
|
||||||
|
}
|
Loading…
Reference in New Issue