From 2a9c9911797358dd84d1504cffbb9f158b2a344a Mon Sep 17 00:00:00 2001 From: Niloyyy Date: Wed, 10 Jun 2026 03:34:12 +0600 Subject: [PATCH 1/3] feat: add Windows-native newgrp command implementation --- Cargo.toml | 1 + deps/newgrp/Cargo.toml | 19 +++++++ deps/newgrp/src/lib.rs | 118 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 deps/newgrp/Cargo.toml create mode 100644 deps/newgrp/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 5e97da5..3e03d95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -125,6 +125,7 @@ uptime = { package = "uu_uptime", path = "deps/coreutils/src/uu/uptime" } findutils = { package = "findutils", path = "deps/findutils" } grep = { package = "uu_grep", path = "deps/grep" } ntfind = { package = "find", path = "deps/ntfind" } +newgrp = { package = "uu_newgrp", path = "deps/newgrp" } # For registry access in main.rs [dependencies.windows-sys] diff --git a/deps/newgrp/Cargo.toml b/deps/newgrp/Cargo.toml new file mode 100644 index 0000000..8cec290 --- /dev/null +++ b/deps/newgrp/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "uu_newgrp" +version = "0.0.0" +edition = "2024" +license = "MIT" +publish = false + +[dependencies] +clap = { version = "4.5", features = ["wrap_help"] } +uucore = { path = "../coreutils/src/uucore" } + +[dependencies.windows-sys] +version = "*" +features = [ + "Win32_Security", + "Win32_System_Threading", + "Win32_Foundation", + "Win32_System_Environment", +] diff --git a/deps/newgrp/src/lib.rs b/deps/newgrp/src/lib.rs new file mode 100644 index 0000000..fb1e05a --- /dev/null +++ b/deps/newgrp/src/lib.rs @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use clap::{Arg, Command}; +use std::process; +use std::ptr; + +use windows_sys::Win32::Foundation::{CloseHandle, GetLastError, ERROR_INSUFFICIENT_BUFFER, HANDLE}; +use windows_sys::Win32::Security::{ + LookupAccountNameW, SetTokenInformation, TokenPrimaryGroup, + TOKEN_ADJUST_DEFAULT, TOKEN_PRIMARY_GROUP, TOKEN_QUERY, +}; +use windows_sys::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken}; + +pub fn uu_app() -> Command { + Command::new("newgrp") + .about("Log in to a new group") + .arg( + Arg::new("group") + .help("The group to log into") + .index(1) + .required(false), + ) +} + +pub fn uumain(args: impl uucore::Args) -> i32 { + let matches = uu_app().get_matches_from(args); + let group = matches.get_one::("group"); + + if let Some(g) = group { + if let Err(e) = change_primary_group(g) { + eprintln!("newgrp: failed to change primary group: {}", e); + return 1; + } + } + + // Spawn the shell + let shell = std::env::var("SHELL").unwrap_or_else(|_| "cmd.exe".to_string()); + + let mut child = match process::Command::new(shell).spawn() { + Ok(c) => c, + Err(e) => { + eprintln!("newgrp: failed to execute shell: {}", e); + return 1; + } + }; + + match child.wait() { + Ok(status) => status.code().unwrap_or(1), + Err(e) => { + eprintln!("newgrp: wait failed: {}", e); + 1 + } + } +} + +fn change_primary_group(group: &str) -> Result<(), String> { + unsafe { + let group_w: Vec = group.encode_utf16().chain(std::iter::once(0)).collect(); + let mut sid_size = 0; + let mut domain_size = 0; + let mut pe_use = 0; + + // First call to get required sizes + LookupAccountNameW( + ptr::null(), + group_w.as_ptr(), + ptr::null_mut(), + &mut sid_size, + ptr::null_mut(), + &mut domain_size, + &mut pe_use, + ); + + if GetLastError() != ERROR_INSUFFICIENT_BUFFER { + return Err(format!("LookupAccountNameW failed getting size: {}", GetLastError())); + } + + let mut sid = vec![0u8; sid_size as usize]; + let mut domain = vec![0u16; domain_size as usize]; + + if LookupAccountNameW( + ptr::null(), + group_w.as_ptr(), + sid.as_mut_ptr() as _, + &mut sid_size, + domain.as_mut_ptr(), + &mut domain_size, + &mut pe_use, + ) == 0 { + return Err(format!("LookupAccountNameW failed: {}", GetLastError())); + } + + let mut token: HANDLE = std::ptr::null_mut(); + if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_DEFAULT, &mut token) == 0 { + return Err(format!("OpenProcessToken failed: {}", GetLastError())); + } + + let mut token_group = TOKEN_PRIMARY_GROUP { + PrimaryGroup: sid.as_mut_ptr() as _, + }; + + let res = SetTokenInformation( + token, + TokenPrimaryGroup, + &mut token_group as *mut _ as *const _, + std::mem::size_of::() as u32, + ); + + CloseHandle(token); + + if res == 0 { + return Err(format!("SetTokenInformation failed: {}", GetLastError())); + } + + Ok(()) + } +} From 039ed2036e626d51de0294272f82d6a09f8f71b7 Mon Sep 17 00:00:00 2001 From: Niloyyy Date: Sat, 13 Jun 2026 19:48:57 +0600 Subject: [PATCH 2/3] refactor: address review feedback --- Cargo.lock | 10 +++ deps/newgrp/src/lib.rs | 167 +++++++++++++++++++++++++++++++---------- 2 files changed, 138 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 79e71f5..ed0b411 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -456,6 +456,7 @@ dependencies = [ "uu_mkdir", "uu_mktemp", "uu_mv", + "uu_newgrp", "uu_nl", "uu_nproc", "uu_numfmt", @@ -2987,6 +2988,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "uu_newgrp" +version = "0.0.0" +dependencies = [ + "clap", + "uucore", + "windows-sys 0.59.0", +] + [[package]] name = "uu_nl" version = "0.8.0" diff --git a/deps/newgrp/src/lib.rs b/deps/newgrp/src/lib.rs index fb1e05a..e452bca 100644 --- a/deps/newgrp/src/lib.rs +++ b/deps/newgrp/src/lib.rs @@ -1,16 +1,23 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use clap::{Arg, Command}; -use std::process; +use clap::{Arg, ArgAction, Command}; +use std::io; use std::ptr; +use uucore::error::{UResult, USimpleError}; -use windows_sys::Win32::Foundation::{CloseHandle, GetLastError, ERROR_INSUFFICIENT_BUFFER, HANDLE}; +use windows_sys::Win32::Foundation::{ + CloseHandle, GetLastError, ERROR_INSUFFICIENT_BUFFER, HANDLE, +}; use windows_sys::Win32::Security::{ - LookupAccountNameW, SetTokenInformation, TokenPrimaryGroup, - TOKEN_ADJUST_DEFAULT, TOKEN_PRIMARY_GROUP, TOKEN_QUERY, + DuplicateTokenEx, LookupAccountNameW, SecurityImpersonation, SetTokenInformation, TokenPrimary, + TokenPrimaryGroup, TOKEN_ADJUST_DEFAULT, TOKEN_ASSIGN_PRIMARY, TOKEN_DUPLICATE, + TOKEN_PRIMARY_GROUP, TOKEN_QUERY, +}; +use windows_sys::Win32::System::Threading::{ + CreateProcessAsUserW, GetCurrentProcess, GetExitCodeProcess, OpenProcessToken, + WaitForSingleObject, INFINITE, PROCESS_INFORMATION, STARTUPINFOW, }; -use windows_sys::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken}; pub fn uu_app() -> Command { Command::new("newgrp") @@ -21,40 +28,131 @@ pub fn uu_app() -> Command { .index(1) .required(false), ) + .arg( + Arg::new("command") + .short('c') + .long("command") + .help("Command to execute") + .action(ArgAction::Set), + ) } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore::main(no_signals)] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().get_matches_from(args); let group = matches.get_one::("group"); + let command = matches.get_one::("command"); - if let Some(g) = group { - if let Err(e) = change_primary_group(g) { - eprintln!("newgrp: failed to change primary group: {}", e); - return 1; + let mut token: HANDLE = std::ptr::null_mut(); + unsafe { + if OpenProcessToken( + GetCurrentProcess(), + TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY | TOKEN_ADJUST_DEFAULT, + &mut token, + ) == 0 + { + return Err(USimpleError::new( + 1, + format!( + "OpenProcessToken failed: {}", + io::Error::from_raw_os_error(GetLastError() as i32) + ), + )); } } - // Spawn the shell - let shell = std::env::var("SHELL").unwrap_or_else(|_| "cmd.exe".to_string()); - - let mut child = match process::Command::new(shell).spawn() { - Ok(c) => c, - Err(e) => { - eprintln!("newgrp: failed to execute shell: {}", e); - return 1; + let mut new_token: HANDLE = std::ptr::null_mut(); + unsafe { + if DuplicateTokenEx( + token, + TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY | TOKEN_ADJUST_DEFAULT, + ptr::null(), + SecurityImpersonation, + TokenPrimary, + &mut new_token, + ) == 0 + { + CloseHandle(token); + return Err(USimpleError::new( + 1, + format!( + "DuplicateTokenEx failed: {}", + io::Error::from_raw_os_error(GetLastError() as i32) + ), + )); } + CloseHandle(token); + } + + if let Some(g) = group { + if let Err(e) = change_primary_group(new_token, g) { + unsafe { + CloseHandle(new_token); + } + return Err(USimpleError::new( + 1, + format!("failed to change primary group: {}", e), + )); + } + } + + // Spawn the shell or command + let cmd = if let Some(c) = command { + format!("cmd.exe /c {}", c) + } else { + "cmd.exe".to_string() }; - match child.wait() { - Ok(status) => status.code().unwrap_or(1), - Err(e) => { - eprintln!("newgrp: wait failed: {}", e); - 1 + let mut cmd_w: Vec = cmd.encode_utf16().chain(std::iter::once(0)).collect(); + + unsafe { + let mut startup_info: STARTUPINFOW = std::mem::zeroed(); + startup_info.cb = std::mem::size_of::() as u32; + let mut process_info: PROCESS_INFORMATION = std::mem::zeroed(); + + if CreateProcessAsUserW( + new_token, + ptr::null(), + cmd_w.as_mut_ptr(), + ptr::null(), + ptr::null(), + 0, + 0, + ptr::null(), + ptr::null(), + &startup_info, + &mut process_info, + ) == 0 + { + let err = GetLastError(); + CloseHandle(new_token); + return Err(USimpleError::new( + 1, + format!( + "CreateProcessAsUserW failed: {}", + io::Error::from_raw_os_error(err as i32) + ), + )); + } + + CloseHandle(new_token); + CloseHandle(process_info.hThread); + + WaitForSingleObject(process_info.hProcess, INFINITE); + + let mut exit_code = 0; + GetExitCodeProcess(process_info.hProcess, &mut exit_code); + CloseHandle(process_info.hProcess); + + if exit_code != 0 { + std::process::exit(exit_code as i32); } } + + Ok(()) } -fn change_primary_group(group: &str) -> Result<(), String> { +fn change_primary_group(token: HANDLE, group: &str) -> io::Result<()> { unsafe { let group_w: Vec = group.encode_utf16().chain(std::iter::once(0)).collect(); let mut sid_size = 0; @@ -73,7 +171,7 @@ fn change_primary_group(group: &str) -> Result<(), String> { ); if GetLastError() != ERROR_INSUFFICIENT_BUFFER { - return Err(format!("LookupAccountNameW failed getting size: {}", GetLastError())); + return Err(io::Error::from_raw_os_error(GetLastError() as i32)); } let mut sid = vec![0u8; sid_size as usize]; @@ -88,29 +186,20 @@ fn change_primary_group(group: &str) -> Result<(), String> { &mut domain_size, &mut pe_use, ) == 0 { - return Err(format!("LookupAccountNameW failed: {}", GetLastError())); - } - - let mut token: HANDLE = std::ptr::null_mut(); - if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_DEFAULT, &mut token) == 0 { - return Err(format!("OpenProcessToken failed: {}", GetLastError())); + return Err(io::Error::from_raw_os_error(GetLastError() as i32)); } let mut token_group = TOKEN_PRIMARY_GROUP { PrimaryGroup: sid.as_mut_ptr() as _, }; - let res = SetTokenInformation( + if SetTokenInformation( token, TokenPrimaryGroup, &mut token_group as *mut _ as *const _, std::mem::size_of::() as u32, - ); - - CloseHandle(token); - - if res == 0 { - return Err(format!("SetTokenInformation failed: {}", GetLastError())); + ) == 0 { + return Err(io::Error::from_raw_os_error(GetLastError() as i32)); } Ok(()) From 5450d9486c2579f95e0a4fb8fd5101311cfbdfea Mon Sep 17 00:00:00 2001 From: Niloyyy Date: Sat, 27 Jun 2026 12:15:54 +0600 Subject: [PATCH 3/3] feat: add group validation, -L flag, and sg alias - Security: validate requested group is in TOKEN_GROUPS with SE_GROUP_ENABLED before switching, matching reference implementations (SFU newgrp, Cygwin newgrp, winsg) - Add -L flag to list all available groups from the current token that have SE_GROUP_ENABLED set - Add sg alias (sg is an alias for newgrp that accepts -c command) - Refactor into focused helper functions following reference code structure --- build.rs | 4 + deps/newgrp/Cargo.toml | 1 + deps/newgrp/src/lib.rs | 308 +++++++++++++++++++++++++++++++---------- 3 files changed, 241 insertions(+), 72 deletions(-) diff --git a/build.rs b/build.rs index 4752313..0d7a37e 100644 --- a/build.rs +++ b/build.rs @@ -49,6 +49,10 @@ fn generate_uutils_map() { if util == "test" { entries.push(("[".into(), value.clone())); } + if util == "newgrp" { + // sg is an alias for newgrp that always runs a command via a shell + entries.push(("sg".into(), value.clone())); + } entries.push((util.clone(), value)); } diff --git a/deps/newgrp/Cargo.toml b/deps/newgrp/Cargo.toml index 8cec290..827e4d5 100644 --- a/deps/newgrp/Cargo.toml +++ b/deps/newgrp/Cargo.toml @@ -16,4 +16,5 @@ features = [ "Win32_System_Threading", "Win32_Foundation", "Win32_System_Environment", + "Win32_System_SystemServices", ] diff --git a/deps/newgrp/src/lib.rs b/deps/newgrp/src/lib.rs index e452bca..3d7c67b 100644 --- a/deps/newgrp/src/lib.rs +++ b/deps/newgrp/src/lib.rs @@ -10,10 +10,12 @@ use windows_sys::Win32::Foundation::{ CloseHandle, GetLastError, ERROR_INSUFFICIENT_BUFFER, HANDLE, }; use windows_sys::Win32::Security::{ - DuplicateTokenEx, LookupAccountNameW, SecurityImpersonation, SetTokenInformation, TokenPrimary, - TokenPrimaryGroup, TOKEN_ADJUST_DEFAULT, TOKEN_ASSIGN_PRIMARY, TOKEN_DUPLICATE, - TOKEN_PRIMARY_GROUP, TOKEN_QUERY, + DuplicateTokenEx, EqualSid, GetTokenInformation, LookupAccountNameW, LookupAccountSidW, + SecurityImpersonation, SetTokenInformation, TokenGroups, TokenPrimary, TokenPrimaryGroup, + SID_NAME_USE, TOKEN_ADJUST_DEFAULT, TOKEN_ASSIGN_PRIMARY, TOKEN_DUPLICATE, + TOKEN_GROUPS, TOKEN_PRIMARY_GROUP, TOKEN_QUERY, }; +use windows_sys::Win32::System::SystemServices::SE_GROUP_ENABLED; use windows_sys::Win32::System::Threading::{ CreateProcessAsUserW, GetCurrentProcess, GetExitCodeProcess, OpenProcessToken, WaitForSingleObject, INFINITE, PROCESS_INFORMATION, STARTUPINFOW, @@ -32,9 +34,16 @@ pub fn uu_app() -> Command { Arg::new("command") .short('c') .long("command") - .help("Command to execute") + .help("Command to execute with /bin/sh") .action(ArgAction::Set), ) + .arg( + Arg::new("list") + .short('L') + .long("list") + .help("List available groups from the current token") + .action(ArgAction::SetTrue), + ) } #[uucore::main(no_signals)] @@ -42,8 +51,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().get_matches_from(args); let group = matches.get_one::("group"); let command = matches.get_one::("command"); + let list = matches.get_flag("list"); - let mut token: HANDLE = std::ptr::null_mut(); + // Open the current process token + let mut token: HANDLE = ptr::null_mut(); unsafe { if OpenProcessToken( GetCurrentProcess(), @@ -54,14 +65,61 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { return Err(USimpleError::new( 1, format!( - "OpenProcessToken failed: {}", + "cannot open process token: {}", io::Error::from_raw_os_error(GetLastError() as i32) ), )); } } - let mut new_token: HANDLE = std::ptr::null_mut(); + // -L: list groups and exit + if list { + let result = list_token_groups(token); + unsafe { CloseHandle(token) }; + return result; + } + + let group = match group { + Some(g) => g, + None => { + unsafe { CloseHandle(token) }; + return Err(USimpleError::new(1, "no group name given")); + } + }; + + // Resolve group name to SID + let sid = match lookup_account_name(group) { + Ok(s) => s, + Err(e) => { + unsafe { CloseHandle(token) }; + return Err(USimpleError::new( + 1, + format!("could not find group '{}': {}", group, e), + )); + } + }; + + // Security: verify the group is in TOKEN_GROUPS with SE_GROUP_ENABLED + match is_group_in_token(token, &sid) { + Ok(true) => {} + Ok(false) => { + unsafe { CloseHandle(token) }; + return Err(USimpleError::new( + 1, + format!("current user is not a member of group '{}'", group), + )); + } + Err(e) => { + unsafe { CloseHandle(token) }; + return Err(USimpleError::new( + 1, + format!("failed to query token groups: {}", e), + )); + } + } + + // Duplicate the token so we don't alter our own process token + let mut new_token: HANDLE = ptr::null_mut(); unsafe { if DuplicateTokenEx( token, @@ -72,97 +130,64 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { &mut new_token, ) == 0 { + let err = GetLastError(); CloseHandle(token); return Err(USimpleError::new( 1, format!( "DuplicateTokenEx failed: {}", - io::Error::from_raw_os_error(GetLastError() as i32) + io::Error::from_raw_os_error(err as i32) ), )); } CloseHandle(token); } - if let Some(g) = group { - if let Err(e) = change_primary_group(new_token, g) { - unsafe { - CloseHandle(new_token); - } - return Err(USimpleError::new( - 1, - format!("failed to change primary group: {}", e), - )); - } + // Set the primary group on the duplicated token + if let Err(e) = set_token_primary_group(new_token, &sid) { + unsafe { CloseHandle(new_token) }; + return Err(USimpleError::new( + 1, + format!("could not switch to new primary group '{}': {}", group, e), + )); } - // Spawn the shell or command + // Build command line: if -c given, wrap in cmd.exe /c; otherwise open interactive shell let cmd = if let Some(c) = command { format!("cmd.exe /c {}", c) } else { "cmd.exe".to_string() }; - let mut cmd_w: Vec = cmd.encode_utf16().chain(std::iter::once(0)).collect(); - - unsafe { - let mut startup_info: STARTUPINFOW = std::mem::zeroed(); - startup_info.cb = std::mem::size_of::() as u32; - let mut process_info: PROCESS_INFORMATION = std::mem::zeroed(); - - if CreateProcessAsUserW( - new_token, - ptr::null(), - cmd_w.as_mut_ptr(), - ptr::null(), - ptr::null(), - 0, - 0, - ptr::null(), - ptr::null(), - &startup_info, - &mut process_info, - ) == 0 - { - let err = GetLastError(); - CloseHandle(new_token); - return Err(USimpleError::new( - 1, - format!( - "CreateProcessAsUserW failed: {}", - io::Error::from_raw_os_error(err as i32) - ), - )); - } - - CloseHandle(new_token); - CloseHandle(process_info.hThread); - - WaitForSingleObject(process_info.hProcess, INFINITE); + let exit_code = spawn_process_as_user(new_token, &cmd).map_err(|e| { + unsafe { CloseHandle(new_token) }; + USimpleError::new( + 1, + format!("failed to execute '{}': {}", cmd, e), + ) + })?; - let mut exit_code = 0; - GetExitCodeProcess(process_info.hProcess, &mut exit_code); - CloseHandle(process_info.hProcess); + unsafe { CloseHandle(new_token) }; - if exit_code != 0 { - std::process::exit(exit_code as i32); - } + if exit_code != 0 { + std::process::exit(exit_code as i32); } Ok(()) } -fn change_primary_group(token: HANDLE, group: &str) -> io::Result<()> { +/// Resolves an account name to a raw SID buffer. +fn lookup_account_name(name: &str) -> io::Result> { unsafe { - let group_w: Vec = group.encode_utf16().chain(std::iter::once(0)).collect(); - let mut sid_size = 0; - let mut domain_size = 0; - let mut pe_use = 0; + let name_w: Vec = name.encode_utf16().chain(std::iter::once(0)).collect(); + let mut sid_size: u32 = 0; + let mut domain_size: u32 = 0; + let mut pe_use: SID_NAME_USE = 0; - // First call to get required sizes + // First call: get required buffer sizes LookupAccountNameW( ptr::null(), - group_w.as_ptr(), + name_w.as_ptr(), ptr::null_mut(), &mut sid_size, ptr::null_mut(), @@ -179,18 +204,119 @@ fn change_primary_group(token: HANDLE, group: &str) -> io::Result<()> { if LookupAccountNameW( ptr::null(), - group_w.as_ptr(), + name_w.as_ptr(), sid.as_mut_ptr() as _, &mut sid_size, domain.as_mut_ptr(), &mut domain_size, &mut pe_use, - ) == 0 { + ) == 0 + { + return Err(io::Error::from_raw_os_error(GetLastError() as i32)); + } + + Ok(sid) + } +} + +/// Returns true if the given SID is present in TOKEN_GROUPS with SE_GROUP_ENABLED. +fn is_group_in_token(token: HANDLE, sid: &[u8]) -> io::Result { + let groups = get_token_groups(token)?; + + unsafe { + let ptgroups = groups.as_ptr() as *const TOKEN_GROUPS; + let count = (*ptgroups).GroupCount as usize; + // SAFETY: TOKEN_GROUPS is followed by GroupCount SID_AND_ATTRIBUTES entries. + let groups_slice = std::slice::from_raw_parts((*ptgroups).Groups.as_ptr(), count); + + for entry in groups_slice { + if entry.Attributes & (SE_GROUP_ENABLED as u32) != 0 + && EqualSid(sid.as_ptr() as _, entry.Sid) != 0 + { + return Ok(true); + } + } + } + + Ok(false) +} + +/// Lists all groups in the token that have the SE_GROUP_ENABLED flag. +fn list_token_groups(token: HANDLE) -> UResult<()> { + let groups = get_token_groups(token).map_err(|e| { + USimpleError::new(1, format!("failed to query token groups: {}", e)) + })?; + + unsafe { + let ptgroups = groups.as_ptr() as *const TOKEN_GROUPS; + let count = (*ptgroups).GroupCount as usize; + let groups_slice = std::slice::from_raw_parts((*ptgroups).Groups.as_ptr(), count); + + for entry in groups_slice { + if entry.Attributes & (SE_GROUP_ENABLED as u32) == 0 { + continue; + } + + let mut name_size: u32 = 256; + let mut name_buf = vec![0u16; name_size as usize]; + let mut domain_size: u32 = 256; + let mut domain_buf = vec![0u16; domain_size as usize]; + let mut pe_use: SID_NAME_USE = 0; + + if LookupAccountSidW( + ptr::null(), + entry.Sid, + name_buf.as_mut_ptr(), + &mut name_size, + domain_buf.as_mut_ptr(), + &mut domain_size, + &mut pe_use, + ) == 0 + { + // Skip entries we can't resolve + continue; + } + + let name = String::from_utf16_lossy(&name_buf[..name_size as usize]); + println!("{}", name); + } + } + + Ok(()) +} + +/// Retrieves TOKEN_GROUPS from the token into a raw byte buffer. +fn get_token_groups(token: HANDLE) -> io::Result> { + unsafe { + let mut needed: u32 = 0; + // First call to get the required size + GetTokenInformation(token, TokenGroups, ptr::null_mut(), 0, &mut needed); + + if GetLastError() != ERROR_INSUFFICIENT_BUFFER { return Err(io::Error::from_raw_os_error(GetLastError() as i32)); } + let mut buf = vec![0u8; needed as usize]; + if GetTokenInformation( + token, + TokenGroups, + buf.as_mut_ptr() as _, + needed, + &mut needed, + ) == 0 + { + return Err(io::Error::from_raw_os_error(GetLastError() as i32)); + } + + Ok(buf) + } +} + +/// Sets the primary group on a token. +fn set_token_primary_group(token: HANDLE, sid: &[u8]) -> io::Result<()> { + unsafe { let mut token_group = TOKEN_PRIMARY_GROUP { - PrimaryGroup: sid.as_mut_ptr() as _, + PrimaryGroup: sid.as_ptr() as _, }; if SetTokenInformation( @@ -198,10 +324,48 @@ fn change_primary_group(token: HANDLE, group: &str) -> io::Result<()> { TokenPrimaryGroup, &mut token_group as *mut _ as *const _, std::mem::size_of::() as u32, - ) == 0 { + ) == 0 + { return Err(io::Error::from_raw_os_error(GetLastError() as i32)); } Ok(()) } } + +/// Spawns a process with the given token and command line, waits for it, and returns its exit code. +fn spawn_process_as_user(token: HANDLE, cmd: &str) -> io::Result { + let mut cmd_w: Vec = cmd.encode_utf16().chain(std::iter::once(0)).collect(); + + unsafe { + let mut startup_info: STARTUPINFOW = std::mem::zeroed(); + startup_info.cb = std::mem::size_of::() as u32; + let mut process_info: PROCESS_INFORMATION = std::mem::zeroed(); + + if CreateProcessAsUserW( + token, + ptr::null(), + cmd_w.as_mut_ptr(), + ptr::null(), + ptr::null(), + 0, + 0, + ptr::null(), + ptr::null(), + &startup_info, + &mut process_info, + ) == 0 + { + return Err(io::Error::from_raw_os_error(GetLastError() as i32)); + } + + CloseHandle(process_info.hThread); + WaitForSingleObject(process_info.hProcess, INFINITE); + + let mut exit_code: u32 = 1; + GetExitCodeProcess(process_info.hProcess, &mut exit_code); + CloseHandle(process_info.hProcess); + + Ok(exit_code) + } +}