// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::utils::to_str; use crate::{ common::{Gid, Uid}, windows::sid::Sid, User, }; use std::ptr::null_mut; use winapi::shared::lmcons::{MAX_PREFERRED_LENGTH, NET_API_STATUS}; use winapi::shared::minwindef::{DWORD, LPBYTE}; use winapi::shared::ntstatus::STATUS_SUCCESS; use winapi::shared::winerror::ERROR_MORE_DATA; use winapi::um::lmaccess::{ NetUserEnum, NetUserGetInfo, NetUserGetLocalGroups, LOCALGROUP_USERS_INFO_0, USER_INFO_23, }; use winapi::um::lmaccess::{FILTER_NORMAL_ACCOUNT, LG_INCLUDE_INDIRECT, USER_INFO_0}; use winapi::um::lmapibuf::NetApiBufferFree; use winapi::um::ntlsa::{ LsaEnumerateLogonSessions, LsaFreeReturnBuffer, LsaGetLogonSessionData, SECURITY_LOGON_SESSION_DATA, }; use winapi::um::winnt::{LPWSTR, LUID}; // FIXME: Can be removed once merged in winapi. #[allow(non_upper_case_globals)] const NERR_Success: NET_API_STATUS = 0; struct NetApiBuffer(*mut T); impl Drop for NetApiBuffer { fn drop(&mut self) { unsafe { if !self.0.is_null() { NetApiBufferFree(self.0 as *mut _); } } } } impl Default for NetApiBuffer { fn default() -> Self { Self(null_mut()) } } impl NetApiBuffer { pub fn inner_mut(&mut self) -> &mut *mut T { assert!(self.0.is_null()); &mut self.0 } pub unsafe fn inner_mut_as_bytes(&mut self) -> &mut LPBYTE { // https://doc.rust-lang.org/std/mem/fn.transmute.html // Turning an &mut T into an &mut U: &mut *(self.inner_mut() as *mut *mut T as *mut LPBYTE) } } struct LsaBuffer(*mut T); impl Drop for LsaBuffer { fn drop(&mut self) { unsafe { if !self.0.is_null() { LsaFreeReturnBuffer(self.0 as *mut _); } } } } impl Default for LsaBuffer { fn default() -> Self { Self(null_mut()) } } impl LsaBuffer { pub fn inner_mut(&mut self) -> &mut *mut T { assert!(self.0.is_null()); &mut self.0 } } unsafe fn get_groups_for_user(username: LPWSTR) -> Vec { let mut buf: NetApiBuffer = Default::default(); let mut nb_entries = 0; let mut total_entries = 0; let mut groups; let status = NetUserGetLocalGroups( [0u16].as_ptr(), username, 0, LG_INCLUDE_INDIRECT, buf.inner_mut_as_bytes(), MAX_PREFERRED_LENGTH, &mut nb_entries, &mut total_entries, ); if status == NERR_Success { groups = Vec::with_capacity(nb_entries as _); if !buf.0.is_null() { let entries = std::slice::from_raw_parts(buf.0, nb_entries as _); groups.extend(entries.iter().map(|entry| to_str(entry.lgrui0_name))); } } else { groups = Vec::new(); sysinfo_debug!("NetUserGetLocalGroups failed with ret code {}", status); } groups } pub unsafe fn get_users() -> Vec { let mut users = Vec::new(); let mut resume_handle: DWORD = 0; loop { let mut buffer: NetApiBuffer = Default::default(); let mut nb_read = 0; let mut total = 0; let status = NetUserEnum( null_mut(), 0, FILTER_NORMAL_ACCOUNT, buffer.inner_mut_as_bytes(), MAX_PREFERRED_LENGTH, &mut nb_read, &mut total, &mut resume_handle, ); if status == NERR_Success || status == ERROR_MORE_DATA { let entries = std::slice::from_raw_parts(buffer.0, nb_read as _); for entry in entries { if entry.usri0_name.is_null() { continue; } let mut user: NetApiBuffer = Default::default(); if NetUserGetInfo(null_mut(), entry.usri0_name, 23, user.inner_mut_as_bytes()) == NERR_Success { if let Some(sid) = Sid::from_psid((*user.0).usri23_user_sid) { // Get the account name from the SID (because it's usually // a better name), but fall back to the name we were given // if this fails. let name = sid .account_name() .unwrap_or_else(|| to_str(entry.usri0_name)); users.push(User { uid: Uid(sid), gid: Gid(0), name, groups: get_groups_for_user(entry.usri0_name), }); } } } } else { sysinfo_debug!( "NetUserEnum error: {}", if status == winapi::shared::winerror::ERROR_ACCESS_DENIED { "access denied" } else if status == winapi::shared::winerror::ERROR_INVALID_LEVEL { "invalid level" } else { "unknown error" } ); } if status != ERROR_MORE_DATA { break; } } // First part done. Second part now! let mut nb_sessions = 0; let mut uids: LsaBuffer = Default::default(); if LsaEnumerateLogonSessions(&mut nb_sessions, uids.inner_mut()) != STATUS_SUCCESS { sysinfo_debug!("LsaEnumerateLogonSessions failed"); } else { let entries = std::slice::from_raw_parts_mut(uids.0, nb_sessions as _); for entry in entries { let mut data: LsaBuffer = Default::default(); if LsaGetLogonSessionData(entry, data.inner_mut()) == STATUS_SUCCESS && !data.0.is_null() { let data = *data.0; if data.LogonType == winapi::um::ntlsa::Network { continue; } let sid = match Sid::from_psid(data.Sid) { Some(sid) => sid, None => continue, }; if users.iter().any(|u| u.uid.0 == sid) { continue; } // Get the account name from the SID (because it's usually // a better name), but fall back to the name we were given // if this fails. let name = sid.account_name().unwrap_or_else(|| { String::from_utf16(std::slice::from_raw_parts( data.UserName.Buffer, data.UserName.Length as usize / std::mem::size_of::(), )) .unwrap_or_else(|_err| { sysinfo_debug!("Failed to convert from UTF-16 string: {}", _err); String::new() }) }); users.push(User { uid: Uid(sid), gid: Gid(0), name, // There is no local groups for a non-local user. groups: Vec::new(), }); } } } users }