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/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..e452bca --- /dev/null +++ b/deps/newgrp/src/lib.rs @@ -0,0 +1,207 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +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::Security::{ + 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, +}; + +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), + ) + .arg( + Arg::new("command") + .short('c') + .long("command") + .help("Command to execute") + .action(ArgAction::Set), + ) +} + +#[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"); + + 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) + ), + )); + } + } + + 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() + }; + + 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(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; + 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(io::Error::from_raw_os_error(GetLastError() as i32)); + } + + 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(io::Error::from_raw_os_error(GetLastError() as i32)); + } + + let mut token_group = TOKEN_PRIMARY_GROUP { + PrimaryGroup: sid.as_mut_ptr() as _, + }; + + if SetTokenInformation( + token, + TokenPrimaryGroup, + &mut token_group as *mut _ as *const _, + std::mem::size_of::() as u32, + ) == 0 { + return Err(io::Error::from_raw_os_error(GetLastError() as i32)); + } + + Ok(()) + } +}