megalock 0.1.0

X11 xlock replacement
use crate::{
    bindings::{
        pam_authenticate, pam_conv, pam_end, pam_handle_t, pam_message, pam_response, pam_set_item,
        pam_start, pam_strerror, PAM_CONV_ERR, PAM_PROMPT_ECHO_OFF, PAM_PROMPT_ECHO_ON,
        PAM_SUCCESS, PAM_USER, PAM_XDISPLAY,
    },
    const_string_ptr,
    utils::{get_display, get_username},
    wm::statics::PASSWORD,
};
use anyhow::{anyhow, Result};
use tracing::{debug, trace};

unsafe extern "C" fn conv_callback(
    num_msg: i32,
    msg: *mut *const pam_message,
    resp: *mut *mut pam_response,
    _appdata_ptr: *mut libc::c_void,
) -> i32 {
    trace!("PAM callback started");

    if num_msg == 0 {
        return PAM_CONV_ERR.try_into().unwrap();
    }

    let mut responses = Vec::new();

    let msg: Vec<*const pam_message> = Vec::from_raw_parts(
        msg,
        num_msg.try_into().unwrap(),
        num_msg.try_into().unwrap(),
    );

    for m in &msg {
        let style: u32 = unsafe { (**m).msg_style }.try_into().unwrap();

        if style != PAM_PROMPT_ECHO_ON && style != PAM_PROMPT_ECHO_OFF {
            responses.push(pam_response {
                resp_retcode: 0,
                resp: std::ptr::null_mut(),
            });
            continue;
        }

        trace!("Setting PAM password to {}", PASSWORD.lock().unwrap());

        let password = PASSWORD.lock().unwrap().as_bytes().to_vec();
        let s = std::ffi::CString::from_vec_unchecked(password);
        let response = pam_response {
            resp_retcode: 0,
            resp: s.as_c_str().as_ptr().cast_mut(),
        };
        responses.push(response);
        std::mem::forget(s);
    }

    std::mem::forget(msg);

    if !responses.is_empty() {
        *resp = responses.as_slice().as_ptr().cast_mut();
        std::mem::forget(responses);
    }

    trace!("PAM callback finished");

    PAM_SUCCESS.try_into().unwrap()
}

pub fn authenticate_password(name: &str) -> Result<()> {
    let conv = pam_conv {
        conv: Some(conv_callback),
        appdata_ptr: std::ptr::null_mut(),
    };

    let mut pam_handle: *mut pam_handle_t = std::ptr::null_mut();

    debug!("Authentication start for '{}'", get_username()?);

    let res = unsafe {
        pam_start(
            const_string_ptr!(name),
            std::ptr::null(),
            &conv,
            &mut pam_handle,
        )
    };
    if res != PAM_SUCCESS.try_into().unwrap() {
        return Err(anyhow!("PAM: {}", unsafe {
            std::ffi::CStr::from_ptr(pam_strerror(pam_handle, res)).to_str()
        }?));
    }

    if pam_handle.is_null() {
        return Err(anyhow!("PAM could not be initialized"));
    }

    let res = unsafe {
        pam_set_item(
            pam_handle,
            PAM_USER.try_into().unwrap(),
            const_string_ptr!(get_username()?),
        )
    };
    if res != PAM_SUCCESS.try_into().unwrap() {
        return Err(anyhow!("PAM: {}", unsafe {
            std::ffi::CStr::from_ptr(pam_strerror(pam_handle, res)).to_str()
        }?));
    }

    let res = unsafe {
        pam_set_item(
            pam_handle,
            PAM_XDISPLAY.try_into().unwrap(),
            const_string_ptr!(get_display()),
        )
    };
    if res != PAM_SUCCESS.try_into().unwrap() {
        return Err(anyhow!("PAM: {}", unsafe {
            std::ffi::CStr::from_ptr(pam_strerror(pam_handle, res)).to_str()
        }?));
    }

    trace!("authentication starting");
    let res = unsafe { pam_authenticate(pam_handle, 0) };
    trace!("authentication finished");

    let authenticated = res == PAM_SUCCESS.try_into().unwrap();

    if authenticated {
        debug!("Authentication successful.");
    } else {
        debug!("Authentication Denied: {} [{}]", res, unsafe {
            std::ffi::CStr::from_ptr(pam_strerror(pam_handle, res)).to_str()
        }?)
    }

    let res = unsafe { pam_end(pam_handle, res) };
    if res != PAM_SUCCESS.try_into().unwrap() {
        return Err(anyhow!("PAM: {}", res));
    }

    if !authenticated {
        return Err(anyhow!("Authentication Denied"));
    }

    trace!("authentication done");

    Ok(())
}