winutil_windows.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package winutil
  4. import (
  5. "errors"
  6. "fmt"
  7. "log"
  8. "os/exec"
  9. "os/user"
  10. "runtime"
  11. "strings"
  12. "syscall"
  13. "time"
  14. "unsafe"
  15. "golang.org/x/sys/windows"
  16. "golang.org/x/sys/windows/registry"
  17. )
  18. const (
  19. regBase = `SOFTWARE\Tailscale IPN`
  20. regPolicyBase = `SOFTWARE\Policies\Tailscale`
  21. )
  22. // ErrNoShell is returned when the shell process is not found.
  23. var ErrNoShell = errors.New("no Shell process is present")
  24. // ErrNoValue is returned when the value doesn't exist in the registry.
  25. var ErrNoValue = registry.ErrNotExist
  26. // GetDesktopPID searches the PID of the process that's running the
  27. // currently active desktop. Returns ErrNoShell if the shell is not present.
  28. // Usually the PID will be for explorer.exe.
  29. func GetDesktopPID() (uint32, error) {
  30. hwnd := windows.GetShellWindow()
  31. if hwnd == 0 {
  32. return 0, ErrNoShell
  33. }
  34. var pid uint32
  35. windows.GetWindowThreadProcessId(hwnd, &pid)
  36. if pid == 0 {
  37. return 0, fmt.Errorf("invalid PID for HWND %v", hwnd)
  38. }
  39. return pid, nil
  40. }
  41. func getPolicyString(name string) (string, error) {
  42. s, err := getRegStringInternal(regPolicyBase, name)
  43. if err != nil {
  44. // Fall back to the legacy path
  45. return getRegString(name)
  46. }
  47. return s, err
  48. }
  49. func getRegString(name string) (string, error) {
  50. s, err := getRegStringInternal(regBase, name)
  51. if err != nil {
  52. return "", err
  53. }
  54. return s, err
  55. }
  56. func getPolicyInteger(name string) (uint64, error) {
  57. i, err := getRegIntegerInternal(regPolicyBase, name)
  58. if err != nil {
  59. // Fall back to the legacy path
  60. return getRegInteger(name)
  61. }
  62. return i, err
  63. }
  64. func getRegInteger(name string) (uint64, error) {
  65. i, err := getRegIntegerInternal(regBase, name)
  66. if err != nil {
  67. return 0, err
  68. }
  69. return i, err
  70. }
  71. func getRegStringInternal(subKey, name string) (string, error) {
  72. key, err := registry.OpenKey(registry.LOCAL_MACHINE, subKey, registry.READ)
  73. if err != nil {
  74. if err != ErrNoValue {
  75. log.Printf("registry.OpenKey(%v): %v", subKey, err)
  76. }
  77. return "", err
  78. }
  79. defer key.Close()
  80. val, _, err := key.GetStringValue(name)
  81. if err != nil {
  82. if err != ErrNoValue {
  83. log.Printf("registry.GetStringValue(%v): %v", name, err)
  84. }
  85. return "", err
  86. }
  87. return val, nil
  88. }
  89. // GetRegStrings looks up a registry value in the local machine path, or returns
  90. // the given default if it can't.
  91. func GetRegStrings(name string, defval []string) []string {
  92. s, err := getRegStringsInternal(regBase, name)
  93. if err != nil {
  94. return defval
  95. }
  96. return s
  97. }
  98. func getRegStringsInternal(subKey, name string) ([]string, error) {
  99. key, err := registry.OpenKey(registry.LOCAL_MACHINE, subKey, registry.READ)
  100. if err != nil {
  101. if err != ErrNoValue {
  102. log.Printf("registry.OpenKey(%v): %v", subKey, err)
  103. }
  104. return nil, err
  105. }
  106. defer key.Close()
  107. val, _, err := key.GetStringsValue(name)
  108. if err != nil {
  109. if err != ErrNoValue {
  110. log.Printf("registry.GetStringValue(%v): %v", name, err)
  111. }
  112. return nil, err
  113. }
  114. return val, nil
  115. }
  116. // SetRegStrings sets a MULTI_SZ value in the in the local machine path
  117. // to the strings specified by values.
  118. func SetRegStrings(name string, values []string) error {
  119. return setRegStringsInternal(regBase, name, values)
  120. }
  121. func setRegStringsInternal(subKey, name string, values []string) error {
  122. key, _, err := registry.CreateKey(registry.LOCAL_MACHINE, subKey, registry.SET_VALUE)
  123. if err != nil {
  124. log.Printf("registry.CreateKey(%v): %v", subKey, err)
  125. }
  126. defer key.Close()
  127. return key.SetStringsValue(name, values)
  128. }
  129. // DeleteRegValue removes a registry value in the local machine path.
  130. func DeleteRegValue(name string) error {
  131. return deleteRegValueInternal(regBase, name)
  132. }
  133. func deleteRegValueInternal(subKey, name string) error {
  134. key, err := registry.OpenKey(registry.LOCAL_MACHINE, subKey, registry.SET_VALUE)
  135. if err == ErrNoValue {
  136. return nil
  137. }
  138. if err != nil {
  139. log.Printf("registry.OpenKey(%v): %v", subKey, err)
  140. return err
  141. }
  142. defer key.Close()
  143. err = key.DeleteValue(name)
  144. if err == ErrNoValue {
  145. err = nil
  146. }
  147. return err
  148. }
  149. func getRegIntegerInternal(subKey, name string) (uint64, error) {
  150. key, err := registry.OpenKey(registry.LOCAL_MACHINE, subKey, registry.READ)
  151. if err != nil {
  152. if err != ErrNoValue {
  153. log.Printf("registry.OpenKey(%v): %v", subKey, err)
  154. }
  155. return 0, err
  156. }
  157. defer key.Close()
  158. val, _, err := key.GetIntegerValue(name)
  159. if err != nil {
  160. if err != ErrNoValue {
  161. log.Printf("registry.GetIntegerValue(%v): %v", name, err)
  162. }
  163. return 0, err
  164. }
  165. return val, nil
  166. }
  167. var (
  168. kernel32 = syscall.NewLazyDLL("kernel32.dll")
  169. procWTSGetActiveConsoleSessionId = kernel32.NewProc("WTSGetActiveConsoleSessionId")
  170. )
  171. // TODO(crawshaw): replace with x/sys/windows... one day.
  172. // https://go-review.googlesource.com/c/sys/+/331909
  173. func WTSGetActiveConsoleSessionId() uint32 {
  174. r1, _, _ := procWTSGetActiveConsoleSessionId.Call()
  175. return uint32(r1)
  176. }
  177. func isSIDValidPrincipal(uid string) bool {
  178. usid, err := syscall.StringToSid(uid)
  179. if err != nil {
  180. return false
  181. }
  182. _, _, accType, err := usid.LookupAccount("")
  183. if err != nil {
  184. return false
  185. }
  186. switch accType {
  187. case syscall.SidTypeUser, syscall.SidTypeGroup, syscall.SidTypeDomain, syscall.SidTypeAlias, syscall.SidTypeWellKnownGroup, syscall.SidTypeComputer:
  188. return true
  189. default:
  190. // Reject deleted users, invalid SIDs, unknown SIDs, mandatory label SIDs, etc.
  191. return false
  192. }
  193. }
  194. // EnableCurrentThreadPrivilege enables the named privilege
  195. // in the current thread's access token. The current goroutine is also locked to
  196. // the OS thread (runtime.LockOSThread). Callers must call the returned disable
  197. // function when done with the privileged task.
  198. func EnableCurrentThreadPrivilege(name string) (disable func(), err error) {
  199. return EnableCurrentThreadPrivileges([]string{name})
  200. }
  201. // EnableCurrentThreadPrivileges enables the named privileges
  202. // in the current thread's access token. The current goroutine is also locked to
  203. // the OS thread (runtime.LockOSThread). Callers must call the returned disable
  204. // function when done with the privileged task.
  205. func EnableCurrentThreadPrivileges(names []string) (disable func(), err error) {
  206. runtime.LockOSThread()
  207. if len(names) == 0 {
  208. // Nothing to enable; no-op isn't really an error...
  209. return runtime.UnlockOSThread, nil
  210. }
  211. if err := windows.ImpersonateSelf(windows.SecurityImpersonation); err != nil {
  212. runtime.UnlockOSThread()
  213. return nil, err
  214. }
  215. disable = func() {
  216. defer runtime.UnlockOSThread()
  217. // If RevertToSelf fails, it's not really recoverable and we should panic.
  218. // Failure to do so would leak the privileges we're enabling, which is a
  219. // security issue.
  220. if err := windows.RevertToSelf(); err != nil {
  221. panic(fmt.Sprintf("RevertToSelf failed: %v", err))
  222. }
  223. }
  224. defer func() {
  225. if err != nil {
  226. disable()
  227. }
  228. }()
  229. var t windows.Token
  230. err = windows.OpenThreadToken(windows.CurrentThread(),
  231. windows.TOKEN_QUERY|windows.TOKEN_ADJUST_PRIVILEGES, false, &t)
  232. if err != nil {
  233. return nil, err
  234. }
  235. defer t.Close()
  236. tp := newTokenPrivileges(len(names))
  237. privs := tp.AllPrivileges()
  238. for i := range privs {
  239. var privStr *uint16
  240. privStr, err = windows.UTF16PtrFromString(names[i])
  241. if err != nil {
  242. return nil, err
  243. }
  244. err = windows.LookupPrivilegeValue(nil, privStr, &privs[i].Luid)
  245. if err != nil {
  246. return nil, err
  247. }
  248. privs[i].Attributes = windows.SE_PRIVILEGE_ENABLED
  249. }
  250. err = windows.AdjustTokenPrivileges(t, false, tp, 0, nil, nil)
  251. if err != nil {
  252. return nil, err
  253. }
  254. return disable, nil
  255. }
  256. func newTokenPrivileges(numPrivs int) *windows.Tokenprivileges {
  257. if numPrivs <= 0 {
  258. panic("numPrivs must be > 0")
  259. }
  260. numBytes := unsafe.Sizeof(windows.Tokenprivileges{}) + (uintptr(numPrivs-1) * unsafe.Sizeof(windows.LUIDAndAttributes{}))
  261. buf := make([]byte, numBytes)
  262. result := (*windows.Tokenprivileges)(unsafe.Pointer(unsafe.SliceData(buf)))
  263. result.PrivilegeCount = uint32(numPrivs)
  264. return result
  265. }
  266. // StartProcessAsChild starts exePath process as a child of parentPID.
  267. // StartProcessAsChild copies parentPID's environment variables into
  268. // the new process, along with any optional environment variables in extraEnv.
  269. func StartProcessAsChild(parentPID uint32, exePath string, extraEnv []string) error {
  270. // The rest of this function requires SeDebugPrivilege to be held.
  271. //
  272. // According to https://docs.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights
  273. //
  274. // ... To open a handle to another process and obtain full access rights,
  275. // you must enable the SeDebugPrivilege privilege. ...
  276. //
  277. // But we only need PROCESS_CREATE_PROCESS. So perhaps SeDebugPrivilege is too much.
  278. //
  279. // https://devblogs.microsoft.com/oldnewthing/20080314-00/?p=23113
  280. //
  281. // TODO: try look for something less than SeDebugPrivilege
  282. disableSeDebug, err := EnableCurrentThreadPrivilege("SeDebugPrivilege")
  283. if err != nil {
  284. return err
  285. }
  286. defer disableSeDebug()
  287. ph, err := windows.OpenProcess(
  288. windows.PROCESS_CREATE_PROCESS|windows.PROCESS_QUERY_INFORMATION|windows.PROCESS_DUP_HANDLE,
  289. false, parentPID)
  290. if err != nil {
  291. return err
  292. }
  293. defer windows.CloseHandle(ph)
  294. var pt windows.Token
  295. err = windows.OpenProcessToken(ph, windows.TOKEN_QUERY, &pt)
  296. if err != nil {
  297. return err
  298. }
  299. defer pt.Close()
  300. env, err := pt.Environ(false)
  301. if err != nil {
  302. return err
  303. }
  304. env = append(env, extraEnv...)
  305. sys := &syscall.SysProcAttr{ParentProcess: syscall.Handle(ph)}
  306. cmd := exec.Command(exePath)
  307. cmd.Env = env
  308. cmd.SysProcAttr = sys
  309. return cmd.Start()
  310. }
  311. // StartProcessAsCurrentGUIUser is like StartProcessAsChild, but if finds
  312. // current logged in user desktop process (normally explorer.exe),
  313. // and passes found PID to StartProcessAsChild.
  314. func StartProcessAsCurrentGUIUser(exePath string, extraEnv []string) error {
  315. // as described in https://devblogs.microsoft.com/oldnewthing/20190425-00/?p=102443
  316. desktop, err := GetDesktopPID()
  317. if err != nil {
  318. return fmt.Errorf("failed to find desktop: %v", err)
  319. }
  320. err = StartProcessAsChild(desktop, exePath, extraEnv)
  321. if err != nil {
  322. return fmt.Errorf("failed to start executable: %v", err)
  323. }
  324. return nil
  325. }
  326. // CreateAppMutex creates a named Windows mutex, returning nil if the mutex
  327. // is created successfully or an error if the mutex already exists or could not
  328. // be created for some other reason.
  329. func CreateAppMutex(name string) (windows.Handle, error) {
  330. return windows.CreateMutex(nil, false, windows.StringToUTF16Ptr(name))
  331. }
  332. // getTokenInfoFixedLen obtains known fixed-length token information. Use this
  333. // function for information classes that output enumerations, BOOLs, integers etc.
  334. func getTokenInfoFixedLen[T any](token windows.Token, infoClass uint32) (result T, err error) {
  335. var actualLen uint32
  336. p := (*byte)(unsafe.Pointer(&result))
  337. err = windows.GetTokenInformation(token, infoClass, p, uint32(unsafe.Sizeof(result)), &actualLen)
  338. return result, err
  339. }
  340. type tokenElevationType int32
  341. const (
  342. tokenElevationTypeDefault tokenElevationType = 1
  343. tokenElevationTypeFull tokenElevationType = 2
  344. tokenElevationTypeLimited tokenElevationType = 3
  345. )
  346. // IsTokenLimited returns whether token is a limited UAC token.
  347. func IsTokenLimited(token windows.Token) (bool, error) {
  348. elevationType, err := getTokenInfoFixedLen[tokenElevationType](token, windows.TokenElevationType)
  349. if err != nil {
  350. return false, err
  351. }
  352. return elevationType == tokenElevationTypeLimited, nil
  353. }
  354. // UserSIDs contains the SIDs for a Windows NT token object's associated user
  355. // as well as its primary group.
  356. type UserSIDs struct {
  357. User *windows.SID
  358. PrimaryGroup *windows.SID
  359. }
  360. // GetCurrentUserSIDs returns a UserSIDs struct containing SIDs for the
  361. // current process' user and primary group.
  362. func GetCurrentUserSIDs() (*UserSIDs, error) {
  363. token, err := windows.OpenCurrentProcessToken()
  364. if err != nil {
  365. return nil, err
  366. }
  367. defer token.Close()
  368. userInfo, err := token.GetTokenUser()
  369. if err != nil {
  370. return nil, err
  371. }
  372. primaryGroup, err := token.GetTokenPrimaryGroup()
  373. if err != nil {
  374. return nil, err
  375. }
  376. return &UserSIDs{userInfo.User.Sid, primaryGroup.PrimaryGroup}, nil
  377. }
  378. // IsCurrentProcessElevated returns true when the current process is
  379. // running with an elevated token, implying Administrator access.
  380. func IsCurrentProcessElevated() bool {
  381. token, err := windows.OpenCurrentProcessToken()
  382. if err != nil {
  383. return false
  384. }
  385. defer token.Close()
  386. return token.IsElevated()
  387. }
  388. // keyOpenTimeout is how long we wait for a registry key to appear. For some
  389. // reason, registry keys tied to ephemeral interfaces can take a long while to
  390. // appear after interface creation, and we can end up racing with that.
  391. const keyOpenTimeout = 20 * time.Second
  392. // RegistryPath represents a path inside a root registry.Key.
  393. type RegistryPath string
  394. // RegistryPathPrefix specifies a RegistryPath prefix that must be suffixed with
  395. // another RegistryPath to make a valid RegistryPath.
  396. type RegistryPathPrefix string
  397. // WithSuffix returns a RegistryPath with the given suffix appended.
  398. func (p RegistryPathPrefix) WithSuffix(suf string) RegistryPath {
  399. return RegistryPath(string(p) + suf)
  400. }
  401. const (
  402. IPv4TCPIPBase RegistryPath = `SYSTEM\CurrentControlSet\Services\Tcpip\Parameters`
  403. IPv6TCPIPBase RegistryPath = `SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters`
  404. NetBTBase RegistryPath = `SYSTEM\CurrentControlSet\Services\NetBT\Parameters`
  405. IPv4TCPIPInterfacePrefix RegistryPathPrefix = `SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\`
  406. IPv6TCPIPInterfacePrefix RegistryPathPrefix = `SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters\Interfaces\`
  407. NetBTInterfacePrefix RegistryPathPrefix = `SYSTEM\CurrentControlSet\Services\NetBT\Parameters\Interfaces\Tcpip_`
  408. )
  409. // ErrKeyWaitTimeout is returned by OpenKeyWait when calls timeout.
  410. var ErrKeyWaitTimeout = errors.New("timeout waiting for registry key")
  411. // OpenKeyWait opens a registry key, waiting for it to appear if necessary. It
  412. // returns the opened key, or ErrKeyWaitTimeout if the key does not appear
  413. // within 20s. The caller must call Close on the returned key.
  414. func OpenKeyWait(k registry.Key, path RegistryPath, access uint32) (registry.Key, error) {
  415. runtime.LockOSThread()
  416. defer runtime.UnlockOSThread()
  417. deadline := time.Now().Add(keyOpenTimeout)
  418. pathSpl := strings.Split(string(path), "\\")
  419. for i := 0; ; i++ {
  420. keyName := pathSpl[i]
  421. isLast := i+1 == len(pathSpl)
  422. event, err := windows.CreateEvent(nil, 0, 0, nil)
  423. if err != nil {
  424. return 0, fmt.Errorf("windows.CreateEvent: %w", err)
  425. }
  426. defer windows.CloseHandle(event)
  427. var key registry.Key
  428. for {
  429. err = windows.RegNotifyChangeKeyValue(windows.Handle(k), false, windows.REG_NOTIFY_CHANGE_NAME, event, true)
  430. if err != nil {
  431. return 0, fmt.Errorf("windows.RegNotifyChangeKeyValue: %w", err)
  432. }
  433. var accessFlags uint32
  434. if isLast {
  435. accessFlags = access
  436. } else {
  437. accessFlags = registry.NOTIFY
  438. }
  439. key, err = registry.OpenKey(k, keyName, accessFlags)
  440. if err == windows.ERROR_FILE_NOT_FOUND || err == windows.ERROR_PATH_NOT_FOUND {
  441. timeout := time.Until(deadline) / time.Millisecond
  442. if timeout < 0 {
  443. timeout = 0
  444. }
  445. s, err := windows.WaitForSingleObject(event, uint32(timeout))
  446. if err != nil {
  447. return 0, fmt.Errorf("windows.WaitForSingleObject: %w", err)
  448. }
  449. if s == uint32(windows.WAIT_TIMEOUT) { // windows.WAIT_TIMEOUT status const is misclassified as error in golang.org/x/sys/windows
  450. return 0, ErrKeyWaitTimeout
  451. }
  452. } else if err != nil {
  453. return 0, fmt.Errorf("registry.OpenKey(%v): %w", path, err)
  454. } else {
  455. if isLast {
  456. return key, nil
  457. }
  458. defer key.Close()
  459. break
  460. }
  461. }
  462. k = key
  463. }
  464. }
  465. func lookupPseudoUser(uid string) (*user.User, error) {
  466. sid, err := windows.StringToSid(uid)
  467. if err != nil {
  468. return nil, err
  469. }
  470. // We're looking for SIDs "S-1-5-x" where 17 <= x <= 20.
  471. // This is checking for the the "5"
  472. if sid.IdentifierAuthority() != windows.SECURITY_NT_AUTHORITY {
  473. return nil, fmt.Errorf(`SID %q does not use "NT AUTHORITY"`, uid)
  474. }
  475. // This is ensuring that there is only one sub-authority.
  476. // In other words, only one value after the "5".
  477. if sid.SubAuthorityCount() != 1 {
  478. return nil, fmt.Errorf("SID %q should have only one subauthority", uid)
  479. }
  480. // Get that sub-authority value (this is "x" above) and check it.
  481. rid := sid.SubAuthority(0)
  482. if rid < 17 || rid > 20 {
  483. return nil, fmt.Errorf("SID %q does not represent a known pseudo-user", uid)
  484. }
  485. // We've got one of the known pseudo-users. Look up the localized name of the
  486. // account.
  487. username, domain, _, err := sid.LookupAccount("")
  488. if err != nil {
  489. return nil, err
  490. }
  491. // This call is best-effort. If it fails, homeDir will be empty.
  492. homeDir, _ := findHomeDirInRegistry(uid)
  493. result := &user.User{
  494. Uid: uid,
  495. Gid: uid, // Gid == Uid with these accounts.
  496. Username: fmt.Sprintf(`%s\%s`, domain, username),
  497. Name: username,
  498. HomeDir: homeDir,
  499. }
  500. return result, nil
  501. }
  502. // findHomeDirInRegistry finds the user home path based on the uid.
  503. // This is borrowed from Go's std lib.
  504. func findHomeDirInRegistry(uid string) (dir string, err error) {
  505. k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\`+uid, registry.QUERY_VALUE)
  506. if err != nil {
  507. return "", err
  508. }
  509. defer k.Close()
  510. dir, _, err = k.GetStringValue("ProfileImagePath")
  511. if err != nil {
  512. return "", err
  513. }
  514. return dir, nil
  515. }
  516. // ProcessImageName returns the fully-qualified path to the executable image
  517. // associated with process.
  518. func ProcessImageName(process windows.Handle) (string, error) {
  519. var pathBuf [windows.MAX_PATH]uint16
  520. pathBufLen := uint32(len(pathBuf))
  521. if err := windows.QueryFullProcessImageName(process, 0, &pathBuf[0], &pathBufLen); err != nil {
  522. return "", err
  523. }
  524. return windows.UTF16ToString(pathBuf[:pathBufLen]), nil
  525. }
  526. // TSSessionIDToLogonSessionID retrieves the logon session ID associated with
  527. // tsSessionId, which is a Terminal Services / RDP session ID. The calling
  528. // process must be running as LocalSystem.
  529. func TSSessionIDToLogonSessionID(tsSessionID uint32) (logonSessionID windows.LUID, err error) {
  530. var token windows.Token
  531. if err := windows.WTSQueryUserToken(tsSessionID, &token); err != nil {
  532. return logonSessionID, fmt.Errorf("WTSQueryUserToken: %w", err)
  533. }
  534. defer token.Close()
  535. return LogonSessionID(token)
  536. }
  537. // TSSessionID obtains the Terminal Services (RDP) session ID associated with token.
  538. func TSSessionID(token windows.Token) (tsSessionID uint32, err error) {
  539. return getTokenInfoFixedLen[uint32](token, windows.TokenSessionId)
  540. }
  541. type tokenOrigin struct {
  542. originatingLogonSession windows.LUID
  543. }
  544. // LogonSessionID obtains the logon session ID associated with token.
  545. func LogonSessionID(token windows.Token) (logonSessionID windows.LUID, err error) {
  546. origin, err := getTokenInfoFixedLen[tokenOrigin](token, windows.TokenOrigin)
  547. if err != nil {
  548. return logonSessionID, err
  549. }
  550. return origin.originatingLogonSession, nil
  551. }