Подсчет вошедших в систему пользователей может быть сложным при использовании функций LsaEnumerateLogonSessions и LsaGetLogonSessionData, если вы никогда раньше не работали с указателями, я надеюсь, что этот пост прояснит процесс.
Для этого проекта мы будем использовать библиотеку windows-rs от Microsoft, так как с ней проще работать, чем с библиотекой winapi, которая ближе к C++, чем к Rust.
[package] name = "test_rust" version = "0.1.0" edition = "2021" [target.'cfg(target_os = "windows")'.dependencies.windows] version = "0.39.0" features = [ "Win32_Foundation", "Win32_Security_Authentication_Identity" ]
Сначала нам нужен main для списка пользователей:
fn main() { unsafe { let users = enumerate_sessions().expect("Must list logged on users"); println!("User/Domain list:"); for user in users { println!("{}/{}", user.0, user.1); } } }
Затем функция, которая возвращает список пользователей:
unsafe fn enumerate_sessions() -> Result<Vec<(String,String)>, ()> { let mut session_count : u32 = 0; let mut session_info : *mut LUID = std::ptr::null_mut(); let session_list : *mut *mut LUID = &mut session_info; match LsaEnumerateLogonSessions(&mut session_count, session_list) { Ok(_) => {}, Err(_) => return Err(()) } let session_list_info : &[LUID] = std::slice::from_raw_parts(session_info, session_count as _);
Здесь мы смешиваем Rust с C++, и, поскольку мы используем Windows API, нам нужно пометить функцию как небезопасную.
LsaEnumerateLogonSessions получает указатель на число и указатель на массив указателей, по этой причине мы используем «std::slice::from_raw_parts» для построения нашего массива LUID (идентификаторов сеанса).
Теперь нам нужно перебрать все наши сеансы и получить информацию, относящуюся к этому сеансу:
let mut user_domain_set : BTreeSet<(String,String)> = BTreeSet::new(); for session_id in session_list_info { let mut session_data : *mut SECURITY_LOGON_SESSION_DATA = std::ptr::null_mut(); match LsaGetLogonSessionData(session_id as _, &mut session_data) { Ok(_) => { let session = *session_data; let user_name = from_unicode_string(&session.UserName); let user_domain = from_unicode_string(&session.LogonDomain); if user_name.len() > 0 && user_domain.len() > 0 { let pair = (user_name, user_domain); if !user_domain_set.contains(&pair) { user_domain_set.insert(pair); } } }, Err(_e) => break } let _ =LsaFreeReturnBuffer( std::mem::transmute(session_data)); }
Прежде всего, нам нужен набор для хранения пар пользователь/домен, потому что один и тот же пользователь может находиться в разных сеансах. После этого мы перебираем все идентификаторы сессий, инициализируем нулевой указатель session_data и передаем его по LUID в LsaGetLogonSessionData для получения информации.
Поскольку строковые данные представлены в формате UNICODE, нам нужно преобразовать их, чтобы их можно было использовать в стандартной программе на Rust:
pub unsafe fn from_unicode_string(val : &UNICODE_STRING) -> String { String::from_utf16_lossy(std::slice::from_raw_parts(val.Buffer.0, (val.Length as usize /2) as usize)) }
И не забывайте про чистку:
let _ =LsaFreeReturnBuffer( std::mem::transmute(session_data)); let _ =LsaFreeReturnBuffer( std::mem::transmute(session_list));
Наконец, нам нужно только вернуть наш список пользователей:
let _ =LsaFreeReturnBuffer( std::mem::transmute(session_list)); let mut users : Vec<(String, String)> = Vec::with_capacity(user_domain_set.len()); for pair in user_domain_set.into_iter() { users.push(pair); } Ok(users)
И это все, выполнение кода приведет к:
Finished dev [unoptimized + debuginfo] target(s) in 4.67s Running `target\debug\test_rust.exe` User/Domain list: secsamdev/PORTATIL007
Готовый проект будет выглядеть так:
Я надеюсь, что этот пост поможет избавиться от страха перед работой с Rust, и его было так же интересно читать, как и писать.