use crate::User;
use libc::{c_char, endpwent, getgrgid, getgrouplist, getpwent, gid_t, setpwent, strlen};
fn cstr_to_rust(c: *const c_char) -> Option<String> {
let mut s = Vec::new();
let mut i = 0;
loop {
let value = unsafe { *c.offset(i) } as u8;
if value == 0 {
break;
}
s.push(value);
i += 1;
}
String::from_utf8(s).ok()
}
fn get_user_groups(name: *const c_char, group_id: gid_t) -> Vec<String> {
let mut add = 0;
loop {
let mut nb_groups = 256 + add;
let mut groups = Vec::with_capacity(nb_groups as _);
if unsafe { getgrouplist(name, group_id as _, groups.as_mut_ptr(), &mut nb_groups) } == -1 {
add += 100;
continue;
}
unsafe {
groups.set_len(nb_groups as _);
}
return groups
.into_iter()
.filter_map(|g| {
let group = unsafe { getgrgid(g as _) };
if group.is_null() {
return None;
}
cstr_to_rust(unsafe { (*group).gr_name })
})
.collect();
}
}
fn endswith(s1: *const c_char, s2: &[u8]) -> bool {
if s1.is_null() {
return false;
}
let mut len = unsafe { strlen(s1) } as isize - 1;
let mut i = s2.len() as isize - 1;
while len >= 0 && i >= 0 && unsafe { *s1.offset(len) } == s2[i as usize] as _ {
i -= 1;
len -= 1;
}
i == -1
}
fn users_list<F>(filter: F) -> Vec<User>
where
F: Fn(*const c_char) -> bool,
{
let mut users = Vec::new();
unsafe { setpwent() };
loop {
let pw = unsafe { getpwent() };
if pw.is_null() {
break;
}
if !filter(unsafe { (*pw).pw_shell }) {
continue;
}
let groups = get_user_groups(unsafe { (*pw).pw_name }, unsafe { (*pw).pw_gid });
if let Some(name) = cstr_to_rust(unsafe { (*pw).pw_name }) {
users.push(User { name, groups });
}
}
unsafe { endpwent() };
users.sort_unstable_by(|x, y| x.name.partial_cmp(&y.name).unwrap());
users.dedup_by(|a, b| a.name == b.name);
users
}
pub fn get_users_list() -> Vec<User> {
users_list(|shell| !endswith(shell, b"/false") && !endswith(shell, b"/uucico"))
}