From 2a9c9911797358dd84d1504cffbb9f158b2a344a Mon Sep 17 00:00:00 2001 From: Niloyyy Date: Wed, 10 Jun 2026 03:34:12 +0600 Subject: [PATCH 1/2] 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/2] 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(())