nt_token/
lib.rs

1//  _   _ _______     _______          _
2// | \ | |_   _\ \   / /_   _|__   ___| |_ ___  _ __ ___
3// |  \| | | |  \ \ / /  | |/ _ \ / __| __/ _ \| '__/ _ \
4// | |\  | | |   \ V /   | | (_) | (__| || (_) | | |  __/
5// |_| \_| |_|    \_/    |_|\___/ \___|\__\___/|_|  \___|
6//
7//             Windows NT Token Utilities – since 2025
8// --------------------------------------------------------
9//! # nt_token
10//!
11//! Memory-safe, ergonomic helpers for working with Windows access tokens and security identifiers (SIDs) in Rust.
12//!
13//! This crate builds on the `windows` crate and deliberately mirrors the `PathBuf` / `Path` API shape:
14//!
15//! * `OwnedToken` – owns a `HANDLE` and releases it automatically on `Drop`.
16//! * `Token` – zero-cost borrowed view (`#[repr(transparent)]`); most high-level methods live here.
17//! * `Sid` – owned, immutable SID with parsing, formatting and account-lookup helpers.
18//!
19//! ## Quick start
20//!
21//! ```rust
22//! use nt_token::{OwnedToken, Sid};
23//! use windows::Win32::Security::TOKEN_QUERY;
24//!
25//! # fn main() -> windows::core::Result<()> {
26//! let token = OwnedToken::from_current_process(TOKEN_QUERY)?;
27//! println!("elevated         = {}", token.is_elevated()?);
28//! println!("integrity level  = {}", token.integrity_level()?);
29//!
30//! for g in token.groups()? {
31//!     println!("group → {g}");
32//! }
33//! # Ok(()) }
34//! ```
35
36use std::{ffi::c_void, ops::Deref};
37use windows::{
38    Win32::{
39        Foundation::{
40            CloseHandle, E_INVALIDARG, ERROR_INSUFFICIENT_BUFFER, ERROR_NOT_ALL_ASSIGNED,
41            GetLastError, HANDLE, HLOCAL, LUID, LocalFree,
42        },
43        Security::{
44            AdjustTokenGroups, AdjustTokenPrivileges, AllocateAndInitializeSid,
45            Authorization::{ConvertSidToStringSidW, ConvertStringSidToSidW},
46            CREATE_RESTRICTED_TOKEN_FLAGS, CheckTokenMembership, CreateRestrictedToken,
47            CreateWellKnownSid, DuplicateTokenEx, FreeSid, GetLengthSid, GetSidSubAuthority,
48            GetSidSubAuthorityCount, GetTokenInformation, IsValidSid, IsWellKnownSid,
49            LUID_AND_ATTRIBUTES, LookupAccountSidW, LookupPrivilegeNameW, LookupPrivilegeValueW,
50            PSID, SE_PRIVILEGE_ENABLED, SE_PRIVILEGE_REMOVED, SECURITY_IMPERSONATION_LEVEL,
51            SECURITY_NT_AUTHORITY, SID_IDENTIFIER_AUTHORITY, SID_NAME_USE, TOKEN_ACCESS_MASK,
52            TOKEN_APPCONTAINER_INFORMATION, TOKEN_ELEVATION, TOKEN_ELEVATION_TYPE, TOKEN_GROUPS,
53            TOKEN_INFORMATION_CLASS, TOKEN_LINKED_TOKEN, TOKEN_MANDATORY_LABEL, TOKEN_PRIVILEGES,
54            TOKEN_PRIVILEGES_ATTRIBUTES, TOKEN_TYPE, TOKEN_USER, TokenAppContainerNumber,
55            TokenAppContainerSid, TokenCapabilities, TokenDeviceGroups, TokenElevation,
56            TokenElevationType, TokenGroups, TokenHasRestrictions, TokenImpersonationLevel,
57            TokenIntegrityLevel, TokenIsAppContainer, TokenLinkedToken, TokenLogonSid, TokenOwner,
58            TokenPrimary, TokenPrimaryGroup, TokenPrivileges, TokenRestrictedDeviceGroups,
59            TokenRestrictedSids, TokenType, TokenUIAccess, TokenUser, TokenVirtualizationAllowed,
60            TokenVirtualizationEnabled, WELL_KNOWN_SID_TYPE,
61        },
62        System::{
63            SystemServices::SE_GROUP_ENABLED,
64            Threading::{GetCurrentProcess, OpenProcessToken},
65        },
66    },
67    core::{BOOL, Error, HSTRING, PCWSTR, PWSTR, Result},
68};
69
70fn buffer_probe(res: Result<()>) -> Result<()> {
71    match res {
72        Ok(()) => Ok(()),
73        Err(e) if e.code() == ERROR_INSUFFICIENT_BUFFER.to_hresult() => Ok(()),
74        Err(e) => Err(e),
75    }
76}
77
78fn nonempty_slice<T>(slice: &[T]) -> Option<&[T]> {
79    if slice.is_empty() { None } else { Some(slice) }
80}
81
82/* ------------------------------------------------------------------------- */
83/* OwnedToken                                                                */
84/* ------------------------------------------------------------------------- */
85
86/// RAII owner for a Windows access‑token `HANDLE`.
87#[derive(Debug)]
88pub struct OwnedToken {
89    handle: HANDLE,
90}
91
92impl OwnedToken {
93    /// Open the current process token with the requested access rights.
94    pub fn from_current_process(access: TOKEN_ACCESS_MASK) -> Result<Self> {
95        unsafe {
96            let mut h = HANDLE::default();
97            OpenProcessToken(GetCurrentProcess(), access, &mut h)?;
98            Ok(Self { handle: h })
99        }
100    }
101
102    /// Wrap an existing token `HANDLE`.
103    ///
104    /// # Safety
105    /// The caller must ensure that `handle` is a valid access-token handle
106    /// that is **exclusively owned** – it will be automatically closed when the
107    /// returned `OwnedToken` is dropped.
108    pub unsafe fn new(handle: HANDLE) -> Self {
109        Self { handle }
110    }
111
112    /// Release ownership and return the raw `HANDLE` without closing it.
113    ///
114    /// This prevents the handle from being closed when the wrapper is dropped;
115    /// the caller becomes responsible for eventually calling `CloseHandle`.
116    pub fn into_raw(self) -> HANDLE {
117        let h = self.handle;
118        std::mem::forget(self);
119        h
120    }
121}
122
123impl Drop for OwnedToken {
124    fn drop(&mut self) {
125        unsafe { CloseHandle(self.handle).ok() };
126    }
127}
128
129impl Deref for OwnedToken {
130    type Target = Token;
131    fn deref(&self) -> &Self::Target {
132        unsafe { Token::new(&self.handle) }
133    }
134}
135
136/* ------------------------------------------------------------------------- */
137/* Token                                                                  */
138/* ------------------------------------------------------------------------- */
139
140/// Borrowed, zero‑cost view of a token `HANDLE` (like `Path`).
141#[repr(transparent)]
142#[derive(Copy, Clone, Debug)]
143pub struct Token {
144    handle: HANDLE,
145}
146
147impl Token {
148    pub unsafe fn new(handle: &HANDLE) -> &Self {
149        unsafe { &*(handle as *const HANDLE as *const Token) }
150    }
151
152    /// Raw handle (borrowed).
153    pub fn handle(&self) -> HANDLE {
154        self.handle
155    }
156
157    /// Duplicate the referenced token.
158    pub fn duplicate(
159        &self,
160        access: TOKEN_ACCESS_MASK,
161        token_type: TOKEN_TYPE,
162        imp_level: SECURITY_IMPERSONATION_LEVEL,
163    ) -> Result<OwnedToken> {
164        unsafe {
165            let mut dup = HANDLE::default();
166            DuplicateTokenEx(self.handle, access, None, imp_level, token_type, &mut dup)?;
167            Ok(OwnedToken { handle: dup })
168        }
169    }
170
171    /// Is the token elevated (Vista+ UAC)?
172    pub fn is_elevated(&self) -> Result<bool> {
173        unsafe {
174            let mut elev: TOKEN_ELEVATION = std::mem::zeroed();
175            let mut ret = 0u32;
176            GetTokenInformation(
177                self.handle,
178                TokenElevation,
179                Some(&mut elev as *mut _ as *mut c_void),
180                std::mem::size_of::<TOKEN_ELEVATION>() as u32,
181                &mut ret,
182            )?;
183            Ok(elev.TokenIsElevated != 0)
184        }
185    }
186
187    /// Return the integrity-level SID.
188    pub fn integrity_level(&self) -> Result<Sid> {
189        unsafe {
190            let mut len = 0u32;
191            buffer_probe(GetTokenInformation(
192                self.handle,
193                TokenIntegrityLevel,
194                None,
195                0,
196                &mut len,
197            ))?;
198            let mut buf = vec![0u8; len as usize];
199            GetTokenInformation(
200                self.handle,
201                TokenIntegrityLevel,
202                Some(buf.as_mut_ptr() as *mut c_void),
203                len,
204                &mut len,
205            )?;
206            let label = &*(buf.as_ptr() as *const TOKEN_MANDATORY_LABEL);
207            Sid::from_ptr(label.Label.Sid)
208        }
209    }
210
211    fn groups_of(&self, class: TOKEN_INFORMATION_CLASS) -> Result<Vec<Group>> {
212        unsafe {
213            let mut len = 0u32;
214            buffer_probe(GetTokenInformation(self.handle, class, None, 0, &mut len))?;
215            let mut buf = vec![0u8; len as usize];
216            GetTokenInformation(
217                self.handle,
218                class,
219                Some(buf.as_mut_ptr() as *mut c_void),
220                len,
221                &mut len,
222            )?;
223            let groups = &*(buf.as_ptr() as *const TOKEN_GROUPS);
224            let slice =
225                std::slice::from_raw_parts(groups.Groups.as_ptr(), groups.GroupCount as usize);
226            slice
227                .iter()
228                .map(|ga| {
229                    Ok(Group {
230                        sid: Sid::from_ptr(ga.Sid)?,
231                        attributes: ga.Attributes,
232                    })
233                })
234                .collect()
235        }
236    }
237
238    /// Enumerate group SIDs (`TokenGroups`).
239    pub fn groups(&self) -> Result<Vec<Group>> {
240        self.groups_of(TokenGroups)
241    }
242
243    /// Enumerate capability SIDs (`TokenCapabilities`).
244    pub fn capabilities(&self) -> Result<Vec<Group>> {
245        self.groups_of(TokenCapabilities)
246    }
247
248    /// Enumerate the logon SID (`TokenLogonSid`). Typically returns a single entry.
249    pub fn logon_sid(&self) -> Result<Vec<Group>> {
250        self.groups_of(TokenLogonSid)
251    }
252
253    /// Enumerate restricted SIDs (`TokenRestrictedSids`).
254    pub fn restricted_sids(&self) -> Result<Vec<Group>> {
255        self.groups_of(TokenRestrictedSids)
256    }
257
258    /// Enumerate device group SIDs (`TokenDeviceGroups`).
259    pub fn device_groups(&self) -> Result<Vec<Group>> {
260        self.groups_of(TokenDeviceGroups)
261    }
262
263    /// Enumerate restricted device group SIDs (`TokenRestrictedDeviceGroups`).
264    pub fn restricted_device_groups(&self) -> Result<Vec<Group>> {
265        self.groups_of(TokenRestrictedDeviceGroups)
266    }
267
268    /// For filtered admin tokens, return the linked administrator token.
269    pub fn linked_token(&self) -> Result<OwnedToken> {
270        unsafe {
271            let mut linked: TOKEN_LINKED_TOKEN = std::mem::zeroed();
272            let mut ret = 0u32;
273            GetTokenInformation(
274                self.handle,
275                TokenLinkedToken,
276                Some(&mut linked as *mut _ as *mut c_void),
277                std::mem::size_of::<TOKEN_LINKED_TOKEN>() as u32,
278                &mut ret,
279            )?;
280            Ok(OwnedToken {
281                handle: linked.LinkedToken,
282            })
283        }
284    }
285
286    /// Retrieve the token's elevation type (default, limited, or full).
287    pub fn elevation_type(&self) -> Result<TOKEN_ELEVATION_TYPE> {
288        unsafe {
289            let mut et: TOKEN_ELEVATION_TYPE = std::mem::zeroed();
290            let mut ret = 0u32;
291            GetTokenInformation(
292                self.handle,
293                TokenElevationType,
294                Some(&mut et as *mut _ as *mut c_void),
295                std::mem::size_of::<TOKEN_ELEVATION_TYPE>() as u32,
296                &mut ret,
297            )?;
298            Ok(et)
299        }
300    }
301
302    /// Retrieve the impersonation level (only valid for impersonation tokens).
303    pub fn impersonation_level(&self) -> Result<SECURITY_IMPERSONATION_LEVEL> {
304        unsafe {
305            let mut lvl: SECURITY_IMPERSONATION_LEVEL = std::mem::zeroed();
306            let mut ret = 0u32;
307            GetTokenInformation(
308                self.handle,
309                TokenImpersonationLevel,
310                Some(&mut lvl as *mut _ as *mut c_void),
311                std::mem::size_of::<SECURITY_IMPERSONATION_LEVEL>() as u32,
312                &mut ret,
313            )?;
314            Ok(lvl)
315        }
316    }
317
318    /// Helper: retrieve a variable-length `GetTokenInformation` buffer.
319    #[inline]
320    fn info_buffer(&self, class: TOKEN_INFORMATION_CLASS) -> Result<Vec<u8>> {
321        unsafe {
322            let mut len = 0u32;
323            buffer_probe(GetTokenInformation(self.handle, class, None, 0, &mut len))?;
324            let mut buf = vec![0u8; len as usize];
325            GetTokenInformation(
326                self.handle,
327                class,
328                Some(buf.as_mut_ptr() as *mut c_void),
329                len,
330                &mut len,
331            )?;
332            Ok(buf)
333        }
334    }
335
336    /// Helper: retrieve a DWORD token information field.
337    #[inline]
338    fn dword_info(&self, class: TOKEN_INFORMATION_CLASS) -> Result<u32> {
339        unsafe {
340            let mut val: u32 = 0;
341            let mut ret = 0u32;
342            GetTokenInformation(
343                self.handle,
344                class,
345                Some(&mut val as *mut _ as *mut c_void),
346                std::mem::size_of::<u32>() as u32,
347                &mut ret,
348            )?;
349            Ok(val)
350        }
351    }
352
353    /// Helper: retrieve a bool token information field.
354    fn bool_info(&self, class: TOKEN_INFORMATION_CLASS) -> Result<bool> {
355        Ok(self.dword_info(class)? != 0)
356    }
357
358    /// Does the token have any restrictions (sandboxed / filtered token)?
359    pub fn has_restrictions(&self) -> Result<bool> {
360        self.bool_info(TokenHasRestrictions)
361    }
362
363    /// Is process virtualization allowed for this token (UAC file/registry virtualization)?
364    pub fn virtualization_allowed(&self) -> Result<bool> {
365        self.bool_info(TokenVirtualizationAllowed)
366    }
367
368    /// Is process virtualization currently enabled for this token?
369    pub fn virtualization_enabled(&self) -> Result<bool> {
370        self.bool_info(TokenVirtualizationEnabled)
371    }
372
373    /// Is this token an AppContainer token?
374    pub fn is_app_container(&self) -> Result<bool> {
375        self.bool_info(TokenIsAppContainer)
376    }
377
378    /// Is this a primary token (`TokenPrimary`) as opposed to an impersonation token?
379    pub fn is_primary(&self) -> Result<bool> {
380        unsafe {
381            let mut ty: TOKEN_TYPE = std::mem::zeroed();
382            let mut ret = 0u32;
383            GetTokenInformation(
384                self.handle,
385                TokenType,
386                Some(&mut ty as *mut _ as *mut c_void),
387                std::mem::size_of::<TOKEN_TYPE>() as u32,
388                &mut ret,
389            )?;
390            Ok(ty == TokenPrimary)
391        }
392    }
393
394    /// Return the token's primary user SID.
395    pub fn user(&self) -> Result<Sid> {
396        let buf = self.info_buffer(TokenUser)?;
397        let tu = unsafe { &*(buf.as_ptr() as *const TOKEN_USER) };
398        unsafe { Sid::from_ptr(tu.User.Sid) }
399    }
400
401    /// Return the token's primary group SID.
402    pub fn primary_group(&self) -> Result<Sid> {
403        let buf = self.info_buffer(TokenPrimaryGroup)?;
404        let tpg =
405            unsafe { &*(buf.as_ptr() as *const windows::Win32::Security::TOKEN_PRIMARY_GROUP) };
406        unsafe { Sid::from_ptr(tpg.PrimaryGroup) }
407    }
408
409    /// Return the token's owner SID.
410    pub fn owner(&self) -> Result<Sid> {
411        let buf = self.info_buffer(TokenOwner)?;
412        let to = unsafe { &*(buf.as_ptr() as *const windows::Win32::Security::TOKEN_OWNER) };
413        unsafe { Sid::from_ptr(to.Owner) }
414    }
415
416    /// Enumerate privileges contained in the token.
417    pub fn privileges(&self) -> Result<Vec<Privilege>> {
418        unsafe {
419            let mut len = 0u32;
420            buffer_probe(GetTokenInformation(
421                self.handle,
422                TokenPrivileges,
423                None,
424                0,
425                &mut len,
426            ))?;
427
428            let mut buf = vec![0u8; len as usize];
429            GetTokenInformation(
430                self.handle,
431                TokenPrivileges,
432                Some(buf.as_mut_ptr() as *mut c_void),
433                len,
434                &mut len,
435            )?;
436
437            let tprivs = &*(buf.as_ptr() as *const TOKEN_PRIVILEGES);
438            let slice = std::slice::from_raw_parts(
439                tprivs.Privileges.as_ptr(),
440                tprivs.PrivilegeCount as usize,
441            );
442
443            Ok(slice.iter().map(|la| Privilege::from_raw(la)).collect())
444        }
445    }
446
447    /// Adjust multiple privileges in one go. The token must have
448    /// `TOKEN_ADJUST_PRIVILEGES` access. Each `Privilege` decides whether
449    /// it should be enabled (`Privilege::new(name, true)`) or disabled
450    /// (`Privilege::new(name, false)`).
451    pub fn adjust_privileges(&self, privs: &[Privilege]) -> Result<()> {
452        if privs.is_empty() {
453            return Ok(());
454        }
455
456        unsafe {
457            // Allocate a variable-length TOKEN_PRIVILEGES buffer.
458            let count = privs.len();
459            let buf_len = std::mem::size_of::<TOKEN_PRIVILEGES>()
460                + (count - 1) * std::mem::size_of::<LUID_AND_ATTRIBUTES>();
461            let mut buf = vec![0u8; buf_len];
462
463            // Write PrivilegeCount.
464            *(buf.as_mut_ptr() as *mut u32) = count as u32;
465
466            // Pointer to the first LUID_AND_ATTRIBUTES entry.
467            let la_ptr =
468                buf.as_mut_ptr().add(std::mem::size_of::<u32>()) as *mut LUID_AND_ATTRIBUTES;
469
470            for (i, p) in privs.iter().enumerate() {
471                *la_ptr.add(i) = LUID_AND_ATTRIBUTES {
472                    Luid: p.inner.Luid,
473                    Attributes: if p.is_enabled() {
474                        SE_PRIVILEGE_ENABLED
475                    } else {
476                        SE_PRIVILEGE_REMOVED
477                    },
478                };
479            }
480
481            let tp_ptr = buf.as_ptr() as *const TOKEN_PRIVILEGES;
482
483            AdjustTokenPrivileges(self.handle, false, Some(&*tp_ptr), 0, None, None)?;
484            if GetLastError() == ERROR_NOT_ALL_ASSIGNED {
485                return Err(Error::from_win32());
486            }
487        }
488        Ok(())
489    }
490
491    /// Check whether this token contains the specified SID (group membership).
492    pub fn check_membership(&self, sid: &Sid) -> Result<bool> {
493        unsafe {
494            let mut is_member = BOOL(0);
495            // Safety: `sid.buf` is a valid SID buffer.
496            CheckTokenMembership(
497                Some(self.handle),
498                PSID(sid.buf.as_ptr() as *mut c_void),
499                &mut is_member,
500            )?;
501            Ok(is_member.as_bool())
502        }
503    }
504
505    /// Does the token have UIAccess privilege (non-elevated UI automation)?
506    pub fn ui_access(&self) -> Result<bool> {
507        self.bool_info(TokenUIAccess)
508    }
509
510    /// Return the app-container SID associated with this token, or `None` if the token is not an AppContainer.
511    pub fn app_container_sid(&self) -> Result<Option<Sid>> {
512        let buf = self.info_buffer(TokenAppContainerSid)?;
513        let info = unsafe { &*(buf.as_ptr() as *const TOKEN_APPCONTAINER_INFORMATION) };
514        if info.TokenAppContainer.0.is_null() {
515            return Ok(None);
516        }
517        let sid = unsafe { Sid::from_ptr(info.TokenAppContainer)? };
518        Ok(Some(sid))
519    }
520
521    /// Return the app-container number assigned by the system.
522    pub fn app_container_number(&self) -> Result<u32> {
523        self.dword_info(TokenAppContainerNumber)
524    }
525
526    /// Adjust multiple group states in one go. The token must have
527    /// `TOKEN_ADJUST_GROUPS` access. Each `Group` instance specifies the
528    /// desired attribute flags for an existing SID in the token. For
529    /// example, to enable a group, include the `SE_GROUP_ENABLED` attribute;
530    /// to disable it, omit that flag. Analogous to `adjust_privileges`.
531    pub fn adjust_groups(&self, groups: &[Group]) -> Result<()> {
532        if groups.is_empty() {
533            return Ok(());
534        }
535
536        unsafe {
537            let sid_attrs = Self::sid_attr_vec(groups);
538            let count = sid_attrs.len();
539
540            // Compute total size for TOKEN_GROUPS buffer.
541            let buf_len = std::mem::size_of::<TOKEN_GROUPS>()
542                + (count - 1) * std::mem::size_of::<windows::Win32::Security::SID_AND_ATTRIBUTES>();
543
544            let usize_slots =
545                (buf_len + std::mem::size_of::<usize>() - 1) / std::mem::size_of::<usize>();
546            let mut buf: Vec<usize> = vec![0; usize_slots];
547
548            let tg_ptr = buf.as_mut_ptr() as *mut TOKEN_GROUPS;
549
550            (*tg_ptr).GroupCount = count as u32;
551
552            let sa_ptr = (*tg_ptr).Groups.as_mut_ptr();
553            std::ptr::copy_nonoverlapping(sid_attrs.as_ptr(), sa_ptr, count);
554
555            AdjustTokenGroups(self.handle, false, Some(&*tg_ptr), 0, None, None)?;
556
557            if GetLastError() == ERROR_NOT_ALL_ASSIGNED {
558                return Err(Error::from_win32());
559            }
560        }
561
562        Ok(())
563    }
564
565    /// Internal helper: build a Vec<SID_AND_ATTRIBUTES> pointing at the SIDs
566    /// owned by the provided `Group` slice. The returned vector keeps the
567    /// pointers valid for the lifetime of the vector.
568    fn sid_attr_vec(groups: &[Group]) -> Vec<windows::Win32::Security::SID_AND_ATTRIBUTES> {
569        groups
570            .iter()
571            .map(|g| windows::Win32::Security::SID_AND_ATTRIBUTES {
572                Sid: PSID(g.sid.buf.as_ptr() as *mut _),
573                Attributes: g.attributes(),
574            })
575            .collect()
576    }
577
578    /// Create a restricted token derived from this token.
579    /// `flags` is a bitmask of `CREATE_RESTRICTED_TOKEN_*` constants (e.g.
580    /// `DISABLE_MAX_PRIVILEGE`). The various slices correspond to the
581    /// parameters of the Win32 `CreateRestrictedToken` API.
582    pub fn create_restricted_token(
583        &self,
584        flags: CREATE_RESTRICTED_TOKEN_FLAGS,
585        sids_to_disable: &[Group],
586        privileges_to_delete: &[Privilege],
587        sids_to_restrict: &[Group],
588    ) -> Result<OwnedToken> {
589        unsafe {
590            // Build SID_AND_ATTRIBUTES buffers.
591            let disable_vec = Self::sid_attr_vec(sids_to_disable);
592            let restrict_vec = Self::sid_attr_vec(sids_to_restrict);
593            // Build LUID_AND_ATTRIBUTES array for privileges.
594            let priv_vec: Vec<LUID_AND_ATTRIBUTES> =
595                privileges_to_delete.iter().map(|p| p.inner).collect();
596            let mut new_tok = HANDLE::default();
597
598            CreateRestrictedToken(
599                self.handle,
600                flags,
601                nonempty_slice(&disable_vec),
602                nonempty_slice(&priv_vec),
603                nonempty_slice(&restrict_vec),
604                &mut new_tok,
605            )?;
606
607            Ok(OwnedToken { handle: new_tok })
608        }
609    }
610}
611
612impl<'a> From<&'a OwnedToken> for Token {
613    fn from(tok: &'a OwnedToken) -> Self {
614        Token { handle: tok.handle }
615    }
616}
617
618/* ------------------------------------------------------------------------- */
619/* Sid                                                                       */
620/* ------------------------------------------------------------------------- */
621
622/// Owned and immutable security identifier.
623#[derive(Clone, Debug, PartialEq, Eq, Hash)]
624pub struct Sid {
625    buf: Vec<u8>,
626}
627
628impl Sid {
629    /// Parse canonical string form ("S‑1‑…").
630    pub fn parse(s: &str) -> Result<Self> {
631        unsafe {
632            let mut psid = PSID::default();
633            let hstring = HSTRING::from(s);
634            ConvertStringSidToSidW(PCWSTR(hstring.as_ptr()), &mut psid)?;
635            let len = GetLengthSid(psid);
636            let slice = std::slice::from_raw_parts(psid.0 as *const u8, len as usize);
637            let v = slice.to_vec();
638            LocalFree(Some(HLOCAL(psid.0 as *mut c_void)));
639            Ok(Self { buf: v })
640        }
641    }
642
643    /// Canonical string representation.
644    pub fn to_string(&self) -> Result<String> {
645        unsafe {
646            let mut pwstr = PWSTR::null();
647            ConvertSidToStringSidW(PSID(self.buf.as_ptr() as *mut c_void), &mut pwstr)?;
648            let s = pwstr.to_string();
649            LocalFree(Some(HLOCAL(pwstr.0 as *mut c_void)));
650            Ok(s?)
651        }
652    }
653
654    /// Resolve account & domain names.
655    pub fn account(&self) -> Result<(String, String)> {
656        unsafe {
657            let mut name_len = 0u32;
658            let mut dom_len = 0u32;
659            let mut use_ty = SID_NAME_USE(0);
660            buffer_probe(LookupAccountSidW(
661                PCWSTR::null(),
662                PSID(self.buf.as_ptr() as *mut _),
663                None,
664                &mut name_len,
665                None,
666                &mut dom_len,
667                &mut use_ty,
668            ))?;
669            let mut name = vec![0u16; name_len as usize];
670            let mut dom = vec![0u16; dom_len as usize];
671            LookupAccountSidW(
672                PCWSTR::null(),
673                PSID(self.buf.as_ptr() as *mut _),
674                Some(PWSTR(name.as_mut_ptr())),
675                &mut name_len,
676                Some(PWSTR(dom.as_mut_ptr())),
677                &mut dom_len,
678                &mut use_ty,
679            )?;
680            Ok((
681                String::from_utf16(&name[..name_len as usize])?,
682                String::from_utf16(&dom[..dom_len as usize])?,
683            ))
684        }
685    }
686
687    /// Build a Windows well‑known SID.
688    pub fn well_known(kind: WELL_KNOWN_SID_TYPE) -> Result<Self> {
689        unsafe {
690            let mut size = 0u32;
691            buffer_probe(CreateWellKnownSid(kind, None, None, &mut size))?;
692            let mut buf = vec![0u8; size as usize];
693            CreateWellKnownSid(
694                kind,
695                None,
696                Some(PSID(buf.as_mut_ptr() as *mut _)),
697                &mut size,
698            )?;
699            Ok(Self { buf })
700        }
701    }
702
703    /* -------------- helpers --------------------------------------------- */
704    pub(crate) unsafe fn from_ptr(psid: PSID) -> Result<Self> {
705        unsafe {
706            let len = GetLengthSid(psid);
707            if len == 0 {
708                return Err(Error::from_win32());
709            }
710            let slice = std::slice::from_raw_parts(psid.0 as *const u8, len as usize);
711            Ok(Self {
712                buf: slice.to_vec(),
713            })
714        }
715    }
716
717    /// Last sub‑authority (RID).
718    pub fn rid(&self) -> Result<u32> {
719        unsafe {
720            let cnt_ptr = GetSidSubAuthorityCount(PSID(self.buf.as_ptr() as *mut c_void));
721            if cnt_ptr.is_null() {
722                return Err(Error::from_win32());
723            }
724            let rid_ptr = GetSidSubAuthority(
725                PSID(self.buf.as_ptr() as *mut c_void),
726                (*cnt_ptr as u32) - 1,
727            );
728            if rid_ptr.is_null() {
729                return Err(Error::from_win32());
730            }
731            Ok(*rid_ptr)
732        }
733    }
734
735    /// Is this SID structurally valid (according to `IsValidSid`)?
736    pub fn is_valid(&self) -> bool {
737        unsafe { IsValidSid(PSID(self.buf.as_ptr() as *mut _)).as_bool() }
738    }
739
740    /// Does this SID match the specified well-known SID type?
741    pub fn is_well_known(&self, kind: WELL_KNOWN_SID_TYPE) -> bool {
742        unsafe { IsWellKnownSid(PSID(self.buf.as_ptr() as *mut _), kind).as_bool() }
743    }
744
745    /// Build a SID from an identifier authority and a slice of 32-bit
746    /// sub-authorities. Internally this uses `AllocateAndInitializeSid`, which
747    /// supports up to 8 sub-authorities.
748    pub fn from_parts(authority: &SID_IDENTIFIER_AUTHORITY, subids: &[u32]) -> Result<Self> {
749        if subids.len() > 8 {
750            return Err(Error::new(E_INVALIDARG, "too many RIDs"));
751        }
752
753        unsafe {
754            let mut psid = PSID::default();
755            // Zero-fill the parameters for the varargs call.
756            let mut subs = [0u32; 8];
757            for (i, &rid) in subids.iter().enumerate() {
758                subs[i] = rid;
759            }
760
761            AllocateAndInitializeSid(
762                authority,
763                subids.len() as u8,
764                subs[0],
765                subs[1],
766                subs[2],
767                subs[3],
768                subs[4],
769                subs[5],
770                subs[6],
771                subs[7],
772                &mut psid,
773            )?;
774
775            // Copy into a Rust-managed buffer then free the Win32 allocation.
776            let len = GetLengthSid(psid);
777            let slice = std::slice::from_raw_parts(psid.0 as *const u8, len as usize);
778            let buf = slice.to_vec();
779            FreeSid(psid);
780
781            Ok(Self { buf })
782        }
783    }
784
785    /// Convenience helper: construct an `S-1-5-…` SID under the NT authority
786    /// (`SECURITY_NT_AUTHORITY`) from the provided subauthorities.
787    pub fn from_nt_authority(subids: &[u32]) -> Result<Self> {
788        Self::from_parts(&SECURITY_NT_AUTHORITY, subids)
789    }
790}
791
792impl std::fmt::Display for Sid {
793    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
794        match self.to_string() {
795            Ok(s) => write!(f, "{s}"),
796            Err(_) => write!(f, "<invalid sid>"),
797        }
798    }
799}
800
801/* ------------------------------------------------------------------------- */
802/* Group                                                                     */
803/* ------------------------------------------------------------------------- */
804
805/// Token group entry – SID plus its attribute flags.
806#[derive(Clone, Debug, PartialEq, Eq)]
807pub struct Group {
808    sid: Sid,
809    attributes: u32,
810}
811
812impl Group {
813    /// Borrow the underlying SID.
814    pub fn sid(&self) -> &Sid {
815        &self.sid
816    }
817
818    /// Raw attribute flags (see `SE_GROUP_*`).
819    pub fn attributes(&self) -> u32 {
820        self.attributes
821    }
822
823    /// Convenience helper – resolve account/domain names (delegates to the SID).
824    pub fn account(&self) -> Result<(String, String)> {
825        self.sid.account()
826    }
827
828    /// Construct a group specification from a `Sid` and an explicit
829    /// attribute mask (see `SE_GROUP_*`). This gives you full control over the
830    /// `SID_AND_ATTRIBUTES::Attributes` field that will be passed to
831    /// `AdjustTokenGroups`.
832    pub fn new(sid: Sid, attributes: u32) -> Self {
833        Self { sid, attributes }
834    }
835
836    /// Convenience constructor: create an enabled group specification
837    /// (`SE_GROUP_ENABLED`).
838    pub fn enabled(sid: Sid) -> Self {
839        Self::new(sid, SE_GROUP_ENABLED as u32)
840    }
841
842    /// Convenience constructor: create a disabled group specification
843    /// (no attributes set).
844    pub fn disabled(sid: Sid) -> Self {
845        Self::new(sid, 0)
846    }
847}
848
849impl std::fmt::Display for Group {
850    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
851        // Delegate to the SID's `Display` impl for concise output.
852        write!(f, "{}", self.sid)
853    }
854}
855
856/* ------------------------------------------------------------------------- */
857/* Privilege                                                                 */
858/* ------------------------------------------------------------------------- */
859
860/// Token privilege (immutable snapshot).
861#[derive(Clone, Debug)]
862pub struct Privilege {
863    inner: LUID_AND_ATTRIBUTES,
864}
865
866impl Privilege {
867    /// Internal helper – build from a raw `LUID_AND_ATTRIBUTES` entry.
868    pub(crate) fn from_raw(inner: &LUID_AND_ATTRIBUTES) -> Self {
869        Self { inner: *inner }
870    }
871
872    /// Return the privilege name (e.g. `SeDebugPrivilege`).
873    pub fn name(&self) -> Result<String> {
874        unsafe {
875            let mut len = 0u32;
876            buffer_probe(LookupPrivilegeNameW(
877                PCWSTR::null(),
878                &self.inner.Luid,
879                None,
880                &mut len,
881            ))?;
882            let mut buf = vec![0u16; len as usize];
883            LookupPrivilegeNameW(
884                PCWSTR::null(),
885                &self.inner.Luid,
886                Some(PWSTR(buf.as_mut_ptr())),
887                &mut len,
888            )?;
889            Ok(String::from_utf16(&buf[..len as usize])?)
890        }
891    }
892
893    /// Raw attributes bitmask (see `SE_PRIVILEGE_*` constants).
894    pub fn attributes(&self) -> TOKEN_PRIVILEGES_ATTRIBUTES {
895        self.inner.Attributes
896    }
897
898    /// Is this privilege currently enabled?
899    pub fn is_enabled(&self) -> bool {
900        self.inner.Attributes.contains(SE_PRIVILEGE_ENABLED)
901    }
902
903    /// Construct a privilege specification by name and desired enabled state.
904    /// `name` is the textual privilege name (e.g. `"SeDebugPrivilege"`).
905    pub fn new(name: &str, enable: bool) -> Result<Self> {
906        unsafe {
907            // Resolve the privilege's LUID.
908            let mut luid = LUID::default();
909            let hstring = HSTRING::from(name);
910            LookupPrivilegeValueW(PCWSTR::null(), PCWSTR(hstring.as_ptr()), &mut luid)?;
911
912            Ok(Self {
913                inner: LUID_AND_ATTRIBUTES {
914                    Luid: luid,
915                    Attributes: if enable {
916                        SE_PRIVILEGE_ENABLED
917                    } else {
918                        SE_PRIVILEGE_REMOVED
919                    },
920                },
921            })
922        }
923    }
924
925    /// Convenience constructor: create an enabled privilege specification
926    /// (equivalent to `Privilege::new(name, true)`).
927    pub fn enabled(name: &str) -> Result<Self> {
928        Self::new(name, true)
929    }
930
931    /// Convenience constructor: create a disabled privilege specification
932    /// (equivalent to `Privilege::new(name, false)`).
933    pub fn disabled(name: &str) -> Result<Self> {
934        Self::new(name, false)
935    }
936}
937
938impl std::fmt::Display for Privilege {
939    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
940        let name = self.name().unwrap_or_else(|_| {
941            let luid = self.inner.Luid;
942            format!("LUID({:?},{:?})", luid.HighPart, luid.LowPart)
943        });
944        let state = if self.is_enabled() {
945            "enabled"
946        } else {
947            "disabled"
948        };
949        write!(f, "{name} ({state})")
950    }
951}