Подсчет вошедших в систему пользователей может быть сложным при использовании функций 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, и его было так же интересно читать, как и писать.