//! PAM password verification for the polkit agent.

use std::ffi::CStr;
use std::mem::MaybeUninit;
use std::path::Path;

fn username_for_uid(uid: u32) -> Option<String> {
    let mut buf = vec![0u8; 1024];
    let mut pwd = MaybeUninit::<libc::passwd>::uninit();
    let mut result: *mut libc::passwd = std::ptr::null_mut();

    loop {
        let rc = unsafe {
            libc::getpwuid_r(
                uid,
                pwd.as_mut_ptr(),
                buf.as_mut_ptr() as *mut libc::c_char,
                buf.len(),
                &mut result,
            )
        };

        if rc == libc::ERANGE && buf.len() < 1 << 20 {
            buf.resize(buf.len() * 2, 0);
            continue;
        }
        if rc != 0 || result.is_null() {
            return None;
        }

        let name = unsafe { CStr::from_ptr((*result).pw_name) };
        return name.to_str().ok().map(str::to_owned);
    }
}

/// Verify `password` for the account owning `uid`. Runs synchronously and
/// blocks (PAM applies its own failure delay); call off the compositor thread.
pub fn verify_password(uid: u32, password: &str) -> bool {
    let Some(user) = username_for_uid(uid) else {
        tracing::error!("PAM: no account for uid {uid}");
        return false;
    };

    let service = if Path::new("/etc/pam.d/polkit-1").exists() {
        "polkit-1"
    } else {
        "login"
    };

    match pam::Authenticator::with_password(service) {
        Ok(mut auth) => {
            auth.get_handler().set_credentials(&user, password);
            auth.authenticate().is_ok()
        }
        Err(e) => {
            tracing::error!("Failed to create PAM authenticator: {e:?}");
            false
        }
    }
}
