view src/conv.rs @ 56:daa2cde64601

Big big refactor. Probably should have been multiple changes. - Makes FFI safer by explicitly specifying c_int in calls. - Uses ToPrimitive/FromPrimitive to make this easier. - Pulls PamFlag variables into a bitflags! struct. - Pulls PamMessageStyle variables into an enum. - Renames ResultCode to ErrorCode. - Switches from PAM_SUCCESS to using a Result<(), ErrorCode>. - Uses thiserror to make ErrorCode into an Error. - Gets rid of pam_try! because now we have Results. - Expands some names (e.g. Conv to Conversation). - Adds more doc comments. - Returns passwords as a SecureString, to avoid unnecessarily keeping it around in memory.
author Paul Fisher <paul@pfish.zone>
date Sun, 04 May 2025 02:56:55 -0400
parents 9d1160b02d2c
children 3f4a77aa88be
line wrap: on
line source

use libc::{c_char, c_int};
use std::ffi::{CStr, CString};
use std::ptr;

use crate::constants::MessageStyle;
use crate::constants::PamResult;
use crate::constants::ErrorCode;
use crate::items::Item;

#[repr(C)]
struct Message {
    msg_style: MessageStyle,
    msg: *const c_char,
}

#[repr(C)]
struct Response {
    resp: *const c_char,
    resp_retcode: libc::c_int, // Unused - always zero
}

#[repr(C)]
pub struct Inner {
    conv: extern "C" fn(
        num_msg: c_int,
        pam_message: &&Message,
        pam_response: &mut *const Response,
        appdata_ptr: *const libc::c_void,
    ) -> c_int,
    appdata_ptr: *const libc::c_void,
}

/// A `Conv`ersation channel with the user.
///
/// Communication is mediated by the PAM client (the application that invoked
/// pam).  Messages sent will be relayed to the user by the client, and response
/// will be relayed back.
pub struct Conv<'a>(&'a Inner);

impl Conv<'_> {
    /// Sends a message to the pam client.
    ///
    /// This will typically result in the user seeing a message or a prompt.
    /// There are several message styles available:
    ///
    /// - PAM_PROMPT_ECHO_OFF
    /// - PAM_PROMPT_ECHO_ON
    /// - PAM_ERROR_MSG
    /// - PAM_TEXT_INFO
    /// - PAM_RADIO_TYPE
    /// - PAM_BINARY_PROMPT
    ///
    /// Note that the user experience will depend on how the client implements
    /// these message styles - and not all applications implement all message
    /// styles.
    pub fn send(&self, style: MessageStyle, msg: &str) -> PamResult<Option<&CStr>> {
        let mut resp_ptr: *const Response = ptr::null();
        let msg_cstr = CString::new(msg).unwrap();
        let msg = Message {
            msg_style: style,
            msg: msg_cstr.as_ptr(),
        };
        let ret = (self.0.conv)(1, &&msg, &mut resp_ptr, self.0.appdata_ptr);
        ErrorCode::result_from(ret)?;

        let result = unsafe {
            match (*resp_ptr).resp {
                p if p.is_null() => None,
                p => Some(CStr::from_ptr(p)),
            }
        };
        Ok(result)
    }
}

impl Item for Conv<'_> {
    type Raw = Inner;

    fn type_id() -> crate::items::ItemType {
        crate::items::ItemType::Conversation
    }

    unsafe fn from_raw(raw: *const Self::Raw) -> Self {
        Self(&*raw)
    }

    fn into_raw(self) -> *const Self::Raw {
        self.0 as _
    }
}